Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b7b41597f | ||
|
|
e56ff8fdf2 | ||
|
|
3f9da8940f | ||
|
|
26d5f69af2 | ||
|
|
6efe4a3fd6 | ||
|
|
2b4ab4a322 | ||
|
|
cca42d1f89 | ||
|
|
724fef87b1 | ||
|
|
d50c0e4cd5 | ||
|
|
c18c037642 | ||
|
|
cda1da5fd0 | ||
|
|
eba1aa3a37 | ||
|
|
81018bb615 | ||
|
|
5fa0ff7b5c | ||
|
|
cabe286ebb | ||
|
|
e337da088b | ||
|
|
de97ea9e75 | ||
|
|
cc331065eb | ||
|
|
9a8e630654 | ||
|
|
17ab977efb | ||
|
|
6bd10d9451 | ||
|
|
5313b9b69c | ||
|
|
8dcfdcc44a | ||
|
|
67422df3ff | ||
|
|
2ad7536eb7 | ||
|
|
6f56e5c4e6 | ||
|
|
5ae8ebe590 | ||
|
|
6ecb97e4e5 | ||
|
|
c8f938dd3e | ||
|
|
0d29e29162 | ||
|
|
be1b3dffce | ||
|
|
3ba2dbe415 | ||
|
|
19a96c92a9 | ||
|
|
c265e3e437 | ||
|
|
7434ac2648 | ||
|
|
63d73a73aa | ||
|
|
00f9258a4d | ||
|
|
7087b43d39 | ||
|
|
945a78b7b1 | ||
|
|
55acc19ab8 | ||
|
|
410f0be05e | ||
|
|
2d7c091071 | ||
|
|
9bf58a54ce | ||
|
|
231adeea44 | ||
|
|
0ea618af39 |
3
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
dist_electron
|
||||
@@ -26,6 +26,7 @@ Demo:[https://wanglin2.github.io/mind-map/](https://wanglin2.github.io/mind-ma
|
||||
- [x] 支持多种节点形状
|
||||
- [x] 支持导出为`json`、`png`、`svg`、`pdf`,支持从`json`、`xmind`导入
|
||||
- [x] 支持小地图
|
||||
- [x] 支持关联线
|
||||
|
||||
# 安装
|
||||
|
||||
@@ -59,4 +60,4 @@ MIT
|
||||
|
||||
# 微信交流群
|
||||
|
||||

|
||||

|
||||
|
Before Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 2.0 MiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 254 KiB |
@@ -1 +1 @@
|
||||
<!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,initial-scale=1"><link rel="icon" href="./dist/logo.png"><title>一个简单的web思维导图实现</title><link href="dist/js/chunk-2d0a3179.3b27bed0.js" rel="prefetch"><link href="dist/js/chunk-2d0aa579.82a73ddc.js" rel="prefetch"><link href="dist/js/chunk-2d0aa978.84fc06da.js" rel="prefetch"><link href="dist/js/chunk-2d0ab10b.a0694d8e.js" rel="prefetch"><link href="dist/js/chunk-2d0abe0f.a9abff6a.js" rel="prefetch"><link href="dist/js/chunk-2d0b361e.d657e190.js" rel="prefetch"><link href="dist/js/chunk-2d0b91e5.433fdc5c.js" rel="prefetch"><link href="dist/js/chunk-2d0b92c3.9d7f8382.js" rel="prefetch"><link href="dist/js/chunk-2d0ba309.3542b9be.js" rel="prefetch"><link href="dist/js/chunk-2d0bd54e.906e86ec.js" rel="prefetch"><link href="dist/js/chunk-2d0be174.0cf53d60.js" rel="prefetch"><link href="dist/js/chunk-2d0c0a44.bdfd7cfb.js" rel="prefetch"><link href="dist/js/chunk-2d0c14fc.7163274e.js" rel="prefetch"><link href="dist/js/chunk-2d0c18d8.8456c878.js" rel="prefetch"><link href="dist/js/chunk-2d0c191e.f425dd57.js" rel="prefetch"><link href="dist/js/chunk-2d0c1a01.e37b19a2.js" rel="prefetch"><link href="dist/js/chunk-2d0c20be.76666437.js" rel="prefetch"><link href="dist/js/chunk-2d0d9fbc.22793f14.js" rel="prefetch"><link href="dist/js/chunk-2d0da701.80759043.js" rel="prefetch"><link href="dist/js/chunk-2d0dad5f.2e4d6938.js" rel="prefetch"><link href="dist/js/chunk-2d0db0f2.b5ce4946.js" rel="prefetch"><link href="dist/js/chunk-2d0dddce.3eea98de.js" rel="prefetch"><link href="dist/js/chunk-2d0ddf37.6ecb5986.js" rel="prefetch"><link href="dist/js/chunk-2d0de01b.a2a047cf.js" rel="prefetch"><link href="dist/js/chunk-2d0e2326.cfbc28b0.js" rel="prefetch"><link href="dist/js/chunk-2d0e268c.eba27ee7.js" rel="prefetch"><link href="dist/js/chunk-2d0e5089.10135360.js" rel="prefetch"><link href="dist/js/chunk-2d0e9742.9abceada.js" rel="prefetch"><link href="dist/js/chunk-2d0f026c.5f73597d.js" rel="prefetch"><link href="dist/js/chunk-2d2082b9.f52387a2.js" rel="prefetch"><link href="dist/js/chunk-2d208ffa.7f14d671.js" rel="prefetch"><link href="dist/js/chunk-2d20ec02.917aff76.js" rel="prefetch"><link href="dist/js/chunk-2d20f68f.ff46a11f.js" rel="prefetch"><link href="dist/js/chunk-2d210a7a.6a4911c4.js" rel="prefetch"><link href="dist/js/chunk-2d216004.1c1e194c.js" rel="prefetch"><link href="dist/js/chunk-2d217907.32a00939.js" rel="prefetch"><link href="dist/js/chunk-2d226d0a.6b1238d2.js" rel="prefetch"><link href="dist/js/chunk-2d2299c3.8f3151dd.js" rel="prefetch"><link href="dist/js/chunk-2d22bd06.29ee05f7.js" rel="prefetch"><link href="dist/js/chunk-2d2308b0.2797f6b4.js" rel="prefetch"><link href="dist/js/chunk-2d238428.7c9ae7c7.js" rel="prefetch"><link href="dist/js/chunk-3a2f3e67.13278516.js" rel="prefetch"><link href="dist/css/app.10014a3e.css" rel="preload" as="style"><link href="dist/css/chunk-vendors.c097b26d.css" rel="preload" as="style"><link href="dist/js/app.4d5a4cb3.js" rel="preload" as="script"><link href="dist/js/chunk-vendors.524ee6e1.js" rel="preload" as="script"><link href="dist/css/chunk-vendors.c097b26d.css" rel="stylesheet"><link href="dist/css/app.10014a3e.css" 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 src="dist/js/chunk-vendors.524ee6e1.js"></script><script src="dist/js/app.4d5a4cb3.js"></script></body></html>
|
||||
<!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,initial-scale=1"><link rel="icon" href="./dist/logo.png"><title>一个简单的web思维导图实现</title><link href="dist/js/chunk-2d0a3179.3b27bed0.js" rel="prefetch"><link href="dist/js/chunk-2d0a514a.64cc7822.js" rel="prefetch"><link href="dist/js/chunk-2d0aa579.61afd8c1.js" rel="prefetch"><link href="dist/js/chunk-2d0aa978.84fc06da.js" rel="prefetch"><link href="dist/js/chunk-2d0ab10b.4d405645.js" rel="prefetch"><link href="dist/js/chunk-2d0abe0f.a9abff6a.js" rel="prefetch"><link href="dist/js/chunk-2d0b1c6f.b8417307.js" rel="prefetch"><link href="dist/js/chunk-2d0b361e.d657e190.js" rel="prefetch"><link href="dist/js/chunk-2d0b91e5.433fdc5c.js" rel="prefetch"><link href="dist/js/chunk-2d0b92c3.9d7f8382.js" rel="prefetch"><link href="dist/js/chunk-2d0ba309.a3915823.js" rel="prefetch"><link href="dist/js/chunk-2d0bd54e.906e86ec.js" rel="prefetch"><link href="dist/js/chunk-2d0be174.0cf53d60.js" rel="prefetch"><link href="dist/js/chunk-2d0c0a44.0f9ebf1b.js" rel="prefetch"><link href="dist/js/chunk-2d0c14fc.7163274e.js" rel="prefetch"><link href="dist/js/chunk-2d0c18d8.2e6ddc70.js" rel="prefetch"><link href="dist/js/chunk-2d0c191e.cd5e6df3.js" rel="prefetch"><link href="dist/js/chunk-2d0c1a01.2e73bf0a.js" rel="prefetch"><link href="dist/js/chunk-2d0c20be.dab092ff.js" rel="prefetch"><link href="dist/js/chunk-2d0d5cb9.2e73ab77.js" rel="prefetch"><link href="dist/js/chunk-2d0d9fbc.4800807d.js" rel="prefetch"><link href="dist/js/chunk-2d0da701.80759043.js" rel="prefetch"><link href="dist/js/chunk-2d0dad5f.f670c26a.js" rel="prefetch"><link href="dist/js/chunk-2d0db0f2.b5ce4946.js" rel="prefetch"><link href="dist/js/chunk-2d0dd3b1.c4647d07.js" rel="prefetch"><link href="dist/js/chunk-2d0dddce.3eea98de.js" rel="prefetch"><link href="dist/js/chunk-2d0ddf37.d960b122.js" rel="prefetch"><link href="dist/js/chunk-2d0de01b.a2a047cf.js" rel="prefetch"><link href="dist/js/chunk-2d0e2326.5072607c.js" rel="prefetch"><link href="dist/js/chunk-2d0e268c.ed75c510.js" rel="prefetch"><link href="dist/js/chunk-2d0e5089.10135360.js" rel="prefetch"><link href="dist/js/chunk-2d0e9742.9abceada.js" rel="prefetch"><link href="dist/js/chunk-2d0f026c.4757f6bb.js" rel="prefetch"><link href="dist/js/chunk-2d2082b9.f52387a2.js" rel="prefetch"><link href="dist/js/chunk-2d208ffa.766e8fa6.js" rel="prefetch"><link href="dist/js/chunk-2d20ec02.917aff76.js" rel="prefetch"><link href="dist/js/chunk-2d20f68f.6c1b14e1.js" rel="prefetch"><link href="dist/js/chunk-2d210a7a.6a4911c4.js" rel="prefetch"><link href="dist/js/chunk-2d216004.704073c5.js" rel="prefetch"><link href="dist/js/chunk-2d217907.32a00939.js" rel="prefetch"><link href="dist/js/chunk-2d226d0a.6b1238d2.js" rel="prefetch"><link href="dist/js/chunk-2d2299c3.8f3151dd.js" rel="prefetch"><link href="dist/js/chunk-2d22bd06.9f7bbd4c.js" rel="prefetch"><link href="dist/js/chunk-2d2308b0.2797f6b4.js" rel="prefetch"><link href="dist/js/chunk-2d238428.7c9ae7c7.js" rel="prefetch"><link href="dist/js/chunk-3a2f3e67.13278516.js" rel="prefetch"><link href="dist/css/app.3b1827be.css" rel="preload" as="style"><link href="dist/css/chunk-vendors.c097b26d.css" rel="preload" as="style"><link href="dist/js/app.e9e06d36.js" rel="preload" as="script"><link href="dist/js/chunk-vendors.49b07a77.js" rel="preload" as="script"><link href="dist/css/chunk-vendors.c097b26d.css" rel="stylesheet"><link href="dist/css/app.3b1827be.css" 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 src="dist/js/chunk-vendors.49b07a77.js"></script><script src="dist/js/app.e9e06d36.js"></script></body></html>
|
||||
BIN
qrcode.jpg
Normal file
|
After Width: | Height: | Size: 49 KiB |
@@ -900,6 +900,17 @@ const data5 = {
|
||||
}
|
||||
}
|
||||
|
||||
// 富文本数据v0.4.0+,需要使用RichText插件才支持富文本编辑
|
||||
const richTextData = {
|
||||
"root": {
|
||||
"data": {
|
||||
"text": "<a href='http://lxqnsys.com/' target='_blank'>理想去年实验室</a>",
|
||||
"richText": true
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
}
|
||||
|
||||
const rootData = {
|
||||
"root": {
|
||||
"data": {
|
||||
|
||||
@@ -31,6 +31,12 @@
|
||||
"isActive": false
|
||||
},
|
||||
"children": []
|
||||
}, {
|
||||
"data": {
|
||||
"text": "<a href='http://lxqnsys.com/' target='_blank'>理想去年实验室</a>",
|
||||
"richText": true
|
||||
},
|
||||
"children": []
|
||||
}]
|
||||
}]
|
||||
},
|
||||
|
||||
@@ -5,9 +5,13 @@ import KeyboardNavigation from './src/KeyboardNavigation.js'
|
||||
import Export from './src/Export.js'
|
||||
import Drag from './src/Drag.js'
|
||||
import Select from './src/Select.js'
|
||||
import AssociativeLine from './src/AssociativeLine'
|
||||
import RichText from './src/RichText'
|
||||
import xmind from './src/parse/xmind.js'
|
||||
import markdown from './src/parse/markdown.js'
|
||||
|
||||
MindMap.xmind = xmind
|
||||
MindMap.markdown = markdown
|
||||
|
||||
MindMap
|
||||
.usePlugin(MiniMap)
|
||||
@@ -16,5 +20,7 @@ MindMap
|
||||
.usePlugin(KeyboardNavigation)
|
||||
.usePlugin(Export)
|
||||
.usePlugin(Select)
|
||||
.usePlugin(AssociativeLine)
|
||||
.usePlugin(RichText)
|
||||
|
||||
export default MindMap
|
||||
@@ -61,7 +61,18 @@ const defaultOpt = {
|
||||
}
|
||||
},
|
||||
// 达到该宽度文本自动换行
|
||||
textAutoWrapWidth: 500
|
||||
textAutoWrapWidth: 500,
|
||||
// 自定义鼠标滚轮事件处理
|
||||
// 可以传一个函数,回调参数为事件对象
|
||||
customHandleMousewheel: null,
|
||||
// 鼠标滚动的行为,如果customHandleMousewheel传了自定义函数,这个属性不生效
|
||||
mousewheelAction: 'zoom',// zoom(放大缩小)、move(上下移动)
|
||||
// 当mousewheelAction设为move时,可以通过该属性控制鼠标滚动一下视图移动的步长,单位px
|
||||
mousewheelMoveStep: 100,
|
||||
// 默认插入的二级节点的文字
|
||||
defaultInsertSecondLevelNodeText: '二级节点',
|
||||
// 默认插入的二级以下节点的文字
|
||||
defaultInsertBelowSecondLevelNodeText: '分支主题'
|
||||
}
|
||||
|
||||
// 思维导图
|
||||
|
||||
928
simple-mind-map/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.4.0",
|
||||
"version": "0.4.7",
|
||||
"description": "一个简单的web在线思维导图",
|
||||
"authors": [
|
||||
{
|
||||
@@ -31,7 +31,9 @@
|
||||
"html2canvas": "^1.4.1",
|
||||
"jspdf": "^2.5.1",
|
||||
"jszip": "^3.10.1",
|
||||
"mdast-util-from-markdown": "^1.3.0",
|
||||
"quill": "^1.3.6",
|
||||
"uuid": "^9.0.0",
|
||||
"xml-js": "^1.6.11"
|
||||
},
|
||||
"keywords": [
|
||||
|
||||
627
simple-mind-map/src/AssociativeLine.js
Normal file
@@ -0,0 +1,627 @@
|
||||
import { walk, bfsWalk, throttle } from './utils/'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import {
|
||||
getAssociativeLineTargetIndex,
|
||||
computeCubicBezierPathPoints,
|
||||
joinCubicBezierPath,
|
||||
cubicBezierPath,
|
||||
getNodePoint,
|
||||
computeNodePoints,
|
||||
getNodeLinePath,
|
||||
getDefaultControlPointOffsets
|
||||
} from './utils/associativeLineUtils'
|
||||
|
||||
// 关联线类
|
||||
class AssociativeLine {
|
||||
constructor(opt = {}) {
|
||||
this.mindMap = opt.mindMap
|
||||
this.draw = this.mindMap.draw
|
||||
// 当前所有连接线
|
||||
this.lineList = []
|
||||
// 当前激活的连接线
|
||||
this.activeLine = null
|
||||
// 当前正在创建连接线
|
||||
this.isCreatingLine = false // 是否正在创建连接线中
|
||||
this.creatingStartNode = null // 起始节点
|
||||
this.creatingLine = null // 创建过程中的连接线
|
||||
this.overlapNode = null // 创建过程中的目标节点
|
||||
// 是否有节点正在被拖拽
|
||||
this.isNodeDragging = false
|
||||
// 箭头图标
|
||||
this.markerPath = null
|
||||
this.marker = this.createMarker()
|
||||
// 控制点
|
||||
this.controlLine1 = null
|
||||
this.controlLine2 = null
|
||||
this.controlPoint1 = null
|
||||
this.controlPoint2 = null
|
||||
this.controlPointDiameter = 10
|
||||
this.isControlPointMousedown = false
|
||||
this.mousedownControlPointKey = ''
|
||||
this.controlPointMousemoveState = {
|
||||
pos: null,
|
||||
startPoint: null,
|
||||
endPoint: null,
|
||||
targetIndex: ''
|
||||
}
|
||||
// 节流一下,不然很卡
|
||||
this.checkOverlapNode = throttle(this.checkOverlapNode, 100, this)
|
||||
this.bindEvent()
|
||||
}
|
||||
|
||||
// 监听事件
|
||||
bindEvent() {
|
||||
// 节点树渲染完毕后渲染连接线
|
||||
this.renderAllLines = this.renderAllLines.bind(this)
|
||||
this.mindMap.on('node_tree_render_end', this.renderAllLines)
|
||||
// 状态改变后重新渲染连接线
|
||||
this.mindMap.on('data_change', this.renderAllLines)
|
||||
// 监听画布和节点点击事件,用于清除当前激活的连接线
|
||||
this.mindMap.on('draw_click', () => {
|
||||
if (this.isControlPointMousedown) {
|
||||
return
|
||||
}
|
||||
this.clearActiveLine()
|
||||
})
|
||||
this.mindMap.on('node_click', node => {
|
||||
if (this.isCreatingLine) {
|
||||
this.completeCreateLine(node)
|
||||
} else {
|
||||
this.clearActiveLine()
|
||||
}
|
||||
})
|
||||
// 注册删除快捷键
|
||||
this.mindMap.keyCommand.addShortcut(
|
||||
'Del|Backspace',
|
||||
this.removeLine.bind(this)
|
||||
)
|
||||
// 注册添加连接线的命令
|
||||
this.mindMap.command.add('ADD_ASSOCIATIVE_LINE', this.addLine.bind(this))
|
||||
// 监听鼠标移动事件
|
||||
this.mindMap.on('mousemove', this.onMousemove.bind(this))
|
||||
// 节点拖拽事件
|
||||
this.mindMap.on('node_dragging', this.onNodeDragging.bind(this))
|
||||
this.mindMap.on('node_dragend', this.onNodeDragend.bind(this))
|
||||
// 拖拽控制点
|
||||
window.addEventListener('mousemove', e => {
|
||||
this.onControlPointMousemove(e)
|
||||
})
|
||||
window.addEventListener('mouseup', e => {
|
||||
this.onControlPointMouseup(e)
|
||||
})
|
||||
}
|
||||
|
||||
// 创建箭头
|
||||
createMarker() {
|
||||
return this.draw.marker(20, 20, add => {
|
||||
add.ref(2, 5)
|
||||
add.size(10, 10)
|
||||
add.attr('orient', 'auto-start-reverse')
|
||||
this.markerPath = add.path('M0,0 L2,5 L0,10 L10,5 Z')
|
||||
})
|
||||
}
|
||||
|
||||
// 渲染所有连线
|
||||
renderAllLines() {
|
||||
// 先移除
|
||||
this.removeAllLines()
|
||||
this.removeControls()
|
||||
this.clearActiveLine()
|
||||
let tree = this.mindMap.renderer.root
|
||||
if (!tree) return
|
||||
let idToNode = new Map()
|
||||
let nodeToIds = new Map()
|
||||
walk(
|
||||
tree,
|
||||
null,
|
||||
cur => {
|
||||
if (!cur) return
|
||||
let data = cur.nodeData.data
|
||||
if (
|
||||
data.associativeLineTargets &&
|
||||
data.associativeLineTargets.length > 0
|
||||
) {
|
||||
nodeToIds.set(cur, data.associativeLineTargets)
|
||||
}
|
||||
if (data.id) {
|
||||
idToNode.set(data.id, cur)
|
||||
}
|
||||
},
|
||||
() => {},
|
||||
true,
|
||||
0
|
||||
)
|
||||
nodeToIds.forEach((ids, node) => {
|
||||
ids.forEach(id => {
|
||||
let toNode = idToNode.get(id)
|
||||
if (!node || !toNode) return
|
||||
let [startPoint, endPoint] = computeNodePoints(node, toNode)
|
||||
this.drawLine(startPoint, endPoint, node, toNode)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 绘制连接线
|
||||
drawLine(startPoint, endPoint, node, toNode) {
|
||||
let {
|
||||
associativeLineWidth,
|
||||
associativeLineColor,
|
||||
associativeLineActiveWidth,
|
||||
associativeLineActiveColor
|
||||
} = this.mindMap.themeConfig
|
||||
// 箭头
|
||||
this.markerPath
|
||||
.stroke({ color: associativeLineColor })
|
||||
.fill({ color: associativeLineColor })
|
||||
// 路径
|
||||
let { path: pathStr, controlPoints } = getNodeLinePath(
|
||||
startPoint,
|
||||
endPoint,
|
||||
node,
|
||||
toNode
|
||||
)
|
||||
// 虚线
|
||||
let path = this.draw.path()
|
||||
path
|
||||
.stroke({
|
||||
width: associativeLineWidth,
|
||||
color: associativeLineColor,
|
||||
dasharray: [6, 4]
|
||||
})
|
||||
.fill({ color: 'none' })
|
||||
path.plot(pathStr)
|
||||
path.marker('end', this.marker)
|
||||
// 不可见的点击线
|
||||
let clickPath = this.draw.path()
|
||||
clickPath
|
||||
.stroke({ width: associativeLineActiveWidth, color: 'transparent' })
|
||||
.fill({ color: 'none' })
|
||||
clickPath.plot(pathStr)
|
||||
// 点击事件
|
||||
clickPath.click(e => {
|
||||
e.stopPropagation()
|
||||
// 如果当前存在激活节点,那么取消激活节点
|
||||
if (this.mindMap.renderer.activeNodeList.length > 0) {
|
||||
this.clearActiveNodes()
|
||||
} else {
|
||||
// 否则清除当前的关联线的激活状态,如果有的话
|
||||
this.clearActiveLine()
|
||||
// 保存当前激活的关联线信息
|
||||
this.activeLine = [path, clickPath, node, toNode]
|
||||
// 让不可见的点击线显示
|
||||
clickPath.stroke({ color: associativeLineActiveColor })
|
||||
// 渲染控制点和连线
|
||||
this.renderControls(
|
||||
startPoint,
|
||||
endPoint,
|
||||
controlPoints[0],
|
||||
controlPoints[1]
|
||||
)
|
||||
this.mindMap.emit(
|
||||
'associative_line_click',
|
||||
path,
|
||||
clickPath,
|
||||
node,
|
||||
toNode
|
||||
)
|
||||
}
|
||||
})
|
||||
this.lineList.push([path, clickPath, node, toNode])
|
||||
}
|
||||
|
||||
// 移除所有连接线
|
||||
removeAllLines() {
|
||||
this.lineList.forEach(line => {
|
||||
line[0].remove()
|
||||
line[1].remove()
|
||||
})
|
||||
this.lineList = []
|
||||
}
|
||||
|
||||
// 从当前激活节点开始创建连接线
|
||||
createLineFromActiveNode() {
|
||||
if (this.mindMap.renderer.activeNodeList.length <= 0) return
|
||||
let node = this.mindMap.renderer.activeNodeList[0]
|
||||
this.createLine(node)
|
||||
}
|
||||
|
||||
// 创建连接线
|
||||
createLine(fromNode) {
|
||||
let { associativeLineWidth, associativeLineColor } =
|
||||
this.mindMap.themeConfig
|
||||
if (this.isCreatingLine || !fromNode) return
|
||||
this.isCreatingLine = true
|
||||
this.creatingStartNode = fromNode
|
||||
this.creatingLine = this.draw.path()
|
||||
this.creatingLine
|
||||
.stroke({
|
||||
width: associativeLineWidth,
|
||||
color: associativeLineColor,
|
||||
dasharray: [6, 4]
|
||||
})
|
||||
.fill({ color: 'none' })
|
||||
this.creatingLine.marker('end', this.marker)
|
||||
}
|
||||
|
||||
// 鼠标移动事件
|
||||
onMousemove(e) {
|
||||
if (!this.isCreatingLine) return
|
||||
this.updateCreatingLine(e)
|
||||
}
|
||||
|
||||
// 更新创建过程中的连接线
|
||||
updateCreatingLine(e) {
|
||||
let { x, y } = this.getTransformedEventPos(e)
|
||||
let startPoint = getNodePoint(this.creatingStartNode)
|
||||
let pathStr = cubicBezierPath(startPoint.x, startPoint.y, x, y)
|
||||
this.creatingLine.plot(pathStr)
|
||||
this.checkOverlapNode(x, y)
|
||||
}
|
||||
|
||||
// 获取转换后的鼠标事件对象的坐标
|
||||
getTransformedEventPos(e) {
|
||||
let { x, y } = this.mindMap.toPos(e.clientX, e.clientY)
|
||||
let { scaleX, scaleY, translateX, translateY } =
|
||||
this.mindMap.draw.transform()
|
||||
return {
|
||||
x: (x - translateX) / scaleX,
|
||||
y: (y - translateY) / scaleY
|
||||
}
|
||||
}
|
||||
|
||||
// 检测当前移动到的目标节点
|
||||
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 === this.creatingStartNode || this.overlapNode) {
|
||||
return
|
||||
}
|
||||
let { left, top, width, height } = node
|
||||
let right = left + width
|
||||
let bottom = top + height
|
||||
if (x >= left && x <= right && y >= top && y <= bottom) {
|
||||
this.overlapNode = node
|
||||
}
|
||||
})
|
||||
if (this.overlapNode && !this.overlapNode.nodeData.data.isActive) {
|
||||
this.mindMap.renderer.setNodeActive(this.overlapNode, true)
|
||||
}
|
||||
}
|
||||
|
||||
// 完成创建连接线
|
||||
completeCreateLine(node) {
|
||||
if (this.creatingStartNode === node) return
|
||||
this.addLine(this.creatingStartNode, node)
|
||||
if (this.overlapNode && this.overlapNode.nodeData.data.isActive) {
|
||||
this.mindMap.renderer.setNodeActive(this.overlapNode, false)
|
||||
}
|
||||
this.isCreatingLine = false
|
||||
this.creatingStartNode = null
|
||||
this.creatingLine.remove()
|
||||
this.creatingLine = null
|
||||
this.overlapNode = null
|
||||
}
|
||||
|
||||
// 添加连接线
|
||||
addLine(fromNode, toNode) {
|
||||
if (!fromNode || !toNode) return
|
||||
// 目标节点如果没有id,则生成一个id
|
||||
let id = toNode.nodeData.data.id
|
||||
if (!id) {
|
||||
id = uuid()
|
||||
this.mindMap.execCommand('SET_NODE_DATA', toNode, {
|
||||
id
|
||||
})
|
||||
}
|
||||
// 将目标节点id保存起来
|
||||
let list = fromNode.nodeData.data.associativeLineTargets || []
|
||||
list.push(id)
|
||||
// 保存控制点
|
||||
let [startPoint, endPoint] = computeNodePoints(fromNode, toNode)
|
||||
let controlPoints = computeCubicBezierPathPoints(
|
||||
startPoint.x,
|
||||
startPoint.y,
|
||||
endPoint.x,
|
||||
endPoint.y
|
||||
)
|
||||
let offsetList =
|
||||
fromNode.nodeData.data.associativeLineTargetControlOffsets || []
|
||||
// 保存的实际是控制点和端点的差值,否则当节点位置改变了,控制点还是原来的位置,连线就不对了
|
||||
offsetList[list.length - 1] = [
|
||||
{
|
||||
x: controlPoints[0].x - startPoint.x,
|
||||
y: controlPoints[0].y - startPoint.y
|
||||
},
|
||||
{
|
||||
x: controlPoints[1].x - endPoint.x,
|
||||
y: controlPoints[1].y - endPoint.y
|
||||
}
|
||||
]
|
||||
this.mindMap.execCommand('SET_NODE_DATA', fromNode, {
|
||||
associativeLineTargets: list,
|
||||
associativeLineTargetControlOffsets: offsetList
|
||||
})
|
||||
}
|
||||
|
||||
// 删除连接线
|
||||
removeLine() {
|
||||
if (!this.activeLine) return
|
||||
let [, , node, toNode] = this.activeLine
|
||||
this.removeControls()
|
||||
let { associativeLineTargets, associativeLineTargetControlOffsets } =
|
||||
node.nodeData.data
|
||||
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
|
||||
this.mindMap.execCommand('SET_NODE_DATA', node, {
|
||||
associativeLineTargets: associativeLineTargets.filter((_, index) => {
|
||||
return index !== targetIndex
|
||||
}),
|
||||
associativeLineTargetControlOffsets: associativeLineTargetControlOffsets
|
||||
? associativeLineTargetControlOffsets.filter((_, index) => {
|
||||
return index !== targetIndex
|
||||
})
|
||||
: []
|
||||
})
|
||||
}
|
||||
|
||||
// 清除当前激活的节点
|
||||
clearActiveNodes() {
|
||||
if (this.mindMap.renderer.activeNodeList.length > 0) {
|
||||
this.mindMap.execCommand('CLEAR_ACTIVE_NODE')
|
||||
}
|
||||
}
|
||||
|
||||
// 清除激活的线
|
||||
clearActiveLine() {
|
||||
if (this.activeLine) {
|
||||
this.activeLine[1].stroke({
|
||||
color: 'transparent'
|
||||
})
|
||||
this.activeLine = null
|
||||
this.removeControls()
|
||||
}
|
||||
}
|
||||
|
||||
// 处理节点正在拖拽事件
|
||||
onNodeDragging() {
|
||||
if (this.isNodeDragging) return
|
||||
this.isNodeDragging = true
|
||||
this.lineList.forEach(line => {
|
||||
line[0].hide()
|
||||
line[1].hide()
|
||||
})
|
||||
this.hideControls()
|
||||
}
|
||||
|
||||
// 处理节点拖拽完成事件
|
||||
onNodeDragend() {
|
||||
if (!this.isNodeDragging) return
|
||||
this.lineList.forEach(line => {
|
||||
line[0].show()
|
||||
line[1].show()
|
||||
})
|
||||
this.showControls()
|
||||
this.isNodeDragging = false
|
||||
}
|
||||
|
||||
// 创建控制点、连线节点
|
||||
createControlNodes() {
|
||||
let { associativeLineActiveColor } = this.mindMap.themeConfig
|
||||
// 连线
|
||||
this.controlLine1 = this.draw
|
||||
.line()
|
||||
.stroke({ color: associativeLineActiveColor, width: 2 })
|
||||
this.controlLine2 = this.draw
|
||||
.line()
|
||||
.stroke({ color: associativeLineActiveColor, width: 2 })
|
||||
// 控制点
|
||||
this.controlPoint1 = this.createOneControlNode('controlPoint1')
|
||||
this.controlPoint2 = this.createOneControlNode('controlPoint2')
|
||||
}
|
||||
|
||||
// 创建控制点
|
||||
createOneControlNode(pointKey) {
|
||||
let { associativeLineActiveColor } = this.mindMap.themeConfig
|
||||
return this.draw
|
||||
.circle(this.controlPointDiameter)
|
||||
.stroke({ color: associativeLineActiveColor })
|
||||
.fill({ color: '#fff' })
|
||||
.click(e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
.mousedown(e => {
|
||||
this.onControlPointMousedown(e, pointKey)
|
||||
})
|
||||
}
|
||||
|
||||
// 控制点的鼠标按下事件
|
||||
onControlPointMousedown(e, pointKey) {
|
||||
e.stopPropagation()
|
||||
this.isControlPointMousedown = true
|
||||
this.mousedownControlPointKey = pointKey
|
||||
}
|
||||
|
||||
// 控制点的鼠标移动事件
|
||||
onControlPointMousemove(e) {
|
||||
if (
|
||||
!this.isControlPointMousedown ||
|
||||
!this.mousedownControlPointKey ||
|
||||
!this[this.mousedownControlPointKey]
|
||||
)
|
||||
return
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
let radius = this.controlPointDiameter / 2
|
||||
// 转换鼠标当前的位置
|
||||
let { x, y } = this.getTransformedEventPos(e)
|
||||
this.controlPointMousemoveState.pos = {
|
||||
x,
|
||||
y
|
||||
}
|
||||
// 更新当前拖拽的控制点的位置
|
||||
this[this.mousedownControlPointKey].x(x - radius).y(y - radius)
|
||||
let [path, clickPath, node, toNode] = this.activeLine
|
||||
let [startPoint, endPoint] = computeNodePoints(node, toNode)
|
||||
this.controlPointMousemoveState.startPoint = startPoint
|
||||
this.controlPointMousemoveState.endPoint = endPoint
|
||||
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
|
||||
this.controlPointMousemoveState.targetIndex = targetIndex
|
||||
let offsets = []
|
||||
let associativeLineTargetControlOffsets = node.nodeData.data.associativeLineTargetControlOffsets
|
||||
if (!associativeLineTargetControlOffsets) {
|
||||
// 兼容0.4.5版本,没有associativeLineTargetControlOffsets的情况
|
||||
offsets = getDefaultControlPointOffsets(startPoint, endPoint)
|
||||
} else {
|
||||
offsets = associativeLineTargetControlOffsets[targetIndex]
|
||||
}
|
||||
let point1 = null
|
||||
let point2 = null
|
||||
// 拖拽的是控制点1
|
||||
if (this.mousedownControlPointKey === 'controlPoint1') {
|
||||
point1 = {
|
||||
x,
|
||||
y
|
||||
}
|
||||
point2 = {
|
||||
x: endPoint.x + offsets[1].x,
|
||||
y: endPoint.y + offsets[1].y
|
||||
}
|
||||
// 更新控制点1的连线
|
||||
this.controlLine1.plot(startPoint.x, startPoint.y, point1.x, point1.y)
|
||||
} else {
|
||||
// 拖拽的是控制点2
|
||||
point1 = {
|
||||
x: startPoint.x + offsets[0].x,
|
||||
y: startPoint.y + offsets[0].y
|
||||
}
|
||||
point2 = {
|
||||
x,
|
||||
y
|
||||
}
|
||||
// 更新控制点2的连线
|
||||
this.controlLine2.plot(endPoint.x, endPoint.y, point2.x, point2.y)
|
||||
}
|
||||
// 更新关联线
|
||||
let pathStr = joinCubicBezierPath(startPoint, endPoint, point1, point2)
|
||||
path.plot(pathStr)
|
||||
clickPath.plot(pathStr)
|
||||
}
|
||||
|
||||
// 控制点的鼠标移动事件
|
||||
onControlPointMouseup(e) {
|
||||
if (!this.isControlPointMousedown) return
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
let { pos, startPoint, endPoint, targetIndex } =
|
||||
this.controlPointMousemoveState
|
||||
let [, , node] = this.activeLine
|
||||
let offsetList = []
|
||||
let associativeLineTargetControlOffsets = node.nodeData.data.associativeLineTargetControlOffsets
|
||||
if (!associativeLineTargetControlOffsets) {
|
||||
// 兼容0.4.5版本,没有associativeLineTargetControlOffsets的情况
|
||||
offsetList[targetIndex] = getDefaultControlPointOffsets(startPoint, endPoint)
|
||||
} else {
|
||||
offsetList = associativeLineTargetControlOffsets
|
||||
}
|
||||
let offset1 = null
|
||||
let offset2 = null
|
||||
if (this.mousedownControlPointKey === 'controlPoint1') {
|
||||
// 更新控制点1数据
|
||||
offset1 = {
|
||||
x: pos.x - startPoint.x,
|
||||
y: pos.y - startPoint.y
|
||||
}
|
||||
offset2 = offsetList[targetIndex][1]
|
||||
} else {
|
||||
// 更新控制点2数据
|
||||
offset1 = offsetList[targetIndex][0]
|
||||
offset2 = {
|
||||
x: pos.x - endPoint.x,
|
||||
y: pos.y - endPoint.y
|
||||
}
|
||||
}
|
||||
offsetList[targetIndex] = [offset1, offset2]
|
||||
this.mindMap.execCommand('SET_NODE_DATA', node, {
|
||||
associativeLineTargetControlOffsets: offsetList
|
||||
})
|
||||
// 这里要加个setTimeout0是因为draw_click事件比mouseup事件触发的晚,所以重置isControlPointMousedown需要等draw_click事件触发完以后
|
||||
setTimeout(() => {
|
||||
this.resetControlPoint()
|
||||
}, 0)
|
||||
}
|
||||
|
||||
// 复位控制点移动
|
||||
resetControlPoint() {
|
||||
this.isControlPointMousedown = false
|
||||
this.mousedownControlPointKey = ''
|
||||
this.controlPointMousemoveState = {
|
||||
pos: null,
|
||||
startPoint: null,
|
||||
endPoint: null,
|
||||
targetIndex: ''
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染控制点
|
||||
renderControls(startPoint, endPoint, point1, point2) {
|
||||
if (!this.controlLine1) {
|
||||
this.createControlNodes()
|
||||
}
|
||||
let radius = this.controlPointDiameter / 2
|
||||
// 控制点和起终点的连线
|
||||
this.controlLine1.plot(startPoint.x, startPoint.y, point1.x, point1.y)
|
||||
this.controlLine2.plot(endPoint.x, endPoint.y, point2.x, point2.y)
|
||||
// 控制点
|
||||
this.controlPoint1.x(point1.x - radius).y(point1.y - radius)
|
||||
this.controlPoint2.x(point2.x - radius).y(point2.y - radius)
|
||||
}
|
||||
|
||||
// 删除控制点
|
||||
removeControls() {
|
||||
if (!this.controlLine1) return
|
||||
;[
|
||||
this.controlLine1,
|
||||
this.controlLine2,
|
||||
this.controlPoint1,
|
||||
this.controlPoint2
|
||||
].forEach(item => {
|
||||
item.remove()
|
||||
})
|
||||
this.controlLine1 = null
|
||||
this.controlLine2 = null
|
||||
this.controlPoint1 = null
|
||||
this.controlPoint2 = null
|
||||
}
|
||||
|
||||
// 隐藏控制点
|
||||
hideControls() {
|
||||
if (!this.controlLine1) return
|
||||
;[
|
||||
this.controlLine1,
|
||||
this.controlLine2,
|
||||
this.controlPoint1,
|
||||
this.controlPoint2
|
||||
].forEach(item => {
|
||||
item.hide()
|
||||
})
|
||||
}
|
||||
|
||||
// 显示控制点
|
||||
showControls() {
|
||||
if (!this.controlLine1) return
|
||||
;[
|
||||
this.controlLine1,
|
||||
this.controlLine2,
|
||||
this.controlPoint1,
|
||||
this.controlPoint2
|
||||
].forEach(item => {
|
||||
item.show()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
AssociativeLine.instanceName = 'associativeLine'
|
||||
|
||||
export default AssociativeLine
|
||||
@@ -1,33 +1,4 @@
|
||||
// 在下一个事件循环里执行任务
|
||||
const nextTick = function (fn, ctx) {
|
||||
let pending = false
|
||||
let timerFunc = null
|
||||
let handle = () => {
|
||||
pending = false
|
||||
ctx ? fn.call(ctx) : fn()
|
||||
}
|
||||
// 支持MutationObserver接口的话使用MutationObserver
|
||||
if (typeof MutationObserver !== 'undefined') {
|
||||
let counter = 1
|
||||
let observer = new MutationObserver(handle)
|
||||
let textNode = document.createTextNode(counter)
|
||||
observer.observe(textNode, {
|
||||
characterData: true // 设为 true 表示监视指定目标节点或子节点树中节点所包含的字符数据的变化
|
||||
})
|
||||
timerFunc = function () {
|
||||
counter = (counter + 1) % 2 // counter会在0和1两者循环变化
|
||||
textNode.data = counter // 节点变化会触发回调handle,
|
||||
}
|
||||
} else {
|
||||
// 否则使用定时器
|
||||
timerFunc = setTimeout
|
||||
}
|
||||
return function () {
|
||||
if (pending) return
|
||||
pending = true
|
||||
timerFunc(handle, 0)
|
||||
}
|
||||
}
|
||||
import { nextTick } from './utils'
|
||||
|
||||
// 批量执行
|
||||
class BatchExecution {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { copyRenderTree, simpleDeepClone } from './utils'
|
||||
import { copyRenderTree, simpleDeepClone, nextTick } from './utils'
|
||||
|
||||
// 命令类
|
||||
class Command {
|
||||
@@ -11,6 +11,7 @@ class Command {
|
||||
this.activeHistoryIndex = 0
|
||||
// 注册快捷键
|
||||
this.registerShortcutKeys()
|
||||
this.addHistory = nextTick(this.addHistory, this)
|
||||
}
|
||||
|
||||
// 清空历史数据
|
||||
@@ -36,7 +37,7 @@ class Command {
|
||||
this.commands[name].forEach(fn => {
|
||||
fn(...args)
|
||||
})
|
||||
if (name === 'BACK' || name === 'FORWARD') {
|
||||
if (['BACK', 'FORWARD', 'SET_NODE_ACTIVE', 'CLEAR_ACTIVE_NODE'].includes(name)) {
|
||||
return
|
||||
}
|
||||
this.addHistory()
|
||||
@@ -76,6 +77,12 @@ class Command {
|
||||
return
|
||||
}
|
||||
let data = this.getCopyData()
|
||||
// 此次数据和上次一样则不重复添加
|
||||
if (this.history.length > 0 && JSON.stringify(this.history[this.history.length - 1]) === JSON.stringify(data)) {
|
||||
return
|
||||
}
|
||||
// 删除当前历史指针后面的数据
|
||||
this.history = this.history.slice(0, this.activeHistoryIndex + 1)
|
||||
this.history.push(simpleDeepClone(data))
|
||||
this.activeHistoryIndex = this.history.length - 1
|
||||
this.mindMap.emit('data_change', data)
|
||||
@@ -98,7 +105,9 @@ class Command {
|
||||
this.activeHistoryIndex,
|
||||
this.history.length
|
||||
)
|
||||
return simpleDeepClone(this.history[this.activeHistoryIndex])
|
||||
let data = simpleDeepClone(this.history[this.activeHistoryIndex])
|
||||
this.mindMap.emit('data_change', data)
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,13 +120,15 @@ class Command {
|
||||
if (this.activeHistoryIndex + step <= len - 1) {
|
||||
this.activeHistoryIndex += step
|
||||
this.mindMap.emit('back_forward', this.activeHistoryIndex)
|
||||
return simpleDeepClone(this.history[this.activeHistoryIndex])
|
||||
let data = simpleDeepClone(this.history[this.activeHistoryIndex])
|
||||
this.mindMap.emit('data_change', data)
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
// 获取渲染树数据副本
|
||||
getCopyData() {
|
||||
return copyRenderTree({}, this.mindMap.renderer.renderTree)
|
||||
return copyRenderTree({}, this.mindMap.renderer.renderTree, true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ class Drag extends Base {
|
||||
if (!this.isMousedown) {
|
||||
return
|
||||
}
|
||||
this.mindMap.emit('node_dragging', this.node)
|
||||
e.preventDefault()
|
||||
let { x, y } = this.mindMap.toPos(e.clientX, e.clientY)
|
||||
this.mouseMoveX = x
|
||||
@@ -136,6 +137,7 @@ class Drag extends Base {
|
||||
this.mindMap.render()
|
||||
}
|
||||
this.reset()
|
||||
this.mindMap.emit('node_dragend')
|
||||
}
|
||||
|
||||
// 创建克隆节点
|
||||
|
||||
@@ -43,12 +43,7 @@ class Event extends EventEmitter {
|
||||
this.mindMap.svg.on('mousedown', this.onSvgMousedown)
|
||||
window.addEventListener('mousemove', this.onMousemove)
|
||||
window.addEventListener('mouseup', this.onMouseup)
|
||||
// 兼容火狐浏览器
|
||||
if (window.navigator.userAgent.toLowerCase().indexOf('firefox') != -1) {
|
||||
this.mindMap.el.addEventListener('DOMMouseScroll', this.onMousewheel)
|
||||
} else {
|
||||
this.mindMap.el.addEventListener('mousewheel', this.onMousewheel)
|
||||
}
|
||||
this.mindMap.el.addEventListener('wheel', this.onMousewheel)
|
||||
this.mindMap.svg.on('contextmenu', this.onContextmenu)
|
||||
window.addEventListener('keyup', this.onKeyup)
|
||||
}
|
||||
@@ -59,7 +54,7 @@ class Event extends EventEmitter {
|
||||
this.mindMap.el.removeEventListener('mousedown', this.onMousedown)
|
||||
window.removeEventListener('mousemove', this.onMousemove)
|
||||
window.removeEventListener('mouseup', this.onMouseup)
|
||||
this.mindMap.el.removeEventListener('mousewheel', this.onMousewheel)
|
||||
this.mindMap.el.removeEventListener('wheel', this.onMousewheel)
|
||||
this.mindMap.svg.off('contextmenu', this.onContextmenu)
|
||||
window.removeEventListener('keyup', this.onKeyup)
|
||||
}
|
||||
@@ -110,10 +105,17 @@ class Event extends EventEmitter {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
let dir
|
||||
if ((e.wheelDeltaY || e.detail) > 0) {
|
||||
dir = 'up'
|
||||
// 解决mac触控板双指缩放方向相反的问题
|
||||
if (e.ctrlKey) {
|
||||
if (e.deltaY > 0) dir = 'up'
|
||||
if (e.deltaY < 0) dir = 'down'
|
||||
if (e.deltaX > 0) dir = 'left'
|
||||
if (e.deltaX < 0) dir = 'right'
|
||||
} else {
|
||||
dir = 'down'
|
||||
if ((e.wheelDeltaY || e.detail) > 0) dir = 'up'
|
||||
if ((e.wheelDeltaY || e.detail) < 0) dir = 'down'
|
||||
if ((e.wheelDeltaX || e.detail) > 0) dir = 'left'
|
||||
if ((e.wheelDeltaX || e.detail) < 0) dir = 'right'
|
||||
}
|
||||
this.emit('mousewheel', e, dir, this)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { imgToDataUrl, downloadFile } from './utils'
|
||||
import JsPDF from 'jspdf'
|
||||
import { SVG } from '@svgdotjs/svg.js'
|
||||
import drawBackgroundImageToCanvas from './utils/simulateCSSBackgroundInCanvas'
|
||||
import { transformToMarkdown } from './parse/toMarkdown'
|
||||
const URL = window.URL || window.webkitURL || window
|
||||
|
||||
// 导出类
|
||||
@@ -40,8 +41,10 @@ class Export {
|
||||
let nodeWithDomToImg = null
|
||||
if (domToImage && this.mindMap.richText) {
|
||||
let res = await this.mindMap.richText.handleSvgDomElements(svg)
|
||||
nodeWithDomToImg = res.svg
|
||||
svgHTML = res.svgHTML
|
||||
if (res) {
|
||||
nodeWithDomToImg = res.svg
|
||||
svgHTML = res.svgHTML
|
||||
}
|
||||
}
|
||||
return {
|
||||
node: svg,
|
||||
@@ -236,6 +239,14 @@ class Export {
|
||||
smm(name, withConfig) {
|
||||
return this.json(name, withConfig)
|
||||
}
|
||||
|
||||
// markdown文件
|
||||
md() {
|
||||
let data = this.mindMap.getData()
|
||||
let content = transformToMarkdown(data)
|
||||
let blob = new Blob([content])
|
||||
return URL.createObjectURL(blob)
|
||||
}
|
||||
}
|
||||
|
||||
Export.instanceName = 'doExport'
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { isKey } from './utils/keyMap'
|
||||
import { bfsWalk } from './utils'
|
||||
|
||||
// 键盘导航类
|
||||
@@ -8,22 +7,29 @@ class KeyboardNavigation {
|
||||
this.opt = opt
|
||||
this.mindMap = opt.mindMap
|
||||
this.onKeyup = this.onKeyup.bind(this)
|
||||
this.mindMap.on('keyup', this.onKeyup)
|
||||
this.mindMap.keyCommand.addShortcut('Left', () => {
|
||||
this.onKeyup('Left')
|
||||
})
|
||||
this.mindMap.keyCommand.addShortcut('Up', () => {
|
||||
this.onKeyup('Up')
|
||||
})
|
||||
this.mindMap.keyCommand.addShortcut('Right', () => {
|
||||
this.onKeyup('Right')
|
||||
})
|
||||
this.mindMap.keyCommand.addShortcut('Down', () => {
|
||||
this.onKeyup('Down')
|
||||
})
|
||||
}
|
||||
|
||||
// 处理按键事件
|
||||
onKeyup(e) {
|
||||
;['Left', 'Up', 'Right', 'Down'].forEach(dir => {
|
||||
if (isKey(e, dir)) {
|
||||
if (this.mindMap.renderer.activeNodeList.length > 0) {
|
||||
this.focus(dir)
|
||||
} else {
|
||||
let root = this.mindMap.renderer.root
|
||||
this.mindMap.renderer.moveNodeToCenter(root)
|
||||
root.active()
|
||||
}
|
||||
}
|
||||
})
|
||||
onKeyup(dir) {
|
||||
if (this.mindMap.renderer.activeNodeList.length > 0) {
|
||||
this.focus(dir)
|
||||
} else {
|
||||
let root = this.mindMap.renderer.root
|
||||
this.mindMap.renderer.moveNodeToCenter(root)
|
||||
root.active()
|
||||
}
|
||||
}
|
||||
|
||||
// 聚焦到下一个节点
|
||||
|
||||
@@ -191,7 +191,9 @@ class Node {
|
||||
'dblclick',
|
||||
'contextmenu',
|
||||
'mousedown',
|
||||
'mouseup'
|
||||
'mouseup',
|
||||
'mouseenter',
|
||||
'mouseleave'
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -670,13 +672,34 @@ class Node {
|
||||
this.active(e)
|
||||
})
|
||||
this.group.on('mousedown', e => {
|
||||
e.stopPropagation()
|
||||
if (!this.isRoot) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
// 多选和取消多选
|
||||
if (e.ctrlKey) {
|
||||
let isActive = this.nodeData.data.isActive
|
||||
this.mindMap.renderer.setNodeActive(this, !isActive)
|
||||
this.mindMap.renderer[isActive ? 'removeActiveNode' : 'addActiveNode'](this)
|
||||
this.mindMap.emit(
|
||||
'node_active',
|
||||
isActive ? null : this,
|
||||
this.mindMap.renderer.activeNodeList
|
||||
)
|
||||
}
|
||||
this.mindMap.emit('node_mousedown', this, e)
|
||||
})
|
||||
this.group.on('mouseup', e => {
|
||||
e.stopPropagation()
|
||||
if (!this.isRoot) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
this.mindMap.emit('node_mouseup', this, e)
|
||||
})
|
||||
this.group.on('mouseenter', e => {
|
||||
this.mindMap.emit('node_mouseenter', this, e)
|
||||
})
|
||||
this.group.on('mouseleave', e => {
|
||||
this.mindMap.emit('node_mouseleave', this, e)
|
||||
})
|
||||
// 双击事件
|
||||
this.group.on('dblclick', e => {
|
||||
if (this.mindMap.opt.readonly) {
|
||||
@@ -687,7 +710,8 @@ class Node {
|
||||
})
|
||||
// 右键菜单事件
|
||||
this.group.on('contextmenu', e => {
|
||||
if (this.mindMap.opt.readonly || this.isGeneralization) {
|
||||
// 按住ctrl键点击鼠标左键不知为何触发的是contextmenu事件
|
||||
if (this.mindMap.opt.readonly || this.isGeneralization || e.ctrlKey) {
|
||||
return
|
||||
}
|
||||
e.stopPropagation()
|
||||
@@ -1209,8 +1233,8 @@ class Node {
|
||||
|
||||
// 设置文本
|
||||
|
||||
setText(text) {
|
||||
this.mindMap.execCommand('SET_NODE_TEXT', this, text)
|
||||
setText(text, richText) {
|
||||
this.mindMap.execCommand('SET_NODE_TEXT', this, text, richText)
|
||||
}
|
||||
|
||||
// 设置图片
|
||||
|
||||
@@ -353,26 +353,36 @@ class Render {
|
||||
}
|
||||
}
|
||||
|
||||
// 规范指定节点数据
|
||||
formatAppointNodes(appointNodes) {
|
||||
if (!appointNodes) return []
|
||||
return Array.isArray(appointNodes) ? appointNodes: [appointNodes]
|
||||
}
|
||||
|
||||
// 插入同级节点,多个节点只会操作第一个节点
|
||||
|
||||
insertNode() {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
insertNode(openEdit = true, appointNodes = [], appointData = null) {
|
||||
appointNodes = this.formatAppointNodes(appointNodes)
|
||||
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
|
||||
return
|
||||
}
|
||||
let first = this.activeNodeList[0]
|
||||
let { defaultInsertSecondLevelNodeText, defaultInsertBelowSecondLevelNodeText } = this.mindMap.opt
|
||||
let list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
|
||||
let first = list[0]
|
||||
if (first.isRoot) {
|
||||
this.insertChildNode()
|
||||
this.insertChildNode(openEdit, appointNodes, appointData)
|
||||
} else {
|
||||
let text = first.layerIndex === 1 ? '二级节点' : '分支主题'
|
||||
let text = first.layerIndex === 1 ? defaultInsertSecondLevelNodeText : defaultInsertBelowSecondLevelNodeText
|
||||
if (first.layerIndex === 1) {
|
||||
first.parent.initRender = true
|
||||
}
|
||||
let index = this.getNodeIndex(first)
|
||||
first.parent.nodeData.children.splice(index + 1, 0, {
|
||||
inserting: true,
|
||||
inserting: openEdit,
|
||||
data: {
|
||||
text: text,
|
||||
expand: true
|
||||
expand: true,
|
||||
...(appointData || {})
|
||||
},
|
||||
children: []
|
||||
})
|
||||
@@ -382,20 +392,24 @@ class Render {
|
||||
|
||||
// 插入子节点
|
||||
|
||||
insertChildNode() {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
insertChildNode(openEdit = true, appointNodes = [], appointData = null) {
|
||||
appointNodes = this.formatAppointNodes(appointNodes)
|
||||
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
|
||||
return
|
||||
}
|
||||
this.activeNodeList.forEach(node => {
|
||||
let { defaultInsertSecondLevelNodeText, defaultInsertBelowSecondLevelNodeText } = this.mindMap.opt
|
||||
let list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
|
||||
list.forEach(node => {
|
||||
if (!node.nodeData.children) {
|
||||
node.nodeData.children = []
|
||||
}
|
||||
let text = node.isRoot ? '二级节点' : '分支主题'
|
||||
let text = node.isRoot ? defaultInsertSecondLevelNodeText : defaultInsertBelowSecondLevelNodeText
|
||||
node.nodeData.children.push({
|
||||
inserting: true,
|
||||
inserting: openEdit,
|
||||
data: {
|
||||
text: text,
|
||||
expand: true
|
||||
expand: true,
|
||||
...(appointData || {})
|
||||
},
|
||||
children: []
|
||||
})
|
||||
@@ -548,11 +562,14 @@ class Render {
|
||||
|
||||
// 移除节点
|
||||
|
||||
removeNode() {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
removeNode(appointNodes = []) {
|
||||
appointNodes = this.formatAppointNodes(appointNodes)
|
||||
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
|
||||
return
|
||||
}
|
||||
let root = this.activeNodeList.find((node) => {
|
||||
let isAppointNodes = appointNodes.length > 0
|
||||
let list = isAppointNodes ? appointNodes : this.activeNodeList
|
||||
let root = list.find((node) => {
|
||||
return node.isRoot
|
||||
})
|
||||
if (root) {
|
||||
@@ -563,8 +580,9 @@ class Render {
|
||||
root.children = []
|
||||
root.nodeData.children = []
|
||||
} else {
|
||||
for (let i = 0; i < this.activeNodeList.length; i++) {
|
||||
let node = this.activeNodeList[i]
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
let node = list[i]
|
||||
if (isAppointNodes) list.splice(i, 1)
|
||||
if (node.isGeneralization) {
|
||||
// 删除概要节点
|
||||
this.setNodeData(node.generalizationBelongNode, {
|
||||
@@ -580,8 +598,7 @@ class Render {
|
||||
}
|
||||
}
|
||||
}
|
||||
this.activeNodeList = []
|
||||
this.mindMap.emit('node_active', null, [])
|
||||
this.mindMap.emit('node_active', null, this.activeNodeList)
|
||||
this.mindMap.render()
|
||||
}
|
||||
|
||||
@@ -629,7 +646,7 @@ class Render {
|
||||
if (node.isRoot) {
|
||||
return
|
||||
}
|
||||
let copyData = copyNodeTree({}, node)
|
||||
let copyData = copyNodeTree({}, node, false, true)
|
||||
this.removeActiveNode(node)
|
||||
this.removeOneNode(node)
|
||||
this.mindMap.emit('node_active', null, this.activeNodeList)
|
||||
|
||||
@@ -97,13 +97,16 @@ class RichText {
|
||||
this.mindMap.renderer.textEdit.registerTmpShortcut()
|
||||
if (!this.textEditNode) {
|
||||
this.textEditNode = document.createElement('div')
|
||||
this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);outline: none; word-break: break-all;`
|
||||
this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;box-shadow: 0 0 20px rgba(0,0,0,.5);outline: none; word-break: break-all;`
|
||||
document.body.appendChild(this.textEditNode)
|
||||
}
|
||||
// 原始宽高
|
||||
let g = node._textData.node
|
||||
let originWidth = g.attr('data-width')
|
||||
let originHeight = g.attr('data-height')
|
||||
// 使用节点的填充色,否则如果节点颜色是白色的话编辑时看不见
|
||||
let bgColor = node.style.merge('fillColor')
|
||||
this.textEditNode.style.backgroundColor = bgColor === 'transparent' ? '#fff' : bgColor
|
||||
this.textEditNode.style.minWidth = originWidth + 'px'
|
||||
this.textEditNode.style.minHeight = originHeight + 'px'
|
||||
this.textEditNode.style.left =
|
||||
@@ -126,7 +129,7 @@ class RichText {
|
||||
this.initQuillEditor()
|
||||
document.querySelector('.ql-editor').style.minHeight = originHeight + 'px'
|
||||
this.showTextEdit = true
|
||||
this.selectAll()
|
||||
this.focus()
|
||||
if (!node.nodeData.data.richText) {
|
||||
// 如果是非富文本的情况,需要手动应用文本样式
|
||||
this.setTextStyleIfNotRichText(node)
|
||||
@@ -228,18 +231,29 @@ class RichText {
|
||||
this.quill.setSelection(0, this.quill.getLength())
|
||||
}
|
||||
|
||||
// 聚焦
|
||||
focus() {
|
||||
let len = this.quill.getLength()
|
||||
this.quill.setSelection(len, len)
|
||||
}
|
||||
|
||||
// 格式化当前选中的文本
|
||||
formatText(config = {}) {
|
||||
formatText(config = {}, clear = false) {
|
||||
if (!this.range && !this.lastRange) return
|
||||
this.syncFormatToNodeConfig(config)
|
||||
this.syncFormatToNodeConfig(config, clear)
|
||||
let rangeLost = !this.range
|
||||
let range = rangeLost ? this.lastRange : this.range
|
||||
this.quill.formatText(range.index, range.length, config)
|
||||
clear ? this.quill.removeFormat(range.index, range.length) : this.quill.formatText(range.index, range.length, config)
|
||||
if (rangeLost) {
|
||||
this.quill.setSelection(this.lastRange.index, this.lastRange.length)
|
||||
}
|
||||
}
|
||||
|
||||
// 清除当前选中文本的样式
|
||||
removeFormat() {
|
||||
this.formatText({}, true)
|
||||
}
|
||||
|
||||
// 格式化指定范围的文本
|
||||
formatRangeText(range, config = {}) {
|
||||
if (!range) return
|
||||
@@ -254,10 +268,17 @@ class RichText {
|
||||
}
|
||||
|
||||
// 同步格式化到节点样式配置
|
||||
syncFormatToNodeConfig(config) {
|
||||
syncFormatToNodeConfig(config, clear) {
|
||||
if (!this.node) return
|
||||
let data = this.richTextStyleToNormalStyle(config)
|
||||
this.mindMap.renderer.setNodeData(this.node, data)
|
||||
if (clear) {
|
||||
// 清除文本样式
|
||||
['fontFamily', 'fontSize', 'fontWeight', 'fontStyle', 'textDecoration', 'color'].forEach((prop) => {
|
||||
delete this.node.nodeData.data[prop]
|
||||
})
|
||||
} else {
|
||||
let data = this.richTextStyleToNormalStyle(config)
|
||||
this.mindMap.renderer.setNodeData(this.node, data)
|
||||
}
|
||||
}
|
||||
|
||||
// 将普通节点样式对象转换成富文本样式对象
|
||||
@@ -372,10 +393,13 @@ class RichText {
|
||||
let canvas = await html2canvas(clone, {
|
||||
backgroundColor: null
|
||||
})
|
||||
// 优先使用原始宽高,因为当设备的window.devicePixelRatio不为1时,html2canvas输出的图片会更大
|
||||
let imgNodeWidth = parent.attr('data-width') || canvas.width
|
||||
let imgNodeHeight = parent.attr('data-height') || canvas.height
|
||||
this.mindMap.el.removeChild(div)
|
||||
let imgNode = new SvgImage()
|
||||
.load(canvas.toDataURL())
|
||||
.size(canvas.width, canvas.height)
|
||||
.size(imgNodeWidth, imgNodeHeight)
|
||||
.x((parent ? parent.attr('data-offsetx') : 0) || 0)
|
||||
item.replace(imgNode)
|
||||
if (index <= len - 1) {
|
||||
@@ -392,7 +416,11 @@ class RichText {
|
||||
reject(error)
|
||||
}
|
||||
}
|
||||
if (len > 0) transform()
|
||||
if (len > 0) {
|
||||
transform()
|
||||
} else {
|
||||
resolve(null)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -414,6 +442,9 @@ class RichText {
|
||||
0,
|
||||
0
|
||||
)
|
||||
// 清空历史数据,并且触发数据变化
|
||||
this.mindMap.command.clearHistory()
|
||||
this.mindMap.command.addHistory()
|
||||
this.mindMap.reRender()
|
||||
}
|
||||
|
||||
|
||||
@@ -55,12 +55,41 @@ class View {
|
||||
})
|
||||
// 放大缩小视图
|
||||
this.mindMap.event.on('mousewheel', (e, dir) => {
|
||||
// // 放大
|
||||
if (dir === 'down') {
|
||||
this.enlarge()
|
||||
if (this.mindMap.opt.customHandleMousewheel && typeof this.mindMap.opt.customHandleMousewheel === 'function') {
|
||||
return this.mindMap.opt.customHandleMousewheel(e)
|
||||
}
|
||||
if (this.mindMap.opt.mousewheelAction === 'zoom') {
|
||||
switch (dir) {
|
||||
// 鼠标滚轮,向上和向左,都是缩小
|
||||
case 'up':
|
||||
case 'left':
|
||||
this.narrow()
|
||||
break
|
||||
// 鼠标滚轮,向下和向右,都是放大
|
||||
case 'down':
|
||||
case 'right':
|
||||
this.enlarge()
|
||||
break
|
||||
}
|
||||
} else {
|
||||
// 缩小
|
||||
this.narrow()
|
||||
switch (dir){
|
||||
// 上移
|
||||
case 'down':
|
||||
this.translateY(-this.mindMap.opt.mousewheelMoveStep)
|
||||
break
|
||||
// 下移
|
||||
case 'up':
|
||||
this.translateY(this.mindMap.opt.mousewheelMoveStep)
|
||||
break
|
||||
// 右移
|
||||
case 'left':
|
||||
this.translateX(-this.mindMap.opt.mousewheelMoveStep)
|
||||
break
|
||||
// 左移
|
||||
case 'right':
|
||||
this.translateX(this.mindMap.opt.mousewheelMoveStep)
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,4 +6,5 @@
|
||||
|
||||
.ql-container {
|
||||
height: auto;
|
||||
font-size: inherit;
|
||||
}
|
||||
7
simple-mind-map/src/parse/markdown.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import { transformToMarkdown } from './toMarkdown'
|
||||
import { transformMarkdownTo } from './markdownTo'
|
||||
|
||||
export default {
|
||||
transformToMarkdown,
|
||||
transformMarkdownTo
|
||||
}
|
||||
98
simple-mind-map/src/parse/markdownTo.js
Normal file
@@ -0,0 +1,98 @@
|
||||
import { fromMarkdown } from 'mdast-util-from-markdown'
|
||||
|
||||
// 处理list的情况
|
||||
const handleList = node => {
|
||||
let list = []
|
||||
let walk = (arr, newArr) => {
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
let cur = arr[i]
|
||||
let node = {}
|
||||
node.data = {
|
||||
// 节点内容
|
||||
text: cur.children[0].children[0].value
|
||||
}
|
||||
node.children = []
|
||||
newArr.push(node)
|
||||
if (cur.children.length > 1) {
|
||||
for (let j = 1; j < cur.children.length; j++) {
|
||||
let cur2 = cur.children[j]
|
||||
if (cur2.type === 'list') {
|
||||
walk(cur2.children, node.children)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
walk(node.children, list)
|
||||
return list
|
||||
}
|
||||
|
||||
// 将markdown转换成节点树
|
||||
export const transformMarkdownTo = async md => {
|
||||
const tree = fromMarkdown(md)
|
||||
let root = {
|
||||
children: []
|
||||
}
|
||||
let childrenQueue = [root.children]
|
||||
let currentChildren = root.children
|
||||
let depthQueue = [-1]
|
||||
let currentDepth = -1
|
||||
for (let i = 0; i < tree.children.length; i++) {
|
||||
let cur = tree.children[i]
|
||||
if (cur.type === 'heading') {
|
||||
if (!cur.children[0]) continue
|
||||
// 创建新节点
|
||||
let node = {}
|
||||
node.data = {
|
||||
// 节点内容
|
||||
text: cur.children[0].value
|
||||
}
|
||||
node.children = []
|
||||
// 如果当前的层级大于上一个节点的层级,那么是其子节点
|
||||
if (cur.depth > currentDepth) {
|
||||
// 添加到上一个节点的子节点列表里
|
||||
currentChildren.push(node)
|
||||
// 更新当前栈和数据
|
||||
childrenQueue.push(node.children)
|
||||
currentChildren = node.children
|
||||
depthQueue.push(cur.depth)
|
||||
currentDepth = cur.depth
|
||||
} else if (cur.depth === currentDepth) {
|
||||
// 如果当前层级等于上一个节点的层级,说明它们是同级节点
|
||||
// 将上一个节点出栈
|
||||
childrenQueue.pop()
|
||||
currentChildren = childrenQueue[childrenQueue.length - 1]
|
||||
depthQueue.pop()
|
||||
currentDepth = depthQueue[depthQueue.length - 1]
|
||||
// 追加到上上个节点的子节点列表里
|
||||
currentChildren.push(node)
|
||||
// 更新当前栈和数据
|
||||
childrenQueue.push(node.children)
|
||||
currentChildren = node.children
|
||||
depthQueue.push(cur.depth)
|
||||
currentDepth = cur.depth
|
||||
} else {
|
||||
// 如果当前层级小于上一个节点的层级,那么一直出栈,直到遇到比当前层级小的节点
|
||||
while (depthQueue.length) {
|
||||
childrenQueue.pop()
|
||||
currentChildren = childrenQueue[childrenQueue.length - 1]
|
||||
depthQueue.pop()
|
||||
currentDepth = depthQueue[depthQueue.length - 1]
|
||||
if (currentDepth < cur.depth) {
|
||||
// 追加到该节点的子节点列表里
|
||||
currentChildren.push(node)
|
||||
// 更新当前栈和数据
|
||||
childrenQueue.push(node.children)
|
||||
currentChildren = node.children
|
||||
depthQueue.push(cur.depth)
|
||||
currentDepth = cur.depth
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (cur.type === 'list') {
|
||||
currentChildren.push(...handleList(cur))
|
||||
}
|
||||
}
|
||||
return root.children[0]
|
||||
}
|
||||
53
simple-mind-map/src/parse/toMarkdown.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import { walk } from '../utils'
|
||||
|
||||
let el = null
|
||||
const getText = str => {
|
||||
if (!el) {
|
||||
el = document.createElement('div')
|
||||
}
|
||||
el.innerHTML = str
|
||||
return el.textContent
|
||||
}
|
||||
|
||||
const getTitleMark = level => {
|
||||
return new Array(level).fill('#').join('')
|
||||
}
|
||||
|
||||
const getIndentMark = level => {
|
||||
return new Array(level - 6).fill(' ').join('') + '*'
|
||||
}
|
||||
|
||||
// 转换成markdown格式
|
||||
export const transformToMarkdown = root => {
|
||||
let content = ''
|
||||
walk(
|
||||
root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
let level = layerIndex + 1
|
||||
let text = node.data.richText ? getText(node.data.text) : node.data.text
|
||||
if (level <= 6) {
|
||||
content += getTitleMark(level)
|
||||
} else {
|
||||
content += getIndentMark(level)
|
||||
}
|
||||
content += ' ' + text
|
||||
// 概要
|
||||
let generalization = node.data.generalization
|
||||
if (generalization && generalization.text) {
|
||||
let generalizationText = generalization.richText
|
||||
? getText(generalization.text)
|
||||
: generalization.text
|
||||
content += `[${generalizationText}]`
|
||||
}
|
||||
content += '\n\n'
|
||||
// 备注
|
||||
if (node.data.note) {
|
||||
content += node.data.note + '\n\n'
|
||||
}
|
||||
},
|
||||
() => {},
|
||||
true
|
||||
)
|
||||
return content
|
||||
}
|
||||
@@ -26,6 +26,14 @@ export default {
|
||||
generalizationLineMargin: 0,
|
||||
// 概要节点距节点的距离
|
||||
generalizationNodeMargin: 20,
|
||||
// 关联线默认状态的粗细
|
||||
associativeLineWidth: 2,
|
||||
// 关联线默认状态的颜色
|
||||
associativeLineColor: 'rgb(51, 51, 51)',
|
||||
// 关联线激活状态的粗细
|
||||
associativeLineActiveWidth: 8,
|
||||
// 关联线激活状态的颜色
|
||||
associativeLineActiveColor: 'rgba(2, 167, 240, 1)',
|
||||
// 背景颜色
|
||||
backgroundColor: '#fafafa',
|
||||
// 背景图片
|
||||
|
||||
182
simple-mind-map/src/utils/associativeLineUtils.js
Normal file
@@ -0,0 +1,182 @@
|
||||
// 获取目标节点在起始节点的目标数组中的索引
|
||||
export const getAssociativeLineTargetIndex = (node, toNode) => {
|
||||
return node.nodeData.data.associativeLineTargets.findIndex(item => {
|
||||
return item === toNode.nodeData.data.id
|
||||
})
|
||||
}
|
||||
|
||||
// 计算贝塞尔曲线的控制点
|
||||
export const computeCubicBezierPathPoints = (x1, y1, x2, y2) => {
|
||||
let cx1 = x1 + (x2 - x1) / 2
|
||||
let cy1 = y1
|
||||
let cx2 = cx1
|
||||
let cy2 = y2
|
||||
if (Math.abs(x1 - x2) <= 5) {
|
||||
cx1 = x1 + (y2 - y1) / 2
|
||||
cx2 = cx1
|
||||
}
|
||||
return [
|
||||
{
|
||||
x: cx1,
|
||||
y: cy1
|
||||
},
|
||||
{
|
||||
x: cx2,
|
||||
y: cy2
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 拼接贝塞尔曲线路径
|
||||
export const joinCubicBezierPath = (startPoint, endPoint, point1, point2) => {
|
||||
return `M ${startPoint.x},${startPoint.y} C ${point1.x},${point1.y} ${point2.x},${point2.y} ${endPoint.x},${endPoint.y}`
|
||||
}
|
||||
|
||||
// 获取节点的位置信息
|
||||
const getNodeRect = node => {
|
||||
let { left, top, width, height } = node
|
||||
return {
|
||||
right: left + width,
|
||||
bottom: top + height,
|
||||
left,
|
||||
top
|
||||
}
|
||||
}
|
||||
|
||||
// 三次贝塞尔曲线
|
||||
export const cubicBezierPath = (x1, y1, x2, y2) => {
|
||||
let points = computeCubicBezierPathPoints(x1, y1, x2, y2)
|
||||
return joinCubicBezierPath(
|
||||
{ x: x1, y: y1 },
|
||||
{ x: x2, y: y2 },
|
||||
points[0],
|
||||
points[1]
|
||||
)
|
||||
}
|
||||
|
||||
// 获取节点的连接点
|
||||
export const getNodePoint = (node, dir = 'right') => {
|
||||
let { left, top, width, height } = node
|
||||
switch (dir) {
|
||||
case 'left':
|
||||
return {
|
||||
x: left,
|
||||
y: top + height / 2
|
||||
}
|
||||
case 'right':
|
||||
return {
|
||||
x: left + width,
|
||||
y: top + height / 2
|
||||
}
|
||||
case 'top':
|
||||
return {
|
||||
x: left + width / 2,
|
||||
y: top
|
||||
}
|
||||
case 'bottom':
|
||||
return {
|
||||
x: left + width / 2,
|
||||
y: top + height
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 根据两个节点的位置计算节点的连接点
|
||||
export const computeNodePoints = (fromNode, toNode) => {
|
||||
let fromRect = getNodeRect(fromNode)
|
||||
let fromCx = (fromRect.right + fromRect.left) / 2
|
||||
let fromCy = (fromRect.bottom + fromRect.top) / 2
|
||||
let toRect = getNodeRect(toNode)
|
||||
let toCx = (toRect.right + toRect.left) / 2
|
||||
let toCy = (toRect.bottom + toRect.top) / 2
|
||||
// 中心点坐标的差值
|
||||
let offsetX = toCx - fromCx
|
||||
let offsetY = toCy - fromCy
|
||||
if (offsetX === 0 && offsetY === 0) return
|
||||
let fromDir = ''
|
||||
let toDir = ''
|
||||
if (offsetX <= 0 && offsetX <= offsetY && offsetX <= -offsetY) {
|
||||
// left
|
||||
fromDir = 'left'
|
||||
toDir = 'right'
|
||||
} else if (offsetX > 0 && offsetX >= -offsetY && offsetX >= offsetY) {
|
||||
// right
|
||||
fromDir = 'right'
|
||||
toDir = 'left'
|
||||
} else if (offsetY <= 0 && offsetY < offsetX && offsetY < -offsetX) {
|
||||
// up
|
||||
fromDir = 'top'
|
||||
toDir = 'bottom'
|
||||
} else if (offsetY > 0 && -offsetY < offsetX && offsetY > offsetX) {
|
||||
// down
|
||||
fromDir = 'bottom'
|
||||
toDir = 'top'
|
||||
}
|
||||
return [getNodePoint(fromNode, fromDir), getNodePoint(toNode, toDir)]
|
||||
}
|
||||
|
||||
// 获取节点的关联线路径
|
||||
export const getNodeLinePath = (startPoint, endPoint, node, toNode) => {
|
||||
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
|
||||
// 控制点
|
||||
let controlPoints = []
|
||||
let associativeLineTargetControlOffsets =
|
||||
node.nodeData.data.associativeLineTargetControlOffsets
|
||||
if (
|
||||
associativeLineTargetControlOffsets &&
|
||||
associativeLineTargetControlOffsets[targetIndex]
|
||||
) {
|
||||
// 节点保存了控制点差值
|
||||
let offsets = associativeLineTargetControlOffsets[targetIndex]
|
||||
controlPoints = [
|
||||
{
|
||||
x: startPoint.x + offsets[0].x,
|
||||
y: startPoint.y + offsets[0].y
|
||||
},
|
||||
{
|
||||
x: endPoint.x + offsets[1].x,
|
||||
y: endPoint.y + offsets[1].y
|
||||
}
|
||||
]
|
||||
} else {
|
||||
// 没有保存控制点则生成默认的
|
||||
controlPoints = computeCubicBezierPathPoints(
|
||||
startPoint.x,
|
||||
startPoint.y,
|
||||
endPoint.x,
|
||||
endPoint.y
|
||||
)
|
||||
}
|
||||
// 根据控制点拼接贝塞尔曲线路径
|
||||
return {
|
||||
path: joinCubicBezierPath(
|
||||
startPoint,
|
||||
endPoint,
|
||||
controlPoints[0],
|
||||
controlPoints[1]
|
||||
),
|
||||
controlPoints
|
||||
}
|
||||
}
|
||||
|
||||
// 获取默认的控制点差值
|
||||
export const getDefaultControlPointOffsets = (startPoint, endPoint) => {
|
||||
let controlPoints = computeCubicBezierPathPoints(
|
||||
startPoint.x,
|
||||
startPoint.y,
|
||||
endPoint.x,
|
||||
endPoint.y
|
||||
)
|
||||
return [
|
||||
{
|
||||
x: controlPoints[0].x - startPoint.x,
|
||||
y: controlPoints[0].y - startPoint.y
|
||||
},
|
||||
{
|
||||
x: controlPoints[1].x - endPoint.x,
|
||||
y: controlPoints[1].y - endPoint.y
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -122,20 +122,25 @@ export const simpleDeepClone = data => {
|
||||
}
|
||||
|
||||
// 复制渲染树数据
|
||||
export const copyRenderTree = (tree, root) => {
|
||||
export const copyRenderTree = (tree, root, removeActiveState = false) => {
|
||||
tree.data = simpleDeepClone(root.data)
|
||||
if (removeActiveState) {
|
||||
tree.data.isActive = false
|
||||
}
|
||||
tree.children = []
|
||||
if (root.children && root.children.length > 0) {
|
||||
root.children.forEach((item, index) => {
|
||||
tree.children[index] = copyRenderTree({}, item)
|
||||
tree.children[index] = copyRenderTree({}, item, removeActiveState)
|
||||
})
|
||||
}
|
||||
return tree
|
||||
}
|
||||
|
||||
// 复制节点树数据
|
||||
export const copyNodeTree = (tree, root, removeActiveState = false) => {
|
||||
export const copyNodeTree = (tree, root, removeActiveState = false, keepId = false) => {
|
||||
tree.data = simpleDeepClone(root.nodeData ? root.nodeData.data : root.data)
|
||||
// 去除节点id,因为节点id不能重复
|
||||
if (tree.data.id && !keepId) delete tree.data.id
|
||||
if (removeActiveState) {
|
||||
tree.data.isActive = false
|
||||
}
|
||||
@@ -193,12 +198,12 @@ export const downloadFile = (file, fileName) => {
|
||||
// 节流函数
|
||||
export const throttle = (fn, time = 300, ctx) => {
|
||||
let timer = null
|
||||
return () => {
|
||||
return (...args) => {
|
||||
if (timer) {
|
||||
return
|
||||
}
|
||||
timer = setTimeout(() => {
|
||||
fn.call(ctx)
|
||||
fn.call(ctx, ...args)
|
||||
timer = null
|
||||
}, time)
|
||||
}
|
||||
@@ -265,4 +270,35 @@ export const measureText = (text, { italic, bold, fontSize, fontFamily }) => {
|
||||
// 拼接font字符串
|
||||
export const joinFontStr = ({ italic, bold, fontSize, fontFamily }) => {
|
||||
return `${italic ? 'italic ' : ''} ${bold ? 'bold ' : ''} ${fontSize}px ${fontFamily} `
|
||||
}
|
||||
|
||||
// 在下一个事件循环里执行任务
|
||||
export const nextTick = function (fn, ctx) {
|
||||
let pending = false
|
||||
let timerFunc = null
|
||||
let handle = () => {
|
||||
pending = false
|
||||
ctx ? fn.call(ctx) : fn()
|
||||
}
|
||||
// 支持MutationObserver接口的话使用MutationObserver
|
||||
if (typeof MutationObserver !== 'undefined') {
|
||||
let counter = 1
|
||||
let observer = new MutationObserver(handle)
|
||||
let textNode = document.createTextNode(counter)
|
||||
observer.observe(textNode, {
|
||||
characterData: true // 设为 true 表示监视指定目标节点或子节点树中节点所包含的字符数据的变化
|
||||
})
|
||||
timerFunc = function () {
|
||||
counter = (counter + 1) % 2 // counter会在0和1两者循环变化
|
||||
textNode.data = counter // 节点变化会触发回调handle,
|
||||
}
|
||||
} else {
|
||||
// 否则使用定时器
|
||||
timerFunc = setTimeout
|
||||
}
|
||||
return function () {
|
||||
if (pending) return
|
||||
pending = true
|
||||
timerFunc(handle, 0)
|
||||
}
|
||||
}
|
||||
@@ -1,539 +0,0 @@
|
||||
/* Logo 字体 */
|
||||
@font-face {
|
||||
font-family: "iconfont logo";
|
||||
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
|
||||
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
|
||||
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
|
||||
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
|
||||
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-family: "iconfont logo";
|
||||
font-size: 160px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* tabs */
|
||||
.nav-tabs {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-more {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 42px;
|
||||
line-height: 42px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
#tabs {
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
#tabs li {
|
||||
cursor: pointer;
|
||||
width: 100px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
border-bottom: 2px solid transparent;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin-bottom: -1px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
|
||||
#tabs .active {
|
||||
border-bottom-color: #f00;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.tab-container .content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 页面布局 */
|
||||
.main {
|
||||
padding: 30px 100px;
|
||||
width: 960px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.main .logo {
|
||||
color: #333;
|
||||
text-align: left;
|
||||
margin-bottom: 30px;
|
||||
line-height: 1;
|
||||
height: 110px;
|
||||
margin-top: -50px;
|
||||
overflow: hidden;
|
||||
*zoom: 1;
|
||||
}
|
||||
|
||||
.main .logo a {
|
||||
font-size: 160px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.helps {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.helps pre {
|
||||
padding: 20px;
|
||||
margin: 10px 0;
|
||||
border: solid 1px #e7e1cd;
|
||||
background-color: #fffdef;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.icon_lists {
|
||||
width: 100% !important;
|
||||
overflow: hidden;
|
||||
*zoom: 1;
|
||||
}
|
||||
|
||||
.icon_lists li {
|
||||
width: 100px;
|
||||
margin-bottom: 10px;
|
||||
margin-right: 20px;
|
||||
text-align: center;
|
||||
list-style: none !important;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.icon_lists li .code-name {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.icon_lists .icon {
|
||||
display: block;
|
||||
height: 100px;
|
||||
line-height: 100px;
|
||||
font-size: 42px;
|
||||
margin: 10px auto;
|
||||
color: #333;
|
||||
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
|
||||
-moz-transition: font-size 0.25s linear, width 0.25s linear;
|
||||
transition: font-size 0.25s linear, width 0.25s linear;
|
||||
}
|
||||
|
||||
.icon_lists .icon:hover {
|
||||
font-size: 100px;
|
||||
}
|
||||
|
||||
.icon_lists .svg-icon {
|
||||
/* 通过设置 font-size 来改变图标大小 */
|
||||
width: 1em;
|
||||
/* 图标和文字相邻时,垂直对齐 */
|
||||
vertical-align: -0.15em;
|
||||
/* 通过设置 color 来改变 SVG 的颜色/fill */
|
||||
fill: currentColor;
|
||||
/* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
|
||||
normalize.css 中也包含这行 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.icon_lists li .name,
|
||||
.icon_lists li .code-name {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* markdown 样式 */
|
||||
.markdown {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.markdown img {
|
||||
vertical-align: middle;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.markdown h1 {
|
||||
color: #404040;
|
||||
font-weight: 500;
|
||||
line-height: 40px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.markdown h2,
|
||||
.markdown h3,
|
||||
.markdown h4,
|
||||
.markdown h5,
|
||||
.markdown h6 {
|
||||
color: #404040;
|
||||
margin: 1.6em 0 0.6em 0;
|
||||
font-weight: 500;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.markdown h1 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.markdown h2 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.markdown h3 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.markdown h4 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.markdown h5 {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.markdown h6 {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.markdown hr {
|
||||
height: 1px;
|
||||
border: 0;
|
||||
background: #e9e9e9;
|
||||
margin: 16px 0;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.markdown p {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.markdown>p,
|
||||
.markdown>blockquote,
|
||||
.markdown>.highlight,
|
||||
.markdown>ol,
|
||||
.markdown>ul {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.markdown ul>li {
|
||||
list-style: circle;
|
||||
}
|
||||
|
||||
.markdown>ul li,
|
||||
.markdown blockquote ul>li {
|
||||
margin-left: 20px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.markdown>ul li p,
|
||||
.markdown>ol li p {
|
||||
margin: 0.6em 0;
|
||||
}
|
||||
|
||||
.markdown ol>li {
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
.markdown>ol li,
|
||||
.markdown blockquote ol>li {
|
||||
margin-left: 20px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.markdown code {
|
||||
margin: 0 3px;
|
||||
padding: 0 5px;
|
||||
background: #eee;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.markdown strong,
|
||||
.markdown b {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown>table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0px;
|
||||
empty-cells: show;
|
||||
border: 1px solid #e9e9e9;
|
||||
width: 95%;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.markdown>table th {
|
||||
white-space: nowrap;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown>table th,
|
||||
.markdown>table td {
|
||||
border: 1px solid #e9e9e9;
|
||||
padding: 8px 16px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.markdown>table th {
|
||||
background: #F7F7F7;
|
||||
}
|
||||
|
||||
.markdown blockquote {
|
||||
font-size: 90%;
|
||||
color: #999;
|
||||
border-left: 4px solid #e9e9e9;
|
||||
padding-left: 0.8em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.markdown blockquote p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.markdown .anchor {
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.markdown .waiting {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.markdown h1:hover .anchor,
|
||||
.markdown h2:hover .anchor,
|
||||
.markdown h3:hover .anchor,
|
||||
.markdown h4:hover .anchor,
|
||||
.markdown h5:hover .anchor,
|
||||
.markdown h6:hover .anchor {
|
||||
opacity: 1;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.markdown>br,
|
||||
.markdown>p>br {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
background: white;
|
||||
padding: 0.5em;
|
||||
color: #333333;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-meta {
|
||||
color: #969896;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-strong,
|
||||
.hljs-emphasis,
|
||||
.hljs-quote {
|
||||
color: #df5000;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag,
|
||||
.hljs-type {
|
||||
color: #a71d5d;
|
||||
}
|
||||
|
||||
.hljs-literal,
|
||||
.hljs-symbol,
|
||||
.hljs-bullet,
|
||||
.hljs-attribute {
|
||||
color: #0086b3;
|
||||
}
|
||||
|
||||
.hljs-section,
|
||||
.hljs-name {
|
||||
color: #63a35c;
|
||||
}
|
||||
|
||||
.hljs-tag {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-attr,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo {
|
||||
color: #795da3;
|
||||
}
|
||||
|
||||
.hljs-addition {
|
||||
color: #55a532;
|
||||
background-color: #eaffea;
|
||||
}
|
||||
|
||||
.hljs-deletion {
|
||||
color: #bd2c00;
|
||||
background-color: #ffecec;
|
||||
}
|
||||
|
||||
.hljs-link {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 代码高亮 */
|
||||
/* PrismJS 1.15.0
|
||||
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
|
||||
/**
|
||||
* prism.js default theme for JavaScript, CSS and HTML
|
||||
* Based on dabblet (http://dabblet.com)
|
||||
* @author Lea Verou
|
||||
*/
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: black;
|
||||
background: none;
|
||||
text-shadow: 0 1px white;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: 1.5;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::-moz-selection,
|
||||
pre[class*="language-"] ::-moz-selection,
|
||||
code[class*="language-"]::-moz-selection,
|
||||
code[class*="language-"] ::-moz-selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::selection,
|
||||
pre[class*="language-"] ::selection,
|
||||
code[class*="language-"]::selection,
|
||||
code[class*="language-"] ::selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
|
||||
@media print {
|
||||
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: .5em 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
:not(pre)>code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background: #f5f2f0;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre)>code[class*="language-"] {
|
||||
padding: .1em;
|
||||
border-radius: .3em;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: slategray;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.namespace {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.constant,
|
||||
.token.symbol,
|
||||
.token.deleted {
|
||||
color: #905;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: #690;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.language-css .token.string,
|
||||
.style .token.string {
|
||||
color: #9a6e3a;
|
||||
background: hsla(0, 0%, 100%, .5);
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.keyword {
|
||||
color: #07a;
|
||||
}
|
||||
|
||||
.token.function,
|
||||
.token.class-name {
|
||||
color: #DD4A68;
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important,
|
||||
.token.variable {
|
||||
color: #e90;
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 2479351 */
|
||||
src: url('iconfont.woff2?t=1677058133223') format('woff2'),
|
||||
url('iconfont.woff?t=1677058133223') format('woff'),
|
||||
url('iconfont.ttf?t=1677058133223') format('truetype');
|
||||
src: url('iconfont.woff2?t=1679621707211') format('woff2'),
|
||||
url('iconfont.woff?t=1679621707211') format('woff'),
|
||||
url('iconfont.ttf?t=1679621707211') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -13,6 +13,86 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.iconwenjian:before {
|
||||
content: "\e607";
|
||||
}
|
||||
|
||||
.iconpdf:before {
|
||||
content: "\e740";
|
||||
}
|
||||
|
||||
.iconPNG:before {
|
||||
content: "\ec18";
|
||||
}
|
||||
|
||||
.iconSVG:before {
|
||||
content: "\e621";
|
||||
}
|
||||
|
||||
.iconmarkdown:before {
|
||||
content: "\ec04";
|
||||
}
|
||||
|
||||
.iconjson:before {
|
||||
content: "\ea42";
|
||||
}
|
||||
|
||||
.iconlianjiexian:before {
|
||||
content: "\e75b";
|
||||
}
|
||||
|
||||
.iconbangzhu:before {
|
||||
content: "\e620";
|
||||
}
|
||||
|
||||
.iconshezhi:before {
|
||||
content: "\e8b7";
|
||||
}
|
||||
|
||||
.iconwushuju:before {
|
||||
content: "\e643";
|
||||
}
|
||||
|
||||
.iconzuijinliulan:before {
|
||||
content: "\e62f";
|
||||
}
|
||||
|
||||
.icon3zuidahua-3:before {
|
||||
content: "\e692";
|
||||
}
|
||||
|
||||
.iconzuixiaohua:before {
|
||||
content: "\e650";
|
||||
}
|
||||
|
||||
.iconzuidahua:before {
|
||||
content: "\e651";
|
||||
}
|
||||
|
||||
.iconguanbi:before {
|
||||
content: "\e652";
|
||||
}
|
||||
|
||||
.icondiannao:before {
|
||||
content: "\eac0";
|
||||
}
|
||||
|
||||
.iconzhuye:before {
|
||||
content: "\e65c";
|
||||
}
|
||||
|
||||
.iconbendi1x:before {
|
||||
content: "\e606";
|
||||
}
|
||||
|
||||
.iconbeijingyanse:before {
|
||||
content: "\e6f8";
|
||||
}
|
||||
|
||||
.iconqingchu:before {
|
||||
content: "\e605";
|
||||
}
|
||||
|
||||
.iconcase:before {
|
||||
content: "\e6c6";
|
||||
}
|
||||
|
||||
@@ -1,380 +0,0 @@
|
||||
{
|
||||
"id": "2479351",
|
||||
"name": "思绪",
|
||||
"font_family": "iconfont",
|
||||
"css_prefix_text": "icon",
|
||||
"description": "思维导图",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "586787",
|
||||
"name": "case",
|
||||
"font_class": "case",
|
||||
"unicode": "e6c6",
|
||||
"unicode_decimal": 59078
|
||||
},
|
||||
{
|
||||
"icon_id": "4354254",
|
||||
"name": "形状-文字",
|
||||
"font_class": "xingzhuang-wenzi",
|
||||
"unicode": "eb99",
|
||||
"unicode_decimal": 60313
|
||||
},
|
||||
{
|
||||
"icon_id": "6337466",
|
||||
"name": "字体加粗",
|
||||
"font_class": "zitijiacu",
|
||||
"unicode": "ec83",
|
||||
"unicode_decimal": 60547
|
||||
},
|
||||
{
|
||||
"icon_id": "6337470",
|
||||
"name": "字体下划线",
|
||||
"font_class": "zitixiahuaxian",
|
||||
"unicode": "ec85",
|
||||
"unicode_decimal": 60549
|
||||
},
|
||||
{
|
||||
"icon_id": "6337471",
|
||||
"name": "字体斜体",
|
||||
"font_class": "zitixieti",
|
||||
"unicode": "ec86",
|
||||
"unicode_decimal": 60550
|
||||
},
|
||||
{
|
||||
"icon_id": "11975179",
|
||||
"name": "删除线",
|
||||
"font_class": "shanchuxian",
|
||||
"unicode": "e612",
|
||||
"unicode_decimal": 58898
|
||||
},
|
||||
{
|
||||
"icon_id": "34198316",
|
||||
"name": "字体颜色",
|
||||
"font_class": "zitiyanse",
|
||||
"unicode": "e854",
|
||||
"unicode_decimal": 59476
|
||||
},
|
||||
{
|
||||
"icon_id": "8760187",
|
||||
"name": "github",
|
||||
"font_class": "github",
|
||||
"unicode": "e64f",
|
||||
"unicode_decimal": 58959
|
||||
},
|
||||
{
|
||||
"icon_id": "1009019",
|
||||
"name": "选择",
|
||||
"font_class": "choose1",
|
||||
"unicode": "e6c5",
|
||||
"unicode_decimal": 59077
|
||||
},
|
||||
{
|
||||
"icon_id": "493507",
|
||||
"name": "主题",
|
||||
"font_class": "zhuti",
|
||||
"unicode": "e7aa",
|
||||
"unicode_decimal": 59306
|
||||
},
|
||||
{
|
||||
"icon_id": "1305460",
|
||||
"name": "导出",
|
||||
"font_class": "daochu1",
|
||||
"unicode": "e63e",
|
||||
"unicode_decimal": 58942
|
||||
},
|
||||
{
|
||||
"icon_id": "4784101",
|
||||
"name": "另存为",
|
||||
"font_class": "lingcunwei",
|
||||
"unicode": "e657",
|
||||
"unicode_decimal": 58967
|
||||
},
|
||||
{
|
||||
"icon_id": "9929033",
|
||||
"name": "export",
|
||||
"font_class": "export",
|
||||
"unicode": "e642",
|
||||
"unicode_decimal": 58946
|
||||
},
|
||||
{
|
||||
"icon_id": "4570294",
|
||||
"name": "打开",
|
||||
"font_class": "dakai",
|
||||
"unicode": "ebdf",
|
||||
"unicode_decimal": 60383
|
||||
},
|
||||
{
|
||||
"icon_id": "5086088",
|
||||
"name": "新建",
|
||||
"font_class": "xinjian",
|
||||
"unicode": "e64e",
|
||||
"unicode_decimal": 58958
|
||||
},
|
||||
{
|
||||
"icon_id": "1117",
|
||||
"name": "剪切",
|
||||
"font_class": "jianqie",
|
||||
"unicode": "e601",
|
||||
"unicode_decimal": 58881
|
||||
},
|
||||
{
|
||||
"icon_id": "1415523",
|
||||
"name": "整理",
|
||||
"font_class": "zhengli",
|
||||
"unicode": "e83b",
|
||||
"unicode_decimal": 59451
|
||||
},
|
||||
{
|
||||
"icon_id": "2815710",
|
||||
"name": "复制",
|
||||
"font_class": "fuzhi",
|
||||
"unicode": "e604",
|
||||
"unicode_decimal": 58884
|
||||
},
|
||||
{
|
||||
"icon_id": "11121506",
|
||||
"name": "粘贴",
|
||||
"font_class": "niantie",
|
||||
"unicode": "e63f",
|
||||
"unicode_decimal": 58943
|
||||
},
|
||||
{
|
||||
"icon_id": "11383392",
|
||||
"name": "上移",
|
||||
"font_class": "shangyi",
|
||||
"unicode": "e6be",
|
||||
"unicode_decimal": 59070
|
||||
},
|
||||
{
|
||||
"icon_id": "11383396",
|
||||
"name": "下移",
|
||||
"font_class": "xiayi",
|
||||
"unicode": "e6bf",
|
||||
"unicode_decimal": 59071
|
||||
},
|
||||
{
|
||||
"icon_id": "14843439",
|
||||
"name": "概括总览",
|
||||
"font_class": "gaikuozonglan",
|
||||
"unicode": "e609",
|
||||
"unicode_decimal": 58889
|
||||
},
|
||||
{
|
||||
"icon_id": "19738998",
|
||||
"name": "全选",
|
||||
"font_class": "quanxuan",
|
||||
"unicode": "f199",
|
||||
"unicode_decimal": 61849
|
||||
},
|
||||
{
|
||||
"icon_id": "17606306",
|
||||
"name": "导入",
|
||||
"font_class": "daoru",
|
||||
"unicode": "e6a3",
|
||||
"unicode_decimal": 59043
|
||||
},
|
||||
{
|
||||
"icon_id": "5110748",
|
||||
"name": "后退-实",
|
||||
"font_class": "houtui-shi",
|
||||
"unicode": "e656",
|
||||
"unicode_decimal": 58966
|
||||
},
|
||||
{
|
||||
"icon_id": "14420971",
|
||||
"name": "前进",
|
||||
"font_class": "qianjin1",
|
||||
"unicode": "e654",
|
||||
"unicode_decimal": 58964
|
||||
},
|
||||
{
|
||||
"icon_id": "1368553",
|
||||
"name": "撤回",
|
||||
"font_class": "withdraw",
|
||||
"unicode": "e603",
|
||||
"unicode_decimal": 58883
|
||||
},
|
||||
{
|
||||
"icon_id": "15006636",
|
||||
"name": "前进",
|
||||
"font_class": "qianjin",
|
||||
"unicode": "e600",
|
||||
"unicode_decimal": 58880
|
||||
},
|
||||
{
|
||||
"icon_id": "19980541",
|
||||
"name": "恢复默认",
|
||||
"font_class": "huifumoren",
|
||||
"unicode": "e60e",
|
||||
"unicode_decimal": 58894
|
||||
},
|
||||
{
|
||||
"icon_id": "1616783",
|
||||
"name": "换行",
|
||||
"font_class": "huanhang",
|
||||
"unicode": "e61e",
|
||||
"unicode_decimal": 58910
|
||||
},
|
||||
{
|
||||
"icon_id": "4777227",
|
||||
"name": "缩小",
|
||||
"font_class": "suoxiao",
|
||||
"unicode": "ec13",
|
||||
"unicode_decimal": 60435
|
||||
},
|
||||
{
|
||||
"icon_id": "18811980",
|
||||
"name": "编辑",
|
||||
"font_class": "bianji",
|
||||
"unicode": "e626",
|
||||
"unicode_decimal": 58918
|
||||
},
|
||||
{
|
||||
"icon_id": "21188137",
|
||||
"name": "放大",
|
||||
"font_class": "fangda",
|
||||
"unicode": "e663",
|
||||
"unicode_decimal": 58979
|
||||
},
|
||||
{
|
||||
"icon_id": "21189639",
|
||||
"name": "全屏",
|
||||
"font_class": "quanping1",
|
||||
"unicode": "e664",
|
||||
"unicode_decimal": 58980
|
||||
},
|
||||
{
|
||||
"icon_id": "397753",
|
||||
"name": "定位",
|
||||
"font_class": "dingwei",
|
||||
"unicode": "e616",
|
||||
"unicode_decimal": 58902
|
||||
},
|
||||
{
|
||||
"icon_id": "2605158",
|
||||
"name": "导航",
|
||||
"font_class": "daohang",
|
||||
"unicode": "e611",
|
||||
"unicode_decimal": 58897
|
||||
},
|
||||
{
|
||||
"icon_id": "6528451",
|
||||
"name": "键盘",
|
||||
"font_class": "jianpan",
|
||||
"unicode": "e64d",
|
||||
"unicode_decimal": 58957
|
||||
},
|
||||
{
|
||||
"icon_id": "7556170",
|
||||
"name": "全屏",
|
||||
"font_class": "quanping",
|
||||
"unicode": "e602",
|
||||
"unicode_decimal": 58882
|
||||
},
|
||||
{
|
||||
"icon_id": "788015",
|
||||
"name": "导出",
|
||||
"font_class": "daochu",
|
||||
"unicode": "e63d",
|
||||
"unicode_decimal": 58941
|
||||
},
|
||||
{
|
||||
"icon_id": "2678575",
|
||||
"name": "标签",
|
||||
"font_class": "biaoqian",
|
||||
"unicode": "e63c",
|
||||
"unicode_decimal": 58940
|
||||
},
|
||||
{
|
||||
"icon_id": "6265396",
|
||||
"name": "流程-备注",
|
||||
"font_class": "flow-Mark",
|
||||
"unicode": "e65b",
|
||||
"unicode_decimal": 58971
|
||||
},
|
||||
{
|
||||
"icon_id": "1790486",
|
||||
"name": "超链接",
|
||||
"font_class": "chaolianjie",
|
||||
"unicode": "e6f4",
|
||||
"unicode_decimal": 59124
|
||||
},
|
||||
{
|
||||
"icon_id": "4608986",
|
||||
"name": "主题",
|
||||
"font_class": "jingzi",
|
||||
"unicode": "e610",
|
||||
"unicode_decimal": 58896
|
||||
},
|
||||
{
|
||||
"icon_id": "11903017",
|
||||
"name": "笑脸",
|
||||
"font_class": "xiaolian",
|
||||
"unicode": "e60f",
|
||||
"unicode_decimal": 58895
|
||||
},
|
||||
{
|
||||
"icon_id": "19657962",
|
||||
"name": "图 片",
|
||||
"font_class": "image",
|
||||
"unicode": "e629",
|
||||
"unicode_decimal": 58921
|
||||
},
|
||||
{
|
||||
"icon_id": "20784489",
|
||||
"name": "结构",
|
||||
"font_class": "jiegou",
|
||||
"unicode": "e61d",
|
||||
"unicode_decimal": 58909
|
||||
},
|
||||
{
|
||||
"icon_id": "15969341",
|
||||
"name": "样式",
|
||||
"font_class": "yangshi",
|
||||
"unicode": "e631",
|
||||
"unicode_decimal": 58929
|
||||
},
|
||||
{
|
||||
"icon_id": "2967176",
|
||||
"name": "符号-大纲树",
|
||||
"font_class": "fuhao-dagangshu",
|
||||
"unicode": "e71f",
|
||||
"unicode_decimal": 59167
|
||||
},
|
||||
{
|
||||
"icon_id": "12316668",
|
||||
"name": "添加子节点",
|
||||
"font_class": "tianjiazijiedian",
|
||||
"unicode": "e622",
|
||||
"unicode_decimal": 58914
|
||||
},
|
||||
{
|
||||
"icon_id": "14435368",
|
||||
"name": "节点",
|
||||
"font_class": "jiedian",
|
||||
"unicode": "e655",
|
||||
"unicode_decimal": 58965
|
||||
},
|
||||
{
|
||||
"icon_id": "15765352",
|
||||
"name": "删 除",
|
||||
"font_class": "shanchu",
|
||||
"unicode": "e696",
|
||||
"unicode_decimal": 59030
|
||||
},
|
||||
{
|
||||
"icon_id": "9592600",
|
||||
"name": "HTSCIT_展开",
|
||||
"font_class": "zhankai",
|
||||
"unicode": "e64c",
|
||||
"unicode_decimal": 58956
|
||||
},
|
||||
{
|
||||
"icon_id": "9900009",
|
||||
"name": "HTSCIT_展开2",
|
||||
"font_class": "zhankai1",
|
||||
"unicode": "e673",
|
||||
"unicode_decimal": 58995
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -369,3 +369,43 @@ export const sidebarTriggerList = [
|
||||
icon: 'iconjianpan'
|
||||
}
|
||||
]
|
||||
|
||||
// 下载类型列表
|
||||
export const downTypeList = [
|
||||
{
|
||||
name: 'Dedicated file',
|
||||
type: 'smm',
|
||||
icon: 'iconwenjian',
|
||||
desc: 'Available for import'
|
||||
},
|
||||
{
|
||||
name: 'JSON',
|
||||
type: 'json',
|
||||
icon: 'iconjson',
|
||||
desc: 'Popular data exchange formats, Available for import'
|
||||
},
|
||||
{
|
||||
name: 'Image',
|
||||
type: 'png',
|
||||
icon: 'iconPNG',
|
||||
desc: 'Suitable for viewing and sharing'
|
||||
},
|
||||
{
|
||||
name: 'SVG',
|
||||
type: 'svg',
|
||||
icon: 'iconSVG',
|
||||
desc: 'Scalable Vector Graphics'
|
||||
},
|
||||
{
|
||||
name: 'PDF',
|
||||
type: 'pdf',
|
||||
icon: 'iconpdf',
|
||||
desc: 'Suitable for printing'
|
||||
},
|
||||
{
|
||||
name: 'Markdown',
|
||||
type: 'md',
|
||||
icon: 'iconmarkdown',
|
||||
desc: 'Easy for other software to open'
|
||||
}
|
||||
]
|
||||
@@ -15,7 +15,8 @@ import {
|
||||
shortcutKeyList as shortcutKeyListZh,
|
||||
shapeList as shapeListZh,
|
||||
sidebarTriggerList as sidebarTriggerListZh,
|
||||
backgroundSizeList as backgroundSizeListZh
|
||||
backgroundSizeList as backgroundSizeListZh,
|
||||
downTypeList as downTypeListZh
|
||||
} from './zh'
|
||||
import {
|
||||
fontFamilyList as fontFamilyListEn,
|
||||
@@ -26,7 +27,8 @@ import {
|
||||
shortcutKeyList as shortcutKeyListEn,
|
||||
shapeList as shapeListEn,
|
||||
sidebarTriggerList as sidebarTriggerListEn,
|
||||
backgroundSizeList as backgroundSizeListEn
|
||||
backgroundSizeList as backgroundSizeListEn,
|
||||
downTypeList as downTypeListEn
|
||||
} from './en'
|
||||
|
||||
const fontFamilyList = {
|
||||
@@ -74,6 +76,11 @@ const sidebarTriggerList = {
|
||||
en: sidebarTriggerListEn
|
||||
}
|
||||
|
||||
const downTypeList = {
|
||||
zh: downTypeListZh,
|
||||
en: downTypeListEn
|
||||
}
|
||||
|
||||
export {
|
||||
fontSizeList,
|
||||
lineHeightList,
|
||||
@@ -91,5 +98,6 @@ export {
|
||||
backgroundSizeList,
|
||||
shortcutKeyList,
|
||||
shapeList,
|
||||
sidebarTriggerList
|
||||
sidebarTriggerList,
|
||||
downTypeList
|
||||
}
|
||||
|
||||
@@ -441,3 +441,43 @@ export const sidebarTriggerList = [
|
||||
icon: 'iconjianpan'
|
||||
}
|
||||
]
|
||||
|
||||
// 下载类型列表
|
||||
export const downTypeList = [
|
||||
{
|
||||
name: '专有文件',
|
||||
type: 'smm',
|
||||
icon: 'iconwenjian',
|
||||
desc: '可用于导入'
|
||||
},
|
||||
{
|
||||
name: 'JSON',
|
||||
type: 'json',
|
||||
icon: 'iconjson',
|
||||
desc: '流行的数据交换格式,可用于导入'
|
||||
},
|
||||
{
|
||||
name: '图片',
|
||||
type: 'png',
|
||||
icon: 'iconPNG',
|
||||
desc: '适合查看分享'
|
||||
},
|
||||
{
|
||||
name: 'SVG',
|
||||
type: 'svg',
|
||||
icon: 'iconSVG',
|
||||
desc: '可缩放矢量图形'
|
||||
},
|
||||
{
|
||||
name: 'PDF',
|
||||
type: 'pdf',
|
||||
icon: 'iconpdf',
|
||||
desc: '适合打印'
|
||||
},
|
||||
{
|
||||
name: 'Markdown',
|
||||
type: 'md',
|
||||
icon: 'iconmarkdown',
|
||||
desc: '便于其他软件打开'
|
||||
}
|
||||
]
|
||||
@@ -35,7 +35,15 @@ export default {
|
||||
watermarkAngle: 'Angle',
|
||||
watermarkTextOpacity: 'Text opacity',
|
||||
watermarkTextFontSize: 'Font size',
|
||||
isEnableNodeRichText: 'Enable node rich text editing'
|
||||
isEnableNodeRichText: 'Enable node rich text editing',
|
||||
mousewheelAction: 'Mouse wheel behavior',
|
||||
zoomView: 'Zoom view',
|
||||
moveViewUpDown: 'Move view up and down',
|
||||
associativeLine: 'Associative line',
|
||||
associativeLineWidth: 'Width',
|
||||
associativeLineColor: 'Color',
|
||||
associativeLineActiveWidth: 'Active width',
|
||||
associativeLineActiveColor: 'Active color'
|
||||
},
|
||||
color: {
|
||||
moreColor: 'More color'
|
||||
@@ -80,6 +88,7 @@ export default {
|
||||
imageFile: 'Image file',
|
||||
svgFile: 'svg file',
|
||||
pdfFile: 'pdf file',
|
||||
markdownFile: 'markdown file',
|
||||
tips: 'tips: .smm and .json file can be import',
|
||||
domToImage: 'Whether to convert rich text nodes in svg into pictures',
|
||||
pngTips: 'tips: Exporting pictures in rich text mode is time-consuming. It is recommended to export to svg format',
|
||||
@@ -95,7 +104,7 @@ export default {
|
||||
import: {
|
||||
title: 'Import',
|
||||
selectFile: 'Select file',
|
||||
supportFile: 'Support .smm、.json、.xmind、.xlsx file'
|
||||
supportFile: 'Support .smm、.json、.xmind、.xlsx、.md file'
|
||||
},
|
||||
navigatorToolbar: {
|
||||
openMiniMap: 'Open mini map',
|
||||
@@ -184,7 +193,8 @@ export default {
|
||||
saveAs: 'Save as',
|
||||
import: 'Import',
|
||||
export: 'Export',
|
||||
shortcutKey: 'Shortcut key'
|
||||
shortcutKey: 'Shortcut key',
|
||||
associativeLine: 'Associative line',
|
||||
},
|
||||
edit: {
|
||||
newFeatureNoticeTitle: 'New feature reminder',
|
||||
|
||||
@@ -35,7 +35,15 @@ export default {
|
||||
watermarkAngle: '旋转角度',
|
||||
watermarkTextOpacity: '文字透明度',
|
||||
watermarkTextFontSize: '文字字号',
|
||||
isEnableNodeRichText: '是否开启节点富文本编辑'
|
||||
isEnableNodeRichText: '是否开启节点富文本编辑',
|
||||
mousewheelAction: '鼠标滚轮行为',
|
||||
zoomView: '缩放视图',
|
||||
moveViewUpDown: '上下移动视图',
|
||||
associativeLine: '关联线',
|
||||
associativeLineWidth: '粗细',
|
||||
associativeLineColor: '颜色',
|
||||
associativeLineActiveWidth: '激活粗细',
|
||||
associativeLineActiveColor: '激活颜色'
|
||||
},
|
||||
color: {
|
||||
moreColor: '更多颜色'
|
||||
@@ -80,6 +88,7 @@ export default {
|
||||
imageFile: '图片文件',
|
||||
svgFile: 'svg文件',
|
||||
pdfFile: 'pdf文件',
|
||||
markdownFile: 'markdown文件',
|
||||
tips: 'tips:.smm和.json文件可用于导入',
|
||||
domToImage: '是否将svg中富文本节点转换成图片',
|
||||
pngTips: 'tips:富文本模式导出图片非常耗时,建议导出为svg格式',
|
||||
@@ -95,7 +104,7 @@ export default {
|
||||
import: {
|
||||
title: '导入',
|
||||
selectFile: '选取文件',
|
||||
supportFile: '支持.smm、.json、.xmind、.xlsx文件'
|
||||
supportFile: '支持.smm、.json、.xmind、.xlsx、.md文件'
|
||||
},
|
||||
navigatorToolbar: {
|
||||
openMiniMap: '开启小地图',
|
||||
@@ -184,7 +193,8 @@ export default {
|
||||
saveAs: '另存为',
|
||||
import: '导入',
|
||||
export: '导出',
|
||||
shortcutKey: '快捷键'
|
||||
shortcutKey: '快捷键',
|
||||
associativeLine: '关联线',
|
||||
},
|
||||
edit: {
|
||||
newFeatureNoticeTitle: '新特性提醒',
|
||||
|
||||
@@ -92,6 +92,10 @@ export default {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,9 @@ let APIList = [
|
||||
'doExport',
|
||||
'miniMap',
|
||||
'watermark',
|
||||
'associativeLine',
|
||||
'xmind',
|
||||
'markdown',
|
||||
'utils'
|
||||
]
|
||||
|
||||
|
||||
89
web/src/pages/Doc/en/associativeLine/index.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# AssociativeLine plugin
|
||||
|
||||
> v0.4.5+
|
||||
|
||||
> The function of adjusting associated line control points is supported from v0.4.6+
|
||||
|
||||
This plugin is used to support the addition of associative lines.
|
||||
|
||||
The plugin is currently not fully functional, and does not support adding text to association lines.
|
||||
|
||||
## Register
|
||||
|
||||
```js
|
||||
import MindMap from 'simple-mind-map'
|
||||
import AssociativeLine from 'simple-mind-map/src/AssociativeLine.js'
|
||||
|
||||
MindMap.usePlugin(AssociativeLine)
|
||||
```
|
||||
|
||||
After registration and instantiation of `MindMap`, the instance can be obtained through `mindMap.associativeLine`.
|
||||
|
||||
## Config
|
||||
|
||||
Support for modifying the thickness and color of associated lines, divided into default and active states. The configuration is as follows:
|
||||
|
||||
- `associativeLineWidth`: The thickness of the default state of the associated line. The default value is `2`
|
||||
|
||||
- `associativeLineColor`: Color of the default state of associative lines. The default value is `rgb(51, 51, 51)`
|
||||
|
||||
- `associativeLineActiveWidth`: The thickness of the active state of the associated line. The default value is `8`
|
||||
|
||||
- `associativeLineActiveColor`: The color of the active state of the associated line. The default value is `rgba(2, 167, 240, 1)`
|
||||
|
||||
The configuration is provided as a theme, so if you want to modify these four properties, you can modify them using the `mindMap.setThemeConfig(config)` method.
|
||||
|
||||
## Props
|
||||
|
||||
### mindMap.associativeLine.lineList
|
||||
|
||||
Currently, all connection line data, array types, and each item of the array are also an array:
|
||||
|
||||
```js
|
||||
[
|
||||
path, // Connector node
|
||||
clickPath, // Invisible click line node
|
||||
node, // Start node
|
||||
toNode // Target node
|
||||
]
|
||||
```
|
||||
|
||||
### mindMap.associativeLine.activeLine
|
||||
|
||||
The currently active connection line and array type are the same as the structure of each item in the `lineList` array.
|
||||
|
||||
## Methods
|
||||
|
||||
### renderAllLines()
|
||||
|
||||
Re-render all associated lines.
|
||||
|
||||
### removeAllLines()
|
||||
|
||||
Remove all associated lines.
|
||||
|
||||
### createLineFromActiveNode()
|
||||
|
||||
Create an associated line from the current active node. If there are multiple active nodes, the default is the first node.
|
||||
|
||||
After calling this method, an association line will be rendered from the first active node to the current mouse real-time position. When a target node is clicked, it represents completion of creation. An association line will be rendered between the first active node and the clicked node.
|
||||
|
||||
### createLine(fromNode)
|
||||
|
||||
Creates an associative line starting at the specified node.
|
||||
|
||||
After calling this method, an association line will be rendered from the specified node to the current mouse real-time position. When a target node is clicked, it represents completion of creation, and an association line will be rendered between the specified node and the clicked node.
|
||||
|
||||
### addLine(fromNode, toNode)
|
||||
|
||||
Add an associative line directly.
|
||||
|
||||
Calling this method will directly create an association line from the `fromNode` to the `toNode` node.
|
||||
|
||||
### removeLine()
|
||||
|
||||
Deletes the currently active associative line. Clicking on an associated line is considered active.
|
||||
|
||||
### clearActiveLine()
|
||||
|
||||
Clears the active state of the currently active association line.
|
||||
78
web/src/pages/Doc/en/associativeLine/index.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>AssociativeLine plugin</h1>
|
||||
<blockquote>
|
||||
<p>v0.4.5+</p>
|
||||
</blockquote>
|
||||
<blockquote>
|
||||
<p>The function of adjusting associated line control points is supported from v0.4.6+</p>
|
||||
</blockquote>
|
||||
<p>This plugin is used to support the addition of associative lines.</p>
|
||||
<p>The plugin is currently not fully functional, and does not support adding text to association lines.</p>
|
||||
<h2>Register</h2>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> MindMap <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map'</span>
|
||||
<span class="hljs-keyword">import</span> AssociativeLine <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/AssociativeLine.js'</span>
|
||||
|
||||
MindMap.usePlugin(AssociativeLine)
|
||||
</code></pre>
|
||||
<p>After registration and instantiation of <code>MindMap</code>, the instance can be obtained through <code>mindMap.associativeLine</code>.</p>
|
||||
<h2>Config</h2>
|
||||
<p>Support for modifying the thickness and color of associated lines, divided into default and active states. The configuration is as follows:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p><code>associativeLineWidth</code>: The thickness of the default state of the associated line. The default value is <code>2</code></p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>associativeLineColor</code>: Color of the default state of associative lines. The default value is <code>rgb(51, 51, 51)</code></p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>associativeLineActiveWidth</code>: The thickness of the active state of the associated line. The default value is <code>8</code></p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>associativeLineActiveColor</code>: The color of the active state of the associated line. The default value is <code>rgba(2, 167, 240, 1)</code></p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>The configuration is provided as a theme, so if you want to modify these four properties, you can modify them using the <code>mindMap.setThemeConfig(config)</code> method.</p>
|
||||
<h2>Props</h2>
|
||||
<h3>mindMap.associativeLine.lineList</h3>
|
||||
<p>Currently, all connection line data, array types, and each item of the array are also an array:</p>
|
||||
<pre class="hljs"><code>[
|
||||
path, <span class="hljs-comment">// Connector node</span>
|
||||
clickPath, <span class="hljs-comment">// Invisible click line node</span>
|
||||
node, <span class="hljs-comment">// Start node</span>
|
||||
toNode <span class="hljs-comment">// Target node</span>
|
||||
]
|
||||
</code></pre>
|
||||
<h3>mindMap.associativeLine.activeLine</h3>
|
||||
<p>The currently active connection line and array type are the same as the structure of each item in the <code>lineList</code> array.</p>
|
||||
<h2>Methods</h2>
|
||||
<h3>renderAllLines()</h3>
|
||||
<p>Re-render all associated lines.</p>
|
||||
<h3>removeAllLines()</h3>
|
||||
<p>Remove all associated lines.</p>
|
||||
<h3>createLineFromActiveNode()</h3>
|
||||
<p>Create an associated line from the current active node. If there are multiple active nodes, the default is the first node.</p>
|
||||
<p>After calling this method, an association line will be rendered from the first active node to the current mouse real-time position. When a target node is clicked, it represents completion of creation. An association line will be rendered between the first active node and the clicked node.</p>
|
||||
<h3>createLine(fromNode)</h3>
|
||||
<p>Creates an associative line starting at the specified node.</p>
|
||||
<p>After calling this method, an association line will be rendered from the specified node to the current mouse real-time position. When a target node is clicked, it represents completion of creation, and an association line will be rendered between the specified node and the clicked node.</p>
|
||||
<h3>addLine(fromNode, toNode)</h3>
|
||||
<p>Add an associative line directly.</p>
|
||||
<p>Calling this method will directly create an association line from the <code>fromNode</code> to the <code>toNode</code> node.</p>
|
||||
<h3>removeLine()</h3>
|
||||
<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>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -1,12 +1,50 @@
|
||||
# Changelog
|
||||
|
||||
## 0.4.7
|
||||
|
||||
optimization: 1.During rich text editing, when initially focusing, all are no longer selected by default; 2.When editing rich text, use the node fill color as the background color to avoid being invisible when the node color is white. 3.Node activation state switching no longer triggers history. 4.Triggering history multiple times in a short time will only add the last data. 5.Optimize the addition of historical records. When there is a rollback, delete the historical data after the current pointer when adding a new record again.
|
||||
|
||||
New: 1.Support for importing and exporting Markdown format files. 2.Support for configuring initial text when inserting nodes. 3.Expand the commands for inserting and deleting nodes to support specifying nodes.
|
||||
|
||||
## 0.4.6
|
||||
|
||||
New: 1.Associated lines support adjusting control points.
|
||||
|
||||
optimization: 1.When adding historical data, filter data that has not changed compared to the previous time.
|
||||
|
||||
Fix: 1.Fixed a conflict between the direction keys and the navigation function of the direction keys during node editing. 2.Fixed the issue of node id loss when dragging a mobile node, which can cause associated lines to be lost.
|
||||
|
||||
## 0.4.5
|
||||
|
||||
New: 1.Supports associative lines. 2.You can also drag the canvas by holding down the root node. 3. Hold down the ctrl key to adjust multiple selected nodes.
|
||||
|
||||
## 0.4.4
|
||||
|
||||
New: Support horizontal scrolling in response to the mouse.
|
||||
|
||||
## 0.4.3
|
||||
|
||||
Fix: No trigger after forward and backward `data_ Change` event.
|
||||
|
||||
New: Support user-defined mouse wheel events; The mouse wheel is adjusted to support zooming and moving the view up and down.
|
||||
|
||||
## 0.4.2
|
||||
|
||||
New: The `setText` method of the Node class adds a second parameter to support setting rich text content.
|
||||
|
||||
## 0.4.1
|
||||
|
||||
New: 1.Add and throw node mouseenter and mouseleave events; 2.Node rich text supports setting background color; 3.Node rich text supports clear style.
|
||||
|
||||
Fix: 1.Mac system touchpad scaling is the opposite problem; 2.When the device window.devicePixelRatio is not 1, the size of the rich text node in the exported image will become larger when there are rich text nodes.
|
||||
|
||||
## 0.4.0
|
||||
|
||||
New: The node supports rich text editing.
|
||||
|
||||
## 0.3.4
|
||||
|
||||
New:Automatic line wrapping function is added to node text.
|
||||
New: Automatic line wrapping function is added to node text.
|
||||
|
||||
Fix: 1.Fix the problem of deletion exceptions if there are root nodes in the batch deleted nodes. 2.Fix the problem that high node height will overlap with other nodes in the case of bottom edge style.
|
||||
|
||||
|
||||
@@ -1,10 +1,29 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Changelog</h1>
|
||||
<h2>0.4.7</h2>
|
||||
<p>optimization: 1.During rich text editing, when initially focusing, all are no longer selected by default; 2.When editing rich text, use the node fill color as the background color to avoid being invisible when the node color is white. 3.Node activation state switching no longer triggers history. 4.Triggering history multiple times in a short time will only add the last data. 5.Optimize the addition of historical records. When there is a rollback, delete the historical data after the current pointer when adding a new record again.</p>
|
||||
<p>New: 1.Support for importing and exporting Markdown format files. 2.Support for configuring initial text when inserting nodes. 3.Expand the commands for inserting and deleting nodes to support specifying nodes.</p>
|
||||
<h2>0.4.6</h2>
|
||||
<p>New: 1.Associated lines support adjusting control points.</p>
|
||||
<p>optimization: 1.When adding historical data, filter data that has not changed compared to the previous time.</p>
|
||||
<p>Fix: 1.Fixed a conflict between the direction keys and the navigation function of the direction keys during node editing. 2.Fixed the issue of node id loss when dragging a mobile node, which can cause associated lines to be lost.</p>
|
||||
<h2>0.4.5</h2>
|
||||
<p>New: 1.Supports associative lines. 2.You can also drag the canvas by holding down the root node. 3. Hold down the ctrl key to adjust multiple selected nodes.</p>
|
||||
<h2>0.4.4</h2>
|
||||
<p>New: Support horizontal scrolling in response to the mouse.</p>
|
||||
<h2>0.4.3</h2>
|
||||
<p>Fix: No trigger after forward and backward <code>data_ Change</code> event.</p>
|
||||
<p>New: Support user-defined mouse wheel events; The mouse wheel is adjusted to support zooming and moving the view up and down.</p>
|
||||
<h2>0.4.2</h2>
|
||||
<p>New: The <code>setText</code> method of the Node class adds a second parameter to support setting rich text content.</p>
|
||||
<h2>0.4.1</h2>
|
||||
<p>New: 1.Add and throw node mouseenter and mouseleave events; 2.Node rich text supports setting background color; 3.Node rich text supports clear style.</p>
|
||||
<p>Fix: 1.Mac system touchpad scaling is the opposite problem; 2.When the device window.devicePixelRatio is not 1, the size of the rich text node in the exported image will become larger when there are rich text nodes.</p>
|
||||
<h2>0.4.0</h2>
|
||||
<p>New: The node supports rich text editing.</p>
|
||||
<h2>0.3.4</h2>
|
||||
<p>New:Automatic line wrapping function is added to node text.</p>
|
||||
<p>New: Automatic line wrapping function is added to node text.</p>
|
||||
<p>Fix: 1.Fix the problem of deletion exceptions if there are root nodes in the batch deleted nodes. 2.Fix the problem that high node height will overlap with other nodes in the case of bottom edge style.</p>
|
||||
<h2>0.3.3</h2>
|
||||
<p>Fix: The root node text cannot wrap.</p>
|
||||
|
||||
@@ -41,6 +41,11 @@ const mindMap = new MindMap({
|
||||
| enableFreeDrag(v0.2.4+) | Boolean | false | Enable node free 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 | |
|
||||
| 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` | |
|
||||
| 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 | |
|
||||
|
||||
### Watermark config
|
||||
|
||||
@@ -178,6 +183,8 @@ Listen to an event. Event list:
|
||||
| node_mouseup | Node mouseup event | this (node instance), e (event object) |
|
||||
| node_dblclick | Node double-click event | this (node instance), e (event object) |
|
||||
| node_contextmenu | Node right-click menu event | e (event object), this (node instance) |
|
||||
| node_mouseenter(v0.4.1+) | Node mouseenter event | this (node instance), e (event object) |
|
||||
| node_mouseleave(v0.4.1+) | Node mouseleave event | this (node instance), e (event object) |
|
||||
| before_node_active | Event before node activation | this (node instance), activeNodeList (current list of active nodes) |
|
||||
| node_active | Node activation event | this (node instance), activeNodeList (current list of active nodes) |
|
||||
| expand_btn_click | Node expand or collapse event | this (node instance) |
|
||||
@@ -188,6 +195,9 @@ Listen to an event. Event list:
|
||||
| node_tree_render_end(v0.2.16+) | Node tree render end event | |
|
||||
| rich_text_selection_change(v0.4.0+) | Available when the `RichText` plugin is registered. Triggered when the text selection area changes when the node is edited | hasRange(Whether there is a selection)、rectInfo(Size and location information of the selected area)、formatInfo(Text formatting information of the selected area) |
|
||||
| transforming-dom-to-images(v0.4.0+) | Available when the `RichText` plugin is registered. When there is a `DOM` node in `svg`, the `DOM` node will be converted to an image when exporting to an image. This event will be triggered during the conversion process. You can use this event to prompt the user about the node to which you are currently converting | index(Index of the node currently converted to)、len(Total number of nodes to be converted) |
|
||||
| node_dragging(v0.4.5+) | Triggered when a node is dragged | node(The currently dragged node) |
|
||||
| node_dragend(v0.4.5+) | Triggered when the node is dragged and ends | |
|
||||
| associative_line_click(v0.4.5+) | Triggered when an associated line is clicked | path(Connector node)、clickPath(Invisible click line node)、node(Start node)、toNode(Target node) |
|
||||
|
||||
### emit(event, ...args)
|
||||
|
||||
@@ -256,39 +266,39 @@ in the options table above.
|
||||
Executes a command, which will add a record to the history stack for undo or
|
||||
redo. All commands are as follows:
|
||||
|
||||
| Command name | Description | Parameters |
|
||||
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| SELECT_ALL | Select all | |
|
||||
| BACK | Go back a specified number of steps | step (the number of steps to go back, default is 1) |
|
||||
| FORWARD | Go forward a specified number of steps | step (the number of steps to go forward, default is 1) |
|
||||
| INSERT_NODE | Insert a sibling node, the active node will be the operation node. If there are multiple active nodes, only the first one will be effective | |
|
||||
| INSERT_CHILD_NODE | Insert a child node, the active node will be the operation node | |
|
||||
| UP_NODE | Move node up, the active node will be the operation node. If there are multiple active nodes, only the first one will be effective. Using this command on the root node or the first node in the list will be invalid | |
|
||||
| DOWN_NODE | Move node down, the active node will be the operation node. If there are multiple active nodes, only the first one will be effective. Using this command on the root node or the last node in the list will be invalid | |
|
||||
| REMOVE_NODE | Remove node, the active node will be the operation node | |
|
||||
| PASTE_NODE | Paste node to a node, the active node will be the operation node | data (the node data to paste, usually obtained through the renderer.copyNode() and renderer.cutNode() methods) |
|
||||
| SET_NODE_STYLE | Modify node style | node (the node to set the style of), prop (style property), value (style property value), isActive (boolean, whether the style being set is for the active state) |
|
||||
| SET_NODE_ACTIVE | Set whether the node is active | node (the node to set), active (boolean, whether to activate) |
|
||||
| CLEAR_ACTIVE_NODE | Clear the active state of the currently active node(s), the active node will be the operation node | |
|
||||
| SET_NODE_EXPAND | Set whether the node is expanded | node (the node to set), expand (boolean, whether to expand) |
|
||||
| EXPAND_ALL | Expand all nodes | |
|
||||
| UNEXPAND_ALL | Collapse all nodes | |
|
||||
| UNEXPAND_TO_LEVEL (v0.2.8+) | Expand to a specified level | level (the level to expand to, 1, 2, 3...) |
|
||||
| SET_NODE_DATA | Update node data, that is, update the data in the data object of the node data object | node (the node to set), data (object, the data to update, e.g. `{expand: true}`) |
|
||||
| SET_NODE_TEXT | Set node text | node (the node to set), text (the new text for the node) |
|
||||
| SET_NODE_IMAGE | Set Node Image | node (node to set), imgData (object, image information, structured as: `{url, title, width, height}`, the width and height of the image must be passed) |
|
||||
| SET_NODE_ICON | Set Node Icon | node (node to set), icons (array, predefined image names array, available icons can be obtained in the nodeIconList list in the [https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/svg/icons.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/svg/icons.js) file, icon name is type_name, such as ['priority_1']) |
|
||||
| SET_NODE_HYPERLINK | Set Node Hyperlink | node (node to set), link (hyperlink address), title (hyperlink name, optional) |
|
||||
| SET_NODE_NOTE | Set Node Note | node (node to set), note (note text) |
|
||||
| SET_NODE_TAG | Set Node Tag | node (node to set), tag (string array, built-in color information can be obtained in [https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/utils/constant.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/utils/constant.js)) |
|
||||
| INSERT_AFTER (v0.1.5+) | Move Node to After Another Node | node (node to move), exist (target node) |
|
||||
| INSERT_BEFORE (v0.1.5+) | Move Node to Before Another Node | node (node to move), exist (target node) |
|
||||
| MOVE_NODE_TO (v0.1.5+) | Move a node as a child of another node | node (the node to move), toNode (the target node) |
|
||||
| ADD_GENERALIZATION (v0.2.0+) | Add a node summary | data (the data for the summary, in object format, all numerical fields of the node are supported, default is `{text: 'summary'}`) |
|
||||
| REMOVE_GENERALIZATION (v0.2.0+) | Remove a node summary | |
|
||||
| SET_NODE_CUSTOM_POSITION (v0.2.0+) | Set a custom position for a node | node (the node to set), left (custom x coordinate, default is undefined), top (custom y coordinate, default is undefined) |
|
||||
| RESET_LAYOUT (v0.2.0+) | Arrange layout with one click | |
|
||||
| SET_NODE_SHAPE (v0.2.4+) | Set the shape of a node | node (the node to set), shape (the shape, all shapes: https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/Shape.js) |
|
||||
| Command name | Description | Parameters |
|
||||
| ---------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||
| SELECT_ALL | Select all | |
|
||||
| BACK | Go back a specified number of steps | step (the number of steps to go back, default is 1) |
|
||||
| FORWARD | Go forward a specified number of steps | step (the number of steps to go forward, default is 1) |
|
||||
| INSERT_NODE | Insert a sibling node, the active node or appoint node will be the operation node. If there are multiple active nodes, only the first one will be effective | openEdit(v0.4.6+, Whether to activate the newly inserted node and enter editing mode, default is `true`) 、 appointNodes(v0.4.7+, Optional, appoint node, Specifying multiple nodes can pass an array)、 appointData(Optional, Specify the data for the newly created node, Such as {text: 'xxx', ...}, Detailed structure can be referred to [https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js) ) |
|
||||
| INSERT_CHILD_NODE | Insert a child node, the active node or appoint node will be the operation node | openEdit(v0.4.6+, Whether to activate the newly inserted node and enter editing mode, default is `true`)、 appointNodes(v0.4.7+, Optional, appoint node, Specifying multiple nodes can pass an array)、 appointData(Optional, Specify the data for the newly created node, Such as {text: 'xxx', ...}, Detailed structure can be referred to [https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js) ) |
|
||||
| UP_NODE | Move node up, the active node will be the operation node. If there are multiple active nodes, only the first one will be effective. Using this command on the root node or the first node in the list will be invalid | |
|
||||
| DOWN_NODE | Move node down, the active node will be the operation node. If there are multiple active nodes, only the first one will be effective. Using this command on the root node or the last node in the list will be invalid | |
|
||||
| REMOVE_NODE | Remove node, the active node or appoint node will be the operation node | appointNodes(v0.4.7+, Optional, appoint node, Specifying multiple nodes can pass an array) |
|
||||
| PASTE_NODE | Paste node to a node, the active node will be the operation node | data (the node data to paste, usually obtained through the renderer.copyNode() and renderer.cutNode() methods) |
|
||||
| SET_NODE_STYLE | Modify node style | node (the node to set the style of), prop (style property), value (style property value), isActive (boolean, whether the style being set is for the active state) |
|
||||
| SET_NODE_ACTIVE | Set whether the node is active | node (the node to set), active (boolean, whether to activate) |
|
||||
| CLEAR_ACTIVE_NODE | Clear the active state of the currently active node(s), the active node will be the operation node | |
|
||||
| SET_NODE_EXPAND | Set whether the node is expanded | node (the node to set), expand (boolean, whether to expand) |
|
||||
| EXPAND_ALL | Expand all nodes | |
|
||||
| UNEXPAND_ALL | Collapse all nodes | |
|
||||
| UNEXPAND_TO_LEVEL (v0.2.8+) | Expand to a specified level | level (the level to expand to, 1, 2, 3...) |
|
||||
| SET_NODE_DATA | Update node data, that is, update the data in the data object of the node data object | node (the node to set), data (object, the data to update, e.g. `{expand: true}`) |
|
||||
| SET_NODE_TEXT | Set node text | node (the node to set), text (the new text for the node), richText(v0.4.0+, If you want to set a rich text character, you need to set it to `true`) |
|
||||
| SET_NODE_IMAGE | Set Node Image | node (node to set), imgData (object, image information, structured as: `{url, title, width, height}`, the width and height of the image must be passed) |
|
||||
| SET_NODE_ICON | Set Node Icon | node (node to set), icons (array, predefined image names array, available icons can be obtained in the nodeIconList list in the [https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/svg/icons.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/svg/icons.js) file, icon name is type_name, such as ['priority_1']) |
|
||||
| SET_NODE_HYPERLINK | Set Node Hyperlink | node (node to set), link (hyperlink address), title (hyperlink name, optional) |
|
||||
| SET_NODE_NOTE | Set Node Note | node (node to set), note (note text) |
|
||||
| SET_NODE_TAG | Set Node Tag | node (node to set), tag (string array, built-in color information can be obtained in [https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/utils/constant.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/utils/constant.js)) |
|
||||
| INSERT_AFTER (v0.1.5+) | Move Node to After Another Node | node (node to move), exist (target node) |
|
||||
| INSERT_BEFORE (v0.1.5+) | Move Node to Before Another Node | node (node to move), exist (target node) |
|
||||
| MOVE_NODE_TO (v0.1.5+) | Move a node as a child of another node | node (the node to move), toNode (the target node) |
|
||||
| ADD_GENERALIZATION (v0.2.0+) | Add a node summary | data (the data for the summary, in object format, all numerical fields of the node are supported, default is `{text: 'summary'}`) |
|
||||
| REMOVE_GENERALIZATION (v0.2.0+) | Remove a node summary | |
|
||||
| SET_NODE_CUSTOM_POSITION (v0.2.0+) | Set a custom position for a node | node (the node to set), left (custom x coordinate, default is undefined), top (custom y coordinate, default is undefined) |
|
||||
| RESET_LAYOUT (v0.2.0+) | Arrange layout with one click | |
|
||||
| SET_NODE_SHAPE (v0.2.4+) | Set the shape of a node | node (the node to set), shape (the shape, all shapes: https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/Shape.js) |
|
||||
|
||||
### setData(data)
|
||||
|
||||
|
||||
@@ -147,6 +147,41 @@
|
||||
<td>Each line of text in the node will wrap automatically when it reaches the width</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>customHandleMousewheel(v0.4.3+)</td>
|
||||
<td>Function</td>
|
||||
<td>null</td>
|
||||
<td>User-defined mouse wheel event processing can pass a function, and the callback parameter is the event object</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>mousewheelAction(v0.4.3+)</td>
|
||||
<td>String</td>
|
||||
<td>zoom</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>
|
||||
<tr>
|
||||
<td>mousewheelMoveStep(v0.4.3+)</td>
|
||||
<td>Number</td>
|
||||
<td>100</td>
|
||||
<td>When the <code>mousewheelAction</code> is set to <code>move</code>, you can use this attribute to control the step length of the view movement when the mouse scrolls. The unit is <code>px</code></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>defaultInsertSecondLevelNodeText(v0.4.7+)</td>
|
||||
<td>String</td>
|
||||
<td>二级节点</td>
|
||||
<td>Text of the default inserted secondary node</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>defaultInsertBelowSecondLevelNodeText(v0.4.7+)</td>
|
||||
<td>String</td>
|
||||
<td>分支主题</td>
|
||||
<td>Text for nodes below the second level inserted by default</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Watermark config</h3>
|
||||
@@ -367,6 +402,16 @@ poor performance and should be used sparingly.</p>
|
||||
<td>e (event object), this (node instance)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>node_mouseenter(v0.4.1+)</td>
|
||||
<td>Node mouseenter event</td>
|
||||
<td>this (node instance), e (event object)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>node_mouseleave(v0.4.1+)</td>
|
||||
<td>Node mouseleave event</td>
|
||||
<td>this (node instance), e (event object)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>before_node_active</td>
|
||||
<td>Event before node activation</td>
|
||||
<td>this (node instance), activeNodeList (current list of active nodes)</td>
|
||||
@@ -416,6 +461,21 @@ poor performance and should be used sparingly.</p>
|
||||
<td>Available when the <code>RichText</code> plugin is registered. When there is a <code>DOM</code> node in <code>svg</code>, the <code>DOM</code> node will be converted to an image when exporting to an image. This event will be triggered during the conversion process. You can use this event to prompt the user about the node to which you are currently converting</td>
|
||||
<td>index(Index of the node currently converted to)、len(Total number of nodes to be converted)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>node_dragging(v0.4.5+)</td>
|
||||
<td>Triggered when a node is dragged</td>
|
||||
<td>node(The currently dragged node)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>node_dragend(v0.4.5+)</td>
|
||||
<td>Triggered when the node is dragged and ends</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>associative_line_click(v0.4.5+)</td>
|
||||
<td>Triggered when an associated line is clicked</td>
|
||||
<td>path(Connector node)、clickPath(Invisible click line node)、node(Start node)、toNode(Target node)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>emit(event, ...args)</h3>
|
||||
@@ -484,13 +544,13 @@ redo. All commands are as follows:</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>INSERT_NODE</td>
|
||||
<td>Insert a sibling node, the active node will be the operation node. If there are multiple active nodes, only the first one will be effective</td>
|
||||
<td></td>
|
||||
<td>Insert a sibling node, the active node or appoint node will be the operation node. If there are multiple active nodes, only the first one will be effective</td>
|
||||
<td>openEdit(v0.4.6+, Whether to activate the newly inserted node and enter editing mode, default is <code>true</code>) 、 appointNodes(v0.4.7+, Optional, appoint node, Specifying multiple nodes can pass an array)、 appointData(Optional, Specify the data for the newly created node, Such as {text: 'xxx', ...}, Detailed structure can be referred to <a href="https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js">https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js</a> )</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>INSERT_CHILD_NODE</td>
|
||||
<td>Insert a child node, the active node will be the operation node</td>
|
||||
<td></td>
|
||||
<td>Insert a child node, the active node or appoint node will be the operation node</td>
|
||||
<td>openEdit(v0.4.6+, Whether to activate the newly inserted node and enter editing mode, default is <code>true</code>)、 appointNodes(v0.4.7+, Optional, appoint node, Specifying multiple nodes can pass an array)、 appointData(Optional, Specify the data for the newly created node, Such as {text: 'xxx', ...}, Detailed structure can be referred to <a href="https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js">https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js</a> )</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>UP_NODE</td>
|
||||
@@ -504,8 +564,8 @@ redo. All commands are as follows:</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>REMOVE_NODE</td>
|
||||
<td>Remove node, the active node will be the operation node</td>
|
||||
<td></td>
|
||||
<td>Remove node, the active node or appoint node will be the operation node</td>
|
||||
<td>appointNodes(v0.4.7+, Optional, appoint node, Specifying multiple nodes can pass an array)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>PASTE_NODE</td>
|
||||
@@ -555,7 +615,7 @@ redo. All commands are as follows:</p>
|
||||
<tr>
|
||||
<td>SET_NODE_TEXT</td>
|
||||
<td>Set node text</td>
|
||||
<td>node (the node to set), text (the new text for the node)</td>
|
||||
<td>node (the node to set), text (the new text for the node), richText(v0.4.0+, If you want to set a rich text character, you need to set it to <code>true</code>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SET_NODE_IMAGE</td>
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
- [x] Supports various node shapes
|
||||
- [x] Supports export to json, png, svg, pdf, and import from json, xmind
|
||||
- [x] Supports mini map、support watermark
|
||||
- [x] Supports associative lines
|
||||
|
||||
## Table of Contents
|
||||
|
||||
@@ -50,10 +51,6 @@ Provide document page service.
|
||||
|
||||
The folder containing the packaged resources for the `web` folder.
|
||||
|
||||
4.`docs`
|
||||
|
||||
Documentation, etc.
|
||||
|
||||
## Related Articles
|
||||
|
||||
[Technical Analysis of Web Mind Map Implementation (chi)](https://juejin.cn/post/6987711560521089061)
|
||||
@@ -62,6 +59,8 @@ Documentation, etc.
|
||||
|
||||
[When you press the direction key, how does the TV find the next focus](https://juejin.cn/post/7199666255883927612)
|
||||
|
||||
[How to simulate the background image style of css in canvas](https://juejin.cn/post/7204854015463538744)
|
||||
|
||||
## Special Note
|
||||
|
||||
This project is rough and has not been thoroughly tested, its features are not
|
||||
|
||||
@@ -4,20 +4,21 @@
|
||||
<p><code>simple-mind-map</code> is a simple and powerful web mind map library, not dependent on any specific framework.</p>
|
||||
<h2>Features</h2>
|
||||
<ul>
|
||||
<li><input type="checkbox" id="checkbox17" checked="true"><label for="checkbox17">Plugin architecture. In addition to core functions, other functions are provided as plugins, which can be used as needed to reduce the overall volume</label></li>
|
||||
<li><input type="checkbox" id="checkbox18" checked="true"><label for="checkbox18">Supports four types of structures: logical structure diagrams, mind maps,</label>
|
||||
<li><input type="checkbox" id="checkbox36" checked="true"><label for="checkbox36">Plugin architecture. In addition to core functions, other functions are provided as plugins, which can be used as needed to reduce the overall volume</label></li>
|
||||
<li><input type="checkbox" id="checkbox37" checked="true"><label for="checkbox37">Supports four types of structures: logical structure diagrams, mind maps,</label>
|
||||
organizational structure diagrams, and directory organization diagrams</li>
|
||||
<li><input type="checkbox" id="checkbox19" checked="true"><label for="checkbox19">Built-in multiple themes and allows for highly customized styles, and support register new themes</label></li>
|
||||
<li><input type="checkbox" id="checkbox20" checked="true"><label for="checkbox20">Supports shortcuts</label></li>
|
||||
<li><input type="checkbox" id="checkbox21" checked="true"><label for="checkbox21">Node content supports images, icons, hyperlinks, notes, tags, and</label>
|
||||
<li><input type="checkbox" id="checkbox38" checked="true"><label for="checkbox38">Built-in multiple themes and allows for highly customized styles, and support register new themes</label></li>
|
||||
<li><input type="checkbox" id="checkbox39" checked="true"><label for="checkbox39">Supports shortcuts</label></li>
|
||||
<li><input type="checkbox" id="checkbox40" checked="true"><label for="checkbox40">Node content supports images, icons, hyperlinks, notes, tags, and</label>
|
||||
summaries</li>
|
||||
<li><input type="checkbox" id="checkbox22" checked="true"><label for="checkbox22">Supports forward and backward navigation</label></li>
|
||||
<li><input type="checkbox" id="checkbox23" checked="true"><label for="checkbox23">Supports dragging and scaling</label></li>
|
||||
<li><input type="checkbox" id="checkbox24" checked="true"><label for="checkbox24">Supports right-click and Ctrl + left-click to select multiple items</label></li>
|
||||
<li><input type="checkbox" id="checkbox25" checked="true"><label for="checkbox25">Supports free dragging and dragging to adjust nodes</label></li>
|
||||
<li><input type="checkbox" id="checkbox26" checked="true"><label for="checkbox26">Supports various node shapes</label></li>
|
||||
<li><input type="checkbox" id="checkbox27" checked="true"><label for="checkbox27">Supports export to json, png, svg, pdf, and import from json, xmind</label></li>
|
||||
<li><input type="checkbox" id="checkbox28" checked="true"><label for="checkbox28">Supports mini map、support watermark</label></li>
|
||||
<li><input type="checkbox" id="checkbox41" checked="true"><label for="checkbox41">Supports forward and backward navigation</label></li>
|
||||
<li><input type="checkbox" id="checkbox42" checked="true"><label for="checkbox42">Supports dragging and scaling</label></li>
|
||||
<li><input type="checkbox" id="checkbox43" checked="true"><label for="checkbox43">Supports right-click and Ctrl + left-click to select multiple items</label></li>
|
||||
<li><input type="checkbox" id="checkbox44" checked="true"><label for="checkbox44">Supports free dragging and dragging to adjust nodes</label></li>
|
||||
<li><input type="checkbox" id="checkbox45" checked="true"><label for="checkbox45">Supports various node shapes</label></li>
|
||||
<li><input type="checkbox" id="checkbox46" checked="true"><label for="checkbox46">Supports export to json, png, svg, pdf, and import from json, xmind</label></li>
|
||||
<li><input type="checkbox" id="checkbox47" checked="true"><label for="checkbox47">Supports mini map、support watermark</label></li>
|
||||
<li><input type="checkbox" id="checkbox48" checked="true"><label for="checkbox48">Supports associative lines</label></li>
|
||||
</ul>
|
||||
<h2>Table of Contents</h2>
|
||||
<p>1.<code>simple-mind-map</code></p>
|
||||
@@ -27,28 +28,27 @@ frameworks such as Vue and React, or without a framework.</p>
|
||||
<p>This is an online mind map built using the <code>simple-mind-map</code> library and based
|
||||
on <code>Vue2.x</code> and <code>ElementUI</code>. Features include:</p>
|
||||
<ul>
|
||||
<li><input type="checkbox" id="checkbox29" checked="true"><label for="checkbox29">Toolbar, which supports inserting and deleting nodes, and editing node</label>
|
||||
<li><input type="checkbox" id="checkbox49" checked="true"><label for="checkbox49">Toolbar, which supports inserting and deleting nodes, and editing node</label>
|
||||
images, icons, hyperlinks, notes, tags, and summaries</li>
|
||||
<li><input type="checkbox" id="checkbox30" checked="true"><label for="checkbox30">Sidebar, with panels for basic style settings, node style settings,</label>
|
||||
<li><input type="checkbox" id="checkbox50" checked="true"><label for="checkbox50">Sidebar, with panels for basic style settings, node style settings,</label>
|
||||
outline, theme selection, and structure selection</li>
|
||||
<li><input type="checkbox" id="checkbox31" checked="true"><label for="checkbox31">Import and export functionality; data is saved in the browser's local</label>
|
||||
<li><input type="checkbox" id="checkbox51" checked="true"><label for="checkbox51">Import and export functionality; data is saved in the browser's local</label>
|
||||
storage by default, but it also supports creating, opening, and editing
|
||||
local files on the computer directly</li>
|
||||
<li><input type="checkbox" id="checkbox32" checked="true"><label for="checkbox32">Right-click menu, which supports operations such as expanding, collapsing,</label>
|
||||
<li><input type="checkbox" id="checkbox52" checked="true"><label for="checkbox52">Right-click menu, which supports operations such as expanding, collapsing,</label>
|
||||
and organizing layout</li>
|
||||
<li><input type="checkbox" id="checkbox33" checked="true"><label for="checkbox33">Bottom bar, which supports node and word count statistics, switching</label>
|
||||
<li><input type="checkbox" id="checkbox53" checked="true"><label for="checkbox53">Bottom bar, which supports node and word count statistics, switching</label>
|
||||
between edit and read-only modes, zooming in and out, and switching to
|
||||
full screen, support mini map</li>
|
||||
</ul>
|
||||
<p>Provide document page service.</p>
|
||||
<p>3.<code>dist</code></p>
|
||||
<p>The folder containing the packaged resources for the <code>web</code> folder.</p>
|
||||
<p>4.<code>docs</code></p>
|
||||
<p>Documentation, etc.</p>
|
||||
<h2>Related Articles</h2>
|
||||
<p><a href="https://juejin.cn/post/6987711560521089061">Technical Analysis of Web Mind Map Implementation (chi)</a></p>
|
||||
<p><a href="https://juejin.cn/post/7157681502506090510">Only a hundred lines of code are needed to add local file operation capability to your Web page. Are you sure not to try?</a></p>
|
||||
<p><a href="https://juejin.cn/post/7199666255883927612">When you press the direction key, how does the TV find the next focus</a></p>
|
||||
<p><a href="https://juejin.cn/post/7204854015463538744">How to simulate the background image style of css in canvas</a></p>
|
||||
<h2>Special Note</h2>
|
||||
<p>This project is rough and has not been thoroughly tested, its features are not
|
||||
yet fully developed, and there are some performance issues, especially when the number of nodes is large. It is only for
|
||||
|
||||
35
web/src/pages/Doc/en/markdown/index.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Markdown parse
|
||||
|
||||
> v0.4.7+
|
||||
|
||||
Provides methods for importing and exporting `Markdown` files.
|
||||
|
||||
## Import
|
||||
|
||||
```js
|
||||
import markdown from 'simple-mind-map/src/parse/markdown.js'
|
||||
```
|
||||
|
||||
If you are using the file in the format of `umd`, you can obtain it in the following way:
|
||||
|
||||
```html
|
||||
<script src="simple-mind-map/dist/simpleMindMap.umd.min.js"></script>
|
||||
```
|
||||
|
||||
```js
|
||||
MindMap.markdown
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
### transformToMarkdown(data)
|
||||
|
||||
- `data`: Mind map data can be obtained using the `mindMap.getData()` method.
|
||||
|
||||
Convert mind map data into `Markdown` format data, and the returned data is a string.
|
||||
|
||||
### transformMarkdownTo(mdContent)
|
||||
|
||||
- `mdContent`: The `Markdown` data to convert, string type.
|
||||
|
||||
Convert the `Markdown` string into node tree data and return a `Promise` instance. You can use the `mindMap.setData()` method to render the converted data onto the canvas.
|
||||
39
web/src/pages/Doc/en/markdown/index.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Markdown parse</h1>
|
||||
<blockquote>
|
||||
<p>v0.4.7+</p>
|
||||
</blockquote>
|
||||
<p>Provides methods for importing and exporting <code>Markdown</code> files.</p>
|
||||
<h2>Import</h2>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> markdown <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/parse/markdown.js'</span>
|
||||
</code></pre>
|
||||
<p>If you are using the file in the format of <code>umd</code>, you can obtain it in the following way:</p>
|
||||
<pre class="hljs"><code><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"simple-mind-map/dist/simpleMindMap.umd.min.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
|
||||
</code></pre>
|
||||
<pre class="hljs"><code>MindMap.markdown
|
||||
</code></pre>
|
||||
<h2>Methods</h2>
|
||||
<h3>transformToMarkdown(data)</h3>
|
||||
<ul>
|
||||
<li><code>data</code>: Mind map data can be obtained using the <code>mindMap.getData()</code> method.</li>
|
||||
</ul>
|
||||
<p>Convert mind map data into <code>Markdown</code> format data, and the returned data is a string.</p>
|
||||
<h3>transformMarkdownTo(mdContent)</h3>
|
||||
<ul>
|
||||
<li><code>mdContent</code>: The <code>Markdown</code> data to convert, string type.</li>
|
||||
</ul>
|
||||
<p>Convert the <code>Markdown</code> string into node tree data and return a <code>Promise</code> instance. You can use the <code>mindMap.setData()</code> method to render the converted data onto the canvas.</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -121,7 +121,9 @@ if `key` is not passed, return the `data` object
|
||||
Set the value of the specified key in the data object of the node's real data
|
||||
nodeData, `SET_NODE_DATA` command's shortcut method
|
||||
|
||||
### setText(text)
|
||||
### setText(text, richText)
|
||||
|
||||
- `richText`: v0.4.2+,`Boolean`, If you want to set rich text content, that is, `html` character, `richText` needs to be passed `true`
|
||||
|
||||
Setting the node text, a shortcut for the `SET_NODE_TEXT` command
|
||||
|
||||
|
||||
@@ -67,7 +67,10 @@ if <code>key</code> is not passed, return the <code>data</code> object</p>
|
||||
<h3>setData(data)</h3>
|
||||
<p>Set the value of the specified key in the data object of the node's real data
|
||||
nodeData, <code>SET_NODE_DATA</code> command's shortcut method</p>
|
||||
<h3>setText(text)</h3>
|
||||
<h3>setText(text, richText)</h3>
|
||||
<ul>
|
||||
<li><code>richText</code>: v0.4.2+,<code>Boolean</code>, If you want to set rich text content, that is, <code>html</code> character, <code>richText</code> needs to be passed <code>true</code></li>
|
||||
</ul>
|
||||
<p>Setting the node text, a shortcut for the <code>SET_NODE_TEXT</code> command</p>
|
||||
<h3>setImage(imgData)</h3>
|
||||
<p>Setting the node image, a shortcut for the <code>SET_NODE_IMAGE</code> command</p>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
This plugin provides the ability to edit rich text of nodes, and takes effect after registration.
|
||||
|
||||
By default, node editing can only uniformly apply styles to all text in the node. This plugin can support rich text editing effects. Currently, it supports bold, italic, underline, strikethrough, font, font size, and color. Underline and line height are not supported.
|
||||
By default, node editing can only uniformly apply styles to all text in the node. This plugin can support rich text editing effects. Currently, it supports bold, italic, underline, strikethrough, font, font size, color, and backgroundColor. Underline and line height are not supported.
|
||||
|
||||
The principle of this plugin is to use [Quill](https://github.com/quilljs/quill) editor implements rich text editing, and then uses the edited `DOM` node directly as the text data of the node, and embeds the `DOM` node through the `svg` `foreignObject` tag during rendering.
|
||||
|
||||
@@ -65,6 +65,12 @@ Replace the built-in font size list during rich text editing. The built-in list
|
||||
|
||||
Select All. When the node is being edited, you can select all the text in the node through this method.
|
||||
|
||||
### focus()
|
||||
|
||||
> v0.4.7+
|
||||
|
||||
Focus.
|
||||
|
||||
### formatText(config = {})
|
||||
|
||||
- `config`:Object. The key is the style attribute and the value is the style value. The complete configuration is as follows:
|
||||
@@ -104,6 +110,12 @@ Formats the text of the specified range.
|
||||
|
||||
Formats all text of the current edit node.
|
||||
|
||||
### removeFormat()
|
||||
|
||||
> v0.4.1+
|
||||
|
||||
Clears the style of the currently selected text.
|
||||
|
||||
### normalStyleToRichTextStyle(style)
|
||||
|
||||
Converts a normal node style object to a rich text style object. Because there are differences between node style attributes and rich text style attributes during non-rich text editing, a conversion operation is required. For example:
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<p>Note: This is a testing nature and imperfect function</p>
|
||||
</blockquote>
|
||||
<p>This plugin provides the ability to edit rich text of nodes, and takes effect after registration.</p>
|
||||
<p>By default, node editing can only uniformly apply styles to all text in the node. This plugin can support rich text editing effects. Currently, it supports bold, italic, underline, strikethrough, font, font size, and color. Underline and line height are not supported.</p>
|
||||
<p>By default, node editing can only uniformly apply styles to all text in the node. This plugin can support rich text editing effects. Currently, it supports bold, italic, underline, strikethrough, font, font size, color, and backgroundColor. Underline and line height are not supported.</p>
|
||||
<p>The principle of this plugin is to use <a href="https://github.com/quilljs/quill">Quill</a> editor implements rich text editing, and then uses the edited <code>DOM</code> node directly as the text data of the node, and embeds the <code>DOM</code> node through the <code>svg</code> <code>foreignObject</code> tag during rendering.</p>
|
||||
<p>This also caused a problem, that is, the function of exporting as a picture was affected, The original principle of exporting <code>svg</code> as an image is very simple, Get the <code>svg</code> string, and then create the <code>blob</code> data of the <code>type=image/svg+xml</code> type. Then use the <code>URL.createObjectURL</code> method to generate the <code>data:url</code> data. Then create a <code>Image</code> tag, use the <code>data:url</code> as the <code>src</code> of the image, and finally draw the image on the <code>canvas</code> object for export, However, after testing, when the <code>DOM</code> node is embedded in the <code>svg</code>, this method of export will cause errors, and after trying many ways, the perfect export effect cannot be achieved, The current method is to traverse the <code>foreignObject</code> node in <code>svg</code>, using <a href="https://github.com/niklasvh/html2canvas">html2canvas</a> Convert the <code>DOM</code> node in the <code>foreignObject</code> node into an image and then replace the <code>foreignObject</code> node. This method can work, but it is very time-consuming. Because the <code>html2canvas</code> conversion takes a long time, it takes about 2 seconds to convert a node. This leads to the more nodes, the slower the conversion time. Therefore, it is recommended not to use this plugin if you cannot tolerate the long time of export.</p>
|
||||
<p>If you have a better way, please leave a message.</p>
|
||||
@@ -50,6 +50,11 @@ MindMap.usePlugin(RichText, opt?)
|
||||
<h2>Method</h2>
|
||||
<h3>selectAll()</h3>
|
||||
<p>Select All. When the node is being edited, you can select all the text in the node through this method.</p>
|
||||
<h3>focus()</h3>
|
||||
<blockquote>
|
||||
<p>v0.4.7+</p>
|
||||
</blockquote>
|
||||
<p>Focus.</p>
|
||||
<h3>formatText(config = {})</h3>
|
||||
<ul>
|
||||
<li><code>config</code>:Object. The key is the style attribute and the value is the style value. The complete configuration is as follows:</li>
|
||||
@@ -83,6 +88,11 @@ MindMap.usePlugin(RichText, opt?)
|
||||
<li><code>config</code>:Same as <code>formatText</code> method</li>
|
||||
</ul>
|
||||
<p>Formats all text of the current edit node.</p>
|
||||
<h3>removeFormat()</h3>
|
||||
<blockquote>
|
||||
<p>v0.4.1+</p>
|
||||
</blockquote>
|
||||
<p>Clears the style of the currently selected text.</p>
|
||||
<h3>normalStyleToRichTextStyle(style)</h3>
|
||||
<p>Converts a normal node style object to a rich text style object. Because there are differences between node style attributes and rich text style attributes during non-rich text editing, a conversion operation is required. For example:</p>
|
||||
<pre class="hljs"><code>{
|
||||
|
||||
@@ -101,12 +101,14 @@ const mindMap = new MindMap({
|
||||
|
||||
The non-packaged 'ES' module is introduced by default, and only contains core functions, not unregistered plugin content, which can effectively reduce the size. However, you need to configure the `babel` compilation `simple mind-map` in your project to prevent some newer `js` syntax some browsers not supporting it.
|
||||
|
||||
If you need a file in the format of `umd` module, such as `CDN` in the browser, you can import it in the following way:
|
||||
If you need a file in the format of `umd` module, such as `CDN` in the browser, Then you can find the `simpleMindMap.umd.min.js` file in the `/simple-mind-map/dist/` directory, copy it to your project, and then import it into the page:
|
||||
|
||||
```js
|
||||
import MindMap from "simple-mind-map/dist/simpleMindMap.umd.min.js";
|
||||
```html
|
||||
<script scr="simpleMindMap.umd.min.js"></script>
|
||||
```
|
||||
|
||||
A global variable `window.simpleMindMap` will be created.
|
||||
|
||||
The disadvantage of this method is that it will contain all the content, including the plugins you have not registered, so the overall volume will be relatively large.
|
||||
|
||||
## Problems
|
||||
|
||||
@@ -72,9 +72,10 @@ compile this dependency:</p>
|
||||
});
|
||||
</code></pre>
|
||||
<p>The non-packaged 'ES' module is introduced by default, and only contains core functions, not unregistered plugin content, which can effectively reduce the size. However, you need to configure the <code>babel</code> compilation <code>simple mind-map</code> in your project to prevent some newer <code>js</code> syntax some browsers not supporting it.</p>
|
||||
<p>If you need a file in the format of <code>umd</code> module, such as <code>CDN</code> in the browser, you can import it in the following way:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> MindMap <span class="hljs-keyword">from</span> <span class="hljs-string">"simple-mind-map/dist/simpleMindMap.umd.min.js"</span>;
|
||||
<p>If you need a file in the format of <code>umd</code> module, such as <code>CDN</code> in the browser, Then you can find the <code>simpleMindMap.umd.min.js</code> file in the <code>/simple-mind-map/dist/</code> directory, copy it to your project, and then import it into the page:</p>
|
||||
<pre class="hljs"><code><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">scr</span>=<span class="hljs-string">"simpleMindMap.umd.min.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
|
||||
</code></pre>
|
||||
<p>A global variable <code>window.simpleMindMap</code> will be created.</p>
|
||||
<p>The disadvantage of this method is that it will contain all the content, including the plugins you have not registered, so the overall volume will be relatively large.</p>
|
||||
<h2>Problems</h2>
|
||||
<h3>Error when using in Vite, indicating xml-js dependency error</h3>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Participate in translation
|
||||
|
||||
Thanks for the English translation provided by [Emircan ERKUL](https://github.com/emircanerkul).
|
||||
Thanks for the first version English translation provided by [Emircan ERKUL](https://github.com/emircanerkul).
|
||||
|
||||
If you want to participate in the translation of this document, you can clone this repository first.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Participate in translation</h1>
|
||||
<p>Thanks for the English translation provided by <a href="https://github.com/emircanerkul">Emircan ERKUL</a>.</p>
|
||||
<p>Thanks for the first version English translation provided by <a href="https://github.com/emircanerkul">Emircan ERKUL</a>.</p>
|
||||
<p>If you want to participate in the translation of this document, you can clone this repository first.</p>
|
||||
<p>The translated documents are in the <code>/web/src/pages/Doc/</code> directory, and currently support English(<code>en</code>) and Simplified Chinese(<code>zh</code>).</p>
|
||||
<p>If you are adding a new language type, you can create a new directory under the <code>/web/src/pages/Doc/</code> directory, Then create a folder for each chapter, You can also directly copy all chapter directories under the existing language directory for translation, Note that you only need to write the <code>index.md</code> file, The <code>index.vue</code> file under the chapter directory is automatically generated by the script according to <code>index.md</code>.</p>
|
||||
|
||||
@@ -72,7 +72,11 @@ Copy render tree data, example:
|
||||
copyRenderTree({}, this.mindMap.renderer.renderTree);
|
||||
```
|
||||
|
||||
#### copyNodeTree(tree, root)
|
||||
#### copyNodeTree(tree, root, removeActiveState, keepId)
|
||||
|
||||
- `removeActiveState`: `Boolean`, default is `false`, Whether to remove the active state of the node
|
||||
|
||||
- `keepId`: v0.4.6+, `Boolean`, default is `false`, Whether to retain the `id` of the replicated node will be deleted by default to prevent duplicate node `id`. However, for mobile node scenarios, the original `id` of the node needs to be retained
|
||||
|
||||
Copy node tree data, mainly eliminating the reference `node` instance `_node`
|
||||
and copying the `data` of the data object, example:
|
||||
|
||||
@@ -39,7 +39,15 @@ basic data, otherwise it will throw an error</p>
|
||||
<p>Copy render tree data, example:</p>
|
||||
<pre class="hljs"><code>copyRenderTree({}, <span class="hljs-built_in">this</span>.mindMap.renderer.renderTree);
|
||||
</code></pre>
|
||||
<h4>copyNodeTree(tree, root)</h4>
|
||||
<h4>copyNodeTree(tree, root, removeActiveState, keepId)</h4>
|
||||
<ul>
|
||||
<li>
|
||||
<p><code>removeActiveState</code>: <code>Boolean</code>, default is <code>false</code>, Whether to remove the active state of the node</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>keepId</code>: v0.4.6+, <code>Boolean</code>, default is <code>false</code>, Whether to retain the <code>id</code> of the replicated node will be deleted by default to prevent duplicate node <code>id</code>. However, for mobile node scenarios, the original <code>id</code> of the node needs to be retained</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>Copy node tree data, mainly eliminating the reference <code>node</code> instance <code>_node</code>
|
||||
and copying the <code>data</code> of the data object, example:</p>
|
||||
<pre class="hljs"><code>copyNodeTree({}, node);
|
||||
|
||||
@@ -12,9 +12,11 @@ import xmind from 'simple-mind-map/src/parse/xmind.js'
|
||||
|
||||
If you are using the file in the format of `umd`, you can obtain it in the following way:
|
||||
|
||||
```js
|
||||
import MindMap from "simple-mind-map/dist/simpleMindMap.umd.min"
|
||||
```html
|
||||
<script src="simple-mind-map/dist/simpleMindMap.umd.min.js"></script>
|
||||
```
|
||||
|
||||
```js
|
||||
MindMap.xmind
|
||||
```
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> xmind <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/parse/xmind.js'</span>
|
||||
</code></pre>
|
||||
<p>If you are using the file in the format of <code>umd</code>, you can obtain it in the following way:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> MindMap <span class="hljs-keyword">from</span> <span class="hljs-string">"simple-mind-map/dist/simpleMindMap.umd.min"</span>
|
||||
|
||||
MindMap.xmind
|
||||
<pre class="hljs"><code><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"simple-mind-map/dist/simpleMindMap.umd.min.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
|
||||
</code></pre>
|
||||
<pre class="hljs"><code>MindMap.xmind
|
||||
</code></pre>
|
||||
<h2>Methods</h2>
|
||||
<h3>xmind.parseXmindFile(file)</h3>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
|
||||
export default [{"lang":"zh","children":[{"path":"batchExecution","title":"BatchExecution实例"},{"path":"changelog","title":"Changelog"},{"path":"command","title":"Command实例"},{"path":"constructor","title":"构造函数"},{"path":"doExport","title":"Export 插件"},{"path":"drag","title":"Drag插件"},{"path":"introduction","title":"简介"},{"path":"keyboardNavigation","title":"KeyboardNavigation插件"},{"path":"keyCommand","title":"KeyCommand实例"},{"path":"miniMap","title":"MiniMap插件"},{"path":"node","title":"Node实例"},{"path":"render","title":"Render实例"},{"path":"richText","title":"RichText插件"},{"path":"select","title":"Select 插件 "},{"path":"start","title":"开始"},{"path":"translate","title":"参与翻译"},{"path":"utils","title":"内置工具方法"},{"path":"view","title":"View实例"},{"path":"watermark","title":"Watermark插件"},{"path":"xmind","title":"XMind解析"}]},{"lang":"en","children":[{"path":"batchExecution","title":"batchExecution instance"},{"path":"changelog","title":"Changelog"},{"path":"command","title":"command instance"},{"path":"constructor","title":"Constructor"},{"path":"doExport","title":"Export plugin"},{"path":"drag","title":"Drag plugin"},{"path":"introduction","title":"Introduction"},{"path":"keyboardNavigation","title":"KeyboardNavigation plugin"},{"path":"keyCommand","title":"KeyCommand instance"},{"path":"miniMap","title":"MiniMap plugin"},{"path":"node","title":"Node instance"},{"path":"render","title":"Render instance"},{"path":"richText","title":"RichText plugin"},{"path":"select","title":"Select plugin"},{"path":"start","title":"Start"},{"path":"translate","title":"Participate in translation"},{"path":"utils","title":"Utility Methods"},{"path":"view","title":"View instance"},{"path":"watermark","title":"Watermark plugin"},{"path":"xmind","title":"XMind parse"}]}]
|
||||
export default [{"lang":"zh","children":[{"path":"associativeLine","title":"AssociativeLine 插件"},{"path":"batchExecution","title":"BatchExecution实例"},{"path":"changelog","title":"Changelog"},{"path":"command","title":"Command实例"},{"path":"constructor","title":"构造函数"},{"path":"doExport","title":"Export 插件"},{"path":"drag","title":"Drag插件"},{"path":"introduction","title":"简介"},{"path":"keyboardNavigation","title":"KeyboardNavigation插件"},{"path":"keyCommand","title":"KeyCommand实例"},{"path":"miniMap","title":"MiniMap插件"},{"path":"node","title":"Node实例"},{"path":"render","title":"Render实例"},{"path":"richText","title":"RichText插件"},{"path":"select","title":"Select 插件 "},{"path":"start","title":"开始"},{"path":"translate","title":"参与翻译"},{"path":"utils","title":"内置工具方法"},{"path":"view","title":"View实例"},{"path":"watermark","title":"Watermark插件"},{"path":"xmind","title":"XMind解析"},{"path":"markdown","title":"Markdown解析"}]},{"lang":"en","children":[{"path":"associativeLine","title":"AssociativeLine plugin"},{"path":"batchExecution","title":"batchExecution instance"},{"path":"changelog","title":"Changelog"},{"path":"command","title":"command instance"},{"path":"constructor","title":"Constructor"},{"path":"doExport","title":"Export plugin"},{"path":"drag","title":"Drag plugin"},{"path":"introduction","title":"Introduction"},{"path":"keyboardNavigation","title":"KeyboardNavigation plugin"},{"path":"keyCommand","title":"KeyCommand instance"},{"path":"miniMap","title":"MiniMap plugin"},{"path":"node","title":"Node instance"},{"path":"render","title":"Render instance"},{"path":"richText","title":"RichText plugin"},{"path":"select","title":"Select plugin"},{"path":"start","title":"Start"},{"path":"translate","title":"Participate in translation"},{"path":"utils","title":"Utility Methods"},{"path":"view","title":"View instance"},{"path":"watermark","title":"Watermark plugin"},{"path":"xmind","title":"XMind parse"},{"path":"markdown","title":"Markdown parse"}]}]
|
||||
|
||||