mirror of
https://github.com/wanglin2/mind-map.git
synced 2026-02-21 18:37:43 +08:00
Merge branch 'feature' into main
This commit is contained in:
commit
74d37f2cbc
@ -140,4 +140,12 @@ const mindMap = new MindMap({
|
||||
<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
@ -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'
|
||||
|
||||
@ -35,6 +35,9 @@ class MindMap {
|
||||
// 初始化主题
|
||||
this.initTheme()
|
||||
|
||||
// 初始化缓存数据
|
||||
this.initCache()
|
||||
|
||||
// 事件类
|
||||
this.event = new Event({
|
||||
mindMap: this
|
||||
@ -129,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() {
|
||||
// 合并主题配置
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.6.8",
|
||||
"version": "0.6.9",
|
||||
"description": "一个简单的web在线思维导图",
|
||||
"authors": [
|
||||
{
|
||||
|
||||
@ -324,4 +324,9 @@ export const nodeDataNoStylePropList = [
|
||||
'resetRichText',
|
||||
'uid',
|
||||
'activeStyle'
|
||||
]
|
||||
]
|
||||
|
||||
// 数据缓存
|
||||
export const commonCaches = {
|
||||
measureCustomNodeContentSizeEl: null
|
||||
}
|
||||
@ -444,6 +444,7 @@ class Render {
|
||||
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
|
||||
return
|
||||
}
|
||||
this.textEdit.hideEditTextBox()
|
||||
let {
|
||||
defaultInsertSecondLevelNodeText,
|
||||
defaultInsertBelowSecondLevelNodeText
|
||||
@ -486,6 +487,7 @@ class Render {
|
||||
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
|
||||
return
|
||||
}
|
||||
this.textEdit.hideEditTextBox()
|
||||
let {
|
||||
defaultInsertSecondLevelNodeText,
|
||||
defaultInsertBelowSecondLevelNodeText
|
||||
@ -973,7 +975,8 @@ class Render {
|
||||
setNodeText(node, text, richText) {
|
||||
this.setNodeDataRender(node, {
|
||||
text,
|
||||
richText
|
||||
richText,
|
||||
resetRichText: richText
|
||||
})
|
||||
}
|
||||
|
||||
@ -1099,7 +1102,7 @@ class Render {
|
||||
}
|
||||
|
||||
// 定位到指定节点
|
||||
goTargetNode(node) {
|
||||
goTargetNode(node, callback = () => {}) {
|
||||
let uid = typeof node === 'string' ? node : node.nodeData.data.uid
|
||||
if (!uid) return
|
||||
this.expandToNodeUid(uid, () => {
|
||||
@ -1107,6 +1110,7 @@ class Render {
|
||||
if (targetNode) {
|
||||
targetNode.active()
|
||||
this.moveNodeToCenter(targetNode)
|
||||
callback()
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -1119,7 +1123,7 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置节点数据,并判断是否渲染
|
||||
setNodeDataRender(node, data) {
|
||||
setNodeDataRender(node, data, notRender = false) {
|
||||
this.setNodeData(node, data)
|
||||
let changed = node.reRender()
|
||||
if (changed) {
|
||||
@ -1127,7 +1131,7 @@ class Render {
|
||||
// 概要节点
|
||||
node.generalizationBelongNode.updateGeneralization()
|
||||
}
|
||||
this.mindMap.render()
|
||||
if (!notRender) this.mindMap.render()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -106,6 +106,9 @@ export default class TextEdit {
|
||||
this.mindMap.keyCommand.addShortcut('Enter', () => {
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
this.mindMap.keyCommand.addShortcut('Tab', () => {
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
}
|
||||
|
||||
// 显示文本编辑框
|
||||
|
||||
@ -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
|
||||
|
||||
@ -36,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
|
||||
@ -67,6 +66,7 @@ function renderGeneralization () {
|
||||
|
||||
// 删除概要节点
|
||||
function removeGeneralization () {
|
||||
if (this.isGeneralization) return
|
||||
if (this._generalizationLine) {
|
||||
this._generalizationLine.remove()
|
||||
this._generalizationLine = null
|
||||
@ -87,6 +87,7 @@ function removeGeneralization () {
|
||||
|
||||
// 隐藏概要节点
|
||||
function hideGeneralization () {
|
||||
if (this.isGeneralization) return
|
||||
if (this._generalizationLine) {
|
||||
this._generalizationLine.hide()
|
||||
}
|
||||
@ -97,6 +98,7 @@ function hideGeneralization () {
|
||||
|
||||
// 显示概要节点
|
||||
function showGeneralization () {
|
||||
if (this.isGeneralization) return
|
||||
if (this._generalizationLine) {
|
||||
this._generalizationLine.show()
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// 小地图类
|
||||
// 小地图插件
|
||||
class MiniMap {
|
||||
// 构造函数
|
||||
constructor(opt) {
|
||||
|
||||
@ -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键
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
143
simple-mind-map/src/plugins/Search.js
Normal file
143
simple-mind-map/src/plugins/Search.js
Normal file
@ -0,0 +1,143 @@
|
||||
import { bfsWalk, getTextFromHtml } from '../utils/index'
|
||||
|
||||
// 搜索插件
|
||||
class Search {
|
||||
// 构造函数
|
||||
constructor({ mindMap }) {
|
||||
this.mindMap = mindMap
|
||||
// 是否正在搜索
|
||||
this.isSearching = false
|
||||
// 搜索文本
|
||||
this.searchText = ''
|
||||
// 匹配的节点列表
|
||||
this.matchNodeList = []
|
||||
// 当前所在的节点列表索引
|
||||
this.currentIndex = -1
|
||||
// 是否正在跳转中
|
||||
this.isJumping = false
|
||||
this.onDataChange = this.onDataChange.bind(this)
|
||||
this.mindMap.on('data_change', this.onDataChange)
|
||||
}
|
||||
|
||||
// 节点数据改变了,需要重新搜索
|
||||
onDataChange() {
|
||||
if (this.isJumping) 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.isJumping = 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.isJumping = true
|
||||
this.mindMap.execCommand('GO_TARGET_NODE', currentNode, () => {
|
||||
this.isJumping = false
|
||||
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)
|
||||
currentNode.setText(text, currentNode.nodeData.data.richText)
|
||||
this.matchNodeList = this.matchNodeList.filter(node => {
|
||||
return currentNode !== node
|
||||
})
|
||||
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
|
||||
|
||||
@ -453,3 +453,16 @@ export const loadImage = imgFile => {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 移除字符串中的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/仓鼠.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=1689407546912') format('woff2'),
|
||||
url('iconfont.woff?t=1689407546912') format('woff'),
|
||||
url('iconfont.ttf?t=1689407546912') 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,10 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.iconsousuo:before {
|
||||
content: "\e693";
|
||||
}
|
||||
|
||||
.iconjiantouyou:before {
|
||||
content: "\e62d";
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -271,6 +271,11 @@ export const shortcutKeyList = [
|
||||
icon: 'iconzhengli',
|
||||
name: 'Arrange layout',
|
||||
value: 'Ctrl + L'
|
||||
},
|
||||
{
|
||||
icon: 'iconsousuo',
|
||||
name: 'Search and Replace',
|
||||
value: 'Ctrl + F'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@ -331,6 +331,11 @@ export const shortcutKeyList = [
|
||||
icon: 'iconzhengli',
|
||||
name: '一键整理布局',
|
||||
value: 'Ctrl + L'
|
||||
},
|
||||
{
|
||||
icon: 'iconsousuo',
|
||||
name: '搜索和替换',
|
||||
value: 'Ctrl + F'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@ -210,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'
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,5 +210,12 @@ export default {
|
||||
mouseAction: {
|
||||
tip1: '当前:左键拖动画布,右键框选节点',
|
||||
tip2: '当前:左键框选节点,右键拖动画布',
|
||||
},
|
||||
search: {
|
||||
searchPlaceholder: '请输入查找内容',
|
||||
replacePlaceholder: '请输入替换内容',
|
||||
replace: '替换',
|
||||
replaceAll: '全部替换',
|
||||
cancel: '取消'
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,6 +32,7 @@ let APIList = [
|
||||
'associativeLine',
|
||||
'touchEvent',
|
||||
'nodeImgAdjust',
|
||||
'search',
|
||||
'xmind',
|
||||
'markdown',
|
||||
'utils'
|
||||
|
||||
@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## 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.
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Changelog</h1>
|
||||
<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>
|
||||
|
||||
@ -347,7 +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) |
|
||||
| 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)
|
||||
|
||||
|
||||
@ -923,7 +923,7 @@ redo. All commands are as follows:</p>
|
||||
<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)</td>
|
||||
<td>node(Node instance or node uid to locate)、callback(v0.6.9+, Callback function after positioning completion)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@ -160,4 +160,12 @@ Open source is not easy. If this project is helpful to you, you can invite the a
|
||||
<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>
|
||||
@ -119,6 +119,14 @@ full screen, support mini map</li>
|
||||
<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`
|
||||
|
||||
@ -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>
|
||||
|
||||
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>
|
||||
@ -196,6 +196,12 @@ Load image, return:
|
||||
}
|
||||
```
|
||||
|
||||
#### getType(data)
|
||||
|
||||
> v0.6.9+
|
||||
|
||||
Get the type of a data, such as `Boolean`、`Array`.
|
||||
|
||||
## Simulate CSS background in Canvas
|
||||
|
||||
Import:
|
||||
|
||||
@ -134,6 +134,11 @@ and copying the <code>data</code> of the data object, example:</p>
|
||||
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>
|
||||
|
||||
@ -49,6 +49,7 @@ export default [
|
||||
{ path: 'client', title: '客户端' },
|
||||
{ path: 'touchEvent', title: 'TouchEvent插件' },
|
||||
{ path: 'nodeImgAdjust', title: 'NodeImgAdjust插件' },
|
||||
{ path: 'search', title: 'Search插件' },
|
||||
{ path: 'help1', title: '概要/关联线' },
|
||||
{ path: 'help2', title: '客户端' }
|
||||
]
|
||||
@ -80,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,13 @@
|
||||
# Changelog
|
||||
|
||||
## 0.6.9
|
||||
|
||||
修复:1.修复给概要节点设置样式概要节点会消失的问题。2.修复自定义节点内容时,二次创建根实例时节点内容不渲染的问题。3.修复节点处于编辑中时添加新节点时新节点的焦点丢失问题。 2.修复连续按tab键无法连续创建子节点的问题。
|
||||
|
||||
新增:1.导出svg时替换svg中存在的` `字符,避免导出的svg报错。 2.支持搜索和替换。
|
||||
|
||||
Demo:1.切换主题时支持选择是否覆盖设置过的基础样式。
|
||||
|
||||
## 0.6.8
|
||||
|
||||
修复:1.修改插入概要的快捷键为Ctrl+G,避免和保存快捷键冲突。 2.修复节点正在编辑时切换富文本编辑配置输入框出现异常的问题。
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Changelog</h1>
|
||||
<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>
|
||||
|
||||
@ -340,7 +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) |
|
||||
| GO_TARGET_NODE(v0.6.7+) | 定位到某个节点,如果该节点被收起,那么会自动展开到该节点 | node(要定位到的节点实例或节点uid)、callback(v0.6.9+,定位完成后的回调函数) |
|
||||
|
||||
### setData(data)
|
||||
|
||||
|
||||
@ -918,7 +918,7 @@ mindMap.setTheme(<span class="hljs-string">'主题名称'</span>)
|
||||
<tr>
|
||||
<td>GO_TARGET_NODE(v0.6.7+)</td>
|
||||
<td>定位到某个节点,如果该节点被收起,那么会自动展开到该节点</td>
|
||||
<td>node(要定位到的节点实例或节点uid)</td>
|
||||
<td>node(要定位到的节点实例或节点uid)、callback(v0.6.9+,定位完成后的回调函数)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@ -151,4 +151,12 @@
|
||||
<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>
|
||||
@ -111,6 +111,14 @@
|
||||
<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: '我是新文本'}`
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
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>
|
||||
@ -191,6 +191,12 @@ copyNodeTree({}, node)
|
||||
}
|
||||
```
|
||||
|
||||
#### getType(data)
|
||||
|
||||
> v0.6.9+
|
||||
|
||||
获取一个数据的类型,比如`Boolean`、`Array`等。
|
||||
|
||||
## 在canvas中模拟css的背景属性
|
||||
|
||||
引入:
|
||||
|
||||
@ -129,6 +129,11 @@
|
||||
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>
|
||||
|
||||
@ -79,6 +79,14 @@ export default {
|
||||
|
||||
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);
|
||||
@ -91,6 +99,15 @@ body {
|
||||
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;
|
||||
|
||||
@ -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 {
|
||||
|
||||
192
web/src/pages/Edit/components/Search.vue
Normal file
192
web/src/pages/Edit/components/Search.vue
Normal file
@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<div class="searchContainer" :class="{ isDark: isDark, show: show }">
|
||||
<div class="closeBtnBox">
|
||||
<span class="closeBtn el-icon-close" @click="close"></span>
|
||||
</div>
|
||||
<div class="searchInputBox">
|
||||
<el-input
|
||||
ref="input"
|
||||
:placeholder="$t('search.searchPlaceholder')"
|
||||
size="small"
|
||||
v-model="searchText"
|
||||
@keyup.native.enter.stop="onSearchNext"
|
||||
>
|
||||
<i slot="prefix" class="el-input__icon el-icon-search"></i>
|
||||
<el-button
|
||||
size="small"
|
||||
slot="append"
|
||||
v-if="!!searchText.trim()"
|
||||
@click="showReplaceInput = true"
|
||||
>{{ $t('search.replace') }}</el-button
|
||||
>
|
||||
</el-input>
|
||||
<div class="searchInfo" v-if="showSearchInfo">
|
||||
{{ currentIndex }} / {{ total }}
|
||||
</div>
|
||||
</div>
|
||||
<el-input
|
||||
v-if="showReplaceInput"
|
||||
:placeholder="$t('search.replacePlaceholder')"
|
||||
size="small"
|
||||
v-model="replaceText"
|
||||
style="margin: 12px 0;"
|
||||
>
|
||||
<i slot="prefix" class="el-input__icon el-icon-edit"></i>
|
||||
<el-button size="small" slot="append" @click="hideReplaceInput">{{
|
||||
$t('search.cancel')
|
||||
}}</el-button>
|
||||
</el-input>
|
||||
<div class="btnList" v-if="showReplaceInput">
|
||||
<el-button size="small" @click="replace">{{
|
||||
$t('search.replace')
|
||||
}}</el-button>
|
||||
<el-button size="small" @click="replaceAll">{{
|
||||
$t('search.replaceAll')
|
||||
}}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
// 搜索替换
|
||||
export default {
|
||||
name: 'Search',
|
||||
props: {
|
||||
mindMap: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
searchText: '',
|
||||
replaceText: '',
|
||||
showReplaceInput: false,
|
||||
currentIndex: 0,
|
||||
total: 0,
|
||||
showSearchInfo: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['isDark'])
|
||||
},
|
||||
watch: {
|
||||
searchText() {
|
||||
if (!this.searchText.trim()) {
|
||||
this.currentIndex = 0
|
||||
this.total = 0
|
||||
this.showSearchInfo = false
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.mindMap.on('search_info_change', data => {
|
||||
this.currentIndex = data.currentIndex + 1
|
||||
this.total = data.total
|
||||
this.showSearchInfo = true
|
||||
})
|
||||
this.mindMap.keyCommand.addShortcut('Control+f', () => {
|
||||
this.$bus.$emit('closeSideBar')
|
||||
this.show = true
|
||||
this.$refs.input.focus()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
hideReplaceInput() {
|
||||
this.showReplaceInput = false
|
||||
this.replaceText = ''
|
||||
},
|
||||
|
||||
onSearchNext() {
|
||||
this.mindMap.search.search(this.searchText, () => {
|
||||
this.$refs.input.focus()
|
||||
})
|
||||
},
|
||||
|
||||
replace() {
|
||||
this.mindMap.search.replace(this.replaceText)
|
||||
},
|
||||
|
||||
replaceAll() {
|
||||
this.mindMap.search.replaceAll(this.replaceText)
|
||||
},
|
||||
|
||||
close() {
|
||||
this.show = false
|
||||
this.showSearchInfo = false
|
||||
this.total = 0
|
||||
this.currentIndex = 0
|
||||
this.searchText = ''
|
||||
this.hideReplaceInput()
|
||||
this.mindMap.search.endSearch()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.searchContainer {
|
||||
position: relative;
|
||||
background-color: #fff;
|
||||
padding: 16px;
|
||||
width: 296px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.1);
|
||||
position: fixed;
|
||||
top: 110px;
|
||||
right: -296px;
|
||||
transition: all 0.3s;
|
||||
|
||||
&.isDark {
|
||||
background-color: #363b3f;
|
||||
|
||||
.closeBtnBox {
|
||||
color: #fff;
|
||||
background-color: #363b3f;
|
||||
}
|
||||
}
|
||||
|
||||
&.show {
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
.btnList {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.closeBtnBox {
|
||||
position: absolute;
|
||||
right: -5px;
|
||||
top: -5px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.1);
|
||||
|
||||
.closeBtn {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.searchInputBox {
|
||||
position: relative;
|
||||
|
||||
.searchInfo {
|
||||
position: absolute;
|
||||
right: 70px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #909090;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -39,7 +39,7 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['isDark']),
|
||||
...mapState(['isDark'])
|
||||
},
|
||||
watch: {
|
||||
show(val, oldVal) {
|
||||
@ -48,6 +48,11 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$bus.$on('closeSideBar', () => {
|
||||
this.close()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['setActiveSidebar']),
|
||||
|
||||
@ -74,10 +79,10 @@ export default {
|
||||
|
||||
&.isDark {
|
||||
background-color: #262a2e;
|
||||
border-left-color: hsla(0,0%,100%,.1);
|
||||
border-left-color: hsla(0, 0%, 100%, 0.1);
|
||||
|
||||
.sidebarHeader {
|
||||
border-bottom-color: hsla(0,0%,100%,.1);
|
||||
border-bottom-color: hsla(0, 0%, 100%, 0.1);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
|
||||
@ -42,7 +42,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
themeList: [...themeList].reverse(),// ...customThemeList
|
||||
themeList: [...themeList].reverse(), // ...customThemeList
|
||||
themeMap,
|
||||
theme: ''
|
||||
}
|
||||
@ -61,32 +61,48 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
created() {
|
||||
this.theme = this.mindMap.getTheme()
|
||||
this.handleDark()
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['setIsDark']),
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-24 23:04:38
|
||||
* @Desc: 使用主题
|
||||
*/
|
||||
useTheme(theme) {
|
||||
this.theme = theme.value
|
||||
this.handleDark()
|
||||
const customThemeConfig = this.mindMap.getCustomThemeConfig()
|
||||
const hasCustomThemeConfig = Object.keys(customThemeConfig).length > 0
|
||||
if (hasCustomThemeConfig) {
|
||||
this.$confirm('你当前自定义过基础样式,是否覆盖?', '提示', {
|
||||
confirmButtonText: '覆盖',
|
||||
cancelButtonText: '保留',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => {
|
||||
this.mindMap.setThemeConfig({})
|
||||
this.changeTheme(theme, {})
|
||||
})
|
||||
.catch(() => {
|
||||
this.changeTheme(theme, customThemeConfig)
|
||||
})
|
||||
} else {
|
||||
this.changeTheme(theme, customThemeConfig)
|
||||
}
|
||||
},
|
||||
|
||||
changeTheme(theme, config) {
|
||||
this.mindMap.setTheme(theme.value)
|
||||
storeConfig({
|
||||
theme: {
|
||||
template: theme.value,
|
||||
config: this.mindMap.getCustomThemeConfig()
|
||||
config
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
handleDark() {
|
||||
let target = themeList.find((item) => {
|
||||
let target = themeList.find(item => {
|
||||
return item.value === this.theme
|
||||
})
|
||||
this.setIsDark(target.dark)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user