Content Section management for Admin application

ccm-docs
Jens Pelzetter 2020-11-28 20:26:04 +01:00
parent 9983db5c42
commit 1b4b6e571b
12 changed files with 500 additions and 4 deletions

View File

@ -232,6 +232,14 @@
<include>resources/</include>
</includes>
</overlay>
<overlay>
<groupId>org.librecms</groupId>
<artifactId>ccm-cms</artifactId>
<type>jar</type>
<includes>
<include>WEB-INF/</include>
</includes>
</overlay>
<overlay>
<groupId>org.librecms</groupId>
<artifactId>ccm-cms</artifactId>

View File

@ -71,6 +71,22 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.mvc</groupId>
<artifactId>javax.mvc-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.krazo</groupId>
<artifactId>krazo-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.krazo.ext</groupId>
<artifactId>krazo-freemarker</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>

View File

@ -0,0 +1,127 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.libreccm.ui.admin.contentsections;
import org.libreccm.l10n.GlobalizationHelper;
import org.librecms.CmsConstants;
import java.text.MessageFormat;
import java.util.AbstractMap;
import java.util.List;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Named("ContentSectionAdminMessages")
public class ContentSectionAdminMessages extends AbstractMap<String, String> {
/**
* Provides access to the locale negoiated by LibreCCM.
*/
@Inject
private GlobalizationHelper globalizationHelper;
/**
* The {@link ResourceBundle} to use.
*/
private ResourceBundle messages;
/**
* Loads the resource bundle.
*/
@PostConstruct
private void init() {
messages = ResourceBundle.getBundle(
CmsConstants.CONTENT_SECTION_DESC_BUNDLE,
globalizationHelper.getNegotiatedLocale()
);
}
/**
* Retrieves a message from the resource bundle.
*
* @param key The key of the message.
*
* @return The translated message or {@code ???message???} if the the key is
* not found in the resource bundle (message is replaced with the
* key).
*/
public String getMessage(final String key) {
if (messages.containsKey(key)) {
return messages.getString(key);
} else {
return String.format("???%s???", key);
}
}
/**
* Retrieves a message with placeholders.
*
* @param key The key of the message.
* @param parameters The parameters for the placeholders.
*
* @return The translated message or {@code ???message???} if the the key is
* not found in the resource bundle (message is replaced with the
* key).
*/
public String getMessage(
final String key, final List<Object> parameters
) {
return getMessage(key, parameters.toArray());
}
/**
* The translated message or {@code ???message???} if the the key is not
* found in the resource bundle (message is replaced with the key).
*
* @param key The key of the message.
* @param parameters The parameters for the placeholders.
*
* @return The translated message or {@code ???message???} if the the key is
* not found in the resource bundle (message is replaced with the
* key).
*/
public String getMessage(
final String key, final Object[] parameters
) {
if (messages.containsKey(key)) {
return MessageFormat.format(messages.getString(key), parameters);
} else {
return String.format("???%s???", key);
}
}
@Override
public String get(final Object key) {
return get((String) key);
}
public String get(final String key) {
return getMessage(key);
}
@Override
public Set<Entry<String, String>> entrySet() {
return messages
.keySet()
.stream()
.collect(
Collectors.toMap(key -> key, key -> messages.getString(key))
)
.entrySet();
}
}

View File

