ArticleTextBodyStep UI mostly works

pull/10/head
Jens Pelzetter 2021-05-11 21:04:20 +02:00
parent b526f9eac2
commit 62d98af857
9 changed files with 361 additions and 74 deletions

View File

@ -74,6 +74,7 @@ public class ContentSectionApplication extends Application {
classes.add(DocumentWorkflowController.class);
classes.addAll(getAuthoringSteps());
classes.addAll(getAuthoringStepResources());
classes.add(IsAuthenticatedFilter.class);
@ -89,4 +90,12 @@ public class ContentSectionApplication extends Application {
.collect(Collectors.toSet());
}
private Set<Class<?>> getAuthoringStepResources() {
return authoringSteps
.stream()
.map(MvcAuthoringSteps::getResourceClasses)
.flatMap(Set::stream)
.collect(Collectors.toSet());
}
}

View File

@ -20,6 +20,7 @@ package org.librecms.ui.contentsections.documents;
import org.librecms.ui.contenttypes.MvcArticlePropertiesStep;
import org.librecms.ui.contenttypes.MvcArticleTextBodyStep;
import org.librecms.ui.contenttypes.MvcArticleTextBodyStepResources;
import java.util.HashSet;
import java.util.Set;
@ -46,4 +47,12 @@ public class CmsMvcAuthoringSteps implements MvcAuthoringSteps {
return classes;
}
@Override
public Set<Class<?>> getResourceClasses() {
final Set<Class<?>> classes = new HashSet<>();
classes.add(MvcArticleTextBodyStepResources.class);
return classes;
}
}

View File

