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) {