diff --git a/README.md b/README.md index a95388aa..0255d124 100644 --- a/README.md +++ b/README.md @@ -140,4 +140,12 @@ const mindMap = new MindMap({ Chris + + + 水车 + + + + 仓鼠 +

\ No newline at end of file diff --git a/index.html b/index.html index 39ec0a9f..553768bd 100644 --- a/index.html +++ b/index.html @@ -1 +1 @@ -思绪思维导图
\ No newline at end of file +思绪思维导图
\ No newline at end of file diff --git a/simple-mind-map/full.js b/simple-mind-map/full.js index c119beb8..2c78b8be 100644 --- a/simple-mind-map/full.js +++ b/simple-mind-map/full.js @@ -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 \ No newline at end of file diff --git a/simple-mind-map/index.js b/simple-mind-map/index.js index 731edd37..babb799c 100644 --- a/simple-mind-map/index.js +++ b/simple-mind-map/index.js @@ -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() { // 合并主题配置 diff --git a/simple-mind-map/package.json b/simple-mind-map/package.json index 007c8ee4..0ef87aee 100644 --- a/simple-mind-map/package.json +++ b/simple-mind-map/package.json @@ -1,6 +1,6 @@ { "name": "simple-mind-map", - "version": "0.6.8", + "version": "0.6.9", "description": "一个简单的web在线思维导图", "authors": [ { diff --git a/simple-mind-map/src/constants/constant.js b/simple-mind-map/src/constants/constant.js index cf2ce546..1593bf81 100644 --- a/simple-mind-map/src/constants/constant.js +++ b/simple-mind-map/src/constants/constant.js @@ -324,4 +324,9 @@ export const nodeDataNoStylePropList = [ 'resetRichText', 'uid', 'activeStyle' -] \ No newline at end of file +] + +// 数据缓存 +export const commonCaches = { + measureCustomNodeContentSizeEl: null +} \ No newline at end of file diff --git a/simple-mind-map/src/core/render/Render.js b/simple-mind-map/src/core/render/Render.js index 1e2664ea..217751de 100644 --- a/simple-mind-map/src/core/render/Render.js +++ b/simple-mind-map/src/core/render/Render.js @@ -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() } } diff --git a/simple-mind-map/src/core/render/TextEdit.js b/simple-mind-map/src/core/render/TextEdit.js index 523cf4ba..e426ecf6 100644 --- a/simple-mind-map/src/core/render/TextEdit.js +++ b/simple-mind-map/src/core/render/TextEdit.js @@ -106,6 +106,9 @@ export default class TextEdit { this.mindMap.keyCommand.addShortcut('Enter', () => { this.hideEditTextBox() }) + this.mindMap.keyCommand.addShortcut('Tab', () => { + this.hideEditTextBox() + }) } // 显示文本编辑框 diff --git a/simple-mind-map/src/core/render/node/nodeCreateContents.js b/simple-mind-map/src/core/render/node/nodeCreateContents.js index 9822484a..c521d2d4 100644 --- a/simple-mind-map/src/core/render/node/nodeCreateContents.js +++ b/simple-mind-map/src/core/render/node/nodeCreateContents.js @@ -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 diff --git a/simple-mind-map/src/core/render/node/nodeGeneralization.js b/simple-mind-map/src/core/render/node/nodeGeneralization.js index ba204186..ec7f95aa 100644 --- a/simple-mind-map/src/core/render/node/nodeGeneralization.js +++ b/simple-mind-map/src/core/render/node/nodeGeneralization.js @@ -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() } diff --git a/simple-mind-map/src/plugins/AssociativeLine.js b/simple-mind-map/src/plugins/AssociativeLine.js index 28223265..2a7ebba1 100644 --- a/simple-mind-map/src/plugins/AssociativeLine.js +++ b/simple-mind-map/src/plugins/AssociativeLine.js @@ -11,7 +11,7 @@ import { import associativeLineControlsMethods from './associativeLine/associativeLineControls' import associativeLineTextMethods from './associativeLine/associativeLineText' -// 关联线类 +// 关联线插件 class AssociativeLine { constructor(opt = {}) { this.mindMap = opt.mindMap diff --git a/simple-mind-map/src/plugins/Drag.js b/simple-mind-map/src/plugins/Drag.js index d85ea667..78a4b8aa 100644 --- a/simple-mind-map/src/plugins/Drag.js +++ b/simple-mind-map/src/plugins/Drag.js @@ -1,8 +1,7 @@ import { bfsWalk, throttle } from '../utils' import Base from '../layouts/Base' -// 节点拖动类 - +// 节点拖动插件 class Drag extends Base { // 构造函数 constructor({ mindMap }) { diff --git a/simple-mind-map/src/plugins/Export.js b/simple-mind-map/src/plugins/Export.js index 1f719d8f..a5747595 100644 --- a/simple-mind-map/src/plugins/Export.js +++ b/simple-mind-map/src/plugins/Export.js @@ -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(`${name}`)) await this.drawBackgroundToSvg(node) let str = node.svg() + str = removeHTMLEntities(str) // 转换成blob数据 let blob = new Blob([str], { type: 'image/svg+xml' diff --git a/simple-mind-map/src/plugins/ExportPDF.js b/simple-mind-map/src/plugins/ExportPDF.js index 2fcc254d..e46b3a3c 100644 --- a/simple-mind-map/src/plugins/ExportPDF.js +++ b/simple-mind-map/src/plugins/ExportPDF.js @@ -1,6 +1,6 @@ import JsPDF from 'jspdf' -// 导出PDF类,需要通过Export插件使用 +// 导出PDF插件,需要通过Export插件使用 class ExportPDF { // 构造函数 constructor(opt) { diff --git a/simple-mind-map/src/plugins/ExportXMind.js b/simple-mind-map/src/plugins/ExportXMind.js index 5f54e220..42ea665c 100644 --- a/simple-mind-map/src/plugins/ExportXMind.js +++ b/simple-mind-map/src/plugins/ExportXMind.js @@ -1,6 +1,6 @@ import xmind from '../parse/xmind' -// 导出XMind类,需要通过Export插件使用 +// 导出XMind插件,需要通过Export插件使用 class ExportXMind { // 构造函数 constructor(opt) { diff --git a/simple-mind-map/src/plugins/KeyboardNavigation.js b/simple-mind-map/src/plugins/KeyboardNavigation.js index 0a3f6098..5ed4b593 100644 --- a/simple-mind-map/src/plugins/KeyboardNavigation.js +++ b/simple-mind-map/src/plugins/KeyboardNavigation.js @@ -1,7 +1,7 @@ import { bfsWalk } from '../utils' import { CONSTANTS } from '../constants/constant' -// 键盘导航类 +// 键盘导航插件 class KeyboardNavigation { // 构造函数 constructor(opt) { diff --git a/simple-mind-map/src/plugins/MiniMap.js b/simple-mind-map/src/plugins/MiniMap.js index 9bc1d6ba..78198685 100644 --- a/simple-mind-map/src/plugins/MiniMap.js +++ b/simple-mind-map/src/plugins/MiniMap.js @@ -1,4 +1,4 @@ -// 小地图类 +// 小地图插件 class MiniMap { // 构造函数 constructor(opt) { diff --git a/simple-mind-map/src/plugins/RichText.js b/simple-mind-map/src/plugins/RichText.js index d501c282..2652428f 100644 --- a/simple-mind-map/src/plugins/RichText.js +++ b/simple-mind-map/src/plugins/RichText.js @@ -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键 + } } } } diff --git a/simple-mind-map/src/plugins/Search.js b/simple-mind-map/src/plugins/Search.js new file mode 100644 index 00000000..5bbd3e77 --- /dev/null +++ b/simple-mind-map/src/plugins/Search.js @@ -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 diff --git a/simple-mind-map/src/plugins/Select.js b/simple-mind-map/src/plugins/Select.js index b2a58c14..bba83b0c 100644 --- a/simple-mind-map/src/plugins/Select.js +++ b/simple-mind-map/src/plugins/Select.js @@ -1,7 +1,6 @@ import { bfsWalk, throttle } from '../utils' -// 选择节点类 - +// 节点选择插件 class Select { // 构造函数 constructor({ mindMap }) { diff --git a/simple-mind-map/src/plugins/TouchEvent.js b/simple-mind-map/src/plugins/TouchEvent.js index 5afc1f04..2d4f78d1 100644 --- a/simple-mind-map/src/plugins/TouchEvent.js +++ b/simple-mind-map/src/plugins/TouchEvent.js @@ -1,5 +1,4 @@ -// 手势事件支持类 - +// 手势事件支持插件 class TouchEvent { // 构造函数 constructor({ mindMap }) { diff --git a/simple-mind-map/src/plugins/Watermark.js b/simple-mind-map/src/plugins/Watermark.js index 7d2ddaca..cd6a3c98 100644 --- a/simple-mind-map/src/plugins/Watermark.js +++ b/simple-mind-map/src/plugins/Watermark.js @@ -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 diff --git a/simple-mind-map/src/utils/index.js b/simple-mind-map/src/utils/index.js index 99470427..886e5385 100644 --- a/simple-mind-map/src/utils/index.js +++ b/simple-mind-map/src/utils/index.js @@ -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) +} \ No newline at end of file diff --git a/web/src/assets/avatar/仓鼠.jpg b/web/src/assets/avatar/仓鼠.jpg new file mode 100644 index 00000000..1eabba76 Binary files /dev/null and b/web/src/assets/avatar/仓鼠.jpg differ diff --git a/web/src/assets/avatar/水车.jpg b/web/src/assets/avatar/水车.jpg new file mode 100644 index 00000000..747178b7 Binary files /dev/null and b/web/src/assets/avatar/水车.jpg differ diff --git a/web/src/assets/icon-font/iconfont.css b/web/src/assets/icon-font/iconfont.css index d216721f..5721282a 100644 --- a/web/src/assets/icon-font/iconfont.css +++ b/web/src/assets/icon-font/iconfont.css @@ -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"; } diff --git a/web/src/assets/icon-font/iconfont.ttf b/web/src/assets/icon-font/iconfont.ttf index 9c801c08..110b3d99 100644 Binary files a/web/src/assets/icon-font/iconfont.ttf and b/web/src/assets/icon-font/iconfont.ttf differ diff --git a/web/src/assets/icon-font/iconfont.woff b/web/src/assets/icon-font/iconfont.woff index 4d446856..45de5a0f 100644 Binary files a/web/src/assets/icon-font/iconfont.woff and b/web/src/assets/icon-font/iconfont.woff differ diff --git a/web/src/assets/icon-font/iconfont.woff2 b/web/src/assets/icon-font/iconfont.woff2 index 1d170d95..0e65009e 100644 Binary files a/web/src/assets/icon-font/iconfont.woff2 and b/web/src/assets/icon-font/iconfont.woff2 differ diff --git a/web/src/config/en.js b/web/src/config/en.js index 0c0bfbde..97aa75d4 100644 --- a/web/src/config/en.js +++ b/web/src/config/en.js @@ -271,6 +271,11 @@ export const shortcutKeyList = [ icon: 'iconzhengli', name: 'Arrange layout', value: 'Ctrl + L' + }, + { + icon: 'iconsousuo', + name: 'Search and Replace', + value: 'Ctrl + F' } ] }, diff --git a/web/src/config/zh.js b/web/src/config/zh.js index 57c6138c..6f8d0965 100644 --- a/web/src/config/zh.js +++ b/web/src/config/zh.js @@ -331,6 +331,11 @@ export const shortcutKeyList = [ icon: 'iconzhengli', name: '一键整理布局', value: 'Ctrl + L' + }, + { + icon: 'iconsousuo', + name: '搜索和替换', + value: 'Ctrl + F' } ] }, diff --git a/web/src/lang/en_us.js b/web/src/lang/en_us.js index 235ebd1d..a89f26ae 100644 --- a/web/src/lang/en_us.js +++ b/web/src/lang/en_us.js @@ -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' } } diff --git a/web/src/lang/zh_cn.js b/web/src/lang/zh_cn.js index c050b0f4..910dedfa 100644 --- a/web/src/lang/zh_cn.js +++ b/web/src/lang/zh_cn.js @@ -210,5 +210,12 @@ export default { mouseAction: { tip1: '当前:左键拖动画布,右键框选节点', tip2: '当前:左键框选节点,右键拖动画布', + }, + search: { + searchPlaceholder: '请输入查找内容', + replacePlaceholder: '请输入替换内容', + replace: '替换', + replaceAll: '全部替换', + cancel: '取消' } } diff --git a/web/src/pages/Doc/catalogList.js b/web/src/pages/Doc/catalogList.js index c48114f6..7aca8d52 100644 --- a/web/src/pages/Doc/catalogList.js +++ b/web/src/pages/Doc/catalogList.js @@ -32,6 +32,7 @@ let APIList = [ 'associativeLine', 'touchEvent', 'nodeImgAdjust', + 'search', 'xmind', 'markdown', 'utils' diff --git a/web/src/pages/Doc/en/changelog/index.md b/web/src/pages/Doc/en/changelog/index.md index 46429b41..f09ad06d 100644 --- a/web/src/pages/Doc/en/changelog/index.md +++ b/web/src/pages/Doc/en/changelog/index.md @@ -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. diff --git a/web/src/pages/Doc/en/changelog/index.vue b/web/src/pages/Doc/en/changelog/index.vue index 25fb9efb..00fd1d31 100644 --- a/web/src/pages/Doc/en/changelog/index.vue +++ b/web/src/pages/Doc/en/changelog/index.vue @@ -1,6 +1,10 @@ diff --git a/web/src/pages/Doc/en/render/index.md b/web/src/pages/Doc/en/render/index.md index 82f871cb..a76e1504 100644 --- a/web/src/pages/Doc/en/render/index.md +++ b/web/src/pages/Doc/en/render/index.md @@ -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` diff --git a/web/src/pages/Doc/en/render/index.vue b/web/src/pages/Doc/en/render/index.vue index 34956eb1..2819d97d 100644 --- a/web/src/pages/Doc/en/render/index.vue +++ b/web/src/pages/Doc/en/render/index.vue @@ -37,7 +37,10 @@ disable the enter key and delete key related shortcuts to prevent conflicts

copyNode()

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)

+

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 is an object, e.g. {text: 'I am new text'}

diff --git a/web/src/pages/Doc/en/search/index.md b/web/src/pages/Doc/en/search/index.md new file mode 100644 index 00000000..9bb87174 --- /dev/null +++ b/web/src/pages/Doc/en/search/index.md @@ -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. \ No newline at end of file diff --git a/web/src/pages/Doc/en/search/index.vue b/web/src/pages/Doc/en/search/index.vue new file mode 100644 index 00000000..600bcaf8 --- /dev/null +++ b/web/src/pages/Doc/en/search/index.vue @@ -0,0 +1,74 @@ + + + + + \ No newline at end of file diff --git a/web/src/pages/Doc/en/utils/index.md b/web/src/pages/Doc/en/utils/index.md index a2610595..80041030 100644 --- a/web/src/pages/Doc/en/utils/index.md +++ b/web/src/pages/Doc/en/utils/index.md @@ -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: diff --git a/web/src/pages/Doc/en/utils/index.vue b/web/src/pages/Doc/en/utils/index.vue index 8d53434b..d812e14f 100644 --- a/web/src/pages/Doc/en/utils/index.vue +++ b/web/src/pages/Doc/en/utils/index.vue @@ -134,6 +134,11 @@ and copying the data of the data object, example:

size// { width, height } width and height of image } +

getType(data)

+
+

v0.6.9+

+
+

Get the type of a data, such as BooleanArray.

Simulate CSS background in Canvas

Import:

import drawBackgroundImageToCanvas from 'simple-mind-map/src/utils/simulateCSSBackgroundInCanvas'
diff --git a/web/src/pages/Doc/routerList.js b/web/src/pages/Doc/routerList.js
index b2a0fcb4..dd4d9db7 100644
--- a/web/src/pages/Doc/routerList.js
+++ b/web/src/pages/Doc/routerList.js
@@ -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' }
     ]
   }
 ]
