Removed ccm-cms-editor

deploy_packages_to_gitea
Jens Pelzetter 2022-05-04 19:23:35 +02:00
parent e3fd73b92f
commit f34efb62b6
13 changed files with 0 additions and 7992 deletions

View File

@ -1 +0,0 @@
target

File diff suppressed because it is too large Load Diff

View File

@ -1,38 +0,0 @@
{
"name": "@librecms/ccm-cms-editor",
"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",
"scripts": {
"build": "npm-run-all build:*",
"build:js": "webpack"
},
"author": "Jens Pelzetter",
"license": "LGPL-3.0-or-later",
"devDependencies": {
"@types/jquery": "^3.5.6",
"npm-run-all": "^4.1.5",
"ts-loader": "^9.2.6",
"typescript": "^4.4.3",
"webpack": "^5.55.1",
"webpack-cli": "^4.8.0"
},
"dependencies": {
"@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",
"jquery": "^3.6.0",
"popper.js": "^1.16.1",
"remixicon": "^2.5.0",
"sortablejs": "^1.14.0"
}
}

View File

@ -1,106 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>libreccm-parent</artifactId>
<groupId>org.libreccm</groupId>
<version>7.0.0-SNAPSHOT</version>
</parent>
<groupId>org.librecms</groupId>
<artifactId>ccm-cms-editor</artifactId>
<name>LibreCMS Editor</name>
<licenses>
<license>
<name>Lesser GPL 2.1</name>
<url>http://www.gnu.org/licenses/old-licenses/lgpl-2.1</url>
</license>
</licenses>
<build>
<finalName>ccm-cms-editor</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
<resource>
<directory>./target/generated-resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<configuration>
<installDirectory>../node</installDirectory>
</configuration>
<executions>
<execution>
<id>Install node.js and NPM</id>
<goals>
<goal>install-node-and-npm</goal>
</goals>
<configuration>
<nodeVersion>${nodeJsVersion}</nodeVersion>
</configuration>
</execution>
<execution>
<id>set package version</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>pkg set version=${npmPackageVersion}</arguments>
</configuration>
</execution>
<execution>
<id>npm install</id>
<goals>
<goal>npm</goal>
</goals>
</execution>
<execution>
<id>build</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>run build</arguments>
</configuration>
</execution>
<execution>
<id>npm link</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>link</arguments>
</configuration>
</execution>
<execution>
<id>npm publish</id>
<goals>
<goal>npm</goal>
</goals>
<phase>deploy</phase>
<configuration>
<arguments>publish --userconfig ../libreccm.npmrc</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,880 +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 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;
}
}
export 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);
console.log(`Got variant: ${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,
});
return new CmsEditor(editor, this.editorElem, this.saveUrl);
} catch (error) {
console.error("Failed to create editor:");
console.trace(error);
throw error;
}
}
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) {
console.error(error);
console.trace(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);
}
}
export 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,472 +0,0 @@
import { Node, nodeInputRule, mergeAttributes, Range } from "@tiptap/core";
import { merge } from "jquery";
import { Node as ProsemirrorNode } from "prosemirror-model";
declare module "@tiptap/core" {
interface Commands<ReturnType> {
libreCmsAudioNode: {
setLibreCmsAudio: (attributes?: { language: string }) => ReturnType;
};
}
}
export const AudioNode = Node.create({
name: "libreCmsAudioNode",
content: "inline*",
marks: "",
group: "block",
code: false,
defining: true,
addAttributes() {
return {
align: {
parseHTML: (element) => {
if (element.hasAttribute("data-align")) {
return element.getAttribute("data-align");
} else {
return "center";
}
},
},
altText: {
parseHTML: (element) => {
const audioElem = element.querySelector("audio");
if (audioElem) {
return audioElem.textContent;
} else {
return "";
}
},
},
figCaption: {
parseHTML: (element) => {
const figCaptionElem = element.querySelector("figcaption");
if (figCaptionElem) {
return figCaptionElem.innerHTML;
} else {
return "";
}
},
},
audioSrc: {
parseHTML: (element) => {
const audioElem = element.querySelector("audio");
if (audioElem) {
return audioElem.src;
} else {
return "";
}
},
},
};
},
addCommands() {
return {
setLibreCmsAudio:
(attributes) =>
({ commands }) => {
return commands.setNode("libreCmsAudioNode", attributes);
},
};
},
addNodeView() {
return ({
editor,
node,
getPos,
HTMLAttributes,
decorations,
extension,
}) => {
const templateNode = document.querySelector(
"#librecms-audio-node-view"
);
if (!templateNode) {
const errorMsg = document.createElement("div");
errorMsg.classList.add("alert", "alert-danger");
errorMsg.textContent = "Failed to create audio node view.";
return errorMsg;
}
const dom = document.createElement("div");
dom.classList.add("librecms-audio-node-view", "p-2");
if (!node.attrs.align) {
node.attrs.align = "center";
}
dom.classList.add(`librecms-audio-node-align-${node.attrs.align}`);
const template = templateNode as HTMLTemplateElement;
const nodeView = template.content.cloneNode(true) as HTMLElement;
const audioElem = nodeView.querySelector("audio");
const dialogIdNr = Math.floor(Math.random() * 1000000000);
const selectDialogId = `librecms-image-audio-select-audio-dialog-${dialogIdNr}`;
const selectDialogTitleId = `librecms-audio-node-select-audio-dialog-${dialogIdNr}-title`;
const selectButtonElem = nodeView.querySelector(
".select-audio-button"
);
if (selectButtonElem) {
selectButtonElem.setAttribute(
"data-target",
`#${selectDialogId}`
);
}
const selectDialogElem = nodeView.querySelector(
".modal.select-audio-dialog"
);
if (selectDialogElem) {
selectDialogElem.id = selectDialogId;
selectDialogElem.setAttribute(
"aria-labelledby",
selectDialogTitleId
);
const selectDialogTitleElem =
selectDialogElem.querySelector(".modal-title");
if (selectDialogTitleElem) {
selectDialogTitleElem.id = selectDialogTitleId;
}
const selectDialogIds =
selectDialogElem.querySelectorAll("*[id]");
selectDialogIds.forEach((elemWithId) => {
elemWithId.id = `${elemWithId.id}-${dialogIdNr}`;
});
const selectDialogLabels =
selectDialogElem.querySelectorAll("*[for]");
selectDialogLabels.forEach((label) => {
label.setAttribute(
"for",
`${label.getAttribute("for")}-${dialogIdNr}`
);
});
const describedElems = selectDialogElem.querySelectorAll(
"*[aria-describedby]"
);
describedElems.forEach((describedElem) => {
describedElem.setAttribute(
"aria-describedby",
`${describedElem.getAttribute(
"aria-describedby"
)}-${dialogIdNr}`
);
});
}
const settingsDialogId = `librecms-audio-node-view-settings-dialog-${dialogIdNr}`;
const settingsDialogTitleId = `${settingsDialogId}-title`;
const settingsButtonElem = nodeView.querySelector(
".audio-settings-button"
);
if (settingsButtonElem) {
settingsButtonElem.setAttribute(
"data-target",
`#${settingsDialogId}`
);
}
const settingsDialogElem = nodeView.querySelector(
".modal.audio-settings-dialog"
);
if (settingsDialogElem) {
settingsDialogElem.id = settingsDialogId;
settingsDialogElem.setAttribute(
"aria-labelledby",
settingsDialogTitleId
);
const settingsDialogTitleElem =
settingsDialogElem.querySelector(".modal-title");
if (settingsDialogTitleElem) {
settingsDialogTitleElem.id = settingsDialogTitleId;
}
const settingDialogIds =
settingsDialogElem.querySelectorAll("*[id]");
for (let i = 0; i < settingDialogIds.length; i++) {
const elemWithId = settingDialogIds.item(i);
elemWithId.id = `${elemWithId.id}-${dialogIdNr}`;
}
const settingDialogLabels =
settingsDialogElem.querySelectorAll("*[for]");
for (let i = 0; i < settingDialogLabels.length; i++) {
const label = settingDialogLabels.item(
i
) as HTMLLabelElement;
label.setAttribute(
"for",
`${label.getAttribute("for")}-${dialogIdNr}`
);
}
const describedElems = settingsDialogElem.querySelectorAll(
"*[aria-describedby]"
);
for (let i = 0; i < describedElems.length; i++) {
const describedElem = describedElems.item(i);
describedElem.setAttribute(
"aria-describedby",
`${describedElem.getAttribute(
"aria-describedby"
)}-${dialogIdNr}`
);
}
const submitButton = settingsDialogElem.querySelector(
"button.audio-settings-dialog-save"
);
const altTextInput = settingsDialogElem.querySelector(
`input#alttext-${dialogIdNr}`
);
const captionInput = settingsDialogElem.querySelector(
`input#caption-${dialogIdNr}`
);
const alignSelect = settingsDialogElem.querySelector(
`select#align-${dialogIdNr}`
);
if (altTextInput) {
(altTextInput as HTMLInputElement).value =
node.attrs.altText;
} else {
console.warn("Input for alt text not found.");
}
if (captionInput) {
(captionInput as HTMLInputElement).value = node.attrs.figCaption;
} else {
console.warn("Input for caption not found.");
}
if (alignSelect) {
const optionElems = alignSelect.querySelectorAll("option");
for (let i = 0; i < optionElems.length; i++) {
const optionElem = optionElems.item(
i
) as HTMLOptionElement;
if (optionElem.value === node.attrs.align) {
optionElem.selected = true;
}
}
} else {
console.warn("Select for image alignment not found.");
}
if (submitButton) {
submitButton.addEventListener("click", (event) => {
const altInputElem = altTextInput as HTMLInputElement;
node.attrs.altText = altInputElem.value;
if (audioElem) {
audioElem.textContent = altInputElem.value;
}
const captionInputElem =
captionInput as HTMLInputElement;
node.attrs.figCaption = captionInputElem.value;
if (figCaptionElem) {
figCaptionElem.textContent = captionInputElem.value;
}
if (alignSelect) {
const selectElem = alignSelect as HTMLSelectElement;
node.attrs.align =
selectElem.selectedOptions.item(0)?.value;
["floatleft", "center", "floatright"].forEach(
(align) =>
dom.classList.remove(
`librecms-audio-node-align-${align}`
)
);
dom.classList.add(
`librecms-audio-node-align-${node.attrs.align}`
);
} else {
console.warn(
"Select for audio alignment not found."
);
}
});
} else {
console.warn(
"Submit button for image settings dialog not found."
);
}
}
const figCaptionElem = nodeView.querySelector("figcaption");
if (node.attrs.figCaption !== "" && figCaptionElem) {
figCaptionElem.innerHTML = node.attrs.figCaption;
}
dom.appendChild(nodeView);
if (selectButtonElem) {
if (audioElem) {
selectButtonElem.addEventListener("click", (event) =>
loadAudioAssets(event, node, audioElem)
);
} else {
console.error("audio elem not found.");
}
}
return {
dom,
};
};
},
parseHTML() {
return [
{
tag: "figure[data-type=librecms-audio-node]",
},
];
},
renderHTML({ node, HTMLAttributes }) {
return [
"figure",
mergeAttributes({
"data-align": node.attrs.align,
"data-type": "librecms-image-node",
}),
[
"audio",
{
src: node.attrs.audioSrc,
textContent: node.attrs.altText,
},
],
["figcaption", node.attrs.figCaption],
];
},
});
function loadAudioAssets(
event: Event,
node: ProsemirrorNode<any>,
audioElem: HTMLAudioElement
) {
const eventTarget = event.currentTarget as HTMLElement;
const editorElem = document.querySelector(".cms-editor");
if (!editorElem) {
return;
}
const baseUrl = editorElem.getAttribute("data-baseUrl");
const contentSection = editorElem.getAttribute("data-contentsection");
const dialogId = eventTarget.getAttribute("data-target");
if (!dialogId) {
console.error("data-target attribute is missing.");
return;
}
const dialog = document.querySelector(dialogId);
if (!dialog) {
console.error("dialog element not found is missing.");
return;
}
const rowTemplateResult = document.querySelector(
"#librecms-audio-node-view-row"
);
if (!rowTemplateResult) {
console.error("template for result row not found.");
return;
}
const rowTemplate = rowTemplateResult as HTMLTemplateElement;
const table = dialog.querySelector("table");
if (!table) {
console.error("result table not found.");
return;
}
const tableBody = table.querySelector("tbody");
if (!tableBody) {
console.error("table body not found.");
return;
}
tableBody.innerHTML = "";
const fetchUrl = `/content-sections/${contentSection}/assets?type=org.librecms.assets.AudioAsset,org.librecms.assets.ExternalAudioAsset`;
fetch(fetchUrl)
.then((response) => {
if (response.ok) {
response
.json()
.then((data) => {
const audioAssets = data as [];
for (const audioAsset of audioAssets) {
const row = rowTemplate.content.cloneNode(
true
) as Element;
const colName = row.querySelector(".col-name");
const colType = row.querySelector(".col-type");
const selectButton =
row.querySelector(".col-action button");
if (colName) {
colName.textContent = audioAsset["name"];
}
if (colType) {
colType.textContent = audioAsset["type"];
}
if (selectButton) {
selectButton.setAttribute(
"data-audiouuid",
audioAsset["uuid"]
);
selectButton.addEventListener(
"click",
(event) => {
const audioUrl = buildAudioMediaUrl(audioAsset);
node.attrs.audioSrc = audioUrl;
if (audioElem) {
audioElem.src = audioUrl;
} else {
console.error(
"audio element not found."
);
}
}
);
}
tableBody.appendChild(row);
}
})
.catch((error) => {
console.error(error);
});
} else {
console.error(
`Error. Status: ${response.status}. Status Text: ${response.statusText}`
);
}
})
.catch((error) => {
console.error(error);
});
}
function buildAudioMediaUrl(audioAsset: any) {
switch (audioAsset["type"]) {
case "org.librecms.assets.ExternalAudioAsset":
return audioAsset["properties"]["targetUrl"];
case "org.librecms.assets.AudioAsset":
return `/content-sections/info/audiomedia/uuid-${audioAsset["uuid"]}`;
default:
console.error(`Unknown audio asset type.`);
return "";
}
}

