CCM NG: InsertMediaCommand for ccm-editor

git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@5226 8810af33-2d31-482b-a856-94f89814c4df
pull/2/head
jensp 2018-01-31 18:31:27 +00:00
parent a9d8be8a22
commit 665752dade
3 changed files with 601 additions and 12 deletions

View File

@ -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;

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@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<String, String> createAssetMapEntry(final Asset asset) {
final Map<String, String> 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<Folder> 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;
}

View File

@ -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 `<img src="${imageUrl}" ${dimensions} alt="" />`;
}
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 `<figure role="group">
<img src="${imageUrl}" ${dimensions} alt="${alt}" />
<figcaption>${caption}</figcaption>
</figure>`
}
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 `<figure role="group">
<span>Not implemented yet</span>
<figcaption>${caption}</figcaption>
</figure>`;
}
private generateAudioHtml(audioUrl: string, caption: string): string {
return `<figure role="group">
<span>Not implemented yet</span>
<figcaption>${caption}</figcaption>
</figure>`;
}
getCommandType(): CCMEditorCommandType {
return CCMEditorCommandType.INSERT_INLINE;
}
selectionChanged(selection: Selection) {
}
enableCommand(): void {
this.button.removeAttribute("disabled");
}
disableCommand(): void {
this.button.setAttribute("disabled", "disabled");
}
}