Forms for adding and editing internal links (not finished yet)

pull/10/head
Jens Pelzetter 2021-07-20 21:09:29 +02:00
parent 69511fe5e7
commit 33c72859ee
12 changed files with 1281 additions and 1006 deletions

View File

@ -1,170 +0,0 @@
/*
* Copyright (C) 2021 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.librecms.ui.contentsections.documents.relatedinfo;
import org.libreccm.l10n.GlobalizationHelper;
import org.libreccm.l10n.LocalizedString;
import org.librecms.assets.RelatedLink;
import org.librecms.contentsection.AttachmentList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
/**
* Model proving the properties of an internal {@link RelatedLink} for the
* internal link edit view of the {@link RelatedInfoStep}.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Named("CmsInternalLinkDetailsModel")
public class InternalLinkDetailsModel {
/**
* Used to retrieve values from {@link LocalizedString}s.
*/
@Inject
private GlobalizationHelper globalizationHelper;
/**
* The identifier of the {@link AttachmentList} of the link.
*/
private String listIdentifier;
/**
* The UUID of the link.
*/
private String uuid;
/**
* The label of the link.
*/
private String label;
/**
* The localized titles of the link.
*/
private Map<String, String> title;
/**
* The locales for which no title has been specified.
*/
private List<String> unusedTitleLocales;
/**
* The UUID of the target item of the link.
*/
private String targetItemUuid;
/**
* The name of the target item of the link
*/
private String targetItemName;
/**
* The title of the target item of the link. This value is determined from
* the title of the target item using {@link GlobalizationHelper#getValueFromLocalizedString(org.libreccm.l10n.LocalizedString)
* }.
*/
private String targetItemTitle;
public String getListIdentifier() {
return listIdentifier;
}
protected void setListIdentifier(final String listIdentifier) {
this.listIdentifier = listIdentifier;
}
public String getUuid() {
return uuid;
}
public String getLabel() {
return label;
}
public Map<String, String> getTitle() {
return Collections.unmodifiableMap(title);
}
public List<String> getUnusedTitleLocales() {
return Collections.unmodifiableList(unusedTitleLocales);
}
public String getTargetItemUuid() {
return targetItemUuid;
}
public String getTargetItemName() {
return targetItemName;
}
public String getTargetItemTitle() {
return targetItemTitle;
}
/**
* 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()
);
}
}

View File

@ -0,0 +1,219 @@
/*
* Copyright (C) 2021 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.librecms.ui.contentsections.documents.relatedinfo;
import org.libreccm.l10n.GlobalizationHelper;
import org.librecms.assets.Bookmark;
import org.librecms.assets.RelatedLink;
import org.librecms.contentsection.AttachmentList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
/**
* Model proving the properties of an internal {@link RelatedLink} for the
* internal link edit view of the {@link RelatedInfoStep}.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Named("CmsLinkDetailsModel")
public class LinkDetailsModel {
/**
* The identifier of the {@link AttachmentList} of the link.
*/
private String listIdentifier;
/**
* The UUID of the link.
*/
private String uuid;
/**
* The localized titles of the link.
*/
private Map<String, String> title;
/**
* The locales for which no title has been specified.
*/
private List<String> unusedTitleLocales;
private String linkType;
/**
* The UUID of the target item of the link.
*/
private String targetItemUuid;
/**
* The name of the target item of the link
*/
private String targetItemName;
private String bookmarkUuid;
private String bookmarkName;
private String sectionName;
/**
* The title of the target item of the link. This value is determined from
* the title of the target item using {@link GlobalizationHelper#getValueFromLocalizedString(org.libreccm.l10n.LocalizedString)
* }.
*/
private String targetItemTitle;
public String getListIdentifier() {
return listIdentifier;
}
public String getLinkType() {
return linkType;
}
public void setLinkType(final String linkType) {
this.linkType = linkType;
}
public String getBookmarkUuid() {
return bookmarkUuid;
}
public void setBookmarkUuid(final String bookmarkUuid) {
this.bookmarkUuid = bookmarkUuid;
}
public String getBookmarkName() {
return bookmarkName;
}
public void setBookmarkName(final String bookmarkName) {
this.bookmarkName = bookmarkName;
}
public String getSectionName() {
return sectionName;
}
public void setSectionName(String sectionName) {
this.sectionName = sectionName;
}
protected void setListIdentifier(final String listIdentifier) {
this.listIdentifier = listIdentifier;
}
public String getUuid() {
return uuid;
}
public void setUuid(final String uuid) {
this.uuid = uuid;
}
public Map<String, String> getTitle() {
return Collections.unmodifiableMap(title);
}
public void setTitle(final Map<String, String> title) {
this.title = new HashMap<>(title);
}
public List<String> getUnusedTitleLocales() {
return Collections.unmodifiableList(unusedTitleLocales);
}
public void setUnusedTitleLocales(final List<String> unusedTitleLocales) {
this.unusedTitleLocales = new ArrayList<>(unusedTitleLocales);
}
public String getTargetItemUuid() {
return targetItemUuid;
}
public void setTargetItemUuid(final String targetItemUuid) {
this.targetItemUuid = targetItemUuid;
}
public String getTargetItemName() {
return targetItemName;
}
public void setTargetItemName(final String targetItemName) {
this.targetItemName = targetItemName;
}
public String getTargetItemTitle() {
return targetItemTitle;
}
public String getBookmarkType() {
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 void setTargetItemTitle(String targetItemTitle) {
this.targetItemTitle = targetItemTitle;
}
}

