ContentItemPicker component and bugfixes for asset picker

pull/10/head
Jens Pelzetter 2021-07-24 17:40:41 +02:00
parent 80cf9143ee
commit c8f36b9099
13 changed files with 409 additions and 62 deletions

View File

@ -244,7 +244,7 @@ import javax.xml.bind.annotation.XmlElementWrapper;
+ ")")
,
@NamedQuery(
name = "ContentItem.findNameAndContentSectionAndVersion",
name = "ContentItem.findByNameAndContentSectionAndVersion",
query = "SELECT DISTINCT i "
+ "FROM ContentItem i "
+ "JOIN i.contentType t "
@ -310,7 +310,9 @@ import javax.xml.bind.annotation.XmlElementWrapper;
+ " )"
+ " )"
+ " OR true = :isSystemUser OR true = :isAdmin"
+ ")")
+ ")"
)
,
@NamedQuery(
name = "ContentItem.findByNameAndTypeAndContentSection",

View File

@ -339,7 +339,7 @@ public class ContentItemRepository
final TypedQuery<ContentItem> query = getEntityManager()
.createNamedQuery(
"ContentItem.findByNameAndContentSectionAndVersion",
"ContentItem.findByTypeAndContentSectionAndVersion",
ContentItem.class);
query.setParameter("section", section);
query.setParameter("type", type);
@ -357,7 +357,7 @@ public class ContentItemRepository
final ContentSection section) {
final TypedQuery<ContentItem> query = getEntityManager()
.createNamedQuery("ContentItem.findByNameAndContentSection",
.createNamedQuery("ContentItem.findByNameAndTypeAndContentSection",
ContentItem.class);
query.setParameter("section", section);
query.setParameter("name", name);

View File

@ -96,7 +96,7 @@ public class ContentItems {
if (ContentItem.class.isAssignableFrom(clazz)) {
@SuppressWarnings("unchecked")
final Class<? extends ContentItem> typeClass
= (Class<? extends ContentItem>) clazz;
= (Class<? extends ContentItem>) clazz;
return typeClass;
} else {
throw new IllegalArgumentException(String.format(
@ -187,25 +187,30 @@ public class ContentItems {
} else if ((query != null && !query.trim().isEmpty())
&& (type == null || type.trim().isEmpty())) {
items = itemRepo.findByNameAndContentSection(query,
contentSection,
itemVersion);
items = itemRepo.findByNameAndContentSection(
query,
contentSection,
itemVersion
);
} else if ((query == null || query.trim().isEmpty())
&& (type != null && !type.trim().isEmpty())) {
final Class<? extends ContentItem> itemType
= toContentItemTypeClass(type);
items = itemRepo.findByTypeAndContentSection(itemType,
contentSection,
itemVersion);
= toContentItemTypeClass(type);
items = itemRepo.findByTypeAndContentSection(
itemType,
contentSection,
itemVersion
);
} else {
final Class<? extends ContentItem> itemType
= toContentItemTypeClass(type);
= toContentItemTypeClass(type);
items = itemRepo.findByNameAndTypeAndContentSection(
query,
itemType,
contentSection,
itemVersion);
itemVersion
);
}
return items
@ -279,11 +284,11 @@ public class ContentItems {
} else if ((query == null || query.trim().isEmpty())
&& (type != null && type.trim().isEmpty())) {
final Class<? extends ContentItem> itemType
= toContentItemTypeClass(type);
= toContentItemTypeClass(type);
items = itemRepo.filterByFolderAndType(folder, itemType);
} else {
final Class<? extends ContentItem> itemType
= toContentItemTypeClass(type);
= toContentItemTypeClass(type);
items = itemRepo.filterByFolderAndTypeAndName(folder,
itemType,
query);

View File

@ -187,43 +187,10 @@ public class LinkDetailsModel {
return Bookmark.class.getName();
}
// /**
// * Sets the properties of this model based on the properties on the provided
// * link.
// *
// * @param link The link to use.
// */
// protected void setInternalLink(final RelatedLink link) {
// Objects.requireNonNull(link);
//
// uuid = link.getUuid();
// label = globalizationHelper.getValueFromLocalizedString(
// link.getTitle()
// );
// title = link
// .getTitle()
// .getValues()
// .entrySet()
// .stream()
// .collect(
// Collectors.toMap(
// entry -> entry.getKey().toString(),
// entry -> entry.getValue()
// )
// );
// final Set<Locale> titleLocales = link.getTitle().getAvailableLocales();
// unusedTitleLocales = globalizationHelper
// .getAvailableLocales()
// .stream()
// .filter(locale -> !titleLocales.contains(locale))
// .map(Locale::toString)
// .collect(Collectors.toList());
// targetItemUuid = link.getTargetItem().getItemUuid();
// targetItemName = link.getTargetItem().getDisplayName();
// targetItemTitle = globalizationHelper.getValueFromLocalizedString(
// link.getTargetItem().getTitle()
// );
// }
public boolean isTargetSet() {
return bookmarkUuid != null || targetItemUuid != null;
}
public void setTargetItemTitle(String targetItemTitle) {
this.targetItemTitle = targetItemTitle;
}

View File

@ -53,7 +53,7 @@
class="modal fade"
id="#{cc.attrs.assetPickerId}-dialog"
tabindex="-1">
<div class="modal-dialog">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<c:choose>
@ -82,7 +82,10 @@
id="#{cc.attrs.assetPickerId}-dialog-title">#{cc.attrs.dialogTitle}</h6>
</c:when>
<c:otherwise>
<div><strong>#{cc.attrs.dialogTitle}</strong></div>
<div class="modal-title"
id="#{cc.attrs.assetPickerId}-dialog-title">
<strong>#{cc.attrs.dialogTitle}</strong>
</div>
</c:otherwise>
</c:choose>
<button
@ -127,9 +130,7 @@
<th>#{CmsAssetsStepsDefaultMessagesBundle['assetpicker.column.action']}</th>
</tr>
</thead>
<tbody>
</tbody>
<tbody></tbody>
</table>
</form>
<div class="modal-footer">

View File

@ -26,7 +26,7 @@
data-target="##{cc.attrs.assetPickerId}-dialog"
type="button">
<bootstrap:svgIcon icon="#{cc.attrs.buttonIcon}" />
<span class="sr-only">#{buttonText}</span>
<span class="sr-only">#{cc.attrs.buttonText}</span>
</button>
</cc:implementation>
</html>

View File

@ -0,0 +1,146 @@
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:bootstrap="http://xmlns.jcp.org/jsf/composite/components/bootstrap"
xmlns:cc="http://xmlns.jcp.org/jsf/composite"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<cc:interface shortDescription="An component for selecting content items">
<cc:attribute name="actionUrl"
required="true"
shortDescription="Base URL for the actions of the asset picker."
type="String" />
<cc:attribute name="contentItemType"
required="false"
shortDescription="Type of the content items to show."
type="String"
default="org.librecms.contentsection.ContentItem" />
<cc:attribute name="contentItemPickerId"
required="true"
shortDescription="ID of the content item picker."
type="String" />
<cc:attribute name="contentSection"
required="true"
shortDescription="The current content section"
type="String" />
<cc:attribute name="dialogTitle"
required="false"
shortDescription="Title of the content item picker dialog"
default="#{CmsDefaultStepsMessageBundle['contentitempicker.title']}"
type="String" />
<cc:attribute name="formParamName"
required="true"
shortDescription="The name of the form parameter that will contain the UUID of the selected asset."
type="String" />
<cc:attribute name="headingLevel"
required="false"
shortDescription="The level of the heading used as title of the content item picker dialog."
default="3"
type="int" />
<cc:attribute name="baseUrl"
required="true"
shortDescription="Base URL for requests. Must include the scheme, the server name, the port (if no standard port is used) and the context path of the application."
type="String" />
</cc:interface>
<cc:implementation>
<div class="ccm-cms-contentitem-picker"
data-contentitem-type="#{cc.attrs.contentItemType}"
data-baseUrl="#{cc.attrs.baseUrl}"
data-contentsection="#{cc.attrs.contentSection}"
id="#{cc.attrs.contentItemPickerId}">
<div aria-hidden="true"
aria-labelledby="#{cc.attrs.contentItemPickerId}-dialog-title"
class="modal fade"
id="#{cc.attrs.contentItemPickerId}-dialog"
tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<c:choose>
<c:when test="#{cc.attrs.headingLevel == 1}">
<h1 class="modal-title"
id="#{cc.attrs.contentItemPickerId}-dialog-title">#{cc.attrs.dialogTitle}</h1>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 2}">
<h2 class="modal-title"
id="#{cc.attrs.contentItemPickerId}-dialog-title">#{cc.attrs.dialogTitle}</h2>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 3}">
<h3 class="modal-title"
id="#{cc.attrs.contentItemPickerId}-dialog-title">#{cc.attrs.dialogTitle}</h3>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 4}">
<h4 class="modal-title"
id="#{cc.attrs.contentItemPickerId}-dialog-title">#{cc.attrs.dialogTitle}</h4>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 5}">
<h5 class="modal-title"
id="#{cc.attrs.contentItemPickerId}-dialog-title">#{cc.attrs.dialogTitle}</h5>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 6}">
<h6 class="modal-title"
id="#{cc.attrs.contentItemPickerId}-dialog-title">#{cc.attrs.dialogTitle}</h6>
</c:when>
<c:otherwise>
<div id="#{cc.attrs.contentItemPickerId}-dialog-title">
<strong>#{cc.attrs.dialogTitle}</strong>
</div>
</c:otherwise>
</c:choose>
<button
aria-label="#{CmsDefaultStepsMessageBundle['contentitempicker.close']}"
class="close"
data-dismiss="modal"
type="button">
<bootstrap:svgIcon icon="x" />
</button>
</div>
<form action="#{cc.attrs.actionUrl}"
class="modal-body"
method="post">
<input class="contentitempicker-param"
id="#{cc.attrs.contentItemPickerId}-param-input"
name="#{cc.attrs.formParamName}"
type="hidden" />
<bootstrap:formGroupText
class="contentitempicker-filter"
help="#{CmsDefaultStepsMessageBundle['contenitempicker.filter.help']}"
inputId="#{cc.attrs.contentItemPickerId}-filter"
label="#{CmsDefaultStepsMessageBundle['contenitempicker.filter.label']}"
name="" />
<template id="#{cc.attrs.contentItemPickerId}-row">
<tr>
<td class="col-name"></td>
<td class="col-type"></td>
<td class="col-action">
<button class="btn btn-primary"
data-itemuuid=""
type="button">
#{CmsDefaultStepsMessageBundle['contentitempicker.select']}
</button>
</td>
</tr>
</template>
<table>
<thead>
<tr>
<th>#{CmsDefaultStepsMessageBundle['contentitempicker.column.name']}</th>
<th>#{CmsDefaultStepsMessageBundle['contentitempicker.column.type']}</th>
<th>#{CmsDefaultStepsMessageBundle['contentitempicker.column.action']}</th>
</tr>
</thead>
<tbody></tbody>
</table>
</form>
<div class="modal-footer">
<button class="btn btn-warning"
data-dismiss="modal"
type="button">
#{CmsDefaultStepsMessageBundle['contentitempicker.close']}
</button>
</div>
</div>
</div>
</div>
</div>
</cc:implementation>
</html>

View File

@ -0,0 +1,32 @@
<!DOCTYPE html [<!ENTITY times '&#215;'>]>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:bootstrap="http://xmlns.jcp.org/jsf/composite/components/bootstrap"
xmlns:cc="http://xmlns.jcp.org/jsf/composite"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<cc:interface shortDescription="An component for selecting content items">
<cc:attribute name="contentItemPickerId"
required="true"
shortDescription="ID of the content item picker"
type="String" />
<cc:attribute name="buttonText"
required="true"
shortDescription="Label of the button"
type="String" />
<cc:attribute name="buttonIcon"
default="pen"
required="false"
shortDescription="Icon of the content item picker button"
type="String" />
</cc:interface>
<cc:implementation>
<button class="btn btn-primary"
data-toggle="modal"
data-target="##{cc.attrs.contentItemPickerId}-dialog"
type="button">
<bootstrap:svgIcon icon="#{cc.attrs.buttonIcon}" />
<span class="sr-only">#{cc.attrs.buttonText}</span>
</button>
</cc:implementation>
</html>

View File

@ -62,11 +62,59 @@
<div class="d-none relatedlink-target"
id="relatedlink-target-internal">
<p>ToDo: Content Item Picker</p>
<c:choose>
<c:when test="#{CmsLinkDetailsModel.targetSet}">
<dl>
<div>
<dt>#{CmsDefaultStepsMessageBundle['relatedinfo.link.details.target.type']}</dt>
<dd>#{CmsDefaultStepsMessageBundle['relatedinfo.links.type.internal']}</dd>
</div>
<div>
<dt>#{CmsDefaultStepsMessageBundle['relatedinfo.link.details.target.item_name']}</dt>
<dd>#{CmsLinkDetailsModel.targetItemName}</dd>
</div>
</dl>
</c:when>
<c:otherwise>
<p>
#{CmsDefaultStepsMessageBundle['relatedinfo.link.details.target.not_set']}
</p>
</c:otherwise>
</c:choose>
<librecms:contentItemPickerButton
contentItemPickerId="item-picker"
buttonText="#{CmsDefaultStepsMessageBundle['relatedinfo.link.details.title.target.select_targetitem']}"
/>
<librecms:contentItemPicker
actionUrl="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@relatedinfo/attachmentlists/#{CmsLinkDetailsModel.listIdentifier}/links/#{CmsLinkDetailsModel.uuid}/details/@set-target-item"
contentItemPickerId="item-picker"
baseUrl="#{CmsLinkDetailsModel.baseUrl}"
contentSection="#{CmsLinkDetailsModel.sectionName}"
formParamName="itemIdentifier"
/>
</div>
<div class="d-none relatedlink-target"
id="relatedlink-target-external">
<c:choose>
<c:when test="#{CmsLinkDetailsModel.targetSet}">
<dl>
<div>
<dt>#{CmsDefaultStepsMessageBundle['relatedinfo.link.details.target.type']}</dt>
<dd>#{CmsDefaultStepsMessageBundle['relatedinfo.links.type.external']}</dd>
</div>
<div>
<dt>#{CmsDefaultStepsMessageBundle['relatedinfo.link.details.target.bookmark_name']}</dt>
<dd>#{CmsLinkDetailsModel.bookmarkName}</dd>
</div>
</dl>
</c:when>
<c:otherwise>
<p>
#{CmsDefaultStepsMessageBundle['relatedinfo.link.details.target.not_set']}
</p>
</c:otherwise>
</c:choose>
<librecms:assetPickerButton
assetPickerId="bookmark-picker"
buttonText="#{CmsDefaultStepsMessageBundle['relatedinfo.link.details.title.target.select_bookmark']}"

View File

@ -205,3 +205,16 @@ relatedinfo.links.type.help=The type of the link. Internal links are linked with
relatedinfo.link.details.title.target=Target of the related link
relatedinfo.link.details.title.target.select_bookmark=Select bookmark
relatedinfo.links.type.none_selected=Please select a link type.
contentitempicker.title=Select content item to use
contentitempicker.close=Cancel
contenitempicker.filter.help=Filters the content item shown below by name.
contenitempicker.filter.label=Filter content items
contentitempicker.select=Select
contentitempicker.column.name=Name
contentitempicker.column.type=Type
contentitempicker.column.action=Actions
relatedinfo.link.details.title.target.select_targetitem=Select the target of the link
relatedinfo.link.details.target.not_set=Target for link has not been set yet.
relatedinfo.link.details.target.type=Type
relatedinfo.link.details.target.item_name=Target
relatedinfo.link.details.target.bookmark_name=Bookmark

View File

@ -205,3 +205,16 @@ relatedinfo.links.type.help=Der Typ des Links. Interne Links verweisen auf ein a
relatedinfo.link.details.title.target=Ziel des weiterf\u00fchrenden Links
relatedinfo.link.details.title.target.select_bookmark=Lesezeichen ausw\u00e4hlen
relatedinfo.links.type.none_selected=Bitte w\u00e4hlen Sie einen Link-Typ aus.
contentitempicker.title=W\u00e4hlen Sie das Content Item, das verwendet werden soll
contentitempicker.close=Abbrechen
contenitempicker.filter.help=Filtert die unten angezeigten Content Item nach Name.
contenitempicker.filter.label=Content Item filtern
contentitempicker.select=Ausw\u00e4hlen
contentitempicker.column.name=Name
contentitempicker.column.type=Typ
contentitempicker.column.action=Aktionen
relatedinfo.link.details.title.target.select_targetitem=Ziel des Links ausw\u00e4hlen
relatedinfo.link.details.target.not_set=Es wurde noch kein Ziel f\u00fcr den Link gesetzt.
relatedinfo.link.details.target.type=Typ
relatedinfo.link.details.target.item_name=Ziel
relatedinfo.link.details.target.bookmark_name=Lesezeichen

View File

@ -4,4 +4,6 @@ import "./cms-assetpicker";
import "./cms-attachment-lists";
import "./cms-contentitempicker";
import "./cms-related-link";

View File

@ -0,0 +1,118 @@
import * as $ from "jquery";
document.addEventListener("DOMContentLoaded", function (event) {
const itemPickers = document.querySelectorAll(
".ccm-cms-contentitem-picker"
);
for (let i = 0; i < itemPickers.length; i++) {
initContentItemPicker(itemPickers[i]);
}
});
async function initContentItemPicker(itemPickerElem: Element) {
const itemPickerId = itemPickerElem.getAttribute("id");
const itemType = getContentType(itemPickerElem);
const baseUrl = itemPickerElem.getAttribute("data-baseUrl");
const contentSection = itemPickerElem.getAttribute("data-contentsection");
console.log(`itemPickerId = ${itemPickerId}`);
if (!baseUrl) {
console.error("No baseUrl provided.");
return;
}
if (!contentSection) {
console.error("No content section provided");
return;
}
const fetchUrl = buildFetchUrl(baseUrl, contentSection, itemType);
try {
const response = await fetch(fetchUrl);
if (response.ok) {
const items = (await response.json()) as [];
const rowTemplate = itemPickerElem.querySelector(
`#${itemPickerId}-row`
) as HTMLTemplateElement;
const tbody = itemPickerElem.querySelector("tbody");
for (const item of items) {
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 = item["name"];
}
if (colType) {
colType.textContent = item["type"];
}
selectButton?.setAttribute("data-itemuuid", item["uuid"]);
selectButton?.addEventListener("click", event =>
selectItem(event, itemPickerElem)
);
tbody?.appendChild(row);
}
} else {
console.error(
`Error. Status: ${response.status}. Status Text: ${response.statusText}`
);
}
} catch (error) {
console.error(error);
}
}
function buildFetchUrl(
baseUrl: string,
contentSection: string,
itemType: string
) {
if (itemType && itemType !== "org.librecms.contentsection.ContentItem") {
return `${baseUrl}/content-sections/${contentSection}/items?type=${itemType}&version=draft`;
} else {
return `${baseUrl}/content-sections/${contentSection}/items?version=draft`;
}
}
function getContentType(itemPickerElem: Element):string {
if (itemPickerElem.hasAttribute("data-contentitem-type")) {
const result = itemPickerElem.getAttribute("data-contentitem-type");
if (result) {
return result;
} else {
return "org.librecms.contentsection.ContentItem";
}
} else {
return "org.librecms.contentsection.ContentItem";
}
}
async function selectItem(event: Event, itemPickerElem: Element) {
const selectButton = event.currentTarget as Element;
const itemUuid = selectButton.getAttribute("data-itemuuid");
if (!itemUuid) {
console.error("itemUuid is null");
return;
}
const itemPickerParam = itemPickerElem.querySelector(
".contentitempicker-param"
) as HTMLInputElement;
if (!itemPickerParam) {
console.error("contentItemPickerParam is null");
return;
}
itemPickerParam.value = `UUID-${itemUuid}`;
const form = itemPickerElem.querySelector("form") as HTMLFormElement;
form.submit();
}