Moved cms-editor back to ccm-cms, refactored to general script configured using data attributes

deploy_packages_to_gitea
Jens Pelzetter 2022-05-03 20:23:45 +02:00
parent 5e44817505
commit 0f457f0ec0
24 changed files with 1932 additions and 61 deletions

View File

@ -1,12 +1,12 @@
{
"name": "@librecms/ccm-cms-editor",
"version": "7.0.0-SNAPSHOT.2022-04-28T174914",
"version": "7.0.0-SNAPSHOT.2022-04-28T183714",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@librecms/ccm-cms-editor",
"version": "7.0.0-SNAPSHOT.2022-04-28T174914",
"version": "7.0.0-SNAPSHOT.2022-04-28T183714",
"license": "LGPL-3.0-or-later",
"dependencies": {
"@tiptap/core": "^2.0.0-beta.127",

View File

@ -1,6 +1,6 @@
{
"name": "@librecms/ccm-cms-editor",
"version": "7.0.0-SNAPSHOT.2022-04-28T174914",
"version": "7.0.0-SNAPSHOT.2022-04-28T183714",
"description": "HTML WYSIWYG editor for LibreCMS based on TipTap",
"main": "target/generated-resources/assets/ccm-cms-editor.js",
"types": "target/generated-resources/assets/ccm-cms-editor.d.ts",

1048
ccm-cms/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "@librecms/ccm-cms",
"version": "7.0.0-SNAPSHOT.2022-04-28T174914",
"version": "7.0.0-SNAPSHOT.2022-05-03T181122",
"description": "JavaScript stuff for ccm-cms",
"main": "target/generated-resources/assets/@content-sections/cms-admin.js",
"types": "target/generated-resources/assets/@content-sections/cms-admin.d.ts",
@ -23,7 +23,14 @@
"webpack-cli": "^4.8.0"
},
"dependencies": {
"@librecms/ccm-cms-editor": "../ccm-cms-editor",
"@tiptap/core": "^2.0.0-beta.127",
"@tiptap/extension-subscript": "^2.0.0-beta.4",
"@tiptap/extension-superscript": "^2.0.0-beta.4",
"@tiptap/extension-table": "^2.0.0-beta.35",
"@tiptap/extension-table-cell": "^2.0.0-beta.15",
"@tiptap/extension-table-header": "^2.0.0-beta.17",
"@tiptap/extension-table-row": "^2.0.0-beta.14",
"@tiptap/starter-kit": "^2.0.0-beta.129",
"acorn": "^8.5.0",
"bootstrap": "^4.6.0",
"bootstrap-icons": "^1.5.0",

View File

@ -42,12 +42,6 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.librecms</groupId>
<artifactId>ccm-cms-editor</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
@ -243,16 +237,7 @@
<arguments>pkg set version=${project.version}.${timestamp}</arguments>
</configuration>
</execution>
<!-- <execution>
<id>npm link @librecms/ccm-cms-editor</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>link @librecms/ccm-cms-editor</arguments>
</configuration>
</execution> -->
<execution>
<execution>
<id>npm install</id>
<goals>
<goal>npm</goal>

View File

@ -305,6 +305,7 @@
data-baseUrl="#{cc.attrs.baseUrl}"
data-contentsection="#{cc.attrs.contentSection}"
data-locale="#{cc.attrs.selectedLocale}"
data-save-button="##{cc.attrs.editorId}-save-button"
data-save-url="#{cc.attrs.editMethod}/#{cc.attrs.selectedLocale}"
data-variant-url="#{cc.attrs.variantUrl}/#{cc.attrs.selectedLocale}"
id="#{cc.attrs.editorId}"
@ -1021,6 +1022,7 @@
</button>
<button class="btn btn-primary video-settings-dialog-save"
data-dismiss="modal"
id="#{cc.attrs.editorId}-save-button"
type="button">
#{CmsAdminMessages['cms_editor.video_node_view.settings.dialog.save']}
</button>

View File

@ -204,7 +204,7 @@
/>
<cc:attribute name="viewButtonLabel" default="View" type="String" />
<cc:implementation>
<div class="cms-editor">
<div class="cms-editor-variants">
<c:choose>
<c:when test="#{cc.attrs.headingLevel == 1}">
<h1>#{cc.attrs.title}</h1>

View File

@ -35,7 +35,7 @@
</ui:define>
<ui:define name="scripts">
<script src="#{request.contextPath}/assets/@content-sections/article-text-step.js"></script>
<script src="#{request.contextPath}/assets/@content-sections/cms-editor.js"></script>
</ui:define>
</ui:composition>
</html>

View File

@ -0,0 +1,889 @@
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 AudioNode from "./cms-editor/audio-node";
import ImageNode from "./cms-editor/image-node";
import VideoNode from "./cms-editor/video-node";
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-audio",
command: (cmsEditor) => {
return cmsEditor
.getEditor()
.chain()
.focus()
.setLibreCmsAudio()
.insertContent("<p></p>")
.run();
},
can: (cmsEditor) => {
return cmsEditor
.getEditor()
.can()
.chain()
.focus()
.setLibreCmsAudio()
.run();
},
},
{
selector: ".tiptap-insert-image",
command: (cmsEditor) => {
return cmsEditor
.getEditor()
.chain()
.focus()
.setLibreCmsImage()
.insertContent("<p></p>")
.run();
},
can: (cmsEditor) => {
return cmsEditor
.getEditor()
.can()
.chain()
.focus()
.setLibreCmsImage()
.run();
},
},
{
selector: ".tiptap-insert-video",
command: (cmsEditor) => {
return cmsEditor
.getEditor()
.chain()
.focus()
.setLibreCmsVideo()
.insertContent("<p></p>")
.run();
},
can: (cmsEditor) => {
return cmsEditor
.getEditor()
.can()
.chain()
.focus()
.setLibreCmsVideo()
.run();
},
},
// {
// selector: "",
// command: cmsEditor => {},
// can: cmsEditor => {}
// },
// {
// selector: "",
// command: cmsEditor => {},
// can: cmsEditor => {}
// },
// {
// selector: "",
// command: cmsEditor => {},
// can: cmsEditor => {}
// }
];
export 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) {
console.log(`Save response status: ${response.status}`);
window.location.href = response.url;
// const location = response.headers.get("Location");
// if (location) {
// window.location.href = location;
// } else {
// this.showMessage("#cms-editor-msg-save-successful");
// }
} else {
this.showSaveFailedMessage(
response.status,
response.statusText
);
}
} catch (error) {
console.error(error);
console.trace(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;
}
}
function buildCmsEditor(editorElement: HTMLElement) {
const saveButtonSelector = editorElement.getAttribute("data-save-button");
const saveUrl = editorElement.getAttribute("data-save-url");
const variantUrl = editorElement.getAttribute("data-variant-url");
if (!saveButtonSelector) {
console.error(
`Element ${editorElement} has no value for data-save-button.`
);
return;
}
if (!saveUrl) {
console.error(
`Element ${editorElement} has no value of data-save-url.`
);
return;
}
if (!variantUrl) {
console.error(
`Element ${editorElement} has not value for data-variant-url.`
);
return;
}
const canvasElement = editorElement.querySelector(
".cms-tiptap-editor-canvas"
);
if (!canvasElement) {
const template = editorElement.querySelector(
"#cms-editor-msg-canvas-element-not-found"
) as HTMLTemplateElement;
const message = template.content.cloneNode(true);
editorElement.querySelector(".cms-editor-messages")?.append(message);
console.error("canvasElem not found.");
throw "canvasElem not found.";
}
fetch(variantUrl, { method: "GET", credentials: "include" })
.then((response) => {
if (response.ok) {
const text = response.text().then((variant) => {
try {
const editor: Editor = new Editor({
element: canvasElement,
extensions: [
AudioNode,
// Gapcursor,
ImageNode,
StarterKit,
Subscript,
Superscript,
Table.configure({
allowTableNodeSelection: true,
cellMinWidth: 100,
handleWidth: 25,
resizable: true,
}),
TableRow,
TableHeader,
TableCell,
VideoNode,
],
content: variant,
});
const cmsEditor = new CmsEditor(
editor,
editorElement,
saveUrl
);
const saveButtons =
document.querySelectorAll(saveButtonSelector);
for (let i = 0; i < saveButtons.length; i++) {
const saveButton = saveButtons[i];
saveButton.addEventListener("click", (event) => {
event.preventDefault();
console.log("HTML output of editor: ");
console.log(cmsEditor.getEditor().getHTML());
});
}
} catch (error) {
console.error(error);
console.trace(error);
}
});
} else {
const template = editorElement.querySelector(
"#cms-editor-msg-variant-load-failed"
) as HTMLTemplateElement;
const message = template.content.cloneNode(true);
editorElement
.querySelector(".cms-editor-messages")
?.append(message);
console.error(
`Failed to load variant. Status: ${response.status}, Status Text: ${response.statusText}`
);
}
})
.catch((error) => {
console.error(error);
console.trace(error);
const template = editorElement.querySelector(
"#cms-editor-msg-variant-load-failed"
) as HTMLTemplateElement;
const message = template.content.cloneNode(true);
editorElement
.querySelector(".cms-editor-messages")
?.append(message);
});
}
interface CmsEditorButton {
selector: string;
command: (cmsEditor: CmsEditor) => boolean;
can: (cmsEditor: CmsEditor) => boolean;
}
document.addEventListener("DOMContentLoaded", (event) => {
const editorElements = document.querySelectorAll(".cms-editor");
for (let i = 0; i < editorElements.length; i++) {
buildCmsEditor(editorElements[i] as HTMLElement);
}
});

