Compare commits

...

19 Commits
0.3.1 ... 0.4.1

Author SHA1 Message Date
wanglin2
2d7c091071 打包0.4.1 2023-02-27 15:31:52 +08:00
wanglin2
9bf58a54ce Feature:节点富文本编辑支持清除文字样式 2023-02-27 15:19:56 +08:00
wanglin2
231adeea44 Feature:富文本支持设置背景颜色 2023-02-27 14:58:01 +08:00
wanglin2
0ea618af39 Fix:1.Mac系统触控板缩放方向相反的问题;2.设备像素比不为1时导出图片中富文本节点尺寸变大的问题.Feater:节点抛出鼠标移入和移出事件 2023-02-26 10:07:03 +08:00
wanglin2
f66297ff99 打包0.4.0 2023-02-25 10:11:16 +08:00
wanglin2
3113bf2e1f 完善文档 2023-02-24 17:14:48 +08:00
wanglin2
8c114dac02 Feature:支持节点富文本编辑 2023-02-24 17:14:24 +08:00
wanglin2
06dba79272 新增图标 2023-02-24 17:13:07 +08:00
wanglin2
8441986ca7 修改文档 2023-02-20 19:25:59 +08:00
wanglin2
c8b50908e1 Fix:修复底边风格的情况下,节点高度过高会和其他节点重叠的问题 2023-02-20 15:10:27 +08:00
wanglin2
7e11fde892 打包0.3.4 2023-02-20 11:30:15 +08:00
wanglin2
3d9f3bd7a8 Fix:修复批量删除的节点中如果存在根节点会出现删除异常的问题 2023-02-20 11:26:26 +08:00
wanglin2
46e11649b0 优化节点文本编辑 2023-02-20 11:02:04 +08:00
wanglin2
161ef7590c feature:节点文本增加自动换行功能 2023-02-20 10:14:34 +08:00
wanglin2
ca660a3c74 打包0.3.3 2023-02-14 09:38:38 +08:00
wanglin2
7a24872331 Fix:修复根节点无法黄的问题 2023-02-14 09:35:56 +08:00
wanglin2
eb4ea9fb3a 打包0.3.2 2023-02-03 11:13:58 +08:00
wanglin2
64228c02ff 修复当思维导图实际内容大于屏幕宽高时,导出的时候超出的部分没有绘制水印的问题 2023-02-03 11:09:43 +08:00
wanglin2
f184a5d948 修复二级节点拖拽到其他节点或其他节点拖拽到二级节点时节点样式没有更新的问题 2023-02-03 10:40:06 +08:00
66 changed files with 4467 additions and 1543 deletions

View File

@@ -55,4 +55,8 @@ const mindMap = new MindMap({
# License
MIT
MIT
# 微信交流群
![](http://assets.lxqnsys.com/IMG_0465.JPG)

View File

@@ -1 +1 @@
<!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,initial-scale=1"><link rel="icon" href="./dist/logo.png"><title>一个简单的web思维导图实现</title><link href="dist/js/chunk-2d0a3179.f79d6590.js" rel="prefetch"><link href="dist/js/chunk-2d0aa579.d901cc5e.js" rel="prefetch"><link href="dist/js/chunk-2d0aa978.ae72a285.js" rel="prefetch"><link href="dist/js/chunk-2d0ab10b.0a72e8ef.js" rel="prefetch"><link href="dist/js/chunk-2d0abe0f.f7178a38.js" rel="prefetch"><link href="dist/js/chunk-2d0b361e.abfe0d6c.js" rel="prefetch"><link href="dist/js/chunk-2d0b91e5.de98db92.js" rel="prefetch"><link href="dist/js/chunk-2d0b92c3.a48a667e.js" rel="prefetch"><link href="dist/js/chunk-2d0bd54e.e56450f0.js" rel="prefetch"><link href="dist/js/chunk-2d0be174.1531a230.js" rel="prefetch"><link href="dist/js/chunk-2d0c0a44.d2768274.js" rel="prefetch"><link href="dist/js/chunk-2d0c14fc.a89a80b4.js" rel="prefetch"><link href="dist/js/chunk-2d0c18d8.4dad4846.js" rel="prefetch"><link href="dist/js/chunk-2d0c191e.93eb5568.js" rel="prefetch"><link href="dist/js/chunk-2d0c1a01.49c23f9e.js" rel="prefetch"><link href="dist/js/chunk-2d0d9fbc.e5848281.js" rel="prefetch"><link href="dist/js/chunk-2d0da701.2e0766e2.js" rel="prefetch"><link href="dist/js/chunk-2d0dad5f.9e9d10ae.js" rel="prefetch"><link href="dist/js/chunk-2d0db0f2.19efc7d4.js" rel="prefetch"><link href="dist/js/chunk-2d0dddce.6a579f6f.js" rel="prefetch"><link href="dist/js/chunk-2d0ddf37.d162efb9.js" rel="prefetch"><link href="dist/js/chunk-2d0de01b.f98f06e7.js" rel="prefetch"><link href="dist/js/chunk-2d0e2326.f3a9c7fc.js" rel="prefetch"><link href="dist/js/chunk-2d0e268c.2255a1cb.js" rel="prefetch"><link href="dist/js/chunk-2d0e5089.2b202405.js" rel="prefetch"><link href="dist/js/chunk-2d0e9742.a2d22497.js" rel="prefetch"><link href="dist/js/chunk-2d0f026c.9cd8732d.js" rel="prefetch"><link href="dist/js/chunk-2d2082b9.fe227afb.js" rel="prefetch"><link href="dist/js/chunk-2d208ffa.48cb1221.js" rel="prefetch"><link href="dist/js/chunk-2d20ec02.917aff76.js" rel="prefetch"><link href="dist/js/chunk-2d20f68f.acd7e356.js" rel="prefetch"><link href="dist/js/chunk-2d210a7a.5a4025ce.js" rel="prefetch"><link href="dist/js/chunk-2d216004.905379d5.js" rel="prefetch"><link href="dist/js/chunk-2d216b67.2d06497a.js" rel="prefetch"><link href="dist/js/chunk-2d217907.cc6917a4.js" rel="prefetch"><link href="dist/js/chunk-2d226d0a.3703455b.js" rel="prefetch"><link href="dist/js/chunk-2d2299c3.ffd15e65.js" rel="prefetch"><link href="dist/js/chunk-2d22bd06.cace3b1b.js" rel="prefetch"><link href="dist/js/chunk-2d2308b0.fb49d06b.js" rel="prefetch"><link href="dist/js/chunk-2d238428.266d8f0c.js" rel="prefetch"><link href="dist/js/chunk-3a2f3e67.13278516.js" rel="prefetch"><link href="dist/css/app.8a532989.css" rel="preload" as="style"><link href="dist/css/chunk-vendors.1790fe42.css" rel="preload" as="style"><link href="dist/js/app.0642ce46.js" rel="preload" as="script"><link href="dist/js/chunk-vendors.7f3b8358.js" rel="preload" as="script"><link href="dist/css/chunk-vendors.1790fe42.css" rel="stylesheet"><link href="dist/css/app.8a532989.css" 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 src="dist/js/chunk-vendors.7f3b8358.js"></script><script src="dist/js/app.0642ce46.js"></script></body></html>
<!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,initial-scale=1"><link rel="icon" href="./dist/logo.png"><title>一个简单的web思维导图实现</title><link href="dist/js/chunk-2d0a3179.05d77cdf.js" rel="prefetch"><link href="dist/js/chunk-2d0aa579.61258a02.js" rel="prefetch"><link href="dist/js/chunk-2d0aa978.040c6f5c.js" rel="prefetch"><link href="dist/js/chunk-2d0ab10b.87f48f42.js" rel="prefetch"><link href="dist/js/chunk-2d0abe0f.ae972b36.js" rel="prefetch"><link href="dist/js/chunk-2d0b361e.b094b87c.js" rel="prefetch"><link href="dist/js/chunk-2d0b91e5.34207f33.js" rel="prefetch"><link href="dist/js/chunk-2d0b92c3.ade5a7e0.js" rel="prefetch"><link href="dist/js/chunk-2d0ba309.c9a9ab22.js" rel="prefetch"><link href="dist/js/chunk-2d0bd54e.db6065c6.js" rel="prefetch"><link href="dist/js/chunk-2d0be174.1ffa155d.js" rel="prefetch"><link href="dist/js/chunk-2d0c0a44.a872e86f.js" rel="prefetch"><link href="dist/js/chunk-2d0c14fc.17d4f60a.js" rel="prefetch"><link href="dist/js/chunk-2d0c18d8.b6a4f7bb.js" rel="prefetch"><link href="dist/js/chunk-2d0c191e.2803233f.js" rel="prefetch"><link href="dist/js/chunk-2d0c1a01.77611624.js" rel="prefetch"><link href="dist/js/chunk-2d0c20be.57f5b62e.js" rel="prefetch"><link href="dist/js/chunk-2d0d9fbc.5f7a8ef7.js" rel="prefetch"><link href="dist/js/chunk-2d0da701.6c0d2c1e.js" rel="prefetch"><link href="dist/js/chunk-2d0dad5f.3ab82f4a.js" rel="prefetch"><link href="dist/js/chunk-2d0db0f2.32d1bf7e.js" rel="prefetch"><link href="dist/js/chunk-2d0dddce.836132f8.js" rel="prefetch"><link href="dist/js/chunk-2d0ddf37.7b4a470a.js" rel="prefetch"><link href="dist/js/chunk-2d0de01b.00dad103.js" rel="prefetch"><link href="dist/js/chunk-2d0e2326.8750dc1f.js" rel="prefetch"><link href="dist/js/chunk-2d0e268c.2d4b7022.js" rel="prefetch"><link href="dist/js/chunk-2d0e5089.a4640577.js" rel="prefetch"><link href="dist/js/chunk-2d0e9742.bd5197f5.js" rel="prefetch"><link href="dist/js/chunk-2d0f026c.67054456.js" rel="prefetch"><link href="dist/js/chunk-2d2082b9.c7c6517f.js" rel="prefetch"><link href="dist/js/chunk-2d208ffa.f4b779ed.js" rel="prefetch"><link href="dist/js/chunk-2d20ec02.917aff76.js" rel="prefetch"><link href="dist/js/chunk-2d20f68f.4cf834ac.js" rel="prefetch"><link href="dist/js/chunk-2d210a7a.e60ccf9b.js" rel="prefetch"><link href="dist/js/chunk-2d216004.41f8f60d.js" rel="prefetch"><link href="dist/js/chunk-2d217907.3772894a.js" rel="prefetch"><link href="dist/js/chunk-2d226d0a.5947204c.js" rel="prefetch"><link href="dist/js/chunk-2d2299c3.0bdd83ab.js" rel="prefetch"><link href="dist/js/chunk-2d22bd06.1447b6d2.js" rel="prefetch"><link href="dist/js/chunk-2d2308b0.4fa18681.js" rel="prefetch"><link href="dist/js/chunk-2d238428.61fffbf5.js" rel="prefetch"><link href="dist/js/chunk-3a2f3e67.13278516.js" rel="prefetch"><link href="dist/css/app.0db5f6e3.css" rel="preload" as="style"><link href="dist/css/chunk-vendors.c097b26d.css" rel="preload" as="style"><link href="dist/js/app.ef341b88.js" rel="preload" as="script"><link href="dist/js/chunk-vendors.524ee6e1.js" rel="preload" as="script"><link href="dist/css/chunk-vendors.c097b26d.css" rel="stylesheet"><link href="dist/css/app.0db5f6e3.css" 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 src="dist/js/chunk-vendors.524ee6e1.js"></script><script src="dist/js/app.ef341b88.js"></script></body></html>

View File

@@ -900,6 +900,17 @@ const data5 = {
}
}
// 富文本数据v0.4.0+需要使用RichText插件才支持富文本编辑
const richTextData = {
"root": {
"data": {
"text": "<a href='http://lxqnsys.com/' target='_blank'>理想去年实验室</a>",
"richText": true
},
"children": []
}
}
const rootData = {
"root": {
"data": {

View File

@@ -31,6 +31,12 @@
"isActive": false
},
"children": []
}, {
"data": {
"text": "<a href='http://lxqnsys.com/' target='_blank'>理想去年实验室</a>",
"richText": true
},
"children": []
}]
}]
},

View File

@@ -59,7 +59,9 @@ const defaultOpt = {
opacity: 0.5,
fontSize: 14
}
}
},
// 达到该宽度文本自动换行
textAutoWrapWidth: 500
}
// 思维导图
@@ -118,9 +120,7 @@ class MindMap {
// 注册插件
MindMap.pluginList.forEach((plugin) => {
this[plugin.instanceName] = new plugin({
mindMap: this
})
this.initPlugin(plugin)
})
// 初始渲染
@@ -142,21 +142,21 @@ class MindMap {
}
// 渲染,部分渲染
render() {
render(callback) {
this.batchExecution.push('render', () => {
this.initTheme()
this.renderer.reRender = false
this.renderer.render()
this.renderer.render(callback)
})
}
// 重新渲染
reRender() {
reRender(callback) {
this.batchExecution.push('render', () => {
this.draw.clear()
this.initTheme()
this.renderer.reRender = true
this.renderer.render()
this.renderer.render(callback)
})
}
@@ -344,7 +344,17 @@ class MindMap {
// 把实际内容变换
draw.translate(-rect.x + elRect.left, -rect.y + elRect.top)
// 克隆一份数据
const clone = svg.clone()
let clone = svg.clone()
// 如果实际图形宽高超出了屏幕宽高,且存在水印的话需要重新绘制水印,否则会出现超出部分没有水印的问题
if ((rect.width > origWidth || rect.height > origHeight) && this.watermark && this.watermark.hasWatermark()) {
this.width = rect.width
this.height = rect.height
this.watermark.draw()
clone = svg.clone()
this.width = origWidth
this.height = origHeight
this.watermark.draw()
}
// 恢复原先的大小和变换信息
svg.size(origWidth, origHeight)
draw.transform(origTransform)
@@ -362,14 +372,51 @@ class MindMap {
scaleY: origTransform.scaleY // 思维导图图形的垂直缩放值
}
}
// 添加插件
addPlugin(plugin, opt) {
let index = MindMap.hasPlugin(plugin)
if (index === -1) {
MindMap.usePlugin(plugin, opt)
this.initPlugin(plugin)
}
}
// 移除插件
removePlugin(plugin) {
let index = MindMap.hasPlugin(plugin)
if (index !== -1) {
MindMap.pluginList.splice(index, 1)
if (this[plugin.instanceName]) {
if (this[plugin.instanceName].beforePluginRemove) {
this[plugin.instanceName].beforePluginRemove()
}
delete this[plugin.instanceName]
}
}
}
// 实例化插件
initPlugin(plugin) {
this[plugin.instanceName] = new plugin({
mindMap: this,
pluginOpt: plugin.pluginOpt
})
}
}
// 插件列表
MindMap.pluginList = []
MindMap.usePlugin = (plugin) => {
MindMap.usePlugin = (plugin, opt = {}) => {
plugin.pluginOpt = opt
MindMap.pluginList.push(plugin)
return MindMap
}
MindMap.hasPlugin = (plugin) => {
return MindMap.pluginList.findIndex((item) => {
return item === plugin
})
}
// 定义新主题
MindMap.defineTheme = (name, config = {}) => {

View File

@@ -1,19 +1,21 @@
{
"name": "simple-mind-map",
"version": "0.2.21",
"version": "0.4.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "0.2.21",
"version": "0.4.0",
"license": "MIT",
"dependencies": {
"@svgdotjs/svg.js": "^3.0.16",
"canvg": "^3.0.7",
"deepmerge": "^1.5.2",
"eventemitter3": "^4.0.7",
"html2canvas": "^1.4.1",
"jspdf": "^2.5.1",
"jszip": "^3.10.1",
"quill": "^1.3.6",
"xml-js": "^1.6.11"
},
"devDependencies": {
@@ -225,7 +227,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
"optional": true,
"engines": {
"node": ">= 0.6.0"
}
@@ -251,6 +252,18 @@
"node": ">= 0.4.0"
}
},
"node_modules/call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"dependencies": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -294,6 +307,14 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
"engines": {
"node": ">=0.8"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -351,7 +372,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
"optional": true,
"dependencies": {
"utrie": "^1.0.2"
}
@@ -373,6 +393,22 @@
}
}
},
"node_modules/deep-equal": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
"integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==",
"dependencies": {
"is-arguments": "^1.0.4",
"is-date-object": "^1.0.1",
"is-regex": "^1.0.4",
"object-is": "^1.0.1",
"object-keys": "^1.1.1",
"regexp.prototype.flags": "^1.2.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -387,6 +423,21 @@
"node": ">=0.10.0"
}
},
"node_modules/define-properties": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
"integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==",
"dependencies": {
"has-property-descriptors": "^1.0.0",
"object-keys": "^1.1.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@@ -586,12 +637,22 @@
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
},
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
},
"node_modules/fast-diff": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz",
"integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig=="
},
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@@ -671,6 +732,32 @@
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true
},
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"node_modules/functions-have-names": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-intrinsic": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
"integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
"dependencies": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -724,6 +811,17 @@
"integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
"dev": true
},
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dependencies": {
"function-bind": "^1.1.1"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -733,11 +831,46 @@
"node": ">=8"
}
},
"node_modules/has-property-descriptors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
"integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
"dependencies": {
"get-intrinsic": "^1.1.1"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
"dependencies": {
"has-symbols": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/html2canvas": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
"optional": true,
"dependencies": {
"css-line-break": "^2.1.0",
"text-segmentation": "^1.0.3"
@@ -800,6 +933,35 @@
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/is-arguments": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
"integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
"dependencies": {
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-date-object": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
"integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
"dependencies": {
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -830,6 +992,21 @@
"node": ">=8"
}
},
"node_modules/is-regex": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
"integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
"dependencies": {
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
@@ -969,6 +1146,29 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true
},
"node_modules/object-is": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz",
"integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==",
"dependencies": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -1030,6 +1230,11 @@
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
},
"node_modules/parchment": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz",
"integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg=="
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -1132,6 +1337,37 @@
}
]
},
"node_modules/quill": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/quill/-/quill-1.3.6.tgz",
"integrity": "sha512-K0mvhimWZN6s+9OQ249CH2IEPZ9JmkFuCQeHAOQax3EZ2nDJ3wfGh59mnlQaZV2i7u8eFarx6wAtvQKgShojug==",
"dependencies": {
"clone": "^2.1.1",
"deep-equal": "^1.0.1",
"eventemitter3": "^2.0.3",
"extend": "^3.0.1",
"parchment": "^1.1.4",
"quill-delta": "^3.6.2"
}
},
"node_modules/quill-delta": {
"version": "3.6.3",
"resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-3.6.3.tgz",
"integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==",
"dependencies": {
"deep-equal": "^1.0.1",
"extend": "^3.0.2",
"fast-diff": "1.1.2"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/quill/node_modules/eventemitter3": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
"integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg=="
},
"node_modules/raf": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
@@ -1159,6 +1395,22 @@
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
"node_modules/regexp.prototype.flags": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
"integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==",
"dependencies": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3",
"functions-have-names": "^1.2.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/regexpp": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
@@ -1336,7 +1588,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
"optional": true,
"dependencies": {
"utrie": "^1.0.2"
}
@@ -1389,7 +1640,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
"optional": true,
"dependencies": {
"base64-arraybuffer": "^1.0.2"
}
@@ -1593,8 +1843,7 @@
"base64-arraybuffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
"optional": true
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ=="
},
"brace-expansion": {
"version": "1.1.11",
@@ -1611,6 +1860,15 @@
"resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
"integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g=="
},
"call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"requires": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
}
},
"callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -1642,6 +1900,11 @@
"supports-color": "^7.1.0"
}
},
"clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -1688,7 +1951,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
"optional": true,
"requires": {
"utrie": "^1.0.2"
}
@@ -1702,6 +1964,19 @@
"ms": "2.1.2"
}
},
"deep-equal": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
"integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==",
"requires": {
"is-arguments": "^1.0.4",
"is-date-object": "^1.0.1",
"is-regex": "^1.0.4",
"object-is": "^1.0.1",
"object-keys": "^1.1.1",
"regexp.prototype.flags": "^1.2.0"
}
},
"deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -1713,6 +1988,15 @@
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-1.5.2.tgz",
"integrity": "sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ=="
},
"define-properties": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
"integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==",
"requires": {
"has-property-descriptors": "^1.0.0",
"object-keys": "^1.1.1"
}
},
"doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@@ -1860,12 +2144,22 @@
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
},
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
},
"fast-diff": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz",
"integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig=="
},
"fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@@ -1933,6 +2227,26 @@
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true
},
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"functions-have-names": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="
},
"get-intrinsic": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
"integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
"requires": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.3"
}
},
"glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -1971,17 +2285,45 @@
"integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
"dev": true
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"requires": {
"function-bind": "^1.1.1"
}
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"has-property-descriptors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
"integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
"requires": {
"get-intrinsic": "^1.1.1"
}
},
"has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
},
"has-tostringtag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
"requires": {
"has-symbols": "^1.0.2"
}
},
"html2canvas": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
"optional": true,
"requires": {
"css-line-break": "^2.1.0",
"text-segmentation": "^1.0.3"
@@ -2029,6 +2371,23 @@
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"is-arguments": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
"integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
"requires": {
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
}
},
"is-date-object": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
"integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
"requires": {
"has-tostringtag": "^1.0.0"
}
},
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -2050,6 +2409,15 @@
"integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
"dev": true
},
"is-regex": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
"integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
"requires": {
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
}
},
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
@@ -2168,6 +2536,20 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true
},
"object-is": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz",
"integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==",
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3"
}
},
"object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -2214,6 +2596,11 @@
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
},
"parchment": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz",
"integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg=="
},
"parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -2275,6 +2662,36 @@
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"dev": true
},
"quill": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/quill/-/quill-1.3.6.tgz",
"integrity": "sha512-K0mvhimWZN6s+9OQ249CH2IEPZ9JmkFuCQeHAOQax3EZ2nDJ3wfGh59mnlQaZV2i7u8eFarx6wAtvQKgShojug==",
"requires": {
"clone": "^2.1.1",
"deep-equal": "^1.0.1",
"eventemitter3": "^2.0.3",
"extend": "^3.0.1",
"parchment": "^1.1.4",
"quill-delta": "^3.6.2"
},
"dependencies": {
"eventemitter3": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
"integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg=="
}
}
},
"quill-delta": {
"version": "3.6.3",
"resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-3.6.3.tgz",
"integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==",
"requires": {
"deep-equal": "^1.0.1",
"extend": "^3.0.2",
"fast-diff": "1.1.2"
}
},
"raf": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
@@ -2302,6 +2719,16 @@
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
"regexp.prototype.flags": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
"integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==",
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3",
"functions-have-names": "^1.2.2"
}
},
"regexpp": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
@@ -2419,7 +2846,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
"optional": true,
"requires": {
"utrie": "^1.0.2"
}
@@ -2463,7 +2889,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
"optional": true,
"requires": {
"base64-arraybuffer": "^1.0.2"
}