diff --git a/web/src/pages/Doc/zh/changelog/index.md b/web/src/pages/Doc/zh/changelog/index.md
index 7e302272..eb8ed945 100644
--- a/web/src/pages/Doc/zh/changelog/index.md
+++ b/web/src/pages/Doc/zh/changelog/index.md
@@ -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.修复节点正在编辑时切换富文本编辑配置输入框出现异常的问题。
diff --git a/web/src/pages/Doc/zh/changelog/index.vue b/web/src/pages/Doc/zh/changelog/index.vue
index 1652ac9f..098a4fa8 100644
--- a/web/src/pages/Doc/zh/changelog/index.vue
+++ b/web/src/pages/Doc/zh/changelog/index.vue
@@ -1,6 +1,10 @@
 
diff --git a/web/src/pages/Doc/zh/render/index.md b/web/src/pages/Doc/zh/render/index.md
index bcb9ee0f..ee32bdb6 100644
--- a/web/src/pages/Doc/zh/render/index.md
+++ b/web/src/pages/Doc/zh/render/index.md
@@ -54,7 +54,9 @@
 
 复制节点,操作节点为当前激活节点,有多个激活节点只会操作第一个节点
 
-### setNodeDataRender(node, data)
+### setNodeDataRender(node, data, notRender)
+
+- `notRender`:v0.6.9+,`Boolean`,默认为`false`,是否不要触发渲染。
 
 设置节点数据,即`data`字段的数据,并会根据节点大小是否变化来判断是否需要重新渲染该节点,`data`为对象,如:`{text: '我是新文本'}`
 
