diff --git a/layout-service/src/main/java/io/meeds/layout/plugin/container/PortletInstanceApplicationAdapter.java b/layout-service/src/main/java/io/meeds/layout/plugin/container/PortletInstanceApplicationAdapter.java index 378195294..2b6d9a589 100644 --- a/layout-service/src/main/java/io/meeds/layout/plugin/container/PortletInstanceApplicationAdapter.java +++ b/layout-service/src/main/java/io/meeds/layout/plugin/container/PortletInstanceApplicationAdapter.java @@ -22,6 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import org.exoplatform.commons.exception.ObjectNotFoundException; import org.exoplatform.portal.application.PortalRequestContext; import org.exoplatform.portal.config.model.Application; import org.exoplatform.portal.config.model.ApplicationState; @@ -279,7 +280,15 @@ public ApplicationData build() { } @Override - public void resetStorage() { + public void checkStorage() throws ObjectNotFoundException { + Application portletApplication = getApplication(); + if (portletApplication != null) { + portletApplication.checkStorage(); + } + } + + @Override + public void resetStorage() throws ObjectNotFoundException { Application portletApplication = getApplication(); if (portletApplication != null) { portletApplication.resetStorage(); diff --git a/layout-service/src/main/java/io/meeds/layout/rest/PageLayoutRest.java b/layout-service/src/main/java/io/meeds/layout/rest/PageLayoutRest.java index 688c645e9..ef4b70be8 100644 --- a/layout-service/src/main/java/io/meeds/layout/rest/PageLayoutRest.java +++ b/layout-service/src/main/java/io/meeds/layout/rest/PageLayoutRest.java @@ -195,7 +195,7 @@ public LayoutModel updatePageLayout( publish.orElse(false).booleanValue(), request.getRemoteUser()); return getPageLayout(request, pageRef, 0, expand); - } catch (IllegalArgumentException e) { + } catch (IllegalArgumentException | IllegalStateException e) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); } catch (ObjectNotFoundException e) { throw new ResponseStatusException(HttpStatus.NOT_FOUND, e.getMessage()); diff --git a/layout-service/src/main/java/io/meeds/layout/service/PageLayoutService.java b/layout-service/src/main/java/io/meeds/layout/service/PageLayoutService.java index edb37b16d..d3be13518 100644 --- a/layout-service/src/main/java/io/meeds/layout/service/PageLayoutService.java +++ b/layout-service/src/main/java/io/meeds/layout/service/PageLayoutService.java @@ -55,6 +55,8 @@ import org.exoplatform.portal.mop.service.LayoutService; import org.exoplatform.portal.pom.spi.portlet.Portlet; import org.exoplatform.portal.pom.spi.portlet.Preference; +import org.exoplatform.services.log.ExoLogger; +import org.exoplatform.services.log.Log; import io.meeds.layout.model.PageCreateModel; import io.meeds.layout.model.PageTemplate; @@ -70,6 +72,8 @@ public class PageLayoutService { public static final String EMPTY_PAGE_TEMPLATE = "empty"; + private static final Log LOG = ExoLogger.getLogger(PageLayoutService.class); + private static final Pattern GENERIC_STYLE_MATCHER_VALIDATOR = Pattern.compile("[#0-9a-zA-Z\\(\\),\\./\"'\\-%_ ]+"); private static final String PAGE_NOT_EXISTS_MESSAGE = "Page with key %s doesn't exist"; @@ -231,9 +235,17 @@ public PageContext updatePageLayout(String pageRef, throw new IllegalAccessException(String.format(PAGE_NOT_EDITABLE_MESSAGE, pageKey.format(), username)); } - // Update Page Layout only - if (publish) { - page.resetStorage(); + try { + if (publish) { + // Update Page Layout only without resetting preferences + page.resetStorage(); + } else { + // Check Page Layout consistency before saving + page.checkStorage(); + } + } catch (ObjectNotFoundException e) { + LOG.debug("Error while accessing page applications storage information", e); + throw new IllegalStateException("layout.pageOutdatedError"); } validateCSSInputs(page); existingPage.setChildren(page.getChildren()); diff --git a/layout-webapp/src/main/resources/locale/portlet/LayoutEditor_en.properties b/layout-webapp/src/main/resources/locale/portlet/LayoutEditor_en.properties index 0a72b25c0..3d51e64e3 100644 --- a/layout-webapp/src/main/resources/locale/portlet/LayoutEditor_en.properties +++ b/layout-webapp/src/main/resources/locale/portlet/LayoutEditor_en.properties @@ -36,6 +36,8 @@ layout.undoChanges=Undo Changes (ctrl+z) layout.redoChanges=Redo Changes (ctrl+y) layout.pageSavingError=An error occurred while saving page. Please try again later or contact the administrator. layout.pageSavedSuccessfully=Page Saved Successfully +layout.pageDraftSavedSuccessfully=Page draft saved successfully +layout.pageOutdatedError=The current page version seems to be outdated. Please try again by refreshing the page or check if another session is ongoing (another browser tab) or contact your administrator. layout.switchToDesktop=Switch to Desktop View layout.switchToMobile=Switch to Mobile View layout.margins=Margins diff --git a/layout-webapp/src/main/webapp/vue-app/common/js/PageLayoutService.js b/layout-webapp/src/main/webapp/vue-app/common/js/PageLayoutService.js index a79d97838..d78853313 100644 --- a/layout-webapp/src/main/webapp/vue-app/common/js/PageLayoutService.js +++ b/layout-webapp/src/main/webapp/vue-app/common/js/PageLayoutService.js @@ -54,6 +54,11 @@ export function updatePageLayout(pageRef, pageLayout, expand, publish) { }).then((resp) => { if (resp?.ok) { return resp.json(); + } else if (resp.status === 400) { + return resp.json() + .then(e => { + throw new Error(e?.message); + }); } else { throw new Error(resp.status); } diff --git a/layout-webapp/src/main/webapp/vue-app/layout-editor/components/LayoutEditor.vue b/layout-webapp/src/main/webapp/vue-app/layout-editor/components/LayoutEditor.vue index 67836b528..01121189a 100644 --- a/layout-webapp/src/main/webapp/vue-app/layout-editor/components/LayoutEditor.vue +++ b/layout-webapp/src/main/webapp/vue-app/layout-editor/components/LayoutEditor.vue @@ -122,7 +122,8 @@ export default { this.draftPageRef, JSON.parse(this.$root.pageTemplate.content), 'contentId')) - .then(draftLayout => this.setDraftLayout(draftLayout)); + .then(draftLayout => this.setDraftLayout(draftLayout)) + .catch(e => this.$root.$emit('alert-message', this.$te(e.message) ? this.$t(e.message) : this.$t('layout.pageSavingError'), 'error')); } else { this.$pageLayoutService.getPageLayout(this.draftPageRef, 'contentId') .then(draftLayout => this.setDraftLayout(draftLayout)); diff --git a/layout-webapp/src/main/webapp/vue-app/layout-editor/components/content/Content.vue b/layout-webapp/src/main/webapp/vue-app/layout-editor/components/content/Content.vue index d4350c38e..4f3f73b1b 100644 --- a/layout-webapp/src/main/webapp/vue-app/layout-editor/components/content/Content.vue +++ b/layout-webapp/src/main/webapp/vue-app/layout-editor/components/content/Content.vue @@ -396,10 +396,16 @@ export default { this.loading++; const layoutToUpdate = this.$layoutUtils.cleanAttributes(layout || this.layoutToEdit, false, true); return this.$pageLayoutService.updatePageLayout(this.$root.draftPageRef, layoutToUpdate, 'contentId') - .then(layout => this.setLayout(layout)) + .then(layout => { + this.setLayout(layout); + this.$root.$emit('layout-draft-saved'); + }) + .catch(e => { + this.$root.$emit('alert-message', this.$te(e.message) ? this.$t(e.message) : this.$t('layout.pageSavingError'), 'error'); + this.$root.$emit('layout-draft-save-error'); + }) .finally(() => { window.setTimeout(() => this.loading--, 200); - this.$root.$emit('layout-draft-saved'); this.$root.$emit('coediting-set-lock'); }); }, diff --git a/layout-webapp/src/main/webapp/vue-app/layout-editor/components/toolbar/actions/PagePreviewButton.vue b/layout-webapp/src/main/webapp/vue-app/layout-editor/components/toolbar/actions/PagePreviewButton.vue index 7d0765611..4a79a6f18 100644 --- a/layout-webapp/src/main/webapp/vue-app/layout-editor/components/toolbar/actions/PagePreviewButton.vue +++ b/layout-webapp/src/main/webapp/vue-app/layout-editor/components/toolbar/actions/PagePreviewButton.vue @@ -52,8 +52,12 @@ export default { previewPage() { this.saving = true; this.$root.$on('layout-draft-saved', this.openPreviewPage); + this.$root.$on('layout-draft-save-error', this.stopLoading); this.$root.$emit('layout-save-draft'); }, + stopLoading() { + this.saving = false; + }, openPreviewPage() { this.$root.$off('layout-draft-saved', this.openPreviewPage); this.saving = false; diff --git a/layout-webapp/src/main/webapp/vue-app/layout-editor/components/toolbar/actions/SaveButton.vue b/layout-webapp/src/main/webapp/vue-app/layout-editor/components/toolbar/actions/SaveButton.vue index 699179006..ec71cd739 100644 --- a/layout-webapp/src/main/webapp/vue-app/layout-editor/components/toolbar/actions/SaveButton.vue +++ b/layout-webapp/src/main/webapp/vue-app/layout-editor/components/toolbar/actions/SaveButton.vue @@ -63,7 +63,7 @@ export default { const layoutToUpdate = this.$layoutUtils.cleanAttributes(this.$root.layout, false, true); return this.$pageLayoutService.updatePageLayout(this.$root.pageRef, layoutToUpdate, 'contentId', true) .then(() => this.$root.$emit('layout-page-saved')) - .catch(() => this.$root.$emit('alert-message', this.$t('layout.pageSavingError'), 'error')) + .catch(e => this.$root.$emit('alert-message', this.$te(e.message) ? this.$t(e.message) : this.$t('layout.pageSavingError'), 'error')) .finally(() => window.setTimeout(() => this.loading = false)); }, }, diff --git a/layout-webapp/src/main/webapp/vue-app/layout-editor/components/toolbar/actions/SaveDraftButton.vue b/layout-webapp/src/main/webapp/vue-app/layout-editor/components/toolbar/actions/SaveDraftButton.vue index 1b8ed24db..5bb97ff5c 100644 --- a/layout-webapp/src/main/webapp/vue-app/layout-editor/components/toolbar/actions/SaveDraftButton.vue +++ b/layout-webapp/src/main/webapp/vue-app/layout-editor/components/toolbar/actions/SaveDraftButton.vue @@ -8,6 +8,7 @@ modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + This program 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 @@ -47,11 +48,22 @@ export default { methods: { saveDraft() { this.loading = true; - this.$root.$on('layout-draft-saved', this.stopLoading); + this.$root.$on('layout-draft-saved', this.stopLoadingSuccess); + this.$root.$on('layout-draft-save-error', this.stopLoadingError); this.$root.$emit('layout-save-draft'); }, - stopLoading() { - this.$root.$off('layout-draft-saved', this.stopLoading); + stopLoadingSuccess() { + this.stopLoading(true); + }, + stopLoadingError() { + this.stopLoading(); + }, + stopLoading(success) { + if (this.loading && success) { + this.$root.$emit('alert-message', this.$t('layout.pageDraftSavedSuccessfully'), 'success'); + } + this.$root.$off('layout-draft-saved', this.stopLoadingSuccess); + this.$root.$off('layout-draft-save-error', this.stopLoadingError); this.loading = false; }, },