From 1beb03eaa675f6ab9e3da8b25e2a528479655c09 Mon Sep 17 00:00:00 2001
From: wanglin2 <1013335014@qq.com>
Date: Wed, 27 Sep 2023 18:21:27 +0800
Subject: [PATCH 01/86] =?UTF-8?q?Feat=EF=BC=9A=E5=B0=9D=E8=AF=95=E6=94=AF?=
=?UTF-8?q?=E6=8C=81=E5=8D=8F=E5=90=8C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
simple-mind-map/bin/server.mjs | 152 ++++++++++
simple-mind-map/package-lock.json | 346 ++++++++++++++++++++++-
simple-mind-map/package.json | 7 +-
simple-mind-map/src/layouts/Base.js | 4 +
simple-mind-map/src/plugins/Cooperate.js | 206 ++++++++++++++
web/src/pages/Edit/components/Edit.vue | 6 +-
6 files changed, 713 insertions(+), 8 deletions(-)
create mode 100644 simple-mind-map/bin/server.mjs
create mode 100644 simple-mind-map/src/plugins/Cooperate.js
diff --git a/simple-mind-map/bin/server.mjs b/simple-mind-map/bin/server.mjs
new file mode 100644
index 00000000..23efb3a7
--- /dev/null
+++ b/simple-mind-map/bin/server.mjs
@@ -0,0 +1,152 @@
+#!/usr/bin/env node
+
+import ws from 'ws'
+import http from 'http'
+import * as map from 'lib0/map'
+
+const wsReadyStateConnecting = 0
+const wsReadyStateOpen = 1
+const wsReadyStateClosing = 2 // eslint-disable-line
+const wsReadyStateClosed = 3 // eslint-disable-line
+
+const pingTimeout = 30000
+
+const port = process.env.PORT || 4444
+// @ts-ignore
+const wss = new ws.Server({ noServer: true })
+
+const server = http.createServer((request, response) => {
+ response.writeHead(200, { 'Content-Type': 'text/plain' })
+ response.end('okay')
+})
+
+/**
+ * Map froms topic-name to set of subscribed clients.
+ * @type {Map>}
+ */
+const topics = new Map()
+
+/**
+ * @param {any} conn
+ * @param {object} message
+ */
+const send = (conn, message) => {
+ if (
+ conn.readyState !== wsReadyStateConnecting &&
+ conn.readyState !== wsReadyStateOpen
+ ) {
+ conn.close()
+ }
+ try {
+ conn.send(JSON.stringify(message))
+ } catch (e) {
+ conn.close()
+ }
+}
+
+/**
+ * Setup a new client
+ * @param {any} conn
+ */
+const onconnection = conn => {
+ /**
+ * @type {Set}
+ */
+ const subscribedTopics = new Set()
+ let closed = false
+ // Check if connection is still alive
+ let pongReceived = true
+ const pingInterval = setInterval(() => {
+ if (!pongReceived) {
+ conn.close()
+ clearInterval(pingInterval)
+ } else {
+ pongReceived = false
+ try {
+ conn.ping()
+ } catch (e) {
+ conn.close()
+ }
+ }
+ }, pingTimeout)
+ conn.on('pong', () => {
+ pongReceived = true
+ })
+ conn.on('close', () => {
+ subscribedTopics.forEach(topicName => {
+ const subs = topics.get(topicName) || new Set()
+ subs.delete(conn)
+ if (subs.size === 0) {
+ topics.delete(topicName)
+ }
+ })
+ subscribedTopics.clear()
+ closed = true
+ })
+ conn.on(
+ 'message',
+ /** @param {object} message */ message => {
+ if (typeof message === 'string') {
+ message = JSON.parse(message)
+ }
+ if (message && message.type && !closed) {
+ switch (message.type) {
+ case 'subscribe':
+ /** @type {Array} */ ;(message.topics || []).forEach(
+ topicName => {
+ if (typeof topicName === 'string') {
+ // add conn to topic
+ const topic = map.setIfUndefined(
+ topics,
+ topicName,
+ () => new Set()
+ )
+ topic.add(conn)
+ // add topic to conn
+ subscribedTopics.add(topicName)
+ }
+ }
+ )
+ break
+ case 'unsubscribe':
+ /** @type {Array} */ ;(message.topics || []).forEach(
+ topicName => {
+ const subs = topics.get(topicName)
+ if (subs) {
+ subs.delete(conn)
+ }
+ }
+ )
+ break
+ case 'publish':
+ if (message.topic) {
+ const receivers = topics.get(message.topic)
+ if (receivers) {
+ message.clients = receivers.size
+ receivers.forEach(receiver => send(receiver, message))
+ }
+ }
+ break
+ case 'ping':
+ send(conn, { type: 'pong' })
+ }
+ }
+ }
+ )
+}
+wss.on('connection', onconnection)
+
+server.on('upgrade', (request, socket, head) => {
+ // You may check auth of request here..
+ /**
+ * @param {any} ws
+ */
+ const handleAuth = ws => {
+ wss.emit('connection', ws, request)
+ }
+ wss.handleUpgrade(request, socket, head, handleAuth)
+})
+
+server.listen(port)
+
+console.log('Signaling server running on localhost:', port)
diff --git a/simple-mind-map/package-lock.json b/simple-mind-map/package-lock.json
index 5efac16e..f3e0ec0b 100644
--- a/simple-mind-map/package-lock.json
+++ b/simple-mind-map/package-lock.json
@@ -18,7 +18,9 @@
"quill": "^1.3.6",
"tern": "^0.24.3",
"uuid": "^9.0.0",
- "xml-js": "^1.6.11"
+ "xml-js": "^1.6.11",
+ "y-webrtc": "^10.2.5",
+ "yjs": "^13.6.8"
},
"devDependencies": {
"eslint": "^8.25.0",
@@ -290,6 +292,25 @@
"node": ">= 0.6.0"
}
},
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -310,6 +331,29 @@
"node": ">= 0.4.0"
}
},
+ "node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
"node_modules/call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
@@ -574,6 +618,11 @@
"node": ">=0.6"
}
},
+ "node_modules/err-code": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz",
+ "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA=="
+ },
"node_modules/errno": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz",
@@ -873,6 +922,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/get-browser-rtc": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz",
+ "integrity": "sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ=="
+ },
"node_modules/get-intrinsic": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
@@ -1012,6 +1066,25 @@
"node": ">=8.0.0"
}
},
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
"node_modules/ignore": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
@@ -1150,6 +1223,15 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true
},
+ "node_modules/isomorphic.js": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz",
+ "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==",
+ "funding": {
+ "type": "GitHub Sponsors ❤",
+ "url": "https://github.com/sponsors/dmonad"
+ }
+ },
"node_modules/js-sdsl": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz",
@@ -1248,6 +1330,25 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/lib0": {
+ "version": "0.2.86",
+ "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.86.tgz",
+ "integrity": "sha512-kxigQTM4Q7NwJkEgdqQvU21qiR37twcqqLmh+/SbiGbRLfPlLVbHyY9sWp7PwXh0Xus9ELDSjsUOwcrdt5yZ4w==",
+ "dependencies": {
+ "isomorphic.js": "^0.2.4"
+ },
+ "bin": {
+ "0gentesthtml": "bin/gentesthtml.js",
+ "0serve": "bin/0serve.js"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "type": "GitHub Sponsors ❤",
+ "url": "https://github.com/sponsors/dmonad"
+ }
+ },
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
@@ -1960,7 +2061,6 @@
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -2016,6 +2116,14 @@
"performance-now": "^2.1.0"
}
},
+ "node_modules/randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dependencies": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
"node_modules/readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
@@ -2176,6 +2284,47 @@
"node": ">=8"
}
},
+ "node_modules/simple-peer": {
+ "version": "9.11.1",
+ "resolved": "https://registry.npmjs.org/simple-peer/-/simple-peer-9.11.1.tgz",
+ "integrity": "sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "buffer": "^6.0.3",
+ "debug": "^4.3.2",
+ "err-code": "^3.0.1",
+ "get-browser-rtc": "^1.1.0",
+ "queue-microtask": "^1.2.3",
+ "randombytes": "^2.1.0",
+ "readable-stream": "^3.6.0"
+ }
+ },
+ "node_modules/simple-peer/node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/stackblur-canvas": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.5.0.tgz",
@@ -2410,6 +2559,27 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
+ "node_modules/ws": {
+ "version": "7.5.9",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
+ "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
+ "optional": true,
+ "engines": {
+ "node": ">=8.3.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": "^5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
"node_modules/xml-js": {
"version": "1.6.11",
"resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz",
@@ -2421,6 +2591,65 @@
"xml-js": "bin/cli.js"
}
},
+ "node_modules/y-protocols": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.6.tgz",
+ "integrity": "sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q==",
+ "license": "MIT",
+ "dependencies": {
+ "lib0": "^0.2.85"
+ },
+ "engines": {
+ "node": ">=16.0.0",
+ "npm": ">=8.0.0"
+ },
+ "funding": {
+ "type": "GitHub Sponsors ❤",
+ "url": "https://github.com/sponsors/dmonad"
+ },
+ "peerDependencies": {
+ "yjs": "^13.0.0"
+ }
+ },
+ "node_modules/y-webrtc": {
+ "version": "10.2.5",
+ "resolved": "https://registry.npmjs.org/y-webrtc/-/y-webrtc-10.2.5.tgz",
+ "integrity": "sha512-ZyBNvTI5L28sQ2PQI0T/JvyWgvuTq05L21vGkIlcvNLNSJqAaLCBJRe3FHEqXoaogqWmRcEAKGfII4ErNXMnNw==",
+ "dependencies": {
+ "lib0": "^0.2.42",
+ "simple-peer": "^9.11.0",
+ "y-protocols": "^1.0.5"
+ },
+ "bin": {
+ "y-webrtc-signaling": "bin/server.js"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "type": "GitHub Sponsors ❤",
+ "url": "https://github.com/sponsors/dmonad"
+ },
+ "optionalDependencies": {
+ "ws": "^7.2.0"
+ }
+ },
+ "node_modules/yjs": {
+ "version": "13.6.8",
+ "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.8.tgz",
+ "integrity": "sha512-ZPq0hpJQb6f59B++Ngg4cKexDJTvfOgeiv0sBc4sUm8CaBWH7OQC4kcCgrqbjJ/B2+6vO49exvTmYfdlPtcjbg==",
+ "dependencies": {
+ "lib0": "^0.2.74"
+ },
+ "engines": {
+ "node": ">=16.0.0",
+ "npm": ">=8.0.0"
+ },
+ "funding": {
+ "type": "GitHub Sponsors ❤",
+ "url": "https://github.com/sponsors/dmonad"
+ }
+ },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@@ -2628,6 +2857,11 @@
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
"optional": true
},
+ "base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
+ },
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -2642,6 +2876,15 @@
"resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
"integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g=="
},
+ "buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "requires": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
"call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
@@ -2834,6 +3077,11 @@
"tapable": "^0.2.3"
}
},
+ "err-code": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz",
+ "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA=="
+ },
"errno": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz",
@@ -3066,6 +3314,11 @@
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="
},
+ "get-browser-rtc": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz",
+ "integrity": "sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ=="
+ },
"get-intrinsic": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
@@ -3163,6 +3416,11 @@
"text-segmentation": "^1.0.3"
}
},
+ "ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
+ },
"ignore": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
@@ -3262,6 +3520,11 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true
},
+ "isomorphic.js": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz",
+ "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw=="
+ },
"js-sdsl": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz",
@@ -3338,6 +3601,14 @@
"type-check": "~0.4.0"
}
},
+ "lib0": {
+ "version": "0.2.86",
+ "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.86.tgz",
+ "integrity": "sha512-kxigQTM4Q7NwJkEgdqQvU21qiR37twcqqLmh+/SbiGbRLfPlLVbHyY9sWp7PwXh0Xus9ELDSjsUOwcrdt5yZ4w==",
+ "requires": {
+ "isomorphic.js": "^0.2.4"
+ }
+ },
"lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
@@ -3765,8 +4036,7 @@
"queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
- "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
- "dev": true
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
},
"quill": {
"version": "1.3.6",
@@ -3807,6 +4077,14 @@
"performance-now": "^2.1.0"
}
},
+ "randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "requires": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
"readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
@@ -3916,6 +4194,32 @@
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true
},
+ "simple-peer": {
+ "version": "9.11.1",
+ "resolved": "https://registry.npmjs.org/simple-peer/-/simple-peer-9.11.1.tgz",
+ "integrity": "sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw==",
+ "requires": {
+ "buffer": "^6.0.3",
+ "debug": "^4.3.2",
+ "err-code": "^3.0.1",
+ "get-browser-rtc": "^1.1.0",
+ "queue-microtask": "^1.2.3",
+ "randombytes": "^2.1.0",
+ "readable-stream": "^3.6.0"
+ },
+ "dependencies": {
+ "readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "requires": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ }
+ }
+ }
+ },
"stackblur-canvas": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.5.0.tgz",
@@ -4088,6 +4392,13 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
+ "ws": {
+ "version": "7.5.9",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
+ "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
+ "optional": true,
+ "requires": {}
+ },
"xml-js": {
"version": "1.6.11",
"resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz",
@@ -4096,6 +4407,33 @@
"sax": "^1.2.4"
}
},
+ "y-protocols": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.6.tgz",
+ "integrity": "sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q==",
+ "requires": {
+ "lib0": "^0.2.85"
+ }
+ },
+ "y-webrtc": {
+ "version": "10.2.5",
+ "resolved": "https://registry.npmjs.org/y-webrtc/-/y-webrtc-10.2.5.tgz",
+ "integrity": "sha512-ZyBNvTI5L28sQ2PQI0T/JvyWgvuTq05L21vGkIlcvNLNSJqAaLCBJRe3FHEqXoaogqWmRcEAKGfII4ErNXMnNw==",
+ "requires": {
+ "lib0": "^0.2.42",
+ "simple-peer": "^9.11.0",
+ "ws": "^7.2.0",
+ "y-protocols": "^1.0.5"
+ }
+ },
+ "yjs": {
+ "version": "13.6.8",
+ "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.8.tgz",
+ "integrity": "sha512-ZPq0hpJQb6f59B++Ngg4cKexDJTvfOgeiv0sBc4sUm8CaBWH7OQC4kcCgrqbjJ/B2+6vO49exvTmYfdlPtcjbg==",
+ "requires": {
+ "lib0": "^0.2.74"
+ }
+ },
"yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
diff --git a/simple-mind-map/package.json b/simple-mind-map/package.json
index 506c5f20..2c88fefc 100644
--- a/simple-mind-map/package.json
+++ b/simple-mind-map/package.json
@@ -22,7 +22,8 @@
"scripts": {
"lint": "eslint src/",
"format": "prettier --write .",
- "types": "npx -p typescript tsc index.js --declaration --allowJs --emitDeclarationOnly --outDir types --target es2017"
+ "types": "npx -p typescript tsc index.js --declaration --allowJs --emitDeclarationOnly --outDir types --target es2017",
+ "serve": "node ./bin/server.mjs"
},
"module": "index.js",
"main": "./dist/simpleMindMap.umd.min.js",
@@ -37,7 +38,9 @@
"quill": "^1.3.6",
"tern": "^0.24.3",
"uuid": "^9.0.0",
- "xml-js": "^1.6.11"
+ "xml-js": "^1.6.11",
+ "y-webrtc": "^10.2.5",
+ "yjs": "^13.6.8"
},
"keywords": [
"javascript",
diff --git a/simple-mind-map/src/layouts/Base.js b/simple-mind-map/src/layouts/Base.js
index 6199059f..a446acfb 100644
--- a/simple-mind-map/src/layouts/Base.js
+++ b/simple-mind-map/src/layouts/Base.js
@@ -129,6 +129,10 @@ class Base {
this.renderer.addActiveNode(newNode)
}
}
+ // 如果当前节点在激活节点列表里,那么添加上激活的状态
+ if (this.mindMap.renderer.findActiveNodeIndex(newNode) !== -1) {
+ newNode.nodeData.data.isActive = true
+ }
// 根节点
if (isRoot) {
newNode.isRoot = true
diff --git a/simple-mind-map/src/plugins/Cooperate.js b/simple-mind-map/src/plugins/Cooperate.js
new file mode 100644
index 00000000..31fcf65f
--- /dev/null
+++ b/simple-mind-map/src/plugins/Cooperate.js
@@ -0,0 +1,206 @@
+import * as Y from 'yjs'
+import { WebrtcProvider } from 'y-webrtc'
+import { isSameObject, simpleDeepClone } from '../utils/index'
+
+// 协同插件
+class Cooperate {
+ constructor(opt) {
+ this.opt = opt
+ this.mindMap = opt.mindMap
+ this.ydoc = new Y.Doc()
+ this.ymap = this.ydoc.getMap()
+ this.provider = new WebrtcProvider('demo-room', this.ydoc, {
+ signaling: ['ws://10.16.83.11:4444']
+ })
+ this.currentData = null
+
+ // 处理数据
+ if (this.mindMap.opt.data) {
+ this.currentData = this.transformTreeDataToObject(this.mindMap.opt.data)
+ Object.keys(this.currentData).forEach(uid => {
+ this.ymap.set(uid, this.currentData[uid])
+ })
+ }
+
+ this.bindEvent()
+ }
+
+ // 绑定事件
+ bindEvent() {
+ // 监听数据同步
+ this.onObserve = this.onObserve.bind(this)
+ this.ymap.observe(this.onObserve)
+
+ // 监听思维导图改变
+ this.onDataChange = this.onDataChange.bind(this)
+ this.mindMap.on('data_change', this.onDataChange)
+ }
+
+ // 解绑事件
+ unBindEvent() {
+ this.ymap.unobserve(this.onObserve)
+ this.mindMap.off('data_change', this.onDataChange)
+ this.ydoc.destroy()
+ }
+
+ // 数据同步时的处理,更新当前思维导图
+ onObserve(event) {
+ const data = event.target.toJSON()
+ if (isSameObject(data, this.currentData)) return
+ this.currentData = data
+ const res = this.transformObjectToTreeData(data)
+ if (!res) return
+ if (this.mindMap.richText) {
+ this.mindMap.renderer.renderTree =
+ this.mindMap.richText.handleSetData(res)
+ } else {
+ this.mindMap.renderer.renderTree = res
+ }
+ this.mindMap.render()
+ this.mindMap.command.addHistory()
+ }
+
+ // 当前思维导图改变后的处理,触发同步
+ onDataChange(data) {
+ const res = this.transformTreeDataToObject(data)
+ this.updateChanges(res)
+ }
+
+ // 将树结构转平级对象
+ /*
+ {
+ data: {
+ uid: 'xxx'
+ },
+ children: [
+ {
+ data: {
+ uid: 'xxx'
+ },
+ children: []
+ }
+ ]
+ }
+ 转为:
+ {
+ uid: {
+ children: [uid1, uid2],
+ data: {}
+ }
+ }
+ */
+ transformTreeDataToObject(data) {
+ const res = {}
+ const walk = (root, parent) => {
+ const uid = root.data.uid
+ if (parent) {
+ parent.children.push(uid)
+ }
+ res[uid] = {
+ isRoot: !parent,
+ data: {
+ ...root.data
+ },
+ children: []
+ }
+ if (root.children && root.children.length > 0) {
+ root.children.forEach(item => {
+ walk(item, res[uid])
+ })
+ }
+ }
+ walk(data, null)
+ return res
+ }
+
+ // 找到父节点的uid
+ findParentUid(data, targetUid) {
+ const uids = Object.keys(data)
+ let res = ''
+ uids.forEach(uid => {
+ const children = data[uid].children
+ const isParent =
+ children.findIndex(childUid => {
+ return childUid === targetUid
+ }) !== -1
+ if (isParent) {
+ res = uid
+ }
+ })
+ return res
+ }
+
+ // 将平级对象转树结构
+ transformObjectToTreeData(data) {
+ const uids = Object.keys(data)
+ if (uids.length <= 0) return null
+ const rootKey = uids.find(uid => {
+ return data[uid].isRoot
+ })
+ if (!rootKey || !data[rootKey]) return null
+ // 根节点
+ const res = {
+ data: simpleDeepClone(data[rootKey].data),
+ children: []
+ }
+ const map = {}
+ map[rootKey] = res
+ uids.forEach(uid => {
+ const parentUid = this.findParentUid(data, uid)
+ const cur = data[uid]
+ const node = map[uid] || {
+ data: simpleDeepClone(cur.data),
+ children: []
+ }
+ if (!map[uid]) {
+ map[uid] = node
+ }
+ if (parentUid) {
+ if (map[parentUid]) {
+ map[parentUid].children.push(node)
+ } else {
+ map[parentUid] = {
+ data: simpleDeepClone(data[parentUid].data),
+ children: [node]
+ }
+ }
+ }
+ })
+ return res
+ }
+
+ // 找出更新点
+ updateChanges(data) {
+ const oldData = this.currentData
+ this.currentData = data
+ this.ydoc.transact(() => {
+ // 找出新增的或修改的
+ Object.keys(data).forEach(uid => {
+ // 新增的或已经存在的,如果数据发生了改变
+ if (!oldData[uid] || !isSameObject(oldData[uid], data[uid])) {
+ this.ymap.set(uid, data[uid])
+ }
+ })
+ // 找出删除的
+ Object.keys(oldData).forEach(uid => {
+ if (!data[uid]) {
+ this.ymap.delete(uid)
+ }
+ })
+ })
+ }
+
+ // 插件被移除前做的事情
+ beforePluginRemove() {
+ this.unBindEvent()
+ }
+
+ // 插件被卸载前做的事情
+ beforePluginDestroy() {
+ this.unBindEvent()
+ }
+}
+
+Cooperate.instanceName = 'cooperate'
+
+export default Cooperate
diff --git a/web/src/pages/Edit/components/Edit.vue b/web/src/pages/Edit/components/Edit.vue
index da5fb909..e0a1a609 100644
--- a/web/src/pages/Edit/components/Edit.vue
+++ b/web/src/pages/Edit/components/Edit.vue
@@ -45,6 +45,7 @@ import SearchPlugin from 'simple-mind-map/src/plugins/Search.js'
import Painter from 'simple-mind-map/src/plugins/Painter.js'
import ScrollbarPlugin from 'simple-mind-map/src/plugins/Scrollbar.js'
import Formula from 'simple-mind-map/src/plugins/Formula.js'
+import Cooperate from 'simple-mind-map/src/plugins/Cooperate.js'
import OutlineSidebar from './OutlineSidebar'
import Style from './Style'
import BaseStyle from './BaseStyle'
@@ -95,6 +96,7 @@ MindMap.usePlugin(MiniMap)
.usePlugin(Painter)
.usePlugin(ScrollbarPlugin)
.usePlugin(Formula)
+ .usePlugin(Cooperate)
// 注册自定义主题
customThemeList.forEach(item => {
@@ -476,13 +478,13 @@ export default {
// 测试动态插入节点
testDynamicCreateNodes() {
- return
+ // return
setTimeout(() => {
// 动态给指定节点添加子节点
// this.mindMap.execCommand(
// 'INSERT_CHILD_NODE',
// false,
- // this.mindMap.renderer.root,
+ // null,
// {
// text: '自定义内容'
// },
From 7d2758a21cf3b7e6877a3cd8d2a71c6ff1fe46ae Mon Sep 17 00:00:00 2001
From: wanglin2 <1013335014@qq.com>
Date: Thu, 28 Sep 2023 07:49:11 +0800
Subject: [PATCH 02/86] update
---
web/package-lock.json | 2 ++
1 file changed, 2 insertions(+)
diff --git a/web/package-lock.json b/web/package-lock.json
index 53a698b9..86dcc9be 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -18488,6 +18488,7 @@
"integrity": "sha512-VCNRiAt2P/bLo09rYt3DLe6xXUMlhJwrvU18Ddd/lYJgC7s8+wvhgYs+MTx4OiAXdu58drGwSBO9SPx7C6J82Q==",
"dev": true,
"requires": {
+ "@babel/core": "^7.11.0",
"@babel/helper-compilation-targets": "^7.9.6",
"@babel/helper-module-imports": "^7.8.3",
"@babel/plugin-proposal-class-properties": "^7.8.3",
@@ -18500,6 +18501,7 @@
"@vue/babel-plugin-jsx": "^1.0.3",
"@vue/babel-preset-jsx": "^1.2.4",
"babel-plugin-dynamic-import-node": "^2.3.3",
+ "core-js": "^3.6.5",
"core-js-compat": "^3.6.5",
"semver": "^6.1.0"
}
From 1550f032d94141d12c5522cb8ab7e79aa1fe4c78 Mon Sep 17 00:00:00 2001
From: wanglin2 <1013335014@qq.com>
Date: Thu, 28 Sep 2023 15:32:41 +0800
Subject: [PATCH 03/86] =?UTF-8?q?=E5=8D=8F=E4=BD=9C=E5=A2=9E=E5=8A=A0?=
=?UTF-8?q?=E7=8A=B6=E6=80=81=E5=90=8C=E6=AD=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/constants/defaultOptions.js | 7 +-
simple-mind-map/src/core/render/node/Node.js | 10 ++
.../src/core/render/node/nodeCooperate.js | 104 ++++++++++++++++++
simple-mind-map/src/plugins/Cooperate.js | 79 ++++++++++++-
web/src/pages/Edit/components/Edit.vue | 10 ++
5 files changed, 208 insertions(+), 2 deletions(-)
create mode 100644 simple-mind-map/src/core/render/node/nodeCooperate.js
diff --git a/simple-mind-map/src/constants/defaultOptions.js b/simple-mind-map/src/constants/defaultOptions.js
index 6a4ad674..d7cc00fb 100644
--- a/simple-mind-map/src/constants/defaultOptions.js
+++ b/simple-mind-map/src/constants/defaultOptions.js
@@ -204,5 +204,10 @@ export const defaultOpt = {
},
// 自定义标签的颜色
// {pass: 'green, unpass: 'red'}
- tagsColorMap: {}
+ tagsColorMap: {},
+ // 节点协作样式配置
+ cooperateStyle: {
+ avatarSize: 22,// 头像大小
+ fontSize: 12,// 如果是文字头像,那么文字的大小
+ }
}
diff --git a/simple-mind-map/src/core/render/node/Node.js b/simple-mind-map/src/core/render/node/Node.js
index d287755b..8dc840d1 100644
--- a/simple-mind-map/src/core/render/node/Node.js
+++ b/simple-mind-map/src/core/render/node/Node.js
@@ -6,6 +6,7 @@ import nodeExpandBtnMethods from './nodeExpandBtn'
import nodeCommandWrapsMethods from './nodeCommandWraps'
import nodeCreateContentsMethods from './nodeCreateContents'
import nodeExpandBtnPlaceholderRectMethods from './nodeExpandBtnPlaceholderRect'
+import nodeCooperateMethods from './nodeCooperate'
import { CONSTANTS } from '../../../constants/constant'
// 节点类
@@ -55,6 +56,8 @@ class Node {
this.parent = opt.parent || null
// 子节点
this.children = opt.children || []
+ // 当前同时操作该节点的用户列表
+ this.userList = []
// 节点内容的容器
this.group = null
this.shapeNode = null // 节点形状节点
@@ -74,6 +77,7 @@ class Node {
this._openExpandNode = null
this._closeExpandNode = null
this._fillExpandNode = null
+ this._userListGroup = null
this._lines = []
this._generalizationLine = null
this._generalizationNode = null
@@ -121,6 +125,10 @@ class Node {
Object.keys(nodeCreateContentsMethods).forEach(item => {
this[item] = nodeCreateContentsMethods[item].bind(this)
})
+ // 协同相关
+ Object.keys(nodeCooperateMethods).forEach((item) => {
+ this[item] = nodeCooperateMethods[item].bind(this)
+ })
// 初始化
this.getSize()
}
@@ -283,6 +291,7 @@ class Node {
this.group.add(this.shapeNode)
// 渲染一个隐藏的矩形区域,用来触发展开收起按钮的显示
this.renderExpandBtnPlaceholderRect()
+ this.createUserListNode()
// 概要节点添加一个带所属节点id的类名
if (this.isGeneralization && this.generalizationBelongNode) {
this.group.addClass('generalization_' + this.generalizationBelongNode.uid)
@@ -527,6 +536,7 @@ class Node {
}
// 更新概要
this.renderGeneralization()
+ this.updateUserListNode()
// 更新节点位置
let t = this.group.transform()
// // 如果上次不在可视区内,且本次也不在,那么直接返回
diff --git a/simple-mind-map/src/core/render/node/nodeCooperate.js b/simple-mind-map/src/core/render/node/nodeCooperate.js
new file mode 100644
index 00000000..970dfb2c
--- /dev/null
+++ b/simple-mind-map/src/core/render/node/nodeCooperate.js
@@ -0,0 +1,104 @@
+import { Circle, G, Text, Image } from '@svgdotjs/svg.js'
+import { generateColorByContent } from '../../../utils/index'
+
+// 协同相关功能
+
+// 创建容器
+function createUserListNode() {
+ // 如果没有注册协作插件,那么需要创建
+ if (!this.mindMap.cooperate) return
+ this._userListGroup = new G()
+ this.group.add(this._userListGroup)
+}
+
+// 创建文本头像
+function createTextAvatar(item) {
+ const { avatarSize, fontSize } = this.mindMap.opt.cooperateStyle
+ const g = new G()
+ const str = item.isMore ? item.name : String(item.name)[0]
+ // 圆
+ const circle = new Circle().size(avatarSize, avatarSize)
+ circle.fill({
+ color: item.color || generateColorByContent(str)
+ })
+ // 文本
+ const text = new Text()
+ .text(str)
+ .fill({
+ color: '#fff'
+ })
+ .css({
+ 'font-size': fontSize
+ })
+ .dx(-fontSize / 2)
+ .dy((avatarSize - fontSize) / 2)
+ g.add(circle).add(text)
+ return g
+}
+
+// 创建图片头像
+function createImageAvatar(item) {
+ const { avatarSize } = this.mindMap.opt.cooperateStyle
+ return new Image().load(item.avatar).size(avatarSize, avatarSize)
+}
+
+// 更新渲染
+function updateUserListNode() {
+ if (!this._userListGroup) return
+ const { avatarSize } = this.mindMap.opt.cooperateStyle
+ this._userListGroup.clear()
+ // 根据当前节点长度计算最多能显示几个
+ const length = this.userList.length
+ const maxShowCount = Math.floor(this.width / avatarSize)
+ const list = []
+ if (length > maxShowCount) {
+ // 如果当前用户数量比最多能显示的多,最后需要显示一个提示信息
+ list.push(...this.userList.slice(0, maxShowCount - 1), {
+ isMore: true,
+ name: '+' + (length - maxShowCount + 1)
+ })
+ } else {
+ list.push(...this.userList)
+ }
+ list.forEach((item, index) => {
+ let node = null
+ if (item.avatar) {
+ node = this.createImageAvatar(item)
+ } else {
+ node = this.createTextAvatar(item)
+ }
+ node.x(index * avatarSize).cy(-avatarSize / 2)
+ this._userListGroup.add(node)
+ })
+}
+
+// 添加用户
+function addUser(userInfo) {
+ if (
+ this.userList.find(item => {
+ return item.id == userInfo.id
+ })
+ )
+ return
+ this.userList.push(userInfo)
+ this.updateUserListNode()
+}
+
+// 移除用户
+function removeUser(userInfo) {
+ const index = this.userList.findIndex(item => {
+ return item.id == userInfo.id
+ })
+ if (index === -1) return
+ this.userList.splice(index, 1)
+ this.updateUserListNode()
+}
+
+export default {
+ createUserListNode,
+ updateUserListNode,
+ createTextAvatar,
+ createImageAvatar,
+ addUser,
+ removeUser
+}
diff --git a/simple-mind-map/src/plugins/Cooperate.js b/simple-mind-map/src/plugins/Cooperate.js
index 31fcf65f..877a4cdc 100644
--- a/simple-mind-map/src/plugins/Cooperate.js
+++ b/simple-mind-map/src/plugins/Cooperate.js
@@ -1,6 +1,6 @@
import * as Y from 'yjs'
import { WebrtcProvider } from 'y-webrtc'
-import { isSameObject, simpleDeepClone } from '../utils/index'
+import { isSameObject, simpleDeepClone, getType, isUndef } from '../utils/index'
// 协同插件
class Cooperate {
@@ -12,7 +12,10 @@ class Cooperate {
this.provider = new WebrtcProvider('demo-room', this.ydoc, {
signaling: ['ws://10.16.83.11:4444']
})
+ this.awareness = this.provider.awareness
this.currentData = null
+ this.userInfo = null
+ this.currentAwarenessData = []
// 处理数据
if (this.mindMap.opt.data) {
@@ -34,6 +37,14 @@ class Cooperate {
// 监听思维导图改变
this.onDataChange = this.onDataChange.bind(this)
this.mindMap.on('data_change', this.onDataChange)
+
+ // 监听思维导图节点激活事件
+ this.onNodeActive = this.onNodeActive.bind(this)
+ this.mindMap.on('node_active', this.onNodeActive)
+
+ // 监听状态同步事件
+ this.onAwareness = this.onAwareness.bind(this)
+ this.awareness.on('change', this.onAwareness)
}
// 解绑事件
@@ -66,6 +77,72 @@ class Cooperate {
this.updateChanges(res)
}
+ // 节点激活状态改变后触发状态显示同步
+ onNodeActive(node, nodeList) {
+ if (this.userInfo) {
+ this.awareness.setLocalStateField(this.userInfo.name, {
+ userInfo: {
+ // 用户信息
+ ...this.userInfo
+ },
+ nodeIdList: nodeList.map(item => {
+ // 当前激活的节点id列表
+ return item.uid
+ })
+ })
+ }
+ }
+
+ // 设置用户信息
+ /**
+ * {
+ * id: '', // 必传,用户唯一的id
+ * name: '', // 用户名称。name和avatar两个只传一个即可,如果都传了,会显示avatar
+ * avatar: '', // 用户头像
+ * color: '' // 如果没有传头像,那么会以一个圆形来显示名称的第一个字,文字的颜色为白色,圆的颜色可以通过该字段设置
+ * }
+ **/
+ setUserInfo(userInfo) {
+ if (
+ getType(userInfo) !== 'Object' ||
+ isUndef(userInfo.id) ||
+ (isUndef(userInfo.name) && isUndef(userInfo.avatar))
+ )
+ return
+ this.userInfo = userInfo || null
+ }
+
+ // 监听状态同步事件
+ onAwareness() {
+ const walk = (list, callback) => {
+ list.forEach(value => {
+ const userName = Object.keys(value)[0]
+ if (!userName) return
+ const data = value[userName]
+ const userInfo = data.userInfo
+ const nodeIdList = data.nodeIdList
+ nodeIdList.forEach(uid => {
+ const node = this.mindMap.renderer.findNodeByUid(uid)
+ if (node) {
+ callback(node, userInfo)
+ }
+ })
+ })
+ }
+ // 清除之前的状态
+ walk(this.currentAwarenessData, (node, userInfo) => {
+ node.removeUser(userInfo)
+ })
+ // 设置当前状态
+ const data = Array.from(this.awareness.getStates().values())
+ this.currentAwarenessData = data
+ walk(data, (node, userInfo) => {
+ // 不显示自己
+ if (userInfo.id === this.userInfo.id) return
+ node.addUser(userInfo)
+ })
+ }
+
// 将树结构转平级对象
/*
{
diff --git a/web/src/pages/Edit/components/Edit.vue b/web/src/pages/Edit/components/Edit.vue
index e0a1a609..480b701c 100644
--- a/web/src/pages/Edit/components/Edit.vue
+++ b/web/src/pages/Edit/components/Edit.vue
@@ -389,6 +389,16 @@ export default {
if (hasFileURL) {
this.$bus.$emit('handle_file_url')
}
+ if (this.$route.query.userName) {
+ this.mindMap.cooperate.setUserInfo({
+ id: Math.random(),
+ name: this.$route.query.userName,
+ color: ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399'][
+ Math.floor(Math.random() * 5)
+ ],
+ avatar: Math.random() > 0.5 ? 'https://img0.baidu.com/it/u=4270674549,2416627993&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1696006800&t=4d32871d14a7224a4591d0c3c7a97311' : ''
+ })
+ }
},
// url中是否存在要打开的文件
From ed82fe5a61fc4c7e42e30e3d338b44fe0c8b4c0c Mon Sep 17 00:00:00 2001
From: wanglin2 <1013335014@qq.com>
Date: Wed, 4 Oct 2023 15:37:57 +0800
Subject: [PATCH 04/86] =?UTF-8?q?Feat:=E5=AF=B9setData=E6=96=B9=E6=B3=95?=
=?UTF-8?q?=E4=BC=A0=E5=85=A5=E7=9A=84=E6=95=B0=E6=8D=AE=E8=BF=9B=E8=A1=8C?=
=?UTF-8?q?=E6=B7=B1=E6=8B=B7=E8=B4=9D;=E6=9B=B4=E6=96=B0=E6=B8=B2?=
=?UTF-8?q?=E6=9F=93=E6=A0=91=E6=95=B0=E6=8D=AE=E7=9A=84=E9=80=BB=E8=BE=91?=
=?UTF-8?q?=E7=A7=BB=E5=88=B0Render=E7=B1=BB=E4=B8=AD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
simple-mind-map/index.js | 8 +++-----
simple-mind-map/src/core/render/Render.js | 9 +++++++++
2 files changed, 12 insertions(+), 5 deletions(-)
diff --git a/simple-mind-map/index.js b/simple-mind-map/index.js
index 031571cc..6dc77fd2 100644
--- a/simple-mind-map/index.js
+++ b/simple-mind-map/index.js
@@ -258,15 +258,13 @@ class MindMap {
// 动态设置思维导图数据,纯节点数据
setData(data) {
+ data = simpleDeepClone(data || {})
this.execCommand('CLEAR_ACTIVE_NODE')
this.command.clearHistory()
this.command.addHistory()
- if (this.richText) {
- this.renderer.renderTree = this.richText.handleSetData(data)
- } else {
- this.renderer.renderTree = data
- }
+ this.renderer.setData(data)
this.reRender(() => {}, CONSTANTS.SET_DATA)
+ this.emit('set_data', data)
}
// 动态设置思维导图数据,包括节点数据、布局、主题、视图
diff --git a/simple-mind-map/src/core/render/Render.js b/simple-mind-map/src/core/render/Render.js
index 5f53134e..d0fa7ea6 100644
--- a/simple-mind-map/src/core/render/Render.js
+++ b/simple-mind-map/src/core/render/Render.js
@@ -97,6 +97,15 @@ class Render {
)(this, this.mindMap.opt.layout)
}
+ // 重新设置思维导图数据
+ setData(data) {
+ if (this.mindMap.richText) {
+ this.renderTree = this.mindMap.richText.handleSetData(data)
+ } else {
+ this.renderTree = data
+ }
+ }
+
// 绑定事件
bindEvent() {
// 点击事件
From ccef5fc581eac86a95445c65b862f39b4aa4f6d1 Mon Sep 17 00:00:00 2001
From: wanglin2 <1013335014@qq.com>
Date: Wed, 4 Oct 2023 15:38:48 +0800
Subject: [PATCH 05/86] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=8D=8F=E5=90=8C?=
=?UTF-8?q?=E6=8F=92=E4=BB=B6=E5=90=8E=E7=AB=AF=E6=9C=8D=E5=8A=A1=E5=90=AF?=
=?UTF-8?q?=E5=8A=A8=E5=91=BD=E4=BB=A4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
simple-mind-map/bin/{server.mjs => wsServer.mjs} | 0
simple-mind-map/package.json | 2 +-
2 files changed, 1 insertion(+), 1 deletion(-)
rename simple-mind-map/bin/{server.mjs => wsServer.mjs} (100%)
diff --git a/simple-mind-map/bin/server.mjs b/simple-mind-map/bin/wsServer.mjs
similarity index 100%
rename from simple-mind-map/bin/server.mjs
rename to simple-mind-map/bin/wsServer.mjs
diff --git a/simple-mind-map/package.json b/simple-mind-map/package.json
index 2c88fefc..5a22c55f 100644
--- a/simple-mind-map/package.json
+++ b/simple-mind-map/package.json
@@ -23,7 +23,7 @@
"lint": "eslint src/",
"format": "prettier --write .",
"types": "npx -p typescript tsc index.js --declaration --allowJs --emitDeclarationOnly --outDir types --target es2017",
- "serve": "node ./bin/server.mjs"
+ "wsServe": "node ./bin/server.mjs"
},
"module": "index.js",
"main": "./dist/simpleMindMap.umd.min.js",
From b95b6af1b16ace76e7be17973a2b2bc63a1f5293 Mon Sep 17 00:00:00 2001
From: wanglin2 <1013335014@qq.com>
Date: Wed, 4 Oct 2023 15:39:45 +0800
Subject: [PATCH 06/86] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=8D=8F=E5=90=8C?=
=?UTF-8?q?=E6=8F=92=E4=BB=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
simple-mind-map/src/plugins/Cooperate.js | 153 ++++++++++++++---------
web/src/pages/Edit/components/Edit.vue | 32 +++--
2 files changed, 117 insertions(+), 68 deletions(-)
diff --git a/simple-mind-map/src/plugins/Cooperate.js b/simple-mind-map/src/plugins/Cooperate.js
index 877a4cdc..d72377ab 100644
--- a/simple-mind-map/src/plugins/Cooperate.js
+++ b/simple-mind-map/src/plugins/Cooperate.js
@@ -7,33 +7,70 @@ class Cooperate {
constructor(opt) {
this.opt = opt
this.mindMap = opt.mindMap
+ // yjs文档
this.ydoc = new Y.Doc()
- this.ymap = this.ydoc.getMap()
- this.provider = new WebrtcProvider('demo-room', this.ydoc, {
- signaling: ['ws://10.16.83.11:4444']
- })
- this.awareness = this.provider.awareness
- this.currentData = null
- this.userInfo = null
+ // 共享数据
+ this.ymap = null
+ // 连接提供者
+ this.provider = null
+ // 感知数据
+ this.awareness = null
this.currentAwarenessData = []
-
- // 处理数据
- if (this.mindMap.opt.data) {
- this.currentData = this.transformTreeDataToObject(this.mindMap.opt.data)
- Object.keys(this.currentData).forEach(uid => {
- this.ymap.set(uid, this.currentData[uid])
- })
- }
-
+ // 当前的平级对象类型的思维导图数据
+ this.currentData = null
+ // 用户信息
+ this.userInfo = null
+ // 绑定事件
this.bindEvent()
+ // 处理实例化时传入的思维导图数据
+ if (this.mindMap.opt.data) {
+ this.initData(this.mindMap.opt.data)
+ }
+ }
+
+ // 初始化数据
+ initData(data) {
+ data = simpleDeepClone(data)
+ // 解绑原来的数据
+ if (this.ymap) {
+ this.ymap.unobserve(this.onObserve)
+ }
+ // 创建共享数据
+ this.ymap = this.ydoc.getMap()
+ // 思维导图树结构转平级对象结构
+ this.currentData = this.transformTreeDataToObject(data)
+ // 将思维导图数据添加到共享数据中
+ Object.keys(this.currentData).forEach(uid => {
+ this.ymap.set(uid, this.currentData[uid])
+ })
+ // 监听数据同步
+ this.onObserve = this.onObserve.bind(this)
+ this.ymap.observe(this.onObserve)
+ }
+
+ // 获取yjs doc实例
+ getDoc() {
+ return this.ydoc
+ }
+
+ // 设置连接提供者
+ setProvider(provider, webrtcProviderConfig = {}) {
+ const { roomName, signalingList, ...otherConfig } = webrtcProviderConfig
+ this.provider =
+ provider ||
+ new WebrtcProvider(roomName, this.ydoc, {
+ signaling: signalingList,
+ ...otherConfig
+ })
+ this.awareness = this.provider.awareness
+
+ // 监听状态同步事件
+ this.onAwareness = this.onAwareness.bind(this)
+ this.awareness.on('change', this.onAwareness)
}
// 绑定事件
bindEvent() {
- // 监听数据同步
- this.onObserve = this.onObserve.bind(this)
- this.ymap.observe(this.onObserve)
-
// 监听思维导图改变
this.onDataChange = this.onDataChange.bind(this)
this.mindMap.on('data_change', this.onDataChange)
@@ -42,31 +79,33 @@ class Cooperate {
this.onNodeActive = this.onNodeActive.bind(this)
this.mindMap.on('node_active', this.onNodeActive)
- // 监听状态同步事件
- this.onAwareness = this.onAwareness.bind(this)
- this.awareness.on('change', this.onAwareness)
+ // 监听设置思维导图数据事件
+ this.initData = this.initData.bind(this)
+ this.mindMap.on('set_data', this.initData)
}
// 解绑事件
unBindEvent() {
- this.ymap.unobserve(this.onObserve)
+ if (this.ymap) {
+ this.ymap.unobserve(this.onObserve)
+ }
this.mindMap.off('data_change', this.onDataChange)
+ this.mindMap.off('node_active', this.onNodeActive)
+ this.mindMap.off('set_data', this.initData)
this.ydoc.destroy()
}
// 数据同步时的处理,更新当前思维导图
onObserve(event) {
const data = event.target.toJSON()
+ // 如果数据没有改变直接返回
if (isSameObject(data, this.currentData)) return
this.currentData = data
+ // 平级对象转树结构
const res = this.transformObjectToTreeData(data)
if (!res) return
- if (this.mindMap.richText) {
- this.mindMap.renderer.renderTree =
- this.mindMap.richText.handleSetData(res)
- } else {
- this.mindMap.renderer.renderTree = res
- }
+ // 更新思维导图画布
+ this.mindMap.renderer.setData(res)
this.mindMap.render()
this.mindMap.command.addHistory()
}
@@ -77,16 +116,37 @@ class Cooperate {
this.updateChanges(res)
}
- // 节点激活状态改变后触发状态显示同步
+ // 找出更新点
+ updateChanges(data) {
+ const oldData = this.currentData
+ this.currentData = data
+ this.ydoc.transact(() => {
+ // 找出新增的或修改的
+ Object.keys(data).forEach(uid => {
+ // 新增的或已经存在的,如果数据发生了改变
+ if (!oldData[uid] || !isSameObject(oldData[uid], data[uid])) {
+ this.ymap.set(uid, data[uid])
+ }
+ })
+ // 找出删除的
+ Object.keys(oldData).forEach(uid => {
+ if (!data[uid]) {
+ this.ymap.delete(uid)
+ }
+ })
+ })
+ }
+
+ // 节点激活状态改变后触发感知数据同步
onNodeActive(node, nodeList) {
if (this.userInfo) {
this.awareness.setLocalStateField(this.userInfo.name, {
+ // 用户信息
userInfo: {
- // 用户信息
...this.userInfo
},
+ // 当前激活的节点id列表
nodeIdList: nodeList.map(item => {
- // 当前激活的节点id列表
return item.uid
})
})
@@ -112,7 +172,7 @@ class Cooperate {
this.userInfo = userInfo || null
}
- // 监听状态同步事件
+ // 监听感知数据同步事件
onAwareness() {
const walk = (list, callback) => {
list.forEach(value => {
@@ -129,11 +189,11 @@ class Cooperate {
})
})
}
- // 清除之前的状态
+ // 清除之前的数据
walk(this.currentAwarenessData, (node, userInfo) => {
node.removeUser(userInfo)
})
- // 设置当前状态
+ // 设置当前数据
const data = Array.from(this.awareness.getStates().values())
this.currentAwarenessData = data
walk(data, (node, userInfo) => {
@@ -246,27 +306,6 @@ class Cooperate {
return res
}
- // 找出更新点
- updateChanges(data) {
- const oldData = this.currentData
- this.currentData = data
- this.ydoc.transact(() => {
- // 找出新增的或修改的
- Object.keys(data).forEach(uid => {
- // 新增的或已经存在的,如果数据发生了改变
- if (!oldData[uid] || !isSameObject(oldData[uid], data[uid])) {
- this.ymap.set(uid, data[uid])
- }
- })
- // 找出删除的
- Object.keys(oldData).forEach(uid => {
- if (!data[uid]) {
- this.ymap.delete(uid)
- }
- })
- })
- }
-
// 插件被移除前做的事情
beforePluginRemove() {
this.unBindEvent()
diff --git a/web/src/pages/Edit/components/Edit.vue b/web/src/pages/Edit/components/Edit.vue
index 480b701c..5696a551 100644
--- a/web/src/pages/Edit/components/Edit.vue
+++ b/web/src/pages/Edit/components/Edit.vue
@@ -96,7 +96,7 @@ MindMap.usePlugin(MiniMap)
.usePlugin(Painter)
.usePlugin(ScrollbarPlugin)
.usePlugin(Formula)
- .usePlugin(Cooperate)
+ // .usePlugin(Cooperate)// 协同插件
// 注册自定义主题
customThemeList.forEach(item => {
@@ -389,16 +389,8 @@ export default {
if (hasFileURL) {
this.$bus.$emit('handle_file_url')
}
- if (this.$route.query.userName) {
- this.mindMap.cooperate.setUserInfo({
- id: Math.random(),
- name: this.$route.query.userName,
- color: ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399'][
- Math.floor(Math.random() * 5)
- ],
- avatar: Math.random() > 0.5 ? 'https://img0.baidu.com/it/u=4270674549,2416627993&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1696006800&t=4d32871d14a7224a4591d0c3c7a97311' : ''
- })
- }
+ // 协同测试
+ this.cooperateTest()
},
// url中是否存在要打开的文件
@@ -591,6 +583,24 @@ export default {
// 动态删除指定节点
// this.mindMap.execCommand('REMOVE_NODE', this.mindMap.renderer.root.children[0])
}, 5000)
+ },
+
+ // 协同测试
+ cooperateTest() {
+ if (this.mindMap.cooperate && this.$route.query.userName) {
+ this.mindMap.cooperate.setProvider(null, {
+ roomName: 'demo-room',
+ signalingList: ['ws://192.168.3.125:4444']
+ })
+ this.mindMap.cooperate.setUserInfo({
+ id: Math.random(),
+ name: this.$route.query.userName,
+ color: ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399'][
+ Math.floor(Math.random() * 5)
+ ],
+ avatar: Math.random() > 0.5 ? 'https://img0.baidu.com/it/u=4270674549,2416627993&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1696006800&t=4d32871d14a7224a4591d0c3c7a97311' : ''
+ })
+ }
}
}
}
From 545e46babcc1f34ffdf67e51744ad9114a769cd1 Mon Sep 17 00:00:00 2001
From: wanglin2 <1013335014@qq.com>
Date: Wed, 4 Oct 2023 16:01:47 +0800
Subject: [PATCH 07/86] =?UTF-8?q?Feat:=E6=B2=A1=E6=9C=89=E6=B3=A8=E5=86=8C?=
=?UTF-8?q?=E5=8D=8F=E5=90=8C=E6=8F=92=E4=BB=B6=E6=97=B6=E4=B8=8D=E7=BB=99?=
=?UTF-8?q?=E8=8A=82=E7=82=B9=E5=AE=9E=E4=BE=8B=E6=B7=BB=E5=8A=A0=E7=9B=B8?=
=?UTF-8?q?=E5=85=B3=E7=9A=84=E6=96=B9=E6=B3=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
simple-mind-map/src/core/render/node/Node.js | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/simple-mind-map/src/core/render/node/Node.js b/simple-mind-map/src/core/render/node/Node.js
index 8dc840d1..9df52019 100644
--- a/simple-mind-map/src/core/render/node/Node.js
+++ b/simple-mind-map/src/core/render/node/Node.js
@@ -126,9 +126,11 @@ class Node {
this[item] = nodeCreateContentsMethods[item].bind(this)
})
// 协同相关
- Object.keys(nodeCooperateMethods).forEach((item) => {
- this[item] = nodeCooperateMethods[item].bind(this)
- })
+ if (this.mindMap.cooperate) {
+ Object.keys(nodeCooperateMethods).forEach((item) => {
+ this[item] = nodeCooperateMethods[item].bind(this)
+ })
+ }
// 初始化
this.getSize()
}
@@ -291,7 +293,8 @@ class Node {
this.group.add(this.shapeNode)
// 渲染一个隐藏的矩形区域,用来触发展开收起按钮的显示
this.renderExpandBtnPlaceholderRect()
- this.createUserListNode()
+ // 创建协同头像节点
+ if (this.createUserListNode) this.createUserListNode()
// 概要节点添加一个带所属节点id的类名
if (this.isGeneralization && this.generalizationBelongNode) {
this.group.addClass('generalization_' + this.generalizationBelongNode.uid)
@@ -536,7 +539,8 @@ class Node {
}
// 更新概要
this.renderGeneralization()
- this.updateUserListNode()
+ // 更新协同头像
+ if (this.updateUserListNode) this.updateUserListNode()
// 更新节点位置
let t = this.group.transform()
// // 如果上次不在可视区内,且本次也不在,那么直接返回
From 99dc4431424046040c6b1cf692bc98d59db95de5 Mon Sep 17 00:00:00 2001
From: wanglin2 <1013335014@qq.com>
Date: Thu, 5 Oct 2023 10:27:24 +0800
Subject: [PATCH 08/86] Doc: update
---
README.md | 12 +++
simple-mind-map/package.json | 2 +-
web/src/assets/avatar/有希.jpg | Bin 0 -> 81395 bytes
web/src/assets/avatar/樊笼.jpg | Bin 0 -> 5282 bytes
web/src/assets/avatar/达仁科技.jpg | Bin 0 -> 4997 bytes
web/src/pages/Doc/catalogList.js | 1 +
web/src/pages/Doc/en/changelog/index.md | 6 ++
web/src/pages/Doc/en/changelog/index.vue | 3 +
web/src/pages/Doc/en/constructor/index.md | 2 +
web/src/pages/Doc/en/constructor/index.vue | 12 +++
web/src/pages/Doc/en/cooperate/index.md | 114 ++++++++++++++++++++
web/src/pages/Doc/en/cooperate/index.vue | 103 ++++++++++++++++++
web/src/pages/Doc/en/introduction/index.md | 12 +++
web/src/pages/Doc/en/introduction/index.vue | 12 +++
web/src/pages/Doc/en/render/index.md | 6 ++
web/src/pages/Doc/en/render/index.vue | 5 +
web/src/pages/Doc/en/utils/index.md | 8 ++
web/src/pages/Doc/en/utils/index.vue | 8 ++
web/src/pages/Doc/routerList.js | 2 +
web/src/pages/Doc/zh/changelog/index.md | 6 ++
web/src/pages/Doc/zh/changelog/index.vue | 3 +
web/src/pages/Doc/zh/constructor/index.md | 2 +
web/src/pages/Doc/zh/constructor/index.vue | 11 ++
web/src/pages/Doc/zh/cooperate/index.md | 114 ++++++++++++++++++++
web/src/pages/Doc/zh/cooperate/index.vue | 103 ++++++++++++++++++
web/src/pages/Doc/zh/introduction/index.md | 12 +++
web/src/pages/Doc/zh/introduction/index.vue | 12 +++
web/src/pages/Doc/zh/render/index.md | 6 ++
web/src/pages/Doc/zh/render/index.vue | 5 +
web/src/pages/Doc/zh/utils/index.md | 8 ++
web/src/pages/Doc/zh/utils/index.vue | 8 ++
31 files changed, 597 insertions(+), 1 deletion(-)
create mode 100644 web/src/assets/avatar/有希.jpg
create mode 100644 web/src/assets/avatar/樊笼.jpg
create mode 100644 web/src/assets/avatar/达仁科技.jpg
create mode 100644 web/src/pages/Doc/en/cooperate/index.md
create mode 100644 web/src/pages/Doc/en/cooperate/index.vue
create mode 100644 web/src/pages/Doc/zh/cooperate/index.md
create mode 100644 web/src/pages/Doc/zh/cooperate/index.vue
diff --git a/README.md b/README.md
index 6b76ad40..885b36af 100644
--- a/README.md
+++ b/README.md
@@ -189,4 +189,16 @@ const mindMap = new MindMap({
沐风牧草
+
+
+ 有希
+
+
+
+ 樊笼
+
+
+
+ 达仁科技
+
\ No newline at end of file
diff --git a/simple-mind-map/package.json b/simple-mind-map/package.json
index 5a22c55f..64ad8ad4 100644
--- a/simple-mind-map/package.json
+++ b/simple-mind-map/package.json
@@ -1,6 +1,6 @@
{
"name": "simple-mind-map",
- "version": "0.7.2",
+ "version": "0.7.3",
"description": "一个简单的web在线思维导图",
"authors": [
{
diff --git a/web/src/assets/avatar/有希.jpg b/web/src/assets/avatar/有希.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b6e3c85c7353fa5d6f6ad7d5a2de7bcdbe984337
GIT binary patch
literal 81395
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