diff --git a/package-lock.json b/package-lock.json index 3d79247f6d..597ffa23ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,15 @@ "license": "Apache-2.0", "dependencies": { "diff": "^8.0.3", + "prosemirror-commands": "^1.7.1", + "prosemirror-dropcursor": "^1.8.2", + "prosemirror-gapcursor": "^1.4.1", + "prosemirror-history": "^1.5.0", + "prosemirror-inputrules": "^1.5.1", + "prosemirror-keymap": "^1.2.3", + "prosemirror-schema-list": "^1.5.1", + "prosemirror-state": "^1.4.4", + "prosemirror-view": "^1.41.7", "sortablejs": "^1.15.6" }, "devDependencies": { @@ -50,7 +59,6 @@ "parse-css-color": "^0.2.1", "postal-mime": "^2.7.3", "prettier": "^3.5.0", - "prosemirror-example-setup": "^1.2.3", "prosemirror-markdown": "^1.13.2", "prosemirror-model": ">=1.22.1", "prosemirror-schema-basic": "^1.2.4", @@ -6022,12 +6030,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/crelt": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", - "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", - "dev": true - }, "node_modules/cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -12736,8 +12738,7 @@ "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==", - "dev": true + "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==" }, "node_modules/own-keys": { "version": "1.0.1", @@ -13359,49 +13360,29 @@ } }, "node_modules/prosemirror-commands": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.5.2.tgz", - "integrity": "sha512-hgLcPaakxH8tu6YvVAaILV2tXYsW3rAdDR8WNkeKGcgeMVQg3/TMhPdVoh7iAmfgVjZGtcOSjKiQaoeKjzd2mQ==", - "dev": true, + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", + "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==", "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.0.0" + "prosemirror-transform": "^1.10.2" } }, "node_modules/prosemirror-dropcursor": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.1.tgz", - "integrity": "sha512-M30WJdJZLyXHi3N8vxN6Zh5O8ZBbQCz0gURTfPmTIBNQ5pxrdU7A58QkNqfa98YEjSAL1HUyyU34f6Pm5xBSGw==", - "dev": true, + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz", + "integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==", "dependencies": { "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.1.0", "prosemirror-view": "^1.1.0" } }, - "node_modules/prosemirror-example-setup": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/prosemirror-example-setup/-/prosemirror-example-setup-1.2.3.tgz", - "integrity": "sha512-+hXZi8+xbFvYM465zZH3rdZ9w7EguVKmUYwYLZjIJIjPK+I0nPTwn8j0ByW2avchVczRwZmOJGNvehblyIerSQ==", - "dev": true, - "dependencies": { - "prosemirror-commands": "^1.0.0", - "prosemirror-dropcursor": "^1.0.0", - "prosemirror-gapcursor": "^1.0.0", - "prosemirror-history": "^1.0.0", - "prosemirror-inputrules": "^1.0.0", - "prosemirror-keymap": "^1.0.0", - "prosemirror-menu": "^1.0.0", - "prosemirror-schema-list": "^1.0.0", - "prosemirror-state": "^1.0.0" - } - }, "node_modules/prosemirror-gapcursor": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.2.tgz", - "integrity": "sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==", - "dev": true, + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.1.tgz", + "integrity": "sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==", "dependencies": { "prosemirror-keymap": "^1.0.0", "prosemirror-model": "^1.0.0", @@ -13410,10 +13391,9 @@ } }, "node_modules/prosemirror-history": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.3.2.tgz", - "integrity": "sha512-/zm0XoU/N/+u7i5zepjmZAEnpvjDtzoPWW6VmKptcAnPadN/SStsBjMImdCEbb3seiNTpveziPTIrXQbHLtU1g==", - "dev": true, + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz", + "integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==", "dependencies": { "prosemirror-state": "^1.2.2", "prosemirror-transform": "^1.0.0", @@ -13422,20 +13402,18 @@ } }, "node_modules/prosemirror-inputrules": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.4.0.tgz", - "integrity": "sha512-6ygpPRuTJ2lcOXs9JkefieMst63wVJBgHZGl5QOytN7oSZs3Co/BYbc3Yx9zm9H37Bxw8kVzCnDsihsVsL4yEg==", - "dev": true, + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz", + "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==", "dependencies": { "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.0.0" } }, "node_modules/prosemirror-keymap": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.2.tgz", - "integrity": "sha512-EAlXoksqC6Vbocqc0GtzCruZEzYgrn+iiGnNjsJsH4mrnIGex4qbLdWWNza3AW5W36ZRrlBID0eM6bdKH4OStQ==", - "dev": true, + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", + "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==", "dependencies": { "prosemirror-state": "^1.0.0", "w3c-keyname": "^2.2.0" @@ -13453,23 +13431,10 @@ "prosemirror-model": "^1.25.0" } }, - "node_modules/prosemirror-menu": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.4.tgz", - "integrity": "sha512-S/bXlc0ODQup6aiBbWVsX/eM+xJgCTAfMq/nLqaO5ID/am4wS0tTCIkzwytmao7ypEtjj39i7YbJjAgO20mIqA==", - "dev": true, - "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.22.3", "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.22.3.tgz", "integrity": "sha512-V4XCysitErI+i0rKFILGt/xClnFJaohe/wrrlT2NSZ+zk8ggQfDH4x2wNK7Gm0Hp4CIoWizvXFP7L9KMaCuI0Q==", - "dev": true, "dependencies": { "orderedmap": "^2.0.0" } @@ -13485,10 +13450,9 @@ } }, "node_modules/prosemirror-schema-list": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.3.0.tgz", - "integrity": "sha512-Hz/7gM4skaaYfRPNgr421CU4GSwotmEwBVvJh5ltGiffUJwm7C8GfN/Bc6DR1EKEp5pDKhODmdXXyi9uIsZl5A==", - "dev": true, + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz", + "integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==", "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-state": "^1.0.0", @@ -13496,10 +13460,9 @@ } }, "node_modules/prosemirror-state": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz", - "integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==", - "dev": true, + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", + "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-transform": "^1.0.0", @@ -13524,17 +13487,14 @@ "version": "1.10.2", "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.2.tgz", "integrity": "sha512-2iUq0wv2iRoJO/zj5mv8uDUriOHWzXRnOTVgCzSXnktS/2iQRa3UUQwVlkBlYZFtygw6Nh1+X4mGqoYBINn5KQ==", - "dev": true, "dependencies": { "prosemirror-model": "^1.21.0" } }, "node_modules/prosemirror-view": { - "version": "1.37.2", - "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.37.2.tgz", - "integrity": "sha512-ApcyrfV/cRcaL65on7TQcfWElwLyOgIjnIynfAuV+fIdlpbSvSWRwfuPaH7T5mo4AbO/FID29qOtjiDIKGWyog==", - "dev": true, - "license": "MIT", + "version": "1.41.7", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.7.tgz", + "integrity": "sha512-jUwKNCEIGiqdvhlS91/2QAg21e4dfU5bH2iwmSDQeosXJgKF7smG0YSplOWK0cjSNgIqXe7VXqo7EIfUFJdt3w==", "dependencies": { "prosemirror-model": "^1.20.0", "prosemirror-state": "^1.0.0", @@ -14691,8 +14651,7 @@ "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==", - "dev": true + "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==" }, "node_modules/run-parallel": { "version": "1.2.0", @@ -17389,8 +17348,7 @@ "node_modules/w3c-keyname": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", - "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", - "dev": true + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" }, "node_modules/walker": { "version": "1.0.8", @@ -21827,12 +21785,6 @@ } } }, - "crelt": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", - "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", - "dev": true - }, "cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -26511,8 +26463,7 @@ "orderedmap": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", - "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", - "dev": true + "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==" }, "own-keys": { "version": "1.0.1", @@ -26935,49 +26886,29 @@ } }, "prosemirror-commands": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.5.2.tgz", - "integrity": "sha512-hgLcPaakxH8tu6YvVAaILV2tXYsW3rAdDR8WNkeKGcgeMVQg3/TMhPdVoh7iAmfgVjZGtcOSjKiQaoeKjzd2mQ==", - "dev": true, + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", + "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==", "requires": { "prosemirror-model": ">=1.22.1", "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.0.0" + "prosemirror-transform": "^1.10.2" } }, "prosemirror-dropcursor": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.1.tgz", - "integrity": "sha512-M30WJdJZLyXHi3N8vxN6Zh5O8ZBbQCz0gURTfPmTIBNQ5pxrdU7A58QkNqfa98YEjSAL1HUyyU34f6Pm5xBSGw==", - "dev": true, + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz", + "integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==", "requires": { "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.1.0", "prosemirror-view": "^1.1.0" } }, - "prosemirror-example-setup": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/prosemirror-example-setup/-/prosemirror-example-setup-1.2.3.tgz", - "integrity": "sha512-+hXZi8+xbFvYM465zZH3rdZ9w7EguVKmUYwYLZjIJIjPK+I0nPTwn8j0ByW2avchVczRwZmOJGNvehblyIerSQ==", - "dev": true, - "requires": { - "prosemirror-commands": "^1.0.0", - "prosemirror-dropcursor": "^1.0.0", - "prosemirror-gapcursor": "^1.0.0", - "prosemirror-history": "^1.0.0", - "prosemirror-inputrules": "^1.0.0", - "prosemirror-keymap": "^1.0.0", - "prosemirror-menu": "^1.0.0", - "prosemirror-schema-list": "^1.0.0", - "prosemirror-state": "^1.0.0" - } - }, "prosemirror-gapcursor": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.2.tgz", - "integrity": "sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==", - "dev": true, + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.1.tgz", + "integrity": "sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==", "requires": { "prosemirror-keymap": "^1.0.0", "prosemirror-model": ">=1.22.1", @@ -26986,10 +26917,9 @@ } }, "prosemirror-history": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.3.2.tgz", - "integrity": "sha512-/zm0XoU/N/+u7i5zepjmZAEnpvjDtzoPWW6VmKptcAnPadN/SStsBjMImdCEbb3seiNTpveziPTIrXQbHLtU1g==", - "dev": true, + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz", + "integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==", "requires": { "prosemirror-state": "^1.2.2", "prosemirror-transform": "^1.0.0", @@ -26998,20 +26928,18 @@ } }, "prosemirror-inputrules": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.4.0.tgz", - "integrity": "sha512-6ygpPRuTJ2lcOXs9JkefieMst63wVJBgHZGl5QOytN7oSZs3Co/BYbc3Yx9zm9H37Bxw8kVzCnDsihsVsL4yEg==", - "dev": true, + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz", + "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==", "requires": { "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.0.0" } }, "prosemirror-keymap": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.2.tgz", - "integrity": "sha512-EAlXoksqC6Vbocqc0GtzCruZEzYgrn+iiGnNjsJsH4mrnIGex4qbLdWWNza3AW5W36ZRrlBID0eM6bdKH4OStQ==", - "dev": true, + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", + "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==", "requires": { "prosemirror-state": "^1.0.0", "w3c-keyname": "^2.2.0" @@ -27028,23 +26956,10 @@ "prosemirror-model": ">=1.22.1" } }, - "prosemirror-menu": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.4.tgz", - "integrity": "sha512-S/bXlc0ODQup6aiBbWVsX/eM+xJgCTAfMq/nLqaO5ID/am4wS0tTCIkzwytmao7ypEtjj39i7YbJjAgO20mIqA==", - "dev": true, - "requires": { - "crelt": "^1.0.0", - "prosemirror-commands": "^1.0.0", - "prosemirror-history": "^1.0.0", - "prosemirror-state": "^1.0.0" - } - }, "prosemirror-model": { "version": "1.22.3", "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.22.3.tgz", "integrity": "sha512-V4XCysitErI+i0rKFILGt/xClnFJaohe/wrrlT2NSZ+zk8ggQfDH4x2wNK7Gm0Hp4CIoWizvXFP7L9KMaCuI0Q==", - "dev": true, "requires": { "orderedmap": "^2.0.0" } @@ -27059,10 +26974,9 @@ } }, "prosemirror-schema-list": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.3.0.tgz", - "integrity": "sha512-Hz/7gM4skaaYfRPNgr421CU4GSwotmEwBVvJh5ltGiffUJwm7C8GfN/Bc6DR1EKEp5pDKhODmdXXyi9uIsZl5A==", - "dev": true, + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz", + "integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==", "requires": { "prosemirror-model": ">=1.22.1", "prosemirror-state": "^1.0.0", @@ -27070,10 +26984,9 @@ } }, "prosemirror-state": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz", - "integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==", - "dev": true, + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", + "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", "requires": { "prosemirror-model": ">=1.22.1", "prosemirror-transform": "^1.0.0", @@ -27097,16 +27010,14 @@ "version": "1.10.2", "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.2.tgz", "integrity": "sha512-2iUq0wv2iRoJO/zj5mv8uDUriOHWzXRnOTVgCzSXnktS/2iQRa3UUQwVlkBlYZFtygw6Nh1+X4mGqoYBINn5KQ==", - "dev": true, "requires": { "prosemirror-model": ">=1.22.1" } }, "prosemirror-view": { - "version": "1.37.2", - "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.37.2.tgz", - "integrity": "sha512-ApcyrfV/cRcaL65on7TQcfWElwLyOgIjnIynfAuV+fIdlpbSvSWRwfuPaH7T5mo4AbO/FID29qOtjiDIKGWyog==", - "dev": true, + "version": "1.41.7", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.7.tgz", + "integrity": "sha512-jUwKNCEIGiqdvhlS91/2QAg21e4dfU5bH2iwmSDQeosXJgKF7smG0YSplOWK0cjSNgIqXe7VXqo7EIfUFJdt3w==", "requires": { "prosemirror-model": ">=1.22.1", "prosemirror-state": "^1.0.0", @@ -27947,8 +27858,7 @@ "rope-sequence": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", - "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==", - "dev": true + "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==" }, "run-parallel": { "version": "1.2.0", @@ -29679,8 +29589,7 @@ "w3c-keyname": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", - "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", - "dev": true + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" }, "walker": { "version": "1.0.8", diff --git a/package.json b/package.json index 5e911fd560..7846df889c 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,6 @@ "parse-css-color": "^0.2.1", "postal-mime": "^2.7.3", "prettier": "^3.5.0", - "prosemirror-example-setup": "^1.2.3", "prosemirror-markdown": "^1.13.2", "prosemirror-model": ">=1.22.1", "prosemirror-schema-basic": "^1.2.4", @@ -126,6 +125,15 @@ "packageManager": "npm@>=10.9", "dependencies": { "diff": "^8.0.3", + "prosemirror-commands": "^1.7.1", + "prosemirror-dropcursor": "^1.8.2", + "prosemirror-gapcursor": "^1.4.1", + "prosemirror-history": "^1.5.0", + "prosemirror-inputrules": "^1.5.1", + "prosemirror-keymap": "^1.2.3", + "prosemirror-schema-list": "^1.5.1", + "prosemirror-state": "^1.4.4", + "prosemirror-view": "^1.41.7", "sortablejs": "^1.15.6" } } diff --git a/src/components/text-editor/prosemirror-adapter/menu/menu-commands.ts b/src/components/text-editor/prosemirror-adapter/menu/menu-commands.ts index 8c79a826c2..56bcdce529 100644 --- a/src/components/text-editor/prosemirror-adapter/menu/menu-commands.ts +++ b/src/components/text-editor/prosemirror-adapter/menu/menu-commands.ts @@ -1,7 +1,24 @@ -import { toggleMark, setBlockType, wrapIn, lift } from 'prosemirror-commands'; +import { + chainCommands, + exitCode, + joinDown, + joinUp, + lift, + selectParentNode, + setBlockType, + toggleMark, + wrapIn, +} from 'prosemirror-commands'; +import { undo, redo } from 'prosemirror-history'; +import { undoInputRule } from 'prosemirror-inputrules'; import { Schema, MarkType, NodeType, Attrs } from 'prosemirror-model'; -import { findWrapping, liftTarget } from 'prosemirror-transform'; +import { + splitListItem, + liftListItem, + sinkListItem, +} from 'prosemirror-schema-list'; import { Command, EditorState, TextSelection } from 'prosemirror-state'; +import { findWrapping, liftTarget } from 'prosemirror-transform'; import { EditorMenuTypes, EditorTextLink, LevelMapping } from './types'; import { getLinkAttributes } from '../plugins/link/utils'; @@ -295,6 +312,61 @@ const createListCommand = ( return command; }; +const createSplitListItemCommand = (schema: Schema): CommandWithActive => { + const type = schema.nodes.list_item; + + return splitListItem(type); +}; + +const createLiftListItemCommand = (schema: Schema): CommandWithActive => { + const type = schema.nodes.list_item; + + return liftListItem(type); +}; + +const createSinkListItemCommand = (schema: Schema): CommandWithActive => { + const type = schema.nodes.list_item; + + return sinkListItem(type); +}; + +const createHardBreakCommand = (schema: Schema): CommandWithActive => { + const br = schema.nodes.hard_break; + + return chainCommands(exitCode, (state, dispatch) => { + if (dispatch) { + dispatch( + state.tr.replaceSelectionWith(br.create()).scrollIntoView() + ); + } + + return true; + }); +}; + +const createHorizontalRuleCommand = (schema: Schema): CommandWithActive => { + const hr = schema.nodes.horizontal_rule; + + return (state, dispatch) => { + if (dispatch) { + dispatch( + state.tr.replaceSelectionWith(hr.create()).scrollIntoView() + ); + } + + return true; + }; +}; + +const createWrapInNodeCommand = ( + schema: Schema, + nodeType: string +): CommandWithActive => { + const type = schema.nodes[nodeType]; + + return wrapIn(type); +}; + const commandMapping: CommandMapping = { strong: createToggleMarkCommand, em: createToggleMarkCommand, @@ -320,15 +392,44 @@ const commandMapping: CommandMapping = { LevelMapping.Heading, LevelMapping.three ), + headerlevel4: (schema) => + createSetNodeTypeCommand( + schema, + LevelMapping.Heading, + LevelMapping.four + ), + headerlevel5: (schema) => + createSetNodeTypeCommand( + schema, + LevelMapping.Heading, + LevelMapping.five + ), + headerlevel6: (schema) => + createSetNodeTypeCommand( + schema, + LevelMapping.Heading, + LevelMapping.six + ), blockquote: (schema) => createWrapInCommand(schema, EditorMenuTypes.Blockquote), - code_block: (schema) => createSetNodeTypeCommand(schema, EditorMenuTypes.CodeBlock), + paragraph: (schema) => + createSetNodeTypeCommand(schema, EditorMenuTypes.Paragraph), ordered_list: (schema) => createListCommand(schema, EditorMenuTypes.OrderedList), bullet_list: (schema) => createListCommand(schema, EditorMenuTypes.BulletList), + split_list_item: (schema) => createSplitListItemCommand(schema), + lift_list_item: (schema) => createLiftListItemCommand(schema), + sink_list_item: (schema) => createSinkListItemCommand(schema), + hard_break: (schema) => createHardBreakCommand(schema), + horizontal_rule: (schema) => createHorizontalRuleCommand(schema), + wrap_bullet_list: (schema) => + createWrapInNodeCommand(schema, 'bullet_list'), + wrap_ordered_list: (schema) => + createWrapInNodeCommand(schema, 'ordered_list'), + wrap_blockquote: (schema) => createWrapInNodeCommand(schema, 'blockquote'), }; export class MenuCommandFactory { @@ -347,16 +448,59 @@ export class MenuCommandFactory { return commandFunc(this.schema, mark, link); } - buildKeymap() { + buildKeymap(): { [key: string]: Command } { return { + // History + 'Mod-z': undo, + 'Shift-Mod-z': redo, + Backspace: undoInputRule, + + // Navigation + 'Alt-ArrowUp': joinUp, + 'Alt-ArrowDown': joinDown, + 'Mod-BracketLeft': lift, + Escape: selectParentNode, + + // Mark toggles + 'Mod-b': this.getCommand(EditorMenuTypes.Bold), 'Mod-B': this.getCommand(EditorMenuTypes.Bold), + 'Mod-i': this.getCommand(EditorMenuTypes.Italic), 'Mod-I': this.getCommand(EditorMenuTypes.Italic), + 'Mod-`': this.getCommand(EditorMenuTypes.Code), + 'Mod-Shift-x': this.getCommand(EditorMenuTypes.Strikethrough), + 'Mod-Shift-X': this.getCommand(EditorMenuTypes.Strikethrough), + + // Block types (Mod-Shift) 'Mod-Shift-1': this.getCommand(EditorMenuTypes.HeaderLevel1), 'Mod-Shift-2': this.getCommand(EditorMenuTypes.HeaderLevel2), 'Mod-Shift-3': this.getCommand(EditorMenuTypes.HeaderLevel3), - 'Mod-Shift-X': this.getCommand(EditorMenuTypes.Strikethrough), - 'Mod-`': this.getCommand(EditorMenuTypes.Code), + 'Mod-Shift-c': this.getCommand(EditorMenuTypes.CodeBlock), 'Mod-Shift-C': this.getCommand(EditorMenuTypes.CodeBlock), + + // Block types (Shift-Ctrl) + 'Shift-Ctrl-0': this.getCommand(EditorMenuTypes.Paragraph), + 'Shift-Ctrl-1': this.getCommand(EditorMenuTypes.HeaderLevel1), + 'Shift-Ctrl-2': this.getCommand(EditorMenuTypes.HeaderLevel2), + 'Shift-Ctrl-3': this.getCommand(EditorMenuTypes.HeaderLevel3), + 'Shift-Ctrl-4': this.getCommand(EditorMenuTypes.HeaderLevel4), + 'Shift-Ctrl-5': this.getCommand(EditorMenuTypes.HeaderLevel5), + 'Shift-Ctrl-6': this.getCommand(EditorMenuTypes.HeaderLevel6), + 'Shift-Ctrl-\\': this.getCommand(EditorMenuTypes.CodeBlock), + + // List operations + Enter: this.getCommand(EditorMenuTypes.SplitListItem), + 'Mod-[': this.getCommand(EditorMenuTypes.LiftListItem), + 'Mod-]': this.getCommand(EditorMenuTypes.SinkListItem), + 'Shift-Ctrl-8': this.getCommand(EditorMenuTypes.WrapInBulletList), + 'Shift-Ctrl-9': this.getCommand(EditorMenuTypes.WrapInOrderedList), + + // Wrapping + 'Ctrl->': this.getCommand(EditorMenuTypes.WrapInBlockquote), + + // Insertions + 'Mod-Enter': this.getCommand(EditorMenuTypes.HardBreak), + 'Shift-Enter': this.getCommand(EditorMenuTypes.HardBreak), + 'Mod-_': this.getCommand(EditorMenuTypes.HorizontalRule), }; } } diff --git a/src/components/text-editor/prosemirror-adapter/menu/types.ts b/src/components/text-editor/prosemirror-adapter/menu/types.ts index e1233822e7..373ec0cd01 100644 --- a/src/components/text-editor/prosemirror-adapter/menu/types.ts +++ b/src/components/text-editor/prosemirror-adapter/menu/types.ts @@ -11,12 +11,24 @@ export const EditorMenuTypes = { HeaderLevel1: 'headerlevel1', HeaderLevel2: 'headerlevel2', HeaderLevel3: 'headerlevel3', + HeaderLevel4: 'headerlevel4', + HeaderLevel5: 'headerlevel5', + HeaderLevel6: 'headerlevel6', Link: 'link', OrderedList: 'ordered_list', BulletList: 'bullet_list', Strikethrough: 'strikethrough', Code: 'code', CodeBlock: 'code_block', + Paragraph: 'paragraph', + HardBreak: 'hard_break', + HorizontalRule: 'horizontal_rule', + SplitListItem: 'split_list_item', + LiftListItem: 'lift_list_item', + SinkListItem: 'sink_list_item', + WrapInBulletList: 'wrap_bullet_list', + WrapInOrderedList: 'wrap_ordered_list', + WrapInBlockquote: 'wrap_blockquote', }; /** @@ -45,6 +57,9 @@ export const LevelMapping = { one: 1, two: 2, three: 3, + four: 4, + five: 5, + six: 6, }; /** diff --git a/src/components/text-editor/prosemirror-adapter/plugins/base-plugins.ts b/src/components/text-editor/prosemirror-adapter/plugins/base-plugins.ts new file mode 100644 index 0000000000..c83a6998dd --- /dev/null +++ b/src/components/text-editor/prosemirror-adapter/plugins/base-plugins.ts @@ -0,0 +1,26 @@ +import { Plugin } from 'prosemirror-state'; +import { Schema } from 'prosemirror-model'; +import { keymap } from 'prosemirror-keymap'; +import { baseKeymap } from 'prosemirror-commands'; +import { history } from 'prosemirror-history'; +import { dropCursor } from 'prosemirror-dropcursor'; +import { gapCursor } from 'prosemirror-gapcursor'; +import { buildInputRules } from './input-rules'; + +/** + * Assembles the base ProseMirror plugins for the text editor. + * + * Provides infrastructure plugins only: input rules, base keymap + * (Enter, Backspace, Delete), drop cursor, gap cursor, and history. + * Schema-aware keybindings are handled by MenuCommandFactory.buildKeymap(). + * @param schema - The ProseMirror schema to build plugins for + */ +export function buildBasePlugins(schema: Schema): Plugin[] { + return [ + buildInputRules(schema), + keymap(baseKeymap), + dropCursor(), + gapCursor(), + history(), + ]; +} diff --git a/src/components/text-editor/prosemirror-adapter/plugins/image/node.ts b/src/components/text-editor/prosemirror-adapter/plugins/image/node.ts index aea9795526..aa432606be 100644 --- a/src/components/text-editor/prosemirror-adapter/plugins/image/node.ts +++ b/src/components/text-editor/prosemirror-adapter/plugins/image/node.ts @@ -120,13 +120,15 @@ function getImageHTML(attrs: ImageNodeAttrs): string { } const styleAttribute = style.length > 0 ? ` style="${style.join('')}"` : ''; + const titleAttribute = attrs.title ? ` title="${attrs.title}"` : ''; - return `${attrs.alt}`; + return `${attrs.alt}`; } export interface ImageNodeAttrs { src: string; alt: string; + title?: string | null; state: EditorImageState; fileInfoId: string | number; height?: string; @@ -140,9 +142,11 @@ function createImageNodeSpec(language: Languages): NodeSpec { return { group: 'inline', inline: true, + draggable: true, attrs: { src: { default: '' }, alt: { default: '' }, + title: { default: null }, fileInfoId: { default: '' }, height: { default: '' }, width: { default: '' }, @@ -169,6 +173,7 @@ function createImageNodeSpec(language: Languages): NodeSpec { return { src: dom.getAttribute('src') || '', alt: dom.getAttribute('alt') || 'file', + title: dom.getAttribute('title') || null, width: dom.style.width || '', height: dom.style.height || '', maxWidth: '100%', @@ -230,6 +235,7 @@ function updateImageElement( node: Node ): HTMLImageElement { img.alt = node.attrs.alt; + img.title = node.attrs.title || ''; applyImageStyles(img, node); return img; @@ -239,6 +245,10 @@ function createImageElement(node: Node): HTMLImageElement { const img = document.createElement('img'); img.src = node.attrs.src; img.alt = node.attrs.alt; + if (node.attrs.title) { + img.title = node.attrs.title; + } + applyImageStyles(img, node); return img; diff --git a/src/components/text-editor/prosemirror-adapter/plugins/input-rules.ts b/src/components/text-editor/prosemirror-adapter/plugins/input-rules.ts new file mode 100644 index 0000000000..c1cec755b9 --- /dev/null +++ b/src/components/text-editor/prosemirror-adapter/plugins/input-rules.ts @@ -0,0 +1,61 @@ +import { + inputRules, + smartQuotes, + emDash, + ellipsis, + wrappingInputRule, + textblockTypeInputRule, + InputRule, +} from 'prosemirror-inputrules'; +import { Schema } from 'prosemirror-model'; +import { Plugin } from 'prosemirror-state'; + +/** + * Builds input rules for the text editor schema. + * + * Includes smart quotes, ellipsis, em dash, and markdown-style + * shortcuts for blockquote, lists, code block, and headings. + * Only creates rules for nodes that exist in the given schema. + * @param schema + */ +export function buildInputRules(schema: Schema): Plugin { + const rules: InputRule[] = [...smartQuotes, ellipsis, emDash]; + + if (schema.nodes.blockquote) { + rules.push(wrappingInputRule(/^\s*>\s$/, schema.nodes.blockquote)); + } + + if (schema.nodes.ordered_list) { + rules.push( + wrappingInputRule( + /^(\d+)\.\s$/, + schema.nodes.ordered_list, + (match) => ({ order: +match[1] }), + (match, node) => + node.childCount + node.attrs.order === +match[1] + ) + ); + } + + if (schema.nodes.bullet_list) { + rules.push( + wrappingInputRule(/^\s*([-+*])\s$/, schema.nodes.bullet_list) + ); + } + + if (schema.nodes.code_block) { + rules.push(textblockTypeInputRule(/^```$/, schema.nodes.code_block)); + } + + if (schema.nodes.heading) { + rules.push( + textblockTypeInputRule( + /^(#{1,6})\s$/, + schema.nodes.heading, + (match) => ({ level: match[1].length }) + ) + ); + } + + return inputRules({ rules }); +} diff --git a/src/components/text-editor/prosemirror-adapter/prosemirror-adapter.tsx b/src/components/text-editor/prosemirror-adapter/prosemirror-adapter.tsx index 8e47f0d11d..9185e5eea9 100644 --- a/src/components/text-editor/prosemirror-adapter/prosemirror-adapter.tsx +++ b/src/components/text-editor/prosemirror-adapter/prosemirror-adapter.tsx @@ -12,10 +12,9 @@ import { import { EditorState, Transaction, Selection } from 'prosemirror-state'; import { EditorView } from 'prosemirror-view'; import { Schema, DOMParser } from 'prosemirror-model'; -import { schema } from 'prosemirror-schema-basic'; -import { addListNodes } from 'prosemirror-schema-list'; -import { exampleSetup } from 'prosemirror-example-setup'; import { keymap } from 'prosemirror-keymap'; +import { createSchema } from './schema'; +import { buildBasePlugins } from './plugins/base-plugins'; import { ActionBarItem } from '../../../components/action-bar/action-bar.types'; import { ListSeparator } from '../../../components/list-item/list-item.types'; import { MenuCommandFactory } from './menu/menu-commands'; @@ -33,15 +32,12 @@ import { createRandomString } from '../../../util/random-string'; import { isItem } from '../../action-bar/is-item'; import { cloneDeep, debounce } from 'lodash-es'; import { Languages } from '../../date-picker/date.types'; -import { strikethrough } from './menu/menu-schema-extender'; import { createLinkPlugin } from './plugins/link/link-plugin'; -import { linkMarkSpec } from './plugins/link/link-mark'; import { createImageInserterPlugin } from './plugins/image/inserter'; import { createImageViewPlugin } from './plugins/image/view'; import { createMenuStateTrackingPlugin } from './plugins/menu-state-tracking-plugin'; import { createActionBarInteractionPlugin } from './plugins/menu-action-interaction-plugin'; import { CustomElementDefinition } from '../../../global/shared-types/custom-element.types'; -import { createNodeSpec } from '../utils/plugin-factory'; import { createTriggerPlugin } from './plugins/trigger/factory'; import { TriggerCharacter, @@ -50,8 +46,8 @@ import { EditorMetadata, EditorLink, } from '../text-editor.types'; -import { getTableNodes, getTableEditingPlugins } from './plugins/table-plugin'; -import { getImageNode, imageCache } from './plugins/image/node'; +import { getTableEditingPlugins } from './plugins/table-plugin'; +import { imageCache } from './plugins/image/node'; import { EditorUiType } from '../types'; import { getMetadataFromDoc, @@ -377,28 +373,10 @@ export class ProsemirrorAdapter { } private initializeSchema() { - let nodes = schema.spec.nodes; - - for (const customElement of this.customElements) { - const newNodeSpec = createNodeSpec(customElement); - const nodeName = customElement.tagName; - - nodes = nodes.append({ [nodeName]: newNodeSpec }); - } - nodes = addListNodes(nodes, 'paragraph block*', 'block'); - - if (this.contentType === 'html') { - nodes = nodes.append(getTableNodes()); - } - - nodes = nodes.append(getImageNode(this.language)); - - return new Schema({ - nodes: nodes, - marks: schema.spec.marks.append({ - strikethrough: strikethrough, - link: linkMarkSpec, - }), + return createSchema({ + customElements: this.customElements, + contentType: this.contentType, + language: this.language, }); } @@ -422,7 +400,7 @@ export class ProsemirrorAdapter { return EditorState.create({ doc: initialDoc, plugins: [ - ...exampleSetup({ schema: this.schema, menuBar: false }), + ...buildBasePlugins(this.schema), keymap(this.menuCommandFactory.buildKeymap()), createTriggerPlugin( this.triggerCharacters, diff --git a/src/components/text-editor/prosemirror-adapter/schema/index.ts b/src/components/text-editor/prosemirror-adapter/schema/index.ts new file mode 100644 index 0000000000..8de1fde09c --- /dev/null +++ b/src/components/text-editor/prosemirror-adapter/schema/index.ts @@ -0,0 +1,26 @@ +import { Schema } from 'prosemirror-model'; +import { CustomElementDefinition } from '../../../../global/shared-types/custom-element.types'; +import { Languages } from '../../../date-picker/date.types'; +import { buildNodes } from './nodes'; +import { buildMarks } from './marks'; + +/** + * Creates the ProseMirror schema for the text editor. + * + * Assembles nodes and marks from cherry-picked prosemirror-schema-basic + * specs and our own custom specs (image, link, strikethrough, tables). + * @param options + * @param options.customElements + * @param options.contentType + * @param options.language + */ +export function createSchema(options: { + customElements?: CustomElementDefinition[]; + contentType?: string; + language?: Languages; +}): Schema { + return new Schema({ + nodes: buildNodes(options), + marks: buildMarks(), + }); +} diff --git a/src/components/text-editor/prosemirror-adapter/schema/marks.ts b/src/components/text-editor/prosemirror-adapter/schema/marks.ts new file mode 100644 index 0000000000..ef15ec1b2a --- /dev/null +++ b/src/components/text-editor/prosemirror-adapter/schema/marks.ts @@ -0,0 +1,22 @@ +import { MarkSpec } from 'prosemirror-model'; +import { marks as basicMarks } from 'prosemirror-schema-basic'; +import OrderedMap from 'orderedmap'; +import { linkMarkSpec } from '../plugins/link/link-mark'; +import { strikethrough } from '../menu/menu-schema-extender'; + +/** + * Assembles the complete mark spec map for the text editor schema. + * + * Cherry-picks `strong`, `em`, and `code` from prosemirror-schema-basic. + * Uses our own `link` mark (with security attributes) and `strikethrough`. + * The basic `link` mark is intentionally excluded. + */ +export function buildMarks(): OrderedMap { + return OrderedMap.from({ + strong: basicMarks.strong, + em: basicMarks.em, + code: basicMarks.code, + strikethrough: strikethrough, + link: linkMarkSpec, + }); +} diff --git a/src/components/text-editor/prosemirror-adapter/schema/nodes.ts b/src/components/text-editor/prosemirror-adapter/schema/nodes.ts new file mode 100644 index 0000000000..41672b47bc --- /dev/null +++ b/src/components/text-editor/prosemirror-adapter/schema/nodes.ts @@ -0,0 +1,63 @@ +import { NodeSpec } from 'prosemirror-model'; +import { nodes as basicNodes } from 'prosemirror-schema-basic'; +import { addListNodes } from 'prosemirror-schema-list'; +import OrderedMap from 'orderedmap'; +import { CustomElementDefinition } from '../../../../global/shared-types/custom-element.types'; +import { createNodeSpec } from '../../utils/plugin-factory'; +import { getTableNodes } from '../plugins/table-plugin'; +import { getImageNode } from '../plugins/image/node'; +import { Languages } from '../../../date-picker/date.types'; + +/** + * Base nodes cherry-picked from prosemirror-schema-basic. + * The basic `image` node is intentionally excluded — we use our own. + */ +const baseNodes: OrderedMap = OrderedMap.from({ + doc: basicNodes.doc, + paragraph: basicNodes.paragraph, + blockquote: basicNodes.blockquote, + horizontal_rule: basicNodes.horizontal_rule, + heading: basicNodes.heading, + code_block: basicNodes.code_block, + text: basicNodes.text, + hard_break: basicNodes.hard_break, +}); + +/** + * Assembles the complete node spec map for the text editor schema. + * + * 1. Starts with cherry-picked base nodes + * 2. Appends custom element nodes + * 3. Adds list nodes (ordered_list, bullet_list, list_item) + * 4. Conditionally adds table nodes (HTML content type only) + * 5. Appends our custom image node + * @param options + * @param options.customElements + * @param options.contentType + * @param options.language + */ +export function buildNodes(options: { + customElements?: CustomElementDefinition[]; + contentType?: string; + language?: Languages; +}): OrderedMap { + const { customElements = [], contentType, language } = options; + + let nodes = baseNodes; + + for (const customElement of customElements) { + const newNodeSpec = createNodeSpec(customElement); + const nodeName = customElement.tagName; + nodes = nodes.append({ [nodeName]: newNodeSpec }); + } + + nodes = addListNodes(nodes, 'paragraph block*', 'block'); + + if (contentType === 'html') { + nodes = nodes.append(getTableNodes()); + } + + nodes = nodes.append(getImageNode(language)); + + return nodes; +} diff --git a/src/components/text-editor/prosemirror-adapter/schema/schema.spec.ts b/src/components/text-editor/prosemirror-adapter/schema/schema.spec.ts new file mode 100644 index 0000000000..1ec80a313d --- /dev/null +++ b/src/components/text-editor/prosemirror-adapter/schema/schema.spec.ts @@ -0,0 +1,94 @@ +import { createSchema } from './index'; + +describe('createSchema', () => { + describe('base schema (markdown mode)', () => { + const schema = createSchema({}); + + it('includes all cherry-picked base nodes', () => { + expect(schema.nodes.doc).toBeDefined(); + expect(schema.nodes.paragraph).toBeDefined(); + expect(schema.nodes.blockquote).toBeDefined(); + expect(schema.nodes.horizontal_rule).toBeDefined(); + expect(schema.nodes.heading).toBeDefined(); + expect(schema.nodes.code_block).toBeDefined(); + expect(schema.nodes.text).toBeDefined(); + expect(schema.nodes.hard_break).toBeDefined(); + }); + + it('includes list nodes', () => { + expect(schema.nodes.ordered_list).toBeDefined(); + expect(schema.nodes.bullet_list).toBeDefined(); + expect(schema.nodes.list_item).toBeDefined(); + }); + + it('includes custom image node', () => { + expect(schema.nodes.image).toBeDefined(); + }); + + it('does not include table nodes in non-html mode', () => { + expect(schema.nodes.table).toBeUndefined(); + expect(schema.nodes.table_row).toBeUndefined(); + expect(schema.nodes.table_cell).toBeUndefined(); + expect(schema.nodes.table_header).toBeUndefined(); + }); + + it('includes all expected marks', () => { + expect(schema.marks.strong).toBeDefined(); + expect(schema.marks.em).toBeDefined(); + expect(schema.marks.code).toBeDefined(); + expect(schema.marks.strikethrough).toBeDefined(); + expect(schema.marks.link).toBeDefined(); + }); + + it('uses our custom link mark with security attributes', () => { + const linkSpec = schema.marks.link.spec; + expect(linkSpec.attrs.target).toBeDefined(); + expect(linkSpec.attrs.rel).toBeDefined(); + expect(linkSpec.attrs.referrerpolicy).toBeDefined(); + }); + }); + + describe('html mode', () => { + const schema = createSchema({ contentType: 'html' }); + + it('includes table nodes', () => { + expect(schema.nodes.table).toBeDefined(); + expect(schema.nodes.table_row).toBeDefined(); + expect(schema.nodes.table_cell).toBeDefined(); + expect(schema.nodes.table_header).toBeDefined(); + }); + }); + + describe('image node spec', () => { + const schema = createSchema({}); + const imageSpec = schema.nodes.image.spec; + + it('is draggable', () => { + expect(imageSpec.draggable).toBe(true); + }); + + it('supports title attribute', () => { + expect(imageSpec.attrs.title).toBeDefined(); + }); + }); + + describe('custom elements', () => { + const schema = createSchema({ + customElements: [ + { + tagName: 'my-widget', + attributes: ['data-id', 'data-label'], + }, + ], + }); + + it('includes the custom element as a node', () => { + expect(schema.nodes['my-widget']).toBeDefined(); + }); + + it('does not break base nodes', () => { + expect(schema.nodes.paragraph).toBeDefined(); + expect(schema.nodes.heading).toBeDefined(); + }); + }); +});