Compare commits

...

23 Commits

Author SHA1 Message Date
街角小林
1b4ca19ad8 打包0.10.3 2024-07-19 14:22:00 +08:00
街角小林
eb72e0eed3 Doc: update 2024-07-19 14:11:23 +08:00
街角小林
6dea1ef9b2 Demo:支持配置外框内边距 2024-07-18 15:18:44 +08:00
街角小林
c6c1ef2117 Feat:新增设置外框内边距的实例化选项 2024-07-18 13:45:50 +08:00
街角小林
4423fd562b Demo:去除引入公式库样式的逻辑 2024-07-18 10:10:23 +08:00
街角小林
7b1ea5e354 Feat:1.支持自定义katex库渲染模式的实例化选项;2.公式插件会默认引入katex库的样式,增加自定义字体文件路径的实例化选项 2024-07-18 10:09:39 +08:00
街角小林
5192753816 Feat:如果开启了公式插件并且存在公式,那么导出svg时需要传入katex库的样式 2024-07-18 10:05:48 +08:00
街角小林
6382e8acd8 Doc: update 2024-07-16 16:47:29 +08:00
街角小林
e293039b3c Doc: update 2024-07-12 17:05:55 +08:00
街角小林
f819cbc5b1 Demo:修复对象类型的标签数据在标签弹窗里回显错误的问题 2024-07-12 15:18:48 +08:00
街角小林
44a883c473 Feat:复制、剪切、移动多个节点时,按其在节点上的顺序进行操作 2024-07-11 09:53:54 +08:00
街角小林
c1f600dc1f Fix:修复同时选中多个节点,可以不停插入概要的问题 2024-07-11 09:34:14 +08:00
街角小林
4777ab3e58 Demo:支持点击节点标签进行文本和颜色的修改 2024-07-09 15:57:46 +08:00
街角小林
12c6479c0d Feat:node_tag_click事件新增两个回调参数 2024-07-09 15:54:06 +08:00
街角小林
f79918ec6f Feat:1.支持定义标签样式;2.新增标签显示位置的实例化选项; 2024-07-09 14:11:18 +08:00
街角小林
ba9a6e501a Doc: update 2024-07-09 10:35:28 +08:00
街角小林
9b55d051dc Fix:修复编辑过节点文本后,再使用滚轮或快捷键缩放画布时上次被编辑的节点会进入编辑状态,以及思维导图快捷键会失效的问题 2024-07-08 16:52:53 +08:00
街角小林
159a4a202c Fix:修复customCreateNodeContent、createNodePrefixContent等方法里获取到的节点的isRoot和parent等值都为null的问题 2024-07-08 16:27:18 +08:00
街角小林
ac72c0c1dc Demo:恢复误删的文件 2024-07-05 10:57:06 +08:00
街角小林
f54f92c303 update 2024-07-04 17:32:00 +08:00
街角小林
c2dbfb41d5 打包0.10.2-fix.1 2024-07-03 12:11:59 +08:00
街角小林
5867649429 Fix:修复修改了外框的线条样式,激活后再取消激活,样式会变成默认的样式的问题 2024-07-03 12:00:44 +08:00
街角小林
de29ec59c5 打包Demo 2024-07-03 11:34:16 +08:00
132 changed files with 2471 additions and 209 deletions

View File

