diff --git a/simple-mind-map/package.json b/simple-mind-map/package.json index 58783e6b..7c98e008 100644 --- a/simple-mind-map/package.json +++ b/simple-mind-map/package.json @@ -1,6 +1,6 @@ { "name": "simple-mind-map", - "version": "0.12.2", + "version": "0.13.0", "description": "一个简单的web在线思维导图", "authors": [ { diff --git a/simple-mind-map/src/constants/constant.js b/simple-mind-map/src/constants/constant.js index 179f6204..5e1381bf 100644 --- a/simple-mind-map/src/constants/constant.js +++ b/simple-mind-map/src/constants/constant.js @@ -3,7 +3,6 @@ export const CONSTANTS = { CHANGE_THEME: 'changeTheme', CHANGE_LAYOUT: 'changeLayout', SET_DATA: 'setData', - TRANSFORM_TO_NORMAL_NODE: 'transformAllNodesToNormalNode', MODE: { READONLY: 'readonly', EDIT: 'edit' @@ -157,7 +156,7 @@ export const nodeDataNoStylePropList = [ 'isActive', 'generalization', 'richText', - 'resetRichText', + 'resetRichText',// 重新创建富文本内容,去掉原有样式 'uid', 'activeStyle', 'associativeLineTargets', @@ -174,7 +173,8 @@ export const nodeDataNoStylePropList = [ 'customTop', 'customTextWidth', 'checkbox', - 'dir' + 'dir', + 'needUpdate'// 重新创建节点内容 ] // 错误类型 @@ -226,3 +226,13 @@ export const selfCloseTagList = [ // 非富文本模式下的节点文本行高 export const noneRichTextNodeLineHeight = 1.2 + +// 富文本支持的样式列表 +export const richTextSupportStyleList = [ + 'fontFamily', + 'fontSize', + 'fontWeight', + 'fontStyle', + 'textDecoration', + 'color' +] diff --git a/simple-mind-map/src/core/render/Render.js b/simple-mind-map/src/core/render/Render.js index 3355b731..8116a5df 100644 --- a/simple-mind-map/src/core/render/Render.js +++ b/simple-mind-map/src/core/render/Render.js @@ -30,7 +30,6 @@ import { createSmmFormatData, checkSmmFormatData, checkIsNodeStyleDataKey, - removeRichTextStyes, formatGetNodeGeneralization, sortNodeList, throttle, @@ -547,13 +546,6 @@ class Render { if (this.reRender) { this.reRender = false } - // 触发一次保存,因为修改了渲染树的数据 - if ( - this.hasRichTextPlugin() && - [CONSTANTS.CHANGE_THEME, CONSTANTS.SET_DATA].includes(source) - ) { - this.mindMap.command.addHistory() - } } this.mindMap.emit('node_tree_render_end') }) @@ -561,13 +553,14 @@ class Render { this.emitNodeActiveEvent() } - // 给当前被收起来的节点数据添加文本复位标志 + // 给当前被收起来的节点数据添加更新标志 resetUnExpandNodeStyle() { - if (!this.renderTree || !this.hasRichTextPlugin()) return + if (!this.renderTree) return walk(this.renderTree, null, node => { if (!node.data.expand) { walk(node, null, node2 => { - node2.data.resetRichText = true + // 主要是触发数据新旧对比,不一样则会重新创建节点 + node2.data['needUpdate'] = true }) return true } @@ -750,11 +743,10 @@ class Render { richText: isRichText, isActive: focusNewNode // 如果同时对多个节点插入子节点,那么需要把新增的节点设为激活状态。如果不进入编辑状态,那么也需要手动设为激活状态 } - if (isRichText) params.resetRichText = isRichText + if (isRichText) params.resetRichText = true // 动态指定的子节点数据也需要添加相关属性 - appointChildren = addDataToAppointNodes(appointChildren, { - ...params - }) + appointChildren = addDataToAppointNodes(appointChildren, params) + const alreadyIsRichText = appointData && appointData.richText list.forEach(node => { if (node.isGeneralization || node.isRoot) { return @@ -767,6 +759,10 @@ class Render { : defaultInsertBelowSecondLevelNodeText // 计算插入位置 const index = getNodeDataIndex(node) + // 如果指定的数据就是富文本格式,那么不需要重新创建 + if (alreadyIsRichText && params.resetRichText) { + delete params.resetRichText + } const newNodeData = { inserting, data: { @@ -802,7 +798,7 @@ class Render { richText: isRichText, isActive: focusNewNode } - if (isRichText) params.resetRichText = isRichText + if (isRichText) params.resetRichText = true nodeList = addDataToAppointNodes(nodeList, params) list.forEach(node => { if (node.isGeneralization || node.isRoot) { @@ -848,11 +844,10 @@ class Render { richText: isRichText, isActive: focusNewNode } - if (isRichText) params.resetRichText = isRichText + if (isRichText) params.resetRichText = true // 动态指定的子节点数据也需要添加相关属性 - appointChildren = addDataToAppointNodes(appointChildren, { - ...params - }) + appointChildren = addDataToAppointNodes(appointChildren, params) + const alreadyIsRichText = appointData && appointData.richText list.forEach(node => { if (node.isGeneralization) { return @@ -863,6 +858,10 @@ class Render { const text = node.isRoot ? defaultInsertSecondLevelNodeText : defaultInsertBelowSecondLevelNodeText + // 如果指定的数据就是富文本格式,那么不需要重新创建 + if (alreadyIsRichText && params.resetRichText) { + delete params.resetRichText + } const newNode = { inserting, data: { @@ -902,7 +901,7 @@ class Render { richText: isRichText, isActive: focusNewNode } - if (isRichText) params.resetRichText = isRichText + if (isRichText) params.resetRichText = true childList = addDataToAppointNodes(childList, params) list.forEach(node => { if (node.isGeneralization) { @@ -947,7 +946,8 @@ class Render { richText: isRichText, isActive: focusNewNode } - if (isRichText) params.resetRichText = isRichText + if (isRichText) params.resetRichText = true + const alreadyIsRichText = appointData && appointData.richText list.forEach(node => { if (node.isGeneralization || node.isRoot) { return @@ -956,6 +956,10 @@ class Render { node.layerIndex === 1 ? defaultInsertSecondLevelNodeText : defaultInsertBelowSecondLevelNodeText + // 如果指定的数据就是富文本格式,那么不需要重新创建 + if (alreadyIsRichText && params.resetRichText) { + delete params.resetRichText + } const newNode = { inserting, data: { @@ -966,11 +970,6 @@ class Render { }, children: [node.nodeData] } - if (isRichText) { - node.setData({ - resetRichText: true - }) - } const parent = node.parent // 获取当前节点所在位置 const index = getNodeDataIndex(node) @@ -1046,7 +1045,6 @@ class Render { const index = getNodeIndexInNodeList(node, parent.children) const parentIndex = getNodeIndexInNodeList(parent, grandpa.children) // 节点数据 - this.checkNodeLayerChange(node, parent) parent.nodeData.children.splice(index, 1) grandpa.nodeData.children.splice(parentIndex + 1, 0, node.nodeData) this.mindMap.render() @@ -1061,10 +1059,10 @@ class Render { delete nodeData[key] } }) - // 如果是富文本,那么还要处理富文本内容 - if (hasCustomStyles && this.hasRichTextPlugin()) { + // 如果是富文本,那么直接全部重新创建,因为有些样式是通过标签来渲染的 + if (this.hasRichTextPlugin()) { + hasCustomStyles = true nodeData.resetRichText = true - nodeData.text = removeRichTextStyes(nodeData.text) } return hasCustomStyles } @@ -1302,7 +1300,6 @@ class Render { nodeList.reverse() } nodeList.forEach(item => { - this.checkNodeLayerChange(item, exist) // 移动节点 let nodeParent = item.parent let nodeBorthers = nodeParent.children @@ -1329,25 +1326,6 @@ class Render { this.mindMap.render() } - // 如果是富文本模式,那么某些层级变化需要更新样式 - checkNodeLayerChange(node, toNode, toNodeIsParent = false) { - if (this.hasRichTextPlugin()) { - // 如果设置了自定义样式那么不需要更新 - if (this.mindMap.richText.checkNodeHasCustomRichTextStyle(node)) { - return - } - const toIndex = toNodeIsParent ? toNode.layerIndex + 1 : toNode.layerIndex - let nodeLayerChanged = - (node.layerIndex === 1 && toIndex !== 1) || - (node.layerIndex !== 1 && toIndex === 1) - if (nodeLayerChanged) { - node.setData({ - resetRichText: true - }) - } - } - } - // 移除节点 removeNode(appointNodes = []) { appointNodes = formatDataToArray(appointNodes) @@ -1531,7 +1509,6 @@ class Render { return !item.isRoot }) nodeList.forEach(item => { - this.checkNodeLayerChange(item, toNode, true) this.removeNodeFromActiveList(item) removeFromParentNodeData(item) toNode.setData({ @@ -1558,18 +1535,7 @@ class Render { node.nodeData.children.push( ...data.map(item => { const newData = simpleDeepClone(item) - createUidForAppointNodes([newData], true, node => { - // 可能跨层级复制,那么富文本样式需要更新 - if (this.hasRichTextPlugin()) { - // 如果设置了自定义样式那么不需要更新 - if ( - this.mindMap.richText.checkNodeHasCustomRichTextStyle(node.data) - ) { - return - } - node.data.resetRichText = true - } - }) + createUidForAppointNodes([newData], true) return newData }) ) @@ -1582,13 +1548,6 @@ class Render { const data = { [prop]: value } - // 如果开启了富文本,则需要应用到富文本上 - if ( - this.hasRichTextPlugin() && - this.mindMap.richText.isHasRichTextStyle(data) - ) { - data.resetRichText = true - } this.setNodeDataRender(node, data) // 更新了连线的样式 if (lineStyleProps.includes(prop)) { @@ -1599,13 +1558,6 @@ class Render { // 设置节点多个样式 setNodeStyles(node, style) { const data = { ...style } - // 如果开启了富文本,则需要应用到富文本上 - if ( - this.hasRichTextPlugin() && - this.mindMap.richText.isHasRichTextStyle(data) - ) { - data.resetRichText = true - } this.setNodeDataRender(node, data) // 更新了连线的样式 let props = Object.keys(style) @@ -1826,6 +1778,7 @@ class Render { list.length > 1 ) let needRender = false + const alreadyIsRichText = data && data.richText list.forEach(item => { const newData = { inserting, @@ -1837,7 +1790,7 @@ class Render { richText: isRichText, isActive: focusNewNode } - if (isRichText) newData.resetRichText = isRichText + if (isRichText && !alreadyIsRichText) newData.resetRichText = isRichText let generalization = item.node.getData('generalization') generalization = generalization ? Array.isArray(generalization) diff --git a/simple-mind-map/src/core/render/node/nodeCreateContents.js b/simple-mind-map/src/core/render/node/nodeCreateContents.js index 9c1e8da5..9c2ee4a9 100644 --- a/simple-mind-map/src/core/render/node/nodeCreateContents.js +++ b/simple-mind-map/src/core/render/node/nodeCreateContents.js @@ -1,19 +1,17 @@ import { resizeImgSize, - removeHtmlStyle, - addHtmlStyle, + removeRichTextStyes, checkIsRichText, isUndef, createForeignObjectNode, addXmlns, - generateColorByContent + generateColorByContent, + camelCaseToHyphen, + getNodeRichTextStyles } from '../../../utils' import { Image as SVGImage, SVG, A, G, Rect, Text } from '@svgdotjs/svg.js' import iconsSvg from '../../../svg/icons' -import { - CONSTANTS, - noneRichTextNodeLineHeight -} from '../../../constants/constant' +import { noneRichTextNodeLineHeight } from '../../../constants/constant' // 测量svg文本宽高 const measureText = (text, style) => { @@ -124,20 +122,6 @@ function createIconNode() { }) } -// 尝试给html指定标签添加内联样式 -function tryAddHtmlStyle(text, style) { - const tagList = ['span', 'strong', 's', 'em', 'u'] - // let _text = text - // for (let i = 0; i < tagList.length; i++) { - // text = addHtmlStyle(text, tagList[i], style) - // if (text !== _text) { - // break - // } - // } - // return text - return addHtmlStyle(text, tagList, style) -} - // 创建富文本节点 function createRichTextNode(specifyText) { const hasCustomWidth = this.hasCustomWidth() @@ -145,40 +129,32 @@ function createRichTextNode(specifyText) { typeof specifyText === 'string' ? specifyText : this.getData('text') let { textAutoWrapWidth, emptyTextMeasureHeightText } = this.mindMap.opt textAutoWrapWidth = hasCustomWidth ? this.customTextWidth : textAutoWrapWidth - let g = new G() - // 重新设置富文本节点内容 + const g = new G() + // 创建富文本结构,或复位富文本样式 let recoverText = false if (this.getData('resetRichText')) { delete this.nodeData.data.resetRichText recoverText = true } - if ([CONSTANTS.CHANGE_THEME].includes(this.mindMap.renderer.renderSource)) { - // 如果自定义过样式则不允许覆盖 - // if (!this.hasCustomStyle() ) { - recoverText = true - // } - } if (recoverText && !isUndef(text)) { - // 判断节点内容是否是富文本 - const isRichText = checkIsRichText(text) - // 获取自定义样式 - const customStyle = this.style.getCustomStyle() - // 样式字符串 - const style = this.style.createStyleText(customStyle) - if (isRichText) { - // 如果是富文本那么线移除内联样式 - text = removeHtmlStyle(text) - // 再添加新的内联样式 - text = this.tryAddHtmlStyle(text, style) + if (checkIsRichText(text)) { + // 如果是富文本那么移除内联样式 + text = removeRichTextStyes(text) } else { - // 非富文本 - text = `

${text}

` + // 非富文本则改为富文本结构 + text = `

${text}

` } this.setData({ - text: text + text }) } - let html = `
${text}
` + // 节点的富文本样式数据 + const nodeTextStyleList = [] + const nodeRichTextStyles = getNodeRichTextStyles(this) + Object.keys(nodeRichTextStyles).forEach(prop => { + nodeTextStyleList.push([prop, nodeRichTextStyles[prop]]) + }) + // 测量文本大小 if (!this.mindMap.commonCaches.measureRichtextNodeTextSizeEl) { this.mindMap.commonCaches.measureRichtextNodeTextSizeEl = document.createElement('div') @@ -190,9 +166,15 @@ function createRichTextNode(specifyText) { this.mindMap.commonCaches.measureRichtextNodeTextSizeEl ) } - let div = this.mindMap.commonCaches.measureRichtextNodeTextSizeEl + const div = this.mindMap.commonCaches.measureRichtextNodeTextSizeEl + // 应用节点的文本样式 + nodeTextStyleList.forEach(([prop, value]) => { + div.style[prop] = value + }) + div.style.lineHeight = 1.2 + const html = `
${text}
` div.innerHTML = html - let el = div.children[0] + const el = div.children[0] el.classList.add('smm-richtext-node-wrap') addXmlns(el) el.style.maxWidth = textAutoWrapWidth + 'px' @@ -219,6 +201,15 @@ function createRichTextNode(specifyText) { width, height }) + // 应用节点文本样式 + // 进入文本编辑时,这个样式也会同样添加到文本编辑框的元素上 + const foreignObjectStyle = { + 'line-height': 1.2 + } + nodeTextStyleList.forEach(([prop, value]) => { + foreignObjectStyle[camelCaseToHyphen(prop)] = value + }) + foreignObject.css(foreignObjectStyle) g.add(foreignObject) return { node: g, @@ -230,6 +221,10 @@ function createRichTextNode(specifyText) { // 创建文本节点 function createTextNode(specifyText) { + if (this.getData('needUpdate')) { + delete this.nodeData.data.needUpdate + } + // 如果是富文本内容,那么转给富文本函数 if (this.getData('richText')) { return this.createRichTextNode(specifyText) } @@ -556,7 +551,6 @@ export default { createImgNode, getImgShowSize, createIconNode, - tryAddHtmlStyle, createRichTextNode, createTextNode, createHyperlinkNode, diff --git a/simple-mind-map/src/layouts/Base.js b/simple-mind-map/src/layouts/Base.js index 67a58d12..87c0caac 100644 --- a/simple-mind-map/src/layouts/Base.js +++ b/simple-mind-map/src/layouts/Base.js @@ -49,10 +49,7 @@ class Base { // 检查当前来源是否需要重新计算节点大小 checkIsNeedResizeSources() { - return [ - CONSTANTS.CHANGE_THEME, - CONSTANTS.TRANSFORM_TO_NORMAL_NODE - ].includes(this.renderer.renderSource) + return [CONSTANTS.CHANGE_THEME].includes(this.renderer.renderSource) } // 层级类型改变 @@ -140,6 +137,7 @@ class Base { isNodeDataChange || isLayerTypeChange || newNode.getData('resetRichText') || + newNode.getData('needUpdate') || isNodeInnerPrefixChange ) { newNode.getSize() @@ -193,6 +191,7 @@ class Base { isNodeDataChange || isLayerTypeChange || newNode.getData('resetRichText') || + newNode.getData('needUpdate') || isNodeInnerPrefixChange ) { newNode.getSize() diff --git a/simple-mind-map/src/plugins/RichText.js b/simple-mind-map/src/plugins/RichText.js index e16a858c..323fb9a2 100644 --- a/simple-mind-map/src/plugins/RichText.js +++ b/simple-mind-map/src/plugins/RichText.js @@ -6,11 +6,11 @@ import { getTextFromHtml, isUndef, checkSmmFormatData, - removeHtmlNodeByClass, formatGetNodeGeneralization, - nodeRichTextToTextWithWrap + nodeRichTextToTextWithWrap, + getNodeRichTextStyles } from '../utils' -import { CONSTANTS } from '../constants/constant' +import { CONSTANTS, richTextSupportStyleList } from '../constants/constant' import MindMapNode from '../core/render/node/MindMapNode' import { Scope } from 'parchment' @@ -53,27 +53,15 @@ class RichText { this.isInserting = false this.styleEl = null this.cacheEditingText = '' - this.lostStyle = false this.isCompositing = false this.textNodePaddingX = 6 this.textNodePaddingY = 4 - this.supportStyleProps = [ - 'fontFamily', - 'fontSize', - 'fontWeight', - 'fontStyle', - 'textDecoration', - 'color' - ] this.initOpt() this.extendQuill() this.appendCss() this.bindEvent() - // 处理数据,转成富文本格式 - if (this.mindMap.opt.data) { - this.mindMap.opt.data = this.handleSetData(this.mindMap.opt.data) - } + this.handleDataToRichTextOnInit() } // 绑定事件 @@ -107,10 +95,6 @@ class RichText { word-break: break-all; user-select: none; } - - .smm-richtext-node-wrap p { - font-family: auto; - } ` ) let cssText = ` @@ -118,7 +102,7 @@ class RichText { overflow: hidden; padding: 0; height: auto; - line-height: normal; + line-height: 1.2; -webkit-user-select: text; } @@ -130,10 +114,6 @@ class RichText { .ql-container.ql-snow { border: none; } - - .smm-richtext-node-edit-wrap p { - font-family: auto; - } ` this.styleEl = document.createElement('style') this.styleEl.type = 'text/css' @@ -238,6 +218,7 @@ class RichText { outline: none; word-break: break-all; padding: ${paddingY}px ${paddingX}px; + line-height: 1.2; ` this.textEditNode.addEventListener('click', e => { e.stopPropagation() @@ -253,6 +234,7 @@ class RichText { const targetNode = customInnerElsAppendTo || document.body targetNode.appendChild(this.textEditNode) } + this.addNodeTextStyleToTextEditNode(node) this.textEditNode.style.marginLeft = `-${paddingX * scaleX}px` this.textEditNode.style.marginTop = `-${paddingY * scaleY}px` this.textEditNode.style.zIndex = nodeTextEditZIndex @@ -277,13 +259,8 @@ class RichText { const isEmptyText = isUndef(nodeText) // 是否是非空的非富文本 const noneEmptyNoneRichText = !node.getData('richText') && !isEmptyText - // 如果是空文本,那么设置为丢失样式状态,否则输入不会带上样式 - if (isEmptyText) { - this.lostStyle = true - } if (isFromKeyDown && autoEmptyTextWhenKeydownEnterEdit) { this.textEditNode.innerHTML = '' - this.lostStyle = true } else if (noneEmptyNoneRichText) { // 还不是富文本 let text = String(nodeText).split(/\n/gim).join('
') @@ -303,10 +280,6 @@ class RichText { this.focus( isInserting || (selectTextOnEnterEditText && !isFromKeyDown) ? 0 : null ) - if (noneEmptyNoneRichText) { - // 如果是非富文本的情况,需要手动应用文本样式 - this.setTextStyleIfNotRichText(node) - } this.cacheEditingText = '' } @@ -325,6 +298,14 @@ class RichText { : '0 0 20px rgba(0,0,0,.5)' } + // 将指定节点的文本样式添加到编辑框元素上 + addNodeTextStyleToTextEditNode(node) { + const style = getNodeRichTextStyles(node) + Object.keys(style).forEach(prop => { + this.textEditNode.style[prop] = style[prop] + }) + } + // 更新文本编辑框的大小和位置 updateTextEditNode() { if (!this.node) return @@ -346,20 +327,6 @@ class RichText { targetNode.removeChild(this.textEditNode) } - // 如果是非富文本的情况,需要手动应用文本样式 - setTextStyleIfNotRichText(node) { - let style = { - font: node.style.merge('fontFamily'), - color: node.style.merge('color'), - italic: node.style.merge('fontStyle') === 'italic', - bold: node.style.merge('fontWeight') === 'bold', - size: node.style.merge('fontSize') + 'px', - underline: node.style.merge('textDecoration') === 'underline', - strike: node.style.merge('textDecoration') === 'line-through' - } - this.pureFormatAllText(style) - } - // 获取当前正在编辑的内容 getEditText() { // https://github.com/slab/quill/issues/4509 @@ -374,18 +341,6 @@ class RichText { // return html.replace(/


<\/p>$/, '') } - // 给html字符串中的节点样式按样式名首字母排序 - sortHtmlNodeStyles(html) { - return html.replace(/(<[^<>]+\s+style=")([^"]+)("\s*>)/g, (_, a, b, c) => { - let arr = b.match(/[^:]+:[^:]+;/g) || [] - arr = arr.map(item => { - return item.trim() - }) - arr.sort() - return a + arr.join('') + c - }) - } - // 隐藏文本编辑控件,即完成编辑 hideEditText(nodes) { if (!this.showTextEdit) { @@ -395,8 +350,7 @@ class RichText { if (typeof beforeHideRichTextEdit === 'function') { beforeHideRichTextEdit(this) } - let html = this.getEditText() - html = this.sortHtmlNodeStyles(html) + const html = this.getEditText() const list = nodes && nodes.length > 0 ? nodes : [this.node] const node = this.node this.textEditNode.style.display = 'none' @@ -536,18 +490,6 @@ class RichText { } }) this.quill.on('text-change', () => { - let contents = this.quill.getContents() - let len = contents.ops.length - // 如果编辑过程中删除所有字符,那么会丢失主题的样式 - if (len <= 0 || (len === 1 && contents.ops[0].insert === '\n')) { - this.lostStyle = true - // 需要删除节点的样式数据 - this.syncFormatToNodeConfig(null, true) - } else if (this.lostStyle && !this.isCompositing) { - // 如果处于样式丢失状态,那么需要进行格式化加回样式 - this.setTextStyleIfNotRichText(this.node) - this.lostStyle = false - } this.mindMap.emit('node_text_edit_change', { node: this.node, text: this.getEditText(), @@ -638,10 +580,6 @@ class RichText { return } this.isCompositing = false - if (!this.lostStyle) { - return - } - this.setTextStyleIfNotRichText(this.node) } // 选中全部 @@ -651,16 +589,15 @@ class RichText { // 聚焦 focus(start) { - let len = this.quill.getLength() + const len = this.quill.getLength() this.quill.setSelection(typeof start === 'number' ? start : len, len) } // 格式化当前选中的文本 - formatText(config = {}, clear = false, pure = false) { + formatText(config = {}, clear = false) { if (!this.range && !this.lastRange) return - if (!pure) this.syncFormatToNodeConfig(config, clear) - let rangeLost = !this.range - let range = rangeLost ? this.lastRange : this.range + const rangeLost = !this.range + const range = rangeLost ? this.lastRange : this.range clear ? this.quill.removeFormat(range.index, range.length) : this.quill.formatText(range.index, range.length, config) @@ -671,56 +608,25 @@ class RichText { // 清除当前选中文本的样式 removeFormat() { - // 先移除全部样式 this.formatText({}, true) - // 再将样式恢复为当前主题改节点的默认样式 - const style = {} - if (this.node) { - this.supportStyleProps.forEach(key => { - style[key] = this.node.style.merge(key) - }) - } - const config = this.normalStyleToRichTextStyle(style) - this.formatText(config, false, true) } // 格式化指定范围的文本 formatRangeText(range, config = {}) { if (!range) return - this.syncFormatToNodeConfig(config) this.quill.formatText(range.index, range.length, config) } // 格式化所有文本 formatAllText(config = {}) { - this.syncFormatToNodeConfig(config) - this.pureFormatAllText(config) - } - - // 纯粹的格式化所有文本 - pureFormatAllText(config = {}) { this.quill.formatText(0, this.quill.getLength(), config) } - // 同步格式化到节点样式配置 - syncFormatToNodeConfig(config, clear) { - if (!this.node) return - if (clear) { - // 清除文本样式 - this.supportStyleProps.forEach(prop => { - delete this.node.nodeData.data[prop] - }) - } else { - let data = this.richTextStyleToNormalStyle(config) - this.mindMap.execCommand('SET_NODE_DATA', this.node, data) - } - } - // 将普通节点样式对象转换成富文本样式对象 normalStyleToRichTextStyle(style) { - let config = {} + const config = {} Object.keys(style).forEach(prop => { - let value = style[prop] + const value = style[prop] switch (prop) { case 'fontFamily': config.font = value @@ -750,9 +656,9 @@ class RichText { // 将富文本样式对象转换成普通节点样式对象 richTextStyleToNormalStyle(config) { - let data = {} + const data = {} Object.keys(config).forEach(prop => { - let value = config[prop] + const value = config[prop] switch (prop) { case 'font': data.fontFamily = value @@ -787,39 +693,50 @@ class RichText { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { const key = keys[i] - if (this.supportStyleProps.includes(key)) { + if (richTextSupportStyleList.includes(key)) { return true } } return false } - // 给未激活的节点设置富文本样式 - setNotActiveNodeStyle(node, style) { - const config = this.normalStyleToRichTextStyle(style) - if (Object.keys(config).length > 0) { - this.showEditText({ node }) - this.formatAllText(config) - this.hideEditText([node]) - } - } - // 检查指定节点是否存在自定义的富文本样式 checkNodeHasCustomRichTextStyle(node) { const nodeData = node instanceof MindMapNode ? node.getData() : node - for (let i = 0; i < this.supportStyleProps.length; i++) { - if (nodeData[this.supportStyleProps[i]] !== undefined) { + for (let i = 0; i < richTextSupportStyleList.length; i++) { + if (nodeData[richTextSupportStyleList[i]] !== undefined) { return true } } return false } + // 转换数据后的渲染操作 + afterHandleData() { + // 清空历史数据,并且触发数据变化 + this.mindMap.command.clearHistory() + this.mindMap.command.addHistory() + this.mindMap.render() + } + + // 插件实例化时处理思维导图数据,转换为富文本数据 + handleDataToRichTextOnInit() { + // 处理数据,转成富文本格式 + if (this.mindMap.renderer.renderTree) { + // 如果已经存在渲染树了,那么直接更新渲染树,并且触发重新渲染 + this.handleSetData(this.mindMap.renderer.renderTree) + this.afterHandleData() + } else if (this.mindMap.opt.data) { + this.handleSetData(this.mindMap.opt.data) + } + } + // 将所有节点转换成非富文本节点 transformAllNodesToNormalNode() { - if (!this.mindMap.renderer.renderTree) return + const renderTree = this.mindMap.renderer.renderTree + if (!renderTree) return walk( - this.mindMap.renderer.renderTree, + renderTree, null, node => { if (node.data.richText) { @@ -840,15 +757,12 @@ class RichText { 0, 0 ) - // 清空历史数据,并且触发数据变化 - this.mindMap.command.clearHistory() - this.mindMap.command.addHistory() - this.mindMap.render(null, CONSTANTS.TRANSFORM_TO_NORMAL_NODE) + this.afterHandleData() } // 处理导入数据 handleSetData(data) { - let walk = root => { + const walk = root => { if (root.data && !root.data.richText) { root.data.richText = true root.data.resetRichText = true @@ -857,8 +771,10 @@ class RichText { if (root.data) { const generalizationList = formatGetNodeGeneralization(root.data) generalizationList.forEach(item => { - item.richText = true - item.resetRichText = true + if (!item.richText) { + item.richText = true + item.resetRichText = true + } }) } if (root.children && root.children.length > 0) { diff --git a/simple-mind-map/src/plugins/Search.js b/simple-mind-map/src/plugins/Search.js index 9e061eca..0682bc26 100644 --- a/simple-mind-map/src/plugins/Search.js +++ b/simple-mind-map/src/plugins/Search.js @@ -228,7 +228,7 @@ class Search { const keep = replaceText.includes(this.searchText) const text = this.getReplacedText(currentNode, this.searchText, replaceText) this.notResetSearchText = true - currentNode.setText(text, currentNode.getData('richText'), true) + currentNode.setText(text, currentNode.getData('richText')) if (keep) { this.updateMatchNodeList(this.matchNodeList) return @@ -258,18 +258,15 @@ class Search { // 如果当前搜索文本是替换文本的子串,那么该节点还是符合搜索结果的 const keep = replaceText.includes(this.searchText) this.notResetSearchText = true - const hasRichTextPlugin = this.mindMap.renderer.hasRichTextPlugin() this.matchNodeList.forEach(node => { const text = this.getReplacedText(node, this.searchText, replaceText) if (this.isNodeInstance(node)) { const data = { text } - if (hasRichTextPlugin) data.resetRichText = !!node.getData('richText') this.mindMap.renderer.setNodeDataRender(node, data, true) } else { node.data.text = text - if (hasRichTextPlugin) node.data.resetRichText = !!node.data.richText } }) this.mindMap.render() diff --git a/simple-mind-map/src/utils/index.js b/simple-mind-map/src/utils/index.js index 97a626b7..d3b7e897 100644 --- a/simple-mind-map/src/utils/index.js +++ b/simple-mind-map/src/utils/index.js @@ -1,7 +1,8 @@ import { v4 as uuidv4 } from 'uuid' import { nodeDataNoStylePropList, - selfCloseTagList + selfCloseTagList, + richTextSupportStyleList } from '../constants/constant' import MersenneTwister from './mersenneTwister' import { ForeignObject } from '@svgdotjs/svg.js' @@ -481,7 +482,7 @@ export const loadImage = imgFile => { // 移除字符串中的html实体 export const removeHTMLEntities = str => { - ;[[' ', ' ']].forEach(item => { + [[' ', ' ']].forEach(item => { str = str.replaceAll(item[0], item[1]) }) return str @@ -949,6 +950,12 @@ export const selectAllInput = el => { // 给指定的节点列表树数据添加附加数据,会修改原数据 export const addDataToAppointNodes = (appointNodes, data = {}) => { + data = { ...data } + const alreadyIsRichText = data && data.richText + // 如果指定的数据就是富文本格式,那么不需要重新创建 + if (alreadyIsRichText && data.resetRichText) { + delete data.resetRichText + } const walk = list => { list.forEach(node => { node.data = { @@ -1027,7 +1034,7 @@ export const generateColorByContent = str => { // html转义 export const htmlEscape = str => { - ;[ + [ ['&', '&'], ['<', '<'], ['>', '>'] @@ -1639,3 +1646,16 @@ export const mergeTheme = (dest, source) => { } }) } + +// 获取节点实例的文本样式数据 +export const getNodeRichTextStyles = node => { + const res = {} + richTextSupportStyleList.forEach(prop => { + let value = node.style.merge(prop) + if (prop === 'fontSize') { + value = value + 'px' + } + res[prop] = value + }) + return res +}