View File

@ -20,19 +20,64 @@ package org.librecms.ui.contentsections.documents.relatedinfo;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.libreccm.l10n.GlobalizationHelper;
import org.libreccm.security.PermissionChecker;
import org.librecms.contentsection.Asset;
import org.librecms.contentsection.AssetFolderEntry;
import org.librecms.contentsection.AssetManager;
import org.librecms.contentsection.AssetRepository;
import org.librecms.contentsection.AttachmentListRepository; import org.librecms.contentsection.AttachmentListRepository;
import org.librecms.contentsection.ContentItem;
import org.librecms.contentsection.ContentItemL10NManager;
import org.librecms.contentsection.ContentItemManager;
import org.librecms.contentsection.ContentItemRepository; import org.librecms.contentsection.ContentItemRepository;
import org.librecms.contentsection.ContentSection;
import org.librecms.contentsection.ContentType;
import org.librecms.contentsection.ContentTypeRepository;
import org.librecms.contentsection.DocumentFolderEntry;
import org.librecms.contentsection.Folder;
import org.librecms.contentsection.FolderManager;
import org.librecms.contentsection.FolderRepository;
import org.librecms.contentsection.FolderType;
import org.librecms.contentsection.ItemAttachmentManager; import org.librecms.contentsection.ItemAttachmentManager;
import org.librecms.contentsection.privileges.ItemPrivileges;
import org.librecms.ui.contentsections.AssetFolderRowModel;
import org.librecms.ui.contentsections.AssetFolderTree;
import org.librecms.ui.contentsections.AssetFolderTreeNode;
import org.librecms.ui.contentsections.AssetPermissionsModel;
import org.librecms.ui.contentsections.AssetPermissionsModelProvider;
import org.librecms.ui.contentsections.ContentSectionsUi; import org.librecms.ui.contentsections.ContentSectionsUi;
import org.librecms.ui.contentsections.DocumentFolderRowModel;
import org.librecms.ui.contentsections.DocumentFolderTree;
import org.librecms.ui.contentsections.DocumentFolderTreeNode;
import org.librecms.ui.contentsections.DocumentPermissions;
import org.librecms.ui.contentsections.documents.MvcAuthoringSteps; import org.librecms.ui.contentsections.documents.MvcAuthoringSteps;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.TreeSet;
import java.util.stream.Collectors;
import javax.enterprise.context.RequestScoped; import javax.enterprise.context.RequestScoped;
import javax.inject.Inject; import javax.inject.Inject;
import javax.transaction.Transactional; import javax.transaction.Transactional;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.ForbiddenException;
import javax.ws.rs.GET;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.POST; import javax.ws.rs.POST;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.PathParam; import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
@ -48,15 +93,66 @@ public class RelatedInfoStepService {
RelatedInfoStepService.class RelatedInfoStepService.class
); );
/**
* The asset folder tree of the current content section.
*/
@Inject
private AssetFolderTree assetFolderTree;
/**
* Used to build the {@link AssetPermissionsModel}.
*/
@Inject
private AssetPermissionsModelProvider assetPermissions;
@Inject
private AssetManager assetManager;
@Inject
private AssetRepository assetRepo;
@Inject @Inject
private AttachmentListRepository attachmentListRepo; private AttachmentListRepository attachmentListRepo;
@Inject
private ContentItemManager itemManager;
@Inject
private ContentItemL10NManager itemL10NManager;
@Inject
private ContentTypeRepository contentTypeRepo;
@Inject
private DocumentFolderTree documentFolderTree;
/**
* Used to check permissions of the current content item.
*/
@Inject
private DocumentPermissions documentPermissions;
@Inject @Inject
private ItemAttachmentManager attachmentManager; private ItemAttachmentManager attachmentManager;
@Inject @Inject
private ContentItemRepository itemRepo; private ContentItemRepository itemRepo;
@Inject
private FolderManager folderManager;
/**
* Used to retrieve folders.
*/
@Inject
private FolderRepository folderRepo;
@Inject
private GlobalizationHelper globalizationHelper;
@Inject
private PermissionChecker permissionChecker;
@Inject @Inject
private ContentSectionsUi sectionsUi; private ContentSectionsUi sectionsUi;
@ -71,6 +167,7 @@ public class RelatedInfoStepService {
final String documentPath, final String documentPath,
final RelatedInfoStepAttachmentOrder order final RelatedInfoStepAttachmentOrder order
) { ) {
// ToDo
LOGGER.info("order = {}", order); LOGGER.info("order = {}", order);
return Response.ok().build(); return Response.ok().build();
@ -101,4 +198,655 @@ public class RelatedInfoStepService {
// //final Map<String, Integer> attachmentListIndexes = // //final Map<String, Integer> attachmentListIndexes =
} }
/**
* Gets the asset folder tree of the current content section as JSON data.
*
* @param sectionIdentifier
* @param documentPath
*
* @return The assets folder tree of the current content section as JSON
* data.
*/
@GET
@Path("/asset-folders")
@Produces(MediaType.APPLICATION_JSON)
@Transactional(Transactional.TxType.REQUIRED)
public List<AssetFolderTreeNode> getAssetFolderTree(
@PathParam(MvcAuthoringSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME)
final String documentPath
) {
final ContentSection section = retrieveContentSection(
sectionIdentifier
);
if (permissionChecker.isPermitted(
ItemPrivileges.EDIT, retrieveDocument(section, documentPath)
)) {
return assetFolderTree.buildFolderTree(
section, section.getRootAssetsFolder()
);
} else {
throw new ForbiddenException();
}
}
/**
* Gets the assets in the folder as JSON data.
*
* @param folderPath The path of the folder.
* @param firstResult The index of the firset result to show.
* @param maxResults The maximum number of results to show.
* @param filterTerm An optional filter term for filtering the assets
* in the folder by their name.
* @param documentPath
* @param sectionIdentifier
*
* @return A list of the assets in the folder as JSON data.
*/
@GET
@Path("/asset-folders/{folderPath}/assets")
@Produces(MediaType.APPLICATION_JSON)
@Transactional(Transactional.TxType.REQUIRED)
public List<AssetFolderRowModel> getAssetsInFolder(
@PathParam(MvcAuthoringSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME)
final String documentPath,
@PathParam("folderPath")
final String folderPath,
@QueryParam("firstResult")
@DefaultValue("0")
final int firstResult,
@QueryParam("maxResults")
@DefaultValue("20")
final int maxResults,
@QueryParam("filterTerm")
@DefaultValue("")
final String filterTerm
) {
final ContentSection section = retrieveContentSection(sectionIdentifier);
if (permissionChecker.isPermitted(
ItemPrivileges.EDIT, retrieveDocument(section, documentPath)
)) {
final Folder folder;
if (folderPath.isEmpty()) {
folder = section.getRootAssetsFolder();
} else {
final Optional<Folder> folderResult = folderRepo.findByPath(
section, folderPath, FolderType.ASSETS_FOLDER
);
if (folderResult.isPresent()) {
folder = folderResult.get();
} else {
return Collections.emptyList();
}
}
return folderRepo
.getAssetFolderEntries(
folder, firstResult, maxResults, filterTerm
)
.stream()
.map(entry -> buildAssetFolderRowModel(section, entry))
.collect(Collectors.toList());
} else {
throw new ForbiddenException();
}
}
/**
* Show all assets of a content section filtered by their name.
*
* @param sectionIdentifier
* @param documentPath
* @param firstResult The index of the first result to show.
* @param maxResults The maximum number of results to show.
* @param searchTerm An optional search term applied to the names of
* the assets.
*
* @return A list of matching assets as JSON.
*/
@GET
@Path("/search-assets")
@Produces(MediaType.APPLICATION_JSON)
@Transactional(Transactional.TxType.REQUIRED)
public List<AssetFolderRowModel> findAssets(
@PathParam(MvcAuthoringSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME)
final String documentPath,
@QueryParam("firstResult")
@DefaultValue("0")
final int firstResult,
@QueryParam("maxResults")
@DefaultValue("20")
final int maxResults,
@QueryParam("searchTerm")
@DefaultValue("")
final String searchTerm
) {
final ContentSection section = retrieveContentSection(sectionIdentifier);
if (permissionChecker.isPermitted(
ItemPrivileges.EDIT, retrieveDocument(section, documentPath)
)) {
return assetRepo.findByTitleAndContentSection(searchTerm, section)
.stream()
.map(asset -> buildAssetFolderRowModel(section, asset))
.collect(Collectors.toList());
} else {
throw new ForbiddenException();
}
}
private ContentSection retrieveContentSection(
final String sectionIdentifier
) {
return sectionsUi.findContentSection(
sectionIdentifier
).orElseThrow(
() -> new NotFoundException(
String.format(
"No content section identified by %s available.",
sectionIdentifier
)
)
);
}
/**
* Gets the document folder tree of the current content section as JSON
* data.
*
* @param sectionIdentifier
* @param documentPath
*
* @return The document folder tree of the current content section as JSON
* data.
*/
@GET
@Path("/document-folders")
@Produces(MediaType.APPLICATION_JSON)
@Transactional(Transactional.TxType.REQUIRED)
public List<DocumentFolderTreeNode> getDocumentFolderTree(
@PathParam(MvcAuthoringSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME)
final String documentPath
) {
final ContentSection section = retrieveContentSection(sectionIdentifier);
final ContentItem document = retrieveDocument(section, documentPath);
if (permissionChecker.isPermitted(
ItemPrivileges.EDIT, document
)) {
return documentFolderTree.buildFolderTree(
section, section.getRootDocumentsFolder()
);
} else {
throw new ForbiddenException();
}
}
/**
* Gets the documents in the folder as JSON data.
*
* @param sectionIdentifier
* @param documentPath
* @param folderPath The path of the folder.
* @param firstResult The index of the firset result to show.
* @param maxResults The maximum number of results to show.
* @param filterTerm An optional filter term for filtering the
* documents in the folder by their name.
*
* @return A list of the documents in the folder as JSON data.
*/
@GET
@Path("/document-folders/{folderPath}/documents")
@Produces(MediaType.APPLICATION_JSON)
@Transactional(Transactional.TxType.REQUIRED)
public List<DocumentFolderRowModel> getDocumentsInFolder(
@PathParam(MvcAuthoringSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME)
final String documentPath,
@PathParam("folderPath")
final String folderPath,
@QueryParam("firstResult")
@DefaultValue("0")
final int firstResult,
@QueryParam("maxResults")
@DefaultValue("20")
final int maxResults,
@QueryParam("filterTerm")
@DefaultValue("")
final String filterTerm
) {
final ContentSection section = retrieveContentSection(sectionIdentifier);
final ContentItem document = retrieveDocument(section, documentPath);
if (permissionChecker.isPermitted(
ItemPrivileges.EDIT, document
)) {
final Folder folder;
if (folderPath.isEmpty()) {
folder = section.getRootDocumentsFolder();
} else {
final Optional<Folder> folderResult = folderRepo.findByPath(
section, folderPath, FolderType.ASSETS_FOLDER
);
if (folderResult.isPresent()) {
folder = folderResult.get();
} else {
return Collections.emptyList();
}
}
return folderRepo
.getDocumentFolderEntries(
folder,
firstResult,
maxResults,
filterTerm
)
.stream()
.map(entry -> buildDocumentFolderRowModel(section, entry))
.collect(Collectors.toList());
} else {
throw new ForbiddenException();
}
}
/**
* Show all documents of a content section filtered by their name.
*
* @param sectionIdentifier
* @param documentPath
* @param firstResult The index of the first result to show.
* @param maxResults The maximum number of results to show.
* @param searchTerm An optional search term applied to the names of
* the docuemnts.
*
* @return A list of matching documents/content items as JSON.
*/
@GET
@Path("/search-documents")
@Produces(MediaType.APPLICATION_JSON)
@Transactional(Transactional.TxType.REQUIRED)
public List<DocumentFolderRowModel> findDocuments(
@PathParam(MvcAuthoringSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME)
final String documentPath,
@QueryParam("firstResult")
@DefaultValue("0")
final int firstResult,
@QueryParam("maxResults")
@DefaultValue("20")
final int maxResults,
@QueryParam("searchTerm")
@DefaultValue("")
final String searchTerm
) {
final ContentSection section = retrieveContentSection(sectionIdentifier);
final ContentItem document = retrieveDocument(section, documentPath);
if (permissionChecker.isPermitted(
ItemPrivileges.EDIT, document
)) {
return itemRepo.findByNameAndContentSection(searchTerm, section)
.stream()
.map(asset -> buildDocumentFolderRowModel(section, asset))
.collect(Collectors.toList());
} else {
throw new ForbiddenException();
}
}
private ContentItem retrieveDocument(
final ContentSection section, final String documentPath
) {
return itemRepo
.findByPath(section, documentPath)
.orElseThrow(
() -> new NotFoundException(
String.format(
"No document with path %s found in content section %s.",
documentPath,
section.getLabel()
)
)
);
}
/**
* Build the model for a row in the asset folder listing.
*
* @param section The content section.
* @param entry The {@link AssetFolderEntry} from which the model is
* build.
*
* @return The {@link AssetFolderRowModel} for the provided {@code entry}.
*/
private AssetFolderRowModel buildAssetFolderRowModel(
final ContentSection section, final AssetFolderEntry entry
) {
Objects.requireNonNull(section);
Objects.requireNonNull(entry);
final AssetFolderRowModel row = new AssetFolderRowModel();
if (entry.isFolder()) {
final Folder folder = folderRepo
.findById(entry.getEntryId())
.get();
row.setDeletable(false);
row.setFolder(true);
row.setFolderPath(
folderManager
.getFolderPath(folder)
.substring(
folderManager
.getFolderPath(section.getRootAssetsFolder())
.length()
)
);
row.setName(entry.getDisplayName());
row.setTitle(
globalizationHelper.getValueFromLocalizedString(
folder.getTitle()
)
);
row.setType(
globalizationHelper.getLocalizedTextsUtil(
"org.librecms.CmsAdminMessages"
).getText("contentsection.assetfolder.types.folder")
);
row.setPermissions(
assetPermissions.buildAssetPermissionsModel(folder)
);
} else {
final Asset asset = assetRepo
.findById(entry.getEntryId())
.get();
row.setDeletable(!assetManager.isAssetInUse(asset));
row.setFolder(false);
row.setName(entry.getDisplayName());
row.setNoneCmsObject(false);
row.setTitle(
globalizationHelper.getValueFromLocalizedString(
asset.getTitle()
)
);
row.setType(asset.getClass().getName());
row.setPermissions(
assetPermissions.buildAssetPermissionsModel(asset)
);
}
return row;
}
/**
* Build the model for a row in the asset folder listing.
*
* @param section The content section.
* @param asset The {@link Asset} from which the model is build.
*
* @return The {@link AssetFolderRowModel} for the provided {@code asset}.
*/
private AssetFolderRowModel buildAssetFolderRowModel(
final ContentSection section, final Asset asset
) {
Objects.requireNonNull(section);
Objects.requireNonNull(asset);
final AssetFolderRowModel row = new AssetFolderRowModel();
row.setDeletable(false);
row.setFolder(false);
row.setName(asset.getDisplayName());
row.setNoneCmsObject(false);
row.setTitle(
globalizationHelper.getValueFromLocalizedString(
asset.getTitle()
)
);
row.setType(asset.getClass().getName());
row.setPermissions(
assetPermissions.buildAssetPermissionsModel(asset)
);
return row;
}
/**
* Build the model for a row in the document folder listing.
*
* @param section The content section.
* @param entry The {@link DocumentFolderEntry} from which the model is
* build.
*
* @return The {@link DocumentFolderRowModel} for the provided
* {@code entry}.
*/
private DocumentFolderRowModel buildDocumentFolderRowModel(
final ContentSection section, final DocumentFolderEntry entry
) {
Objects.requireNonNull(section);
Objects.requireNonNull(entry);
final DocumentFolderRowModel row = new DocumentFolderRowModel();
if (entry.isFolder()) {
final Folder folder = folderRepo
.findById(entry.getEntryId())
.get();
row.setCreated("");
row.setDeletable(
folderManager
.folderIsDeletable(folder)
== FolderManager.FolderIsDeletable.YES
);
row.setFolder(true);
row.setFolderPath(
folderManager
.getFolderPath(folder)
.substring(
folderManager
.getFolderPath(section.getRootDocumentsFolder())
.length()
)
);
row.setLanguages(Collections.emptySortedSet());
row.setLastEditPublished(false);
row.setLastEdited("");
row.setName(entry.getDisplayName());
row.setTitle(
globalizationHelper.getValueFromLocalizedString(
folder.getTitle()
)
);
row.setType(
globalizationHelper.getLocalizedTextsUtil(
"org.librecms.CmsAdminMessages"
).getText("contentsection.documentfolder.types.folder")
);
row.setPermissions(
documentPermissions.buildDocumentPermissionsModel(folder)
);
} else {
final ContentItem contentItem = itemRepo
.findById(entry.getEntryId())
.get();
row.setCreated(
DateTimeFormatter.ISO_DATE.format(
LocalDate.ofInstant(
contentItem.getCreationDate().toInstant(),
ZoneId.systemDefault()
)
)
);
row.setDeletable(!itemManager.isLive(contentItem));
row.setFolder(false);
row.setFolderPath(itemManager.getItemPath(contentItem));
row.setLanguages(
new TreeSet<>(
itemL10NManager
.availableLanguages(contentItem)
.stream()
.map(Locale::toString)
.collect(Collectors.toSet())
)
);
if (itemManager.isLive(contentItem)) {
final LocalDate draftLastModified = LocalDate.ofInstant(
contentItem.getLastModified().toInstant(),
ZoneId.systemDefault()
);
final LocalDate liveLastModified = LocalDate.ofInstant(
itemManager
.getLiveVersion(contentItem, contentItem.getClass())
.map(ContentItem::getLastModified)
.map(Date::toInstant)
.get(),
ZoneId.systemDefault()
);
row.setLastEditPublished(
liveLastModified.isBefore(draftLastModified)
);
} else {
row.setLastEditPublished(false);
}
row.setLastEdited(
DateTimeFormatter.ISO_DATE.format(
LocalDate.ofInstant(
contentItem.getLastModified().toInstant(),
ZoneId.systemDefault()
)
)
);
row.setName(entry.getDisplayName());
row.setNoneCmsObject(false);
row.setTitle(
globalizationHelper.getValueFromLocalizedString(
contentItem.getTitle()
)
);
row.setType(
contentTypeRepo
.findByContentSectionAndClass(
section, contentItem.getClass()
)
.map(ContentType::getLabel)
.map(
label -> globalizationHelper
.getValueFromLocalizedString(
label
)
).orElse("?")
);
row.setPermissions(
documentPermissions.buildDocumentPermissionsModel(
contentItem
)
);
}
return row;
}
/**
* Build the model for a row in the document folder listing.
*
* @param section The content section.
* @param contentItem The {@link Contentitem} from which the model is build.
*
* @return The {@link DocumentFolderRowModel} for the provided
* {@code contentItem}.
*/
private DocumentFolderRowModel buildDocumentFolderRowModel(
final ContentSection section, final ContentItem contentItem
) {
Objects.requireNonNull(section);
Objects.requireNonNull(contentItem);
final DocumentFolderRowModel row = new DocumentFolderRowModel();
row.setCreated(
DateTimeFormatter.ISO_DATE.format(
LocalDate.ofInstant(
contentItem.getCreationDate().toInstant(),
ZoneId.systemDefault()
)
)
);
row.setDeletable(!itemManager.isLive(contentItem));
row.setFolder(false);
row.setFolderPath(itemManager.getItemPath(contentItem));
row.setLanguages(
new TreeSet<>(
itemL10NManager
.availableLanguages(contentItem)
.stream()
.map(Locale::toString)
.collect(Collectors.toSet())
)
);
if (itemManager.isLive(contentItem)) {
final LocalDate draftLastModified = LocalDate.ofInstant(
contentItem.getLastModified().toInstant(),
ZoneId.systemDefault()
);
final LocalDate liveLastModified = LocalDate.ofInstant(
itemManager
.getLiveVersion(contentItem, contentItem.getClass())
.map(ContentItem::getLastModified)
.map(Date::toInstant)
.get(),
ZoneId.systemDefault()
);
row.setLastEditPublished(
liveLastModified.isBefore(draftLastModified)
);
} else {
row.setLastEditPublished(false);
}
row.setLastEdited(
DateTimeFormatter.ISO_DATE.format(
LocalDate.ofInstant(
contentItem.getLastModified().toInstant(),
ZoneId.systemDefault()
)
)
);
row.setName(contentItem.getDisplayName());
row.setNoneCmsObject(false);
row.setTitle(
globalizationHelper.getValueFromLocalizedString(
contentItem.getTitle()
)
);
row.setType(
contentTypeRepo
.findByContentSectionAndClass(
section, contentItem.getClass()
)
.map(ContentType::getLabel)
.map(
label -> globalizationHelper
.getValueFromLocalizedString(
label
)
).orElse("?")
);
row.setPermissions(
documentPermissions.buildDocumentPermissionsModel(
contentItem
)
);
return row;
}
} }

