mirror of
https://github.com/wanglin2/mind-map.git
synced 2026-02-17 22:08:25 +08:00
Compare commits
42 Commits
0.6.6
...
0.6.9-fix.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14bd7c3705 | ||
|
|
5d0c9dcab1 | ||
|
|
def2f02eea | ||
|
|
a74a60c22d | ||
|
|
74d37f2cbc | ||
|
|
6ca9a116c2 | ||
|
|
8e59677623 | ||
|
|
d06f57a5dc | ||
|
|
07be48d342 | ||
|
|
5a5c7702f5 | ||
|
|
7e1a43143d | ||
|
|
5ff9137745 | ||
|
|
8e30d4a94f | ||
|
|
4b2ebb2e1c | ||
|
|
8c79ffd723 | ||
|
|
839c79405f | ||
|
|
d645adfd37 | ||
|
|
1b1551c6e3 | ||
|
|
78638dc291 | ||
|
|
e972924143 | ||
|
|
8871d8727b | ||
|
|
55945a1a2c | ||
|
|
d5e4044fb2 | ||
|
|
4ee458c509 | ||
|
|
a76ec0dad8 | ||
|
|
f2b247e85c | ||
|
|
3aed09a8c3 | ||
|
|
17b7a023ba | ||
|
|
10cb36829f | ||
|
|
90a55bb995 | ||
|
|
08a37d8971 | ||
|
|
89983f9f47 | ||
|
|
b71a80e383 | ||
|
|
2bf146816b | ||
|
|
daf9888da4 | ||
|
|
b42cee7a2f | ||
|
|
adccef5699 | ||
|
|
94230f8ec6 | ||
|
|
a161661c6b | ||
|
|
08b971cd9a | ||
|
|
b64c8f132b | ||
|
|
d6181591c5 |
12
README.md
12
README.md
@@ -136,4 +136,16 @@ const mindMap = new MindMap({
|
||||
<img src="./web/src/assets/avatar/suka.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>suka</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/Chris.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>Chris</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/水车.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>水车</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/仓鼠.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>仓鼠</span>
|
||||
</span>
|
||||
</p>
|
||||
File diff suppressed because one or more lines are too long
BIN
qrcode.jpg
BIN
qrcode.jpg
Binary file not shown.
|
Before Width: | Height: | Size: 184 KiB After Width: | Height: | Size: 48 KiB |
@@ -10,6 +10,7 @@ import AssociativeLine from './src/plugins/AssociativeLine'
|
||||
import RichText from './src/plugins/RichText'
|
||||
import NodeImgAdjust from './src/plugins/NodeImgAdjust.js'
|
||||
import TouchEvent from './src/plugins/TouchEvent.js'
|
||||
import Search from './src/plugins/Search.js'
|
||||
import xmind from './src/parse/xmind.js'
|
||||
import markdown from './src/parse/markdown.js'
|
||||
import icons from './src/svg/icons.js'
|
||||
@@ -36,5 +37,6 @@ MindMap
|
||||
.usePlugin(RichText)
|
||||
.usePlugin(TouchEvent)
|
||||
.usePlugin(NodeImgAdjust)
|
||||
.usePlugin(Search)
|
||||
|
||||
export default MindMap
|
||||
@@ -7,9 +7,9 @@ import Style from './src/core/render/node/Style'
|
||||
import KeyCommand from './src/core/command/KeyCommand'
|
||||
import Command from './src/core/command/Command'
|
||||
import BatchExecution from './src/utils/BatchExecution'
|
||||
import { layoutValueList, CONSTANTS } from './src/constants/constant'
|
||||
import { layoutValueList, CONSTANTS, commonCaches } from './src/constants/constant'
|
||||
import { SVG } from '@svgdotjs/svg.js'
|
||||
import { simpleDeepClone } from './src/utils'
|
||||
import { simpleDeepClone, getType } from './src/utils'
|
||||
import defaultTheme, { checkIsNodeSizeIndependenceConfig } from './src/themes/default'
|
||||
import { defaultOpt } from './src/constants/defaultOptions'
|
||||
|
||||
@@ -32,12 +32,12 @@ class MindMap {
|
||||
this.svg = SVG().addTo(this.el).size(this.width, this.height)
|
||||
this.draw = this.svg.group()
|
||||
|
||||
// 节点id
|
||||
this.uid = 1
|
||||
|
||||
// 初始化主题
|
||||
this.initTheme()
|
||||
|
||||
// 初始化缓存数据
|
||||
this.initCache()
|
||||
|
||||
// 事件类
|
||||
this.event = new Event({
|
||||
mindMap: this
|
||||
@@ -132,6 +132,23 @@ class MindMap {
|
||||
this.event.off(event, fn)
|
||||
}
|
||||
|
||||
// 初始化缓存数据
|
||||
initCache() {
|
||||
Object.keys(commonCaches).forEach((key) => {
|
||||
let type = getType(commonCaches[key])
|
||||
let value = ''
|
||||
switch(type) {
|
||||
case 'Boolean':
|
||||
value = false
|
||||
break
|
||||
default:
|
||||
value = null
|
||||
break
|
||||
}
|
||||
commonCaches[key] = value
|
||||
})
|
||||
}
|
||||
|
||||
// 设置主题
|
||||
initTheme() {
|
||||
// 合并主题配置
|
||||
@@ -238,7 +255,7 @@ class MindMap {
|
||||
|
||||
// 获取思维导图数据,节点树、主题、布局等
|
||||
getData(withConfig) {
|
||||
let nodeData = this.command.removeDataUid(this.command.getCopyData())
|
||||
let nodeData = this.command.getCopyData()
|
||||
let data = {}
|
||||
if (withConfig) {
|
||||
data = {
|
||||
|
||||
4
simple-mind-map/package-lock.json
generated
4
simple-mind-map/package-lock.json
generated
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.6.0",
|
||||
"version": "0.6.7",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "0.6.0",
|
||||
"version": "0.6.7",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@svgdotjs/svg.js": "^3.0.16",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.6.6",
|
||||
"version": "0.6.9-fix.1",
|
||||
"description": "一个简单的web在线思维导图",
|
||||
"authors": [
|
||||
{
|
||||
|
||||
@@ -27,130 +27,162 @@ export const themeList = [
|
||||
{
|
||||
name: '默认',
|
||||
value: 'default',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '暗色2',
|
||||
value: 'dark2',
|
||||
dark: true
|
||||
},
|
||||
{
|
||||
name: '天清绿',
|
||||
value: 'skyGreen',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '脑图经典2',
|
||||
value: 'classic2',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '脑图经典3',
|
||||
value: 'classic3',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '经典绿',
|
||||
value: 'classicGreen',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '经典蓝',
|
||||
value: 'classicBlue',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '天空蓝',
|
||||
value: 'blueSky',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '脑残粉',
|
||||
value: 'brainImpairedPink',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '暗色',
|
||||
value: 'dark',
|
||||
dark: true
|
||||
},
|
||||
{
|
||||
name: '泥土黄',
|
||||
value: 'earthYellow',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '清新绿',
|
||||
value: 'freshGreen',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '清新红',
|
||||
value: 'freshRed',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '浪漫紫',
|
||||
value: 'romanticPurple',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '粉红葡萄',
|
||||
value: 'pinkGrape',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '薄荷',
|
||||
value: 'mint',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '金色vip',
|
||||
value: 'gold',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '活力橙',
|
||||
value: 'vitalityOrange',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '绿叶',
|
||||
value: 'greenLeaf',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '脑图经典',
|
||||
value: 'classic',
|
||||
dark: true
|
||||
},
|
||||
{
|
||||
name: '脑图经典4',
|
||||
value: 'classic4',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '小黄人',
|
||||
value: 'minions',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '简约黑',
|
||||
value: 'simpleBlack',
|
||||
dark: true
|
||||
},
|
||||
{
|
||||
name: '课程绿',
|
||||
value: 'courseGreen',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '咖啡',
|
||||
value: 'coffee',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '红色精神',
|
||||
value: 'redSpirit',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '黑色幽默',
|
||||
value: 'blackHumour',
|
||||
dark: true
|
||||
},
|
||||
{
|
||||
name: '深夜办公室',
|
||||
value: 'lateNightOffice',
|
||||
dark: true
|
||||
},
|
||||
{
|
||||
name: '黑金',
|
||||
value: 'blackGold',
|
||||
dark: true
|
||||
},
|
||||
{
|
||||
name: '牛油果',
|
||||
value: 'avocado',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '秋天',
|
||||
value: 'autumn',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '橙汁',
|
||||
value: 'orangeJuice',
|
||||
dark: true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -213,6 +245,10 @@ export const CONSTANTS = {
|
||||
TOP: 'top',
|
||||
RIGHT: 'right',
|
||||
BOTTOM: 'bottom'
|
||||
},
|
||||
PASTE_TYPE: {
|
||||
CLIP_BOARD: 'clipBoard',
|
||||
CANVAS: 'canvas'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,4 +324,9 @@ export const nodeDataNoStylePropList = [
|
||||
'resetRichText',
|
||||
'uid',
|
||||
'activeStyle'
|
||||
]
|
||||
]
|
||||
|
||||
// 数据缓存
|
||||
export const commonCaches = {
|
||||
measureCustomNodeContentSizeEl: null
|
||||
}
|
||||
@@ -79,10 +79,6 @@ export const defaultOpt = {
|
||||
},
|
||||
// 是否只有当鼠标在画布内才响应快捷键事件
|
||||
enableShortcutOnlyWhenMouseInSvg: true,
|
||||
// 是否开启节点动画过渡
|
||||
enableNodeTransitionMove: true,
|
||||
// 如果开启节点动画过渡,可以通过该属性设置过渡的时间,单位ms
|
||||
nodeTransitionMoveDuration: 300,
|
||||
// 初始根节点的位置
|
||||
initRootNodePosition: null,
|
||||
// 导出png、svg、pdf时的图形内边距
|
||||
|
||||
@@ -89,7 +89,7 @@ class Command {
|
||||
this.history.shift()
|
||||
}
|
||||
this.activeHistoryIndex = this.history.length - 1
|
||||
this.mindMap.emit('data_change', this.removeDataUid(data))
|
||||
this.mindMap.emit('data_change', data)
|
||||
this.mindMap.emit(
|
||||
'back_forward',
|
||||
this.activeHistoryIndex,
|
||||
@@ -110,7 +110,7 @@ class Command {
|
||||
this.history.length
|
||||
)
|
||||
let data = simpleDeepClone(this.history[this.activeHistoryIndex])
|
||||
this.mindMap.emit('data_change', this.removeDataUid(data))
|
||||
this.mindMap.emit('data_change', data)
|
||||
return data
|
||||
}
|
||||
}
|
||||
@@ -125,7 +125,7 @@ class Command {
|
||||
this.activeHistoryIndex += step
|
||||
this.mindMap.emit('back_forward', this.activeHistoryIndex, this.history.length)
|
||||
let data = simpleDeepClone(this.history[this.activeHistoryIndex])
|
||||
this.mindMap.emit('data_change', this.removeDataUid(data))
|
||||
this.mindMap.emit('data_change', data)
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,8 +57,11 @@ export default class KeyCommand {
|
||||
}
|
||||
Object.keys(this.shortcutMap).forEach(key => {
|
||||
if (this.checkKey(e, key)) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
// 粘贴事件不组织,因为要监听paste事件
|
||||
if (!this.checkKey(e, 'Control+v')) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}
|
||||
this.shortcutMap[key].forEach(fn => {
|
||||
fn()
|
||||
})
|
||||
|
||||
@@ -7,7 +7,13 @@ import Timeline from '../../layouts/Timeline'
|
||||
import VerticalTimeline from '../../layouts/VerticalTimeline'
|
||||
import Fishbone from '../../layouts/Fishbone'
|
||||
import TextEdit from './TextEdit'
|
||||
import { copyNodeTree, simpleDeepClone, walk } from '../../utils'
|
||||
import {
|
||||
copyNodeTree,
|
||||
simpleDeepClone,
|
||||
walk,
|
||||
bfsWalk,
|
||||
loadImage
|
||||
} from '../../utils'
|
||||
import { shapeList } from './node/Shape'
|
||||
import { lineStyleProps } from '../../themes/default'
|
||||
import { CONSTANTS } from '../../constants/constant'
|
||||
@@ -29,7 +35,7 @@ const layouts = {
|
||||
// 竖向时间轴
|
||||
[CONSTANTS.LAYOUT.VERTICAL_TIMELINE]: VerticalTimeline,
|
||||
// 鱼骨图
|
||||
[CONSTANTS.LAYOUT.FISHBONE]: Fishbone,
|
||||
[CONSTANTS.LAYOUT.FISHBONE]: Fishbone
|
||||
}
|
||||
|
||||
// 渲染
|
||||
@@ -42,7 +48,7 @@ class Render {
|
||||
this.themeConfig = this.mindMap.themeConfig
|
||||
this.draw = this.mindMap.draw
|
||||
// 渲染树,操作过程中修改的都是这里的数据
|
||||
this.renderTree = merge({}, this.mindMap.opt.data || {})
|
||||
this.renderTree = merge({}, simpleDeepClone(this.mindMap.opt.data) || {})
|
||||
// 是否重新渲染
|
||||
this.reRender = false
|
||||
// 是否正在渲染中
|
||||
@@ -60,6 +66,12 @@ class Render {
|
||||
this.root = null
|
||||
// 文本编辑框,需要再bindEvent之前实例化,否则单击事件只能触发隐藏文本编辑框,而无法保存文本修改
|
||||
this.textEdit = new TextEdit(this)
|
||||
// 当前复制的数据
|
||||
this.lastBeingCopyData = null
|
||||
this.beingCopyData = null
|
||||
this.beingPasteText = ''
|
||||
this.beingPasteImgSize = 0
|
||||
this.currentBeingPasteType = ''
|
||||
// 布局
|
||||
this.setLayout()
|
||||
// 绑定事件
|
||||
@@ -82,18 +94,24 @@ class Render {
|
||||
// 绑定事件
|
||||
bindEvent() {
|
||||
// 点击事件
|
||||
this.mindMap.on('draw_click', (e) => {
|
||||
this.mindMap.on('draw_click', e => {
|
||||
// 清除激活状态
|
||||
let isTrueClick = true
|
||||
let { useLeftKeySelectionRightKeyDrag } = this.mindMap.opt
|
||||
if (useLeftKeySelectionRightKeyDrag) {
|
||||
let mousedownPos = this.mindMap.event.mousedownPos
|
||||
isTrueClick = Math.abs(e.clientX - mousedownPos.x) <= 5 && Math.abs(e.clientY - mousedownPos.y) <= 5
|
||||
isTrueClick =
|
||||
Math.abs(e.clientX - mousedownPos.x) <= 5 &&
|
||||
Math.abs(e.clientY - mousedownPos.y) <= 5
|
||||
}
|
||||
if (isTrueClick && this.activeNodeList.length > 0) {
|
||||
this.mindMap.execCommand('CLEAR_ACTIVE_NODE')
|
||||
}
|
||||
})
|
||||
// 粘贴事件
|
||||
this.mindMap.on('paste', data => {
|
||||
this.onPaste(data)
|
||||
})
|
||||
}
|
||||
|
||||
// 注册命令
|
||||
@@ -195,6 +213,9 @@ class Render {
|
||||
// 设置节点形状
|
||||
this.setNodeShape = this.setNodeShape.bind(this)
|
||||
this.mindMap.command.add('SET_NODE_SHAPE', this.setNodeShape)
|
||||
// 定位节点
|
||||
this.goTargetNode = this.goTargetNode.bind(this)
|
||||
this.mindMap.command.add('GO_TARGET_NODE', this.goTargetNode)
|
||||
}
|
||||
|
||||
// 注册快捷键
|
||||
@@ -212,7 +233,7 @@ class Render {
|
||||
}
|
||||
this.mindMap.keyCommand.addShortcut('Enter', this.insertNodeWrap)
|
||||
// 插入概要
|
||||
this.mindMap.keyCommand.addShortcut('Control+s', this.addGeneralization)
|
||||
this.mindMap.keyCommand.addShortcut('Control+g', this.addGeneralization)
|
||||
// 展开/收起节点
|
||||
this.toggleActiveExpand = this.toggleActiveExpand.bind(this)
|
||||
this.mindMap.keyCommand.addShortcut('/', this.toggleActiveExpand)
|
||||
@@ -239,6 +260,14 @@ class Render {
|
||||
// 下移节点
|
||||
this.mindMap.keyCommand.addShortcut('Control+Down', this.downNode)
|
||||
// 复制节点、剪切节点、粘贴节点的快捷键需开发者自行注册实现,可参考demo
|
||||
this.copy = this.copy.bind(this)
|
||||
this.mindMap.keyCommand.addShortcut('Control+c', this.copy)
|
||||
this.mindMap.keyCommand.addShortcut('Control+v', () => {
|
||||
// 隐藏输入框可能会失去焦点,所以要重新聚焦
|
||||
this.textEdit.focusHiddenInput()
|
||||
})
|
||||
this.cut = this.cut.bind(this)
|
||||
this.mindMap.keyCommand.addShortcut('Control+x', this.cut)
|
||||
}
|
||||
|
||||
// 开启文字编辑,会禁用回车键和删除键相关快捷键防止冲突
|
||||
@@ -259,7 +288,6 @@ class Render {
|
||||
|
||||
// 渲染
|
||||
render(callback = () => {}, source) {
|
||||
let t = Date.now()
|
||||
// 如果当前还没有渲染完毕,不再触发渲染
|
||||
if (this.isRendering) {
|
||||
// 等待当前渲染完毕后再进行一次渲染
|
||||
@@ -279,7 +307,7 @@ class Render {
|
||||
// 计算布局
|
||||
this.layout.doLayout(root => {
|
||||
// 删除本次渲染时不再需要的节点
|
||||
Object.keys(this.lastNodeCache).forEach((uid) => {
|
||||
Object.keys(this.lastNodeCache).forEach(uid => {
|
||||
if (!this.nodeCache[uid]) {
|
||||
this.lastNodeCache[uid].destroy()
|
||||
if (this.lastNodeCache[uid].parent) {
|
||||
@@ -290,7 +318,7 @@ class Render {
|
||||
// 更新根节点
|
||||
this.root = root
|
||||
// 渲染节点
|
||||
const onEnd = () => {
|
||||
this.root.render(() => {
|
||||
this.isRendering = false
|
||||
this.mindMap.emit('node_tree_render_end')
|
||||
callback && callback()
|
||||
@@ -299,22 +327,13 @@ class Render {
|
||||
this.render(callback, source)
|
||||
} else {
|
||||
// 触发一次保存,因为修改了渲染树的数据
|
||||
if (this.mindMap.richText && [CONSTANTS.CHANGE_THEME, CONSTANTS.SET_DATA].includes(source)) {
|
||||
if (
|
||||
this.mindMap.richText &&
|
||||
[CONSTANTS.CHANGE_THEME, CONSTANTS.SET_DATA].includes(source)
|
||||
) {
|
||||
this.mindMap.command.addHistory()
|
||||
}
|
||||
}
|
||||
}
|
||||
let { enableNodeTransitionMove, nodeTransitionMoveDuration } =
|
||||
this.mindMap.opt
|
||||
this.root.render(() => {
|
||||
let dur = Date.now() - t
|
||||
if (enableNodeTransitionMove && dur <= nodeTransitionMoveDuration) {
|
||||
setTimeout(() => {
|
||||
onEnd()
|
||||
}, nodeTransitionMoveDuration - dur);
|
||||
} else {
|
||||
onEnd()
|
||||
}
|
||||
})
|
||||
})
|
||||
this.mindMap.emit('node_active', null, this.activeNodeList)
|
||||
@@ -416,7 +435,7 @@ class Render {
|
||||
// 规范指定节点数据
|
||||
formatAppointNodes(appointNodes) {
|
||||
if (!appointNodes) return []
|
||||
return Array.isArray(appointNodes) ? appointNodes: [appointNodes]
|
||||
return Array.isArray(appointNodes) ? appointNodes : [appointNodes]
|
||||
}
|
||||
|
||||
// 插入同级节点,多个节点只会操作第一个节点
|
||||
@@ -425,7 +444,11 @@ class Render {
|
||||
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
|
||||
return
|
||||
}
|
||||
let { defaultInsertSecondLevelNodeText, defaultInsertBelowSecondLevelNodeText } = this.mindMap.opt
|
||||
this.textEdit.hideEditTextBox()
|
||||
let {
|
||||
defaultInsertSecondLevelNodeText,
|
||||
defaultInsertBelowSecondLevelNodeText
|
||||
} = this.mindMap.opt
|
||||
let list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
|
||||
let first = list[0]
|
||||
if (first.isGeneralization) {
|
||||
@@ -434,16 +457,22 @@ class Render {
|
||||
if (first.isRoot) {
|
||||
this.insertChildNode(openEdit, appointNodes, appointData)
|
||||
} else {
|
||||
let text = first.layerIndex === 1 ? defaultInsertSecondLevelNodeText : defaultInsertBelowSecondLevelNodeText
|
||||
let text =
|
||||
first.layerIndex === 1
|
||||
? defaultInsertSecondLevelNodeText
|
||||
: defaultInsertBelowSecondLevelNodeText
|
||||
if (first.layerIndex === 1) {
|
||||
first.parent.destroy()
|
||||
}
|
||||
let index = this.getNodeIndex(first)
|
||||
let isRichText = !!this.mindMap.richText
|
||||
first.parent.nodeData.children.splice(index + 1, 0, {
|
||||
inserting: openEdit,
|
||||
data: {
|
||||
text: text,
|
||||
expand: true,
|
||||
richText: isRichText,
|
||||
resetRichText: isRichText,
|
||||
...(appointData || {})
|
||||
},
|
||||
children: []
|
||||
@@ -458,7 +487,11 @@ class Render {
|
||||
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
|
||||
return
|
||||
}
|
||||
let { defaultInsertSecondLevelNodeText, defaultInsertBelowSecondLevelNodeText } = this.mindMap.opt
|
||||
this.textEdit.hideEditTextBox()
|
||||
let {
|
||||
defaultInsertSecondLevelNodeText,
|
||||
defaultInsertBelowSecondLevelNodeText
|
||||
} = this.mindMap.opt
|
||||
let list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
|
||||
list.forEach(node => {
|
||||
if (node.isGeneralization) {
|
||||
@@ -467,12 +500,17 @@ class Render {
|
||||
if (!node.nodeData.children) {
|
||||
node.nodeData.children = []
|
||||
}
|
||||
let text = node.isRoot ? defaultInsertSecondLevelNodeText : defaultInsertBelowSecondLevelNodeText
|
||||
let text = node.isRoot
|
||||
? defaultInsertSecondLevelNodeText
|
||||
: defaultInsertBelowSecondLevelNodeText
|
||||
let isRichText = !!this.mindMap.richText
|
||||
node.nodeData.children.push({
|
||||
inserting: openEdit,
|
||||
data: {
|
||||
text: text,
|
||||
expand: true,
|
||||
richText: isRichText,
|
||||
resetRichText: isRichText,
|
||||
...(appointData || {})
|
||||
},
|
||||
children: []
|
||||
@@ -540,13 +578,81 @@ class Render {
|
||||
this.mindMap.render()
|
||||
}
|
||||
|
||||
// 复制节点
|
||||
copy() {
|
||||
this.beingCopyData = this.copyNode()
|
||||
}
|
||||
|
||||
// 剪切节点
|
||||
cut() {
|
||||
this.mindMap.execCommand('CUT_NODE', copyData => {
|
||||
this.beingCopyData = copyData
|
||||
})
|
||||
}
|
||||
|
||||
// 粘贴节点
|
||||
paste() {
|
||||
if (this.beingCopyData) {
|
||||
this.mindMap.execCommand('PASTE_NODE', this.beingCopyData)
|
||||
}
|
||||
}
|
||||
|
||||
// 粘贴事件
|
||||
async onPaste({ text, img }) {
|
||||
// 检查剪切板数据是否有变化
|
||||
// 通过图片大小来判断图片是否发生变化,可能是不准确的,但是目前没有其他好方法
|
||||
const imgSize = img ? img.size : 0
|
||||
if (this.beingPasteText !== text || this.beingPasteImgSize !== imgSize) {
|
||||
this.currentBeingPasteType = CONSTANTS.PASTE_TYPE.CLIP_BOARD
|
||||
this.beingPasteText = text
|
||||
this.beingPasteImgSize = imgSize
|
||||
}
|
||||
// 检查要粘贴的节点数据是否有变化,节点优先级高于剪切板
|
||||
if (this.lastBeingCopyData !== this.beingCopyData) {
|
||||
this.lastBeingCopyData = this.beingCopyData
|
||||
this.currentBeingPasteType = CONSTANTS.PASTE_TYPE.CANVAS
|
||||
}
|
||||
// 粘贴剪切板的数据
|
||||
if (this.currentBeingPasteType === CONSTANTS.PASTE_TYPE.CLIP_BOARD) {
|
||||
// 存在文本,则创建子节点
|
||||
if (text) {
|
||||
this.mindMap.execCommand('INSERT_CHILD_NODE', false, [], {
|
||||
text
|
||||
})
|
||||
}
|
||||
// 存在图片,则添加到当前激活节点
|
||||
if (img) {
|
||||
try {
|
||||
let imgData = await loadImage(img)
|
||||
if (this.activeNodeList.length > 0) {
|
||||
this.activeNodeList.forEach(node => {
|
||||
this.mindMap.execCommand('SET_NODE_IMAGE', node, {
|
||||
url: imgData.url,
|
||||
title: '',
|
||||
width: imgData.size.width,
|
||||
height: imgData.size.height
|
||||
})
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 粘贴节点数据
|
||||
this.paste()
|
||||
}
|
||||
}
|
||||
|
||||
// 将节点移动到另一个节点的前面
|
||||
insertBefore(node, exist) {
|
||||
if (node.isRoot) {
|
||||
return
|
||||
}
|
||||
// 如果是二级节点变成了下级节点,或是下级节点变成了二级节点,节点样式需要更新
|
||||
let nodeLayerChanged = (node.layerIndex === 1 && exist.layerIndex !== 1) || (node.layerIndex !== 1 && exist.layerIndex === 1)
|
||||
let nodeLayerChanged =
|
||||
(node.layerIndex === 1 && exist.layerIndex !== 1) ||
|
||||
(node.layerIndex !== 1 && exist.layerIndex === 1)
|
||||
// 移动节点
|
||||
let nodeParent = node.parent
|
||||
let nodeBorthers = nodeParent.children
|
||||
@@ -583,7 +689,9 @@ class Render {
|
||||
return
|
||||
}
|
||||
// 如果是二级节点变成了下级节点,或是下级节点变成了二级节点,节点样式需要更新
|
||||
let nodeLayerChanged = (node.layerIndex === 1 && exist.layerIndex !== 1) || (node.layerIndex !== 1 && exist.layerIndex === 1)
|
||||
let nodeLayerChanged =
|
||||
(node.layerIndex === 1 && exist.layerIndex !== 1) ||
|
||||
(node.layerIndex !== 1 && exist.layerIndex === 1)
|
||||
// 移动节点
|
||||
let nodeParent = node.parent
|
||||
let nodeBorthers = nodeParent.children
|
||||
@@ -623,7 +731,7 @@ class Render {
|
||||
}
|
||||
let isAppointNodes = appointNodes.length > 0
|
||||
let list = isAppointNodes ? appointNodes : this.activeNodeList
|
||||
let root = list.find((node) => {
|
||||
let root = list.find(node => {
|
||||
return node.isRoot
|
||||
})
|
||||
if (root) {
|
||||
@@ -709,7 +817,7 @@ class Render {
|
||||
|
||||
// 粘贴节点到节点
|
||||
pasteNode(data) {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
if (this.activeNodeList.length <= 0 || !data) {
|
||||
return
|
||||
}
|
||||
this.activeNodeList.forEach(item => {
|
||||
@@ -867,7 +975,8 @@ class Render {
|
||||
setNodeText(node, text, richText) {
|
||||
this.setNodeDataRender(node, {
|
||||
text,
|
||||
richText
|
||||
richText,
|
||||
resetRichText: richText
|
||||
})
|
||||
}
|
||||
|
||||
@@ -992,6 +1101,20 @@ class Render {
|
||||
})
|
||||
}
|
||||
|
||||
// 定位到指定节点
|
||||
goTargetNode(node, callback = () => {}) {
|
||||
let uid = typeof node === 'string' ? node : node.nodeData.data.uid
|
||||
if (!uid) return
|
||||
this.expandToNodeUid(uid, () => {
|
||||
let targetNode = this.findNodeByUid(uid)
|
||||
if (targetNode) {
|
||||
targetNode.active()
|
||||
this.moveNodeToCenter(targetNode)
|
||||
callback()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 更新节点数据
|
||||
setNodeData(node, data) {
|
||||
Object.keys(data).forEach(key => {
|
||||
@@ -1000,7 +1123,7 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置节点数据,并判断是否渲染
|
||||
setNodeDataRender(node, data) {
|
||||
setNodeDataRender(node, data, notRender = false) {
|
||||
this.setNodeData(node, data)
|
||||
let changed = node.reRender()
|
||||
if (changed) {
|
||||
@@ -1008,7 +1131,7 @@ class Render {
|
||||
// 概要节点
|
||||
node.generalizationBelongNode.updateGeneralization()
|
||||
}
|
||||
this.mindMap.render()
|
||||
if (!notRender) this.mindMap.render()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1028,6 +1151,44 @@ class Render {
|
||||
this.mindMap.view.translateY(offsetY)
|
||||
this.mindMap.view.setScale(1)
|
||||
}
|
||||
|
||||
// 展开到指定uid的节点
|
||||
expandToNodeUid(uid, callback = () => {}) {
|
||||
let parentsList = []
|
||||
const cache = {}
|
||||
bfsWalk(this.renderTree, (node, parent) => {
|
||||
if (node.data.uid === uid) {
|
||||
parentsList = parent ? [...cache[parent.data.uid], parent] : []
|
||||
return 'stop'
|
||||
} else {
|
||||
cache[node.data.uid] = parent ? [...cache[parent.data.uid], parent] : []
|
||||
}
|
||||
})
|
||||
let needRender = false
|
||||
parentsList.forEach(node => {
|
||||
if (!node.data.expand) {
|
||||
needRender = true
|
||||
node.data.expand = true
|
||||
}
|
||||
})
|
||||
if (needRender) {
|
||||
this.mindMap.render(callback)
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
// 根据uid找到对应的节点实例
|
||||
findNodeByUid(uid) {
|
||||
let res = null
|
||||
walk(this.root, null, node => {
|
||||
if (node.nodeData.data.uid === uid) {
|
||||
res = node
|
||||
return true
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
export default Render
|
||||
|
||||
@@ -10,11 +10,14 @@ export default class TextEdit {
|
||||
this.currentNode = null
|
||||
// 文本编辑框
|
||||
this.textEditNode = null
|
||||
// 隐藏的文本输入框
|
||||
this.hiddenInputEl = null
|
||||
// 文本编辑框是否显示
|
||||
this.showTextEdit = false
|
||||
// 如果编辑过程中缩放画布了,那么缓存当前编辑的内容
|
||||
this.cacheEditingText = ''
|
||||
this.bindEvent()
|
||||
this.createHiddenInput()
|
||||
}
|
||||
|
||||
// 事件
|
||||
@@ -46,6 +49,10 @@ export default class TextEdit {
|
||||
this.mindMap.on('before_node_active', () => {
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
// 节点激活事件
|
||||
this.mindMap.on('node_active', () => {
|
||||
this.focusHiddenInput()
|
||||
})
|
||||
// 注册编辑快捷键
|
||||
this.mindMap.keyCommand.addShortcut('F2', () => {
|
||||
if (this.renderer.activeNodeList.length <= 0) {
|
||||
@@ -56,12 +63,52 @@ export default class TextEdit {
|
||||
this.mindMap.on('scale', this.onScale)
|
||||
}
|
||||
|
||||
// 创建一个隐藏的文本输入框
|
||||
createHiddenInput() {
|
||||
if (this.hiddenInputEl) return
|
||||
this.hiddenInputEl = document.createElement('input')
|
||||
this.hiddenInputEl.type = 'text'
|
||||
this.hiddenInputEl.style.cssText = `
|
||||
position: fixed;
|
||||
left: -99999px;
|
||||
top: -99999px;
|
||||
`
|
||||
// 监听粘贴事件
|
||||
this.hiddenInputEl.addEventListener('paste', async event => {
|
||||
event.preventDefault()
|
||||
const text = (event.clipboardData || window.clipboardData).getData('text')
|
||||
const files = event.clipboardData.files
|
||||
let img = null
|
||||
if (files.length > 0) {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
if (/^image\//.test(files[i].type)) {
|
||||
img = files[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
this.mindMap.emit('paste', {
|
||||
text,
|
||||
img
|
||||
})
|
||||
})
|
||||
document.body.appendChild(this.hiddenInputEl)
|
||||
}
|
||||
|
||||
// 让隐藏的文本输入框聚焦
|
||||
focusHiddenInput() {
|
||||
if (this.hiddenInputEl) this.hiddenInputEl.focus()
|
||||
}
|
||||
|
||||
// 注册临时快捷键
|
||||
registerTmpShortcut() {
|
||||
// 注册回车快捷键
|
||||
this.mindMap.keyCommand.addShortcut('Enter', () => {
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
this.mindMap.keyCommand.addShortcut('Tab', () => {
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
}
|
||||
|
||||
// 显示文本编辑框
|
||||
@@ -96,7 +143,8 @@ export default class TextEdit {
|
||||
onScale() {
|
||||
if (!this.currentNode) return
|
||||
if (this.mindMap.richText) {
|
||||
this.mindMap.richText.cacheEditingText = this.mindMap.richText.getEditText()
|
||||
this.mindMap.richText.cacheEditingText =
|
||||
this.mindMap.richText.getEditText()
|
||||
this.mindMap.richText.showTextEdit = false
|
||||
} else {
|
||||
this.cacheEditingText = this.getEditText()
|
||||
@@ -124,7 +172,9 @@ export default class TextEdit {
|
||||
let scale = this.mindMap.view.scale
|
||||
let lineHeight = node.style.merge('lineHeight')
|
||||
let fontSize = node.style.merge('fontSize')
|
||||
let textLines = (this.cacheEditingText || node.nodeData.data.text).split(/\n/gim)
|
||||
let textLines = (this.cacheEditingText || node.nodeData.data.text).split(
|
||||
/\n/gim
|
||||
)
|
||||
let isMultiLine = node._textData.node.attr('data-ismultiLine') === 'true'
|
||||
node.style.domText(this.textEditNode, scale, isMultiLine)
|
||||
this.textEditNode.style.zIndex = this.mindMap.opt.nodeTextEditZIndex
|
||||
@@ -134,9 +184,12 @@ export default class TextEdit {
|
||||
this.textEditNode.style.left = rect.left + 'px'
|
||||
this.textEditNode.style.top = rect.top + 'px'
|
||||
this.textEditNode.style.display = 'block'
|
||||
this.textEditNode.style.maxWidth = this.mindMap.opt.textAutoWrapWidth * scale + 'px'
|
||||
this.textEditNode.style.maxWidth =
|
||||
this.mindMap.opt.textAutoWrapWidth * scale + 'px'
|
||||
if (isMultiLine && lineHeight !== 1) {
|
||||
this.textEditNode.style.transform = `translateY(${-((lineHeight * fontSize - fontSize) / 2) * scale}px)`
|
||||
this.textEditNode.style.transform = `translateY(${
|
||||
-((lineHeight * fontSize - fontSize) / 2) * scale
|
||||
}px)`
|
||||
}
|
||||
this.showTextEdit = true
|
||||
// 选中文本
|
||||
|
||||
@@ -365,8 +365,8 @@ class Node {
|
||||
this._unVisibleRectRegionNode.fill({
|
||||
color: 'transparent'
|
||||
})
|
||||
this.group.add(this._unVisibleRectRegionNode)
|
||||
}
|
||||
this.group.add(this._unVisibleRectRegionNode)
|
||||
this.renderer.layout.renderExpandBtnRect(this._unVisibleRectRegionNode, this.expandBtnSize, width, height, this)
|
||||
}
|
||||
}
|
||||
@@ -476,8 +476,6 @@ class Node {
|
||||
return
|
||||
}
|
||||
let {
|
||||
enableNodeTransitionMove,
|
||||
nodeTransitionMoveDuration,
|
||||
alwaysShowExpandBtn
|
||||
} = this.mindMap.opt
|
||||
if (alwaysShowExpandBtn) {
|
||||
@@ -503,13 +501,7 @@ class Node {
|
||||
let t = this.group.transform()
|
||||
// 如果节点位置没有变化,则返回
|
||||
if (this.left === t.translateX && this.top === t.translateY) return
|
||||
if (!isLayout && enableNodeTransitionMove) {
|
||||
this.group
|
||||
.animate(nodeTransitionMoveDuration)
|
||||
.translate(this.left - t.translateX, this.top - t.translateY)
|
||||
} else {
|
||||
this.group.translate(this.left - t.translateX, this.top - t.translateY)
|
||||
}
|
||||
this.group.translate(this.left - t.translateX, this.top - t.translateY)
|
||||
}
|
||||
|
||||
// 重新渲染节点,即重新创建节点内容、计算节点大小、计算节点内容布局、更新展开收起按钮,概要及位置
|
||||
@@ -531,8 +523,6 @@ class Node {
|
||||
|
||||
// 递归渲染
|
||||
render(callback = () => {}) {
|
||||
let { enableNodeTransitionMove, nodeTransitionMoveDuration } =
|
||||
this.mindMap.opt
|
||||
// 节点
|
||||
// 重新渲染连线
|
||||
this.renderLine()
|
||||
@@ -580,13 +570,7 @@ class Node {
|
||||
})
|
||||
)
|
||||
} else {
|
||||
if (enableNodeTransitionMove && !isLayout) {
|
||||
setTimeout(() => {
|
||||
callback()
|
||||
}, nodeTransitionMoveDuration)
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
callback()
|
||||
}
|
||||
// 手动插入的节点立即获得焦点并且开启编辑模式
|
||||
if (this.nodeData.inserting) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { measureText, resizeImgSize, getTextFromHtml } from '../../../utils'
|
||||
import { Image, SVG, A, G, Rect, Text, ForeignObject } from '@svgdotjs/svg.js'
|
||||
import iconsSvg from '../../../svg/icons'
|
||||
import { CONSTANTS } from '../../../constants/constant'
|
||||
import { CONSTANTS, commonCaches } from '../../../constants/constant'
|
||||
|
||||
// 创建图片节点
|
||||
function createImgNode() {
|
||||
@@ -293,20 +293,19 @@ function createNoteNode() {
|
||||
}
|
||||
|
||||
// 测量自定义节点内容元素的宽高
|
||||
let warpEl = null
|
||||
function measureCustomNodeContentSize (content) {
|
||||
if (!warpEl) {
|
||||
warpEl = document.createElement('div')
|
||||
warpEl.style.cssText = `
|
||||
if (!commonCaches.measureCustomNodeContentSizeEl) {
|
||||
commonCaches.measureCustomNodeContentSizeEl = document.createElement('div')
|
||||
commonCaches.measureCustomNodeContentSizeEl.style.cssText = `
|
||||
position: fixed;
|
||||
left: -99999px;
|
||||
top: -99999px;
|
||||
`
|
||||
this.mindMap.el.appendChild(warpEl)
|
||||
this.mindMap.el.appendChild(commonCaches.measureCustomNodeContentSizeEl)
|
||||
}
|
||||
warpEl.innerHTML = ''
|
||||
warpEl.appendChild(content)
|
||||
let rect = warpEl.getBoundingClientRect()
|
||||
commonCaches.measureCustomNodeContentSizeEl.innerHTML = ''
|
||||
commonCaches.measureCustomNodeContentSizeEl.appendChild(content)
|
||||
let rect = commonCaches.measureCustomNodeContentSizeEl.getBoundingClientRect()
|
||||
return {
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Node from './Node'
|
||||
import { createUid } from '../../../utils/index'
|
||||
|
||||
// 检查是否存在概要
|
||||
function checkHasGeneralization () {
|
||||
@@ -18,7 +19,7 @@ function createGeneralizationNode () {
|
||||
data: {
|
||||
data: this.nodeData.data.generalization
|
||||
},
|
||||
uid: this.mindMap.uid++,
|
||||
uid: createUid(),
|
||||
renderer: this.renderer,
|
||||
mindMap: this.mindMap,
|
||||
draw: this.draw,
|
||||
@@ -35,15 +36,14 @@ function createGeneralizationNode () {
|
||||
|
||||
// 更新概要节点
|
||||
function updateGeneralization () {
|
||||
if (this.isGeneralization) return
|
||||
this.removeGeneralization()
|
||||
this.createGeneralizationNode()
|
||||
}
|
||||
|
||||
// 渲染概要节点
|
||||
function renderGeneralization () {
|
||||
if (this.isGeneralization) {
|
||||
return
|
||||
}
|
||||
if (this.isGeneralization) return
|
||||
if (!this.checkHasGeneralization()) {
|
||||
this.removeGeneralization()
|
||||
this._generalizationNodeWidth = 0
|
||||
@@ -66,6 +66,7 @@ function renderGeneralization () {
|
||||
|
||||
// 删除概要节点
|
||||
function removeGeneralization () {
|
||||
if (this.isGeneralization) return
|
||||
if (this._generalizationLine) {
|
||||
this._generalizationLine.remove()
|
||||
this._generalizationLine = null
|
||||
@@ -86,6 +87,7 @@ function removeGeneralization () {
|
||||
|
||||
// 隐藏概要节点
|
||||
function hideGeneralization () {
|
||||
if (this.isGeneralization) return
|
||||
if (this._generalizationLine) {
|
||||
this._generalizationLine.hide()
|
||||
}
|
||||
@@ -96,6 +98,7 @@ function hideGeneralization () {
|
||||
|
||||
// 显示概要节点
|
||||
function showGeneralization () {
|
||||
if (this.isGeneralization) return
|
||||
if (this._generalizationLine) {
|
||||
this._generalizationLine.show()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Node from '../core/render/node/Node'
|
||||
import { CONSTANTS, initRootNodePositionMap } from '../constants/constant'
|
||||
import Lru from '../utils/Lru'
|
||||
import { createUid } from '../utils/index'
|
||||
|
||||
// 布局基类
|
||||
class Base {
|
||||
@@ -101,7 +102,7 @@ class Base {
|
||||
}
|
||||
} else {
|
||||
// 创建新节点
|
||||
let uid = this.mindMap.uid++
|
||||
let uid = data.data.uid || createUid()
|
||||
newNode = new Node({
|
||||
data,
|
||||
uid,
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import associativeLineControlsMethods from './associativeLine/associativeLineControls'
|
||||
import associativeLineTextMethods from './associativeLine/associativeLineText'
|
||||
|
||||
// 关联线类
|
||||
// 关联线插件
|
||||
class AssociativeLine {
|
||||
constructor(opt = {}) {
|
||||
this.mindMap = opt.mindMap
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { bfsWalk, throttle } from '../utils'
|
||||
import Base from '../layouts/Base'
|
||||
|
||||
// 节点拖动类
|
||||
|
||||
// 节点拖动插件
|
||||
class Drag extends Base {
|
||||
// 构造函数
|
||||
constructor({ mindMap }) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { imgToDataUrl, downloadFile, readBlob } from '../utils'
|
||||
import { imgToDataUrl, downloadFile, readBlob, removeHTMLEntities } from '../utils'
|
||||
import { SVG } from '@svgdotjs/svg.js'
|
||||
import drawBackgroundImageToCanvas from '../utils/simulateCSSBackgroundInCanvas'
|
||||
import { transformToMarkdown } from '../parse/toMarkdown'
|
||||
|
||||
// 导出类
|
||||
// 导出插件
|
||||
class Export {
|
||||
// 构造函数
|
||||
constructor(opt) {
|
||||
@@ -154,6 +154,7 @@ class Export {
|
||||
*/
|
||||
async png(name, transparent = false) {
|
||||
let { node, str } = await this.getSvgData()
|
||||
str = removeHTMLEntities(str)
|
||||
// 如果开启了富文本,则使用htmltocanvas转换为图片
|
||||
if (this.mindMap.richText) {
|
||||
let res = await this.mindMap.richText.handleExportPng(node.node)
|
||||
@@ -207,6 +208,7 @@ class Export {
|
||||
node.first().before(SVG(`<title>${name}</title>`))
|
||||
await this.drawBackgroundToSvg(node)
|
||||
let str = node.svg()
|
||||
str = removeHTMLEntities(str)
|
||||
// 转换成blob数据
|
||||
let blob = new Blob([str], {
|
||||
type: 'image/svg+xml'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import JsPDF from 'jspdf'
|
||||
|
||||
// 导出PDF类,需要通过Export插件使用
|
||||
// 导出PDF插件,需要通过Export插件使用
|
||||
class ExportPDF {
|
||||
// 构造函数
|
||||
constructor(opt) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import xmind from '../parse/xmind'
|
||||
|
||||
// 导出XMind类,需要通过Export插件使用
|
||||
// 导出XMind插件,需要通过Export插件使用
|
||||
class ExportXMind {
|
||||
// 构造函数
|
||||
constructor(opt) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { bfsWalk } from '../utils'
|
||||
import { CONSTANTS } from '../constants/constant'
|
||||
|
||||
// 键盘导航类
|
||||
// 键盘导航插件
|
||||
class KeyboardNavigation {
|
||||
// 构造函数
|
||||
constructor(opt) {
|
||||
@@ -28,8 +28,7 @@ class KeyboardNavigation {
|
||||
this.focus(dir)
|
||||
} else {
|
||||
let root = this.mindMap.renderer.root
|
||||
this.mindMap.renderer.moveNodeToCenter(root)
|
||||
root.active()
|
||||
this.mindMap.execCommand('GO_TARGET_NODE', root)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,8 +80,7 @@ class KeyboardNavigation {
|
||||
|
||||
// 找到了则让目标节点聚焦
|
||||
if (targetNode) {
|
||||
this.mindMap.renderer.moveNodeToCenter(targetNode)
|
||||
targetNode.active()
|
||||
this.mindMap.execCommand('GO_TARGET_NODE', targetNode)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// 小地图类
|
||||
// 小地图插件
|
||||
class MiniMap {
|
||||
// 构造函数
|
||||
constructor(opt) {
|
||||
|
||||
@@ -47,7 +47,7 @@ class NodeImgAdjust {
|
||||
// 节点图片鼠标移动事件
|
||||
onNodeImgMousemove(node, img) {
|
||||
// 如果当前正在拖动调整中那么直接返回
|
||||
if (this.isMousedown || this.isAdjusted) return
|
||||
if (this.isMousedown || this.isAdjusted || this.mindMap.opt.readonly) return
|
||||
// 如果在当前节点内移动,以及自定义元素已经是显示状态,那么直接返回
|
||||
if (this.node === node && this.isShowHandleEl) return
|
||||
// 更新当前节点信息
|
||||
|
||||
@@ -28,7 +28,7 @@ let fontSizeList = new Array(100).fill(0).map((_, index) => {
|
||||
return index + 'px'
|
||||
})
|
||||
|
||||
// 节点支持富文本编辑功能
|
||||
// 富文本编辑插件
|
||||
class RichText {
|
||||
constructor({ mindMap, pluginOpt }) {
|
||||
this.mindMap = mindMap
|
||||
@@ -268,6 +268,12 @@ class RichText {
|
||||
handler: function () {
|
||||
// 覆盖默认的回车键换行
|
||||
}
|
||||
},
|
||||
tab: {
|
||||
key: 9,
|
||||
handler: function () {
|
||||
// 覆盖默认的tab键
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
151
simple-mind-map/src/plugins/Search.js
Normal file
151
simple-mind-map/src/plugins/Search.js
Normal file
@@ -0,0 +1,151 @@
|
||||
import { bfsWalk, getTextFromHtml } from '../utils/index'
|
||||
|
||||
// 搜索插件
|
||||
class Search {
|
||||
// 构造函数
|
||||
constructor({ mindMap }) {
|
||||
this.mindMap = mindMap
|
||||
// 是否正在搜索
|
||||
this.isSearching = false
|
||||
// 搜索文本
|
||||
this.searchText = ''
|
||||
// 匹配的节点列表
|
||||
this.matchNodeList = []
|
||||
// 当前所在的节点列表索引
|
||||
this.currentIndex = -1
|
||||
// 不要复位搜索文本
|
||||
this.notResetSearchText = false
|
||||
this.onDataChange = this.onDataChange.bind(this)
|
||||
this.mindMap.on('data_change', this.onDataChange)
|
||||
}
|
||||
|
||||
// 节点数据改变了,需要重新搜索
|
||||
onDataChange() {
|
||||
if (this.notResetSearchText) {
|
||||
this.notResetSearchText = false
|
||||
return
|
||||
}
|
||||
this.searchText = ''
|
||||
}
|
||||
|
||||
// 搜索
|
||||
search(text, callback) {
|
||||
text = String(text).trim()
|
||||
if (!text) return this.endSearch()
|
||||
this.isSearching = true
|
||||
if (this.searchText === text) {
|
||||
// 和上一次搜索文本一样,那么搜索下一个
|
||||
this.searchNext(callback)
|
||||
} else {
|
||||
// 和上次搜索文本不一样,那么重新开始
|
||||
this.searchText = text
|
||||
this.doSearch()
|
||||
this.searchNext(callback)
|
||||
}
|
||||
this.emitEvent()
|
||||
}
|
||||
|
||||
// 结束搜索
|
||||
endSearch() {
|
||||
if (!this.isSearching) return
|
||||
this.searchText = ''
|
||||
this.matchNodeList = []
|
||||
this.currentIndex = -1
|
||||
this.notResetSearchText = false
|
||||
this.isSearching = false
|
||||
this.emitEvent()
|
||||
}
|
||||
|
||||
// 搜索匹配的节点
|
||||
doSearch() {
|
||||
this.matchNodeList = []
|
||||
this.currentIndex = -1
|
||||
bfsWalk(this.mindMap.renderer.root, node => {
|
||||
let { richText, text } = node.nodeData.data
|
||||
if (richText) {
|
||||
text = getTextFromHtml(text)
|
||||
}
|
||||
if (text.includes(this.searchText)) {
|
||||
this.matchNodeList.push(node)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 搜索下一个,定位到下一个匹配节点
|
||||
searchNext(callback) {
|
||||
if (!this.isSearching || this.matchNodeList.length <= 0) return
|
||||
if (this.currentIndex < this.matchNodeList.length - 1) {
|
||||
this.currentIndex++
|
||||
} else {
|
||||
this.currentIndex = 0
|
||||
}
|
||||
let currentNode = this.matchNodeList[this.currentIndex]
|
||||
this.notResetSearchText = true
|
||||
this.mindMap.execCommand('GO_TARGET_NODE', currentNode, () => {
|
||||
callback()
|
||||
})
|
||||
}
|
||||
|
||||
// 替换当前节点
|
||||
replace(replaceText) {
|
||||
replaceText = String(replaceText).trim()
|
||||
if (!replaceText || !this.isSearching || this.matchNodeList.length <= 0)
|
||||
return
|
||||
let currentNode = this.matchNodeList[this.currentIndex]
|
||||
if (!currentNode) return
|
||||
let text = this.getReplacedText(currentNode, this.searchText, replaceText)
|
||||
this.notResetSearchText = true
|
||||
currentNode.setText(text, currentNode.nodeData.data.richText)
|
||||
this.matchNodeList = this.matchNodeList.filter(node => {
|
||||
return currentNode !== node
|
||||
})
|
||||
if (this.currentIndex > this.matchNodeList.length - 1) {
|
||||
this.currentIndex = -1
|
||||
} else {
|
||||
this.currentIndex--
|
||||
}
|
||||
this.emitEvent()
|
||||
}
|
||||
|
||||
// 替换所有
|
||||
replaceAll(replaceText) {
|
||||
replaceText = String(replaceText).trim()
|
||||
if (!replaceText || !this.isSearching || this.matchNodeList.length <= 0)
|
||||
return
|
||||
this.matchNodeList.forEach(node => {
|
||||
let text = this.getReplacedText(node, this.searchText, replaceText)
|
||||
this.mindMap.renderer.setNodeDataRender(
|
||||
node,
|
||||
{
|
||||
text,
|
||||
resetRichText: !!node.nodeData.data.richText
|
||||
},
|
||||
true
|
||||
)
|
||||
})
|
||||
this.mindMap.render()
|
||||
this.mindMap.command.addHistory()
|
||||
this.endSearch()
|
||||
}
|
||||
|
||||
// 获取某个节点替换后的文本
|
||||
getReplacedText(node, searchText, replaceText) {
|
||||
let { richText, text } = node.nodeData.data
|
||||
if (richText) {
|
||||
text = getTextFromHtml(text)
|
||||
}
|
||||
return text.replaceAll(searchText, replaceText)
|
||||
}
|
||||
|
||||
// 发送事件
|
||||
emitEvent() {
|
||||
this.mindMap.emit('search_info_change', {
|
||||
currentIndex: this.currentIndex,
|
||||
total: this.matchNodeList.length
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Search.instanceName = 'search'
|
||||
|
||||
export default Search
|
||||
@@ -1,7 +1,6 @@
|
||||
import { bfsWalk, throttle } from '../utils'
|
||||
|
||||
// 选择节点类
|
||||
|
||||
// 节点选择插件
|
||||
class Select {
|
||||
// 构造函数
|
||||
constructor({ mindMap }) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// 手势事件支持类
|
||||
|
||||
// 手势事件支持插件
|
||||
class TouchEvent {
|
||||
// 构造函数
|
||||
constructor({ mindMap }) {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Text, G } from '@svgdotjs/svg.js'
|
||||
import { degToRad, camelCaseToHyphen } from '../utils'
|
||||
import merge from 'deepmerge'
|
||||
|
||||
// 水印类
|
||||
// 水印插件
|
||||
class Watermark {
|
||||
constructor(opt = {}) {
|
||||
this.mindMap = opt.mindMap
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
// 深度优先遍历树
|
||||
export const walk = (
|
||||
root,
|
||||
@@ -31,9 +33,11 @@ export const walk = (
|
||||
|
||||
// 广度优先遍历树
|
||||
export const bfsWalk = (root, callback) => {
|
||||
callback(root)
|
||||
let stack = [root]
|
||||
let isStop = false
|
||||
if (callback(root, null) === 'stop') {
|
||||
isStop = true
|
||||
}
|
||||
while (stack.length) {
|
||||
if (isStop) {
|
||||
break
|
||||
@@ -41,8 +45,9 @@ export const bfsWalk = (root, callback) => {
|
||||
let cur = stack.shift()
|
||||
if (cur.children && cur.children.length) {
|
||||
cur.children.forEach(item => {
|
||||
if (isStop) return
|
||||
stack.push(item)
|
||||
if (callback(item) === 'stop') {
|
||||
if (callback(item, cur) === 'stop') {
|
||||
isStop = true
|
||||
}
|
||||
})
|
||||
@@ -424,3 +429,40 @@ export const getImageSize = src => {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 创建节点唯一的id
|
||||
export const createUid = () => {
|
||||
return uuidv4()
|
||||
}
|
||||
|
||||
// 加载图片文件
|
||||
export const loadImage = imgFile => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let fr = new FileReader()
|
||||
fr.readAsDataURL(imgFile)
|
||||
fr.onload = async e => {
|
||||
let url = e.target.result
|
||||
let size = await getImageSize(url)
|
||||
resolve({
|
||||
url,
|
||||
size
|
||||
})
|
||||
}
|
||||
fr.onerror = error => {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 移除字符串中的html实体
|
||||
export const removeHTMLEntities = (str) => {
|
||||
[[' ', ' ']].forEach((item) => {
|
||||
str = str.replaceAll(item[0], item[1])
|
||||
})
|
||||
return str
|
||||
}
|
||||
|
||||
// 获取一个数据的类型
|
||||
export const getType = (data) => {
|
||||
return Object.prototype.toString.call(data).slice(7, -1)
|
||||
}
|
||||
BIN
web/src/assets/avatar/Chris.jpg
Normal file
BIN
web/src/assets/avatar/Chris.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
BIN
web/src/assets/avatar/仓鼠.jpg
Normal file
BIN
web/src/assets/avatar/仓鼠.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
BIN
web/src/assets/avatar/水车.jpg
Normal file
BIN
web/src/assets/avatar/水车.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 2479351 */
|
||||
src: url('iconfont.woff2?t=1689210173189') format('woff2'),
|
||||
url('iconfont.woff?t=1689210173189') format('woff'),
|
||||
url('iconfont.ttf?t=1689210173189') format('truetype');
|
||||
src: url('iconfont.woff2?t=1690506335310') format('woff2'),
|
||||
url('iconfont.woff?t=1690506335310') format('woff'),
|
||||
url('iconfont.ttf?t=1690506335310') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -13,6 +13,26 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.iconsousuo:before {
|
||||
content: "\e693";
|
||||
}
|
||||
|
||||
.iconjiantouyou:before {
|
||||
content: "\e62d";
|
||||
}
|
||||
|
||||
.iconbianji1:before {
|
||||
content: "\e60a";
|
||||
}
|
||||
|
||||
.icondaohang1:before {
|
||||
content: "\e632";
|
||||
}
|
||||
|
||||
.iconyanjing:before {
|
||||
content: "\e8bf";
|
||||
}
|
||||
|
||||
.iconwangzhan:before {
|
||||
content: "\e628";
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -210,7 +210,7 @@ export const shortcutKeyList = [
|
||||
{
|
||||
icon: 'icongaikuozonglan',
|
||||
name: 'Insert summary',
|
||||
value: 'Ctrl + S'
|
||||
value: 'Ctrl + G'
|
||||
},
|
||||
{
|
||||
icon: 'iconzhankai',
|
||||
@@ -271,6 +271,11 @@ export const shortcutKeyList = [
|
||||
icon: 'iconzhengli',
|
||||
name: 'Arrange layout',
|
||||
value: 'Ctrl + L'
|
||||
},
|
||||
{
|
||||
icon: 'iconsousuo',
|
||||
name: 'Search and Replace',
|
||||
value: 'Ctrl + F'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -270,7 +270,7 @@ export const shortcutKeyList = [
|
||||
{
|
||||
icon: 'icongaikuozonglan',
|
||||
name: '插入概要',
|
||||
value: 'Ctrl + S'
|
||||
value: 'Ctrl + G'
|
||||
},
|
||||
{
|
||||
icon: 'iconzhankai',
|
||||
@@ -331,6 +331,11 @@ export const shortcutKeyList = [
|
||||
icon: 'iconzhengli',
|
||||
name: '一键整理布局',
|
||||
value: 'Ctrl + L'
|
||||
},
|
||||
{
|
||||
icon: 'iconsousuo',
|
||||
name: '搜索和替换',
|
||||
value: 'Ctrl + F'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -114,8 +114,9 @@ export default {
|
||||
},
|
||||
navigatorToolbar: {
|
||||
openMiniMap: 'Open mini map',
|
||||
readonly: 'Readonly',
|
||||
edit: 'Edit'
|
||||
closeMiniMap: 'Close mini map',
|
||||
readonly: 'Change to eadonly',
|
||||
edit: 'Change to edit'
|
||||
},
|
||||
nodeHyperlink: {
|
||||
title: 'Link',
|
||||
@@ -209,5 +210,12 @@ export default {
|
||||
mouseAction: {
|
||||
tip1: 'Current: Left click to drag the canvas, right click to box select nodes',
|
||||
tip2: 'Current: Left click to box select nodes, right click to drag the canvas',
|
||||
},
|
||||
search: {
|
||||
searchPlaceholder: 'Please enter the search content',
|
||||
replacePlaceholder: 'Please enter replacement content',
|
||||
replace: 'Replace',
|
||||
replaceAll: 'Replace all',
|
||||
cancel: 'Cancel'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,8 +114,9 @@ export default {
|
||||
},
|
||||
navigatorToolbar: {
|
||||
openMiniMap: '开启小地图',
|
||||
readonly: '只读模式',
|
||||
edit: '编辑模式'
|
||||
closeMiniMap: '关闭小地图',
|
||||
readonly: '切换为只读模式',
|
||||
edit: '切换为编辑模式'
|
||||
},
|
||||
nodeHyperlink: {
|
||||
title: '超链接',
|
||||
@@ -209,5 +210,12 @@ export default {
|
||||
mouseAction: {
|
||||
tip1: '当前:左键拖动画布,右键框选节点',
|
||||
tip2: '当前:左键框选节点,右键拖动画布',
|
||||
},
|
||||
search: {
|
||||
searchPlaceholder: '请输入查找内容',
|
||||
replacePlaceholder: '请输入替换内容',
|
||||
replace: '替换',
|
||||
replaceAll: '全部替换',
|
||||
cancel: '取消'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ let langList = [
|
||||
}
|
||||
]
|
||||
let StartList = ['introduction', 'start', 'deploy', 'client', 'translate', 'changelog']
|
||||
let CourseList = new Array(20).fill(0).map((_, index) => {
|
||||
let CourseList = new Array(21).fill(0).map((_, index) => {
|
||||
return 'course' + (index + 1)
|
||||
})
|
||||
let APIList = [
|
||||
@@ -32,10 +32,14 @@ let APIList = [
|
||||
'associativeLine',
|
||||
'touchEvent',
|
||||
'nodeImgAdjust',
|
||||
'search',
|
||||
'xmind',
|
||||
'markdown',
|
||||
'utils'
|
||||
]
|
||||
let helpList = new Array(2).fill(0).map((_, index) => {
|
||||
return 'help' + (index + 1)
|
||||
})
|
||||
|
||||
const createList = (lang, list) => {
|
||||
let langRouter = routerList.find(item => {
|
||||
@@ -62,28 +66,39 @@ export default {
|
||||
zh: [
|
||||
{
|
||||
groupName: '开始',
|
||||
type: 'doc',
|
||||
list: createList('zh', StartList)
|
||||
},
|
||||
{
|
||||
groupName: '教程',
|
||||
type: 'doc',
|
||||
list: createList('zh', CourseList)
|
||||
},
|
||||
{
|
||||
groupName: 'API',
|
||||
type: 'doc',
|
||||
list: createList('zh', APIList)
|
||||
},
|
||||
{
|
||||
groupName: '使用帮助',
|
||||
type: 'help',
|
||||
list: createList('zh', helpList)
|
||||
}
|
||||
],
|
||||
en: [
|
||||
{
|
||||
groupName: 'Start',
|
||||
type: 'doc',
|
||||
list: createList('en', StartList)
|
||||
},
|
||||
{
|
||||
groupName: 'Course',
|
||||
type: 'doc',
|
||||
list: createList('zh', CourseList)
|
||||
},
|
||||
{
|
||||
groupName: 'API',
|
||||
type: 'doc',
|
||||
list: createList('en', APIList)
|
||||
}
|
||||
]
|
||||
|
||||
@@ -66,18 +66,18 @@ export default {
|
||||
methods: {
|
||||
// 获取当前语言
|
||||
initLang() {
|
||||
let lang = /^\/doc\/([^\/]+)\//.exec(this.$route.path)
|
||||
if (lang && lang[1]) {
|
||||
this.lang = lang[1]
|
||||
let lang = /^\/(doc|help)\/([^\/]+)\//.exec(this.$route.path)
|
||||
if (lang && lang[2]) {
|
||||
this.lang = lang[2]
|
||||
}
|
||||
},
|
||||
|
||||
// 初始化二级标题目录
|
||||
initCatalogList(newPath, oldPath) {
|
||||
let newPathRes = /^\/doc\/[^\/]+\/([^\/]+)/.exec(newPath)
|
||||
let oldPathRes = /^\/doc\/[^\/]+\/([^\/]+)/.exec(oldPath)
|
||||
let newPathRes = /^\/(doc|help)\/[^\/]+\/([^\/]+)/.exec(newPath)
|
||||
let oldPathRes = /^\/(doc|help)\/[^\/]+\/([^\/]+)/.exec(oldPath)
|
||||
// 语言变了、章节变了,需要重新获取二级标题目录
|
||||
if ((!newPath && !oldPath) || newPathRes[1] !== oldPathRes[1]) {
|
||||
if ((!newPath && !oldPath) || newPathRes[2] !== oldPathRes[2]) {
|
||||
this.$emit('scroll', 0)
|
||||
this.resetActive()
|
||||
let container = document.getElementById('doc')
|
||||
@@ -93,9 +93,9 @@ export default {
|
||||
|
||||
// 如果url中存在二级标题,那么滚动到该标题所在位置
|
||||
scrollToCatalog() {
|
||||
let url = /^\/doc\/[^\/]+\/[^\/]+\/([^\/]+)($|\/)/.exec(this.$route.path)
|
||||
if (url && url[1]) {
|
||||
let h = decodeURIComponent(url[1])
|
||||
let url = /^\/(doc|help)\/[^\/]+\/[^\/]+\/([^\/]+)($|\/)/.exec(this.$route.path)
|
||||
if (url && url[2]) {
|
||||
let h = decodeURIComponent(url[2])
|
||||
let item = this.list.find(item => {
|
||||
return item.title === h
|
||||
})
|
||||
@@ -126,15 +126,15 @@ export default {
|
||||
let path = this.$route.path
|
||||
let url = ''
|
||||
if (!title) {
|
||||
url = path.replace(/^(\/doc\/[^\/]+\/[^\/]+)($|\/|.*)$/, '$1')
|
||||
} else if (/^\/doc\/[^\/]+\/[^\/]+($|\/)$/.test(path)) {
|
||||
url = path.replace(/^(\/(doc|help)\/[^\/]+\/[^\/]+)($|\/|.*)$/, '$1')
|
||||
} else if (/^\/(doc|help)\/[^\/]+\/[^\/]+($|\/)$/.test(path)) {
|
||||
url = path.replace(
|
||||
/^(\/doc\/[^\/]+\/[^\/]+)($|\/)$/,
|
||||
/^(\/(doc|help)\/[^\/]+\/[^\/]+)($|\/)$/,
|
||||
'$1/' + encodeURIComponent(title)
|
||||
)
|
||||
} else {
|
||||
url = path.replace(
|
||||
/^(\/doc\/[^\/]+\/[^\/]+\/)([^\/]+)($|\/)/,
|
||||
/^(\/(doc|help)\/[^\/]+\/[^\/]+\/)([^\/]+)($|\/)/,
|
||||
(...args) => {
|
||||
return args[1] + encodeURIComponent(title)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="headerContainer">
|
||||
<div class="left">
|
||||
<div class="title">
|
||||
<div class="title" @click="toIndex">
|
||||
<img src="../../../assets/img/logo2.png" alt="">
|
||||
SimpleMindMap
|
||||
</div>
|
||||
@@ -74,6 +74,10 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
toIndex() {
|
||||
this.$router.push('/index')
|
||||
},
|
||||
|
||||
toDemo() {
|
||||
this.$router.push('/')
|
||||
},
|
||||
@@ -108,6 +112,7 @@ export default {
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
img {
|
||||
width: 30px;
|
||||
|
||||
@@ -31,7 +31,8 @@ export default {
|
||||
return {
|
||||
groupList: [],
|
||||
lang: '',
|
||||
currentPath: ''
|
||||
currentPath: '',
|
||||
type: ''// doc、help
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@@ -47,20 +48,24 @@ export default {
|
||||
if (item.path === this.currentPath) {
|
||||
return
|
||||
}
|
||||
this.$router.push(`/doc/${this.lang}/${item.path}`)
|
||||
this.$router.push(`/${this.type}/${this.lang}/${item.path}`)
|
||||
},
|
||||
|
||||
initCatalog() {
|
||||
// 目录列表
|
||||
let lang = /^\/doc\/([^\/]+)\//.exec(this.$route.path)
|
||||
if (lang && lang[1]) {
|
||||
this.lang = lang[1]
|
||||
this.groupList = catalogList[this.lang]
|
||||
let lang = /^\/(doc|help)\/([^\/]+)\//.exec(this.$route.path)
|
||||
if (lang && lang[2]) {
|
||||
this.type = lang[1]// 判断是开发文档还是帮助文档
|
||||
this.lang = lang[2]
|
||||
// 过滤出对应文档的章节
|
||||
this.groupList = catalogList[this.lang].filter((item) => {
|
||||
return item.type === this.type
|
||||
})
|
||||
}
|
||||
// 当前所在路径
|
||||
let path = /^\/doc\/[^\/]+\/([^\/]+)(\/|$)/.exec(this.$route.path)
|
||||
if (path && path[1]) {
|
||||
this.currentPath = path[1]
|
||||
let path = /^\/(doc|help)\/[^\/]+\/([^\/]+)(\/|$)/.exec(this.$route.path)
|
||||
if (path && path[2]) {
|
||||
this.currentPath = path[2]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,37 @@
|
||||
# Changelog
|
||||
|
||||
## 0.6.9-fix.1
|
||||
|
||||
Fix: 1.Fix the issue of incorrect replacement after a single search.
|
||||
|
||||
New: 1.We will no longer directly modify the incoming data object, but will make a deep copy internally.
|
||||
|
||||
## 0.6.9
|
||||
|
||||
Fix: 1.Fixed an issue where setting styles to summary nodes would cause summary nodes to disappear. 2.Fixed the issue of node content not rendering when creating a root instance again when customizing node content. 3.Fix the issue of losing focus when adding a new node while the node is in editing. 2.Fix the issue of continuously pressing the tab key not being able to continuously create child nodes.
|
||||
|
||||
New: 1.Replace existing ` ` in SVG when exporting Characters to avoid exporting SVG errors. 2.Support for search and replace.
|
||||
|
||||
Demo: 1.When switching themes, it is supported to choose whether to overwrite the set basic style.
|
||||
|
||||
## 0.6.8
|
||||
|
||||
Fix: 1.Change the shortcut key for inserting a summary to Ctrl+G to avoid conflicts with the save shortcut key. 2.Fix the issue of abnormal switching between rich text editing configuration input boxes while nodes are being edited.
|
||||
|
||||
New: 1.Modify the copy, cut, and paste logic, and support pasting data from the clipboard.
|
||||
|
||||
Demo: 1.Fix the issue of not saving the outer margin of the basic style setting node. 2.Supports automatic switching to dark mode based on the theme.
|
||||
|
||||
## 0.6.7
|
||||
|
||||
Fix: 1.Fixed the issue of missing placeholder elements for the expand and collapse button after node collapse and expansion. 2.Fixed the issue of being able to scale images in read-only mode.
|
||||
|
||||
New: 1.Support locating to a node based on node instance or node uid. 2.Modify the creation method of node uids and export data to add node uids.
|
||||
|
||||
Remove: 1.Remove the node transition effect.
|
||||
|
||||
Demo: 1.Add website homepage. 2.Fixed the issue of missing node styles when creating new nodes in the outline. 3.Fixed the issue of missing edited text after pressing Enter or Tab after editing nodes in the outline. 4.Optimize the node positioning of the outline, and the collapsed nodes will automatically expand. 5.The sidebar button supports folding. 6.Optimize small screen adaptation.
|
||||
|
||||
## 0.6.6
|
||||
|
||||
New: 1.Support exporting to Xmind new version files. 2.Importing the new version of Xmind file supports importing images from nodes. 3.Add a vertical timeline structure.
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Changelog</h1>
|
||||
<h2>0.6.9-fix.1</h2>
|
||||
<p>Fix: 1.Fix the issue of incorrect replacement after a single search.</p>
|
||||
<p>New: 1.We will no longer directly modify the incoming data object, but will make a deep copy internally.</p>
|
||||
<h2>0.6.9</h2>
|
||||
<p>Fix: 1.Fixed an issue where setting styles to summary nodes would cause summary nodes to disappear. 2.Fixed the issue of node content not rendering when creating a root instance again when customizing node content. 3.Fix the issue of losing focus when adding a new node while the node is in editing. 2.Fix the issue of continuously pressing the tab key not being able to continuously create child nodes.</p>
|
||||
<p>New: 1.Replace existing <code>&nbsp;</code> in SVG when exporting Characters to avoid exporting SVG errors. 2.Support for search and replace.</p>
|
||||
<p>Demo: 1.When switching themes, it is supported to choose whether to overwrite the set basic style.</p>
|
||||
<h2>0.6.8</h2>
|
||||
<p>Fix: 1.Change the shortcut key for inserting a summary to Ctrl+G to avoid conflicts with the save shortcut key. 2.Fix the issue of abnormal switching between rich text editing configuration input boxes while nodes are being edited.</p>
|
||||
<p>New: 1.Modify the copy, cut, and paste logic, and support pasting data from the clipboard.</p>
|
||||
<p>Demo: 1.Fix the issue of not saving the outer margin of the basic style setting node. 2.Supports automatic switching to dark mode based on the theme.</p>
|
||||
<h2>0.6.7</h2>
|
||||
<p>Fix: 1.Fixed the issue of missing placeholder elements for the expand and collapse button after node collapse and expansion. 2.Fixed the issue of being able to scale images in read-only mode.</p>
|
||||
<p>New: 1.Support locating to a node based on node instance or node uid. 2.Modify the creation method of node uids and export data to add node uids.</p>
|
||||
<p>Remove: 1.Remove the node transition effect.</p>
|
||||
<p>Demo: 1.Add website homepage. 2.Fixed the issue of missing node styles when creating new nodes in the outline. 3.Fixed the issue of missing edited text after pressing Enter or Tab after editing nodes in the outline. 4.Optimize the node positioning of the outline, and the collapsed nodes will automatically expand. 5.The sidebar button supports folding. 6.Optimize small screen adaptation.</p>
|
||||
<h2>0.6.6</h2>
|
||||
<p>New: 1.Support exporting to Xmind new version files. 2.Importing the new version of Xmind file supports importing images from nodes. 3.Add a vertical timeline structure.</p>
|
||||
<p>Fix: 1.The TouchEvent plugin no longer sends click events, solving the problem of two windows opening when clicking on a hyperlink on the mobile end. 2.Fix the issue of dragging and moving a node to become a child node of another node, where the parent node of that node points to not being updated. 3.Fixed an issue where the node border style was not updated when dragging a second level node into a third level node. 4.Fix the issue where the mouse will not trigger the button display when moving into the unfolded or retracted button position, except for the structure growing to the right.</p>
|
||||
|
||||
@@ -51,8 +51,8 @@ const mindMap = new MindMap({
|
||||
| expandBtnStyle(v0.5.0+) | Object | { color: '#808080', fill: '#fff' } | Expand the color of the stow button | |
|
||||
| expandBtnIcon(v0.5.0+) | Object | { open: '', close: '' } | Customize the icon of the expand/collapse button, and you can transfer the svg string of the icon | |
|
||||
| enableShortcutOnlyWhenMouseInSvg(v0.5.1+) | Boolean | true | Only respond to shortcut key events when the mouse is inside the canvas | |
|
||||
| enableNodeTransitionMove(v0.5.1+) | Boolean | true | Whether to enable node animation transition | |
|
||||
| nodeTransitionMoveDuration(v0.5.1+) | Number | 300 | If node animation transition is enabled, the transition time can be set using this attribute, in milliseconds | |
|
||||
| enableNodeTransitionMove(v0.5.1+)(v0.6.7+ is remove this feature) | Boolean | true | Whether to enable node animation transition | |
|
||||
| nodeTransitionMoveDuration(v0.5.1+)(v0.6.7+ is remove this feature) | Number | 300 | If node animation transition is enabled, the transition time can be set using this attribute, in milliseconds | |
|
||||
| initRootNodePosition(v0.5.3+) | Array | null | The position of the initial root node can be passed as an array, default is `['center', 'center']`, Represents the root node at the center of the canvas, In addition to `center`, keywords can also be set to `left`, `top`, `right`, and `bottom`, In addition to passing keywords, each item in the array can also pass a number representing a specific pixel, Can pass a percentage string, such as `['40%', '60%']`, Represents a horizontal position at `40%` of the canvas width, and a vertical position at `60%` of the canvas height | |
|
||||
| exportPaddingX(v0.5.5+) | Number | 10 | Horizontal padding of graphics when exporting PNG, SVG, and PDF | |
|
||||
| exportPaddingY(v0.5.5+) | Number | 10 | Vertical padding of graphics when exporting PNG, SVG, and PDF | |
|
||||
@@ -347,6 +347,7 @@ redo. All commands are as follows:
|
||||
| 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: [Shape.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/core/render/node/Shape.js)) |
|
||||
| GO_TARGET_NODE(v0.6.7+) | Navigate to a node, and if the node is collapsed, it will automatically expand to that node | node(Node instance or node uid to locate)、callback(v0.6.9+, Callback function after positioning completion) |
|
||||
|
||||
### setData(data)
|
||||
|
||||
|
||||
@@ -218,14 +218,14 @@
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>enableNodeTransitionMove(v0.5.1+)</td>
|
||||
<td>enableNodeTransitionMove(v0.5.1+)(v0.6.7+ is remove this feature)</td>
|
||||
<td>Boolean</td>
|
||||
<td>true</td>
|
||||
<td>Whether to enable node animation transition</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>nodeTransitionMoveDuration(v0.5.1+)</td>
|
||||
<td>nodeTransitionMoveDuration(v0.5.1+)(v0.6.7+ is remove this feature)</td>
|
||||
<td>Number</td>
|
||||
<td>300</td>
|
||||
<td>If node animation transition is enabled, the transition time can be set using this attribute, in milliseconds</td>
|
||||
@@ -920,6 +920,11 @@ redo. All commands are as follows:</p>
|
||||
<td>Set the shape of a node</td>
|
||||
<td>node (the node to set), shape (the shape, all shapes: <a href="https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/core/render/node/Shape.js">Shape.js</a>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>GO_TARGET_NODE(v0.6.7+)</td>
|
||||
<td>Navigate to a node, and if the node is collapsed, it will automatically expand to that node</td>
|
||||
<td>node(Node instance or node uid to locate)、callback(v0.6.9+, Callback function after positioning completion)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>setData(data)</h3>
|
||||
|
||||
@@ -1 +1,66 @@
|
||||
# Deploy
|
||||
# Deploy
|
||||
|
||||
The 'web' directory of this project provides a complete project developed based on the 'simple mind map' library, 'Vue2. x', and 'ElementUI'. The data is stored locally on the computer by default, and can also be manipulated locally on the computer. Originally intended as an online 'demo', it can also be directly used as an online version of the mind map application, online address: [https://wanglin2.github.io/mind-map/](https://wanglin2.github.io/mind-map/).
|
||||
|
||||
If your network environment is slow to access the 'GitHub' service, you can also deploy it to your server.
|
||||
|
||||
## Deploying to a static file server
|
||||
|
||||
The project itself does not rely on the backend, so it can be deployed to a static file server. The following commands can be executed in sequence:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/wanglin2/mind-map.git
|
||||
cd mind-map
|
||||
cd simple-mind-map
|
||||
npm i
|
||||
npm link
|
||||
cd ..
|
||||
cd web
|
||||
npm i
|
||||
npm link simple-mind-map
|
||||
```
|
||||
|
||||
Then you can choose to start the local service:
|
||||
|
||||
```bash
|
||||
npm run serve
|
||||
```
|
||||
|
||||
You can also directly package and generate construction products:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
The packaged entry page 'index.html' can be found in the project root directory, and the corresponding static resources are located in the 'dist' directory under the root directory. The 'html' file will access the resources in the 'dist' directory through relative paths, such as 'dist/xxx'. You can directly upload these two files or directories to your static file server. In fact, this project is deployed to 'GitHub Pages' in this way.
|
||||
|
||||
If you do not have any code modification requirements, it is also possible to directly copy these files from this repository.
|
||||
|
||||
If you want to package 'index.html' into the 'dist' directory as well, you can modify the 'scripts.build' command in the 'web/package.json' file to delete '&& node ../copy.js' from 'vue-cli-service build && node ../copy.js'.
|
||||
|
||||
If you want to modify the directory for packaging output, you can modify the 'outputDir' configuration of the 'web/vue.config.js' file to the path you want to output.
|
||||
|
||||
If you want to modify the path of the 'index. html' file referencing static resources, you can modify the 'publicPath' configuration of the 'web/vue.config.js' file.
|
||||
|
||||
In addition, the default route used is 'hash ', which means that there will be '#'in the path. If you want to use the 'history' route, you can modify the 'web/src/router.js' file to:
|
||||
|
||||
```js
|
||||
const router = new VueRouter({
|
||||
routes
|
||||
})
|
||||
```
|
||||
|
||||
Change to:
|
||||
|
||||
```js
|
||||
const router = new VueRouter({
|
||||
mode: 'history',
|
||||
routes
|
||||
})
|
||||
```
|
||||
|
||||
However, this requires backend support, as our application is a single page client application. If the backend is not properly configured, users will return 404 when accessing sub routes directly in the browser. Therefore, you need to add a candidate resource on the server that covers all situations: if the 'URL' cannot match any static resources, the same 'index. html' page should be returned.
|
||||
|
||||
## Docker
|
||||
|
||||
In writing...
|
||||
55
web/src/pages/Doc/en/deploy/index.vue
Normal file
55
web/src/pages/Doc/en/deploy/index.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Deploy</h1>
|
||||
<p>The 'web' directory of this project provides a complete project developed based on the 'simple mind map' library, 'Vue2. x', and 'ElementUI'. The data is stored locally on the computer by default, and can also be manipulated locally on the computer. Originally intended as an online 'demo', it can also be directly used as an online version of the mind map application, online address: <a href="https://wanglin2.github.io/mind-map/">https://wanglin2.github.io/mind-map/</a>.</p>
|
||||
<p>If your network environment is slow to access the 'GitHub' service, you can also deploy it to your server.</p>
|
||||
<h2>Deploying to a static file server</h2>
|
||||
<p>The project itself does not rely on the backend, so it can be deployed to a static file server. The following commands can be executed in sequence:</p>
|
||||
<pre class="hljs"><code>git <span class="hljs-built_in">clone</span> https://github.com/wanglin2/mind-map.git
|
||||
<span class="hljs-built_in">cd</span> mind-map
|
||||
<span class="hljs-built_in">cd</span> simple-mind-map
|
||||
npm i
|
||||
npm link
|
||||
<span class="hljs-built_in">cd</span> ..
|
||||
<span class="hljs-built_in">cd</span> web
|
||||
npm i
|
||||
npm link simple-mind-map
|
||||
</code></pre>
|
||||
<p>Then you can choose to start the local service:</p>
|
||||
<pre class="hljs"><code>npm run serve
|
||||
</code></pre>
|
||||
<p>You can also directly package and generate construction products:</p>
|
||||
<pre class="hljs"><code>npm run build
|
||||
</code></pre>
|
||||
<p>The packaged entry page 'index.html' can be found in the project root directory, and the corresponding static resources are located in the 'dist' directory under the root directory. The 'html' file will access the resources in the 'dist' directory through relative paths, such as 'dist/xxx'. You can directly upload these two files or directories to your static file server. In fact, this project is deployed to 'GitHub Pages' in this way.</p>
|
||||
<p>If you do not have any code modification requirements, it is also possible to directly copy these files from this repository.</p>
|
||||
<p>If you want to package 'index.html' into the 'dist' directory as well, you can modify the 'scripts.build' command in the 'web/package.json' file to delete '&& node ../copy.js' from 'vue-cli-service build && node ../copy.js'.</p>
|
||||
<p>If you want to modify the directory for packaging output, you can modify the 'outputDir' configuration of the 'web/vue.config.js' file to the path you want to output.</p>
|
||||
<p>If you want to modify the path of the 'index. html' file referencing static resources, you can modify the 'publicPath' configuration of the 'web/vue.config.js' file.</p>
|
||||
<p>In addition, the default route used is 'hash ', which means that there will be '#'in the path. If you want to use the 'history' route, you can modify the 'web/src/router.js' file to:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">const</span> router = <span class="hljs-keyword">new</span> VueRouter({
|
||||
routes
|
||||
})
|
||||
</code></pre>
|
||||
<p>Change to:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">const</span> router = <span class="hljs-keyword">new</span> VueRouter({
|
||||
<span class="hljs-attr">mode</span>: <span class="hljs-string">'history'</span>,
|
||||
routes
|
||||
})
|
||||
</code></pre>
|
||||
<p>However, this requires backend support, as our application is a single page client application. If the backend is not properly configured, users will return 404 when accessing sub routes directly in the browser. Therefore, you need to add a candidate resource on the server that covers all situations: if the 'URL' cannot match any static resources, the same 'index. html' page should be returned.</p>
|
||||
<h2>Docker</h2>
|
||||
<p>In writing...</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -156,4 +156,16 @@ Open source is not easy. If this project is helpful to you, you can invite the a
|
||||
<img src="../../../../assets/avatar/suka.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>suka</p>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
|
||||
<img src="../../../../assets/avatar/Chris.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>Chris</p>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
|
||||
<img src="../../../../assets/avatar/水车.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>水车</p>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
|
||||
<img src="../../../../assets/avatar/仓鼠.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>仓鼠</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -115,6 +115,18 @@ full screen, support mini map</li>
|
||||
<img src="../../../../assets/avatar/suka.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>suka</p>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
|
||||
<img src="../../../../assets/avatar/Chris.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>Chris</p>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
|
||||
<img src="../../../../assets/avatar/水车.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>水车</p>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
|
||||
<img src="../../../../assets/avatar/仓鼠.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>仓鼠</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -61,7 +61,9 @@ Delete a specific node
|
||||
Copy a node, the active node is the node to be operated on, if there are
|
||||
multiple active nodes, only the first node will be operated on
|
||||
|
||||
### setNodeDataRender(node, data)
|
||||
### setNodeDataRender(node, data, notRender)
|
||||
|
||||
- `notRender`: v0.6.9+, `Boolean`, Default is `false`, Do not trigger rendering.
|
||||
|
||||
Set node `data`, i.e. the data in the data field, and will determine whether the
|
||||
node needs to be re-rendered based on whether the node size has changed, `data`
|
||||
@@ -91,4 +93,40 @@ Move a node behind another node
|
||||
|
||||
Move a node to the center of the canvas.
|
||||
|
||||
Currently, if there is zoom, returning to the center will reset the zoom.
|
||||
Currently, if there is zoom, returning to the center will reset the zoom.
|
||||
|
||||
### expandToNodeUid(uid, callback)
|
||||
|
||||
> v0.6.7+
|
||||
|
||||
- `uid`: uid of node
|
||||
|
||||
- `callback`: Expand completed callback function
|
||||
|
||||
Expand to the node of the specified uid.
|
||||
|
||||
### findNodeByUid(uid)
|
||||
|
||||
> v0.6.7+
|
||||
|
||||
- `uid`: uid of node
|
||||
|
||||
Find the corresponding node instance based on the uid.
|
||||
|
||||
### copy()
|
||||
|
||||
> v0.6.8+
|
||||
|
||||
Copy nodes. After calling this method, the current activated node data will be stored. Multiple activated nodes will only operate on the first node, and subsequent calls to the 'paste()' method can be pasted.
|
||||
|
||||
### cut()
|
||||
|
||||
> v0.6.8+
|
||||
|
||||
Cut a node. After calling this method, the currently active node will be cut and the node data will be stored. Multiple nodes will only operate on the first node, and subsequent calls to the 'paste()' method can be pasted.
|
||||
|
||||
### paste()
|
||||
|
||||
> v0.6.8+
|
||||
|
||||
Pasting nodes can be done by calling the 'copy()' or 'cut()' method after calling it. This method does not support pasting data from the user's clipboard. Please use the built-in 'Ctrl+v' shortcut key.
|
||||
@@ -37,7 +37,10 @@ disable the enter key and delete key related shortcuts to prevent conflicts</p>
|
||||
<h3>copyNode()</h3>
|
||||
<p>Copy a node, the active node is the node to be operated on, if there are
|
||||
multiple active nodes, only the first node will be operated on</p>
|
||||
<h3>setNodeDataRender(node, data)</h3>
|
||||
<h3>setNodeDataRender(node, data, notRender)</h3>
|
||||
<ul>
|
||||
<li><code>notRender</code>: v0.6.9+, <code>Boolean</code>, Default is <code>false</code>, Do not trigger rendering.</li>
|
||||
</ul>
|
||||
<p>Set node <code>data</code>, i.e. the data in the data field, and will determine whether the
|
||||
node needs to be re-rendered based on whether the node size has changed, <code>data</code>
|
||||
is an object, e.g. <code>{text: 'I am new text'}</code></p>
|
||||
@@ -62,6 +65,42 @@ is an object, e.g. <code>{text: 'I am new text'}</code></p>
|
||||
</blockquote>
|
||||
<p>Move a node to the center of the canvas.</p>
|
||||
<p>Currently, if there is zoom, returning to the center will reset the zoom.</p>
|
||||
<h3>expandToNodeUid(uid, callback)</h3>
|
||||
<blockquote>
|
||||
<p>v0.6.7+</p>
|
||||
</blockquote>
|
||||
<ul>
|
||||
<li>
|
||||
<p><code>uid</code>: uid of node</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>callback</code>: Expand completed callback function</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>Expand to the node of the specified uid.</p>
|
||||
<h3>findNodeByUid(uid)</h3>
|
||||
<blockquote>
|
||||
<p>v0.6.7+</p>
|
||||
</blockquote>
|
||||
<ul>
|
||||
<li><code>uid</code>: uid of node</li>
|
||||
</ul>
|
||||
<p>Find the corresponding node instance based on the uid.</p>
|
||||
<h3>copy()</h3>
|
||||
<blockquote>
|
||||
<p>v0.6.8+</p>
|
||||
</blockquote>
|
||||
<p>Copy nodes. After calling this method, the current activated node data will be stored. Multiple activated nodes will only operate on the first node, and subsequent calls to the 'paste()' method can be pasted.</p>
|
||||
<h3>cut()</h3>
|
||||
<blockquote>
|
||||
<p>v0.6.8+</p>
|
||||
</blockquote>
|
||||
<p>Cut a node. After calling this method, the currently active node will be cut and the node data will be stored. Multiple nodes will only operate on the first node, and subsequent calls to the 'paste()' method can be pasted.</p>
|
||||
<h3>paste()</h3>
|
||||
<blockquote>
|
||||
<p>v0.6.8+</p>
|
||||
</blockquote>
|
||||
<p>Pasting nodes can be done by calling the 'copy()' or 'cut()' method after calling it. This method does not support pasting data from the user's clipboard. Please use the built-in 'Ctrl+v' shortcut key.</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
68
web/src/pages/Doc/en/search/index.md
Normal file
68
web/src/pages/Doc/en/search/index.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Search plugin
|
||||
|
||||
> v0.6.9+
|
||||
|
||||
This plugin provides the ability to search and replace node content.
|
||||
|
||||
## Register
|
||||
|
||||
```js
|
||||
import MindMap from 'simple-mind-map'
|
||||
import Search from 'simple-mind-map/src/plugins/Search.js'
|
||||
MindMap.usePlugin(Search)
|
||||
```
|
||||
|
||||
After registration and instantiation of `MindMap`, the instance can be obtained through `mindMap.Search`.
|
||||
|
||||
## Event
|
||||
|
||||
### search_info_change
|
||||
|
||||
You can listen to 'search_info_change' event to get the number of current search results and the index currently located.
|
||||
|
||||
```js
|
||||
mindMap.on('search_info_change', (data) => {
|
||||
/*
|
||||
data: {
|
||||
currentIndex,// Index, from zero
|
||||
total
|
||||
}
|
||||
*/
|
||||
})
|
||||
```
|
||||
|
||||
## Method
|
||||
|
||||
### search(searchText, callback)
|
||||
|
||||
- `searchText`: Text to search for
|
||||
|
||||
- `callback`: The callback function that completes this search will be triggered after jumping to the node
|
||||
|
||||
Search for node content, which can be called repeatedly. Each call will search and locate to the next matching node. If the search text changes, it will be searched again.
|
||||
|
||||
### endSearch()
|
||||
|
||||
End search.
|
||||
|
||||
### replace(replaceText)
|
||||
|
||||
- `replaceText`: Text to be replaced
|
||||
|
||||
To replace the content of the current node, call the 'search' method after calling it to replace the content of the currently located matching node.
|
||||
|
||||
### replaceAll(replaceText)
|
||||
|
||||
- `replaceText`: Text to be replaced
|
||||
|
||||
Replace all matching node contents, and call it after calling the 'search' method.
|
||||
|
||||
### getReplacedText(node, searchText, replaceText)
|
||||
|
||||
- `node`: Node instance
|
||||
|
||||
- `searchText`: Text to search for
|
||||
|
||||
- `replaceText`: Text to be replaced
|
||||
|
||||
Return the text content of the node after search and replacement. Note that the node content will not be actually changed, but is only used to calculate the content of a node after replacement.
|
||||
74
web/src/pages/Doc/en/search/index.vue
Normal file
74
web/src/pages/Doc/en/search/index.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Search plugin</h1>
|
||||
<blockquote>
|
||||
<p>v0.6.9+</p>
|
||||
</blockquote>
|
||||
<p>This plugin provides the ability to search and replace node content.</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> Search <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/plugins/Search.js'</span>
|
||||
MindMap.usePlugin(Search)
|
||||
</code></pre>
|
||||
<p>After registration and instantiation of <code>MindMap</code>, the instance can be obtained through <code>mindMap.Search</code>.</p>
|
||||
<h2>Event</h2>
|
||||
<h3>search_info_change</h3>
|
||||
<p>You can listen to 'search_info_change' event to get the number of current search results and the index currently located.</p>
|
||||
<pre class="hljs"><code>mindMap.on(<span class="hljs-string">'search_info_change'</span>, <span class="hljs-function">(<span class="hljs-params">data</span>) =></span> {
|
||||
<span class="hljs-comment">/*
|
||||
data: {
|
||||
currentIndex,// Index, from zero
|
||||
total
|
||||
}
|
||||
*/</span>
|
||||
})
|
||||
</code></pre>
|
||||
<h2>Method</h2>
|
||||
<h3>search(searchText, callback)</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<p><code>searchText</code>: Text to search for</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>callback</code>: The callback function that completes this search will be triggered after jumping to the node</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>Search for node content, which can be called repeatedly. Each call will search and locate to the next matching node. If the search text changes, it will be searched again.</p>
|
||||
<h3>endSearch()</h3>
|
||||
<p>End search.</p>
|
||||
<h3>replace(replaceText)</h3>
|
||||
<ul>
|
||||
<li><code>replaceText</code>: Text to be replaced</li>
|
||||
</ul>
|
||||
<p>To replace the content of the current node, call the 'search' method after calling it to replace the content of the currently located matching node.</p>
|
||||
<h3>replaceAll(replaceText)</h3>
|
||||
<ul>
|
||||
<li><code>replaceText</code>: Text to be replaced</li>
|
||||
</ul>
|
||||
<p>Replace all matching node contents, and call it after calling the 'search' method.</p>
|
||||
<h3>getReplacedText(node, searchText, replaceText)</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<p><code>node</code>: Node instance</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>searchText</code>: Text to search for</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>replaceText</code>: Text to be replaced</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>Return the text content of the node after search and replacement. Note that the node content will not be actually changed, but is only used to calculate the content of a node after replacement.</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -181,6 +181,27 @@ Get the size of image, return:
|
||||
}
|
||||
```
|
||||
|
||||
#### loadImage(imgFile)
|
||||
|
||||
> v0.6.8+
|
||||
|
||||
- `imgFile`: File object of image type
|
||||
|
||||
Load image, return:
|
||||
|
||||
```js
|
||||
{
|
||||
url,// DataUrl
|
||||
size// { width, height } width and height of image
|
||||
}
|
||||
```
|
||||
|
||||
#### getType(data)
|
||||
|
||||
> v0.6.9+
|
||||
|
||||
Get the type of a data, such as `Boolean`、`Array`.
|
||||
|
||||
## Simulate CSS background in Canvas
|
||||
|
||||
Import:
|
||||
|
||||
@@ -121,6 +121,24 @@ and copying the <code>data</code> of the data object, example:</p>
|
||||
height
|
||||
}
|
||||
</code></pre>
|
||||
<h4>loadImage(imgFile)</h4>
|
||||
<blockquote>
|
||||
<p>v0.6.8+</p>
|
||||
</blockquote>
|
||||
<ul>
|
||||
<li><code>imgFile</code>: File object of image type</li>
|
||||
</ul>
|
||||
<p>Load image, return:</p>
|
||||
<pre class="hljs"><code>{
|
||||
url,<span class="hljs-comment">// DataUrl</span>
|
||||
size<span class="hljs-comment">// { width, height } width and height of image</span>
|
||||
}
|
||||
</code></pre>
|
||||
<h4>getType(data)</h4>
|
||||
<blockquote>
|
||||
<p>v0.6.9+</p>
|
||||
</blockquote>
|
||||
<p>Get the type of a data, such as <code>Boolean</code>、<code>Array</code>.</p>
|
||||
<h2>Simulate CSS background in Canvas</h2>
|
||||
<p>Import:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> drawBackgroundImageToCanvas <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/utils/simulateCSSBackgroundInCanvas'</span>
|
||||
|
||||
@@ -27,6 +27,7 @@ export default [
|
||||
{ path: 'course18', title: '如何持久化数据' },
|
||||
{ path: 'course19', title: '插入和扩展节点图标' },
|
||||
{ path: 'course20', title: '如何自定义节点内容' },
|
||||
{ path: 'course21', title: '如何复制、剪切、粘贴' },
|
||||
{ path: 'doExport', title: 'Export 插件' },
|
||||
{ path: 'drag', title: 'Drag插件' },
|
||||
{ path: 'introduction', title: '简介' },
|
||||
@@ -47,7 +48,10 @@ export default [
|
||||
{ path: 'deploy', title: '部署' },
|
||||
{ path: 'client', title: '客户端' },
|
||||
{ path: 'touchEvent', title: 'TouchEvent插件' },
|
||||
{ path: 'nodeImgAdjust', title: 'NodeImgAdjust插件' }
|
||||
{ path: 'nodeImgAdjust', title: 'NodeImgAdjust插件' },
|
||||
{ path: 'search', title: 'Search插件' },
|
||||
{ path: 'help1', title: '概要/关联线' },
|
||||
{ path: 'help2', title: '客户端' }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -77,7 +81,8 @@ export default [
|
||||
{ path: 'xmind', title: 'XMind parse' },
|
||||
{ path: 'deploy', title: 'Deploy' },
|
||||
{ path: 'touchEvent', title: 'TouchEvent plugin' },
|
||||
{ path: 'nodeImgAdjust', title: 'NodeImgAdjust plugin' }
|
||||
{ path: 'nodeImgAdjust', title: 'NodeImgAdjust plugin' },
|
||||
{ path: 'search', title: 'Search plugin' }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,5 +1,37 @@
|
||||
# Changelog
|
||||
|
||||
## 0.6.9-fix.1
|
||||
|
||||
修复:1.修复搜索进行一次单个替换后再全部替换不正确的问题。
|
||||
|
||||
新增:1.不会再直接修改传入的data对象,内部会深拷贝一份。
|
||||
|
||||
## 0.6.9
|
||||
|
||||
修复:1.修复给概要节点设置样式概要节点会消失的问题。2.修复自定义节点内容时,二次创建根实例时节点内容不渲染的问题。3.修复节点处于编辑中时添加新节点时新节点的焦点丢失问题。 2.修复连续按tab键无法连续创建子节点的问题。
|
||||
|
||||
新增:1.导出svg时替换svg中存在的` `字符,避免导出的svg报错。 2.支持搜索和替换。
|
||||
|
||||
Demo:1.切换主题时支持选择是否覆盖设置过的基础样式。
|
||||
|
||||
## 0.6.8
|
||||
|
||||
修复:1.修改插入概要的快捷键为Ctrl+G,避免和保存快捷键冲突。 2.修复节点正在编辑时切换富文本编辑配置输入框出现异常的问题。
|
||||
|
||||
新增:1.修改复制、剪切、粘贴逻辑,支持粘贴剪切板中的数据。
|
||||
|
||||
Demo:1.修复基础样式-设置节点外边距未保存的问题。 2.支持根据主题自动切换为暗黑模式。
|
||||
|
||||
## 0.6.7
|
||||
|
||||
修复:1.修复节点收起再展开后展开收起按钮占位元素丢失的问题。 2.修复只读模式下可以缩放图片的问题。
|
||||
|
||||
新增:1.支持根据节点实例或节点uid定位到某个节点。 2.修改节点uid的创建方式,导出数据添加节点的uid。
|
||||
|
||||
移除:1.移除节点过渡效果。
|
||||
|
||||
Demo:1.添加网站首页。 2.修复大纲里创建新节点时节点样式丢失的问题。 3.修复大纲里编辑节点后按回车或Tab键后编辑文本丢失的问题。 4.优化大纲的节点定位,被收起的节点会自动展开。 5.侧边栏按钮支持收起。 6.优化小屏适配。
|
||||
|
||||
## 0.6.6
|
||||
|
||||
新增:1.支持导出为Xmind新版文件。2.导入Xmind新版文件支持导入节点中的图片。 3.新增竖向时间轴结构。
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Changelog</h1>
|
||||
<h2>0.6.9-fix.1</h2>
|
||||
<p>修复:1.修复搜索进行一次单个替换后再全部替换不正确的问题。</p>
|
||||
<p>新增:1.不会再直接修改传入的data对象,内部会深拷贝一份。</p>
|
||||
<h2>0.6.9</h2>
|
||||
<p>修复:1.修复给概要节点设置样式概要节点会消失的问题。2.修复自定义节点内容时,二次创建根实例时节点内容不渲染的问题。3.修复节点处于编辑中时添加新节点时新节点的焦点丢失问题。 2.修复连续按tab键无法连续创建子节点的问题。</p>
|
||||
<p>新增:1.导出svg时替换svg中存在的<code>&nbsp;</code>字符,避免导出的svg报错。 2.支持搜索和替换。</p>
|
||||
<p>Demo:1.切换主题时支持选择是否覆盖设置过的基础样式。</p>
|
||||
<h2>0.6.8</h2>
|
||||
<p>修复:1.修改插入概要的快捷键为Ctrl+G,避免和保存快捷键冲突。 2.修复节点正在编辑时切换富文本编辑配置输入框出现异常的问题。</p>
|
||||
<p>新增:1.修改复制、剪切、粘贴逻辑,支持粘贴剪切板中的数据。</p>
|
||||
<p>Demo:1.修复基础样式-设置节点外边距未保存的问题。 2.支持根据主题自动切换为暗黑模式。</p>
|
||||
<h2>0.6.7</h2>
|
||||
<p>修复:1.修复节点收起再展开后展开收起按钮占位元素丢失的问题。 2.修复只读模式下可以缩放图片的问题。</p>
|
||||
<p>新增:1.支持根据节点实例或节点uid定位到某个节点。 2.修改节点uid的创建方式,导出数据添加节点的uid。</p>
|
||||
<p>移除:1.移除节点过渡效果。</p>
|
||||
<p>Demo:1.添加网站首页。 2.修复大纲里创建新节点时节点样式丢失的问题。 3.修复大纲里编辑节点后按回车或Tab键后编辑文本丢失的问题。 4.优化大纲的节点定位,被收起的节点会自动展开。 5.侧边栏按钮支持收起。 6.优化小屏适配。</p>
|
||||
<h2>0.6.6</h2>
|
||||
<p>新增:1.支持导出为Xmind新版文件。2.导入Xmind新版文件支持导入节点中的图片。 3.新增竖向时间轴结构。</p>
|
||||
<p>修复:1.TouchEvent插件不再派发click事件,解决移动端点击超链接会打开两个窗口的问题。 2.修复拖拽移动一个节点成为另一个节点的子节点时该节点的父节点指向未更新的问题。 3.修复二级节点拖拽成三级节点时节点边框样式未更新的问题。 4.修复向右生长的结构外其他结构鼠标移入展开收起按钮位置时不会触发按钮显示的问题。</p>
|
||||
|
||||
@@ -51,8 +51,8 @@ const mindMap = new MindMap({
|
||||
| expandBtnStyle(v0.5.0+) | Object | { color: '#808080', fill: '#fff' } | 展开收起按钮的颜色 | |
|
||||
| expandBtnIcon(v0.5.0+) | Object | { open: '', close: '' } | 自定义展开收起按钮的图标,可以传图标的svg字符串 | |
|
||||
| enableShortcutOnlyWhenMouseInSvg(v0.5.1+) | Boolean | true | 是否只有当鼠标在画布内才响应快捷键事件 | |
|
||||
| enableNodeTransitionMove(v0.5.1+) | Boolean | true | 是否开启节点动画过渡 | |
|
||||
| nodeTransitionMoveDuration(v0.5.1+) | Number | 300 | 如果开启节点动画过渡,可以通过该属性设置过渡的时间,单位ms | |
|
||||
| enableNodeTransitionMove(v0.5.1+)(v0.6.7+已去除该特性) | Boolean | true | 是否开启节点动画过渡 | |
|
||||
| nodeTransitionMoveDuration(v0.5.1+)(v0.6.7+已去除该特性) | Number | 300 | 如果开启节点动画过渡,可以通过该属性设置过渡的时间,单位ms | |
|
||||
| initRootNodePosition(v0.5.3+) | Array | null | 初始根节点的位置,可传一个数组,默认为`['center', 'center']`,代表根节点处于画布中心位置,除了`center`,关键词还可以设置`left`、`top`、`right`、`bottom`,除了可以传关键词,数组的每项还可以传递一个数字,代表具体的像素,可以传递一个百分比字符串,比如`['40%', '60%']`,代表水平位置在画布宽度的`40%`的位置,垂直位置在画布高度的`60%`的位置 | |
|
||||
| exportPaddingX(v0.5.5+) | Number | 10 | 导出png、svg、pdf时的图形水平内边距 | |
|
||||
| exportPaddingY(v0.5.5+) | Number | 10 | 导出png、svg、pdf时的图形垂直内边距 | |
|
||||
@@ -340,6 +340,7 @@ mindMap.updateConfig({
|
||||
| SET_NODE_CUSTOM_POSITION(v0.2.0+) | 设置节点自定义位置 | node(要设置的节点)、 left(自定义的x坐标,默认为undefined)、 top(自定义的y坐标,默认为undefined) |
|
||||
| RESET_LAYOUT(v0.2.0+) | 一键整理布局 | |
|
||||
| SET_NODE_SHAPE(v0.2.4+) | 设置节点形状 | node(要设置的节点)、shape(形状,全部形状:[Shape.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/core/render/node/Shape.js)) |
|
||||
| GO_TARGET_NODE(v0.6.7+) | 定位到某个节点,如果该节点被收起,那么会自动展开到该节点 | node(要定位到的节点实例或节点uid)、callback(v0.6.9+,定位完成后的回调函数) |
|
||||
|
||||
### setData(data)
|
||||
|
||||
|
||||
@@ -218,14 +218,14 @@
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>enableNodeTransitionMove(v0.5.1+)</td>
|
||||
<td>enableNodeTransitionMove(v0.5.1+)(v0.6.7+已去除该特性)</td>
|
||||
<td>Boolean</td>
|
||||
<td>true</td>
|
||||
<td>是否开启节点动画过渡</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>nodeTransitionMoveDuration(v0.5.1+)</td>
|
||||
<td>nodeTransitionMoveDuration(v0.5.1+)(v0.6.7+已去除该特性)</td>
|
||||
<td>Number</td>
|
||||
<td>300</td>
|
||||
<td>如果开启节点动画过渡,可以通过该属性设置过渡的时间,单位ms</td>
|
||||
@@ -915,6 +915,11 @@ mindMap.setTheme(<span class="hljs-string">'主题名称'</span>)
|
||||
<td>设置节点形状</td>
|
||||
<td>node(要设置的节点)、shape(形状,全部形状:<a href="https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/core/render/node/Shape.js">Shape.js</a>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>GO_TARGET_NODE(v0.6.7+)</td>
|
||||
<td>定位到某个节点,如果该节点被收起,那么会自动展开到该节点</td>
|
||||
<td>node(要定位到的节点实例或节点uid)、callback(v0.6.9+,定位完成后的回调函数)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>setData(data)</h3>
|
||||
|
||||
@@ -9,6 +9,8 @@ import { themeList } from 'simple-mind-map/src/constants/constant'
|
||||
// import { themeList } from 'simple-mind-map/src/utils/constant' v0.6.0以下版本使用该路径
|
||||
```
|
||||
|
||||
> v0.6.8+,主题列表增加了代表是否是暗黑主题的字段dark,你可以根据这个字段来将界面切换为暗黑模式。
|
||||
|
||||
可以在实例化`simple-mind-map`时指定使用的主题:
|
||||
|
||||
```js
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> { themeList } <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/constants/constant'</span>
|
||||
<span class="hljs-comment">// import { themeList } from 'simple-mind-map/src/utils/constant' v0.6.0以下版本使用该路径</span>
|
||||
</code></pre>
|
||||
<blockquote>
|
||||
<p>v0.6.8+,主题列表增加了代表是否是暗黑主题的字段dark,你可以根据这个字段来将界面切换为暗黑模式。</p>
|
||||
</blockquote>
|
||||
<p>可以在实例化<code>simple-mind-map</code>时指定使用的主题:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">new</span> MindMap({
|
||||
<span class="hljs-attr">theme</span>: <span class="hljs-string">'minions'</span>
|
||||
|
||||
@@ -18,6 +18,9 @@ mindMap.on('data_change', (data) => {
|
||||
const node = data._node
|
||||
mindMap.renderer.moveNodeToCenter(node)
|
||||
node.active()
|
||||
|
||||
// 在v0.6.7+版本可以这么做:
|
||||
mindMap.execCommand('GO_TARGET_NODE', node)// 或者传节点的uid
|
||||
```
|
||||
|
||||
当在大纲树上编辑了某个节点的内容,需要同步到思维导图树上:
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
<pre class="hljs"><code><span class="hljs-keyword">const</span> node = data._node
|
||||
mindMap.renderer.moveNodeToCenter(node)
|
||||
node.active()
|
||||
|
||||
<span class="hljs-comment">// 在v0.6.7+版本可以这么做:</span>
|
||||
mindMap.execCommand(<span class="hljs-string">'GO_TARGET_NODE'</span>, node)<span class="hljs-comment">// 或者传节点的uid</span>
|
||||
</code></pre>
|
||||
<p>当在大纲树上编辑了某个节点的内容,需要同步到思维导图树上:</p>
|
||||
<pre class="hljs"><code>data._node.setText(<span class="hljs-string">'xxx'</span>)
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
{
|
||||
name: '插入概要',
|
||||
value: 'Ctrl + S'
|
||||
value: 'Ctrl + S'// v0.6.8+改为Ctrl + G
|
||||
},
|
||||
{
|
||||
name: '展开/收起节点',
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
},
|
||||
{
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">'插入概要'</span>,
|
||||
<span class="hljs-attr">value</span>: <span class="hljs-string">'Ctrl + S'</span>
|
||||
<span class="hljs-attr">value</span>: <span class="hljs-string">'Ctrl + S'</span><span class="hljs-comment">// v0.6.8+改为Ctrl + G</span>
|
||||
},
|
||||
{
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">'展开/收起节点'</span>,
|
||||
|
||||
@@ -116,4 +116,20 @@ import CustomNodeContent from './CustomNodeContent.vue'
|
||||
|
||||
## 示例4:渲染react组件
|
||||
|
||||
如果你成功渲染了`react`组件,欢迎[提交](https://github.com/wanglin2/mind-map/issues/new)示例代码给我~
|
||||
```js
|
||||
import { createRoot } from 'react-dom/client'
|
||||
|
||||
{
|
||||
customCreateNodeContent: (node) => {
|
||||
const el = document.createElement('div')
|
||||
el.style.width = '227px'
|
||||
el.style.height = '60px'
|
||||
const currentNode = node.nodeData.data
|
||||
const root = createRoot(el)
|
||||
root.render({currentNode.text})
|
||||
return el
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 感谢[h5chenhang](https://github.com/h5chenhang)贡献的[示例代码](https://github.com/wanglin2/mind-map/issues/192)。
|
||||
@@ -94,7 +94,23 @@
|
||||
}
|
||||
</code></pre>
|
||||
<h2>示例4:渲染react组件</h2>
|
||||
<p>如果你成功渲染了<code>react</code>组件,欢迎<a href="https://github.com/wanglin2/mind-map/issues/new">提交</a>示例代码给我~</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> { createRoot } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-dom/client'</span>
|
||||
|
||||
{
|
||||
<span class="hljs-attr">customCreateNodeContent</span>: <span class="hljs-function">(<span class="hljs-params">node</span>) =></span> {
|
||||
<span class="hljs-keyword">const</span> el = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'div'</span>)
|
||||
el.style.width = <span class="hljs-string">'227px'</span>
|
||||
el.style.height = <span class="hljs-string">'60px'</span>
|
||||
<span class="hljs-keyword">const</span> currentNode = node.nodeData.data
|
||||
<span class="hljs-keyword">const</span> root = createRoot(el)
|
||||
root.render({currentNode.text})
|
||||
<span class="hljs-keyword">return</span> el
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<blockquote>
|
||||
<p>感谢<a href="https://github.com/h5chenhang">h5chenhang</a>贡献的<a href="https://github.com/wanglin2/mind-map/issues/192">示例代码</a>。</p>
|
||||
</blockquote>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
40
web/src/pages/Doc/zh/course21/index.md
Normal file
40
web/src/pages/Doc/zh/course21/index.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# 如何复制、剪切、粘贴
|
||||
|
||||
## 使用快捷键
|
||||
|
||||
核心库内部默认支持`Ctrl+c`、`Ctrl+x`、`Ctrl+v`三个快捷键来执行复制、剪切、粘贴操作。
|
||||
|
||||
当激活了某个节点,按`Ctrl+c`会复制当前激活节点的数据,按`Ctrl+x`会删除当前激活节点,同时保存该节点的数据,这两个操作当同时存在多个激活节点,只会对第一个生效。
|
||||
|
||||
当按了`Ctrl+c`或`Ctrl+x`后,按`Ctrl+v`会在当前激活的节点粘贴复制或剪切的数据,也就是被复制或剪切的节点会作为该节点的子节点。
|
||||
|
||||
除了支持粘贴在画布中复制或剪切的节点数据外,如果你再其他地方复制了文本或图片,也支持进行粘贴,也就是会粘贴你当前剪贴板中的数据,如果你的剪切板中存在文本数据,那么会粘贴作为当前激活节点的子节点,如果存在图片数据,那么会直接给当前激活节点添加或替换图片。
|
||||
|
||||
如果复制或剪切了画布数据,同时剪切板中也存在数据,那么默认会以最新的`Ctrl+c`或`Ctrl+x`操作为准,比如你先复制了节点,然后又复制了其他地方的文本,那么会粘贴最后一次的操作,也就是其他地方复制的文本数据。
|
||||
|
||||
## 使用按钮触发
|
||||
|
||||
一般会在右键菜单上下文中显然复制、剪切、粘贴三个按钮,当点击了这三个按钮也需要能执行复制、剪切、粘贴操作,这需要调用内部的一些方法来完成:
|
||||
|
||||
```js
|
||||
// 点击了复制按钮
|
||||
const onCopyBtnClick = () => {
|
||||
mindMap.renderer.copy()
|
||||
}
|
||||
|
||||
// 点击了剪切按钮
|
||||
const onCutBtnClick = () => {
|
||||
mindMap.renderer.cut()
|
||||
}
|
||||
|
||||
// 点击了粘贴按钮
|
||||
const onPasteBtnClick = () => {
|
||||
mindMap.renderer.paste()
|
||||
}
|
||||
```
|
||||
|
||||
需要注意的是,这三个方法只能复制、剪切、粘贴画布中的节点数据,不支持操作用户的剪切板数据。
|
||||
|
||||
### 完整示例
|
||||
|
||||
<iframe style="width: 100%; height: 455px; border: none;" src="https://wanglin2.github.io/playground/#eNrFVc1OGzEQfpWRqypJFXaD1FMaEIX20AMV4lpz2Ow6ianXXq29BIRy4VCgopeeeuyhUqUeeqhQJejrEKBv0fH+k0RVbkSKtJ75vm/Gnhn7mLyMIucgYaRLetqPeWRAM5NE61TyMFKxgWOI2aANSm6rRBoWtEGPPCHUeJcNYAKDWIXQQIVGydjmMtj2osxFiUazYCshWldCL6KESgAqBTNgbRa5BjIRgkoqXRfuTq6mp39urj9Mv32anv2+vTj/+/knlb6S2mAaWyo62jRyS3D/PRKbLVhbh2OrCYWeEzMZsJjFjo/gZovKyZz2+Y/p2emcdmKWl07MQuW7X1/uLy9nlXc8bdjS2pFFl+rl0TdrrNrRsXFx5M1ckIkuBMpPQiaNM2TmtWD2c/PoTdBs5MwtJY3HJYsbrXbGCjzjdYucsETEGiipmTKzYYfGmim5/Xp1//EEt54V1f4muZgF+iMuAtyUBb+rNGbkFkaZjXRzfXF3/X022MOAC4LuVb467pEyKD5zW8HjkptdpcxbFbAdpbnhSiKzIdjANNrQ8LF0WKa9FD5pvcC2wNboudm84qTiwjAcMs8wXAH0An4AvvC0XqMkL/crFipKUncO4EHlLZsBIT0XvXVgoWSUEn3PQoqN9PqJMUrChm/bGiEPxxOR2Qz33Az4X2I1e5aXzucyvAeThcxs/maY1Z6Kr55bOzJcanMkstPbyK8xShw3u7vy6XKYDh1fa0qwBPYKA3Bqp1u0z5gHZtSF1U7naYoDiMqixgwj8gOWOtJ+sP8ns1UopCqi19dKJCYjAtjW6EInXxkVVYv58CPGhyOEP+90osMi8uK4z8oryYuHHOMWqpEXBFwOC0OZupM3xZIZrxYZ5EmXaxTEhk5rQNokq4B9LZx9rSQ+Tqk8zR1YgXJYKcG3J5tQx8VPJ8arkofMFmulH6uxxvt0Hxn5sC14jzLufKktK89tQib/AMXMadM=" />
|
||||
41
web/src/pages/Doc/zh/course21/index.vue
Normal file
41
web/src/pages/Doc/zh/course21/index.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>如何复制、剪切、粘贴</h1>
|
||||
<h2>使用快捷键</h2>
|
||||
<p>核心库内部默认支持<code>Ctrl+c</code>、<code>Ctrl+x</code>、<code>Ctrl+v</code>三个快捷键来执行复制、剪切、粘贴操作。</p>
|
||||
<p>当激活了某个节点,按<code>Ctrl+c</code>会复制当前激活节点的数据,按<code>Ctrl+x</code>会删除当前激活节点,同时保存该节点的数据,这两个操作当同时存在多个激活节点,只会对第一个生效。</p>
|
||||
<p>当按了<code>Ctrl+c</code>或<code>Ctrl+x</code>后,按<code>Ctrl+v</code>会在当前激活的节点粘贴复制或剪切的数据,也就是被复制或剪切的节点会作为该节点的子节点。</p>
|
||||
<p>除了支持粘贴在画布中复制或剪切的节点数据外,如果你再其他地方复制了文本或图片,也支持进行粘贴,也就是会粘贴你当前剪贴板中的数据,如果你的剪切板中存在文本数据,那么会粘贴作为当前激活节点的子节点,如果存在图片数据,那么会直接给当前激活节点添加或替换图片。</p>
|
||||
<p>如果复制或剪切了画布数据,同时剪切板中也存在数据,那么默认会以最新的<code>Ctrl+c</code>或<code>Ctrl+x</code>操作为准,比如你先复制了节点,然后又复制了其他地方的文本,那么会粘贴最后一次的操作,也就是其他地方复制的文本数据。</p>
|
||||
<h2>使用按钮触发</h2>
|
||||
<p>一般会在右键菜单上下文中显然复制、剪切、粘贴三个按钮,当点击了这三个按钮也需要能执行复制、剪切、粘贴操作,这需要调用内部的一些方法来完成:</p>
|
||||
<pre class="hljs"><code><span class="hljs-comment">// 点击了复制按钮</span>
|
||||
<span class="hljs-keyword">const</span> onCopyBtnClick = <span class="hljs-function">() =></span> {
|
||||
mindMap.renderer.copy()
|
||||
}
|
||||
|
||||
<span class="hljs-comment">// 点击了剪切按钮</span>
|
||||
<span class="hljs-keyword">const</span> onCutBtnClick = <span class="hljs-function">() =></span> {
|
||||
mindMap.renderer.cut()
|
||||
}
|
||||
|
||||
<span class="hljs-comment">// 点击了粘贴按钮</span>
|
||||
<span class="hljs-keyword">const</span> onPasteBtnClick = <span class="hljs-function">() =></span> {
|
||||
mindMap.renderer.paste()
|
||||
}
|
||||
</code></pre>
|
||||
<p>需要注意的是,这三个方法只能复制、剪切、粘贴画布中的节点数据,不支持操作用户的剪切板数据。</p>
|
||||
<h3>完整示例</h3>
|
||||
<iframe style="width: 100%; height: 455px; border: none;" src="https://wanglin2.github.io/playground/#eNrFVc1OGzEQfpWRqypJFXaD1FMaEIX20AMV4lpz2Ow6ianXXq29BIRy4VCgopeeeuyhUqUeeqhQJejrEKBv0fH+k0RVbkSKtJ75vm/Gnhn7mLyMIucgYaRLetqPeWRAM5NE61TyMFKxgWOI2aANSm6rRBoWtEGPPCHUeJcNYAKDWIXQQIVGydjmMtj2osxFiUazYCshWldCL6KESgAqBTNgbRa5BjIRgkoqXRfuTq6mp39urj9Mv32anv2+vTj/+/knlb6S2mAaWyo62jRyS3D/PRKbLVhbh2OrCYWeEzMZsJjFjo/gZovKyZz2+Y/p2emcdmKWl07MQuW7X1/uLy9nlXc8bdjS2pFFl+rl0TdrrNrRsXFx5M1ckIkuBMpPQiaNM2TmtWD2c/PoTdBs5MwtJY3HJYsbrXbGCjzjdYucsETEGiipmTKzYYfGmim5/Xp1//EEt54V1f4muZgF+iMuAtyUBb+rNGbkFkaZjXRzfXF3/X022MOAC4LuVb467pEyKD5zW8HjkptdpcxbFbAdpbnhSiKzIdjANNrQ8LF0WKa9FD5pvcC2wNboudm84qTiwjAcMs8wXAH0An4AvvC0XqMkL/crFipKUncO4EHlLZsBIT0XvXVgoWSUEn3PQoqN9PqJMUrChm/bGiEPxxOR2Qz33Az4X2I1e5aXzucyvAeThcxs/maY1Z6Kr55bOzJcanMkstPbyK8xShw3u7vy6XKYDh1fa0qwBPYKA3Bqp1u0z5gHZtSF1U7naYoDiMqixgwj8gOWOtJ+sP8ns1UopCqi19dKJCYjAtjW6EInXxkVVYv58CPGhyOEP+90osMi8uK4z8oryYuHHOMWqpEXBFwOC0OZupM3xZIZrxYZ5EmXaxTEhk5rQNokq4B9LZx9rSQ+Tqk8zR1YgXJYKcG3J5tQx8VPJ8arkofMFmulH6uxxvt0Hxn5sC14jzLufKktK89tQib/AMXMadM=" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
11
web/src/pages/Doc/zh/help1/index.md
Normal file
11
web/src/pages/Doc/zh/help1/index.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# 概要/关联线
|
||||
|
||||
## 概要
|
||||
|
||||
可以选中一个节点添加概要,如果想给多个节点添加一个概要,只能通过给它们的父节点添加来实现。
|
||||
|
||||
概要节点后面无法再添加节点,后续该特性也不会支持。
|
||||
|
||||
## 关联线
|
||||
|
||||
关联线添加完后要删除,需要先点击选中关联线,然后按删除键即可。
|
||||
21
web/src/pages/Doc/zh/help1/index.vue
Normal file
21
web/src/pages/Doc/zh/help1/index.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>概要/关联线</h1>
|
||||
<h2>概要</h2>
|
||||
<p>可以选中一个节点添加概要,如果想给多个节点添加一个概要,只能通过给它们的父节点添加来实现。</p>
|
||||
<p>概要节点后面无法再添加节点,后续该特性也不会支持。</p>
|
||||
<h2>关联线</h2>
|
||||
<p>关联线添加完后要删除,需要先点击选中关联线,然后按删除键即可。</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
26
web/src/pages/Doc/zh/help2/index.md
Normal file
26
web/src/pages/Doc/zh/help2/index.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# 客户端
|
||||
|
||||
## 安装
|
||||
|
||||
目前提供了两个客户端下载方式:
|
||||
|
||||
1.百度云:[https://pan.baidu.com/s/1huasEbKsGNH2Af68dvWiOg?pwd=3bp3](https://pan.baidu.com/s/1huasEbKsGNH2Af68dvWiOg?pwd=3bp3)
|
||||
|
||||
2.Github:[https://github.com/wanglin2/mind-map/releases](https://github.com/wanglin2/mind-map/releases)
|
||||
|
||||
Windows用户请下载`Setup.[版本].exe`或`[版本].exe`文件。
|
||||
|
||||
Mac用户请下载`.dmg`文件。
|
||||
|
||||
Linux用户请下载`.AppImage`文件。
|
||||
|
||||
如果Mac安装出现`已损坏`问题,可以参考这篇文章:[苹果M1芯片安装xxx.app 显示已损坏,无法打开,你应该将它移到废纸篓/打不开 xxx,因为它来自身份不明的开发者 如何解决?](https://www.jianshu.com/p/031efc52ccea)。
|
||||
|
||||
## 使用
|
||||
|
||||
客户端名称为`思绪`,对应的思维导图文件后缀名为`.smm`。
|
||||
|
||||
客户端相比在线版,多了一个显示最近文件列表的页面,你打开过的文件都会显示在最近列表里。
|
||||
|
||||
除了点击打开文件按钮打开文件外,你也可以直接将文件拖入到列表页面,如果拖入一个文件,那么会直接打开进行编辑,如果同时拖入了多个文件,那么会直接添加到列表里,不会打开编辑。
|
||||
|
||||
28
web/src/pages/Doc/zh/help2/index.vue
Normal file
28
web/src/pages/Doc/zh/help2/index.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>客户端</h1>
|
||||
<h2>安装</h2>
|
||||
<p>目前提供了两个客户端下载方式:</p>
|
||||
<p>1.百度云:<a href="https://pan.baidu.com/s/1huasEbKsGNH2Af68dvWiOg?pwd=3bp3">https://pan.baidu.com/s/1huasEbKsGNH2Af68dvWiOg?pwd=3bp3</a></p>
|
||||
<p>2.Github:<a href="https://github.com/wanglin2/mind-map/releases">https://github.com/wanglin2/mind-map/releases</a></p>
|
||||
<p>Windows用户请下载<code>Setup.[版本].exe</code>或<code>[版本].exe</code>文件。</p>
|
||||
<p>Mac用户请下载<code>.dmg</code>文件。</p>
|
||||
<p>Linux用户请下载<code>.AppImage</code>文件。</p>
|
||||
<p>如果Mac安装出现<code>已损坏</code>问题,可以参考这篇文章:<a href="https://www.jianshu.com/p/031efc52ccea">苹果M1芯片安装xxx.app 显示已损坏,无法打开,你应该将它移到废纸篓/打不开 xxx,因为它来自身份不明的开发者 如何解决?</a>。</p>
|
||||
<h2>使用</h2>
|
||||
<p>客户端名称为<code>思绪</code>,对应的思维导图文件后缀名为<code>.smm</code>。</p>
|
||||
<p>客户端相比在线版,多了一个显示最近文件列表的页面,你打开过的文件都会显示在最近列表里。</p>
|
||||
<p>除了点击打开文件按钮打开文件外,你也可以直接将文件拖入到列表页面,如果拖入一个文件,那么会直接打开进行编辑,如果同时拖入了多个文件,那么会直接添加到列表里,不会打开编辑。</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -147,4 +147,16 @@
|
||||
<img src="../../../../assets/avatar/suka.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>suka</p>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
|
||||
<img src="../../../../assets/avatar/Chris.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>Chris</p>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
|
||||
<img src="../../../../assets/avatar/水车.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>水车</p>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
|
||||
<img src="../../../../assets/avatar/仓鼠.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>仓鼠</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -107,6 +107,18 @@
|
||||
<img src="../../../../assets/avatar/suka.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>suka</p>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
|
||||
<img src="../../../../assets/avatar/Chris.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>Chris</p>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
|
||||
<img src="../../../../assets/avatar/水车.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>水车</p>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; width: fit-content; margin: 5px;">
|
||||
<img src="../../../../assets/avatar/仓鼠.jpg" style="width: 50px;height: 50px;object-fit: cover;border-radius: 50%;" />
|
||||
<p>仓鼠</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -54,7 +54,9 @@
|
||||
|
||||
复制节点,操作节点为当前激活节点,有多个激活节点只会操作第一个节点
|
||||
|
||||
### setNodeDataRender(node, data)
|
||||
### setNodeDataRender(node, data, notRender)
|
||||
|
||||
- `notRender`:v0.6.9+,`Boolean`,默认为`false`,是否不要触发渲染。
|
||||
|
||||
设置节点数据,即`data`字段的数据,并会根据节点大小是否变化来判断是否需要重新渲染该节点,`data`为对象,如:`{text: '我是新文本'}`
|
||||
|
||||
@@ -82,4 +84,40 @@
|
||||
|
||||
移动节点到画布中心。
|
||||
|
||||
目前如果是存在缩放的情况下回到中心会重置缩放。
|
||||
目前如果是存在缩放的情况下回到中心会重置缩放。
|
||||
|
||||
### expandToNodeUid(uid, callback)
|
||||
|
||||
> v0.6.7+
|
||||
|
||||
- `uid`:节点uid
|
||||
|
||||
- `callback`:展开完成的回调函数
|
||||
|
||||
展开到指定uid的节点。
|
||||
|
||||
### findNodeByUid(uid)
|
||||
|
||||
> v0.6.7+
|
||||
|
||||
- `uid`:节点uid
|
||||
|
||||
根据uid找到对应的节点实例。
|
||||
|
||||
### copy()
|
||||
|
||||
> v0.6.8+
|
||||
|
||||
复制节点,调用该方法后会存储当前激活的节点数据,多个激活节点只会操作第一个节点,后续调用`paste()`方法时可以进行粘贴。
|
||||
|
||||
### cut()
|
||||
|
||||
> v0.6.8+
|
||||
|
||||
剪切节点,调用该方法后会剪切当前激活的节点,并且存储该节点数据,多个节点只会操作第一个节点,后续调用`paste()`方法时可以进行粘贴。
|
||||
|
||||
### paste()
|
||||
|
||||
> v0.6.8+
|
||||
|
||||
粘贴节点,在调用了`copy()`或`cut()`方法后可以调用该方法进行粘贴节点。该方法不支持粘贴用户剪贴板中的数据,请使用内置的`Ctrl+v`快捷键。
|
||||
@@ -28,7 +28,10 @@
|
||||
<p>删除某个指定节点</p>
|
||||
<h3>copyNode()</h3>
|
||||
<p>复制节点,操作节点为当前激活节点,有多个激活节点只会操作第一个节点</p>
|
||||
<h3>setNodeDataRender(node, data)</h3>
|
||||
<h3>setNodeDataRender(node, data, notRender)</h3>
|
||||
<ul>
|
||||
<li><code>notRender</code>:v0.6.9+,<code>Boolean</code>,默认为<code>false</code>,是否不要触发渲染。</li>
|
||||
</ul>
|
||||
<p>设置节点数据,即<code>data</code>字段的数据,并会根据节点大小是否变化来判断是否需要重新渲染该节点,<code>data</code>为对象,如:<code>{text: '我是新文本'}</code></p>
|
||||
<h3>moveNodeTo(node, toNode)</h3>
|
||||
<blockquote>
|
||||
@@ -51,6 +54,42 @@
|
||||
</blockquote>
|
||||
<p>移动节点到画布中心。</p>
|
||||
<p>目前如果是存在缩放的情况下回到中心会重置缩放。</p>
|
||||
<h3>expandToNodeUid(uid, callback)</h3>
|
||||
<blockquote>
|
||||
<p>v0.6.7+</p>
|
||||
</blockquote>
|
||||
<ul>
|
||||
<li>
|
||||
<p><code>uid</code>:节点uid</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>callback</code>:展开完成的回调函数</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>展开到指定uid的节点。</p>
|
||||
<h3>findNodeByUid(uid)</h3>
|
||||
<blockquote>
|
||||
<p>v0.6.7+</p>
|
||||
</blockquote>
|
||||
<ul>
|
||||
<li><code>uid</code>:节点uid</li>
|
||||
</ul>
|
||||
<p>根据uid找到对应的节点实例。</p>
|
||||
<h3>copy()</h3>
|
||||
<blockquote>
|
||||
<p>v0.6.8+</p>
|
||||
</blockquote>
|
||||
<p>复制节点,调用该方法后会存储当前激活的节点数据,多个激活节点只会操作第一个节点,后续调用<code>paste()</code>方法时可以进行粘贴。</p>
|
||||
<h3>cut()</h3>
|
||||
<blockquote>
|
||||
<p>v0.6.8+</p>
|
||||
</blockquote>
|
||||
<p>剪切节点,调用该方法后会剪切当前激活的节点,并且存储该节点数据,多个节点只会操作第一个节点,后续调用<code>paste()</code>方法时可以进行粘贴。</p>
|
||||
<h3>paste()</h3>
|
||||
<blockquote>
|
||||
<p>v0.6.8+</p>
|
||||
</blockquote>
|
||||
<p>粘贴节点,在调用了<code>copy()</code>或<code>cut()</code>方法后可以调用该方法进行粘贴节点。该方法不支持粘贴用户剪贴板中的数据,请使用内置的<code>Ctrl+v</code>快捷键。</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
68
web/src/pages/Doc/zh/search/index.md
Normal file
68
web/src/pages/Doc/zh/search/index.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Search 插件
|
||||
|
||||
> v0.6.9+
|
||||
|
||||
该插件提供搜索和替换节点内容的功能。
|
||||
|
||||
## 注册
|
||||
|
||||
```js
|
||||
import MindMap from 'simple-mind-map'
|
||||
import Search from 'simple-mind-map/src/plugins/Search.js'
|
||||
MindMap.usePlugin(Search)
|
||||
```
|
||||
|
||||
注册完且实例化`MindMap`后可通过`mindMap.search`获取到该实例。
|
||||
|
||||
## 事件
|
||||
|
||||
### search_info_change
|
||||
|
||||
可以通过监听`search_info_change`事件来获取当前搜索结果的数量和当前定位到的索引。
|
||||
|
||||
```js
|
||||
mindMap.on('search_info_change', (data) => {
|
||||
/*
|
||||
data: {
|
||||
currentIndex,// 索引,从0开始
|
||||
total
|
||||
}
|
||||
*/
|
||||
})
|
||||
```
|
||||
|
||||
## 方法
|
||||
|
||||
### search(searchText, callback)
|
||||
|
||||
- `searchText`:要进行搜索的文本
|
||||
|
||||
- `callback`:本次搜索完成的回调函数,会在跳转到节点后触发
|
||||
|
||||
搜索节点内容,可以重复调用,每调一次,会搜索和定位到下一个匹配的节点。如果搜索文本改变了,那么会重新搜索。
|
||||
|
||||
### endSearch()
|
||||
|
||||
结束搜索。
|
||||
|
||||
### replace(replaceText)
|
||||
|
||||
- `replaceText`:要进行替换的文本
|
||||
|
||||
替换当前节点内容,要在调用了`search`方法之后调用,会替换当前定位到的匹配节点内容。
|
||||
|
||||
### replaceAll(replaceText)
|
||||
|
||||
- `replaceText`:要进行替换的文本
|
||||
|
||||
替换所有匹配的节点内容,要在调用了`search`方法之后调用。
|
||||
|
||||
### getReplacedText(node, searchText, replaceText)
|
||||
|
||||
- `node`:节点实例
|
||||
|
||||
- `searchText`:要进行搜索的文本
|
||||
|
||||
- `replaceText`:要进行替换的文本
|
||||
|
||||
返回该节点搜索和替换后的文本内容,注意,不会实际改变节点内容,只是用来计算一个节点替换后的内容。
|
||||
74
web/src/pages/Doc/zh/search/index.vue
Normal file
74
web/src/pages/Doc/zh/search/index.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Search 插件</h1>
|
||||
<blockquote>
|
||||
<p>v0.6.9+</p>
|
||||
</blockquote>
|
||||
<p>该插件提供搜索和替换节点内容的功能。</p>
|
||||
<h2>注册</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> Search <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/plugins/Search.js'</span>
|
||||
MindMap.usePlugin(Search)
|
||||
</code></pre>
|
||||
<p>注册完且实例化<code>MindMap</code>后可通过<code>mindMap.search</code>获取到该实例。</p>
|
||||
<h2>事件</h2>
|
||||
<h3>search_info_change</h3>
|
||||
<p>可以通过监听<code>search_info_change</code>事件来获取当前搜索结果的数量和当前定位到的索引。</p>
|
||||
<pre class="hljs"><code>mindMap.on(<span class="hljs-string">'search_info_change'</span>, <span class="hljs-function">(<span class="hljs-params">data</span>) =></span> {
|
||||
<span class="hljs-comment">/*
|
||||
data: {
|
||||
currentIndex,// 索引,从0开始
|
||||
total
|
||||
}
|
||||
*/</span>
|
||||
})
|
||||
</code></pre>
|
||||
<h2>方法</h2>
|
||||
<h3>search(searchText, callback)</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<p><code>searchText</code>:要进行搜索的文本</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>callback</code>:本次搜索完成的回调函数,会在跳转到节点后触发</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>搜索节点内容,可以重复调用,每调一次,会搜索和定位到下一个匹配的节点。如果搜索文本改变了,那么会重新搜索。</p>
|
||||
<h3>endSearch()</h3>
|
||||
<p>结束搜索。</p>
|
||||
<h3>replace(replaceText)</h3>
|
||||
<ul>
|
||||
<li><code>replaceText</code>:要进行替换的文本</li>
|
||||
</ul>
|
||||
<p>替换当前节点内容,要在调用了<code>search</code>方法之后调用,会替换当前定位到的匹配节点内容。</p>
|
||||
<h3>replaceAll(replaceText)</h3>
|
||||
<ul>
|
||||
<li><code>replaceText</code>:要进行替换的文本</li>
|
||||
</ul>
|
||||
<p>替换所有匹配的节点内容,要在调用了<code>search</code>方法之后调用。</p>
|
||||
<h3>getReplacedText(node, searchText, replaceText)</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<p><code>node</code>:节点实例</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>searchText</code>:要进行搜索的文本</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>replaceText</code>:要进行替换的文本</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>返回该节点搜索和替换后的文本内容,注意,不会实际改变节点内容,只是用来计算一个节点替换后的内容。</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -176,6 +176,27 @@ copyNodeTree({}, node)
|
||||
}
|
||||
```
|
||||
|
||||
#### loadImage(imgFile)
|
||||
|
||||
> v0.6.8+
|
||||
|
||||
- `imgFile`:图片类型的File对象
|
||||
|
||||
加载图片,返回:
|
||||
|
||||
```js
|
||||
{
|
||||
url,// DataUrl
|
||||
size// { width, height } 图片宽高
|
||||
}
|
||||
```
|
||||
|
||||
#### getType(data)
|
||||
|
||||
> v0.6.9+
|
||||
|
||||
获取一个数据的类型,比如`Boolean`、`Array`等。
|
||||
|
||||
## 在canvas中模拟css的背景属性
|
||||
|
||||
引入:
|
||||
|
||||
@@ -116,6 +116,24 @@
|
||||
height
|
||||
}
|
||||
</code></pre>
|
||||
<h4>loadImage(imgFile)</h4>
|
||||
<blockquote>
|
||||
<p>v0.6.8+</p>
|
||||
</blockquote>
|
||||
<ul>
|
||||
<li><code>imgFile</code>:图片类型的File对象</li>
|
||||
</ul>
|
||||
<p>加载图片,返回:</p>
|
||||
<pre class="hljs"><code>{
|
||||
url,<span class="hljs-comment">// DataUrl</span>
|
||||
size<span class="hljs-comment">// { width, height } 图片宽高</span>
|
||||
}
|
||||
</code></pre>
|
||||
<h4>getType(data)</h4>
|
||||
<blockquote>
|
||||
<p>v0.6.9+</p>
|
||||
</blockquote>
|
||||
<p>获取一个数据的类型,比如<code>Boolean</code>、<code>Array</code>等。</p>
|
||||
<h2>在canvas中模拟css的背景属性</h2>
|
||||
<p>引入:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> drawBackgroundImageToCanvas <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/utils/simulateCSSBackgroundInCanvas'</span>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="container" :class="{ isDark: isDark }">
|
||||
<template v-if="show">
|
||||
<Toolbar v-if="!isZenMode"></Toolbar>
|
||||
<Edit></Edit>
|
||||
@@ -26,9 +26,15 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
isZenMode: state => state.localConfig.isZenMode
|
||||
isZenMode: state => state.localConfig.isZenMode,
|
||||
isDark: state => state.isDark
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
isDark() {
|
||||
this.setBodyDark()
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.initLocalConfig()
|
||||
const loading = this.$loading({
|
||||
@@ -38,6 +44,7 @@ export default {
|
||||
await this.getUserMindMapData()
|
||||
this.show = true
|
||||
loading.close()
|
||||
this.setBodyDark()
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['getUserMindMapData']),
|
||||
@@ -56,12 +63,158 @@ export default {
|
||||
...config
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
setBodyDark() {
|
||||
this.isDark
|
||||
? document.body.classList.add('isDark')
|
||||
: document.body.classList.remove('isDark')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.container {
|
||||
<style lang="less">
|
||||
.container {}
|
||||
|
||||
body {
|
||||
&.isDark {
|
||||
/* el-button */
|
||||
.el-button {
|
||||
background-color: #363b3f;
|
||||
color: hsla(0,0%,100%,.9);
|
||||
border-color: hsla(0, 0%, 100%, 0.1);
|
||||
}
|
||||
|
||||
/* el-input */
|
||||
.el-input__inner {
|
||||
background-color: #363b3f;
|
||||
border-color: hsla(0, 0%, 100%, 0.1);
|
||||
color: hsla(0, 0%, 100%, 0.9);
|
||||
}
|
||||
|
||||
.el-input.is-disabled .el-input__inner {
|
||||
background-color: #363b3f;
|
||||
border-color: hsla(0, 0%, 100%, 0.1);
|
||||
color: hsla(0,0%,100%,.3);
|
||||
}
|
||||
|
||||
.el-input-group__append, .el-input-group__prepend {
|
||||
background-color: #363b3f;
|
||||
border-color: hsla(0, 0%, 100%, 0.1);
|
||||
}
|
||||
|
||||
.el-input-group__append button.el-button {
|
||||
color: hsla(0, 0%, 100%, 0.9);
|
||||
}
|
||||
|
||||
/* el-select */
|
||||
.el-select-dropdown {
|
||||
background-color: #36393d;
|
||||
border-color: hsla(0, 0%, 100%, 0.1);
|
||||
|
||||
.el-select-dropdown__item {
|
||||
color: hsla(0, 0%, 100%, 0.6);
|
||||
}
|
||||
|
||||
.el-select-dropdown__item.selected {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.el-select-dropdown__item.hover,
|
||||
.el-select-dropdown__item:hover {
|
||||
background-color: hsla(0, 0%, 100%, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.el-select .el-input.is-disabled .el-input__inner:hover {
|
||||
border-color: hsla(0, 0%, 100%, 0.1);
|
||||
}
|
||||
|
||||
/* el-popper*/
|
||||
.el-popper {
|
||||
background-color: #36393d;
|
||||
border-color: hsla(0, 0%, 100%, 0.1);
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='bottom'] .popper__arrow {
|
||||
background-color: #36393d;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='bottom'] .popper__arrow::after {
|
||||
border-bottom-color: #36393d;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^=top] .popper__arrow {
|
||||
background-color: #36393d;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^=top] .popper__arrow::after {
|
||||
border-top-color: #36393d;
|
||||
}
|
||||
|
||||
/* el-tabs */
|
||||
.el-tabs__item {
|
||||
color: hsla(0, 0%, 100%, 0.6);
|
||||
|
||||
&:hover,
|
||||
&.is-active {
|
||||
color: #409EFF;
|
||||
}
|
||||
}
|
||||
|
||||
.el-tabs__nav-wrap::after {
|
||||
background-color: hsla(0, 0%, 100%, 0.6);
|
||||
}
|
||||
|
||||
/* el-slider */
|
||||
.el-slider__runway {
|
||||
background-color: hsla(0, 0%, 100%, 0.6);
|
||||
}
|
||||
|
||||
/* el-radio-group */
|
||||
.el-radio-group {
|
||||
.el-radio-button__inner {
|
||||
background-color: #36393d;
|
||||
color: hsla(0, 0%, 100%, 0.6);
|
||||
}
|
||||
|
||||
.el-radio-button__orig-radio:checked+.el-radio-button__inner {
|
||||
color: #FFF;
|
||||
background-color: #409EFF;
|
||||
}
|
||||
}
|
||||
|
||||
/* el-dialog */
|
||||
.el-dialog {
|
||||
background-color: #262a2e;
|
||||
|
||||
.el-dialog__header {
|
||||
border-bottom: 1px solid hsla(0,0%,100%,.1);
|
||||
}
|
||||
|
||||
.el-dialog__title {
|
||||
color: hsla(0,0%,100%,.9);
|
||||
}
|
||||
|
||||
.el-dialog__body {
|
||||
background-color: #262a2e;
|
||||
}
|
||||
|
||||
.el-dialog__footer {
|
||||
border-top: 1px solid hsla(0,0%,100%,.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* el-upload */
|
||||
.el-upload__tip {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 富文本编辑器 */
|
||||
.toastui-editor-main-container {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<Sidebar ref="sidebar" :title="$t('baseStyle.title')">
|
||||
<div class="sidebarContent" v-if="data">
|
||||
<div class="sidebarContent" :class="{ isDark: isDark }" v-if="data">
|
||||
<!-- 背景 -->
|
||||
<div class="title noTop">{{ $t('baseStyle.background') }}</div>
|
||||
<div class="row">
|
||||
@@ -711,7 +711,7 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['activeSidebar', 'localConfig']),
|
||||
...mapState(['activeSidebar', 'localConfig', 'isDark']),
|
||||
|
||||
lineStyleList() {
|
||||
return lineStyleList[this.$i18n.locale] || lineStyleList.zh
|
||||
@@ -879,6 +879,12 @@ export default {
|
||||
}
|
||||
this.data.theme.config[this.marginActiveTab][type] = value
|
||||
this.mindMap.setThemeConfig(this.data.theme.config)
|
||||
storeConfig({
|
||||
theme: {
|
||||
template: this.mindMap.getTheme(),
|
||||
config: this.data.theme.config
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 切换显示水印与否
|
||||
@@ -894,6 +900,7 @@ export default {
|
||||
|
||||
// 切换是否开启节点富文本编辑
|
||||
enableNodeRichTextChange(e) {
|
||||
this.mindMap.renderer.textEdit.hideEditTextBox()
|
||||
this.setLocalConfig({
|
||||
openNodeRichText: e
|
||||
})
|
||||
@@ -915,6 +922,20 @@ export default {
|
||||
padding: 20px;
|
||||
padding-top: 10px;
|
||||
|
||||
&.isDark {
|
||||
.title {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.row {
|
||||
.rowItem {
|
||||
.name {
|
||||
color: hsla(0,0%,100%,.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-family: PingFangSC-Medium, PingFang SC;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="colorContainer" :class="{ isDark: isDark }">
|
||||
<div class="colorList">
|
||||
<span
|
||||
class="colorItem"
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
<script>
|
||||
import { colorList } from '@/config'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
@@ -42,6 +43,9 @@ export default {
|
||||
selectColor: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['isDark']),
|
||||
},
|
||||
watch: {
|
||||
color() {
|
||||
this.selectColor = this.color
|
||||
@@ -73,6 +77,14 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.colorContainer {
|
||||
&.isDark {
|
||||
.moreColor {
|
||||
color: hsla(0, 0%, 100%, 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.colorList {
|
||||
width: 240px;
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
:class="{ disabled: insertNodeBtnDisabled }"
|
||||
>
|
||||
{{ $t('contextmenu.insertSummary') }}
|
||||
<span class="desc">Ctrl + S</span>
|
||||
<span class="desc">Ctrl + G</span>
|
||||
</div>
|
||||
<div
|
||||
class="item"
|
||||
@@ -55,7 +55,6 @@
|
||||
</div>
|
||||
<div
|
||||
class="item"
|
||||
:class="{ disabled: copyData === null }"
|
||||
@click="exec('PASTE_NODE')"
|
||||
>
|
||||
{{ $t('contextmenu.pasteNode') }}
|
||||
@@ -123,7 +122,6 @@ export default {
|
||||
left: 0,
|
||||
top: 0,
|
||||
node: null,
|
||||
copyData: null,
|
||||
type: '',
|
||||
isMousedown: false,
|
||||
mosuedownX: 0,
|
||||
@@ -180,10 +178,6 @@ export default {
|
||||
this.$bus.$on('expand_btn_click', this.hide)
|
||||
this.$bus.$on('svg_mousedown', this.onMousedown)
|
||||
this.$bus.$on('mouseup', this.onMouseup)
|
||||
// 注册快捷键
|
||||
this.mindMap.keyCommand.addShortcut('Control+c', this.copy)
|
||||
this.mindMap.keyCommand.addShortcut('Control+v', this.paste)
|
||||
this.mindMap.keyCommand.addShortcut('Control+x', this.cut)
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$bus.$off('node_contextmenu', this.show)
|
||||
@@ -192,10 +186,6 @@ export default {
|
||||
this.$bus.$off('expand_btn_click', this.hide)
|
||||
this.$bus.$on('svg_mousedown', this.onMousedown)
|
||||
this.$bus.$on('mouseup', this.onMouseup)
|
||||
// 移除快捷键
|
||||
this.mindMap.keyCommand.removeShortcut('Control+c', this.copy)
|
||||
this.mindMap.keyCommand.removeShortcut('Control+v', this.paste)
|
||||
this.mindMap.keyCommand.removeShortcut('Control+x', this.cut)
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['setLocalConfig']),
|
||||
@@ -282,15 +272,13 @@ export default {
|
||||
}
|
||||
switch (key) {
|
||||
case 'COPY_NODE':
|
||||
this.copyData = this.mindMap.renderer.copyNode()
|
||||
this.mindMap.renderer.copy()
|
||||
break
|
||||
case 'CUT_NODE':
|
||||
this.$bus.$emit('execCommand', key, copyData => {
|
||||
this.copyData = copyData
|
||||
})
|
||||
this.mindMap.renderer.cut()
|
||||
break
|
||||
case 'PASTE_NODE':
|
||||
this.$bus.$emit('execCommand', key, this.copyData)
|
||||
this.mindMap.renderer.paste()
|
||||
break
|
||||
case 'RETURN_CENTER':
|
||||
this.mindMap.view.reset()
|
||||
@@ -308,33 +296,6 @@ export default {
|
||||
break
|
||||
}
|
||||
this.hide()
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-08-04 14:25:45
|
||||
* @Desc: 复制
|
||||
*/
|
||||
copy() {
|
||||
this.exec('COPY_NODE')
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-08-04 14:26:43
|
||||
* @Desc: 粘贴
|
||||
*/
|
||||
paste() {
|
||||
this.exec('PASTE_NODE')
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-08-04 14:27:32
|
||||
* @Desc: 剪切
|
||||
*/
|
||||
cut() {
|
||||
this.exec('CUT_NODE')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="countContainer">
|
||||
<div class="countContainer" :class="{ isDark: isDark }">
|
||||
<div class="item">
|
||||
<span class="name">{{ $t('count.words') }}</span>
|
||||
<span class="value">{{ words }}</span>
|
||||
@@ -12,6 +12,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-24 22:53:10
|
||||
@@ -29,6 +31,9 @@ export default {
|
||||
num: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['isDark']),
|
||||
},
|
||||
created() {
|
||||
this.$bus.$on('data_change', this.onDataChange)
|
||||
},
|
||||
@@ -82,6 +87,14 @@ export default {
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
|
||||
&.isDark {
|
||||
background: #262a2e;
|
||||
|
||||
.item {
|
||||
color: hsla(0,0%,100%,.6);
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
color: #555;
|
||||
margin-right: 15px;
|
||||
@@ -95,4 +108,10 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 635px) {
|
||||
.countContainer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<Outline :mindMap="mindMap"></Outline>
|
||||
<Style v-if="!isZenMode"></Style>
|
||||
<BaseStyle :data="mindMapData" :mindMap="mindMap"></BaseStyle>
|
||||
<Theme :mindMap="mindMap"></Theme>
|
||||
<Theme v-if="mindMap" :mindMap="mindMap"></Theme>
|
||||
<Structure :mindMap="mindMap"></Structure>
|
||||
<ShortcutKey></ShortcutKey>
|
||||
<Contextmenu v-if="mindMap" :mindMap="mindMap"></Contextmenu>
|
||||
@@ -18,6 +18,7 @@
|
||||
></NodeNoteContentShow>
|
||||
<NodeImgPreview v-if="mindMap" :mindMap="mindMap"></NodeImgPreview>
|
||||
<SidebarTrigger v-if="!isZenMode"></SidebarTrigger>
|
||||
<Search v-if="mindMap" :mindMap="mindMap"></Search>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -35,6 +36,7 @@ import RichText from 'simple-mind-map/src/plugins/RichText.js'
|
||||
import AssociativeLine from 'simple-mind-map/src/plugins/AssociativeLine.js'
|
||||
import TouchEvent from 'simple-mind-map/src/plugins/TouchEvent.js'
|
||||
import NodeImgAdjust from 'simple-mind-map/src/plugins/NodeImgAdjust.js'
|
||||
import SearchPlugin from 'simple-mind-map/src/plugins/Search.js'
|
||||
import Outline from './Outline'
|
||||
import Style from './Style'
|
||||
import BaseStyle from './BaseStyle'
|
||||
@@ -59,6 +61,7 @@ import Vue from 'vue'
|
||||
import router from '../../../router'
|
||||
import store from '../../../store'
|
||||
import i18n from '../../../i18n'
|
||||
import Search from './Search.vue'
|
||||
|
||||
// 注册插件
|
||||
MindMap
|
||||
@@ -73,6 +76,7 @@ MindMap
|
||||
.usePlugin(AssociativeLine)
|
||||
.usePlugin(NodeImgAdjust)
|
||||
.usePlugin(TouchEvent)
|
||||
.usePlugin(SearchPlugin)
|
||||
|
||||
// 注册自定义主题
|
||||
// customThemeList.forEach((item) => {
|
||||
@@ -100,7 +104,8 @@ export default {
|
||||
NodeNoteContentShow,
|
||||
Navigator,
|
||||
NodeImgPreview,
|
||||
SidebarTrigger
|
||||
SidebarTrigger,
|
||||
Search
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
element-loading-spinner="el-icon-loading"
|
||||
element-loading-background="rgba(0, 0, 0, 0.8)"
|
||||
>
|
||||
<div>
|
||||
<div class="exportContainer" :class="{ isDark: isDark }">
|
||||
<div class="nameInputBox">
|
||||
<span class="name">{{ $t('export.filename') }}</span>
|
||||
<el-input
|
||||
@@ -99,6 +99,7 @@ export default {
|
||||
computed: {
|
||||
...mapState({
|
||||
openNodeRichText: state => state.localConfig.openNodeRichText,
|
||||
isDark: state => state.isDark
|
||||
}),
|
||||
|
||||
downTypeList() {
|
||||
@@ -180,6 +181,22 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.exportContainer {
|
||||
&.isDark {
|
||||
.downloadTypeList {
|
||||
.downloadTypeItem {
|
||||
background-color: #363b3f;
|
||||
|
||||
.info {
|
||||
.name {
|
||||
color: hsla(0,0%,100%,.9);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nodeDialog {
|
||||
/deep/ .el-dialog__body {
|
||||
background-color: #f2f4f7;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="fullscreenContainer">
|
||||
<div class="fullscreenContainer" :class="{ isDark: isDark }">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
@@ -32,6 +32,9 @@ export default {
|
||||
props: {
|
||||
mindMap: {
|
||||
type: Object
|
||||
},
|
||||
isDark: {
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -63,6 +66,12 @@ export default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.isDark {
|
||||
.btn {
|
||||
color: hsla(0,0%,100%,.6);
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
margin-right: 12px;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="mouseActionContainer">
|
||||
<div class="mouseActionContainer" :class="{ isDark: isDark }">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
@@ -32,6 +32,9 @@ export default {
|
||||
props: {
|
||||
mindMap: {
|
||||
type: Object
|
||||
},
|
||||
isDark: {
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -64,6 +67,12 @@ export default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.isDark{
|
||||
.btn {
|
||||
color: hsla(0,0%,100%,.6);
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
margin-right: 12px;
|
||||
|
||||
@@ -74,6 +83,7 @@ export default {
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<div
|
||||
v-if="showMiniMap"
|
||||
class="navigatorBox"
|
||||
:class="{ isDark: isDark }"
|
||||
ref="navigatorBox"
|
||||
@mousedown="onMousedown"
|
||||
@mousemove="onMousemove"
|
||||
@@ -21,6 +22,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
mindMap: {
|
||||
@@ -44,6 +47,9 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['isDark']),
|
||||
},
|
||||
mounted() {
|
||||
this.$bus.$on('toggle_mini_map', this.toggle_mini_map)
|
||||
this.$bus.$on('data_change', this.data_change)
|
||||
@@ -128,6 +134,10 @@ export default {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
&.isDark {
|
||||
background-color: #262a2e;
|
||||
}
|
||||
|
||||
.svgBox {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="navigatorContainer">
|
||||
<div class="navigatorContainer" :class="{ isDark: isDark }">
|
||||
<div class="item">
|
||||
<el-select
|
||||
v-model="lang"
|
||||
@@ -16,27 +16,53 @@
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="item">
|
||||
<MouseAction :mindMap="mindMap"></MouseAction>
|
||||
<MouseAction :isDark="isDark" :mindMap="mindMap"></MouseAction>
|
||||
</div>
|
||||
<div class="item">
|
||||
<el-checkbox v-model="openMiniMap" @change="toggleMiniMap">{{
|
||||
$t('navigatorToolbar.openMiniMap')
|
||||
}}</el-checkbox>
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="
|
||||
openMiniMap
|
||||
? $t('navigatorToolbar.closeMiniMap')
|
||||
: $t('navigatorToolbar.openMiniMap')
|
||||
"
|
||||
placement="top"
|
||||
>
|
||||
<div
|
||||
class="btn iconfont icondaohang1"
|
||||
@click="toggleMiniMap"
|
||||
></div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="item">
|
||||
<el-switch
|
||||
<!-- <el-switch
|
||||
v-model="isReadonly"
|
||||
:active-text="$t('navigatorToolbar.readonly')"
|
||||
:inactive-text="$t('navigatorToolbar.edit')"
|
||||
@change="readonlyChange"
|
||||
>
|
||||
</el-switch>
|
||||
</el-switch> -->
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="
|
||||
isReadonly
|
||||
? $t('navigatorToolbar.edit')
|
||||
: $t('navigatorToolbar.readonly')
|
||||
"
|
||||
placement="top"
|
||||
>
|
||||
<div
|
||||
class="btn iconfont"
|
||||
:class="[isReadonly ? 'iconyanjing' : 'iconbianji1']"
|
||||
@click="readonlyChange"
|
||||
></div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="item">
|
||||
<Scale :mindMap="mindMap"></Scale>
|
||||
<Fullscreen :isDark="isDark" :mindMap="mindMap"></Fullscreen>
|
||||
</div>
|
||||
<div class="item">
|
||||
<Fullscreen :mindMap="mindMap"></Fullscreen>
|
||||
<Scale :isDark="isDark" :mindMap="mindMap"></Scale>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a href="https://github.com/wanglin2/mind-map" target="_blank">
|
||||
@@ -53,6 +79,7 @@ import MouseAction from './MouseAction.vue'
|
||||
import { langList } from '@/config'
|
||||
import i18n from '@/i18n'
|
||||
import { storeLang, getLang } from '@/api'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
@@ -79,16 +106,18 @@ export default {
|
||||
openMiniMap: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.toggleMiniMap(this.openMiniMap)
|
||||
computed: {
|
||||
...mapState(['isDark']),
|
||||
},
|
||||
methods: {
|
||||
readonlyChange(value) {
|
||||
this.mindMap.setMode(value ? 'readonly' : 'edit')
|
||||
readonlyChange() {
|
||||
this.isReadonly = !this.isReadonly
|
||||
this.mindMap.setMode(this.isReadonly ? 'readonly' : 'edit')
|
||||
},
|
||||
|
||||
toggleMiniMap(show) {
|
||||
this.$bus.$emit('toggle_mini_map', show)
|
||||
toggleMiniMap() {
|
||||
this.openMiniMap = !this.openMiniMap
|
||||
this.$bus.$emit('toggle_mini_map', this.openMiniMap)
|
||||
},
|
||||
|
||||
onLangChange(lang) {
|
||||
@@ -113,6 +142,20 @@ export default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.isDark {
|
||||
background: #262a2e;
|
||||
|
||||
.item {
|
||||
a {
|
||||
color: hsla(0,0%,100%,.6);
|
||||
}
|
||||
|
||||
.btn {
|
||||
color: hsla(0,0%,100%,.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
margin-right: 20px;
|
||||
|
||||
@@ -124,6 +167,18 @@ export default {
|
||||
color: #303133;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 502px) {
|
||||
.navigatorContainer {
|
||||
left: 20px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<Sidebar ref="sidebar" :title="$t('outline.title')">
|
||||
<el-tree
|
||||
class="outlineTree"
|
||||
:class="{ isDark: isDark }"
|
||||
:data="data"
|
||||
:props="defaultProps"
|
||||
:expand-on-click-node="false"
|
||||
@@ -46,15 +47,14 @@ export default {
|
||||
data: [],
|
||||
defaultProps: {
|
||||
label(data) {
|
||||
return data.data.text.replaceAll(/\n/g, '</br>')
|
||||
return data.data.richText ? data.data.text : data.data.text.replaceAll(/\n/g, '</br>')
|
||||
}
|
||||
},
|
||||
notHandleDataChange: false,
|
||||
isCreateNode: false
|
||||
notHandleDataChange: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['activeSidebar'])
|
||||
...mapState(['activeSidebar', 'isDark'])
|
||||
},
|
||||
watch: {
|
||||
activeSidebar(val) {
|
||||
@@ -77,11 +77,12 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
onBlur(e, node) {
|
||||
if (this.isCreateNode) {
|
||||
this.isCreateNode = false
|
||||
return
|
||||
const richText = node.data.data.richText
|
||||
if (richText) {
|
||||
node.data._node.setText(e.target.innerHTML, true)
|
||||
} else {
|
||||
node.data._node.setText(e.target.innerText)
|
||||
}
|
||||
node.data._node.setText(e.target.innerText)
|
||||
},
|
||||
|
||||
getKey() {
|
||||
@@ -102,24 +103,21 @@ export default {
|
||||
// 插入兄弟节点
|
||||
insertNode() {
|
||||
this.notHandleDataChange = false
|
||||
this.isCreateNode = true
|
||||
this.mindMap.execCommand('INSERT_NODE', false)
|
||||
},
|
||||
|
||||
// 插入下级节点
|
||||
insertChildNode() {
|
||||
this.notHandleDataChange = false
|
||||
this.isCreateNode = true
|
||||
this.mindMap.execCommand('INSERT_CHILD_NODE', false)
|
||||
},
|
||||
|
||||
// 激活当前节点且移动当前节点到画布中间
|
||||
onClick(e, data) {
|
||||
onClick(e, node) {
|
||||
this.notHandleDataChange = true
|
||||
let node = data.data._node
|
||||
if (node.nodeData.data.isActive) return
|
||||
node.mindMap.renderer.moveNodeToCenter(node)
|
||||
node.active()
|
||||
let targetNode = node.data._node
|
||||
if (targetNode && targetNode.nodeData.data.isActive) return
|
||||
this.mindMap.execCommand('GO_TARGET_NODE', node.data.data.uid)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -153,6 +151,10 @@ export default {
|
||||
}
|
||||
|
||||
.outlineTree {
|
||||
|
||||
&.isDark {
|
||||
background-color: #262a2e;
|
||||
}
|
||||
/deep/ .el-tree-node__content {
|
||||
height: auto;
|
||||
margin: 5px 0;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="scaleContainer">
|
||||
<div class="scaleContainer" :class="{ isDark: isDark }">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
@@ -31,6 +31,9 @@ export default {
|
||||
props: {
|
||||
mindMap: {
|
||||
type: Object
|
||||
},
|
||||
isDark: {
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -84,6 +87,16 @@ export default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.isDark {
|
||||
.btn {
|
||||
color: hsla(0,0%,100%,.6);
|
||||
}
|
||||
|
||||
.scaleInfo {
|
||||
color: hsla(0,0%,100%,.6);
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user