@ -0,0 +1,134 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.libreccm.ui.admin.contentsections;
import org.libreccm.core.CoreConstants;
import org.libreccm.security.AuthorizationRequired;
import org.libreccm.security.RequiresPrivilege;
import org.libreccm.ui.admin.applications.ApplicationController;
import org.librecms.contentsection.ContentSection;
import org.librecms.contentsection.ContentSectionManager;
import org.librecms.contentsection.ContentSectionRepository;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.mvc.Controller;
import javax.mvc.Models;
import javax.transaction.Transactional;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Controller
@Path("/applications/content-sections")
public class ContentSectionApplicationController
implements ApplicationController {
@Inject
private Models models;
@Inject
private ContentSectionManager sectionManager;
@Inject
private ContentSectionRepository sectionRepository;
@GET
@Path("/")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
@Override
public String getApplication() {
final List<ContentSection> contentSections = sectionRepository.findAll();
models.put(
"sections",
sectionRepository
.findAll()
.stream()
.map(this::buildContentSectionTableRow)
.sorted()
.collect(Collectors.toList())
);
return "org/libreccm/ui/admin/applications/content-sections/content-sections.xhtml";
}
@POST
@Path("/add")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public String addContentSection(
@FormParam("label") final String label
) {
sectionManager.createContentSection(label);
return "redirect:applications/content-sections";
}
@POST
@Path("/{sectionId}/update")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public String updateContentSection(
@PathParam("sectionId") final long sectionId,
@FormParam("label") final String label
) {
final Optional<ContentSection> result = sectionRepository
.findById(sectionId);
if (result.isPresent()) {
sectionManager.renameContentSection(result.get(), label);
}
return "redirect:applications/content-sections";
}
@POST
@Path("/{sectionId}/delete")
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public String deleteContentSection(
@PathParam("sectionId") final long sectionId,
@FormParam("confirmed") final String confirmed
) {
final Optional<ContentSection> result = sectionRepository
.findById(sectionId);
if (result.isPresent() && "true".equals(confirmed)) {
sectionRepository.delete(result.get());
}
return "redirect:applications/content-sections";
}
private ContentSectionTableRow buildContentSectionTableRow(
final ContentSection section
) {
final ContentSectionTableRow row = new ContentSectionTableRow();
row.setSectionId(section.getObjectId());
row.setUuid(section.getUuid());
row.setLabel(section.getLabel());
return row;
}
}

View File

@ -0,0 +1,57 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.libreccm.ui.admin.contentsections;
import java.util.Comparator;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class ContentSectionTableRow implements
Comparable<ContentSectionTableRow> {
private long sectionId;
private String uuid;
private String label;
public long getSectionId() {
return sectionId;
}
protected void setSectionId(long sectionId) {
this.sectionId = sectionId;
}
public String getUuid() {
return uuid;
}
protected void setUuid(final String uuid) {
this.uuid = uuid;
}
public String getLabel() {
return label;
}
protected void setLabel(final String label) {
this.label = label;
}
@Override
public int compareTo(final ContentSectionTableRow other) {
return Comparator
.nullsFirst(
Comparator
.comparing(ContentSectionTableRow::getLabel)
.thenComparing(ContentSectionTableRow::getSectionId)
).compare(this, other);
}
}

View File