@@ -25,6 +25,8 @@ Github[releases](https://github.com/wanglin2/mind-map/releases)。百度云
> 客户端版本会落后于在线版本,尝试最新功能请优先使用在线版。
【云存储版本】如果你需要带后端的云存储版本,可以尝试我们开发的另一个项目[理想文档](https://github.com/wanglin2/lx-doc)。
# 特性
- [x] 插件化架构,除核心功能外,其他功能作为插件提供,按需使用,减小打包体积
@@ -35,7 +37,7 @@ Github[releases](https://github.com/wanglin2/mind-map/releases)。百度云
- [x] 支持画布拖动、缩放
- [x] 支持鼠标按键拖动选择和 Ctrl+左键两种多选节点方式
- [x] 支持导出为`json``png``svg``pdf``markdown``xmind``txt`,支持从`json``xmind``markdown`导入
- [x] 支持快捷键、前进后退、关联线、搜索替换、小地图、水印、滚动条、手绘风格、彩虹线条
- [x] 支持快捷键、前进后退、关联线、搜索替换、小地图、水印、滚动条、手绘风格、彩虹线条、标记、外框
- [x] 提供丰富的配置,满足各种场景各种使用习惯
- [x] 支持协同编辑
- [x] 支持演示模式
@@ -413,4 +415,20 @@ const mindMap = new MindMap({
<img src="./web/src/assets/avatar/buddy.jpg" style="width: 50px;height: 50px;" />
<span>buddy</span>
</span>
<span>
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
<span>小川</span>
</span>
<span>
<img src="./web/src/assets/avatar/Tobin.jpg" style="width: 50px;height: 50px;" />
<span>Tobin</span>
</span>
<span>
<img src="./web/src/assets/avatar/夏虫不语冰.jpg" style="width: 50px;height: 50px;" />
<span>夏虫不语冰</span>
</span>
<span>
<img src="./web/src/assets/avatar/晴空.jpg" style="width: 50px;height: 50px;" />
<span>晴空</span>
</span>
</p>

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
dist/img/Tobin.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
dist/img/夏虫不语冰.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
dist/img/晴空.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

2
dist/js/app.js vendored

File diff suppressed because one or more lines are too long

1
dist/js/chunk-2c6ccfd7.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/js/chunk-b84a6bba.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,11 +1,15 @@
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1"><link rel="icon" href="dist/logo.ico"><title>思绪思维导图</title><script>// 自定义静态资源的路径
window.externalPublicPath = './dist/'
// 接管应用
window.takeOverApp = false</script><script charset="UTF-8" id="LA_COLLECT" src="//sdk.51.la/js-sdk-pro.min.js"></script><script>LA.init({
id: 'KRO0WxK8GT66tYCQ',
ck: 'KRO0WxK8GT66tYCQ',
autoTrack: false
})</script><link href="dist/css/chunk-vendors.css?dd8fa3cd99060d550179" rel="stylesheet"><link href="dist/css/app.css?dd8fa3cd99060d550179" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script>const getDataFromBackend = () => {
window.takeOverApp = false</script><script charset="UTF-8" id="LA_COLLECT" src="//sdk.51.la/js-sdk-pro.min.js"></script><script>try {
LA.init({
id: 'KRO0WxK8GT66tYCQ',
ck: 'KRO0WxK8GT66tYCQ',
autoTrack: false
})
} catch (error) {
console.log(error)
}</script><link href="dist/css/chunk-vendors.css?dca8675d85e0a079cc96" rel="stylesheet"><link href="dist/css/app.css?dca8675d85e0a079cc96" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script>const getDataFromBackend = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
@@ -70,4 +74,4 @@
// 可以通过window.$bus.$on()来监听应用的一些事件
// 实例化页面
window.initApp()
}</script><script src="dist/js/chunk-vendors.js?dd8fa3cd99060d550179"></script><script src="dist/js/app.js?dd8fa3cd99060d550179"></script></body></html>
}</script><script src="dist/js/chunk-vendors.js?dca8675d85e0a079cc96"></script><script src="dist/js/app.js?dca8675d85e0a079cc96"></script></body></html>

View File

@@ -31,7 +31,7 @@ MindMap.iconList = icons.nodeIconList
MindMap.constants = constants
MindMap.themes = themes
MindMap.defaultTheme = defaultTheme
MindMap.version = '0.10.2'
MindMap.version = '0.10.3'
MindMap.usePlugin(MiniMap)
.usePlugin(Watermark)

View File

@@ -1,6 +1,6 @@
{
"name": "simple-mind-map",
"version": "0.10.2",
"version": "0.10.3",
"description": "一个简单的web在线思维导图",
"authors": [
{

View File

@@ -235,6 +235,10 @@ export const CONSTANTS = {
DEFAULT: 'default',
NOT_ACTIVE: 'notActive',
ACTIVE_ONLY: 'activeOnly'
},
TAG_POSITION: {
RIGHT: 'right',
BOTTOM: 'bottom'
}
}

View File

@@ -23,6 +23,8 @@ export const defaultOpt = {
mouseScaleCenterUseMousePosition: true,
// 最多显示几个标签
maxTag: 5,
// 标签显示的位置相对于节点文本bottom下方、right右侧
tagPosition: CONSTANTS.TAG_POSITION.RIGHT,
// 展开收缩按钮尺寸
expandBtnSize: 20,
// 节点里图片和文字的间距
@@ -379,6 +381,12 @@ export const defaultOpt = {
// 【Formula插件】
// 是否开启在富文本编辑框中直接编辑数学公式
enableEditFormulaInRichTextEdit: true,
// katex库的字体文件的请求路径。仅当katex的output配置为html时才会请求字体文件。可以通过mindMap.formula.getKatexConfig()方法来获取当前的配置
// 字体文件可以从node_modules中找到katex/dist/fonts/。可以上传到你的服务器或cdn
// 最终的字体请求路径为`${katexFontPath}fonts/KaTeX_AMS-Regular.woff2`,可以自行拼接进行测试是否可以访问
katexFontPath: 'https://unpkg.com/katex@0.16.11/dist/',
// 自定义katex库的输出模式。默认当Chrome内核100以下会使用html方式否则使用mathml方式如果你有自己的规则那么可以传递一个函数函数返回值为mathml或html
getKatexOutputType: null,
// 【RichText插件】
// 转换富文本内容,当进入富文本编辑时,可以通过该参数传递一个函数,函数接收文本内容,需要返回你处理后的文本内容
@@ -387,5 +395,9 @@ export const defaultOpt = {
beforeHideRichTextEdit: null,
// 设置富文本节点编辑框和节点大小一致,形成伪原地编辑的效果
// 需要注意的是,只有当节点内只有文本、且形状是矩形才会有比较好的效果
richTextEditFakeInPlace: false
richTextEditFakeInPlace: false,
// 【OuterFrame】插件
outerFramePaddingX: 10,
outerFramePaddingY: 10
}

View File

@@ -31,7 +31,8 @@ import {
checkSmmFormatData,
checkIsNodeStyleDataKey,
removeRichTextStyes,
formatGetNodeGeneralization
formatGetNodeGeneralization,
sortNodeList
} from '../../utils'
import { shapeList } from './node/Shape'
import { lineStyleProps } from '../../themes/default'
@@ -1373,7 +1374,8 @@ class Render {
if (this.activeNodeList.length <= 0) {
return null
}
const nodeList = getTopAncestorsFomNodeList(this.activeNodeList)
let nodeList = getTopAncestorsFomNodeList(this.activeNodeList)
nodeList = sortNodeList(nodeList)
return nodeList.map(node => {
return copyNodeTree({}, node, true)
})
@@ -1385,11 +1387,12 @@ class Render {
return
}
// 找出激活节点中的顶层节点列表,并过滤掉根节点
const nodeList = getTopAncestorsFomNodeList(this.activeNodeList).filter(
let nodeList = getTopAncestorsFomNodeList(this.activeNodeList).filter(
node => {
return !node.isRoot
}
)
nodeList = sortNodeList(nodeList)
// 复制数据
const copyData = nodeList.map(node => {
return copyNodeTree({}, node, true)
@@ -1665,11 +1668,13 @@ class Render {
)
})
const list = parseAddGeneralizationNodeList(nodeList)
if (list.length <= 0) return
const isRichText = !!this.mindMap.richText
const { focusNewNode, inserting } = this.getNewNodeBehavior(
openEdit,
list.length > 1
)
let needRender = false
list.forEach(item => {
const newData = {
inserting,
@@ -1683,15 +1688,30 @@ class Render {
isActive: focusNewNode
}
let generalization = item.node.getData('generalization')
if (generalization) {
if (Array.isArray(generalization)) {
generalization.push(newData)
} else {
generalization = [generalization, newData]
generalization = generalization
? Array.isArray(generalization)
? generalization
: [generalization]
: []
// 如果是范围概要,那么检查该范围是否存在
if (item.range) {
const isExist = !!generalization.find(item2 => {
return (
item2.range &&
item2.range[0] === item.range[0] &&
item2.range[1] === item.range[1]
)
})
if (isExist) {
return
}
// 不存在则添加
generalization.push(newData)
} else {
generalization = [newData]
// 不是范围概要直接添加,因为前面已经判断过是否存在
generalization.push(newData)
}
needRender = true
this.mindMap.execCommand('SET_NODE_DATA', item.node, {
generalization
})
@@ -1700,6 +1720,7 @@ class Render {
expand: true
})
})
if (!needRender) return
// 需要清除原来激活的节点
if (focusNewNode) {
this.clearActiveNodeList()

View File

@@ -187,7 +187,8 @@ export default class TextEdit {
// 处理画布缩放
onScale() {
if (!this.currentNode) return
const node = this.getCurrentEditNode()
if (!node) return
if (this.mindMap.richText) {
this.mindMap.richText.cacheEditingText =
this.mindMap.richText.getEditText()
@@ -197,7 +198,7 @@ export default class TextEdit {
this.showTextEdit = false
}
this.show({
node: this.currentNode,
node,
isFromScale: true
})
}

View File

@@ -253,11 +253,15 @@ class Node {
height: rect.height
}
}
const { tagPosition } = this.mindMap.opt
const tagIsBottom = tagPosition === CONSTANTS.TAG_POSITION.BOTTOM
// 宽高
let imgContentWidth = 0
let imgContentHeight = 0
let textContentWidth = 0
let textContentHeight = 0
let tagContentWidth = 0
let tagContentHeight = 0
// 存在图片
if (this._imgData) {
this._rectInfo.imgContentWidth = imgContentWidth = this._imgData.width
@@ -290,10 +294,20 @@ class Node {
}
// 标签
if (this._tagData.length > 0) {
textContentWidth += this._tagData.reduce((sum, cur) => {
textContentHeight = Math.max(textContentHeight, cur.height)
let maxTagHeight = 0
const totalTagWidth = this._tagData.reduce((sum, cur) => {
maxTagHeight = Math.max(maxTagHeight, cur.height)
return (sum += cur.width + this.textContentItemMargin)
}, 0)
if (tagIsBottom) {
// 文字下方
tagContentWidth = totalTagWidth
tagContentHeight = maxTagHeight
} else {
// 否则在右侧
textContentWidth += totalTagWidth
textContentHeight = Math.max(textContentHeight, maxTagHeight)
}
}
// 备注
if (this._noteData) {
@@ -325,6 +339,15 @@ class Node {
// 纯内容宽高
let _width = Math.max(imgContentWidth, textContentWidth)
let _height = imgContentHeight + textContentHeight
// 如果标签在文字下方
if (tagIsBottom && tagContentHeight > 0 && textContentHeight > 0) {
// 那么文字和标签之间也需要间距
margin += this.blockContentMargin
// 整体高度要考虑标签宽度
_width = Math.max(_width, tagContentWidth)
// 整体高度要加上标签的高度
_height += tagContentHeight
}
// 计算节点形状需要的附加内边距
let { paddingX: shapePaddingX, paddingY: shapePaddingY } =
this.shapeInstance.getShapePadding(_width, _height, paddingX, paddingY)
@@ -342,7 +365,7 @@ class Node {
layout() {
// 清除之前的内容
this.group.clear()
const { hoverRectPadding } = this.mindMap.opt
const { hoverRectPadding, tagPosition } = this.mindMap.opt
let { width, height, textContentItemMargin } = this
let { paddingY } = this.getPaddingVale()
const halfBorderWidth = this.getBorderWidth() / 2
@@ -382,6 +405,8 @@ class Node {
addHoverNode()
return
}
const tagIsBottom = tagPosition === CONSTANTS.TAG_POSITION.BOTTOM
const { textContentHeight } = this._rectInfo
// 图片节点
let imgHeight = 0
if (this._imgData) {
@@ -401,7 +426,7 @@ class Node {
})
foreignObject
.x(textContentOffsetX)
.y((this._rectInfo.textContentHeight - this._prefixData.height) / 2)
.y((textContentHeight - this._prefixData.height) / 2)
textContentNested.add(foreignObject)
textContentOffsetX += this._prefixData.width + textContentItemMargin
}
@@ -412,7 +437,7 @@ class Node {
this._iconData.forEach(item => {
item.node
.x(textContentOffsetX + iconLeft)
.y((this._rectInfo.textContentHeight - item.height) / 2)
.y((textContentHeight - item.height) / 2)
iconNested.add(item.node)
iconLeft += item.width + textContentItemMargin
})
@@ -427,7 +452,7 @@ class Node {
;(this._textData.nodeContent || this._textData.node)
.x(-oldX) // 修复非富文本模式下同时存在图标和换行的文本时,被收起和展开时图标与文字距离会逐渐拉大的问题
.x(textContentOffsetX)
.y((this._rectInfo.textContentHeight - this._textData.height) / 2)
.y((textContentHeight - this._textData.height) / 2)
textContentNested.add(this._textData.node)
textContentOffsetX += this._textData.width + textContentItemMargin
}
@@ -435,29 +460,50 @@ class Node {
if (this._hyperlinkData) {
this._hyperlinkData.node
.x(textContentOffsetX)
.y((this._rectInfo.textContentHeight - this._hyperlinkData.height) / 2)
.y((textContentHeight - this._hyperlinkData.height) / 2)
textContentNested.add(this._hyperlinkData.node)
textContentOffsetX += this._hyperlinkData.width + textContentItemMargin
}
// 标签
let tagNested = new G()
if (this._tagData && this._tagData.length > 0) {
let tagLeft = 0
this._tagData.forEach(item => {
item.node
.x(textContentOffsetX + tagLeft)
.y((this._rectInfo.textContentHeight - item.height) / 2)
tagNested.add(item.node)
tagLeft += item.width + textContentItemMargin
})
textContentNested.add(tagNested)
textContentOffsetX += tagLeft
if (tagIsBottom) {
// 标签显示在文字下方
let tagLeft = 0
this._tagData.forEach(item => {
item.node.x(tagLeft).y(0)
tagNested.add(item.node)
tagLeft += item.width + textContentItemMargin
})
tagNested.cx(width / 2).y(
paddingY + // 内边距
imgHeight + // 图片高度
textContentHeight + // 文本区域高度
(imgHeight > 0 && textContentHeight > 0
? this.blockContentMargin
: 0) + // 图片和文本之间的间距
this.blockContentMargin // 标签和文本之间的间距
)
this.group.add(tagNested)
} else {
// 标签显示在文字右侧
let tagLeft = 0
this._tagData.forEach(item => {
item.node
.x(textContentOffsetX + tagLeft)
.y((textContentHeight - item.height) / 2)
tagNested.add(item.node)
tagLeft += item.width + textContentItemMargin
})
textContentNested.add(tagNested)
textContentOffsetX += tagLeft
}
}
// 备注
if (this._noteData) {
this._noteData.node
.x(textContentOffsetX)
.y((this._rectInfo.textContentHeight - this._noteData.height) / 2)
.y((textContentHeight - this._noteData.height) / 2)
textContentNested.add(this._noteData.node)
textContentOffsetX += this._noteData.width
}
@@ -465,7 +511,7 @@ class Node {
if (this._attachmentData) {
this._attachmentData.node
.x(textContentOffsetX)
.y((this._rectInfo.textContentHeight - this._attachmentData.height) / 2)
.y((textContentHeight - this._attachmentData.height) / 2)
textContentNested.add(this._attachmentData.node)
textContentOffsetX += this._attachmentData.width
}
@@ -478,18 +524,16 @@ class Node {
})
foreignObject
.x(textContentOffsetX)
.y((this._rectInfo.textContentHeight - this._postfixData.height) / 2)
.y((textContentHeight - this._postfixData.height) / 2)
textContentNested.add(foreignObject)
textContentOffsetX += this._postfixData.width
}
// 文字内容整体
textContentNested.translate(
width / 2 - textContentNested.bbox().width / 2,
imgHeight +
paddingY +
(imgHeight > 0 && this._rectInfo.textContentHeight > 0
? this.blockContentMargin
: 0)
paddingY + // 内边距
imgHeight + // 图片高度
(imgHeight > 0 && textContentHeight > 0 ? this.blockContentMargin : 0) // 和图片的间距
)
this.group.add(textContentNested)
addHoverNode()

View File

@@ -1,8 +1,4 @@
import {
checkIsNodeStyleDataKey,
generateColorByContent
} from '../../../utils/index'
import { Gradient } from '@svgdotjs/svg.js'
import { checkIsNodeStyleDataKey } from '../../../utils/index'
const rootProp = ['paddingX', 'paddingY']
const backgroundStyleProps = [
@@ -182,21 +178,24 @@ class Style {
}
// 标签文字
tagText(node) {
tagText(node, style) {
node
.fill({
color: '#fff'
})
.css({
'font-size': '12px'
'font-size': style.fontSize + 'px'
})
}
// 标签矩形
tagRect(node, text, color) {
tagRect(node, style) {
node.fill({
color: color || generateColorByContent(text.node.textContent)
color: style.fill
})
if (style.radius) {
node.radius(style.radius)
}
}
// 内置图标

View File

@@ -6,12 +6,23 @@ import {
checkIsRichText,
isUndef,
createForeignObjectNode,
addXmlns
addXmlns,
generateColorByContent
} from '../../../utils'
import { Image as SVGImage, SVG, A, G, Rect, Text } from '@svgdotjs/svg.js'
import iconsSvg from '../../../svg/icons'
import { CONSTANTS } from '../../../constants/constant'
// 标签默认的样式
const defaultTagStyle = {
radius: 3, // 标签矩形的圆角大小
fontSize: 12, // 字号建议文字高度不要大于height
fill: '', // 标签矩形的背景颜色
height: 20, // 标签矩形的高度
paddingX: 8 // 水平内边距如果设置了width将忽略该配置
//width: 30 // 标签矩形的宽度,如果不设置,默认以文字的宽度+paddingX*2为宽度
}
// 创建图片节点
function createImgNode() {
const img = this.getData('image')
@@ -284,31 +295,69 @@ function createHyperlinkNode() {
// 创建标签节点
function createTagNode() {
let tagData = this.getData('tag')
const tagData = this.getData('tag')
if (!tagData || tagData.length <= 0) {
return []
}
let nodes = []
tagData.slice(0, this.mindMap.opt.maxTag).forEach((item, index) => {
let tag = new G()
let { maxTag, tagsColorMap } = this.mindMap.opt
tagsColorMap = tagsColorMap || {}
const nodes = []
tagData.slice(0, maxTag).forEach((item, index) => {
let str = ''
let style = {
...defaultTagStyle
}
// 旧版只支持字符串类型
if (typeof item === 'string') {
str = item
} else {
// v0.10.3+版本支持对象类型
str = item.text
style = { ...defaultTagStyle, ...item.style }
}
// 是否手动设置了标签宽度
const hasCustomWidth = typeof style.width !== 'undefined'
// 创建容器节点
const tag = new G()
tag.on('click', () => {
this.mindMap.emit('node_tag_click', this, item)
this.mindMap.emit('node_tag_click', this, item, index, tag)
})
// 标签文本
let text = new Text().text(item).x(8).cy(8)
this.style.tagText(text, index)
let { width } = text.bbox()
const text = new Text().text(str)
this.style.tagText(text, style)
// 获取文本宽高
const { width: textWidth, height: textHeight } = text.bbox()
// 矩形宽度
const rectWidth = hasCustomWidth
? style.width
: textWidth + style.paddingX * 2
// 取文本和矩形最大宽高作为标签宽高
const maxWidth = hasCustomWidth ? Math.max(rectWidth, textWidth) : rectWidth
const maxHeight = Math.max(style.height, textHeight)
// 文本居中
if (hasCustomWidth) {
text.x((maxWidth - textWidth) / 2)
} else {
text.x(hasCustomWidth ? 0 : style.paddingX)
}
text.cy(-maxHeight / 2)
// 标签矩形
let rect = new Rect().size(width + 16, 20)
// 先从自定义的颜色中获取颜色,没有的话就按照内容生成
const tagsColorList = this.mindMap.opt.tagsColorMap || {}
const color = tagsColorList[text.node.textContent]
this.style.tagRect(rect, text, color)
const rect = new Rect().size(rectWidth, style.height).cy(-maxHeight / 2)
if (hasCustomWidth) {
rect.x((maxWidth - rectWidth) / 2)
}
this.style.tagRect(rect, {
...style,
fill:
style.fill || // 优先节点自身配置
tagsColorMap[text.node.textContent] || // 否则尝试从实例化选项tagsColorMap映射中获取颜色
generateColorByContent(text.node.textContent) // 否则按照标签内容生成
})
tag.add(rect).add(text)
nodes.push({
node: tag,
width: width + 16,
height: 20
width: maxWidth,
height: maxHeight
})
})
return nodes

View File

@@ -83,6 +83,11 @@ class Base {
)
newNode.reset()
newNode.layerIndex = layerIndex
if (isRoot) {
newNode.isRoot = true
} else {
newNode.parent = parent._node
}
this.cacheNode(data._node.uid, newNode)
this.checkIsLayoutChangeRerenderExpandBtnPlaceholderRect(newNode)
// 主题或主题配置改变了、节点层级改变了,需要重新渲染节点文本等情况需要重新计算节点大小和布局
@@ -112,6 +117,11 @@ class Base {
newNode.reset()
newNode.nodeData = newNode.handleData(data || {})
newNode.layerIndex = layerIndex
if (isRoot) {
newNode.isRoot = true
} else {
newNode.parent = parent._node
}
this.cacheNode(uid, newNode)
this.checkIsLayoutChangeRerenderExpandBtnPlaceholderRect(newNode)
data._node = newNode
@@ -137,7 +147,9 @@ class Base {
renderer: this.renderer,
mindMap: this.mindMap,
draw: this.draw,
layerIndex
layerIndex,
isRoot,
parent: !isRoot ? parent._node : null
})
// uid保存到数据上为了节点复用
data.data.uid = newUid
@@ -157,11 +169,9 @@ class Base {
}
// 根节点
if (isRoot) {
newNode.isRoot = true
this.root = newNode
} else {
// 互相收集
newNode.parent = parent._node
parent._node.addChildren(newNode)
}
return newNode

View File

@@ -31,11 +31,14 @@ class LogicalStructure extends Base {
// 遍历数据计算节点的left、width、height
computedBaseValue() {
let sortIndex = 0
walk(
this.renderer.renderTree,
null,
(cur, parent, isRoot, layerIndex) => {
let newNode = this.createNode(cur, parent, isRoot, layerIndex)
newNode.sortIndex = sortIndex
sortIndex++
// 根节点定位在画布中心位置
if (isRoot) {
this.setNodeCenter(newNode)

View File

@@ -2,7 +2,8 @@ import {
bfsWalk,
throttle,
getTopAncestorsFomNodeList,
getNodeIndexInNodeList
getNodeIndexInNodeList,
sortNodeList
} from '../utils'
import Base from '../layouts/Base'
import { CONSTANTS } from '../constants/constant'
@@ -258,11 +259,14 @@ class Drag extends Base {
// 如果鼠标按下的节点是激活节点,那么保存当前所有激活的节点
if (node.getData('isActive')) {
// 找出这些激活节点中的最顶层节点
this.beingDragNodeList = getTopAncestorsFomNodeList(
// 过滤掉根节点和概要节点
this.mindMap.renderer.activeNodeList.filter(item => {
return !item.isRoot && !item.isGeneralization
})
// 并按索引从小到大排序
this.beingDragNodeList = sortNodeList(
getTopAncestorsFomNodeList(
// 过滤掉根节点和概要节点
this.mindMap.renderer.activeNodeList.filter(item => {
return !item.isRoot && !item.isGeneralization
})
)
)
} else {
// 否则只拖拽按下的节点

View File

@@ -4,7 +4,8 @@ import {
readBlob,
removeHTMLEntities,
resizeImgSize,
handleSelfCloseTags
handleSelfCloseTags,
addXmlns
} from '../utils'
import { SVG } from '@svgdotjs/svg.js'
import drawBackgroundImageToCanvas from '../utils/simulateCSSBackgroundInCanvas'
@@ -95,6 +96,20 @@ class Export {
foreignObjectList[0].add(SVG(`<style>${resetCss}</style>`))
svgIsChange = true
}
// 如果还开启了数学公式还要插入katex库的样式
if (this.mindMap.formula) {
const formulaList = svg.find('.ql-formula')
if (formulaList.length > 0) {
const styleText = this.mindMap.formula.getStyleText()
if (styleText) {
const styleEl = document.createElement('style')
styleEl.innerHTML = styleText
addXmlns(styleEl)
foreignObjectList[0].add(styleEl)
svgIsChange = true
}
}
}
}
// 自定义处理svg的方法
if (typeof handleBeingExportSvg === 'function') {

View File

@@ -1,6 +1,7 @@
import katex from 'katex'
import Quill from 'quill'
import { getChromeVersion } from '../utils/index'
import { getBaseStyleText, getFontStyleText } from './FormulaStyle'
// 数学公式支持插件
// 该插件在富文本模式下可用
@@ -11,6 +12,9 @@ class Formula {
this.mindMap = opt.mindMap
window.katex = katex
this.init()
this.config = this.getKatexConfig()
this.cssEl = null
this.addStyle()
this.extendQuill()
}
@@ -29,11 +33,18 @@ class Formula {
errorColor: '#f00',
output: 'mathml' // 默认只输出公式
}
// Chrome内核100以下mathml配置公式无法正确渲染
const chromeVersion = getChromeVersion()
if (chromeVersion && chromeVersion <= 100) {
config.output = 'html'
}
let { getKatexOutputType } = this.mindMap.opt
getKatexOutputType =
getKatexOutputType ||
function () {
// Chrome内核100以下mathml配置公式无法正确渲染
const chromeVersion = getChromeVersion()
if (chromeVersion && chromeVersion <= 100) {
return 'html'
}
}
const output = getKatexOutputType() || 'mathml'
config.output = ['mathml', 'html'].includes(output) ? output : 'mathml'
return config
}
@@ -46,7 +57,7 @@ class Formula {
static create(value) {
let node = super.create(value)
if (typeof value === 'string') {
katex.render(value, node, self.getKatexConfig())
katex.render(value, node, self.config)
node.setAttribute('data-value', value)
}
return node
@@ -56,6 +67,27 @@ class Formula {
Quill.register('formats/formula', CustomFormulaBlot, true)
}
getStyleText() {
const { katexFontPath } = this.mindMap.opt
let text = ''
if (this.config.output === 'html') {
text = getFontStyleText(katexFontPath)
}
text += getBaseStyleText()
return text
}
addStyle() {
this.cssEl = document.createElement('style')
this.cssEl.type = 'text/css'
this.cssEl.innerHTML = this.getStyleText()
document.head.appendChild(this.cssEl)
}
removeStyle() {
document.head.removeChild(this.cssEl)
}
// 给指定的节点插入指定公式
insertFormulaToNode(node, formula) {
let richTextPlugin = this.mindMap.richText
@@ -136,6 +168,16 @@ class Formula {
return false
}
}
// 插件被移除前做的事情
beforePluginRemove() {
this.removeStyle()
}
// 插件被卸载前做的事情
beforePluginDestroy() {
this.removeStyle()
}
}
Formula.instanceName = 'formula'

File diff suppressed because it is too large Load Diff

View File

@@ -144,8 +144,6 @@ class OuterFrame {
this.createDrawContainer()
this.outerFrameElList = []
this.activeOuterFrame = null
this.paddingX = 10
this.paddingY = 10
this.bindEvent()
}
@@ -287,6 +285,7 @@ class OuterFrame {
let tree = this.mindMap.renderer.root
if (!tree) return
const t = this.mindMap.draw.transform()
const { outerFramePaddingX, outerFramePaddingY } = this.mindMap.opt
walk(
tree,
null,
@@ -299,12 +298,18 @@ class OuterFrame {
const { left, top, width, height } =
getNodeListBoundingRect(nodeList)
const el = this.createOuterFrameEl(
(left - this.paddingX - this.mindMap.elRect.left - t.translateX) /
(left -
outerFramePaddingX -
this.mindMap.elRect.left -
t.translateX) /
t.scaleX,
(top - this.paddingY - this.mindMap.elRect.top - t.translateY) /
(top -
outerFramePaddingY -
this.mindMap.elRect.top -
t.translateY) /
t.scaleY,
(width + this.paddingX * 2) / t.scaleX,
(height + this.paddingY * 2) / t.scaleY,
(width + outerFramePaddingX * 2) / t.scaleX,
(height + outerFramePaddingY * 2) / t.scaleY,
nodeList[0].getData('outerFrame') // 使用第一个节点的外框样式
)
el.on('click', e => {
@@ -340,7 +345,7 @@ class OuterFrame {
if (!this.activeOuterFrame) return
const { el } = this.activeOuterFrame
el.stroke({
dasharray: '5,5'
dasharray: el.cacheStyle.dasharray || defaultStyle.strokeDasharray
})
this.activeOuterFrame = null
}
@@ -362,6 +367,9 @@ class OuterFrame {
})
.x(x)
.y(y)
el.cacheStyle = {
dasharray: styleConfig.strokeDasharray
}
this.outerFrameElList.push(el)
return el
}

View File

@@ -1580,3 +1580,12 @@ export const defenseXSS = text => {
export const addXmlns = el => {
el.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml')
}
// 给一组节点实例升序排序依据其sortIndex值
export const sortNodeList = nodeList => {
nodeList = [...nodeList]
nodeList.sort((a, b) => {
return a.sortIndex - b.sortIndex
})
return nodeList
}

View File

@@ -21,11 +21,15 @@
src="//sdk.51.la/js-sdk-pro.min.js"
></script>
<script>
LA.init({
id: 'KRO0WxK8GT66tYCQ',
ck: 'KRO0WxK8GT66tYCQ',
autoTrack: false
})
try {
LA.init({
id: 'KRO0WxK8GT66tYCQ',
ck: 'KRO0WxK8GT66tYCQ',
autoTrack: false
})
} catch (error) {
console.log(error)
}
</script>
</head>
<body>

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Some files were not shown because too many files have changed in this diff Show More