View File

@@ -1,6 +1,6 @@
{
"name": "simple-mind-map",
"version": "0.3.1",
"version": "0.4.1",
"description": "一个简单的web在线思维导图",
"authors": [
{
@@ -28,8 +28,10 @@
"canvg": "^3.0.7",
"deepmerge": "^1.5.2",
"eventemitter3": "^4.0.7",
"html2canvas": "^1.4.1",
"jspdf": "^2.5.1",
"jszip": "^3.10.1",
"quill": "^1.3.6",
"xml-js": "^1.6.11"
},
"keywords": [

View File

@@ -43,12 +43,7 @@ class Event extends EventEmitter {
this.mindMap.svg.on('mousedown', this.onSvgMousedown)
window.addEventListener('mousemove', this.onMousemove)
window.addEventListener('mouseup', this.onMouseup)
// 兼容火狐浏览器
if (window.navigator.userAgent.toLowerCase().indexOf('firefox') != -1) {
this.mindMap.el.addEventListener('DOMMouseScroll', this.onMousewheel)
} else {
this.mindMap.el.addEventListener('mousewheel', this.onMousewheel)
}
this.mindMap.el.addEventListener('wheel', this.onMousewheel)
this.mindMap.svg.on('contextmenu', this.onContextmenu)
window.addEventListener('keyup', this.onKeyup)
}
@@ -59,7 +54,7 @@ class Event extends EventEmitter {
this.mindMap.el.removeEventListener('mousedown', this.onMousedown)
window.removeEventListener('mousemove', this.onMousemove)
window.removeEventListener('mouseup', this.onMouseup)
this.mindMap.el.removeEventListener('mousewheel', this.onMousewheel)
this.mindMap.el.removeEventListener('wheel', this.onMousewheel)
this.mindMap.svg.off('contextmenu', this.onContextmenu)
window.removeEventListener('keyup', this.onKeyup)
}
@@ -110,10 +105,16 @@ class Event extends EventEmitter {
e.stopPropagation()
e.preventDefault()
let dir
if ((e.wheelDeltaY || e.detail) > 0) {
dir = 'up'
// 解决mac触控板双指缩放方向相反的问题
if (e.ctrlKey) {
if (e.deltaY > 0) dir = 'up'
if (e.deltaY < 0) dir = 'down'
} else {
dir = 'down'
if ((e.wheelDeltaY || e.detail) > 0) {
dir = 'up'
} else {
dir = 'down'
}
}
this.emit('mousewheel', e, dir, this)
}

View File

@@ -26,7 +26,7 @@ class Export {
}
// 获取svg数据
async getSvgData() {
async getSvgData(domToImage) {
let { svg, svgHTML } = this.mindMap.getSvgData()
// 把图片的url转换成data:url类型否则导出会丢失图片
let imageList = svg.find('image')
@@ -36,9 +36,17 @@ class Export {
item.attr('href', imgData)
})
await Promise.all(task)
// 如果开启了富文本编辑需要把svg中的dom元素转换成图片
let nodeWithDomToImg = null
if (domToImage && this.mindMap.richText) {
let res = await this.mindMap.richText.handleSvgDomElements(svg)
nodeWithDomToImg = res.svg
svgHTML = res.svgHTML
}
return {
node: svg,
str: svgHTML
str: svgHTML,
nodeWithDomToImg
}
}
@@ -123,7 +131,7 @@ class Export {
* 方法2.把svg的图片提取出来再挨个绘制到canvas里最后一起转换
*/
async png() {
let { str } = await this.getSvgData()
let { str } = await this.getSvgData(true)
// 转换成blob数据
let blob = new Blob([str], {
type: 'image/svg+xml'
@@ -191,8 +199,21 @@ class Export {
}
// 导出为svg
async svg(name) {
let { node } = await this.getSvgData()
// domToImage是否将svg中的dom节点转换成图片的形式
// plusCssText附加的css样式如果svg中存在dom节点想要设置一些针对节点的样式可以通过这个参数传入
async svg(name, domToImage = false, plusCssText) {
let { node, nodeWithDomToImg } = await this.getSvgData(domToImage)
// 开启了节点富文本编辑
if (this.mindMap.richText) {
if (domToImage) {
node = nodeWithDomToImg
} else if (plusCssText) {
let foreignObjectList = node.find('foreignObject')
if (foreignObjectList.length > 0) {
foreignObjectList[0].add(SVG(`<style>${plusCssText}</style>`))
}
}
}
node.first().before(SVG(`<title>${name}</title>`))
await this.drawBackgroundToSvg(node)
let str = node.svg()

File diff suppressed because it is too large Load Diff

View File

@@ -239,7 +239,7 @@ class Render {
// 渲染
render() {
render(callback = () => {}) {
if (this.reRender) {
this.clearActive()
}
@@ -247,6 +247,7 @@ class Render {
this.root = root
this.root.render(() => {
this.mindMap.emit('node_tree_render_end')
callback()
})
})
this.mindMap.emit('node_active', null, this.activeNodeList)
@@ -472,6 +473,8 @@ class Render {
if (node.isRoot) {
return
}
// 如果是二级节点变成了下级节点,或是下级节点变成了二级节点,节点样式需要更新
let nodeLayerChanged = (node.layerIndex === 1 && exist.layerIndex !== 1) || (node.layerIndex !== 1 && exist.layerIndex === 1)
// 移动节点
let nodeParent = node.parent
let nodeBorthers = nodeParent.children
@@ -495,7 +498,12 @@ class Render {
}
existBorthers.splice(existIndex, 0, node)
existParent.nodeData.children.splice(existIndex, 0, node.nodeData)
this.mindMap.render()
this.mindMap.render(() => {
if (nodeLayerChanged) {
node.getSize()
node.renderNode()
}
})
}
// 将节点移动到另一个节点的后面
@@ -504,6 +512,8 @@ class Render {
if (node.isRoot) {
return
}
// 如果是二级节点变成了下级节点,或是下级节点变成了二级节点,节点样式需要更新
let nodeLayerChanged = (node.layerIndex === 1 && exist.layerIndex !== 1) || (node.layerIndex !== 1 && exist.layerIndex === 1)
// 移动节点
let nodeParent = node.parent
let nodeBorthers = nodeParent.children
@@ -528,7 +538,12 @@ class Render {
existIndex++
existBorthers.splice(existIndex, 0, node)
existParent.nodeData.children.splice(existIndex, 0, node.nodeData)
this.mindMap.render()
this.mindMap.render(() => {
if (nodeLayerChanged) {
node.getSize()
node.renderNode()
}
})
}
// 移除节点
@@ -537,29 +552,35 @@ class Render {
if (this.activeNodeList.length <= 0) {
return
}
for (let i = 0; i < this.activeNodeList.length; i++) {
let node = this.activeNodeList[i]
if (node.isGeneralization) {
// 删除概要节点
this.setNodeData(node.generalizationBelongNode, {
generalization: null
})
node.generalizationBelongNode.update()
this.removeActiveNode(node)
i--
} else if (node.isRoot) {
node.children.forEach(child => {
child.remove()
})
node.children = []
node.nodeData.children = []
break
} else {
this.removeActiveNode(node)
this.removeOneNode(node)
i--
let root = this.activeNodeList.find((node) => {
return node.isRoot
})
if (root) {
this.clearActive()
root.children.forEach(child => {
child.remove()
})
root.children = []
root.nodeData.children = []
} else {
for (let i = 0; i < this.activeNodeList.length; i++) {
let node = this.activeNodeList[i]
if (node.isGeneralization) {
// 删除概要节点
this.setNodeData(node.generalizationBelongNode, {
generalization: null
})
node.generalizationBelongNode.update()
this.removeActiveNode(node)
i--
} else {
this.removeActiveNode(node)
this.removeOneNode(node)
i--
}
}
}
this.activeNodeList = []
this.mindMap.emit('node_active', null, [])
this.mindMap.render()
}
@@ -647,6 +668,15 @@ class Render {
[prop]: value
}
}
// 如果开启了富文本,则需要应用到富文本上
if (this.mindMap.richText) {
this.mindMap.richText.showEditText(node)
let config = this.mindMap.richText.normalStyleToRichTextStyle({
[prop]: value
})
this.mindMap.richText.formatAllText(config)
this.mindMap.richText.hideEditText()
}
this.setNodeDataRender(node, data)
// 更新了连线的样式
if (lineStyleProps.includes(prop)) {
@@ -767,9 +797,10 @@ class Render {
// 设置节点文本
setNodeText(node, text) {
setNodeText(node, text, richText) {
this.setNodeDataRender(node, {
text
text,
richText
})
}

View File

@@ -0,0 +1,443 @@
import Quill from 'quill'
import 'quill/dist/quill.snow.css'
import './css/quill.css'
import html2canvas from 'html2canvas'
import { Image as SvgImage } from '@svgdotjs/svg.js'
import { walk } from './utils'
let extended = false
// 扩展quill的字体列表
let fontFamilyList = [
'宋体, SimSun, Songti SC',
'微软雅黑, Microsoft YaHei',
'楷体, 楷体_GB2312, SimKai, STKaiti',
'黑体, SimHei, Heiti SC',
'隶书, SimLi',
'andale mono',
'arial, helvetica, sans-serif',
'arial black, avant garde',
'comic sans ms',
'impact, chicago',
'times new roman',
'sans-serif',
'serif'
]
// 扩展quill的字号列表
let fontSizeList = new Array(100).fill(0).map((_, index) => {
return index + 'px'
})
// 节点支持富文本编辑功能
class RichText {
constructor({ mindMap, pluginOpt }) {
this.mindMap = mindMap
this.pluginOpt = pluginOpt
this.textEditNode = null
this.showTextEdit = false
this.quill = null
this.range = null
this.lastRange = null
this.node = null
this.initOpt()
this.extendQuill()
}
// 处理选项参数
initOpt() {
if (
this.pluginOpt.fontFamilyList &&
Array.isArray(this.pluginOpt.fontFamilyList)
) {
fontFamilyList = this.pluginOpt.fontFamilyList
}
if (
this.pluginOpt.fontSizeList &&
Array.isArray(this.pluginOpt.fontSizeList)
) {
fontSizeList = this.pluginOpt.fontSizeList
}
}
// 扩展quill编辑器
extendQuill() {
if (extended) {
return
}
extended = true
// 扩展quill的字体列表
const FontAttributor = Quill.import('attributors/class/font')
FontAttributor.whitelist = fontFamilyList
Quill.register(FontAttributor, true)
const FontStyle = Quill.import('attributors/style/font')
FontStyle.whitelist = fontFamilyList
Quill.register(FontStyle, true)
// 扩展quill的字号列表
const SizeAttributor = Quill.import('attributors/class/size')
SizeAttributor.whitelist = fontSizeList
Quill.register(SizeAttributor, true)
const SizeStyle = Quill.import('attributors/style/size')
SizeStyle.whitelist = fontSizeList
Quill.register(SizeStyle, true)
}
// 显示文本编辑控件
showEditText(node, rect) {
if (this.showTextEdit) {
return
}
this.node = node
if (!rect) rect = node._textData.node.node.getBoundingClientRect()
this.mindMap.emit('before_show_text_edit')
this.mindMap.renderer.textEdit.registerTmpShortcut()
if (!this.textEditNode) {
this.textEditNode = document.createElement('div')
this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);outline: none; word-break: break-all;`
document.body.appendChild(this.textEditNode)
}
// 原始宽高
let g = node._textData.node
let originWidth = g.attr('data-width')
let originHeight = g.attr('data-height')
this.textEditNode.style.minWidth = originWidth + 'px'
this.textEditNode.style.minHeight = originHeight + 'px'
this.textEditNode.style.left =
rect.left + (rect.width - originWidth) / 2 + 'px'
this.textEditNode.style.top =
rect.top + (rect.height - originHeight) / 2 + 'px'
this.textEditNode.style.display = 'block'
this.textEditNode.style.maxWidth = this.mindMap.opt.textAutoWrapWidth + 'px'
this.textEditNode.style.transform = `scale(${rect.width / originWidth}, ${
rect.height / originHeight
})`
if (!node.nodeData.data.richText) {
// 还不是富文本的情况
let text = node.nodeData.data.text.split(/\n/gim).join('<br>')
let html = `<p>${text}</p>`
this.textEditNode.innerHTML = html
} else {
this.textEditNode.innerHTML = node.nodeData.data.text
}
this.initQuillEditor()
document.querySelector('.ql-editor').style.minHeight = originHeight + 'px'
this.showTextEdit = true
this.selectAll()
if (!node.nodeData.data.richText) {
// 如果是非富文本的情况,需要手动应用文本样式
this.setTextStyleIfNotRichText(node)
}
}
// 如果是非富文本的情况,需要手动应用文本样式
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.formatText(style)
}
// 隐藏文本编辑控件,即完成编辑
hideEditText() {
if (!this.showTextEdit) {
return
}
let html = this.quill.container.firstChild.innerHTML
// 去除最后的空行
html = html.replace(/<p><br><\/p>$/, '')
this.mindMap.renderer.activeNodeList.forEach(node => {
this.mindMap.execCommand('SET_NODE_TEXT', node, html, true)
if (node.isGeneralization) {
// 概要节点
node.generalizationBelongNode.updateGeneralization()
}
this.mindMap.render()
})
this.mindMap.emit(
'hide_text_edit',
this.textEditNode,
this.mindMap.renderer.activeNodeList
)
this.textEditNode.style.display = 'none'
this.showTextEdit = false
this.mindMap.emit('rich_text_selection_change', false)
this.node = null
}
// 初始化Quill富文本编辑器
initQuillEditor() {
this.quill = new Quill(this.textEditNode, {
modules: {
toolbar: false,
keyboard: {
bindings: {
enter: {
key: 13,
handler: function () {
// 覆盖默认的回车键换行
}
}
}
}
},
theme: 'snow'
})
this.quill.on('selection-change', range => {
this.lastRange = this.range
this.range = null
if (range) {
let bounds = this.quill.getBounds(range.index, range.length)
let rect = this.textEditNode.getBoundingClientRect()
let rectInfo = {
left: bounds.left + rect.left,
top: bounds.top + rect.top,
right: bounds.right + rect.left,
bottom: bounds.bottom + rect.top,
width: bounds.width
}
let formatInfo = this.quill.getFormat(range.index, range.length)
let hasRange = false
if (range.length == 0) {
hasRange = false
} else {
this.range = range
hasRange = true
}
this.mindMap.emit(
'rich_text_selection_change',
hasRange,
rectInfo,
formatInfo
)
}
})
}
// 选中全部
selectAll() {
this.quill.setSelection(0, this.quill.getLength())
}
// 格式化当前选中的文本
formatText(config = {}, clear = false) {
if (!this.range && !this.lastRange) return
this.syncFormatToNodeConfig(config, clear)
let rangeLost = !this.range
let range = rangeLost ? this.lastRange : this.range
clear ? this.quill.removeFormat(range.index, range.length) : this.quill.formatText(range.index, range.length, config)
if (rangeLost) {
this.quill.setSelection(this.lastRange.index, this.lastRange.length)
}
}
// 清除当前选中文本的样式
removeFormat() {
this.formatText({}, true)
}
// 格式化指定范围的文本
formatRangeText(range, config = {}) {
if (!range) return
this.syncFormatToNodeConfig(config)
this.quill.formatText(range.index, range.length, config)
}
// 格式化所有文本
formatAllText(config = {}) {
this.syncFormatToNodeConfig(config)
this.quill.formatText(0, this.quill.getLength(), config)
}
// 同步格式化到节点样式配置
syncFormatToNodeConfig(config, clear) {
if (!this.node) return
if (clear) {
// 清除文本样式
['fontFamily', 'fontSize', 'fontWeight', 'fontStyle', 'textDecoration', 'color'].forEach((prop) => {
delete this.node.nodeData.data[prop]
})
} else {
let data = this.richTextStyleToNormalStyle(config)
this.mindMap.renderer.setNodeData(this.node, data)
}
}
// 将普通节点样式对象转换成富文本样式对象
normalStyleToRichTextStyle(style) {
let config = {}
Object.keys(style).forEach(prop => {
let value = style[prop]
switch (prop) {
case 'fontFamily':
config.font = value
break
case 'fontSize':
config.size = value + 'px'
break
case 'fontWeight':
config.bold = value === 'bold'
break
case 'fontStyle':
config.italic = value === 'italic'
break
case 'textDecoration':
config.underline = value === 'underline'
config.strike = value === 'line-through'
break
case 'color':
config.color = value
break
default:
break
}
})
return config
}
// 将富文本样式对象转换成普通节点样式对象
richTextStyleToNormalStyle(config) {
let data = {}
Object.keys(config).forEach(prop => {
let value = config[prop]
switch (prop) {
case 'font':
data.fontFamily = value
break
case 'size':
data.fontSize = parseFloat(value)
break
case 'bold':
data.fontWeight = value ? 'bold' : 'normal'
break
case 'italic':
data.fontStyle = value ? 'italic' : 'normal'
break
case 'underline':
data.textDecoration = value ? 'underline' : 'none'
break
case 'strike':
data.textDecoration = value ? 'line-through' : 'none'
break
case 'color':
data.color = value
break
default:
break
}
})
return data
}
// 将svg中嵌入的dom元素转换成图片
async _handleSvgDomElements(svg) {
svg = svg.clone()
let foreignObjectList = svg.find('foreignObject')
let task = foreignObjectList.map(async item => {
let clone = item.first().node.cloneNode(true)
let div = document.createElement('div')
div.style.cssText = `position: fixed; left: -999999px;`
div.appendChild(clone)
this.mindMap.el.appendChild(div)
let canvas = await html2canvas(clone, {
backgroundColor: null
})
this.mindMap.el.removeChild(div)
let imgNode = new SvgImage()
.load(canvas.toDataURL())
.size(canvas.width, canvas.height)
item.replace(imgNode)
})
await Promise.all(task)
return {
svg: svg,
svgHTML: svg.svg()
}
}
// 将svg中嵌入的dom元素转换成图片
handleSvgDomElements(svg) {
return new Promise((resolve, reject) => {
svg = svg.clone()
let foreignObjectList = svg.find('foreignObject')
let index = 0
let len = foreignObjectList.length
let transform = async () => {
this.mindMap.emit('transforming-dom-to-images', index, len)
try {
let item = foreignObjectList[index++]
let parent = item.parent()
let clone = item.first().node.cloneNode(true)
let div = document.createElement('div')
div.style.cssText = `position: fixed; left: -999999px;`
div.appendChild(clone)
this.mindMap.el.appendChild(div)
let canvas = await html2canvas(clone, {
backgroundColor: null
})
// 优先使用原始宽高因为当设备的window.devicePixelRatio不为1时html2canvas输出的图片会更大
let imgNodeWidth = parent.attr('data-width') || canvas.width
let imgNodeHeight = parent.attr('data-height') || canvas.height
this.mindMap.el.removeChild(div)
let imgNode = new SvgImage()
.load(canvas.toDataURL())
.size(imgNodeWidth, imgNodeHeight)
.x((parent ? parent.attr('data-offsetx') : 0) || 0)
item.replace(imgNode)
if (index <= len - 1) {
setTimeout(() => {
transform()
}, 0)
} else {
resolve({
svg: svg,
svgHTML: svg.svg()
})
}
} catch (error) {
reject(error)
}
}
if (len > 0) transform()
})
}
// 将所有节点转换成非富文本节点
transformAllNodesToNormalNode() {
let div = document.createElement('div')
walk(
this.mindMap.renderer.renderTree,
null,
node => {
if (node.data.richText) {
node.data.richText = false
div.innerHTML = node.data.text
node.data.text = div.textContent
}
},
null,
true,
0,
0
)
this.mindMap.reRender()
}
// 插件被移除前做的事情
beforePluginRemove() {
this.transformAllNodesToNormalNode()
}
}
RichText.instanceName = 'richText'
export default RichText

View File

@@ -124,12 +124,24 @@ class Style {
})
}
// 获取文本样式
getTextFontStyle() {
return {
italic: this.merge('fontStyle') === 'italic',
bold: this.merge('fontWeight'),
fontSize: this.merge('fontSize'),
fontFamily: this.merge('fontFamily')
}
}
// html文字节点
domText(node, fontSizeScale = 1) {
node.style.fontFamily = this.merge('fontFamily')
node.style.fontSize = this.merge('fontSize') * fontSizeScale + 'px'
node.style.fontWeight = this.merge('fontWeight') || 'normal'
node.style.lineHeight = this.merge('lineHeight')
node.style.fontStyle = this.merge('fontStyle')
}
// 标签文字

View File

@@ -50,7 +50,12 @@ export default class TextEdit {
// 显示文本编辑框
show(node) {
this.showEditTextBox(node, node._textData.node.node.getBoundingClientRect())
let rect = node._textData.node.node.getBoundingClientRect()
if (this.mindMap.richText) {
this.mindMap.richText.showEditText(node, rect)
return
}
this.showEditTextBox(node, rect)
}
// 显示文本编辑框
@@ -59,14 +64,17 @@ export default class TextEdit {
this.registerTmpShortcut()
if (!this.textEditNode) {
this.textEditNode = document.createElement('div')
this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);padding: 3px 5px;margin-left: -5px;margin-top: -3px;outline: none;`
this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);padding: 3px 5px;margin-left: -5px;margin-top: -3px;outline: none; word-break: break-all;`
this.textEditNode.setAttribute('contenteditable', true)
this.textEditNode.addEventListener('keyup', e => {
e.stopPropagation()
})
document.body.appendChild(this.textEditNode)
}
node.style.domText(this.textEditNode, this.mindMap.view.scale)
let scale = this.mindMap.view.scale
let lineHeight = node.style.merge('lineHeight')
let fontSize = node.style.merge('fontSize')
node.style.domText(this.textEditNode, scale)
this.textEditNode.innerHTML = node.nodeData.data.text
.split(/\n/gim)
.join('<br>')
@@ -75,6 +83,8 @@ export default class TextEdit {
this.textEditNode.style.left = rect.left + 'px'
this.textEditNode.style.top = rect.top + 'px'
this.textEditNode.style.display = 'block'
this.textEditNode.style.maxWidth = this.mindMap.opt.textAutoWrapWidth * scale + 'px'
this.textEditNode.style.transform = `translateY(${-(lineHeight * fontSize - fontSize) / 2 * scale}px)`
this.showTextEdit = true
// 选中文本
this.selectNodeText()
@@ -91,6 +101,9 @@ export default class TextEdit {
// 隐藏文本编辑框
hideEditTextBox() {
if (this.mindMap.richText) {
return this.mindMap.richText.hideEditText()
}
if (!this.showTextEdit) {
return
}

View File

@@ -20,6 +20,11 @@ class Watermark {
this.updateWatermark(this.mindMap.opt.watermarkConfig || {})
}
// 获取是否存在水印
hasWatermark() {
return !!this.text.trim()
}
// 处理水印配置
handleConfig({ text, lineSpacing, textSpacing, angle, textStyle }) {
this.text = text === undefined ? '' : String(text).trim()
@@ -36,7 +41,7 @@ class Watermark {
// 非精确绘制,会绘制一些超出可视区域的水印
draw() {
this.watermarkDraw.clear()
if (!this.text.trim()) {
if (!this.hasWatermark()) {
return
}
let x = 0

View File

@@ -0,0 +1,10 @@
.ql-editor {
overflow: hidden;
padding: 0;
height: auto;
}
.ql-container {
height: auto;
font-size: inherit;
}

View File

@@ -1,267 +1,289 @@
import Base from './Base'
import { walk, asyncRun } from '../utils'
// 逻辑结构图
class LogicalStructure extends Base {
// 构造函数
constructor(opt = {}) {
super(opt)
}
// 布局
doLayout(callback) {
let task = [
() => {
this.computedBaseValue()
},
() => {
this.computedTopValue()
},
() => {
this.adjustTopValue()
},
() => {
callback(this.root)
}
]
asyncRun(task)
}
// 遍历数据计算节点的left、width、height
computedBaseValue() {
walk(
this.renderer.renderTree,
null,
(cur, parent, isRoot, layerIndex) => {
let newNode = this.createNode(cur, parent, isRoot, layerIndex)
// 根节点定位在画布中心位置
if (isRoot) {
this.setNodeCenter(newNode)
} else {
// 非根节点
// 定位到父节点右侧
newNode.left =
parent._node.left + parent._node.width + this.getMarginX(layerIndex)
}
if (!cur.data.expand) {
return true
}
},
(cur, parent, isRoot, layerIndex) => {
// 返回时计算节点的areaHeight也就是子节点所占的高度之和包括外边距
let len = cur.data.expand === false ? 0 : cur._node.children.length
cur._node.childrenAreaHeight = len
? cur._node.children.reduce((h, item) => {
return h + item.height
}, 0) +
(len + 1) * this.getMarginY(layerIndex + 1)
: 0
},
true,
0
)
}
// 遍历节点树计算节点的top
computedTopValue() {
walk(
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (
node.nodeData.data.expand &&
node.children &&
node.children.length
) {
let marginY = this.getMarginY(layerIndex + 1)
// 第一个子节点的top值 = 该节点中心的top值 - 子节点的高度之和的一半
let top = node.top + node.height / 2 - node.childrenAreaHeight / 2
let totalTop = top + marginY
node.children.forEach(cur => {
cur.top = totalTop
totalTop += cur.height + marginY
})
}
},
null,
true
)
}
// 调整节点top
adjustTopValue() {
walk(
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (!node.nodeData.data.expand) {
return
}
// 判断子节点所占的高度之和是否大于该节点自身,大于则需要调整位置
let difference =
node.childrenAreaHeight -
this.getMarginY(layerIndex + 1) * 2 -
node.height
if (difference > 0) {
this.updateBrothers(node, difference / 2)
}
},
null,
true
)
}
// 更新兄弟节点的top
updateBrothers(node, addHeight) {
if (node.parent) {
let childrenList = node.parent.children
let index = childrenList.findIndex(item => {
return item === node
})
childrenList.forEach((item, _index) => {
if (item === node || item.hasCustomPosition()) {
// 适配自定义位置
return
}
let _offset = 0
// 上面的节点往上移
if (_index < index) {
_offset = -addHeight
} else if (_index > index) {
// 下面的节点往下移
_offset = addHeight
}
item.top += _offset
// 同步更新子节点的位置
if (item.children && item.children.length) {
this.updateChildren(item.children, 'top', _offset)
}
})
// 更新父节点的位置
this.updateBrothers(node.parent, addHeight)
}
}
// 绘制连线,连接该节点到其子节点
renderLine(node, lines, style, lineStyle) {
if (lineStyle === 'curve') {
this.renderLineCurve(node, lines, style)
} else if (lineStyle === 'direct') {
this.renderLineDirect(node, lines, style)
} else {
this.renderLineStraight(node, lines, style)
}
}
// 直线风格连线
renderLineStraight(node, lines, style) {
if (node.children.length <= 0) {
return []
}
let { left, top, width, height, expandBtnSize } = node
let marginX = this.getMarginX(node.layerIndex + 1)
let s1 = (marginX - expandBtnSize) * 0.6
node.children.forEach((item, index) => {
let x1 =
node.layerIndex === 0 ? left + width : left + width + expandBtnSize
let y1 = top + height / 2
let x2 = item.left
let y2 = item.top + item.height / 2
// 节点使用横线风格,需要额外渲染横线
let nodeUseLineStyleOffset = this.mindMap.themeConfig.nodeUseLineStyle
? item.width
: 0
let path = `M ${x1},${y1} L ${x1 + s1},${y1} L ${x1 + s1},${y2} L ${
x2 + nodeUseLineStyleOffset
},${y2}`
lines[index].plot(path)
style && style(lines[index], item)
})
}
// 直连风格
renderLineDirect(node, lines, style) {
if (node.children.length <= 0) {
return []
}
let { left, top, width, height, expandBtnSize } = node
node.children.forEach((item, index) => {
let x1 =
node.layerIndex === 0 ? left + width / 2 : left + width + expandBtnSize
let y1 = top + height / 2
let x2 = item.left
let y2 = item.top + item.height / 2
// 节点使用横线风格,需要额外渲染横线
let nodeUseLineStylePath = this.mindMap.themeConfig.nodeUseLineStyle
? ` L ${item.left + item.width},${y2}`
: ''
let path = `M ${x1},${y1} L ${x2},${y2}` + nodeUseLineStylePath
lines[index].plot(path)
style && style(lines[index], item)
})
}
// 曲线风格连线
renderLineCurve(node, lines, style) {
if (node.children.length <= 0) {
return []
}
let { left, top, width, height, expandBtnSize } = node
node.children.forEach((item, index) => {
let x1 =
node.layerIndex === 0 ? left + width / 2 : left + width + expandBtnSize
let y1 = top + height / 2
let x2 = item.left
let y2 = item.top + item.height / 2
let path = ''
// 节点使用横线风格,需要额外渲染横线
let nodeUseLineStylePath = this.mindMap.themeConfig.nodeUseLineStyle
? ` L ${item.left + item.width},${y2}`
: ''
if (node.isRoot) {
path = this.quadraticCurvePath(x1, y1, x2, y2) + nodeUseLineStylePath
} else {
path = this.cubicBezierPath(x1, y1, x2, y2) + nodeUseLineStylePath
}
lines[index].plot(path)
style && style(lines[index], item)
})
}
// 渲染按钮
renderExpandBtn(node, btn) {
let { width, height } = node
let { translateX, translateY } = btn.transform()
// 节点使用横线风格,需要调整展开收起按钮位置
let nodeUseLineStyleOffset = this.mindMap.themeConfig.nodeUseLineStyle
? height / 2
: 0
btn.translate(
width - translateX,
height / 2 - translateY + nodeUseLineStyleOffset
)
}
// 创建概要节点
renderGeneralization(node, gLine, gNode) {
let {
top,
bottom,
right,
generalizationLineMargin,
generalizationNodeMargin
} = this.getNodeBoundaries(node, 'h')
let x1 = right + generalizationLineMargin
import Base from './Base'
import { walk, asyncRun } from '../utils'
// 逻辑结构图
class LogicalStructure extends Base {
// 构造函数
constructor(opt = {}) {
super(opt)
}
// 布局
doLayout(callback) {
let task = [
() => {
this.computedBaseValue()
},
() => {
this.computedTopValue()
},
() => {
this.adjustTopValue()
},
() => {
callback(this.root)
}
]
asyncRun(task)
}
// 遍历数据计算节点的left、width、height
computedBaseValue() {
walk(
this.renderer.renderTree,
null,
(cur, parent, isRoot, layerIndex) => {
let newNode = this.createNode(cur, parent, isRoot, layerIndex)
// 根节点定位在画布中心位置
if (isRoot) {
this.setNodeCenter(newNode)
} else {
// 非根节点
// 定位到父节点右侧
newNode.left =
parent._node.left + parent._node.width + this.getMarginX(layerIndex)
}
if (!cur.data.expand) {
return true
}
},
(cur, parent, isRoot, layerIndex) => {
// 返回时计算节点的areaHeight也就是子节点所占的高度之和包括外边距
let len = cur.data.expand === false ? 0 : cur._node.children.length
cur._node.childrenAreaHeight = len
? cur._node.children.reduce((h, item) => {
return h + item.height
}, 0) +
(len + 1) * this.getMarginY(layerIndex + 1)
: 0
},
true,
0
)
}
// 遍历节点树计算节点的top
computedTopValue() {
walk(
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (
node.nodeData.data.expand &&
node.children &&
node.children.length
) {
let marginY = this.getMarginY(layerIndex + 1)
// 第一个子节点的top值 = 该节点中心的top值 - 子节点的高度之和的一半
let top = node.top + node.height / 2 - node.childrenAreaHeight / 2
let totalTop = top + marginY
node.children.forEach(cur => {
cur.top = totalTop
totalTop += cur.height + marginY
})
}
},
null,
true
)
}
// 调整节点top
adjustTopValue() {
walk(
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (!node.nodeData.data.expand) {
return
}
// 判断子节点所占的高度之和是否大于该节点自身,大于则需要调整位置
let difference =
node.childrenAreaHeight -
this.getMarginY(layerIndex + 1) * 2 -
node.height
if (difference > 0) {
this.updateBrothers(node, difference / 2)
}
},
null,
true
)
}
// 更新兄弟节点的top
updateBrothers(node, addHeight) {
if (node.parent) {
let childrenList = node.parent.children
let index = childrenList.findIndex(item => {
return item === node
})
childrenList.forEach((item, _index) => {
if (item === node || item.hasCustomPosition()) {
// 适配自定义位置
return
}
let _offset = 0
// 上面的节点往上移
if (_index < index) {
_offset = -addHeight
} else if (_index > index) {
// 下面的节点往下移
_offset = addHeight
}
item.top += _offset
// 同步更新子节点的位置
if (item.children && item.children.length) {
this.updateChildren(item.children, 'top', _offset)
}
})
// 更新父节点的位置
this.updateBrothers(node.parent, addHeight)
}
}
// 绘制连线,连接该节点到其子节点
renderLine(node, lines, style, lineStyle) {
if (lineStyle === 'curve') {
this.renderLineCurve(node, lines, style)
} else if (lineStyle === 'direct') {
this.renderLineDirect(node, lines, style)
} else {
this.renderLineStraight(node, lines, style)
}
}
// 直线风格连线
renderLineStraight(node, lines, style) {
if (node.children.length <= 0) {
return []
}
let { left, top, width, height, expandBtnSize } = node
let marginX = this.getMarginX(node.layerIndex + 1)
let s1 = (marginX - expandBtnSize) * 0.6
let nodeUseLineStyle = this.mindMap.themeConfig.nodeUseLineStyle
node.children.forEach((item, index) => {
let x1 =
node.layerIndex === 0 ? left + width : left + width + expandBtnSize
let y1 = top + height / 2
let x2 = item.left
let y2 = item.top + item.height / 2
// 节点使用横线风格,需要额外渲染横线
let nodeUseLineStyleOffset = nodeUseLineStyle
? item.width
: 0
y1 = nodeUseLineStyle && !node.isRoot ? y1 + height / 2 : y1
y2 = nodeUseLineStyle ? y2 + item.height / 2 : y2
let path = `M ${x1},${y1} L ${x1 + s1},${y1} L ${x1 + s1},${y2} L ${
x2 + nodeUseLineStyleOffset
},${y2}`
lines[index].plot(path)
style && style(lines[index], item)
})
}
// 直连风格
renderLineDirect(node, lines, style) {
if (node.children.length <= 0) {
return []
}
let { left, top, width, height, expandBtnSize } = node
let nodeUseLineStyle = this.mindMap.themeConfig.nodeUseLineStyle
node.children.forEach((item, index) => {
let x1 =
node.layerIndex === 0 ? left + width / 2 : left + width + expandBtnSize
let y1 = top + height / 2
let x2 = item.left
let y2 = item.top + item.height / 2
y1 = nodeUseLineStyle && !node.isRoot ? y1 + height / 2 : y1
y2 = nodeUseLineStyle ? y2 + item.height / 2 : y2
// 节点使用横线风格,需要额外渲染横线
let nodeUseLineStylePath = nodeUseLineStyle
? ` L ${item.left + item.width},${y2}`
: ''
let path = `M ${x1},${y1} L ${x2},${y2}` + nodeUseLineStylePath
lines[index].plot(path)
style && style(lines[index], item)
})
}
// 曲线风格连线
renderLineCurve(node, lines, style) {
if (node.children.length <= 0) {
return []
}
let { left, top, width, height, expandBtnSize } = node
let nodeUseLineStyle = this.mindMap.themeConfig.nodeUseLineStyle
node.children.forEach((item, index) => {
let x1 =
node.layerIndex === 0 ? left + width / 2 : left + width + expandBtnSize
let y1 = top + height / 2
let x2 = item.left
let y2 = item.top + item.height / 2
let path = ''
y1 = nodeUseLineStyle && !node.isRoot ? y1 + height / 2 : y1
y2 = nodeUseLineStyle ? y2 + item.height / 2 : y2
// 节点使用横线风格,需要额外渲染横线
let nodeUseLineStylePath = nodeUseLineStyle
? ` L ${item.left + item.width},${y2}`
: ''
if (node.isRoot) {
path = this.quadraticCurvePath(x1, y1, x2, y2) + nodeUseLineStylePath
} else {
path = this.cubicBezierPath(x1, y1, x2, y2) + nodeUseLineStylePath
}
lines[index].plot(path)
style && style(lines[index], item)
})
}
// 渲染按钮
renderExpandBtn(node, btn) {
let { width, height } = node
let { translateX, translateY } = btn.transform()
// 节点使用横线风格,需要调整展开收起按钮位置
let nodeUseLineStyleOffset = this.mindMap.themeConfig.nodeUseLineStyle
? height / 2
: 0
btn.translate(
width - translateX,
height / 2 - translateY + nodeUseLineStyleOffset
)
}
// 创建概要节点
renderGeneralization(node, gLine, gNode) {
let {
top,
bottom,
right,
generalizationLineMargin,
generalizationNodeMargin
} = this.getNodeBoundaries(node, 'h')
let x1 = right + generalizationLineMargin
let y1 = top
let x2 = right + generalizationLineMargin
let y2 = bottom
let cx = x1 + 20
let cy = y1 + (y2 - y1) / 2
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
gLine.plot(path)
gNode.left = right + generalizationNodeMargin
gNode.top = top + (bottom - top - gNode.height) / 2
}
}
export default LogicalStructure

View File

@@ -208,11 +208,12 @@ class MindMap extends Base {
let { left, top, width, height, expandBtnSize } = node
let marginX = this.getMarginX(node.layerIndex + 1)
let s1 = (marginX - expandBtnSize) * 0.6
let nodeUseLineStyle = this.mindMap.themeConfig.nodeUseLineStyle
node.children.forEach((item, index) => {
let x1 = 0
let _s = 0
// 节点使用横线风格,需要额外渲染横线
let nodeUseLineStyleOffset = this.mindMap.themeConfig.nodeUseLineStyle
let nodeUseLineStyleOffset = nodeUseLineStyle
? item.width
: 0
if (item.dir === 'left') {
@@ -226,6 +227,8 @@ class MindMap extends Base {
let y1 = top + height / 2
let x2 = item.dir === 'left' ? item.left + item.width : item.left
let y2 = item.top + item.height / 2
y1 = nodeUseLineStyle && !node.isRoot ? y1 + height / 2 : y1
y2 = nodeUseLineStyle ? y2 + item.height / 2 : y2
let path = `M ${x1},${y1} L ${x1 + _s},${y1} L ${x1 + _s},${y2} L ${
x2 + nodeUseLineStyleOffset
},${y2}`
@@ -241,6 +244,7 @@ class MindMap extends Base {
return []
}
let { left, top, width, height, expandBtnSize } = node
let nodeUseLineStyle = this.mindMap.themeConfig.nodeUseLineStyle
node.children.forEach((item, index) => {
let x1 =
node.layerIndex === 0
@@ -251,9 +255,11 @@ class MindMap extends Base {
let y1 = top + height / 2
let x2 = item.dir === 'left' ? item.left + item.width : item.left
let y2 = item.top + item.height / 2
y1 = nodeUseLineStyle && !node.isRoot ? y1 + height / 2 : y1
y2 = nodeUseLineStyle ? y2 + item.height / 2 : y2
// 节点使用横线风格,需要额外渲染横线
let nodeUseLineStylePath = ''
if (this.mindMap.themeConfig.nodeUseLineStyle) {
if (nodeUseLineStyle) {
if (item.dir === 'left') {
nodeUseLineStylePath = ` L ${item.left},${y2}`
} else {
@@ -273,6 +279,7 @@ class MindMap extends Base {
return []
}
let { left, top, width, height, expandBtnSize } = node
let nodeUseLineStyle = this.mindMap.themeConfig.nodeUseLineStyle
node.children.forEach((item, index) => {
let x1 =
node.layerIndex === 0
@@ -284,6 +291,8 @@ class MindMap extends Base {
let x2 = item.dir === 'left' ? item.left + item.width : item.left
let y2 = item.top + item.height / 2
let path = ''
y1 = nodeUseLineStyle && !node.isRoot ? y1 + height / 2 : y1
y2 = nodeUseLineStyle ? y2 + item.height / 2 : y2
// 节点使用横线风格,需要额外渲染横线
let nodeUseLineStylePath = ''
if (this.mindMap.themeConfig.nodeUseLineStyle) {

View File

@@ -235,4 +235,34 @@ export const camelCaseToHyphen = (str) => {
return str.replace(/([a-z])([A-Z])/g, (...args) => {
return args[1] + '-' + args[2].toLowerCase()
})
}
//计算节点的文本长宽
let measureTextContext = null
export const measureText = (text, { italic, bold, fontSize, fontFamily }) => {
const font = joinFontStr({
italic,
bold,
fontSize,
fontFamily
})
if (!measureTextContext) {
const canvas = document.createElement('canvas')
measureTextContext = canvas.getContext('2d')
}
measureTextContext.save()
measureTextContext.font = font
const {
width,
actualBoundingBoxAscent,
actualBoundingBoxDescent
} = measureTextContext.measureText(text)
measureTextContext.restore()
const height = actualBoundingBoxAscent + actualBoundingBoxDescent
return { width, height }
}
// 拼接font字符串
export const joinFontStr = ({ italic, bold, fontSize, fontFamily }) => {
return `${italic ? 'italic ' : ''} ${bold ? 'bold ' : ''} ${fontSize}px ${fontFamily} `
}

2
web/package-lock.json generated
View File

@@ -17906,6 +17906,7 @@
"integrity": "sha512-VCNRiAt2P/bLo09rYt3DLe6xXUMlhJwrvU18Ddd/lYJgC7s8+wvhgYs+MTx4OiAXdu58drGwSBO9SPx7C6J82Q==",
"dev": true,
"requires": {
"@babel/core": "^7.11.0",
"@babel/helper-compilation-targets": "^7.9.6",
"@babel/helper-module-imports": "^7.8.3",
"@babel/plugin-proposal-class-properties": "^7.8.3",
@@ -17918,6 +17919,7 @@
"@vue/babel-plugin-jsx": "^1.0.3",
"@vue/babel-preset-jsx": "^1.2.4",
"babel-plugin-dynamic-import-node": "^2.3.3",
"core-js": "^3.6.5",
"core-js-compat": "^3.6.5",
"semver": "^6.1.0"
}

View File

@@ -54,6 +54,60 @@
<div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe6f8;</span>
<div class="name">背景颜色</div>
<div class="code-name">&amp;#xe6f8;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe605;</span>
<div class="name">清除</div>
<div class="code-name">&amp;#xe605;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe6c6;</span>
<div class="name">case</div>
<div class="code-name">&amp;#xe6c6;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xeb99;</span>
<div class="name">形状-文字</div>
<div class="code-name">&amp;#xeb99;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xec83;</span>
<div class="name">字体加粗</div>
<div class="code-name">&amp;#xec83;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xec85;</span>
<div class="name">字体下划线</div>
<div class="code-name">&amp;#xec85;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xec86;</span>
<div class="name">字体斜体</div>
<div class="code-name">&amp;#xec86;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe612;</span>
<div class="name">删除线</div>
<div class="code-name">&amp;#xe612;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe854;</span>
<div class="name">字体颜色</div>
<div class="code-name">&amp;#xe854;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe64f;</span>
<div class="name">github</div>
@@ -348,9 +402,9 @@
<pre><code class="language-css"
>@font-face {
font-family: 'iconfont';
src: url('iconfont.woff2?t=1673600274529') format('woff2'),
url('iconfont.woff?t=1673600274529') format('woff'),
url('iconfont.ttf?t=1673600274529') format('truetype');
src: url('iconfont.woff2?t=1677478278322') format('woff2'),
url('iconfont.woff?t=1677478278322') format('woff'),
url('iconfont.ttf?t=1677478278322') format('truetype');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@@ -376,6 +430,87 @@
<div class="content font-class">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont iconbeijingyanse"></span>
<div class="name">
背景颜色
</div>
<div class="code-name">.iconbeijingyanse
</div>
</li>
<li class="dib">
<span class="icon iconfont iconqingchu"></span>
<div class="name">
清除
</div>
<div class="code-name">.iconqingchu
</div>
</li>
<li class="dib">
<span class="icon iconfont iconcase"></span>
<div class="name">
case
</div>
<div class="code-name">.iconcase
</div>
</li>
<li class="dib">
<span class="icon iconfont iconxingzhuang-wenzi"></span>
<div class="name">
形状-文字
</div>
<div class="code-name">.iconxingzhuang-wenzi
</div>
</li>
<li class="dib">
<span class="icon iconfont iconzitijiacu"></span>
<div class="name">
字体加粗
</div>
<div class="code-name">.iconzitijiacu
</div>
</li>
<li class="dib">
<span class="icon iconfont iconzitixiahuaxian"></span>
<div class="name">
字体下划线
</div>
<div class="code-name">.iconzitixiahuaxian
</div>
</li>
<li class="dib">
<span class="icon iconfont iconzitixieti"></span>
<div class="name">
字体斜体
</div>
<div class="code-name">.iconzitixieti
</div>
</li>
<li class="dib">
<span class="icon iconfont iconshanchuxian"></span>
<div class="name">
删除线
</div>
<div class="code-name">.iconshanchuxian
</div>
</li>
<li class="dib">
<span class="icon iconfont iconzitiyanse"></span>
<div class="name">
字体颜色
</div>
<div class="code-name">.iconzitiyanse
</div>
</li>
<li class="dib">
<span class="icon iconfont icongithub"></span>
<div class="name">
@@ -817,6 +952,78 @@
<div class="content symbol">
<ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconbeijingyanse"></use>
</svg>
<div class="name">背景颜色</div>
<div class="code-name">#iconbeijingyanse</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconqingchu"></use>
</svg>
<div class="name">清除</div>
<div class="code-name">#iconqingchu</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconcase"></use>
</svg>
<div class="name">case</div>
<div class="code-name">#iconcase</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconxingzhuang-wenzi"></use>
</svg>
<div class="name">形状-文字</div>
<div class="code-name">#iconxingzhuang-wenzi</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconzitijiacu"></use>
</svg>
<div class="name">字体加粗</div>
<div class="code-name">#iconzitijiacu</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconzitixiahuaxian"></use>
</svg>
<div class="name">字体下划线</div>
<div class="code-name">#iconzitixiahuaxian</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconzitixieti"></use>
</svg>
<div class="name">字体斜体</div>
<div class="code-name">#iconzitixieti</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconshanchuxian"></use>
</svg>
<div class="name">删除线</div>
<div class="code-name">#iconshanchuxian</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconzitiyanse"></use>
</svg>
<div class="name">字体颜色</div>
<div class="code-name">#iconzitiyanse</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icongithub"></use>

View File

@@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 2479351 */
src: url('iconfont.woff2?t=1673600274529') format('woff2'),
url('iconfont.woff?t=1673600274529') format('woff'),
url('iconfont.ttf?t=1673600274529') format('truetype');
src: url('iconfont.woff2?t=1677478278322') format('woff2'),
url('iconfont.woff?t=1677478278322') format('woff'),
url('iconfont.ttf?t=1677478278322') format('truetype');
}
.iconfont {
@@ -13,6 +13,42 @@
-moz-osx-font-smoothing: grayscale;
}
.iconbeijingyanse:before {
content: "\e6f8";
}
.iconqingchu:before {
content: "\e605";
}
.iconcase:before {
content: "\e6c6";
}
.iconxingzhuang-wenzi:before {
content: "\eb99";
}
.iconzitijiacu:before {
content: "\ec83";
}
.iconzitixiahuaxian:before {
content: "\ec85";
}
.iconzitixieti:before {
content: "\ec86";
}
.iconshanchuxian:before {
content: "\e612";
}
.iconzitiyanse:before {
content: "\e854";
}
.icongithub:before {
content: "\e64f";
}

File diff suppressed because one or more lines are too long

View File

@@ -5,6 +5,69 @@
"css_prefix_text": "icon",
"description": "思维导图",
"glyphs": [
{
"icon_id": "1790495",
"name": "背景颜色",
"font_class": "beijingyanse",
"unicode": "e6f8",
"unicode_decimal": 59128
},
{
"icon_id": "11321310",
"name": "清除",
"font_class": "qingchu",
"unicode": "e605",
"unicode_decimal": 58885
},
{
"icon_id": "586787",
"name": "case",
"font_class": "case",
"unicode": "e6c6",
"unicode_decimal": 59078
},
{
"icon_id": "4354254",
"name": "形状-文字",
"font_class": "xingzhuang-wenzi",
"unicode": "eb99",
"unicode_decimal": 60313
},
{
"icon_id": "6337466",
"name": "字体加粗",
"font_class": "zitijiacu",
"unicode": "ec83",
"unicode_decimal": 60547
},
{
"icon_id": "6337470",
"name": "字体下划线",
"font_class": "zitixiahuaxian",
"unicode": "ec85",
"unicode_decimal": 60549
},
{
"icon_id": "6337471",
"name": "字体斜体",
"font_class": "zitixieti",
"unicode": "ec86",
"unicode_decimal": 60550
},
{
"icon_id": "11975179",
"name": "删除线",
"font_class": "shanchuxian",
"unicode": "e612",
"unicode_decimal": 58898
},
{
"icon_id": "34198316",
"name": "字体颜色",
"font_class": "zitiyanse",
"unicode": "e854",
"unicode_decimal": 59476
},
{
"icon_id": "8760187",
"name": "github",

View File

@@ -34,7 +34,8 @@ export default {
watermarkTextSpacing: 'Text spacing',
watermarkAngle: 'Angle',
watermarkTextOpacity: 'Text opacity',
watermarkTextFontSize: 'Font size'
watermarkTextFontSize: 'Font size',
isEnableNodeRichText: 'Enable node rich text editing'
},
color: {
moreColor: 'More color'
@@ -79,7 +80,13 @@ export default {
imageFile: 'Image file',
svgFile: 'svg file',
pdfFile: 'pdf file',
tips: 'tips.smm and .json file can be import'
tips: 'tips: .smm and .json file can be import',
domToImage: 'Whether to convert rich text nodes in svg into pictures',
pngTips: 'tips: Exporting pictures in rich text mode is time-consuming. It is recommended to export to svg format',
svgTips: 'tips: Exporting pictures in rich text mode is time-consuming',
transformingDomToImages: 'Converting nodes: ',
notifyTitle: 'Info',
notifyMessage: 'If the download is not triggered, check whether it is blocked by the browser'
},
fullscreen: {
fullscreenShow: 'Full screen show',
@@ -178,5 +185,9 @@ export default {
import: 'Import',
export: 'Export',
shortcutKey: 'Shortcut key'
},
edit: {
newFeatureNoticeTitle: 'New feature reminder',
newFeatureNoticeMessage: 'This update supports node rich text editing, But there are some defects, The most important impact is that the time to export the image is proportional to the number of nodes, Therefore, if you are more dependent on export requirements, you can use【Base style】-【Other config】-【Enable node rich text editing】Set to turn off rich text editing mode.'
}
}

View File

@@ -34,7 +34,8 @@ export default {
watermarkTextSpacing: '水印文字间距',
watermarkAngle: '旋转角度',
watermarkTextOpacity: '文字透明度',
watermarkTextFontSize: '文字字号'
watermarkTextFontSize: '文字字号',
isEnableNodeRichText: '是否开启节点富文本编辑'
},
color: {
moreColor: '更多颜色'
@@ -79,7 +80,13 @@ export default {
imageFile: '图片文件',
svgFile: 'svg文件',
pdfFile: 'pdf文件',
tips: 'tips.smm和.json文件可用于导入'
tips: 'tips.smm和.json文件可用于导入',
domToImage: '是否将svg中富文本节点转换成图片',
pngTips: 'tips富文本模式导出图片非常耗时建议导出为svg格式',
svgTips: 'tips富文本模式导出图片非常耗时',
transformingDomToImages: '正在转换节点:',
notifyTitle: '消息',
notifyMessage: '如果没有触发下载,请检查是否被浏览器拦截了'
},
fullscreen: {
fullscreenShow: '全屏查看',
@@ -178,5 +185,9 @@ export default {
import: '导入',
export: '导出',
shortcutKey: '快捷键'
},
edit: {
newFeatureNoticeTitle: '新特性提醒',
newFeatureNoticeMessage: '本次更新支持了节点富文本编辑,但是存在一定缺陷,最主要的影响是导出为图片的时间和节点数量成正比,所以对导出需求比较依赖的话可以通过【基础样式】-【其他配置】-【是否开启节点富文本编辑】设置关掉富文本编辑模式。'
}
}

View File

@@ -19,6 +19,7 @@ let APIList = [
'keyCommand',
'command',
'batchExecution',
'richText',
'select',
'drag',
'keyboardNavigation',

View File

@@ -52,7 +52,6 @@ export default {
this.onScroll()
},
lang(newVal, oldVal) {
console.log(newVal, oldVal)
if (!oldVal) {
return
}

View File

@@ -1,5 +1,29 @@
# Changelog
## 0.4.1
New: 1.Add and throw node mouseenter and mouseleave events; 2.Node rich text supports setting background color; 3.Node rich text supports clear style.
Fix: 1.Mac system touchpad scaling is the opposite problem; 2.When the device window.devicePixelRatio is not 1, the size of the rich text node in the exported image will become larger when there are rich text nodes.
## 0.4.0
New: The node supports rich text editing.
## 0.3.4
New: Automatic line wrapping function is added to node text.
Fix: 1.Fix the problem of deletion exceptions if there are root nodes in the batch deleted nodes. 2.Fix the problem that high node height will overlap with other nodes in the case of bottom edge style.
## 0.3.3
Fix: The root node text cannot wrap.
## 0.3.2
Fix: 1.Fix the problem that the node style is not updated when the secondary node is dragged to other nodes or other nodes are dragged to the secondary node; 2.Fix the problem that when the actual content of the mind map is larger than the screen width and height, the excess part is not watermarked when exporting.
## 0.3.1
Fix: 1.The problem that deleting the background image does not take effect; 2.The problem that the connector runs above the root node when the node is dragged to the root node.

View File

@@ -1,6 +1,18 @@
<template>
<div>
<h1>Changelog</h1>
<h2>0.4.1</h2>
<p>New: 1.Add and throw node mouseenter and mouseleave events; 2.Node rich text supports setting background color; 3.Node rich text supports clear style.</p>
<p>Fix: 1.Mac system touchpad scaling is the opposite problem; 2.When the device window.devicePixelRatio is not 1, the size of the rich text node in the exported image will become larger when there are rich text nodes.</p>
<h2>0.4.0</h2>
<p>New: The node supports rich text editing.</p>
<h2>0.3.4</h2>
<p>New: Automatic line wrapping function is added to node text.</p>
<p>Fix: 1.Fix the problem of deletion exceptions if there are root nodes in the batch deleted nodes. 2.Fix the problem that high node height will overlap with other nodes in the case of bottom edge style.</p>
<h2>0.3.3</h2>
<p>Fix: The root node text cannot wrap.</p>
<h2>0.3.2</h2>
<p>Fix: 1.Fix the problem that the node style is not updated when the secondary node is dragged to other nodes or other nodes are dragged to the secondary node; 2.Fix the problem that when the actual content of the mind map is larger than the screen width and height, the excess part is not watermarked when exporting.</p>
<h2>0.3.1</h2>
<p>Fix: 1.The problem that deleting the background image does not take effect; 2.The problem that the connector runs above the root node when the node is dragged to the root node.</p>
<p>New: Add position and size settings for background image display. This setting is also supported for exported pictures.</p>

View File

@@ -40,6 +40,7 @@ const mindMap = new MindMap({
| readonlyv0.1.7+ | Boolean | false | Whether it is read-only mode | |
| enableFreeDragv0.2.4+ | Boolean | false | Enable node free drag | |
| watermarkConfigv0.2.4+ | Object | | Watermark config, Please refer to the table 【Watermark config】 below for detailed configuration | |
| textAutoWrapWidthv0.3.4+ | Number | 500 | Each line of text in the node will wrap automatically when it reaches the width | |
### Watermark config
@@ -81,14 +82,23 @@ mindMap.setTheme('Theme name')
For all configurations of theme, please refer to [Default Topic](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/themes/default.js). The `defineTheme`method will merge the configuration you passed in with the default configuration. Most of the themes do not need custom many parts. For a typical customized theme configuration, please refer to [blueSky](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/themes/blueSky.js).
### usePlugin(plugin)
### usePlugin(plugin, opt = {})
> v0.3.0+
- `opt`v0.4.0+Plugin options. If a plugin supports custom options, it can be passed in through this parameter.
If you need to use some non-core functions, such as mini map, watermark, etc, you can register plugin through this method. Can be called in chain.
Note: The plugin needs to be registered before instantiating `MindMap`.
### hasPlugin(plugin)
> v0.4.0+
Get whether a plugin is registered, The index of the plugin in the registered plugin list is returned, If it is `-1`, it means that the plugin is not registered.
## Static props
### pluginList
@@ -119,12 +129,16 @@ Get the `svg` data and return an object. The detailed structure is as follows:
}
```
### render()
### render(callback)
- `callback`: `v0.3.2+`, `Function`, Called when the re-rendering is complete
Triggers a full rendering, which will reuse nodes for better performance. If
only the node positions have changed, this method can be called to `reRender`.
### reRender()
### reRender(callback)
- `callback`: `v0.3.2+`, `Function`, Called when the re-rendering is complete
Performs a full re-render, clearing the canvas and creating new nodes. This has
poor performance and should be used sparingly.
@@ -164,6 +178,8 @@ Listen to an event. Event list:
| node_mouseup | Node mouseup event | this (node instance), e (event object) |
| node_dblclick | Node double-click event | this (node instance), e (event object) |
| node_contextmenu | Node right-click menu event | e (event object), this (node instance) |
| node_mouseenterv0.4.1+ | Node mouseenter event | this (node instance), e (event object) |
| node_mouseleavev0.4.1+ | Node mouseleave event | this (node instance), e (event object) |
| before_node_active | Event before node activation | this (node instance), activeNodeList (current list of active nodes) |
| node_active | Node activation event | this (node instance), activeNodeList (current list of active nodes) |
| expand_btn_click | Node expand or collapse event | this (node instance) |
@@ -172,6 +188,8 @@ Listen to an event. Event list:
| scale | Zoom event | scale (zoom ratio) |
| node_img_dblclickv0.2.15+ | Node image double-click event | this (node instance), e (event object) |
| node_tree_render_endv0.2.16+ | Node tree render end event | |
| rich_text_selection_changev0.4.0+ | Available when the `RichText` plugin is registered. Triggered when the text selection area changes when the node is edited | hasRangeWhether there is a selection、rectInfoSize and location information of the selected area、formatInfoText formatting information of the selected area |
| transforming-dom-to-imagesv0.4.0+ | Available when the `RichText` plugin is registered. When there is a `DOM` node in `svg`, the `DOM` node will be converted to an image when exporting to an image. This event will be triggered during the conversion process. You can use this event to prompt the user about the node to which you are currently converting | indexIndex of the node currently converted to、lenTotal number of nodes to be converted |
### emit(event, ...args)
@@ -319,4 +337,16 @@ map).
> v0.1.5+
Convert the coordinates of the browser's visible window to coordinates relative
to the canvas.
to the canvas.
### addPlugin(plugin, opt)
> v0.4.0+
Register plugin, Use `MindMap.usePlugin` to register plugin only before instantiation, The registered plugin will not take effect after instantiation, So if you want to register the plugin after instantiation, you can use the `addPlugin` method of the instance.
### removePlugin(plugin)
> v0.4.0+
Remove registered plugin, Plugins registered through the `usePlugin` or `addPlugin` methods can be removed.

View File

@@ -140,6 +140,13 @@
<td>Watermark config, Please refer to the table Watermark config below for detailed configuration</td>
<td></td>
</tr>
<tr>
<td>textAutoWrapWidthv0.3.4+</td>
<td>Number</td>
<td>500</td>
<td>Each line of text in the node will wrap automatically when it reaches the width</td>
<td></td>
</tr>
</tbody>
</table>
<h3>Watermark config</h3>
@@ -207,12 +214,20 @@ MindMap.defineTheme(<span class="hljs-string">&#x27;Theme name&#x27;</span>, {})
mindMap.setTheme(<span class="hljs-string">&#x27;Theme name&#x27;</span>)
</code></pre>
<p>For all configurations of theme, please refer to <a href="https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/themes/default.js">Default Topic</a>. The <code>defineTheme</code>method will merge the configuration you passed in with the default configuration. Most of the themes do not need custom many parts. For a typical customized theme configuration, please refer to <a href="https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/themes/blueSky.js">blueSky</a>.</p>
<h3>usePlugin(plugin)</h3>
<h3>usePlugin(plugin, opt = {})</h3>
<blockquote>
<p>v0.3.0+</p>
</blockquote>
<ul>
<li><code>opt</code>v0.4.0+Plugin options. If a plugin supports custom options, it can be passed in through this parameter.</li>
</ul>
<p>If you need to use some non-core functions, such as mini map, watermark, etc, you can register plugin through this method. Can be called in chain.</p>
<p>Note: The plugin needs to be registered before instantiating <code>MindMap</code>.</p>
<h3>hasPlugin(plugin)</h3>
<blockquote>
<p>v0.4.0+</p>
</blockquote>
<p>Get whether a plugin is registered, The index of the plugin in the registered plugin list is returned, If it is <code>-1</code>, it means that the plugin is not registered.</p>
<h2>Static props</h2>
<h3>pluginList</h3>
<blockquote>
@@ -235,10 +250,16 @@ mindMap.setTheme(<span class="hljs-string">&#x27;Theme name&#x27;</span>)
scaleY, <span class="hljs-comment">// Number, vertical zoom value of mind map graphics</span>
}
</code></pre>
<h3>render()</h3>
<h3>render(callback)</h3>
<ul>
<li><code>callback</code>: <code>v0.3.2+</code>, <code>Function</code>, Called when the re-rendering is complete</li>
</ul>
<p>Triggers a full rendering, which will reuse nodes for better performance. If
only the node positions have changed, this method can be called to <code>reRender</code>.</p>
<h3>reRender()</h3>
<h3>reRender(callback)</h3>
<ul>
<li><code>callback</code>: <code>v0.3.2+</code>, <code>Function</code>, Called when the re-rendering is complete</li>
</ul>
<p>Performs a full re-render, clearing the canvas and creating new nodes. This has
poor performance and should be used sparingly.</p>
<h3>resize()</h3>
@@ -346,6 +367,16 @@ poor performance and should be used sparingly.</p>
<td>e (event object), this (node instance)</td>
</tr>
<tr>
<td>node_mouseenterv0.4.1+</td>
<td>Node mouseenter event</td>
<td>this (node instance), e (event object)</td>
</tr>
<tr>
<td>node_mouseleavev0.4.1+</td>
<td>Node mouseleave event</td>
<td>this (node instance), e (event object)</td>
</tr>
<tr>
<td>before_node_active</td>
<td>Event before node activation</td>
<td>this (node instance), activeNodeList (current list of active nodes)</td>
@@ -385,6 +416,16 @@ poor performance and should be used sparingly.</p>
<td>Node tree render end event</td>
<td></td>
</tr>
<tr>
<td>rich_text_selection_changev0.4.0+</td>
<td>Available when the <code>RichText</code> plugin is registered. Triggered when the text selection area changes when the node is edited</td>
<td>hasRangeWhether there is a selectionrectInfoSize and location information of the selected areaformatInfoText formatting information of the selected area</td>
</tr>
<tr>
<td>transforming-dom-to-imagesv0.4.0+</td>
<td>Available when the <code>RichText</code> plugin is registered. When there is a <code>DOM</code> node in <code>svg</code>, the <code>DOM</code> node will be converted to an image when exporting to an image. This event will be triggered during the conversion process. You can use this event to prompt the user about the node to which you are currently converting</td>
<td>indexIndex of the node currently converted tolenTotal number of nodes to be converted</td>
</tr>
</tbody>
</table>
<h3>emit(event, ...args)</h3>
@@ -628,6 +669,16 @@ map).</p>
</blockquote>
<p>Convert the coordinates of the browser's visible window to coordinates relative
to the canvas.</p>
<h3>addPlugin(plugin, opt)</h3>
<blockquote>
<p>v0.4.0+</p>
</blockquote>
<p>Register plugin, Use <code>MindMap.usePlugin</code> to register plugin only before instantiation, The registered plugin will not take effect after instantiation, So if you want to register the plugin after instantiation, you can use the <code>addPlugin</code> method of the instance.</p>
<h3>removePlugin(plugin)</h3>
<blockquote>
<p>v0.4.0+</p>
</blockquote>
<p>Remove registered plugin, Plugins registered through the <code>usePlugin</code> or <code>addPlugin</code> methods can be removed.</p>
</div>
</template>

View File

@@ -20,19 +20,40 @@ After registration and instantiation of `MindMap`, the instance can be obtained
Exports as `png`, an async method that returns image data, `data:url` data which
can be downloaded or displayed.
### svg()
### svg(name, domToImage = false, plusCssText)
- `name``svg` title
- `domToImage`v0.4.0+, When node rich text editing is enabled, you can use this parameter to specify whether to convert the `dom` node in the `svg` into a picture
- `plusCssText`v0.4.0+, When node rich text editing is enabled and `domToImage` passes `false`, additional `css` styles can be added. If there is a `dom` node in `svg`, you can set some styles for the node through this parameter, such as:
```js
svg(
'',
false,
`* {
margin: 0;
padding: 0;
box-sizing: border-box;
}`
)
```
Exports as `svg`, an async method that returns `svg` data, `data:url` data which
can be downloaded or displayed.
### getSvgData()
### getSvgData(domToImage)
- `domToImage`v0.4.0+, If node rich text is enabled, you can use this parameter to specify whether to convert the `DOM` node embedded in `svg` into a picture.
Gets `svg` data, an async method that returns an object:
```js
{
node; // svg object
str; // svg string
str; // svg string, if rich text editing is enabled and domToImage is set to true, the dom node in the svg character returned by this value will be converted into the form of an image
nodeWithDomToImg// v0.4.0+The svg object after the DOM node is converted to an image has a value only when rich text editing is enabled and domToImage is set to true, otherwise null
}
```

View File

@@ -13,14 +13,39 @@ MindMap.usePlugin(Export)
<h3>png()</h3>
<p>Exports as <code>png</code>, an async method that returns image data, <code>data:url</code> data which
can be downloaded or displayed.</p>
<h3>svg()</h3>
<h3>svg(name, domToImage = false, plusCssText)</h3>
<ul>
<li>
<p><code>name</code><code>svg</code> title</p>
</li>
<li>
<p><code>domToImage</code>v0.4.0+, When node rich text editing is enabled, you can use this parameter to specify whether to convert the <code>dom</code> node in the <code>svg</code> into a picture</p>
</li>
<li>
<p><code>plusCssText</code>v0.4.0+, When node rich text editing is enabled and <code>domToImage</code> passes <code>false</code>, additional <code>css</code> styles can be added. If there is a <code>dom</code> node in <code>svg</code>, you can set some styles for the node through this parameter, such as:</p>
</li>
</ul>
<pre class="hljs"><code>svg(
<span class="hljs-string">&#x27;&#x27;</span>,
<span class="hljs-literal">false</span>,
<span class="hljs-string">`* {
margin: 0;
padding: 0;
box-sizing: border-box;
}`</span>
)
</code></pre>
<p>Exports as <code>svg</code>, an async method that returns <code>svg</code> data, <code>data:url</code> data which
can be downloaded or displayed.</p>
<h3>getSvgData()</h3>
<h3>getSvgData(domToImage)</h3>
<ul>
<li><code>domToImage</code>v0.4.0+, If node rich text is enabled, you can use this parameter to specify whether to convert the <code>DOM</code> node embedded in <code>svg</code> into a picture.</li>
</ul>
<p>Gets <code>svg</code> data, an async method that returns an object:</p>
<pre class="hljs"><code>{
node; <span class="hljs-comment">// svg object</span>
str; <span class="hljs-comment">// svg string</span>
str; <span class="hljs-comment">// svg string, if rich text editing is enabled and domToImage is set to true, the dom node in the svg character returned by this value will be converted into the form of an image</span>
nodeWithDomToImg<span class="hljs-comment">// v0.4.0+The svg object after the DOM node is converted to an image has a value only when rich text editing is enabled and domToImage is set to true, otherwise null</span>
}
</code></pre>
<h3>pdf(name)</h3>

View File

@@ -60,6 +60,8 @@ Documentation, etc.
[Only a hundred lines of code are needed to add local file operation capability to your Web page. Are you sure not to try?](https://juejin.cn/post/7157681502506090510)
[When you press the direction key, how does the TV find the next focus](https://juejin.cn/post/7199666255883927612)
## Special Note
This project is rough and has not been thoroughly tested, its features are not

View File

@@ -48,6 +48,7 @@ full screen, support mini map</li>
<h2>Related Articles</h2>
<p><a href="https://juejin.cn/post/6987711560521089061">Technical Analysis of Web Mind Map Implementation (chi)</a></p>
<p><a href="https://juejin.cn/post/7157681502506090510">Only a hundred lines of code are needed to add local file operation capability to your Web page. Are you sure not to try?</a></p>
<p><a href="https://juejin.cn/post/7199666255883927612">When you press the direction key, how does the TV find the next focus</a></p>
<h2>Special Note</h2>
<p>This project is rough and has not been thoroughly tested, its features are not
yet fully developed, and there are some performance issues, especially when the number of nodes is large. It is only for

View File

@@ -0,0 +1,153 @@
# RichText plugin
> v0.4.0+
> Note: This is a testing nature and imperfect function
This plugin provides the ability to edit rich text of nodes, and takes effect after registration.
By default, node editing can only uniformly apply styles to all text in the node. This plugin can support rich text editing effects. Currently, it supports bold, italic, underline, strikethrough, font, font size, color, and backgroundColor. Underline and line height are not supported.
The principle of this plugin is to use [Quill](https://github.com/quilljs/quill) editor implements rich text editing, and then uses the edited `DOM` node directly as the text data of the node, and embeds the `DOM` node through the `svg` `foreignObject` tag during rendering.
This also caused a problem, that is, the function of exporting as a picture was affected, The original principle of exporting `svg` as an image is very simple, Get the `svg` string, and then create the `blob` data of the `type=image/svg+xml` type. Then use the `URL.createObjectURL` method to generate the `data:url` data. Then create a `Image` tag, use the `data:url` as the `src` of the image, and finally draw the image on the `canvas` object for export, However, after testing, when the `DOM` node is embedded in the `svg`, this method of export will cause errors, and after trying many ways, the perfect export effect cannot be achieved, The current method is to traverse the `foreignObject` node in `svg`, using [html2canvas](https://github.com/niklasvh/html2canvas) Convert the `DOM` node in the `foreignObject` node into an image and then replace the `foreignObject` node. This method can work, but it is very time-consuming. Because the `html2canvas` conversion takes a long time, it takes about 2 seconds to convert a node. This leads to the more nodes, the slower the conversion time. Therefore, it is recommended not to use this plugin if you cannot tolerate the long time of export.
If you have a better way, please leave a message.
## Register
```js
import MindMap from 'simple-mind-map'
import RichText from 'simple-mind-map/src/RichText.js'
MindMap.usePlugin(RichText, opt?)
```
After registration and instantiation of `MindMap`, the instance can be obtained through `mindMap.richText`.
### Register options
The `opt` option can pass the following parameters:
- `opt.fontFamilyList`
Replace the built-in font list during rich text editing. The built-in list is:
```js
[
'宋体, SimSun, Songti SC',
'微软雅黑, Microsoft YaHei',
'楷体, 楷体_GB2312, SimKai, STKaiti',
'黑体, SimHei, Heiti SC',
'隶书, SimLi',
'andale mono',
'arial, helvetica, sans-serif',
'arial black, avant garde',
'comic sans ms',
'impact, chicago',
'times new roman',
'sans-serif',
'serif'
]
```
- `opt.fontSizeList`
Replace the built-in font size list during rich text editing. The built-in list is:
```js
[1, 2, 3, ...100]
```
## Method
### selectAll()
Select All. When the node is being edited, you can select all the text in the node through this method.
### formatText(config = {})
- `config`Object. The key is the style attribute and the value is the style value. The complete configuration is as follows:
```js
{
font: '字体',
size: '12px,' // font size
bold: true, // Bold or not, true/false
italic: true, // Italic or not, true/false
underline: true, // Show underline or not, true/false
strike: true, // Whether to display strikethrough, true/false
color: '#333' // color
}
```
Formats the currently selected text.
### formatRangeText(range, config = {})
- `range`The range object of `Quill`, has the following format:
```js
{
index,
length
}
```
- `config`Same as `formatText` method
Formats the text of the specified range.
### formatAllText(config = {})
- `config`Same as `formatText` method
Formats all text of the current edit node.
### removeFormat()
> v0.4.1+
Clears the style of the currently selected text.
### normalStyleToRichTextStyle(style)
Converts a normal node style object to a rich text style object. Because there are differences between node style attributes and rich text style attributes during non-rich text editing, a conversion operation is required. For example:
```js
{
fontFamily: 'xxx'
}
// After conversion
{
font: 'xxx'
}
```
### richTextStyleToNormalStyle(config)
Converts rich text style objects to normal node style objects. For example:
```js
{
size: '16px'
}
// After conversion
{
fontSize: 16
}
```
### handleSvgDomElements(svg)
- `svg`: `svg` node
Convert the `dom` element embedded in the `svg` into a picture and return a `Promise`.
### transformAllNodesToNormalNode()
Convert all nodes to non-rich text nodes.

View File

@@ -0,0 +1,134 @@
<template>
<div>
<h1>RichText plugin</h1>
<blockquote>
<p>v0.4.0+</p>
</blockquote>
<blockquote>
<p>Note: This is a testing nature and imperfect function</p>
</blockquote>
<p>This plugin provides the ability to edit rich text of nodes, and takes effect after registration.</p>
<p>By default, node editing can only uniformly apply styles to all text in the node. This plugin can support rich text editing effects. Currently, it supports bold, italic, underline, strikethrough, font, font size, color, and backgroundColor. Underline and line height are not supported.</p>
<p>The principle of this plugin is to use <a href="https://github.com/quilljs/quill">Quill</a> editor implements rich text editing, and then uses the edited <code>DOM</code> node directly as the text data of the node, and embeds the <code>DOM</code> node through the <code>svg</code> <code>foreignObject</code> tag during rendering.</p>
<p>This also caused a problem, that is, the function of exporting as a picture was affected, The original principle of exporting <code>svg</code> as an image is very simple, Get the <code>svg</code> string, and then create the <code>blob</code> data of the <code>type=image/svg+xml</code> type. Then use the <code>URL.createObjectURL</code> method to generate the <code>data:url</code> data. Then create a <code>Image</code> tag, use the <code>data:url</code> as the <code>src</code> of the image, and finally draw the image on the <code>canvas</code> object for export, However, after testing, when the <code>DOM</code> node is embedded in the <code>svg</code>, this method of export will cause errors, and after trying many ways, the perfect export effect cannot be achieved, The current method is to traverse the <code>foreignObject</code> node in <code>svg</code>, using <a href="https://github.com/niklasvh/html2canvas">html2canvas</a> Convert the <code>DOM</code> node in the <code>foreignObject</code> node into an image and then replace the <code>foreignObject</code> node. This method can work, but it is very time-consuming. Because the <code>html2canvas</code> conversion takes a long time, it takes about 2 seconds to convert a node. This leads to the more nodes, the slower the conversion time. Therefore, it is recommended not to use this plugin if you cannot tolerate the long time of export.</p>
<p>If you have a better way, please leave a message.</p>
<h2>Register</h2>
<pre class="hljs"><code><span class="hljs-keyword">import</span> MindMap <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map&#x27;</span>
<span class="hljs-keyword">import</span> RichText <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map/src/RichText.js&#x27;</span>
MindMap.usePlugin(RichText, opt?)
</code></pre>
<p>After registration and instantiation of <code>MindMap</code>, the instance can be obtained through <code>mindMap.richText</code>.</p>
<h3>Register options</h3>
<p>The <code>opt</code> option can pass the following parameters:</p>
<ul>
<li><code>opt.fontFamilyList</code></li>
</ul>
<p>Replace the built-in font list during rich text editing. The built-in list is:</p>
<pre class="hljs"><code>[
<span class="hljs-string">&#x27;宋体, SimSun, Songti SC&#x27;</span>,
<span class="hljs-string">&#x27;微软雅黑, Microsoft YaHei&#x27;</span>,
<span class="hljs-string">&#x27;楷体, 楷体_GB2312, SimKai, STKaiti&#x27;</span>,
<span class="hljs-string">&#x27;黑体, SimHei, Heiti SC&#x27;</span>,
<span class="hljs-string">&#x27;隶书, SimLi&#x27;</span>,
<span class="hljs-string">&#x27;andale mono&#x27;</span>,
<span class="hljs-string">&#x27;arial, helvetica, sans-serif&#x27;</span>,
<span class="hljs-string">&#x27;arial black, avant garde&#x27;</span>,
<span class="hljs-string">&#x27;comic sans ms&#x27;</span>,
<span class="hljs-string">&#x27;impact, chicago&#x27;</span>,
<span class="hljs-string">&#x27;times new roman&#x27;</span>,
<span class="hljs-string">&#x27;sans-serif&#x27;</span>,
<span class="hljs-string">&#x27;serif&#x27;</span>
]
</code></pre>
<ul>
<li><code>opt.fontSizeList</code></li>
</ul>
<p>Replace the built-in font size list during rich text editing. The built-in list is:</p>
<pre class="hljs"><code>[<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, ..<span class="hljs-number">.100</span>]
</code></pre>
<h2>Method</h2>
<h3>selectAll()</h3>
<p>Select All. When the node is being edited, you can select all the text in the node through this method.</p>
<h3>formatText(config = {})</h3>
<ul>
<li><code>config</code>Object. The key is the style attribute and the value is the style value. The complete configuration is as follows:</li>
</ul>
<pre class="hljs"><code>{
<span class="hljs-attr">font</span>: <span class="hljs-string">&#x27;字体&#x27;</span>,
<span class="hljs-attr">size</span>: <span class="hljs-string">&#x27;12px,&#x27;</span> <span class="hljs-comment">// font size</span>
<span class="hljs-attr">bold</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// Bold or not, true/false </span>
<span class="hljs-attr">italic</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// Italic or not, true/false </span>
<span class="hljs-attr">underline</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// Show underline or not, true/false </span>
<span class="hljs-attr">strike</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// Whether to display strikethrough, true/false </span>
<span class="hljs-attr">color</span>: <span class="hljs-string">&#x27;#333&#x27;</span> <span class="hljs-comment">// color</span>
}
</code></pre>
<p>Formats the currently selected text.</p>
<h3>formatRangeText(range, config = {})</h3>
<ul>
<li><code>range</code>The range object of <code>Quill</code>, has the following format:</li>
</ul>
<pre class="hljs"><code>{
index,
length
}
</code></pre>
<ul>
<li><code>config</code>Same as <code>formatText</code> method</li>
</ul>
<p>Formats the text of the specified range.</p>
<h3>formatAllText(config = {})</h3>
<ul>
<li><code>config</code>Same as <code>formatText</code> method</li>
</ul>
<p>Formats all text of the current edit node.</p>
<h3>removeFormat()</h3>
<blockquote>
<p>v0.4.1+</p>
</blockquote>
<p>Clears the style of the currently selected text.</p>
<h3>normalStyleToRichTextStyle(style)</h3>
<p>Converts a normal node style object to a rich text style object. Because there are differences between node style attributes and rich text style attributes during non-rich text editing, a conversion operation is required. For example:</p>
<pre class="hljs"><code>{
<span class="hljs-attr">fontFamily</span>: <span class="hljs-string">&#x27;xxx&#x27;</span>
}
<span class="hljs-comment">// After conversion</span>
{
<span class="hljs-attr">font</span>: <span class="hljs-string">&#x27;xxx&#x27;</span>
}
</code></pre>
<h3>richTextStyleToNormalStyle(config)</h3>
<p>Converts rich text style objects to normal node style objects. For example:</p>
<pre class="hljs"><code>{
<span class="hljs-attr">size</span>: <span class="hljs-string">&#x27;16px&#x27;</span>
}
<span class="hljs-comment">// After conversion</span>
{
<span class="hljs-attr">fontSize</span>: <span class="hljs-number">16</span>
}
</code></pre>
<h3>handleSvgDomElements(svg)</h3>
<ul>
<li><code>svg</code>: <code>svg</code> node</li>
</ul>
<p>Convert the <code>dom</code> element embedded in the <code>svg</code> into a picture and return a <code>Promise</code>.</p>
<h3>transformAllNodesToNormalNode()</h3>
<p>Convert all nodes to non-rich text nodes.</p>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>

View File

@@ -109,6 +109,22 @@ Angle to radian
CamelCase to hyphen
#### joinFontStr({ italic, bold, fontSize, fontFamily })
> v0.3.4+
Join the `font` attribute value of the `css` font
#### measureText(text, { italic, bold, fontSize, fontFamily })
> v0.3.4+
Measure the width and height of the text, return value:
```js
{ width, height }
```
## Simulate CSS background in Canvas
Import:

View File

@@ -62,6 +62,18 @@ and copying the <code>data</code> of the data object, example:</p>
<p>v0.2.24+</p>
</blockquote>
<p>CamelCase to hyphen</p>
<h4>joinFontStr({ italic, bold, fontSize, fontFamily })</h4>
<blockquote>
<p>v0.3.4+</p>
</blockquote>
<p>Join the <code>font</code> attribute value of the <code>css</code> font</p>
<h4>measureText(text, { italic, bold, fontSize, fontFamily })</h4>
<blockquote>
<p>v0.3.4+</p>
</blockquote>
<p>Measure the width and height of the text, return value:</p>
<pre class="hljs"><code>{ width, height }
</code></pre>
<h2>Simulate CSS background in Canvas</h2>
<p>Import:</p>
<pre class="hljs"><code><span class="hljs-keyword">import</span> drawBackgroundImageToCanvas <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map/src/utils/simulateCSSBackgroundInCanvas&#x27;</span>

View File

@@ -41,4 +41,10 @@ mindMap.watermark.updateWatermark({
fontSize: 20
}
})
```
```
### hasWatermark()
> v0.3.2+
Gets whether the watermark exists.

View File

@@ -31,6 +31,11 @@ MindMap.usePlugin(Watermark)
}
})
</code></pre>
<h3>hasWatermark()</h3>
<blockquote>
<p>v0.3.2+</p>
</blockquote>
<p>Gets whether the watermark exists.</p>
</div>
</template>

View File

@@ -1,3 +1,3 @@
export default [{"lang":"zh","children":[{"path":"batchExecution","title":"BatchExecution实例"},{"path":"changelog","title":"Changelog"},{"path":"command","title":"Command实例"},{"path":"constructor","title":"构造函数"},{"path":"doExport","title":"Export 插件"},{"path":"drag","title":"Drag插件"},{"path":"introduction","title":"简介"},{"path":"keyboardNavigation","title":"KeyboardNavigation插件"},{"path":"keyCommand","title":"KeyCommand实例"},{"path":"miniMap","title":"MiniMap插件"},{"path":"node","title":"Node实例"},{"path":"render","title":"Render实例"},{"path":"select","title":"Select 插件 "},{"path":"start","title":"开始"},{"path":"translate","title":"参与翻译"},{"path":"utils","title":"内置工具方法"},{"path":"view","title":"View实例"},{"path":"watermark","title":"Watermark插件"},{"path":"xmind","title":"XMind解析"}]},{"lang":"en","children":[{"path":"batchExecution","title":"batchExecution instance"},{"path":"changelog","title":"Changelog"},{"path":"command","title":"command instance"},{"path":"constructor","title":"Constructor"},{"path":"doExport","title":"Export plugin"},{"path":"drag","title":"Drag plugin"},{"path":"introduction","title":"Introduction"},{"path":"keyboardNavigation","title":"KeyboardNavigation plugin"},{"path":"keyCommand","title":"KeyCommand instance"},{"path":"miniMap","title":"MiniMap plugin"},{"path":"node","title":"Node instance"},{"path":"render","title":"Render instance"},{"path":"select","title":"Select plugin"},{"path":"start","title":"Start"},{"path":"translate","title":"Participate in translation"},{"path":"utils","title":"Utility Methods"},{"path":"view","title":"View instance"},{"path":"watermark","title":"Watermark plugin"},{"path":"xmind","title":"XMind parse"}]}]
export default [{"lang":"zh","children":[{"path":"batchExecution","title":"BatchExecution实例"},{"path":"changelog","title":"Changelog"},{"path":"command","title":"Command实例"},{"path":"constructor","title":"构造函数"},{"path":"doExport","title":"Export 插件"},{"path":"drag","title":"Drag插件"},{"path":"introduction","title":"简介"},{"path":"keyboardNavigation","title":"KeyboardNavigation插件"},{"path":"keyCommand","title":"KeyCommand实例"},{"path":"miniMap","title":"MiniMap插件"},{"path":"node","title":"Node实例"},{"path":"render","title":"Render实例"},{"path":"richText","title":"RichText插件"},{"path":"select","title":"Select 插件 "},{"path":"start","title":"开始"},{"path":"translate","title":"参与翻译"},{"path":"utils","title":"内置工具方法"},{"path":"view","title":"View实例"},{"path":"watermark","title":"Watermark插件"},{"path":"xmind","title":"XMind解析"}]},{"lang":"en","children":[{"path":"batchExecution","title":"batchExecution instance"},{"path":"changelog","title":"Changelog"},{"path":"command","title":"command instance"},{"path":"constructor","title":"Constructor"},{"path":"doExport","title":"Export plugin"},{"path":"drag","title":"Drag plugin"},{"path":"introduction","title":"Introduction"},{"path":"keyboardNavigation","title":"KeyboardNavigation plugin"},{"path":"keyCommand","title":"KeyCommand instance"},{"path":"miniMap","title":"MiniMap plugin"},{"path":"node","title":"Node instance"},{"path":"render","title":"Render instance"},{"path":"richText","title":"RichText plugin"},{"path":"select","title":"Select plugin"},{"path":"start","title":"Start"},{"path":"translate","title":"Participate in translation"},{"path":"utils","title":"Utility Methods"},{"path":"view","title":"View instance"},{"path":"watermark","title":"Watermark plugin"},{"path":"xmind","title":"XMind parse"}]}]

View File

@@ -1,5 +1,29 @@
# Changelog
## 0.4.1
新增1.新增抛出节点鼠标移入和移除事件2.节点富文本支持设置背景颜色3.节点富文本支持清除样式。
修复1.Mac系统触控板缩放相反的问题2.设备window.devicePixelRatio不为1时当存在富文本节点时导出的图片中富文本节点尺寸会变大的问题。
## 0.4.0
新增:节点支持富文本编辑。
## 0.3.4
新增:节点文本增加自动换行功能。
修复1.修复批量删除的节点中如果存在根节点会出现删除异常的问题。2.修复底边风格的情况下,节点高度过高会和其他节点重叠的问题。
## 0.3.3
修复:根节点文字无法换行的问题。
## 0.3.2
修复1.修复二级节点拖拽到其他节点或其他节点拖拽到二级节点时节点样式没有更新的问题2.修复当思维导图实际内容大于屏幕宽高时,导出的时候超出的部分没有绘制水印的问题。
## 0.3.1
修复1.删除背景图片不生效的问题2.节点拖拽到根节点时连接线跑到根节点上方的问题。

View File

@@ -1,6 +1,18 @@
<template>
<div>
<h1>Changelog</h1>
<h2>0.4.1</h2>
<p>新增1.新增抛出节点鼠标移入和移除事件2.节点富文本支持设置背景颜色3.节点富文本支持清除样式</p>
<p>修复1.Mac系统触控板缩放相反的问题2.设备window.devicePixelRatio不为1时当存在富文本节点时导出的图片中富文本节点尺寸会变大的问题</p>
<h2>0.4.0</h2>
<p>新增节点支持富文本编辑</p>
<h2>0.3.4</h2>
<p>新增节点文本增加自动换行功能</p>
<p>修复1.修复批量删除的节点中如果存在根节点会出现删除异常的问题2.修复底边风格的情况下节点高度过高会和其他节点重叠的问题</p>
<h2>0.3.3</h2>
<p>修复根节点文字无法换行的问题</p>
<h2>0.3.2</h2>
<p>修复1.修复二级节点拖拽到其他节点或其他节点拖拽到二级节点时节点样式没有更新的问题2.修复当思维导图实际内容大于屏幕宽高时导出的时候超出的部分没有绘制水印的问题</p>
<h2>0.3.1</h2>
<p>修复1.删除背景图片不生效的问题2.节点拖拽到根节点时连接线跑到根节点上方的问题</p>
<p>新增背景图片展示增加位置和大小设置导出的图片也同步支持该设置</p>

View File

@@ -40,6 +40,7 @@ const mindMap = new MindMap({
| readonlyv0.1.7+ | Boolean | false | 是否是只读模式 | |
| enableFreeDragv0.2.4+ | Boolean | false | 是否开启节点自由拖拽 | |
| watermarkConfigv0.2.4+ | Object | | 水印配置,详细配置请参考下方表格【水印配置】 | |
| textAutoWrapWidthv0.3.4+ | Number | 500 | 节点内每行文本达到该宽度后自动换行 | |
### 水印配置
@@ -83,14 +84,23 @@ mindMap.setTheme('主题名称')
主题的所有配置可以参考[默认主题](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/themes/default.js)。`defineTheme`方法会把你传入的配置和默认配置做合并。大部分主题其实需要自定义的部分不是很多,一个典型的自定义主题配置可以参考[blueSky](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/themes/blueSky.js)。
### usePlugin(plugin)
### usePlugin(plugin, opt = {})
> v0.3.0+
- `opt`v0.4.0+,插件参数。如果某个插件支持自定义选项的话可以通过这个参数传入。
注册插件,如果需要使用非核心的一些功能,比如小地图、水印等,可以通过该方法进行注册。可链式调用。
注意:插件需要在实例化`MindMap`前注册。
### hasPlugin(plugin)
> v0.4.0+
获取是否注册了某个插件,返回的是插件在注册插件列表里的索引,为`-1`则代表插件没有注册。
## 静态属性
### pluginList
@@ -119,11 +129,15 @@ mindMap.setTheme('主题名称')
}
```
### render()
### render(callback)
- `callback``v0.3.2+``Function`,当重新渲染完成时调用
触发整体渲染,会进行节点复用,性能较`reRender`会更好一点,如果只是节点位置变化了可以调用该方法进行渲染
### reRender()
### reRender(callback)
- `callback``v0.3.2+``Function`,当重新渲染完成时调用
整体重新渲染,会清空画布,节点也会重新创建,性能不好,慎重使用
@@ -161,6 +175,8 @@ mindMap.setTheme('主题名称')
| node_mouseup | 节点的鼠标松开事件 | this节点实例、e事件对象 |
| node_dblclick | 节点的双击事件 | this节点实例、e事件对象 |
| node_contextmenu | 节点的右键菜单事件 | e事件对象、this节点实例 |
| node_mouseenterv0.4.1+ | 节点的鼠标移入事件 | this节点实例、e事件对象 |
| node_mouseleavev0.4.1+ | 节点的鼠标移出事件 | this节点实例、e事件对象 |
| before_node_active | 节点激活前事件 | this节点实例、activeNodeList当前激活的所有节点列表 |
| node_active | 节点激活事件 | this节点实例、activeNodeList当前激活的所有节点列表 |
| expand_btn_click | 节点展开或收缩事件 | this节点实例 |
@@ -169,6 +185,8 @@ mindMap.setTheme('主题名称')
| scale | 放大缩小事件 | scale缩放比例 |
| node_img_dblclickv0.2.15+ | 节点内图片的双击事件 | this节点实例、e事件对象 |
| node_tree_render_endv0.2.16+ | 节点树渲染完毕事件 | |
| rich_text_selection_changev0.4.0+ | 当注册了`RichText`插件时可用。当节点编辑时,文本选区发生改变时触发 | hasRange是否存在选区、rectInfo选区的尺寸和位置信息、formatInfo选区的文本格式化信息 |
| transforming-dom-to-imagesv0.4.0+ | 当注册了`RichText`插件时可用。当`svg`中存在`DOM`节点时,导出为图片时会将`DOM`节点转换为图片,转换过程中会触发该事件,可用通过该事件给用户提示,告知目前转换到的节点 | index当前转换到的节点索引、len一共需要转换的节点数量 |
### emit(event, ...args)
@@ -307,4 +325,16 @@ mindMap.updateConfig({
> v0.1.5+
将浏览器可视窗口的坐标转换成相对于画布的坐标
将浏览器可视窗口的坐标转换成相对于画布的坐标
### addPlugin(plugin, opt)
> v0.4.0+
注册插件,使用`MindMap.usePlugin`注册插件只能在实例化之前,实例化后注册的插件是不会生效的,所以如果想在实例化后注册插件可以使用实例的`addPlugin`方法。
### removePlugin(plugin)
> v0.4.0+
移除注册的插件,无论是通过`usePlugin`还是`addPlugin`方法注册的插件都可以移除。

View File

@@ -140,6 +140,13 @@
<td>水印配置详细配置请参考下方表格水印配置</td>
<td></td>
</tr>
<tr>
<td>textAutoWrapWidthv0.3.4+</td>
<td>Number</td>
<td>500</td>
<td>节点内每行文本达到该宽度后自动换行</td>
<td></td>
</tr>
</tbody>
</table>
<h3>水印配置</h3>
@@ -207,12 +214,20 @@ MindMap.defineTheme(<span class="hljs-string">&#x27;主题名称&#x27;</span>, {
mindMap.setTheme(<span class="hljs-string">&#x27;主题名称&#x27;</span>)
</code></pre>
<p>主题的所有配置可以参考<a href="https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/themes/default.js">默认主题</a><code>defineTheme</code>方法会把你传入的配置和默认配置做合并大部分主题其实需要自定义的部分不是很多一个典型的自定义主题配置可以参考<a href="https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/themes/blueSky.js">blueSky</a></p>
<h3>usePlugin(plugin)</h3>
<h3>usePlugin(plugin, opt = {})</h3>
<blockquote>
<p>v0.3.0+</p>
</blockquote>
<ul>
<li><code>opt</code>v0.4.0+插件参数如果某个插件支持自定义选项的话可以通过这个参数传入</li>
</ul>
<p>注册插件如果需要使用非核心的一些功能比如小地图水印等可以通过该方法进行注册可链式调用</p>
<p>注意插件需要在实例化<code>MindMap</code>前注册</p>
<h3>hasPlugin(plugin)</h3>
<blockquote>
<p>v0.4.0+</p>
</blockquote>
<p>获取是否注册了某个插件返回的是插件在注册插件列表里的索引<code>-1</code>则代表插件没有注册</p>
<h2>静态属性</h2>
<h3>pluginList</h3>
<blockquote>
@@ -235,9 +250,15 @@ mindMap.setTheme(<span class="hljs-string">&#x27;主题名称&#x27;</span>)
scaleY, <span class="hljs-comment">// Number思维导图图形的垂直缩放值</span>
}
</code></pre>
<h3>render()</h3>
<h3>render(callback)</h3>
<ul>
<li><code>callback</code><code>v0.3.2+</code><code>Function</code>当重新渲染完成时调用</li>
</ul>
<p>触发整体渲染会进行节点复用性能较<code>reRender</code>会更好一点如果只是节点位置变化了可以调用该方法进行渲染</p>
<h3>reRender()</h3>
<h3>reRender(callback)</h3>
<ul>
<li><code>callback</code><code>v0.3.2+</code><code>Function</code>当重新渲染完成时调用</li>
</ul>
<p>整体重新渲染会清空画布节点也会重新创建性能不好慎重使用</p>
<h3>resize()</h3>
<p>容器尺寸变化后需要调用该方法进行适应</p>
@@ -339,6 +360,16 @@ mindMap.setTheme(<span class="hljs-string">&#x27;主题名称&#x27;</span>)
<td>e事件对象this节点实例</td>
</tr>
<tr>
<td>node_mouseenterv0.4.1+</td>
<td>节点的鼠标移入事件</td>
<td>this节点实例e事件对象</td>
</tr>
<tr>
<td>node_mouseleavev0.4.1+</td>
<td>节点的鼠标移出事件</td>
<td>this节点实例e事件对象</td>
</tr>
<tr>
<td>before_node_active</td>
<td>节点激活前事件</td>
<td>this节点实例activeNodeList当前激活的所有节点列表</td>
@@ -378,6 +409,16 @@ mindMap.setTheme(<span class="hljs-string">&#x27;主题名称&#x27;</span>)
<td>节点树渲染完毕事件</td>
<td></td>
</tr>
<tr>
<td>rich_text_selection_changev0.4.0+</td>
<td>当注册了<code>RichText</code>插件时可用当节点编辑时文本选区发生改变时触发</td>
<td>hasRange是否存在选区rectInfo选区的尺寸和位置信息formatInfo选区的文本格式化信息</td>
</tr>
<tr>
<td>transforming-dom-to-imagesv0.4.0+</td>
<td>当注册了<code>RichText</code>插件时可用<code>svg</code>中存在<code>DOM</code>节点时导出为图片时会将<code>DOM</code>节点转换为图片转换过程中会触发该事件可用通过该事件给用户提示告知目前转换到的节点</td>
<td>index当前转换到的节点索引len一共需要转换的节点数量</td>
</tr>
</tbody>
</table>
<h3>emit(event, ...args)</h3>
@@ -616,6 +657,16 @@ mindMap.setTheme(<span class="hljs-string">&#x27;主题名称&#x27;</span>)
<p>v0.1.5+</p>
</blockquote>
<p>将浏览器可视窗口的坐标转换成相对于画布的坐标</p>
<h3>addPlugin(plugin, opt)</h3>
<blockquote>
<p>v0.4.0+</p>
</blockquote>
<p>注册插件使用<code>MindMap.usePlugin</code>注册插件只能在实例化之前实例化后注册的插件是不会生效的所以如果想在实例化后注册插件可以使用实例的<code>addPlugin</code>方法</p>
<h3>removePlugin(plugin)</h3>
<blockquote>
<p>v0.4.0+</p>
</blockquote>
<p>移除注册的插件无论是通过<code>usePlugin</code>还是<code>addPlugin</code>方法注册的插件都可以移除</p>
</div>
</template>

View File

@@ -19,18 +19,39 @@ MindMap.usePlugin(Export)
导出为`png`,异步方法,返回图片数据,`data:url`数据,可以自行下载或显示
### svg()
### svg(name, domToImage = false, plusCssText)
- `name``svg`标题
- `domToImage`v0.4.0+,当开启了节点富文本编辑,可以通过该参数指定是否将`svg`中的`dom`节点转换成图片的形式
- `plusCssText`v0.4.0+,当开启了节点富文本编辑,且`domToImage`传了`false`时,可以添加附加的`css`样式,如果`svg`中存在`dom`节点,想要设置一些针对节点的样式可以通过这个参数传入,比如:
```js
svg(
'',
false,
`* {
margin: 0;
padding: 0;
box-sizing: border-box;
}`
)
```
导出为`svg`,异步方法,返回`svg`数据,`data:url`数据,可以自行下载或显示
### getSvgData()
### getSvgData(domToImage)
- `domToImage`v0.4.0+,如果开启了节点富文本,则可以通过该参数指定是否要将`svg`中嵌入的`DOM`节点转换为图片。
获取`svg`数据,异步方法,返回一个对象:
```js
{
node// svg对象
str// svg字符串
str// svg字符串如果开启了富文本编辑且domToImage设为true那么该值返回的svg字符内的dom节点会被转换成图片的形式
nodeWithDomToImg// v0.4.0+DOM节点转换为图片后的svg对象只有当开启了富文本编辑且domToImage设为true才有值否则为null
}
```

View File

@@ -12,13 +12,38 @@ MindMap.usePlugin(Export)
<h2>方法</h2>
<h3>png()</h3>
<p>导出为<code>png</code>异步方法返回图片数据<code>data:url</code>数据可以自行下载或显示</p>
<h3>svg()</h3>
<h3>svg(name, domToImage = false, plusCssText)</h3>
<ul>
<li>
<p><code>name</code><code>svg</code>标题</p>
</li>
<li>
<p><code>domToImage</code>v0.4.0+当开启了节点富文本编辑可以通过该参数指定是否将<code>svg</code>中的<code>dom</code>节点转换成图片的形式</p>
</li>
<li>
<p><code>plusCssText</code>v0.4.0+当开启了节点富文本编辑<code>domToImage</code>传了<code>false</code>可以添加附加的<code>css</code>样式如果<code>svg</code>中存在<code>dom</code>节点想要设置一些针对节点的样式可以通过这个参数传入比如</p>
</li>
</ul>
<pre class="hljs"><code>svg(
<span class="hljs-string">&#x27;&#x27;</span>,
<span class="hljs-literal">false</span>,
<span class="hljs-string">`* {
margin: 0;
padding: 0;
box-sizing: border-box;
}`</span>
)
</code></pre>
<p>导出为<code>svg</code>异步方法返回<code>svg</code>数据<code>data:url</code>数据可以自行下载或显示</p>
<h3>getSvgData()</h3>
<h3>getSvgData(domToImage)</h3>
<ul>
<li><code>domToImage</code>v0.4.0+如果开启了节点富文本则可以通过该参数指定是否要将<code>svg</code>中嵌入的<code>DOM</code>节点转换为图片</li>
</ul>
<p>获取<code>svg</code>数据异步方法返回一个对象</p>
<pre class="hljs"><code>{
node<span class="hljs-comment">// svg对象</span>
str<span class="hljs-comment">// svg字符串</span>
str<span class="hljs-comment">// svg字符串如果开启了富文本编辑且domToImage设为true那么该值返回的svg字符内的dom节点会被转换成图片的形式</span>
nodeWithDomToImg<span class="hljs-comment">// v0.4.0+DOM节点转换为图片后的svg对象只有当开启了富文本编辑且domToImage设为true才有值否则为null</span>
}
</code></pre>
<h3>pdf(name)</h3>

View File

@@ -49,6 +49,8 @@
[只需百来行代码为你的Web页面增加本地文件操作能力确定不试试吗](https://juejin.cn/post/7157681502506090510)
[当你按下方向键,电视是如何寻找下一个焦点的](https://juejin.cn/post/7199666255883927612)
## 特别说明
本项目较粗糙,未进行完整测试,功能尚不是很完善,性能也存在一些问题,尤其当节点数量比较庞大的时候,仅用于学习和参考,请谨慎用于实际项目。

View File

@@ -37,6 +37,7 @@
<h2>相关文章</h2>
<p><a href="https://juejin.cn/post/6987711560521089061">Web思维导图实现的技术点分析</a></p>
<p><a href="https://juejin.cn/post/7157681502506090510">只需百来行代码为你的Web页面增加本地文件操作能力确定不试试吗</a></p>
<p><a href="https://juejin.cn/post/7199666255883927612">当你按下方向键电视是如何寻找下一个焦点的</a></p>
<h2>特别说明</h2>
<p>本项目较粗糙未进行完整测试功能尚不是很完善性能也存在一些问题尤其当节点数量比较庞大的时候仅用于学习和参考请谨慎用于实际项目</p>
<p>项目内置的主题和图标来自于</p>

View File

@@ -0,0 +1,153 @@
# RichText插件
> v0.4.0+
> 注意:这是一个测试性质和不完善的功能
该插件提供节点富文本编辑的能力,注册了即可生效。
默认节点编辑只能对节点内所有文本统一应用样式,通过该插件可以支持富文本编辑的效果,目前支持:加粗、斜体、下划线、删除线、字体、字号、颜色、背景颜色。不支持上划线、行高。
该插件的原理是使用[Quill](https://github.com/quilljs/quill)编辑器实现富文本编辑,然后把编辑后生成的`DOM`节点直接作为节点的文本数据,并且在渲染的时候通过`svg``foreignObject`标签嵌入`DOM`节点。
这样也造成了一个问题,就是导出为图片的功能受到了影响,原本将`svg`导出为图片的原理很简单,获取到`svg`字符串,然后创建为`type=image/svg+xml`类型的`blob`数据,再使用`URL.createObjectURL`方法生成`data:url`数据,再创建一个`Image`标签,将`data:url`作为该图片的`src`,最后再将这个图片绘制到`canvas`对象上进行导出,但是经过测试,当`svg`中嵌入了`DOM`节点,这种方式导出会出错,并且尝试了多种方式后都无法实现完美的导出效果,目前的方式是遍历`svg`中的`foreignObject`节点,使用[html2canvas](https://github.com/niklasvh/html2canvas)将`foreignObject`节点内的`DOM`节点转换为图片再替换掉`foreignObject`节点,这种方式可以工作,但是非常耗时,因为`html2canvas`转换一次的时间很长导致转换一个节点都需要耗时差不多2秒这样导致节点越多转换时间越慢所以如果无法忍受长时间的导出的话推荐不要使用该插件。
如果你有更好的方式也欢迎留言。
## 注册
```js
import MindMap from 'simple-mind-map'
import RichText from 'simple-mind-map/src/RichText.js'
MindMap.usePlugin(RichText, opt?)
```
注册完且实例化`MindMap`后可通过`mindMap.richText`获取到该实例。
### 注册选项
`opt`选项可以传递以下参数:
- `opt.fontFamilyList`
替换富文本编辑时内置字体列表。内置的列表为:
```js
[
'宋体, SimSun, Songti SC',
'微软雅黑, Microsoft YaHei',
'楷体, 楷体_GB2312, SimKai, STKaiti',
'黑体, SimHei, Heiti SC',
'隶书, SimLi',
'andale mono',
'arial, helvetica, sans-serif',
'arial black, avant garde',
'comic sans ms',
'impact, chicago',
'times new roman',
'sans-serif',
'serif'
]
```
- `opt.fontSizeList`
替换富文本编辑时内置字号列表。内置的列表为:
```js
[1, 2, 3, ...100]
```
## 方法
### selectAll()
选中全部。当节点正在编辑中可以通过该方法选中节点内的所有文本。
### formatText(config = {})
- `config`:对象,键为样式属性,值为样式值,完整的配置如下:
```js
{
font: '字体',
size: '12px,' // 字号
bold: true, // 是否加粗true/false
italic: true, // 是否斜体true/false
underline: true, // 是否显示下划线true/false
strike: true, // 是否显示删除线true/false
color: '#333' // 颜色
}
```
格式化当前选中的文本。
### formatRangeText(range, config = {})
- `range``Quill`的范围对象,格式如下:
```js
{
index,
length
}
```
- `config`:同`formatText`方法
格式化指定范围的文本。
### formatAllText(config = {})
- `config`:同`formatText`方法
格式化当前编辑节点的所有文本。
### removeFormat()
> v0.4.1+
清除当前选中文本的样式。
### normalStyleToRichTextStyle(style)
将普通节点样式对象转换成富文本样式对象。因为非富文本编辑时的节点样式属性和富文本样式属性是存在差异的,所以需要一个转换操作。比如:
```js
{
fontFamily: 'xxx'
}
// 转换后
{
font: 'xxx'
}
```
### richTextStyleToNormalStyle(config)
将富文本样式对象转换成普通节点样式对象。比如:
```js
{
size: '16px'
}
// 转换后
{
fontSize: 16
}
```
### handleSvgDomElements(svg)
- `svg` `svg`节点
`svg`中嵌入的`dom`元素转换成图片,返回一个`Promise`
### transformAllNodesToNormalNode()
将所有节点转换成非富文本节点。

View File

@@ -0,0 +1,134 @@
<template>
<div>
<h1>RichText插件</h1>
<blockquote>
<p>v0.4.0+</p>
</blockquote>
<blockquote>
<p>注意这是一个测试性质和不完善的功能</p>
</blockquote>
<p>该插件提供节点富文本编辑的能力注册了即可生效</p>
<p>默认节点编辑只能对节点内所有文本统一应用样式通过该插件可以支持富文本编辑的效果目前支持加粗斜体下划线删除线字体字号颜色背景颜色不支持上划线行高</p>
<p>该插件的原理是使用<a href="https://github.com/quilljs/quill">Quill</a>编辑器实现富文本编辑然后把编辑后生成的<code>DOM</code>节点直接作为节点的文本数据并且在渲染的时候通过<code>svg</code><code>foreignObject</code>标签嵌入<code>DOM</code>节点</p>
<p>这样也造成了一个问题就是导出为图片的功能受到了影响原本将<code>svg</code>导出为图片的原理很简单获取到<code>svg</code>字符串然后创建为<code>type=image/svg+xml</code>类型的<code>blob</code>数据再使用<code>URL.createObjectURL</code>方法生成<code>data:url</code>数据再创建一个<code>Image</code>标签<code>data:url</code>作为该图片的<code>src</code>最后再将这个图片绘制到<code>canvas</code>对象上进行导出但是经过测试<code>svg</code>中嵌入了<code>DOM</code>节点这种方式导出会出错并且尝试了多种方式后都无法实现完美的导出效果目前的方式是遍历<code>svg</code>中的<code>foreignObject</code>节点使用<a href="https://github.com/niklasvh/html2canvas">html2canvas</a><code>foreignObject</code>节点内的<code>DOM</code>节点转换为图片再替换掉<code>foreignObject</code>节点这种方式可以工作但是非常耗时因为<code>html2canvas</code>转换一次的时间很长导致转换一个节点都需要耗时差不多2秒这样导致节点越多转换时间越慢所以如果无法忍受长时间的导出的话推荐不要使用该插件</p>
<p>如果你有更好的方式也欢迎留言</p>
<h2>注册</h2>
<pre class="hljs"><code><span class="hljs-keyword">import</span> MindMap <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map&#x27;</span>
<span class="hljs-keyword">import</span> RichText <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map/src/RichText.js&#x27;</span>
MindMap.usePlugin(RichText, opt?)
</code></pre>
<p>注册完且实例化<code>MindMap</code>后可通过<code>mindMap.richText</code>获取到该实例</p>
<h3>注册选项</h3>
<p><code>opt</code>选项可以传递以下参数</p>
<ul>
<li><code>opt.fontFamilyList</code></li>
</ul>
<p>替换富文本编辑时内置字体列表内置的列表为</p>
<pre class="hljs"><code>[
<span class="hljs-string">&#x27;宋体, SimSun, Songti SC&#x27;</span>,
<span class="hljs-string">&#x27;微软雅黑, Microsoft YaHei&#x27;</span>,
<span class="hljs-string">&#x27;楷体, 楷体_GB2312, SimKai, STKaiti&#x27;</span>,
<span class="hljs-string">&#x27;黑体, SimHei, Heiti SC&#x27;</span>,
<span class="hljs-string">&#x27;隶书, SimLi&#x27;</span>,
<span class="hljs-string">&#x27;andale mono&#x27;</span>,
<span class="hljs-string">&#x27;arial, helvetica, sans-serif&#x27;</span>,
<span class="hljs-string">&#x27;arial black, avant garde&#x27;</span>,
<span class="hljs-string">&#x27;comic sans ms&#x27;</span>,
<span class="hljs-string">&#x27;impact, chicago&#x27;</span>,
<span class="hljs-string">&#x27;times new roman&#x27;</span>,
<span class="hljs-string">&#x27;sans-serif&#x27;</span>,
<span class="hljs-string">&#x27;serif&#x27;</span>
]
</code></pre>
<ul>
<li><code>opt.fontSizeList</code></li>
</ul>
<p>替换富文本编辑时内置字号列表内置的列表为</p>
<pre class="hljs"><code>[<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, ..<span class="hljs-number">.100</span>]
</code></pre>
<h2>方法</h2>
<h3>selectAll()</h3>
<p>选中全部当节点正在编辑中可以通过该方法选中节点内的所有文本</p>
<h3>formatText(config = {})</h3>
<ul>
<li><code>config</code>对象键为样式属性值为样式值完整的配置如下</li>
</ul>
<pre class="hljs"><code>{
<span class="hljs-attr">font</span>: <span class="hljs-string">&#x27;字体&#x27;</span>,
<span class="hljs-attr">size</span>: <span class="hljs-string">&#x27;12px,&#x27;</span> <span class="hljs-comment">// 字号</span>
<span class="hljs-attr">bold</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// 是否加粗true/false </span>
<span class="hljs-attr">italic</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// 是否斜体true/false </span>
<span class="hljs-attr">underline</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// 是否显示下划线true/false </span>
<span class="hljs-attr">strike</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// 是否显示删除线true/false </span>
<span class="hljs-attr">color</span>: <span class="hljs-string">&#x27;#333&#x27;</span> <span class="hljs-comment">// 颜色</span>
}
</code></pre>
<p>格式化当前选中的文本</p>
<h3>formatRangeText(range, config = {})</h3>
<ul>
<li><code>range</code><code>Quill</code>的范围对象格式如下</li>
</ul>
<pre class="hljs"><code>{
index,
length
}
</code></pre>
<ul>
<li><code>config</code><code>formatText</code>方法</li>
</ul>
<p>格式化指定范围的文本</p>
<h3>formatAllText(config = {})</h3>
<ul>
<li><code>config</code><code>formatText</code>方法</li>
</ul>
<p>格式化当前编辑节点的所有文本</p>
<h3>removeFormat()</h3>
<blockquote>
<p>v0.4.1+</p>
</blockquote>
<p>清除当前选中文本的样式</p>
<h3>normalStyleToRichTextStyle(style)</h3>
<p>将普通节点样式对象转换成富文本样式对象因为非富文本编辑时的节点样式属性和富文本样式属性是存在差异的所以需要一个转换操作比如</p>
<pre class="hljs"><code>{
<span class="hljs-attr">fontFamily</span>: <span class="hljs-string">&#x27;xxx&#x27;</span>
}
<span class="hljs-comment">// 转换后</span>
{
<span class="hljs-attr">font</span>: <span class="hljs-string">&#x27;xxx&#x27;</span>
}
</code></pre>
<h3>richTextStyleToNormalStyle(config)</h3>
<p>将富文本样式对象转换成普通节点样式对象比如</p>
<pre class="hljs"><code>{
<span class="hljs-attr">size</span>: <span class="hljs-string">&#x27;16px&#x27;</span>
}
<span class="hljs-comment">// 转换后</span>
{
<span class="hljs-attr">fontSize</span>: <span class="hljs-number">16</span>
}
</code></pre>
<h3>handleSvgDomElements(svg)</h3>
<ul>
<li><code>svg</code> <code>svg</code>节点</li>
</ul>
<p><code>svg</code>中嵌入的<code>dom</code>元素转换成图片返回一个<code>Promise</code></p>
<h3>transformAllNodesToNormalNode()</h3>
<p>将所有节点转换成非富文本节点</p>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>

View File

@@ -104,6 +104,22 @@ copyNodeTree({}, node)
驼峰转连字符
#### joinFontStr({ italic, bold, fontSize, fontFamily })
> v0.3.4+
拼接`css`字体的`font`属性值
#### measureText(text, { italic, bold, fontSize, fontFamily })
> v0.3.4+
测量文本的宽高,返回值:
```js
{ width, height }
```
## 在canvas中模拟css的背景属性
引入:

View File

@@ -57,6 +57,18 @@
<p>v0.2.24+</p>
</blockquote>
<p>驼峰转连字符</p>
<h4>joinFontStr({ italic, bold, fontSize, fontFamily })</h4>
<blockquote>
<p>v0.3.4+</p>
</blockquote>
<p>拼接<code>css</code>字体的<code>font</code>属性值</p>
<h4>measureText(text, { italic, bold, fontSize, fontFamily })</h4>
<blockquote>
<p>v0.3.4+</p>
</blockquote>
<p>测量文本的宽高返回值</p>
<pre class="hljs"><code>{ width, height }
</code></pre>
<h2>在canvas中模拟css的背景属性</h2>
<p>引入</p>
<pre class="hljs"><code><span class="hljs-keyword">import</span> drawBackgroundImageToCanvas <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map/src/utils/simulateCSSBackgroundInCanvas&#x27;</span>

View File

@@ -41,4 +41,10 @@ mindMap.watermark.updateWatermark({
fontSize: 20
}
})
```
```
### hasWatermark()
> v0.3.2+
获取是否存在水印。

View File

@@ -31,6 +31,11 @@ MindMap.usePlugin(Watermark)
}
})
</code></pre>
<h3>hasWatermark()</h3>
<blockquote>
<p>v0.3.2+</p>
</blockquote>
<p>获取是否存在水印</p>
</div>
</template>

View File

@@ -429,6 +429,11 @@
">{{ $t('baseStyle.enableFreeDrag') }}</el-checkbox>
</div>
</div>
<div class="row">
<div class="rowItem">
<el-checkbox v-model="enableNodeRichText" @change="enableNodeRichTextChange">{{ this.$t('baseStyle.isEnableNodeRichText') }}</el-checkbox>
</div>
</div>
</div>
</Sidebar>
</template>
@@ -439,7 +444,7 @@ import Color from './Color'
import { lineWidthList, lineStyleList, backgroundRepeatList, backgroundPositionList, backgroundSizeList } from '@/config'
import ImgUpload from '@/components/ImgUpload'
import { storeConfig } from '@/api'
import { mapState } from 'vuex'
import { mapState, mapMutations } from 'vuex'
/**
* @Author: 王林
@@ -502,11 +507,12 @@ export default {
fontSize: 1
}
},
updateWatermarkTimer: null
updateWatermarkTimer: null,
enableNodeRichText: true
}
},
computed: {
...mapState(['activeSidebar']),
...mapState(['activeSidebar', 'localConfig']),
lineStyleList() {
return lineStyleList[this.$i18n.locale] || lineStyleList.zh
@@ -533,7 +539,12 @@ export default {
}
}
},
created () {
this.enableNodeRichText = this.localConfig.openNodeRichText
},
methods: {
...mapMutations(['setLocalConfig']),
/**
* @Author: 王林
* @Date: 2021-05-05 14:02:12
@@ -668,6 +679,13 @@ export default {
this.watermarkConfig.text = ''
}
this.updateWatermarkConfig()
},
// 切换是否开启节点富文本编辑
enableNodeRichTextChange(e) {
this.setLocalConfig({
openNodeRichText: e
})
}
}
}

View File

@@ -11,6 +11,7 @@
<Structure :mindMap="mindMap"></Structure>
<ShortcutKey></ShortcutKey>
<Contextmenu v-if="mindMap" :mindMap="mindMap"></Contextmenu>
<RichTextToolbar v-if="mindMap" :mindMap="mindMap"></RichTextToolbar>
<NodeNoteContentShow
v-if="mindMap"
:mindMap="mindMap"
@@ -28,6 +29,7 @@ import KeyboardNavigation from 'simple-mind-map/src/KeyboardNavigation.js'
import Export from 'simple-mind-map/src/Export.js'
import Drag from 'simple-mind-map/src/Drag.js'
import Select from 'simple-mind-map/src/Select.js'
import RichText from 'simple-mind-map/src/RichText.js'
import Outline from './Outline'
import Style from './Style'
import BaseStyle from './BaseStyle'
@@ -37,6 +39,7 @@ import Count from './Count'
import NavigatorToolbar from './NavigatorToolbar'
import ShortcutKey from './ShortcutKey'
import Contextmenu from './Contextmenu'
import RichTextToolbar from './RichTextToolbar'
import NodeNoteContentShow from './NodeNoteContentShow.vue'
import { getData, storeData, storeConfig } from '@/api'
import Navigator from './Navigator.vue'
@@ -76,6 +79,7 @@ export default {
NavigatorToolbar,
ShortcutKey,
Contextmenu,
RichTextToolbar,
NodeNoteContentShow,
Navigator,
NodeImgPreview,
@@ -91,10 +95,21 @@ export default {
},
computed: {
...mapState({
isZenMode: state => state.localConfig.isZenMode
isZenMode: state => state.localConfig.isZenMode,
openNodeRichText: state => state.localConfig.openNodeRichText,
})
},
watch: {
openNodeRichText() {
if (this.openNodeRichText) {
this.addRichTextPlugin()
} else {
this.removeRichTextPlugin()
}
}
},
mounted() {
this.showNewFeatureInfo()
this.getData()
this.init()
this.$bus.$on('execCommand', this.execCommand)
@@ -263,6 +278,7 @@ export default {
},
...(config || {})
})
if (this.openNodeRichText) this.addRichTextPlugin()
this.mindMap.keyCommand.addShortcut('Control+s', () => {
this.manualSave()
})
@@ -279,7 +295,9 @@ export default {
'svg_mousedown',
'mouseup',
'mode_change',
'node_tree_render_end'
'node_tree_render_end',
'rich_text_selection_change',
'transforming-dom-to-images'
].forEach(event => {
this.mindMap.on(event, (...args) => {
this.$bus.$emit(event, ...args)
@@ -331,6 +349,32 @@ export default {
} catch (error) {
console.log(error)
}
},
// 显示新特性提示
showNewFeatureInfo() {
let showed = localStorage.getItem('SIMPLE_MIND_MAP_NEW_FEATURE_TIP_1')
if (!showed) {
this.$notify.info({
title: this.$t('edit.newFeatureNoticeTitle'),
message: this.$t('edit.newFeatureNoticeMessage'),
duration: 0,
onClose: () => {
localStorage.setItem('SIMPLE_MIND_MAP_NEW_FEATURE_TIP_1', true)
}
})
}
},
// 加载节点富文本编辑插件
addRichTextPlugin() {
if (!this.mindMap) return
this.mindMap.addPlugin(RichText)
},
// 移除节点富文本编辑插件
removeRichTextPlugin() {
this.mindMap.removePlugin(RichText)
}
}
}

View File

@@ -4,6 +4,10 @@
:title="$t('export.title')"
:visible.sync="dialogVisible"
width="700px"
v-loading.fullscreen.lock="loading"
:element-loading-text="loadingText"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)"
>
<div>
<div class="nameInputBox">
@@ -19,6 +23,12 @@
style="margin-left: 12px"
>{{ $t('export.include') }}</el-checkbox
>
<el-checkbox
v-show="['svg'].includes(exportType)"
v-model="domToImage"
style="margin-left: 12px"
>{{ $t('export.domToImage') }}</el-checkbox
>
</div>
<el-radio-group v-model="exportType" size="mini">
<el-radio-button label="smm"
@@ -38,6 +48,8 @@
>
</el-radio-group>
<div class="tip">{{ $t('export.tips') }}</div>
<div class="tip warning" v-if="openNodeRichText && ['png', 'pdf'].includes(exportType)">{{ $t('export.pngTips') }}</div>
<div class="tip warning" v-if="openNodeRichText && exportType === 'svg' && domToImage">{{ $t('export.svgTips') }}</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="cancel">{{ $t('dialog.cancel') }}</el-button>
@@ -49,6 +61,8 @@
</template>
<script>
import { mapState } from 'vuex'
/**
* @Author: 王林
* @Date: 2021-06-24 22:53:54
@@ -61,13 +75,28 @@ export default {
dialogVisible: false,
exportType: 'smm',
fileName: '思维导图',
widthConfig: true
widthConfig: true,
domToImage: false,
loading: false,
loadingText: ''
}
},
computed: {
...mapState({
openNodeRichText: state => state.localConfig.openNodeRichText,
})
},
created() {
this.$bus.$on('showExport', () => {
this.dialogVisible = true
})
this.$bus.$on('transforming-dom-to-images', (index, len) => {
this.loading = true
this.loadingText = `${this.$t('export.transformingDomToImages')}${index + 1}/${len}`
if (index >= len - 1) {
this.loading = false
}
})
},
methods: {
/**
@@ -85,16 +114,31 @@ export default {
* @Desc: 确定
*/
confirm() {
this.$bus.$emit(
'export',
this.exportType,
true,
this.fileName,
this.widthConfig
)
if (this.exportType === 'svg') {
this.$bus.$emit(
'export',
this.exportType,
true,
this.fileName,
this.domToImage,
`* {
margin: 0;
padding: 0;
box-sizing: border-box;
}`
)
} else {
this.$bus.$emit(
'export',
this.exportType,
true,
this.fileName,
this.widthConfig
)
}
this.$notify.info({
title: '消息',
message: '如果没有触发下载,请检查是否被浏览器拦截了'
title: this.$t('export.notifyTitle'),
message: this.$t('export.notifyMessage')
})
this.cancel()
}
@@ -114,6 +158,10 @@ export default {
.tip {
margin-top: 10px;
&.warning {
color: #F56C6C;
}
}
}
</style>

View File

@@ -0,0 +1,285 @@
<template>
<div
class="richTextToolbar"
ref="richTextToolbar"
:style="style"
@click.stop.passive
v-show="showRichTextToolbar"
>
<el-tooltip content="加粗" placement="top">
<div class="btn" :class="{ active: formatInfo.bold }" @click="toggleBold">
<span class="icon iconfont iconzitijiacu"></span>
</div>
</el-tooltip>
<el-tooltip content="斜体" placement="top">
<div
class="btn"
:class="{ active: formatInfo.italic }"
@click="toggleItalic"
>
<span class="icon iconfont iconzitixieti"></span>
</div>
</el-tooltip>
<el-tooltip content="下划线" placement="top">
<div
class="btn"
:class="{ active: formatInfo.underline }"
@click="toggleUnderline"
>
<span class="icon iconfont iconzitixiahuaxian"></span>
</div>
</el-tooltip>
<el-tooltip content="删除线" placement="top">
<div
class="btn"
:class="{ active: formatInfo.strike }"
@click="toggleStrike"
>
<span class="icon iconfont iconshanchuxian"></span>
</div>
</el-tooltip>
<el-tooltip content="字体" placement="top">
<el-popover placement="bottom" trigger="hover">
<div class="fontOptionsList">
<div
class="fontOptionItem"
v-for="item in fontFamilyList"
:key="item.value"
:style="{ fontFamily: item.value }"
:class="{ active: formatInfo.font === item.value }"
@click="changeFontFamily(item.value)"
>
{{ item.name }}
</div>
</div>
<div class="btn" slot="reference">
<span class="icon iconfont iconxingzhuang-wenzi"></span>
</div>
</el-popover>
</el-tooltip>
<el-tooltip content="字号" placement="top">
<el-popover placement="bottom" trigger="hover">
<div class="fontOptionsList">
<div
class="fontOptionItem"
v-for="item in fontSizeList"
:key="item"
:style="{ fontSize: item + 'px' }"
:class="{ active: formatInfo.size === item + 'px' }"
@click="changeFontSize(item)"
>
{{ item }}px
</div>
</div>
<div class="btn" slot="reference" :style="{ color: formatInfo.color }">
<span class="icon iconfont iconcase fontColor"></span>
</div>
</el-popover>
</el-tooltip>
<el-tooltip content="字体颜色" placement="top">
<el-popover placement="bottom" trigger="hover">
<Color :color="fontColor" @change="changeFontColor"></Color>
<div class="btn" slot="reference">
<span class="icon iconfont iconzitiyanse"></span>
</div>
</el-popover>
</el-tooltip>
<el-tooltip content="背景颜色" placement="top">
<el-popover placement="bottom" trigger="hover">
<Color :color="fontBackgroundColor" @change="changeFontBackgroundColor"></Color>
<div class="btn" slot="reference">
<span class="icon iconfont iconbeijingyanse"></span>
</div>
</el-popover>
</el-tooltip>
<el-tooltip content="清除样式" placement="top">
<div
class="btn" @click="removeFormat"
>
<span class="icon iconfont iconqingchu"></span>
</div>
</el-tooltip>
</div>
</template>
<script>
import { fontFamilyList, fontSizeList } from '@/config'
import Color from './Color'
export default {
name: 'RichTextToolbar',
components: {
Color
},
props: {
mindMap: {
type: Object
}
},
data() {
return {
fontSizeList,
showRichTextToolbar: false,
style: {
left: 0,
top: 0
},
fontColor: '',
fontBackgroundColor: '',
formatInfo: {}
}
},
computed: {
fontFamilyList() {
return fontFamilyList[this.$i18n.locale] || fontFamilyList.zh
}
},
created() {
this.$bus.$on('rich_text_selection_change', this.onRichTextSelectionChange)
},
mounted() {
document.body.append(this.$refs.richTextToolbar)
},
beforeDestroy() {
this.$bus.$off('rich_text_selection_change', this.onRichTextSelectionChange)
},
methods: {
onRichTextSelectionChange(hasRange, rect, formatInfo) {
if (hasRange) {
this.style.left = rect.left + rect.width / 2 + 'px'
this.style.top = rect.top - 60 + 'px'
this.formatInfo = { ...(formatInfo || {}) }
}
this.showRichTextToolbar = hasRange
},
toggleBold() {
this.formatInfo.bold = !this.formatInfo.bold
this.mindMap.richText.formatText({
bold: this.formatInfo.bold
})
},
toggleItalic() {
this.formatInfo.italic = !this.formatInfo.italic
this.mindMap.richText.formatText({
italic: this.formatInfo.italic
})
},
toggleUnderline() {
this.formatInfo.underline = !this.formatInfo.underline
this.mindMap.richText.formatText({
underline: this.formatInfo.underline
})
},
toggleStrike() {
this.formatInfo.strike = !this.formatInfo.strike
this.mindMap.richText.formatText({
strike: this.formatInfo.strike
})
},
changeFontFamily(font) {
this.formatInfo.font = font
this.mindMap.richText.formatText({
font
})
},
changeFontSize(size) {
this.formatInfo.size = size
this.mindMap.richText.formatText({
size: size + 'px'
})
},
changeFontColor(color) {
this.formatInfo.color = color
this.mindMap.richText.formatText({
color
})
},
changeFontBackgroundColor(background) {
this.formatInfo.background = background
this.mindMap.richText.formatText({
background
})
},
removeFormat() {
this.mindMap.richText.removeFormat()
}
}
}
</script>
<style lang="less" scoped>
.richTextToolbar {
position: fixed;
z-index: 99999;
height: 55px;
background: #fff;
border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: 8px;
box-shadow: 0 2px 16px 0 rgba(0, 0, 0, 0.06);
display: flex;
align-items: center;
transform: translateX(-50%);
.btn {
width: 55px;
height: 55px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
&:hover {
background-color: #eefbed;
}
&.active {
color: #12bb37;
}
.icon {
font-size: 20px;
&.fontColor {
font-size: 26px;
}
}
}
}
.fontOptionsList {
width: 150px;
.fontOptionItem {
height: 30px;
width: 100%;
display: flex;
align-items: center;
cursor: pointer;
&:hover {
background-color: #f7f7f7;
}
&.active {
color: #12bb37;
}
}
}
</style>

View File

@@ -45,6 +45,7 @@
:key="item"
:label="item"
:value="item"
:style="{ fontSize: item + 'px' }"
>
</el-option>
</el-select>
@@ -125,7 +126,7 @@
<el-popover
ref="popover"
placement="bottom"
trigger="click"
trigger="hover"
:disabled="checkDisabled('color')"
>
<Color :color="style.color" @change="changeFontColor"></Color>
@@ -133,7 +134,7 @@
<el-popover
ref="popover2"
placement="bottom"
trigger="click"
trigger="hover"
:disabled="checkDisabled('textDecoration')"
>
<el-radio-group
@@ -167,7 +168,7 @@
<el-popover
ref="popover3"
placement="bottom"
trigger="click"
trigger="hover"
:disabled="checkDisabled('borderColor')"
>
<Color
@@ -250,7 +251,7 @@
<el-popover
ref="popover4"
placement="bottom"
trigger="click"
trigger="hover"
:disabled="checkDisabled('fillColor')"
>
<Color :color="style.fillColor" @change="changeFillColor"></Color>
@@ -294,7 +295,7 @@
<el-popover
ref="popover5"
placement="bottom"
trigger="click"
trigger="hover"
:disabled="checkDisabled('lineColor')"
>
<Color :color="style.lineColor" @change="changeLineColor"></Color>

View File

@@ -11,7 +11,9 @@ const store = new Vuex.Store({
isHandleLocalFile: false, // 是否操作的是本地文件
localConfig: {
// 本地配置
isZenMode: false // 是否是禅模式
isZenMode: false, // 是否是禅模式
// 是否开启节点富文本
openNodeRichText: true
},
activeSidebar: '' // 当前显示的侧边栏
},