View File

@ -6,16 +6,6 @@
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"> xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<ui:composition template="/WEB-INF/views/org/librecms/ui/contentsection/documents/authoringstep.xhtml"> <ui:composition template="/WEB-INF/views/org/librecms/ui/contentsection/documents/authoringstep.xhtml">
<!-- <ui:param name="activePage" value="document" />
<ui:param name="title" value="#{CmsDefaultStepsMessageBundle['contentsection.documents.relatedinfo.attachmentlist.details.title']}" />
<ui:define name="breadcrumb">
<ui:include src="document-breadcrumbs.xhtml" />
<li aria-current="page" class="breadcrumb-item">
#{CmsDefaultStepsMessageBundle['contentsection.documents.relatedinfo.attachmentlist.details.title']}
</li>
</ui:define>-->
<ui:define name="authoringStep"> <ui:define name="authoringStep">
<div class="d-flex mb-3"> <div class="d-flex mb-3">
<a class="btn btn-secondary mr-3" <a class="btn btn-secondary mr-3"

View File

@ -0,0 +1,93 @@
<!DOCTYPE html [<!ENTITY times '&#215;'>]>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:bootstrap="http://xmlns.jcp.org/jsf/composite/components/bootstrap"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
xmlns:libreccm="http://xmlns.jcp.org/jsf/composite/components/libreccm"
xmlns:librecms="http://xmlns.jcp.org/jsf/composite/components/librecms"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<ui:composition template="/WEB-INF/views/org/librecms/ui/contentsection/documents/authoringstep.xhtml">
<ui:define name="authoringStep">
<div class="d-flex mb-3">
<a class="btn btn-secondary mr-3"
href="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@relatedinfo">
<bootstrap:svgIcon icon="arrow-left-circle" />
<span>#{CmsDefaultStepsMessageBundle['relatedinfo.links.create.back']}</span>
</a>
<h3>
#{CmsDefaultStepsMessageBundle.getMessage('relatedinfo.links.create.title', [CmsAttachmentListDetailsModel.name])}
</h3>
</div>
<c:forEach items="#{messages.entrySet()}"
var="message">
<div class="alert alert-#{message.key}" role="alert">
#{message.value}
</div>
</c:forEach>
<form action="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@relatedinfo/attachmentlists/#{CmsAttachmentListDetailsModel.name}/links/@create"
method="post">
<bootstrap:formGroupText
help="#{CmsAssetsStepsDefaultMessagesBundle['createform.name.help']}"
inputId="name"
label="#{CmsAssetsStepsDefaultMessagesBundle['createform.name.label']}"
name="name"
pattern="^([a-zA-Z0-9_-]*)$"
required="true"
value="#{CmsBookmarkCreateStep.name}"
/>
<bootstrap:formGroupSelect
help="#{CmsAssetsStepsDefaultMessagesBundle['createform.initial_locale.help']}"
inputId="locale"
label="#{CmsAssetsStepsDefaultMessagesBundle['createform.initial_locale.label']}"
name="locale"
options="#{CmsBookmarkCreateStep.availableLocales}"
required="true"
selectedOptions="#{[CmsBookmarkCreateStep.initialLocale]}"
/>
<bootstrap:formGroupText
help="#{CmsAssetsStepsDefaultMessagesBundle['createform.title.help']}"
inputId="title"
label="#{CmsAssetsStepsDefaultMessagesBundle['createform.title.label']}"
name="title"
required="true"
/>
<!-- <fieldset aria-describedby="linktype-help"
class="form-group">
<div class="row">
<legend class="col-form-label col-sm-2 pt-0">#{CmsDefaultStepsMessageBundle['relatedinfo.links.type.label']}</legend>
<div class="col-sm-10">
<div class="from-check">
<input
checked="#{"internal".equals(selectedType)}"
class="form-check-input"
id="linktype-internal"
name="linkType"
type="radio"
/>
<input
checked="#{"external".equals(selectedType)}"
class="form-check-input"
id="linktype-internal"
name="linkType"
type="external"
/>
</div>
</div>
<small class="form-text text-muted"
id="linktype-help">
#{CmsDefaultStepsMessageBundle['relatedinfo.links.type.help']}
</small>
</div>
</fieldset>-->
</form>
</ui:define>
</ui:composition>
</html>

