Reverted to Tiptap

pull/10/head
Jens Pelzetter 2021-09-27 20:25:25 +02:00
parent 50600de813
commit a28673c8e2
15 changed files with 1182 additions and 1870 deletions

View File

@ -248,6 +248,14 @@
<include>assets/</include>
</includes>
</overlay>
<overlay>
<groupId>org.librecms</groupId>
<artifactId>ccm-cms</artifactId>
<type>jar</type>
<includes>
<include>icons/</include>
</includes>
</overlay>
<overlay>
<groupId>org.librecms</groupId>
<artifactId>ccm-cms</artifactId>

View File

@ -10,36 +10,6 @@
"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.104",
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.0.0-beta.104.tgz",
@ -243,11 +213,6 @@
"@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",
@ -761,16 +726,6 @@
"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",
@ -826,11 +781,6 @@
"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",
@ -1466,11 +1416,6 @@
"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",

View File

@ -22,11 +22,6 @@
"webpack-cli": "^4.8.0"
},
"dependencies": {
"@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",
@ -35,11 +30,9 @@
"@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.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"

View File

@ -1,659 +0,0 @@
<!DOCTYPE html [<!ENTITY times '&#215;'>]>
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:bootstrap="http://xmlns.jcp.org/jsf/composite/components/bootstrap"
xmlns:cc="http://xmlns.jcp.org/jsf/composite"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
>
<cc:interface
shortDescription="A editor component for HTML texts using the TipTap editor. To use this component you have also to include the cms-editor.js file into the page."
>
<!-- <cc:attribute
name="addMethod"
required="true"
shortDescription="URL of teh endpoint for adding localized values."
type="String"
/>
<cc:attribute
name="addButtonLabel"
default="Add"
required="false"
shortDescription="Label for the add button"
type="String"
/>
<cc:attribute
name="addDialogCancelLabel"
default="#{CmsAdminMessages['cancel_button.label']}"
required="false"
shortDescription="Label for the cancel and close buttons."
type="String"
/>
<cc:attribute
name="addDialogLocaleSelectHelp"
default="The locale of the value of add"
required="false"
shortDescription="Help text for the locale select field"
type="String"
/>
<cc:attribute
name="addDialogLocaleSelectLabel"
default="#{CmsAdminMessages['locale_select.label']}"
required="false"
shortDescription="Label for the locale select field"
type="String"
/>
<cc:attribute
name="addDialogSubmitLabel"
default="#{CmsAdminMessages['locale_add.label']}"
required="false"
shortDescription="Label for the submit button"
type="String"
/>
<cc:attribute
name="addDialogTitle"
default="#{CmsAdminMessages['locale_add.dialog.title']}"
required="false"
shortDescription="Title for the dialog."
type="String"
/>-->
<cc:attribute
name="backUrl"
required="true"
shortDescription="The back URL."
type="String"
/>
<cc:attribute
name="canEdit"
default="true"
required="false"
shortDescription="Can the current user edit the text?"
type="boolean"
/>
<!-- <cc:attribute
name="editButtonLabel"
default="#{CmsAdminMessages['edit_button.label']}"
required="false"
shortDescription="Label for the edit button"
type="String"
/>-->
<cc:attribute
name="editDialogCancelLabel"
default="#{CmsAdminMessages['cancel_button.label']}"
required="false"
shortDescription="Label for the cancel and close button of the edit dialog"
type="String"
/>
<cc:attribute
name="editDialogSubmitLabel"
default="#{CmsAdminMessages['save_button.label']}"
required="false"
shortDescription="Label for the submit button of the edit dialog"
type="String"
/>
<cc:attribute
name="editDialogValueHelp"
default="Value to update"
required="false"
shortDescription="Help text for the value field"
type="String"
/>
<cc:attribute
name="editDialogValueLabel"
default="Value"
required="false"
shortDescription="Label for the value field"
type="String"
/>
<cc:attribute
name="editDialogTitle"
default="#{CmsAdminMessages['text.edit.dialog.title']}"
required="false"
shortDescription="Title for the edit dialog"
type="String"
/>
<cc:attribute
name="editMethod"
required="true"
shortDescription="URL of the endpoint for editing/updating a value. The value of the locale to update is added after the provided URL, eg. /de for updating the german value."
type="String"
/>
<!-- <cc:attribute
name="hasUnusedLocales"
required="true"
shortDescription="Are there unused locales? This will usually be an expression pointing to some method. The result must resolve to boolean."
type="boolean"
/>-->
<cc:attribute
name="headingLevel"
default="2"
required="false"
shortDescription="Level of the heading used for the component. Also determines the heading levels used for other parts of the component."
type="int"
/>
<cc:attribute
name="editorId"
required="true"
shortDescription="ID for the editor. Also used as prefix to generate IDs for some subcomponents"
type="String"
/>
<!-- <cc:attribute
name="emptyText"
default="#{text.editor.no_localized_values}"
required="false"
shortDescription="Text shown if the localized has no values yet."
type="String"
/>-->
<cc:attribute
name="messageSaveFailed"
default="Failed to save."
required="false"
type="String"
/>
<cc:attribute
name="messageSaveSuccessful"
default="Saved sucessfully."
required="false"
type="String"
/>
<cc:attribute
name="messageVariantLoadFailed"
default="Failed to load variant."
required="false"
type="String"
/>
<cc:attribute
name="mode"
default="full"
shortDescription="The editor mode. Use 'full' to enable all options including adding images etc. Use 'textonly' for a minimal version that only supports text editing, but does not allow insertation of images etc."
required="false"
type="String"
/>
<cc:attribute
name="objectIdentifier"
required="true"
shortDescription="Identifier of the object to which the localized string belongs"
type="String"
/>
<cc:attribute
name="selectedLocale"
required="true"
shortDescription="The selected locale."
type="String"
/>
<!-- <cc:attribute
name="removeButtonLabel"
default="Remove"
required="false"
shortDescription="Label for the remove button"
type="String"
/>
<cc:attribute
name="removeDialogCancelLabel"
default="Cancel"
required="false"
shortDescription="Label for the cancel and close buttons of the remove dialog"
type="String"
/>
<cc:attribute
name="removeDialogSubmitLabel"
default="Remove localized value"
required="false"
shortDescription="Label for the submit button of the remove dialog (removes the localized value)"
type="String"
/>
<cc:attribute
name="removeDialogText"
default="Are you sure to remove the following localized value?"
required="false"
shortDescription="Text for the remove dialog"
type="String"
/>
<cc:attribute
name="removeDialogTitle"
default="Remove localized value"
required="false"
shortDescription="The title of the remove dialog"
type="String"
/>
<cc:attribute
name="removeMethod"
required="true"
shortDescription="URL of the endpoint for remvoving a value. The locale of the value to remove is added to the end of the URL. Eg. /de for removing the value for the locale de."
type="String"
/>
<cc:attribute
name="tableActionsHeading"
default="Actions"
required="false"
shortDescription="Heading for the action columns"
type="String"
/>
<cc:attribute
name="tableLocaleHeading"
default="Locale"
required="false"
shortDescription="Heading for the locale column"
type="String"
/>
<cc:attribute
name="tableWordCountHeading"
default="Words"
required="false"
shortDescription="Heading for the word count column"
type="String"
/>-->
<cc:attribute
name="title"
required="true"
shortDescription="Title/Heading of the editor widget"
type="String"
/>
<!-- <cc:attribute
name="unusedLocales"
required="true"
shortDescription="A collection of the unused locales of the edited localized string"
type="java.util.Collection"
/>
<cc:attribute
name="variants"
required="true"
shortDescription="Info about the available variants. Must be a List of CmsEditorLocaleVariantRow objects."
type="java.util.List"
/>-->
<cc:attribute
name="variantUrl"
required="true"
shortDescription="URL of the endpoint for retrieving a variant. The locale of the variant to retrieve is appended as last token."
type="String"
/>
<!-- <cc:attribute name="viewButtonLabel" default="View" type="String" />
<cc:attribute name="viewDialogTitle" default="View" type="String" />
<cc:attribute
name="viewDialogEditButtonLabel"
default="Edit"
type="String"
/>
<cc:attribute
name="viewDialogCloseButtonLabel"
default="Close"
type="String"
/>
<cc:attribute
name="wordCountUrl"
required="true"
shortDescription="URL of the endpoint for retrieving the wordcount of a variant. The locale of the variant to retrieve is appended as last token."
type="String"
/>-->
</cc:interface>
<cc:implementation>
<div
class="cms-editor"
data-locale="#{cc.attrs.selectedLocale}"
data-save-url="#{cc.attrs.editMethod}/#{variant.locale}"
data-variant-url="#{cc.attrs.variantUrl}/#{cc.attrs.selectedLocale}"
id="#{cc.attrs.editorId}"
>
<c:choose>
<c:when test="#{cc.attrs.headingLevel == 1}">
<h1>#{cc.attrs.title}</h1>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 2}">
<h2>#{cc.attrs.title}</h2>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 3}">
<h3>#{cc.attrs.title}</h3>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 4}">
<h4>#{cc.attrs.title}</h4>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 5}">
<h5>#{cc.attrs.title}</h5>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 6}">
<h6>#{cc.attrs.title}</h6>
</c:when>
<c:otherwise>
<div>#{cc.attrs.title}</div>
</c:otherwise>
</c:choose>
<div class="cms-editor-messages">
<template id="cms-editor-msg-canvas-element-not-found">
<div class="alert alert alert-danger" role="alert">
#{CmsAdminMessages['cms_editor.internal_error']}
</div>
</template>
<template id="cms-editor-msg-save-failed">
<div class="alert alert alert-danger" role="alert">
#{cc.attrs.messageSaveFailed}
</div>
</template>
<template id="cms-editor-msg-save-successful">
<div class="alert alert-success" role="alert">
#{cc.attrs.messageSaveSuccessful}
</div>
</template>
<template id="cms-editor-msg-variant-load-failed">
<div class="alert alert-warning" role="alert">
#{cc.attrs.messageVariantLoadFailed}
</div>
</template>
</div>
<c:choose>
<c:when test="#{cc.attrs.headingLevel == 1}">
<h2 id="#{cc.attrs.editorId}-edit-dialog-title">
#{cc.attrs.editDialogTitle} #{cc.attrs.selectedLocale}
</h2>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 2}">
<h3 id="#{cc.attrs.editorId}-edit-dialog-title">
#{cc.attrs.editDialogTitle} #{cc.attrs.selectedLocale}
</h3>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 3}">
<h4 id="#{cc.attrs.editorId}-edit-dialog-title">
#{cc.attrs.editDialogTitle} #{cc.attrs.selectedLocale}
</h4>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 4}">
<h5 id="#{cc.attrs.editorId}-edit-dialog-title">
#{cc.attrs.editDialogTitle} #{cc.attrs.selectedLocale}
</h5>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 5}">
<h6 id="#{cc.attrs.editorId}-edit-dialog-title">
#{cc.attrs.editDialogTitle} #{cc.attrs.selectedLocale}
</h6>
</c:when>
<c:otherwise>
<div id="#{cc.attrs.editorId}-edit-dialog-title">
#{cc.attrs.editDialogTitle} #{cc.attrs.selectedLocale}
</div>
</c:otherwise>
</c:choose>
<div
class="cms-tiptap-editor"
data-locale="#{cc.attrs.selectedLocale}"
data-variant-url="#{cc.attrs.variantUrl}/#{cc.attrs.selectedLocale}"
>
<div class="cms-tiptap-editor-buttons mb-1">
<div class="px-2 cms-tiptap-editor-button-row cms-tiptap-editor-textformatting">
<button
class="btn btn-outline-dark tiptap-emph"
title="#{CmsAdminMessages['cms_editor.buttons.emph']}"
type="button"
>
<bootstrap:svgIcon icon="type-italic" />
</button>
<button
class="btn btn-outline-dark tiptap-strong-emph"
title="#{CmsAdminMessages['cms_editor.buttons.strong_emph']}"
type="button"
>
<bootstrap:svgIcon icon="type-bold" />
</button>
<button
class="btn btn-outline-dark tiptap-code"
title="#{CmsAdminMessages['cms_editor.buttons.code']}"
type="button"
>
<bootstrap:svgIcon icon="code" />
</button>
<button
class="btn btn-outline-dark tiptap-strikethrough"
title="#{CmsAdminMessages['cms_editor.buttons.strikethrough']}"
type="button"
>
<bootstrap:svgIcon icon="type-strikethrough" />
</button>
<button
class="btn btn-outline-dark tiptap-subscript"
title="#{CmsAdminMessages['cms_editor.buttons.subscript']}"
type="button"
>
<span aria-hidden="true">x<sub>n</sub></span>
</button>
<button
class="btn btn-outline-dark tiptap-superscript"
title="#{CmsAdminMessages['cms_editor.buttons.superscript']}"
type="button"
>
<span aria-hidden="true">x<sup>n</sup></span>
</button>
</div>
<div class="px-2 cms-tiptap-editor-button-row cms-tiptap-editor-headings">
<ui:repeat begin="1" end="6" var="level">
<button
class="btn btn-outline-dark tiptap-h#{level}"
title="#{CmsAdminMessages['cms_editor.buttons.h'.concat(level)]}"
type="button"
>
<span aria-hidden="true">H#{level}</span>
</button>
</ui:repeat>
</div>
<div class="px-2 cms-tiptap-editor-button-row cms-tiptap-editor-paragraphs">
<button
class="btn btn-outline-dark tiptap-paragraph"
title="#{CmsAdminMessages['cms_editor.buttons.paragraph']}"
type="button"
>
<bootstrap:svgIcon icon="type" />
</button>
<button
class="btn btn-outline-dark tiptap-blockquote"
title="#{CmsAdminMessages['cms_editor.buttons.blockquote']}"
type="button"
>
<bootstrap:svgIcon icon="blockquote-left" />
</button>
<button
class="btn btn-outline-dark tiptap-codeblock"
title="#{CmsAdminMessages['cms_editor.buttons.codeblock']}"
type="button"
>
<bootstrap:svgIcon icon="code-square" />
</button>
</div>
<div class="px-2 cms-tiptap-editor-button-row cms-tiptap-editor-lists">
<button
class="btn btn-outline-dark tiptap-ul"
title="#{CmsAdminMessages['cms_editor.buttons.ul']}"
type="button"
>
<bootstrap:svgIcon icon="list-ul" />
</button>
<button
class="btn btn-outline-dark tiptap-ol"
title="#{CmsAdminMessages['cms_editor.buttons.ol']}"
type="button"
>
<bootstrap:svgIcon icon="list-ol" />
</button>
</div>
<div class="px-2 cms-tiptap-editor-button-row cms-tiptap-editor-table">
<button
class="btn btn-outline-dark tiptap-insert-table"
data-target="#insert-table-dialog"
data-toggle="modal"
title="#{CmsAdminMessage['cms_editor.buttons.insert_table']}"
type="button"
>
<bootstrap:svgIcon icon="table" />
</button>
<div
aria-hidden="true"
aria-labelledby="insert-table-dialog-title"
class="modal fade cms-editor-insert-table-dialog"
id="insert-table-dialog"
tabindex="-1"
>
<div class="modal-dialog">
<form class="modal-content">
<div class="modal-header">
<h4
class="modal-title"
id="insert-table-dialog-title"
>
#{CmsAdminMessages['cms_editor.dialogs.insert_table.title']}
</h4>
<button
aria-label="#{CmsDefaultStepsMessageBundle['cms_editor.dialogs.insert_table.close']}"
class="close"
data-dismiss="modal"
type="button"
>
<bootstrap:svgIcon icon="x" />
</button>
</div>
<div class="modal-body">
<bootstrap:formGroupNumber
help="#{CmsDefaultStepsMessageBundle['cms_editor.dialogs.insert_table.rows.help']}"
inputId="rows"
label="#{CmsDefaultStepsMessageBundle['cms_editor.dialogs.insert_table.rows.label']}"
name="rows"
/>
<bootstrap:formGroupNumber
help="#{CmsDefaultStepsMessageBundle['cms_editor.dialogs.insert_table.cols.help']}"
inputId="cols"
label="#{CmsDefaultStepsMessageBundle['cms_editor.dialogs.insert_table.cols.label']}"
name="cols"
/>
<bootstrap:formCheck
checked="true"
inputId="headerRow"
label="#{CmsDefaultStepsMessageBundle['cms_editor.dialogs.insert_table.header_row.label']}"
name="headerRow"
value="true"
/>
</div>
<div class="modal-footer">
<button
class="
btn btn-warning
cms-editor-cancel-button
"
data-dismiss="modal"
type="button"
>
#{CmsDefaultStepsMessageBundle['cms_editor.dialogs.insert_table.close']}
</button>
<button
class="btn btn-success"
data-dismiss="modal"
data-backdrop="false"
type="submit"
>
#{CmsDefaultStepsMessageBundle['cms_editor.dialogs.insert_table.submit']}
</button>
</div>
</form>
</div>
</div>
<button
class="btn btn-outline-dark tiptap-remove-table"
title="#{CmsAdminMessage['cms_editor.buttons.remove_table']}"
type="button"
>
<img src="#{request.contextPath}/icons/cms-editor/table-remove.svg" />
</button>
<button
class="btn btn-outline-dark tiptap-insert-table-row-before"
title="#{CmsAdminMessage['cms_editor.buttons.insert_table_row_before']}"
type="button"
>
<img src="#{request.contextPath}/icons/cms-editor/table-add-row-before.svg" />
</button>
<button
class="btn btn-outline-dark tiptap-insert-table-row-after"
title="#{CmsAdminMessage['cms_editor.buttons.insert_table_row_after']}"
type="button"
>
<img src="#{request.contextPath}/icons/cms-editor/table-add-row-after.svg" />
</button>
<button
class="btn btn-outline-dark tiptap-table-remove-row"
title="#{CmsAdminMessages['cms_editor.buttons.remove_table_row']}"
type="button">
<img src="#{request.contextPath}/icons/cms-editor/table-remove-row.svg" />
</button>
<button
class="btn btn-outline-dark tiptap-insert-table-column-before"
title="#{CmsAdminMessage['cms_editor.buttons.insert_table_column_before']}"
type="button"
>
<img src="#{request.contextPath}/icons/cms-editor/table-add-column-before.svg" />
</button>
<button
class="btn btn-outline-dark tiptap-insert-table-column-after"
title="#{CmsAdminMessage['cms_editor.buttons.insert_table_column_after']}"
type="button"
>
<img src="#{request.contextPath}/icons/cms-editor/table-add-column-after.svg" />
</button>
<button
class="btn btn-outline-dark tiptap-remove-table-column"
title="#{CmsAdminMessage['cms_editor.buttons.remove_table_column']}"
type="button"
>
<img src="#{request.contextPath}/icons/cms-editor/table-remove-column.svg" />
</button>
<button
class="btn btn-outline-dark tiptap-toggle-table-header-row"
title="#{CmsAdminMessage['cms_editor.buttons.toggle_header_row']}"
type="button">
<img src="#{request.contextPath}/icons/cms-editor/table-toggle-header-row.svg" />
</button>
<button
class="btn btn-outline-dark tiptap-toggle-table-header-column"
title="#{CmsAdminMessage['cms_editor.buttons.toggle_header_column']}"
type="button">
<img src="#{request.contextPath}/icons/cms-editor/table-toggle-header-column.svg" />
</button>
<button
class="btn btn-outline-dark tiptap-merge-table-cells"
title="#{CmsAdminMessage['cms_editor.buttons.merge-table-cells']}"
type="button">
<img src="#{request.contextPath}/icons/cms-editor/table-merge-cells.svg" />
</button>
<button
class="btn btn-outline-dark tiptap-split-table-cell"
title="#{CmsAdminMessage['cms_editor.buttons.split-table-cell']}"
type="button">
<img src="#{request.contextPath}/icons/cms-editor/table-split-cells.svg" />
</button>
</div>
<div class="px-2 cms-tiptap-editor-button-row cms-tiptap-editor-media">
<button
class="btn btn-outline-dark tiptap-insert-image"
title="#{CmsAdminMessage['cms_editor.buttons.insert_image']}"
type="button">
<bootstrap:svgIcon icon="image" />
</button>
</div>
</div>
<div
class="cms-tiptap-editor-canvas border"
data-locale="#{cc.attrs.selectedLocale}"
data-variant-url="#{cc.attrs.variantUrl}"
></div>
<div id="html-preview"></div>
<div class="mt-3">
<a
class="btn btn-warning cms-editor-cancel-button"
href="#{backUrl}"
>
#{cc.attrs.editDialogCancelLabel}
</a>
<button
class="btn btn-success cms-editor-save-button"
disabled="#{cc.attrs.canEdit ? '' : 'disabled'}"
type="button"
>
#{cc.attrs.editDialogSubmitLabel}
</button>
</div>
</div>
</div>
</cc:implementation>
</html>

