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 `
`;
+ return `
`;
}
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();
+ });
+ });
+});