From 668028b93fcf176bb3ed882a0d3ea11ee7d4a5a5 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Tue, 15 Oct 2024 17:50:10 -0500 Subject: [PATCH] Add ProseMirrorTextarea --- course/utils.py | 47 ++++ package-lock.json | 374 ++++++++++++++++++++++++++++++++ package.json | 46 ++-- relate/static/js/prosemirror.js | 113 ++++++++++ rollup.config.mjs | 10 + 5 files changed, 573 insertions(+), 17 deletions(-) create mode 100644 relate/static/js/prosemirror.js diff --git a/course/utils.py b/course/utils.py index b215d12d5..0d5132bb6 100644 --- a/course/utils.py +++ b/course/utils.py @@ -1044,6 +1044,53 @@ def get_codemirror_widget( # }}} +# {{{ prosemirror + +class ProseMirrorTextarea(forms.Textarea): + @property + def media(self): + return forms.Media(js=["bundle-prosemirror.js"]) + + def render(self, name, value, attrs=None, renderer=None) -> SafeString: + output = [super().render( + name, value, attrs, renderer), + f""" + + """] + + return mark_safe("\n".join(output)) + + math_help_text = mark_safe(r""" + See the list of supported math commands. + More tips for using this editor to type math: + + """) + +# }}} + + # {{{ facility processing def get_facilities_config( diff --git a/package-lock.json b/package-lock.json index f5850b677..ad15596e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "packages": { "": { "dependencies": { + "@benrbray/prosemirror-math": "^1.0.0", "@codemirror/autocomplete": "^6.18.1", "@codemirror/commands": "^6.7.0", "@codemirror/lang-markdown": "^6.2.0", @@ -33,7 +34,18 @@ "htmx.org": "^2", "jquery": "^3.5.1", "jstree": "^3.3.5", + "katex": "^0.16.11", "mathjax": "^3.2.0", + "prosemirror-commands": "^1.6.1", + "prosemirror-example-setup": "^1.2.3", + "prosemirror-inputrules": "^1.4.0", + "prosemirror-keymap": "^1.2.2", + "prosemirror-menu": "^1.2.4", + "prosemirror-model": "^1.23.0", + "prosemirror-schema-basic": "^1.2.3", + "prosemirror-schema-list": "^1.4.1", + "prosemirror-state": "^1.4.3", + "prosemirror-view": "^1.34.3", "select2": "^4.0.5", "select2-bootstrap-theme": "^0.1.0-beta.10", "video.js": "^7.14.3" @@ -103,6 +115,22 @@ "node": ">=6.9.0" } }, + "node_modules/@benrbray/prosemirror-math": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@benrbray/prosemirror-math/-/prosemirror-math-1.0.0.tgz", + "integrity": "sha512-5fPeOKP6SJJ3usXhhf6vnLXGJnfPHPzv0OdsOJlGkCdZvNfCuC6f8fZqgpmnP8vxDKjB8fvSVSmAHTMsaiXc6w==", + "peerDependencies": { + "katex": "^0.16.10", + "prosemirror-commands": "^1.5.2", + "prosemirror-history": "^1.4.0", + "prosemirror-inputrules": "^1.4.0", + "prosemirror-keymap": "^1.2.2", + "prosemirror-model": "^1.20.0", + "prosemirror-state": "^1.4.3", + "prosemirror-transform": "^1.8.0", + "prosemirror-view": "^1.33.4" + } + }, "node_modules/@codemirror/autocomplete": { "version": "6.18.1", "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.1.tgz", @@ -4672,6 +4700,29 @@ "jquery": ">=1.9.1" } }, + "node_modules/katex": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.11.tgz", + "integrity": "sha512-RQrI8rlHY92OLf3rho/Ts8i/XvjgguEjOkO1BEXcU3N8BqPpSzBNwV/G0Ukr+P/l3ivvJUE/Fa/CwbS6HesGNQ==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, "node_modules/keycode": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", @@ -6072,6 +6123,11 @@ "node": ">= 0.8.0" } }, + "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==" + }, "node_modules/p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -6876,6 +6932,147 @@ "node": ">=10" } }, + "node_modules/prosemirror-commands": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.6.1.tgz", + "integrity": "sha512-tNy4uaGWzvuUYXDke7B28krndIrdQJhSh0OLpubtwtEwFbjItOj/eoAfPvstBJyyV0S2+b5t4G+4XPXdxar6pg==", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^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==", + "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==", + "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==", + "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.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.4.1.tgz", + "integrity": "sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==", + "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.4.0", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.4.0.tgz", + "integrity": "sha512-6ygpPRuTJ2lcOXs9JkefieMst63wVJBgHZGl5QOytN7oSZs3Co/BYbc3Yx9zm9H37Bxw8kVzCnDsihsVsL4yEg==", + "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==", + "dependencies": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.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==", + "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.23.0", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.23.0.tgz", + "integrity": "sha512-Q/fgsgl/dlOAW9ILu4OOhYWQbc7TQd4BwKH/RwmUjyVf8682Be4zj3rOYdLnYEcGzyg8LL9Q5IWYKD8tdToreQ==", + "dependencies": { + "orderedmap": "^2.0.0" + } + }, + "node_modules/prosemirror-schema-basic": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.3.tgz", + "integrity": "sha512-h+H0OQwZVqMon1PNn0AG9cTfx513zgIG2DY00eJ00Yvgb3UD+GQ/VlWW5rcaxacpCGT1Yx8nuhwXk4+QbXUfJA==", + "dependencies": { + "prosemirror-model": "^1.19.0" + } + }, + "node_modules/prosemirror-schema-list": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.4.1.tgz", + "integrity": "sha512-jbDyaP/6AFfDfu70VzySsD75Om2t3sXTOdl5+31Wlxlg62td1haUpty/ybajSfJ1pkGadlOfwQq9kgW5IMo1Rg==", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.7.3" + } + }, + "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==", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.27.0" + } + }, + "node_modules/prosemirror-transform": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.2.tgz", + "integrity": "sha512-2iUq0wv2iRoJO/zj5mv8uDUriOHWzXRnOTVgCzSXnktS/2iQRa3UUQwVlkBlYZFtygw6Nh1+X4mGqoYBINn5KQ==", + "dependencies": { + "prosemirror-model": "^1.21.0" + } + }, + "node_modules/prosemirror-view": { + "version": "1.34.3", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.34.3.tgz", + "integrity": "sha512-mKZ54PrX19sSaQye+sef+YjBbNu2voNwLS1ivb6aD2IRmxRGW64HU9B644+7OfJStGLyxvOreKqEgfvXa91WIA==", + "dependencies": { + "prosemirror-model": "^1.20.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, "node_modules/prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -7350,6 +7547,11 @@ "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true }, + "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==" + }, "node_modules/rust-result": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/rust-result/-/rust-result-1.0.0.tgz", @@ -8585,6 +8787,12 @@ "regenerator-runtime": "^0.13.4" } }, + "@benrbray/prosemirror-math": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@benrbray/prosemirror-math/-/prosemirror-math-1.0.0.tgz", + "integrity": "sha512-5fPeOKP6SJJ3usXhhf6vnLXGJnfPHPzv0OdsOJlGkCdZvNfCuC6f8fZqgpmnP8vxDKjB8fvSVSmAHTMsaiXc6w==", + "requires": {} + }, "@codemirror/autocomplete": { "version": "6.18.1", "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.1.tgz", @@ -11996,6 +12204,21 @@ "jquery": ">=1.9.1" } }, + "katex": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.11.tgz", + "integrity": "sha512-RQrI8rlHY92OLf3rho/Ts8i/XvjgguEjOkO1BEXcU3N8BqPpSzBNwV/G0Ukr+P/l3ivvJUE/Fa/CwbS6HesGNQ==", + "requires": { + "commander": "^8.3.0" + }, + "dependencies": { + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==" + } + } + }, "keycode": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", @@ -13055,6 +13278,11 @@ "word-wrap": "^1.2.3" } }, + "orderedmap": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", + "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==" + }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -13574,6 +13802,147 @@ "retry": "^0.12.0" } }, + "prosemirror-commands": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.6.1.tgz", + "integrity": "sha512-tNy4uaGWzvuUYXDke7B28krndIrdQJhSh0OLpubtwtEwFbjItOj/eoAfPvstBJyyV0S2+b5t4G+4XPXdxar6pg==", + "requires": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^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==", + "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==", + "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==", + "requires": { + "prosemirror-keymap": "^1.0.0", + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-view": "^1.0.0" + } + }, + "prosemirror-history": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.4.1.tgz", + "integrity": "sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==", + "requires": { + "prosemirror-state": "^1.2.2", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.31.0", + "rope-sequence": "^1.3.0" + } + }, + "prosemirror-inputrules": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.4.0.tgz", + "integrity": "sha512-6ygpPRuTJ2lcOXs9JkefieMst63wVJBgHZGl5QOytN7oSZs3Co/BYbc3Yx9zm9H37Bxw8kVzCnDsihsVsL4yEg==", + "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==", + "requires": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.0" + } + }, + "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==", + "requires": { + "crelt": "^1.0.0", + "prosemirror-commands": "^1.0.0", + "prosemirror-history": "^1.0.0", + "prosemirror-state": "^1.0.0" + } + }, + "prosemirror-model": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.23.0.tgz", + "integrity": "sha512-Q/fgsgl/dlOAW9ILu4OOhYWQbc7TQd4BwKH/RwmUjyVf8682Be4zj3rOYdLnYEcGzyg8LL9Q5IWYKD8tdToreQ==", + "requires": { + "orderedmap": "^2.0.0" + } + }, + "prosemirror-schema-basic": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.3.tgz", + "integrity": "sha512-h+H0OQwZVqMon1PNn0AG9cTfx513zgIG2DY00eJ00Yvgb3UD+GQ/VlWW5rcaxacpCGT1Yx8nuhwXk4+QbXUfJA==", + "requires": { + "prosemirror-model": "^1.19.0" + } + }, + "prosemirror-schema-list": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.4.1.tgz", + "integrity": "sha512-jbDyaP/6AFfDfu70VzySsD75Om2t3sXTOdl5+31Wlxlg62td1haUpty/ybajSfJ1pkGadlOfwQq9kgW5IMo1Rg==", + "requires": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.7.3" + } + }, + "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==", + "requires": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.27.0" + } + }, + "prosemirror-transform": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.2.tgz", + "integrity": "sha512-2iUq0wv2iRoJO/zj5mv8uDUriOHWzXRnOTVgCzSXnktS/2iQRa3UUQwVlkBlYZFtygw6Nh1+X4mGqoYBINn5KQ==", + "requires": { + "prosemirror-model": "^1.21.0" + } + }, + "prosemirror-view": { + "version": "1.34.3", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.34.3.tgz", + "integrity": "sha512-mKZ54PrX19sSaQye+sef+YjBbNu2voNwLS1ivb6aD2IRmxRGW64HU9B644+7OfJStGLyxvOreKqEgfvXa91WIA==", + "requires": { + "prosemirror-model": "^1.20.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -13933,6 +14302,11 @@ "tslib": "^2.6.2" } }, + "rope-sequence": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", + "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==" + }, "rust-result": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/rust-result/-/rust-result-1.0.0.tgz", diff --git a/package.json b/package.json index 8d99fd8d1..93bf29668 100644 --- a/package.json +++ b/package.json @@ -1,37 +1,49 @@ { "dependencies": { + "@benrbray/prosemirror-math": "^1.0.0", + "@codemirror/autocomplete": "^6.18.1", + "@codemirror/commands": "^6.7.0", + "@codemirror/lang-markdown": "^6.2.0", + "@codemirror/lang-python": "^6.1.3", + "@codemirror/lang-yaml": "^6.1.1", + "@codemirror/language": "^6.8.0", + "@codemirror/legacy-modes": "^6.4.1", + "@codemirror/search": "^6.5.6", + "@codemirror/state": "^6.4.1", + "@codemirror/view": "^6.29", "@fullcalendar/core": "^6.1.15", "@fullcalendar/daygrid": "^6.1.15", "@fullcalendar/list": "^6.1.15", "@fullcalendar/timegrid": "^6.1.15", + "@lezer/highlight": "^1.2.1", "@popperjs/core": "^2.11.2", - "bootstrap": "^5.3.1", - "@codemirror/state": "^6.4.1", - "@codemirror/view": "^6.29", - "@codemirror/commands": "^6.7.0", - "@codemirror/autocomplete": "^6.18.1", - "@codemirror/search": "^6.5.6", - "@codemirror/language": "^6.8.0", - "@codemirror/lang-python": "^6.1.3", - "@codemirror/lang-markdown": "^6.2.0", - "@codemirror/lang-yaml": "^6.1.1", - "@codemirror/legacy-modes": "^6.4.1", - "@replit/codemirror-vim": "^6.2.1", "@replit/codemirror-emacs": "^6.0.1", - "@lezer/highlight": "^1.2.1", + "@replit/codemirror-vim": "^6.2.1", + "bootstrap": "^5.3.1", + "bootstrap-icons": "^1.8.1", "datatables-i18n": "github:dzhuang/datatables-i18n", "datatables.net": "^1.13.5", "datatables.net-bs5": "^2.1.8", "datatables.net-fixedcolumns": "^5.0.3", "datatables.net-fixedcolumns-bs5": "^4.3.0", - "bootstrap-icons": "^1.8.1", + "htmx.org": "^2", "jquery": "^3.5.1", "jstree": "^3.3.5", + "katex": "^0.16.11", "mathjax": "^3.2.0", + "prosemirror-commands": "^1.6.1", + "prosemirror-example-setup": "^1.2.3", + "prosemirror-inputrules": "^1.4.0", + "prosemirror-keymap": "^1.2.2", + "prosemirror-menu": "^1.2.4", + "prosemirror-model": "^1.23.0", + "prosemirror-schema-basic": "^1.2.3", + "prosemirror-schema-list": "^1.4.1", + "prosemirror-state": "^1.4.3", + "prosemirror-view": "^1.34.3", "select2": "^4.0.5", "select2-bootstrap-theme": "^0.1.0-beta.10", - "video.js": "^7.14.3", - "htmx.org": "^2" + "video.js": "^7.14.3" }, "devDependencies": { "@rollup/plugin-commonjs": "^28.0.0", @@ -55,4 +67,4 @@ "build": "rollup --config", "dev": "rollup --config --watch" } -} \ No newline at end of file +} diff --git a/relate/static/js/prosemirror.js b/relate/static/js/prosemirror.js new file mode 100644 index 000000000..7e774d2de --- /dev/null +++ b/relate/static/js/prosemirror.js @@ -0,0 +1,113 @@ +import 'katex/dist/katex.min.css'; + +// prosemirror imports +import { Schema, Node } from 'prosemirror-model'; +import { EditorView } from 'prosemirror-view'; +import { EditorState } from 'prosemirror-state'; +import { schema as basicSchema } from 'prosemirror-schema-basic'; +import { addListNodes } from 'prosemirror-schema-list'; +import { + chainCommands, deleteSelection, selectNodeBackward, joinBackward, +} from 'prosemirror-commands'; +import { keymap } from 'prosemirror-keymap'; +import { inputRules } from 'prosemirror-inputrules'; +import 'prosemirror-view/style/prosemirror.css'; +import 'prosemirror-menu/style/menu.css'; + +import { exampleSetup } from 'prosemirror-example-setup'; +import 'prosemirror-example-setup/style/style.css'; + +import { + mathPlugin, mathBackspaceCmd, insertMathCmd, mathSerializer, + makeBlockMathInputRule, makeInlineMathInputRule, + REGEX_INLINE_MATH_DOLLARS, REGEX_BLOCK_MATH_DOLLARS, +} from '@benrbray/prosemirror-math'; +import '@benrbray/prosemirror-math/dist/prosemirror-math.css'; + +const schema = new Schema({ + nodes: addListNodes(basicSchema.spec.nodes, 'paragraph block*', 'block') + .remove('image') + .addToEnd('math_inline', { + group: 'inline math', + content: 'text*', // important! + inline: true, // important! + atom: true, // important! + toDOM: () => ['math-inline', { class: 'math-node' }, 0], + parseDOM: [{ + tag: 'math-inline', // important! + }], + }) + .addToEnd('math_display', { + group: 'block math', + content: 'text*', // important! + atom: true, // important! + code: true, // important! + toDOM: () => ['math-display', { class: 'math-node' }, 0], + parseDOM: [{ + tag: 'math-display', // important! + }], + }), + marks: basicSchema.spec.marks, +}); + +const inlineMathInputRule = makeInlineMathInputRule(REGEX_INLINE_MATH_DOLLARS, schema.nodes.math_inline); +const blockMathInputRule = makeBlockMathInputRule( + REGEX_BLOCK_MATH_DOLLARS, schema.nodes.math_display); + +export function editorFromTextArea(textarea, autofocus) { + const plugins = [ + ...exampleSetup({ + schema, + }), + mathPlugin, + keymap({ + 'Mod-Space': insertMathCmd(schema.nodes.math_inline), + + // modify the default keymap chain for backspace + Backspace: chainCommands( + deleteSelection, + mathBackspaceCmd, + joinBackward, + selectNodeBackward, + ), + }), + inputRules({ rules: [inlineMathInputRule, blockMathInputRule] }), + ]; + + let doc; + if (textarea.value) { + doc = Node.fromJSON(JSON.parse(textarea.value)); + } else { + doc = schema.topNodeType.createAndFill(); + } + + const state = EditorState.create({ schema, plugins, doc }); + + const editorElt = document.createElement('div'); + const editable = !(textarea.disabled || textarea.readOnly); + const view = new EditorView(editorElt, { + state, + // editable, + clipboardTextSerializer: mathSerializer.serializeSlice, + }); + + if (autofocus) { + document.addEventListener('DOMContentLoaded', () => { + view.focus(); + }); + } + + textarea.parentNode.insertBefore(editorElt, textarea); + + // eslint-disable-next-line no-param-reassign + textarea.style.display = 'none'; + if (textarea.form) { + textarea.form.addEventListener('submit', () => { + // eslint-disable-next-line no-param-reassign + textarea.value = JSON.stringify(state.doc.toJSON()); + }); + } + textarea.classList.add('rl-managed-by-prosemirror'); + + return view; +} diff --git a/rollup.config.mjs b/rollup.config.mjs index 871f12ebb..8e144fc39 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -83,4 +83,14 @@ export default [ }, plugins: defaultPlugins, }, + { + input: 'relate/static/js/prosemirror.js', + output: { + file: 'frontend-dist/bundle-prosemirror.js', + format: 'iife', + sourcemap: true, + name: 'rlProsemirror', + }, + plugins: defaultPlugins, + }, ];