View File

@ -293,22 +293,358 @@
data-save-url="#{cc.attrs.editMethod}/#{variant.locale}"
data-variant-url="#{cc.attrs.variantUrl}/#{cc.attrs.selectedLocale}"
id="#{cc.attrs.editorId}"
></div>
<div id="editor-preview"></div>
<div class="mt-3">
<a
class="btn btn-warning cms-editor-cancel-button"
href="#{backUrl}"
>
<c:choose>
<c:when test="#{cc.attrs.headingLevel == 1}">
<h1>#{cc.attrs.title}</h1>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 2}">
<h2>#{cc.attrs.title}</h2>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 3}">
<h3>#{cc.attrs.title}</h3>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 4}">
<h4>#{cc.attrs.title}</h4>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 5}">
<h5>#{cc.attrs.title}</h5>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 6}">
<h6>#{cc.attrs.title}</h6>
</c:when>
<c:otherwise>
<div>#{cc.attrs.title}</div>
</c:otherwise>
</c:choose>
<div class="cms-editor-messages">
<template id="cms-editor-msg-canvas-element-not-found">
<div class="alert alert alert-danger" role="alert">
#{CmsAdminMessages['cms_editor.internal_error']}
</div>
</template>
<template id="cms-editor-msg-save-failed">
<div class="alert alert alert-danger" role="alert">
#{cc.attrs.messageSaveFailed}
</div>
</template>
<template id="cms-editor-msg-save-successful">
<div class="alert alert-success" role="alert">
#{cc.attrs.messageSaveSuccessful}
</div>
</template>
<template id="cms-editor-msg-variant-load-failed">
<div class="alert alert-warning" role="alert">
#{cc.attrs.messageVariantLoadFailed}
</div>
</template>
</div>
<c:choose>
<c:when test="#{cc.attrs.headingLevel == 1}">
<h2 id="#{cc.attrs.editorId}-edit-dialog-title">
#{cc.attrs.editDialogTitle} #{cc.attrs.selectedLocale}
</h2>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 2}">
<h3 id="#{cc.attrs.editorId}-edit-dialog-title">
#{cc.attrs.editDialogTitle} #{cc.attrs.selectedLocale}
</h3>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 3}">
<h4 id="#{cc.attrs.editorId}-edit-dialog-title">
#{cc.attrs.editDialogTitle} #{cc.attrs.selectedLocale}
</h4>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 4}">
<h5 id="#{cc.attrs.editorId}-edit-dialog-title">
#{cc.attrs.editDialogTitle} #{cc.attrs.selectedLocale}
</h5>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 5}">
<h6 id="#{cc.attrs.editorId}-edit-dialog-title">
#{cc.attrs.editDialogTitle} #{cc.attrs.selectedLocale}
</h6>
</c:when>
<c:otherwise>
<div id="#{cc.attrs.editorId}-edit-dialog-title">
#{cc.attrs.editDialogTitle} #{cc.attrs.selectedLocale}
</div>
</c:otherwise>
</c:choose>
<div
class="cms-tiptap-editor"
data-locale="#{cc.attrs.selectedLocale}"
data-variant-url="#{cc.attrs.variantUrl}/#{cc.attrs.selectedLocale}"
>
#{cc.attrs.editDialogCancelLabel}
</a>
<button
class="btn btn-success cms-editor-save-button"
disabled="#{cc.attrs.canEdit ? '' : 'disabled'}"
type="button"
>
#{cc.attrs.editDialogSubmitLabel}
</button>
<div class="cms-tiptap-editor-buttons mb-1">
<div class="px-2 cms-tiptap-editor-button-row cms-tiptap-editor-textformatting">
<button
class="btn btn-outline-dark tiptap-emph"
title="#{CmsAdminMessages['cms_editor.buttons.emph']}"
type="button"
>
<bootstrap:svgIcon icon="type-italic" />
</button>
<button
class="btn btn-outline-dark tiptap-strong-emph"
title="#{CmsAdminMessages['cms_editor.buttons.strong_emph']}"
type="button"
>
<bootstrap:svgIcon icon="type-bold" />
</button>
<button
class="btn btn-outline-dark tiptap-code"
title="#{CmsAdminMessages['cms_editor.buttons.code']}"
type="button"
>
<bootstrap:svgIcon icon="code" />
</button>
<button
class="btn btn-outline-dark tiptap-strikethrough"
title="#{CmsAdminMessages['cms_editor.buttons.strikethrough']}"
type="button"
>
<bootstrap:svgIcon icon="type-strikethrough" />
</button>
<button
class="btn btn-outline-dark tiptap-subscript"
title="#{CmsAdminMessages['cms_editor.buttons.subscript']}"
type="button"
>
<span aria-hidden="true">x<sub>n</sub></span>
</button>
<button
class="btn btn-outline-dark tiptap-superscript"
title="#{CmsAdminMessages['cms_editor.buttons.superscript']}"
type="button"
>
<span aria-hidden="true">x<sup>n</sup></span>
</button>
</div>
<div class="px-2 cms-tiptap-editor-button-row cms-tiptap-editor-headings">
<ui:repeat begin="1" end="6" var="level">
<button
class="btn btn-outline-dark tiptap-h#{level}"
title="#{CmsAdminMessages['cms_editor.buttons.h'.concat(level)]}"
type="button"
>
<span aria-hidden="true">H#{level}</span>
</button>
</ui:repeat>
</div>
<div class="px-2 cms-tiptap-editor-button-row cms-tiptap-editor-paragraphs">
<button
class="btn btn-outline-dark tiptap-paragraph"
title="#{CmsAdminMessages['cms_editor.buttons.paragraph']}"
type="button"
>
<bootstrap:svgIcon icon="type" />
</button>
<button
class="btn btn-outline-dark tiptap-blockquote"
title="#{CmsAdminMessages['cms_editor.buttons.blockquote']}"
type="button"
>
<bootstrap:svgIcon icon="blockquote-left" />
</button>
<button
class="btn btn-outline-dark tiptap-codeblock"
title="#{CmsAdminMessages['cms_editor.buttons.codeblock']}"
type="button"
>
<bootstrap:svgIcon icon="code-square" />
</button>
</div>
<div class="px-2 cms-tiptap-editor-button-row cms-tiptap-editor-lists">
<button
class="btn btn-outline-dark tiptap-ul"
title="#{CmsAdminMessages['cms_editor.buttons.ul']}"
type="button"
>
<bootstrap:svgIcon icon="list-ul" />
</button>
<button
class="btn btn-outline-dark tiptap-ol"
title="#{CmsAdminMessages['cms_editor.buttons.ol']}"
type="button"
>
<bootstrap:svgIcon icon="list-ol" />
</button>
</div>
<div class="px-2 cms-tiptap-editor-button-row cms-tiptap-editor-table">
<button
class="btn btn-outline-dark tiptap-insert-table"
data-target="#insert-table-dialog"
data-toggle="modal"
title="#{CmsAdminMessage['cms_editor.buttons.insert_table']}"
type="button"
>
<bootstrap:svgIcon icon="table" />
</button>
<div
aria-hidden="true"
aria-labelledby="insert-table-dialog-title"
class="modal fade cms-editor-insert-table-dialog"
id="insert-table-dialog"
tabindex="-1"
>
<div class="modal-dialog">
<form class="modal-content">
<div class="modal-header">
<h4
class="modal-title"
id="insert-table-dialog-title"
>
#{CmsAdminMessages['cms_editor.dialogs.insert_table.title']}
</h4>
<button
aria-label="#{CmsDefaultStepsMessageBundle['cms_editor.dialogs.insert_table.close']}"
class="close"
data-dismiss="modal"
type="button"
>
<bootstrap:svgIcon icon="x" />
</button>
</div>
<div class="modal-body">
<bootstrap:formGroupNumber
help="#{CmsDefaultStepsMessageBundle['cms_editor.dialogs.insert_table.rows.help']}"
inputId="rows"
label="#{CmsDefaultStepsMessageBundle['cms_editor.dialogs.insert_table.rows.label']}"
name="rows"
/>
<bootstrap:formGroupNumber
help="#{CmsDefaultStepsMessageBundle['cms_editor.dialogs.insert_table.cols.help']}"
inputId="cols"
label="#{CmsDefaultStepsMessageBundle['cms_editor.dialogs.insert_table.cols.label']}"
name="cols"
/>
<bootstrap:formCheck
checked="true"
inputId="headerRow"
label="#{CmsDefaultStepsMessageBundle['cms_editor.dialogs.insert_table.header_row.label']}"
name="headerRow"
value="true"
/>
</div>
<div class="modal-footer">
<button
class="
btn btn-warning
cms-editor-cancel-button
"
data-dismiss="modal"
type="button"
>
#{CmsDefaultStepsMessageBundle['cms_editor.dialogs.insert_table.close']}
</button>
<button
class="btn btn-success"
data-dismiss="modal"
data-backdrop="false"
type="submit"
>
#{CmsDefaultStepsMessageBundle['cms_editor.dialogs.insert_table.submit']}
</button>
</div>
</form>
</div>
</div>
<button
class="btn btn-outline-dark tiptap-remove-table"
title="#{CmsAdminMessage['cms_editor.buttons.remove_table']}"
type="button"
>
<img src="#{request.contextPath}/icons/cms-editor/table-remove.svg" />
</button>
<button
class="btn btn-outline-dark tiptap-insert-table-row-before"
title="#{CmsAdminMessage['cms_editor.buttons.insert_table_row_before']}"
type="button"
>
<img src="#{request.contextPath}/icons/cms-editor/table-add-row-before.svg" />
</button>
<button
class="btn btn-outline-dark tiptap-insert-table-row-after"
title="#{CmsAdminMessage['cms_editor.buttons.insert_table_row_after']}"
type="button"
>
<img src="#{request.contextPath}/icons/cms-editor/table-add-row-after.svg" />
</button>
<button
class="btn btn-outline-dark tiptap-table-remove-row"
title="#{CmsAdminMessages['cms_editor.buttons.remove_table_row']}"
type="button">
<img src="#{request.contextPath}/icons/cms-editor/table-remove-row.svg" />
</button>
<button
class="btn btn-outline-dark tiptap-insert-table-column-before"
title="#{CmsAdminMessage['cms_editor.buttons.insert_table_column_before']}"
type="button"
>
<img src="#{request.contextPath}/icons/cms-editor/table-add-column-before.svg" />
</button>
<button
class="btn btn-outline-dark tiptap-insert-table-column-after"
title="#{CmsAdminMessage['cms_editor.buttons.insert_table_column_after']}"
type="button"
>
<img src="#{request.contextPath}/icons/cms-editor/table-add-column-after.svg" />
</button>
<button
class="btn btn-outline-dark tiptap-remove-table-column"
title="#{CmsAdminMessage['cms_editor.buttons.remove_table_column']}"
type="button"
>
<img src="#{request.contextPath}/icons/cms-editor/table-remove-column.svg" />
</button>
<button
class="btn btn-outline-dark tiptap-toggle-table-header-row"
title="#{CmsAdminMessage['cms_editor.buttons.toggle_header_row']}"
type="button">
<img src="#{request.contextPath}/icons/cms-editor/table-toggle-header-row.svg" />
</button>
<button
class="btn btn-outline-dark tiptap-toggle-table-header-column"
title="#{CmsAdminMessage['cms_editor.buttons.toggle_header_column']}"
type="button">
<img src="#{request.contextPath}/icons/cms-editor/table-toggle-header-column.svg" />
</button>
<button
class="btn btn-outline-dark tiptap-merge-table-cells"
title="#{CmsAdminMessage['cms_editor.buttons.merge-table-cells']}"
type="button">
<img src="#{request.contextPath}/icons/cms-editor/table-merge-cells.svg" />
</button>
<button
class="btn btn-outline-dark tiptap-split-table-cell"
title="#{CmsAdminMessage['cms_editor.buttons.split-table-cell']}"
type="button">
<img src="#{request.contextPath}/icons/cms-editor/table-split-cells.svg" />
</button>
</div>
</div>
<div
class="cms-tiptap-editor-canvas border"
data-locale="#{cc.attrs.selectedLocale}"
data-variant-url="#{cc.attrs.variantUrl}"
></div>
<div class="mt-3">
<a
class="btn btn-warning cms-editor-cancel-button"
href="#{backUrl}"
>
#{cc.attrs.editDialogCancelLabel}
</a>
<button
class="btn btn-success cms-editor-save-button"
disabled="#{cc.attrs.canEdit ? '' : 'disabled'}"
type="button"
>
#{cc.attrs.editDialogSubmitLabel}
</button>
</div>
</div>
</div>
</cc:implementation>
</html>