View File

@ -1,5 +0,0 @@
import { AudioNode } from "./audio-node";
export * from "./audio-node";
export default AudioNode;

View File

@ -1,570 +0,0 @@
import { Node, nodeInputRule, mergeAttributes, Range } from "@tiptap/core";
import { Node as ProsemirrorNode } from "prosemirror-model";
declare module "@tiptap/core" {
interface Commands<ReturnType> {
libreCmsImageNode: {
setLibreCmsImage: (attributes?: { language: string }) => ReturnType;
};
}
}
export const ImageNode = Node.create({
name: "libreCmsImageNode",
content: "inline*",
marks: "",
group: "block",
code: false,
defining: true,
addAttributes() {
return {
align: {
parseHTML: (element) => {
if (element.hasAttribute("data-align")) {
return element.getAttribute("data-align");
} else {
return "center";
}
},
},
altText: {
parseHTML: (element) => {
const imgElem = element.querySelector("img");
if (imgElem) {
return imgElem.alt;
} else {
return "";
}
},
},
figCaption: {
parseHTML: (element) => {
const figCaptionElem = element.querySelector("figcaption");
if (figCaptionElem) {
return figCaptionElem.innerHTML;
} else {
return "";
}
},
},
fullSizeOverlay: {
parseHTML: (element) => {
if (element.hasAttribute("data-fullsizeoverlay")) {
return (
element.getAttribute("data-fullsizeoverlay") ===
"true"
);
} else {
return false;
}
},
},
imgSrc: {
parseHTML: (element) => {
const imgElem = element.querySelector("img");
if (imgElem) {
return imgElem.src;
} else {
return "";
}
},
},
size: {
parseHTML: (element) => {
if (element.hasAttribute("data-size")) {
return element.getAttribute("data-size");
} else {
return "50";
}
},
},
};
},
addCommands() {
return {
setLibreCmsImage:
(attributes) =>
({ commands }) => {
return commands.setNode("libreCmsImageNode", attributes);
},
};
},
addNodeView() {
return ({
editor,
node,
getPos,
HTMLAttributes,
decorations,
extension,
}) => {
const templateNode = document.querySelector(
"#librecms-image-node-view"
);
if (!templateNode) {
const errorMsg = document.createElement("div");
errorMsg.classList.add("alert", "alert-danger");
errorMsg.textContent = "Failed to create image node view.";
return errorMsg;
}
// console.log("Node: ");
// console.dir(node);
// console.log(`getPos =`);
// console.dir(getPos);
// console.log(`HTMLAttributes =`);
// console.dir(HTMLAttributes);
// console.log(`decorations = ${decorations}`);
// console.dir(decorations);
// console.log(`extension = ${extension}`);
// console.dir(extension);
const dom = document.createElement("div");
dom.classList.add("librecms-image-node-view", "p-2");
if (!node.attrs.size) {
node.attrs.size = "50";
}
dom.classList.add(`librecms-image-node-width-${node.attrs.size}`);
if (!node.attrs.align) {
node.attrs.align = "center";
}
dom.classList.add(`librecms-image-node-align-${node.attrs.align}`);
const template = templateNode as HTMLTemplateElement;
const nodeView = template.content.cloneNode(true) as HTMLElement;
const imgElem = nodeView.querySelector("img");
if (imgElem) {
imgElem.src = node.attrs.imgSrc;
imgElem.alt = node.attrs.altText;
} else {
console.error("No img element.");
}
const dialogIdNr = Math.floor(Math.random() * 1000000000);
const selectDialogId = `librecms-image-node-select-image-dialog-${dialogIdNr}`;
const selectDialogTitleId = `librecms-image-node-select-image-dialog-${dialogIdNr}-title`;
const selectButtonElem = nodeView.querySelector(
".select-image-button"
);
if (selectButtonElem) {
selectButtonElem.setAttribute(
"data-target",
`#${selectDialogId}`
);
}
const selectDialogElem = nodeView.querySelector(
".modal.select-image-dialog"
);
if (selectDialogElem) {
selectDialogElem.id = selectDialogId;
selectDialogElem.setAttribute(
"aria-labelledby",
selectDialogTitleId
);
const selectDialogTitleElem =
selectDialogElem.querySelector(".modal-title");
if (selectDialogTitleElem) {
selectDialogTitleElem.id = selectDialogTitleId;
}
const selectDialogIds =
selectDialogElem.querySelectorAll("*[id]");
selectDialogIds.forEach((elemWithId) => {
elemWithId.id = `${elemWithId.id}-${dialogIdNr}`;
});
const selectDialogLabels =
selectDialogElem.querySelectorAll("*[for]");
selectDialogLabels.forEach((label) => {
label.setAttribute(
"for",
`${label.getAttribute("for")}-${dialogIdNr}`
);
});
const describedElems = selectDialogElem.querySelectorAll(
"*[aria-describedby]"
);
describedElems.forEach((describedElem) => {
describedElem.setAttribute(
"aria-describedby",
`${describedElem.getAttribute(
"aria-describedby"
)}-${dialogIdNr}`
);
});
}
const settingsDialogId = `librecms-image-node-view-settings-dialog-${dialogIdNr}`;
const settingsDialogTitleId = `${settingsDialogId}-title`;
const settingsButtonElem = nodeView.querySelector(
".image-settings-button"
);
if (settingsButtonElem) {
settingsButtonElem.setAttribute(
"data-target",
`#${settingsDialogId}`
);
}
const settingsDialogElem = nodeView.querySelector(
".modal.image-settings-dialog"
);
if (settingsDialogElem) {
settingsDialogElem.id = settingsDialogId;
settingsDialogElem.setAttribute(
"aria-labelledby",
settingsDialogTitleId
);
const settingsDialogTitleElem =
settingsDialogElem.querySelector(".modal-title");
if (settingsDialogTitleElem) {
settingsDialogTitleElem.id = settingsDialogTitleId;
}
const settingDialogIds =
settingsDialogElem.querySelectorAll("*[id]");
for (let i = 0; i < settingDialogIds.length; i++) {
const elemWithId = settingDialogIds.item(i);
elemWithId.id = `${elemWithId.id}-${dialogIdNr}`;
}
const settingDialogLabels =
settingsDialogElem.querySelectorAll("*[for]");
for (let i = 0; i < settingDialogLabels.length; i++) {
const label = settingDialogLabels.item(
i
) as HTMLLabelElement;
label.setAttribute(
"for",
`${label.getAttribute("for")}-${dialogIdNr}`
);
}
const describedElems = settingsDialogElem.querySelectorAll(
"*[aria-describedby]"
);
for (let i = 0; i < describedElems.length; i++) {
const describedElem = describedElems.item(i);
describedElem.setAttribute(
"aria-describedby",
`${describedElem.getAttribute(
"aria-describedby"
)}-${dialogIdNr}`
);
}
const submitButton = settingsDialogElem.querySelector(
"button.image-settings-dialog-save"
);
const altTextInput = settingsDialogElem.querySelector(
`input#alttext-${dialogIdNr}`
);
const captionInput = settingsDialogElem.querySelector(
`input#caption-${dialogIdNr}`
);
const alignSelect = settingsDialogElem.querySelector(
`select#align-${dialogIdNr}`
);
const sizeSelect = settingsDialogElem.querySelector(
`select#size-${dialogIdNr}`
);
const fullSizeOverlayInput = settingsDialogElem.querySelector(
`input#fullsizeoverlay-${dialogIdNr}`
);
if (altTextInput) {
(altTextInput as HTMLInputElement).value =
node.attrs.altText;
} else {
console.warn("Input for alt text not found.");
}
if (captionInput) {
(captionInput as HTMLInputElement).value =
node.attrs.figCaption;
} else {
console.warn("Input for caption not found.");
}
if (alignSelect) {
const optionElems = alignSelect.querySelectorAll("option");
for (let i = 0; i < optionElems.length; i++) {
const optionElem = optionElems.item(
i
) as HTMLOptionElement;
if (optionElem.value === node.attrs.align) {
optionElem.selected = true;
}
}
} else {
console.warn("Select for image alignment not found.");
}
if (sizeSelect) {
const optionElems = sizeSelect.querySelectorAll("option");
for (let i = 0; i < optionElems.length; i++) {
const optionElem = optionElems.item(
i
) as HTMLOptionElement;
if (optionElem.value === node.attrs.size) {
optionElem.selected = true;
}
}
} else {
console.warn("Select for image size not found.");
}
if (fullSizeOverlayInput) {
(fullSizeOverlayInput as HTMLInputElement).checked =
node.attrs.fullSizeOverlay;
} else {
console.warn("Input for fullSizeOverlay not found.");
}
if (submitButton) {
submitButton.addEventListener("click", (event) => {
const altInputElem = altTextInput as HTMLInputElement;
node.attrs.altText = altInputElem.value;
if (imgElem) {
imgElem.alt = altInputElem.value;
}
const captionInputElem =
captionInput as HTMLInputElement;
node.attrs.figCaption = captionInputElem.value;
if (figCaptionElem) {
figCaptionElem.textContent = captionInputElem.value;
}
if (alignSelect) {
const selectElem = alignSelect as HTMLSelectElement;
node.attrs.align =
selectElem.selectedOptions.item(0)?.value;
["floatleft", "center", "floatright"].forEach(
(align) =>
dom.classList.remove(
`librecms-image-node-align-${align}`
)
);
dom.classList.add(
`librecms-image-node-align-${node.attrs.align}`
);
} else {
console.warn(
"Select for image alignment not found."
);
}
if (sizeSelect) {
const selectElem = sizeSelect as HTMLSelectElement;
node.attrs.size =
selectElem.selectedOptions.item(0)?.value;
["25", "33", "50", "66", "75", "100"].forEach(
(size) =>
dom.classList.remove(
`librecms-image-node-width-${size}`
)
);
dom.classList.add(
`librecms-image-node-width-${node.attrs.size}`
);
} else {
console.warn("Select for image size not found.");
}
if (fullSizeOverlayInput) {
const inputElem =
fullSizeOverlayInput as HTMLInputElement;
node.attrs.fullSizeOverlay = inputElem.checked;
} else {
console.warn(
"Input for fullSizeOverlay not found."
);
}
});
} else {
console.warn(
"Submit button for image settings dialog not found."
);
}
}
const figCaptionElem = nodeView.querySelector("figcaption");
if (node.attrs.figCaption !== "" && figCaptionElem) {
figCaptionElem.innerHTML = node.attrs.figCaption;
}
dom.appendChild(nodeView);
if (selectButtonElem) {
if (imgElem) {
selectButtonElem.addEventListener("click", (event) =>
loadImages(event, node, imgElem)
);
} else {
console.error("img elem not found.");
}
}
return {
dom,
};
};
},
parseHTML() {
return [
{
tag: "figure[data-type=librecms-image-node]",
},
];
},
renderHTML({ node, HTMLAttributes }) {
// console.log("node = ");
// console.dir(node);
return [
"figure",
mergeAttributes({
"data-align": node.attrs.align,
"data-fullsizeoverlay": node.attrs.fullSizeOverlay,
"data-size": node.attrs.size,
"data-type": "librecms-image-node",
}),
[
"img",
{
alt: node.attrs.altText,
src: node.attrs.imgSrc,
},
],
["figcaption", node.attrs.figCaption],
];
},
});
function loadImages(
event: Event,
node: ProsemirrorNode<any>,
imgElem: HTMLImageElement
) {
// console.log("Loading images...");
const eventTarget = event.currentTarget as HTMLElement;
const editorElem = document.querySelector(".cms-editor");
if (!editorElem) {
return;
}
const baseUrl = editorElem.getAttribute("data-baseUrl");
const contentSection = editorElem.getAttribute("data-contentsection");
const dialogId = eventTarget.getAttribute("data-target");
if (!dialogId) {
console.error("data-target attribute is missing.");
return;
}
const dialog = document.querySelector(dialogId);
if (!dialog) {
console.error("dialog element not found is missing.");
return;
}
const rowTemplateResult = document.querySelector(
"#librecms-image-node-view-row"
);
if (!rowTemplateResult) {
console.error("template for result row not found.");
return;
}
const rowTemplate = rowTemplateResult as HTMLTemplateElement;
const table = dialog.querySelector("table");
if (!table) {
console.error("result table not found.");
return;
}
const tableBody = table.querySelector("tbody");
if (!tableBody) {
console.error("table body not found.");
return;
}
tableBody.innerHTML = "";
const fetchUrl = `/content-sections/${contentSection}/assets?type=org.librecms.assets.Image`;
fetch(fetchUrl)
.then((response) => {
if (response.ok) {
response
.json()
.then((data) => {
const images = data as [];
for (const image of images) {
const row = rowTemplate.content.cloneNode(
true
) as Element;
const colName = row.querySelector(".col-name");
const colType = row.querySelector(".col-type");
const selectButton =
row.querySelector(".col-action button");
if (colName) {
colName.textContent = image["name"];
}
if (colType) {
colType.textContent = image["type"];
}
if (selectButton) {
selectButton.setAttribute(
"data-imageuuid",
image["uuid"]
);
selectButton.addEventListener(
"click",
(event) => {
const imgUrl = `/content-sections/info/images/uuid-${image["uuid"]}`;
node.attrs.imgSrc = imgUrl;
if (imgElem) {
imgElem.src = imgUrl;
} else {
console.error(
"img element not found."
);
}
}
);
}
tableBody.appendChild(row);
}
})
.catch((error) => {
console.error(error);
console.trace(error);
});
} else {
console.error(
`Error. Status: ${response.status}. Status Text: ${response.statusText}`
);
}
})
.catch((error) => {
console.error(error);
console.trace(error);
});
}

