diff --git a/package-lock.json b/package-lock.json index 7a59f0ac..77ef9f03 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "markpad", - "version": "2.6.13", + "version": "2.6.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "markpad", - "version": "2.6.13", + "version": "2.6.14", "license": "MIT", "dependencies": { "@tauri-apps/api": "^2", @@ -16,11 +16,11 @@ "@tauri-apps/plugin-process": "^2.3.1", "@tauri-apps/plugin-updater": "^2.10.1", "@types/dompurify": "^3.0.5", - "dompurify": "^3.3.1", + "dompurify": "3.4.12", "highlight.js": "^11.11.1", "highlightjs-svelte": "^1.0.6", "katex": "^0.16.27", - "mermaid": "^11.12.2", + "mermaid": "^11.16.0", "monaco-editor": "^0.55.1", "monaco-vim": "^0.4.4", "node-stream-zip": "^1.15.0", @@ -57,55 +57,10 @@ "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==", "license": "MIT" }, - "node_modules/@chevrotain/cst-dts-gen": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", - "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/gast": "11.0.3", - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" - } - }, - "node_modules/@chevrotain/cst-dts-gen/node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, - "node_modules/@chevrotain/gast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", - "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" - } - }, - "node_modules/@chevrotain/gast/node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, - "node_modules/@chevrotain/regexp-to-ast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", - "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", - "license": "Apache-2.0" - }, "node_modules/@chevrotain/types": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", - "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", - "license": "Apache-2.0" - }, - "node_modules/@chevrotain/utils": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", - "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", + "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/@esbuild/aix-ppc64": { @@ -618,12 +573,12 @@ } }, "node_modules/@mermaid-js/parser": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.3.tgz", - "integrity": "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==", + "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": { - "langium": "3.3.1" + "@chevrotain/types": "~11.1.2" } }, "node_modules/@polka/url": { @@ -1619,6 +1574,16 @@ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "license": "MIT" }, + "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/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -1651,38 +1616,6 @@ "node": ">= 0.4" } }, - "node_modules/chevrotain": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", - "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/cst-dts-gen": "11.0.3", - "@chevrotain/gast": "11.0.3", - "@chevrotain/regexp-to-ast": "11.0.3", - "@chevrotain/types": "11.0.3", - "@chevrotain/utils": "11.0.3", - "lodash-es": "4.17.21" - } - }, - "node_modules/chevrotain-allstar": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", - "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", - "license": "MIT", - "dependencies": { - "lodash-es": "^4.17.21" - }, - "peerDependencies": { - "chevrotain": "^11.0.0" - } - }, - "node_modules/chevrotain/node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -1744,9 +1677,9 @@ } }, "node_modules/cytoscape": { - "version": "3.33.1", - "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", - "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.34.0.tgz", + "integrity": "sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg==", "license": "MIT", "engines": { "node": ">=0.10" @@ -2242,9 +2175,9 @@ } }, "node_modules/dagre-d3-es": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.13.tgz", - "integrity": "sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==", + "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", @@ -2252,9 +2185,9 @@ } }, "node_modules/dayjs": { - "version": "1.11.19", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", - "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "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": { @@ -2286,9 +2219,9 @@ } }, "node_modules/delaunator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", - "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "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" @@ -2302,14 +2235,25 @@ "license": "MIT" }, "node_modules/dompurify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", - "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", + "version": "3.4.12", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.12.tgz", + "integrity": "sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, + "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/esbuild": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", @@ -2454,9 +2398,9 @@ } }, "node_modules/katex": { - "version": "0.16.27", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.27.tgz", - "integrity": "sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw==", + "version": "0.16.47", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", + "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", "funding": [ "https://opencollective.com/katex", "https://github.com/sponsors/katex" @@ -2484,22 +2428,6 @@ "node": ">=6" } }, - "node_modules/langium": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz", - "integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==", - "license": "MIT", - "dependencies": { - "chevrotain": "~11.0.3", - "chevrotain-allstar": "~0.3.0", - "vscode-languageserver": "~9.0.1", - "vscode-languageserver-textdocument": "~1.0.11", - "vscode-uri": "~3.0.8" - }, - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/layout-base": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", @@ -2514,9 +2442,9 @@ "license": "MIT" }, "node_modules/lodash-es": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", - "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", "license": "MIT" }, "node_modules/magic-string": { @@ -2542,31 +2470,32 @@ } }, "node_modules/mermaid": { - "version": "11.12.2", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.2.tgz", - "integrity": "sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==", + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.16.0.tgz", + "integrity": "sha512-Zvm3kbstgdpvIJPPItlL7fppIZ3kibvc1oZIGxdvk9t6UFz6flv+Jw7FtRGKwfcI8OckmH04LqG6LlS6X4B1pA==", "license": "MIT", "dependencies": { - "@braintree/sanitize-url": "^7.1.1", - "@iconify/utils": "^3.0.1", - "@mermaid-js/parser": "^0.6.3", + "@braintree/sanitize-url": "^7.1.2", + "@iconify/utils": "^3.0.2", + "@mermaid-js/parser": "^1.2.0", "@types/d3": "^7.4.3", - "cytoscape": "^3.29.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.13", - "dayjs": "^1.11.18", - "dompurify": "^3.2.5", - "katex": "^0.16.22", + "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", - "lodash-es": "^4.17.21", - "marked": "^16.2.1", + "marked": "^16.3.0", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", - "uuid": "^11.1.0" + "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" } }, "node_modules/mermaid/node_modules/marked": { @@ -2603,15 +2532,6 @@ "marked": "14.0.0" } }, - "node_modules/monaco-editor/node_modules/dompurify": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", - "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", - "license": "(MPL-2.0 OR Apache-2.0)", - "optionalDependencies": { - "@types/trusted-types": "^2.0.7" - } - }, "node_modules/monaco-vim": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/monaco-vim/-/monaco-vim-0.4.4.tgz", @@ -2787,9 +2707,9 @@ } }, "node_modules/robust-predicates": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", - "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "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": { @@ -3532,16 +3452,16 @@ "license": "MIT" }, "node_modules/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "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/esm/bin/uuid" + "uuid": "dist-node/bin/uuid" } }, "node_modules/vite": { @@ -3639,55 +3559,6 @@ } } }, - "node_modules/vscode-jsonrpc": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", - "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/vscode-languageserver": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", - "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", - "license": "MIT", - "dependencies": { - "vscode-languageserver-protocol": "3.17.5" - }, - "bin": { - "installServerIntoExtension": "bin/installServerIntoExtension" - } - }, - "node_modules/vscode-languageserver-protocol": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", - "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", - "license": "MIT", - "dependencies": { - "vscode-jsonrpc": "8.2.0", - "vscode-languageserver-types": "3.17.5" - } - }, - "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", - "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", - "license": "MIT" - }, - "node_modules/vscode-languageserver-types": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", - "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", - "license": "MIT" - }, - "node_modules/vscode-uri": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", - "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", - "license": "MIT" - }, "node_modules/yaml": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", diff --git a/package.json b/package.json index c4f9621f..3dff3790 100644 --- a/package.json +++ b/package.json @@ -25,11 +25,11 @@ "@tauri-apps/plugin-process": "^2.3.1", "@tauri-apps/plugin-updater": "^2.10.1", "@types/dompurify": "^3.0.5", - "dompurify": "^3.3.1", + "dompurify": "3.4.12", "highlight.js": "^11.11.1", "highlightjs-svelte": "^1.0.6", "katex": "^0.16.27", - "mermaid": "^11.12.2", + "mermaid": "^11.16.0", "monaco-editor": "^0.55.1", "monaco-vim": "^0.4.4", "node-stream-zip": "^1.15.0", @@ -45,5 +45,8 @@ "tsx": "^4.22.4", "typescript": "~5.6.2", "vite": "^6.0.3" + }, + "overrides": { + "dompurify": "3.4.12" } -} \ No newline at end of file +} diff --git a/scripts/documentLoadFailure.test.ts b/scripts/documentLoadFailure.test.ts new file mode 100644 index 00000000..8fc579d0 --- /dev/null +++ b/scripts/documentLoadFailure.test.ts @@ -0,0 +1,10 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import test from 'node:test'; + +const session = readFileSync('src/lib/sessions/documentSession.svelte.ts', 'utf8'); + +test('a transient missing-file read error preserves the open tab', () => { + assert.doesNotMatch(session, /tabManager\.closeTab/); + assert.match(session, /options\.onError\('Error loading file', error\);/); +}); diff --git a/scripts/fileAssociations.test.ts b/scripts/fileAssociations.test.ts new file mode 100644 index 00000000..03ade7b9 --- /dev/null +++ b/scripts/fileAssociations.test.ts @@ -0,0 +1,13 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import test from 'node:test'; + +const config = JSON.parse(readFileSync('src-tauri/tauri.conf.json', 'utf8')) as { + bundle: { fileAssociations: Array<{ ext: string[] }> }; +}; + +test('installer advertises only Markdown file associations', () => { + const extensions = config.bundle.fileAssociations.flatMap((association) => association.ext); + assert.deepEqual(extensions, ['md', 'markdown']); + assert.equal(extensions.includes('txt'), false); +}); diff --git a/scripts/liveModeWatchedPath.test.ts b/scripts/liveModeWatchedPath.test.ts new file mode 100644 index 00000000..7f009744 --- /dev/null +++ b/scripts/liveModeWatchedPath.test.ts @@ -0,0 +1,20 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import test from 'node:test'; + +const runtime = readFileSync('src-tauri/src/window_runtime.rs', 'utf8'); +const viewer = readFileSync('src/lib/MarkdownViewer.svelte', 'utf8'); + +test('Live Mode routes a watcher notification to its watched path', () => { + assert.match(runtime, /emit_to\(event_label\.as_str\(\), "file-changed", watched_path\.clone\(\)\)/); + assert.match(viewer, /await appWindow\.listen\('file-changed', \(event\) => \{/); + assert.match(viewer, /const changedPath = event\.payload as string;/); + assert.match(viewer, /if \(!liveMode \|\| !currentFile \|\| changedPath !== currentFile\) return;/); +}); + +test('Live Mode follows the active file instead of retaining a previous tab watcher', () => { + assert.match(viewer, /if \(liveMode && currentFile\) \{\n\t\t\tinvoke\('watch_file', \{ path: currentFile \}\)/); + assert.doesNotMatch(readFileSync('src/lib/sessions/documentSession.svelte.ts', 'utf8'), /isLiveMode\(\)\) invoke\('watch_file'/); + const toggleLiveMode = viewer.slice(viewer.indexOf('function toggleLiveMode'), viewer.indexOf('async function saveImageAs')); + assert.doesNotMatch(toggleLiveMode, /loadMarkdown\(/); +}); diff --git a/scripts/macosOpenDocumentPaths.test.ts b/scripts/macosOpenDocumentPaths.test.ts new file mode 100644 index 00000000..6b373ab7 --- /dev/null +++ b/scripts/macosOpenDocumentPaths.test.ts @@ -0,0 +1,16 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import test from 'node:test'; + +const runtime = readFileSync('src-tauri/src/window_runtime.rs', 'utf8'); +const tauriLib = readFileSync('src-tauri/src/lib.rs', 'utf8'); +const viewer = readFileSync('src/lib/MarkdownViewer.svelte', 'utf8'); + +test('macOS open-document events preserve every delivered file path', () => { + assert.match(runtime, /startup_files: Mutex>/); + assert.match(tauriLib, /for url in urls/); + assert.match(tauriLib, /startup_files\.lock\(\)\.unwrap\(\)\.push\(path_str\.clone\(\)\)/); + assert.match(runtime, /startup_files\.lock\(\)\.unwrap\(\)\.drain\(\.\.\)\.collect\(\)/); + assert.match(runtime, /for path in startup_files\.into_iter\(\)\.rev\(\)/); + assert.match(viewer, /for \(const path of args\) await loadMarkdown\(path\);/); +}); diff --git a/scripts/macosPdfExport.test.ts b/scripts/macosPdfExport.test.ts new file mode 100644 index 00000000..d563937c --- /dev/null +++ b/scripts/macosPdfExport.test.ts @@ -0,0 +1,16 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import test from 'node:test'; + +test('all Markpad webviews may invoke Tauri native printing', () => { + const capability = JSON.parse(readFileSync('src-tauri/capabilities/default.json', 'utf8')) as { + windows: string[]; + permissions: string[]; + }; + + assert.deepEqual(capability.windows, ['main', 'installer', 'window-*']); + assert.ok( + capability.permissions.includes('core:webview:allow-print'), + 'macOS replaces window.print() with Tauri native printing, which requires this permission', + ); +}); diff --git a/scripts/openMultipleFiles.test.ts b/scripts/openMultipleFiles.test.ts new file mode 100644 index 00000000..3efbbd33 --- /dev/null +++ b/scripts/openMultipleFiles.test.ts @@ -0,0 +1,12 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import test from 'node:test'; + +const viewer = readFileSync('src/lib/MarkdownViewer.svelte', 'utf8'); +const selectFile = viewer.slice(viewer.indexOf('async function selectFile'), viewer.indexOf('async function reloadFromDisk')); + +test('Open File accepts multiple documents and loads each selected path', () => { + assert.match(selectFile, /multiple: true/); + assert.match(selectFile, /const paths = Array\.isArray\(selected\) \? selected : \[selected\];/); + assert.match(selectFile, /for \(const path of paths\) await loadMarkdown\(path\);/); +}); diff --git a/scripts/reloadOpenToolbar.test.ts b/scripts/reloadOpenToolbar.test.ts index b7830656..194660df 100644 --- a/scripts/reloadOpenToolbar.test.ts +++ b/scripts/reloadOpenToolbar.test.ts @@ -24,10 +24,10 @@ test('preview-level open shortcut handles Ctrl/Cmd+O outside Monaco without doub assert.match(markdownViewer, /cmdOrCtrl[\s\S]*key === 'o'[\s\S]*selectFile\(\)/); }); -test('editor toolbar delegates to Monaco actions and exposes expected Markdown commands', () => { +test('editor toolbar forwards Monaco actions and optional payloads', () => { assert.match(markdownViewer, /import EditorToolbar from '\.\/components\/EditorToolbar\.svelte'/); - assert.match(markdownViewer, / editorPane\?\.runEditorAction\(actionId\)\}/); - assert.match(editor, /export function runEditorAction\(actionId: string\)/); + assert.match(markdownViewer, / editorPane\?\.runEditorAction\(actionId, payload\)\}/); + assert.match(editor, /export function runEditorAction\(actionId: string, payload\?: any\)/); for (const actionId of [ 'fmt-bold', diff --git a/scripts/scrollSyncInput.test.ts b/scripts/scrollSyncInput.test.ts new file mode 100644 index 00000000..bbbd5373 --- /dev/null +++ b/scripts/scrollSyncInput.test.ts @@ -0,0 +1,11 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import test from 'node:test'; + +const editor = readFileSync('src/lib/components/Editor.svelte', 'utf8'); +const syncEffect = editor.slice(editor.indexOf('if (editor && onscrollsync)'), editor.indexOf('\n\t$effect(() => {', editor.indexOf('if (editor && onscrollsync)') + 1)); + +test('typing does not initiate split scroll synchronization', () => { + assert.match(syncEffect, /editor\.onDidScrollChange/); + assert.doesNotMatch(syncEffect, /onDidChangeCursorPosition/); +}); diff --git a/scripts/windowTitleCapability.test.ts b/scripts/windowTitleCapability.test.ts new file mode 100644 index 00000000..78c0a8ef --- /dev/null +++ b/scripts/windowTitleCapability.test.ts @@ -0,0 +1,14 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import test from 'node:test'; + +test('all Markpad windows may update their native title', () => { + const capability = JSON.parse(readFileSync('src-tauri/capabilities/default.json', 'utf8')) as { + permissions: string[]; + }; + + assert.ok( + capability.permissions.includes('core:window:allow-set-title'), + 'window tags and active document names call appWindow.setTitle(), which requires this permission', + ); +}); diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 955f6753..c41704e0 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -13,7 +13,9 @@ "opener:allow-open-url", "opener:allow-open-path", "dialog:default", + "core:webview:allow-print", "core:window:allow-start-dragging", + "core:window:allow-set-title", "core:window:allow-minimize", "core:window:allow-toggle-maximize", "core:window:allow-close", @@ -25,4 +27,4 @@ "updater:default", "process:default" ] -} \ No newline at end of file +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 99ce9cd8..73f16664 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -303,6 +303,11 @@ mod tests { fs::remove_dir_all(root).unwrap(); fs::remove_dir_all(outside).unwrap(); } + + #[test] + fn theme_slug_collapses_punctuation_runs() { + assert_eq!(theme_slug("SynthWave '84"), "synthwave-84"); + } } mod setup; @@ -818,6 +823,15 @@ async fn get_app_mode() -> String { } } +fn theme_slug(value: &str) -> String { + let lowercase = value.to_lowercase(); + lowercase + .split(|c: char| !c.is_alphanumeric()) + .filter(|segment| !segment.is_empty()) + .collect::>() + .join("-") +} + #[tauri::command] async fn fetch_vscode_theme(app: AppHandle, url: String) -> Result { use std::io::{Cursor, Read}; @@ -892,9 +906,7 @@ async fn fetch_vscode_theme(app: AppHandle, url: String) -> Result(); - *state.startup_file.lock().unwrap() = Some(path_str.clone()); + state.startup_files.lock().unwrap().push(path_str.clone()); if let Some(window) = pick_delivery_window(_app_handle) { let _ = _app_handle.emit_to(window.label(), "file-path", path_str); diff --git a/src-tauri/src/window_runtime.rs b/src-tauri/src/window_runtime.rs index a1a77709..41767b7d 100644 --- a/src-tauri/src/window_runtime.rs +++ b/src-tauri/src/window_runtime.rs @@ -21,7 +21,7 @@ impl WatcherState { } pub struct AppState { - pub(crate) startup_file: Mutex>, + pub(crate) startup_files: Mutex>, pub(crate) last_focused_viewer: Mutex>, window_registry: Mutex>, window_counter: AtomicU64, @@ -30,7 +30,7 @@ pub struct AppState { impl AppState { pub fn new() -> Self { Self { - startup_file: Mutex::new(None), + startup_files: Mutex::new(Vec::new()), last_focused_viewer: Mutex::new(None), window_registry: Mutex::new(HashMap::new()), window_counter: AtomicU64::new(0), @@ -288,10 +288,11 @@ pub fn watch_file( let mut watchers = state.watchers.lock().unwrap(); watchers.remove(&label); let event_label = label.clone(); + let watched_path = path.clone(); let mut watcher = RecommendedWatcher::new( move |result: Result| { if result.is_ok() { - let _ = handle.emit_to(event_label.as_str(), "file-changed", ()); + let _ = handle.emit_to(event_label.as_str(), "file-changed", watched_path.clone()); } }, Config::default(), @@ -314,7 +315,8 @@ pub fn send_markdown_path(state: State<'_, AppState>) -> Vec { .skip(1) .filter(|arg| !arg.starts_with('-')) .collect(); - if let Some(path) = state.startup_file.lock().unwrap().take() { + let startup_files: Vec = state.startup_files.lock().unwrap().drain(..).collect(); + for path in startup_files.into_iter().rev() { if !files.contains(&path) { files.insert(0, path); } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 3c9c6b8a..d416c61e 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -49,14 +49,6 @@ "name": "Markdown File", "description": "Opens Markdown files with Markpad", "role": "Editor" - }, - { - "ext": ["txt"], - "contentTypes": ["public.plain-text"], - "mimeType": "text/plain", - "name": "Text File", - "description": "Opens Text files with Markpad", - "role": "Editor" } ], "windows": { diff --git a/src/lib/MarkdownViewer.svelte b/src/lib/MarkdownViewer.svelte index d2044950..9b4c4761 100644 --- a/src/lib/MarkdownViewer.svelte +++ b/src/lib/MarkdownViewer.svelte @@ -525,7 +525,6 @@ import { createDocumentSession, type LoadMarkdownOptions } from './sessions/docu scrollFuture = []; }, renderMarkdown: renderMarkdownPreview, - isLiveMode: () => liveMode, afterLoad: tick, saveRecentFile, deleteRecentFile, @@ -620,6 +619,14 @@ import { createDocumentSession, type LoadMarkdownOptions } from './sessions/docu findOpen = false; }); + $effect(() => { + if (liveMode && currentFile) { + invoke('watch_file', { path: currentFile }).catch(console.error); + } else { + invoke('unwatch_file').catch(console.error); + } + }); + function processHighlights(root: Element) { const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, { acceptNode(node) { @@ -1841,13 +1848,15 @@ import { createDocumentSession, type LoadMarkdownOptions } from './sessions/docu async function selectFile() { const selected = await open({ - multiple: false, + multiple: true, filters: [ { name: 'Markdown', extensions: ['md', 'markdown', 'mdown', 'mkd'] }, { name: 'All Files', extensions: ['*'] }, ], }); - if (selected && typeof selected === 'string') loadMarkdown(selected); + if (!selected) return; + const paths = Array.isArray(selected) ? selected : [selected]; + for (const path of paths) await loadMarkdown(path); } async function reloadFromDisk() { @@ -1907,14 +1916,8 @@ import { createDocumentSession, type LoadMarkdownOptions } from './sessions/docu if (currentFile) await invoke('open_file_folder', { path: currentFile }); } - async function toggleLiveMode() { + function toggleLiveMode() { liveMode = !liveMode; - if (liveMode && currentFile) { - await invoke('watch_file', { path: currentFile }); - if (tabManager.activeTabId) await loadMarkdown(currentFile); - } else { - await invoke('unwatch_file'); - } } async function saveImageAs(src: string) { @@ -2638,8 +2641,9 @@ import { createDocumentSession, type LoadMarkdownOptions } from './sessions/docu }), ); unlisteners.push( - await appWindow.listen('file-changed', () => { - if (!liveMode || !currentFile) return; + await appWindow.listen('file-changed', (event) => { + const changedPath = event.payload as string; + if (!liveMode || !currentFile || changedPath !== currentFile) return; // Skip events caused by our own auto-save / save invocations, // otherwise the reload would clobber any keystrokes that landed // between fs::write and this listener firing. @@ -2943,7 +2947,7 @@ import { createDocumentSession, type LoadMarkdownOptions } from './sessions/docu try { const args: string[] = await invoke('send_markdown_path'); if (!isDisposed && args?.length > 0) { - await loadMarkdown(args[0]); + for (const path of args) await loadMarkdown(path); } } catch (error) { console.error('Error receiving Markdown file path:', error); diff --git a/src/lib/components/Editor.svelte b/src/lib/components/Editor.svelte index 8463f74f..7ecefeb5 100644 --- a/src/lib/components/Editor.svelte +++ b/src/lib/components/Editor.svelte @@ -1149,17 +1149,13 @@ onscrollsync?.(getEditorScrollSyncPosition()); }; - const d1 = editor.onDidChangeCursorPosition((e) => { - emitSync(); - }); - const d2 = editor.onDidScrollChange((e) => { + const scrollListener = editor.onDidScrollChange((e) => { if (e.scrollTopChanged) { emitSync(); } }); return () => { - d1.dispose(); - d2.dispose(); + scrollListener.dispose(); }; } }); diff --git a/src/lib/components/Settings.svelte b/src/lib/components/Settings.svelte index f1b8d7d6..15d8673e 100644 --- a/src/lib/components/Settings.svelte +++ b/src/lib/components/Settings.svelte @@ -1172,9 +1172,13 @@