View File

@ -30,37 +30,11 @@
/>
</c:if>
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal">
Launch demo modal
</button>
<!-- Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
...
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
</ui:define>
<ui:define name="scripts">
<script src="#{request.contextPath}/assets/@content-sections/article-text-step.js"></script>
</ui:define>
</ui:composition>
</html>

View File

@ -1,55 +0,0 @@
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);
});
}
});
}
});

View File

@ -0,0 +1,28 @@
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();
}
});

View File

@ -1,27 +0,0 @@
// 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();
// }
});

View File

@ -1,131 +0,0 @@
import * as jquery from "jquery";
export class CmsImage {
static get toolbox() {
return {
title: "Image",
icon: "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-image\" viewBox=\"0 0 16 16\"><path d=\"M6.002 5.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z\"/><path d=\"M2.002 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2h-12zm12 1a1 1 0 0 1 1 1v6.5l-3.777-1.947a.5.5 0 0 0-.577.093l-3.71 3.71-2.66-1.772a.5.5 0 0 0-.63.062L1.002 12V3a1 1 0 0 1 1-1h12z\"/></svg>"
}
}
public render() {
const figureElem = document.createElement("figure");
const imgElem = document.createElement("img");
imgElem.src = "data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-image\" viewBox=\"0 0 16 16\"><path d=\"M6.002 5.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z\"/><path d=\"M2.002 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2h-12zm12 1a1 1 0 0 1 1 1v6.5l-3.777-1.947a.5.5 0 0 0-.577.093l-3.71 3.71-2.66-1.772a.5.5 0 0 0-.63.062L1.002 12V3a1 1 0 0 1 1-1h12z\"/></svg>";
imgElem.setAttribute("style", "width: 100%");
figureElem.appendChild(imgElem);
const captionElem = document.createElement("figcaption");
captionElem.contentEditable = "true";
captionElem.innerHTML = "<i>Caption placeholder</i>"
figureElem.appendChild(captionElem);
return figureElem;
}
public renderSettings() {
const updateImageButton = document.createElement("button");
updateImageButton.title = "Update image";
updateImageButton.classList.add("cdx-settings-button");
updateImageButton.innerHTML = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-image\" viewBox=\"0 0 16 16\"><path d=\"M6.002 5.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z\"/><path d=\"M2.002 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2h-12zm12 1a1 1 0 0 1 1 1v6.5l-3.777-1.947a.5.5 0 0 0-.577.093l-3.71 3.71-2.66-1.772a.5.5 0 0 0-.63.062L1.002 12V3a1 1 0 0 1 1-1h12z\"/></svg>";
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 = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-x-circle\" viewBox=\"0 0 16 16\"><path d=\"M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z\"/><path d=\"M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z\"/></svg>";
modalHeaderElem.appendChild(modalHeaderCloseButton);
const modalBodyElem = document.createElement("div");
modalBodyElem.classList.add("modal-body");
modalBodyElem.innerHTML = "<i>Update image placeholder</i>";
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");
}
}

View File

@ -1,808 +0,0 @@
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<CmsEditor> {
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<string> {
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 };

View File

@ -1,2 +1,795 @@
//import EditorJS from '@editorjs/editorjs';
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";
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<CmsEditor> {
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<string> {
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 };

View File

@ -1,80 +0,0 @@
import { Node, mergeAttributes } from "@tiptap/core";
import { insertContent } from "@tiptap/core/dist/packages/core/src/extensions/commands";
export interface CcmImageOptions {
HTMLAttributes: Record<string, any>;
}
declare module "@tiptap/core" {
interface Commands<ReturnType> {
ccmImage: {
addCcmImage: () => ReturnType;
};
}
}
export const CcmImage = Node.create<CcmImageOptions>({
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
}
}
}
});

View File

@ -1,5 +0,0 @@
import { CcmImage } from "./image";
export * from "./image";
export default CcmImage;

View File

@ -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.js"
"article-text-step": "./src/main/typescript/content-sections/article-text-step.ts"
},
output: {
filename: "[name].js",