diff --git a/ccm-cms/package-lock.json b/ccm-cms/package-lock.json index 1bdaf855b..d6175d880 100644 --- a/ccm-cms/package-lock.json +++ b/ccm-cms/package-lock.json @@ -10,10 +10,40 @@ "integrity": "sha512-Fxt+AfXgjMoin2maPIYzFZnQjAXjAL0PHscM5pRTtatFqB+vZxAM9tLp2Optnuw3QOQC40jTNeGYFOMvyf7v9g==", "dev": true }, + "@editorjs/editorjs": { + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/@editorjs/editorjs/-/editorjs-2.22.2.tgz", + "integrity": "sha512-rPCv7Z5LZebreQaaL4DZuWzoVGEqwB+P7BF1dsefGQNBmLyeLF412topeW2b6e+g4l1oQ7t75kCOACNTEyYYIA==", + "requires": { + "codex-notifier": "^1.1.2", + "codex-tooltip": "^1.0.2", + "nanoid": "^3.1.22" + } + }, + "@editorjs/header": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@editorjs/header/-/header-2.6.1.tgz", + "integrity": "sha512-EsnyVFv5uThpU9tbQ/dUPFCQoa/sBFy2n+9tN3wOXJGx7sjea4fdcacJ2UYhO+7pCgZ+aSgmMOyGLYHUFbchvA==" + }, + "@editorjs/nested-list": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@editorjs/nested-list/-/nested-list-1.0.2.tgz", + "integrity": "sha512-NumQfEivI29lOAuDMyVhn+VXUDGvWUPJMkjgKlUYRbnwgnPL4tK007+UzoVPLxv/f6lPOqeKcApvCj/MfskPNw==" + }, + "@editorjs/quote": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@editorjs/quote/-/quote-2.4.0.tgz", + "integrity": "sha512-IWOBWjL2ngPP63GcIAltyD9kc7OVZFma4kS+T5JRHvKKDspYsnmrxsbRmCPc+coZQzqPxXHkiOZuNMdmGX/Y3w==" + }, + "@editorjs/table": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@editorjs/table/-/table-2.0.1.tgz", + "integrity": "sha512-PB8VM+GPRwGy7IlF+WrEQw2A2c36xEXBnYIvf2VGNJo8A7PjYHtuWrlyHHCnGpY4lHXYnavZ/U8pKAfXv86XjA==" + }, "@tiptap/core": { - "version": "2.0.0-beta.102", - "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.0.0-beta.102.tgz", - "integrity": "sha512-ykSAyYfyb14xiYWQ6mTaa+GF6j5dQvSDgeXQDNcy8xvBjZHm1g+51D0jm9FF/dsrY0rEps5h8yX883h7MaGFHA==", + "version": "2.0.0-beta.104", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.0.0-beta.104.tgz", + "integrity": "sha512-eGmHljAgGFcIFHTykBaxOCUOeGJoZOJOwa3il7ci2GxrrMyvhvU421mv5njFoHxAiNQJEgdu+kWitDGYuxbixg==", "requires": { "@types/prosemirror-commands": "^1.0.4", "@types/prosemirror-inputrules": "^1.0.4", @@ -22,7 +52,7 @@ "@types/prosemirror-schema-list": "^1.0.3", "@types/prosemirror-state": "^1.2.7", "@types/prosemirror-transform": "^1.1.4", - "@types/prosemirror-view": "^1.17.2", + "@types/prosemirror-view": "^1.19.0", "prosemirror-commands": "^1.1.10", "prosemirror-inputrules": "^1.1.3", "prosemirror-keymap": "^1.1.3", @@ -30,7 +60,7 @@ "prosemirror-schema-list": "^1.1.5", "prosemirror-state": "^1.3.4", "prosemirror-transform": "^1.3.2", - "prosemirror-view": "^1.19.3" + "prosemirror-view": "^1.20.0" } }, "@tiptap/extension-blockquote": { @@ -60,9 +90,9 @@ "integrity": "sha512-Kakg/RMiVrxjzIkLVDXtbCzRh/9W8dgSG04IhMZNOI8N9vWn8Z78jdUyxEEDTcL/JyWWcMxn9AsJw2U5ajO3pA==" }, "@tiptap/extension-code-block": { - "version": "2.0.0-beta.17", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.0.0-beta.17.tgz", - "integrity": "sha512-u3RY991mXtjuw+trVaDwbAhuPPlU8l6kS4rXIxWJ5W/sNElbmfHLVu7RP++YwM8KOQrCrQl8TJbZTEIekMw61w==", + "version": "2.0.0-beta.18", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.0.0-beta.18.tgz", + "integrity": "sha512-E2gz7ovl9nXLZzheqLyN3hi7A10fCaodDn4DvIl4wiEbKZpF7WFBNeb+FQetWNay9UWNeDO94SCX9+rT9H+yHA==", "requires": { "prosemirror-inputrules": "^1.1.3" } @@ -91,9 +121,9 @@ } }, "@tiptap/extension-hard-break": { - "version": "2.0.0-beta.15", - "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.0.0-beta.15.tgz", - "integrity": "sha512-MS7MjGOtKtC1bVNAShwCetFRuk8nPr/j18OOzKChNrJFrZXWNJrid3dUojwDLqCraYdzSTmiOmMgU+yoUe/gnw==" + "version": "2.0.0-beta.16", + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.0.0-beta.16.tgz", + "integrity": "sha512-vRw8OIJlvlr17Y7mtJGL/dWbotX9fjgmA/zYqL//UIXQjp1FWW5JMh5E1Z5+jlJpGWjsWGH8fHpGSM2JCZVPRw==" }, "@tiptap/extension-heading": { "version": "2.0.0-beta.15", @@ -131,9 +161,9 @@ "integrity": "sha512-t6xwEqP+d5443Ul2Jvqz9kXb3ro7bA7yY9HA0vskm3120WxxHW9jxgxZN+82Ot5Tm7nXOAlsN6vuqnt4idnxZQ==" }, "@tiptap/extension-ordered-list": { - "version": "2.0.0-beta.15", - "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.0.0-beta.15.tgz", - "integrity": "sha512-j9Xh8CYtV+C/wrTXEWN+U7NJIQ/cQrjta80Mm2hFiE2KDtFNkpsPqG6UBoky04EPFphR5xDUsO1nCT7T7Tei5A==", + "version": "2.0.0-beta.16", + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.0.0-beta.16.tgz", + "integrity": "sha512-3n0h5FBfQqBrN/zqF/Ngoyd1bZxeIRLwWI7ak4KulpvOg5V/yw3sw5CSxr2f13ZI9AgGaTq8yOsTYs9dkCCnsQ==", "requires": { "prosemirror-inputrules": "^1.1.3" } @@ -159,12 +189,12 @@ "integrity": "sha512-rTQCnSnloSf6UN1y3zhu6j41MxrcCVWm5JIPX8VEt60WsOXJLAc/YJHLYi2FWhh/Psq8k78sPrmZbjYUrj3Dkw==" }, "@tiptap/extension-table": { - "version": "2.0.0-beta.29", - "resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-2.0.0-beta.29.tgz", - "integrity": "sha512-2yK4dZboe7+KQoJeM0p6v7xx+G/yKDWbbtDtYRnFecD2Oiz1u44DeMKGzVqPXcMYWxmCnAeUK0kcbmwnPYyBTg==", + "version": "2.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-2.0.0-beta.30.tgz", + "integrity": "sha512-s6+HRo3sFv7SUprsUAAF27hg7inITpzl78If3XdrpscuzTVuRmd7GsFnY+aZGPVikekwCMjp/0klE92P4A7w0w==", "requires": { "prosemirror-tables": "^1.1.1", - "prosemirror-view": "^1.19.3" + "prosemirror-view": "^1.20.0" } }, "@tiptap/extension-table-cell": { @@ -188,31 +218,36 @@ "integrity": "sha512-0EtAwuRldCAoFaL/iXgkRepEeOd55rPg5N4FQUN1xTwZT7PDofukP0DG/2jff/Uj17x4uTaJAa9qlFWuNnDvjw==" }, "@tiptap/starter-kit": { - "version": "2.0.0-beta.101", - "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.0.0-beta.101.tgz", - "integrity": "sha512-1SoVrPe2JMPo5/Je0NprZWN8G0q0EOTpoVHQ/+VwthectJBz1TyCuNOqoR1WNL9II6Fj9AZelyhPWacw7vM27A==", + "version": "2.0.0-beta.103", + "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.0.0-beta.103.tgz", + "integrity": "sha512-Kmd7hLCc2JmmMijc6ng7u+OQBJYZBQJKznjgiUSuwJhAkROVdjgx7mz8p2J+tl81QpM4yHVBpj4W9Al2kiFRng==", "requires": { - "@tiptap/core": "^2.0.0-beta.102", + "@tiptap/core": "^2.0.0-beta.104", "@tiptap/extension-blockquote": "^2.0.0-beta.15", "@tiptap/extension-bold": "^2.0.0-beta.15", "@tiptap/extension-bullet-list": "^2.0.0-beta.15", "@tiptap/extension-code": "^2.0.0-beta.16", - "@tiptap/extension-code-block": "^2.0.0-beta.17", + "@tiptap/extension-code-block": "^2.0.0-beta.18", "@tiptap/extension-document": "^2.0.0-beta.13", "@tiptap/extension-dropcursor": "^2.0.0-beta.19", "@tiptap/extension-gapcursor": "^2.0.0-beta.19", - "@tiptap/extension-hard-break": "^2.0.0-beta.15", + "@tiptap/extension-hard-break": "^2.0.0-beta.16", "@tiptap/extension-heading": "^2.0.0-beta.15", "@tiptap/extension-history": "^2.0.0-beta.16", "@tiptap/extension-horizontal-rule": "^2.0.0-beta.19", "@tiptap/extension-italic": "^2.0.0-beta.15", "@tiptap/extension-list-item": "^2.0.0-beta.14", - "@tiptap/extension-ordered-list": "^2.0.0-beta.15", + "@tiptap/extension-ordered-list": "^2.0.0-beta.16", "@tiptap/extension-paragraph": "^2.0.0-beta.17", "@tiptap/extension-strike": "^2.0.0-beta.17", "@tiptap/extension-text": "^2.0.0-beta.13" } }, + "@types/editorjs__header": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@types/editorjs__header/-/editorjs__header-2.6.0.tgz", + "integrity": "sha512-J9TyO/BjNVddi+syyXpvMRMtVz5Z62pwmFKynWsgP+wnJYdWF8ABqgomokIsAvuEwH5NHa/YxsTltYcPPGCRRQ==" + }, "@types/eslint": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.0.tgz", @@ -358,9 +393,9 @@ } }, "@types/prosemirror-view": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/@types/prosemirror-view/-/prosemirror-view-1.18.0.tgz", - "integrity": "sha512-7NBy7qIV/ig49ThfkrIJrvW8E+HwumMgmpopUTYJlKwOh/fQ6SVUG/RtdnAIBLD+4uK0R2SMObbGZm06x6OwbA==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@types/prosemirror-view/-/prosemirror-view-1.19.0.tgz", + "integrity": "sha512-Y8OX9L+Yni0HgXAN9wcNSf61IId13uqpURnRC5WkmCOlVDsr35vfGjj+tcaQL4dZzblsu3bRkXI/c0oGXp+xgw==", "requires": { "@types/prosemirror-model": "*", "@types/prosemirror-state": "*", @@ -726,6 +761,16 @@ "shallow-clone": "^3.0.0" } }, + "codex-notifier": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/codex-notifier/-/codex-notifier-1.1.2.tgz", + "integrity": "sha512-DCp6xe/LGueJ1N5sXEwcBc3r3PyVkEEDNWCVigfvywAkeXcZMk9K41a31tkEFBW0Ptlwji6/JlAb49E3Yrxbtg==" + }, + "codex-tooltip": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/codex-tooltip/-/codex-tooltip-1.0.2.tgz", + "integrity": "sha512-oC+Bu5X/zyhbPydgMSLWKoM/+vkJMqaLWu3Dt/jZgXS3MWK23INwC5DMBrVXZSufAFk0i0SUni38k9rLMyZn/w==" + }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -781,6 +826,11 @@ "object-keys": "^1.0.12" } }, + "editorjs-latex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/editorjs-latex/-/editorjs-latex-1.0.0.tgz", + "integrity": "sha512-HIfVZFy4CJpLQk6xgNUmftxzTo5p4POFFEnW3cPUAKoqcL9FhLPr3vm8Yll+qirHla1b8okNmyw8lPIfUtXLaw==" + }, "electron-to-chromium": { "version": "1.3.830", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.830.tgz", @@ -1416,6 +1466,11 @@ "brace-expansion": "^1.1.7" } }, + "nanoid": { + "version": "3.1.25", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", + "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==" + }, "neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -1734,9 +1789,9 @@ } }, "prosemirror-view": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.20.0.tgz", - "integrity": "sha512-OqU/bHUIiJhpyb2ytX4fLalYAJJOyZ0k5H0AibP/WPsdHq9CqmJFU676gO+N8WWhR5tVz1NxsqMZgEBy5Lc6GQ==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.20.1.tgz", + "integrity": "sha512-djWORhy3a706mUH4A2dgEEV0IPZqQd1tFyz/ZVHJNoqhSgq82FwG6dq7uqHeUB2KdVSNfI2yc3rwfqlC/ll2pA==", "requires": { "prosemirror-model": "^1.14.3", "prosemirror-state": "^1.0.0", diff --git a/ccm-cms/package.json b/ccm-cms/package.json index 9987adc99..24f32b5c6 100644 --- a/ccm-cms/package.json +++ b/ccm-cms/package.json @@ -22,17 +22,24 @@ "webpack-cli": "^4.8.0" }, "dependencies": { - "@tiptap/core": "^2.0.0-beta.102", + "@editorjs/editorjs": "^2.22.2", + "@editorjs/header": "^2.6.1", + "@editorjs/nested-list": "^1.0.2", + "@editorjs/quote": "^2.4.0", + "@editorjs/table": "^2.0.1", + "@tiptap/core": "^2.0.0-beta.103", "@tiptap/extension-subscript": "^2.0.0-beta.4", "@tiptap/extension-superscript": "^2.0.0-beta.4", - "@tiptap/extension-table": "^2.0.0-beta.29", + "@tiptap/extension-table": "^2.0.0-beta.30", "@tiptap/extension-table-cell": "^2.0.0-beta.14", "@tiptap/extension-table-header": "^2.0.0-beta.16", "@tiptap/extension-table-row": "^2.0.0-beta.14", - "@tiptap/starter-kit": "^2.0.0-beta.101", + "@tiptap/starter-kit": "^2.0.0-beta.102", + "@types/editorjs__header": "^2.6.0", "acorn": "^8.4.1", "bootstrap": "^4.6.0", "bootstrap-icons": "^1.5.0", + "editorjs-latex": "^1.0.0", "jquery": "^3.6.0", "popper.js": "^1.16.1", "sortablejs": "^1.14.0" diff --git a/ccm-cms/src/main/resources/META-INF/resources/components/librecms/cmsEditor-tiptap.xhtml b/ccm-cms/src/main/resources/META-INF/resources/components/librecms/cmsEditor-tiptap.xhtml new file mode 100644 index 000000000..eeb024410 --- /dev/null +++ b/ccm-cms/src/main/resources/META-INF/resources/components/librecms/cmsEditor-tiptap.xhtml @@ -0,0 +1,659 @@ +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +

#{cc.attrs.title}

+
+ +

#{cc.attrs.title}

+
+ +

#{cc.attrs.title}

+
+ +

#{cc.attrs.title}

+
+ +
#{cc.attrs.title}
+
+ +
#{cc.attrs.title}
+
+ +
#{cc.attrs.title}
+
+
+
+ + + + +
+ + +

+ #{cc.attrs.editDialogTitle} #{cc.attrs.selectedLocale} +

+
+ +

+ #{cc.attrs.editDialogTitle} #{cc.attrs.selectedLocale} +

+
+ +

+ #{cc.attrs.editDialogTitle} #{cc.attrs.selectedLocale} +

+
+ +
+ #{cc.attrs.editDialogTitle} #{cc.attrs.selectedLocale} +
+
+ +
+ #{cc.attrs.editDialogTitle} #{cc.attrs.selectedLocale} +
+
+ +
+ #{cc.attrs.editDialogTitle} #{cc.attrs.selectedLocale} +
+
+
+
+
+
+ + + + + + +
+
+ + + +
+
+ + + +
+
+ + +
+
+ + + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + #{cc.attrs.editDialogCancelLabel} + + +
+
+
+
+ diff --git a/ccm-cms/src/main/resources/META-INF/resources/components/librecms/cmsEditor.xhtml b/ccm-cms/src/main/resources/META-INF/resources/components/librecms/cmsEditor.xhtml index 7c799ffe8..bb47d66a5 100644 --- a/ccm-cms/src/main/resources/META-INF/resources/components/librecms/cmsEditor.xhtml +++ b/ccm-cms/src/main/resources/META-INF/resources/components/librecms/cmsEditor.xhtml @@ -293,358 +293,22 @@ data-save-url="#{cc.attrs.editMethod}/#{variant.locale}" data-variant-url="#{cc.attrs.variantUrl}/#{cc.attrs.selectedLocale}" id="#{cc.attrs.editorId}" - > - - -

#{cc.attrs.title}

-
- -

#{cc.attrs.title}

-
- -

#{cc.attrs.title}

-
- -

#{cc.attrs.title}

-
- -
#{cc.attrs.title}
-
- -
#{cc.attrs.title}
-
- -
#{cc.attrs.title}
-
-
-
- - - - -
- - -

- #{cc.attrs.editDialogTitle} #{cc.attrs.selectedLocale} -

-
- -

- #{cc.attrs.editDialogTitle} #{cc.attrs.selectedLocale} -

-
- -

- #{cc.attrs.editDialogTitle} #{cc.attrs.selectedLocale} -

-
- -
- #{cc.attrs.editDialogTitle} #{cc.attrs.selectedLocale} -
-
- -
- #{cc.attrs.editDialogTitle} #{cc.attrs.selectedLocale} -
-
- -
- #{cc.attrs.editDialogTitle} #{cc.attrs.selectedLocale} -
-
-
-
+
+
+ -
-
- - - - - - -
-
- - - -
-
- - - -
-
- - -
-
- - - - - - - - - - - - - -
-
-
-
- - #{cc.attrs.editDialogCancelLabel} - - -
-
+ #{cc.attrs.editDialogCancelLabel} + + diff --git a/ccm-cms/src/main/resources/WEB-INF/views/org/librecms/ui/contenttypes/article/article-text/edit.xhtml b/ccm-cms/src/main/resources/WEB-INF/views/org/librecms/ui/contenttypes/article/article-text/edit.xhtml index f4e695674..2c05f63ba 100644 --- a/ccm-cms/src/main/resources/WEB-INF/views/org/librecms/ui/contenttypes/article/article-text/edit.xhtml +++ b/ccm-cms/src/main/resources/WEB-INF/views/org/librecms/ui/contenttypes/article/article-text/edit.xhtml @@ -30,11 +30,37 @@ /> + + + + + + diff --git a/ccm-cms/src/main/typescript/content-sections/article-text-step.js b/ccm-cms/src/main/typescript/content-sections/article-text-step.js new file mode 100644 index 000000000..1deaaa2d4 --- /dev/null +++ b/ccm-cms/src/main/typescript/content-sections/article-text-step.js @@ -0,0 +1,55 @@ +import EditorJS from "@editorjs/editorjs"; +import Header from "@editorjs/header"; +import NestedList from "@editorjs/nested-list"; +import Quote from "@editorjs/quote"; +import Table from "@editorjs/table"; +import { CmsImage } from "./ccm-cms-editorjs-image/ccm-cms-editorjs-image"; + +document.addEventListener("DOMContentLoaded", event => { + const editor = new EditorJS({ + data: { + blocks: [ + { + type: "header", + data: { + text: "Editor.js test", + level: 1 + } + }, + { + type: "paragraph", + data: { + text: "Lorem ipsum" + } + } + ] + }, + holder: "cms-article-text-editor", + tools: { + image: { + class: CmsImage, + inlineToolbar: true + }, + header: Header, + list: { + class: NestedList, + inlineToolbar: true + }, + quote: Quote, + table: Table + } + }); + + const saveButton = document.querySelector(".cms-editor-save-button"); + if (saveButton) { + saveButton.addEventListener("click", event => { + event.preventDefault(); + const preview = document.querySelector("#editor-preview"); + if (preview) { + editor.save().then(outputData => { + preview.innerHTML = JSON.stringify(outputData); + }); + } + }); + } +}); diff --git a/ccm-cms/src/main/typescript/content-sections/article-text-step.ts b/ccm-cms/src/main/typescript/content-sections/article-text-step.ts deleted file mode 100644 index 566325bfd..000000000 --- a/ccm-cms/src/main/typescript/content-sections/article-text-step.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { CmsEditorBuilder, CmsEditor } from "./cms-editor"; - -document.addEventListener("DOMContentLoaded", event => { - const editorElem = document.querySelector("#cms-article-text-editor"); - - if (editorElem) { - const saveUrl = editorElem.getAttribute("data-save-url"); - const variantUrl = editorElem.getAttribute("data-variant-url"); - - if (!saveUrl) { - console.error("saveUrl is null"); - return; - } - - if (!variantUrl) { - console.error("variantUrl is null"); - return; - } - - const builder = new CmsEditorBuilder( - editorElem as HTMLElement, - saveUrl, - variantUrl - ); - - builder.buildEditor(); - } -}); diff --git a/ccm-cms/src/main/typescript/content-sections/article-text-step.ts.off b/ccm-cms/src/main/typescript/content-sections/article-text-step.ts.off new file mode 100644 index 000000000..57260930a --- /dev/null +++ b/ccm-cms/src/main/typescript/content-sections/article-text-step.ts.off @@ -0,0 +1,27 @@ +// import { CmsEditorBuilder, CmsEditor } from "./cms-editor"; + + // const editorElem = document.querySelector("#cms-article-text-editor"); + + // if (editorElem) { + // const saveUrl = editorElem.getAttribute("data-save-url"); + // const variantUrl = editorElem.getAttribute("data-variant-url"); + + // if (!saveUrl) { + // console.error("saveUrl is null"); + // return; + // } + + // if (!variantUrl) { + // console.error("variantUrl is null"); + // return; + // } + + // const builder = new CmsEditorBuilder( + // editorElem as HTMLElement, + // saveUrl, + // variantUrl + // ); + + // builder.buildEditor(); + // } +}); diff --git a/ccm-cms/src/main/typescript/content-sections/ccm-cms-editorjs-image/ccm-cms-editorjs-image.ts b/ccm-cms/src/main/typescript/content-sections/ccm-cms-editorjs-image/ccm-cms-editorjs-image.ts new file mode 100644 index 000000000..fb5d6c2bd --- /dev/null +++ b/ccm-cms/src/main/typescript/content-sections/ccm-cms-editorjs-image/ccm-cms-editorjs-image.ts @@ -0,0 +1,131 @@ +import * as jquery from "jquery"; + +export class CmsImage { + static get toolbox() { + return { + title: "Image", + icon: "" + } + } + + public render() { + const figureElem = document.createElement("figure"); + + const imgElem = document.createElement("img"); + imgElem.src = "data:image/svg+xml;utf8,"; + imgElem.setAttribute("style", "width: 100%"); + figureElem.appendChild(imgElem); + + const captionElem = document.createElement("figcaption"); + captionElem.contentEditable = "true"; + captionElem.innerHTML = "Caption placeholder" + figureElem.appendChild(captionElem); + + return figureElem; + } + + public renderSettings() { + const updateImageButton = document.createElement("button"); + updateImageButton.title = "Update image"; + updateImageButton.classList.add("cdx-settings-button"); + updateImageButton.innerHTML = ""; + updateImageButton.addEventListener("click", (event) => { + event.preventDefault(); + this.updateImage(); + }); + + const settings = document.createElement("div"); + settings.appendChild(updateImageButton); + + return settings; + } + + public save(blockContent: any) { + return { + url: blockContent.value + } + } + + private updateImage() { + (jquery("#exampleModal") as any).modal("toggle"); + + const modalElem = document.createElement("div"); + modalElem.classList.add("modal"); + modalElem.classList.add("fade"); + modalElem.id = "cms-editor-image-dialog"; + modalElem.setAttribute("aria-labelledby", "cms-editor-image-dialog-title"); + // modalElem.setAttribute("data-show", "true"); + + const modalDialogElem = document.createElement("div"); + modalDialogElem.classList.add("modal-dialog"); + modalElem.appendChild(modalDialogElem); + + const modalContentElem = document.createElement("div"); + modalContentElem.classList.add("modal-content"); + modalDialogElem.appendChild(modalContentElem); + + const modalHeaderElem = document.createElement("div"); + modalHeaderElem.classList.add("modal-header"); + modalContentElem.appendChild(modalHeaderElem); + + const modalTitleElem = document.createElement("h3"); + modalTitleElem.classList.add("modal-title"); + modalTitleElem.id = "cms-editor-image-dialog-title"; + modalTitleElem.textContent = "Update image"; + modalHeaderElem.appendChild(modalTitleElem); + + const modalHeaderCloseButton = document.createElement("button"); + modalHeaderCloseButton.classList.add("close"); + modalHeaderCloseButton.setAttribute("aria-label", "Cancel"); + modalHeaderCloseButton.type = "button"; + modalHeaderCloseButton.innerHTML = ""; + modalHeaderElem.appendChild(modalHeaderCloseButton); + + const modalBodyElem = document.createElement("div"); + modalBodyElem.classList.add("modal-body"); + modalBodyElem.innerHTML = "Update image placeholder"; + modalContentElem.appendChild(modalBodyElem); + + const modalFooterElem = document.createElement("div"); + const cancelButtonElem = document.createElement("button"); + cancelButtonElem.classList.add("btn"); + cancelButtonElem.classList.add("btn-warning"); + cancelButtonElem.type = "button"; + const saveButtonElem = document.createElement("button"); + saveButtonElem.classList.add("btn"); + saveButtonElem.classList.add("btn-success"); + modalFooterElem.appendChild(cancelButtonElem); + modalFooterElem.appendChild(saveButtonElem); + modalContentElem.appendChild(modalFooterElem); + + modalHeaderCloseButton.addEventListener("click", event => { + event.preventDefault(); + const bodyElem = document.querySelector("body"); + bodyElem?.removeChild(modalElem); + }); + + cancelButtonElem.addEventListener("click", event => { + event.preventDefault(); + const bodyElem = document.querySelector("body"); + bodyElem?.removeChild(modalElem); + }); + + saveButtonElem.addEventListener("click", event => { + event.preventDefault(); + + console.log("updating image..."); + + const bodyElem = document.querySelector("body"); + bodyElem?.removeChild(modalElem); + }); + + const bodyElem = document.querySelector("body"); + bodyElem?.appendChild(modalElem); + + + console.dir(jquery); + + (jquery("#cms-editor-image-dialog") as any).modal(); + (jquery("#cms-editor-image-dialog") as any).modal("toggle"); + } +} \ No newline at end of file diff --git a/ccm-cms/src/main/typescript/content-sections/cms-editor-tiptap.ts b/ccm-cms/src/main/typescript/content-sections/cms-editor-tiptap.ts new file mode 100644 index 000000000..3906a9790 --- /dev/null +++ b/ccm-cms/src/main/typescript/content-sections/cms-editor-tiptap.ts @@ -0,0 +1,808 @@ +import "bootstrap"; +import * as $ from "jquery"; +import { ChainedCommands, Editor, Node, mergeAttributes } from "@tiptap/core"; +import Gapcursor from "@tiptap/extension-gapcursor"; +import StarterKit from "@tiptap/starter-kit"; +import Subscript from "@tiptap/extension-subscript"; +import Superscript from "@tiptap/extension-superscript"; +import Table from "@tiptap/extension-table"; +import TableRow from "@tiptap/extension-table-row"; +import TableCell from "@tiptap/extension-table-cell"; +import TableHeader from "@tiptap/extension-table-header"; + +import CcmImage from "./cms-editor/image"; + +const BUTTONS: CmsEditorButton[] = [ + { + selector: ".tiptap-emph", + command: cmsEditor => { + return cmsEditor.getEditor().chain().focus().toggleItalic().run(); + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .toggleItalic() + .run(); + } + }, + { + selector: ".tiptap-strong-emph", + command: cmsEditor => { + return cmsEditor.getEditor().chain().focus().toggleBold().run(); + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .toggleBold() + .run(); + } + }, + { + selector: ".tiptap-code", + command: cmsEditor => { + return cmsEditor.getEditor().chain().focus().toggleCode().run(); + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .toggleCode() + .run(); + } + }, + { + selector: ".tiptap-strikethrough", + command: cmsEditor => { + return cmsEditor.getEditor().chain().focus().toggleStrike().run(); + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .toggleStrike() + .run(); + } + }, + { + selector: ".tiptap-subscript", + command: cmsEditor => { + return cmsEditor + .getEditor() + .chain() + .focus() + .toggleSubscript() + .run(); + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .toggleSubscript() + .run(); + } + }, + { + selector: ".tiptap-superscript", + command: cmsEditor => { + return cmsEditor + .getEditor() + .chain() + .focus() + .toggleSuperscript() + .run(); + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .toggleSuperscript() + .run(); + } + }, + { + selector: ".tiptap-h1", + command: cmsEditor => { + return cmsEditor + .getEditor() + .chain() + .focus() + .toggleHeading({ level: 1 }) + .run(); + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .toggleHeading({ level: 1 }) + .run(); + } + }, + { + selector: ".tiptap-h2", + command: cmsEditor => { + return cmsEditor + .getEditor() + .chain() + .focus() + .toggleHeading({ level: 2 }) + .run(); + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .toggleHeading({ level: 2 }) + .run(); + } + }, + { + selector: ".tiptap-h3", + command: cmsEditor => { + return cmsEditor + .getEditor() + .chain() + .focus() + .toggleHeading({ level: 3 }) + .run(); + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .toggleHeading({ level: 3 }) + .run(); + } + }, + { + selector: ".tiptap-h5", + command: cmsEditor => { + return cmsEditor + .getEditor() + .chain() + .focus() + .toggleHeading({ level: 5 }) + .run(); + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .toggleHeading({ level: 5 }) + .run(); + } + }, + { + selector: ".tiptap-h6", + command: cmsEditor => { + return cmsEditor + .getEditor() + .chain() + .focus() + .toggleHeading({ level: 6 }) + .run(); + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .toggleHeading({ level: 6 }) + .run(); + } + }, + { + selector: ".tiptap-paragraph", + command: cmsEditor => { + return cmsEditor.getEditor().chain().focus().clearNodes().run(); + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .clearNodes() + .run(); + } + }, + { + selector: ".tiptap-blockquote", + command: cmsEditor => { + return cmsEditor + .getEditor() + .chain() + .focus() + .toggleBlockquote() + .run(); + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .toggleBlockquote() + .run(); + } + }, + { + selector: ".tiptap-codeblock", + command: cmsEditor => { + return cmsEditor + .getEditor() + .chain() + .focus() + .toggleCodeBlock() + .run(); + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .toggleCodeBlock() + .run(); + } + }, + { + selector: ".tiptap-ul", + command: cmsEditor => { + return cmsEditor + .getEditor() + .chain() + .focus() + .toggleBulletList() + .run(); + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .toggleBulletList() + .run(); + } + }, + { + selector: ".tiptap-ol", + command: cmsEditor => { + return cmsEditor + .getEditor() + .chain() + .focus() + .toggleOrderedList() + .run(); + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .toggleOrderedList() + .run(); + } + }, + { + selector: ".cms-editor-insert-table-dialog", + command: cmsEditor => { + return true; + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .insertTable() + .run(); + } + }, + { + selector: ".cms-editor-insert-table-dialog .btn-success", + command: cmsEditor => { + const dialog = cmsEditor + .getEditorElem() + .querySelector(".cms-editor-insert-table-dialog"); + if (!dialog) { + return false; + } + const rowsInput = dialog.querySelector( + "input#rows" + ) as HTMLInputElement; + const colsInput = dialog.querySelector( + "input#cols" + ) as HTMLInputElement; + const headerRowInput = dialog.querySelector( + "input#headerRow" + ) as HTMLInputElement; + console.log(`rowsInput = ${rowsInput}`); + console.log(`colsInput = ${colsInput}`); + console.log(`headerRowInput = ${headerRowInput}`); + const rows = parseInt(rowsInput.value, 10); + const cols = parseInt(colsInput.value, 10); + const headerRow = JSON.parse(headerRowInput.value) as Boolean; + const insertTableDialog = $("#insert-table-dialog") as any; + insertTableDialog.modal("hide"); + return cmsEditor + .getEditor() + .chain() + .focus() + .insertTable({ + // allowTableNodeSelection: true, + // cellMinWidth: 150, + cols: cols, + // headerRow: headerRow, + // resizable: true, + rows: rows + }) + .run(); + }, + can: cmsEditor => { + return true; + } + }, + { + selector: ".tiptap-insert-table-row-before", + command: cmsEditor => { + return cmsEditor.getEditor().chain().focus().addRowBefore().run(); + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .addRowBefore() + .run(); + } + }, + { + selector: ".tiptap-insert-table-row-after", + command: cmsEditor => { + return cmsEditor.getEditor().chain().focus().addRowAfter().run(); + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .addRowAfter() + .run(); + } + }, + { + selector: ".tiptap-insert-table-column-before", + command: cmsEditor => { + return cmsEditor + .getEditor() + .chain() + .focus() + .addColumnBefore() + .run(); + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .addColumnBefore() + .run(); + } + }, + { + selector: ".tiptap-insert-table-column-after", + command: cmsEditor => { + return cmsEditor.getEditor().chain().focus().addColumnAfter().run(); + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .addColumnAfter() + .run(); + } + }, + { + selector: ".tiptap-remove-table-row", + command: cmsEditor => { + return cmsEditor.getEditor().chain().focus().deleteRow().run(); + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .deleteRow() + .run(); + } + }, + { + selector: ".tiptap-remove-table-column", + command: cmsEditor => { + return cmsEditor.getEditor().chain().focus().deleteColumn().run(); + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .deleteColumn() + .run(); + } + }, + { + selector: ".tiptap-remove-table", + command: cmsEditor => { + return cmsEditor.getEditor().chain().focus().deleteTable().run(); + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .deleteTable() + .run(); + } + }, + { + selector: ".tiptap-toggle-table-header-row", + command: cmsEditor => { + return cmsEditor + .getEditor() + .chain() + .focus() + .toggleHeaderRow() + .run(); + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .toggleHeaderRow() + .run(); + } + }, + { + selector: ".tiptap-toggle-table-header-column", + command: cmsEditor => { + return cmsEditor + .getEditor() + .chain() + .focus() + .toggleHeaderColumn() + .run(); + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .toggleHeaderColumn() + .run(); + } + }, + { + selector: ".tiptap-merge-table-cells", + command: cmsEditor => { + return cmsEditor.getEditor().chain().focus().mergeCells().run(); + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .mergeCells() + .run(); + } + }, + { + selector: ".tiptap-split-table-cell", + command: cmsEditor => { + return cmsEditor.getEditor().chain().focus().splitCell().run(); + }, + can: cmsEditor => { + return cmsEditor + .getEditor() + .can() + .chain() + .focus() + .splitCell() + .run(); + } + }, + { + selector: ".tiptap-insert-image", + command: cmsEditor => { + return cmsEditor.getEditor().chain().focus().addCcmImage().run(); + }, + can: cmsEditor => { + return cmsEditor.getEditor().can().chain().focus().addCcmImage().run(); + } + } + // { + // selector: "", + // command: cmsEditor => {}, + // can: cmsEditor => {} + // }, + // { + // selector: "", + // command: cmsEditor => {}, + // can: cmsEditor => {} + // }, + // { + // selector: "", + // command: cmsEditor => {}, + // can: cmsEditor => {} + // } +]; + +class CmsEditor { + private editor: Editor; + private editorElem: HTMLElement; + private saveUrl: string; + + public constructor( + editor: Editor, + editorElem: HTMLElement, + saveUrl: string + ) { + this.editor = editor; + this.editorElem = editorElem; + this.saveUrl = saveUrl; + + console.log("initializing editor buttons"); + const buttonsElem = editorElem.querySelector( + ".cms-tiptap-editor-buttons" + ); + if (buttonsElem) { + for (const button of BUTTONS) { + const buttonElem = buttonsElem.querySelector(button.selector); + if (buttonElem) { + buttonElem.addEventListener("click", event => { + event.preventDefault(); + button.command(this); + }); + } else { + continue; + } + } + } else { + console.error("editorButtonsElem not found."); + return; + } + + editor.on("selectionUpdate", ({ editor }: { editor: Editor }) => { + console.log(`checkButton - this.editorElem = ${this.editorElem}`); + const buttonsElem = editorElem.querySelector( + ".cms-tiptap-editor-buttons" + ); + if (!buttonsElem) { + return; + } + for (const button of BUTTONS) { + const elem = buttonsElem.querySelector(button.selector); + if (elem) { + const buttonElem = elem as HTMLButtonElement; + if (button.can(this)) { + buttonElem.removeAttribute("disabled"); + } else { + buttonElem.setAttribute("disabled", "disabled"); + } + } else { + continue; + } + } + }); + + console.log(`editorElem = ${editorElem}`); + + const saveButton = editorElem.querySelector(".cms-editor-save-button"); + saveButton?.addEventListener("click", event => this.save(event)); + } + + protected async save(event: Event) { + event.preventDefault(); + + const params = new URLSearchParams(); + params.append("value", this.editor.getHTML()); + + try { + const response = await fetch(this.saveUrl, { + method: "POST", + credentials: "include", + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + body: params + }); + if (response.ok) { + } else { + this.showSaveFailedMessage( + response.status, + response.statusText + ); + } + } catch (error) { + this.showSaveFailedErrMessage(error as string); + } + } + + protected showSaveFailedMessage(status: number, statusText: string) { + this.showMessage("#cms-editor-msg-save-failed"); + console.error( + `Failed to save text. Status: ${statusText}. Status Text: ${statusText}` + ); + } + + protected showSaveFailedErrMessage(error: string) { + this.showMessage("#cms-editor-msg-save-failed"); + console.error(error); + } + + protected showMessage(messageId: string) { + const template = this.editorElem.querySelector( + messageId + ) as HTMLTemplateElement; + const message = template.content.cloneNode(true); + this.editorElem.querySelector(".cms-editor-messages")?.append(message); + } + + public getEditor(): Editor { + return this.editor; + } + + public getEditorElem(): HTMLElement { + return this.editorElem; + } +} + +class CmsEditorBuilder { + private editorElem: HTMLElement; + private saveUrl: string; + private variantUrl: string; + + constructor(editorElem: HTMLElement, saveUrl: string, variantUrl: string) { + this.editorElem = editorElem; + this.saveUrl = saveUrl; + this.variantUrl = variantUrl; + } + + public async buildEditor(): Promise { + console.log("Build CMS Editor."); + const canvasElement = this.editorElem.querySelector( + ".cms-tiptap-editor-canvas" + ); + if (!canvasElement) { + this.showMessage("#cms-editor-msg-canvas-element-not-found"); + console.error("canvasElem not found."); + throw "canvasElem not found."; + } + + const variant = await this.fetchVariant(this.variantUrl); + + const editor: Editor = new Editor({ + element: canvasElement, + extensions: [ + Gapcursor, + CcmImage, + StarterKit, + Subscript, + Superscript, + Table.configure({ + allowTableNodeSelection: true, + cellMinWidth: 100, + handleWidth: 25, + resizable: true + }), + TableRow, + TableHeader, + TableCell + ], + content: variant, + onUpdate({editor}) { + const preview = document.querySelector("#html-preview"); + if (preview) { + preview.innerHTML = editor.getHTML(); + } + } + }); + + return new CmsEditor(editor, this.editorElem, this.saveUrl); + } + + protected async fetchVariant(variantUrl: string): Promise { + try { + const response = await fetch(variantUrl, { + method: "GET", + credentials: "include" + }); + + if (response.ok) { + return await response.text(); + } else { + this.showLoadVariantFailedMessage( + response.status, + response.statusText + ); + throw `Failed to load variant. Status: ${response.status}, Status Text: ${response.statusText}`; + } + } catch (error) { + this.showLoadVariantFailedErrorMessage(error as string); + throw error; + } + } + + protected showLoadVariantFailedMessage(status: number, statusText: string) { + this.showMessage("#cms-editor-msg-variant-load-failed"); + console.error( + `Failed to load variant: HTTP Status: ${status}, statusText: ${statusText}` + ); + } + + protected showLoadVariantFailedErrorMessage(error: string) { + this.showMessage("#cms-editor-msg-variant-load-failed"); + console.error(`Failed to load variant: ${error}`); + } + + protected showMessage(messageId: string) { + const template = this.editorElem.querySelector( + messageId + ) as HTMLTemplateElement; + const message = template.content.cloneNode(true); + this.editorElem.querySelector(".cms-editor-messages")?.append(message); + } +} + +interface CmsEditorParameters { + editorElem: HTMLElement; + variantUrl: string; +} + +interface CmsEditorButton { + selector: string; + command: (cmsEditor: CmsEditor) => boolean; + can: (cmsEditor: CmsEditor) => boolean; +} + +interface EditorParam { + editor: Editor; +} + +export { CmsEditor, CmsEditorBuilder, CmsEditorParameters }; diff --git a/ccm-cms/src/main/typescript/content-sections/cms-editor.ts b/ccm-cms/src/main/typescript/content-sections/cms-editor.ts index f7d725a09..57ea77c70 100644 --- a/ccm-cms/src/main/typescript/content-sections/cms-editor.ts +++ b/ccm-cms/src/main/typescript/content-sections/cms-editor.ts @@ -1,795 +1,2 @@ -import "bootstrap"; -import * as $ from "jquery"; -import { ChainedCommands, Editor } from "@tiptap/core"; -import Gapcursor from "@tiptap/extension-gapcursor"; -import StarterKit from "@tiptap/starter-kit"; -import Subscript from "@tiptap/extension-subscript"; -import Superscript from "@tiptap/extension-superscript"; -import Table from "@tiptap/extension-table"; -import TableRow from "@tiptap/extension-table-row"; -import TableCell from "@tiptap/extension-table-cell"; -import TableHeader from "@tiptap/extension-table-header"; +//import EditorJS from '@editorjs/editorjs'; -const BUTTONS: CmsEditorButton[] = [ - { - selector: ".tiptap-emph", - command: cmsEditor => { - return cmsEditor.getEditor().chain().focus().toggleItalic().run(); - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .toggleItalic() - .run(); - } - }, - { - selector: ".tiptap-strong-emph", - command: cmsEditor => { - return cmsEditor.getEditor().chain().focus().toggleBold().run(); - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .toggleBold() - .run(); - } - }, - { - selector: ".tiptap-code", - command: cmsEditor => { - return cmsEditor.getEditor().chain().focus().toggleCode().run(); - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .toggleCode() - .run(); - } - }, - { - selector: ".tiptap-strikethrough", - command: cmsEditor => { - return cmsEditor.getEditor().chain().focus().toggleStrike().run(); - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .toggleStrike() - .run(); - } - }, - { - selector: ".tiptap-subscript", - command: cmsEditor => { - return cmsEditor - .getEditor() - .chain() - .focus() - .toggleSubscript() - .run(); - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .toggleSubscript() - .run(); - } - }, - { - selector: ".tiptap-superscript", - command: cmsEditor => { - return cmsEditor - .getEditor() - .chain() - .focus() - .toggleSuperscript() - .run(); - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .toggleSuperscript() - .run(); - } - }, - { - selector: ".tiptap-h1", - command: cmsEditor => { - return cmsEditor - .getEditor() - .chain() - .focus() - .toggleHeading({ level: 1 }) - .run(); - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .toggleHeading({ level: 1 }) - .run(); - } - }, - { - selector: ".tiptap-h2", - command: cmsEditor => { - return cmsEditor - .getEditor() - .chain() - .focus() - .toggleHeading({ level: 2 }) - .run(); - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .toggleHeading({ level: 2 }) - .run(); - } - }, - { - selector: ".tiptap-h3", - command: cmsEditor => { - return cmsEditor - .getEditor() - .chain() - .focus() - .toggleHeading({ level: 3 }) - .run(); - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .toggleHeading({ level: 3 }) - .run(); - } - }, - { - selector: ".tiptap-h5", - command: cmsEditor => { - return cmsEditor - .getEditor() - .chain() - .focus() - .toggleHeading({ level: 5 }) - .run(); - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .toggleHeading({ level: 5 }) - .run(); - } - }, - { - selector: ".tiptap-h6", - command: cmsEditor => { - return cmsEditor - .getEditor() - .chain() - .focus() - .toggleHeading({ level: 6 }) - .run(); - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .toggleHeading({ level: 6 }) - .run(); - } - }, - { - selector: ".tiptap-paragraph", - command: cmsEditor => { - return cmsEditor.getEditor().chain().focus().clearNodes().run(); - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .clearNodes() - .run(); - } - }, - { - selector: ".tiptap-blockquote", - command: cmsEditor => { - return cmsEditor - .getEditor() - .chain() - .focus() - .toggleBlockquote() - .run(); - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .toggleBlockquote() - .run(); - } - }, - { - selector: ".tiptap-codeblock", - command: cmsEditor => { - return cmsEditor - .getEditor() - .chain() - .focus() - .toggleCodeBlock() - .run(); - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .toggleCodeBlock() - .run(); - } - }, - { - selector: ".tiptap-ul", - command: cmsEditor => { - return cmsEditor - .getEditor() - .chain() - .focus() - .toggleBulletList() - .run(); - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .toggleBulletList() - .run(); - } - }, - { - selector: ".tiptap-ol", - command: cmsEditor => { - return cmsEditor - .getEditor() - .chain() - .focus() - .toggleOrderedList() - .run(); - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .toggleOrderedList() - .run(); - } - }, - { - selector: ".cms-editor-insert-table-dialog", - command: cmsEditor => { - return true; - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .insertTable() - .run(); - } - }, - { - selector: ".cms-editor-insert-table-dialog .btn-success", - command: cmsEditor => { - const dialog = cmsEditor - .getEditorElem() - .querySelector(".cms-editor-insert-table-dialog"); - if (!dialog) { - return false; - } - const rowsInput = dialog.querySelector( - "input#rows" - ) as HTMLInputElement; - const colsInput = dialog.querySelector( - "input#cols" - ) as HTMLInputElement; - const headerRowInput = dialog.querySelector( - "input#headerRow" - ) as HTMLInputElement; - console.log(`rowsInput = ${rowsInput}`); - console.log(`colsInput = ${colsInput}`); - console.log(`headerRowInput = ${headerRowInput}`); - const rows = parseInt(rowsInput.value, 10); - const cols = parseInt(colsInput.value, 10); - const headerRow = JSON.parse(headerRowInput.value) as Boolean; - const insertTableDialog = $("#insert-table-dialog") as any; - insertTableDialog.modal("hide"); - return cmsEditor - .getEditor() - .chain() - .focus() - .insertTable({ - // allowTableNodeSelection: true, - // cellMinWidth: 150, - cols: cols, - // headerRow: headerRow, - // resizable: true, - rows: rows - }) - .run(); - }, - can: cmsEditor => { - return true; - } - }, - { - selector: ".tiptap-insert-table-row-before", - command: cmsEditor => { - return cmsEditor.getEditor().chain().focus().addRowBefore().run(); - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .addRowBefore() - .run(); - } - }, - { - selector: ".tiptap-insert-table-row-after", - command: cmsEditor => { - return cmsEditor.getEditor().chain().focus().addRowAfter().run(); - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .addRowAfter() - .run(); - } - }, - { - selector: ".tiptap-insert-table-column-before", - command: cmsEditor => { - return cmsEditor - .getEditor() - .chain() - .focus() - .addColumnBefore() - .run(); - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .addColumnBefore() - .run(); - } - }, - { - selector: ".tiptap-insert-table-column-after", - command: cmsEditor => { - return cmsEditor.getEditor().chain().focus().addColumnAfter().run(); - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .addColumnAfter() - .run(); - } - }, - { - selector: ".tiptap-remove-table-row", - command: cmsEditor => { - return cmsEditor.getEditor().chain().focus().deleteRow().run(); - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .deleteRow() - .run(); - } - }, - { - selector: ".tiptap-remove-table-column", - command: cmsEditor => { - return cmsEditor.getEditor().chain().focus().deleteColumn().run(); - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .deleteColumn() - .run(); - } - }, - { - selector: ".tiptap-remove-table", - command: cmsEditor => { - return cmsEditor.getEditor().chain().focus().deleteTable().run(); - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .deleteTable() - .run(); - } - }, - { - selector: ".tiptap-toggle-table-header-row", - command: cmsEditor => { - return cmsEditor - .getEditor() - .chain() - .focus() - .toggleHeaderRow() - .run(); - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .toggleHeaderRow() - .run(); - } - }, - { - selector: ".tiptap-toggle-table-header-column", - command: cmsEditor => { - return cmsEditor - .getEditor() - .chain() - .focus() - .toggleHeaderColumn() - .run(); - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .toggleHeaderColumn() - .run(); - } - }, - { - selector: ".tiptap-merge-table-cells", - command: cmsEditor => { - return cmsEditor.getEditor().chain().focus().mergeCells().run(); - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .mergeCells() - .run(); - } - }, - { - selector: ".tiptap-split-table-cell", - command: cmsEditor => { - return cmsEditor.getEditor().chain().focus().splitCell().run(); - }, - can: cmsEditor => { - return cmsEditor - .getEditor() - .can() - .chain() - .focus() - .splitCell() - .run(); - } - } - // { - // selector: "", - // command: cmsEditor => {}, - // can: cmsEditor => {} - // }, - // { - // selector: "", - // command: cmsEditor => {}, - // can: cmsEditor => {} - // }, - // { - // selector: "", - // command: cmsEditor => {}, - // can: cmsEditor => {} - // }, - // { - // selector: "", - // command: cmsEditor => {}, - // can: cmsEditor => {} - // } -]; - -class CmsEditor { - private editor: Editor; - private editorElem: HTMLElement; - private saveUrl: string; - - public constructor( - editor: Editor, - editorElem: HTMLElement, - saveUrl: string - ) { - this.editor = editor; - this.editorElem = editorElem; - this.saveUrl = saveUrl; - - console.log("initializing editor buttons"); - const buttonsElem = editorElem.querySelector( - ".cms-tiptap-editor-buttons" - ); - if (buttonsElem) { - for (const button of BUTTONS) { - const buttonElem = buttonsElem.querySelector(button.selector); - if (buttonElem) { - buttonElem.addEventListener("click", event => { - event.preventDefault(); - button.command(this); - }); - } else { - continue; - } - } - } else { - console.error("editorButtonsElem not found."); - return; - } - - editor.on("selectionUpdate", ({ editor }: { editor: Editor }) => { - console.log(`checkButton - this.editorElem = ${this.editorElem}`); - const buttonsElem = editorElem.querySelector( - ".cms-tiptap-editor-buttons" - ); - if (!buttonsElem) { - return; - } - for (const button of BUTTONS) { - const elem = buttonsElem.querySelector(button.selector); - if (elem) { - const buttonElem = elem as HTMLButtonElement; - if (button.can(this)) { - buttonElem.removeAttribute("disabled"); - } else { - buttonElem.setAttribute("disabled", "disabled"); - } - } else { - continue; - } - } - }); - - console.log(`editorElem = ${editorElem}`); - - const saveButton = editorElem.querySelector(".cms-editor-save-button"); - saveButton?.addEventListener("click", event => this.save(event)); - } - - protected async save(event: Event) { - event.preventDefault(); - - const params = new URLSearchParams(); - params.append("value", this.editor.getHTML()); - - try { - const response = await fetch(this.saveUrl, { - method: "POST", - credentials: "include", - headers: { - "Content-Type": "application/x-www-form-urlencoded" - }, - body: params - }); - if (response.ok) { - } else { - this.showSaveFailedMessage( - response.status, - response.statusText - ); - } - } catch (error) { - this.showSaveFailedErrMessage(error as string); - } - } - - protected showSaveFailedMessage(status: number, statusText: string) { - this.showMessage("#cms-editor-msg-save-failed"); - console.error( - `Failed to save text. Status: ${statusText}. Status Text: ${statusText}` - ); - } - - protected showSaveFailedErrMessage(error: string) { - this.showMessage("#cms-editor-msg-save-failed"); - console.error(error); - } - - protected showMessage(messageId: string) { - const template = this.editorElem.querySelector( - messageId - ) as HTMLTemplateElement; - const message = template.content.cloneNode(true); - this.editorElem.querySelector(".cms-editor-messages")?.append(message); - } - - public getEditor(): Editor { - return this.editor; - } - - public getEditorElem(): HTMLElement { - return this.editorElem; - } -} - -class CmsEditorBuilder { - private editorElem: HTMLElement; - private saveUrl: string; - private variantUrl: string; - - constructor(editorElem: HTMLElement, saveUrl: string, variantUrl: string) { - this.editorElem = editorElem; - this.saveUrl = saveUrl; - this.variantUrl = variantUrl; - } - - public async buildEditor(): Promise { - console.log("Build CMS Editor."); - const canvasElement = this.editorElem.querySelector( - ".cms-tiptap-editor-canvas" - ); - if (!canvasElement) { - this.showMessage("#cms-editor-msg-canvas-element-not-found"); - console.error("canvasElem not found."); - throw "canvasElem not found."; - } - - const variant = await this.fetchVariant(this.variantUrl); - - const editor: Editor = new Editor({ - element: canvasElement, - extensions: [ - Gapcursor, - StarterKit, - Subscript, - Superscript, - Table.configure({ - allowTableNodeSelection: true, - cellMinWidth: 100, - handleWidth: 25, - resizable: true - }), - TableRow, - TableHeader, - TableCell - ], - content: variant - }); - - return new CmsEditor(editor, this.editorElem, this.saveUrl); - } - - protected async fetchVariant(variantUrl: string): Promise { - try { - const response = await fetch(variantUrl, { - method: "GET", - credentials: "include" - }); - - if (response.ok) { - return await response.text(); - } else { - this.showLoadVariantFailedMessage( - response.status, - response.statusText - ); - throw `Failed to load variant. Status: ${response.status}, Status Text: ${response.statusText}`; - } - } catch (error) { - this.showLoadVariantFailedErrorMessage(error as string); - throw error; - } - } - - protected showLoadVariantFailedMessage(status: number, statusText: string) { - this.showMessage("#cms-editor-msg-variant-load-failed"); - console.error( - `Failed to load variant: HTTP Status: ${status}, statusText: ${statusText}` - ); - } - - protected showLoadVariantFailedErrorMessage(error: string) { - this.showMessage("#cms-editor-msg-variant-load-failed"); - console.error(`Failed to load variant: ${error}`); - } - - protected showMessage(messageId: string) { - const template = this.editorElem.querySelector( - messageId - ) as HTMLTemplateElement; - const message = template.content.cloneNode(true); - this.editorElem.querySelector(".cms-editor-messages")?.append(message); - } -} - -interface CmsEditorParameters { - editorElem: HTMLElement; - variantUrl: string; -} - -interface CmsEditorButton { - selector: string; - command: (cmsEditor: CmsEditor) => boolean; - can: (cmsEditor: CmsEditor) => boolean; -} - -interface EditorParam { - editor: Editor; -} - -export { CmsEditor, CmsEditorBuilder, CmsEditorParameters }; diff --git a/ccm-cms/src/main/typescript/content-sections/cms-editor/image/image.ts b/ccm-cms/src/main/typescript/content-sections/cms-editor/image/image.ts new file mode 100644 index 000000000..e3b0deb3c --- /dev/null +++ b/ccm-cms/src/main/typescript/content-sections/cms-editor/image/image.ts @@ -0,0 +1,80 @@ +import { Node, mergeAttributes } from "@tiptap/core"; +import { insertContent } from "@tiptap/core/dist/packages/core/src/extensions/commands"; + +export interface CcmImageOptions { + HTMLAttributes: Record; +} + +declare module "@tiptap/core" { + interface Commands { + ccmImage: { + addCcmImage: () => ReturnType; + }; + } +} + +export const CcmImage = Node.create({ + name: "ccmImage", + + // priority: 1000, + + defaultOptions: { + HTMLAttributes: {}, + }, + + group: "block", + + content: "inline*", + + parseHTML() { + return [ + { tag: "figure"} + ] + }, + + renderHTML({ HTMLAttributes }) { + return ["node-view", mergeAttributes(HTMLAttributes)]; + // return [ + // "figure", + // mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), + // ["figcaption", HTMLAttributes, 0], + // // ["img", HTMLAttributes, 0] + // ] + }, + + addCommands() { + return { + addCcmImage: () => ({ commands }) => { + return commands.setNode("ccmImage"); + } + } + }, + + // addKeyboardShortcut() { + // return { + // "Mod-Alt-i": () => this.editor.commands.addCcmImage() + // } + // }, + + addNodeView() { + return () => { + const dom = document.createElement("figure"); + dom.classList.add("ccm-image"); + const caption = document.createElement("figcaption") as HTMLElement; + caption.classList.add("label"); + caption.innerHTML = "Node View"; + caption.contentEditable = "true"; + + const content = document.createElement("div"); + content.classList.add("content"); + + dom.append(caption, content); + + return { + dom, + contentDom: content + } + } + } +}); + diff --git a/ccm-cms/src/main/typescript/content-sections/cms-editor/image/index.ts b/ccm-cms/src/main/typescript/content-sections/cms-editor/image/index.ts new file mode 100644 index 000000000..2401e2032 --- /dev/null +++ b/ccm-cms/src/main/typescript/content-sections/cms-editor/image/index.ts @@ -0,0 +1,5 @@ +import { CcmImage } from "./image"; + +export * from "./image"; + +export default CcmImage; \ No newline at end of file diff --git a/ccm-cms/tsconfig.json b/ccm-cms/tsconfig.json index c4864df59..6b0c56e0a 100644 --- a/ccm-cms/tsconfig.json +++ b/ccm-cms/tsconfig.json @@ -11,6 +11,7 @@ "outDir": "target/generated-resources/assets/@content-sections", "sourceMap": true, "strict": true, + "target": "ES5" }, "include": [ "src/main/typescript/**/*" diff --git a/ccm-cms/webpack.config.js b/ccm-cms/webpack.config.js index c19f3a6c8..294cdf130 100644 --- a/ccm-cms/webpack.config.js +++ b/ccm-cms/webpack.config.js @@ -3,7 +3,7 @@ module.exports = { devtool: "source-map", entry: { "cms-admin": "./src/main/typescript/content-sections/cms-admin.ts", - "article-text-step": "./src/main/typescript/content-sections/article-text-step.ts" + "article-text-step": "./src/main/typescript/content-sections/article-text-step.js" }, output: { filename: "[name].js",