View File

@ -1,39 +0,0 @@
<!DOCTYPE html [<!ENTITY times '&#215;'>]>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:bootstrap="http://xmlns.jcp.org/jsf/composite/components/bootstrap"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
xmlns:libreccm="http://xmlns.jcp.org/jsf/composite/components/libreccm"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<ui:composition template="/WEB-INF/views/org/librecms/ui/contentsection/documents/document.xhtml">
<ui:param name="activePage" value="document" />
<ui:param name="title" value="#{CmsDefaultStepsMessageBundle['contentsection.documents.relatedinfo.internallink.details.title']}" />
<ui:define name="breadcrumb">
<ui:include src="document-breadcrumbs.xhtml" />
<li aria-current="page" class="breadcrumb-item">
#{CmsDefaultStepsMessageBundle['contentsection.documents.relatedinfo.internallink.details.title']}
</li>
</ui:define>
<ui:define name="authoringStep">
<h3>
#{CmsDefaultStepsMessageBundle.getMessage('relatedinfo.internallink.details.title', [CmsInternalLinkDetailsModel.label])}
</h3>
<libreccm:localizedStringEditor addMethod="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@authoringSteps/relatedinfo/attachmentlists/#{list.name}/internal-links/#{CmsInternalLinkDetailsModel.uuid}/title/@add"
editMethod="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@authoringSteps/relatedinfo/attachmentlists/#{list.name}//internal-links/#{CmsInternalLinkDetailsModel.uuid}/title/@edit"
editorId="list-title-editor"
hasUnusedLocales="#{!CmsAttachmentListDetailsModel.unusedDescriptionLocales.isEmpty()}"
objectIdentifier="#{CmsAttachmentListDetailsModel.name}"
removeMethod="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@authoringSteps/relatedinfo/attachmentlists/#{list.name}//internal-links/#{CmsInternalLinkDetailsModel.uuid}/title/@remove"
title="CmsDefaultStepsMessageBundle['relatedinfo.internallink.details.title_editor.title']}"
unusedLocales="#{CmsAttachmentListDetailsModel.unusedDescriptionLocales}"
values="#{CmsAttachmentListDetailsModel.titles}"
/>
</ui:define>
</ui:composition>
</html>

