diff --git a/package-lock.json b/package-lock.json index c2386930..44854e0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,22 +1,23 @@ { "name": "@wonderwhy-er/desktop-commander", - "version": "0.2.37", + "version": "0.2.39", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@wonderwhy-er/desktop-commander", - "version": "0.2.37", + "version": "0.2.39", "hasInstallScript": true, "license": "MIT", "dependencies": { + "@codemirror/commands": "^6.10.3", + "@codemirror/lang-markdown": "^6.5.0", + "@codemirror/state": "^6.6.0", + "@codemirror/view": "^6.41.1", + "@lezer/markdown": "^1.6.3", "@modelcontextprotocol/sdk": "^1.9.0", "@opendocsg/pdf2md": "^0.2.2", "@supabase/supabase-js": "^2.89.0", - "@tiptap/core": "^3.22.3", - "@tiptap/extension-image": "^3.22.3", - "@tiptap/pm": "^3.22.3", - "@tiptap/starter-kit": "^3.22.3", "@vscode/ripgrep": "^1.15.9", "cross-fetch": "^4.1.0", "exceljs": "^4.4.0", @@ -25,17 +26,11 @@ "glob": "^10.3.10", "highlight.js": "^11.11.1", "isbinaryfile": "^5.0.4", - "markdown-it": "^14.1.0", "md-to-pdf": "^5.2.5", "open": "^10.2.0", "pdf-lib": "^1.17.1", "pizzip": "^3.2.0", - "remark": "^15.0.1", - "remark-gfm": "^4.0.1", - "remark-parse": "^11.0.0", "sharp": "^0.34.5", - "tiptap-markdown": "^0.9.0", - "unified": "^11.0.5", "unpdf": "^1.4.0", "zod": "^3.24.1", "zod-to-json-schema": "^3.23.5" @@ -127,6 +122,136 @@ "dev": true, "license": "MIT" }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.1", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.1.tgz", + "integrity": "sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.3.tgz", + "integrity": "sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.6.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-css": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", + "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/css": "^1.1.7" + } + }, + "node_modules/@codemirror/lang-html": { + "version": "6.4.11", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz", + "integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/css": "^1.1.0", + "@lezer/html": "^1.3.12" + } + }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz", + "integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-markdown": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.5.0.tgz", + "integrity": "sha512-0K40bZ35jpHya6FriukbgaleaqzBLZfOh7HuzqbMxBXkbYMJDxfF39c23xOgxFezR+3G+tR2/Mup+Xk865OMvw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.7.1", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.3.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.2.1", + "@lezer/markdown": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.3.tgz", + "integrity": "sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.9.5", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.5.tgz", + "integrity": "sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.35.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.6.0.tgz", + "integrity": "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.41.1", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.41.1.tgz", + "integrity": "sha512-ToDnWKbBnke+ZLrP6vgTTDScGi5H37YYuZGniQaBzxMVdtCxMrslsmtnOvbPZk4RX9bvkQqnWR/WS/35tJA0qg==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.6.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -1485,6 +1610,73 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@lezer/common": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz", + "integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==", + "license": "MIT" + }, + "node_modules/@lezer/css": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.3.tgz", + "integrity": "sha512-RzBo8r+/6QJeow7aPHIpGVIH59xTcJXp399820gZoMo9noQDRVpJLheIBUicYwKcsbOYoBRoLZlf2720dG/4Tg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/html": { + "version": "1.3.13", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.13.tgz", + "integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/javascript": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz", + "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.10.tgz", + "integrity": "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/markdown": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.6.3.tgz", + "integrity": "sha512-jpGm5Ps+XErS+xA4urw7ogEGkeZOahVQF21Z6oECF0sj+2liwZopd2+I8uH5I/vZsRuuze3OxBREIANLf6KKUw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0" + } + }, "node_modules/@mapbox/node-pre-gyp": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", @@ -1559,6 +1751,12 @@ "semver": "bin/semver.js" } }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "license": "MIT" + }, "node_modules/@modelcontextprotocol/ext-apps": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.0.1.tgz", @@ -2143,12 +2341,6 @@ "node": ">=18" } }, - "node_modules/@remirror/core-constants": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz", - "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==", - "license": "MIT" - }, "node_modules/@rollup/rollup-darwin-arm64": { "version": "4.57.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", @@ -2359,563 +2551,127 @@ "node": ">=14.16" } }, - "node_modules/@tiptap/core": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.22.3.tgz", - "integrity": "sha512-Dv9MKK5BDWCF0N2l6/Pxv3JNCce2kwuWf2cKMBc2bEetx0Pn6o7zlFmSxMvYK4UtG1Tw9Yg/ZHi6QOFWK0Zm9Q==", + "node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", "license": "MIT", - "peer": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" + "dependencies": { + "debug": "^4.4.3", + "token-types": "^6.1.1" }, - "peerDependencies": { - "@tiptap/pm": "^3.22.3" - } - }, - "node_modules/@tiptap/extension-blockquote": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.22.3.tgz", - "integrity": "sha512-IaUx3zh7yLHXzIXKL+fw/jzFhsIImdhJyw0lMhe8FfYrefFqXJFYW/sey6+L/e8B3AWvTksPA6VBwefzbH77JA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" + "engines": { + "node": ">=18" }, - "peerDependencies": { - "@tiptap/core": "^3.22.3" - } - }, - "node_modules/@tiptap/extension-bold": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.22.3.tgz", - "integrity": "sha512-tysipHla2zCWr8XNIWRaW9O+7i7/SoEqnRqSRUUi2ailcJjlia+RBy3RykhkgyThrQDStu5KGBS/UvrXwA+O1A==", - "license": "MIT", "funding": { "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.22.3" + "url": "https://github.com/sponsors/Borewit" } }, - "node_modules/@tiptap/extension-bullet-list": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.22.3.tgz", - "integrity": "sha512-xOmW/b1hgECIE6r3IeZvKn4VVlG3+dfTjCWE6lnnyLaqdNkNhKS1CwUmDZdYNLUS2ryIUtgz5ID1W/8A3PhbiA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/extension-list": "^3.22.3" - } + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "license": "MIT" }, - "node_modules/@tiptap/extension-code": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.22.3.tgz", - "integrity": "sha512-wafWTDQOuMKtXpZEuk1PFQmzopabBciNLryL90MB9S03MNLaQQZYLnmYkDBlzAaLAbgF5QiC+2XZQEBQuTVjFQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.22.3" - } + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "license": "MIT" }, - "node_modules/@tiptap/extension-code-block": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.22.3.tgz", - "integrity": "sha512-RiQtEjDAPrHpdo6sw6b7fOw/PijqgFIsozKKkGcSeBgWHQuFg7q9OxJTj+l0e60rVwSu/5gmKEEobzM9bX+t2Q==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.22.3", - "@tiptap/pm": "^3.22.3" - } + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, + "license": "MIT" }, - "node_modules/@tiptap/extension-document": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.22.3.tgz", - "integrity": "sha512-MCSr1PFPtTd++lA3H1RNgqAczAE59XXJ5wUFIQf2F+/0DPY5q2SU4g5QsNJVxPPft5mrNT4C6ty8xBPrALFEdA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.22.3" - } + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" }, - "node_modules/@tiptap/extension-dropcursor": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.22.3.tgz", - "integrity": "sha512-taXq9Tl5aybdFbptJtFRHX9LFJzbXphAbPp4/vutFyTrBu5meXDxuS+B9pEmE+Or0XcolTlW2nDZB0Tqnr18JQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/extensions": "^3.22.3" - } + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" }, - "node_modules/@tiptap/extension-gapcursor": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.22.3.tgz", - "integrity": "sha512-L/Px4UeQEVG/D9WIlcAOIej+4wyIBCMUSYicSR+hW68UsObe4rxVbUas1QgidQKm6DOhoT7U7D4KQHA/Gdg/7A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/extensions": "^3.22.3" - } + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" }, - "node_modules/@tiptap/extension-hard-break": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.22.3.tgz", - "integrity": "sha512-J0v8I99y9tbvVmgKYKzKP/JYNsWaZYS7avn4rzLft2OhnyTfwt3OoY8DtpHmmi6apSUaCtoWHWta/TmoEfK1nQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.22.3" - } + "node_modules/@types/emscripten": { + "version": "1.41.5", + "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.41.5.tgz", + "integrity": "sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q==", + "dev": true, + "license": "MIT" }, - "node_modules/@tiptap/extension-heading": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.22.3.tgz", - "integrity": "sha512-XBHuhiEV2EEhZHpOLcplLqAmBIhJciU3I6AtwmqeEqDC0P114uMEfAO7JGlbBZdCYotNer26PKnu44TBTeNtkw==", + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.22.3" + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" } }, - "node_modules/@tiptap/extension-horizontal-rule": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.22.3.tgz", - "integrity": "sha512-wI2bFzScs+KgWeBH/BtypcVKeYelCyqV0RG8nxsZMWtPrBhqixzNd0Oi3gEKtjSjKUqMQ/kjJAIRuESr5UzlHA==", + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.22.3", - "@tiptap/pm": "^3.22.3" + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" } }, - "node_modules/@tiptap/extension-image": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-3.22.3.tgz", - "integrity": "sha512-Qpp8c5LOQaNpHrzjqZtoxtIR+8sSqJ7k8v+8anmYw3nxjvt2kpfT28Vd7aWMX55ZS43LaxMx+MkZqbmgUmMP0w==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.22.3" - } + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" }, - "node_modules/@tiptap/extension-italic": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.22.3.tgz", - "integrity": "sha512-LteA4cb4EGCiUtrK2JHvDF/Zg0/YqV4DUyHhAAho+oGEQDupZlsS6m0ia5wQcclkiTLzsoPrwcSNu6RDGQ16wQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.22.3" - } + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" }, - "node_modules/@tiptap/extension-link": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.22.3.tgz", - "integrity": "sha512-S8/P2o9pv6B3kqLjH2TRWwSAximGbciNc6R8/QcN6HWLYxp0N0JoqN3rZHl9VWIBAGRWc4zkt80dhqrl2xmgfQ==", + "node_modules/@types/mute-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", + "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", + "dev": true, "license": "MIT", "dependencies": { - "linkifyjs": "^4.3.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.22.3", - "@tiptap/pm": "^3.22.3" + "@types/node": "*" } }, - "node_modules/@tiptap/extension-list": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.22.3.tgz", - "integrity": "sha512-rqvv/dtqwbX+8KnPv0eMYp6PnBcuhPMol5cv1GlS8Nq/Cxt68EWGUHBuTFesw+hdnRQLmKwzoO1DlRn7PhxYRQ==", - "license": "MIT", - "peer": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.22.3", - "@tiptap/pm": "^3.22.3" - } - }, - "node_modules/@tiptap/extension-list-item": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.22.3.tgz", - "integrity": "sha512-80CNf4oO5y8+LdckT4CyMe1t01EyhpRrQC9H45JW20P7559Nrchp5my3vvMtIAJbpTPPZtcB7LwdzWGKsG5drg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/extension-list": "^3.22.3" - } - }, - "node_modules/@tiptap/extension-list-keymap": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-3.22.3.tgz", - "integrity": "sha512-pKuyj5llu35zd/s2u/H9aydKZjmPRAIK5P1q/YXULhhCNln2RnmuRfQ5NklAqTD3yGciQ2lxDwwf7J6iw3ergA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/extension-list": "^3.22.3" - } - }, - "node_modules/@tiptap/extension-ordered-list": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.22.3.tgz", - "integrity": "sha512-orAghtmd+K4Euu4BgI1hG+iZDXBYOyl5YTwiLBc2mQn+pqtZ9LqaH2us4ETwEwNP3/IWXGSAimUZ19nuL+eM2w==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/extension-list": "^3.22.3" - } - }, - "node_modules/@tiptap/extension-paragraph": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.22.3.tgz", - "integrity": "sha512-oO7rhfyhEuwm+50s9K3GZPjYyEEEvFAvm1wXopvZnhbkBLydIWImBfrZoC5IQh4/sRDlTIjosV2C+ji5y0tUSg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.22.3" - } - }, - "node_modules/@tiptap/extension-strike": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.22.3.tgz", - "integrity": "sha512-jY2InoUlKkuk5KHoIDGdML1OCA2n6PRHAtxwHNkAmiYh0Khf0zaVPGFpx4dgQrN7W5Q1WE6oBZnjrvy6qb7w0g==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.22.3" - } - }, - "node_modules/@tiptap/extension-text": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.22.3.tgz", - "integrity": "sha512-Q9R7JsTdomP5uUjtPjNKxHT1xoh/i9OJZnmgJLe7FcgZEaPOQ3bWxmKZoLZQfDfZjyB8BtH+Hc7nUvhCMOePxw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.22.3" - } - }, - "node_modules/@tiptap/extension-underline": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.22.3.tgz", - "integrity": "sha512-Ch6CBWRa5w90yYSPUW6x9Py9JdrXMqk3pZ9OIlMYD8A7BqyZGfiHerX7XDMYDS09KjyK3U9XH60/zxYOzXdDLA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.22.3" - } - }, - "node_modules/@tiptap/extensions": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.22.3.tgz", - "integrity": "sha512-s5eiMq0m5N6N+W7dU6rd60KgZyyCD7FvtPNNswISfPr12EQwJBfbjWwTqd0UKNzA4fNrhQEERXnzORkykttPeA==", - "license": "MIT", - "peer": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.22.3", - "@tiptap/pm": "^3.22.3" - } - }, - "node_modules/@tiptap/pm": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.22.3.tgz", - "integrity": "sha512-NjfWjZuvrqmpICT+GZWNIjtOdhPyqFKDMtQy7tsQ5rErM9L2ZQdy/+T/BKSO1JdTeBhdg9OP+0yfsqoYp2aT6A==", - "license": "MIT", - "peer": true, - "dependencies": { - "prosemirror-changeset": "^2.3.0", - "prosemirror-collab": "^1.3.1", - "prosemirror-commands": "^1.6.2", - "prosemirror-dropcursor": "^1.8.1", - "prosemirror-gapcursor": "^1.3.2", - "prosemirror-history": "^1.4.1", - "prosemirror-inputrules": "^1.4.0", - "prosemirror-keymap": "^1.2.2", - "prosemirror-markdown": "^1.13.1", - "prosemirror-menu": "^1.2.4", - "prosemirror-model": "^1.24.1", - "prosemirror-schema-basic": "^1.2.3", - "prosemirror-schema-list": "^1.5.0", - "prosemirror-state": "^1.4.3", - "prosemirror-tables": "^1.6.4", - "prosemirror-trailing-node": "^3.0.0", - "prosemirror-transform": "^1.10.2", - "prosemirror-view": "^1.38.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - } - }, - "node_modules/@tiptap/starter-kit": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-3.22.3.tgz", - "integrity": "sha512-vdW/Oo1fdwTL1VOQ5YYbTov00ANeHLquBVEZyL/EkV7Xv5io9rXQsCysJfTSHhiQlyr2MtWFB4+CPGuwXjQWOQ==", - "license": "MIT", - "dependencies": { - "@tiptap/core": "^3.22.3", - "@tiptap/extension-blockquote": "^3.22.3", - "@tiptap/extension-bold": "^3.22.3", - "@tiptap/extension-bullet-list": "^3.22.3", - "@tiptap/extension-code": "^3.22.3", - "@tiptap/extension-code-block": "^3.22.3", - "@tiptap/extension-document": "^3.22.3", - "@tiptap/extension-dropcursor": "^3.22.3", - "@tiptap/extension-gapcursor": "^3.22.3", - "@tiptap/extension-hard-break": "^3.22.3", - "@tiptap/extension-heading": "^3.22.3", - "@tiptap/extension-horizontal-rule": "^3.22.3", - "@tiptap/extension-italic": "^3.22.3", - "@tiptap/extension-link": "^3.22.3", - "@tiptap/extension-list": "^3.22.3", - "@tiptap/extension-list-item": "^3.22.3", - "@tiptap/extension-list-keymap": "^3.22.3", - "@tiptap/extension-ordered-list": "^3.22.3", - "@tiptap/extension-paragraph": "^3.22.3", - "@tiptap/extension-strike": "^3.22.3", - "@tiptap/extension-text": "^3.22.3", - "@tiptap/extension-underline": "^3.22.3", - "@tiptap/extensions": "^3.22.3", - "@tiptap/pm": "^3.22.3" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - } - }, - "node_modules/@tokenizer/inflate": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", - "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "token-types": "^6.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@tokenizer/token": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", - "license": "MIT" - }, - "node_modules/@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "license": "MIT" - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", - "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, - "node_modules/@types/emscripten": { - "version": "1.41.5", - "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.41.5.tgz", - "integrity": "sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/http-cache-semantics": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", - "license": "MIT" - }, - "node_modules/@types/markdown-it": { - "version": "14.1.2", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", - "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", - "license": "MIT", - "dependencies": { - "@types/linkify-it": "^5", - "@types/mdurl": "^2" - } - }, - "node_modules/@types/mdast": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", - "license": "MIT" - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "license": "MIT" - }, - "node_modules/@types/mute-stream": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", - "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/node": { - "version": "20.19.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz", - "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", + "node_modules/@types/node": { + "version": "20.19.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz", + "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", "license": "MIT", "peer": true, "dependencies": { @@ -2952,12 +2708,6 @@ "source-map": "^0.6.1" } }, - "node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "license": "MIT" - }, "node_modules/@types/webpack": { "version": "4.41.40", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.40.tgz", @@ -3707,16 +3457,6 @@ } } }, - "node_modules/bail": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", - "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -4294,16 +4034,6 @@ "node": ">=4" } }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/chainsaw": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", @@ -4332,16 +4062,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/character-entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", @@ -4888,19 +4608,6 @@ } } }, - "node_modules/decode-named-character-reference": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", - "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", - "license": "MIT", - "dependencies": { - "character-entities": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/decode-uri-component": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", @@ -5294,15 +5001,6 @@ "node": ">= 0.8" } }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -5322,19 +5020,6 @@ "node": ">=8" } }, - "node_modules/devlop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", - "license": "MIT", - "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/devtools-protocol": { "version": "0.0.1534754", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1534754.tgz", @@ -5724,18 +5409,6 @@ "node": ">=10.13.0" } }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/enumify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/enumify/-/enumify-1.0.4.tgz", @@ -6137,12 +5810,6 @@ "node": ">=4" } }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT" - }, "node_modules/extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", @@ -7960,21 +7627,6 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, - "node_modules/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", - "license": "MIT", - "dependencies": { - "uc.micro": "^2.0.0" - } - }, - "node_modules/linkifyjs": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.2.tgz", - "integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==", - "license": "MIT" - }, "node_modules/listenercount": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", @@ -8436,16 +8088,6 @@ "node": ">=4" } }, - "node_modules/longest-streak": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", - "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/lowercase-keys": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", @@ -8496,45 +8138,6 @@ "dev": true, "license": "ISC" }, - "node_modules/markdown-it": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", - "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1", - "entities": "^4.4.0", - "linkify-it": "^5.0.0", - "mdurl": "^2.0.0", - "punycode.js": "^2.3.1", - "uc.micro": "^2.1.0" - }, - "bin": { - "markdown-it": "bin/markdown-it.mjs" - } - }, - "node_modules/markdown-it-task-lists": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/markdown-it-task-lists/-/markdown-it-task-lists-2.1.1.tgz", - "integrity": "sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==", - "license": "ISC" - }, - "node_modules/markdown-it/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/markdown-table": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", - "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/marked": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", @@ -8596,213 +8199,6 @@ "node": ">=0.10.0" } }, - "node_modules/mdast-util-find-and-replace": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", - "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "escape-string-regexp": "^5.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mdast-util-from-markdown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", - "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark": "^4.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", - "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", - "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-gfm-autolink-literal": "^2.0.0", - "mdast-util-gfm-footnote": "^2.0.0", - "mdast-util-gfm-strikethrough": "^2.0.0", - "mdast-util-gfm-table": "^2.0.0", - "mdast-util-gfm-task-list-item": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-autolink-literal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", - "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "ccount": "^2.0.0", - "devlop": "^1.0.0", - "mdast-util-find-and-replace": "^3.0.0", - "micromark-util-character": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-strikethrough": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", - "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", - "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "markdown-table": "^3.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-task-list-item": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", - "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-phrasing": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", - "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-markdown": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", - "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^4.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "unist-util-visit": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", - "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", - "license": "MIT" - }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -8845,580 +8241,17 @@ "dev": true, "license": "ISC", "engines": { - "node": ">=10.4.0" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromark": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", - "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", - "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-destination": "^2.0.0", - "micromark-factory-label": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-title": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-html-tag-name": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", - "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", - "license": "MIT", - "dependencies": { - "micromark-extension-gfm-autolink-literal": "^2.0.0", - "micromark-extension-gfm-footnote": "^2.0.0", - "micromark-extension-gfm-strikethrough": "^2.0.0", - "micromark-extension-gfm-table": "^2.0.0", - "micromark-extension-gfm-tagfilter": "^2.0.0", - "micromark-extension-gfm-task-list-item": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", - "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-strikethrough": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", - "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-table": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", - "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", - "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-task-list-item": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", - "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-factory-destination": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", - "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-label": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", - "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-title": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", - "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-whitespace": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", - "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-chunked": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", - "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-classify-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", - "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-combine-extensions": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", - "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-chunked": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", - "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-string": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", - "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-encode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-html-tag-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", - "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-normalize-identifier": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", - "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-resolve-all": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", - "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-subtokenize": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", - "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" + "node": ">=10.4.0" + } }, - "node_modules/micromark-util-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } }, "node_modules/micromatch": { "version": "4.0.8", @@ -10154,12 +8987,6 @@ "node": ">=4" } }, - "node_modules/orderedmap": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", - "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", - "license": "MIT" - }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -10590,216 +9417,6 @@ "node": ">=0.4.0" } }, - "node_modules/prosemirror-changeset": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.1.tgz", - "integrity": "sha512-96WBLhOaYhJ+kPhLg3uW359Tz6I/MfcrQfL4EGv4SrcqKEMC1gmoGrXHecPE8eOwTVCJ4IwgfzM8fFad25wNfw==", - "license": "MIT", - "dependencies": { - "prosemirror-transform": "^1.0.0" - } - }, - "node_modules/prosemirror-collab": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz", - "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==", - "license": "MIT", - "dependencies": { - "prosemirror-state": "^1.0.0" - } - }, - "node_modules/prosemirror-commands": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", - "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==", - "license": "MIT", - "dependencies": { - "prosemirror-model": "^1.0.0", - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.10.2" - } - }, - "node_modules/prosemirror-dropcursor": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz", - "integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==", - "license": "MIT", - "dependencies": { - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.1.0", - "prosemirror-view": "^1.1.0" - } - }, - "node_modules/prosemirror-gapcursor": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.1.tgz", - "integrity": "sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==", - "license": "MIT", - "dependencies": { - "prosemirror-keymap": "^1.0.0", - "prosemirror-model": "^1.0.0", - "prosemirror-state": "^1.0.0", - "prosemirror-view": "^1.0.0" - } - }, - "node_modules/prosemirror-history": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz", - "integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==", - "license": "MIT", - "dependencies": { - "prosemirror-state": "^1.2.2", - "prosemirror-transform": "^1.0.0", - "prosemirror-view": "^1.31.0", - "rope-sequence": "^1.3.0" - } - }, - "node_modules/prosemirror-inputrules": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz", - "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==", - "license": "MIT", - "dependencies": { - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.0.0" - } - }, - "node_modules/prosemirror-keymap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", - "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==", - "license": "MIT", - "dependencies": { - "prosemirror-state": "^1.0.0", - "w3c-keyname": "^2.2.0" - } - }, - "node_modules/prosemirror-markdown": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.4.tgz", - "integrity": "sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==", - "license": "MIT", - "dependencies": { - "@types/markdown-it": "^14.0.0", - "markdown-it": "^14.0.0", - "prosemirror-model": "^1.25.0" - } - }, - "node_modules/prosemirror-menu": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.3.1.tgz", - "integrity": "sha512-2OSIKBFyLo2iqDpjQHEC7tKt3lluhY7L44pcRai8EpoU9R7cZDj/dklEsOOIubNKWUXab6dL7y4JtAWnrlR4lA==", - "license": "MIT", - "dependencies": { - "crelt": "^1.0.0", - "prosemirror-commands": "^1.0.0", - "prosemirror-history": "^1.0.0", - "prosemirror-state": "^1.0.0" - } - }, - "node_modules/prosemirror-model": { - "version": "1.25.4", - "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz", - "integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==", - "license": "MIT", - "peer": true, - "dependencies": { - "orderedmap": "^2.0.0" - } - }, - "node_modules/prosemirror-schema-basic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz", - "integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==", - "license": "MIT", - "dependencies": { - "prosemirror-model": "^1.25.0" - } - }, - "node_modules/prosemirror-schema-list": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz", - "integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==", - "license": "MIT", - "dependencies": { - "prosemirror-model": "^1.0.0", - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.7.3" - } - }, - "node_modules/prosemirror-state": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", - "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", - "license": "MIT", - "peer": true, - "dependencies": { - "prosemirror-model": "^1.0.0", - "prosemirror-transform": "^1.0.0", - "prosemirror-view": "^1.27.0" - } - }, - "node_modules/prosemirror-tables": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz", - "integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==", - "license": "MIT", - "dependencies": { - "prosemirror-keymap": "^1.2.3", - "prosemirror-model": "^1.25.4", - "prosemirror-state": "^1.4.4", - "prosemirror-transform": "^1.10.5", - "prosemirror-view": "^1.41.4" - } - }, - "node_modules/prosemirror-trailing-node": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz", - "integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==", - "license": "MIT", - "dependencies": { - "@remirror/core-constants": "3.0.0", - "escape-string-regexp": "^4.0.0" - }, - "peerDependencies": { - "prosemirror-model": "^1.22.1", - "prosemirror-state": "^1.4.2", - "prosemirror-view": "^1.33.8" - } - }, - "node_modules/prosemirror-trailing-node/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/prosemirror-transform": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.12.0.tgz", - "integrity": "sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==", - "license": "MIT", - "dependencies": { - "prosemirror-model": "^1.21.0" - } - }, - "node_modules/prosemirror-view": { - "version": "1.41.8", - "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.8.tgz", - "integrity": "sha512-TnKDdohEatgyZNGCDWIdccOHXhYloJwbwU+phw/a23KBvJIR9lWQWW7WHHK3vBdOLDNuF7TaX98GObUZOWkOnA==", - "license": "MIT", - "peer": true, - "dependencies": { - "prosemirror-model": "^1.20.0", - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.1.0" - } - }, "node_modules/proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -10878,15 +9495,6 @@ "once": "^1.3.1" } }, - "node_modules/punycode.js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", - "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/puppeteer": { "version": "24.34.0", "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.34.0.tgz", @@ -11083,71 +9691,6 @@ "node": ">= 0.10" } }, - "node_modules/remark": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/remark/-/remark-15.0.1.tgz", - "integrity": "sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "remark-parse": "^11.0.0", - "remark-stringify": "^11.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-gfm": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", - "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-gfm": "^3.0.0", - "micromark-extension-gfm": "^3.0.0", - "remark-parse": "^11.0.0", - "remark-stringify": "^11.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-parse": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", - "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-stringify": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", - "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-to-markdown": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -11357,12 +9900,6 @@ "node": "*" } }, - "node_modules/rope-sequence": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", - "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==", - "license": "MIT" - }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -12436,6 +10973,12 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -12640,46 +11183,6 @@ "node": ">=0.10.0" } }, - "node_modules/tiptap-markdown": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/tiptap-markdown/-/tiptap-markdown-0.9.0.tgz", - "integrity": "sha512-dKLQ9iiuGNgrlGVjrNauF/UBzWu4LYOx5pkD0jNkmQt/GOwfCJsBuzZTsf1jZ204ANHOm572mZ9PYvGh1S7tpQ==", - "license": "MIT", - "workspaces": [ - "example" - ], - "dependencies": { - "@types/markdown-it": "^13.0.7", - "markdown-it": "^14.1.0", - "markdown-it-task-lists": "^2.1.1", - "prosemirror-markdown": "^1.11.1" - }, - "peerDependencies": { - "@tiptap/core": "^3.0.1" - } - }, - "node_modules/tiptap-markdown/node_modules/@types/linkify-it": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz", - "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==", - "license": "MIT" - }, - "node_modules/tiptap-markdown/node_modules/@types/markdown-it": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-13.0.9.tgz", - "integrity": "sha512-1XPwR0+MgXLWfTn9gCsZ55AHOKW1WN+P9vr0PaQh5aerR9LLQXUbjfEAFhjmEmyoYFWAyuN2Mqkn40MZ4ukjBw==", - "license": "MIT", - "dependencies": { - "@types/linkify-it": "^3", - "@types/mdurl": "^1" - } - }, - "node_modules/tiptap-markdown/node_modules/@types/mdurl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz", - "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==", - "license": "MIT" - }, "node_modules/tmp": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", @@ -12788,16 +11291,6 @@ "node": ">=0.10.0" } }, - "node_modules/trough": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", - "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/ts-loader": { "version": "9.5.4", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.4.tgz", @@ -12981,12 +11474,6 @@ "node": ">=14.17" } }, - "node_modules/uc.micro": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", - "license": "MIT" - }, "node_modules/uint8array-extras": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", @@ -13023,92 +11510,6 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, - "node_modules/unified": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", - "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "bail": "^2.0.0", - "devlop": "^1.0.0", - "extend": "^3.0.0", - "is-plain-obj": "^4.0.0", - "trough": "^2.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unified/node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/unist-util-is": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", - "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", - "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", - "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -13284,34 +11685,6 @@ "node": ">= 0.8" } }, - "node_modules/vfile": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-message": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", - "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/w3c-keyname": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", @@ -13936,16 +12309,6 @@ "peerDependencies": { "zod": "^3.25 || ^4" } - }, - "node_modules/zwitch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } } } } diff --git a/package.json b/package.json index aa784e93..e4e8bd73 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "bump": "node scripts/sync-version.js --bump", "bump:minor": "node scripts/sync-version.js --bump --minor", "bump:major": "node scripts/sync-version.js --bump --major", + "prebuild": "npm run clean", "build": "tsc && shx cp setup-claude-server.js uninstall-claude-server.js track-installation.js dist/ && shx chmod +x dist/*.js && shx mkdir -p dist/data && shx cp src/data/onboarding-prompts.json dist/data/ && shx mkdir -p dist/remote-device/scripts && shx cp src/remote-device/scripts/blocking-offline-update.js dist/remote-device/scripts/ && node scripts/build-ui-runtime.cjs", "watch": "tsc --watch", "start": "node dist/index.js", @@ -84,13 +85,14 @@ "file-operations" ], "dependencies": { + "@codemirror/commands": "^6.10.3", + "@codemirror/lang-markdown": "^6.5.0", + "@codemirror/state": "^6.6.0", + "@codemirror/view": "^6.41.1", + "@lezer/markdown": "^1.6.3", "@modelcontextprotocol/sdk": "^1.9.0", "@opendocsg/pdf2md": "^0.2.2", "@supabase/supabase-js": "^2.89.0", - "@tiptap/core": "^3.22.3", - "@tiptap/extension-image": "^3.22.3", - "@tiptap/pm": "^3.22.3", - "@tiptap/starter-kit": "^3.22.3", "@vscode/ripgrep": "^1.15.9", "cross-fetch": "^4.1.0", "exceljs": "^4.4.0", @@ -99,17 +101,11 @@ "glob": "^10.3.10", "highlight.js": "^11.11.1", "isbinaryfile": "^5.0.4", - "markdown-it": "^14.1.0", "md-to-pdf": "^5.2.5", "open": "^10.2.0", "pdf-lib": "^1.17.1", "pizzip": "^3.2.0", - "remark": "^15.0.1", - "remark-gfm": "^4.0.1", - "remark-parse": "^11.0.0", "sharp": "^0.34.5", - "tiptap-markdown": "^0.9.0", - "unified": "^11.0.5", "unpdf": "^1.4.0", "zod": "^3.24.1", "zod-to-json-schema": "^3.23.5" diff --git a/src/ui/file-preview/src/components/markdown-renderer.ts b/src/ui/file-preview/src/components/markdown-renderer.ts deleted file mode 100644 index 5f7d0aa0..00000000 --- a/src/ui/file-preview/src/components/markdown-renderer.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Markdown rendering pipeline for preview mode. It configures markdown-it and highlighting so markdown content is rendered consistently with code block support. - */ -import { highlightSource } from './highlighting.js'; -import { createMarkdownIt, prepareMarkdownSource, readHeadingProjection, type MarkdownToken } from '../markdown/parser.js'; -import { createSlugTracker } from '../markdown/slugify.js'; - -const markdown = createMarkdownIt({ - highlight(code: string, language: string): string { - const normalizedLanguage = (language || 'text').toLowerCase(); - const highlighted = highlightSource(code, normalizedLanguage); - return `
${highlighted}
`; - } -}); - -const renderHeadingOpen = markdown.renderer.rules.heading_open; -markdown.renderer.rules.heading_open = (...args: unknown[]): string => { - const tokens = args[0] as MarkdownToken[]; - const index = args[1] as number; - const options = args[2] as unknown; - const environment = (args[3] as Record | undefined) ?? {}; - const self = args[4] as { renderToken: (tokens: Array>, index: number, options: unknown) => string }; - const nextSlug = typeof environment.nextSlug === 'function' - ? environment.nextSlug as (text: string) => string - : createSlugTracker(); - environment.nextSlug = nextSlug; - - const heading = readHeadingProjection(tokens, index, nextSlug); - const token = tokens[index] as { attrSet?: (name: string, value: string) => void }; - if (heading) { - token.attrSet?.('id', heading.id); - token.attrSet?.('data-heading-id', heading.id); - } - - if (typeof renderHeadingOpen === 'function') { - return renderHeadingOpen(...args); - } - - return self.renderToken(tokens as Array>, index, options); -}; - -const renderLinkOpen = markdown.renderer.rules.link_open; -markdown.renderer.rules.link_open = (...args: unknown[]): string => { - const tokens = args[0] as Array>; - const index = args[1] as number; - const options = args[2] as unknown; - const self = args[4] as { renderToken: (tokens: Array>, index: number, options: unknown) => string }; - const token = tokens[index] as { attrSet?: (name: string, value: string) => void; attrGet?: (name: string) => string | null; attrs?: Array<[string, string]> }; - token.attrSet?.('data-markdown-link', 'true'); - const title = token.attrGet?.('title'); - if (title?.startsWith('mcp-wiki:')) { - const rawWikiLink = decodeURIComponent(title.slice('mcp-wiki:'.length)); - token.attrSet?.('data-wiki-link', rawWikiLink); - if (Array.isArray(token.attrs)) { - token.attrs = token.attrs.filter(([name]) => name !== 'title'); - } - } - - if (typeof renderLinkOpen === 'function') { - return renderLinkOpen(...args); - } - - return self.renderToken(tokens, index, options); -}; - -export function renderMarkdown(content: string): string { - return markdown.render(prepareMarkdownSource(content), { nextSlug: createSlugTracker() }); -} diff --git a/src/ui/file-preview/src/markdown/conflict-dialog.ts b/src/ui/file-preview/src/markdown/conflict-dialog.ts index b63e9389..4223e72d 100644 --- a/src/ui/file-preview/src/markdown/conflict-dialog.ts +++ b/src/ui/file-preview/src/markdown/conflict-dialog.ts @@ -2,7 +2,7 @@ * The "file changed on disk" conflict resolver. * * Shown when saveDocument detected that disk differs from what the editor - * thought it had. The editor has already re-synced its sourceContent to the + * thought it had. The editor has already re-synced its disk baseline to the * fresh disk content with keepDraft: true — so the dialog's two actions map * onto concrete state transitions: * diff --git a/src/ui/file-preview/src/markdown/controller.ts b/src/ui/file-preview/src/markdown/controller.ts index abafa0eb..016f37f7 100644 --- a/src/ui/file-preview/src/markdown/controller.ts +++ b/src/ui/file-preview/src/markdown/controller.ts @@ -1,15 +1,13 @@ import { attachDocumentOutline, renderDocumentOutline, type DocumentOutlineHandle } from '../document-outline.js'; import { getDocumentFullscreenAvailability, parseReadRange, shouldAutoLoadDocumentOnEnterFullscreen, stripReadStatusLine } from '../document-workspace.js'; import type { MarkdownWorkspaceState, RenderBodyResult, RenderPayload } from '../model.js'; -import { assertSuccessfulEditBlockResult, extractRenderPayload, extractToolText } from '../payload-utils.js'; +import { assertSuccessfulEditBlockResult, extractRenderPayload, extractToolText, getFileExtensionForAnalytics } from '../payload-utils.js'; import { getAncestorDirectories, getParentDirectory, toPosixRelativePath } from '../path-utils.js'; import { mountMarkdownEditor, renderMarkdownEditorShell, type MarkdownEditorHandle, type MarkdownEditorView, type MarkdownLinkHeading, type MarkdownLinkSearchItem } from './editor.js'; import type { OpenConflictDialogOptions } from './conflict-dialog.js'; import { resolveMarkdownLink } from './linking.js'; import { extractMarkdownOutline } from './outline.js'; -import { getRenderedMarkdownCopyText } from './preview.js'; import { slugifyMarkdownHeading } from './slugify.js'; -import { getFileExtensionForAnalytics } from '../payload-utils.js'; export interface MarkdownControllerDependencies { callTool?: (name: string, args: Record) => Promise; @@ -152,10 +150,6 @@ function computeEditBlocks(oldText: string, newText: string): Array<{ old_string const context = 3; const merged = mergeCloseHunks(hunks, context * 2 + 1); - const totalChanged = merged.reduce((sum, hunk) => sum + (hunk.oldEnd - hunk.oldStart), 0); - if (totalChanged > oldLines.length * 0.7) { - return [{ old_string: oldText, new_string: newText }]; - } return merged.map((hunk) => { const contextBefore = Math.max(0, hunk.oldStart - context); @@ -172,6 +166,16 @@ function computeEditBlocks(oldText: string, newText: string): Array<{ old_string }).filter((block) => block.old_string !== block.new_string); } +function joinDocumentChunks(first: string, second: string): string { + if (!first) { + return second; + } + if (!second) { + return first; + } + return `${first}${first.endsWith('\n') ? '' : '\n'}${second}`; +} + function isToolErrorResult(value: unknown): value is ToolErrorResult { return typeof value === 'object' && value !== null; } @@ -192,8 +196,10 @@ export function createMarkdownController(dependencies: MarkdownControllerDepende let markdownEditorHandle: MarkdownEditorHandle | undefined; let markdownTocHandle: DocumentOutlineHandle | undefined; let autosaveTimer: ReturnType | null = null; + let outlineRefreshTimer: ReturnType | null = null; const AUTOSAVE_DEBOUNCE_MS = 1000; + const OUTLINE_REFRESH_DEBOUNCE_MS = 200; function scheduleAutosave(): void { if (autosaveTimer !== null) { @@ -212,8 +218,37 @@ export function createMarkdownController(dependencies: MarkdownControllerDepende } } + function cancelOutlineRefresh(): void { + if (outlineRefreshTimer !== null) { + clearTimeout(outlineRefreshTimer); + outlineRefreshTimer = null; + } + } + + function applyOutlineUpdate(state: MarkdownWorkspaceState, nextOutline: MarkdownWorkspaceState['outline']): void { + if (areOutlineItemsEqual(state.outline, nextOutline)) { + return; + } + state.outline = nextOutline; + if (!state.outline.some((item) => item.id === state.activeHeadingId)) { + state.activeHeadingId = state.outline[0]?.id ?? null; + } + markdownTocHandle?.refresh(state.outline, state.activeHeadingId); + } + + function scheduleOutlineRefresh(state: MarkdownWorkspaceState): void { + if (outlineRefreshTimer !== null) { + clearTimeout(outlineRefreshTimer); + } + outlineRefreshTimer = setTimeout(() => { + outlineRefreshTimer = null; + applyOutlineUpdate(state, extractMarkdownOutline(state.draftContent)); + }, OUTLINE_REFRESH_DEBOUNCE_MS); + } + function disposeHandles(): void { cancelAutosave(); + cancelOutlineRefresh(); markdownEditorHandle?.destroy(); markdownEditorHandle = undefined; markdownTocHandle?.dispose(); @@ -235,7 +270,6 @@ export function createMarkdownController(dependencies: MarkdownControllerDepende options: { keepDraft?: boolean } = {} ): void { const nextDraftContent = options.keepDraft ? state.draftContent : content; - state.sourceContent = content; state.fullDocumentContent = content; state.draftContent = nextDraftContent; state.outline = extractMarkdownOutline(content); @@ -279,15 +313,13 @@ export function createMarkdownController(dependencies: MarkdownControllerDepende function getState(payload: RenderPayload): MarkdownWorkspaceState { const cleanedContent = stripReadStatusLine(payload.content); - if (!workspaceState || workspaceState.filePath !== payload.filePath || workspaceState.sourceContent !== cleanedContent) { + if (!workspaceState || workspaceState.filePath !== payload.filePath || workspaceState.fullDocumentContent !== cleanedContent) { const outline = extractMarkdownOutline(cleanedContent); workspaceState = { filePath: payload.filePath, - sourceContent: cleanedContent, fullDocumentContent: cleanedContent, draftContent: cleanedContent, outline, - mode: 'edit', dirty: false, activeHeadingId: outline[0]?.id ?? null, pendingAnchor: null, @@ -321,6 +353,13 @@ export function createMarkdownController(dependencies: MarkdownControllerDepende const notice = [state.error, state.notice] .find((value): value is string => typeof value === 'string' && value.trim().length > 0); + // While loading the full document, suppress the editor entirely so the + // user never sees the partial source briefly (especially in raw mode, + // where it would otherwise read as the content actually being edited). + const mainContent = state.loadingDocument + ? '
Loading full document…
' + : renderMarkdownEditorShell({ view: state.editorView }); + return { notice, html: ` @@ -328,7 +367,7 @@ export function createMarkdownController(dependencies: MarkdownControllerDepende
${tocHtml}
- ${renderMarkdownEditorShell({ view: state.editorView })} + ${mainContent}
@@ -404,7 +443,17 @@ export function createMarkdownController(dependencies: MarkdownControllerDepende return extractMarkdownOutline(readPayloadContent(payload)).map((item) => ({ id: item.id, text: item.text })); } - function findHeading(anchor: string): HTMLElement | null { + function resolveOutlineHeading(anchor: string): MarkdownWorkspaceState['outline'][number] | null { + const trimmedAnchor = anchor.trim(); + if (!workspaceState || !trimmedAnchor) { + return null; + } + + const slug = slugifyMarkdownHeading(trimmedAnchor); + return workspaceState.outline.find((item) => item.id === trimmedAnchor || item.id === slug) ?? null; + } + + function findDomHeading(anchor: string): HTMLElement | null { const trimmedAnchor = anchor.trim(); if (!trimmedAnchor) { return null; @@ -414,7 +463,16 @@ export function createMarkdownController(dependencies: MarkdownControllerDepende } function scrollHeadingIntoView(anchor: string): boolean { - const heading = findHeading(anchor); + const outlineHeading = resolveOutlineHeading(anchor); + if (outlineHeading && typeof outlineHeading.line === 'number') { + markdownEditorHandle?.revealLine(outlineHeading.line, outlineHeading.id); + if (workspaceState) { + workspaceState.activeHeadingId = outlineHeading.id; + } + return true; + } + + const heading = findDomHeading(anchor); if (!heading) { return false; } @@ -522,11 +580,10 @@ export function createMarkdownController(dependencies: MarkdownControllerDepende const range = parseReadRange(payload.content); if (!range?.isPartial) { if (options.keepEditMode) { - state.mode = 'edit'; state.editorView = 'markdown'; state.notice = null; state.error = null; - state.draftContent = state.sourceContent; + state.draftContent = state.fullDocumentContent; state.dirty = false; dependencies.rerender(); } @@ -553,9 +610,8 @@ export function createMarkdownController(dependencies: MarkdownControllerDepende nextState.loadingDocument = false; nextState.notice = null; nextState.error = null; - syncStateFromContent(nextState, nextState.sourceContent); + syncStateFromContent(nextState, nextState.fullDocumentContent); if (options.keepEditMode) { - nextState.mode = 'edit'; nextState.editorView = 'markdown'; dependencies.rerender(); } @@ -631,7 +687,6 @@ export function createMarkdownController(dependencies: MarkdownControllerDepende return; } - state.mode = 'edit'; state.draftContent = state.fullDocumentContent; state.dirty = false; state.editorView = 'markdown'; @@ -678,7 +733,8 @@ export function createMarkdownController(dependencies: MarkdownControllerDepende state.notice = null; try { - const blocks = computeEditBlocks(state.fullDocumentContent, state.draftContent); + const savedContent = state.draftContent; + const blocks = computeEditBlocks(state.fullDocumentContent, savedContent); if (blocks.length === 0) { state.saving = false; state.saveIndicator = 'idle'; @@ -728,17 +784,18 @@ export function createMarkdownController(dependencies: MarkdownControllerDepende throw err; } - state.fullDocumentContent = state.draftContent; - state.sourceContent = state.draftContent; - state.outline = extractMarkdownOutline(state.sourceContent); - state.dirty = false; + const hasNewUnsavedDraft = state.draftContent !== savedContent; + state.fullDocumentContent = savedContent; + if (!hasNewUnsavedDraft) { + state.outline = extractMarkdownOutline(state.fullDocumentContent); + } + state.dirty = hasNewUnsavedDraft; state.saving = false; - state.saveIndicator = 'saved'; + state.saveIndicator = hasNewUnsavedDraft ? 'idle' : 'saved'; if (!state.outline.some((item) => item.id === state.activeHeadingId)) { state.activeHeadingId = state.outline[0]?.id ?? null; } - const savedContent = state.draftContent; const currentPayload = dependencies.getCurrentPayload(); if (currentPayload) { const statusLineMatch = currentPayload.content.match(/^(\[Reading [^\]]+\]\r?\n(?:\r?\n)?)/); @@ -750,13 +807,17 @@ export function createMarkdownController(dependencies: MarkdownControllerDepende if (revert) { revert.disabled = !isUndoAvailable(state); } - flashSaveStatus('Saved', 'saved', 1800, () => { - if (!state.dirty && !state.saving) { - state.saveIndicator = 'idle'; - return true; - } - return false; - }); + if (hasNewUnsavedDraft) { + scheduleAutosave(); + } else { + flashSaveStatus('Saved', 'saved', 1800, () => { + if (!state.dirty && !state.saving) { + state.saveIndicator = 'idle'; + return true; + } + return false; + }); + } dependencies.trackUiEvent?.('markdown_saved', { file_extension: getFileExtensionForAnalytics(state.filePath), blocks: blocks.length, @@ -838,7 +899,7 @@ export function createMarkdownController(dependencies: MarkdownControllerDepende action: 'save_mine', }); // Re-run saveDocument. computeEditBlocks will diff against - // the fresh sourceContent that keepDraft: true left in place, + // the fresh disk baseline that keepDraft: true left in place, // so hunks the user actually modified win over disk on // those specific lines and disk-only changes elsewhere are // preserved. @@ -919,6 +980,9 @@ export function createMarkdownController(dependencies: MarkdownControllerDepende currentFilePath: payload.filePath, searchLinks: (query) => searchLinkTargets(payload.filePath, query), loadHeadings: (targetPath) => loadLinkHeadings(payload.filePath, targetPath), + onOpenLink: (href) => { + void navigateLink(payload, href); + }, onChange: (value) => { state.draftContent = value; state.dirty = value !== state.fullDocumentContent; @@ -932,14 +996,7 @@ export function createMarkdownController(dependencies: MarkdownControllerDepende if (state.dirty) { scheduleAutosave(); } - const nextOutline = extractMarkdownOutline(value); - if (!areOutlineItemsEqual(state.outline, nextOutline)) { - state.outline = nextOutline; - if (!state.outline.some((item) => item.id === state.activeHeadingId)) { - state.activeHeadingId = state.outline[0]?.id ?? null; - } - markdownTocHandle?.refresh(state.outline, state.activeHeadingId); - } + scheduleOutlineRefresh(state); if (state.dirty && state.saveIndicator === 'saved') { state.saveIndicator = 'idle'; } @@ -977,22 +1034,6 @@ export function createMarkdownController(dependencies: MarkdownControllerDepende void requestFullscreen(); }); - if (wrapper) { - wrapper.addEventListener('click', (event) => { - const target = event.target as HTMLElement | null; - const link = target?.closest('a[href]'); - if (!link || !link.closest('.markdown-doc')) { - return; - } - const href = link.getAttribute('href'); - if (!href) { - return; - } - - event.preventDefault(); - void navigateLink(payload, href); - }); - } const tocShell = document.querySelector('.document-outline-shell') as HTMLElement | null; if (tocShell && wrapper) { @@ -1017,10 +1058,41 @@ export function createMarkdownController(dependencies: MarkdownControllerDepende function getCopyText(payload: RenderPayload): string | null { const state = getState(payload); - const source = state.draftContent; - return state.editorView === 'raw' - ? source - : (getRenderedMarkdownCopyText(source) || source); + return state.draftContent; + } + + function expandPartialPayload(payload: RenderPayload, direction: 'before' | 'after', loadedContent: string): RenderPayload { + const state = getState(payload); + const range = parseReadRange(payload.content); + if (!range?.isPartial) { + return payload; + } + + const cleanLoaded = stripReadStatusLine(loadedContent); + const nextBaseline = direction === 'before' + ? joinDocumentChunks(cleanLoaded, state.fullDocumentContent) + : joinDocumentChunks(state.fullDocumentContent, cleanLoaded); + const nextDraft = direction === 'before' + ? joinDocumentChunks(cleanLoaded, state.draftContent) + : joinDocumentChunks(state.draftContent, cleanLoaded); + const newFrom = direction === 'before' ? 1 : range.fromLine; + const newTo = direction === 'after' ? range.totalLines : range.toLine; + const lineCount = newTo - newFrom + 1; + const remaining = range.totalLines - newTo; + const isStillPartial = newFrom > 1 || newTo < range.totalLines; + const statusLine = isStillPartial + ? `[Reading ${lineCount} lines from ${newFrom === 1 ? 'start' : `line ${newFrom}`} (total: ${range.totalLines} lines, ${remaining} remaining)]\n` + : ''; + + state.fullDocumentContent = nextBaseline; + state.draftContent = nextDraft; + state.outline = extractMarkdownOutline(nextDraft); + state.dirty = nextDraft !== nextBaseline; + if (!state.outline.some((item) => item.id === state.activeHeadingId)) { + state.activeHeadingId = state.outline[0]?.id ?? null; + } + + return { ...payload, content: statusLine + nextBaseline }; } async function handleInlineExitFromFullscreen(originalPayload?: RenderPayload): Promise { @@ -1046,7 +1118,7 @@ export function createMarkdownController(dependencies: MarkdownControllerDepende buildBody, clear, disposeHandles, - ensureCompletePayload, + expandPartialPayload, getCopyText, getState, handleInlineExitFromFullscreen, @@ -1058,7 +1130,6 @@ export function createMarkdownController(dependencies: MarkdownControllerDepende requestEditMode, requestFullscreen, saveDocument, - setEditorView, }; } diff --git a/src/ui/file-preview/src/markdown/editor.ts b/src/ui/file-preview/src/markdown/editor.ts index 0188c484..9cb157fa 100644 --- a/src/ui/file-preview/src/markdown/editor.ts +++ b/src/ui/file-preview/src/markdown/editor.ts @@ -1,9 +1,7 @@ -import { Editor } from '@tiptap/core'; -import StarterKit from '@tiptap/starter-kit'; -import Image from '@tiptap/extension-image'; -import { Markdown } from 'tiptap-markdown'; -import { restoreWikiLinks, rewriteWikiLinks } from './linking.js'; -import { createSlugTracker } from './slugify.js'; +import { defaultKeymap, history, historyKeymap, indentWithTab } from '@codemirror/commands'; +import { markdown } from '@codemirror/lang-markdown'; +import { EditorSelection, EditorState, RangeSetBuilder } from '@codemirror/state'; +import { Decoration, EditorView, ViewPlugin, WidgetType, keymap, type DecorationSet } from '@codemirror/view'; export type MarkdownEditorView = 'raw' | 'markdown'; @@ -19,6 +17,104 @@ export interface MarkdownLinkHeading { text: string; } +interface MarkdownLinkRange { + from: number; + labelFrom: number; + labelTo: number; + to: number; + label: string; + href: string; + kind: 'markdown' | 'wiki'; +} + +function isEscaped(text: string, index: number): boolean { + let slashCount = 0; + for (let current = index - 1; current >= 0 && text[current] === '\\'; current -= 1) { + slashCount += 1; + } + return slashCount % 2 === 1; +} + +function readBareMarkdownHref(value: string): string { + const trimmed = value.trim(); + if (trimmed.startsWith('<')) { + const closeIndex = trimmed.indexOf('>'); + return closeIndex >= 0 ? trimmed.slice(1, closeIndex).trim() : trimmed; + } + + let depth = 0; + for (let index = 0; index < trimmed.length; index += 1) { + const char = trimmed[index]; + if (char === '(' && !isEscaped(trimmed, index)) { + depth += 1; + } else if (char === ')' && depth > 0 && !isEscaped(trimmed, index)) { + depth -= 1; + } else if (/\s/.test(char) && depth === 0) { + return trimmed.slice(0, index); + } + } + + return trimmed; +} + +function findMarkdownLinksInLine(text: string): Array<{ start: number; end: number; label: string; href: string }> { + const links: Array<{ start: number; end: number; label: string; href: string }> = []; + let index = 0; + + while (index < text.length) { + const start = text.indexOf('[', index); + if (start < 0) { + break; + } + if (text[start - 1] === '!' || isEscaped(text, start)) { + index = start + 1; + continue; + } + + let labelEnd = start + 1; + while (labelEnd < text.length && (text[labelEnd] !== ']' || isEscaped(text, labelEnd))) { + if (text[labelEnd] === '\n') { + break; + } + labelEnd += 1; + } + if (text[labelEnd] !== ']' || text[labelEnd + 1] !== '(') { + index = start + 1; + continue; + } + + let depth = 1; + let hrefEnd = labelEnd + 2; + while (hrefEnd < text.length && depth > 0) { + const char = text[hrefEnd]; + if (char === '\n') { + break; + } + if (char === '(' && !isEscaped(text, hrefEnd)) { + depth += 1; + } else if (char === ')' && !isEscaped(text, hrefEnd)) { + depth -= 1; + } + hrefEnd += 1; + } + if (depth !== 0) { + index = start + 1; + continue; + } + + const hrefContent = text.slice(labelEnd + 2, hrefEnd - 1); + links.push({ + start, + end: hrefEnd, + label: text.slice(start + 1, labelEnd), + href: readBareMarkdownHref(hrefContent), + }); + index = hrefEnd; + } + + return links; +} + export interface MarkdownEditorHandle { destroy: () => void; focus: () => void; @@ -95,7 +191,7 @@ export function renderMarkdownEditorShell(options: { return `
- ${isMarkdownView ? `` : ''} + ${isMarkdownView ? `` : ''}
@@ -111,35 +207,254 @@ function applyRawTab(textarea: HTMLTextAreaElement): void { textarea.selectionEnd = start + 1; } -/** - * Walk the prose-mirror DOM and assign slug-based id attributes to headings - * so the outline's revealLine can scroll to them. Re-run after every update; - * no-op writes are skipped so identical ids don't dirty the style engine. - */ -function syncHeadingIds(root: HTMLElement): void { - const nextSlug = createSlugTracker(); - const headings = Array.from(root.querySelectorAll('h1, h2, h3, h4, h5, h6')); - for (const heading of headings) { - const text = heading.textContent?.trim() ?? ''; - if (!text) { - if (heading.hasAttribute('id')) { - heading.removeAttribute('id'); - } - if (heading.hasAttribute('data-heading-id')) { - heading.removeAttribute('data-heading-id'); +function isInlineMarkerAt(source: string, index: number, marker: string): boolean { + if (!source.startsWith(marker, index)) { + return false; + } + if (marker === '*') { + return source[index - 1] !== '*' && source[index + 1] !== '*'; + } + return true; +} + +class BulletWidget extends WidgetType { + eq(other: WidgetType): boolean { + return other instanceof BulletWidget; + } + toDOM(): HTMLElement { + const span = document.createElement('span'); + span.className = 'cm-md-bullet'; + span.textContent = '•'; + return span; + } + // CodeMirror's default is to treat events from inside a widget as belonging + // to it (so clicks won't move the caret). We want clicks on the bullet to + // place the caret at the underlying marker position, like normal text. + ignoreEvent(): boolean { + return false; + } +} + +const SHARED_BULLET_WIDGET = new BulletWidget(); + +const UNORDERED_LIST_PREFIX = /^(\s*)[-*+]\s+/; +const ORDERED_LIST_PREFIX = /^\s*\d+[.)]\s+/; +const BLOCKQUOTE_PREFIX = /^\s*>\s?/; +const HORIZONTAL_RULE_LINE = /^\s*(?:-{3,}|\*{3,}|_{3,})\s*$/; + +// Order matters: longer prefixes first so `**` is matched before `*`. +const SPANNING_WRAPPER_KINDS: ReadonlyArray<{ prefix: string; suffix: string; className: string }> = [ + { prefix: '**', suffix: '**', className: 'cm-md-strong-text' }, + { prefix: '~~', suffix: '~~', className: 'cm-md-strike-text' }, + { prefix: '*', suffix: '*', className: 'cm-md-emphasis-text' }, + { prefix: '`', suffix: '`', className: 'cm-md-inline-code-text' }, +]; + +interface MarkerRange { + from: number; + to: number; + className?: string; + widget?: WidgetType; +} + +function buildMarkdownLineDecorations(view: EditorView): DecorationSet { + const builder = new RangeSetBuilder(); + const addAbsoluteMark = ( + ranges: MarkerRange[], + from: number, + to: number, + className: string + ): void => { + if (to > from) { + ranges.push({ from, to, className }); + } + }; + const addMark = ( + ranges: MarkerRange[], + lineFrom: number, + from: number, + to: number, + className: string + ): void => { + if (to > from) { + ranges.push({ from: lineFrom + from, to: lineFrom + to, className }); + } + }; + + const collectSpanningWrapperRanges = (): MarkerRange[] => { + const source = view.state.doc.toString(); + const ranges: MarkerRange[] = []; + // Single linear scan that recognizes all wrapper kinds at once. Order + // matters only for length tie-breaks (longer markers first); `*` vs `**` + // is already disambiguated by isInlineMarkerAt's neighbor check. + const opens: Array = SPANNING_WRAPPER_KINDS.map(() => null); + let index = 0; + while (index < source.length) { + let consumed = 0; + for (let kind = 0; kind < SPANNING_WRAPPER_KINDS.length; kind += 1) { + const { prefix, suffix, className } = SPANNING_WRAPPER_KINDS[kind]; + const openPos = opens[kind]; + if (openPos === null) { + if (isInlineMarkerAt(source, index, prefix)) { + opens[kind] = index; + consumed = prefix.length; + break; + } + continue; + } + if (isInlineMarkerAt(source, index, suffix)) { + const contentFrom = openPos + prefix.length; + const contentTo = index; + if (source.slice(contentFrom, contentTo).includes('\n')) { + addAbsoluteMark(ranges, openPos, contentFrom, 'cm-md-hidden-marker'); + let line = view.state.doc.lineAt(contentFrom); + while (line.from <= contentTo) { + addAbsoluteMark( + ranges, + Math.max(contentFrom, line.from), + Math.min(contentTo, line.to), + className + ); + if (line.to >= contentTo || line.number >= view.state.doc.lines) { + break; + } + line = view.state.doc.line(line.number + 1); + } + addAbsoluteMark(ranges, contentTo, index + suffix.length, 'cm-md-hidden-marker'); + } + opens[kind] = null; + consumed = suffix.length; + break; + } } - continue; + index += consumed > 0 ? consumed : 1; + } + return ranges; + }; + + const spanningInlineRanges = collectSpanningWrapperRanges(); + + const collectInlineRanges = (text: string, lineFrom: number): MarkerRange[] => { + const ranges: MarkerRange[] = []; + for (const match of text.matchAll(/\[([^\]\n]+)\]\(([^)\n]+)\)/g)) { + const start = match.index ?? 0; + const label = match[1] ?? ''; + if (text[start - 1] === '!' || label.startsWith('![')) { + continue; + } + addMark(ranges, lineFrom, start, start + 1, 'cm-md-hidden-marker'); + addMark(ranges, lineFrom, start + 1, start + 1 + label.length, 'cm-md-link-text'); + addMark(ranges, lineFrom, start + 1 + label.length, start + match[0].length, 'cm-md-hidden-marker'); + } + for (const match of text.matchAll(/\[\[([^\]\n]+)\]\]/g)) { + const start = match.index ?? 0; + const body = match[1] ?? ''; + const pipeIndex = body.lastIndexOf('|'); + const labelStart = pipeIndex >= 0 ? pipeIndex + 1 : 0; + const label = body.slice(labelStart); + addMark(ranges, lineFrom, start, start + 2, 'cm-md-hidden-marker'); + if (labelStart > 0) { + addMark(ranges, lineFrom, start + 2, start + 2 + labelStart, 'cm-md-hidden-marker'); + } + addMark(ranges, lineFrom, start + 2 + labelStart, start + 2 + labelStart + label.length, 'cm-md-link-text'); + addMark(ranges, lineFrom, start + 2 + labelStart + label.length, start + match[0].length, 'cm-md-hidden-marker'); + } + for (const match of text.matchAll(/(`+)([^`\n]+)\1/g)) { + const start = match.index ?? 0; + const ticks = match[1]?.length ?? 1; + addMark(ranges, lineFrom, start, start + ticks, 'cm-md-hidden-marker'); + addMark(ranges, lineFrom, start + ticks, start + match[0].length - ticks, 'cm-md-inline-code-text'); + addMark(ranges, lineFrom, start + match[0].length - ticks, start + match[0].length, 'cm-md-hidden-marker'); + } + for (const match of text.matchAll(/\*\*([^*\n]+)\*\*/g)) { + const start = match.index ?? 0; + addMark(ranges, lineFrom, start, start + 2, 'cm-md-hidden-marker'); + addMark(ranges, lineFrom, start + 2, start + match[0].length - 2, 'cm-md-strong-text'); + addMark(ranges, lineFrom, start + match[0].length - 2, start + match[0].length, 'cm-md-hidden-marker'); } - const headingId = nextSlug(text); - if (heading.id !== headingId) { - heading.id = headingId; + for (const match of text.matchAll(/~~([^~\n]+)~~/g)) { + const start = match.index ?? 0; + addMark(ranges, lineFrom, start, start + 2, 'cm-md-hidden-marker'); + addMark(ranges, lineFrom, start + 2, start + match[0].length - 2, 'cm-md-strike-text'); + addMark(ranges, lineFrom, start + match[0].length - 2, start + match[0].length, 'cm-md-hidden-marker'); } - if (heading.getAttribute('data-heading-id') !== headingId) { - heading.setAttribute('data-heading-id', headingId); + for (const match of text.matchAll(/(^|[^*])\*([^*\n]+)\*/g)) { + const start = (match.index ?? 0) + (match[1]?.length ?? 0); + addMark(ranges, lineFrom, start, start + 1, 'cm-md-hidden-marker'); + addMark(ranges, lineFrom, start + 1, start + match[0].length - (match[1]?.length ?? 0) - 1, 'cm-md-emphasis-text'); + addMark(ranges, lineFrom, start + match[0].length - (match[1]?.length ?? 0) - 1, start + match[0].length - (match[1]?.length ?? 0), 'cm-md-hidden-marker'); + } + return ranges.sort((left, right) => left.from - right.from || left.to - right.to); + }; + + for (const { from, to } of view.visibleRanges) { + let line = view.state.doc.lineAt(from); + while (line.from <= to) { + const text = line.text; + const headingMatch = /^(#{1,6})\s+/.exec(text); + let className = ''; + const markerRanges: MarkerRange[] = []; + if (headingMatch) { + className = `cm-md-heading cm-md-heading-${headingMatch[1].length}`; + addMark(markerRanges, line.from, 0, headingMatch[0].length, 'cm-md-hidden-marker'); + } else if (BLOCKQUOTE_PREFIX.test(text)) { + className = 'cm-md-quote'; + const marker = text.match(BLOCKQUOTE_PREFIX); + addMark(markerRanges, line.from, 0, marker?.[0].length ?? 0, 'cm-md-hidden-marker'); + } else { + const unorderedMatch = UNORDERED_LIST_PREFIX.exec(text); + if (unorderedMatch) { + className = 'cm-md-list cm-md-list-unordered'; + const markerStart = unorderedMatch[1].length; + markerRanges.push({ + from: line.from + markerStart, + to: line.from + markerStart + 1, + widget: SHARED_BULLET_WIDGET, + }); + } else if (ORDERED_LIST_PREFIX.test(text)) { + className = 'cm-md-list cm-md-list-ordered'; + } else if (HORIZONTAL_RULE_LINE.test(text)) { + className = 'cm-md-rule'; + } + } + + if (className) { + builder.add(line.from, line.from, Decoration.line({ class: className })); + } + markerRanges.push(...spanningInlineRanges.filter((range) => range.to > line.from && range.from < line.to)); + markerRanges.push(...collectInlineRanges(text, line.from)); + for (const range of markerRanges.sort((left, right) => left.from - right.from || left.to - right.to)) { + if (range.widget) { + builder.add(range.from, range.to, Decoration.replace({ widget: range.widget })); + } else if (range.className) { + builder.add(range.from, range.to, Decoration.mark({ class: range.className })); + } + } + if (line.to >= to || line.number >= view.state.doc.lines) { + break; + } + line = view.state.doc.line(line.number + 1); } } + return builder.finish(); } +const markdownLinePreviewPlugin = ViewPlugin.fromClass(class { + decorations: DecorationSet; + + constructor(view: EditorView) { + this.decorations = buildMarkdownLineDecorations(view); + } + + update(update: { docChanged: boolean; viewportChanged: boolean; view: EditorView }) { + if (update.docChanged || update.viewportChanged) { + this.decorations = buildMarkdownLineDecorations(update.view); + } + } +}, { + decorations: (plugin) => plugin.decorations, +}); + export function mountMarkdownEditor(options: { target: HTMLElement; value: string; @@ -148,6 +463,7 @@ export function mountMarkdownEditor(options: { currentFilePath: string; searchLinks?: (query: string) => Promise; loadHeadings?: (filePath: string) => Promise; + onOpenLink?: (href: string) => void | Promise; onChange: (value: string) => void; onBlur?: () => void; }): MarkdownEditorHandle { @@ -177,86 +493,304 @@ export function mountMarkdownEditor(options: { if (options.view === 'markdown') { options.target.replaceChildren(); + let suppressChange = false; + let positionToolbar: (preferredPoint?: { x: number; y: number }) => void = () => {}; + let activePopoverLink: MarkdownLinkRange | null = null; + let popoverHideTimer: ReturnType | null = null; + const view = new EditorView({ + parent: options.target, + state: EditorState.create({ + doc: options.value, + extensions: [ + history(), + markdown(), + EditorView.lineWrapping, + markdownLinePreviewPlugin, + keymap.of([indentWithTab, ...defaultKeymap, ...historyKeymap]), + EditorView.updateListener.of((update) => { + if (!suppressChange && update.docChanged) { + options.onChange(update.state.doc.toString()); + } + if (update.selectionSet || update.focusChanged) { + window.requestAnimationFrame(() => positionToolbar()); + } + }), + ], + }), + }); + const linkPopover = document.createElement('div'); + linkPopover.className = 'markdown-link-popover'; + linkPopover.hidden = true; + shell?.appendChild(linkPopover); - const getTiptapMarkdown = (): string => { - const storage = tiptap.storage as { markdown?: { getMarkdown: () => string } }; - return restoreWikiLinks(storage.markdown?.getMarkdown() ?? ''); - }; - - const tiptap = new Editor({ - element: options.target, - extensions: [ - StarterKit.configure({ - heading: { levels: [1, 2, 3, 4, 5, 6] }, - codeBlock: { HTMLAttributes: { class: 'code-viewer' } }, - link: { - openOnClick: false, - autolink: true, - HTMLAttributes: { 'data-markdown-link': 'true' }, - }, - }), - Image.configure({ allowBase64: true, inline: true }), - Markdown.configure({ - html: true, - tightLists: true, - bulletListMarker: '-', - linkify: true, - breaks: false, - transformPastedText: true, - transformCopiedText: false, - }), - ], - content: rewriteWikiLinks(options.value), - editorProps: { - attributes: { - class: 'markdown-editor-surface markdown-editor-surface--markdown markdown markdown-doc', - role: 'textbox', - 'aria-multiline': 'true', - }, - }, - onUpdate: ({ editor }) => { - syncHeadingIds(editor.view.dom as HTMLElement); - options.onChange(getTiptapMarkdown()); - }, - onSelectionUpdate: () => { - updateContextMenu(); - }, - onBlur: ({ event }) => { - if (shouldIgnoreBlur(shell, event as FocusEvent)) { - return; + const getValue = (): string => view.state.doc.toString(); + + const getSelectedText = (): string => { + const selection = view.state.selection.main; + return view.state.doc.sliceString(selection.from, selection.to); + }; + + const insertText = (insert: string, selectFrom?: number, selectTo?: number): void => { + const selection = view.state.selection.main; + if (typeof selectFrom === 'number') { + view.dispatch({ + changes: { from: selection.from, to: selection.to, insert }, + selection: EditorSelection.range(selection.from + selectFrom, selection.from + (selectTo ?? selectFrom)), + userEvent: 'input', + }); + } else { + view.dispatch({ + changes: { from: selection.from, to: selection.to, insert }, + userEvent: 'input', + }); + } + view.focus(); + window.requestAnimationFrame(() => positionToolbar()); + }; + + const findEnclosingWrapper = (prefix: string, suffix: string = prefix): { from: number; contentFrom: number; contentTo: number; to: number } | null => { + const selection = view.state.selection.main; + const line = view.state.doc.lineAt(selection.from); + if (selection.to > line.to) { + return null; + } + const lineText = line.text; + const relativeFrom = selection.from - line.from; + const relativeTo = selection.to - line.from; + let openIndex = lineText.lastIndexOf(prefix, Math.max(0, relativeFrom - 1)); + while (openIndex >= 0) { + const contentFrom = openIndex + prefix.length; + const closeIndex = lineText.indexOf(suffix, Math.max(contentFrom, relativeTo)); + if (closeIndex >= 0 && contentFrom <= relativeFrom && relativeTo <= closeIndex) { + return { + from: line.from + openIndex, + contentFrom: line.from + contentFrom, + contentTo: line.from + closeIndex, + to: line.from + closeIndex + suffix.length, + }; } - if (contextMenu) { - contextMenu.hidden = true; + openIndex = lineText.lastIndexOf(prefix, openIndex - 1); + } + return null; + }; + + const findWrapperRanges = (prefix: string, suffix: string = prefix): Array<{ + from: number; + contentFrom: number; + contentTo: number; + to: number; + }> => { + const source = view.state.doc.toString(); + const ranges: Array<{ from: number; contentFrom: number; contentTo: number; to: number }> = []; + let open: number | null = null; + for (let index = 0; index < source.length;) { + if (open === null && isInlineMarkerAt(source, index, prefix)) { + open = index; + index += prefix.length; + continue; } - options.onBlur?.(); - }, - }); + if (open !== null && isInlineMarkerAt(source, index, suffix)) { + ranges.push({ + from: open, + contentFrom: open + prefix.length, + contentTo: index, + to: index + suffix.length, + }); + open = null; + index += suffix.length; + continue; + } + index += 1; + } + return ranges; + }; - const editorDom = tiptap.view.dom as HTMLElement; - syncHeadingIds(editorDom); + const selectionTouchesOnlyMarkedText = (prefix: string, suffix: string = prefix): boolean => { + const selection = view.state.selection.main; + if (selection.empty) { + return false; + } + const source = view.state.doc.toString(); + const ranges = findWrapperRanges(prefix, suffix); + let sawContent = false; + + for (let position = selection.from; position < selection.to; position += 1) { + const char = source[position]; + if (!char || /\s/.test(char)) { + continue; + } + const isMarker = ranges.some((range) => ( + (range.from <= position && position < range.contentFrom) + || (range.contentTo <= position && position < range.to) + )); + if (isMarker) { + continue; + } + sawContent = true; + const isCovered = ranges.some((range) => range.contentFrom <= position && position < range.contentTo); + if (!isCovered) { + return false; + } + } + + return sawContent; + }; - const updateContextMenu = (): void => { - if (!contextMenu) { + const getBoundaryWrapperState = (prefix: string, suffix: string = prefix): { startsInside: boolean; endsInside: boolean } => { + const selection = view.state.selection.main; + const ranges = findWrapperRanges(prefix, suffix); + return { + startsInside: ranges.some((range) => range.contentFrom <= selection.from && selection.from < range.contentTo), + endsInside: ranges.some((range) => range.contentFrom < selection.to && selection.to <= range.contentTo), + }; + }; + + const removeWrappersTouchingSelection = (prefix: string, suffix: string = prefix): boolean => { + const selection = view.state.selection.main; + const changes = findWrapperRanges(prefix, suffix) + .filter((range) => range.contentTo > selection.from && range.contentFrom < selection.to) + .flatMap((range) => [ + { from: range.from, to: range.contentFrom, insert: '' }, + { from: range.contentTo, to: range.to, insert: '' }, + ]) + .sort((left, right) => left.from - right.from); + + if (changes.length === 0) { + return false; + } + + const removedBefore = (position: number): number => changes.reduce((total, change) => ( + change.to <= position ? total + (change.to - change.from) : total + ), 0); + view.dispatch({ + changes, + selection: EditorSelection.range( + Math.max(0, selection.from - removedBefore(selection.from)), + Math.max(0, selection.to - removedBefore(selection.to)) + ), + userEvent: 'input', + }); + view.focus(); + window.requestAnimationFrame(() => positionToolbar()); + return true; + }; + + const toggleWrapper = (prefix: string, suffix: string = prefix, placeholder = 'text'): void => { + const selected = getSelectedText(); + const selection = view.state.selection.main; + + if (selected.startsWith(prefix) && selected.endsWith(suffix) && selected.length >= prefix.length + suffix.length) { + const body = selected.slice(prefix.length, selected.length - suffix.length); + view.dispatch({ + changes: { from: selection.from, to: selection.to, insert: body }, + selection: EditorSelection.range(selection.from, selection.from + body.length), + userEvent: 'input', + }); + view.focus(); + window.requestAnimationFrame(() => positionToolbar()); return; } - const { from, to, empty } = tiptap.state.selection; - if (empty || !tiptap.isFocused) { - contextMenu.hidden = true; + + const hasSurroundingMarkers = selection.from >= prefix.length + && view.state.doc.sliceString(selection.from - prefix.length, selection.from) === prefix + && view.state.doc.sliceString(selection.to, selection.to + suffix.length) === suffix; + if (hasSurroundingMarkers) { + view.dispatch({ + changes: [ + { from: selection.from - prefix.length, to: selection.from, insert: '' }, + { from: selection.to, to: selection.to + suffix.length, insert: '' }, + ], + selection: EditorSelection.range(selection.from - prefix.length, selection.to - prefix.length), + userEvent: 'input', + }); + view.focus(); + window.requestAnimationFrame(() => positionToolbar()); return; } - const start = tiptap.view.coordsAtPos(from); - const end = tiptap.view.coordsAtPos(to); - const shellEl = shell as HTMLElement | null; - if (!shellEl) { + + if (!selection.empty && selectionTouchesOnlyMarkedText(prefix, suffix)) { + if (removeWrappersTouchingSelection(prefix, suffix)) { + return; + } + } + + const enclosingWrapper = findEnclosingWrapper(prefix, suffix); + if (enclosingWrapper) { + const cursor = selection.empty + ? Math.max(enclosingWrapper.from, selection.from - prefix.length) + : selection.from - prefix.length; + view.dispatch({ + changes: [ + { from: enclosingWrapper.from, to: enclosingWrapper.contentFrom, insert: '' }, + { from: enclosingWrapper.contentTo, to: enclosingWrapper.to, insert: '' }, + ], + selection: EditorSelection.cursor(Math.max(enclosingWrapper.from, cursor)), + userEvent: 'input', + }); + view.focus(); + window.requestAnimationFrame(() => positionToolbar()); return; } - const shellRect = shellEl.getBoundingClientRect(); - const midX = (start.left + end.right) / 2; - contextMenu.hidden = false; - const left = Math.max(12, midX - shellRect.left - contextMenu.offsetWidth / 2); - const top = Math.max(12, start.top - shellRect.top - contextMenu.offsetHeight - 10); - contextMenu.style.left = `${left}px`; - contextMenu.style.top = `${top}px`; + + if (!selection.empty) { + const { startsInside, endsInside } = getBoundaryWrapperState(prefix, suffix); + const body = selected; + const insertPrefix = startsInside ? '' : prefix; + const insertSuffix = endsInside ? '' : suffix; + const insert = `${insertPrefix}${body}${insertSuffix}`; + view.dispatch({ + changes: { from: selection.from, to: selection.to, insert }, + selection: EditorSelection.range( + selection.from + insertPrefix.length, + selection.from + insertPrefix.length + body.length + ), + userEvent: 'input', + }); + view.focus(); + window.requestAnimationFrame(() => positionToolbar()); + return; + } + + const body = selected || placeholder; + insertText(`${prefix}${body}${suffix}`, prefix.length, prefix.length + body.length); + }; + + const updateSelectedLines = (mapLine: (line: string, index: number) => string): void => { + const doc = view.state.doc; + const selection = view.state.selection.main; + const fromLine = doc.lineAt(selection.from); + const toLine = doc.lineAt(selection.to); + const changes: Array<{ from: number; to: number; insert: string }> = []; + for (let lineNumber = fromLine.number; lineNumber <= toLine.number; lineNumber += 1) { + const line = doc.line(lineNumber); + const nextLine = mapLine(line.text, lineNumber - fromLine.number); + if (nextLine !== line.text) { + changes.push({ from: line.from, to: line.to, insert: nextLine }); + } + } + if (changes.length > 0) { + view.dispatch({ changes, userEvent: 'input' }); + } + view.focus(); + window.requestAnimationFrame(() => positionToolbar()); + }; + + const setHeadingLevel = (level: 0 | 1 | 2 | 3 | 4 | 5 | 6): void => { + updateSelectedLines((line) => { + const stripped = line.replace(/^\s{0,3}#{1,6}\s+/, ''); + if (level === 0) { + return stripped; + } + const existing = line.match(/^\s{0,3}(#{1,6})\s+/); + if (existing?.[1].length === level) { + return stripped; + } + return `${'#'.repeat(level)} ${stripped || 'Heading'}`; + }); + }; + + const toggleLinePrefix = (prefix: string, pattern: RegExp): void => { + updateSelectedLines((line) => pattern.test(line) ? line.replace(pattern, '') : `${prefix}${line || 'List item'}`); }; const setLinkHeadingOptions = (headings: MarkdownLinkHeading[] = [], placeholder: string = 'None'): void => { @@ -400,18 +934,86 @@ export function mountMarkdownEditor(options: { renderLinkResults(); }; - const openLinkModalForSelection = (): void => { + const findMarkdownLinkInLine = (line: { from: number; text: string }, relativeFrom: number, relativeTo: number = relativeFrom): MarkdownLinkRange | null => { + for (const link of findMarkdownLinksInLine(line.text)) { + const { start, end, label, href } = link; + if (start <= relativeFrom && relativeTo <= end) { + return { + from: line.from + start, + labelFrom: line.from + start + 1, + labelTo: line.from + start + 1 + label.length, + to: line.from + end, + label, + href, + kind: 'markdown', + }; + } + } + + for (const match of line.text.matchAll(/\[\[([^\]\n]+)\]\]/g)) { + const start = match.index ?? 0; + const end = start + match[0].length; + if (start <= relativeFrom && relativeTo <= end) { + const body = match[1] ?? ''; + const pipeIndex = body.lastIndexOf('|'); + const label = pipeIndex >= 0 ? body.slice(pipeIndex + 1) : body; + const labelOffset = pipeIndex >= 0 ? 2 + pipeIndex + 1 : 2; + return { + from: line.from + start, + labelFrom: line.from + start + labelOffset, + labelTo: line.from + start + labelOffset + label.length, + to: line.from + end, + label, + href: match[0], + kind: 'wiki', + }; + } + } + + return null; + }; + + const findMarkdownLinkAtPosition = (position: number): MarkdownLinkRange | null => { + const safePosition = Math.max(0, Math.min(view.state.doc.length, position)); + const line = view.state.doc.lineAt(safePosition); + const relativePosition = safePosition - line.from; + return findMarkdownLinkInLine(line, relativePosition, relativePosition); + }; + + const findEnclosingMarkdownLink = (): MarkdownLinkRange | null => { + const selection = view.state.selection.main; + const line = view.state.doc.lineAt(selection.from); + if (selection.to > line.to) { + return null; + } + return findMarkdownLinkInLine(line, selection.from - line.from, selection.to - line.from); + }; + + const openLinkModalForSelection = (existingLink?: MarkdownLinkRange): void => { if (!linkModal) { return; } - const selectedText = tiptap.state.doc.textBetween(tiptap.state.selection.from, tiptap.state.selection.to, ' ').trim(); + if (existingLink) { + view.dispatch({ selection: EditorSelection.range(existingLink.from, existingLink.to) }); + } + const selectedText = existingLink?.label ?? getSelectedText().trim(); linkModal.removeAttribute('hidden'); - updateLinkMode('url'); + updateLinkMode(existingLink?.kind === 'wiki' ? 'file' : 'url'); if (linkLabelInput) { linkLabelInput.value = selectedText; } if (linkInput) { - linkInput.value = ''; + linkInput.value = existingLink?.kind === 'markdown' ? existingLink.href : ''; + } + if (linkAliasInput) { + linkAliasInput.value = existingLink?.kind === 'wiki' ? existingLink.label : ''; + } + if (linkSearchInput) { + linkSearchInput.value = ''; + } + if (existingLink?.kind === 'wiki') { + linkSearchInput?.focus(); + } else if (linkInput) { linkInput.focus(); } linkSearchResults = []; @@ -429,23 +1031,17 @@ export function mountMarkdownEditor(options: { return; } const label = linkLabelInput?.value?.trim() || href; - const { from, to, empty } = tiptap.state.selection; - if (empty) { - tiptap.chain().focus().insertContent({ - type: 'text', - text: label, - marks: [{ type: 'link', attrs: { href } }], - }).run(); + const existingLink = findEnclosingMarkdownLink(); + if (existingLink) { + view.dispatch({ + changes: { from: existingLink.from, to: existingLink.to, insert: `[${label}](${href})` }, + selection: EditorSelection.range(existingLink.from + 1, existingLink.from + 1 + label.length), + userEvent: 'input', + }); + view.focus(); + window.requestAnimationFrame(() => positionToolbar()); } else { - tiptap.chain() - .focus() - .deleteRange({ from, to }) - .insertContent({ - type: 'text', - text: label, - marks: [{ type: 'link', attrs: { href } }], - }) - .run(); + insertText(`[${label}](${href})`, 1, 1 + label.length); } } else if (selectedLinkItem) { const selectedHeadingId = linkHeadingSelect?.value?.trim(); @@ -453,24 +1049,19 @@ export function mountMarkdownEditor(options: { const alias = linkAliasInput?.value?.trim(); const pathPart = selectedLinkItem.path === options.currentFilePath ? '' : selectedLinkItem.wikiPath; const wikiLink = `[[${pathPart}${selectedHeadingId ? `#${selectedHeadingId}` : ''}${alias ? `|${alias}` : ''}]]`; - const href = `${selectedLinkItem.relativePath}${selectedHeadingId ? `#${selectedHeadingId}` : ''}`; const label = alias || selectedHeadingText || selectedLinkItem.title; - const { from, to, empty } = tiptap.state.selection; - const insertChain = tiptap.chain().focus(); - if (!empty) { - insertChain.deleteRange({ from, to }); + const existingLink = findEnclosingMarkdownLink(); + if (existingLink) { + view.dispatch({ + changes: { from: existingLink.from, to: existingLink.to, insert: wikiLink }, + selection: EditorSelection.range(existingLink.from + 2, existingLink.from + 2 + label.length), + userEvent: 'input', + }); + view.focus(); + window.requestAnimationFrame(() => positionToolbar()); + } else { + insertText(wikiLink, 2, 2 + label.length); } - insertChain.insertContent({ - type: 'text', - text: label, - marks: [{ - type: 'link', - attrs: { - href, - title: `mcp-wiki:${encodeURIComponent(wikiLink)}`, - }, - }], - }).run(); } closeLinkModal(); }; @@ -483,24 +1074,37 @@ export function mountMarkdownEditor(options: { } switch (format) { case 'bold': - tiptap.chain().focus().toggleBold().run(); + toggleWrapper('**', '**', 'bold text'); break; case 'italic': - tiptap.chain().focus().toggleItalic().run(); + toggleWrapper('*', '*', 'italic text'); break; case 'strike': - tiptap.chain().focus().toggleStrike().run(); + toggleWrapper('~~', '~~', 'struck text'); break; case 'quote': - tiptap.chain().focus().toggleBlockquote().run(); + toggleLinePrefix('> ', BLOCKQUOTE_PREFIX); break; case 'list': - tiptap.chain().focus().toggleBulletList().run(); + toggleLinePrefix('- ', UNORDERED_LIST_PREFIX); break; case 'code': - tiptap.chain().focus().toggleCode().run(); + toggleWrapper('`', '`', 'code'); break; case 'link': + { + const existingLink = findEnclosingMarkdownLink(); + if (existingLink) { + view.dispatch({ + changes: { from: existingLink.from, to: existingLink.to, insert: existingLink.label }, + selection: EditorSelection.range(existingLink.from, existingLink.from + existingLink.label.length), + userEvent: 'input', + }); + view.focus(); + window.requestAnimationFrame(() => positionToolbar()); + break; + } + } openLinkModalForSelection(); break; } @@ -512,111 +1116,206 @@ export function mountMarkdownEditor(options: { return; } if (value === 'p') { - tiptap.chain().focus().setParagraph().run(); + setHeadingLevel(0); return; } const match = /^h([1-6])$/.exec(value); if (match) { const level = Number.parseInt(match[1], 10) as 1 | 2 | 3 | 4 | 5 | 6; - tiptap.chain().focus().toggleHeading({ level }).run(); + setHeadingLevel(level); } }; - const linkPopover = document.createElement('div'); - linkPopover.className = 'markdown-link-popover'; - linkPopover.hidden = true; - editorDom.parentElement?.appendChild(linkPopover); - let popoverHideTimer: ReturnType | null = null; - - const showLinkPopover = (anchor: HTMLAnchorElement): void => { - if (popoverHideTimer) { - clearTimeout(popoverHideTimer); - popoverHideTimer = null; + const handleLinkModeFileClick = (): void => updateLinkMode('file'); + const handleLinkModeUrlClick = (): void => { + updateLinkMode('url'); + linkInput?.focus(); + }; + const handleSearchInput = (): void => { void runLinkSearch(); }; + const handleModalBackdropClick = (e: MouseEvent): void => { + if (e.target === linkModal) { + closeLinkModal(); } - const href = anchor.getAttribute('href') ?? ''; - linkPopover.innerHTML = ``; - linkPopover.hidden = false; + }; + const handleFocusOut = (event: FocusEvent): void => { + if (shouldIgnoreBlur(shell, event)) { + return; + } + if (contextMenu) { + contextMenu.hidden = true; + } + options.onBlur?.(); + }; - linkPopover.querySelector('#link-popover-open')?.addEventListener('click', (e) => { - e.preventDefault(); - e.stopPropagation(); - linkPopover.hidden = true; - anchor.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true })); - }, { once: true }); + positionToolbar = (preferredPoint?: { x: number; y: number }): void => { + if (!contextMenu || !shell) { + return; + } - linkPopover.querySelector('#link-popover-edit')?.addEventListener('click', (e) => { - e.preventDefault(); - e.stopPropagation(); - linkPopover.hidden = true; - if (!linkModal) { - return; - } - const pos = tiptap.view.posAtDOM(anchor, 0); - if (pos >= 0) { - const endPos = pos + (anchor.textContent?.length ?? 0); - tiptap.chain().focus().setTextSelection({ from: pos, to: endPos }).run(); - } - const label = anchor.textContent?.trim() ?? ''; - linkModal.removeAttribute('hidden'); - updateLinkMode('url'); - if (linkInput) { linkInput.value = href; } - if (linkLabelInput) { linkLabelInput.value = label; } - }, { once: true }); - - const rect = anchor.getBoundingClientRect(); - const parent = editorDom.parentElement; - if (!parent) { + const activeElement = document.activeElement; + if (!view.hasFocus && activeElement && !shell.contains(activeElement)) { + contextMenu.hidden = true; return; } - const parentRect = parent.getBoundingClientRect(); - linkPopover.style.left = `${Math.max(4, rect.left - parentRect.left)}px`; - linkPopover.style.top = `${rect.bottom - parentRect.top + 4}px`; + + const selection = view.state.selection.main; + const fromCoords = view.coordsAtPos(selection.from); + const toCoords = view.coordsAtPos(selection.to); + const shellRect = (shell as HTMLElement).getBoundingClientRect(); + let clientX = preferredPoint?.x ?? fromCoords?.left ?? shellRect.left + shellRect.width / 2; + let clientY = preferredPoint?.y ?? fromCoords?.top ?? shellRect.top + 24; + + if (!selection.empty && fromCoords && toCoords) { + clientX = (fromCoords.left + toCoords.right) / 2; + clientY = Math.min(fromCoords.top, toCoords.top); + } + + contextMenu.hidden = false; + const toolbarWidth = contextMenu.offsetWidth || 1; + const toolbarHeight = contextMenu.offsetHeight || 1; + const minLeft = 8; + const maxLeft = Math.max(minLeft, shellRect.width - toolbarWidth - 8); + const unclampedLeft = clientX - shellRect.left - toolbarWidth / 2; + const left = Math.min(Math.max(unclampedLeft, minLeft), maxLeft); + let top = clientY - shellRect.top - toolbarHeight - 10; + if (top < 8) { + top = clientY - shellRect.top + 18; + } + const maxTop = Math.max(8, shellRect.height - toolbarHeight - 8); + top = Math.min(Math.max(top, 8), maxTop); + contextMenu.style.left = `${left}px`; + contextMenu.style.top = `${top}px`; }; - const hideLinkPopover = (): void => { + const hideLinkPopover = (delayMs = 180): void => { + if (popoverHideTimer) { + clearTimeout(popoverHideTimer); + } popoverHideTimer = setTimeout(() => { linkPopover.hidden = true; - }, 200); + activePopoverLink = null; + popoverHideTimer = null; + }, delayMs); }; - const handleMouseOver = (e: MouseEvent): void => { - const target = (e.target as HTMLElement)?.closest?.('a[href]') as HTMLAnchorElement | null; - if (target && editorDom.contains(target)) { - showLinkPopover(target); - } - }; - const handleMouseOut = (e: MouseEvent): void => { - const target = (e.target as HTMLElement)?.closest?.('a[href]'); - if (target) { - hideLinkPopover(); + const showLinkPopover = (link: MarkdownLinkRange, clientX: number, clientY: number): void => { + if (!shell) { + return; } - }; - const handlePopoverEnter = (): void => { if (popoverHideTimer) { clearTimeout(popoverHideTimer); popoverHideTimer = null; } + activePopoverLink = link; + linkPopover.innerHTML = ` + + + `; + linkPopover.hidden = false; + + // Position alongside the link rather than below it so the mouse can + // travel from the link to the popover without crossing another line — + // moving down would otherwise hover the next line's link and replace + // this popover before the user can click. + const fromCoords = view.coordsAtPos(link.labelFrom); + const toCoords = view.coordsAtPos(link.labelTo); + const shellRect = (shell as HTMLElement).getBoundingClientRect(); + const popoverWidth = linkPopover.offsetWidth || 1; + const popoverHeight = linkPopover.offsetHeight || 1; + const gap = 8; + const linkRight = toCoords?.right ?? clientX; + const linkLeft = fromCoords?.left ?? clientX; + const linkTop = fromCoords?.top ?? clientY; + const linkBottom = fromCoords?.bottom ?? clientY; + const linkMid = (linkTop + linkBottom) / 2; + let anchorLeft = linkRight + gap; + if (anchorLeft + popoverWidth > shellRect.right - gap) { + anchorLeft = linkLeft - popoverWidth - gap; + } + const anchorTop = linkMid - popoverHeight / 2; + const left = Math.min( + Math.max(anchorLeft - shellRect.left, gap), + Math.max(gap, shellRect.width - popoverWidth - gap) + ); + const top = Math.min( + Math.max(anchorTop - shellRect.top, gap), + Math.max(gap, shellRect.height - popoverHeight - gap) + ); + linkPopover.style.left = `${left}px`; + linkPopover.style.top = `${top}px`; }; - const handlePopoverLeave = (): void => { - hideLinkPopover(); + + const handleLinkMouseMove = (event: MouseEvent): void => { + const pos = view.posAtCoords({ x: event.clientX, y: event.clientY }); + if (typeof pos !== 'number') { + hideLinkPopover(); + return; + } + const link = findMarkdownLinkAtPosition(pos); + if (!link) { + hideLinkPopover(); + return; + } + showLinkPopover(link, event.clientX, event.clientY); }; - const handleLinkModeFileClick = (): void => updateLinkMode('file'); - const handleLinkModeUrlClick = (): void => { - updateLinkMode('url'); - linkInput?.focus(); + + const handleLinkPopoverMouseDown = (event: MouseEvent): void => { + event.preventDefault(); }; - const handleSearchInput = (): void => { void runLinkSearch(); }; - const handleModalBackdropClick = (e: MouseEvent): void => { - if (e.target === linkModal) { - closeLinkModal(); + + const handleLinkPopoverClick = (event: MouseEvent): void => { + const button = (event.target as HTMLElement | null)?.closest('button'); + if (!button || !activePopoverLink) { + return; + } + event.preventDefault(); + event.stopPropagation(); + const link = activePopoverLink; + linkPopover.hidden = true; + activePopoverLink = null; + + if (button.id === 'link-popover-open') { + void options.onOpenLink?.(link.href); + return; + } + + if (button.id === 'link-popover-edit') { + openLinkModalForSelection(link); } }; - editorDom.addEventListener('mouseover', handleMouseOver); - editorDom.addEventListener('mouseout', handleMouseOut); - linkPopover.addEventListener('mouseenter', handlePopoverEnter); - linkPopover.addEventListener('mouseleave', handlePopoverLeave); + const handleEditorMouseUp = (event: MouseEvent): void => { + positionToolbar({ x: event.clientX, y: event.clientY }); + }; + const handleEditorKeyUp = (): void => { + positionToolbar(); + }; + const handleEditorFocusIn = (): void => { + positionToolbar(); + }; + const handleFormatMouseDown = (event: MouseEvent): void => { + event.preventDefault(); + }; + + view.dom.addEventListener('focusout', handleFocusOut); + view.dom.addEventListener('mousemove', handleLinkMouseMove); + view.dom.addEventListener('mouseleave', () => hideLinkPopover()); + view.dom.addEventListener('mouseup', handleEditorMouseUp); + view.dom.addEventListener('keyup', handleEditorKeyUp); + view.dom.addEventListener('focusin', handleEditorFocusIn); + const handleLinkPopoverMouseEnter = (): void => { + if (popoverHideTimer) { + clearTimeout(popoverHideTimer); + popoverHideTimer = null; + } + }; + const handleLinkPopoverMouseLeave = (): void => hideLinkPopover(); + linkPopover.addEventListener('mousedown', handleLinkPopoverMouseDown); + linkPopover.addEventListener('click', handleLinkPopoverClick); + linkPopover.addEventListener('mouseenter', handleLinkPopoverMouseEnter); + linkPopover.addEventListener('mouseleave', handleLinkPopoverMouseLeave); formatButtons.forEach((button) => button.addEventListener('click', handleFormatClick)); + formatButtons.forEach((button) => button.addEventListener('mousedown', handleFormatMouseDown)); blockStyleSelect?.addEventListener('change', handleBlockStyleChange); linkModeFile?.addEventListener('click', handleLinkModeFileClick); linkModeUrl?.addEventListener('click', handleLinkModeUrlClick); @@ -626,17 +1325,23 @@ export function mountMarkdownEditor(options: { linkModal?.addEventListener('click', handleModalBackdropClick); if (typeof options.initialScrollTop === 'number') { - editorDom.scrollTop = options.initialScrollTop; + view.scrollDOM.scrollTop = options.initialScrollTop; } renderLinkResults(); return { destroy: () => { - editorDom.removeEventListener('mouseover', handleMouseOver); - editorDom.removeEventListener('mouseout', handleMouseOut); - linkPopover.removeEventListener('mouseenter', handlePopoverEnter); - linkPopover.removeEventListener('mouseleave', handlePopoverLeave); + view.dom.removeEventListener('focusout', handleFocusOut); + view.dom.removeEventListener('mousemove', handleLinkMouseMove); + view.dom.removeEventListener('mouseup', handleEditorMouseUp); + view.dom.removeEventListener('keyup', handleEditorKeyUp); + view.dom.removeEventListener('focusin', handleEditorFocusIn); + linkPopover.removeEventListener('mousedown', handleLinkPopoverMouseDown); + linkPopover.removeEventListener('click', handleLinkPopoverClick); + linkPopover.removeEventListener('mouseenter', handleLinkPopoverMouseEnter); + linkPopover.removeEventListener('mouseleave', handleLinkPopoverMouseLeave); formatButtons.forEach((button) => button.removeEventListener('click', handleFormatClick)); + formatButtons.forEach((button) => button.removeEventListener('mousedown', handleFormatMouseDown)); blockStyleSelect?.removeEventListener('change', handleBlockStyleChange); linkModeFile?.removeEventListener('click', handleLinkModeFileClick); linkModeUrl?.removeEventListener('click', handleLinkModeUrlClick); @@ -644,34 +1349,31 @@ export function mountMarkdownEditor(options: { linkApply?.removeEventListener('click', handleLinkApply); linkCancel?.removeEventListener('click', closeLinkModal); linkModal?.removeEventListener('click', handleModalBackdropClick); - linkPopover.remove(); if (popoverHideTimer) { clearTimeout(popoverHideTimer); } - tiptap.destroy(); + linkPopover.remove(); + view.destroy(); options.target.replaceChildren(); }, focus: () => { - tiptap.commands.focus(); + view.focus(); }, - getValue: () => getTiptapMarkdown(), + getValue, setValue: (value: string) => { - tiptap.commands.setContent(rewriteWikiLinks(value), { emitUpdate: false }); - syncHeadingIds(editorDom); + suppressChange = true; + view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: value } }); + suppressChange = false; }, - revealLine: (_lineNumber: number, headingId?: string) => { - if (headingId) { - const heading = editorDom.querySelector(`#${CSS.escape(headingId)}`); - if (heading) { - heading.scrollIntoView({ block: 'start', inline: 'nearest' }); - editorDom.scrollTop = Math.max(editorDom.scrollTop - 24, 0); - heading.setAttribute('tabindex', '-1'); - heading.focus({ preventScroll: true }); - return; - } - } - tiptap.commands.focus(); + revealLine: (lineNumber: number) => { + const targetLine = Math.max(1, Math.min(view.state.doc.lines, Math.floor(lineNumber))); + const line = view.state.doc.line(targetLine); + view.dispatch({ + selection: EditorSelection.cursor(line.from), + effects: EditorView.scrollIntoView(line.from, { y: 'start', yMargin: 48 }), + }); + view.focus(); }, setScrollTop: (scrollTop: number) => { - editorDom.scrollTop = Math.max(0, scrollTop); + view.scrollDOM.scrollTop = Math.max(0, scrollTop); }, }; } @@ -718,9 +1420,6 @@ export function mountMarkdownEditor(options: { textarea.addEventListener('keydown', handleKeyDown); textarea.addEventListener('focusout', handleFocusOut); autosize(); - if (typeof options.initialScrollTop === 'number') { - textarea.scrollTop = options.initialScrollTop; - } return { destroy: () => { diff --git a/src/ui/file-preview/src/markdown/linking.ts b/src/ui/file-preview/src/markdown/linking.ts index dfe0baac..c7d6c1bc 100644 --- a/src/ui/file-preview/src/markdown/linking.ts +++ b/src/ui/file-preview/src/markdown/linking.ts @@ -15,9 +15,6 @@ interface ParsedWikiLink { alias?: string; } -const WIKI_LINK_PATTERN = /\[\[([^\]|#]*)(?:#([^\]|]+))?(?:\|([^\]]+))?\]\]/g; -const FENCE_PATTERN = /^(`{3,}|~{3,})/; - function encodeLinkPath(pathValue: string): string { return encodeURI(normalizePathSeparators(pathValue)); } @@ -43,22 +40,6 @@ function parseWikiLink(rawHref: string): ParsedWikiLink | null { }; } -function buildWikiDisplayText(link: ParsedWikiLink): string { - if (link.alias && link.alias.length > 0) { - return link.alias; - } - - if (link.path && link.anchor) { - return `${link.path}#${link.anchor}`; - } - - if (link.path) { - return link.path; - } - - return link.anchor ?? ''; -} - function appendMarkdownExtension(pathValue: string): string { if (/\.[A-Za-z0-9_-]+$/.test(pathValue)) { return pathValue; @@ -92,51 +73,6 @@ function buildWikiHref(link: ParsedWikiLink): string { return `${encodedPath}#${slugifyMarkdownHeading(link.anchor)}`; } -function rewriteWikiLinksInPlainText(segment: string): string { - return segment.replace(WIKI_LINK_PATTERN, (match) => { - const parsed = parseWikiLink(match); - if (!parsed) { - return match; - } - - const displayText = buildWikiDisplayText(parsed); - const href = buildWikiHref(parsed); - return `[${displayText}](${href} "mcp-wiki:${encodeURIComponent(match)}")`; - }); -} - -function replaceWikiLinksOutsideInlineCode(line: string): string { - let result = ''; - let cursor = 0; - - while (cursor < line.length) { - const codeStart = line.indexOf('`', cursor); - if (codeStart === -1) { - result += rewriteWikiLinksInPlainText(line.slice(cursor)); - break; - } - - result += rewriteWikiLinksInPlainText(line.slice(cursor, codeStart)); - - let delimiterEnd = codeStart; - while (delimiterEnd < line.length && line[delimiterEnd] === '`') { - delimiterEnd += 1; - } - - const delimiter = line.slice(codeStart, delimiterEnd); - const codeEnd = line.indexOf(delimiter, delimiterEnd); - if (codeEnd === -1) { - result += line.slice(codeStart); - break; - } - - result += line.slice(codeStart, codeEnd + delimiter.length); - cursor = codeEnd + delimiter.length; - } - - return result; -} - function decodeAnchorFragment(fragment: string | undefined): string | undefined { if (!fragment || fragment.length === 0) { return undefined; @@ -199,47 +135,6 @@ function resolveFileTargetPath(currentPath: string, rawPath: string): string { return normalizeFilePath(fromFileUrl(resolvedUrl)); } -/** - * Invert `rewriteWikiLinks`: convert `[alias](href "mcp-wiki:ENCODED")` links - * back to their original `[[...]]` form. Used when serializing a WYSIWYG - * edit session back to markdown — the `mcp-wiki:` title prefix is the - * round-trip marker written by `rewriteWikiLinks`. - */ -export function restoreWikiLinks(markdown: string): string { - return markdown.replace(/\[([^\]]*)\]\(([^)\s]*)(?:\s+"mcp-wiki:([^"]+)")\)/g, (_, _alias, _href, encoded) => { - try { - return decodeURIComponent(encoded); - } catch { - return `[[${encoded}]]`; - } - }); -} - -export function rewriteWikiLinks(source: string): string { - const lines = source.split('\n'); - let activeFence: string | null = null; - - return lines.map((line) => { - const trimmedStart = line.trimStart(); - const fenceMatch = trimmedStart.match(FENCE_PATTERN); - if (fenceMatch) { - const marker = fenceMatch[1]; - if (!activeFence) { - activeFence = marker; - } else if (marker[0] === activeFence[0] && marker.length >= activeFence.length) { - activeFence = null; - } - return line; - } - - if (activeFence) { - return line; - } - - return replaceWikiLinksOutsideInlineCode(line); - }).join('\n'); -} - export function resolveMarkdownLink(currentPath: string, rawHref: string): ResolvedMarkdownLink { const wikiLink = parseWikiLink(rawHref); if (wikiLink) { diff --git a/src/ui/file-preview/src/markdown/outline.ts b/src/ui/file-preview/src/markdown/outline.ts index 36ec29a0..ea853237 100644 --- a/src/ui/file-preview/src/markdown/outline.ts +++ b/src/ui/file-preview/src/markdown/outline.ts @@ -1,21 +1,87 @@ import type { DocumentOutlineItem } from '../document-outline.js'; -import { createMarkdownIt, prepareMarkdownSource, readHeadingProjection } from './parser.js'; +import { GFM, parser } from '@lezer/markdown'; import { createSlugTracker } from './slugify.js'; -const outlineParser = createMarkdownIt(); + +const outlineParser = parser.configure([GFM]); +const HEADING_NODE_PATTERN = /^(?:ATXHeading|SetextHeading)([1-6])$/; + +function buildLineStarts(source: string): number[] { + const starts = [0]; + for (let index = 0; index < source.length; index += 1) { + if (source[index] === '\n') { + starts.push(index + 1); + } + } + return starts; +} + +function lineNumberForOffset(lineStarts: number[], offset: number): number { + let low = 0; + let high = lineStarts.length - 1; + while (low <= high) { + const mid = Math.floor((low + high) / 2); + if (lineStarts[mid] <= offset) { + low = mid + 1; + } else { + high = mid - 1; + } + } + return Math.max(1, high + 1); +} + +function stripInlineMarkdown(text: string): string { + return text + .replace(/!\[([^\]]*)\]\([^)]*\)/g, '$1') + .replace(/\[([^\]]+)\]\([^)]*\)/g, '$1') + .replace(/\[\[([^\]|#]*(?:#[^\]|]+)?)(?:\|([^\]]+))?\]\]/g, (_match, target, alias) => alias || target) + .replace(/`([^`]+)`/g, '$1') + .replace(/\*\*([^*]+)\*\*/g, '$1') + .replace(/__([^_]+)__/g, '$1') + .replace(/~~([^~]+)~~/g, '$1') + .replace(/(^|[^*])\*([^*]+)\*/g, '$1$2') + .replace(/(^|[^_])_([^_]+)_/g, '$1$2') + .replace(/<[^>]+>/g, '') + .replace(/\\([\\`*{}\[\]()#+\-.!_>~|])/g, '$1') + .trim(); +} + +function readHeadingText(source: string, from: number, to: number, nodeName: string): string { + const rawHeading = source.slice(from, to); + const isSetext = nodeName.startsWith('SetextHeading'); + const text = isSetext + ? rawHeading.split(/\r?\n/)[0] ?? '' + : rawHeading + .replace(/^\s{0,3}#{1,6}\s*/, '') + .replace(/\s+#+\s*$/, ''); + return stripInlineMarkdown(text); +} export function extractMarkdownOutline(source: string): DocumentOutlineItem[] { - const tokens = outlineParser.parse(prepareMarkdownSource(source), {}); + const tree = outlineParser.parse(source); + const cursor = tree.cursor(); const nextSlug = createSlugTracker(); const outline: DocumentOutlineItem[] = []; + const lineStarts = buildLineStarts(source); - for (let index = 0; index < tokens.length; index += 1) { - const heading = readHeadingProjection(tokens, index, nextSlug); - if (!heading) { - continue; + cursor.iterate((node) => { + const match = node.name.match(HEADING_NODE_PATTERN); + if (!match) { + return; } - outline.push(heading); - } + const level = Number.parseInt(match[1], 10); + const text = readHeadingText(source, node.from, node.to, node.name); + if (!text) { + return; + } + + outline.push({ + id: nextSlug(text), + text, + level, + line: lineNumberForOffset(lineStarts, node.from), + }); + }); return outline; } diff --git a/src/ui/file-preview/src/markdown/parser.ts b/src/ui/file-preview/src/markdown/parser.ts deleted file mode 100644 index 83888b6a..00000000 --- a/src/ui/file-preview/src/markdown/parser.ts +++ /dev/null @@ -1,85 +0,0 @@ -// markdown-it is intentionally typed locally here to avoid maintaining ambient module declarations. -import MarkdownIt from 'markdown-it'; -import type { MarkdownSlugTracker } from './slugify.js'; -import { rewriteWikiLinks } from './linking.js'; -import { extractInlineText } from './utils.js'; - -export interface MarkdownToken { - type?: string; - tag?: string; - map?: number[]; - children?: unknown; - content?: unknown; - attrSet?: (name: string, value: string) => void; - attrGet?: (name: string) => string | null; - attrs?: Array<[string, string]>; -} - -interface MarkdownItInstance { - render: (source: string, env?: Record) => string; - parse: (source: string, env?: Record) => MarkdownToken[]; - renderer: { - rules: Record string>; - }; -} - -type MarkdownItConstructor = new (options?: { - html?: boolean; - linkify?: boolean; - typographer?: boolean; - highlight?: (code: string, language: string) => string; -}) => MarkdownItInstance; - -export interface MarkdownHeadingProjection { - id: string; - text: string; - level: number; - line: number; -} - -const MarkdownItCtor = MarkdownIt as unknown as MarkdownItConstructor; - -export function createMarkdownIt(options: { - highlight?: (code: string, language: string) => string; -} = {}): MarkdownItInstance { - return new MarkdownItCtor({ - html: false, - linkify: true, - typographer: false, - ...(options.highlight ? { highlight: options.highlight } : {}), - }); -} - -export function prepareMarkdownSource(source: string): string { - return rewriteWikiLinks(source); -} - -export function readHeadingProjection( - tokens: MarkdownToken[], - index: number, - nextSlug: MarkdownSlugTracker -): MarkdownHeadingProjection | null { - const token = tokens[index]; - if (token?.type !== 'heading_open' || typeof token.tag !== 'string') { - return null; - } - - const level = Number.parseInt(token.tag.replace(/^h/i, ''), 10); - if (!Number.isFinite(level)) { - return null; - } - - const inlineToken = tokens[index + 1] as Record | undefined; - const text = extractInlineText(inlineToken).trim(); - if (!text) { - return null; - } - - const lineMap = Array.isArray(token.map) ? token.map : undefined; - return { - id: nextSlug(text), - text, - level, - line: typeof lineMap?.[0] === 'number' ? lineMap[0] + 1 : index + 1, - }; -} diff --git a/src/ui/file-preview/src/markdown/preview.ts b/src/ui/file-preview/src/markdown/preview.ts deleted file mode 100644 index a8364d63..00000000 --- a/src/ui/file-preview/src/markdown/preview.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { renderMarkdown } from '../components/markdown-renderer.js'; - -export function getRenderedMarkdownCopyText(content: string): string { - const html = renderMarkdown(content); - const normalizedHtml = html - .replace(/<\s*br\s*\/?>/gi, '\n') - .replace(/<\/p>/gi, '\n\n') - .replace(/<\/h[1-6]>/gi, '\n\n') - .replace(/<\/li>/gi, '\n') - .replace(/
  • /gi, '- ') - .replace(/<[^>]+>/g, ''); - - return normalizedHtml - .replace(/ /g, ' ') - .replace(/&/g, '&') - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/'/g, "'") - .replace(/"/g, '"') - .replace(/\n{3,}/g, '\n\n') - .trim(); -} - diff --git a/src/ui/file-preview/src/markdown/utils.ts b/src/ui/file-preview/src/markdown/utils.ts deleted file mode 100644 index a7a0e7ca..00000000 --- a/src/ui/file-preview/src/markdown/utils.ts +++ /dev/null @@ -1,17 +0,0 @@ -export function extractInlineText(token: Record | undefined): string { - if (!token) { - return ''; - } - - const children = Array.isArray(token.children) ? token.children : []; - if (children.length === 0) { - return typeof token.content === 'string' ? token.content : ''; - } - - return children.map((child) => { - if (typeof child.content === 'string') { - return child.content; - } - return ''; - }).join(''); -} diff --git a/src/ui/file-preview/src/model.ts b/src/ui/file-preview/src/model.ts index df5adc6e..98730cd0 100644 --- a/src/ui/file-preview/src/model.ts +++ b/src/ui/file-preview/src/model.ts @@ -6,11 +6,9 @@ export type RenderPayload = FilePreviewStructuredContent & { content: string }; export interface MarkdownWorkspaceState { filePath: string; - sourceContent: string; fullDocumentContent: string; draftContent: string; outline: DocumentOutlineItem[]; - mode: 'edit'; dirty: boolean; activeHeadingId: string | null; pendingAnchor: string | null; diff --git a/src/ui/file-preview/src/panel-actions.ts b/src/ui/file-preview/src/panel-actions.ts index 47051145..7b3703fa 100644 --- a/src/ui/file-preview/src/panel-actions.ts +++ b/src/ui/file-preview/src/panel-actions.ts @@ -160,7 +160,6 @@ export function attachPanelActions(options: { return; } - const currentContent = stripReadStatusLine(options.payload.content); const loadLines = async (button: HTMLButtonElement, direction: 'before' | 'after'): Promise => { const originalText = button.textContent; button.textContent = 'Loading…'; @@ -180,24 +179,11 @@ export function attachPanelActions(options: { const newText = extractToolText(result); if (newText && typeof newText === 'string') { - const cleanNew = stripReadStatusLine(newText); - const merged = direction === 'before' - ? `${cleanNew}${cleanNew.endsWith('\n') ? '' : '\n'}${currentContent}` - : `${currentContent}${currentContent.endsWith('\n') ? '' : '\n'}${cleanNew}`; - - const newFrom = direction === 'before' ? 1 : range.fromLine; - const newTo = direction === 'after' ? range.totalLines : range.toLine; - const lineCount = newTo - newFrom + 1; - const remaining = range.totalLines - newTo; - const isStillPartial = newFrom > 1 || newTo < range.totalLines; - const statusLine = isStillPartial - ? `[Reading ${lineCount} lines from ${newFrom === 1 ? 'start' : `line ${newFrom}`} (total: ${range.totalLines} lines, ${remaining} remaining)]\n` - : ''; - - options.render({ - ...options.payload, - content: statusLine + merged, - }, options.htmlMode, options.getIsExpanded()); + options.render( + options.markdownController.expandPartialPayload(options.payload, direction, newText), + options.htmlMode, + options.getIsExpanded() + ); return; } } catch { diff --git a/src/ui/styles/apps/file-preview.css b/src/ui/styles/apps/file-preview.css index afdfe7d8..45ae19b9 100644 --- a/src/ui/styles/apps/file-preview.css +++ b/src/ui/styles/apps/file-preview.css @@ -397,28 +397,41 @@ html:has(.fullscreen) #app { max-width: none; } -.markdown-editor-context-menu { +.markdown-editor-loading { + display: flex; + align-items: center; + justify-content: center; + min-height: min(50vh, 480px); + color: var(--markdown-muted); + font-size: 14px; +} + +.markdown-editor-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding: 6px; - background: #171717; - border: 1px solid color-mix(in srgb, var(--border) 34%, transparent); - border-radius: 10px; position: absolute; - z-index: 20; - box-shadow: 0 12px 32px rgba(0, 0, 0, 0.28); + width: fit-content; + margin: 0; + z-index: 22; + background: color-mix(in srgb, var(--panel) 92%, transparent); + border: 1px solid color-mix(in srgb, var(--border) 55%, transparent); + border-radius: 10px; + box-shadow: 0 10px 30px rgba(15, 23, 42, 0.12); + backdrop-filter: blur(12px); + transform: translateZ(0); } -.markdown-editor-context-menu[hidden] { +.markdown-editor-toolbar[hidden] { display: none; } .markdown-format-button { border: none; background: transparent; - color: rgba(255, 255, 255, 0.9); + color: var(--text); border-radius: 6px; width: 32px; height: 32px; @@ -433,11 +446,149 @@ html:has(.fullscreen) #app { } .markdown-format-button:hover { - background: rgba(255, 255, 255, 0.14); + background: color-mix(in srgb, var(--panel-subtle) 72%, transparent); } -.markdown-format-button--swatch { - padding: 0; +.markdown-editor-root .cm-editor { + min-height: min(70vh, 800px); + width: 100%; + color: var(--markdown-text); + background: transparent; + outline: none; +} + +.markdown-editor-root .cm-focused { + outline: none; +} + +.markdown-editor-root .cm-scroller { + min-height: min(70vh, 800px); + overflow: auto; + font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); +} + +.markdown-editor-root .cm-content { + max-width: 780px; + min-height: min(70vh, 800px); + margin: 0 auto; + padding: 30px 28px 42px; + line-height: 1.78; + caret-color: var(--text); +} + +.markdown-editor-root .cm-line { + padding: 0 2px; + color: var(--markdown-muted); +} + +.markdown-editor-root .cm-md-hidden-marker { + display: inline-block; + width: 0; + min-width: 0; + max-width: 0; + overflow: hidden; + opacity: 0; + white-space: pre; +} + +.markdown-editor-root .cm-md-strong-text { + color: var(--markdown-text); + font-weight: 700; +} + +.markdown-editor-root .cm-md-emphasis-text { + font-style: italic; +} + +.markdown-editor-root .cm-md-strike-text { + text-decoration: line-through; +} + +.markdown-editor-root .cm-md-link-text { + color: inherit; + text-decoration: underline; + text-decoration-color: color-mix(in srgb, var(--text-secondary) 45%, transparent); + text-underline-offset: 0.18em; +} + +.markdown-editor-root .cm-md-inline-code-text { + font-family: var(--font-mono, ui-monospace, monospace); + font-size: .9em; + background: var(--inline-code-bg); + color: var(--inline-code-text); + border: 1px solid var(--inline-code-border); + border-radius: 6px; + padding: 1px 5px; +} + +.markdown-editor-root .cm-md-heading { + color: var(--markdown-text); + letter-spacing: -0.015em; +} + +.markdown-editor-root .cm-md-heading-1 { + padding-top: 4px; + padding-bottom: 12px; + font-size: 28px; + line-height: 1.25; + font-weight: 700; +} + +.markdown-editor-root .cm-md-heading-2 { + padding-top: 22px; + padding-bottom: 8px; + font-size: 22px; + line-height: 1.3; + font-weight: 650; +} + +.markdown-editor-root .cm-md-heading-3 { + padding-top: 16px; + padding-bottom: 6px; + font-size: 18px; + line-height: 1.35; + font-weight: 620; +} + +.markdown-editor-root .cm-md-quote { + margin: 8px 0; + border-left: 3px solid color-mix(in srgb, var(--border) 70%, transparent); + color: var(--markdown-muted); + font-style: italic; +} + +.markdown-editor-root .cm-md-list { + line-height: 1.65; + position: relative; + padding-left: 1.15em; +} + +.markdown-editor-root .cm-md-bullet { + color: var(--markdown-text); + line-height: inherit; +} + +.markdown-editor-root .cm-md-rule { + color: color-mix(in srgb, var(--markdown-muted) 45%, transparent); + letter-spacing: 0.12em; +} + +.markdown-editor-root .cm-activeLine { + background: color-mix(in srgb, var(--panel-subtle) 42%, transparent); +} + +.markdown-editor-root .cm-selectionBackground, +.markdown-editor-root .cm-content ::selection { + background: color-mix(in srgb, var(--accent) 22%, transparent); +} + +.markdown-editor-root .cm-cursor { + border-left-color: var(--text); +} + +.markdown-editor-root .cm-matchingBracket, +.markdown-editor-root .cm-nonmatchingBracket { + background: color-mix(in srgb, var(--panel-subtle) 70%, transparent); } .markdown-format-size { @@ -447,14 +598,14 @@ html:has(.fullscreen) #app { height: 32px; border-radius: 6px; overflow: hidden; - background: rgba(255, 255, 255, 0.08); + background: color-mix(in srgb, var(--panel-subtle) 72%, transparent); } .markdown-format-size select { height: 32px; border: none; background: transparent; - color: rgba(255, 255, 255, 0.9); + color: var(--text); padding: 0 8px; font: inherit; font-size: 12px; @@ -464,7 +615,7 @@ html:has(.fullscreen) #app { .markdown-format-sep { width: 1px; height: 20px; - background: rgba(255, 255, 255, 0.14); + background: color-mix(in srgb, var(--border) 65%, transparent); } .markdown-link-modal { @@ -781,7 +932,7 @@ html:has(.fullscreen) #app { .markdown-link-popover { position: absolute; - z-index: 10; + z-index: 24; display: flex; align-items: center; gap: 2px; @@ -817,18 +968,6 @@ html:has(.fullscreen) #app { position: relative; } -.markdown-editor-surface { - flex: 1 1 auto; - min-height: min(70vh, 760px); - outline: none; -} - -.markdown-editor-surface--markdown { - max-width: none; - margin: 0; - padding: 22px 24px 28px; -} - .markdown-editor-textarea { width: 100%; min-height: min(70vh, 760px); @@ -1010,110 +1149,6 @@ html:has(.fullscreen) #app { .markdown p { margin: 1em 0; } .markdown ul, .markdown ol { margin: .8em 0; padding-left: 1.4rem; } -.markdown-doc { - max-width: 720px; - margin: 0 auto; - padding: 32px 28px 36px; - line-height: 1.8; - color: var(--markdown-text); - font-size: 16px; - background: transparent; - border: 0; - border-radius: 0; -} - -.markdown-doc h1 { - margin: 0 0 24px; - font-size: 28px; - font-weight: 600; - line-height: 1.25; - letter-spacing: -0.02em; - color: var(--markdown-text); -} - -.markdown-doc h2 { - margin: 36px 0 16px; - font-size: 22px; - font-weight: 600; - line-height: 1.3; - letter-spacing: -0.01em; - color: var(--markdown-text); -} - -.markdown-doc h3 { - margin: 28px 0 12px; - font-size: 18px; - font-weight: 600; - line-height: 1.35; - color: var(--markdown-text); -} - -.markdown-doc p { - font-size: 15px; - line-height: 1.75; - color: var(--markdown-muted); - margin: 0 0 1.2em; -} - -.markdown-doc li { - font-size: 15px; - line-height: 1.7; - color: var(--markdown-muted); - margin-bottom: 0.3em; -} - -.markdown-doc ul, .markdown-doc ol { - margin: 8px 0 20px; - padding-left: 1.3em; -} - -.markdown-doc blockquote { - margin: 20px 0; - padding: 2px 0 2px 20px; - border-left: 3px solid color-mix(in srgb, var(--border) 70%, transparent); - color: var(--markdown-muted); - font-style: italic; -} - -.markdown-doc hr { - border: none; - border-top: 1px solid color-mix(in srgb, var(--border) 40%, transparent); - margin: 32px 0; -} - -.markdown-doc img { - max-width: 100%; - border-radius: 8px; - margin: 16px 0; -} - -.markdown-doc code:not(.hljs) { - font-family: var(--font-mono, ui-monospace, monospace); - font-size: .9em; - background: var(--inline-code-bg); - color: var(--inline-code-text); - border: 1px solid var(--inline-code-border); - border-radius: 6px; - padding: 2px 6px; -} - -.markdown-doc .code-viewer { - margin: 14px 0; - border: 1px solid color-mix(in srgb, var(--border) 50%, transparent); - border-radius: 10px; -} - -.markdown-doc a { - color: inherit; - text-decoration: underline; - text-decoration-color: color-mix(in srgb, var(--text-secondary) 45%, transparent); - text-underline-offset: 0.18em; -} - -.markdown-doc a:hover { - text-decoration-color: currentcolor; -} - /* ── HTML frame ── */ .html-rendered-frame { @@ -1171,14 +1206,6 @@ html:has(.fullscreen) #app { min-height: 58vh; padding: 14px; } - .markdown-editor-surface--markdown { - padding: 16px; - } - .markdown-doc { padding: 16px; } - .markdown-doc h1 { font-size: 27px; } - .markdown-doc h2 { font-size: 22px; } - .markdown-doc h3 { font-size: 18px; } - .markdown-doc p, .markdown-doc li { font-size: 15px; } } /* ── Directory tree ── */ diff --git a/test/test-markdown-preview.js b/test/test-markdown-preview.js index 5f48ebb0..fe62d66c 100644 --- a/test/test-markdown-preview.js +++ b/test/test-markdown-preview.js @@ -1,10 +1,8 @@ import assert from 'assert'; import { pathToFileURL } from 'url'; -import { renderMarkdown } from '../dist/ui/file-preview/src/components/markdown-renderer.js'; -import { resolveMarkdownLink, rewriteWikiLinks } from '../dist/ui/file-preview/src/markdown/linking.js'; +import { resolveMarkdownLink } from '../dist/ui/file-preview/src/markdown/linking.js'; import { extractMarkdownOutline } from '../dist/ui/file-preview/src/markdown/outline.js'; -import { getRenderedMarkdownCopyText } from '../dist/ui/file-preview/src/markdown/preview.js'; import { renderMarkdownEditorShell } from '../dist/ui/file-preview/src/markdown/editor.js'; import { createMarkdownController } from '../dist/ui/file-preview/src/markdown/controller.js'; import { createSlugTracker, slugifyMarkdownHeading } from '../dist/ui/file-preview/src/markdown/slugify.js'; @@ -125,40 +123,28 @@ async function testLinkResolution() { console.log('✓ anchors, file links, absolute paths, external URLs, and wiki links resolve correctly'); } -async function testWikiRewriteAndRendering() { - console.log('\n--- Test 4: wiki link rewrite and rendering ---'); +async function testOutlineFromMarkdownSource() { + console.log('\n--- Test 4: source-backed outline text ---'); - const rewritten = rewriteWikiLinks('See [[Meeting Notes#Action Items|Actions]] and `[[Code]]`.'); - assert.ok(rewritten.includes('[Actions](./Meeting%20Notes.md#action-items "mcp-wiki:'), 'Wiki links should rewrite to markdown links with round-trip metadata'); - assert.ok(rewritten.includes('`[[Code]]`'), 'Inline code should remain untouched'); - const multiTickRewrite = rewriteWikiLinks('Use ``[[Code]]`` and `code [[still-not-link]]` samples.'); - assert.ok(multiTickRewrite.includes('``[[Code]]``'), 'Multi-backtick inline code should remain untouched'); - assert.ok(multiTickRewrite.includes('`code [[still-not-link]]`'), 'Wiki links inside inline code should stay literal'); - - const fencedRewrite = rewriteWikiLinks([ - '````md', - '```', - '[[Inside Code]]', - '````', - '[[Outside Code]]', - ].join('\n')); - assert.ok(fencedRewrite.includes('[[Inside Code]]'), 'Long code fences should remain open until a matching-length close fence appears'); - assert.ok(fencedRewrite.includes('[Outside Code](./Outside%20Code.md "mcp-wiki:'), 'Wiki links outside closed fences should still rewrite'); - - const html = renderMarkdown([ + const outline = extractMarkdownOutline([ '# Title', '## Details', '## Details', '', - 'Go to [[Meeting Notes#Action Items|Actions]].', + '### Linked [Section](#details)', ].join('\n')); - assert.ok(html.includes('id="title"'), 'Rendered markdown should include slugged heading ids'); - assert.ok(html.includes('id="details-2"'), 'Duplicate headings should receive unique ids'); - assert.ok(html.includes('href="./Meeting%20Notes.md#action-items"'), 'Rendered markdown should keep rewritten wiki links'); - assert.ok(html.includes('data-wiki-link="[[Meeting Notes#Action Items|Actions]]"'), 'Rendered markdown should preserve original wiki-link syntax for editing'); + assert.deepStrictEqual( + outline.map((item) => ({ id: item.id, text: item.text, level: item.level })), + [ + { id: 'title', text: 'Title', level: 1 }, + { id: 'details', text: 'Details', level: 2 }, + { id: 'details-2', text: 'Details', level: 2 }, + { id: 'linked-section', text: 'Linked Section', level: 3 }, + ], + ); - console.log('✓ markdown rendering uses preview heading ids and rewritten wiki links'); + console.log('✓ outline text strips inline markdown and dedupes heading slugs'); } async function testFailedSaveResyncsEditBaseline() { @@ -274,7 +260,6 @@ async function testFailedSaveResyncsEditBaseline() { 'omega', '', ].join('\n'), 'The simulated disk should keep the partial save'); - assert.strictEqual(state.sourceContent, diskContent, 'Source content should match the latest disk contents'); assert.strictEqual(state.fullDocumentContent, diskContent, 'The full document baseline should match the latest disk contents'); assert.strictEqual(state.draftContent, [ 'alpha updated', @@ -370,6 +355,80 @@ async function testSuccessfulSaveResetsUndoBaseline() { console.log('✓ successful saves clear undo state against the latest saved content'); } +async function testInFlightSaveKeepsNewerDraftDirty() { + console.log('\n--- Test 13: in-flight saves keep newer drafts dirty ---'); + + const payload = { + fileName: 'notes.md', + filePath: '/Users/tester/docs/notes.md', + fileType: 'markdown', + content: 'alpha\n', + }; + + const previousWindow = globalThis.window; + const previousDocument = globalThis.document; + globalThis.window = { setTimeout: globalThis.setTimeout }; + globalThis.document = { + getElementById: () => null, + querySelector: () => null, + }; + + let resolveEditBlock; + let savedString = null; + + const controller = createMarkdownController({ + callTool: async (name, args) => { + if (name !== 'edit_block') { + throw new Error(`Unexpected tool call: ${name}`); + } + savedString = args.new_string; + await new Promise((resolve) => { + resolveEditBlock = resolve; + }); + return { + content: [{ type: 'text', text: 'Successfully applied 1 edit(s) to notes.md' }], + structuredContent: { + fileName: payload.fileName, + filePath: payload.filePath, + fileType: payload.fileType, + }, + }; + }, + getAvailableDisplayModes: () => ['inline', 'fullscreen'], + getCurrentDisplayMode: () => 'inline', + getCurrentPayload: () => payload, + setExpanded: () => {}, + storePayloadOverride: () => {}, + rerender: () => {}, + updateSaveStatus: () => {}, + }); + + try { + const state = controller.getState(payload); + state.draftContent = 'beta\n'; + state.dirty = true; + + const savePromise = controller.saveDocument(); + await new Promise((resolve) => setTimeout(resolve, 0)); + + state.draftContent = 'gamma\n'; + state.dirty = true; + resolveEditBlock(); + await savePromise; + + assert.strictEqual(savedString, 'beta\n', 'The in-flight save should write the original save snapshot'); + assert.strictEqual(state.fullDocumentContent, 'beta\n', 'The saved snapshot should become the disk baseline'); + assert.strictEqual(state.draftContent, 'gamma\n', 'Newer local edits should remain in the draft'); + assert.strictEqual(state.dirty, true, 'Newer local edits should stay dirty after the older save completes'); + } finally { + controller.disposeHandles(); + globalThis.window = previousWindow; + globalThis.document = previousDocument; + } + + console.log('✓ in-flight saves keep newer local edits dirty'); +} + async function testFullscreenWorkspaceHelpers() { console.log('\n--- Test 6: fullscreen document helpers ---'); @@ -399,10 +458,22 @@ async function testFullscreenWorkspaceHelpers() { async function testCopyFormatsAndEditorShell() { console.log('\n--- Test 8: copy formats and editor shell ---'); - const renderedCopy = getRenderedMarkdownCopyText('# Title\n\n- First\n- Second\n\n**Bold** text'); - assert.ok(renderedCopy.includes('Title'), 'Rendered copy should preserve heading text'); - assert.ok(renderedCopy.includes('- First'), 'Rendered copy should preserve list text'); - assert.ok(renderedCopy.includes('Bold text'), 'Rendered copy should flatten formatted inline text'); + const copySource = '# Title\n\n- First\n- Second\n\n**Bold** text'; + const controller = createMarkdownController({ + getAvailableDisplayModes: () => ['inline', 'fullscreen'], + getCurrentDisplayMode: () => 'inline', + getCurrentPayload: () => undefined, + setExpanded: () => {}, + storePayloadOverride: () => {}, + rerender: () => {}, + updateSaveStatus: () => {}, + }); + assert.strictEqual(controller.getCopyText({ + fileName: 'notes.md', + filePath: '/Users/tester/docs/notes.md', + fileType: 'markdown', + content: copySource, + }), copySource, 'Copy should preserve markdown source exactly'); const markdownShell = renderMarkdownEditorShell({ view: 'markdown', @@ -424,7 +495,7 @@ async function testCopyFormatsAndEditorShell() { assert.ok(!rawShell.includes('markdown-editor-context-menu'), 'Raw mode should not include markdown formatting context controls'); assert.ok(!rawShell.includes('data-format="bold"'), 'Raw mode should not include formatting buttons'); - console.log('✓ raw/rendered copy support and mode-specific editor shell are wired'); + console.log('✓ source copy support and mode-specific editor shell are wired'); } async function testPartialDocumentBecomesNewEditBaseline() { @@ -468,7 +539,6 @@ async function testPartialDocumentBecomesNewEditBaseline() { await controller.requestEditMode(partialPayload); const nextState = controller.getState(currentPayload); - assert.strictEqual(nextState.mode, 'edit'); assert.strictEqual(nextState.fullDocumentContent, fullContent, 'The full document should replace the truncated edit baseline'); assert.strictEqual(nextState.draftContent, fullContent, 'Draft content should start from the full document'); assert.strictEqual(controller.isUndoAvailable(nextState), false, 'Undo should stay disabled until the user edits the full document'); @@ -525,13 +595,14 @@ export default async function runTests() { await testSlugGeneration(); await testOutlineExtraction(); await testLinkResolution(); - await testWikiRewriteAndRendering(); + await testOutlineFromMarkdownSource(); await testFullscreenWorkspaceHelpers(); await testCopyFormatsAndEditorShell(); await testPartialDocumentBecomesNewEditBaseline(); await testRefreshDoesNotMisclassifyMarkdownContentAsDeletion(); await testFailedSaveResyncsEditBaseline(); await testSuccessfulSaveResetsUndoBaseline(); + await testInFlightSaveKeepsNewerDraftDirty(); console.log('\n✅ Markdown preview tests passed!'); return true; } catch (error) {