Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b5c8ac4
fix: reset global page design width to 1320 instead of 0/300
srenault-meeds Jul 7, 2026
3cb3662
feat: add gradient options to page background component (task-88418)
srenault-meeds Jul 7, 2026
4501e50
feat: add ratio slider for gradient backgrounds - EXO-88425
srenault-meeds Jul 7, 2026
ef04220
Fix: Ensure transparency display of gradient value - EXO-88418
srenault-meeds Jul 8, 2026
27a9fa9
feat: add confirmation dialog before deleting content blocks - exo-88…
srenault-meeds Jul 9, 2026
c621276
feat: add margins and radius options to background - EXO-88427 (#538)
srenault-meeds Jul 10, 2026
3e192f6
feat: Allow to cancel section move when editing page - EXO-88433 (#534)
srenault-meeds Jul 10, 2026
14dd90c
Feat: Add filter option when adding app while editing page - EXO-8842…
srenault-meeds Jul 10, 2026
8381d9e
feat: Allow to scroll down/up when dragging an app in editing mode - …
srenault-meeds Jul 10, 2026
512efbd
fix: Enhance display of section options when full page mode enabled -…
srenault-meeds Jul 10, 2026
189fa36
fix: Allow space admins to clone/duplicate sections - EXO-88436 (#533)
srenault-meeds Jul 10, 2026
1fcb44a
feat: allow to hide section when using mobile or desktop - EXO-88437 …
srenault-meeds Jul 10, 2026
fdf990d
feat: add left and right margin options to section - EXO-88438
srenault-meeds Jul 9, 2026
4e9be4b
fix: page background default now reflects actual branding value - EXO…
srenault-meeds Aug 4, 2026
bde8b5f
fix: page layout width default now Custom instead of Full window - EX…
srenault-meeds Aug 4, 2026
47f6013
fix: page/site background now reflects branding default when disabled…
srenault-meeds Aug 4, 2026
0c86356
feat: allow to hide an app when using desktop - EXO-88439 (#554)
srenault-meeds Aug 4, 2026
8ec4b46
feat: add background option to application/section text style - EXO-8…
srenault-meeds Aug 5, 2026
ac34033
fix: restore JaCoCo coverage after text-background attachment plugin …
srenault-meeds Aug 5, 2026
093433d
Fix/pr build coverage EXO-88488 (#557)
srenault-meeds Aug 6, 2026
77aa37c
fix: Add Application card + button no longer intercepted by title - E…
srenault-meeds Aug 6, 2026
d877466
fix: Add Application card + button no longer intercepted by title - E…
srenault-meeds Aug 7, 2026
fd6e0fc
fix: text-truncate + Text Background collision sites - EXO-88493 (#561)
srenault-meeds Aug 10, 2026
8d86b04
feat: Delete usage of Background Style in Text Body - EXO-88493
boubaker Aug 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

}
Loading
Loading