View File

@ -0,0 +1,94 @@
<!DOCTYPE html [<!ENTITY times '&#215;'>]>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:bootstrap="http://xmlns.jcp.org/jsf/composite/components/bootstrap"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
xmlns:libreccm="http://xmlns.jcp.org/jsf/composite/components/libreccm"
xmlns:librecms="http://xmlns.jcp.org/jsf/composite/components/librecms"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<ui:composition template="/WEB-INF/views/org/librecms/ui/contentsection/documents/authoringstep.xhtml">
<ui:param name="activePage" value="document" />
<ui:param name="title" value="#{CmsDefaultStepsMessageBundle['contentsection.documents.relatedinfo.internallink.details.title']}" />
<ui:define name="authoringStep">
<h2>
#{CmsDefaultStepsMessageBundle['relatedinfo.link.details.title']}
</h2>
<libreccm:localizedStringEditor addMethod="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@authoringSteps/relatedinfo/attachmentlists/#{list.name}/internal-links/#{CmsInternalLinkDetailsModel.uuid}/title/@add"
editMethod="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@authoringSteps/relatedinfo/attachmentlists/#{list.name}//internal-links/#{CmsInternalLinkDetailsModel.uuid}/title/@edit"
editorId="list-title-editor"
hasUnusedLocales="#{!CmsAttachmentListDetailsModel.unusedDescriptionLocales.isEmpty()}"
objectIdentifier="#{CmsAttachmentListDetailsModel.name}"
removeMethod="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@authoringSteps/relatedinfo/attachmentlists/#{list.name}//internal-links/#{CmsInternalLinkDetailsModel.uuid}/title/@remove"
title="CmsDefaultStepsMessageBundle['relatedinfo.internallink.details.title_editor.title']}"
unusedLocales="#{CmsAttachmentListDetailsModel.unusedDescriptionLocales}"
values="#{CmsAttachmentListDetailsModel.titles}"
/>
<form>
<fieldset aria-describedby="linktype-help"
class="form-group">
<div class="row">
<legend class="col-form-label col-sm-2 pt-0">#{CmsDefaultStepsMessageBundle['relatedinfo.links.type.label']}</legend>
<div class="col-sm-10">
<div class="from-check">
<input
checked="#{"internal".equals(selectedType)}"
class="form-check-input link-type-select"
id="linktype-internal"
name="linkType"
type="radio"
value="internal"
/>
<label for="linktype-internal">#{CmsDefaultStepsMessageBundle['relatedinfo.links.type.internal']}</label>
<input
checked="#{"external".equals(selectedType)}"
class="form-check-input link-type-select"
id="linktype-internal"
name="linkType"
type="external"
value="external"
/>
<label for="linktype-internal">#{CmsDefaultStepsMessageBundle['relatedinfo.links.type.external']}</label>
</div>
</div>
<small class="form-text text-muted"
id="linktype-help">
#{CmsDefaultStepsMessageBundle['relatedinfo.links.type.help']}
</small>
</div>
</fieldset>
</form>
<h3>#{CmsDefaultStepsMessageBundle['relatedinfo.link.details.title.target']}</h3>
<div class="d-none relatedlink-target"
id="relatedlink-target-internal">
<p>ToDo: Content Item Picker</p>
</div>
<div class="d-none relatedlink-target"
id="relatedlink-target-external">
<librecms:assetPickerButton
assetPickerId="bookmark-picker"
buttonText="#{CmsDefaultStepsMessageBundle['relatedinfo.link.details.title.target.select_bookmark']}"
/>
<librecms:assetPicker
actionUrl="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@relatedinfo/attachmentlists/#{CmsAttachmentListDetailsModel.name}/links/#{CmsLinkDetailsModel.uuid}/details/@set-target-bookmark"
assetType="#{CmsLinkDetailsModel.bookmarkType}"
assetPickerId="bookmark-picker"
baseUrl="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@relatedinfo-service"
contentSection="#{CmsLinkDetailsModel.sectionName}"
formParamName="bookmarkIdentifier"
/>
</div>
</ui:define>
</ui:composition>
</html>