View File

@ -6,13 +6,14 @@ module.exports = {
},
entry: {
"cms-admin": "./src/main/typescript/content-sections/cms-admin.ts",
"article-text-step": "./src/main/typescript/content-sections/article-text-step.ts",
"event-info-step-eventdate": "./src/main/typescript/content-sections/event-info-step-eventdate.ts",
"event-info-step-eventtype": "./src/main/typescript/content-sections/event-info-step-eventtype.ts",
"event-info-step-location": "./src/main/typescript/content-sections/event-info-step-location.ts",
"event-info-step-maincontributor": "./src/main/typescript/content-sections/event-info-step-maincontributor.ts",
"mpa-section-edit-text": "./src/main/typescript/content-sections/mpa-section-edit-text.ts",
"news-text-step": "./src/main/typescript/content-sections/news-text-step.ts"
"cms-editor": "./src/main/typescript/content-sections/cms-editor.ts"
// "article-text-step": "./src/main/typescript/content-sections/article-text-step.ts",
// "event-info-step-eventdate": "./src/main/typescript/content-sections/event-info-step-eventdate.ts",
// "event-info-step-eventtype": "./src/main/typescript/content-sections/event-info-step-eventtype.ts",
// "event-info-step-location": "./src/main/typescript/content-sections/event-info-step-location.ts",
// "event-info-step-maincontributor": "./src/main/typescript/content-sections/event-info-step-maincontributor.ts",
// "mpa-section-edit-text": "./src/main/typescript/content-sections/mpa-section-edit-text.ts",
// "news-text-step": "./src/main/typescript/content-sections/news-text-step.ts"
},
output: {
filename: "[name].js",

View File

@ -86,7 +86,6 @@
<!--<module>ccm-cms-pagemodelseditor</module>-->
<!--<module>ccm-cms-tinymce</module>-->
<module>ccm-cms-editor</module>
<module>ccm-cms-types-agenda</module>
<module>ccm-cms-types-bookmark</module>