diff --git a/web/src/pages/Doc/zh/render/index.vue b/web/src/pages/Doc/zh/render/index.vue
index 3a2d5d2b..27ee69ae 100644
--- a/web/src/pages/Doc/zh/render/index.vue
+++ b/web/src/pages/Doc/zh/render/index.vue
@@ -28,7 +28,10 @@
 

删除某个指定节点

copyNode()

复制节点,操作节点为当前激活节点,有多个激活节点只会操作第一个节点

-

setNodeDataRender(node, data)

+

setNodeDataRender(node, data, notRender)

+

设置节点数据,即data字段的数据,并会根据节点大小是否变化来判断是否需要重新渲染该节点,data为对象,如:{text: '我是新文本'}

moveNodeTo(node, toNode)

diff --git a/web/src/pages/Doc/zh/search/index.md b/web/src/pages/Doc/zh/search/index.md new file mode 100644 index 00000000..736ef63f --- /dev/null +++ b/web/src/pages/Doc/zh/search/index.md @@ -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`:要进行替换的文本 + +返回该节点搜索和替换后的文本内容,注意,不会实际改变节点内容,只是用来计算一个节点替换后的内容。 \ No newline at end of file diff --git a/web/src/pages/Doc/zh/search/index.vue b/web/src/pages/Doc/zh/search/index.vue new file mode 100644 index 00000000..997de619 --- /dev/null +++ b/web/src/pages/Doc/zh/search/index.vue @@ -0,0 +1,74 @@ + + + + + \ No newline at end of file diff --git a/web/src/pages/Doc/zh/utils/index.md b/web/src/pages/Doc/zh/utils/index.md index a1f48086..814205e5 100644 --- a/web/src/pages/Doc/zh/utils/index.md +++ b/web/src/pages/Doc/zh/utils/index.md @@ -191,6 +191,12 @@ copyNodeTree({}, node) } ``` +#### getType(data) + +> v0.6.9+ + +获取一个数据的类型,比如`Boolean`、`Array`等。 + ## 在canvas中模拟css的背景属性 引入: diff --git a/web/src/pages/Doc/zh/utils/index.vue b/web/src/pages/Doc/zh/utils/index.vue index e874c85e..0552eabb 100644 --- a/web/src/pages/Doc/zh/utils/index.vue +++ b/web/src/pages/Doc/zh/utils/index.vue @@ -129,6 +129,11 @@ size// { width, height } 图片宽高 }
+