View File

@ -427,13 +427,12 @@
<bootstrap:svgIcon icon="plus-circle" /> <bootstrap:svgIcon icon="plus-circle" />
<span>#{CmsDefaultStepsMessageBundle['relatedinfo.attachmentlists.attachment.add.label']}</span> <span>#{CmsDefaultStepsMessageBundle['relatedinfo.attachmentlists.attachment.add.label']}</span>
</button> </button>
<button class="btn btn-primary" <a class="btn btn-primary"
data-target="#attachmentlist-#{list.name}-add-internallink-dialog" href="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@relatedinfo/attachmentlists/#{list.name}/links/@create"
data-toggle="modal"
type="button"> type="button">
<bootstrap:svgIcon icon="link-45deg" /> <bootstrap:svgIcon icon="link-45deg" />
<span>#{CmsDefaultStepsMessageBundle['relatedinfo.attachmentlists.link.add.label']}</span> <span>#{CmsDefaultStepsMessageBundle['relatedinfo.attachmentlists.link.add.label']}</span>
</button> </a>
<button class="btn btn-danger" <button class="btn btn-danger"
type="button"> type="button">
<bootstrap:svgIcon icon="x-circle" /> <bootstrap:svgIcon icon="x-circle" />
@ -459,14 +458,15 @@
<bootstrap:svgIcon icon="info-circle" /> <bootstrap:svgIcon icon="info-circle" />
<span class="sr-only">#{CmsDefaultStepsMessageBundle['relatedinfo.attachments.info.button']}"</span> <span class="sr-only">#{CmsDefaultStepsMessageBundle['relatedinfo.attachments.info.button']}"</span>
</button> </button>
<button class="btn btn-primary" <a class="btn btn-primary"
href="#"
type="button"> type="button">
<bootstrap:svgIcon icon="pen" /> <bootstrap:svgIcon icon="pen" />
<span class="sr-only">#{CmsDefaultStepsMessageBundle['relatedinfo.attachments.edit.button']}"</span> <span class="sr-only">#{CmsDefaultStepsMessageBundle['relatedinfo.attachments.edit.button']}"</span>
</button> </a>
<button class="btn btn-danger" <button class="btn btn-danger"
type="button"> type="button">
<bootstrap:svgIcon icon="pen" /> <bootstrap:svgIcon icon="x-circle" />
<span class="sr-only">#{CmsDefaultStepsMessageBundle['relatedinfo.attachments.delete.button']}"</span> <span class="sr-only">#{CmsDefaultStepsMessageBundle['relatedinfo.attachments.delete.button']}"</span>
</button> </button>
</div> </div>

View File

@ -3,3 +3,5 @@ import "bootstrap";
import "./cms-assetpicker"; import "./cms-assetpicker";
import "./cms-attachment-lists"; import "./cms-attachment-lists";
import "./cms-related-link";

View File

@ -0,0 +1,22 @@
document.addEventListener("DOMContentLoaded", function(event) {
const linkTypes = document.querySelectorAll(".link-type-select");
for(let i = 0; i < linkTypes.length; i++) {
linkTypes[i].addEventListener("selected", function(event) {
const target = event.currentTarget as HTMLElement;
const value = target.getAttribute("value");
const selectedTypeElem = document.querySelector(
`#relatedlink-target-${value}`
);
if (selectedTypeElem) {
const types = document.querySelectorAll(".relatedlink-target");
for(let j = 0; j < types.length; j++) {
types[j].classList.add("d-none");
}
selectedTypeElem.classList.remove("d-none");
}
});
}
});