diff --git a/layout-service/src/main/java/io/meeds/layout/model/LayoutModel.java b/layout-service/src/main/java/io/meeds/layout/model/LayoutModel.java index ebdc4218e..097ec0422 100644 --- a/layout-service/src/main/java/io/meeds/layout/model/LayoutModel.java +++ b/layout-service/src/main/java/io/meeds/layout/model/LayoutModel.java @@ -160,6 +160,70 @@ public class LayoutModel { private String textSubtitleFontStyle; + private String textTitleBackgroundColor; + + private String textTitleBackgroundImage; + + private String textTitleBackgroundEffect; + + private String textTitleBackgroundPosition; + + private String textTitleBackgroundSize; + + private String textTitleBackgroundRepeat; + + private String textTitleBackgroundPadding; + + private String textTitleBackgroundRadius; + + private String textHeaderBackgroundColor; + + private String textHeaderBackgroundImage; + + private String textHeaderBackgroundEffect; + + private String textHeaderBackgroundPosition; + + private String textHeaderBackgroundSize; + + private String textHeaderBackgroundRepeat; + + private String textHeaderBackgroundPadding; + + private String textHeaderBackgroundRadius; + + private String textBackgroundColor; + + private String textBackgroundImage; + + private String textBackgroundEffect; + + private String textBackgroundPosition; + + private String textBackgroundSize; + + private String textBackgroundRepeat; + + private String textBackgroundPadding; + + private String textBackgroundRadius; + + private String textSubtitleBackgroundColor; + + private String textSubtitleBackgroundImage; + + private String textSubtitleBackgroundEffect; + + private String textSubtitleBackgroundPosition; + + private String textSubtitleBackgroundSize; + + private String textSubtitleBackgroundRepeat; + + private String textSubtitleBackgroundPadding; + + private String textSubtitleBackgroundRadius; + private String[] accessPermissions; // Specific to container @@ -243,6 +307,38 @@ private void init(ModelObject model, PortletInstanceService portletInstanceServi this.textSubtitleFontSize = cssStyle.getTextSubtitleFontSize(); this.textSubtitleFontWeight = cssStyle.getTextSubtitleFontWeight(); this.textSubtitleFontStyle = cssStyle.getTextSubtitleFontStyle(); + this.textTitleBackgroundColor = cssStyle.getTextTitleBackgroundColor(); + this.textTitleBackgroundImage = cssStyle.getTextTitleBackgroundImage(); + this.textTitleBackgroundEffect = cssStyle.getTextTitleBackgroundEffect(); + this.textTitleBackgroundPosition = cssStyle.getTextTitleBackgroundPosition(); + this.textTitleBackgroundSize = cssStyle.getTextTitleBackgroundSize(); + this.textTitleBackgroundRepeat = cssStyle.getTextTitleBackgroundRepeat(); + this.textTitleBackgroundPadding = cssStyle.getTextTitleBackgroundPadding(); + this.textTitleBackgroundRadius = cssStyle.getTextTitleBackgroundRadius(); + this.textHeaderBackgroundColor = cssStyle.getTextHeaderBackgroundColor(); + this.textHeaderBackgroundImage = cssStyle.getTextHeaderBackgroundImage(); + this.textHeaderBackgroundEffect = cssStyle.getTextHeaderBackgroundEffect(); + this.textHeaderBackgroundPosition = cssStyle.getTextHeaderBackgroundPosition(); + this.textHeaderBackgroundSize = cssStyle.getTextHeaderBackgroundSize(); + this.textHeaderBackgroundRepeat = cssStyle.getTextHeaderBackgroundRepeat(); + this.textHeaderBackgroundPadding = cssStyle.getTextHeaderBackgroundPadding(); + this.textHeaderBackgroundRadius = cssStyle.getTextHeaderBackgroundRadius(); + this.textBackgroundColor = cssStyle.getTextBackgroundColor(); + this.textBackgroundImage = cssStyle.getTextBackgroundImage(); + this.textBackgroundEffect = cssStyle.getTextBackgroundEffect(); + this.textBackgroundPosition = cssStyle.getTextBackgroundPosition(); + this.textBackgroundSize = cssStyle.getTextBackgroundSize(); + this.textBackgroundRepeat = cssStyle.getTextBackgroundRepeat(); + this.textBackgroundPadding = cssStyle.getTextBackgroundPadding(); + this.textBackgroundRadius = cssStyle.getTextBackgroundRadius(); + this.textSubtitleBackgroundColor = cssStyle.getTextSubtitleBackgroundColor(); + this.textSubtitleBackgroundImage = cssStyle.getTextSubtitleBackgroundImage(); + this.textSubtitleBackgroundEffect = cssStyle.getTextSubtitleBackgroundEffect(); + this.textSubtitleBackgroundPosition = cssStyle.getTextSubtitleBackgroundPosition(); + this.textSubtitleBackgroundSize = cssStyle.getTextSubtitleBackgroundSize(); + this.textSubtitleBackgroundRepeat = cssStyle.getTextSubtitleBackgroundRepeat(); + this.textSubtitleBackgroundPadding = cssStyle.getTextSubtitleBackgroundPadding(); + this.textSubtitleBackgroundRadius = cssStyle.getTextSubtitleBackgroundRadius(); } if (model instanceof PageBody pageBody) { diff --git a/layout-service/src/main/java/io/meeds/layout/plugin/attachment/AbstractLayoutAttachmentPlugin.java b/layout-service/src/main/java/io/meeds/layout/plugin/attachment/AbstractLayoutAttachmentPlugin.java new file mode 100644 index 000000000..f7086f6b0 --- /dev/null +++ b/layout-service/src/main/java/io/meeds/layout/plugin/attachment/AbstractLayoutAttachmentPlugin.java @@ -0,0 +1,131 @@ +/** + * This file is part of the Meeds project (https://meeds.io/). + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package io.meeds.layout.plugin.attachment; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; + +import org.exoplatform.commons.exception.ObjectNotFoundException; +import org.exoplatform.portal.config.model.Page; +import org.exoplatform.portal.config.model.PortalConfig; +import org.exoplatform.portal.mop.SiteKey; +import org.exoplatform.portal.mop.SiteType; +import org.exoplatform.portal.mop.page.PageKey; +import org.exoplatform.portal.mop.service.LayoutService; +import org.exoplatform.services.security.Identity; +import org.exoplatform.social.attachment.AttachmentPlugin; +import org.exoplatform.social.attachment.AttachmentService; + +import io.meeds.layout.service.LayoutAclService; + +import jakarta.annotation.PostConstruct; + +/** + * Common logic shared by every container/section background attachment + * plugin (the whole app/section background, and each per-text-type + * background). Subclasses only declare their own {@code OBJECT_TYPE}. + */ +public abstract class AbstractLayoutAttachmentPlugin extends AttachmentPlugin { + + private static final String PAGE_PREFIX = "page_"; + + private static final String SITE_PREFIX = "site_"; + + @Autowired + private LayoutAclService layoutAclService; + + @Autowired + private LayoutService layoutService; + + @Autowired + private AttachmentService attachmentService; + + @PostConstruct + public void init() { + attachmentService.addPlugin(this); + } + + @Override + public boolean hasEditPermission(Identity userIdentity, String entityId) throws ObjectNotFoundException { + if (StringUtils.contains(entityId, SITE_PREFIX)) { + SiteKey siteKey = getSiteKey(entityId); + return layoutAclService.canEditSite(siteKey, getUsername(userIdentity)); + } else { + PageKey pageKey = getPageKey(entityId); + return pageKey != null && layoutAclService.canEditPage(pageKey, getUsername(userIdentity)); + } + } + + @Override + public boolean hasAccessPermission(Identity userIdentity, String entityId) throws ObjectNotFoundException { + if (StringUtils.contains(entityId, SITE_PREFIX)) { + SiteKey siteKey = getSiteKey(entityId); + return siteKey.getType() == SiteType.GROUP_TEMPLATE + || siteKey.getType() == SiteType.PORTAL_TEMPLATE + || layoutAclService.canViewSite(siteKey, getUsername(userIdentity)); + } else { + PageKey pageKey = getPageKey(entityId); + if (pageKey == null) { + return false; + } else { + SiteKey siteKey = pageKey.getSite(); + return siteKey.getType() == SiteType.GROUP_TEMPLATE + || siteKey.getType() == SiteType.PORTAL_TEMPLATE + || layoutAclService.canViewPage(pageKey, getUsername(userIdentity)); + } + } + } + + @Override + public long getAudienceId(String objectId) throws ObjectNotFoundException { + return 0; + } + + @Override + public long getSpaceId(String objectId) throws ObjectNotFoundException { + return 0; + } + + private PageKey getPageKey(String entityId) { + String pageUuid = entityId.split("_")[0].replace(PAGE_PREFIX, ""); + long pageId = StringUtils.isNumeric(pageUuid) ? Long.parseLong(pageUuid) : 0; + if (pageId == 0) { + pageUuid = entityId.replace(PAGE_PREFIX, "").split("_")[0]; + pageId = StringUtils.isNumeric(pageUuid) ? Long.parseLong(pageUuid) : 0; + } + if (pageId == 0) { + return null; + } else { + Page page = layoutService.getPage(pageId); + return page == null ? null : page.getPageKey(); + } + } + + private SiteKey getSiteKey(String entityId) { + String siteUuid = entityId.replace(SITE_PREFIX, "").split("_")[0]; + long siteId = StringUtils.isNumeric(siteUuid) ? Long.parseLong(siteUuid) : 0; + PortalConfig site = layoutService.getPortalConfig(siteId); + return new SiteKey(site.getType(), site.getName()); + } + + private String getUsername(Identity userIdentity) { + return userIdentity == null ? null : userIdentity.getUserId(); + } + +} diff --git a/layout-service/src/main/java/io/meeds/layout/plugin/attachment/LayoutBackgroundAttachmentPlugin.java b/layout-service/src/main/java/io/meeds/layout/plugin/attachment/LayoutBackgroundAttachmentPlugin.java index 8df1cb348..f6758f6af 100644 --- a/layout-service/src/main/java/io/meeds/layout/plugin/attachment/LayoutBackgroundAttachmentPlugin.java +++ b/layout-service/src/main/java/io/meeds/layout/plugin/attachment/LayoutBackgroundAttachmentPlugin.java @@ -18,121 +18,19 @@ */ package io.meeds.layout.plugin.attachment; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; -import org.exoplatform.commons.exception.ObjectNotFoundException; -import org.exoplatform.portal.config.model.Page; -import org.exoplatform.portal.config.model.PortalConfig; -import org.exoplatform.portal.mop.SiteKey; -import org.exoplatform.portal.mop.SiteType; -import org.exoplatform.portal.mop.page.PageKey; -import org.exoplatform.portal.mop.service.LayoutService; -import org.exoplatform.services.security.Identity; -import org.exoplatform.social.attachment.AttachmentPlugin; -import org.exoplatform.social.attachment.AttachmentService; - -import io.meeds.layout.service.LayoutAclService; - -import jakarta.annotation.PostConstruct; - @Component @Order(Ordered.HIGHEST_PRECEDENCE) -public class LayoutBackgroundAttachmentPlugin extends AttachmentPlugin { - - public static final String OBJECT_TYPE = "containerBackground"; - - private static final String PAGE_PREFIX = "page_"; - - private static final String SITE_PREFIX = "site_"; - - @Autowired - private LayoutAclService layoutAclService; +public class LayoutBackgroundAttachmentPlugin extends AbstractLayoutAttachmentPlugin { - @Autowired - private LayoutService layoutService; - - @Autowired - private AttachmentService attachmentService; - - @PostConstruct - public void init() { - attachmentService.addPlugin(this); - } + public static final String OBJECT_TYPE = "containerBackground"; @Override public String getObjectType() { return OBJECT_TYPE; } - @Override - public boolean hasEditPermission(Identity userIdentity, String entityId) throws ObjectNotFoundException { - if (StringUtils.contains(entityId, SITE_PREFIX)) { - SiteKey siteKey = getSiteKey(entityId); - return layoutAclService.canEditSite(siteKey, getUsername(userIdentity)); - } else { - PageKey pageKey = getPageKey(entityId); - return pageKey != null && layoutAclService.canEditPage(pageKey, getUsername(userIdentity)); - } - } - - @Override - public boolean hasAccessPermission(Identity userIdentity, String entityId) throws ObjectNotFoundException { - if (StringUtils.contains(entityId, SITE_PREFIX)) { - SiteKey siteKey = getSiteKey(entityId); - return siteKey.getType() == SiteType.GROUP_TEMPLATE - || siteKey.getType() == SiteType.PORTAL_TEMPLATE - || layoutAclService.canViewSite(siteKey, getUsername(userIdentity)); - } else { - PageKey pageKey = getPageKey(entityId); - if (pageKey == null) { - return false; - } else { - SiteKey siteKey = pageKey.getSite(); - return siteKey.getType() == SiteType.GROUP_TEMPLATE - || siteKey.getType() == SiteType.PORTAL_TEMPLATE - || layoutAclService.canViewPage(pageKey, getUsername(userIdentity)); - } - } - } - - @Override - public long getAudienceId(String objectId) throws ObjectNotFoundException { - return 0; - } - - @Override - public long getSpaceId(String objectId) throws ObjectNotFoundException { - return 0; - } - - private PageKey getPageKey(String entityId) { - String pageUuid = entityId.split("_")[0].replace(PAGE_PREFIX, ""); - long pageId = StringUtils.isNumeric(pageUuid) ? Long.parseLong(pageUuid) : 0; - if (pageId == 0) { - pageUuid = entityId.replace(PAGE_PREFIX, "").split("_")[0]; - pageId = StringUtils.isNumeric(pageUuid) ? Long.parseLong(pageUuid) : 0; - } - if (pageId == 0) { - return null; - } else { - Page page = layoutService.getPage(pageId); - return page == null ? null : page.getPageKey(); - } - } - - private SiteKey getSiteKey(String entityId) { - String siteUuid = entityId.replace(SITE_PREFIX, "").split("_")[0]; - long siteId = StringUtils.isNumeric(siteUuid) ? Long.parseLong(siteUuid) : 0; - PortalConfig site = layoutService.getPortalConfig(siteId); - return new SiteKey(site.getType(), site.getName()); - } - - private String getUsername(Identity userIdentity) { - return userIdentity == null ? null : userIdentity.getUserId(); - } - } diff --git a/layout-service/src/main/java/io/meeds/layout/plugin/attachment/LayoutTextBodyBackgroundAttachmentPlugin.java b/layout-service/src/main/java/io/meeds/layout/plugin/attachment/LayoutTextBodyBackgroundAttachmentPlugin.java new file mode 100644 index 000000000..4ef0e4f25 --- /dev/null +++ b/layout-service/src/main/java/io/meeds/layout/plugin/attachment/LayoutTextBodyBackgroundAttachmentPlugin.java @@ -0,0 +1,36 @@ +/** + * This file is part of the Meeds project (https://meeds.io/). + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package io.meeds.layout.plugin.attachment; + +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +public class LayoutTextBodyBackgroundAttachmentPlugin extends AbstractLayoutAttachmentPlugin { + + public static final String OBJECT_TYPE = "containerTextBodyBackground"; + + @Override + public String getObjectType() { + return OBJECT_TYPE; + } + +} diff --git a/layout-service/src/main/java/io/meeds/layout/plugin/attachment/LayoutTextHeaderBackgroundAttachmentPlugin.java b/layout-service/src/main/java/io/meeds/layout/plugin/attachment/LayoutTextHeaderBackgroundAttachmentPlugin.java new file mode 100644 index 000000000..080a587fc --- /dev/null +++ b/layout-service/src/main/java/io/meeds/layout/plugin/attachment/LayoutTextHeaderBackgroundAttachmentPlugin.java @@ -0,0 +1,36 @@ +/** + * This file is part of the Meeds project (https://meeds.io/). + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package io.meeds.layout.plugin.attachment; + +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +public class LayoutTextHeaderBackgroundAttachmentPlugin extends AbstractLayoutAttachmentPlugin { + + public static final String OBJECT_TYPE = "containerTextHeaderBackground"; + + @Override + public String getObjectType() { + return OBJECT_TYPE; + } + +} diff --git a/layout-service/src/main/java/io/meeds/layout/plugin/attachment/LayoutTextSubtitleBackgroundAttachmentPlugin.java b/layout-service/src/main/java/io/meeds/layout/plugin/attachment/LayoutTextSubtitleBackgroundAttachmentPlugin.java new file mode 100644 index 000000000..3f578a750 --- /dev/null +++ b/layout-service/src/main/java/io/meeds/layout/plugin/attachment/LayoutTextSubtitleBackgroundAttachmentPlugin.java @@ -0,0 +1,36 @@ +/** + * This file is part of the Meeds project (https://meeds.io/). + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package io.meeds.layout.plugin.attachment; + +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +public class LayoutTextSubtitleBackgroundAttachmentPlugin extends AbstractLayoutAttachmentPlugin { + + public static final String OBJECT_TYPE = "containerTextSubtitleBackground"; + + @Override + public String getObjectType() { + return OBJECT_TYPE; + } + +} diff --git a/layout-service/src/main/java/io/meeds/layout/plugin/attachment/LayoutTextTitleBackgroundAttachmentPlugin.java b/layout-service/src/main/java/io/meeds/layout/plugin/attachment/LayoutTextTitleBackgroundAttachmentPlugin.java new file mode 100644 index 000000000..69a72cb7c --- /dev/null +++ b/layout-service/src/main/java/io/meeds/layout/plugin/attachment/LayoutTextTitleBackgroundAttachmentPlugin.java @@ -0,0 +1,36 @@ +/** + * This file is part of the Meeds project (https://meeds.io/). + * + * Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package io.meeds.layout.plugin.attachment; + +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +public class LayoutTextTitleBackgroundAttachmentPlugin extends AbstractLayoutAttachmentPlugin { + + public static final String OBJECT_TYPE = "containerTextTitleBackground"; + + @Override + public String getObjectType() { + return OBJECT_TYPE; + } + +} diff --git a/layout-service/src/main/java/io/meeds/layout/service/ContainerLayoutService.java b/layout-service/src/main/java/io/meeds/layout/service/ContainerLayoutService.java index 2314dadd1..7de24f11d 100644 --- a/layout-service/src/main/java/io/meeds/layout/service/ContainerLayoutService.java +++ b/layout-service/src/main/java/io/meeds/layout/service/ContainerLayoutService.java @@ -25,8 +25,11 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; import java.util.Objects; import java.util.UUID; +import java.util.function.BiConsumer; +import java.util.function.Function; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.io.FileUtils; @@ -56,6 +59,10 @@ import org.exoplatform.upload.UploadService; import io.meeds.layout.model.LayoutModel; +import io.meeds.layout.plugin.attachment.LayoutTextBodyBackgroundAttachmentPlugin; +import io.meeds.layout.plugin.attachment.LayoutTextHeaderBackgroundAttachmentPlugin; +import io.meeds.layout.plugin.attachment.LayoutTextSubtitleBackgroundAttachmentPlugin; +import io.meeds.layout.plugin.attachment.LayoutTextTitleBackgroundAttachmentPlugin; import io.meeds.layout.util.EntityMapper; import lombok.SneakyThrows; @@ -63,7 +70,24 @@ @Service public class ContainerLayoutService { - private static final String CONTAINER_BACKGROUND_IMAGE_URI = String.format("/%s/", OBJECT_TYPE); + private record TextBackgroundImageField(Function getter, + BiConsumer setter, + String objectType) { + } + + private static final List TEXT_BACKGROUND_IMAGE_FIELDS = List.of( + new TextBackgroundImageField(ModelStyle::getTextTitleBackgroundImage, + ModelStyle::setTextTitleBackgroundImage, + LayoutTextTitleBackgroundAttachmentPlugin.OBJECT_TYPE), + new TextBackgroundImageField(ModelStyle::getTextHeaderBackgroundImage, + ModelStyle::setTextHeaderBackgroundImage, + LayoutTextHeaderBackgroundAttachmentPlugin.OBJECT_TYPE), + new TextBackgroundImageField(ModelStyle::getTextBackgroundImage, + ModelStyle::setTextBackgroundImage, + LayoutTextBodyBackgroundAttachmentPlugin.OBJECT_TYPE), + new TextBackgroundImageField(ModelStyle::getTextSubtitleBackgroundImage, + ModelStyle::setTextSubtitleBackgroundImage, + LayoutTextSubtitleBackgroundAttachmentPlugin.OBJECT_TYPE)); @Autowired private AttachmentService attachmentService; @@ -138,11 +162,30 @@ public void impersonateContainer(Container container, Page page) throws Exceptio appBackgroundStyle.setBackgroundImage(clonedBackgroundImageUrl); } } + if (cssStyle != null) { + for (TextBackgroundImageField field : TEXT_BACKGROUND_IMAGE_FIELDS) { + String textBackgroundImage = field.getter().apply(cssStyle); + if (StringUtils.isNotBlank(textBackgroundImage)) { + String clonedBackgroundImageUrl = null; + try { + clonedBackgroundImageUrl = cloneBackgroundUrl(container, page, textBackgroundImage, field.objectType()); + } finally { + // Even in case of exception thrown, set null + field.setter().accept(cssStyle, clonedBackgroundImageUrl); + } + } + } + } } public String cloneBackgroundUrl(ModelObject container, Page page, String backgroundImageUrl) throws Exception { // NOSONAR + return cloneBackgroundUrl(container, page, backgroundImageUrl, OBJECT_TYPE); + } + + public String cloneBackgroundUrl(ModelObject container, Page page, String backgroundImageUrl, String objectType) throws Exception { // NOSONAR String clonedBackgroundImageUrl = null; - if (StringUtils.contains(backgroundImageUrl, CONTAINER_BACKGROUND_IMAGE_URI)) { + String backgroundImageUri = String.format("/%s/", objectType); + if (StringUtils.contains(backgroundImageUrl, backgroundImageUri)) { String[] backgroundImageUrlParts = backgroundImageUrl.split("/"); String fileIdString = backgroundImageUrlParts[backgroundImageUrlParts.length - 1]; if (StringUtils.isNotBlank(fileIdString) && NumberUtils.isCreatable(fileIdString)) { @@ -150,9 +193,9 @@ public String cloneBackgroundUrl(ModelObject container, Page page, String backgr if (file != null) { String objectId = getObjectId(container, page); UploadResource uploadResource = createUploadResource(file); - ObjectAttachmentDetail attachment = saveAttachment(objectId, uploadResource); + ObjectAttachmentDetail attachment = saveAttachment(objectId, uploadResource, objectType); if (attachment != null) { - clonedBackgroundImageUrl = buildBackgroundUrl(objectId, attachment); + clonedBackgroundImageUrl = buildBackgroundUrl(objectId, attachment, objectType); } } } @@ -166,22 +209,27 @@ private String getObjectId(ModelObject container, Page page) { container.getStorageId()); } - private String buildBackgroundUrl(String objectId, ObjectAttachmentDetail attachment) { + private String buildBackgroundUrl(String objectId, ObjectAttachmentDetail attachment, String objectType) { return String.format("/portal/rest/v1/social/attachments/%s/%s/%s", - OBJECT_TYPE, + objectType, objectId, attachment.getId()); } @SneakyThrows private ObjectAttachmentDetail saveAttachment(String objectId, UploadResource uploadResource) { + return saveAttachment(objectId, uploadResource, OBJECT_TYPE); + } + + @SneakyThrows + private ObjectAttachmentDetail saveAttachment(String objectId, UploadResource uploadResource, String objectType) { UploadedAttachmentDetail attachmentDetail = new UploadedAttachmentDetail(uploadResource); attachmentService.saveAttachment(attachmentDetail, - OBJECT_TYPE, + objectType, objectId, null, getSuperUserIdentityId()); - ObjectAttachmentList attachmentList = attachmentService.getAttachments(OBJECT_TYPE, objectId); + ObjectAttachmentList attachmentList = attachmentService.getAttachments(objectType, objectId); if (attachmentList != null && CollectionUtils.isNotEmpty(attachmentList.getAttachments())) { return attachmentList.getAttachments().get(0); } else { diff --git a/layout-service/src/main/java/io/meeds/layout/util/DatabindUtils.java b/layout-service/src/main/java/io/meeds/layout/util/DatabindUtils.java index da11a5639..be204c940 100644 --- a/layout-service/src/main/java/io/meeds/layout/util/DatabindUtils.java +++ b/layout-service/src/main/java/io/meeds/layout/util/DatabindUtils.java @@ -20,6 +20,10 @@ import io.meeds.layout.model.LayoutModel; import io.meeds.layout.plugin.attachment.LayoutBackgroundAttachmentPlugin; +import io.meeds.layout.plugin.attachment.LayoutTextBodyBackgroundAttachmentPlugin; +import io.meeds.layout.plugin.attachment.LayoutTextHeaderBackgroundAttachmentPlugin; +import io.meeds.layout.plugin.attachment.LayoutTextSubtitleBackgroundAttachmentPlugin; +import io.meeds.layout.plugin.attachment.LayoutTextTitleBackgroundAttachmentPlugin; import lombok.SneakyThrows; import org.apache.commons.codec.binary.Base64; import org.apache.commons.collections4.CollectionUtils; @@ -35,8 +39,11 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.util.List; import java.util.Random; import java.util.UUID; +import java.util.function.BiConsumer; +import java.util.function.Function; public class DatabindUtils { @@ -46,6 +53,25 @@ private DatabindUtils() { private static final Random RANDOM = new Random(); + private record TextBackgroundImageField(Function getter, + BiConsumer setter, + String objectType) { + } + + private static final List TEXT_BACKGROUND_IMAGE_FIELDS = List.of( + new TextBackgroundImageField(LayoutModel::getTextTitleBackgroundImage, + LayoutModel::setTextTitleBackgroundImage, + LayoutTextTitleBackgroundAttachmentPlugin.OBJECT_TYPE), + new TextBackgroundImageField(LayoutModel::getTextHeaderBackgroundImage, + LayoutModel::setTextHeaderBackgroundImage, + LayoutTextHeaderBackgroundAttachmentPlugin.OBJECT_TYPE), + new TextBackgroundImageField(LayoutModel::getTextBackgroundImage, + LayoutModel::setTextBackgroundImage, + LayoutTextBodyBackgroundAttachmentPlugin.OBJECT_TYPE), + new TextBackgroundImageField(LayoutModel::getTextSubtitleBackgroundImage, + LayoutModel::setTextSubtitleBackgroundImage, + LayoutTextSubtitleBackgroundAttachmentPlugin.OBJECT_TYPE)); + @SneakyThrows public static void retrieveBackgroundImages(LayoutModel layout, FileService fileService) { if (layout == null) { @@ -74,6 +100,19 @@ public static void retrieveBackgroundImages(LayoutModel layout, FileService file } } + for (TextBackgroundImageField field : TEXT_BACKGROUND_IMAGE_FIELDS) { + String textBackgroundImage = field.getter().apply(layout); + if (textBackgroundImage != null && !textBackgroundImage.isEmpty()) { + String[] parts = textBackgroundImage.split("/"); + String lastPart = parts[parts.length - 1]; + + FileItem file = fileService.getFile(Long.parseLong(lastPart)); + if (file != null) { + field.setter().accept(layout, Base64.encodeBase64String(file.getAsByte())); + } + } + } + if (layout.getChildren() != null) { for (LayoutModel child : layout.getChildren()) { retrieveBackgroundImages(child, fileService); @@ -91,6 +130,9 @@ public static void saveAppBackgroundImages(long pageTemplateId, } attachmentService.deleteAttachments(LayoutBackgroundAttachmentPlugin.OBJECT_TYPE, String.valueOf(pageTemplateId)); + for (TextBackgroundImageField field : TEXT_BACKGROUND_IMAGE_FIELDS) { + attachmentService.deleteAttachments(field.objectType(), String.valueOf(pageTemplateId)); + } saveAppBackgroundImagesInternal(pageTemplateId, layout, attachmentService, userIdentityId); } @@ -106,10 +148,13 @@ private static void saveAppBackgroundImagesInternal(long pageTemplateId, if (appBackgroundImage != null && !appBackgroundImage.isEmpty()) { ObjectAttachmentDetail attachment = saveAppBackgroundImage(pageTemplateId, Base64.decodeBase64(appBackgroundImage), + LayoutBackgroundAttachmentPlugin.OBJECT_TYPE, attachmentService, userIdentityId); if (attachment != null) { - layout.setAppBackgroundImage(buildBackgroundUrl(String.valueOf(pageTemplateId), attachment)); + layout.setAppBackgroundImage(buildBackgroundUrl(LayoutBackgroundAttachmentPlugin.OBJECT_TYPE, + String.valueOf(pageTemplateId), + attachment)); } } @@ -117,10 +162,27 @@ private static void saveAppBackgroundImagesInternal(long pageTemplateId, if (backgroundImage != null && !backgroundImage.isEmpty()) { ObjectAttachmentDetail attachment = saveAppBackgroundImage(pageTemplateId, Base64.decodeBase64(backgroundImage), + LayoutBackgroundAttachmentPlugin.OBJECT_TYPE, attachmentService, userIdentityId); if (attachment != null) { - layout.setBackgroundImage(buildBackgroundUrl(String.valueOf(pageTemplateId), attachment)); + layout.setBackgroundImage(buildBackgroundUrl(LayoutBackgroundAttachmentPlugin.OBJECT_TYPE, + String.valueOf(pageTemplateId), + attachment)); + } + } + + for (TextBackgroundImageField field : TEXT_BACKGROUND_IMAGE_FIELDS) { + String textBackgroundImage = field.getter().apply(layout); + if (textBackgroundImage != null && !textBackgroundImage.isEmpty()) { + ObjectAttachmentDetail attachment = saveAppBackgroundImage(pageTemplateId, + Base64.decodeBase64(textBackgroundImage), + field.objectType(), + attachmentService, + userIdentityId); + if (attachment != null) { + field.setter().accept(layout, buildBackgroundUrl(field.objectType(), String.valueOf(pageTemplateId), attachment)); + } } } if (layout.getChildren() != null) { @@ -131,14 +193,19 @@ private static void saveAppBackgroundImagesInternal(long pageTemplateId, } public static String buildBackgroundUrl(String objectId, ObjectAttachmentDetail attachment) { + return buildBackgroundUrl(LayoutBackgroundAttachmentPlugin.OBJECT_TYPE, objectId, attachment); + } + + public static String buildBackgroundUrl(String objectType, String objectId, ObjectAttachmentDetail attachment) { return String.format("/portal/rest/v1/social/attachments/%s/%s/%s", - LayoutBackgroundAttachmentPlugin.OBJECT_TYPE, + objectType, objectId, attachment.getId()); } private static ObjectAttachmentDetail saveAppBackgroundImage(long templateId, byte[] backgroundImageBytes, + String objectType, AttachmentService attachmentService, long userIdentityId) { @@ -156,12 +223,11 @@ private static ObjectAttachmentDetail saveAppBackgroundImage(long templateId, String objectId = String.valueOf(templateId); attachmentService.saveAttachment(uploadedAttachmentDetail, - LayoutBackgroundAttachmentPlugin.OBJECT_TYPE, + objectType, objectId, null, userIdentityId); - ObjectAttachmentList attachmentList = attachmentService.getAttachments(LayoutBackgroundAttachmentPlugin.OBJECT_TYPE, - objectId); + ObjectAttachmentList attachmentList = attachmentService.getAttachments(objectType, objectId); if (attachmentList != null && CollectionUtils.isNotEmpty(attachmentList.getAttachments())) { return attachmentList.getAttachments().getFirst(); } else { diff --git a/layout-service/src/main/java/io/meeds/layout/util/EntityMapper.java b/layout-service/src/main/java/io/meeds/layout/util/EntityMapper.java index 80f004a28..4442cfd34 100644 --- a/layout-service/src/main/java/io/meeds/layout/util/EntityMapper.java +++ b/layout-service/src/main/java/io/meeds/layout/util/EntityMapper.java @@ -167,7 +167,15 @@ private static ModelStyle mapToStyle(LayoutModel layoutModel) { || StringUtils.isNotBlank(layoutModel.getTextTitleColor()) || StringUtils.isNotBlank(layoutModel.getTextColor()) || StringUtils.isNotBlank(layoutModel.getTextHeaderColor()) - || StringUtils.isNotBlank(layoutModel.getTextSubtitleColor()); + || StringUtils.isNotBlank(layoutModel.getTextSubtitleColor()) + || StringUtils.isNotBlank(layoutModel.getTextTitleBackgroundColor()) + || StringUtils.isNotBlank(layoutModel.getTextTitleBackgroundImage()) + || StringUtils.isNotBlank(layoutModel.getTextHeaderBackgroundColor()) + || StringUtils.isNotBlank(layoutModel.getTextHeaderBackgroundImage()) + || StringUtils.isNotBlank(layoutModel.getTextBackgroundColor()) + || StringUtils.isNotBlank(layoutModel.getTextBackgroundImage()) + || StringUtils.isNotBlank(layoutModel.getTextSubtitleBackgroundColor()) + || StringUtils.isNotBlank(layoutModel.getTextSubtitleBackgroundImage()); if (hasStyle) { cssStyle = new ModelStyle(); cssStyle.setBorderColor(layoutModel.getBorderColor()); @@ -203,6 +211,38 @@ private static ModelStyle mapToStyle(LayoutModel layoutModel) { cssStyle.setTextSubtitleFontSize(layoutModel.getTextSubtitleFontSize()); cssStyle.setTextSubtitleFontWeight(layoutModel.getTextSubtitleFontWeight()); cssStyle.setTextSubtitleFontStyle(layoutModel.getTextSubtitleFontStyle()); + cssStyle.setTextTitleBackgroundColor(layoutModel.getTextTitleBackgroundColor()); + cssStyle.setTextTitleBackgroundImage(layoutModel.getTextTitleBackgroundImage()); + cssStyle.setTextTitleBackgroundEffect(layoutModel.getTextTitleBackgroundEffect()); + cssStyle.setTextTitleBackgroundPosition(layoutModel.getTextTitleBackgroundPosition()); + cssStyle.setTextTitleBackgroundSize(layoutModel.getTextTitleBackgroundSize()); + cssStyle.setTextTitleBackgroundRepeat(layoutModel.getTextTitleBackgroundRepeat()); + cssStyle.setTextTitleBackgroundPadding(layoutModel.getTextTitleBackgroundPadding()); + cssStyle.setTextTitleBackgroundRadius(layoutModel.getTextTitleBackgroundRadius()); + cssStyle.setTextHeaderBackgroundColor(layoutModel.getTextHeaderBackgroundColor()); + cssStyle.setTextHeaderBackgroundImage(layoutModel.getTextHeaderBackgroundImage()); + cssStyle.setTextHeaderBackgroundEffect(layoutModel.getTextHeaderBackgroundEffect()); + cssStyle.setTextHeaderBackgroundPosition(layoutModel.getTextHeaderBackgroundPosition()); + cssStyle.setTextHeaderBackgroundSize(layoutModel.getTextHeaderBackgroundSize()); + cssStyle.setTextHeaderBackgroundRepeat(layoutModel.getTextHeaderBackgroundRepeat()); + cssStyle.setTextHeaderBackgroundPadding(layoutModel.getTextHeaderBackgroundPadding()); + cssStyle.setTextHeaderBackgroundRadius(layoutModel.getTextHeaderBackgroundRadius()); + cssStyle.setTextBackgroundColor(layoutModel.getTextBackgroundColor()); + cssStyle.setTextBackgroundImage(layoutModel.getTextBackgroundImage()); + cssStyle.setTextBackgroundEffect(layoutModel.getTextBackgroundEffect()); + cssStyle.setTextBackgroundPosition(layoutModel.getTextBackgroundPosition()); + cssStyle.setTextBackgroundSize(layoutModel.getTextBackgroundSize()); + cssStyle.setTextBackgroundRepeat(layoutModel.getTextBackgroundRepeat()); + cssStyle.setTextBackgroundPadding(layoutModel.getTextBackgroundPadding()); + cssStyle.setTextBackgroundRadius(layoutModel.getTextBackgroundRadius()); + cssStyle.setTextSubtitleBackgroundColor(layoutModel.getTextSubtitleBackgroundColor()); + cssStyle.setTextSubtitleBackgroundImage(layoutModel.getTextSubtitleBackgroundImage()); + cssStyle.setTextSubtitleBackgroundEffect(layoutModel.getTextSubtitleBackgroundEffect()); + cssStyle.setTextSubtitleBackgroundPosition(layoutModel.getTextSubtitleBackgroundPosition()); + cssStyle.setTextSubtitleBackgroundSize(layoutModel.getTextSubtitleBackgroundSize()); + cssStyle.setTextSubtitleBackgroundRepeat(layoutModel.getTextSubtitleBackgroundRepeat()); + cssStyle.setTextSubtitleBackgroundPadding(layoutModel.getTextSubtitleBackgroundPadding()); + cssStyle.setTextSubtitleBackgroundRadius(layoutModel.getTextSubtitleBackgroundRadius()); } return cssStyle; } diff --git a/layout-service/src/test/java/io/meeds/layout/plugin/attachment/LayoutBackgroundAttachmentPluginTest.java b/layout-service/src/test/java/io/meeds/layout/plugin/attachment/LayoutBackgroundAttachmentPluginTest.java new file mode 100644 index 000000000..a86cd52c1 --- /dev/null +++ b/layout-service/src/test/java/io/meeds/layout/plugin/attachment/LayoutBackgroundAttachmentPluginTest.java @@ -0,0 +1,178 @@ +/** + * This file is part of the Meeds project (https://meeds.io/). + * + * Copyright (C) 2020 - 2025 Meeds Association contact@meeds.io + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package io.meeds.layout.plugin.attachment; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import org.exoplatform.portal.config.model.Page; +import org.exoplatform.portal.config.model.PortalConfig; +import org.exoplatform.portal.mop.SiteKey; +import org.exoplatform.portal.mop.SiteType; +import org.exoplatform.portal.mop.page.PageKey; +import org.exoplatform.portal.mop.service.LayoutService; +import org.exoplatform.services.security.Identity; +import org.exoplatform.social.attachment.AttachmentService; + +import io.meeds.layout.service.LayoutAclService; + +import lombok.SneakyThrows; + +@SpringBootTest(classes = { + LayoutBackgroundAttachmentPlugin.class, +}) +@ExtendWith(MockitoExtension.class) +public class LayoutBackgroundAttachmentPluginTest { + + private static final String PAGE_ENTITY_ID = "page_1"; + + private static final String SITE_ENTITY_ID = "site_2"; + + @Mock + private Identity userIdentity; + + @Mock + private Page page; + + @Mock + private PageKey pageKey; + + @Mock + private PortalConfig portalConfig; + + @MockBean + private AttachmentService attachmentService; + + @MockBean + private LayoutAclService layoutAclService; + + @MockBean + private LayoutService layoutService; + + @Autowired + private LayoutBackgroundAttachmentPlugin attachmentPlugin; + + @Test + void getObjectType() { + assertEquals("containerBackground", attachmentPlugin.getObjectType()); + assertEquals(LayoutBackgroundAttachmentPlugin.OBJECT_TYPE, attachmentPlugin.getObjectType()); + } + + @Test + @SneakyThrows + void hasEditPermissionOnPage() { + when(userIdentity.getUserId()).thenReturn("test"); + when(layoutService.getPage(1l)).thenReturn(page); + when(page.getPageKey()).thenReturn(pageKey); + + assertFalse(attachmentPlugin.hasEditPermission(userIdentity, PAGE_ENTITY_ID)); + + when(layoutAclService.canEditPage(pageKey, "test")).thenReturn(true); + assertTrue(attachmentPlugin.hasEditPermission(userIdentity, PAGE_ENTITY_ID)); + } + + @Test + @SneakyThrows + void hasEditPermissionOnPageNotFound() { + when(layoutService.getPage(1l)).thenReturn(null); + + assertFalse(attachmentPlugin.hasEditPermission(userIdentity, PAGE_ENTITY_ID)); + } + + @Test + @SneakyThrows + void hasEditPermissionOnSite() { + when(userIdentity.getUserId()).thenReturn("test"); + when(layoutService.getPortalConfig(2l)).thenReturn(portalConfig); + when(portalConfig.getType()).thenReturn(SiteType.PORTAL.getName()); + when(portalConfig.getName()).thenReturn("site2"); + + assertFalse(attachmentPlugin.hasEditPermission(userIdentity, SITE_ENTITY_ID)); + + when(layoutAclService.canEditSite(new SiteKey(SiteType.PORTAL.getName(), "site2"), "test")).thenReturn(true); + assertTrue(attachmentPlugin.hasEditPermission(userIdentity, SITE_ENTITY_ID)); + } + + @Test + @SneakyThrows + void hasAccessPermissionOnPage() { + when(layoutService.getPage(1l)).thenReturn(page); + when(page.getPageKey()).thenReturn(pageKey); + when(pageKey.getSite()).thenReturn(new SiteKey(SiteType.PORTAL.getName(), "site2")); + + assertFalse(attachmentPlugin.hasAccessPermission(userIdentity, PAGE_ENTITY_ID)); + + when(layoutAclService.canViewPage(pageKey, null)).thenReturn(true); + assertTrue(attachmentPlugin.hasAccessPermission(userIdentity, PAGE_ENTITY_ID)); + } + + @Test + @SneakyThrows + void hasAccessPermissionOnPageNotFound() { + when(layoutService.getPage(1l)).thenReturn(null); + + assertFalse(attachmentPlugin.hasAccessPermission(userIdentity, PAGE_ENTITY_ID)); + } + + @Test + @SneakyThrows + void hasAccessPermissionOnSite() { + when(layoutService.getPortalConfig(2l)).thenReturn(portalConfig); + when(portalConfig.getType()).thenReturn(SiteType.PORTAL.getName()); + when(portalConfig.getName()).thenReturn("site2"); + + assertFalse(attachmentPlugin.hasAccessPermission(userIdentity, SITE_ENTITY_ID)); + + when(layoutAclService.canViewSite(new SiteKey(SiteType.PORTAL.getName(), "site2"), null)).thenReturn(true); + assertTrue(attachmentPlugin.hasAccessPermission(userIdentity, SITE_ENTITY_ID)); + } + + @Test + @SneakyThrows + void hasAccessPermissionOnTemplateSite() { + when(layoutService.getPortalConfig(2l)).thenReturn(portalConfig); + when(portalConfig.getType()).thenReturn(SiteType.PORTAL_TEMPLATE.getName()); + when(portalConfig.getName()).thenReturn("site2"); + + assertTrue(attachmentPlugin.hasAccessPermission(userIdentity, SITE_ENTITY_ID)); + } + + @Test + @SneakyThrows + void getAudienceId() { + assertEquals(0l, attachmentPlugin.getAudienceId(PAGE_ENTITY_ID)); + } + + @Test + @SneakyThrows + void getSpaceId() { + assertEquals(0l, attachmentPlugin.getSpaceId(PAGE_ENTITY_ID)); + } + +} 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 5ce5017cb..7f8bbdc93 100644 --- a/layout-webapp/src/main/resources/locale/portlet/LayoutEditor_en.properties +++ b/layout-webapp/src/main/resources/locale/portlet/LayoutEditor_en.properties @@ -29,8 +29,13 @@ layout.moveCell=Move Cell layout.resizeCell=Resize Cell layout.selectApplicationCategoryTitle=Choose a Category layout.addApplicationFromCategoryTitle=Add Application from {0} +layout.filterApplication.label=Search for an app +layout.filterApplication.placeholder=Filter by name or description layout.editApplication=Edit Application layout.deleteApplication=Delete Application +layout.deleteApplicationConfirmTitle=Removal Confirmation +layout.deleteApplicationConfirmMessage=You are about to remove this app from the page. If you confirm it, you will lose any custom content added to it. Do you confirm it? +layout.confirm=Confirm layout.editApplicationTitleNoCategory=Edit {0} layout.editPortletTitleNoCategory=Edit Properties for {0} layout.applicationHeight=Edit Application @@ -85,6 +90,11 @@ layout.scrollStickyBehavior=Behaviour when scrolling down layout.scrollNoSticky=No sticky behaviour layout.scrollStickyLastApp=Last app in the column layout.mobileView=Mobile View +layout.viewOptions=View options +layout.sectionDisplayBoth=Display for both desktop & mobile view +layout.sectionHiddenOnDesktop=Hide when using desktop +layout.appDisplayBoth=Display for both desktop & mobile view +layout.appHiddenOnDesktop=Hide when using desktop layout.listAppsInRows=List apps in rows layout.listAppsInColumns=List apps in columns layout.saveAsTemplate=Save as template @@ -406,6 +416,16 @@ layout.imagePositionBottomRight=Bottom right layout.gradient=Gradient layout.gradientFrom=From layout.gradientTo=To +layout.linearGradient=Linear Gradient +layout.radialGradient=Radial Gradient +layout.angularGradient=Angular Gradient +layout.gradientDirectionTopToBottom=Top to Bottom +layout.gradientDirectionLeftToRight=Left to Right +layout.gradientCornerTopLeft=Top Left +layout.gradientCornerTopRight=Top Right +layout.gradientCornerBottomRight=Bottom Right +layout.gradientCornerBottomLeft=Bottom Left +layout.gradientRatio=Ratio layout.saveDraft=Save layout.saveDraftTooltip=Save page as draft layout.saveSiteDraftTooltip=Save site as draft @@ -427,7 +447,8 @@ layout.textColorTitle=Title layout.textColorHeader=Header layout.textColorBody=Body layout.textColorSubtitle=Subtitle -layout.color=Color +layout.textBackground=Text Background +layout.color=Solid Color layout.applicationStyling=Application Styling layout.globalPageDesign=Global Page Design layout.globalPageBackground=Page Background diff --git a/layout-webapp/src/main/webapp/skin/less/editor.less b/layout-webapp/src/main/webapp/skin/less/editor.less index 6bb1fe9a6..723462cd7 100644 --- a/layout-webapp/src/main/webapp/skin/less/editor.less +++ b/layout-webapp/src/main/webapp/skin/less/editor.less @@ -61,6 +61,10 @@ .layout-mobile-columns(); } +.v-application .layout-mobile-view .hidden-md-and-up { + display: block !important; +} + .layout-moving-chosen-container { border: thin solid @secondaryColor !important; } @@ -113,6 +117,16 @@ background-size: 4px 4px !important; } +.layout-color-picker-swatch { + background-image: + linear-gradient(45deg, #bbb 25%, transparent 25%), + linear-gradient(-45deg, #bbb 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, #bbb 75%), + linear-gradient(-45deg, transparent 75%, #bbb 75%); + background-size: 10px 10px; + background-position: 0 0, 0 5px, 5px -5px, -5px 0; +} + .layout-section { .layout-section-hover { diff --git a/layout-webapp/src/main/webapp/vue-app/common-layout/components/content/base/ContainerBase.vue b/layout-webapp/src/main/webapp/vue-app/common-layout/components/content/base/ContainerBase.vue index 348642b5d..e263bbbdd 100644 --- a/layout-webapp/src/main/webapp/vue-app/common-layout/components/content/base/ContainerBase.vue +++ b/layout-webapp/src/main/webapp/vue-app/common-layout/components/content/base/ContainerBase.vue @@ -19,7 +19,7 @@ --> @@ -119,6 +126,12 @@ export default { drawerOpened() { return this.$root.drawerOpened; }, + portlet() { + return this.$root.portlets?.find?.(p => p?.contentId === this.container?.contentId); + }, + editablePortlet() { + return this.portlet?.editable || false; + }, }, watch: { drawerOpened() { @@ -161,6 +174,16 @@ export default { } this.$emit('move-start', event, 'drag', this.container); }, + deleteApplication() { + if (this.editablePortlet) { + this.$refs.deleteApplicationConfirmDialog.open(); + } else { + this.emitDeleteApplication(); + } + }, + emitDeleteApplication() { + this.$root.$emit('layout-delete-application', this.sectionId, this.container); + }, }, }; diff --git a/layout-webapp/src/main/webapp/vue-app/common-layout/components/content/common/SectionMenuBottom.vue b/layout-webapp/src/main/webapp/vue-app/common-layout/components/content/common/SectionMenuBottom.vue index ac973f945..daea17a70 100644 --- a/layout-webapp/src/main/webapp/vue-app/common-layout/components/content/common/SectionMenuBottom.vue +++ b/layout-webapp/src/main/webapp/vue-app/common-layout/components/content/common/SectionMenuBottom.vue @@ -20,7 +20,7 @@ -->