diff --git a/ccm-cms/src/main/java/org/librecms/assets/VideoAsset.java b/ccm-cms/src/main/java/org/librecms/assets/VideoAsset.java
index cd7a3ffab..a49526dab 100644
--- a/ccm-cms/src/main/java/org/librecms/assets/VideoAsset.java
+++ b/ccm-cms/src/main/java/org/librecms/assets/VideoAsset.java
@@ -19,6 +19,7 @@
package org.librecms.assets;
import com.arsdigita.cms.ui.assets.forms.AudioForm;
+
import org.hibernate.envers.Audited;
import java.io.Serializable;
diff --git a/ccm-cms/src/main/java/org/librecms/contentsection/rs/Assets.java b/ccm-cms/src/main/java/org/librecms/contentsection/rs/Assets.java
index e8d0e6fc4..a3b8d20de 100644
--- a/ccm-cms/src/main/java/org/librecms/contentsection/rs/Assets.java
+++ b/ccm-cms/src/main/java/org/librecms/contentsection/rs/Assets.java
@@ -53,9 +53,9 @@ import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
/**
- * Provides a Web Service (build using JAX-RS). Used for example by the
+ * Provides a Web Service (build using JAX-RS). Used for example by the
* {@link AssetSearchWidget}.
- *
+ *
* @author Jens Pelzetter
*/
@RequestScoped
@@ -67,7 +67,7 @@ public class Assets {
@Inject
private FolderRepository folderRepo;
-
+
@Inject
private FolderManager folderManager;
@@ -76,7 +76,7 @@ public class Assets {
@Inject
private AssetManager assetManager;
-
+
@Inject
private AssetTypesManager assetTypesManager;
@@ -116,16 +116,18 @@ public class Assets {
result.put("type",
Folder.class.getName());
result.put("place", "");
-
+
return result;
}
private Map createAssetMapEntry(final Asset asset) {
final Map result = new HashMap<>();
- result.put("assetId",
+ result.put("assetId",
Long.toString(asset.getObjectId()));
-
+
+ result.put("uuid", asset.getUuid());
+
result.put("title",
asset.getTitle().getValue(globalizationHelper
.getNegotiatedLocale()));
@@ -142,12 +144,12 @@ public class Assets {
final Optional assetFolder = assetManager.getAssetFolder(asset);
if (assetFolder.isPresent()) {
- result.put("place",
- folderManager.getFolderPath(assetFolder.get()));
+ result.put("place",
+ folderManager.getFolderPath(assetFolder.get()));
} else {
result.put("place", "");
}
-
+
return result;
}
diff --git a/ccm-editor/src/main/typescript/ccm-editor/ccm-cms-editor.ts b/ccm-editor/src/main/typescript/ccm-editor/ccm-cms-editor.ts
index 9080e98dc..75e2c8ee6 100644
--- a/ccm-editor/src/main/typescript/ccm-editor/ccm-cms-editor.ts
+++ b/ccm-editor/src/main/typescript/ccm-editor/ccm-cms-editor.ts
@@ -62,11 +62,11 @@ export class InsertInternalLinkCommand extends CCMEditorCommand {
.setAttribute("id", "ccm-editor-contentsection-select");
const filterInputLabel: Element = filterForm
.appendChild(document.createElement("label"));
- filterInputLabel.setAttribute("id", "ccm-editor-itemfilter");
+ filterInputLabel.setAttribute("id", "ccm-editor-assetfilter");
filterInputLabel.textContent = "Filter items";
const filterInput: HTMLInputElement = filterForm
.appendChild(document.createElement("input"));
- filterInput.setAttribute("id", "ccm-editor-itemfilter");
+ filterInput.setAttribute("id", "ccm-editor-assetfilter");
filterInput.setAttribute("type", "text");
const applyFiltersButton: Element = filterForm
.appendChild(document.createElement("button"));
@@ -223,3 +223,589 @@ export class InsertInternalLinkCommand extends CCMEditorCommand {
this.button.setAttribute("disabled", "true");
}
}
+
+export class InsertMediaAssetCommand extends CCMEditorCommand {
+
+ private button: HTMLElement;
+
+ private IMAGE_TYPE: string = "org.libreccm.assets.ImageAsset";
+ private VIDEO_TYPE: string = "org.libreccm.assets.VideoAsset";
+ private EXTERNAL_VIDEO_TYPE: string
+ = "org.libreccm.assets.ExternalVideoAsset";
+ private AUDIO_TYPE: string = "org.libreccm.assets.Audio";
+ private EXTERNAL_AUDIO_TYPE: string
+ = "org.libreccm.assets.ExternalAudioAsset";
+
+ constructor(editor: CCMEditor, settings: any) {
+
+ super(editor, settings);
+
+ this.button = this.fragment
+ .appendChild(document.createElement("button"));
+
+ const icon: Element = this.button
+ .appendChild(document.createElement("i"));
+ icon.className = "fa fa-file-image-o";
+
+ const text: Element = this.button
+ .appendChild(document.createElement("span"));
+ this.button.setAttribute("title",
+ "Insert media content (image, audio, video)");
+ text.textContent = `Insert media content like an image, a audio file or
+ a video`;
+ text.className = "ccm-editor-accessibility";
+
+ const command: InsertMediaAssetCommand = this;
+
+ this.button.addEventListener("click", function() {
+
+ event.preventDefault();
+
+ const currentRange: Range = document.getSelection().getRangeAt(0);
+
+ const dialogFragment: DocumentFragment = document
+ .createDocumentFragment();
+ const dialogElem: HTMLElement = dialogFragment
+ .appendChild(document.createElement("div"));
+ dialogElem.className = "ccm-editor-selectdialog";
+ const dialogTitleElem: Element = dialogElem
+ .appendChild(document.createElement("h1"));
+ dialogTitleElem.textContent = "Insert media";
+
+ const closeButton: HTMLButtonElement = dialogElem
+ .appendChild(document.createElement("button"));
+ closeButton.className = "ccm-editor-selectdialog-closebutton";
+ closeButton.textContent = "\u2715";
+ closeButton.addEventListener("click", function() {
+ event.preventDefault();
+ const bodyElem = document.getElementsByTagName("body").item(0);
+ bodyElem.removeChild(dialogElem);
+ document.getSelection().removeAllRanges();
+ document.getSelection().addRange(currentRange);
+ });
+
+ const filterForm: HTMLElement = dialogElem
+ .appendChild(document.createElement("div"));
+ const contentSectionSelectLabel: HTMLElement = filterForm
+ .appendChild(document.createElement("label"));
+ contentSectionSelectLabel
+ .setAttribute("for", "ccm-editor-contentsection-select");
+ contentSectionSelectLabel.textContent
+ = "Show media assets from Content Section";
+ const contentSectionSelect: HTMLSelectElement = filterForm
+ .appendChild(document.createElement("select"));
+ contentSectionSelect
+ .setAttribute("id", "ccm-editor-contentsection-select");
+ const typeSelectLabel: HTMLElement = filterForm
+ .appendChild(document.createElement("label"));
+ typeSelectLabel.setAttribute("for", "ccm-editor-assettype-select");
+ typeSelectLabel.textContent = "Type";
+ const typeSelect: HTMLSelectElement = filterForm
+ .appendChild(document.createElement("select"));
+ typeSelect.setAttribute("id", "ccm-editor-assettype-select");
+ const typeSelectOptions = [
+ {
+ type: command.IMAGE_TYPE,
+ label: "Image",
+ },
+ {
+ type: command.VIDEO_TYPE,
+ label: "Video",
+ },
+ {
+ type: command.EXTERNAL_VIDEO_TYPE,
+ label: "External Video",
+ },
+ {
+ type: command.AUDIO_TYPE,
+ label: "Audio file",
+ },
+ {
+ type: command.EXTERNAL_AUDIO_TYPE,
+ label: "External Audio file",
+ },
+ ];
+ for (const typeOption of typeSelectOptions) {
+ const option: HTMLElement = document.createElement("option");
+ option.setAttribute("value", typeOption.type);
+ option.textContent = typeOption.label
+ typeSelect.appendChild(option);
+ }
+ const filterInputLabel: Element = filterForm
+ .appendChild(document.createElement("label"));
+ filterInputLabel.setAttribute("id", "ccm-editor-assetfilter");
+ filterInputLabel.textContent = "Filter items";
+ const filterInput: HTMLInputElement = filterForm
+ .appendChild(document.createElement("input"));
+ filterInput.setAttribute("id", "ccm-editor-assetfilter");
+ filterInput.setAttribute("type", "text");
+ const applyFiltersButton: Element = filterForm
+ .appendChild(document.createElement("button"));
+ applyFiltersButton.textContent = "Clear filters";
+
+ const clearFiltersButton: Element = filterForm
+ .appendChild(document.createElement("button"));
+ clearFiltersButton.textContent = "Clear filters";
+ clearFiltersButton.addEventListener("click", function(event){
+ event.preventDefault();
+ filterInput.value = "";
+ });
+
+ const table: HTMLElement = dialogElem
+ .appendChild(document.createElement("table"));
+ const tableHead: HTMLElement = table
+ .appendChild(document.createElement("thead"));
+ const headerRow: HTMLElement = tableHead
+ .appendChild(document.createElement("tr"));
+ const titleColHeader: HTMLElement = headerRow
+ .appendChild(document.createElement("th"));
+ const typeColHeader: Element = headerRow
+ .appendChild(document.createElement("th"));
+ const placeColHeader: Element = headerRow
+ .appendChild(document.createElement("th"));
+ titleColHeader.textContent = "Title";
+ typeColHeader.textContent = "Type";
+ placeColHeader.textContent = "Place";
+ const tableBody: HTMLElement = table
+ .appendChild(document.createElement("tbody"));
+
+ const contextPrefix = editor.getDataAttribute("content-prefix");
+ // Get content sections
+ const currentSection = editor
+ .getDataAttribute("current-contentsection-primaryurl");
+ const sectionsUrl = contextPrefix + "/content-sections/";
+ fetch(sectionsUrl, { credentials: "include" }).then((response) => {
+
+ if (response.ok) {
+ response.json().then((data) => {
+ for(const section of data) {
+ const option: HTMLElement = contentSectionSelect
+ .appendChild(document.createElement("option"));
+ option.setAttribute("value", section.primaryUrl);
+ option.textContent = section.primaryUrl;
+ if (section.primaryUrl === currentSection) {
+ option.setAttribute("selected", "selected");
+ }
+ }
+ });
+ }
+ });
+
+ contentSectionSelect.addEventListener("change", (event) => {
+ event.preventDefault();
+ command.fetchAssets(dialogElem,
+ contentSectionSelect,
+ typeSelect,
+ filterInput,
+ tableBody,
+ currentRange);
+ });
+ typeSelect.addEventListener("change", (event) => {
+ event.preventDefault();
+ command.fetchAssets(dialogElem,
+ contentSectionSelect,
+ typeSelect,
+ filterInput,
+ tableBody,
+ currentRange);
+ });
+ applyFiltersButton.addEventListener("click", (event) => {
+ event.preventDefault();
+ command.fetchAssets(dialogElem,
+ contentSectionSelect,
+ typeSelect,
+ filterInput,
+ tableBody,
+ currentRange);
+ });
+ });
+ }
+
+ private fetchAssets(dialogElem: HTMLElement,
+ contentSectionSelect: HTMLSelectElement,
+ typeSelect: HTMLSelectElement,
+ filterField: HTMLInputElement,
+ resultsTableBody: HTMLElement,
+ currentRange: Range): void {
+
+ const assetsUrl = this.buildAssetsUrl(contentSectionSelect.value,
+ typeSelect.value,
+ filterField.value);
+
+ fetch(assetsUrl, { credentials: "include" }).then((response) => {
+
+ if (response.ok) {
+ response.json().then((data) => {
+
+ resultsTableBody.innerHTML = "";
+
+ for(const asset of data) {
+ const row: HTMLElement = resultsTableBody
+ .appendChild(document.createElement("tr"));
+ const dataTitle: HTMLElement = row
+ .appendChild(document.createElement("td"));
+ const dataType: HTMLElement = row
+ .appendChild(document.createElement("td"));
+ const dataPlace = row
+ .appendChild(document.createElement("td"));
+
+ const selectAssetButton = dataTitle
+ .appendChild(document.createElement("button"));
+ selectAssetButton.textContent = asset.title;
+
+ selectAssetButton
+ .addEventListener("click", event => {
+ event.preventDefault();
+
+ this.insertMedia(
+ dialogElem,
+ this.editor
+ .getDataAttribute("content-prefix"),
+ contentSectionSelect.value,
+ asset.uuid,
+ asset.title,
+ asset.type,
+ asset.typeLabel,
+ currentRange);
+ });
+
+ dataType.textContent = asset.typeLabel;
+ dataType.textContent = asset.place;
+ }
+ },
+ (failure) => {
+ const errorMsgElement = document.createElement("div");
+ errorMsgElement.classList.add("ccm-editor-error");
+ errorMsgElement.textContent = `Failed to fetch assets from
+ ${assetsUrl}: ${failure}`;
+ resultsTableBody.parentNode.parentNode
+ .insertBefore(errorMsgElement,
+ resultsTableBody.parentNode);
+ });
+ } else {
+ const errorMsgElement = document.createElement("div");
+ errorMsgElement.classList.add("ccm-editor-error");
+ errorMsgElement.textContent = `Failed to fetch assets from
+ ${assetsUrl}. Status code: ${response.status}.
+ Status text: ${response.statusText}`;
+ resultsTableBody.parentNode.parentNode
+ .insertBefore(errorMsgElement, resultsTableBody.parentNode);
+ }
+ },
+ (failure) => {
+ const errorMsgElement = document.createElement("div");
+ errorMsgElement.classList.add("ccm-editor-error");
+ errorMsgElement.textContent = `Failed to fetch assets from
+ ${assetsUrl}: ${failure}`;
+ resultsTableBody.parentNode.parentNode
+ .insertBefore(errorMsgElement, resultsTableBody.parentNode);
+ });
+ }
+
+ private buildAssetsUrl(contentSection: string,
+ type: string,
+ query?: string): string {
+ const contextPrefix = this.editor.getDataAttribute("content-prefix");
+
+ let url: string = `${contextPrefix}/content-sections`;
+ if (!(new RegExp("^/.*").test(contentSection))) {
+ url = `${url}/`;
+ }
+ url = `${url}${contentSection}`;
+ if (!(new RegExp(".*/$").test(url))) {
+ url = `${url}/`
+ }
+ if (query) {
+ url = `${url}?query=${query}`;
+ }
+
+ return url;
+ }
+
+ private insertMedia(selectDialogElem: HTMLElement,
+ contextPrefix: string,
+ contentSection: string,
+ assetUuid: string,
+ assetTitle: string,
+ assetType: string,
+ assetTypeLabel: string,
+ currentRange: Range): void {
+
+ selectDialogElem.setAttribute("style", "display: none");
+
+ const dialogId: string = "ccm-editor-insertmedia";
+ const captionFieldId: string = `${dialogId}-caption`;
+ const altFieldId: string = `${dialogId}-alt`;
+ const decorativeCheckboxId: string
+ = `${dialogId}-isdecorative`;
+ const widthFieldId: string = `${dialogId}-width`;
+ const heightFieldId: string = `${dialogId}-height`;
+
+ const fragment: DocumentFragment = document.createDocumentFragment();
+ const insertDialogElem: HTMLElement = fragment
+ .appendChild(document.createElement("div"));
+ insertDialogElem.classList.add("ccm-editor-dialog");
+ insertDialogElem.id = dialogId;
+
+ const headingElem: HTMLElement = insertDialogElem
+ .appendChild(document.createElement("h1"));
+ headingElem.textContent = `Insert media asset "${assetTitle}" of
+ type ${assetTypeLabel}`;
+
+ const formElem: HTMLElement = insertDialogElem
+ .appendChild(document.createElement("form"));
+
+ const captionLabel: HTMLLabelElement = formElem
+ .appendChild(document.createElement("label"));
+ captionLabel.htmlFor = captionFieldId;
+ captionLabel.textContent = "Caption";
+ const captionField: HTMLInputElement = formElem
+ .appendChild(document.createElement("input"));
+ captionField.id = captionFieldId;
+ captionField.type = "text";
+
+ if (this.IMAGE_TYPE === assetType) {
+
+ const altLabel: HTMLLabelElement = formElem
+ .appendChild(document.createElement("label"));
+ altLabel.htmlFor = altFieldId;
+ altLabel.textContent = "Alternativ text for image";
+ const altField: HTMLInputElement = formElem
+ .appendChild(document.createElement("input"));
+ altField.type = "text";
+ altField.id = altFieldId;
+
+ const decorativeLabel: HTMLLabelElement = formElem
+ .insertBefore(document.createElement("label"), captionLabel);
+ decorativeLabel.htmlFor = decorativeCheckboxId;
+ decorativeLabel.textContent = "Is decorative image?";
+ const decorativeCheckbox: HTMLInputElement = formElem
+ .insertBefore(document.createElement("input"), captionLabel);
+ decorativeCheckbox.id = decorativeCheckboxId;
+ decorativeCheckbox.type = "checkbox";
+
+ decorativeCheckbox.addEventListener("change", (event) => {
+ if (decorativeCheckbox.checked) {
+ captionLabel.setAttribute("style", "display: none");
+ captionField.setAttribute("style", "display: none");
+ captionField.value = "";
+
+ altLabel.setAttribute("style", "display: none");
+ altField.setAttribute("style", "display: none");
+ altField.value = "";
+ } else {
+ captionLabel.removeAttribute("style");
+ captionField.removeAttribute("style");
+ altLabel.removeAttribute("style");
+ altField.removeAttribute("style");
+ }
+ });
+
+ if (this.IMAGE_TYPE === assetType
+ || this.VIDEO_TYPE === assetType
+ || this.EXTERNAL_VIDEO_TYPE === assetType) {
+
+ const widthLabel: HTMLLabelElement = formElem
+ .appendChild(document.createElement("label"));
+ widthLabel.htmlFor = widthFieldId;
+ widthLabel.textContent = "Width";
+ const widthField: HTMLInputElement = formElem
+ .appendChild(document.createElement("input"));
+ widthField.id = widthFieldId;
+ widthField.type = "number";
+
+ const heightLabel: HTMLLabelElement = formElem
+ .appendChild(document.createElement("label"));
+ heightLabel.htmlFor = heightFieldId;
+ heightLabel.textContent = "height";
+ const heightField: HTMLInputElement = formElem
+ .appendChild(document.createElement("input"));
+ heightField.id = heightFieldId;
+ heightField.type = "number";
+ }
+
+ const insertButton: HTMLButtonElement = formElem
+ .appendChild(document.createElement("button"));
+ insertButton.textContent = "insert";
+ insertButton.addEventListener("click", (event) => {
+ event.preventDefault();
+
+ const captionField: HTMLInputElement = document
+ .getElementById(captionFieldId) as HTMLInputElement;
+ let mediaHtml: string;
+ switch(assetType) {
+ case this.AUDIO_TYPE: {
+ mediaHtml = this
+ .generateAudioHtml("0", captionField.value);
+ }
+ case this.EXTERNAL_AUDIO_TYPE: {
+ mediaHtml = this
+ .generateAudioHtml("0", captionField.value);
+ }
+ case this.EXTERNAL_VIDEO_TYPE: {
+ const widthField: HTMLInputElement = document
+ .getElementById(widthFieldId) as HTMLInputElement;
+ const heightField: HTMLInputElement = document
+ .getElementById(widthFieldId) as
+ HTMLInputElement;
+ mediaHtml = this
+ .generateVideoHtml("0",
+ captionField.value,
+ Number(widthField.value),
+ Number(heightField.value));
+ }
+ case this.IMAGE_TYPE: {
+ const altField: HTMLInputElement = document
+ .getElementById(altFieldId) as HTMLInputElement;
+ const widthField: HTMLInputElement = document
+ .getElementById(widthFieldId) as HTMLInputElement;
+ const heightField: HTMLInputElement = document
+ .getElementById(widthFieldId) as
+ HTMLInputElement;
+ const imageUrl: string = this
+ .generateImageUrl(contextPrefix,
+ contentSection,
+ assetUuid);
+
+ if (decorativeCheckbox.checked) {
+ mediaHtml = this
+ .generateDecorativeImageHtml(
+ imageUrl,
+ Number(widthField.value),
+ Number(heightField.value));
+ } else {
+ mediaHtml = this
+ .generateImageHtml(imageUrl,
+ captionField.value,
+ altField.value,
+ Number(widthField.value),
+ Number(heightField.value));
+ }
+ }
+ case this.VIDEO_TYPE: {
+ const widthField: HTMLInputElement = document
+ .getElementById(widthFieldId) as HTMLInputElement;
+ const heightField: HTMLInputElement = document
+ .getElementById(widthFieldId) as
+ HTMLInputElement;
+ mediaHtml = this
+ .generateVideoHtml("0",
+ captionField.value,
+ Number(widthField.value),
+ Number(heightField.value));
+ }
+ default:
+ mediaHtml = "";
+ }
+
+ document.getSelection().removeAllRanges();
+ document.getSelection().addRange(currentRange);
+ document.execCommand("insertHTML", false, mediaHtml);
+
+ const bodyElem = document.getElementsByTagName("body").item(0);
+ bodyElem.removeChild(insertDialogElem);
+ bodyElem.removeChild(selectDialogElem);
+ });
+
+ const cancelButton: HTMLButtonElement = formElem
+ .appendChild(document.createElement("button"));
+ cancelButton.textContent = "Cancel";
+ cancelButton.addEventListener("click", (event) => {
+ event.preventDefault();
+
+ const bodyElem = document.getElementsByTagName("body").item(0);
+ bodyElem.removeChild(insertDialogElem);
+ selectDialogElem.removeAttribute("style");
+ });
+ }
+ }
+
+ private generateDecorativeImageHtml(imageUrl: string,
+ width: number = -1,
+ height: number = -1): string {
+
+ let dimensions: string;
+ if (width > 0) {
+ dimensions = `${dimensions} width="${width}"`;
+ }
+ if (height > 0) {
+ dimensions = `${dimensions} height="${height}"`;
+ }
+
+ return `
`;
+ }
+
+ private generateImageHtml(imageUrl: string,
+ caption: string,
+ alt: string,
+ width: number = -1,
+ height: number = -1): string {
+
+ let dimensions: string;
+ if (width > 0) {
+ dimensions = `${dimensions} width="${width}"`;
+ }
+ if (height > 0) {
+ dimensions = `${dimensions} height="${height}"`;
+ }
+
+ return `
+
+ ${caption}
+ `
+ }
+
+ private generateImageUrl(contextPrefix: string,
+ contentSection: string,
+ assetUuid: string,
+ width: number = -1,
+ height: number = -1): string {
+
+ let imageUrl: string = `${contextPrefix}/content-sections`;
+ if (!(new RegExp("^/.*").test(contentSection))) {
+ imageUrl = `${imageUrl}/`;
+ }
+ imageUrl = `${imageUrl}{$contentSection}`;
+ if (!(new RegExp(".*/$").test(contentSection))) {
+ imageUrl = `${imageUrl}/`;
+ }
+ return `${imageUrl}/images/uuid-${assetUuid}
+ ?width=${width}&height=${height}`;
+ }
+
+ private generateVideoHtml(videoUrl: string,
+ caption: string,
+ width: number = -1,
+ height: number = -1): string {
+
+ return `
+ Not implemented yet
+ ${caption}
+ `;
+ }
+
+ private generateAudioHtml(audioUrl: string, caption: string): string {
+
+ return `
+ Not implemented yet
+ ${caption}
+ `;
+ }
+
+ getCommandType(): CCMEditorCommandType {
+ return CCMEditorCommandType.INSERT_INLINE;
+ }
+
+ selectionChanged(selection: Selection) {
+
+ }
+
+ enableCommand(): void {
+ this.button.removeAttribute("disabled");
+ }
+
+ disableCommand(): void {
+ this.button.setAttribute("disabled", "disabled");
+ }
+
+}