diff --git a/README.md b/README.md
index 8f40f711..dc3cca95 100644
--- a/README.md
+++ b/README.md
@@ -44,7 +44,7 @@ Github:[releases](https://github.com/wanglin2/mind-map/releases)。百度云
官方提供了如下插件,可根据需求按需引入(某个功能不生效大概率是因为你没有引入对应的插件),具体使用方式请查看文档:
-> RichText(节点富文本插件)、Select(鼠标多选节点插件)、Drag(节点拖拽插件)、AssociativeLine(关联线插件)、Export(导出插件)、KeyboardNavigation(键盘导航插件)、MiniMap(小地图插件)、Watermark(水印插件)、TouchEvent(移动端触摸事件支持插件)、NodeImgAdjust(拖拽调整节点图片大小插件)、Search(搜索插件)、Painter(节点格式刷插件)、Scrollbar(滚动条插件)、Formula(数学公式插件)、Cooperate(协同编辑插件)、RainbowLines(彩虹线条插件)、Demonstrate(演示模式插件)、OuterFrame(外框插件)、HandDrawnLikeStyle(手绘风格插件)[收费]、Notation(节点标记插件)[收费]、Numbers(节点编号插件)[收费]
+> RichText(节点富文本插件)、Select(鼠标多选节点插件)、Drag(节点拖拽插件)、AssociativeLine(关联线插件)、Export(导出插件)、KeyboardNavigation(键盘导航插件)、MiniMap(小地图插件)、Watermark(水印插件)、TouchEvent(移动端触摸事件支持插件)、NodeImgAdjust(拖拽调整节点图片大小插件)、Search(搜索插件)、Painter(节点格式刷插件)、Scrollbar(滚动条插件)、Formula(数学公式插件)、Cooperate(协同编辑插件)、RainbowLines(彩虹线条插件)、Demonstrate(演示模式插件)、OuterFrame(外框插件)、HandDrawnLikeStyle(手绘风格插件)[收费]、Notation(节点标记插件)[收费]、Numbers(节点编号插件)[收费]、Freemind(Freemind格式导入导出插件)[收费]、Excel(Excel格式导入导出插件)[收费]
本项目不会实现的特性:
@@ -457,4 +457,16 @@ const mindMap = new MindMap({
炫
+
+
+ Lawliet
+
+
+
+ 一叶孤舟
+
+
+
+ 晏江
+
abc123我和你
' + div.innerHTML = `${emptyTextMeasureHeightText}
` let elTmp = div.children[0] elTmp.classList.add('smm-richtext-node-wrap') height = elTmp.getBoundingClientRect().height @@ -199,10 +200,12 @@ function createRichTextNode() { } // 创建文本节点 -function createTextNode() { +function createTextNode(specifyText) { if (this.getData('richText')) { - return this.createRichTextNode() + return this.createRichTextNode(specifyText) } + const text = + typeof specifyText === 'string' ? specifyText : this.getData('text') if (this.getData('resetRichText')) { delete this.nodeData.data.resetRichText } @@ -212,10 +215,11 @@ function createTextNode() { // 文本超长自动换行 let textStyle = this.style.getTextFontStyle() let textArr = [] - if (!isUndef(this.getData('text'))) { - textArr = String(this.getData('text')).split(/\n/gim) + if (!isUndef(text)) { + textArr = String(text).split(/\n/gim) } - let maxWidth = this.mindMap.opt.textAutoWrapWidth + const { textAutoWrapWidth: maxWidth, emptyTextMeasureHeightText } = + this.mindMap.opt let isMultiLine = false textArr.forEach((item, index) => { let arr = item.split('') @@ -247,6 +251,13 @@ function createTextNode() { g.add(node) }) let { width, height } = g.bbox() + // 如果文本为空,那么需要计算一个默认高度 + if (height <= 0) { + const tmpNode = new Text().text(emptyTextMeasureHeightText) + this.style.text(tmpNode) + const tmpBbox = tmpNode.bbox() + height = tmpBbox.height + } width = Math.min(Math.ceil(width), maxWidth) height = Math.ceil(height) g.attr('data-width', width) diff --git a/simple-mind-map/src/core/render/node/nodeExpandBtn.js b/simple-mind-map/src/core/render/node/nodeExpandBtn.js index 87941fde..baf26b36 100644 --- a/simple-mind-map/src/core/render/node/nodeExpandBtn.js +++ b/simple-mind-map/src/core/render/node/nodeExpandBtn.js @@ -1,5 +1,6 @@ import btnsSvg from '../../../svg/btns' import { SVG, Circle, G, Text } from '@svgdotjs/svg.js' +import { isUndef } from '../../../utils' // 创建展开收起按钮的内容节点 function createExpandNodeContent() { @@ -78,7 +79,12 @@ function updateExpandBtnNode() { }) // 计算子节点数量 let count = this.sumNode(this.nodeData.children) - count = expandBtnNumHandler(count) + if (typeof expandBtnNumHandler === 'function') { + const res = expandBtnNumHandler(count, this) + if (!isUndef(res)) { + count = res + } + } node.text(String(count)) } else { this._fillExpandNode.stroke('none') diff --git a/simple-mind-map/src/core/render/node/nodeGeneralization.js b/simple-mind-map/src/core/render/node/nodeGeneralization.js index 34e6908e..3bc735d5 100644 --- a/simple-mind-map/src/core/render/node/nodeGeneralization.js +++ b/simple-mind-map/src/core/render/node/nodeGeneralization.js @@ -186,15 +186,29 @@ function handleGeneralizationMouseenter() { const list = belongNode.formatGetGeneralization() const index = belongNode.getGeneralizationNodeIndex(this) const generalizationData = list[index] + // 如果主题中设置了hoverRectColor颜色,那么使用该颜色 + // 否则使用hoverRectColor实例化选项的颜色 + // 兜底使用highlightNode方法的默认颜色 + const hoverRectColor = this.getStyle('hoverRectColor') + const color = hoverRectColor || this.mindMap.opt.hoverRectColor + const style = color + ? { + stroke: color + } + : null // 区间概要,框子节点 if ( Array.isArray(generalizationData.range) && generalizationData.range.length > 0 ) { - this.mindMap.renderer.highlightNode(belongNode, generalizationData.range) + this.mindMap.renderer.highlightNode( + belongNode, + generalizationData.range, + style + ) } else { // 否则框自己 - this.mindMap.renderer.highlightNode(belongNode) + this.mindMap.renderer.highlightNode(belongNode, null, style) } } diff --git a/simple-mind-map/src/layouts/Base.js b/simple-mind-map/src/layouts/Base.js index 63eb4e48..6e2084dc 100644 --- a/simple-mind-map/src/layouts/Base.js +++ b/simple-mind-map/src/layouts/Base.js @@ -128,8 +128,9 @@ class Base { ) } // 主题或主题配置改变了、节点层级改变了,需要重新渲染节点文本等情况需要重新计算节点大小和布局 + const isNeedResizeSources = this.checkIsNeedResizeSources() if ( - this.checkIsNeedResizeSources() || + isNeedResizeSources || isLayerTypeChange || newNode.getData('resetRichText') || isNumberChange @@ -137,7 +138,7 @@ class Base { newNode.getSize() newNode.needLayout = true } - this.checkGetGeneralizationChange(newNode) + this.checkGetGeneralizationChange(newNode, isNeedResizeSources) } else if ( (this.lru.has(uid) || this.renderer.lastNodeCache[uid]) && !this.renderer.reRender @@ -186,7 +187,7 @@ class Base { newNode.getSize() newNode.needLayout = true } - this.checkGetGeneralizationChange(newNode) + this.checkGetGeneralizationChange(newNode, isResizeSource) } else { // 创建新节点 const newUid = uid || createUid() @@ -228,7 +229,7 @@ class Base { } // 检查概要节点是否需要更新 - checkGetGeneralizationChange(node) { + checkGetGeneralizationChange(node, isResizeSource) { const generalizationList = node.getData('generalization') if ( generalizationList && @@ -239,7 +240,10 @@ class Base { const gNode = item.generalizationNode const oldData = gNode.getData() const newData = generalizationList[index] - if (newData && JSON.stringify(oldData) !== JSON.stringify(newData)) { + if ( + isResizeSource || + (newData && JSON.stringify(oldData) !== JSON.stringify(newData)) + ) { gNode.nodeData.data = newData gNode.getSize() gNode.needLayout = true @@ -371,18 +375,32 @@ class Base { } // 二次贝塞尔曲线 - quadraticCurvePath(x1, y1, x2, y2) { - let cx = x1 + (x2 - x1) * 0.2 - let cy = y1 + (y2 - y1) * 0.8 + quadraticCurvePath(x1, y1, x2, y2, v = false) { + let cx, cy + if (v) { + cx = x1 + (x2 - x1) * 0.8 + cy = y1 + (y2 - y1) * 0.2 + } else { + cx = x1 + (x2 - x1) * 0.2 + cy = y1 + (y2 - y1) * 0.8 + } return `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}` } // 三次贝塞尔曲线 - cubicBezierPath(x1, y1, x2, y2) { - let cx1 = x1 + (x2 - x1) / 2 - let cy1 = y1 - let cx2 = cx1 - let cy2 = y2 + cubicBezierPath(x1, y1, x2, y2, v = false) { + let cx1, cy1, cx2, cy2 + if (v) { + cx1 = x1 + cy1 = y1 + (y2 - y1) / 2 + cx2 = x2 + cy2 = cy1 + } else { + cx1 = x1 + (x2 - x1) / 2 + cy1 = y1 + cx2 = cx1 + cy2 = y2 + } return `M ${x1},${y1} C ${cx1},${cy1} ${cx2},${cy2} ${x2},${y2}` } diff --git a/simple-mind-map/src/layouts/OrganizationStructure.js b/simple-mind-map/src/layouts/OrganizationStructure.js index 475bf7c6..ff5d0730 100644 --- a/simple-mind-map/src/layouts/OrganizationStructure.js +++ b/simple-mind-map/src/layouts/OrganizationStructure.js @@ -34,7 +34,14 @@ class OrganizationStructure extends Base { this.renderer.renderTree, null, (cur, parent, isRoot, layerIndex, index, ancestors) => { - let newNode = this.createNode(cur, parent, isRoot, layerIndex, index, ancestors) + let newNode = this.createNode( + cur, + parent, + isRoot, + layerIndex, + index, + ancestors + ) // 根节点定位在画布中心位置 if (isRoot) { this.setNodeCenter(newNode) @@ -148,13 +155,56 @@ class OrganizationStructure extends Base { // 绘制连线,连接该节点到其子节点 renderLine(node, lines, style, lineStyle) { - if (lineStyle === 'direct') { + if (lineStyle === 'curve') { + this.renderLineCurve(node, lines, style) + } else if (lineStyle === 'direct') { this.renderLineDirect(node, lines, style) } else { this.renderLineStraight(node, lines, style) } } + // 曲线风格连线 + renderLineCurve(node, lines, style) { + if (node.children.length <= 0) { + return [] + } + let { left, top, width, height, expandBtnSize } = node + const { alwaysShowExpandBtn, notShowExpandBtn } = this.mindMap.opt + if (!alwaysShowExpandBtn || notShowExpandBtn) { + expandBtnSize = 0 + } + const { + nodeUseLineStyle, + rootLineStartPositionKeepSameInCurve, + rootLineKeepSameInCurve + } = this.mindMap.themeConfig + node.children.forEach((item, index) => { + if (node.layerIndex === 0) { + expandBtnSize = 0 + } + let x1 = left + width / 2 + let y1 = + node.layerIndex === 0 && !rootLineStartPositionKeepSameInCurve + ? top + height / 2 + : top + height + expandBtnSize + let x2 = item.left + item.width / 2 + let y2 = item.top + let path = '' + // 节点使用横线风格,需要额外渲染横线 + let nodeUseLineStylePath = nodeUseLineStyle + ? ` L ${item.left},${y2} L ${item.left + item.width},${y2}` + : '' + if (node.isRoot && !rootLineKeepSameInCurve) { + path = + this.quadraticCurvePath(x1, y1, x2, y2, true) + nodeUseLineStylePath + } else { + path = this.cubicBezierPath(x1, y1, x2, y2, true) + nodeUseLineStylePath + } + this.setLineStyle(style, lines[index], path, item) + }) + } + // 直连风格 renderLineDirect(node, lines, style) { if (node.children.length <= 0) { diff --git a/simple-mind-map/src/plugins/Formula.js b/simple-mind-map/src/plugins/Formula.js index b28926a8..af7e49fb 100644 --- a/simple-mind-map/src/plugins/Formula.js +++ b/simple-mind-map/src/plugins/Formula.js @@ -1,6 +1,6 @@ import katex from 'katex' import Quill from 'quill' -import { getChromeVersion } from '../utils/index' +import { getChromeVersion, htmlEscape } from '../utils/index' import { getBaseStyleText, getFontStyleText } from './FormulaStyle' // 数学公式支持插件 @@ -58,7 +58,7 @@ class Formula { let node = super.create(value) if (typeof value === 'string') { katex.render(value, node, self.config) - node.setAttribute('data-value', value) + node.setAttribute('data-value', htmlEscape(value)) } return node } @@ -110,11 +110,7 @@ class Formula { for (const el of els) nodeText = nodeText.replace( el.outerHTML, - `\$${el - .getAttribute('data-value') - .replaceAll('&', '&') - .replaceAll('<', '<') - .replaceAll('>', '>')}\$` + `\$${el.getAttribute('data-value')}\$` ) } return nodeText diff --git a/simple-mind-map/src/plugins/NodeImgAdjust.js b/simple-mind-map/src/plugins/NodeImgAdjust.js index c21987d1..fd7dbb2c 100644 --- a/simple-mind-map/src/plugins/NodeImgAdjust.js +++ b/simple-mind-map/src/plugins/NodeImgAdjust.js @@ -192,8 +192,14 @@ class NodeImgAdjust { if (this.isMousedown) return this.hideHandleEl() }) - btnRemove.addEventListener('click', e => { - this.mindMap.execCommand('SET_NODE_IMAGE', this.node, { url: null }) + btnRemove.addEventListener('click', async e => { + let stop = false + if (typeof this.mindMap.opt.beforeDeleteNodeImg === 'function') { + stop = await this.mindMap.opt.beforeDeleteNodeImg(this.node) + } + if (!stop) { + this.mindMap.execCommand('SET_NODE_IMAGE', this.node, { url: null }) + } }) // 添加元素到页面 const targetNode = this.mindMap.opt.customInnerElsAppendTo || document.body diff --git a/simple-mind-map/src/plugins/Painter.js b/simple-mind-map/src/plugins/Painter.js index 8dcf90e8..3b7be755 100644 --- a/simple-mind-map/src/plugins/Painter.js +++ b/simple-mind-map/src/plugins/Painter.js @@ -53,17 +53,22 @@ class Painter { node.uid === this.painterNode.uid ) return - const style = {} + let style = {} + // 格式刷节点所有生效的样式 + if (!this.mindMap.opt.onlyPainterNodeCustomStyles) { + style = { + ...this.painterNode.effectiveStyles + } + } const painterNodeData = this.painterNode.getData() Object.keys(painterNodeData).forEach(key => { if (checkIsNodeStyleDataKey(key)) { style[key] = painterNodeData[key] } }) + // 先去除目标节点的样式 + this.mindMap.renderer._handleRemoveCustomStyles(node.getData()) node.setStyles(style) - if (painterNodeData.activeStyle) { - node.setStyles(painterNodeData.activeStyle, true) - } } // 插件被移除前做的事情 diff --git a/simple-mind-map/src/plugins/RichText.js b/simple-mind-map/src/plugins/RichText.js index 2388c33f..e32c96cc 100644 --- a/simple-mind-map/src/plugins/RichText.js +++ b/simple-mind-map/src/plugins/RichText.js @@ -57,6 +57,8 @@ class RichText { this.cacheEditingText = '' this.lostStyle = false this.isCompositing = false + this.textNodePaddingX = 6 + this.textNodePaddingY = 4 this.initOpt() this.extendQuill() this.appendCss() @@ -71,14 +73,17 @@ class RichText { // 绑定事件 bindEvent() { this.onCompositionStart = this.onCompositionStart.bind(this) + this.onCompositionUpdate = this.onCompositionUpdate.bind(this) this.onCompositionEnd = this.onCompositionEnd.bind(this) window.addEventListener('compositionstart', this.onCompositionStart) + window.addEventListener('compositionupdate', this.onCompositionUpdate) window.addEventListener('compositionend', this.onCompositionEnd) } // 解绑事件 unbindEvent() { window.removeEventListener('compositionstart', this.onCompositionStart) + window.removeEventListener('compositionupdate', this.onCompositionUpdate) window.removeEventListener('compositionend', this.onCompositionEnd) } @@ -198,8 +203,8 @@ class RichText { let scaleX = rect.width / originWidth let scaleY = rect.height / originHeight // 内边距 - let paddingX = 6 - let paddingY = 4 + let paddingX = this.textNodePaddingX + let paddingY = this.textNodePaddingY if (richTextEditFakeInPlace) { let paddingValue = node.getPaddingVale() paddingX = paddingValue.paddingX @@ -287,6 +292,20 @@ class RichText { this.cacheEditingText = '' } + // 更新文本编辑框的大小和位置 + updateTextEditNode() { + if (!this.node) return + const rect = this.node._textData.node.node.getBoundingClientRect() + const g = this.node._textData.node + const originWidth = g.attr('data-width') + const originHeight = g.attr('data-height') + this.textEditNode.style.minWidth = + originWidth + this.textNodePaddingX * 2 + 'px' + this.textEditNode.style.minHeight = originHeight + 'px' + this.textEditNode.style.left = rect.left + 'px' + this.textEditNode.style.top = rect.top + 'px' + } + // 删除文本编辑框元素 removeTextEditEl() { if (!this.textEditNode) return @@ -340,6 +359,18 @@ 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) {
@@ -350,6 +381,7 @@ class RichText {
beforeHideRichTextEdit(this)
}
let html = this.getEditText()
+ html = this.sortHtmlNodeStyles(html)
let list =
nodes && nodes.length > 0 ? nodes : this.mindMap.renderer.activeNodeList
list.forEach(node => {
@@ -360,12 +392,13 @@ class RichText {
// }
this.mindMap.render()
})
- this.mindMap.emit('hide_text_edit', this.textEditNode, list, this.node)
+ const node = this.node
this.textEditNode.style.display = 'none'
this.showTextEdit = false
this.mindMap.emit('rich_text_selection_change', false)
this.node = null
this.isInserting = false
+ this.mindMap.emit('hide_text_edit', this.textEditNode, list, node)
}
// 初始化Quill富文本编辑器
@@ -490,6 +523,11 @@ class RichText {
this.setTextStyleIfNotRichText(this.node)
this.lostStyle = false
}
+ this.mindMap.emit('node_text_edit_change', {
+ node: this.node,
+ text: this.getEditText(),
+ richText: true
+ })
})
// 拦截粘贴,只允许粘贴纯文本
// this.quill.clipboard.addMatcher(Node.TEXT_NODE, node => {
@@ -544,6 +582,16 @@ class RichText {
this.isCompositing = true
}
+ // 中文输入中
+ onCompositionUpdate() {
+ if (!this.showTextEdit || !this.node) return
+ this.mindMap.emit('node_text_edit_change', {
+ node: this.node,
+ text: this.getEditText(),
+ richText: true
+ })
+ }
+
// 中文输入结束
onCompositionEnd() {
if (!this.showTextEdit) {
diff --git a/simple-mind-map/src/themes/default.js b/simple-mind-map/src/themes/default.js
index d0baa737..fb2a6b8e 100644
--- a/simple-mind-map/src/themes/default.js
+++ b/simple-mind-map/src/themes/default.js
@@ -82,8 +82,12 @@ export default {
gradientStyle: false,
startColor: '#549688',
endColor: '#fff',
+ startDir: [0, 0],
+ endDir: [1, 0],
// 连线标记的位置,start(头部)、end(尾部),该配置在showLineMarker配置为true时生效
- lineMarkerDir: 'end'
+ lineMarkerDir: 'end',
+ // 节点鼠标hover和激活时显示的矩形边框的颜色,主题里不设置,默认会取hoverRectColor实例化选项的值
+ hoverRectColor: ''
},
// 二级节点样式
second: {
@@ -94,7 +98,7 @@ export default {
fontFamily: '微软雅黑, Microsoft YaHei',
color: '#565656',
fontSize: 16,
- fontWeight: 'noraml',
+ fontWeight: 'normal',
fontStyle: 'normal',
lineHeight: 1.5,
borderColor: '#549688',
@@ -105,7 +109,10 @@ export default {
gradientStyle: false,
startColor: '#549688',
endColor: '#fff',
- lineMarkerDir: 'end'
+ startDir: [0, 0],
+ endDir: [1, 0],
+ lineMarkerDir: 'end',
+ hoverRectColor: ''
},
// 三级及以下节点样式
node: {
@@ -116,7 +123,7 @@ export default {
fontFamily: '微软雅黑, Microsoft YaHei',
color: '#6a6d6c',
fontSize: 14,
- fontWeight: 'noraml',
+ fontWeight: 'normal',
fontStyle: 'normal',
lineHeight: 1.5,
borderColor: 'transparent',
@@ -127,7 +134,10 @@ export default {
gradientStyle: false,
startColor: '#549688',
endColor: '#fff',
- lineMarkerDir: 'end'
+ startDir: [0, 0],
+ endDir: [1, 0],
+ lineMarkerDir: 'end',
+ hoverRectColor: ''
},
// 概要节点样式
generalization: {
@@ -138,7 +148,7 @@ export default {
fontFamily: '微软雅黑, Microsoft YaHei',
color: '#565656',
fontSize: 16,
- fontWeight: 'noraml',
+ fontWeight: 'normal',
fontStyle: 'normal',
lineHeight: 1.5,
borderColor: '#549688',
@@ -148,7 +158,10 @@ export default {
textDecoration: 'none',
gradientStyle: false,
startColor: '#549688',
- endColor: '#fff'
+ endColor: '#fff',
+ startDir: [0, 0],
+ endDir: [1, 0],
+ hoverRectColor: ''
}
}
@@ -179,7 +192,10 @@ const nodeSizeIndependenceList = [
'gradientStyle',
'lineRadius',
'startColor',
- 'endColor'
+ 'endColor',
+ 'startDir',
+ 'endDir',
+ 'hoverRectColor'
]
export const checkIsNodeSizeIndependenceConfig = config => {
let keys = Object.keys(config)
diff --git a/simple-mind-map/src/utils/index.js b/simple-mind-map/src/utils/index.js
index 5f0cbf8c..132ca77c 100644
--- a/simple-mind-map/src/utils/index.js
+++ b/simple-mind-map/src/utils/index.js
@@ -1184,9 +1184,18 @@ export const handleInputPasteText = (e, text) => {
// 去除格式
text = getTextFromHtml(text)
// 去除换行
- text = text.replaceAll(/\n/g, '')
- const node = document.createTextNode(text)
- selection.getRangeAt(0).insertNode(node)
+ // text = text.replaceAll(/\n/g, '')
+ const textArr = text.split(/\n/g)
+ const fragment = document.createDocumentFragment()
+ textArr.forEach((item, index) => {
+ const node = document.createTextNode(item)
+ fragment.appendChild(node)
+ if (index < textArr.length - 1) {
+ const br = document.createElement('br')
+ fragment.appendChild(br)
+ }
+ })
+ selection.getRangeAt(0).insertNode(fragment)
selection.collapseToEnd()
}
diff --git a/web/scripts/transformMdToVue.js b/web/scripts/transformMdToVue.js
deleted file mode 100644
index cb36f3ee..00000000
--- a/web/scripts/transformMdToVue.js
+++ /dev/null
@@ -1,33 +0,0 @@
-const path = require('path')
-const fs = require('fs')
-const hljs = require('highlight.js')
-const md = require('markdown-it')({
- html: true,
- xhtmlOut: true,
- highlight: function(str, lang) {
- if (lang && hljs.getLanguage(lang)) {
- try {
- return (
- '
' +
- hljs.highlight(str, {
- language: lang,
- ignoreIllegals: true
- }).value +
- ''
- )
- } catch (__) {}
- }
-
- return (
- '' + md.utils.escapeHtml(str) + ''
- )
- }
-}).use(require('markdown-it-checkbox'))
-
-const templatePath = path.join(__dirname, '../src/pages/Doc/Template.vue')
-
-exports.transformMdToVue = (content) => {
- let result = md.render(content)
- let template = fs.readFileSync(templatePath, 'utf-8')
- return template.replace('$$$$', result)
-}
\ No newline at end of file
diff --git a/web/src/assets/avatar/Lawliet.jpg b/web/src/assets/avatar/Lawliet.jpg
new file mode 100644
index 00000000..4d8260d3
Binary files /dev/null and b/web/src/assets/avatar/Lawliet.jpg differ
diff --git a/web/src/assets/avatar/一叶孤舟.jpg b/web/src/assets/avatar/一叶孤舟.jpg
new file mode 100644
index 00000000..68c32e89
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 dadf7589..ff910e7e 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=1719815803051') format('woff2'),
- url('iconfont.woff?t=1719815803051') format('woff'),
- url('iconfont.ttf?t=1719815803051') format('truetype');
+ src: url('iconfont.woff2?t=1726022313538') format('woff2'),
+ url('iconfont.woff?t=1726022313538') format('woff'),
+ url('iconfont.ttf?t=1726022313538') format('truetype');
}
.iconfont {
@@ -13,6 +13,14 @@
-moz-osx-font-smoothing: grayscale;
}
+.iconfile-excel:before {
+ content: "\e7b7";
+}
+
+.iconfreemind:before {
+ content: "\e97d";
+}
+
.iconwaikuang:before {
content: "\e640";
}
diff --git a/web/src/assets/icon-font/iconfont.ttf b/web/src/assets/icon-font/iconfont.ttf
index 9b4dcc24..be64d2e5 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 ae10beee..c7c1342e 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 a21e2a92..d711ab4d 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/constant.js b/web/src/config/constant.js
index 3189e33a..d85420f7 100644
--- a/web/src/config/constant.js
+++ b/web/src/config/constant.js
@@ -85,12 +85,14 @@ export const formulaList = [
'\\begin{cases}3x + 5y + z \\\\7x - 2y + 4z \\\\-6x + 3y + 2z\\end{cases}'
]
+// 支持某种连线类型的结构
export const supportLineStyleLayoutsMap = {
curve: [
'logicalStructure',
'logicalStructureLeft',
'mindMap',
- 'verticalTimeline'
+ 'verticalTimeline',
+ 'organizationStructure'
],
direct: [
'logicalStructure',
@@ -101,6 +103,7 @@ export const supportLineStyleLayoutsMap = {
]
}
+// 直线模式支持设置圆角的结构
export const supportLineRadiusLayouts = [
'logicalStructure',
'logicalStructureLeft',
@@ -108,6 +111,7 @@ export const supportLineRadiusLayouts = [
'verticalTimeline'
]
+// 支持只显示底边直线风格的结构
export const supportNodeUseLineStyleLayouts = [
'logicalStructure',
'logicalStructureLeft',
@@ -116,10 +120,12 @@ export const supportNodeUseLineStyleLayouts = [
'organizationStructure'
]
+// 支持曲线模式下,根节点样式和其他节点样式保持一致的结构
export const supportRootLineKeepSameInCurveLayouts = [
'logicalStructure',
'logicalStructureLeft',
- 'mindMap'
+ 'mindMap',
+ 'organizationStructure'
]
// 彩虹线条配置
diff --git a/web/src/config/en.js b/web/src/config/en.js
index 5a5f0968..d3e12f6b 100644
--- a/web/src/config/en.js
+++ b/web/src/config/en.js
@@ -495,6 +495,18 @@ export const downTypeList = [
type: 'txt',
icon: 'iconTXT',
desc: 'Plain text file'
+ },
+ {
+ name: 'FreeMind',
+ type: 'mm',
+ icon: 'iconfreemind',
+ desc: 'FreeMind software format'
+ },
+ {
+ name: 'Excel',
+ type: 'xlsx',
+ icon: 'iconfile-excel',
+ desc: 'Excel software format'
}
]
@@ -557,3 +569,55 @@ export const numberLevelList = [
value: 0
}
]
+
+// 背景渐变方向
+export const linearGradientDirList = [
+ {
+ name: 'Left to right',
+ value: '1',
+ start: [0, 0],
+ end: [1, 0]
+ },
+ {
+ name: 'Right to left',
+ value: '2',
+ start: [1, 0],
+ end: [0, 0]
+ },
+ {
+ name: 'Top to bottom',
+ value: '3',
+ start: [0, 0],
+ end: [0, 1]
+ },
+ {
+ name: 'Bottom to top',
+ value: '4',
+ start: [0, 1],
+ end: [0, 0]
+ },
+ {
+ name: 'Left top to right bottom',
+ value: '5',
+ start: [0, 0],
+ end: [1, 1]
+ },
+ {
+ name: 'Left bottom to right top',
+ value: '6',
+ start: [0, 1],
+ end: [1, 0]
+ },
+ {
+ name: 'Right top to left bottom',
+ value: '7',
+ start: [1, 0],
+ end: [0, 1]
+ },
+ {
+ name: 'Right bottom to left top',
+ value: '8',
+ start: [1, 1],
+ end: [0, 0]
+ }
+]
diff --git a/web/src/config/index.js b/web/src/config/index.js
index 32affc86..b8ceff17 100644
--- a/web/src/config/index.js
+++ b/web/src/config/index.js
@@ -21,7 +21,8 @@ import {
shapeListMap as shapeListMapZh,
lineStyleMap as lineStyleMapZh,
numberTypeList as numberTypeListZh,
- numberLevelList as numberLevelListZh
+ numberLevelList as numberLevelListZh,
+ linearGradientDirList as linearGradientDirListZh
} from './zh'
import {
fontFamilyList as fontFamilyListEn,
@@ -36,7 +37,8 @@ import {
backgroundSizeList as backgroundSizeListEn,
downTypeList as downTypeListEn,
numberTypeList as numberTypeListEn,
- numberLevelList as numberLevelListEn
+ numberLevelList as numberLevelListEn,
+ linearGradientDirList as linearGradientDirListEn
} from './en'
const fontFamilyList = {
@@ -114,6 +116,11 @@ const numberLevelList = {
en: numberLevelListEn
}
+const linearGradientDirList = {
+ zh: linearGradientDirListZh,
+ en: linearGradientDirListEn
+}
+
export {
fontSizeList,
lineHeightList,
@@ -137,5 +144,6 @@ export {
sidebarTriggerList,
downTypeList,
numberTypeList,
- numberLevelList
+ numberLevelList,
+ linearGradientDirList
}
diff --git a/web/src/config/zh.js b/web/src/config/zh.js
index 704f2611..bfddedfc 100644
--- a/web/src/config/zh.js
+++ b/web/src/config/zh.js
@@ -593,6 +593,18 @@ export const downTypeList = [
type: 'txt',
icon: 'iconTXT',
desc: '纯文本文件'
+ },
+ {
+ name: 'FreeMind',
+ type: 'mm',
+ icon: 'iconfreemind',
+ desc: 'FreeMind软件格式'
+ },
+ {
+ name: 'Excel',
+ type: 'xlsx',
+ icon: 'iconfile-excel',
+ desc: 'Excel软件格式'
}
]
@@ -655,3 +667,55 @@ export const numberLevelList = [
value: 0
}
]
+
+// 背景渐变方向
+export const linearGradientDirList = [
+ {
+ name: '从左到右',
+ value: '1',
+ start: [0, 0],
+ end: [1, 0]
+ },
+ {
+ name: '从右到左',
+ value: '2',
+ start: [1, 0],
+ end: [0, 0]
+ },
+ {
+ name: '从上到下',
+ value: '3',
+ start: [0, 0],
+ end: [0, 1]
+ },
+ {
+ name: '从下到上',
+ value: '4',
+ start: [0, 1],
+ end: [0, 0]
+ },
+ {
+ name: '从左上到右下',
+ value: '5',
+ start: [0, 0],
+ end: [1, 1]
+ },
+ {
+ name: '从左下到右上',
+ value: '6',
+ start: [0, 1],
+ end: [1, 0]
+ },
+ {
+ name: '从右上到左下',
+ value: '7',
+ start: [1, 0],
+ end: [0, 1]
+ },
+ {
+ name: '从右下到左上',
+ value: '8',
+ start: [1, 1],
+ end: [0, 0]
+ }
+]
diff --git a/web/src/lang/en_us.js b/web/src/lang/en_us.js
index d313a98e..930081a4 100644
--- a/web/src/lang/en_us.js
+++ b/web/src/lang/en_us.js
@@ -157,8 +157,9 @@ export default {
import: {
title: 'Import',
selectFile: 'Select file',
- supportFile: 'Support .smm、.json、.xmind、.xlsx、.md file',
- enableFileTip: 'Please select .smm、.json、.xmind、.xlsx、.md file',
+ support: 'Support',
+ file: 'file',
+ pleaseSelect: 'Please select',
maxFileNum: 'At most one file can be selected',
notSelectTip: 'Please select the file to import',
fileContentError: 'The file content is incorrect',
@@ -238,7 +239,8 @@ export default {
endColor: 'End',
arrowDir: 'Arrow dir',
arrowDirStart: 'Start',
- arrowDirEnd: 'End'
+ arrowDirEnd: 'End',
+ direction: 'Direction'
},
theme: {
title: 'Theme',
@@ -305,7 +307,8 @@ export default {
yes: 'Yes',
no: 'No',
exportError: 'Export failed',
- dragTip: 'Release here to import the file'
+ dragTip: 'Release here to import the file',
+ deleteNodeImgTip: 'Are you sure to delete the node image?'
},
mouseAction: {
tip1:
diff --git a/web/src/lang/zh_cn.js b/web/src/lang/zh_cn.js
index cb351c11..dda55c7d 100644
--- a/web/src/lang/zh_cn.js
+++ b/web/src/lang/zh_cn.js
@@ -155,8 +155,9 @@ export default {
import: {
title: '导入',
selectFile: '选取文件',
- supportFile: '支持.smm、.json、.xmind、.xlsx、.md文件',
- enableFileTip: '请选择.smm、.json、.xmind、.xlsx、.md文件',
+ support: '支持',
+ file: '文件',
+ pleaseSelect: '请选择',
maxFileNum: '最多只能选择一个文件',
notSelectTip: '请选择要导入的文件',
fileContentError: '文件内容有误',
@@ -236,7 +237,8 @@ export default {
endColor: '结束',
arrowDir: '箭头位置',
arrowDirStart: '头部',
- arrowDirEnd: '尾部'
+ arrowDirEnd: '尾部',
+ direction: '方向'
},
theme: {
title: '主题',
@@ -299,7 +301,8 @@ export default {
yes: '是',
no: '否',
exportError: '导出失败',
- dragTip: '在此释放以导入该文件'
+ dragTip: '在此释放以导入该文件',
+ deleteNodeImgTip: '是否确认删除该节点图片?'
},
mouseAction: {
tip1: '当前:左键拖动画布,右键框选节点',
diff --git a/web/src/pages/Edit/components/Edit.vue b/web/src/pages/Edit/components/Edit.vue
index ef9d9ec0..3f2f2244 100644
--- a/web/src/pages/Edit/components/Edit.vue
+++ b/web/src/pages/Edit/components/Edit.vue
@@ -79,6 +79,11 @@ import OuterFrame from 'simple-mind-map/src/plugins/OuterFrame.js'
// import Notation from 'simple-mind-map-plugin-notation'
// 编号插件,该插件为付费插件,详情请查看开发文档
// import Numbers from 'simple-mind-map-plugin-numbers'
+// Freemind软件格式导入导出插件,该插件为付费插件,详情请查看开发文档
+// import Freemind from 'simple-mind-map-plugin-freemind'
+// Excel软件格式导入导出插件,该插件为付费插件,详情请查看开发文档
+// import Excel from 'simple-mind-map-plugin-excel'
+// npm link simple-mind-map-plugin-excel simple-mind-map-plugin-freemind simple-mind-map-plugin-numbers simple-mind-map-plugin-notation simple-mind-map-plugin-handdrawnlikestyle simple-mind-map
import OutlineSidebar from './OutlineSidebar'
import Style from './Style'
import BaseStyle from './BaseStyle'
@@ -418,6 +423,25 @@ export default {
},
expandBtnNumHandler: num => {
return num >= 100 ? '…' : num
+ },
+ beforeDeleteNodeImg: node => {
+ return new Promise(resolve => {
+ this.$confirm(
+ this.$t('edit.deleteNodeImgTip'),
+ this.$t('edit.tip'),
+ {
+ confirmButtonText: this.$t('edit.yes'),
+ cancelButtonText: this.$t('edit.no'),
+ type: 'warning'
+ }
+ )
+ .then(() => {
+ resolve(false)
+ })
+ .catch(() => {
+ resolve(true)
+ })
+ })
}
// createNodePrefixContent: (node) => {
// const el = document.createElement('div')
@@ -544,6 +568,16 @@ export default {
this.mindMap.addPlugin(Numbers)
this.$store.commit('setSupportNumbers', true)
}
+ if (typeof Freemind !== 'undefined') {
+ this.mindMap.addPlugin(Freemind)
+ this.$store.commit('setSupportFreemind', true)
+ Vue.prototype.Freemind = Freemind
+ }
+ if (typeof Excel !== 'undefined') {
+ this.mindMap.addPlugin(Excel)
+ this.$store.commit('setSupportExcel', true)
+ Vue.prototype.Excel = Excel
+ }
this.mindMap.keyCommand.addShortcut('Control+s', () => {
this.manualSave()
})
diff --git a/web/src/pages/Edit/components/Export.vue b/web/src/pages/Edit/components/Export.vue
index aa96e22c..f7994256 100644
--- a/web/src/pages/Edit/components/Export.vue
+++ b/web/src/pages/Edit/components/Export.vue
@@ -8,7 +8,7 @@
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)"
:width="isMobile ? '90%' : '50%'"
- :top="isMobile? '20px' : '15vh'"
+ :top="isMobile ? '20px' : '15vh'"
>