Compare commits
102 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ce9533763 | ||
|
|
43ecdafdd1 | ||
|
|
5ff8704778 | ||
|
|
d206d6dd99 | ||
|
|
850b9ed936 | ||
|
|
26508b5a62 | ||
|
|
061c82459e | ||
|
|
d2bf5be7c7 | ||
|
|
b726aa30e5 | ||
|
|
6546ec090e | ||
|
|
4dc5754f1d | ||
|
|
7f199e6c2f | ||
|
|
2548ac4eb4 | ||
|
|
60e503ab1f | ||
|
|
c718cbc030 | ||
|
|
beb2b550f0 | ||
|
|
d3e1389f10 | ||
|
|
68cb24c0b6 | ||
|
|
e3959107bd | ||
|
|
4b3c81ab91 | ||
|
|
76b5d9d11d | ||
|
|
22d0fe5ac4 | ||
|
|
8b8d549abd | ||
|
|
731a5a504d | ||
|
|
008e697b74 | ||
|
|
fe1745e779 | ||
|
|
e6ac9be402 | ||
|
|
b427a9ed1b | ||
|
|
a4dc9210b3 | ||
|
|
fb681de1f5 | ||
|
|
3757622521 | ||
|
|
12265be7d4 | ||
|
|
df1aed7e04 | ||
|
|
c32c9d1ba1 | ||
|
|
26d75c9203 | ||
|
|
8d1e9fa8e9 | ||
|
|
ebc99e97af | ||
|
|
efe4aa0ec2 | ||
|
|
3f659af1e1 | ||
|
|
64209a6392 | ||
|
|
8d1e5dd2e9 | ||
|
|
d218122752 | ||
|
|
9af8afca22 | ||
|
|
002ec41ba8 | ||
|
|
ffff257f57 | ||
|
|
41d0b675a0 | ||
|
|
7601d6730d | ||
|
|
f0e9b76bb5 | ||
|
|
4ec33062a4 | ||
|
|
edc63b1293 | ||
|
|
ec83976818 | ||
|
|
393410757b | ||
|
|
f20748744a | ||
|
|
067131f76d | ||
|
|
f689d333f9 | ||
|
|
b3059bb6a3 | ||
|
|
b135f6a61c | ||
|
|
2a8b71497f | ||
|
|
726ebc5e88 | ||
|
|
2c56cd453c | ||
|
|
bf2f9b2697 | ||
|
|
fbd061e8b3 | ||
|
|
680320ba76 | ||
|
|
0d992bd6b1 | ||
|
|
c80eacc5e7 | ||
|
|
6d202b4b7d | ||
|
|
94478fe9f3 | ||
|
|
5745e4567b | ||
|
|
c6a3f4ac7b | ||
|
|
f6609b3050 | ||
|
|
4d69778a50 | ||
|
|
c8dfbd3b87 | ||
|
|
c0aab1e921 | ||
|
|
6efee6e859 | ||
|
|
dd82bf0879 | ||
|
|
2d41b5f9e6 | ||
|
|
7e7b6fae9d | ||
|
|
29020daaf0 | ||
|
|
6bcead7784 | ||
|
|
9625129691 | ||
|
|
765d0ee212 | ||
|
|
099d4cd78e | ||
|
|
d9a6981df4 | ||
|
|
bbf424c6d2 | ||
|
|
5cde3b76fe | ||
|
|
75af742053 | ||
|
|
69e192ea9d | ||
|
|
5a32c1d99d | ||
|
|
e81e0a5512 | ||
|
|
2fef76c55c | ||
|
|
49735950f2 | ||
|
|
c005455144 | ||
|
|
ca4afb5440 | ||
|
|
885647cedf | ||
|
|
dc8efbe3ef | ||
|
|
888b8e725a | ||
|
|
15d65db19d | ||
|
|
2ce0d4cd11 | ||
|
|
13f3c2c20c | ||
|
|
d707329526 | ||
|
|
69faa8bb3e | ||
|
|
86b184d5c1 |
14
README.md
@@ -97,7 +97,7 @@ const mindMap = new MindMap({
|
||||
|
||||
> 厚椰乳一盒 + 纯牛奶半盒 + 冰块 + 咖啡液 = 生椰拿铁 yyds
|
||||
|
||||
> 转账请备注【思维导图】。你的头像和名字将会出现在下面和[文档页面](https://wanglin2.github.io/mind-map/#/doc/zh/introduction/%E8%AF%B7%E4%BD%9C%E8%80%85%E5%96%9D%E6%9D%AF%E5%92%96%E5%95%A1)
|
||||
> 推荐使用支付宝,微信获取不到头像。转账请备注【思维导图】。你的头像和名字将会出现在下面和[文档页面](https://wanglin2.github.io/mind-map/#/doc/zh/introduction/%E8%AF%B7%E4%BD%9C%E8%80%85%E5%96%9D%E6%9D%AF%E5%92%96%E5%95%A1)
|
||||
|
||||
<p>
|
||||
<img src="./web/src/assets/img/alipay.jpg" style="width: 300px" />
|
||||
@@ -153,4 +153,16 @@ const mindMap = new MindMap({
|
||||
<img src="./web/src/assets/avatar/才镇.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>才镇</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/小米.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>小米bbᯤ²ᴳ</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/棐.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>*棐</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
|
||||
<span>Luke</span>
|
||||
</span>
|
||||
</p>
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1"><link rel="icon" href="dist/logo.ico"><title>思绪思维导图</title><script>// 自定义静态资源的路径
|
||||
window.externalPublicPath = './dist/'
|
||||
// 接管应用
|
||||
window.takeOverApp = false</script><link href="dist/css/chunk-vendors.css?f6cb6d932d2f3720fc06" rel="stylesheet"><link href="dist/css/app.css?f6cb6d932d2f3720fc06" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script>const getDataFromBackend = () => {
|
||||
window.takeOverApp = false</script><link href="dist/css/chunk-vendors.css?3f27c97d35a340dfd1c0" rel="stylesheet"><link href="dist/css/app.css?3f27c97d35a340dfd1c0" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script>const getDataFromBackend = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
@@ -66,4 +66,4 @@
|
||||
// 可以通过window.$bus.$on()来监听应用的一些事件
|
||||
// 实例化页面
|
||||
window.initApp()
|
||||
}</script><script src="dist/js/chunk-vendors.js?f6cb6d932d2f3720fc06"></script><script src="dist/js/app.js?f6cb6d932d2f3720fc06"></script></body></html>
|
||||
}</script><script src="dist/js/chunk-vendors.js?3f27c97d35a340dfd1c0"></script><script src="dist/js/app.js?3f27c97d35a340dfd1c0"></script></body></html>
|
||||
BIN
qrcode.jpg
|
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 49 KiB |
14
simple-mind-map/index.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
// declare module "simple-mind-map";
|
||||
declare module 'simple-mind-map'{
|
||||
class MindMap {
|
||||
constructor(opt:any);
|
||||
handleOpt(opt:any):void;
|
||||
render(callback:any, source:string):void;
|
||||
reRender(callback:any, source:string):void;
|
||||
resize():void;
|
||||
on(event:any, fn:any):void;
|
||||
setFullData(data:any):void;
|
||||
getData(withConfig:any):any;
|
||||
}
|
||||
export default MindMap;
|
||||
}
|
||||
@@ -7,10 +7,17 @@ import Style from './src/core/render/node/Style'
|
||||
import KeyCommand from './src/core/command/KeyCommand'
|
||||
import Command from './src/core/command/Command'
|
||||
import BatchExecution from './src/utils/BatchExecution'
|
||||
import { layoutValueList, CONSTANTS, commonCaches } from './src/constants/constant'
|
||||
import {
|
||||
layoutValueList,
|
||||
CONSTANTS,
|
||||
commonCaches,
|
||||
ERROR_TYPES
|
||||
} from './src/constants/constant'
|
||||
import { SVG } from '@svgdotjs/svg.js'
|
||||
import { simpleDeepClone, getType } from './src/utils'
|
||||
import defaultTheme, { checkIsNodeSizeIndependenceConfig } from './src/themes/default'
|
||||
import defaultTheme, {
|
||||
checkIsNodeSizeIndependenceConfig
|
||||
} from './src/themes/default'
|
||||
import { defaultOpt } from './src/constants/defaultOptions'
|
||||
|
||||
// 思维导图
|
||||
@@ -22,11 +29,13 @@ class MindMap {
|
||||
|
||||
// 容器元素
|
||||
this.el = this.opt.el
|
||||
if (!this.el) throw new Error('缺少容器元素el')
|
||||
this.elRect = this.el.getBoundingClientRect()
|
||||
|
||||
// 画布宽高
|
||||
this.width = this.elRect.width
|
||||
this.height = this.elRect.height
|
||||
if (this.width <= 0 || this.height <= 0) throw new Error('容器元素el的宽高不能为0')
|
||||
|
||||
// 画布
|
||||
this.svg = SVG().addTo(this.el).size(this.width, this.height)
|
||||
@@ -68,7 +77,7 @@ class MindMap {
|
||||
this.batchExecution = new BatchExecution()
|
||||
|
||||
// 注册插件
|
||||
MindMap.pluginList.forEach((plugin) => {
|
||||
MindMap.pluginList.forEach(plugin => {
|
||||
this.initPlugin(plugin)
|
||||
})
|
||||
|
||||
@@ -136,10 +145,10 @@ class MindMap {
|
||||
|
||||
// 初始化缓存数据
|
||||
initCache() {
|
||||
Object.keys(commonCaches).forEach((key) => {
|
||||
Object.keys(commonCaches).forEach(key => {
|
||||
let type = getType(commonCaches[key])
|
||||
let value = ''
|
||||
switch(type) {
|
||||
switch (type) {
|
||||
case 'Boolean':
|
||||
value = false
|
||||
break
|
||||
@@ -164,7 +173,7 @@ class MindMap {
|
||||
this.renderer.clearAllActive()
|
||||
this.opt.theme = theme
|
||||
this.render(null, CONSTANTS.CHANGE_THEME)
|
||||
this.emit('view_theme_change', theme)
|
||||
this.emit('view_theme_change', theme)
|
||||
}
|
||||
|
||||
// 获取当前主题
|
||||
@@ -278,8 +287,12 @@ class MindMap {
|
||||
|
||||
// 导出
|
||||
async export(...args) {
|
||||
let result = await this.doExport.export(...args)
|
||||
return result
|
||||
try {
|
||||
let result = await this.doExport.export(...args)
|
||||
return result
|
||||
} catch (error) {
|
||||
this.opt.errorHandler(ERROR_TYPES.EXPORT_ERROR, error)
|
||||
}
|
||||
}
|
||||
|
||||
// 转换位置
|
||||
@@ -317,9 +330,9 @@ class MindMap {
|
||||
// 获取变换后的位置尺寸信息,其实是getBoundingClientRect方法的包装方法
|
||||
const rect = draw.rbox()
|
||||
// 内边距
|
||||
rect.width += paddingX
|
||||
rect.height += paddingY
|
||||
draw.translate(paddingX / 2, paddingY / 2)
|
||||
rect.width += paddingX * 2
|
||||
rect.height += paddingY * 2
|
||||
draw.translate(paddingX, paddingY)
|
||||
// 将svg设置为实际内容的宽高
|
||||
svg.size(rect.width, rect.height)
|
||||
// 把实际内容变换
|
||||
@@ -327,7 +340,11 @@ class MindMap {
|
||||
// 克隆一份数据
|
||||
let clone = svg.clone()
|
||||
// 如果实际图形宽高超出了屏幕宽高,且存在水印的话需要重新绘制水印,否则会出现超出部分没有水印的问题
|
||||
if ((rect.width > origWidth || rect.height > origHeight) && this.watermark && this.watermark.hasWatermark()) {
|
||||
if (
|
||||
(rect.width > origWidth || rect.height > origHeight) &&
|
||||
this.watermark &&
|
||||
this.watermark.hasWatermark()
|
||||
) {
|
||||
this.width = rect.width
|
||||
this.height = rect.height
|
||||
this.watermark.draw()
|
||||
@@ -388,7 +405,7 @@ class MindMap {
|
||||
// 销毁
|
||||
destroy() {
|
||||
// 移除插件
|
||||
[...MindMap.pluginList].forEach((plugin) => {
|
||||
;[...MindMap.pluginList].forEach(plugin => {
|
||||
this[plugin.instanceName] = null
|
||||
})
|
||||
// 解绑事件
|
||||
@@ -408,8 +425,8 @@ MindMap.usePlugin = (plugin, opt = {}) => {
|
||||
MindMap.pluginList.push(plugin)
|
||||
return MindMap
|
||||
}
|
||||
MindMap.hasPlugin = (plugin) => {
|
||||
return MindMap.pluginList.findIndex((item) => {
|
||||
MindMap.hasPlugin = plugin => {
|
||||
return MindMap.pluginList.findIndex(item => {
|
||||
return item === plugin
|
||||
})
|
||||
}
|
||||
|
||||
17
simple-mind-map/package-lock.json
generated
@@ -1,17 +1,16 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.6.11-fix.1",
|
||||
"version": "0.6.15-fix.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "0.6.11-fix.1",
|
||||
"version": "0.6.15-fix.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@svgdotjs/svg.js": "^3.0.16",
|
||||
"deepmerge": "^1.5.2",
|
||||
"eventemitter3": "^4.0.7",
|
||||
"html2canvas": "^1.4.1",
|
||||
"jspdf": "^2.5.1",
|
||||
"jszip": "^3.10.1",
|
||||
"mdast-util-from-markdown": "^1.3.0",
|
||||
@@ -255,6 +254,7 @@
|
||||
"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"
|
||||
}
|
||||
@@ -411,6 +411,7 @@
|
||||
"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"
|
||||
}
|
||||
@@ -937,6 +938,7 @@
|
||||
"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"
|
||||
@@ -2142,6 +2144,7 @@
|
||||
"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"
|
||||
}
|
||||
@@ -2206,6 +2209,7 @@
|
||||
"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"
|
||||
}
|
||||
@@ -2461,7 +2465,8 @@
|
||||
"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=="
|
||||
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
|
||||
"optional": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
@@ -2576,6 +2581,7 @@
|
||||
"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"
|
||||
}
|
||||
@@ -2966,6 +2972,7 @@
|
||||
"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"
|
||||
@@ -3749,6 +3756,7 @@
|
||||
"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"
|
||||
}
|
||||
@@ -3800,6 +3808,7 @@
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.6.12",
|
||||
"version": "0.6.17",
|
||||
"description": "一个简单的web在线思维导图",
|
||||
"authors": [
|
||||
{
|
||||
@@ -27,7 +27,6 @@
|
||||
"@svgdotjs/svg.js": "^3.0.16",
|
||||
"deepmerge": "^1.5.2",
|
||||
"eventemitter3": "^4.0.7",
|
||||
"html2canvas": "^1.4.1",
|
||||
"jspdf": "^2.5.1",
|
||||
"jszip": "^3.10.1",
|
||||
"mdast-util-from-markdown": "^1.3.0",
|
||||
|
||||
@@ -330,5 +330,16 @@ export const nodeDataNoStylePropList = [
|
||||
|
||||
// 数据缓存
|
||||
export const commonCaches = {
|
||||
measureCustomNodeContentSizeEl: null
|
||||
measureCustomNodeContentSizeEl: null,
|
||||
measureRichtextNodeTextSizeEl: null
|
||||
}
|
||||
|
||||
// 错误类型
|
||||
export const ERROR_TYPES = {
|
||||
READ_CLIPBOARD_ERROR: 'read_clipboard_error',
|
||||
PARSE_PASTE_DATA_ERROR: 'parse_paste_data_error',
|
||||
CUSTOM_HANDLE_CLIPBOARD_TEXT_ERROR: 'custom_handle_clipboard_text_error',
|
||||
LOAD_CLIPBOARD_IMAGE_ERROR: 'load_clipboard_image_error',
|
||||
BEFORE_TEXT_EDIT_ERROR: 'before_text_edit_error',
|
||||
EXPORT_ERROR: 'export_error'
|
||||
}
|
||||
@@ -18,8 +18,6 @@ export const defaultOpt = {
|
||||
mouseScaleCenterUseMousePosition: true,
|
||||
// 最多显示几个标签
|
||||
maxTag: 5,
|
||||
// 导出图片时的内边距
|
||||
exportPadding: 20,
|
||||
// 展开收缩按钮尺寸
|
||||
expandBtnSize: 20,
|
||||
// 节点里图片和文字的间距
|
||||
@@ -81,7 +79,7 @@ export const defaultOpt = {
|
||||
enableShortcutOnlyWhenMouseInSvg: true,
|
||||
// 初始根节点的位置
|
||||
initRootNodePosition: null,
|
||||
// 导出png、svg、pdf时的图形内边距
|
||||
// 导出png、svg、pdf时的图形内边距,注意是单侧内边距
|
||||
exportPaddingX: 10,
|
||||
exportPaddingY: 10,
|
||||
// 节点文本编辑框的z-index
|
||||
@@ -126,5 +124,47 @@ export const defaultOpt = {
|
||||
// 指定内部一些元素(节点文本编辑元素、节点备注显示元素、关联线文本编辑元素、节点图片调整按钮元素)添加到的位置,默认添加到document.body下
|
||||
customInnerElsAppendTo: null,
|
||||
// 拖拽元素时,指示元素新位置的块的最大高度
|
||||
nodeDragPlaceholderMaxSize: 20
|
||||
nodeDragPlaceholderMaxSize: 20,
|
||||
// 是否在存在一个激活节点时,当按下中文、英文、数字按键时自动进入文本编辑模式
|
||||
// 开启该特性后,需要给你的输入框绑定keydown事件,并禁止冒泡
|
||||
enableAutoEnterTextEditWhenKeydown: false,
|
||||
// 设置富文本节点编辑框和节点大小一致,形成伪原地编辑的效果
|
||||
// 需要注意的是,只有当节点内只有文本、且形状是矩形才会有比较好的效果
|
||||
richTextEditFakeInPlace: false,
|
||||
// 自定义对剪贴板文本的处理。当按ctrl+v粘贴时会读取用户剪贴板中的文本和图片,默认只会判断文本是否是普通文本和simple-mind-map格式的节点数据,如果你想处理其他思维导图的数据,比如processon、zhixi等,那么可以传递一个函数,接受当前剪贴板中的文本为参数,返回处理后的数据,可以返回两种类型:
|
||||
/*
|
||||
1.返回一个纯文本,那么会直接以该文本创建一个子节点
|
||||
|
||||
2.返回一个节点对象,格式如下:
|
||||
{
|
||||
// 代表是simple-mind-map格式的数据
|
||||
simpleMindMap: true,
|
||||
// 节点数据,同simple-mind-map节点数据格式
|
||||
data: {
|
||||
data: {
|
||||
text: ''
|
||||
},
|
||||
children: []
|
||||
}
|
||||
}
|
||||
*/
|
||||
// 如果你的处理逻辑存在异步逻辑,也可以返回一个promise
|
||||
customHandleClipboardText: null,
|
||||
// 禁止鼠标滚轮缩放,你仍旧可以使用api进行缩放
|
||||
disableMouseWheelZoom: false,
|
||||
// 错误处理函数
|
||||
errorHandler: (code, error) => {
|
||||
console.error(code, error)
|
||||
},
|
||||
// 设置导出图片和svg时,针对富文本节点内容,也就是嵌入到svg中的html节点的默认样式覆盖
|
||||
// 如果不覆盖,会发生偏移问题
|
||||
resetCss: `
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`,
|
||||
// 开启鼠标双击复位思维导图位置及缩放
|
||||
enableDblclickReset: true
|
||||
}
|
||||
|
||||
@@ -108,6 +108,11 @@ export default class KeyCommand {
|
||||
return arr
|
||||
}
|
||||
|
||||
// 判断是否按下了组合键
|
||||
hasCombinationKey(e) {
|
||||
return e.ctrlKey || e.metaKey || e.altKey || e.shiftKey
|
||||
}
|
||||
|
||||
// 获取快捷键对应的键值数组
|
||||
getKeyCodeArr(key) {
|
||||
let keyArr = key.split(/\s*\+\s*/)
|
||||
|
||||
@@ -12,11 +12,12 @@ import {
|
||||
simpleDeepClone,
|
||||
walk,
|
||||
bfsWalk,
|
||||
loadImage
|
||||
loadImage,
|
||||
isUndef
|
||||
} from '../../utils'
|
||||
import { shapeList } from './node/Shape'
|
||||
import { lineStyleProps } from '../../themes/default'
|
||||
import { CONSTANTS } from '../../constants/constant'
|
||||
import { CONSTANTS, ERROR_TYPES } from '../../constants/constant'
|
||||
|
||||
// 布局列表
|
||||
const layouts = {
|
||||
@@ -48,7 +49,7 @@ class Render {
|
||||
this.themeConfig = this.mindMap.themeConfig
|
||||
this.draw = this.mindMap.draw
|
||||
// 渲染树,操作过程中修改的都是这里的数据
|
||||
this.renderTree = merge({},this.mindMap.opt.data || {})
|
||||
this.renderTree = merge({}, this.mindMap.opt.data || {})
|
||||
// 是否重新渲染
|
||||
this.reRender = false
|
||||
// 是否正在渲染中
|
||||
@@ -108,10 +109,13 @@ class Render {
|
||||
this.mindMap.execCommand('CLEAR_ACTIVE_NODE')
|
||||
}
|
||||
})
|
||||
// 粘贴事件
|
||||
this.mindMap.on('paste', data => {
|
||||
this.onPaste(data)
|
||||
})
|
||||
// let timer = null
|
||||
// this.mindMap.on('view_data_change', () => {
|
||||
// clearTimeout(timer)
|
||||
// timer = setTimeout(() => {
|
||||
// this.render()
|
||||
// }, 300)
|
||||
// })
|
||||
}
|
||||
|
||||
// 注册命令
|
||||
@@ -266,8 +270,7 @@ class Render {
|
||||
this.copy = this.copy.bind(this)
|
||||
this.mindMap.keyCommand.addShortcut('Control+c', this.copy)
|
||||
this.mindMap.keyCommand.addShortcut('Control+v', () => {
|
||||
// 隐藏输入框可能会失去焦点,所以要重新聚焦
|
||||
this.textEdit.focusHiddenInput()
|
||||
this.onPaste()
|
||||
})
|
||||
this.cut = this.cut.bind(this)
|
||||
this.mindMap.keyCommand.addShortcut('Control+x', this.cut)
|
||||
@@ -442,7 +445,12 @@ class Render {
|
||||
}
|
||||
|
||||
// 插入同级节点,多个节点只会操作第一个节点
|
||||
insertNode(openEdit = true, appointNodes = [], appointData = null) {
|
||||
insertNode(
|
||||
openEdit = true,
|
||||
appointNodes = [],
|
||||
appointData = null,
|
||||
appointChildren = []
|
||||
) {
|
||||
appointNodes = this.formatAppointNodes(appointNodes)
|
||||
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
|
||||
return
|
||||
@@ -478,14 +486,19 @@ class Render {
|
||||
resetRichText: isRichText,
|
||||
...(appointData || {})
|
||||
},
|
||||
children: []
|
||||
children: [...appointChildren]
|
||||
})
|
||||
this.mindMap.render()
|
||||
}
|
||||
}
|
||||
|
||||
// 插入子节点
|
||||
insertChildNode(openEdit = true, appointNodes = [], appointData = null) {
|
||||
insertChildNode(
|
||||
openEdit = true,
|
||||
appointNodes = [],
|
||||
appointData = null,
|
||||
appointChildren = []
|
||||
) {
|
||||
appointNodes = this.formatAppointNodes(appointNodes)
|
||||
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
|
||||
return
|
||||
@@ -516,7 +529,7 @@ class Render {
|
||||
resetRichText: isRichText,
|
||||
...(appointData || {})
|
||||
},
|
||||
children: []
|
||||
children: [...appointChildren]
|
||||
})
|
||||
// 插入子节点时自动展开子节点
|
||||
node.nodeData.data.expand = true
|
||||
@@ -584,15 +597,29 @@ class Render {
|
||||
// 复制节点
|
||||
copy() {
|
||||
this.beingCopyData = this.copyNode()
|
||||
this.setCoptyDataToClipboard(this.beingCopyData)
|
||||
}
|
||||
|
||||
// 剪切节点
|
||||
cut() {
|
||||
this.mindMap.execCommand('CUT_NODE', copyData => {
|
||||
this.beingCopyData = copyData
|
||||
this.setCoptyDataToClipboard(copyData)
|
||||
})
|
||||
}
|
||||
|
||||
// 将粘贴或剪切的数据设置到用户剪切板中
|
||||
setCoptyDataToClipboard(data) {
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(
|
||||
JSON.stringify({
|
||||
simpleMindMap: true,
|
||||
data
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 粘贴节点
|
||||
paste() {
|
||||
if (this.beingCopyData) {
|
||||
@@ -601,7 +628,29 @@ class Render {
|
||||
}
|
||||
|
||||
// 粘贴事件
|
||||
async onPaste({ text, img }) {
|
||||
async onPaste() {
|
||||
const { errorHandler } = this.mindMap.opt
|
||||
// 读取剪贴板的文字和图片
|
||||
let text = null
|
||||
let img = null
|
||||
if (navigator.clipboard) {
|
||||
try {
|
||||
text = await navigator.clipboard.readText()
|
||||
const items = await navigator.clipboard.read()
|
||||
if (items && items.length > 0) {
|
||||
for (const clipboardItem of items) {
|
||||
for (const type of clipboardItem.types) {
|
||||
if (/^image\//.test(type)) {
|
||||
img = await clipboardItem.getType(type)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
errorHandler(ERROR_TYPES.READ_CLIPBOARD_ERROR, error)
|
||||
}
|
||||
}
|
||||
// 检查剪切板数据是否有变化
|
||||
// 通过图片大小来判断图片是否发生变化,可能是不准确的,但是目前没有其他好方法
|
||||
const imgSize = img ? img.size : 0
|
||||
@@ -619,9 +668,51 @@ class Render {
|
||||
if (this.currentBeingPasteType === CONSTANTS.PASTE_TYPE.CLIP_BOARD) {
|
||||
// 存在文本,则创建子节点
|
||||
if (text) {
|
||||
this.mindMap.execCommand('INSERT_CHILD_NODE', false, [], {
|
||||
text
|
||||
})
|
||||
// 判断粘贴的是否是simple-mind-map的数据
|
||||
let smmData = null
|
||||
let useDefault = true
|
||||
// 用户自定义处理
|
||||
if (this.mindMap.opt.customHandleClipboardText) {
|
||||
try {
|
||||
const res = await this.mindMap.opt.customHandleClipboardText(text)
|
||||
if (!isUndef(res)) {
|
||||
useDefault = false
|
||||
if (typeof res === 'object' && res.simpleMindMap) {
|
||||
smmData = res.data
|
||||
} else {
|
||||
text = String(res)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
errorHandler(ERROR_TYPES.CUSTOM_HANDLE_CLIPBOARD_TEXT_ERROR, error)
|
||||
}
|
||||
}
|
||||
// 默认处理
|
||||
if (useDefault) {
|
||||
try {
|
||||
const parsedData = JSON.parse(text)
|
||||
if (parsedData && parsedData.simpleMindMap) {
|
||||
smmData = parsedData.data
|
||||
}
|
||||
} catch (error) {
|
||||
errorHandler(ERROR_TYPES.PARSE_PASTE_DATA_ERROR, error)
|
||||
}
|
||||
}
|
||||
if (smmData) {
|
||||
this.mindMap.execCommand(
|
||||
'INSERT_CHILD_NODE',
|
||||
false,
|
||||
[],
|
||||
{
|
||||
...smmData.data
|
||||
},
|
||||
[...smmData.children]
|
||||
)
|
||||
} else {
|
||||
this.mindMap.execCommand('INSERT_CHILD_NODE', false, [], {
|
||||
text
|
||||
})
|
||||
}
|
||||
}
|
||||
// 存在图片,则添加到当前激活节点
|
||||
if (img) {
|
||||
@@ -638,7 +729,7 @@ class Render {
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
errorHandler(ERROR_TYPES.LOAD_CLIPBOARD_IMAGE_ERROR, error)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -888,7 +979,7 @@ class Render {
|
||||
// 更新了连线的样式
|
||||
let props = Object.keys(style)
|
||||
let hasLineStyleProps = false
|
||||
props.forEach((key) => {
|
||||
props.forEach(key => {
|
||||
if (lineStyleProps.includes(key)) {
|
||||
hasLineStyleProps = true
|
||||
}
|
||||
@@ -1020,7 +1111,8 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置节点图片
|
||||
setNodeImage(node, { url, title, width, height, custom = false }) {
|
||||
setNodeImage(node, data) {
|
||||
const { url, title, width, height, custom = false } = data || { url: '', title: '', width: 0, height: 0, custom: false }
|
||||
this.setNodeDataRender(node, {
|
||||
image: url,
|
||||
imageTitle: title || '',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getStrWithBrFromHtml, checkNodeOuter } from '../../utils'
|
||||
import { ERROR_TYPES } from '../../constants/constant'
|
||||
|
||||
// 节点文字编辑类
|
||||
export default class TextEdit {
|
||||
@@ -10,16 +11,11 @@ export default class TextEdit {
|
||||
this.currentNode = null
|
||||
// 文本编辑框
|
||||
this.textEditNode = null
|
||||
// 隐藏的文本输入框
|
||||
this.hiddenInputEl = null
|
||||
// 节点激活时默认聚焦到隐藏输入框
|
||||
this.enableFocus = true
|
||||
// 文本编辑框是否显示
|
||||
this.showTextEdit = false
|
||||
// 如果编辑过程中缩放画布了,那么缓存当前编辑的内容
|
||||
this.cacheEditingText = ''
|
||||
this.bindEvent()
|
||||
this.createHiddenInput()
|
||||
}
|
||||
|
||||
// 事件
|
||||
@@ -51,10 +47,6 @@ export default class TextEdit {
|
||||
this.mindMap.on('before_node_active', () => {
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
// 节点激活事件
|
||||
this.mindMap.on('node_active', () => {
|
||||
this.focusHiddenInput()
|
||||
})
|
||||
// 注册编辑快捷键
|
||||
this.mindMap.keyCommand.addShortcut('F2', () => {
|
||||
if (this.renderer.activeNodeList.length <= 0) {
|
||||
@@ -63,53 +55,29 @@ export default class TextEdit {
|
||||
this.show(this.renderer.activeNodeList[0])
|
||||
})
|
||||
this.mindMap.on('scale', this.onScale)
|
||||
}
|
||||
|
||||
// 创建一个隐藏的文本输入框
|
||||
createHiddenInput() {
|
||||
if (this.hiddenInputEl) return
|
||||
this.hiddenInputEl = document.createElement('input')
|
||||
this.hiddenInputEl.type = 'text'
|
||||
this.hiddenInputEl.style.cssText = `
|
||||
position: fixed;
|
||||
left: -99999px;
|
||||
top: -99999px;
|
||||
`
|
||||
// 监听粘贴事件
|
||||
this.hiddenInputEl.addEventListener('paste', async event => {
|
||||
event.preventDefault()
|
||||
const text = (event.clipboardData || window.clipboardData).getData('text')
|
||||
const files = event.clipboardData.files
|
||||
let img = null
|
||||
if (files.length > 0) {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
if (/^image\//.test(files[i].type)) {
|
||||
img = files[i]
|
||||
break
|
||||
}
|
||||
// // 监听按键事件,判断是否自动进入文本编辑模式
|
||||
if (this.mindMap.opt.enableAutoEnterTextEditWhenKeydown) {
|
||||
window.addEventListener('keydown', e => {
|
||||
const activeNodeList = this.mindMap.renderer.activeNodeList
|
||||
if (activeNodeList.length <= 0 || activeNodeList.length > 1) return
|
||||
const node = activeNodeList[0]
|
||||
// 当正在输入中文或英文或数字时,如果没有按下组合键,那么自动进入文本编辑模式
|
||||
if (node && this.checkIsAutoEnterTextEditKey(e)) {
|
||||
this.show(node)
|
||||
}
|
||||
}
|
||||
this.mindMap.emit('paste', {
|
||||
text,
|
||||
img
|
||||
})
|
||||
})
|
||||
document.body.appendChild(this.hiddenInputEl)
|
||||
}
|
||||
}
|
||||
|
||||
// 让隐藏的文本输入框聚焦
|
||||
focusHiddenInput() {
|
||||
if (this.hiddenInputEl && this.enableFocus) this.hiddenInputEl.focus()
|
||||
}
|
||||
|
||||
// 关闭默认聚焦
|
||||
stopFocusOnNodeActive() {
|
||||
this.enableFocus = false
|
||||
}
|
||||
|
||||
// 开启默认聚焦
|
||||
openFocusOnNodeActive() {
|
||||
this.enableFocus = true
|
||||
// 判断是否是自动进入文本编模式的按钮
|
||||
checkIsAutoEnterTextEditKey(e) {
|
||||
const keyCode = e.keyCode
|
||||
return (
|
||||
(keyCode === 229 ||
|
||||
(keyCode >= 65 && keyCode <= 90) ||
|
||||
(keyCode >= 48 && keyCode <= 57)) &&
|
||||
!this.mindMap.keyCommand.hasCombinationKey(e)
|
||||
)
|
||||
}
|
||||
|
||||
// 注册临时快捷键
|
||||
@@ -137,6 +105,7 @@ export default class TextEdit {
|
||||
isShow = await beforeTextEdit(node, isInserting)
|
||||
} catch (error) {
|
||||
isShow = false
|
||||
this.mindMap.opt.errorHandler(ERROR_TYPES.BEFORE_TEXT_EDIT_ERROR, error)
|
||||
}
|
||||
if (!isShow) return
|
||||
}
|
||||
@@ -148,7 +117,7 @@ export default class TextEdit {
|
||||
this.mindMap.richText.showEditText(node, rect, isInserting)
|
||||
return
|
||||
}
|
||||
this.showEditTextBox(node, rect)
|
||||
this.showEditTextBox(node, rect, isInserting)
|
||||
}
|
||||
|
||||
// 处理画布缩放
|
||||
@@ -166,7 +135,8 @@ export default class TextEdit {
|
||||
}
|
||||
|
||||
// 显示文本编辑框
|
||||
showEditTextBox(node, rect) {
|
||||
showEditTextBox(node, rect, isInserting) {
|
||||
if (this.showTextEdit) return
|
||||
this.mindMap.emit('before_show_text_edit')
|
||||
this.registerTmpShortcut()
|
||||
if (!this.textEditNode) {
|
||||
@@ -179,10 +149,16 @@ export default class TextEdit {
|
||||
this.textEditNode.addEventListener('click', e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
this.textEditNode.addEventListener('mousedown', (e) => {
|
||||
this.textEditNode.addEventListener('mousedown', e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
const targetNode = this.mindMap.opt.customInnerElsAppendTo || document.body
|
||||
this.textEditNode.addEventListener('keydown', e => {
|
||||
if (this.checkIsAutoEnterTextEditKey(e)) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
})
|
||||
const targetNode =
|
||||
this.mindMap.opt.customInnerElsAppendTo || document.body
|
||||
targetNode.appendChild(this.textEditNode)
|
||||
}
|
||||
let scale = this.mindMap.view.scale
|
||||
@@ -209,12 +185,27 @@ export default class TextEdit {
|
||||
}
|
||||
this.showTextEdit = true
|
||||
// 选中文本
|
||||
if (!this.cacheEditingText) {
|
||||
// if (!this.cacheEditingText) {
|
||||
// this.selectNodeText()
|
||||
// }
|
||||
if (isInserting) {
|
||||
this.selectNodeText()
|
||||
} else {
|
||||
this.focus()
|
||||
}
|
||||
this.cacheEditingText = ''
|
||||
}
|
||||
|
||||
// 聚焦
|
||||
focus() {
|
||||
let selection = window.getSelection()
|
||||
let range = document.createRange()
|
||||
range.selectNodeContents(this.textEditNode)
|
||||
range.collapse()
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
}
|
||||
|
||||
// 选中文本
|
||||
selectNodeText() {
|
||||
let selection = window.getSelection()
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import Style from './Style'
|
||||
import Shape from './Shape'
|
||||
import { asyncRun, nodeToHTML } from '../../../utils'
|
||||
import { G, Rect, ForeignObject, SVG } from '@svgdotjs/svg.js'
|
||||
import { G, ForeignObject, SVG } from '@svgdotjs/svg.js'
|
||||
import nodeGeneralizationMethods from './nodeGeneralization'
|
||||
import nodeExpandBtnMethods from './nodeExpandBtn'
|
||||
import nodeCommandWrapsMethods from './nodeCommandWraps'
|
||||
import nodeCreateContentsMethods from './nodeCreateContents'
|
||||
import nodeExpandBtnPlaceholderRectMethods from './nodeExpandBtnPlaceholderRect'
|
||||
import { CONSTANTS } from '../../../constants/constant'
|
||||
|
||||
// 节点类
|
||||
@@ -98,6 +98,8 @@ class Node {
|
||||
this.isMultipleChoice = false
|
||||
// 是否需要重新layout
|
||||
this.needLayout = false
|
||||
// 当前是否是隐藏状态
|
||||
this.isHide = false
|
||||
// 概要相关方法
|
||||
Object.keys(nodeGeneralizationMethods).forEach(item => {
|
||||
this[item] = nodeGeneralizationMethods[item].bind(this)
|
||||
@@ -106,6 +108,10 @@ class Node {
|
||||
Object.keys(nodeExpandBtnMethods).forEach(item => {
|
||||
this[item] = nodeExpandBtnMethods[item].bind(this)
|
||||
})
|
||||
// 展开收起按钮占位元素相关方法
|
||||
Object.keys(nodeExpandBtnPlaceholderRectMethods).forEach(item => {
|
||||
this[item] = nodeExpandBtnPlaceholderRectMethods[item].bind(this)
|
||||
})
|
||||
// 命令的相关方法
|
||||
Object.keys(nodeCommandWrapsMethods).forEach(item => {
|
||||
this[item] = nodeCommandWrapsMethods[item].bind(this)
|
||||
@@ -251,9 +257,11 @@ class Node {
|
||||
this.shapeInstance.getShapePadding(_width, _height, paddingX, paddingY)
|
||||
this.shapePadding.paddingX = shapePaddingX
|
||||
this.shapePadding.paddingY = shapePaddingY
|
||||
// 边框宽度,因为边框是以中线向两端发散,所以边框会超出节点
|
||||
const borderWidth = this.getBorderWidth()
|
||||
return {
|
||||
width: _width + paddingX * 2 + shapePaddingX * 2,
|
||||
height: _height + paddingY * 2 + margin + shapePaddingY * 2
|
||||
width: _width + paddingX * 2 + shapePaddingX * 2 + borderWidth,
|
||||
height: _height + paddingY * 2 + margin + shapePaddingY * 2 + borderWidth
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,10 +271,12 @@ class Node {
|
||||
this.group.clear()
|
||||
let { width, height, textContentItemMargin } = this
|
||||
let { paddingY } = this.getPaddingVale()
|
||||
paddingY += this.shapePadding.paddingY
|
||||
const halfBorderWidth = this.getBorderWidth() / 2
|
||||
paddingY += this.shapePadding.paddingY + halfBorderWidth
|
||||
// 节点形状
|
||||
this.shapeNode = this.shapeInstance.createShape()
|
||||
this.shapeNode.addClass('smm-node-shape')
|
||||
this.shapeNode.translate(halfBorderWidth, halfBorderWidth)
|
||||
this.group.add(this.shapeNode)
|
||||
this.updateNodeShape()
|
||||
// 渲染一个隐藏的矩形区域,用来触发展开收起按钮的显示
|
||||
@@ -357,21 +367,6 @@ class Node {
|
||||
this.group.add(textContentNested)
|
||||
}
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
renderExpandBtnPlaceholderRect() {
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
let { width, height } = this
|
||||
if (!this._unVisibleRectRegionNode) {
|
||||
this._unVisibleRectRegionNode = new Rect()
|
||||
this._unVisibleRectRegionNode.fill({
|
||||
color: 'transparent'
|
||||
})
|
||||
}
|
||||
this.group.add(this._unVisibleRectRegionNode)
|
||||
this.renderer.layout.renderExpandBtnRect(this._unVisibleRectRegionNode, this.expandBtnSize, width, height, this)
|
||||
}
|
||||
}
|
||||
|
||||
// 给节点绑定事件
|
||||
bindGroupEvent() {
|
||||
// 单击事件,选中节点
|
||||
@@ -388,7 +383,7 @@ class Node {
|
||||
if (this.isRoot && e.which === 3 && !this.mindMap.opt.readonly) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
if (!this.isRoot && !this.mindMap.opt.readonly) {
|
||||
if (!this.isRoot && e.which !== 2 && !this.mindMap.opt.readonly) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
// 多选和取消多选
|
||||
@@ -414,7 +409,7 @@ class Node {
|
||||
this.mindMap.emit('node_mousedown', this, e)
|
||||
})
|
||||
this.group.on('mouseup', e => {
|
||||
if (!this.isRoot && !this.mindMap.opt.readonly) {
|
||||
if (!this.isRoot && e.which !== 2 && !this.mindMap.opt.readonly) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
this.mindMap.emit('node_mouseup', this, e)
|
||||
@@ -476,9 +471,7 @@ class Node {
|
||||
if (!this.group) {
|
||||
return
|
||||
}
|
||||
let {
|
||||
alwaysShowExpandBtn
|
||||
} = this.mindMap.opt
|
||||
let { alwaysShowExpandBtn } = this.mindMap.opt
|
||||
if (alwaysShowExpandBtn) {
|
||||
// 需要移除展开收缩按钮
|
||||
if (this._expandBtn && this.nodeData.children.length <= 0) {
|
||||
@@ -500,11 +493,48 @@ class Node {
|
||||
this.renderGeneralization()
|
||||
// 更新节点位置
|
||||
let t = this.group.transform()
|
||||
// // 如果上次不在可视区内,且本次也不在,那么直接返回
|
||||
// let { left: ox, top: oy } = this.getNodePosInClient(
|
||||
// t.translateX,
|
||||
// t.translateY
|
||||
// )
|
||||
// let oldIsInClient =
|
||||
// ox > 0 && oy > 0 && ox < this.mindMap.width && oy < this.mindMap.height
|
||||
// let { left: nx, top: ny } = this.getNodePosInClient(this.left, this.top)
|
||||
// let newIsNotInClient =
|
||||
// nx + this.width < 0 ||
|
||||
// ny + this.height < 0 ||
|
||||
// nx > this.mindMap.width ||
|
||||
// ny > this.mindMap.height
|
||||
// if (!oldIsInClient && newIsNotInClient) {
|
||||
// if (!this.isHide) {
|
||||
// this.isHide = true
|
||||
// this.group.hide()
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
// // 如果当前是隐藏状态,那么先显示
|
||||
// if (this.isHide) {
|
||||
// this.isHide = false
|
||||
// this.group.show()
|
||||
// }
|
||||
// 如果节点位置没有变化,则返回
|
||||
if (this.left === t.translateX && this.top === t.translateY) return
|
||||
this.group.translate(this.left - t.translateX, this.top - t.translateY)
|
||||
}
|
||||
|
||||
// 获取节点相当于画布的位置
|
||||
getNodePosInClient(_left, _top) {
|
||||
let drawTransform = this.mindMap.draw.transform()
|
||||
let { scaleX, scaleY, translateX, translateY } = drawTransform
|
||||
let left = _left * scaleX + translateX
|
||||
let top = _top * scaleY + translateY
|
||||
return {
|
||||
left,
|
||||
top
|
||||
}
|
||||
}
|
||||
|
||||
// 重新渲染节点,即重新创建节点内容、计算节点大小、计算节点内容布局、更新展开收起按钮,概要及位置
|
||||
reRender() {
|
||||
let sizeChange = this.getSize()
|
||||
@@ -546,10 +576,7 @@ class Node {
|
||||
this.needLayout = false
|
||||
this.layout()
|
||||
}
|
||||
if (this.needRerenderExpandBtnPlaceholderRect) {
|
||||
this.needRerenderExpandBtnPlaceholderRect = false
|
||||
this.renderExpandBtnPlaceholderRect()
|
||||
}
|
||||
this.updateExpandBtnPlaceholderRect()
|
||||
this.update()
|
||||
}
|
||||
// 子节点
|
||||
@@ -559,18 +586,14 @@ class Node {
|
||||
this.nodeData.data.expand !== false
|
||||
) {
|
||||
let index = 0
|
||||
asyncRun(
|
||||
this.children.map(item => {
|
||||
return () => {
|
||||
item.render(() => {
|
||||
index++
|
||||
if (index >= this.children.length) {
|
||||
callback()
|
||||
}
|
||||
})
|
||||
this.children.forEach(item => {
|
||||
item.render(() => {
|
||||
index++
|
||||
if (index >= this.children.length) {
|
||||
callback()
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
@@ -592,13 +615,9 @@ class Node {
|
||||
this.removeLine()
|
||||
// 子节点
|
||||
if (this.children && this.children.length) {
|
||||
asyncRun(
|
||||
this.children.map(item => {
|
||||
return () => {
|
||||
item.remove()
|
||||
}
|
||||
})
|
||||
)
|
||||
this.children.forEach(item => {
|
||||
item.remove()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -624,13 +643,9 @@ class Node {
|
||||
}
|
||||
// 子节点
|
||||
if (this.children && this.children.length) {
|
||||
asyncRun(
|
||||
this.children.map(item => {
|
||||
return () => {
|
||||
item.hide()
|
||||
}
|
||||
})
|
||||
)
|
||||
this.children.forEach(item => {
|
||||
item.hide()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -650,13 +665,9 @@ class Node {
|
||||
}
|
||||
// 子节点
|
||||
if (this.children && this.children.length) {
|
||||
asyncRun(
|
||||
this.children.map(item => {
|
||||
return () => {
|
||||
item.show()
|
||||
}
|
||||
})
|
||||
)
|
||||
this.children.forEach(item => {
|
||||
item.show()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -820,6 +831,11 @@ class Node {
|
||||
) // 父级
|
||||
}
|
||||
|
||||
// 获取节点非节点状态的边框大小
|
||||
getBorderWidth() {
|
||||
return this.style.merge('borderWidth', false, false) || 0
|
||||
}
|
||||
|
||||
// 获取数据
|
||||
getData(key) {
|
||||
return key ? this.nodeData.data[key] || '' : this.nodeData.data
|
||||
|
||||
@@ -62,7 +62,10 @@ export default class Shape {
|
||||
// 创建形状节点
|
||||
createShape() {
|
||||
const shape = this.node.getShape()
|
||||
const borderWidth = this.node.getBorderWidth()
|
||||
let { width, height } = this.node
|
||||
width -= borderWidth
|
||||
height -= borderWidth
|
||||
let node = null
|
||||
// 矩形
|
||||
if (shape === CONSTANTS.SHAPE.RECTANGLE) {
|
||||
|
||||
@@ -17,7 +17,7 @@ class Style {
|
||||
// 设置新样式
|
||||
let { backgroundColor, backgroundImage, backgroundRepeat, backgroundPosition, backgroundSize } = themeConfig
|
||||
el.style.backgroundColor = backgroundColor
|
||||
if (backgroundImage) {
|
||||
if (backgroundImage && backgroundImage !== 'none') {
|
||||
el.style.backgroundImage = `url(${backgroundImage})`
|
||||
el.style.backgroundRepeat = backgroundRepeat
|
||||
el.style.backgroundPosition = backgroundPosition
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { measureText, resizeImgSize, removeHtmlStyle, addHtmlStyle, checkIsRichText } from '../../../utils'
|
||||
import {
|
||||
measureText,
|
||||
resizeImgSize,
|
||||
removeHtmlStyle,
|
||||
addHtmlStyle,
|
||||
checkIsRichText
|
||||
} from '../../../utils'
|
||||
import { Image, SVG, A, G, Rect, Text, ForeignObject } from '@svgdotjs/svg.js'
|
||||
import iconsSvg from '../../../svg/icons'
|
||||
import { CONSTANTS, commonCaches } from '../../../constants/constant'
|
||||
@@ -54,7 +60,10 @@ function createIconNode() {
|
||||
}
|
||||
let iconSize = this.mindMap.themeConfig.iconSize
|
||||
return _data.icon.map(item => {
|
||||
let src = iconsSvg.getNodeIconListIcon(item, this.mindMap.opt.iconList || [])
|
||||
let src = iconsSvg.getNodeIconListIcon(
|
||||
item,
|
||||
this.mindMap.opt.iconList || []
|
||||
)
|
||||
let node = null
|
||||
// svg图标
|
||||
if (/^<svg/.test(src)) {
|
||||
@@ -77,6 +86,7 @@ function createIconNode() {
|
||||
|
||||
// 创建富文本节点
|
||||
function createRichTextNode() {
|
||||
const { textAutoWrapWidth } = this.mindMap.opt
|
||||
let g = new G()
|
||||
// 重新设置富文本节点内容
|
||||
let recoverText = false
|
||||
@@ -108,25 +118,34 @@ function createRichTextNode() {
|
||||
this.nodeData.data.text = text
|
||||
}
|
||||
let html = `<div>${this.nodeData.data.text}</div>`
|
||||
let div = document.createElement('div')
|
||||
if (!commonCaches.measureRichtextNodeTextSizeEl) {
|
||||
commonCaches.measureRichtextNodeTextSizeEl = document.createElement('div')
|
||||
commonCaches.measureRichtextNodeTextSizeEl.style.position = 'fixed'
|
||||
commonCaches.measureRichtextNodeTextSizeEl.style.left = '-999999px'
|
||||
this.mindMap.el.appendChild(commonCaches.measureRichtextNodeTextSizeEl)
|
||||
}
|
||||
let div = commonCaches.measureRichtextNodeTextSizeEl
|
||||
div.innerHTML = html
|
||||
div.style.cssText = `position: fixed; left: -999999px;`
|
||||
let el = div.children[0]
|
||||
el.classList.add('smm-richtext-node-wrap')
|
||||
el.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml')
|
||||
el.style.maxWidth = this.mindMap.opt.textAutoWrapWidth + 'px'
|
||||
this.mindMap.el.appendChild(div)
|
||||
el.style.maxWidth = textAutoWrapWidth + 'px'
|
||||
let { width, height } = el.getBoundingClientRect()
|
||||
width = Math.ceil(width) + 1// 修复getBoundingClientRect方法对实际宽度是小数的元素获取到的值是整数,导致宽度不够文本发生换行的问题
|
||||
// 如果文本为空,那么需要计算一个默认高度
|
||||
if (height <= 0) {
|
||||
div.innerHTML = '<p>abc123我和你</p>'
|
||||
let elTmp = div.children[0]
|
||||
elTmp.classList.add('smm-richtext-node-wrap')
|
||||
height = elTmp.getBoundingClientRect().height
|
||||
}
|
||||
width = Math.ceil(width) + 1 // 修复getBoundingClientRect方法对实际宽度是小数的元素获取到的值是整数,导致宽度不够文本发生换行的问题
|
||||
height = Math.ceil(height)
|
||||
g.attr('data-width', width)
|
||||
g.attr('data-height', height)
|
||||
html = div.innerHTML
|
||||
this.mindMap.el.removeChild(div)
|
||||
let foreignObject = new ForeignObject()
|
||||
foreignObject.width(width)
|
||||
foreignObject.height(height)
|
||||
foreignObject.add(SVG(html))
|
||||
foreignObject.add(div.children[0])
|
||||
g.add(foreignObject)
|
||||
return {
|
||||
node: g,
|
||||
@@ -274,9 +293,10 @@ function createNoteNode() {
|
||||
box-shadow: 0 2px 5px rgb(0 0 0 / 10%);
|
||||
display: none;
|
||||
background-color: #fff;
|
||||
z-index: ${ this.mindMap.opt.nodeNoteTooltipZIndex }
|
||||
z-index: ${this.mindMap.opt.nodeNoteTooltipZIndex}
|
||||
`
|
||||
const targetNode = this.mindMap.opt.customInnerElsAppendTo || document.body
|
||||
const targetNode =
|
||||
this.mindMap.opt.customInnerElsAppendTo || document.body
|
||||
targetNode.appendChild(this.noteEl)
|
||||
}
|
||||
this.noteEl.innerText = this.nodeData.data.note
|
||||
@@ -310,7 +330,7 @@ function createNoteNode() {
|
||||
}
|
||||
|
||||
// 测量自定义节点内容元素的宽高
|
||||
function measureCustomNodeContentSize (content) {
|
||||
function measureCustomNodeContentSize(content) {
|
||||
if (!commonCaches.measureCustomNodeContentSizeEl) {
|
||||
commonCaches.measureCustomNodeContentSizeEl = document.createElement('div')
|
||||
commonCaches.measureCustomNodeContentSizeEl.style.cssText = `
|
||||
@@ -330,19 +350,19 @@ function measureCustomNodeContentSize (content) {
|
||||
}
|
||||
|
||||
// 是否使用的是自定义节点内容
|
||||
function isUseCustomNodeContent() {
|
||||
function isUseCustomNodeContent() {
|
||||
return !!this._customNodeContent
|
||||
}
|
||||
|
||||
export default {
|
||||
createImgNode,
|
||||
getImgShowSize,
|
||||
createIconNode,
|
||||
createRichTextNode,
|
||||
createTextNode,
|
||||
createHyperlinkNode,
|
||||
createTagNode,
|
||||
createNoteNode,
|
||||
measureCustomNodeContentSize,
|
||||
isUseCustomNodeContent
|
||||
}
|
||||
createImgNode,
|
||||
getImgShowSize,
|
||||
createIconNode,
|
||||
createRichTextNode,
|
||||
createTextNode,
|
||||
createHyperlinkNode,
|
||||
createTagNode,
|
||||
createNoteNode,
|
||||
measureCustomNodeContentSize,
|
||||
isUseCustomNodeContent
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Rect } from '@svgdotjs/svg.js'
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
function renderExpandBtnPlaceholderRect() {
|
||||
// 根节点或没有子节点不需要渲染
|
||||
if (
|
||||
!this.nodeData.children ||
|
||||
this.nodeData.children.length <= 0 ||
|
||||
this.isRoot
|
||||
) {
|
||||
return
|
||||
}
|
||||
// 默认显示展开按钮的情况下也不需要渲染
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
let { width, height } = this
|
||||
if (!this._unVisibleRectRegionNode) {
|
||||
this._unVisibleRectRegionNode = new Rect()
|
||||
this._unVisibleRectRegionNode.fill({
|
||||
color: 'transparent'
|
||||
})
|
||||
}
|
||||
this.group.add(this._unVisibleRectRegionNode)
|
||||
this.renderer.layout.renderExpandBtnRect(
|
||||
this._unVisibleRectRegionNode,
|
||||
this.expandBtnSize,
|
||||
width,
|
||||
height,
|
||||
this
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 删除展开收起按钮的隐藏占位元素
|
||||
function clearExpandBtnPlaceholderRect() {
|
||||
if (!this._unVisibleRectRegionNode) {
|
||||
return
|
||||
}
|
||||
this._unVisibleRectRegionNode.remove()
|
||||
this._unVisibleRectRegionNode = null
|
||||
}
|
||||
|
||||
// 更新展开收起按钮的隐藏占位元素
|
||||
function updateExpandBtnPlaceholderRect() {
|
||||
// 布局改变需要重新渲染
|
||||
if (this.needRerenderExpandBtnPlaceholderRect) {
|
||||
this.needRerenderExpandBtnPlaceholderRect = false
|
||||
this.renderExpandBtnPlaceholderRect()
|
||||
}
|
||||
// 没有子节点到有子节点需要渲染
|
||||
if (this.nodeData.children && this.nodeData.children.length > 0) {
|
||||
if (!this._unVisibleRectRegionNode) {
|
||||
this.renderExpandBtnPlaceholderRect()
|
||||
}
|
||||
} else {
|
||||
// 有子节点到没子节点,需要删除
|
||||
if (this._unVisibleRectRegionNode) {
|
||||
this.clearExpandBtnPlaceholderRect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
renderExpandBtnPlaceholderRect,
|
||||
clearExpandBtnPlaceholderRect,
|
||||
updateExpandBtnPlaceholderRect
|
||||
}
|
||||
@@ -32,6 +32,7 @@ class View {
|
||||
this.fit()
|
||||
})
|
||||
this.mindMap.svg.on('dblclick', () => {
|
||||
if (!this.mindMap.opt.enableDblclickReset) return
|
||||
this.reset()
|
||||
})
|
||||
// 拖动视图
|
||||
@@ -65,7 +66,8 @@ class View {
|
||||
mousewheelAction,
|
||||
mouseScaleCenterUseMousePosition,
|
||||
mousewheelMoveStep,
|
||||
mousewheelZoomActionReverse
|
||||
mousewheelZoomActionReverse,
|
||||
disableMouseWheelZoom
|
||||
} = this.mindMap.opt
|
||||
// 是否自定义鼠标滚轮事件
|
||||
if (
|
||||
@@ -76,21 +78,28 @@ class View {
|
||||
}
|
||||
// 鼠标滚轮事件控制缩放
|
||||
if (mousewheelAction === CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM) {
|
||||
let cx = mouseScaleCenterUseMousePosition ? e.clientX : undefined
|
||||
let cy = mouseScaleCenterUseMousePosition ? e.clientY : undefined
|
||||
if (disableMouseWheelZoom) return
|
||||
const { x: clientX, y: clientY } = this.mindMap.toPos(e.clientX, e.clientY)
|
||||
let cx = mouseScaleCenterUseMousePosition ? clientX : undefined
|
||||
let cy = mouseScaleCenterUseMousePosition ? clientY : undefined
|
||||
switch (dir) {
|
||||
// 鼠标滚轮,向上和向左,都是缩小
|
||||
case CONSTANTS.DIR.UP:
|
||||
case CONSTANTS.DIR.LEFT:
|
||||
mousewheelZoomActionReverse ? this.enlarge(cx, cy, isTouchPad) : this.narrow(cx, cy, isTouchPad)
|
||||
mousewheelZoomActionReverse
|
||||
? this.enlarge(cx, cy, isTouchPad)
|
||||
: this.narrow(cx, cy, isTouchPad)
|
||||
break
|
||||
// 鼠标滚轮,向下和向右,都是放大
|
||||
case CONSTANTS.DIR.DOWN:
|
||||
case CONSTANTS.DIR.RIGHT:
|
||||
mousewheelZoomActionReverse ? this.narrow(cx, cy, isTouchPad) : this.enlarge(cx, cy, isTouchPad)
|
||||
mousewheelZoomActionReverse
|
||||
? this.narrow(cx, cy, isTouchPad)
|
||||
: this.enlarge(cx, cy, isTouchPad)
|
||||
break
|
||||
}
|
||||
} else {// 鼠标滚轮事件控制画布移动
|
||||
} else {
|
||||
// 鼠标滚轮事件控制画布移动
|
||||
let step = mousewheelMoveStep
|
||||
if (isTouchPad) {
|
||||
step = 5
|
||||
@@ -147,6 +156,7 @@ class View {
|
||||
|
||||
// 平移x,y方向
|
||||
translateXY(x, y) {
|
||||
if (x === 0 && y === 0) return
|
||||
this.x += x
|
||||
this.y += y
|
||||
this.transform()
|
||||
@@ -154,24 +164,28 @@ class View {
|
||||
|
||||
// 平移x方向
|
||||
translateX(step) {
|
||||
if (step === 0) return
|
||||
this.x += step
|
||||
this.transform()
|
||||
}
|
||||
|
||||
// 平移x方式到
|
||||
translateXTo(x) {
|
||||
if (x === 0) return
|
||||
this.x = x
|
||||
this.transform()
|
||||
}
|
||||
|
||||
// 平移y方向
|
||||
translateY(step) {
|
||||
if (step === 0) return
|
||||
this.y += step
|
||||
this.transform()
|
||||
}
|
||||
|
||||
// 平移y方向到
|
||||
translateYTo(y) {
|
||||
if (y === 0) return
|
||||
this.y = y
|
||||
this.transform()
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ const handleList = node => {
|
||||
}
|
||||
|
||||
// 将markdown转换成节点树
|
||||
export const transformMarkdownTo = async md => {
|
||||
export const transformMarkdownTo = md => {
|
||||
const tree = fromMarkdown(md)
|
||||
let root = {
|
||||
children: []
|
||||
|
||||
@@ -14,11 +14,13 @@ const parseXmindFile = file => {
|
||||
async zip => {
|
||||
try {
|
||||
let content = ''
|
||||
if (zip.files['content.json']) {
|
||||
let json = await zip.files['content.json'].async('string')
|
||||
let jsonFile = zip.files['content.json']
|
||||
let xmlFile = zip.files['content.xml'] || zip.files['/content.xml']
|
||||
if (jsonFile) {
|
||||
let json = await jsonFile.async('string')
|
||||
content = await transformXmind(json, zip.files)
|
||||
} else if (zip.files['content.xml']) {
|
||||
let xml = await zip.files['content.xml'].async('string')
|
||||
} else if (xmlFile) {
|
||||
let xml = await xmlFile.async('string')
|
||||
let json = xmlConvert.xml2json(xml)
|
||||
content = transformOldXmind(json)
|
||||
}
|
||||
@@ -64,13 +66,13 @@ const transformXmind = async (content, files) => {
|
||||
}
|
||||
// 图片
|
||||
if (node.image && /\.(jpg|jpeg|png|gif|webp)$/.test(node.image.src)) {
|
||||
// 处理异步逻辑
|
||||
let resolve = null
|
||||
let promise = new Promise(_resolve => {
|
||||
resolve = _resolve
|
||||
})
|
||||
waitLoadImageList.push(promise)
|
||||
try {
|
||||
// 处理异步逻辑
|
||||
let resolve = null
|
||||
let promise = new Promise(_resolve => {
|
||||
resolve = _resolve
|
||||
})
|
||||
waitLoadImageList.push(promise)
|
||||
// 读取图片
|
||||
let imageType = /\.([^.]+)$/.exec(node.image.src)[1]
|
||||
let imageBase64 =
|
||||
@@ -187,7 +189,7 @@ const transformOldXmind = content => {
|
||||
if (_children && _children.elements && _children.elements.length > 0) {
|
||||
_children.elements.forEach(item => {
|
||||
if (item.name === 'topics') {
|
||||
item.elements.forEach(item2 => {
|
||||
(item.elements || []).forEach(item2 => {
|
||||
let newChild = {}
|
||||
newNode.children.push(newChild)
|
||||
walk(item2, newChild)
|
||||
|
||||
@@ -204,7 +204,7 @@ class Drag extends Base {
|
||||
|
||||
// 检测重叠节点
|
||||
checkOverlapNode() {
|
||||
if (!this.drawTransform) {
|
||||
if (!this.drawTransform || !this.placeholder) {
|
||||
return
|
||||
}
|
||||
const { nodeDragPlaceholderMaxSize } = this.mindMap.opt
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { imgToDataUrl, downloadFile, readBlob, removeHTMLEntities } from '../utils'
|
||||
import {
|
||||
imgToDataUrl,
|
||||
downloadFile,
|
||||
readBlob,
|
||||
removeHTMLEntities
|
||||
} from '../utils'
|
||||
import { SVG } from '@svgdotjs/svg.js'
|
||||
import drawBackgroundImageToCanvas from '../utils/simulateCSSBackgroundInCanvas'
|
||||
import { transformToMarkdown } from '../parse/toMarkdown'
|
||||
@@ -8,7 +13,6 @@ class Export {
|
||||
// 构造函数
|
||||
constructor(opt) {
|
||||
this.mindMap = opt.mindMap
|
||||
this.exportPadding = this.mindMap.opt.exportPadding
|
||||
}
|
||||
|
||||
// 导出
|
||||
@@ -35,6 +39,10 @@ class Export {
|
||||
let imageList = svg.find('image')
|
||||
let task = imageList.map(async item => {
|
||||
let imgUlr = item.attr('href') || item.attr('xlink:href')
|
||||
// 已经是data:URL形式不用转换
|
||||
if (/^data:/.test(imgUlr)) {
|
||||
return
|
||||
}
|
||||
let imgData = await imgToDataUrl(imgUlr)
|
||||
item.attr('href', imgData)
|
||||
})
|
||||
@@ -49,7 +57,7 @@ class Export {
|
||||
}
|
||||
|
||||
// svg转png
|
||||
svgToPng(svgSrc, transparent) {
|
||||
svgToPng(svgSrc, transparent, rotateWhenWidthLongerThenHeight = false) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image()
|
||||
// 跨域图片需要添加这个属性,否则画布被污染了无法导出图片
|
||||
@@ -57,12 +65,24 @@ class Export {
|
||||
img.onload = async () => {
|
||||
try {
|
||||
let canvas = document.createElement('canvas')
|
||||
canvas.width = img.width + this.exportPadding * 2
|
||||
canvas.height = img.height + this.exportPadding * 2
|
||||
// 如果宽比高长,那么旋转90度
|
||||
let needRotate =
|
||||
rotateWhenWidthLongerThenHeight && img.width / img.height > 1
|
||||
if (needRotate) {
|
||||
canvas.width = img.height
|
||||
canvas.height = img.width
|
||||
} else {
|
||||
canvas.width = img.width
|
||||
canvas.height = img.height
|
||||
}
|
||||
let ctx = canvas.getContext('2d')
|
||||
if (needRotate) {
|
||||
ctx.rotate(0.5 * Math.PI)
|
||||
ctx.translate(0, -img.height)
|
||||
}
|
||||
// 绘制背景
|
||||
if (!transparent) {
|
||||
await this.drawBackgroundToCanvas(ctx, canvas.width, canvas.height)
|
||||
await this.drawBackgroundToCanvas(ctx, img.width, img.height)
|
||||
}
|
||||
// 图片绘制到canvas里
|
||||
ctx.drawImage(
|
||||
@@ -71,8 +91,8 @@ class Export {
|
||||
0,
|
||||
img.width,
|
||||
img.height,
|
||||
this.exportPadding,
|
||||
this.exportPadding,
|
||||
0,
|
||||
0,
|
||||
img.width,
|
||||
img.height
|
||||
)
|
||||
@@ -96,7 +116,7 @@ class Export {
|
||||
backgroundImage,
|
||||
backgroundRepeat = 'no-repeat',
|
||||
backgroundPosition = 'center center',
|
||||
backgroundSize = 'cover',
|
||||
backgroundSize = 'cover'
|
||||
} = this.mindMap.themeConfig
|
||||
// 背景颜色
|
||||
ctx.save()
|
||||
@@ -107,18 +127,25 @@ class Export {
|
||||
// 背景图片
|
||||
if (backgroundImage && backgroundImage !== 'none') {
|
||||
ctx.save()
|
||||
drawBackgroundImageToCanvas(ctx, width, height, backgroundImage, {
|
||||
backgroundRepeat,
|
||||
backgroundPosition,
|
||||
backgroundSize
|
||||
}, (err) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve()
|
||||
drawBackgroundImageToCanvas(
|
||||
ctx,
|
||||
width,
|
||||
height,
|
||||
backgroundImage,
|
||||
{
|
||||
backgroundRepeat,
|
||||
backgroundPosition,
|
||||
backgroundSize
|
||||
},
|
||||
err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
ctx.restore()
|
||||
}
|
||||
ctx.restore()
|
||||
})
|
||||
)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
@@ -152,14 +179,25 @@ class Export {
|
||||
* 方法1.把svg的图片都转化成data:url格式,再转换
|
||||
* 方法2.把svg的图片提取出来再挨个绘制到canvas里,最后一起转换
|
||||
*/
|
||||
async png(name, transparent = false) {
|
||||
async png(name, transparent = false, rotateWhenWidthLongerThenHeight) {
|
||||
let { node, str } = await this.getSvgData()
|
||||
str = removeHTMLEntities(str)
|
||||
// 如果开启了富文本,则使用htmltocanvas转换为图片
|
||||
if (this.mindMap.richText) {
|
||||
let res = await this.mindMap.richText.handleExportPng(node.node)
|
||||
let imgDataUrl = await this.svgToPng(res, transparent)
|
||||
return imgDataUrl
|
||||
// 覆盖html默认的样式
|
||||
let foreignObjectList = node.find('foreignObject')
|
||||
if (foreignObjectList.length > 0) {
|
||||
foreignObjectList[0].add(SVG(`<style>${ this.mindMap.opt.resetCss }</style>`))
|
||||
}
|
||||
str = node.svg()
|
||||
// 使用其他库(html2canvas、dom-to-image-more等)来完成导出
|
||||
// let res = await this.mindMap.richText.handleExportPng(node.node)
|
||||
// let imgDataUrl = await this.svgToPng(
|
||||
// res,
|
||||
// transparent,
|
||||
// rotateWhenWidthLongerThenHeight
|
||||
// )
|
||||
// return imgDataUrl
|
||||
}
|
||||
// 转换成blob数据
|
||||
let blob = new Blob([str], {
|
||||
@@ -168,17 +206,21 @@ class Export {
|
||||
// 转换成data:url数据
|
||||
let svgUrl = await readBlob(blob)
|
||||
// 绘制到canvas上
|
||||
let res = await this.svgToPng(svgUrl, transparent)
|
||||
let res = await this.svgToPng(
|
||||
svgUrl,
|
||||
transparent,
|
||||
rotateWhenWidthLongerThenHeight
|
||||
)
|
||||
return res
|
||||
}
|
||||
|
||||
// 导出为pdf
|
||||
async pdf(name) {
|
||||
async pdf(name, useMultiPageExport) {
|
||||
if (!this.mindMap.doExportPDF) {
|
||||
throw new Error('请注册ExportPDF插件')
|
||||
}
|
||||
let img = await this.png()
|
||||
this.mindMap.doExportPDF.pdf(name, img)
|
||||
let img = await this.png('', false, true)
|
||||
this.mindMap.doExportPDF.pdf(name, img, useMultiPageExport)
|
||||
}
|
||||
|
||||
// 导出为xmind
|
||||
@@ -194,15 +236,13 @@ class Export {
|
||||
|
||||
// 导出为svg
|
||||
// plusCssText:附加的css样式,如果svg中存在dom节点,想要设置一些针对节点的样式可以通过这个参数传入
|
||||
async svg(name, plusCssText) {
|
||||
async svg(name) {
|
||||
let { node } = await this.getSvgData()
|
||||
// 开启了节点富文本编辑
|
||||
if (this.mindMap.richText) {
|
||||
if (plusCssText) {
|
||||
let foreignObjectList = node.find('foreignObject')
|
||||
if (foreignObjectList.length > 0) {
|
||||
foreignObjectList[0].add(SVG(`<style>${plusCssText}</style>`))
|
||||
}
|
||||
let foreignObjectList = node.find('foreignObject')
|
||||
if (foreignObjectList.length > 0) {
|
||||
foreignObjectList[0].add(SVG(`<style>${ this.mindMap.opt.resetCss }</style>`))
|
||||
}
|
||||
}
|
||||
node.first().before(SVG(`<title>${name}</title>`))
|
||||
|
||||
@@ -8,7 +8,16 @@ class ExportPDF {
|
||||
}
|
||||
|
||||
// 导出为pdf
|
||||
pdf(name, img) {
|
||||
pdf(name, img, useMultiPageExport = false) {
|
||||
if (useMultiPageExport) {
|
||||
this.multiPageExport(name, img)
|
||||
} else {
|
||||
this.onePageExport(name, img)
|
||||
}
|
||||
}
|
||||
|
||||
// 单页导出
|
||||
onePageExport(name, img) {
|
||||
let pdf = new JsPDF('', 'pt', 'a4')
|
||||
let a4Width = 595
|
||||
let a4Height = 841
|
||||
@@ -37,6 +46,52 @@ class ExportPDF {
|
||||
}
|
||||
image.src = img
|
||||
}
|
||||
|
||||
// 多页导出
|
||||
multiPageExport(name, img) {
|
||||
let image = new Image()
|
||||
const a4Width = 592.28
|
||||
const a4Height = 841.89
|
||||
image.onload = () => {
|
||||
let imageWidth = image.width
|
||||
let imageHeight = image.height
|
||||
// 一页pdf显示高度
|
||||
let pageHeight = (imageWidth / a4Width) * a4Height
|
||||
// 未生成pdf的高度
|
||||
let leftHeight = imageHeight
|
||||
// 偏移
|
||||
let position = 0
|
||||
// a4纸的尺寸[595.28,841.89],图片在pdf中图片的宽高
|
||||
let imgWidth = a4Width
|
||||
let imgHeight = (a4Width / imageWidth) * imageHeight
|
||||
let pdf = new JsPDF('', 'pt', 'a4')
|
||||
// 有两个高度需要区分,一个是图片的实际高度,和生成pdf的页面高度(841.89)
|
||||
// 当内容未超过pdf一页显示的范围,无需分页
|
||||
if (leftHeight < pageHeight) {
|
||||
pdf.addImage(
|
||||
img,
|
||||
'PNG',
|
||||
(a4Width - imgWidth) / 2,
|
||||
(a4Height - imgHeight) / 2,
|
||||
imgWidth,
|
||||
imgHeight
|
||||
)
|
||||
} else {
|
||||
// 分页
|
||||
while (leftHeight > 0) {
|
||||
pdf.addImage(img, 'PNG', 0, position, imgWidth, imgHeight)
|
||||
leftHeight -= pageHeight
|
||||
position -= a4Height
|
||||
// 避免添加空白页
|
||||
if (leftHeight > 0) {
|
||||
pdf.addPage()
|
||||
}
|
||||
}
|
||||
}
|
||||
pdf.save(name)
|
||||
}
|
||||
image.src = img
|
||||
}
|
||||
}
|
||||
|
||||
ExportPDF.instanceName = 'doExportPDF'
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import Quill from 'quill'
|
||||
import Delta from 'quill-delta'
|
||||
import 'quill/dist/quill.snow.css'
|
||||
import html2canvas from 'html2canvas'
|
||||
import { walk, getTextFromHtml, isWhite, getVisibleColorFromTheme } from '../utils'
|
||||
import {
|
||||
walk,
|
||||
getTextFromHtml,
|
||||
isWhite,
|
||||
getVisibleColorFromTheme
|
||||
} from '../utils'
|
||||
import { CONSTANTS } from '../constants/constant'
|
||||
|
||||
let extended = false
|
||||
@@ -38,6 +43,7 @@ class RichText {
|
||||
this.quill = null
|
||||
this.range = null
|
||||
this.lastRange = null
|
||||
this.pasteUseRange = null
|
||||
this.node = null
|
||||
this.isInserting = false
|
||||
this.styleEl = null
|
||||
@@ -150,6 +156,12 @@ class RichText {
|
||||
if (this.showTextEdit) {
|
||||
return
|
||||
}
|
||||
const {
|
||||
richTextEditFakeInPlace,
|
||||
customInnerElsAppendTo,
|
||||
nodeTextEditZIndex,
|
||||
textAutoWrapWidth
|
||||
} = this.mindMap.opt
|
||||
this.node = node
|
||||
this.isInserting = isInserting
|
||||
if (!rect) rect = node._textData.node.node.getBoundingClientRect()
|
||||
@@ -163,19 +175,36 @@ class RichText {
|
||||
let scaleX = rect.width / originWidth
|
||||
let scaleY = rect.height / originHeight
|
||||
// 内边距
|
||||
const paddingX = 6
|
||||
const paddingY = 4
|
||||
let paddingX = 6
|
||||
let paddingY = 4
|
||||
if (richTextEditFakeInPlace) {
|
||||
let paddingValue = node.getPaddingVale()
|
||||
paddingX = paddingValue.paddingX
|
||||
paddingY = paddingValue.paddingY
|
||||
}
|
||||
if (!this.textEditNode) {
|
||||
this.textEditNode = document.createElement('div')
|
||||
this.textEditNode.classList.add('smm-richtext-node-edit-wrap')
|
||||
this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;box-shadow: 0 0 20px rgba(0,0,0,.5);outline: none; word-break: break-all;padding: ${paddingY}px ${paddingX}px;`
|
||||
this.textEditNode.style.cssText = `
|
||||
position:fixed;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 0 20px rgba(0,0,0,.5);
|
||||
outline: none;
|
||||
word-break:
|
||||
break-all;padding: ${paddingY}px ${paddingX}px;
|
||||
`
|
||||
this.textEditNode.addEventListener('click', e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
this.textEditNode.addEventListener('mousedown', (e) => {
|
||||
this.textEditNode.addEventListener('mousedown', e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
const targetNode = this.mindMap.opt.customInnerElsAppendTo || document.body
|
||||
this.textEditNode.addEventListener('keydown', e => {
|
||||
if (this.mindMap.renderer.textEdit.checkIsAutoEnterTextEditKey(e)) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
})
|
||||
const targetNode = customInnerElsAppendTo || document.body
|
||||
targetNode.appendChild(this.textEditNode)
|
||||
}
|
||||
// 使用节点的填充色,否则如果节点颜色是白色的话编辑时看不见
|
||||
@@ -183,18 +212,28 @@ class RichText {
|
||||
let color = node.style.merge('color')
|
||||
this.textEditNode.style.marginLeft = `-${paddingX * scaleX}px`
|
||||
this.textEditNode.style.marginTop = `-${paddingY * scaleY}px`
|
||||
this.textEditNode.style.zIndex = this.mindMap.opt.nodeTextEditZIndex
|
||||
this.textEditNode.style.zIndex = nodeTextEditZIndex
|
||||
this.textEditNode.style.backgroundColor =
|
||||
bgColor === 'transparent' ? isWhite(color) ? getVisibleColorFromTheme(this.mindMap.themeConfig) : '#fff' : bgColor
|
||||
bgColor === 'transparent'
|
||||
? isWhite(color)
|
||||
? getVisibleColorFromTheme(this.mindMap.themeConfig)
|
||||
: '#fff'
|
||||
: bgColor
|
||||
this.textEditNode.style.minWidth = originWidth + paddingX * 2 + 'px'
|
||||
this.textEditNode.style.minHeight = originHeight + 'px'
|
||||
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 + paddingX * 2 + 'px'
|
||||
this.textEditNode.style.maxWidth = textAutoWrapWidth + paddingX * 2 + 'px'
|
||||
this.textEditNode.style.transform = `scale(${scaleX}, ${scaleY})`
|
||||
this.textEditNode.style.transformOrigin = 'left top'
|
||||
if (richTextEditFakeInPlace) {
|
||||
this.textEditNode.style.borderRadius =
|
||||
(node.style.merge('borderRadius') || 5) + 'px'
|
||||
if (node.style.merge('shape') == 'roundedRectangle') {
|
||||
this.textEditNode.style.borderRadius = (node.height || 50) + 'px'
|
||||
}
|
||||
}
|
||||
if (!node.nodeData.data.richText) {
|
||||
// 还不是富文本的情况
|
||||
let text = node.nodeData.data.text.split(/\n/gim).join('<br>')
|
||||
@@ -291,6 +330,7 @@ class RichText {
|
||||
this.lastRange = this.range
|
||||
this.range = null
|
||||
if (range) {
|
||||
this.pasteUseRange = range
|
||||
let bounds = this.quill.getBounds(range.index, range.length)
|
||||
let rect = this.textEditNode.getBoundingClientRect()
|
||||
let rectInfo = {
|
||||
@@ -330,6 +370,38 @@ class RichText {
|
||||
this.lostStyle = false
|
||||
}
|
||||
})
|
||||
// 拦截粘贴,只允许粘贴纯文本
|
||||
this.quill.clipboard.addMatcher(Node.TEXT_NODE, (node) => {
|
||||
let style = this.getPasteTextStyle()
|
||||
return new Delta().insert(node.data, style)
|
||||
})
|
||||
this.quill.clipboard.addMatcher(Node.ELEMENT_NODE, (node, delta) => {
|
||||
let ops = []
|
||||
let style = this.getPasteTextStyle()
|
||||
delta.ops.forEach(op => {
|
||||
// 过滤出文本内容,过滤掉换行
|
||||
if (op.insert && typeof op.insert === 'string' && op.insert !== '\n') {
|
||||
ops.push({
|
||||
attributes: { ...style },
|
||||
insert: op.insert
|
||||
})
|
||||
}
|
||||
})
|
||||
delta.ops = ops
|
||||
return delta
|
||||
})
|
||||
}
|
||||
|
||||
// 获取粘贴的文本的样式
|
||||
getPasteTextStyle() {
|
||||
// 粘贴的数据使用当前光标位置处的文本样式
|
||||
if (this.pasteUseRange) {
|
||||
return this.quill.getFormat(
|
||||
this.pasteUseRange.index,
|
||||
this.pasteUseRange.length
|
||||
)
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
// 正则输入中文
|
||||
@@ -502,11 +574,16 @@ class RichText {
|
||||
}
|
||||
}
|
||||
walk(node)
|
||||
let canvas = await html2canvas(el, {
|
||||
backgroundColor: null
|
||||
})
|
||||
|
||||
// 如果使用html2canvas
|
||||
// let canvas = await html2canvas(el, {
|
||||
// backgroundColor: null
|
||||
// })
|
||||
// return canvas.toDataURL()
|
||||
|
||||
const res = await domtoimage.toPng(el)
|
||||
this.mindMap.el.removeChild(el)
|
||||
return canvas.toDataURL()
|
||||
return res
|
||||
}
|
||||
|
||||
// 将所有节点转换成非富文本节点
|
||||
|
||||
@@ -6,7 +6,7 @@ class TouchEvent {
|
||||
this.touchesNum = 0
|
||||
this.singleTouchstartEvent = null
|
||||
this.clickNum = 0
|
||||
this.doubleTouchmoveDistance = 0
|
||||
this.touchStartScaleView = null
|
||||
this.bindEvent()
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ class TouchEvent {
|
||||
// 手指按下事件
|
||||
onTouchstart(e) {
|
||||
this.touchesNum = e.touches.length
|
||||
this.touchStartScaleView = null
|
||||
if (this.touchesNum === 1) {
|
||||
let touch = e.touches[0]
|
||||
this.singleTouchstartEvent = touch
|
||||
@@ -53,18 +54,46 @@ class TouchEvent {
|
||||
let oy = touch1.clientY - touch2.clientY
|
||||
let distance = Math.sqrt(Math.pow(ox, 2) + Math.pow(oy, 2))
|
||||
// 以两指中心点进行缩放
|
||||
let { x: touch1ClientX, y: touch1ClientY } = this.mindMap.toPos(touch1.clientX, touch1.clientY)
|
||||
let { x: touch2ClientX, y: touch2ClientY } = this.mindMap.toPos(touch2.clientX, touch2.clientY)
|
||||
let { x: touch1ClientX, y: touch1ClientY } = this.mindMap.toPos(
|
||||
touch1.clientX,
|
||||
touch1.clientY
|
||||
)
|
||||
let { x: touch2ClientX, y: touch2ClientY } = this.mindMap.toPos(
|
||||
touch2.clientX,
|
||||
touch2.clientY
|
||||
)
|
||||
let cx = (touch1ClientX + touch2ClientX) / 2
|
||||
let cy = (touch1ClientY + touch2ClientY) / 2
|
||||
if (distance > this.doubleTouchmoveDistance) {
|
||||
// 放大
|
||||
this.mindMap.view.enlarge(cx, cy, true)
|
||||
} else {
|
||||
// 缩小
|
||||
this.mindMap.view.narrow(cx, cy, true)
|
||||
// 手势缩放,基于最开始的位置进行缩放(基于前一个位置缩放不是线性关系); 缩放同时支持位置拖动
|
||||
const view = this.mindMap.view
|
||||
if (!this.touchStartScaleView) {
|
||||
this.touchStartScaleView = {
|
||||
distance: distance,
|
||||
scale: view.scale,
|
||||
x: view.x,
|
||||
y: view.y,
|
||||
cx: cx,
|
||||
cy: cy
|
||||
}
|
||||
return
|
||||
}
|
||||
this.doubleTouchmoveDistance = distance
|
||||
const viewBefore = this.touchStartScaleView
|
||||
let scale = viewBefore.scale * (distance / viewBefore.distance)
|
||||
if (Math.abs(distance - viewBefore.distance) <= 10) {
|
||||
scale = viewBefore.scale
|
||||
}
|
||||
const ratio = 1 - scale / viewBefore.scale
|
||||
view.scale = scale < 0.1 ? 0.1 : scale
|
||||
view.x =
|
||||
viewBefore.x +
|
||||
(cx - viewBefore.x) * ratio +
|
||||
(cx - viewBefore.cx) * scale
|
||||
view.y =
|
||||
viewBefore.y +
|
||||
(cy - viewBefore.y) * ratio +
|
||||
(cy - viewBefore.cy) * scale
|
||||
view.transform()
|
||||
this.mindMap.emit('scale', scale)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +120,7 @@ class TouchEvent {
|
||||
}
|
||||
this.touchesNum = 0
|
||||
this.singleTouchstartEvent = null
|
||||
this.doubleTouchmoveDistance = 0
|
||||
this.touchStartScaleView = null
|
||||
}
|
||||
|
||||
// 发送鼠标事件
|
||||
|
||||
@@ -455,25 +455,25 @@ export const loadImage = imgFile => {
|
||||
}
|
||||
|
||||
// 移除字符串中的html实体
|
||||
export const removeHTMLEntities = (str) => {
|
||||
[[' ', ' ']].forEach((item) => {
|
||||
export const removeHTMLEntities = str => {
|
||||
;[[' ', ' ']].forEach(item => {
|
||||
str = str.replaceAll(item[0], item[1])
|
||||
})
|
||||
return str
|
||||
}
|
||||
|
||||
// 获取一个数据的类型
|
||||
export const getType = (data) => {
|
||||
export const getType = data => {
|
||||
return Object.prototype.toString.call(data).slice(7, -1)
|
||||
}
|
||||
|
||||
// 判断一个数据是否是null和undefined和空字符串
|
||||
export const isUndef = (data) => {
|
||||
export const isUndef = data => {
|
||||
return data === null || data === undefined || data === ''
|
||||
}
|
||||
|
||||
// 移除html字符串中节点的内联样式
|
||||
export const removeHtmlStyle = (html) => {
|
||||
export const removeHtmlStyle = html => {
|
||||
return html.replaceAll(/(<[^\s]+)\s+style=["'][^'"]+["']\s*(>)/g, '$1$2')
|
||||
}
|
||||
|
||||
@@ -485,12 +485,12 @@ export const addHtmlStyle = (html, tag, style) => {
|
||||
|
||||
// 检查一个字符串是否是富文本字符
|
||||
let checkIsRichTextEl = null
|
||||
export const checkIsRichText = (str) => {
|
||||
export const checkIsRichText = str => {
|
||||
if (!checkIsRichTextEl) {
|
||||
checkIsRichTextEl = document.createElement('div')
|
||||
}
|
||||
checkIsRichTextEl.innerHTML = str
|
||||
for (let c = checkIsRichTextEl.childNodes, i = c.length; i--;) {
|
||||
for (let c = checkIsRichTextEl.childNodes, i = c.length; i--; ) {
|
||||
if (c[i].nodeType == 1) return true
|
||||
}
|
||||
return false
|
||||
@@ -503,13 +503,20 @@ export const replaceHtmlText = (html, searchText, replaceText) => {
|
||||
replaceHtmlTextEl = document.createElement('div')
|
||||
}
|
||||
replaceHtmlTextEl.innerHTML = html
|
||||
let walk = (root) => {
|
||||
let walk = root => {
|
||||
let childNodes = root.childNodes
|
||||
childNodes.forEach((node) => {
|
||||
if (node.nodeType === 1) {// 元素节点
|
||||
childNodes.forEach(node => {
|
||||
if (node.nodeType === 1) {
|
||||
// 元素节点
|
||||
walk(node)
|
||||
} else if (node.nodeType === 3) {// 文本节点
|
||||
root.replaceChild(document.createTextNode(node.nodeValue.replaceAll(searchText, replaceText)), node)
|
||||
} else if (node.nodeType === 3) {
|
||||
// 文本节点
|
||||
root.replaceChild(
|
||||
document.createTextNode(
|
||||
node.nodeValue.replaceAll(searchText, replaceText)
|
||||
),
|
||||
node
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -518,22 +525,39 @@ export const replaceHtmlText = (html, searchText, replaceText) => {
|
||||
}
|
||||
|
||||
// 判断一个颜色是否是白色
|
||||
export const isWhite = (color) => {
|
||||
export const isWhite = color => {
|
||||
color = String(color).replaceAll(/\s+/g, '')
|
||||
return ['#fff', '#ffffff', '#FFF', '#FFFFFF', 'rgb(255,255,255)'].includes(color) || /rgba\(255,255,255,[^)]+\)/.test(color)
|
||||
return (
|
||||
['#fff', '#ffffff', '#FFF', '#FFFFFF', 'rgb(255,255,255)'].includes(
|
||||
color
|
||||
) || /rgba\(255,255,255,[^)]+\)/.test(color)
|
||||
)
|
||||
}
|
||||
|
||||
// 判断一个颜色是否是透明
|
||||
export const isTransparent = (color) => {
|
||||
export const isTransparent = color => {
|
||||
color = String(color).replaceAll(/\s+/g, '')
|
||||
return ['', 'transparent'].includes(color) || /rgba\(\d+,\d+,\d+,0\)/.test(color)
|
||||
return (
|
||||
['', 'transparent'].includes(color) || /rgba\(\d+,\d+,\d+,0\)/.test(color)
|
||||
)
|
||||
}
|
||||
|
||||
// 从当前主题里获取一个非透明非白色的颜色
|
||||
export const getVisibleColorFromTheme = (themeConfig) => {
|
||||
export const getVisibleColorFromTheme = themeConfig => {
|
||||
let { lineColor, root, second, node } = themeConfig
|
||||
let list = [lineColor, root.fillColor, root.color, second.fillColor, second.color, node.fillColor, node.color, root.borderColor, second.borderColor, node.borderColor]
|
||||
for(let i = 0; i < list.length; i++) {
|
||||
let list = [
|
||||
lineColor,
|
||||
root.fillColor,
|
||||
root.color,
|
||||
second.fillColor,
|
||||
second.color,
|
||||
node.fillColor,
|
||||
node.color,
|
||||
root.borderColor,
|
||||
second.borderColor,
|
||||
node.borderColor
|
||||
]
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
let color = list[i]
|
||||
if (!isTransparent(color) && !isWhite(color)) {
|
||||
return color
|
||||
@@ -541,24 +565,26 @@ export const getVisibleColorFromTheme = (themeConfig) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 将<p><span></span><p>形式的节点富文本内容转换成<br>换行的文本
|
||||
// 将<p><span></span><p>形式的节点富文本内容转换成\n换行的文本
|
||||
let nodeRichTextToTextWithWrapEl = null
|
||||
export const nodeRichTextToTextWithWrap = (html) => {
|
||||
export const nodeRichTextToTextWithWrap = html => {
|
||||
if (!nodeRichTextToTextWithWrapEl) {
|
||||
nodeRichTextToTextWithWrapEl = document.createElement('div')
|
||||
}
|
||||
nodeRichTextToTextWithWrapEl.innerHTML = html
|
||||
const childNodes = nodeRichTextToTextWithWrapEl.childNodes
|
||||
let res = ''
|
||||
for(let i = 0; i < childNodes.length; i++) {
|
||||
for (let i = 0; i < childNodes.length; i++) {
|
||||
const node = childNodes[i]
|
||||
if (node.nodeType === 1) {// 元素节点
|
||||
if (node.nodeType === 1) {
|
||||
// 元素节点
|
||||
if (node.tagName.toLowerCase() === 'p') {
|
||||
res += node.textContent + '\n'
|
||||
} else {
|
||||
res += node.textContent
|
||||
}
|
||||
} else if (node.nodeType === 3) {// 文本节点
|
||||
} else if (node.nodeType === 3) {
|
||||
// 文本节点
|
||||
res += node.nodeValue
|
||||
}
|
||||
}
|
||||
@@ -567,7 +593,7 @@ export const nodeRichTextToTextWithWrap = (html) => {
|
||||
|
||||
// 将<br>换行的文本转换成<p><span></span><p>形式的节点富文本内容
|
||||
let textToNodeRichTextWithWrapEl = null
|
||||
export const textToNodeRichTextWithWrap = (html) => {
|
||||
export const textToNodeRichTextWithWrap = html => {
|
||||
if (!textToNodeRichTextWithWrapEl) {
|
||||
textToNodeRichTextWithWrapEl = document.createElement('div')
|
||||
}
|
||||
@@ -575,23 +601,34 @@ export const textToNodeRichTextWithWrap = (html) => {
|
||||
const childNodes = textToNodeRichTextWithWrapEl.childNodes
|
||||
let list = []
|
||||
let str = ''
|
||||
for(let i = 0; i < childNodes.length; i++) {
|
||||
for (let i = 0; i < childNodes.length; i++) {
|
||||
const node = childNodes[i]
|
||||
if (node.nodeType === 1) {// 元素节点
|
||||
if (node.nodeType === 1) {
|
||||
// 元素节点
|
||||
if (node.tagName.toLowerCase() === 'br') {
|
||||
list.push(str)
|
||||
str = ''
|
||||
} else {
|
||||
str += node.textContent
|
||||
}
|
||||
} else if (node.nodeType === 3) {// 文本节点
|
||||
} else if (node.nodeType === 3) {
|
||||
// 文本节点
|
||||
str += node.nodeValue
|
||||
}
|
||||
}
|
||||
if (str) {
|
||||
list.push(str)
|
||||
}
|
||||
return list.map((item) => {
|
||||
return `<p><span>${item}</span></p>`
|
||||
}).join('')
|
||||
}
|
||||
return list
|
||||
.map(item => {
|
||||
return `<p><span>${item}</span></p>`
|
||||
})
|
||||
.join('')
|
||||
}
|
||||
|
||||
// 判断是否是移动端环境
|
||||
export const isMobile = () => {
|
||||
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
|
||||
navigator.userAgent
|
||||
)
|
||||
}
|
||||
|
||||
BIN
web/src/assets/avatar/小米.jpg
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
web/src/assets/avatar/棐.jpg
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 2479351 */
|
||||
src: url('iconfont.woff2?t=1690537337895') format('woff2'),
|
||||
url('iconfont.woff?t=1690537337895') format('woff'),
|
||||
url('iconfont.ttf?t=1690537337895') format('truetype');
|
||||
src: url('iconfont.woff2?t=1691822758372') format('woff2'),
|
||||
url('iconfont.woff?t=1691822758372') format('woff'),
|
||||
url('iconfont.ttf?t=1691822758372') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -13,6 +13,10 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icontouming:before {
|
||||
content: "\e60c";
|
||||
}
|
||||
|
||||
.iconlieri:before {
|
||||
content: "\e60b";
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 13 KiB |
@@ -1,56 +1,56 @@
|
||||
// 布局结构图片映射
|
||||
export const layoutImgMap = {
|
||||
logicalStructure: require('../assets/img/logicalStructure.png'),
|
||||
mindMap: require('../assets/img/mindMap.png'),
|
||||
organizationStructure: require('../assets/img/organizationStructure.png'),
|
||||
catalogOrganization: require('../assets/img/catalogOrganization.png'),
|
||||
timeline: require('../assets/img/timeline.png'),
|
||||
timeline2: require('../assets/img/timeline2.png'),
|
||||
fishbone: require('../assets/img/fishbone.png'),
|
||||
verticalTimeline: require('../assets/img/verticalTimeline.png'),
|
||||
logicalStructure: require('../assets/img/structures/logicalStructure.png'),
|
||||
mindMap: require('../assets/img/structures/mindMap.png'),
|
||||
organizationStructure: require('../assets/img/structures/organizationStructure.png'),
|
||||
catalogOrganization: require('../assets/img/structures/catalogOrganization.png'),
|
||||
timeline: require('../assets/img/structures/timeline.png'),
|
||||
timeline2: require('../assets/img/structures/timeline2.png'),
|
||||
fishbone: require('../assets/img/structures/fishbone.png'),
|
||||
verticalTimeline: require('../assets/img/structures/verticalTimeline.png'),
|
||||
}
|
||||
|
||||
// 主题图片映射
|
||||
export const themeMap = {
|
||||
default: require('../assets/img/default.jpg'),
|
||||
classic: require('../assets/img/classic.jpg'),
|
||||
minions: require('../assets/img/minions.jpg'),
|
||||
pinkGrape: require('../assets/img/pinkGrape.jpg'),
|
||||
mint: require('../assets/img/mint.jpg'),
|
||||
gold: require('../assets/img/gold.jpg'),
|
||||
vitalityOrange: require('../assets/img/vitalityOrange.jpg'),
|
||||
greenLeaf: require('../assets/img/greenLeaf.jpg'),
|
||||
dark2: require('../assets/img/dark2.jpg'),
|
||||
skyGreen: require('../assets/img/skyGreen.jpg'),
|
||||
classic2: require('../assets/img/classic2.jpg'),
|
||||
classic3: require('../assets/img/classic3.jpg'),
|
||||
classic4: require('../assets/img/classic4.jpg'),
|
||||
classicGreen: require('../assets/img/classicGreen.jpg'),
|
||||
classicBlue: require('../assets/img/classicBlue.jpg'),
|
||||
blueSky: require('../assets/img/blueSky.jpg'),
|
||||
brainImpairedPink: require('../assets/img/brainImpairedPink.jpg'),
|
||||
dark: require('../assets/img/dark.jpg'),
|
||||
earthYellow: require('../assets/img/earthYellow.jpg'),
|
||||
freshGreen: require('../assets/img/freshGreen.jpg'),
|
||||
freshRed: require('../assets/img/freshRed.jpg'),
|
||||
romanticPurple: require('../assets/img/romanticPurple.jpg'),
|
||||
simpleBlack: require('../assets/img/simpleBlack.jpg'),
|
||||
courseGreen: require('../assets/img/courseGreen.jpg'),
|
||||
coffee: require('../assets/img/coffee.jpg'),
|
||||
redSpirit: require('../assets/img/redSpirit.jpg'),
|
||||
blackHumour: require('../assets/img/blackHumour.jpg'),
|
||||
lateNightOffice: require('../assets/img/lateNightOffice.jpg'),
|
||||
blackGold: require('../assets/img/blackGold.jpg'),
|
||||
autumn: require('../assets/img/autumn.jpg'),
|
||||
avocado: require('../assets/img/avocado.jpg'),
|
||||
orangeJuice: require('../assets/img/orangeJuice.jpg'),
|
||||
oreo: require('../assets/img/oreo.jpg'),
|
||||
shallowSea: require('../assets/img/shallowSea.jpg'),
|
||||
lemonBubbles: require('../assets/img/lemonBubbles.jpg'),
|
||||
rose: require('../assets/img/rose.jpg'),
|
||||
seaBlueLine: require('../assets/img/seaBlueLine.jpg'),
|
||||
neonLamp: require('../assets/img/neonLamp.jpg'),
|
||||
darkNightLceBlade: require('../assets/img/darkNightLceBlade.jpg'),
|
||||
morandi: require('../assets/img/morandi.jpg'),
|
||||
default: require('../assets/img/themes/default.jpg'),
|
||||
classic: require('../assets/img/themes/classic.jpg'),
|
||||
minions: require('../assets/img/themes/minions.jpg'),
|
||||
pinkGrape: require('../assets/img/themes/pinkGrape.jpg'),
|
||||
mint: require('../assets/img/themes/mint.jpg'),
|
||||
gold: require('../assets/img/themes/gold.jpg'),
|
||||
vitalityOrange: require('../assets/img/themes/vitalityOrange.jpg'),
|
||||
greenLeaf: require('../assets/img/themes/greenLeaf.jpg'),
|
||||
dark2: require('../assets/img/themes/dark2.jpg'),
|
||||
skyGreen: require('../assets/img/themes/skyGreen.jpg'),
|
||||
classic2: require('../assets/img/themes/classic2.jpg'),
|
||||
classic3: require('../assets/img/themes/classic3.jpg'),
|
||||
classic4: require('../assets/img/themes/classic4.jpg'),
|
||||
classicGreen: require('../assets/img/themes/classicGreen.jpg'),
|
||||
classicBlue: require('../assets/img/themes/classicBlue.jpg'),
|
||||
blueSky: require('../assets/img/themes/blueSky.jpg'),
|
||||
brainImpairedPink: require('../assets/img/themes/brainImpairedPink.jpg'),
|
||||
dark: require('../assets/img/themes/dark.jpg'),
|
||||
earthYellow: require('../assets/img/themes/earthYellow.jpg'),
|
||||
freshGreen: require('../assets/img/themes/freshGreen.jpg'),
|
||||
freshRed: require('../assets/img/themes/freshRed.jpg'),
|
||||
romanticPurple: require('../assets/img/themes/romanticPurple.jpg'),
|
||||
simpleBlack: require('../assets/img/themes/simpleBlack.jpg'),
|
||||
courseGreen: require('../assets/img/themes/courseGreen.jpg'),
|
||||
coffee: require('../assets/img/themes/coffee.jpg'),
|
||||
redSpirit: require('../assets/img/themes/redSpirit.jpg'),
|
||||
blackHumour: require('../assets/img/themes/blackHumour.jpg'),
|
||||
lateNightOffice: require('../assets/img/themes/lateNightOffice.jpg'),
|
||||
blackGold: require('../assets/img/themes/blackGold.jpg'),
|
||||
autumn: require('../assets/img/themes/autumn.jpg'),
|
||||
avocado: require('../assets/img/themes/avocado.jpg'),
|
||||
orangeJuice: require('../assets/img/themes/orangeJuice.jpg'),
|
||||
oreo: require('../assets/img/themes/oreo.jpg'),
|
||||
shallowSea: require('../assets/img/themes/shallowSea.jpg'),
|
||||
lemonBubbles: require('../assets/img/themes/lemonBubbles.jpg'),
|
||||
rose: require('../assets/img/themes/rose.jpg'),
|
||||
seaBlueLine: require('../assets/img/themes/seaBlueLine.jpg'),
|
||||
neonLamp: require('../assets/img/themes/neonLamp.jpg'),
|
||||
darkNightLceBlade: require('../assets/img/themes/darkNightLceBlade.jpg'),
|
||||
morandi: require('../assets/img/themes/morandi.jpg'),
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ import {
|
||||
shapeList as shapeListZh,
|
||||
sidebarTriggerList as sidebarTriggerListZh,
|
||||
backgroundSizeList as backgroundSizeListZh,
|
||||
downTypeList as downTypeListZh
|
||||
downTypeList as downTypeListZh,
|
||||
shapeListMap as shapeListMapZh,
|
||||
lineStyleMap as lineStyleMapZh
|
||||
} from './zh'
|
||||
import {
|
||||
fontFamilyList as fontFamilyListEn,
|
||||
@@ -48,6 +50,11 @@ const lineStyleList = {
|
||||
en: lineStyleListEn
|
||||
}
|
||||
|
||||
const lineStyleMap = {
|
||||
zh: lineStyleMapZh,
|
||||
en: lineStyleMapZh
|
||||
}
|
||||
|
||||
const rootLineKeepSameInCurveList = {
|
||||
zh: rootLineKeepSameInCurveListZh,
|
||||
en: rootLineKeepSameInCurveListEn
|
||||
@@ -78,6 +85,11 @@ const shapeList = {
|
||||
en: shapeListEn
|
||||
}
|
||||
|
||||
const shapeListMap = {
|
||||
zh: shapeListMapZh,
|
||||
en: shapeListMapZh
|
||||
}
|
||||
|
||||
const sidebarTriggerList = {
|
||||
zh: sidebarTriggerListZh,
|
||||
en: sidebarTriggerListEn
|
||||
@@ -100,12 +112,14 @@ export {
|
||||
fontFamilyList,
|
||||
borderDasharrayList,
|
||||
lineStyleList,
|
||||
lineStyleMap,
|
||||
rootLineKeepSameInCurveList,
|
||||
backgroundRepeatList,
|
||||
backgroundPositionList,
|
||||
backgroundSizeList,
|
||||
shortcutKeyList,
|
||||
shapeList,
|
||||
shapeListMap,
|
||||
sidebarTriggerList,
|
||||
downTypeList
|
||||
}
|
||||
|
||||
@@ -97,7 +97,8 @@ export const colorList = [
|
||||
'#0C797D',
|
||||
'#0062B1',
|
||||
'#653294',
|
||||
'#AB149E'
|
||||
'#AB149E',
|
||||
'transparent'
|
||||
]
|
||||
|
||||
// 边框宽度
|
||||
@@ -141,6 +142,12 @@ export const borderRadiusList = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
// 线宽
|
||||
export const lineWidthList = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
|
||||
export const lineStyleMap = {
|
||||
straight: `<svg width="60" height="26"><path d="M18,14L30,14L30,5L42,5" fill="none" stroke="#000" stroke-width="2"></path><path d="M18,14L30,14L30,23L42,23" fill="none" stroke="#000" stroke-width="2"></path></svg>`,
|
||||
curve: `<svg width="60" height="26"><path d="M18,14L30,14A12,-9 0 0 1 42,5" fill="none" stroke="#000" stroke-width="2"></path><path d="M18,14L30,14A12,9 0 0 0 42,23" fill="none" stroke="#000" stroke-width="2"></path></svg>`,
|
||||
direct: `<svg width="60" height="26"><path d="M18,14L30,14L42,5" fill="none" stroke="#000" stroke-width="2"></path><path d="M18,14L30,14L42,23" fill="none" stroke="#000" stroke-width="2"></path></svg>`
|
||||
}
|
||||
|
||||
// 连线风格
|
||||
export const lineStyleList = [
|
||||
{
|
||||
@@ -378,6 +385,22 @@ export const shortcutKeyList = [
|
||||
}
|
||||
]
|
||||
|
||||
export const shapeListMap = {
|
||||
rectangle: 'M 4 12 L 4 3 L 56 3 L 56 21 L 4 21 L 4 12 Z',
|
||||
diamond: 'M 4 12 L 30 3 L 56 12 L 30 21 L 4 12 Z',
|
||||
parallelogram: 'M 10 3 L 56 3 L 50 21 L 4 21 L 10 3 Z',
|
||||
roundedRectangle:
|
||||
'M 13 3 L 47 3 A 9 9 0, 0 1 47 21 L 13 21 A 9 9 0, 0 1 13 3 Z',
|
||||
octagonalRectangle:
|
||||
'M 4 12 L 4 9 L 10 3 L 50 3 L 56 9 L 56 15 L 50 21 L 10 21 L 4 15 L 4 12 Z',
|
||||
outerTriangularRectangle:
|
||||
'M 4 12 L 10 3 L 50 3 L 56 12 L 50 21 L 10 21 L 4 12 Z',
|
||||
innerTriangularRectangle:
|
||||
'M 10 12 L 4 3 L 56 3 L 50 12 L 56 21 L 4 21 L 10 12 Z',
|
||||
ellipse: 'M 4 12 A 26 9 0, 1, 0 30 3 A 26 9 0, 0, 0 4 12 Z',
|
||||
circle: 'M 21 12 A 9 9 0, 1, 0 30 3 A 9 9 0, 0, 0 21 12 Z'
|
||||
}
|
||||
|
||||
// 形状列表
|
||||
export const shapeList = [
|
||||
{
|
||||
@@ -508,4 +531,4 @@ export const downTypeList = [
|
||||
icon: 'iconxmind',
|
||||
desc: 'XMind格式'
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -77,7 +77,10 @@ export default {
|
||||
level5: 'Level5',
|
||||
level6: 'Level6',
|
||||
zenMode: 'Zen mode',
|
||||
fitCanvas: 'Fit canvas'
|
||||
fitCanvas: 'Fit canvas',
|
||||
removeImage: 'Remove image',
|
||||
removeHyperlink: 'Remove hyperlink',
|
||||
removeNote: 'Remove note'
|
||||
},
|
||||
count: {
|
||||
words: 'Words',
|
||||
@@ -105,7 +108,8 @@ export default {
|
||||
notifyTitle: 'Info',
|
||||
notifyMessage: 'If the download is not triggered, check whether it is blocked by the browser',
|
||||
paddingX: 'Padding x',
|
||||
paddingY: 'Padding y'
|
||||
paddingY: 'Padding y',
|
||||
useMultiPageExport: 'Export multi page'
|
||||
},
|
||||
fullscreen: {
|
||||
fullscreenShow: 'Full screen show',
|
||||
@@ -142,7 +146,8 @@ export default {
|
||||
addTip: 'Press Enter to add'
|
||||
},
|
||||
outline: {
|
||||
title: 'Outline'
|
||||
title: 'Outline',
|
||||
nodeDefaultText: 'Branch node'
|
||||
},
|
||||
scale: {
|
||||
zoomIn: 'Zoom in',
|
||||
@@ -167,6 +172,7 @@ export default {
|
||||
italic: 'Italic',
|
||||
textDecoration: 'Text decoration',
|
||||
underline: 'Underline',
|
||||
none: 'None',
|
||||
lineThrough: 'Line through',
|
||||
overline: 'Overline',
|
||||
border: 'Border',
|
||||
|
||||
@@ -77,7 +77,10 @@ export default {
|
||||
level5: '五级主题',
|
||||
level6: '六级主题',
|
||||
zenMode: '禅模式',
|
||||
fitCanvas: '适应画布'
|
||||
fitCanvas: '适应画布',
|
||||
removeImage: '移除图片',
|
||||
removeHyperlink: '移除超链接',
|
||||
removeNote: '移除备注'
|
||||
},
|
||||
count: {
|
||||
words: '字数',
|
||||
@@ -105,7 +108,8 @@ export default {
|
||||
notifyTitle: '消息',
|
||||
notifyMessage: '如果没有触发下载,请检查是否被浏览器拦截了',
|
||||
paddingX: '水平内边距',
|
||||
paddingY: '垂直内边距'
|
||||
paddingY: '垂直内边距',
|
||||
useMultiPageExport: '是否多页导出'
|
||||
},
|
||||
fullscreen: {
|
||||
fullscreenShow: '全屏查看',
|
||||
@@ -142,7 +146,8 @@ export default {
|
||||
addTip: '请按回车键添加'
|
||||
},
|
||||
outline: {
|
||||
title: '大纲'
|
||||
title: '大纲',
|
||||
nodeDefaultText: '分支节点'
|
||||
},
|
||||
scale: {
|
||||
zoomIn: '放大',
|
||||
@@ -166,6 +171,7 @@ export default {
|
||||
addFontWeight: '加粗',
|
||||
italic: '斜体',
|
||||
textDecoration: '划线',
|
||||
none: '无',
|
||||
underline: '下划线',
|
||||
lineThrough: '中划线',
|
||||
overline: '上划线',
|
||||
|
||||
@@ -11,7 +11,7 @@ let langList = [
|
||||
}
|
||||
]
|
||||
let StartList = ['introduction', 'start', 'deploy', 'client', 'translate', 'changelog']
|
||||
let CourseList = new Array(21).fill(0).map((_, index) => {
|
||||
let CourseList = new Array(22).fill(0).map((_, index) => {
|
||||
return 'course' + (index + 1)
|
||||
})
|
||||
let APIList = [
|
||||
|
||||
@@ -1,5 +1,137 @@
|
||||
# Changelog
|
||||
|
||||
## 0.6.17
|
||||
|
||||
Fix:
|
||||
|
||||
> 1.Fix the issue of error reporting in the xmind file exported from Baidu Brain Map.
|
||||
>
|
||||
> 2.Fix the mindMap. export method code error.
|
||||
|
||||
New:
|
||||
|
||||
> 1.Create index.d.ts file。
|
||||
>
|
||||
> 2.Support configuration to enable double click reset mind map.
|
||||
>
|
||||
> 3.Intercept paste operations during rich text editing, remove formatting, and only allow pasting pure text.
|
||||
|
||||
## 0.6.16
|
||||
|
||||
Fix:
|
||||
|
||||
> 1.Optimize the logic of rich text measurement elements, remove duplicate settings for styles, and add duplicate nodes
|
||||
>
|
||||
> 2.Optimize the export image logic, and when traversing the node to convert the URL of the image, if it is already in the form of data: URL, do not handle it repeatedly.
|
||||
|
||||
New:
|
||||
|
||||
> 1.Remove the second parameter of the exported SVG method and configure it through instantiation instead.
|
||||
>
|
||||
> 2.Export images without using external libraries.
|
||||
|
||||
Demo:
|
||||
|
||||
> 1.Fixed a bug where siblings can be added to the root node when editing the outline separately.
|
||||
|
||||
## 0.6.15-fix.2
|
||||
|
||||
Fix: Fixed an issue where rich text nodes cannot be displayed in Firefox browser.
|
||||
|
||||
## 0.6.15-fix.1
|
||||
|
||||
New:
|
||||
|
||||
> 1.Export PDF supports pagination export based on image size.
|
||||
>
|
||||
> 2.Exporting PDF supports automatic direction adjustment based on aspect ratio.
|
||||
>
|
||||
> 3.Optimize the placeholder elements of the expand and collapse buttons: 1. Nodes without child nodes do not render this element; 2. Dynamically update the element based on the existence of child nodes.
|
||||
>
|
||||
> 4.Add a configuration that prohibits mouse wheel scaling.
|
||||
>
|
||||
> 5.Supports passing error handling functions.
|
||||
|
||||
Fix:
|
||||
|
||||
> 1.Fix the issue of displaying exceptions when node text is empty.
|
||||
>
|
||||
> 2.Change the paddingX and paddingY of exported SVG graphics to single sided padding.
|
||||
>
|
||||
> 3.Fixed an issue where the mouse is not centered when zooming when the canvas is not 0 from the top left corner of the browser window.
|
||||
>
|
||||
> 4.Fix the issue of overlapping node borders.
|
||||
|
||||
Demo:
|
||||
|
||||
> 1.The bottom right corner supports jumping to related links.
|
||||
>
|
||||
> 2.Adjust the position of the mini map to solve the problem of being blocked by side buttons.
|
||||
>
|
||||
> 3.Fix the issue where the prompt in the upper right corner of the open local file cannot be closed.
|
||||
>
|
||||
> 4.Editing the outline separately is no longer linked to the canvas, optimizing the editing experience under large data volume.
|
||||
>
|
||||
> 5.The sidebar involves graphical options to increase visualization effects.
|
||||
|
||||
## 0.6.14
|
||||
|
||||
New:
|
||||
|
||||
> 1.Remove and create hidden input boxes, and copy and paste them through navigator. clipboard; Support cross browser pasting of mind map node data; Support custom processing of text data in the clipboard.
|
||||
|
||||
Demo:
|
||||
|
||||
> 1.Fix the issue of enabling input to automatically enter text editing mode and conflicting with other input boxes.
|
||||
>
|
||||
> 2.Fix the issue of not being able to delete node images in the node image pop-up window.
|
||||
>
|
||||
> 3.Fixed an issue where the text decoration line style of nodes cannot be removed in the node style sidebar.
|
||||
>
|
||||
> 4.The color selector supports selecting transparent colors.
|
||||
>
|
||||
> 5.Fix the issue of importing mind map data without updating the sidebar data when the basic style sidebar is open.
|
||||
>
|
||||
> 6.Fixed the issue of not focusing when modifying the text of one node in the outline and then clicking on other nodes.
|
||||
>
|
||||
> 7.Fixed an issue where the node and word count statistics in the bottom left corner were not updated after exiting Zen mode.
|
||||
>
|
||||
> 8.Support deleting hyperlinks and notes of nodes from the right-click menu.
|
||||
>
|
||||
> 9.Support pasting node data of Zhixi Mind Map.
|
||||
|
||||
## 0.6.13
|
||||
|
||||
Fix:
|
||||
|
||||
> 1.Fix the issue of the inability to drag the canvas while holding down the middle mouse button on a node in read-only mode.
|
||||
>
|
||||
> 2.Fixed the issue of probabilistic error reporting after quickly dragging nodes several times.
|
||||
>
|
||||
> 3.Fix the issue of pulling up the input method during operations such as activating nodes on the mobile end, expanding and collapsing.
|
||||
>
|
||||
> 4.Fix the issue where an exception request is initiated when the background image in the theme configuration is none.
|
||||
|
||||
New:
|
||||
|
||||
> 1.Mobile gesture scaling optimization: Scale according to a linear relationship, and adjust the canvas position with double finger displacement.
|
||||
>
|
||||
> 2.Remove the logic of asynchronous rendering nodes and improve the speed of creating new nodes.
|
||||
>
|
||||
> 3.The export of images has been changed from the html2canvas library to the dom to image more library to address the issue of missing text styles in exporting rich text nodes.
|
||||
>
|
||||
> 4.When a non rich text input box enters the editing state, it is deselected by default.
|
||||
>
|
||||
> 5.When there is an activation node, it supports automatically entering text editing mode when pressing the Chinese, numeric, or English buttons.
|
||||
|
||||
Demo:
|
||||
|
||||
> 1.Add anti shake operations when saving view data to optimize performance.
|
||||
>
|
||||
> 2.Some time-consuming operations add loading effects.
|
||||
>
|
||||
> 3.Improve the dark mode of right-click menus and rich text toolbars.
|
||||
|
||||
## 0.6.12
|
||||
|
||||
Fix:
|
||||
|
||||