View File

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

View File

@ -1,5 +0,0 @@
import { VideoNode } from "./video-node";
export * from "./video-node";
export default VideoNode;

View File

@ -1,520 +0,0 @@
import { Node, nodeInputRule, mergeAttributes, Range } from "@tiptap/core";
import { merge } from "jquery";
import { Node as ProsemirrorNode } from "prosemirror-model";
declare module "@tiptap/core" {
interface Commands<ReturnType> {
libreCmsVideoNode: {
setLibreCmsVideo: (attributes?: { language: string }) => ReturnType;
};
}
}
export const VideoNode = Node.create({
name: "libreCmsVideoNode",
content: "inline*",
marks: "",
group: "block",
code: false,
defining: true,
addAttributes() {
return {
align: {
parseHTML: (element) => {
if (element.hasAttribute("data-align")) {
return element.getAttribute("data-align");
} else {
return "center";
}
},
},
altText: {
parseHTML: (element) => {
const videoElem = element.querySelector("video");
if (videoElem) {
return videoElem.textContent;
} else {
return "";
}
},
},
figCaption: {
parseHTML: (element) => {
const figCaptionElem = element.querySelector("figcaption");
if (figCaptionElem) {
return figCaptionElem.innerHTML;
} else {
return "";
}
},
},
videoSrc: {
parseHTML: (element) => {
const videoElem = element.querySelector("video");
if (videoElem) {
return videoElem.src;
} else {
return "";
}
},
},
size: {
parseHTML: (element) => {
if (element.hasAttribute("data-size")) {
return element.getAttribute("data-size");
} else {
return "50";
}
},
},
};
},
addCommands() {
return {
setLibreCmsVideo:
(attributes) =>
({ commands }) => {
return commands.setNode("libreCmsVideoNode", attributes);
},
};
},
addNodeView() {
return ({
editor,
node,
getPos,
HTMLAttributes,
decorations,
extension,
}) => {
const templateNode = document.querySelector(
"#librecms-video-node-view"
);
if (!templateNode) {
const errorMsg = document.createElement("div");
errorMsg.classList.add("alert alert-danger");
errorMsg.textContent = "Failed to create video node view.";
}
const dom = document.createElement("div");
dom.classList.add("librecms-video-node-view", "p-2");
if (!node.attrs.size) {
node.attrs.size = "50";
}
dom.classList.add(`librecms-video-node-width-${node.attrs.size}`);
if (!node.attrs.align) {
node.attrs.align = "center";
}
dom.classList.add(`librecms-video-node-align-${node.attrs.align}`);
const template = templateNode as HTMLTemplateElement;
const nodeView = template.content.cloneNode(true) as HTMLElement;
const videoElem = nodeView.querySelector("video");
const dialogIdNr = Math.floor(Math.random() * 1000000000);
const selectDialogId = `librecms-video-node-select-video-dialog-title-${dialogIdNr}`;
const selectDialogTitleId = `librecms-video-node-select-video-dialog-${dialogIdNr}-title`;
const selectButtonElem = nodeView.querySelector(
".select-video-button"
);
if (selectButtonElem) {
selectButtonElem.setAttribute(
"data-target",
`#${selectDialogId}`
);
}
const selectDialogElem = nodeView.querySelector(
".modal.select-video-dialog"
);
if (selectDialogElem) {
selectDialogElem.id = selectDialogId;
selectDialogElem.setAttribute(
"aria-labelledby",
selectDialogTitleId
);
const selectDialogTitleElem =
selectDialogElem.querySelector(".modal-title");
if (selectDialogTitleElem) {
selectDialogTitleElem.id = selectDialogTitleId;
}
const selectDialogIds =
selectDialogElem.querySelectorAll("*[id]");
selectDialogIds.forEach((elemWithId) => {
elemWithId.id = `${elemWithId.id}-${dialogIdNr}`;
});
const selectDialogLabels =
selectDialogElem.querySelectorAll("*[for]");
selectDialogLabels.forEach((label) => {
label.setAttribute(
"for",
`${label.getAttribute("for")}-${dialogIdNr}`
);
});
const describedElems = selectDialogElem.querySelectorAll(
"*[aria-describedby]"
);
describedElems.forEach((describedElem) => {
describedElem.setAttribute(
"aria-describedby",
`${describedElem.getAttribute(
"aria-describedby"
)}-${dialogIdNr}}`
);
});
}
const settingsDialogId = `librecms-video-node-view-settings-dialog-${dialogIdNr}`;
const settingsDialogTitleId = `${settingsDialogId}-title`;
const settingsButtonElem = nodeView.querySelector(
".video-settings-button"
);
if (settingsButtonElem) {
settingsButtonElem.setAttribute(
"data-target",
`#${settingsDialogId}`
);
}
const settingsDialogElem = nodeView.querySelector(
".modal.video-settings-dialog"
);
if (settingsDialogElem) {
settingsDialogElem.id = settingsDialogId;
settingsDialogElem.setAttribute(
"aria-labelledby",
settingsDialogTitleId
);
const settingsDialogTitleElem =
settingsDialogElem.querySelector(".modal-title");
if (settingsDialogTitleElem) {
settingsDialogTitleElem.id = settingsDialogTitleId;
}
const settingDialogIds =
settingsDialogElem.querySelectorAll("*[id]");
for (let i = 0; i < settingDialogIds.length; i++) {
const elemWithId = settingDialogIds.item(i);
elemWithId.id = `${elemWithId.id}-${dialogIdNr}`;
}
const settingDialogLabels =
settingsDialogElem.querySelectorAll("*[for]");
for (let i = 0; i < settingDialogLabels.length; i++) {
const label = settingDialogLabels.item(
i
) as HTMLLabelElement;
label.setAttribute(
"for",
`${label.getAttribute("for")}-${dialogIdNr}`
);
}
const describedElems = settingsDialogElem.querySelectorAll(
"*[aria-describedby]"
);
for (let i = 0; i < describedElems.length; i++) {
const describedElem = describedElems.item(i);
describedElem.setAttribute(
"aria-describedby",
`${describedElem.getAttribute(
"aria-describedby"
)}-${dialogIdNr}`
);
}
const submitButton = settingsDialogElem.querySelector(
"button.video-settings-dialog-save"
);
const altTextInput = settingsDialogElem.querySelector(
`input#alttext-${dialogIdNr}`
);
const captionInput = settingsDialogElem.querySelector(
`input#caption-${dialogIdNr}`
);
const alignSelect = settingsDialogElem.querySelector(
`select#align-${dialogIdNr}`
);
const sizeSelect = settingsDialogElem.querySelector(
`select#size-${dialogIdNr}`
);
if (altTextInput) {
(altTextInput as HTMLInputElement).value =
node.attrs.altText;
} else {
console.warn("Input for alt text not found.");
}
if (captionInput) {
(captionInput as HTMLInputElement).value =
node.attrs.figCaption;
} else {
console.warn("Input for caption not found.");
}
if (alignSelect) {
const optionElems = alignSelect.querySelectorAll("option");
for (let i = 0; i < optionElems.length; i++) {
const optionElem = optionElems.item(
i
) as HTMLOptionElement;
if (optionElem.value === node.attrs.align) {
optionElem.selected = true;
}
}
} else {
console.warn("Select for video alignment not found.");
}
if (sizeSelect) {
const optionElems = sizeSelect.querySelectorAll("option");
for (let i = 0; i < optionElems.length; i++) {
const optionElem = optionElems.item(
i
) as HTMLOptionElement;
if (optionElem.value === node.attrs.size) {
optionElem.selected = true;
}
}
} else {
console.warn("Select for video size not found.");
}
if (submitButton) {
submitButton.addEventListener("click", (event) => {
const altInputElem = altTextInput as HTMLInputElement;
node.attrs.altText = altInputElem.value;
if (videoElem) {
videoElem.textContent = altInputElem.value;
}
const captionInputElem =
captionInput as HTMLInputElement;
node.attrs.figCaption = captionInputElem.value;
if (figCaptionElem) {
figCaptionElem.textContent = captionInputElem.value;
}
if (alignSelect) {
const selectElem = alignSelect as HTMLSelectElement;
node.attrs.align =
selectElem.selectedOptions.item(0)?.value;
["floatleft", "center", "floatright"].forEach(
(align) =>
dom.classList.remove(
`librecms-video-node-align-${align}`
)
);
dom.classList.add(
`librecms-video-node-align-${node.attrs.align}`
);
} else {
console.warn(
"Select for video alignment not found."
);
}
if (sizeSelect) {
const selectElem = sizeSelect as HTMLSelectElement;
node.attrs.size =
selectElem.selectedOptions.item(0)?.value;
["25", "33", "50", "66", "75", "100"].forEach(
(size) =>
dom.classList.remove(
`librecms-video-node-width-${size}`
)
);
dom.classList.add(
`librecms-video-node-width-${node.attrs.size}`
);
} else {
console.warn("Select for video size not found.");
}
});
} else {
console.warn(
"Submit button for video settings dialog not found."
);
}
}
const figCaptionElem = nodeView.querySelector("figcaption");
if (node.attrs.figCaption !== "" && figCaptionElem) {
figCaptionElem.innerHTML = node.attrs.figCaption;
}
dom.appendChild(nodeView);
if (selectButtonElem) {
if (videoElem) {
selectButtonElem.addEventListener("click", (event) =>
loadVideos(event, node, videoElem)
);
} else {
console.error("video elem not found.");
}
}
return {
dom,
};
};
},
parseHTML() {
return [
{
tag: "figure[data-type=librecms-video-node]",
},
];
},
renderHTML({ node, HTMLAttributes }) {
return [
"figure",
mergeAttributes({
"data-align": node.attrs.align,
"data-size": node.attrs.size,
"data-type": "librecms-video-node",
}),
[
"video",
{
src: node.attrs.videoSrc,
textContent: node.attrs.altText,
},
],
["figcaption", node.attrs.figCaption],
];
},
});
function loadVideos(
event: Event,
node: ProsemirrorNode<any>,
videoElem: HTMLVideoElement
) {
const eventTarget = event.currentTarget as HTMLElement;
const editorElem = document.querySelector(".cms-editor");
if (!editorElem) {
return;
}
const baseUrl = editorElem.getAttribute("data-baseUrl");
const contentSection = editorElem.getAttribute("data-contentsection");
const dialogId = eventTarget.getAttribute("data-target");
if (!dialogId) {
console.error("data-target attribute is missing.");
return;
}
const dialog = document.querySelector(dialogId);
if (!dialog) {
console.error("dialog element not found is missing.");
return;
}
const rowTemplateResult = document.querySelector(
"#librecms-video-node-view-row"
);
if (!rowTemplateResult) {
console.error("template for result row not found.");
return;
}
const rowTemplate = rowTemplateResult as HTMLTemplateElement;
const table = dialog.querySelector("table");
if (!table) {
console.error("result table not found.");
return;
}
const tableBody = table.querySelector("tbody");
if (!tableBody) {
console.error("table body not found.");
return;
}
tableBody.innerHTML = "";
const fetchUrl = `/content-sections/${contentSection}/assets?type=org.librecms.assets.VideoAsset,org.librecms.assets.ExternalVideoAsset`;
fetch(fetchUrl)
.then((response) => {
if (response.ok) {
response
.json()
.then((data) => {
const videos = data as [];
for (const video of videos) {
const row = rowTemplate.content.cloneNode(
true
) as Element;
const colName = row.querySelector(".col-name");
const colType = row.querySelector(".col-type");
const selectButton =
row.querySelector(".col-action button");
if (colName) {
colName.textContent = video["name"];
}
if (colType) {
colType.textContent = video["type"];
}
if (selectButton) {
selectButton.setAttribute(
"data-videouuid",
video["uuid"]
);
selectButton.addEventListener(
"click",
(event) => {
const videoUrl = buildVideoUrl(video);
node.attrs.videoSrc = videoUrl;
if (videoElem) {
videoElem.src = videoUrl;
} else {
console.error(
"video element not found."
);
}
}
);
}
tableBody.appendChild(row);
}
})
.catch((error) => {
console.error(error);
});
} else {
console.error(
`Error. Status: ${response.status}. Status Text: ${response.statusText}`
);
}
})
.catch((error) => {
console.error(error);
});
}
function buildVideoUrl(video: any) {
switch (video["type"]) {
case "org.librecms.assets.ExternalVideoAsset":
return video["properties"]["targetUrl"];
case "org.librecms.assets.VideoAsset":
return `/content-sections/info/videos/uuid-${video["uuid"]}`;
default:
console.error(`Unknown video asset type.`);
return "";
}
}

View File

@ -1,19 +0,0 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"declaration": true,
"lib": [
"DOM",
"ES2016"
],
"module": "commonjs",
"moduleResolution": "node",
"outDir": "target/generated-resources/assets",
"sourceMap": true,
"strict": true,
"target": "ES6"
},
"include": [
"src/main/typescript/**/*"
]
}

View File

@ -1,23 +0,0 @@
module.exports = {
mode: "development",
devtool: "source-map",
optimization: {
chunkIds: false
},
entry: {
"ccm-cms-editor": "./src/main/typescript/ccm-cms-editor.ts"
},
output: {
filename: "[name].js",
path: __dirname + "/target/generated-resources/assets"
},
resolve: {
extensions: [".tsx", ".ts", ".js", ".json"]
},
module: {
rules: [
// all files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'
{ test: /\.tsx?$/, use: ["ts-loader"], exclude: /node_modules/ }
]
}
}