@ -32,6 +32,12 @@ import javax.ws.rs.Path;
*/
public interface MvcAuthoringStep {
/**
* Returns the class implementing the step. This method is used by CCM is to
* get the correct class instead of a CDI proxy.
*
* @return The class implementing the step.
*/
Class<? extends MvcAuthoringStep> getStepClass();
ContentSection getContentSection() throws ContentSectionNotFoundException;
@ -52,7 +58,9 @@ public interface MvcAuthoringStep {
* admin priviliges for the content section of the item.</li>
* </ul>
*
* @return {@code true} if the current user can edit the document/item, {@false} otherwise.
* @return {@code true} if the current user can edit the document/item, {
*
* @false} otherwise.
*/
boolean getCanEdit();

View File

@ -18,6 +18,7 @@
*/
package org.librecms.ui.contentsections.documents;
import java.util.Collections;
import java.util.Set;
/**
@ -34,8 +35,13 @@ public interface MvcAuthoringSteps {
public static final String DOCUMENT_PATH_PATH_PARAM_NAME = "documentPath";
public static final String DOCUMENT_PATH_PATH_PARAM = DOCUMENT_PATH_PATH_PARAM_NAME + ":(.+)?";
public static final String DOCUMENT_PATH_PATH_PARAM
= DOCUMENT_PATH_PATH_PARAM_NAME + ":(.+)?";
Set<Class<?>> getClasses();
default Set<Class<?>> getResourceClasses() {
return Collections.emptySet();
}
}

View File

@ -226,38 +226,38 @@ public class MvcArticleTextBodyStep extends AbstractMvcAuthoringStep {
}
}
@GET
// @Path("/{locale}/@view")
@Path("/variants/{locale}")
@Transactional(Transactional.TxType.REQUIRED)
public String viewTextValue(
@PathParam(MvcAuthoringSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME)
final String documentPath,
@PathParam("locale") final String localeParam
) {
try {
init();
} catch (ContentSectionNotFoundException ex) {
return ex.showErrorMessage();
} catch (DocumentNotFoundException ex) {
return ex.showErrorMessage();
}
if (itemPermissionChecker.canEditItem(getArticle())) {
selectedLocale = new Locale(localeParam).toString();
// return "org/librecms/ui/contenttypes/article/article-text/view.xhtml";
return getTextValues().get(localeParam);
} else {
return documentUi.showAccessDenied(
getContentSection(),
getArticle(),
articleMessageBundle.getMessage("article.edit.denied")
);
}
}
// @GET
//// @Path("/{locale}/@view")
// @Path("/variants/{locale}")
// @Transactional(Transactional.TxType.REQUIRED)
// public String viewTextValue(
// @PathParam(MvcAuthoringSteps.SECTION_IDENTIFIER_PATH_PARAM)
// final String sectionIdentifier,
// @PathParam(MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME)
// final String documentPath,
// @PathParam("locale") final String localeParam
// ) {
// try {
// init();
// } catch (ContentSectionNotFoundException ex) {
// return ex.showErrorMessage();
// } catch (DocumentNotFoundException ex) {
// return ex.showErrorMessage();
// }
//
// if (itemPermissionChecker.canEditItem(getArticle())) {
// selectedLocale = new Locale(localeParam).toString();
//
//// return "org/librecms/ui/contenttypes/article/article-text/view.xhtml";
// return getTextValues().get(localeParam);
// } else {
// return documentUi.showAccessDenied(
// getContentSection(),
// getArticle(),
// articleMessageBundle.getMessage("article.edit.denied")
// );
// }
// }
@GET

View File

@ -0,0 +1,103 @@
/*
* 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.contenttypes;
import org.librecms.contentsection.ContentItem;
import org.librecms.contentsection.ContentItemRepository;
import org.librecms.contentsection.ContentSection;
import org.librecms.contenttypes.Article;
import org.librecms.ui.contentsections.ContentSectionsUi;
import org.librecms.ui.contentsections.ItemPermissionChecker;
import org.librecms.ui.contentsections.documents.MvcAuthoringSteps;
import java.util.Locale;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.transaction.Transactional;
import javax.ws.rs.ForbiddenException;
import javax.ws.rs.GET;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Path(MvcAuthoringSteps.PATH_PREFIX + "article-text-resources")
public class MvcArticleTextBodyStepResources {
/**
* Used for retrieving and saving the article.
*/
@Inject
private ContentItemRepository itemRepo;
@Inject
private ContentSectionsUi sectionsUi;
@Inject
private ItemPermissionChecker itemPermissionChecker;
@GET
// @Path("/{locale}/@view")
@Path("/variants/{locale}")
@Transactional(Transactional.TxType.REQUIRED)
public String viewTextValue(
@PathParam(MvcAuthoringSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME)
final String documentPathParam,
@PathParam("locale") final String localeParam
) {
// try {
// init();
// } catch (ContentSectionNotFoundException ex) {
// return ex.showErrorMessage();
// } catch (DocumentNotFoundException ex) {
// return ex.showErrorMessage();
// }
final ContentSection contentSection = sectionsUi
.findContentSection(sectionIdentifier)
.orElseThrow(
() -> new NotFoundException()
);
final ContentItem document = itemRepo
.findByPath(contentSection, documentPathParam)
.orElseThrow(
() -> new NotFoundException()
);
if (!(document instanceof Article)) {
throw new NotFoundException();
}
final Article article = (Article) document;
if (itemPermissionChecker.canEditItem(article)) {
return article.getText().getValue(new Locale(localeParam));
} else {
throw new ForbiddenException();
}
}
}

View File

@ -217,7 +217,7 @@
</div>
</template>
<template id="cms-editor-msg-save-successful">
<div class="alert alert-danger" role="alert">
<div class="alert alert-success" role="alert">
#{cc.attrs.messageSaveSuccessful}
</div>
</template>
@ -354,6 +354,7 @@
<button class="btn btn-primary cms-editor-edit-button"
data-edit-dialog="#{cc.attrs.editorId}-edit-dialog"
data-variant-url="#{cc.attrs.variantUrl}/#{variant.locale}"
data-save-url="#{cc.attrs.editMethod}/#{variant.locale}"
type="button">
<bootstrap:svgIcon icon="pen" />
<span class="sr-only">
@ -440,34 +441,34 @@
</div>
<div aria-describedby="#{cc.attrs.editorId}-view-dialog-title"
aria-hidden="true"
class="modal modal-xl fade"
class="modal fade"
data-backdrop="static"
id="#{cc.attrs.editorId}-view-dialog"
tabindex="-1">
<div class="modal-dialog">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<c:choose>
<c:when test="#{cc.attrs.headingLevel == 1}">
<h2 id="#{cc.attrs.editorId}-view-dialog-title">#{cc.attrs.removeDialogTitle}</h2>
<h2 id="#{cc.attrs.editorId}-view-dialog-title">#{cc.attrs.viewDialogTitle}</h2>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 2}">
<h3 id="#{cc.attrs.editorId}-view-dialog-title">#{cc.attrs.removeDialogTitle}</h3>
<h3 id="#{cc.attrs.editorId}-view-dialog-title">#{cc.attrs.viewDialogTitle}</h3>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 3}">
<h4 id="#{cc.attrs.editorId}-view-dialog-title">#{cc.attrs.removeDialogTitle}</h4>
<h4 id="#{cc.attrs.editorId}-view-dialog-title">#{cc.attrs.viewDialogTitle}</h4>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 4}">
<h5 id="#{cc.attrs.editorId}-view-dialog-title">#{cc.attrs.removeDialogTitle}</h5>
<h5 id="#{cc.attrs.editorId}-view-dialog-title">#{cc.attrs.viewDialogTitle}</h5>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 5}">
<h6 id="#{cc.attrs.editorId}-view-dialog-title">#{cc.attrs.removeDialogTitle}</h6>
<h6 id="#{cc.attrs.editorId}-view-dialog-title">#{cc.attrs.viewDialogTitle}</h6>
</c:when>
<c:otherwise>
<div id="#{cc.attrs.editorId}-view-dialog-title">#{cc.attrs.removeDialogTitle}</div>
<div id="#{cc.attrs.editorId}-view-dialog-title">#{cc.attrs.viewDialogTitle}</div>
</c:otherwise>
</c:choose>
<button aria-label="#{cc.attrs.removeDialogCancelLabel}"
<button aria-label="#{cc.attrs.viewDialogCancelLabel}"
class="close"
data-dismiss="modal"
type="button" >
@ -488,42 +489,41 @@
</div>
<div aria-describedby="#{cc.attrs.editorId}-edit-dialog-title"
aria-hidden="true"
class="modal modal-xl fade"
class="modal fade"
data-backdrop="static"
id="#{cc.attrs.editorId}-edit-dialog"
tabindex="-1">
<div class="modal-dialog">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<c:choose>
<c:when test="#{cc.attrs.headingLevel == 1}">
<h2 id="#{cc.attrs.editorId}-edit-dialog-title">#{cc.attrs.removeDialogTitle}</h2>
<h2 id="#{cc.attrs.editorId}-edit-dialog-title">#{cc.attrs.editDialogTitle}</h2>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 2}">
<h3 id="#{cc.attrs.editorId}-edit-dialog-title">#{cc.attrs.removeDialogTitle}</h3>
<h3 id="#{cc.attrs.editorId}-edit-dialog-title">#{cc.attrs.editDialogTitle}</h3>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 3}">
<h4 id="#{cc.attrs.editorId}-edit-dialog-title">#{cc.attrs.removeDialogTitle}</h4>
<h4 id="#{cc.attrs.editorId}-edit-dialog-title">#{cc.attrs.editDialogTitle}</h4>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 4}">
<h5 id="#{cc.attrs.editorId}-edit-dialog-title">#{cc.attrs.removeDialogTitle}</h5>
<h5 id="#{cc.attrs.editorId}-edit-dialog-title">#{cc.attrs.editDialogTitle}</h5>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 5}">
<h6 id="#{cc.attrs.editorId}-edit-dialog-title">#{cc.attrs.removeDialogTitle}</h6>
<h6 id="#{cc.attrs.editorId}-edit-dialog-title">#{cc.attrs.editDialogTitle}</h6>
</c:when>
<c:otherwise>
<div id="#{cc.attrs.editorId}-edit-dialog-title">#{cc.attrs.removeDialogTitle}</div>
<div id="#{cc.attrs.editorId}-edit-dialog-title">#{cc.attrs.editDialogTitle}</div>
</c:otherwise>
</c:choose>
<button aria-label="#{cc.attrs.removeDialogCancelLabel}"
<button aria-label="#{cc.attrs.editDialogCancelLabel}"
class="close"
data-dismiss="modal"
type="button" >
<bootstrap:svgIcon icon="x" />
</button>
</div>
<div class="modal-body">
<div class="cms-tiptap-editor-buttons">
<div class="cms-tiptap-editor-buttons mb-1">
<button class="btn btn-outline-dark tiptap-emph"
type="button">
<bootstrap:svgIcon icon="type-italic" />
@ -539,17 +539,17 @@
</span>
</button>
</div>
<div class="cms-tiptap-editor">
<div class="cms-tiptap-editor border">
</div>
</div>
<div class="modal-footer">
<button class="btn btn-warning"
data-dismiss="modal"
<button class="btn btn-warning cms-editor-cancel-button"
type="button" >
#{cc.attrs.editDialogCancelLabel}
</button>
<button type="submit" class="btn btn-success">
<button class="btn btn-success cms-editor-save-button"
type="button" >
#{cc.attrs.editDialogSubmitLabel}
</button>
</div>

View File

@ -35,7 +35,7 @@
removeMethod="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@article-text/remove"
title="#{CmsArticleMessageBundle['text.editor.header']}"
unusedLocales="#{CmsArticleTextBodyStep.unusedLocales}"
variantUrl="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@article-text/variants"
variantUrl="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@article-text-resources/variants"
variants="#{CmsArticleTextBodyStep.variants}"
/>

View File

@ -3,6 +3,12 @@ import * as $ from "jquery";
import { Editor } from "@tiptap/core";
import StarterKit from "@tiptap/starter-kit";
function showMessage(messageId: string) {
const template = document.querySelector(messageId) as HTMLTemplateElement;
const msg = template.content.cloneNode(true);
document.querySelector(".cms-editor-messages").append(msg);
}
document.addEventListener("DOMContentLoaded", function (event) {
document
.querySelector(
@ -33,25 +39,171 @@ document.addEventListener("DOMContentLoaded", function (event) {
viewDialogBody.textContent = text;
$(`#${viewDialogId}`).modal('toggle');
$(`#${viewDialogId}`).modal("toggle");
})
.catch(err => {
const template = document.querySelector(
showMessage(
"#cms-editor-msg-variant-load-failed"
) as HTMLTemplateElement;
const msg = template.content.cloneNode(true);
document
.querySelector(".cms-editor-messages")
.append(msg);
);
});
} else {
showMessage("#cms-editor-msg-variant-load-failed");
}
})
.catch(err => {
const template = document.querySelector(
"#cms-editor-msg-variant-load-failed"
) as HTMLTemplateElement;
const msg = template.content.cloneNode(true);
document.querySelector(".cms-editor-messages").append(msg);
showMessage("#cms-editor-msg-variant-load-failed");
});
});
document
.querySelector(
".cms-editor .cms-editor-variants .cms-editor-edit-button"
)
.addEventListener("click", function (event) {
event.preventDefault();
const target = event.currentTarget as Element;
const variantUrl = target.getAttribute("data-variant-url");
const editDialogId = target.getAttribute("data-edit-dialog");
const saveUrl = target.getAttribute("data-save-url");
fetch(variantUrl, {
method: "GET",
credentials: "include"
})
.then(response => {
if (response.ok) {
response
.text()
.then(text => {
const editDialog = document.querySelector(
`#${editDialogId}`
);
const tiptapDiv = editDialog.querySelector(
".modal-body .cms-tiptap-editor"
);
if (!tiptapDiv) {
console.warn("tiptapDiv is null");
}
const editor = new Editor({
element: tiptapDiv,
extensions: [StarterKit],
content: text
});
const buttonsDiv = editDialog.querySelector(
".cms-tiptap-editor-buttons"
);
if (!buttonsDiv) {
console.warn("buttonsDiv is null.");
}
const emphButton = buttonsDiv.querySelector(
".tiptap-emph"
);
if (!emphButton) {
console.warn("emphButton not found.");
}
emphButton.addEventListener("click", event => {
event.preventDefault();
editor.chain().focus().toggleItalic().run();
});
const strongEmphButton = buttonsDiv.querySelector(
".tiptap-strong-emph"
);
if (!strongEmphButton) {
console.warn("strongEmphButton not found.");
}
strongEmphButton.addEventListener(
"click",
event => {
event.preventDefault();
editor
.chain()
.focus()
.toggleBold()
.run();
}
);
const closeButton = editDialog.querySelector(
".modal-header .close"
);
const cancelButton = editDialog.querySelector(
".modal-footer .cms-editor-cancel-button"
);
const saveButton = editDialog.querySelector(
".modal-footer .cms-editor-save-button"
);
closeButton.addEventListener("click", event => {
editor.chain().clearContent();
editor.destroy();
$(`#${editDialogId}`).modal("toggle");
});
cancelButton.addEventListener(
"click",
event => {
editor.chain().clearContent();
editor.destroy();
$(`#${editDialogId}`).modal("toggle");
}
);
saveButton.addEventListener("click", event => {
const html = editor.getHTML();
const formData = new FormData();
formData.append("value", html);
fetch(saveUrl, {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: formData
})
.then(saveResponse => {
if (saveResponse.ok) {
showMessage(
"#cms-editor-msg-save-successful"
);
} else {
showMessage(
"#cms-editor-msg-save-failed"
);
}
$(`#${editDialogId}`).modal(
"toggle"
);
})
.catch(err => {
showMessage(
"#cms-editor-msg-save-failed"
);
console.error(err);
$(`#${editDialogId}`).modal(
"toggle"
);
});
});
$(`#${editDialogId}`).modal("toggle");
})
.catch(err => {
showMessage(
"#cms-editor-msg-variant-load-failed"
);
console.error(err);
});
} else {
showMessage("#cms-editor-msg-variant-load-failed");
}
})
.catch(err => {
showMessage("#cms-editor-msg-variant-load-failed");
console.error(err);
});
});