{t('settings.toolbarsSettings', settings.language)}

-
-
-

{t('settings.applicationToolbar', settings.language)}

+
+ + + {t('settings.applicationToolbar', settings.language)} + +
+
{/each} +
-
- -
-
-

{t('settings.editorToolbar', settings.language)}

+ + +
+ + + {t('settings.editorToolbar', settings.language)} + +
+
{/each} +
-
+ {:else if activeCategory === 'files'}
@@ -1712,10 +1722,21 @@ display: none; } - .toolbar-section { - display: flex; - flex-direction: column; - gap: 8px; + .toolbar-settings-chevron { + width: 7px; + height: 7px; + border-right: 1.5px solid var(--color-fg-muted); + border-bottom: 1.5px solid var(--color-fg-muted); + transform: rotate(-45deg); + transition: transform 0.12s ease; + } + + .toolbar-settings[open] .toolbar-settings-chevron { + transform: rotate(45deg); + } + + .toolbar-settings-body { + padding-bottom: 12px; } .toolbar-section-header { @@ -1725,15 +1746,6 @@ padding-bottom: 2px; } - .toolbar-section-header h3 { - margin: 0; - font-size: 12px; - font-weight: 600; - color: var(--color-fg-muted); - text-transform: uppercase; - letter-spacing: 0.5px; - } - .toolbar-settings-list { display: flex; flex-direction: column; diff --git a/src/lib/sessions/documentSession.svelte.ts b/src/lib/sessions/documentSession.svelte.ts index 4817f096..18021636 100644 --- a/src/lib/sessions/documentSession.svelte.ts +++ b/src/lib/sessions/documentSession.svelte.ts @@ -16,7 +16,6 @@ type DocumentSessionOptions = { currentFile: () => string; resetScrollHistory: () => void; renderMarkdown: (raw: string, path: string) => Promise; - isLiveMode: () => boolean; afterLoad: () => Promise; saveRecentFile: (path: string) => void; deleteRecentFile: (path: string) => void; @@ -137,16 +136,14 @@ export function createDocumentSession(options: DocumentSessionOptions) { if (tab) tab.isEditing = true; tabManager.setTabRawContent(activeId, content); } - if (options.isLiveMode()) invoke('watch_file', { path: filePath }).catch(console.error); await options.afterLoad(); if (filePath) options.saveRecentFile(filePath); } catch (error) { console.error('Error loading file:', error); - const errorText = String(error); - if (errorText.includes('The system cannot find the file specified') || errorText.includes('No such file or directory')) { - options.deleteRecentFile(filePath); - if (tabManager.activeTab?.path === filePath) tabManager.closeTab(tabManager.activeTab.id); - } else options.onError('Error loading file', error); + // A watcher can observe a transient gap while another process replaces + // the file. Keep the already-open buffer and recent-file entry instead + // of closing the tab and discarding the user's recovery path. + options.onError('Error loading file', error); } }