Feat/model usage charts and paper cards完善第二阶段功能与验收:后台索引、图表交互、扩展社区及第三阶段规划 #31
@@ -51,6 +51,7 @@
|
||||
"@vitejs/plugin-vue": "^5.0.0",
|
||||
"@vue/test-utils": "^2.5.0",
|
||||
"happy-dom": "^20.11.15",
|
||||
"jsdom": "^30.0.1",
|
||||
"typescript": "~5.9.3",
|
||||
"vite": "^6.0.0",
|
||||
"vitest": "^4.1.11",
|
||||
|
||||
Generated
+323
-2
@@ -123,6 +123,9 @@ importers:
|
||||
happy-dom:
|
||||
specifier: ^20.11.15
|
||||
version: 20.11.15
|
||||
jsdom:
|
||||
specifier: ^30.0.1
|
||||
version: 30.0.1
|
||||
typescript:
|
||||
specifier: ~5.9.3
|
||||
version: 5.9.3
|
||||
@@ -131,7 +134,7 @@ importers:
|
||||
version: 6.4.3(@types/node@22.20.1)(yaml@2.9.0)
|
||||
vitest:
|
||||
specifier: ^4.1.11
|
||||
version: 4.1.11(@types/node@22.20.1)(happy-dom@20.11.15)(vite@6.4.3(@types/node@22.20.1)(yaml@2.9.0))
|
||||
version: 4.1.11(@types/node@22.20.1)(happy-dom@20.11.15)(jsdom@30.0.1)(vite@6.4.3(@types/node@22.20.1)(yaml@2.9.0))
|
||||
vue-tsc:
|
||||
specifier: ^2.0.0
|
||||
version: 2.2.12(typescript@5.9.3)
|
||||
@@ -141,6 +144,14 @@ packages:
|
||||
'@antfu/install-pkg@1.1.0':
|
||||
resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}
|
||||
|
||||
'@asamuzakjp/css-color@6.0.7':
|
||||
resolution: {integrity: sha512-vC/bk1Lz7Tn/EfU9/apOTBk80/8dyGyWMowPoV1tJ52muDGsDqt2HPT2klrFUiY60MQmQv9q8yIht15JnBgDGw==}
|
||||
engines: {node: ^22.13.0 || >=24.0.0}
|
||||
|
||||
'@asamuzakjp/dom-selector@8.3.2':
|
||||
resolution: {integrity: sha512-93Z1N+BQNXysodoicpOIyNh2drHfz/CTf9nnT0FEx72GJcIiwgydD7tGAr78j41LsYn3hlRn+LdGPuBLn1Bl8Q==}
|
||||
engines: {node: ^22.13.0 || >=24.0.0}
|
||||
|
||||
'@babel/helper-string-parser@7.29.7':
|
||||
resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -161,6 +172,10 @@ packages:
|
||||
'@braintree/sanitize-url@7.1.2':
|
||||
resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==}
|
||||
|
||||
'@bramus/specificity@2.4.2':
|
||||
resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==}
|
||||
hasBin: true
|
||||
|
||||
'@chevrotain/types@11.1.2':
|
||||
resolution: {integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==}
|
||||
|
||||
@@ -257,6 +272,42 @@ packages:
|
||||
'@codemirror/view@6.43.9':
|
||||
resolution: {integrity: sha512-sTuUzTpPMFebRhg6dawChoKKgndIwfjmJgKVxBefPElcU2NwQ6AFroupk0SFqEerQyZOGRfDNnSN8Dw/lMAsXw==}
|
||||
|
||||
'@csstools/color-helpers@6.1.1':
|
||||
resolution: {integrity: sha512-gLNsunvwf3mCi5u5o46/Z/JcJMnhbHSaZ69rkgPzNM3J4s8hWwpPUQB6/tt0EDFyCiWzxANlx+2LJwpYj4zS1w==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
'@csstools/css-calc@3.3.0':
|
||||
resolution: {integrity: sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
peerDependencies:
|
||||
'@csstools/css-parser-algorithms': ^4.0.0
|
||||
'@csstools/css-tokenizer': ^4.0.0
|
||||
|
||||
'@csstools/css-color-parser@4.2.2':
|
||||
resolution: {integrity: sha512-3QKjR/vxyjcSXBLgb6lP0S3MGdvwbmqSsvLPbYdVORqPDc8FX1HAJ0Spk38bxaRXgvENTA47tlhhbb5Z2e8hEg==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
peerDependencies:
|
||||
'@csstools/css-parser-algorithms': ^4.0.0
|
||||
'@csstools/css-tokenizer': ^4.0.0
|
||||
|
||||
'@csstools/css-parser-algorithms@4.0.0':
|
||||
resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
peerDependencies:
|
||||
'@csstools/css-tokenizer': ^4.0.0
|
||||
|
||||
'@csstools/css-syntax-patches-for-csstree@1.1.12':
|
||||
resolution: {integrity: sha512-3vLQK+dXxhBMR2Wx99PTCifE+vHtW2ndZWyla8yK813ev6oGhyn8Lja8jCyGAWTJ+LEYZK7EVtJxrDj8ztevJw==}
|
||||
peerDependencies:
|
||||
css-tree: ^3.2.1
|
||||
peerDependenciesMeta:
|
||||
css-tree:
|
||||
optional: true
|
||||
|
||||
'@csstools/css-tokenizer@4.0.0':
|
||||
resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
'@element-plus/icons-vue@2.3.2':
|
||||
resolution: {integrity: sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==}
|
||||
peerDependencies:
|
||||
@@ -418,6 +469,15 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@exodus/bytes@1.15.1':
|
||||
resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==}
|
||||
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
|
||||
peerDependencies:
|
||||
'@noble/hashes': ^1.8.0 || ^2.0.0
|
||||
peerDependenciesMeta:
|
||||
'@noble/hashes':
|
||||
optional: true
|
||||
|
||||
'@floating-ui/core@1.8.0':
|
||||
resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==}
|
||||
|
||||
@@ -1083,6 +1143,9 @@ packages:
|
||||
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
|
||||
bidi-js@1.0.3:
|
||||
resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
|
||||
|
||||
birpc@2.9.0:
|
||||
resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==}
|
||||
|
||||
@@ -1160,6 +1223,10 @@ packages:
|
||||
crelt@1.0.7:
|
||||
resolution: {integrity: sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==}
|
||||
|
||||
css-tree@3.2.1:
|
||||
resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==}
|
||||
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
|
||||
|
||||
csstype@3.2.3:
|
||||
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
||||
|
||||
@@ -1319,6 +1386,10 @@ packages:
|
||||
dagre-d3-es@7.0.14:
|
||||
resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==}
|
||||
|
||||
data-urls@7.0.0:
|
||||
resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==}
|
||||
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
|
||||
|
||||
dayjs@1.11.23:
|
||||
resolution: {integrity: sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==}
|
||||
|
||||
@@ -1334,6 +1405,9 @@ packages:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
decimal.js@10.6.0:
|
||||
resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
|
||||
|
||||
decode-named-character-reference@1.3.0:
|
||||
resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==}
|
||||
|
||||
@@ -1359,6 +1433,10 @@ packages:
|
||||
resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
entities@8.0.0:
|
||||
resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
es-module-lexer@2.3.2:
|
||||
resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==}
|
||||
|
||||
@@ -1434,6 +1512,10 @@ packages:
|
||||
hookable@5.5.3:
|
||||
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
|
||||
|
||||
html-encoding-sniffer@6.0.0:
|
||||
resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==}
|
||||
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
|
||||
|
||||
html-void-elements@3.0.0:
|
||||
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
|
||||
|
||||
@@ -1458,6 +1540,9 @@ packages:
|
||||
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
is-potential-custom-element-name@1.0.1:
|
||||
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
|
||||
|
||||
js-beautify@2.0.3:
|
||||
resolution: {integrity: sha512-cyFbh3tkPhknnTD/0bLf0T0yy2ZIbqL05mttzbt4y1Zfr7NxqXQZ62dkBLKs3oHH/lpjmDRAnciJiSUyOy8XwQ==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -1466,6 +1551,15 @@ packages:
|
||||
js-cookie@3.0.8:
|
||||
resolution: {integrity: sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==}
|
||||
|
||||
jsdom@30.0.1:
|
||||
resolution: {integrity: sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==}
|
||||
engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0}
|
||||
peerDependencies:
|
||||
canvas: ^3.2.3
|
||||
peerDependenciesMeta:
|
||||
canvas:
|
||||
optional: true
|
||||
|
||||
katex@0.16.47:
|
||||
resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==}
|
||||
hasBin: true
|
||||
@@ -1559,6 +1653,9 @@ packages:
|
||||
mdast-util-to-string@4.0.0:
|
||||
resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==}
|
||||
|
||||
mdn-data@2.27.1:
|
||||
resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==}
|
||||
|
||||
mermaid@11.17.2:
|
||||
resolution: {integrity: sha512-V6K3C8EBdEsPFZXSKMJe6ppQOENxuHARr9GvHX4hh47lAbhMRD9qf4oEK7LoaRQxULMa80/qt5gHO73aCleBBg==}
|
||||
|
||||
@@ -1704,6 +1801,9 @@ packages:
|
||||
package-manager-detector@1.8.0:
|
||||
resolution: {integrity: sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==}
|
||||
|
||||
parse5@8.0.1:
|
||||
resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==}
|
||||
|
||||
path-browserify@1.0.1:
|
||||
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
|
||||
|
||||
@@ -1818,6 +1918,10 @@ packages:
|
||||
proto-list@1.2.4:
|
||||
resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
|
||||
|
||||
punycode@2.3.1:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
quansync@0.2.11:
|
||||
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
|
||||
|
||||
@@ -1852,6 +1956,10 @@ packages:
|
||||
remark@15.0.1:
|
||||
resolution: {integrity: sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==}
|
||||
|
||||
require-from-string@2.0.2:
|
||||
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
robust-predicates@3.0.3:
|
||||
resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==}
|
||||
|
||||
@@ -1872,6 +1980,10 @@ packages:
|
||||
safer-buffer@2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
|
||||
saxes@6.0.0:
|
||||
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
|
||||
engines: {node: '>=v12.22.7'}
|
||||
|
||||
scule@1.3.0:
|
||||
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
|
||||
|
||||
@@ -1912,6 +2024,9 @@ packages:
|
||||
stylis@4.4.0:
|
||||
resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==}
|
||||
|
||||
symbol-tree@3.2.4:
|
||||
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
|
||||
|
||||
tinybench@2.9.0:
|
||||
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
||||
|
||||
@@ -1927,6 +2042,21 @@ packages:
|
||||
resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
tldts-core@7.4.11:
|
||||
resolution: {integrity: sha512-CW3WN2rIIE/Of21mulhgnGOwoDyEFNygyIBOONSdyAuSATgMMUCpLeUlB+E8sAwA5xRV9hYPl+kyZ9citHCaKg==}
|
||||
|
||||
tldts@7.4.11:
|
||||
resolution: {integrity: sha512-aBiNayCfTQxuIJBm06M+xR14cYaYlDlSXZbgsnKzKNxDKUVq7KFwTjwBSsb7m9Y5xO8WfPnBc63WaYFMTGlvqw==}
|
||||
hasBin: true
|
||||
|
||||
tough-cookie@6.0.2:
|
||||
resolution: {integrity: sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
tr46@6.0.0:
|
||||
resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
trim-lines@3.0.1:
|
||||
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
|
||||
|
||||
@@ -1948,6 +2078,10 @@ packages:
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
|
||||
undici@8.10.2:
|
||||
resolution: {integrity: sha512-/y4/bH9YNU5hi9NIrpOuvGXFcxrj3CMrV+/AYpowAYTpHn8gX/XPFjNy766FPoYY0miQhdW977JFWKGNhBdwyQ==}
|
||||
engines: {node: '>=22.19.0'}
|
||||
|
||||
unified@11.0.5:
|
||||
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
|
||||
|
||||
@@ -2138,6 +2272,14 @@ packages:
|
||||
w3c-keyname@2.2.8:
|
||||
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
|
||||
|
||||
w3c-xmlserializer@5.0.0:
|
||||
resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
webidl-conversions@8.0.1:
|
||||
resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
webpack-virtual-modules@0.6.2:
|
||||
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
|
||||
|
||||
@@ -2145,6 +2287,18 @@ packages:
|
||||
resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
whatwg-mimetype@5.0.0:
|
||||
resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
whatwg-url@16.0.1:
|
||||
resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==}
|
||||
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
|
||||
|
||||
whatwg-url@17.1.0:
|
||||
resolution: {integrity: sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==}
|
||||
engines: {node: ^22.14.0 || >=24.0.0}
|
||||
|
||||
why-is-node-running@2.3.0:
|
||||
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -2162,6 +2316,13 @@ packages:
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
|
||||
xml-name-validator@5.0.0:
|
||||
resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
xmlchars@2.2.0:
|
||||
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
|
||||
|
||||
yaml@2.9.0:
|
||||
resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==}
|
||||
engines: {node: '>= 14.6'}
|
||||
@@ -2177,6 +2338,21 @@ snapshots:
|
||||
package-manager-detector: 1.8.0
|
||||
tinyexec: 1.3.0
|
||||
|
||||
'@asamuzakjp/css-color@6.0.7':
|
||||
dependencies:
|
||||
'@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
|
||||
'@csstools/css-color-parser': 4.2.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
|
||||
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
|
||||
'@csstools/css-tokenizer': 4.0.0
|
||||
lru-cache: 11.5.2
|
||||
|
||||
'@asamuzakjp/dom-selector@8.3.2':
|
||||
dependencies:
|
||||
bidi-js: 1.0.3
|
||||
css-tree: 3.2.1
|
||||
is-potential-custom-element-name: 1.0.1
|
||||
lru-cache: 11.5.2
|
||||
|
||||
'@babel/helper-string-parser@7.29.7': {}
|
||||
|
||||
'@babel/helper-validator-identifier@7.29.7': {}
|
||||
@@ -2192,6 +2368,10 @@ snapshots:
|
||||
|
||||
'@braintree/sanitize-url@7.1.2': {}
|
||||
|
||||
'@bramus/specificity@2.4.2':
|
||||
dependencies:
|
||||
css-tree: 3.2.1
|
||||
|
||||
'@chevrotain/types@11.1.2': {}
|
||||
|
||||
'@codemirror/autocomplete@6.20.3':
|
||||
@@ -2452,6 +2632,30 @@ snapshots:
|
||||
style-mod: 4.1.3
|
||||
w3c-keyname: 2.2.8
|
||||
|
||||
'@csstools/color-helpers@6.1.1': {}
|
||||
|
||||
'@csstools/css-calc@3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
|
||||
dependencies:
|
||||
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
|
||||
'@csstools/css-tokenizer': 4.0.0
|
||||
|
||||
'@csstools/css-color-parser@4.2.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
|
||||
dependencies:
|
||||
'@csstools/color-helpers': 6.1.1
|
||||
'@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
|
||||
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
|
||||
'@csstools/css-tokenizer': 4.0.0
|
||||
|
||||
'@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)':
|
||||
dependencies:
|
||||
'@csstools/css-tokenizer': 4.0.0
|
||||
|
||||
'@csstools/css-syntax-patches-for-csstree@1.1.12(css-tree@3.2.1)':
|
||||
optionalDependencies:
|
||||
css-tree: 3.2.1
|
||||
|
||||
'@csstools/css-tokenizer@4.0.0': {}
|
||||
|
||||
'@element-plus/icons-vue@2.3.2(vue@3.5.42(typescript@5.9.3))':
|
||||
dependencies:
|
||||
vue: 3.5.42(typescript@5.9.3)
|
||||
@@ -2534,6 +2738,8 @@ snapshots:
|
||||
'@esbuild/win32-x64@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@exodus/bytes@1.15.1': {}
|
||||
|
||||
'@floating-ui/core@1.8.0':
|
||||
dependencies:
|
||||
'@floating-ui/utils': 0.2.12
|
||||
@@ -3478,6 +3684,10 @@ snapshots:
|
||||
|
||||
balanced-match@4.0.4: {}
|
||||
|
||||
bidi-js@1.0.3:
|
||||
dependencies:
|
||||
require-from-string: 2.0.2
|
||||
|
||||
birpc@2.9.0: {}
|
||||
|
||||
brace-expansion@2.1.4:
|
||||
@@ -3547,6 +3757,11 @@ snapshots:
|
||||
|
||||
crelt@1.0.7: {}
|
||||
|
||||
css-tree@3.2.1:
|
||||
dependencies:
|
||||
mdn-data: 2.27.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
csstype@3.2.3: {}
|
||||
|
||||
cytoscape-cose-bilkent@4.1.0(cytoscape@3.34.2):
|
||||
@@ -3733,6 +3948,13 @@ snapshots:
|
||||
d3: 7.9.0
|
||||
lodash-es: 4.18.1
|
||||
|
||||
data-urls@7.0.0:
|
||||
dependencies:
|
||||
whatwg-mimetype: 5.0.0
|
||||
whatwg-url: 16.0.1
|
||||
transitivePeerDependencies:
|
||||
- '@noble/hashes'
|
||||
|
||||
dayjs@1.11.23: {}
|
||||
|
||||
de-indent@1.0.2: {}
|
||||
@@ -3741,6 +3963,8 @@ snapshots:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
decimal.js@10.6.0: {}
|
||||
|
||||
decode-named-character-reference@1.3.0:
|
||||
dependencies:
|
||||
character-entities: 2.0.2
|
||||
@@ -3768,6 +3992,8 @@ snapshots:
|
||||
|
||||
entities@7.0.1: {}
|
||||
|
||||
entities@8.0.0: {}
|
||||
|
||||
es-module-lexer@2.3.2: {}
|
||||
|
||||
es-toolkit@1.52.0: {}
|
||||
@@ -3871,6 +4097,12 @@ snapshots:
|
||||
|
||||
hookable@5.5.3: {}
|
||||
|
||||
html-encoding-sniffer@6.0.0:
|
||||
dependencies:
|
||||
'@exodus/bytes': 1.15.1
|
||||
transitivePeerDependencies:
|
||||
- '@noble/hashes'
|
||||
|
||||
html-void-elements@3.0.0: {}
|
||||
|
||||
iconv-lite@0.6.3:
|
||||
@@ -3887,6 +4119,8 @@ snapshots:
|
||||
|
||||
is-plain-obj@4.1.0: {}
|
||||
|
||||
is-potential-custom-element-name@1.0.1: {}
|
||||
|
||||
js-beautify@2.0.3:
|
||||
dependencies:
|
||||
config-chain: 1.1.13
|
||||
@@ -3897,6 +4131,32 @@ snapshots:
|
||||
|
||||
js-cookie@3.0.8: {}
|
||||
|
||||
jsdom@30.0.1:
|
||||
dependencies:
|
||||
'@asamuzakjp/css-color': 6.0.7
|
||||
'@asamuzakjp/dom-selector': 8.3.2
|
||||
'@bramus/specificity': 2.4.2
|
||||
'@csstools/css-syntax-patches-for-csstree': 1.1.12(css-tree@3.2.1)
|
||||
'@exodus/bytes': 1.15.1
|
||||
css-tree: 3.2.1
|
||||
data-urls: 7.0.0
|
||||
decimal.js: 10.6.0
|
||||
html-encoding-sniffer: 6.0.0
|
||||
is-potential-custom-element-name: 1.0.1
|
||||
lru-cache: 11.5.2
|
||||
parse5: 8.0.1
|
||||
saxes: 6.0.0
|
||||
symbol-tree: 3.2.4
|
||||
tough-cookie: 6.0.2
|
||||
undici: 8.10.2
|
||||
w3c-xmlserializer: 5.0.0
|
||||
webidl-conversions: 8.0.1
|
||||
whatwg-mimetype: 5.0.0
|
||||
whatwg-url: 17.1.0
|
||||
xml-name-validator: 5.0.0
|
||||
transitivePeerDependencies:
|
||||
- '@noble/hashes'
|
||||
|
||||
katex@0.16.47:
|
||||
dependencies:
|
||||
commander: 8.3.0
|
||||
@@ -4069,6 +4329,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.4
|
||||
|
||||
mdn-data@2.27.1: {}
|
||||
|
||||
mermaid@11.17.2:
|
||||
dependencies:
|
||||
'@braintree/sanitize-url': 7.1.2
|
||||
@@ -4340,6 +4602,10 @@ snapshots:
|
||||
|
||||
package-manager-detector@1.8.0: {}
|
||||
|
||||
parse5@8.0.1:
|
||||
dependencies:
|
||||
entities: 8.0.0
|
||||
|
||||
path-browserify@1.0.1: {}
|
||||
|
||||
path-data-parser@0.1.0: {}
|
||||
@@ -4486,6 +4752,8 @@ snapshots:
|
||||
|
||||
proto-list@1.2.4: {}
|
||||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
quansync@0.2.11: {}
|
||||
|
||||
readdirp@5.1.1: {}
|
||||
@@ -4550,6 +4818,8 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
require-from-string@2.0.2: {}
|
||||
|
||||
robust-predicates@3.0.3: {}
|
||||
|
||||
rollup@4.63.1:
|
||||
@@ -4597,6 +4867,10 @@ snapshots:
|
||||
|
||||
safer-buffer@2.1.2: {}
|
||||
|
||||
saxes@6.0.0:
|
||||
dependencies:
|
||||
xmlchars: 2.2.0
|
||||
|
||||
scule@1.3.0: {}
|
||||
|
||||
semver@7.8.5: {}
|
||||
@@ -4633,6 +4907,8 @@ snapshots:
|
||||
|
||||
stylis@4.4.0: {}
|
||||
|
||||
symbol-tree@3.2.4: {}
|
||||
|
||||
tinybench@2.9.0: {}
|
||||
|
||||
tinyexec@1.3.0: {}
|
||||
@@ -4644,6 +4920,20 @@ snapshots:
|
||||
|
||||
tinyrainbow@3.1.1: {}
|
||||
|
||||
tldts-core@7.4.11: {}
|
||||
|
||||
tldts@7.4.11:
|
||||
dependencies:
|
||||
tldts-core: 7.4.11
|
||||
|
||||
tough-cookie@6.0.2:
|
||||
dependencies:
|
||||
tldts: 7.4.11
|
||||
|
||||
tr46@6.0.0:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
|
||||
trim-lines@3.0.1: {}
|
||||
|
||||
trough@2.2.0: {}
|
||||
@@ -4656,6 +4946,8 @@ snapshots:
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
|
||||
undici@8.10.2: {}
|
||||
|
||||
unified@11.0.5:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
@@ -4734,7 +5026,7 @@ snapshots:
|
||||
fsevents: 2.3.3
|
||||
yaml: 2.9.0
|
||||
|
||||
vitest@4.1.11(@types/node@22.20.1)(happy-dom@20.11.15)(vite@6.4.3(@types/node@22.20.1)(yaml@2.9.0)):
|
||||
vitest@4.1.11(@types/node@22.20.1)(happy-dom@20.11.15)(jsdom@30.0.1)(vite@6.4.3(@types/node@22.20.1)(yaml@2.9.0)):
|
||||
dependencies:
|
||||
'@vitest/expect': 4.1.11
|
||||
'@vitest/mocker': 4.1.11(vite@6.4.3(@types/node@22.20.1)(yaml@2.9.0))
|
||||
@@ -4759,6 +5051,7 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/node': 22.20.1
|
||||
happy-dom: 20.11.15
|
||||
jsdom: 30.0.1
|
||||
transitivePeerDependencies:
|
||||
- msw
|
||||
|
||||
@@ -4817,10 +5110,34 @@ snapshots:
|
||||
|
||||
w3c-keyname@2.2.8: {}
|
||||
|
||||
w3c-xmlserializer@5.0.0:
|
||||
dependencies:
|
||||
xml-name-validator: 5.0.0
|
||||
|
||||
webidl-conversions@8.0.1: {}
|
||||
|
||||
webpack-virtual-modules@0.6.2: {}
|
||||
|
||||
whatwg-mimetype@3.0.0: {}
|
||||
|
||||
whatwg-mimetype@5.0.0: {}
|
||||
|
||||
whatwg-url@16.0.1:
|
||||
dependencies:
|
||||
'@exodus/bytes': 1.15.1
|
||||
tr46: 6.0.0
|
||||
webidl-conversions: 8.0.1
|
||||
transitivePeerDependencies:
|
||||
- '@noble/hashes'
|
||||
|
||||
whatwg-url@17.1.0:
|
||||
dependencies:
|
||||
'@exodus/bytes': 1.15.1
|
||||
tr46: 6.0.0
|
||||
webidl-conversions: 8.0.1
|
||||
transitivePeerDependencies:
|
||||
- '@noble/hashes'
|
||||
|
||||
why-is-node-running@2.3.0:
|
||||
dependencies:
|
||||
siginfo: 2.0.0
|
||||
@@ -4828,6 +5145,10 @@ snapshots:
|
||||
|
||||
ws@8.21.3: {}
|
||||
|
||||
xml-name-validator@5.0.0: {}
|
||||
|
||||
xmlchars@2.2.0: {}
|
||||
|
||||
yaml@2.9.0: {}
|
||||
|
||||
zwitch@2.0.4: {}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @vitest-environment happy-dom
|
||||
// @vitest-environment jsdom
|
||||
import { expect, it, vi } from 'vitest'
|
||||
import { flushPromises, mount } from '@vue/test-utils'
|
||||
import DiagramInteractions from './DiagramInteractions.vue'
|
||||
@@ -24,3 +24,42 @@ it.each(['markdown-mermaid', 'editor-mermaid-preview'])('handles copied SVG cont
|
||||
expect(dialog.textContent).toContain('Diagram')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
|
||||
it('arms wheel zoom with middle click and releases page scrolling after mouse movement', async () => {
|
||||
const wrapper = mount(DiagramInteractions, { slots: { default: '<div class="markdown-mermaid"><svg viewBox="0 0 400 200"><text>Chart</text></svg></div>' }, attachTo: document.body })
|
||||
const svg = wrapper.get('svg').element as SVGSVGElement
|
||||
const scroll = () => { const event = new WheelEvent('wheel', { deltaY: -100, bubbles: true, cancelable: true }); svg.dispatchEvent(event); return event }
|
||||
expect(scroll().defaultPrevented).toBe(false)
|
||||
await wrapper.get('svg').trigger('mousedown', { button: 1, clientX: 10, clientY: 20 })
|
||||
expect(scroll().defaultPrevented).toBe(true)
|
||||
expect(parseFloat(svg.style.width)).toBeGreaterThan(400)
|
||||
const width = svg.style.width
|
||||
document.dispatchEvent(new MouseEvent('mousemove', { clientX: 11, clientY: 20 }))
|
||||
expect(scroll().defaultPrevented).toBe(false)
|
||||
expect(svg.style.width).toBe(width)
|
||||
await wrapper.get('svg').trigger('mousedown', { button: 1 })
|
||||
window.dispatchEvent(new Event('blur'))
|
||||
expect(scroll().defaultPrevented).toBe(false)
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
|
||||
it('preserves Mermaid HTML node and edge labels in the viewer while removing active HTML', async () => {
|
||||
const container = document.createElement('div')
|
||||
container.className = 'markdown-mermaid'
|
||||
container.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 200"><g class="nodeLabel"><foreignObject width="100" height="30"><div xmlns="http://www.w3.org/1999/xhtml"><span onclick="alert(1)">系统验证</span></div></foreignObject></g><g class="edgeLabel"><foreignObject width="100" height="30"><div xmlns="http://www.w3.org/1999/xhtml">验证通过<img src="x" onerror="alert(1)" /></div></foreignObject></g><text>结束</text></svg>`
|
||||
appendDiagramControls(container)
|
||||
const wrapper = mount(DiagramInteractions, { slots: { default: '<div class="markdown-mermaid"></div>' }, attachTo: document.body })
|
||||
wrapper.get('.markdown-mermaid').element.innerHTML = container.innerHTML
|
||||
const dialog = document.querySelector('dialog')!
|
||||
dialog.showModal = vi.fn()
|
||||
await wrapper.get('[data-diagram-action="view"]').trigger('click')
|
||||
await flushPromises()
|
||||
expect(dialog.querySelectorAll('foreignObject')).toHaveLength(2)
|
||||
expect(dialog.textContent).toContain('系统验证')
|
||||
expect(dialog.textContent).toContain('验证通过')
|
||||
expect(dialog.textContent).toContain('结束')
|
||||
expect(dialog.querySelector('[onclick], [onerror], script')).toBeNull()
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { nextTick, ref } from 'vue'
|
||||
import { nextTick, ref, onBeforeUnmount } from 'vue'
|
||||
import AppIcon from './AppIcon.vue'
|
||||
import { ZoomIn, ZoomOut, Refresh, Close } from '@element-plus/icons-vue'
|
||||
import DOMPurify from 'dompurify'
|
||||
|
||||
const viewer = ref<HTMLDialogElement | null>(null)
|
||||
@@ -7,6 +9,45 @@ const svgHtml = ref('')
|
||||
const scale = ref(1)
|
||||
const baseWidth = ref(800)
|
||||
let opener: HTMLElement | null = null
|
||||
let wheelTarget: HTMLElement | null = null
|
||||
let anchor = { x: 0, y: 0 }
|
||||
const wheelActive = ref(false)
|
||||
function disarm() {
|
||||
wheelTarget?.removeAttribute('data-wheel-zoom')
|
||||
wheelTarget = null; wheelActive.value = false
|
||||
document.removeEventListener('mousemove', moved, true)
|
||||
document.removeEventListener('wheel', wheel, true)
|
||||
window.removeEventListener('blur', disarm)
|
||||
}
|
||||
function moved(event: MouseEvent) { if (event.clientX !== anchor.x || event.clientY !== anchor.y) disarm() }
|
||||
function arm(event: MouseEvent) {
|
||||
if (event.button !== 1 || !(event.target instanceof Element) || !event.target.closest('svg') || event.target.closest('.diagram-controls')) return
|
||||
const target = event.target.closest<HTMLElement>('.editor-mermaid-preview, .markdown-mermaid, .diagram-viewer-image')
|
||||
if (!target) return
|
||||
event.preventDefault(); event.stopPropagation(); disarm()
|
||||
wheelTarget = target; wheelActive.value = true; anchor = { x: event.clientX, y: event.clientY }
|
||||
target.dataset.wheelZoom = 'true'
|
||||
document.addEventListener('mousemove', moved, true)
|
||||
document.addEventListener('wheel', wheel, { capture: true, passive: false })
|
||||
window.addEventListener('blur', disarm)
|
||||
}
|
||||
function wheel(event: WheelEvent) {
|
||||
if (!wheelTarget?.isConnected || !(event.target instanceof Node) || !wheelTarget.contains(event.target)) { disarm(); return }
|
||||
event.preventDefault(); event.stopPropagation()
|
||||
const delta = event.deltaY * (event.deltaMode === 1 ? 16 : event.deltaMode === 2 ? 400 : 1)
|
||||
const factor = Math.exp(-Math.max(-200, Math.min(200, delta)) * .002)
|
||||
if (wheelTarget.classList.contains('diagram-viewer-image')) scale.value = Math.max(.2, Math.min(5, scale.value * factor))
|
||||
else zoom(wheelTarget, Math.max(.2, Math.min(5, Number(wheelTarget.dataset.diagramScale || 1) * factor)))
|
||||
}
|
||||
function zoom(diagram: HTMLElement, next: number) {
|
||||
const svg = diagram.querySelector<SVGSVGElement>('svg')
|
||||
if (!svg) return
|
||||
diagram.dataset.diagramScale = String(next)
|
||||
svg.style.width = next === 1 ? '' : `${widthOf(svg) * next}px`
|
||||
svg.style.maxWidth = next === 1 ? '' : 'none'
|
||||
svg.style.height = 'auto'
|
||||
}
|
||||
onBeforeUnmount(disarm)
|
||||
function widthOf(svg: SVGSVGElement) {
|
||||
return svg.viewBox?.baseVal?.width || Number(svg.getAttribute('viewBox')?.split(/[ ,]+/)[2]) || svg.getBoundingClientRect().width || 800
|
||||
}
|
||||
@@ -22,7 +63,17 @@ async function interact(event: MouseEvent) {
|
||||
if (action === 'view') {
|
||||
opener = button
|
||||
baseWidth.value = widthOf(svg)
|
||||
svgHtml.value = DOMPurify.sanitize(svg.outerHTML, { USE_PROFILES: { svg: true, svgFilters: true, html: true } })
|
||||
// Mermaid HTML labels live in SVG foreignObject nodes. Preserve that
|
||||
// integration point while still sanitizing the embedded HTML and handlers.
|
||||
const copy = svg.cloneNode(true) as SVGSVGElement
|
||||
for (const label of copy.querySelectorAll('foreignObject, foreignobject')) {
|
||||
label.innerHTML = DOMPurify.sanitize(label.innerHTML, { USE_PROFILES: { html: true } })
|
||||
}
|
||||
svgHtml.value = DOMPurify.sanitize(copy.outerHTML, {
|
||||
USE_PROFILES: { svg: true, svgFilters: true, html: true },
|
||||
ADD_TAGS: ['foreignObject'], ADD_ATTR: ['xmlns'],
|
||||
HTML_INTEGRATION_POINTS: { foreignobject: true },
|
||||
})
|
||||
scale.value = 1
|
||||
await nextTick()
|
||||
viewer.value?.showModal()
|
||||
@@ -30,25 +81,23 @@ async function interact(event: MouseEvent) {
|
||||
}
|
||||
const previous = Number(diagram.dataset.diagramScale || 1)
|
||||
const next = action === 'reset' ? 1 : Math.max(.2, Math.min(5, previous * (action === 'in' ? 1.2 : 1 / 1.2)))
|
||||
diagram.dataset.diagramScale = String(next)
|
||||
svg.style.width = next === 1 ? '' : `${widthOf(svg) * next}px`
|
||||
svg.style.maxWidth = next === 1 ? '' : 'none'
|
||||
svg.style.height = 'auto'
|
||||
zoom(diagram, next)
|
||||
}
|
||||
function close() { viewer.value?.close(); svgHtml.value = ''; opener?.focus() }
|
||||
function close() { disarm(); viewer.value?.close(); svgHtml.value = ''; opener?.focus() }
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="diagram-interactions" @click.capture="interact">
|
||||
<div class="diagram-interactions" @click.capture="interact" @mousedown.capture="arm">
|
||||
<slot />
|
||||
<span v-if="wheelActive" class="wheel-zoom-hint" role="status">滚轮缩放中 · 移动鼠标退出</span>
|
||||
<Teleport to="body">
|
||||
<dialog ref="viewer" class="diagram-viewer" aria-label="图表大图查看" @cancel.prevent="close">
|
||||
<dialog ref="viewer" class="diagram-viewer" aria-label="图表大图查看" @cancel.prevent="close" @mousedown.capture="arm">
|
||||
<header><strong>图表查看</strong><div class="diagram-controls">
|
||||
<button type="button" @click="scale = Math.max(.2, scale / 1.2)">缩小</button>
|
||||
<button type="button" @click="scale = Math.max(.2, scale / 1.2)"><AppIcon :icon="ZoomOut" :size="16" />缩小</button>
|
||||
<output>{{ Math.round(scale * 100) }}%</output>
|
||||
<button type="button" @click="scale = Math.min(5, scale * 1.2)">放大</button>
|
||||
<button type="button" @click="scale = 1">重置</button>
|
||||
<button type="button" autofocus @click="close">关闭</button>
|
||||
<button type="button" @click="scale = Math.min(5, scale * 1.2)"><AppIcon :icon="ZoomIn" :size="16" />放大</button>
|
||||
<button type="button" @click="scale = 1"><AppIcon :icon="Refresh" :size="16" />重置</button>
|
||||
<button type="button" autofocus @click="close"><AppIcon :icon="Close" :size="16" />关闭</button>
|
||||
</div></header>
|
||||
<div class="diagram-viewer-scroll"><div class="diagram-viewer-image" :style="{ width: `${baseWidth * scale}px` }" v-html="svgHtml" /></div>
|
||||
</dialog>
|
||||
@@ -59,13 +108,20 @@ function close() { viewer.value?.close(); svgHtml.value = ''; opener?.focus() }
|
||||
<style>
|
||||
.diagram-interactions { min-width: 0; }
|
||||
.diagram-controls { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin: 8px 0; }
|
||||
.diagram-controls button { padding: 5px 10px; border: 1px solid var(--color-border-default); border-radius: var(--radius-sm); color: var(--color-text-primary); background: var(--color-surface-primary); cursor: pointer; font: inherit; font-size: 12px; }
|
||||
.diagram-controls button { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; border: 1px solid var(--color-border-default); border-radius: var(--radius-sm); color: var(--color-text-primary); background: var(--color-surface-primary); cursor: pointer; font: inherit; font-size: 12px; }
|
||||
.diagram-controls button:hover { border-color: var(--color-accent-primary); }
|
||||
.diagram-controls button:focus-visible { outline: 2px solid var(--color-accent-primary); }
|
||||
.diagram-viewer { width: min(1200px, 94vw); max-width: 94vw; height: 85vh; padding: 16px; color: var(--color-text-primary); background: var(--color-background-primary); border: 1px solid var(--color-border-default); border-radius: var(--radius-md); }
|
||||
.diagram-viewer::backdrop { background: #0008; }
|
||||
.diagram-viewer header { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
|
||||
.diagram-viewer-scroll { height: calc(100% - 64px); overflow: auto; }
|
||||
.diagram-viewer-image { margin: auto; }
|
||||
.diagram-viewer-image { margin: auto; transition: width 180ms ease-out; }
|
||||
.diagram-viewer-image svg { width: 100% !important; max-width: none !important; height: auto !important; }
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.editor-mermaid-preview > svg, .markdown-mermaid > svg { transition: width 180ms ease-out; }
|
||||
[data-wheel-zoom="true"] { outline: 2px solid var(--color-accent-primary); outline-offset: -2px; cursor: zoom-in; }
|
||||
.wheel-zoom-hint { position: fixed; bottom: 32px; left: 50%; transform: translateX(-50%); z-index: 2000; padding: 8px 14px; border-radius: var(--radius-md); background: var(--color-surface-elevated); color: var(--color-text-primary); border: 1px solid var(--color-border-default); pointer-events: none; }
|
||||
@media (prefers-reduced-motion: reduce) { .editor-mermaid-preview > svg, .markdown-mermaid > svg, .diagram-viewer-image { transition: none; } }
|
||||
</style>
|
||||
|
||||
@@ -267,9 +267,19 @@ onMounted(load)
|
||||
|
||||
<style scoped>
|
||||
.editor-fields { display: grid; gap: var(--space-lg); border: 0; padding: 0; margin: 0; min-width: 0; }
|
||||
.mcp-page { overflow: auto; }.notice-banner,.error-banner { margin-bottom: var(--space-lg); }.server-list { display: grid; gap: var(--space-lg); }.server-card { display: grid; gap: var(--space-md); }
|
||||
.mcp-page { overflow: auto; }
|
||||
.mcp-page > :is(.feature-header, .notice-banner, .error-banner, .server-list, .empty) { box-sizing: border-box; width: 100%; max-width: 1180px; margin-inline: auto; }
|
||||
.server-list { grid-template-columns: minmax(0, 1fr); }
|
||||
.server-card, .server-main, .server-title, .server-title > div, .secret-input { min-width: 0; }
|
||||
.server-title { flex: 1; }
|
||||
.server-title h2, .metadata span, .secrets label > span { overflow-wrap: anywhere; }
|
||||
.server-title code { display: block; white-space: pre-wrap; }
|
||||
.server-main > .badge { flex-shrink: 0; }
|
||||
.secret-input input { min-width: 0; width: 100%; border: 1px solid var(--color-border-default); border-radius: var(--radius-sm); padding: 8px 10px; background: var(--color-background-secondary); color: var(--color-text-primary); font: inherit; }
|
||||
.secret-input button { flex-shrink: 0; }
|
||||
.notice-banner,.error-banner { margin-bottom: var(--space-lg); }.server-list { display: grid; gap: var(--space-lg); }.server-card { display: grid; gap: var(--space-md); }
|
||||
.server-main,.server-title,.metadata,.card-actions,.inline-actions,.template-row,.modal-card header,.modal-card footer { display: flex; align-items: center; gap: var(--space-sm); }.server-main { justify-content: space-between; }.server-title { align-items: flex-start; }.server-title h2 { margin-bottom: 4px; }.server-title code { color: var(--color-text-secondary); overflow-wrap: anywhere; }.metadata { flex-wrap: wrap; color: var(--color-text-tertiary); font-size: var(--font-size-sm); }.metadata span + span::before { content: '·'; margin-right: var(--space-sm); }.compact { margin: 0; }
|
||||
.card-actions { justify-content: flex-end; border-top: 1px solid var(--color-border-subtle); padding-top: var(--space-md); }.empty { text-align: center; place-items: center; display: grid; gap: var(--space-md); padding: 64px; }.secrets { border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md); padding: var(--space-md); display: grid; gap: var(--space-sm); }.secrets label { display: grid; grid-template-columns: minmax(220px,.7fr) 1fr; align-items: center; gap: var(--space-md); }.secrets small,.modal-card small { color: var(--color-text-tertiary); }.secret-input { display: flex; gap: var(--space-sm); }.secret-input input { flex: 1; }
|
||||
.card-actions { flex-wrap: wrap; justify-content: flex-end; border-top: 1px solid var(--color-border-subtle); padding-top: var(--space-md); }.empty { text-align: center; place-items: center; display: grid; gap: var(--space-md); padding: 64px; }.secrets { border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md); padding: var(--space-md); display: grid; gap: var(--space-sm); }.secrets label { display: grid; grid-template-columns: minmax(0,.7fr) minmax(0,1fr); align-items: center; gap: var(--space-md); }.secrets small,.modal-card small { color: var(--color-text-tertiary); }.secret-input { display: flex; gap: var(--space-sm); }.secret-input input { flex: 1; }
|
||||
.modal-backdrop { position: fixed; inset: 0; z-index: 1000; background: rgb(0 0 0 / .48); display: grid; place-items: center; padding: var(--space-xl); }.modal-card { width: min(800px,100%); max-height: calc(100vh - 48px); overflow: auto; background: var(--color-background-primary); border: 1px solid var(--color-border-default); border-radius: var(--radius-xl); box-shadow: var(--shadow-xl); padding: var(--space-xl); display: grid; gap: var(--space-lg); animation: modal-in var(--motion-normal) ease-out; }.modal-card header,.modal-card footer { justify-content: space-between; }.modal-card footer { justify-content: flex-end; }.modal-card label { display: grid; gap: var(--space-xs); font-weight: 600; }.modal-card input,.modal-card textarea { width: 100%; border: 1px solid var(--color-border-default); border-radius: var(--radius-md); padding: 10px 12px; color: var(--color-text-primary); background: var(--color-background-secondary); font: inherit; }.modal-card textarea { resize: vertical; font-family: var(--font-family-mono); font-size: var(--font-size-sm); }.json-editor { line-height: 1.55; }.close { border: 0; background: transparent; color: var(--color-text-secondary); font-size: 28px; cursor: pointer; }
|
||||
.template-row { flex-wrap: wrap; }.template-row > span { margin-right: auto; font-weight: 600; }.template,.mode-tabs button { border: 1px solid var(--color-border-default); background: var(--color-background-secondary); color: var(--color-text-secondary); padding: 7px 10px; border-radius: var(--radius-md); cursor: pointer; }.template.active,.mode-tabs button.active { color: var(--color-accent-primary); border-color: var(--color-accent-primary); background: var(--color-accent-soft); }.mode-tabs { display: inline-flex; justify-self: start; gap: 2px; padding: 3px; border-radius: var(--radius-md); background: var(--color-background-secondary); }.two-columns { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-md); }
|
||||
@keyframes modal-in { from { opacity: 0; transform: translateY(8px) scale(.99); } } @media (max-width:720px) { .two-columns,.secrets label { grid-template-columns:1fr; }.card-actions { flex-wrap:wrap; } }
|
||||
|
||||
@@ -8,6 +8,10 @@ it('distinguishes unreported tokens from zero and switches to request counts', a
|
||||
api: { requests: 2, totals: { input_tokens: 0 }, coverage: { input_tokens: 1 } },
|
||||
}] } })
|
||||
expect(wrapper.findAll('.usage-bar.missing')).toHaveLength(1)
|
||||
expect(wrapper.get('.usage-pie').attributes('aria-label')).toContain('本地模型: 0')
|
||||
await wrapper.get('.chart-column').trigger('mouseenter')
|
||||
expect(wrapper.get('.chart-column').classes()).toContain('highlighted')
|
||||
expect(wrapper.get('.chart-readout').text()).toContain('未提供')
|
||||
expect(wrapper.get('.usage-bar.local').attributes('aria-label')).toContain('未提供')
|
||||
expect(wrapper.get('.usage-bar.api').attributes('aria-label')).toContain('覆盖 1/2')
|
||||
await wrapper.get('select').setValue('requests')
|
||||
|
||||
@@ -11,6 +11,11 @@ export interface UsageBucket {
|
||||
}
|
||||
const props = defineProps<{ buckets: UsageBucket[] }>()
|
||||
const metric = ref('input_tokens')
|
||||
const hovered = ref<string | null>(null)
|
||||
const activeBucket = computed(() => props.buckets.find(bucket => bucket.date === hovered.value))
|
||||
const sums = computed(() => sources.map(source => ({ source, value: props.buckets.reduce((sum, bucket) => sum + (value(bucket, source) ?? 0), 0), coverage: props.buckets.reduce((sum, bucket) => sum + (metric.value === 'requests' ? bucket[source].requests : bucket[source].coverage[metric.value] ?? 0), 0), requests: props.buckets.reduce((sum, bucket) => sum + bucket[source].requests, 0) })))
|
||||
const total = computed(() => sums.value.reduce((sum, item) => sum + item.value, 0))
|
||||
const pie = computed(() => total.value ? `conic-gradient(var(--color-info) 0 ${sums.value[0]!.value / total.value * 100}%, var(--color-accent-primary) 0 100%)` : 'var(--color-border-subtle)')
|
||||
const sources = ['local', 'api'] as const
|
||||
const labels = computed(() => ({ local: t('本地模型', 'Local models'), api: t('模型提供商', 'Providers') }))
|
||||
const metricLabel = computed(() => metric.value === 'requests' ? t('请求次数', 'Requests') : metric.value === 'input_tokens' ? t('输入 Token', 'Input tokens') : metric.value === 'output_tokens' ? t('输出 Token', 'Output tokens') : t('总 Token', 'Total tokens'))
|
||||
@@ -38,33 +43,48 @@ const maximum = computed(() => Math.max(1, ...props.buckets.flatMap(bucket => so
|
||||
</select>
|
||||
<div class="chart-legend"><span class="local"><AppIcon :icon="Cpu" :size="16" />{{ labels.local }} · {{ t('实色', 'Solid') }}</span><span class="api"><AppIcon :icon="Connection" :size="16" />{{ labels.api }} · {{ t('斜纹', 'Striped') }}</span></div>
|
||||
</div>
|
||||
<div class="chart-layout"><div class="bar-pane">
|
||||
<p class="subtle chart-scale">{{ metricLabel }} · 0 — {{ maximum.toLocaleString() }}</p>
|
||||
<div class="chart-scroll" tabindex="0" :aria-label="t('按日期横向滚动查看柱状图', 'Scroll the chart by date')">
|
||||
<div class="chart-columns" :style="{ minWidth: `${buckets.length * 48}px` }">
|
||||
<div v-for="bucket in buckets" :key="bucket.date" class="chart-column">
|
||||
<div class="chart-columns" :style="{ minWidth: `${buckets.length * 14}px` }">
|
||||
<div v-for="bucket in buckets" :key="bucket.date" class="chart-column" :class="{ highlighted: hovered === bucket.date }" tabindex="0" @mouseenter="hovered = bucket.date" @mouseleave="hovered = null" @focus="hovered = bucket.date" @blur="hovered = null">
|
||||
<div class="chart-bars">
|
||||
<div v-for="source in sources" :key="source" class="usage-bar" :class="[source, { missing: value(bucket, source) === null }]"
|
||||
:style="{ height: value(bucket, source) === null ? '8px' : `${(value(bucket, source) ?? 0) / maximum * 160}px` }"
|
||||
role="img" :aria-label="description(bucket, source)" :title="description(bucket, source)" />
|
||||
</div>
|
||||
<small :title="`${bucket.date} – ${bucket.end_date}`">{{ bucket.date.slice(5) }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-readout" aria-live="polite"><template v-if="activeBucket"><strong>{{ activeBucket.date }}</strong><span v-for="source in sources" :key="source" :class="source">{{ description(activeBucket, source) }}</span></template><span v-else>{{ t('悬停或聚焦日期查看数据', 'Hover or focus a date for details') }}</span></div>
|
||||
</div><aside class="pie-pane"><h4>{{ t('来源占比', 'Usage by source') }}</h4>
|
||||
<div class="usage-pie" role="img" :aria-label="sums.map(item => `${labels[item.source]}: ${item.value}`).join(' · ')" :style="{ background: pie }"><div><strong>{{ total.toLocaleString() }}</strong><small>{{ metricLabel }}</small></div></div>
|
||||
<div v-for="item in sums" :key="item.source" class="pie-value" :class="item.source"><AppIcon :icon="item.source === 'local' ? Cpu : Connection" :size="16" /><span>{{ labels[item.source] }}</span><strong>{{ item.coverage ? item.value.toLocaleString() : item.requests ? t('未提供', 'Unavailable') : '0' }} · {{ total ? (item.value / total * 100).toFixed(1) + '%' : '—' }}</strong><small>{{ t('覆盖', 'Coverage') }} {{ item.coverage }}/{{ item.requests }}</small></div>
|
||||
<p class="subtle">{{ t('占比仅基于已报告值;缺失指标不计入分母。', 'Shares use reported values only; missing counters are excluded.') }}</p>
|
||||
</aside></div>
|
||||
<p class="subtle">{{ t('按本机时区分组;柱高仅汇总已报告值,悬停可查看覆盖请求数。虚线表示有请求但未提供该指标,不作为零消耗。', 'Grouped by your local UTC offset. Bars sum reported values; hover for coverage. Dashed markers mean requests with unavailable counters, not zero usage.') }}</p>
|
||||
<details><summary>{{ t('查看图表数据', 'View chart data') }}</summary><div class="chart-scroll"><table><thead><tr><th>{{ t('日期', 'Date') }}</th><th>{{ labels.local }}</th><th>{{ labels.api }}</th></tr></thead><tbody><tr v-for="bucket in buckets" :key="bucket.date"><th>{{ bucket.date }}<template v-if="bucket.date !== bucket.end_date"> – {{ bucket.end_date }}</template></th><td v-for="source in sources" :key="source">{{ description(bucket, source) }}</td></tr></tbody></table></div></details>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.chart-layout { display: grid; grid-template-columns: minmax(0, 2fr) minmax(220px, 1fr); gap: 24px; margin-top: 16px; }
|
||||
.bar-pane { min-width: 0; }.pie-pane { border-left: 1px dashed var(--color-border-default); padding-left: 24px; min-width: 0; }
|
||||
.usage-pie { width: 170px; aspect-ratio: 1; margin: 20px auto; border-radius: 50%; display: grid; place-items: center; }
|
||||
.usage-pie > div { width: 112px; aspect-ratio: 1; border-radius: 50%; background: var(--color-surface-primary); display: flex; flex-direction: column; align-items: center; justify-content: center; }.usage-pie strong { font-size: 20px; }.usage-pie small { font-size: 11px; }
|
||||
.pie-value { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; font-size: 12px; margin: 10px 0; }.pie-value small { width: 100%; color: var(--color-text-secondary); }.pie-pane > p { font-size: 12px; }
|
||||
.chart-column { transition: background-color 150ms; border-radius: 4px; }.chart-column.highlighted { background: var(--color-accent-soft); outline: 1px solid var(--color-border-focus); outline-offset: -1px; }.highlighted .usage-bar { filter: brightness(1.15); }
|
||||
.chart-readout { min-height: 76px; font-size: 12px; display: flex; flex-direction: column; gap: 4px; margin-top: 12px; padding: 10px; border: 1px dashed var(--color-border-subtle); border-radius: var(--radius-sm); background: var(--color-surface-primary); overflow-wrap: anywhere; }
|
||||
@media (max-width: 760px) { .chart-layout { grid-template-columns: 1fr; }.pie-pane { border-left: 0; border-top: 1px dashed var(--color-border-default); padding: 20px 0 0; } }
|
||||
|
||||
.usage-chart { min-width: 0; padding: 16px; border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md); background: var(--color-background-primary); }
|
||||
.chart-toolbar, .chart-legend, .chart-legend span { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
||||
.chart-toolbar { justify-content: space-between; }.chart-toolbar .select { width: auto; }.chart-legend { font-size: 12px; }
|
||||
.local { color: var(--color-info); }.api { color: var(--color-accent-primary); }
|
||||
.chart-scale { margin: 12px 0 0; font-size: 12px; }.chart-scroll { overflow-x: auto; padding-bottom: 8px; }.chart-columns { display: flex; gap: 8px; }
|
||||
.chart-column { flex: 1; min-width: 40px; text-align: center; }.chart-column small { font-size: 11px; color: var(--color-text-secondary); white-space: nowrap; }
|
||||
.chart-bars { height: 176px; display: flex; align-items: flex-end; justify-content: center; gap: 4px; border-bottom: 1px solid var(--color-border-default); background: repeating-linear-gradient(to top, transparent 0 39px, var(--color-border-subtle) 39px 40px); }
|
||||
.usage-bar { width: 14px; max-width: 35%; background: currentColor; border-radius: 3px 3px 0 0; }.usage-bar.api { background: repeating-linear-gradient(45deg, currentColor 0 4px, color-mix(in srgb, currentColor 45%, var(--color-surface-primary)) 4px 7px); }
|
||||
.chart-scale { margin: 12px 0 0; font-size: 12px; }.chart-scroll { overflow-x: auto; padding-bottom: 2px; }.chart-columns { display: flex; gap: 2px; }
|
||||
.chart-column { flex: 1; min-width: 12px; text-align: center; }
|
||||
.chart-bars { height: 176px; display: flex; align-items: flex-end; justify-content: center; gap: 2px; border-bottom: 1px solid var(--color-border-default); background: repeating-linear-gradient(to top, transparent 0 39px, var(--color-border-subtle) 39px 40px); }
|
||||
.usage-bar { width: 35%; max-width: 28px; background: currentColor; border-radius: 3px 3px 0 0; }.usage-bar.api { background: repeating-linear-gradient(45deg, currentColor 0 4px, color-mix(in srgb, currentColor 45%, var(--color-surface-primary)) 4px 7px); }
|
||||
.usage-bar.missing { background: transparent; border: 1px dashed currentColor; box-sizing: border-box; }
|
||||
.usage-chart > p:last-of-type { font-size: 12px; margin: 12px 0; }.usage-chart summary { cursor: pointer; color: var(--color-text-link); font-size: 12px; }
|
||||
table { width: 100%; border-collapse: collapse; font-size: 12px; }th, td { padding: 8px; text-align: left; border-bottom: 1px solid var(--color-border-subtle); }
|
||||
|
||||
@@ -7,7 +7,21 @@ export function appendDiagramControls(container: HTMLElement) {
|
||||
const button = document.createElement('button')
|
||||
button.type = 'button'
|
||||
button.dataset.diagramAction = action
|
||||
button.textContent = label
|
||||
const paths: Record<string, string> = {
|
||||
out: 'M8 11h6 M15 15l5 5 M17 10a7 7 0 1 1-14 0a7 7 0 0 1 14 0',
|
||||
in: 'M8 10h6 M11 7v6 M15 15l5 5 M17 10a7 7 0 1 1-14 0a7 7 0 0 1 14 0',
|
||||
reset: 'M3 10a9 9 0 1 1 2 8 M3 3v7h7',
|
||||
view: 'M3 9V3h6 M15 3h6v6 M21 15v6h-6 M9 21H3v-6',
|
||||
}
|
||||
const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
||||
icon.setAttribute('viewBox', '0 0 24 24')
|
||||
icon.setAttribute('width', '16'); icon.setAttribute('height', '16')
|
||||
icon.setAttribute('aria-hidden', 'true')
|
||||
const path = document.createElementNS(icon.namespaceURI, 'path')
|
||||
path.setAttribute('d', paths[action!]!)
|
||||
path.setAttribute('fill', 'none'); path.setAttribute('stroke', 'currentColor'); path.setAttribute('stroke-width', '1.8')
|
||||
icon.append(path)
|
||||
button.append(icon, document.createTextNode(label!))
|
||||
controls.append(button)
|
||||
}
|
||||
container.append(controls)
|
||||
|
||||
Reference in New Issue
Block a user