feat: render interactive Mermaid diagrams
This commit is contained in:
Generated
+646
-1
@@ -15,12 +15,14 @@
|
|||||||
"@wecom/aibot-node-sdk": "^1.0.6",
|
"@wecom/aibot-node-sdk": "^1.0.6",
|
||||||
"cross-spawn": "^7.0.6",
|
"cross-spawn": "^7.0.6",
|
||||||
"dingtalk-stream": "^2.1.6-beta.1",
|
"dingtalk-stream": "^2.1.6-beta.1",
|
||||||
|
"dompurify": "^3.4.13",
|
||||||
"fflate": "^0.8.3",
|
"fflate": "^0.8.3",
|
||||||
"html-to-text": "^10.0.0",
|
"html-to-text": "^10.0.0",
|
||||||
"i18next": "^25.10.10",
|
"i18next": "^25.10.10",
|
||||||
"json5": "^2.2.3",
|
"json5": "^2.2.3",
|
||||||
"katex": "^0.16.47",
|
"katex": "^0.16.47",
|
||||||
"lucide-react": "^1.27.0",
|
"lucide-react": "^1.27.0",
|
||||||
|
"mermaid": "^11.16.1",
|
||||||
"onnxruntime-web": "^1.23.2",
|
"onnxruntime-web": "^1.23.2",
|
||||||
"pdfjs-dist": "^6.2.108",
|
"pdfjs-dist": "^6.2.108",
|
||||||
"ppu-paddle-ocr": "^6.4.0",
|
"ppu-paddle-ocr": "^6.4.0",
|
||||||
@@ -76,6 +78,19 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@antfu/install-pkg": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"package-manager-detector": "^1.3.0",
|
||||||
|
"tinyexec": "^1.0.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@antv/algorithm": {
|
"node_modules/@antv/algorithm": {
|
||||||
"version": "0.1.26",
|
"version": "0.1.26",
|
||||||
"resolved": "https://registry.npmjs.org/@antv/algorithm/-/algorithm-0.1.26.tgz",
|
"resolved": "https://registry.npmjs.org/@antv/algorithm/-/algorithm-0.1.26.tgz",
|
||||||
@@ -671,6 +686,12 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@braintree/sanitize-url": {
|
||||||
|
"version": "7.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz",
|
||||||
|
"integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@bramus/specificity": {
|
"node_modules/@bramus/specificity": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
|
||||||
@@ -684,6 +705,12 @@
|
|||||||
"specificity": "bin/cli.js"
|
"specificity": "bin/cli.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@chevrotain/types": {
|
||||||
|
"version": "11.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz",
|
||||||
|
"integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/@continuedev/cli": {
|
"node_modules/@continuedev/cli": {
|
||||||
"version": "1.5.47",
|
"version": "1.5.47",
|
||||||
"resolved": "https://registry.npmjs.org/@continuedev/cli/-/cli-1.5.47.tgz",
|
"resolved": "https://registry.npmjs.org/@continuedev/cli/-/cli-1.5.47.tgz",
|
||||||
@@ -1929,6 +1956,23 @@
|
|||||||
"url": "https://github.com/sponsors/nzakas"
|
"url": "https://github.com/sponsors/nzakas"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@iconify/types": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@iconify/utils": {
|
||||||
|
"version": "3.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.4.tgz",
|
||||||
|
"integrity": "sha512-b1S7B1k9ohZ+iNTi2ATxbRYG9fTrJmUT0rc46bvVnNxqNRGW7dyo/vRREwyniI5IRN2RSJHDcm+s3BjWrSAjHw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@antfu/install-pkg": "^1.1.0",
|
||||||
|
"@iconify/types": "^2.0.0",
|
||||||
|
"import-meta-resolve": "^4.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@img/sharp-darwin-arm64": {
|
"node_modules/@img/sharp-darwin-arm64": {
|
||||||
"version": "0.33.5",
|
"version": "0.33.5",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
|
||||||
@@ -2285,6 +2329,15 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@mermaid-js/parser": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-oYPyv8A4As1yH5Bx+04iQEQxXuIQDe0GKCNSRgao6z8AM9jixXIfP0vsppRLvGf+nKIOb9/LdpWA4YuJiVvESA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@chevrotain/types": "~11.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@modelcontextprotocol/sdk": {
|
"node_modules/@modelcontextprotocol/sdk": {
|
||||||
"version": "1.30.0",
|
"version": "1.30.0",
|
||||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz",
|
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz",
|
||||||
@@ -3337,24 +3390,111 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/d3": {
|
||||||
|
"version": "7.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz",
|
||||||
|
"integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-array": "*",
|
||||||
|
"@types/d3-axis": "*",
|
||||||
|
"@types/d3-brush": "*",
|
||||||
|
"@types/d3-chord": "*",
|
||||||
|
"@types/d3-color": "*",
|
||||||
|
"@types/d3-contour": "*",
|
||||||
|
"@types/d3-delaunay": "*",
|
||||||
|
"@types/d3-dispatch": "*",
|
||||||
|
"@types/d3-drag": "*",
|
||||||
|
"@types/d3-dsv": "*",
|
||||||
|
"@types/d3-ease": "*",
|
||||||
|
"@types/d3-fetch": "*",
|
||||||
|
"@types/d3-force": "*",
|
||||||
|
"@types/d3-format": "*",
|
||||||
|
"@types/d3-geo": "*",
|
||||||
|
"@types/d3-hierarchy": "*",
|
||||||
|
"@types/d3-interpolate": "*",
|
||||||
|
"@types/d3-path": "*",
|
||||||
|
"@types/d3-polygon": "*",
|
||||||
|
"@types/d3-quadtree": "*",
|
||||||
|
"@types/d3-random": "*",
|
||||||
|
"@types/d3-scale": "*",
|
||||||
|
"@types/d3-scale-chromatic": "*",
|
||||||
|
"@types/d3-selection": "*",
|
||||||
|
"@types/d3-shape": "*",
|
||||||
|
"@types/d3-time": "*",
|
||||||
|
"@types/d3-time-format": "*",
|
||||||
|
"@types/d3-timer": "*",
|
||||||
|
"@types/d3-transition": "*",
|
||||||
|
"@types/d3-zoom": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/d3-array": {
|
"node_modules/@types/d3-array": {
|
||||||
"version": "3.2.2",
|
"version": "3.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
|
||||||
"integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
|
"integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/d3-axis": {
|
||||||
|
"version": "3.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz",
|
||||||
|
"integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-selection": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-brush": {
|
||||||
|
"version": "3.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz",
|
||||||
|
"integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-selection": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-chord": {
|
||||||
|
"version": "3.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz",
|
||||||
|
"integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/d3-color": {
|
"node_modules/@types/d3-color": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
|
||||||
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
|
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/d3-contour": {
|
||||||
|
"version": "3.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz",
|
||||||
|
"integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-array": "*",
|
||||||
|
"@types/geojson": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-delaunay": {
|
||||||
|
"version": "6.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
|
||||||
|
"integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/d3-dispatch": {
|
"node_modules/@types/d3-dispatch": {
|
||||||
"version": "3.0.7",
|
"version": "3.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz",
|
||||||
"integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==",
|
"integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/d3-drag": {
|
||||||
|
"version": "3.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz",
|
||||||
|
"integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-selection": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/d3-dsv": {
|
"node_modules/@types/d3-dsv": {
|
||||||
"version": "3.0.7",
|
"version": "3.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz",
|
||||||
@@ -3418,6 +3558,12 @@
|
|||||||
"integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
|
"integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/d3-polygon": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/d3-quadtree": {
|
"node_modules/@types/d3-quadtree": {
|
||||||
"version": "3.0.6",
|
"version": "3.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz",
|
||||||
@@ -3445,6 +3591,12 @@
|
|||||||
"integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==",
|
"integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/d3-selection": {
|
||||||
|
"version": "3.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz",
|
||||||
|
"integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/d3-shape": {
|
"node_modules/@types/d3-shape": {
|
||||||
"version": "3.1.8",
|
"version": "3.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz",
|
||||||
@@ -3460,12 +3612,37 @@
|
|||||||
"integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
|
"integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/d3-time-format": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/d3-timer": {
|
"node_modules/@types/d3-timer": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
|
||||||
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
|
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/d3-transition": {
|
||||||
|
"version": "3.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz",
|
||||||
|
"integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-selection": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-zoom": {
|
||||||
|
"version": "3.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
|
||||||
|
"integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-interpolate": "*",
|
||||||
|
"@types/d3-selection": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/debug": {
|
"node_modules/@types/debug": {
|
||||||
"version": "4.1.13",
|
"version": "4.1.13",
|
||||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz",
|
||||||
@@ -3629,6 +3806,13 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/trusted-types": {
|
||||||
|
"version": "2.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||||
|
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/@types/unist": {
|
"node_modules/@types/unist": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
|
||||||
@@ -3884,6 +4068,16 @@
|
|||||||
"integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==",
|
"integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/@upsetjs/venn.js": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optionalDependencies": {
|
||||||
|
"d3-selection": "^3.0.0",
|
||||||
|
"d3-transition": "^3.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@vitejs/plugin-react": {
|
"node_modules/@vitejs/plugin-react": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz",
|
||||||
@@ -5120,6 +5314,15 @@
|
|||||||
"url": "https://opencollective.com/express"
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cose-base": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"layout-base": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-dirname": {
|
"node_modules/cross-dirname": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz",
|
||||||
@@ -5179,6 +5382,95 @@
|
|||||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/cytoscape": {
|
||||||
|
"version": "3.34.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.34.1.tgz",
|
||||||
|
"integrity": "sha512-Lr0RvH9H75y9ar8h9Toy6u4lxRSCcxUq+hHcQ26sVWo6BnaQp1gwEZOYqwuYTZhyW7npyKnNLP8oJ2p1/3OZ7g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cytoscape-cose-bilkent": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cose-base": "^1.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"cytoscape": "^3.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cytoscape-fcose": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cose-base": "^2.2.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"cytoscape": "^3.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cytoscape-fcose/node_modules/cose-base": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"layout-base": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cytoscape-fcose/node_modules/layout-base": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/d3": {
|
||||||
|
"version": "7.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz",
|
||||||
|
"integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "3",
|
||||||
|
"d3-axis": "3",
|
||||||
|
"d3-brush": "3",
|
||||||
|
"d3-chord": "3",
|
||||||
|
"d3-color": "3",
|
||||||
|
"d3-contour": "4",
|
||||||
|
"d3-delaunay": "6",
|
||||||
|
"d3-dispatch": "3",
|
||||||
|
"d3-drag": "3",
|
||||||
|
"d3-dsv": "3",
|
||||||
|
"d3-ease": "3",
|
||||||
|
"d3-fetch": "3",
|
||||||
|
"d3-force": "3",
|
||||||
|
"d3-format": "3",
|
||||||
|
"d3-geo": "3",
|
||||||
|
"d3-hierarchy": "3",
|
||||||
|
"d3-interpolate": "3",
|
||||||
|
"d3-path": "3",
|
||||||
|
"d3-polygon": "3",
|
||||||
|
"d3-quadtree": "3",
|
||||||
|
"d3-random": "3",
|
||||||
|
"d3-scale": "4",
|
||||||
|
"d3-scale-chromatic": "3",
|
||||||
|
"d3-selection": "3",
|
||||||
|
"d3-shape": "3",
|
||||||
|
"d3-time": "3",
|
||||||
|
"d3-time-format": "4",
|
||||||
|
"d3-timer": "3",
|
||||||
|
"d3-transition": "3",
|
||||||
|
"d3-zoom": "3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/d3-array": {
|
"node_modules/d3-array": {
|
||||||
"version": "3.2.4",
|
"version": "3.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||||
@@ -5191,12 +5483,49 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/d3-axis": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/d3-binarytree": {
|
"node_modules/d3-binarytree": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/d3-binarytree/-/d3-binarytree-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/d3-binarytree/-/d3-binarytree-1.0.2.tgz",
|
||||||
"integrity": "sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==",
|
"integrity": "sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/d3-brush": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-dispatch": "1 - 3",
|
||||||
|
"d3-drag": "2 - 3",
|
||||||
|
"d3-interpolate": "1 - 3",
|
||||||
|
"d3-selection": "3",
|
||||||
|
"d3-transition": "3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-chord": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-path": "1 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/d3-color": {
|
"node_modules/d3-color": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
||||||
@@ -5206,6 +5535,30 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/d3-contour": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "^3.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-delaunay": {
|
||||||
|
"version": "6.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
|
||||||
|
"integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"delaunator": "5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/d3-dispatch": {
|
"node_modules/d3-dispatch": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
|
||||||
@@ -5215,6 +5568,19 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/d3-drag": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-dispatch": "1 - 3",
|
||||||
|
"d3-selection": "3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/d3-dsv": {
|
"node_modules/d3-dsv": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz",
|
||||||
@@ -5399,6 +5765,15 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/d3-polygon": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/d3-quadtree": {
|
"node_modules/d3-quadtree": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz",
|
||||||
@@ -5423,6 +5798,46 @@
|
|||||||
"integrity": "sha512-PF8GWEL70cHHWpx2jUQXc68r1pyPHIA+St16muk/XRokETzlegj5LriNKg7o4LR0TySug4nHYPJNNRz/W+/Niw==",
|
"integrity": "sha512-PF8GWEL70cHHWpx2jUQXc68r1pyPHIA+St16muk/XRokETzlegj5LriNKg7o4LR0TySug4nHYPJNNRz/W+/Niw==",
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
|
"node_modules/d3-sankey": {
|
||||||
|
"version": "0.12.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz",
|
||||||
|
"integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "1 - 2",
|
||||||
|
"d3-shape": "^1.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-sankey/node_modules/d3-array": {
|
||||||
|
"version": "2.12.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
|
||||||
|
"integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"internmap": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-sankey/node_modules/d3-path": {
|
||||||
|
"version": "1.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
|
||||||
|
"integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/d3-sankey/node_modules/d3-shape": {
|
||||||
|
"version": "1.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
|
||||||
|
"integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-path": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-sankey/node_modules/internmap": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/d3-scale": {
|
"node_modules/d3-scale": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
|
||||||
@@ -5452,6 +5867,15 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/d3-selection": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/d3-shape": {
|
"node_modules/d3-shape": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
|
||||||
@@ -5497,6 +5921,41 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/d3-transition": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-color": "1 - 3",
|
||||||
|
"d3-dispatch": "1 - 3",
|
||||||
|
"d3-ease": "1 - 3",
|
||||||
|
"d3-interpolate": "1 - 3",
|
||||||
|
"d3-timer": "1 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"d3-selection": "2 - 3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-zoom": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-dispatch": "1 - 3",
|
||||||
|
"d3-drag": "2 - 3",
|
||||||
|
"d3-interpolate": "1 - 3",
|
||||||
|
"d3-selection": "2 - 3",
|
||||||
|
"d3-transition": "2 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dagre": {
|
"node_modules/dagre": {
|
||||||
"version": "0.8.5",
|
"version": "0.8.5",
|
||||||
"resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz",
|
"resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz",
|
||||||
@@ -5507,6 +5966,16 @@
|
|||||||
"lodash": "^4.17.15"
|
"lodash": "^4.17.15"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dagre-d3-es": {
|
||||||
|
"version": "7.0.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz",
|
||||||
|
"integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"d3": "^7.9.0",
|
||||||
|
"lodash-es": "^4.17.21"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/data-urls": {
|
"node_modules/data-urls": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz",
|
||||||
@@ -5536,6 +6005,12 @@
|
|||||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dayjs": {
|
||||||
|
"version": "1.11.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz",
|
||||||
|
"integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.3",
|
"version": "4.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||||
@@ -5675,6 +6150,15 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/delaunator": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"robust-predicates": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/delayed-stream": {
|
"node_modules/delayed-stream": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
@@ -5856,6 +6340,15 @@
|
|||||||
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dompurify": {
|
||||||
|
"version": "3.4.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.13.tgz",
|
||||||
|
"integrity": "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==",
|
||||||
|
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@types/trusted-types": "^2.0.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/domutils": {
|
"node_modules/domutils": {
|
||||||
"version": "3.2.2",
|
"version": "3.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
|
||||||
@@ -6245,6 +6738,17 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/es-toolkit": {
|
||||||
|
"version": "1.50.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.50.0.tgz",
|
||||||
|
"integrity": "sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"workspaces": [
|
||||||
|
"docs",
|
||||||
|
"benchmarks",
|
||||||
|
"tests/types"
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/es6-error": {
|
"node_modules/es6-error": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
|
||||||
@@ -7254,6 +7758,12 @@
|
|||||||
"integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==",
|
"integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/hachure-fill": {
|
||||||
|
"version": "0.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz",
|
||||||
|
"integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/has-flag": {
|
"node_modules/has-flag": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
@@ -7789,6 +8299,16 @@
|
|||||||
"node": ">= 4"
|
"node": ">= 4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/import-meta-resolve": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/imurmurhash": {
|
"node_modules/imurmurhash": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
|
||||||
@@ -8234,6 +8754,17 @@
|
|||||||
"json-buffer": "3.0.1"
|
"json-buffer": "3.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/khroma": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="
|
||||||
|
},
|
||||||
|
"node_modules/layout-base": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lazy-val": {
|
"node_modules/lazy-val": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz",
|
||||||
@@ -8381,6 +8912,18 @@
|
|||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/marked": {
|
||||||
|
"version": "16.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz",
|
||||||
|
"integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"marked": "bin/marked.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/matcher": {
|
"node_modules/matcher": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
|
||||||
@@ -8737,6 +9280,35 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mermaid": {
|
||||||
|
"version": "11.16.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.16.1.tgz",
|
||||||
|
"integrity": "sha512-TQsq6u22fAn3rek5VOubrhKPo1g5hwC3FXUN9hiyupTckcYiGuuKGkNQrKYwGJkXUxZdojwRG46gsSCFZMDp4g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@braintree/sanitize-url": "^7.1.2",
|
||||||
|
"@iconify/utils": "^3.0.2",
|
||||||
|
"@mermaid-js/parser": "^1.2.0",
|
||||||
|
"@types/d3": "^7.4.3",
|
||||||
|
"@upsetjs/venn.js": "^2.0.0",
|
||||||
|
"cytoscape": "^3.33.3",
|
||||||
|
"cytoscape-cose-bilkent": "^4.1.0",
|
||||||
|
"cytoscape-fcose": "^2.2.0",
|
||||||
|
"d3": "^7.9.0",
|
||||||
|
"d3-sankey": "^0.12.3",
|
||||||
|
"dagre-d3-es": "7.0.14",
|
||||||
|
"dayjs": "^1.11.20",
|
||||||
|
"dompurify": "^3.3.3",
|
||||||
|
"es-toolkit": "^1.45.1",
|
||||||
|
"katex": "^0.16.45",
|
||||||
|
"khroma": "^2.1.0",
|
||||||
|
"marked": "^16.3.0",
|
||||||
|
"roughjs": "^4.6.6",
|
||||||
|
"stylis": "^4.3.6",
|
||||||
|
"ts-dedent": "^2.2.0",
|
||||||
|
"uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/micromark": {
|
"node_modules/micromark": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz",
|
||||||
@@ -10050,6 +10622,12 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/package-manager-detector": {
|
||||||
|
"version": "1.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.8.0.tgz",
|
||||||
|
"integrity": "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/parchment": {
|
"node_modules/parchment": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/parchment/-/parchment-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/parchment/-/parchment-3.0.0.tgz",
|
||||||
@@ -10116,6 +10694,12 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/path-data-parser": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz",
|
||||||
|
"integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/path-exists": {
|
"node_modules/path-exists": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||||
@@ -10323,6 +10907,22 @@
|
|||||||
"node": ">=14.19.0"
|
"node": ">=14.19.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/points-on-curve": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/points-on-path": {
|
||||||
|
"version": "0.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz",
|
||||||
|
"integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"path-data-parser": "0.1.0",
|
||||||
|
"points-on-curve": "0.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.25",
|
"version": "8.5.25",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz",
|
||||||
@@ -11194,6 +11794,12 @@
|
|||||||
"node": ">=8.0"
|
"node": ">=8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/robust-predicates": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==",
|
||||||
|
"license": "Unlicense"
|
||||||
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.62.3",
|
"version": "4.62.3",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.3.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.3.tgz",
|
||||||
@@ -11239,6 +11845,18 @@
|
|||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/roughjs": {
|
||||||
|
"version": "4.6.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz",
|
||||||
|
"integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"hachure-fill": "^0.5.2",
|
||||||
|
"path-data-parser": "^0.1.0",
|
||||||
|
"points-on-curve": "^0.2.0",
|
||||||
|
"points-on-path": "^0.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/router": {
|
"node_modules/router": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
|
||||||
@@ -11753,6 +12371,12 @@
|
|||||||
"inline-style-parser": "0.2.7"
|
"inline-style-parser": "0.2.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/stylis": {
|
||||||
|
"version": "4.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz",
|
||||||
|
"integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/sumchecker": {
|
"node_modules/sumchecker": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
|
||||||
@@ -11885,7 +12509,6 @@
|
|||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz",
|
||||||
"integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==",
|
"integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
@@ -12036,6 +12659,15 @@
|
|||||||
"typescript": ">=4.8.4"
|
"typescript": ">=4.8.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ts-dedent": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tslib": {
|
"node_modules/tslib": {
|
||||||
"version": "2.8.1",
|
"version": "2.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
@@ -12427,6 +13059,19 @@
|
|||||||
"base64-arraybuffer": "^1.0.2"
|
"base64-arraybuffer": "^1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "14.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.1.tgz",
|
||||||
|
"integrity": "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==",
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/sponsors/broofa",
|
||||||
|
"https://github.com/sponsors/ctavan"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist-node/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vary": {
|
"node_modules/vary": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
|
|||||||
@@ -120,6 +120,18 @@
|
|||||||
{
|
{
|
||||||
"from": "node_modules/katex/LICENSE",
|
"from": "node_modules/katex/LICENSE",
|
||||||
"to": "licenses/katex-MIT.txt"
|
"to": "licenses/katex-MIT.txt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"from": "node_modules/mermaid/LICENSE",
|
||||||
|
"to": "licenses/mermaid-MIT.txt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"from": "node_modules/dompurify/LICENSE",
|
||||||
|
"to": "licenses/dompurify-Apache-2.0.txt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"from": "node_modules/dompurify/LICENSE-MPL",
|
||||||
|
"to": "licenses/dompurify-MPL-2.0.txt"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"win": {
|
"win": {
|
||||||
@@ -165,12 +177,14 @@
|
|||||||
"@wecom/aibot-node-sdk": "^1.0.6",
|
"@wecom/aibot-node-sdk": "^1.0.6",
|
||||||
"cross-spawn": "^7.0.6",
|
"cross-spawn": "^7.0.6",
|
||||||
"dingtalk-stream": "^2.1.6-beta.1",
|
"dingtalk-stream": "^2.1.6-beta.1",
|
||||||
|
"dompurify": "^3.4.13",
|
||||||
"fflate": "^0.8.3",
|
"fflate": "^0.8.3",
|
||||||
"html-to-text": "^10.0.0",
|
"html-to-text": "^10.0.0",
|
||||||
"i18next": "^25.10.10",
|
"i18next": "^25.10.10",
|
||||||
"json5": "^2.2.3",
|
"json5": "^2.2.3",
|
||||||
"katex": "^0.16.47",
|
"katex": "^0.16.47",
|
||||||
"lucide-react": "^1.27.0",
|
"lucide-react": "^1.27.0",
|
||||||
|
"mermaid": "^11.16.1",
|
||||||
"onnxruntime-web": "^1.23.2",
|
"onnxruntime-web": "^1.23.2",
|
||||||
"pdfjs-dist": "^6.2.108",
|
"pdfjs-dist": "^6.2.108",
|
||||||
"ppu-paddle-ocr": "^6.4.0",
|
"ppu-paddle-ocr": "^6.4.0",
|
||||||
|
|||||||
@@ -4314,6 +4314,82 @@ describe('App', () => {
|
|||||||
).not.toBeInTheDocument()
|
).not.toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('excludes ignored heartbeat suggestions from the navigation badge', async () => {
|
||||||
|
const heartbeatId = '00000000-0000-4000-8000-000000000701'
|
||||||
|
const cancelledTaskId = '00000000-0000-4000-8000-000000000801'
|
||||||
|
const pendingTaskId = '00000000-0000-4000-8000-000000000802'
|
||||||
|
vi.mocked(api.heartbeats.list).mockResolvedValue([
|
||||||
|
{
|
||||||
|
id: heartbeatId,
|
||||||
|
projectId,
|
||||||
|
name: '每日回顾',
|
||||||
|
timezone: 'Asia/Shanghai',
|
||||||
|
recurrence: { type: 'daily', localTime: '09:00' },
|
||||||
|
enabled: true,
|
||||||
|
lookbackHours: 24,
|
||||||
|
retentionDays: 30,
|
||||||
|
nextRunAt: '2026-08-05T01:00:00.000Z',
|
||||||
|
createdAt: '2026-08-01T00:00:00.000Z',
|
||||||
|
updatedAt: '2026-08-01T00:00:00.000Z'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
vi.mocked(api.heartbeats.history).mockResolvedValue({
|
||||||
|
runs: [],
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
id: '00000000-0000-4000-8000-000000000901',
|
||||||
|
configId: heartbeatId,
|
||||||
|
runId: '00000000-0000-4000-8000-000000000902',
|
||||||
|
scheduledFor: '2026-08-04T01:00:00.000Z',
|
||||||
|
summary: '建议处理两个后续行动。',
|
||||||
|
highlights: [],
|
||||||
|
proposedMemoryIds: [],
|
||||||
|
followUpTaskIds: [cancelledTaskId, pendingTaskId],
|
||||||
|
createdAt: '2026-08-04T01:00:00.000Z'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
vi.mocked(api.tasks.list).mockResolvedValue([
|
||||||
|
{
|
||||||
|
id: cancelledTaskId,
|
||||||
|
projectId,
|
||||||
|
title: '已忽略建议',
|
||||||
|
instructions: '无需继续处理。',
|
||||||
|
origin: 'assistant',
|
||||||
|
status: 'cancelled',
|
||||||
|
createdAt: '2026-08-04T01:00:00.000Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: pendingTaskId,
|
||||||
|
projectId,
|
||||||
|
title: '待处理建议',
|
||||||
|
instructions: '继续处理此建议。',
|
||||||
|
origin: 'assistant',
|
||||||
|
status: 'paused',
|
||||||
|
createdAt: '2026-08-04T01:00:00.000Z'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
try {
|
||||||
|
render(<App />)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await screen.findByLabelText('1 条待处理建议')
|
||||||
|
).toBeInTheDocument()
|
||||||
|
expect(
|
||||||
|
screen.queryByLabelText('2 条待处理建议')
|
||||||
|
).not.toBeInTheDocument()
|
||||||
|
} finally {
|
||||||
|
cleanup()
|
||||||
|
vi.mocked(api.heartbeats.list).mockResolvedValue([])
|
||||||
|
vi.mocked(api.heartbeats.history).mockResolvedValue({
|
||||||
|
runs: [],
|
||||||
|
entries: []
|
||||||
|
})
|
||||||
|
vi.mocked(api.tasks.list).mockResolvedValue([])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
it('shows retryable page-local knowledge errors without an empty-state flash', async () => {
|
it('shows retryable page-local knowledge errors without an empty-state flash', async () => {
|
||||||
vi.mocked(api.knowledge.getSnapshot).mockRejectedValueOnce(
|
vi.mocked(api.knowledge.getSnapshot).mockRejectedValueOnce(
|
||||||
new Error('知识数据库暂时不可用')
|
new Error('知识数据库暂时不可用')
|
||||||
|
|||||||
@@ -2120,7 +2120,9 @@ function App(): React.JSX.Element {
|
|||||||
).length +
|
).length +
|
||||||
assistantTasks.filter(
|
assistantTasks.filter(
|
||||||
(task) =>
|
(task) =>
|
||||||
taskIds.has(task.id) && task.status !== 'completed'
|
taskIds.has(task.id) &&
|
||||||
|
task.status !== 'completed' &&
|
||||||
|
task.status !== 'cancelled'
|
||||||
).length
|
).length
|
||||||
)
|
)
|
||||||
}, [assistantMemories, assistantTasks, heartbeatEntries])
|
}, [assistantMemories, assistantTasks, heartbeatEntries])
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import type {
|
|||||||
KnowledgeGraphNode,
|
KnowledgeGraphNode,
|
||||||
KnowledgeGraphRelation
|
KnowledgeGraphRelation
|
||||||
} from '../../shared/contracts'
|
} from '../../shared/contracts'
|
||||||
|
import { useDocumentTheme } from './use-document-theme'
|
||||||
|
|
||||||
type ChartKnowledgeGraphNode = Omit<
|
type ChartKnowledgeGraphNode = Omit<
|
||||||
KnowledgeGraphNode,
|
KnowledgeGraphNode,
|
||||||
@@ -347,7 +348,7 @@ export function KnowledgeGraphChart({
|
|||||||
() => graphRevision(nodes, relations),
|
() => graphRevision(nodes, relations),
|
||||||
[nodes, relations]
|
[nodes, relations]
|
||||||
)
|
)
|
||||||
const [themeRevision, setThemeRevision] = useState(0)
|
const documentTheme = useDocumentTheme()
|
||||||
const [renderError, setRenderError] = useState<string>()
|
const [renderError, setRenderError] = useState<string>()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -407,20 +408,6 @@ export function KnowledgeGraphChart({
|
|||||||
})
|
})
|
||||||
}, [dataRevision, fitViewRequest, renderErrorFallback])
|
}, [dataRevision, fitViewRequest, renderErrorFallback])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (typeof MutationObserver !== 'function') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const observer = new MutationObserver(() => {
|
|
||||||
setThemeRevision((revision) => revision + 1)
|
|
||||||
})
|
|
||||||
observer.observe(document.documentElement, {
|
|
||||||
attributes: true,
|
|
||||||
attributeFilter: ['data-theme']
|
|
||||||
})
|
|
||||||
return () => observer.disconnect()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const container = containerRef.current
|
const container = containerRef.current
|
||||||
if (!container) {
|
if (!container) {
|
||||||
@@ -583,7 +570,7 @@ export function KnowledgeGraphChart({
|
|||||||
locale,
|
locale,
|
||||||
relationFallback,
|
relationFallback,
|
||||||
renderErrorFallback,
|
renderErrorFallback,
|
||||||
themeRevision
|
documentTheme
|
||||||
])
|
])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,10 +1,45 @@
|
|||||||
import { cleanup, render, screen } from '@testing-library/react'
|
import {
|
||||||
import { afterEach, describe, expect, it } from 'vitest'
|
act,
|
||||||
|
cleanup,
|
||||||
|
fireEvent,
|
||||||
|
render,
|
||||||
|
screen,
|
||||||
|
waitFor
|
||||||
|
} from '@testing-library/react'
|
||||||
|
import {
|
||||||
|
afterEach,
|
||||||
|
beforeEach,
|
||||||
|
describe,
|
||||||
|
expect,
|
||||||
|
it,
|
||||||
|
vi
|
||||||
|
} from 'vitest'
|
||||||
import { changeUiLocale } from './i18n'
|
import { changeUiLocale } from './i18n'
|
||||||
import { MarkdownRenderer } from './MarkdownRenderer'
|
import { MarkdownRenderer } from './MarkdownRenderer'
|
||||||
|
|
||||||
|
const mermaidMock = vi.hoisted(() => ({
|
||||||
|
initialize: vi.fn(),
|
||||||
|
render: vi.fn()
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('mermaid', () => ({
|
||||||
|
default: mermaidMock
|
||||||
|
}))
|
||||||
|
|
||||||
describe('MarkdownRenderer', () => {
|
describe('MarkdownRenderer', () => {
|
||||||
afterEach(cleanup)
|
beforeEach(() => {
|
||||||
|
mermaidMock.render.mockResolvedValue({
|
||||||
|
svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 50">
|
||||||
|
<rect id="safe-node" width="100" height="50" />
|
||||||
|
</svg>`
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup()
|
||||||
|
delete document.documentElement.dataset.theme
|
||||||
|
vi.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
it('renders CommonMark and GitHub Flavored Markdown', () => {
|
it('renders CommonMark and GitHub Flavored Markdown', () => {
|
||||||
render(
|
render(
|
||||||
@@ -165,6 +200,282 @@ $\\href{javascript:alert(1)}{unsafe}$`}</MarkdownRenderer>
|
|||||||
).not.toBeInTheDocument()
|
).not.toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('renders Mermaid fences with strict bounded configuration', async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<MarkdownRenderer>{`\`\`\`mermaid
|
||||||
|
flowchart LR
|
||||||
|
Start --> Finish
|
||||||
|
\`\`\``}</MarkdownRenderer>
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByText('正在绘制 Mermaid 图表…')
|
||||||
|
).toHaveAttribute('role', 'status')
|
||||||
|
const diagram = await screen.findByRole('region', {
|
||||||
|
name: 'Mermaid 图表,可横向滚动'
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(diagram).toHaveAttribute('tabindex', '0')
|
||||||
|
expect(container.querySelector('#safe-node')).toBeInTheDocument()
|
||||||
|
expect(mermaidMock.render).toHaveBeenCalledWith(
|
||||||
|
expect.stringMatching(/^goodbuddy-mermaid-\d+-0$/u),
|
||||||
|
'flowchart LR\n Start --> Finish'
|
||||||
|
)
|
||||||
|
expect(mermaidMock.initialize).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
htmlLabels: false,
|
||||||
|
maxEdges: 300,
|
||||||
|
maxTextSize: 20_000,
|
||||||
|
securityLevel: 'strict',
|
||||||
|
startOnLoad: false,
|
||||||
|
suppressErrorRendering: true,
|
||||||
|
theme: 'default'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows source and opens an accessible zoomable Mermaid viewer', async () => {
|
||||||
|
render(
|
||||||
|
<div className="app-shell">
|
||||||
|
<MarkdownRenderer>{`\`\`\`mermaid
|
||||||
|
flowchart LR
|
||||||
|
Start --> Finish
|
||||||
|
\`\`\``}</MarkdownRenderer>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
await screen.findByRole('region', {
|
||||||
|
name: 'Mermaid 图表,可横向滚动'
|
||||||
|
})
|
||||||
|
const sourceButton = screen.getByRole('button', {
|
||||||
|
name: '查看源码'
|
||||||
|
})
|
||||||
|
expect(sourceButton).toHaveAttribute('aria-expanded', 'false')
|
||||||
|
fireEvent.click(sourceButton)
|
||||||
|
expect(
|
||||||
|
screen.getByRole('button', { name: '隐藏源码' })
|
||||||
|
).toHaveAttribute('aria-expanded', 'true')
|
||||||
|
expect(screen.getByText(/Start --> Finish/u)).toBeInTheDocument()
|
||||||
|
|
||||||
|
const viewerButton = screen.getByRole('button', {
|
||||||
|
name: '打开大图'
|
||||||
|
})
|
||||||
|
viewerButton.focus()
|
||||||
|
fireEvent.click(viewerButton)
|
||||||
|
const dialog = screen.getByRole('dialog', {
|
||||||
|
name: 'Mermaid 大图'
|
||||||
|
})
|
||||||
|
expect(dialog).toHaveAttribute('aria-modal', 'true')
|
||||||
|
expect(
|
||||||
|
screen.getByRole('button', { name: '关闭 Mermaid 大图' })
|
||||||
|
).toHaveFocus()
|
||||||
|
expect(
|
||||||
|
document.querySelector<HTMLElement>('.app-shell')?.inert
|
||||||
|
).toBe(true)
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: '放大图表' }))
|
||||||
|
expect(screen.getByLabelText('当前缩放比例')).toHaveTextContent(
|
||||||
|
'125%'
|
||||||
|
)
|
||||||
|
const canvas = screen.getByRole('region', {
|
||||||
|
name: '可缩放、可拖动的 Mermaid 图表'
|
||||||
|
})
|
||||||
|
expect(canvas).toHaveAttribute('tabindex', '0')
|
||||||
|
canvas.focus()
|
||||||
|
expect(canvas).toHaveFocus()
|
||||||
|
fireEvent.keyDown(dialog, { key: 'Tab' })
|
||||||
|
expect(
|
||||||
|
screen.getByRole('button', { name: '缩小图表' })
|
||||||
|
).toHaveFocus()
|
||||||
|
fireEvent.wheel(canvas, { deltaY: -100 })
|
||||||
|
fireEvent.wheel(canvas, { deltaY: -100 })
|
||||||
|
fireEvent.wheel(canvas, { deltaY: -100 })
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByLabelText('当前缩放比例')).toHaveTextContent(
|
||||||
|
'200%'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: '重置缩放' }))
|
||||||
|
expect(screen.getByLabelText('当前缩放比例')).toHaveTextContent(
|
||||||
|
'100%'
|
||||||
|
)
|
||||||
|
|
||||||
|
fireEvent.keyDown(dialog, { key: 'Escape' })
|
||||||
|
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
|
||||||
|
await waitFor(() => expect(viewerButton).toHaveFocus())
|
||||||
|
expect(
|
||||||
|
document.querySelector<HTMLElement>('.app-shell')?.inert
|
||||||
|
).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('closes Mermaid controls when streamed source changes', async () => {
|
||||||
|
const { rerender } = render(
|
||||||
|
<MarkdownRenderer>{`\`\`\`mermaid
|
||||||
|
flowchart LR
|
||||||
|
A --> B
|
||||||
|
\`\`\``}</MarkdownRenderer>
|
||||||
|
)
|
||||||
|
await screen.findByRole('region', {
|
||||||
|
name: 'Mermaid 图表,可横向滚动'
|
||||||
|
})
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: '查看源码' }))
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: '打开大图' }))
|
||||||
|
expect(screen.getByRole('dialog')).toBeInTheDocument()
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<MarkdownRenderer>{`\`\`\`mermaid
|
||||||
|
flowchart LR
|
||||||
|
A --> B --> C
|
||||||
|
\`\`\``}</MarkdownRenderer>
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
|
||||||
|
await screen.findByRole('region', {
|
||||||
|
name: 'Mermaid 图表,可横向滚动'
|
||||||
|
})
|
||||||
|
expect(
|
||||||
|
screen.getByRole('button', { name: '查看源码' })
|
||||||
|
).toHaveAttribute('aria-expanded', 'false')
|
||||||
|
expect(screen.queryByText(/A --> B --> C/u)).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('pans the large Mermaid diagram with pointer dragging', async () => {
|
||||||
|
render(
|
||||||
|
<MarkdownRenderer>{`\`\`\`mermaid
|
||||||
|
flowchart LR
|
||||||
|
A --> B
|
||||||
|
\`\`\``}</MarkdownRenderer>
|
||||||
|
)
|
||||||
|
await screen.findByRole('region', {
|
||||||
|
name: 'Mermaid 图表,可横向滚动'
|
||||||
|
})
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: '打开大图' }))
|
||||||
|
const canvas = screen.getByRole('region', {
|
||||||
|
name: '可缩放、可拖动的 Mermaid 图表'
|
||||||
|
})
|
||||||
|
const setPointerCapture = vi.fn()
|
||||||
|
const releasePointerCapture = vi.fn()
|
||||||
|
Object.defineProperties(canvas, {
|
||||||
|
setPointerCapture: { value: setPointerCapture },
|
||||||
|
hasPointerCapture: { value: () => true },
|
||||||
|
releasePointerCapture: { value: releasePointerCapture }
|
||||||
|
})
|
||||||
|
canvas.scrollLeft = 30
|
||||||
|
canvas.scrollTop = 40
|
||||||
|
|
||||||
|
fireEvent.pointerDown(canvas, {
|
||||||
|
button: 0,
|
||||||
|
clientX: 100,
|
||||||
|
clientY: 100,
|
||||||
|
pointerId: 9
|
||||||
|
})
|
||||||
|
fireEvent.pointerMove(canvas, {
|
||||||
|
clientX: 70,
|
||||||
|
clientY: 60,
|
||||||
|
pointerId: 9
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(setPointerCapture).toHaveBeenCalledWith(9)
|
||||||
|
expect(canvas).toHaveClass('mermaid-viewer__canvas--dragging')
|
||||||
|
expect(canvas.scrollLeft).toBe(60)
|
||||||
|
expect(canvas.scrollTop).toBe(80)
|
||||||
|
|
||||||
|
fireEvent.pointerUp(canvas, { pointerId: 9 })
|
||||||
|
expect(releasePointerCapture).toHaveBeenCalledWith(9)
|
||||||
|
expect(canvas).not.toHaveClass(
|
||||||
|
'mermaid-viewer__canvas--dragging'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sanitizes Mermaid SVG without enabling diagram interactions', async () => {
|
||||||
|
mermaidMock.render.mockResolvedValueOnce({
|
||||||
|
svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 50">
|
||||||
|
<style>@\\69mport url("https://unsafe.test/style.css");</style>
|
||||||
|
<script>window.bad = true</script>
|
||||||
|
<a href="javascript:alert(1)">
|
||||||
|
<rect id="unsafe-node" onclick="alert(1)"
|
||||||
|
style="fill: url(javascript:alert(1))" width="100" height="50" />
|
||||||
|
</a>
|
||||||
|
<rect id="safe-node" fill="url(#gradient)" width="20" height="20" />
|
||||||
|
</svg>`
|
||||||
|
})
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<MarkdownRenderer>{`\`\`\`mermaid
|
||||||
|
flowchart LR
|
||||||
|
A --> B
|
||||||
|
\`\`\``}</MarkdownRenderer>
|
||||||
|
)
|
||||||
|
|
||||||
|
await screen.findByRole('region', {
|
||||||
|
name: 'Mermaid 图表,可横向滚动'
|
||||||
|
})
|
||||||
|
expect(container.querySelector('script')).not.toBeInTheDocument()
|
||||||
|
expect(container.querySelector('a')).not.toBeInTheDocument()
|
||||||
|
expect(container.querySelector('style')).not.toBeInTheDocument()
|
||||||
|
expect(container.querySelector('#unsafe-node')).not.toHaveAttribute(
|
||||||
|
'onclick'
|
||||||
|
)
|
||||||
|
expect(container.querySelector('#unsafe-node')).not.toHaveAttribute(
|
||||||
|
'style'
|
||||||
|
)
|
||||||
|
expect(container.querySelector('#safe-node')).toHaveAttribute(
|
||||||
|
'fill',
|
||||||
|
'url(#gradient)'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('rerenders Mermaid diagrams when the application theme changes', async () => {
|
||||||
|
render(
|
||||||
|
<MarkdownRenderer>{`\`\`\`mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
Alice->>Bob: Hello
|
||||||
|
\`\`\``}</MarkdownRenderer>
|
||||||
|
)
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mermaidMock.render).toHaveBeenCalledTimes(1)
|
||||||
|
)
|
||||||
|
act(() => {
|
||||||
|
document.documentElement.dataset.theme = 'dark'
|
||||||
|
})
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mermaidMock.render).toHaveBeenCalledTimes(2)
|
||||||
|
)
|
||||||
|
expect(mermaidMock.initialize).toHaveBeenLastCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
darkMode: true,
|
||||||
|
theme: 'dark'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows Mermaid source when rendering fails or input is oversized', async () => {
|
||||||
|
mermaidMock.render.mockRejectedValueOnce(
|
||||||
|
new Error('Invalid Mermaid syntax')
|
||||||
|
)
|
||||||
|
const { rerender } = render(
|
||||||
|
<MarkdownRenderer>{`\`\`\`mermaid
|
||||||
|
not a diagram
|
||||||
|
\`\`\``}</MarkdownRenderer>
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await screen.findByRole('alert')
|
||||||
|
).toHaveTextContent('无法绘制 Mermaid 图表')
|
||||||
|
expect(screen.getByText('not a diagram')).toBeInTheDocument()
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<MarkdownRenderer>{`\`\`\`mermaid
|
||||||
|
${'A'.repeat(20_001)}
|
||||||
|
\`\`\``}</MarkdownRenderer>
|
||||||
|
)
|
||||||
|
expect(
|
||||||
|
await screen.findByRole('alert')
|
||||||
|
).toHaveTextContent('无法绘制 Mermaid 图表')
|
||||||
|
expect(mermaidMock.render).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
it('updates table accessibility copy when the locale changes', async () => {
|
it('updates table accessibility copy when the locale changes', async () => {
|
||||||
render(
|
render(
|
||||||
<MarkdownRenderer>{`| Name |
|
<MarkdownRenderer>{`| Name |
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
import { memo, useMemo } from 'react'
|
import {
|
||||||
|
Children,
|
||||||
|
isValidElement,
|
||||||
|
lazy,
|
||||||
|
memo,
|
||||||
|
Suspense,
|
||||||
|
useMemo
|
||||||
|
} from 'react'
|
||||||
import rehypeKatex from 'rehype-katex'
|
import rehypeKatex from 'rehype-katex'
|
||||||
import ReactMarkdown from 'react-markdown'
|
import ReactMarkdown from 'react-markdown'
|
||||||
import remarkGfm from 'remark-gfm'
|
import remarkGfm from 'remark-gfm'
|
||||||
@@ -6,6 +13,12 @@ import remarkMath from 'remark-math'
|
|||||||
import type { Components } from 'react-markdown'
|
import type { Components } from 'react-markdown'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
const MermaidDiagram = lazy(() =>
|
||||||
|
import('./MermaidDiagram').then((module) => ({
|
||||||
|
default: module.MermaidDiagram
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
const linkComponent: Components['a'] = ({
|
const linkComponent: Components['a'] = ({
|
||||||
children,
|
children,
|
||||||
node,
|
node,
|
||||||
@@ -19,9 +32,46 @@ const linkComponent: Components['a'] = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function markdownComponents(tableAriaLabel: string): Components {
|
function markdownComponents(
|
||||||
|
tableAriaLabel: string,
|
||||||
|
mermaidLoadingLabel: string
|
||||||
|
): Components {
|
||||||
return {
|
return {
|
||||||
a: linkComponent,
|
a: linkComponent,
|
||||||
|
pre: ({ children, node, ...properties }) => {
|
||||||
|
void node
|
||||||
|
const child = Children.count(children) === 1
|
||||||
|
? Children.only(children)
|
||||||
|
: undefined
|
||||||
|
if (
|
||||||
|
isValidElement<{
|
||||||
|
children?: React.ReactNode
|
||||||
|
className?: string
|
||||||
|
}>(child) &&
|
||||||
|
/(?:^|\s)language-mermaid(?:\s|$)/iu.test(
|
||||||
|
child.props.className ?? ''
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const source = String(child.props.children ?? '').replace(
|
||||||
|
/\n$/u,
|
||||||
|
''
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
<Suspense
|
||||||
|
fallback={
|
||||||
|
<figure aria-busy="true" className="mermaid-diagram">
|
||||||
|
<figcaption role="status">
|
||||||
|
{mermaidLoadingLabel}
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MermaidDiagram source={source} />
|
||||||
|
</Suspense>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return <pre {...properties}>{children}</pre>
|
||||||
|
},
|
||||||
table: ({ children, node, ...properties }) => {
|
table: ({ children, node, ...properties }) => {
|
||||||
void node
|
void node
|
||||||
return (
|
return (
|
||||||
@@ -149,7 +199,11 @@ export const MarkdownRenderer = memo(function MarkdownRenderer({
|
|||||||
}: MarkdownRendererProps): React.JSX.Element {
|
}: MarkdownRendererProps): React.JSX.Element {
|
||||||
const { t } = useTranslation('app')
|
const { t } = useTranslation('app')
|
||||||
const components = useMemo(
|
const components = useMemo(
|
||||||
() => markdownComponents(t('markdown.scrollableTable')),
|
() =>
|
||||||
|
markdownComponents(
|
||||||
|
t('markdown.scrollableTable'),
|
||||||
|
t('markdown.mermaidLoading')
|
||||||
|
),
|
||||||
[t]
|
[t]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,617 @@
|
|||||||
|
import DOMPurify from 'dompurify'
|
||||||
|
import {
|
||||||
|
Code2,
|
||||||
|
Maximize2,
|
||||||
|
RotateCcw,
|
||||||
|
X,
|
||||||
|
ZoomIn,
|
||||||
|
ZoomOut
|
||||||
|
} from 'lucide-react'
|
||||||
|
import {
|
||||||
|
useEffect,
|
||||||
|
useId,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState
|
||||||
|
} from 'react'
|
||||||
|
import { createPortal } from 'react-dom'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import type { Mermaid, MermaidConfig } from 'mermaid'
|
||||||
|
import { activateModalFocus, trapTabFocus } from './dialog-focus'
|
||||||
|
import { useDocumentTheme } from './use-document-theme'
|
||||||
|
import './markdown-mermaid.css'
|
||||||
|
|
||||||
|
const MAX_MERMAID_TEXT_SIZE = 20_000
|
||||||
|
const MAX_MERMAID_EDGES = 300
|
||||||
|
const MAX_MERMAID_DIMENSION = 4_096
|
||||||
|
const MIN_VIEWER_ZOOM = 0.5
|
||||||
|
const MAX_VIEWER_ZOOM = 3
|
||||||
|
const VIEWER_ZOOM_STEP = 0.25
|
||||||
|
const SVG_NAMESPACE = 'http://www.w3.org/2000/svg'
|
||||||
|
const unsafeCssPattern =
|
||||||
|
/(?:@import|\bexpression\s*\(|(?:https?|ftp|file|data|javascript|vbscript):|\/\/|-moz-binding|\bbehavior\s*:)/iu
|
||||||
|
const cssUrlPattern = /url\s*\(\s*(['"]?)(.*?)\1\s*\)/giu
|
||||||
|
|
||||||
|
const forbiddenTags = [
|
||||||
|
'a',
|
||||||
|
'animate',
|
||||||
|
'animatemotion',
|
||||||
|
'animatetransform',
|
||||||
|
'discard',
|
||||||
|
'foreignobject',
|
||||||
|
'iframe',
|
||||||
|
'image',
|
||||||
|
'mpath',
|
||||||
|
'set',
|
||||||
|
'script'
|
||||||
|
]
|
||||||
|
const forbiddenAttributes = ['href', 'xlink:href']
|
||||||
|
const sharedSanitizerConfig = {
|
||||||
|
ALLOW_DATA_ATTR: false,
|
||||||
|
ALLOW_UNKNOWN_PROTOCOLS: false,
|
||||||
|
FORBID_ATTR: forbiddenAttributes,
|
||||||
|
FORBID_TAGS: forbiddenTags
|
||||||
|
} as const
|
||||||
|
const secureConfigKeys = [
|
||||||
|
'secure',
|
||||||
|
'securityLevel',
|
||||||
|
'startOnLoad',
|
||||||
|
'maxTextSize',
|
||||||
|
'suppressErrorRendering',
|
||||||
|
'maxEdges',
|
||||||
|
'htmlLabels',
|
||||||
|
'dompurifyConfig',
|
||||||
|
'theme',
|
||||||
|
'themeCSS',
|
||||||
|
'themeVariables',
|
||||||
|
'fontFamily',
|
||||||
|
'altFontFamily',
|
||||||
|
'flowchart'
|
||||||
|
]
|
||||||
|
|
||||||
|
let mermaidModulePromise: Promise<Mermaid> | undefined
|
||||||
|
let mermaidRenderQueue = Promise.resolve()
|
||||||
|
let mermaidDiagramSequence = 0
|
||||||
|
|
||||||
|
type MermaidRenderState =
|
||||||
|
| {
|
||||||
|
source: string
|
||||||
|
status: 'ready'
|
||||||
|
svg: string
|
||||||
|
theme: 'light' | 'dark'
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
source: string
|
||||||
|
status: 'error'
|
||||||
|
theme: 'light' | 'dark'
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadMermaid(): Promise<Mermaid> {
|
||||||
|
mermaidModulePromise ??= import('mermaid').then(
|
||||||
|
(module) => module.default
|
||||||
|
)
|
||||||
|
return mermaidModulePromise
|
||||||
|
}
|
||||||
|
|
||||||
|
function containsUnsafeCss(value: string): boolean {
|
||||||
|
const normalizedValue = value
|
||||||
|
.replace(/\/\*[\s\S]*?\*\//gu, '')
|
||||||
|
.replace(
|
||||||
|
/\\([0-9a-f]{1,6}\s?|.)/giu,
|
||||||
|
(_match, escaped: string) => {
|
||||||
|
const hex = escaped.trim()
|
||||||
|
return /^[0-9a-f]{1,6}$/iu.test(hex)
|
||||||
|
? String.fromCodePoint(Number.parseInt(hex, 16))
|
||||||
|
: escaped
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (unsafeCssPattern.test(normalizedValue)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
cssUrlPattern.lastIndex = 0
|
||||||
|
for (
|
||||||
|
let match = cssUrlPattern.exec(normalizedValue);
|
||||||
|
match;
|
||||||
|
match = cssUrlPattern.exec(normalizedValue)
|
||||||
|
) {
|
||||||
|
if (!match[2]?.trim().startsWith('#')) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeMermaidSvg(svg: string): string {
|
||||||
|
const sanitized = DOMPurify.sanitize(svg, {
|
||||||
|
...sharedSanitizerConfig,
|
||||||
|
USE_PROFILES: {
|
||||||
|
svg: true,
|
||||||
|
svgFilters: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const documentNode = new DOMParser().parseFromString(
|
||||||
|
sanitized,
|
||||||
|
'image/svg+xml'
|
||||||
|
)
|
||||||
|
const root = documentNode.documentElement
|
||||||
|
if (
|
||||||
|
root.localName !== 'svg' ||
|
||||||
|
root.namespaceURI !== SVG_NAMESPACE ||
|
||||||
|
documentNode.querySelector('parsererror')
|
||||||
|
) {
|
||||||
|
throw new Error('Mermaid returned invalid SVG')
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const element of [root, ...root.querySelectorAll('*')]) {
|
||||||
|
if (
|
||||||
|
element.localName === 'style' &&
|
||||||
|
containsUnsafeCss(element.textContent ?? '')
|
||||||
|
) {
|
||||||
|
element.remove()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for (const attribute of [...element.attributes]) {
|
||||||
|
const attributeName = attribute.name.toLowerCase()
|
||||||
|
if (
|
||||||
|
attributeName.startsWith('on') ||
|
||||||
|
forbiddenAttributes.includes(attributeName) ||
|
||||||
|
containsUnsafeCss(attribute.value)
|
||||||
|
) {
|
||||||
|
element.removeAttribute(attribute.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewBox = root
|
||||||
|
.getAttribute('viewBox')
|
||||||
|
?.trim()
|
||||||
|
.split(/[\s,]+/u)
|
||||||
|
.map(Number)
|
||||||
|
if (
|
||||||
|
viewBox?.length === 4 &&
|
||||||
|
viewBox.every(Number.isFinite) &&
|
||||||
|
viewBox[2]! > 0 &&
|
||||||
|
viewBox[3]! > 0
|
||||||
|
) {
|
||||||
|
const scale = Math.min(
|
||||||
|
1,
|
||||||
|
MAX_MERMAID_DIMENSION / viewBox[2]!,
|
||||||
|
MAX_MERMAID_DIMENSION / viewBox[3]!
|
||||||
|
)
|
||||||
|
root.setAttribute('width', String(Math.ceil(viewBox[2]! * scale)))
|
||||||
|
root.setAttribute('height', String(Math.ceil(viewBox[3]! * scale)))
|
||||||
|
root.style.removeProperty('max-width')
|
||||||
|
}
|
||||||
|
|
||||||
|
return new XMLSerializer().serializeToString(root)
|
||||||
|
}
|
||||||
|
|
||||||
|
function mermaidConfig(darkTheme: boolean): MermaidConfig {
|
||||||
|
return {
|
||||||
|
darkMode: darkTheme,
|
||||||
|
deterministicIds: true,
|
||||||
|
dompurifyConfig: sharedSanitizerConfig,
|
||||||
|
fontFamily:
|
||||||
|
'"Inter Variable", "Noto Sans SC Variable", "Segoe UI Variable", sans-serif',
|
||||||
|
htmlLabels: false,
|
||||||
|
logLevel: 'fatal',
|
||||||
|
maxEdges: MAX_MERMAID_EDGES,
|
||||||
|
maxTextSize: MAX_MERMAID_TEXT_SIZE,
|
||||||
|
secure: secureConfigKeys,
|
||||||
|
securityLevel: 'strict',
|
||||||
|
startOnLoad: false,
|
||||||
|
suppressErrorRendering: true,
|
||||||
|
theme: darkTheme ? 'dark' : 'default'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderMermaid(
|
||||||
|
source: string,
|
||||||
|
id: string,
|
||||||
|
darkTheme: boolean,
|
||||||
|
shouldRender: () => boolean
|
||||||
|
): Promise<string> {
|
||||||
|
if (source.length > MAX_MERMAID_TEXT_SIZE) {
|
||||||
|
return Promise.reject(new Error('Mermaid source is too large'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const render = mermaidRenderQueue.then(async () => {
|
||||||
|
if (!shouldRender()) {
|
||||||
|
throw new Error('Mermaid render was superseded')
|
||||||
|
}
|
||||||
|
const mermaid = await loadMermaid()
|
||||||
|
if (!shouldRender()) {
|
||||||
|
throw new Error('Mermaid render was superseded')
|
||||||
|
}
|
||||||
|
mermaid.initialize(mermaidConfig(darkTheme))
|
||||||
|
const result = await mermaid.render(id, source)
|
||||||
|
return sanitizeMermaidSvg(result.svg)
|
||||||
|
})
|
||||||
|
mermaidRenderQueue = render.then(
|
||||||
|
() => undefined,
|
||||||
|
() => undefined
|
||||||
|
)
|
||||||
|
return render
|
||||||
|
}
|
||||||
|
|
||||||
|
type MermaidDiagramProps = {
|
||||||
|
source: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type MermaidViewerProps = {
|
||||||
|
onClose: () => void
|
||||||
|
svg: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function clampViewerZoom(zoom: number): number {
|
||||||
|
return Math.min(MAX_VIEWER_ZOOM, Math.max(MIN_VIEWER_ZOOM, zoom))
|
||||||
|
}
|
||||||
|
|
||||||
|
function MermaidViewer({
|
||||||
|
onClose,
|
||||||
|
svg
|
||||||
|
}: MermaidViewerProps): React.JSX.Element {
|
||||||
|
const { i18n, t } = useTranslation('app')
|
||||||
|
const titleId = useId()
|
||||||
|
const hintId = useId()
|
||||||
|
const dialogRef = useRef<HTMLElement>(null)
|
||||||
|
const closeRef = useRef<HTMLButtonElement>(null)
|
||||||
|
const dragRef = useRef<{
|
||||||
|
pointerId: number
|
||||||
|
startX: number
|
||||||
|
startY: number
|
||||||
|
scrollLeft: number
|
||||||
|
scrollTop: number
|
||||||
|
} | undefined>(undefined)
|
||||||
|
const [dragging, setDragging] = useState(false)
|
||||||
|
const [zoom, setZoom] = useState(1)
|
||||||
|
const pendingWheelZoomRef = useRef(0)
|
||||||
|
const zoomFrameRef = useRef<number | undefined>(undefined)
|
||||||
|
const percentFormatter = useMemo(
|
||||||
|
() =>
|
||||||
|
new Intl.NumberFormat(i18n.resolvedLanguage, {
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
style: 'percent'
|
||||||
|
}),
|
||||||
|
[i18n.resolvedLanguage]
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const deactivateModalFocus = activateModalFocus(
|
||||||
|
() => closeRef.current
|
||||||
|
)
|
||||||
|
return () => {
|
||||||
|
if (zoomFrameRef.current !== undefined) {
|
||||||
|
cancelAnimationFrame(zoomFrameRef.current)
|
||||||
|
}
|
||||||
|
deactivateModalFocus()
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const adjustZoom = (delta: number): void => {
|
||||||
|
setZoom((current) => clampViewerZoom(current + delta))
|
||||||
|
}
|
||||||
|
|
||||||
|
const queueWheelZoom = (delta: number): void => {
|
||||||
|
pendingWheelZoomRef.current += delta
|
||||||
|
if (zoomFrameRef.current !== undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
zoomFrameRef.current = requestAnimationFrame(() => {
|
||||||
|
const pendingZoom = pendingWheelZoomRef.current
|
||||||
|
pendingWheelZoomRef.current = 0
|
||||||
|
zoomFrameRef.current = undefined
|
||||||
|
adjustZoom(pendingZoom)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const finishDragging = (
|
||||||
|
event: React.PointerEvent<HTMLDivElement>
|
||||||
|
): void => {
|
||||||
|
if (dragRef.current?.pointerId !== event.pointerId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
|
||||||
|
event.currentTarget.releasePointerCapture(event.pointerId)
|
||||||
|
}
|
||||||
|
dragRef.current = undefined
|
||||||
|
setDragging(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return createPortal(
|
||||||
|
<div
|
||||||
|
className="mermaid-viewer-backdrop"
|
||||||
|
onMouseDown={(event) => {
|
||||||
|
if (event.target === event.currentTarget) {
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<section
|
||||||
|
aria-describedby={hintId}
|
||||||
|
aria-labelledby={titleId}
|
||||||
|
aria-modal="true"
|
||||||
|
className="mermaid-viewer"
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
event.preventDefault()
|
||||||
|
onClose()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
trapTabFocus(event, dialogRef.current)
|
||||||
|
}}
|
||||||
|
ref={dialogRef}
|
||||||
|
role="dialog"
|
||||||
|
>
|
||||||
|
<header className="mermaid-viewer__header">
|
||||||
|
<div>
|
||||||
|
<strong id={titleId}>{t('markdown.mermaidViewerTitle')}</strong>
|
||||||
|
<small id={hintId}>
|
||||||
|
{t('markdown.mermaidViewerHint')}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<div className="mermaid-viewer__controls">
|
||||||
|
<button
|
||||||
|
aria-label={t('markdown.mermaidZoomOut')}
|
||||||
|
className="icon-button"
|
||||||
|
disabled={zoom <= MIN_VIEWER_ZOOM}
|
||||||
|
onClick={() => adjustZoom(-VIEWER_ZOOM_STEP)}
|
||||||
|
title={t('markdown.mermaidZoomOut')}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<ZoomOut aria-hidden="true" size={17} />
|
||||||
|
</button>
|
||||||
|
<output
|
||||||
|
aria-label={t('markdown.mermaidZoomLevel')}
|
||||||
|
className="mermaid-viewer__zoom"
|
||||||
|
>
|
||||||
|
{percentFormatter.format(zoom)}
|
||||||
|
</output>
|
||||||
|
<button
|
||||||
|
aria-label={t('markdown.mermaidZoomIn')}
|
||||||
|
className="icon-button"
|
||||||
|
disabled={zoom >= MAX_VIEWER_ZOOM}
|
||||||
|
onClick={() => adjustZoom(VIEWER_ZOOM_STEP)}
|
||||||
|
title={t('markdown.mermaidZoomIn')}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<ZoomIn aria-hidden="true" size={17} />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-label={t('markdown.mermaidResetZoom')}
|
||||||
|
className="icon-button"
|
||||||
|
disabled={zoom === 1}
|
||||||
|
onClick={() => setZoom(1)}
|
||||||
|
title={t('markdown.mermaidResetZoom')}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<RotateCcw aria-hidden="true" size={16} />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-label={t('markdown.mermaidCloseViewer')}
|
||||||
|
className="icon-button"
|
||||||
|
onClick={onClose}
|
||||||
|
ref={closeRef}
|
||||||
|
title={t('markdown.mermaidCloseViewer')}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<X aria-hidden="true" size={18} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div
|
||||||
|
aria-label={t('markdown.mermaidViewerCanvas')}
|
||||||
|
className={`mermaid-viewer__canvas${
|
||||||
|
dragging ? ' mermaid-viewer__canvas--dragging' : ''
|
||||||
|
}`}
|
||||||
|
onPointerCancel={finishDragging}
|
||||||
|
onPointerDown={(event) => {
|
||||||
|
if (event.button !== 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
event.preventDefault()
|
||||||
|
dragRef.current = {
|
||||||
|
pointerId: event.pointerId,
|
||||||
|
startX: event.clientX,
|
||||||
|
startY: event.clientY,
|
||||||
|
scrollLeft: event.currentTarget.scrollLeft,
|
||||||
|
scrollTop: event.currentTarget.scrollTop
|
||||||
|
}
|
||||||
|
event.currentTarget.setPointerCapture(event.pointerId)
|
||||||
|
setDragging(true)
|
||||||
|
}}
|
||||||
|
onPointerMove={(event) => {
|
||||||
|
const drag = dragRef.current
|
||||||
|
if (drag?.pointerId !== event.pointerId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
event.preventDefault()
|
||||||
|
event.currentTarget.scrollLeft =
|
||||||
|
drag.scrollLeft - (event.clientX - drag.startX)
|
||||||
|
event.currentTarget.scrollTop =
|
||||||
|
drag.scrollTop - (event.clientY - drag.startY)
|
||||||
|
}}
|
||||||
|
onPointerUp={finishDragging}
|
||||||
|
onWheel={(event) => {
|
||||||
|
if (event.deltaY === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
event.preventDefault()
|
||||||
|
queueWheelZoom(
|
||||||
|
event.deltaY < 0
|
||||||
|
? VIEWER_ZOOM_STEP
|
||||||
|
: -VIEWER_ZOOM_STEP
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
role="region"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="mermaid-viewer__diagram"
|
||||||
|
dangerouslySetInnerHTML={{ __html: svg }}
|
||||||
|
style={{ zoom }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>,
|
||||||
|
document.body
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MermaidDiagram({
|
||||||
|
source
|
||||||
|
}: MermaidDiagramProps): React.JSX.Element {
|
||||||
|
const { t } = useTranslation('app')
|
||||||
|
const idRef = useRef(
|
||||||
|
`goodbuddy-mermaid-${++mermaidDiagramSequence}`
|
||||||
|
)
|
||||||
|
const renderRevisionRef = useRef(0)
|
||||||
|
const sourceId = useId()
|
||||||
|
const documentTheme = useDocumentTheme()
|
||||||
|
const [renderState, setRenderState] =
|
||||||
|
useState<MermaidRenderState>()
|
||||||
|
const [visibleSource, setVisibleSource] = useState<string>()
|
||||||
|
const [viewerSource, setViewerSource] = useState<string>()
|
||||||
|
const sourceVisible = visibleSource === source
|
||||||
|
const viewerOpen = viewerSource === source
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let active = true
|
||||||
|
const renderRevision = renderRevisionRef.current++
|
||||||
|
const timeout = window.setTimeout(() => {
|
||||||
|
void renderMermaid(
|
||||||
|
source.trim(),
|
||||||
|
`${idRef.current}-${renderRevision}`,
|
||||||
|
documentTheme === 'dark',
|
||||||
|
() => active
|
||||||
|
).then(
|
||||||
|
(svg) => {
|
||||||
|
if (active) {
|
||||||
|
setRenderState({
|
||||||
|
source,
|
||||||
|
status: 'ready',
|
||||||
|
svg,
|
||||||
|
theme: documentTheme
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
if (active) {
|
||||||
|
setRenderState({
|
||||||
|
source,
|
||||||
|
status: 'error',
|
||||||
|
theme: documentTheme
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}, 120)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
active = false
|
||||||
|
window.clearTimeout(timeout)
|
||||||
|
}
|
||||||
|
}, [documentTheme, source])
|
||||||
|
|
||||||
|
const currentRenderState =
|
||||||
|
renderState?.source === source &&
|
||||||
|
renderState.theme === documentTheme
|
||||||
|
? renderState
|
||||||
|
: undefined
|
||||||
|
const retainedSvg =
|
||||||
|
renderState?.status === 'ready' &&
|
||||||
|
renderState.source === source
|
||||||
|
? renderState.svg
|
||||||
|
: undefined
|
||||||
|
const svg =
|
||||||
|
currentRenderState?.status === 'ready'
|
||||||
|
? currentRenderState.svg
|
||||||
|
: retainedSvg
|
||||||
|
|
||||||
|
if (currentRenderState?.status === 'error') {
|
||||||
|
return (
|
||||||
|
<figure className="mermaid-diagram mermaid-diagram--error">
|
||||||
|
<figcaption role="alert">
|
||||||
|
{t('markdown.mermaidError')}
|
||||||
|
</figcaption>
|
||||||
|
<pre>
|
||||||
|
<code className="language-mermaid">{source}</code>
|
||||||
|
</pre>
|
||||||
|
</figure>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<figure
|
||||||
|
aria-busy={!currentRenderState}
|
||||||
|
className="mermaid-diagram"
|
||||||
|
>
|
||||||
|
{svg ? (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
aria-label={t('markdown.mermaidActions')}
|
||||||
|
className="mermaid-diagram__actions"
|
||||||
|
role="group"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-controls={sourceId}
|
||||||
|
aria-expanded={sourceVisible}
|
||||||
|
aria-label={t(
|
||||||
|
sourceVisible
|
||||||
|
? 'markdown.mermaidHideSource'
|
||||||
|
: 'markdown.mermaidViewSource'
|
||||||
|
)}
|
||||||
|
className={`icon-button${
|
||||||
|
sourceVisible ? ' icon-button--active' : ''
|
||||||
|
}`}
|
||||||
|
onClick={() =>
|
||||||
|
setVisibleSource(sourceVisible ? undefined : source)
|
||||||
|
}
|
||||||
|
title={t(
|
||||||
|
sourceVisible
|
||||||
|
? 'markdown.mermaidHideSource'
|
||||||
|
: 'markdown.mermaidViewSource'
|
||||||
|
)}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<Code2 aria-hidden="true" size={17} />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-label={t('markdown.mermaidOpenViewer')}
|
||||||
|
className="icon-button"
|
||||||
|
onClick={() => setViewerSource(source)}
|
||||||
|
title={t('markdown.mermaidOpenViewer')}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<Maximize2 aria-hidden="true" size={17} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{!viewerOpen && (
|
||||||
|
<div
|
||||||
|
aria-label={t('markdown.mermaidDiagram')}
|
||||||
|
className="mermaid-diagram__viewport"
|
||||||
|
dangerouslySetInnerHTML={{ __html: svg }}
|
||||||
|
role="region"
|
||||||
|
tabIndex={0}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{sourceVisible && (
|
||||||
|
<pre className="mermaid-diagram__source" id={sourceId}>
|
||||||
|
<code className="language-mermaid">{source}</code>
|
||||||
|
</pre>
|
||||||
|
)}
|
||||||
|
{viewerOpen && (
|
||||||
|
<MermaidViewer
|
||||||
|
onClose={() => setViewerSource(undefined)}
|
||||||
|
svg={svg}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<figcaption className="mermaid-diagram__loading" role="status">
|
||||||
|
{t('markdown.mermaidLoading')}
|
||||||
|
</figcaption>
|
||||||
|
)}
|
||||||
|
</figure>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ type TabKeyEvent = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const focusableSelector =
|
const focusableSelector =
|
||||||
'button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled])'
|
'button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'
|
||||||
|
|
||||||
export function trapTabFocus(
|
export function trapTabFocus(
|
||||||
event: TabKeyEvent,
|
event: TabKeyEvent,
|
||||||
|
|||||||
@@ -473,6 +473,23 @@ export const app = {
|
|||||||
evidenceExcerpt: '{{source}}: {{excerpt}}'
|
evidenceExcerpt: '{{source}}: {{excerpt}}'
|
||||||
},
|
},
|
||||||
markdown: {
|
markdown: {
|
||||||
scrollableTable: 'Table, horizontally scrollable'
|
scrollableTable: 'Table, horizontally scrollable',
|
||||||
|
mermaidDiagram: 'Mermaid diagram, horizontally scrollable',
|
||||||
|
mermaidLoading: 'Rendering Mermaid diagram…',
|
||||||
|
mermaidError:
|
||||||
|
'The Mermaid diagram could not be rendered. Its source is shown below.',
|
||||||
|
mermaidActions: 'Mermaid diagram actions',
|
||||||
|
mermaidViewSource: 'View source',
|
||||||
|
mermaidHideSource: 'Hide source',
|
||||||
|
mermaidOpenViewer: 'Open large diagram',
|
||||||
|
mermaidViewerTitle: 'Large Mermaid diagram',
|
||||||
|
mermaidViewerHint:
|
||||||
|
'Use the mouse wheel or buttons to zoom, and drag the canvas to pan.',
|
||||||
|
mermaidViewerCanvas: 'Zoomable, pannable Mermaid diagram',
|
||||||
|
mermaidZoomOut: 'Zoom out diagram',
|
||||||
|
mermaidZoomIn: 'Zoom in diagram',
|
||||||
|
mermaidResetZoom: 'Reset zoom',
|
||||||
|
mermaidZoomLevel: 'Current zoom level',
|
||||||
|
mermaidCloseViewer: 'Close large Mermaid diagram'
|
||||||
}
|
}
|
||||||
} satisfies TranslationShape<typeof chineseApp>
|
} satisfies TranslationShape<typeof chineseApp>
|
||||||
|
|||||||
@@ -439,6 +439,21 @@ export const app = {
|
|||||||
evidenceExcerpt: '{{source}}:{{excerpt}}'
|
evidenceExcerpt: '{{source}}:{{excerpt}}'
|
||||||
},
|
},
|
||||||
markdown: {
|
markdown: {
|
||||||
scrollableTable: '表格,可横向滚动'
|
scrollableTable: '表格,可横向滚动',
|
||||||
|
mermaidDiagram: 'Mermaid 图表,可横向滚动',
|
||||||
|
mermaidLoading: '正在绘制 Mermaid 图表…',
|
||||||
|
mermaidError: '无法绘制 Mermaid 图表,已保留原始图表代码。',
|
||||||
|
mermaidActions: 'Mermaid 图表操作',
|
||||||
|
mermaidViewSource: '查看源码',
|
||||||
|
mermaidHideSource: '隐藏源码',
|
||||||
|
mermaidOpenViewer: '打开大图',
|
||||||
|
mermaidViewerTitle: 'Mermaid 大图',
|
||||||
|
mermaidViewerHint: '使用滚轮或按钮缩放,拖动画布浏览。',
|
||||||
|
mermaidViewerCanvas: '可缩放、可拖动的 Mermaid 图表',
|
||||||
|
mermaidZoomOut: '缩小图表',
|
||||||
|
mermaidZoomIn: '放大图表',
|
||||||
|
mermaidResetZoom: '重置缩放',
|
||||||
|
mermaidZoomLevel: '当前缩放比例',
|
||||||
|
mermaidCloseViewer: '关闭 Mermaid 大图'
|
||||||
}
|
}
|
||||||
} as const
|
} as const
|
||||||
|
|||||||
@@ -0,0 +1,205 @@
|
|||||||
|
.mermaid-diagram {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
min-width: 0;
|
||||||
|
margin: var(--space-3) 0;
|
||||||
|
border: 1px solid var(--border-subtle);
|
||||||
|
border-radius: var(--radius-control);
|
||||||
|
background: var(--surface-raised);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-diagram__actions {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
top: var(--space-2);
|
||||||
|
right: var(--space-2);
|
||||||
|
display: flex;
|
||||||
|
padding: 2px;
|
||||||
|
border: 1px solid var(--border-subtle);
|
||||||
|
border-radius: var(--radius-control);
|
||||||
|
background: var(--surface-raised);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-diagram__actions .icon-button {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-diagram__viewport {
|
||||||
|
min-width: 0;
|
||||||
|
padding: var(--space-4);
|
||||||
|
overflow-x: auto;
|
||||||
|
overscroll-behavior-inline: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-diagram__viewport:focus-visible {
|
||||||
|
outline: 2px solid var(--accent);
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-diagram__viewport > svg {
|
||||||
|
display: block;
|
||||||
|
min-width: min(100%, 320px);
|
||||||
|
max-width: none !important;
|
||||||
|
height: auto;
|
||||||
|
min-height: 80px;
|
||||||
|
margin-inline: auto;
|
||||||
|
font-family: var(--font-family-ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content .mermaid-diagram__source {
|
||||||
|
max-height: 320px;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
border-top: 1px solid var(--border-subtle);
|
||||||
|
border-radius: 0 0 var(--radius-control) var(--radius-control);
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-diagram__loading,
|
||||||
|
.mermaid-diagram--error > figcaption {
|
||||||
|
padding: var(--space-3) var(--space-4);
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: var(--font-caption);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-diagram--error {
|
||||||
|
border-color: var(--danger-border);
|
||||||
|
background: var(--danger-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-diagram--error > figcaption {
|
||||||
|
color: var(--danger-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content .mermaid-diagram--error > pre {
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
border-top: 1px solid var(--danger-border);
|
||||||
|
border-radius: 0 0 var(--radius-control) var(--radius-control);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-viewer-backdrop {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 120;
|
||||||
|
display: grid;
|
||||||
|
padding: var(--space-4);
|
||||||
|
background: var(--overlay-backdrop);
|
||||||
|
inset: 38px 0 0;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-viewer {
|
||||||
|
display: grid;
|
||||||
|
width: min(1280px, 100%);
|
||||||
|
height: min(860px, calc(100vh - 70px));
|
||||||
|
min-height: 0;
|
||||||
|
border: 1px solid var(--border-default);
|
||||||
|
border-radius: var(--radius-card);
|
||||||
|
background: var(--surface-raised);
|
||||||
|
box-shadow: var(--shadow-dialog);
|
||||||
|
grid-template-rows: auto minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-viewer__header {
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--space-3) var(--space-4);
|
||||||
|
border-bottom: 1px solid var(--border-subtle);
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-viewer__header > div:first-child {
|
||||||
|
display: grid;
|
||||||
|
min-width: 0;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-viewer__header strong {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: var(--font-section-title);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-viewer__header small {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: var(--font-caption);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-viewer__controls {
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-viewer__zoom {
|
||||||
|
min-width: 48px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: var(--font-caption);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-viewer__canvas {
|
||||||
|
display: grid;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
padding: var(--space-6);
|
||||||
|
overflow: auto;
|
||||||
|
background: var(--surface-subtle);
|
||||||
|
cursor: grab;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
place-items: center;
|
||||||
|
touch-action: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-viewer__canvas--dragging {
|
||||||
|
cursor: grabbing;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-viewer__canvas:focus-visible {
|
||||||
|
outline: 2px solid var(--accent);
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-viewer__diagram {
|
||||||
|
display: grid;
|
||||||
|
min-width: max-content;
|
||||||
|
min-height: max-content;
|
||||||
|
transform-origin: center;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-viewer__diagram > svg {
|
||||||
|
display: block;
|
||||||
|
max-width: none !important;
|
||||||
|
height: auto;
|
||||||
|
font-family: var(--font-family-ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
.mermaid-viewer-backdrop {
|
||||||
|
padding: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-viewer {
|
||||||
|
height: calc(100vh - 54px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-viewer__header {
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-viewer__controls {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { useSyncExternalStore } from 'react'
|
||||||
|
import type { ResolvedAppearanceTheme } from './theme'
|
||||||
|
|
||||||
|
const listeners = new Set<() => void>()
|
||||||
|
let observer: MutationObserver | undefined
|
||||||
|
|
||||||
|
function resolvedDocumentTheme(): ResolvedAppearanceTheme {
|
||||||
|
return document.documentElement.dataset.theme === 'dark'
|
||||||
|
? 'dark'
|
||||||
|
: 'light'
|
||||||
|
}
|
||||||
|
|
||||||
|
function notifyThemeListeners(): void {
|
||||||
|
for (const listener of listeners) {
|
||||||
|
listener()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function subscribeToDocumentTheme(listener: () => void): () => void {
|
||||||
|
listeners.add(listener)
|
||||||
|
if (
|
||||||
|
listeners.size === 1 &&
|
||||||
|
typeof MutationObserver === 'function'
|
||||||
|
) {
|
||||||
|
observer = new MutationObserver(notifyThemeListeners)
|
||||||
|
observer.observe(document.documentElement, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ['data-theme']
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
listeners.delete(listener)
|
||||||
|
if (listeners.size === 0) {
|
||||||
|
observer?.disconnect()
|
||||||
|
observer = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDocumentTheme(): ResolvedAppearanceTheme {
|
||||||
|
return useSyncExternalStore(
|
||||||
|
subscribeToDocumentTheme,
|
||||||
|
resolvedDocumentTheme,
|
||||||
|
resolvedDocumentTheme
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user