@ -23,6 +23,7 @@ import org.libreccm.modules.RequiredModule;
import org.libreccm.modules.ShutdownEvent;
import org.libreccm.modules.UnInstallEvent;
import org.libreccm.pagemodel.PageModelComponentModel;
import org.libreccm.ui.admin.contentsections.ContentSectionApplicationController;
import org.libreccm.web.ApplicationType;
import org.libreccm.web.CcmApplication;
import org.librecms.assets.AssetTypes;
@ -80,7 +81,8 @@ import java.util.Properties;
settingsPane = SettingsPane.class,
descBundle = CmsConstants.CONTENT_SECTION_DESC_BUNDLE,
creator = ContentSectionCreator.class,
servletPath = "/templates/servlet/content-section"
servletPath = "/templates/servlet/content-section",
applicationController = ContentSectionApplicationController.class
),
@ApplicationType(
name = "org.librecms.pages.Pages",

View File

@ -0,0 +1,115 @@
<!DOCTYPE html>
<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:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
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/libreccm/ui/admin/ccm-admin.xhtml">
<ui:param name="activePage" value="applications" />
<ui:param name="title" value="#{AdminMessages['applications.label']}" />
<ui:define name="breadcrumb">
<li class="breadcrumb-item">
#{AdminMessages['applications.label']}
</li>
<li class="breadcrumb-item active">
#{ContentSectionAdminMessages['application_title']}
</li>
</ui:define>
<ui:define name="main">
<div class="container">
<h1>#{ContentSectionAdminMessages['application_title']}</h1>
<div class="mb-2">
<bootstrap:modalForm actionTarget="#{mvc.uri('ContentSectionApplicationController#addContentSection')}"
buttonIcon="plus-circle"
buttonText="#{ContentSectionAdminMessages['contentsections.ui.admin.instances.add']}"
buttonTextClass="text-right"
dialogId="contentsection-add-form"
>
<f:facet name="title">
<h3>#{ContentSectionAdminMessages['contentsections.ui.admin.instances.add_form.title']}</h3>
</f:facet>
<f:facet name="body">
<bootstrap:formGroupText help="#{ContentSectionAdminMessages['contentsections.ui.admin.instances.add_form.name.help']}"
inputId="contentsection-add-form-name"
label="#{ContentSectionAdminMessages['contentsections.ui.admin.instances.add_form.name.label']}"
name="label"
pattern="[\\w-.]"/>
</f:facet>
<f:facet name="footer">
<button class="btn btn-secondary"
data-dismiss="modal"
type="button" >
#{ContentSectionAdminMessages['contentsections.ui.admin.instances.add_form.cancel']}
</button>
<button type="submit" class="btn btn-primary">
#{ContentSectionAdminMessages['contentsections.ui.admin.instances.add_form.submit']}
</button>
</f:facet>
</bootstrap:modalForm>
</div>
<table class="table table-hover">
<thead>
<tr>
<th>#{ContentSectionAdminMessages['contentsections.ui.admin.instances_table.col_name.header']}</th>
<th class="text-center" colspan="2">#{ContentSectionAdminMessages['contentsections.ui.admin.instances_table.col_name.actions']}</th>
</tr>
</thead>
<tbody>
<c:forEach items="#{sections}" var="section">
<tr>
<td>#{section.label}</td>
<td class="text-center">
<bootstrap:modalForm actionTarget="#{mvc.uri('ContentSectionApplicationController#updateContentSection', { 'sectionId': section.sectionId })}"
buttonIcon="plus-circle"
buttonText="#{ContentSectionAdminMessages['contentsections.ui.admin.instances.edit']}"
buttonTextClass="text-right"
dialogId="contentsection-#{section.sectionId}-edit-form"
>
<f:facet name="title">
<h3>#{ContentSectionAdminMessages['contentsections.ui.admin.instances.add_form.title']}</h3>
</f:facet>
<f:facet name="body">
<bootstrap:formGroupText help="#{ContentSectionAdminMessages['contentsections.ui.admin.instances.edit_form.name.help']}"
inputId="contentsection-add-form-name"
label="#{ContentSectionAdminMessages['contentsections.ui.admin.instances.edit_form.name.label']}"
name="label"
pattern="[\\w-.]"
value="#{section.label}" />
</f:facet>
<f:facet name="footer">
<button class="btn btn-secondary"
data-dismiss="modal"
type="button" >
#{ContentSectionAdminMessages['contentsections.ui.admin.instances.edit_form.cancel']}
</button>
<button type="submit" class="btn btn-primary">
#{ContentSectionAdminMessages['contentsections.ui.admin.instances.edit_form.submit']}
</button>
</f:facet>
</bootstrap:modalForm>
</td>
<td class="text-center">
<libreccm:deleteDialog actionTarget="#{mvc.uri('ContentSectionApplicationController#deleteContentSection', { 'sectionId': section.sectionId })}"
buttonText="#{ContentSectionAdminMessages['contentsections.ui.admin.instances.delete_dialog.button_text']}"
cancelLabel="#{ContentSectionAdminMessages['contentsections.ui.admin.instances.delete_dialog.cancel']}"
confirmLabel="#{ContentSectionAdminMessages['contentsections.ui.admin.instances.delete_dialog.confirm']}"
dialogId="contentsection-#{section.sectionId}-deletedialog"
dialogTitle="#{ContentSectionAdminMessages['contentsections.ui.admin.instances.delete_dialog.title']}"
message="#{ContentSectionAdminMessages.getMessage('contentsections.ui.admin.instances.delete_dialog.message', [section.label])}" />
</td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
</ui:define>
</ui:composition>
</html>

View File

@ -17,3 +17,21 @@
application_title=Content Section
application_desc=A content section is used to group similar content.
contentsections.ui.admin.instances_table.col_name.header=Name
contentsections.ui.admin.instances_table.col_name.actions=Actions
contentsections.ui.admin.instances.add_form.title=Create new Content Section
contentsections.ui.admin.instances.add=Add Content Section
contentsections.ui.admin.instances.add_form.name.help=The name of the new Content Section. Also used as URL stub, therefore only characters in an URL can be used.
contentsections.ui.admin.instances.add_form.name.label=Name
contentsections.ui.admin.instances.add_form.cancel=Cancel
contentsections.ui.admin.instances.add_form.submit=Create Content Section
contentsections.ui.admin.instances.delete_dialog.button_text=Delete
contentsections.ui.admin.instances.delete_dialog.cancel=Cancel
contentsections.ui.admin.instances.delete_dialog.confirm=Delete Content Section
contentsections.ui.admin.instances.delete_dialog.title=Content Section l\u00f6schen best\u00e4tigen
contentsections.ui.admin.instances.delete_dialog.message=Are you sure to delete the Content Section {0}?
contentsections.ui.admin.instances.edit=Edit
contentsections.ui.admin.instances.edit_form.name.help=The name of the Content Section. Also used as URL stub, therefore only characters in an URL can be used.
contentsections.ui.admin.instances.edit_form.name.label=Name
contentsections.ui.admin.instances.edit_form.cancel=Cancel
contentsections.ui.admin.instances.edit_form.submit=Save

View File

@ -17,3 +17,21 @@
application_title=Content Section
application_desc=A content section is used to group similar content.
contentsections.ui.admin.instances_table.col_name.header=Name
contentsections.ui.admin.instances_table.col_name.actions=Aktionen
contentsections.ui.admin.instances.add_form.title=Neue Content Section anlegen
contentsections.ui.admin.instances.add=Content Section hinzuf\u00fcgen
contentsections.ui.admin.instances.add_form.name.help=Der Name der neuen Content Section. Wird auch als URL-Fragment genutzt, daher sind Zeichen erlaubt, die in einer URL vorkommen d\u00fcrfen.
contentsections.ui.admin.instances.add_form.name.label=Name
contentsections.ui.admin.instances.add_form.cancel=Abbrechen
contentsections.ui.admin.instances.add_form.submit=Content Section anlegen
contentsections.ui.admin.instances.delete_dialog.button_text=L\u00f6schen
contentsections.ui.admin.instances.delete_dialog.cancel=Abbrechen
contentsections.ui.admin.instances.delete_dialog.confirm=Content Section l\u00f6schen
contentsections.ui.admin.instances.delete_dialog.title=Content Section l\u00f6schen best\u00e4tigen
contentsections.ui.admin.instances.delete_dialog.message=Sind Sie sicher dass Sie die Content Section {0} l\u00f6schen wollen?
contentsections.ui.admin.instances.edit=Bearbeiten
contentsections.ui.admin.instances.edit_form.name.help=Der Name der Content Section. Wird auch als URL-Fragment genutzt, daher sind Zeichen erlaubt, die in einer URL vorkommen d\u00fcrfen.
contentsections.ui.admin.instances.edit_form.name.label=Name
contentsections.ui.admin.instances.edit_form.cancel=Abbrechen
contentsections.ui.admin.instances.edit_form.submit=Speichern

View File

@ -76,6 +76,7 @@
<dependency>
<groupId>javax.mvc</groupId>
<artifactId>javax.mvc-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.krazo</groupId>

View File

@ -20,7 +20,6 @@ package org.libreccm.ui.admin.applications.shortcuts;
import org.libreccm.l10n.GlobalizationHelper;
import org.libreccm.shortcuts.ShortcutsConstants;
import org.libreccm.ui.admin.AdminConstants;
import java.text.MessageFormat;
import java.util.AbstractMap;

View File

@ -113,12 +113,13 @@ public class ShortcutsApplicationController implements ApplicationController {
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public String removeShortcut(
@PathParam("shortcutId") final long shortcutId
@PathParam("shortcutId") final long shortcutId,
@FormParam("confirmed") final String confirmed
) {
final Optional<Shortcut> result = shortcutRepository
.findById(shortcutId);
if (result.isPresent()) {
if (result.isPresent() && "true".equals(confirmed)) {
shortcutRepository.delete(result.get());
}