getType(data)

+
+

v0.6.9+

+
+

获取一个数据的类型,比如BooleanArray等。

在canvas中模拟css的背景属性

引入:

import drawBackgroundImageToCanvas from 'simple-mind-map/src/utils/simulateCSSBackgroundInCanvas'
diff --git a/web/src/pages/Edit/Index.vue b/web/src/pages/Edit/Index.vue
index 997d69ac..bc81551a 100644
--- a/web/src/pages/Edit/Index.vue
+++ b/web/src/pages/Edit/Index.vue
@@ -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;
diff --git a/web/src/pages/Edit/components/Edit.vue b/web/src/pages/Edit/components/Edit.vue
index 94f3ea52..7dc8c17b 100644
--- a/web/src/pages/Edit/components/Edit.vue
+++ b/web/src/pages/Edit/components/Edit.vue
@@ -18,6 +18,7 @@
     >
     
     
+    
   
 
 
@@ -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 {
diff --git a/web/src/pages/Edit/components/Search.vue b/web/src/pages/Edit/components/Search.vue
new file mode 100644
index 00000000..396ac1fe
--- /dev/null
+++ b/web/src/pages/Edit/components/Search.vue
@@ -0,0 +1,192 @@
+
+
+
+
+
diff --git a/web/src/pages/Edit/components/Sidebar.vue b/web/src/pages/Edit/components/Sidebar.vue
index 5f0cec24..d82b5153 100644
--- a/web/src/pages/Edit/components/Sidebar.vue
+++ b/web/src/pages/Edit/components/Sidebar.vue
@@ -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;
     }
 
diff --git a/web/src/pages/Edit/components/Theme.vue b/web/src/pages/Edit/components/Theme.vue
index b294e411..b5f09302 100644
--- a/web/src/pages/Edit/components/Theme.vue
+++ b/web/src/pages/Edit/components/Theme.vue
@@ -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)