diff --git a/jahia-test-module/src/react/server/views/testAbsoluteAreas/TestAbsoluteAreas.tsx b/jahia-test-module/src/react/server/views/testAbsoluteAreas/TestAbsoluteAreas.tsx index d240bcfd..8e5b9922 100644 --- a/jahia-test-module/src/react/server/views/testAbsoluteAreas/TestAbsoluteAreas.tsx +++ b/jahia-test-module/src/react/server/views/testAbsoluteAreas/TestAbsoluteAreas.tsx @@ -7,79 +7,81 @@ jahiaComponent( displayName: "Test Absolute Areas (react)", componentType: "view", }, - () => ( + (_, { currentNode, renderContext }) => ( <>

React JAbsoluteArea test component

Basic Area

- +

Area with allowed types

Area with number of items

- +

Area with areaView

- +
-

Area with subNodesView

-
- +

Area with parent

+
+
-

Area with path

-
- +

Absolute Area with home page

+
+
-

Absolute Area with home page content

-
- +

Absolute Area with custom page (sub-level)

+
+

Non editable area

- -
- -

Absolute area level

-
- +

Area type

- +

Limited absolute area editing

- +

Absolute Area parameters

diff --git a/jahia-test-module/src/react/server/views/testAreaColumns/TestAreaColumns.tsx b/jahia-test-module/src/react/server/views/testAreaColumns/TestAreaColumns.tsx index cfd0f61d..4f18cb3e 100644 --- a/jahia-test-module/src/react/server/views/testAreaColumns/TestAreaColumns.tsx +++ b/jahia-test-module/src/react/server/views/testAreaColumns/TestAreaColumns.tsx @@ -10,10 +10,10 @@ jahiaComponent( return (
- +
- +
); diff --git a/jahia-test-module/src/react/server/views/testAreas/TestAreas.tsx b/jahia-test-module/src/react/server/views/testAreas/TestAreas.tsx index 3cace9af..926b4e8d 100644 --- a/jahia-test-module/src/react/server/views/testAreas/TestAreas.tsx +++ b/jahia-test-module/src/react/server/views/testAreas/TestAreas.tsx @@ -18,7 +18,7 @@ jahiaComponent(

Area with allowed types

- +

Area with number of items

@@ -28,17 +28,12 @@ jahiaComponent(

Area with areaView

- -
- -

Area with subNodesView

-
- +

Area with path

-
- +
+

Non editable area

@@ -46,21 +41,16 @@ jahiaComponent(
-

Area as sub node

-
- -
-

Area type

- +

Area parameters

${junit.version} test + + pl.pragmatists + JUnitParams + 1.1.1 + test + diff --git a/javascript-modules-engine-java/src/main/java/org/jahia/modules/javascript/modules/engine/js/server/RenderHelper.java b/javascript-modules-engine-java/src/main/java/org/jahia/modules/javascript/modules/engine/js/server/RenderHelper.java index a86493f4..82245b96 100644 --- a/javascript-modules-engine-java/src/main/java/org/jahia/modules/javascript/modules/engine/js/server/RenderHelper.java +++ b/javascript-modules-engine-java/src/main/java/org/jahia/modules/javascript/modules/engine/js/server/RenderHelper.java @@ -49,7 +49,6 @@ import javax.servlet.jsp.tagext.TagSupport; import java.io.IOException; import java.io.Serializable; -import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.net.URLDecoder; import java.util.*; @@ -60,9 +59,8 @@ */ public class RenderHelper { private static final Logger logger = LoggerFactory.getLogger(RenderHelper.class); - - private static final Set ABSOLUTEAREA_ALLOWED_ATTRIBUTES = Set.of("name", "path", "areaView", "allowedTypes", "numberOfItems", "subNodesView", "editable", "areaType", "level", "limitedAbsoluteAreaEdit", "parameters"); - private static final Set AREA_ALLOWED_ATTRIBUTES = Set.of("name", "path", "areaView", "allowedTypes", "numberOfItems", "subNodesView", "editable", "areaAsSubNode", "areaType", "parameters"); + private static final Set ABSOLUTEAREA_ALLOWED_ATTRIBUTES = Set.of("name", "parent", "view", "allowedNodeTypes", "numberOfItems", "nodeType", "editable", "areaType", "limitedAbsoluteAreaEdit", "parameters"); + private static final Set AREA_ALLOWED_ATTRIBUTES = Set.of("name", "view", "allowedNodeTypes", "numberOfItems", "nodeType", "editable", "parameters"); private JCRSessionFactory jcrSessionFactory; private JCRTemplate jcrTemplate; @@ -80,6 +78,7 @@ public ProxyObject transformToJsNode(JCRNodeWrapper node, boolean includeChildre /** * Get the render parameters for the given resource + * * @param resource the resource for which to retrieve the render parameters * @return a Map<String,Object> containing the render parameters */ @@ -93,6 +92,7 @@ public ProxyObject getRenderParameters(Resource resource) { * don't need encoding are those defined 'unreserved' in section 2.3 of * the 'URI generic syntax' RFC 2396. Not the entire path string is escaped, * but every individual part (i.e. the slashes are not escaped). + * * @param path the path to encode * @return a String containing the escaped path * @throws NullPointerException if path is null. @@ -104,9 +104,10 @@ public String escapePath(String path) { /** * Find the first displayable node in the given node's hierarchy. A displayable node is a node that has a content * or page template associated with it. - * @param node the node at which to start the resolution + * + * @param node the node at which to start the resolution * @param renderContext the current render context - * @param contextSite the site in which to resolve the template + * @param contextSite the site in which to resolve the template * @return the first displayable {@link JCRNodeWrapper} found in the hierarchy */ public JCRNodeWrapper findDisplayableNode(JCRNodeWrapper node, RenderContext renderContext, @Nullable JCRSiteNode contextSite) { @@ -115,7 +116,8 @@ public JCRNodeWrapper findDisplayableNode(JCRNodeWrapper node, RenderContext ren /** * Returns the node which corresponds to the bound component of the j:bindedComponent property in the specified node. - * @param node the node to get the bound component for + * + * @param node the node to get the bound component for * @param context current render context * @return the bound {@link JCRNodeWrapper} */ @@ -132,48 +134,48 @@ public String renderComponent(Map attr, RenderContext renderContext) return jcrTemplate.doExecuteWithSystemSessionAsUser(user, renderContext.getWorkspace(), renderContext.getMainResource().getLocale(), session -> { - JCRNodeWrapper node = JSNodeMapper.toVirtualNode((Map) attr.get("content"), session, renderContext); - String renderConfig = attr.get("advanceRenderingConfig") != null ? (String) attr.get("advanceRenderingConfig") : ""; - String templateType = attr.get("templateType") != null ? (String) attr.get("templateType") : "html"; - - if ("OPTION".equals(renderConfig)) { - Map optionAttr = new HashMap<>(); - optionAttr.put("node", node); - optionAttr.put("templateType", templateType); - optionAttr.put("view", attr.get("view")); - optionAttr.put("parameters", attr.get("parameters")); - optionAttr.put("nodetype", node.getPrimaryNodeType().getName()); - try { - return renderTag(new OptionTag(), optionAttr, renderContext); - } catch (IllegalAccessException | InvocationTargetException | JspException | IOException e) { - throw new RepositoryException(e); - } - } else { - Resource r = new Resource(node, templateType, (String) attr.get("view"), - "INCLUDE".equals(renderConfig) ? "include" : "module"); - // TODO TECH-1335 use TO_CACHE_WITH_PARENT_FRAGMENT constant once minimal jahia version >= 8.2.0.0 - r.getModuleParams().put("toCacheWithParentFragment", true); - - try { - // handle render parameters for JSON rendering - Map renderParameters = (Map) attr.get("parameters"); - if (renderParameters != null && !renderParameters.isEmpty()) { - String charset = renderContext.getResponse().getCharacterEncoding(); - for (Map.Entry renderParam : renderParameters.entrySet()) { - // only allow String params for compatibility reasons due to JSP ParamParent parameters being a map - if (renderParam.getValue() instanceof String) { - r.getModuleParams().put(URLDecoder.decode(renderParam.getKey(), charset), - URLDecoder.decode((String) renderParam.getValue(), charset)); + JCRNodeWrapper node = JSNodeMapper.toVirtualNode((Map) attr.get("content"), session, renderContext); + String renderConfig = attr.get("advanceRenderingConfig") != null ? (String) attr.get("advanceRenderingConfig") : ""; + String templateType = attr.get("templateType") != null ? (String) attr.get("templateType") : "html"; + + if ("OPTION".equals(renderConfig)) { + Map optionAttr = new HashMap<>(); + optionAttr.put("node", node); + optionAttr.put("templateType", templateType); + optionAttr.put("view", attr.get("view")); + optionAttr.put("parameters", attr.get("parameters")); + optionAttr.put("nodetype", node.getPrimaryNodeType().getName()); + try { + return renderTag(new OptionTag(), optionAttr, renderContext); + } catch (IllegalAccessException | InvocationTargetException | JspException | IOException e) { + throw new RepositoryException(e); + } + } else { + Resource r = new Resource(node, templateType, (String) attr.get("view"), + "INCLUDE".equals(renderConfig) ? "include" : "module"); + // TODO TECH-1335 use TO_CACHE_WITH_PARENT_FRAGMENT constant once minimal jahia version >= 8.2.0.0 + r.getModuleParams().put("toCacheWithParentFragment", true); + + try { + // handle render parameters for JSON rendering + Map renderParameters = (Map) attr.get("parameters"); + if (renderParameters != null && !renderParameters.isEmpty()) { + String charset = renderContext.getResponse().getCharacterEncoding(); + for (Map.Entry renderParam : renderParameters.entrySet()) { + // only allow String params for compatibility reasons due to JSP ParamParent parameters being a map + if (renderParam.getValue() instanceof String) { + r.getModuleParams().put(URLDecoder.decode(renderParam.getKey(), charset), + URLDecoder.decode((String) renderParam.getValue(), charset)); + } + } } + + return renderService.render(r, renderContext); + } catch (RenderException | IOException e) { + throw new RepositoryException(e); } } - - return renderService.render(r, renderContext); - } catch (RenderException | IOException e) { - throw new RepositoryException(e); - } - } - }); + }); } public String createContentButtons(String childName, String nodeTypes, boolean editCheck, RenderContext renderContext, Resource currentResource) throws JspException, InvocationTargetException, IllegalAccessException, RepositoryException, IOException { @@ -227,49 +229,50 @@ public String render(Map attr, RenderContext renderContext, Reso /** * Render a tag that adds resources to the page. Resources might for example be CSS files, Javascript files or inline - * @param attr may contain the following: - *
    - *
  • insert (boolean) : If true, the resource will be inserted into the document. Typically used - * for on-demand loading of resources.
  • - *
  • async (boolean) : If true, the resource will be loaded asynchronously. For scripts, this means - * the script - * will be executed as soon as it's available, without blocking the rest of the page.
  • - *
  • defer (boolean) : If true, the resource will be deferred, i.e., loaded after the document - * has been parsed. - * For scripts, this means the script will not be executed until after the page has loaded.
  • - *
  • type (string) : The type of the resource. This could be 'javascript' for .js files, 'css' for - * .css files, etc. - * The type will be used to resolve the directory in the module where the resources are located. For example - * for the 'css' type it will look for the resources in the css directory of the module.
  • - *
  • resources (string) : The path to the resource file, relative to the module. It is also allowed to - * specify multiple resources by separating them with commas. It is also allowed to use absolute URLs to - * include remote resources.
  • - *
  • inlineResource (string) : Inline HTML that markup will be considered as a resource.
  • - *
  • title (string) : The title of the resource. This is typically not used for scripts or stylesheets, - * but may be used for other types of resources.
  • - *
  • key (string) : A unique key for the resource. This could be used to prevent duplicate resources - * from being added to the document.
  • - *
  • targetTag (string): The HTML tag where the resource should be added. This could be 'head' for - * resources that should be added to the <head> tag, 'body' for resources that should be added to - * the <body> tag, etc.
  • - *
  • rel (string) : The relationship of the resource to the document. This is typically 'stylesheet' - * for CSS files.
  • - *
  • media (string) : The media for which the resource is intended. This is typically used for CSS - * files, with values like 'screen', 'print', etc.
  • - *
  • condition (string) : A condition that must be met for the resource to be loaded. This could be - * used for conditional comments in IE, for example.
  • - *
+ * + * @param attr may contain the following: + *
    + *
  • insert (boolean) : If true, the resource will be inserted into the document. Typically used + * for on-demand loading of resources.
  • + *
  • async (boolean) : If true, the resource will be loaded asynchronously. For scripts, this means + * the script + * will be executed as soon as it's available, without blocking the rest of the page.
  • + *
  • defer (boolean) : If true, the resource will be deferred, i.e., loaded after the document + * has been parsed. + * For scripts, this means the script will not be executed until after the page has loaded.
  • + *
  • type (string) : The type of the resource. This could be 'javascript' for .js files, 'css' for + * .css files, etc. + * The type will be used to resolve the directory in the module where the resources are located. For example + * for the 'css' type it will look for the resources in the css directory of the module.
  • + *
  • resources (string) : The path to the resource file, relative to the module. It is also allowed to + * specify multiple resources by separating them with commas. It is also allowed to use absolute URLs to + * include remote resources.
  • + *
  • inlineResource (string) : Inline HTML that markup will be considered as a resource.
  • + *
  • title (string) : The title of the resource. This is typically not used for scripts or stylesheets, + * but may be used for other types of resources.
  • + *
  • key (string) : A unique key for the resource. This could be used to prevent duplicate resources + * from being added to the document.
  • + *
  • targetTag (string): The HTML tag where the resource should be added. This could be 'head' for + * resources that should be added to the <head> tag, 'body' for resources that should be added to + * the <body> tag, etc.
  • + *
  • rel (string) : The relationship of the resource to the document. This is typically 'stylesheet' + * for CSS files.
  • + *
  • media (string) : The media for which the resource is intended. This is typically used for CSS + * files, with values like 'screen', 'print', etc.
  • + *
  • condition (string) : A condition that must be met for the resource to be loaded. This could be + * used for conditional comments in IE, for example.
  • + *
* @param renderContext the current rendering context * @return a String containing the rendered HTML tags for the provided resources. - * @throws IllegalAccessException if the underlying tag cannot be accessed + * @throws IllegalAccessException if the underlying tag cannot be accessed * @throws InvocationTargetException if the underlying tag cannot be invoked - * @throws JspException if the underlying tag throws a JSP exception - * @throws IOException if the underlying tag throws an IO exception + * @throws JspException if the underlying tag throws a JSP exception + * @throws IOException if the underlying tag throws an IO exception */ public String addResources(Map attr, RenderContext renderContext) throws IllegalAccessException, InvocationTargetException, JspException, IOException { Map ressourcesAttrs = new HashMap<>(attr); - if (attr.containsKey("inlineResource")){ + if (attr.containsKey("inlineResource")) { ressourcesAttrs.put("body", attr.get("inlineResource").toString()); } return renderTag(new AddResourcesTag(), ressourcesAttrs, renderContext); @@ -278,61 +281,105 @@ public String addResources(Map attr, RenderContext renderContext /** * Add a cache dependency to the current resource. This will be used to flush the current resource when the * dependencies are modified. - * @param attr may be the following: - *
    - *
  • node (JCRNodeWrapper) : The node to add as a dependency.
  • - *
  • uuid (String) : The UUID of the node to add as a dependency.
  • - *
  • path (String) : The path of the node to add as a dependency.
  • - *
  • flushOnPathMatchingRegexp (String) : A regular expression that will be used to flush the cache - * when the path of the modified nodes matches the regular expression.
  • - *
+ * + * @param attr may be the following: + *
    + *
  • node (JCRNodeWrapper) : The node to add as a dependency.
  • + *
  • uuid (String) : The UUID of the node to add as a dependency.
  • + *
  • path (String) : The path of the node to add as a dependency.
  • + *
  • flushOnPathMatchingRegexp (String) : A regular expression that will be used to flush the cache + * when the path of the modified nodes matches the regular expression.
  • + *
* @param renderContext the current rendering context - * @throws IllegalAccessException if the underlying tag cannot be accessed + * @throws IllegalAccessException if the underlying tag cannot be accessed * @throws InvocationTargetException if the underlying tag cannot be invoked - * @throws JspException if the underlying tag throws a JSP exception - * @throws IOException if the underlying tag throws an IO exception + * @throws JspException if the underlying tag throws a JSP exception + * @throws IOException if the underlying tag throws an IO exception */ public void addCacheDependency(Map attr, RenderContext renderContext) throws IllegalAccessException, InvocationTargetException, JspException, IOException { renderTag(new AddCacheDependencyTag(), attr, renderContext); } + /** + * Calculate the relative path from one node to another node. + * + * @param currentPath the path of the current node from which to calculate the relative path + * @param basePath the path of the base node to which to calculate the relative path + * @return the relative path from currentPath to basePath + */ + static String calculateRelativePath(String currentPath, String basePath) { + // special case when the parent is the current node + if (currentPath.equals(basePath)) { + return "."; + } + String[] currentPathParts = currentPath.split("/"); + String[] parentPathParts = basePath.split("/"); + + // Find the common prefix length + int commonLength = 0; + while (commonLength < currentPathParts.length && commonLength < parentPathParts.length + && currentPathParts[commonLength].equals(parentPathParts[commonLength])) { + commonLength++; + } + + // Calculate the number of steps to go up from the current node to the common prefix + int stepsUp = currentPathParts.length - commonLength; + + // Build the relative path + StringBuilder relativePath = new StringBuilder(); + for (int i = 0; i < stepsUp; i++) { + relativePath.append("../"); + } + for (int i = commonLength; i < parentPathParts.length; i++) { + relativePath.append(parentPathParts[i]); + if (i < parentPathParts.length - 1) { + relativePath.append("/"); + } + } + + return relativePath.toString(); + } + + private static T readMandatoryAttribute(Map attr, String attributeName) { + T name = (T) attr.remove(attributeName); + if (name == null) { + throw new IllegalStateException(String.format("Attribute '%s' is required", attributeName)); + } + return name; + } + public String renderAbsoluteArea(Map attr, RenderContext renderContext) throws JspException, InvocationTargetException, IllegalAccessException, IOException { checkAttributes(attr, ABSOLUTEAREA_ALLOWED_ATTRIBUTES); + // path handling + String name = readMandatoryAttribute(attr, "name"); + JCRNodeWrapper parent = readMandatoryAttribute(attr, "parent"); + String relativePath = calculateRelativePath(renderContext.getSite().getPath(), parent.getPath()); + attr.put("path", relativePath + "/" + name); return internalRenderArea(attr, "absoluteArea", renderContext); } public String renderArea(Map attr, RenderContext renderContext) throws IllegalAccessException, InvocationTargetException, JspException, IOException { checkAttributes(attr, AREA_ALLOWED_ATTRIBUTES); + String name = readMandatoryAttribute(attr, "name"); + attr.put("path", renderContext.getMainResource().getNode().getPath() + "/" + name); return internalRenderArea(attr, "area", renderContext); } private String internalRenderArea(Map attr, String moduleType, RenderContext renderContext) throws IllegalAccessException, InvocationTargetException, JspException, IOException { // We copy the attributes to avoid modifying the original map - Map areaAttr = new HashMap<>(attr); + Map areaAttr = new HashMap<>(attr); areaAttr.put("moduleType", moduleType); // We now have to transform the attr properties into something that the AreaTag can understand - if (areaAttr.get("path") != null && areaAttr.get("name") != null) { - logger.warn("Both path and name are set on the area [ " + areaAttr.get("name") + "] tag, name will be ignored"); - areaAttr.remove("name"); - } else { - if (areaAttr.get("name") != null) { - areaAttr.put("path", areaAttr.get("name")); - areaAttr.remove("name"); - } - } - if (areaAttr.get("areaView") != null) { - areaAttr.put("view", areaAttr.get("areaView")); - areaAttr.remove("areaView"); - } - Object allowedTypes = areaAttr.get("allowedTypes"); + Object allowedTypes = areaAttr.get("allowedNodeTypes"); if (allowedTypes != null) { if (allowedTypes instanceof List) { areaAttr.put("nodeTypes", StringUtils.join((List) allowedTypes, ' ')); - areaAttr.remove("allowedTypes"); - } if (allowedTypes.getClass().isArray()) { + areaAttr.remove("allowedNodeTypes"); + } + if (allowedTypes.getClass().isArray()) { Object[] allowedTypeArray = (Object[]) allowedTypes; areaAttr.put("nodeTypes", StringUtils.join(allowedTypeArray, ' ')); - areaAttr.remove("allowedTypes"); + areaAttr.remove("allowedNodeTypes"); } } @@ -340,21 +387,16 @@ private String internalRenderArea(Map attr, String moduleType, R areaAttr.put("listLimit", areaAttr.get("numberOfItems")); areaAttr.remove("numberOfItems"); } - - AreaTag areaTag = new AreaTag(); - // The subNodesView is an attribute that is passed as a tag parameter - if (areaAttr.get("subNodesView") != null) { - areaTag.addParameter("subNodesView", (String) areaAttr.get("subNodesView")); - areaAttr.remove("subNodesView"); - } + areaAttr.put("areaType", areaAttr.remove("nodeType")); + areaAttr.put("level", -1); // force the "path" parameter to be computed against the root node of the site (ex: /sites/) // Now we remove any null attribute to make sure they don't override default tag attributes areaAttr = cleanupNullValues(areaAttr); - return renderTag(areaTag, areaAttr, renderContext); + return renderTag(new AreaTag(), areaAttr, renderContext); } - private Map cleanupNullValues(Map mapToClean) { + private Map cleanupNullValues(Map mapToClean) { return mapToClean.entrySet().stream() .filter(entry -> entry.getValue() != null) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); @@ -372,8 +414,8 @@ private String renderTag(TagSupport tag, Map attr, RenderContext } String body = null; - if (tag instanceof BodyTagSupport && attr.get("body") != null) { - body = (String) attr.get("body"); + if (tag instanceof BodyTagSupport && attr.get("body") != null) { + body = (String) attr.get("body"); } safePopulate(tag, attr); @@ -420,7 +462,7 @@ private void safePopulate(Object tag, Map attr) { for (Map.Entry entry : attr.entrySet()) { try { BeanUtils.setProperty(tag, entry.getKey(), entry.getValue()); - } catch (Throwable e) { + } catch (Throwable e) { // Ignore any errors } } @@ -444,7 +486,7 @@ public void setRenderService(RenderService renderService) { this.renderService = renderService; } - private void checkAttributes(Map attributes, Set allowedAttributes) { + private void checkAttributes(Map attributes, Set allowedAttributes) { for (String attr : attributes.keySet()) { if (!allowedAttributes.contains(attr)) { throw new IllegalArgumentException("Attribute " + attr + " is not allowed"); diff --git a/javascript-modules-engine-java/src/test/java/org/jahia/modules/javascript/modules/engine/js/server/RenderHelperTest.java b/javascript-modules-engine-java/src/test/java/org/jahia/modules/javascript/modules/engine/js/server/RenderHelperTest.java new file mode 100644 index 00000000..2687f198 --- /dev/null +++ b/javascript-modules-engine-java/src/test/java/org/jahia/modules/javascript/modules/engine/js/server/RenderHelperTest.java @@ -0,0 +1,31 @@ +package org.jahia.modules.javascript.modules.engine.js.server; + +import junit.framework.TestCase; +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(JUnitParamsRunner.class) +public class RenderHelperTest extends TestCase { + + @Test + @Parameters({ + // siblings: + "/home, /about, ../about", + "/sites/mysite/home, /sites/mysite/about, ../about", + // identical: + "/home, /home, .", + // parents: + "/home/sub-level-1, /home, ../", + "/home/sub-level-1/sub-level-2, /home, ../../", + "/home/sub-level-1/sub-level-2, /home/sub-level-1, ../", + // children: + "/home, /home/sub-level-1, sub-level-1", + "/home, /home/sub-level-1, sub-level-1", + "/home, /home/sub-level-1/sub-level-2, sub-level-1/sub-level-2" + }) + public void testCalculateRelativePath(String currentPath, String basePath, String expectedRelativePath) { + assertEquals(expectedRelativePath, RenderHelper.calculateRelativePath(currentPath, basePath)); + } +} \ No newline at end of file diff --git a/javascript-modules-engine/tests/cypress.config.ts b/javascript-modules-engine/tests/cypress.config.ts index 345eabb2..1f60d892 100644 --- a/javascript-modules-engine/tests/cypress.config.ts +++ b/javascript-modules-engine/tests/cypress.config.ts @@ -3,6 +3,7 @@ import { defineConfig } from 'cypress' export default defineConfig({ chromeWebSecurity: false, failOnStatusCode: false, + watchForFileChanges: false, defaultCommandTimeout: 30000, videoUploadOnPasses: false, reporter: 'cypress-multi-reporters', diff --git a/javascript-modules-engine/tests/cypress/e2e/ui/absoluteAreaTest.cy.ts b/javascript-modules-engine/tests/cypress/e2e/ui/absoluteAreaTest.cy.ts index 4e74472e..2954b412 100644 --- a/javascript-modules-engine/tests/cypress/e2e/ui/absoluteAreaTest.cy.ts +++ b/javascript-modules-engine/tests/cypress/e2e/ui/absoluteAreaTest.cy.ts @@ -52,13 +52,21 @@ describe('Absolute Area test', () => { name: 'pagecontent', primaryNodeType: 'jnt:contentList', }, - ]).then(() => { - addNode({ - parentPathOrId: `/sites/javascriptTestSite/home/${pageName}/pagecontent`, - name: 'test', - primaryNodeType: 'javascriptExample:testAbsoluteAreas', - }) + ]) + addNode({ + parentPathOrId: `/sites/javascriptTestSite/home/${pageName}/pagecontent`, + name: 'test', + primaryNodeType: 'javascriptExample:testAbsoluteAreas', + }) + addNode({ + parentPathOrId: `/sites/javascriptTestSite/home/${pageName}/pagecontent/test`, + // the content node is expected to exist in TestAbsolutArea + name: 'basicArea', + primaryNodeType: 'jnt:contentList', }) + addSimplePage('/sites/javascriptTestSite', 'custom', 'Custom', 'en', 'simple').then(() => + addSimplePage('/sites/javascriptTestSite/custom', 'sub-level', 'Sub level', 'en', 'simple'), + ) }) it(`${pageName}: Basic Area test`, () => { @@ -117,35 +125,20 @@ describe('Absolute Area test', () => { cy.logout() }) - it(`${pageName}: subNodesView Area`, () => { + it(`${pageName}: absolute Area home page`, () => { cy.login() cy.visit(`/jahia/jcontent/javascriptTestSite/en/pages/home/${pageName}`) - addNode({ - parentPathOrId: `/sites/javascriptTestSite/home/${pageName}/subNodesViewArea`, - name: 'item1', - primaryNodeType: 'jnt:bigText', - }) - cy.reload() cy.iframe('#page-builder-frame-1').within(() => { - cy.get('div[data-testid="subNodesViewArea"]').find('a').contains('item1') + cy.get('div[data-testid="absoluteAreaHomePage"] div[data-testid="row-twoColumns"]').should('exist') }) cy.logout() }) - it(`${pageName}: path Area`, () => { + it(`${pageName}: absolute Area custom page (sub-level)`, () => { cy.login() cy.visit(`/jahia/jcontent/javascriptTestSite/en/pages/home/${pageName}`) cy.iframe('#page-builder-frame-1').within(() => { - cy.get('div[data-testid="pathArea"]').find('div[type="absoluteArea"]').should('exist') - }) - cy.logout() - }) - - it(`${pageName}: absolute Area`, () => { - cy.login() - cy.visit(`/jahia/jcontent/javascriptTestSite/en/pages/home/${pageName}`) - cy.iframe('#page-builder-frame-1').within(() => { - cy.get('div[data-testid="absoluteArea"]').find('div[data-testid="row-twoColumns"]').should('exist') + cy.get('div[data-testid="absoluteAreaCustomPage"]').find('div[type="absoluteArea"]').should('be.visible') }) cy.logout() }) @@ -159,15 +152,6 @@ describe('Absolute Area test', () => { cy.logout() }) - it(`${pageName}: absolute Area level`, () => { - cy.login() - cy.visit(`/jahia/jcontent/javascriptTestSite/en/pages/home/${pageName}`) - cy.iframe('#page-builder-frame-1').within(() => { - cy.get('div[data-testid="absoluteAreaLevel"]').find('div[data-testid="row-twoColumns"]').should('exist') - }) - cy.logout() - }) - it(`${pageName}: Area type`, () => { cy.login() cy.visit(`/jahia/jcontent/javascriptTestSite/en/pages/home/${pageName}`) diff --git a/javascript-modules-engine/tests/cypress/e2e/ui/areaTest.cy.ts b/javascript-modules-engine/tests/cypress/e2e/ui/areaTest.cy.ts index 75f24bfb..df28870a 100644 --- a/javascript-modules-engine/tests/cypress/e2e/ui/areaTest.cy.ts +++ b/javascript-modules-engine/tests/cypress/e2e/ui/areaTest.cy.ts @@ -78,26 +78,11 @@ describe('Area test', () => { cy.logout() }) - it(`${pageName}: subNodesView Area`, () => { - cy.login() - cy.visit(`/jahia/jcontent/javascriptTestSite/en/pages/home/${pageName}`) - addNode({ - parentPathOrId: `/sites/javascriptTestSite/home/${pageName}/subNodesViewArea`, - name: 'item1', - primaryNodeType: 'jnt:bigText', - }) - cy.reload() - cy.iframe('#page-builder-frame-1').within(() => { - cy.get('div[data-testid="subNodesViewArea"]').find('a').contains('item1') - }) - cy.logout() - }) - it(`${pageName}: path Area`, () => { cy.login() cy.visit(`/jahia/jcontent/javascriptTestSite/en/pages/home/${pageName}`) cy.iframe('#page-builder-frame-1').within(() => { - cy.get('div[data-testid="pathArea"]').find('div[type="area"]').should('exist') + cy.get('div[data-testid="parentArea"]').find('div[type="area"]').should('exist') }) cy.logout() }) @@ -111,18 +96,6 @@ describe('Area test', () => { cy.logout() }) - it(`${pageName}: Area as sub node`, () => { - cy.login() - cy.visit(`/jahia/jcontent/javascriptTestSite/en/pages/home/${pageName}`) - cy.iframe('#page-builder-frame-1').within(() => { - cy.get('div[data-testid="areaAsSubNode"]') - .find('div[type="area"]') - .should('have.attr', 'path') - .and('match', /\/pagecontent\/test\/areaAsSubNode$/) - }) - cy.logout() - }) - it(`${pageName}: Area type`, () => { cy.login() cy.visit(`/jahia/jcontent/javascriptTestSite/en/pages/home/${pageName}`) diff --git a/javascript-modules-library/README.md b/javascript-modules-library/README.md index ca438ae7..4ed3c9f8 100644 --- a/javascript-modules-library/README.md +++ b/javascript-modules-library/README.md @@ -56,10 +56,10 @@ This component renders all children of the current node. It's a thin wrapper aro ### `AbsoluteArea` -This component creates an absolute area in the page. It's an area of user-contributable content that is synchronized between all pages where it is present. +This component creates an absolute area in the page. It's an area of user-contributable content, child of the node of your choice. ```tsx - + ``` ### `Area` diff --git a/javascript-modules-library/src/core/server/components/AbsoluteArea.tsx b/javascript-modules-library/src/core/server/components/AbsoluteArea.tsx index b4784402..758b11e8 100644 --- a/javascript-modules-library/src/core/server/components/AbsoluteArea.tsx +++ b/javascript-modules-library/src/core/server/components/AbsoluteArea.tsx @@ -1,6 +1,7 @@ import type React from "react"; import { useServerContext } from "../hooks/useServerContext.js"; import server from "virtual:jahia-server"; +import type { JCRNodeWrapper } from "org.jahia.services.content"; /** * Generates an absolute area in which editors may insert content objects. @@ -9,28 +10,41 @@ import server from "virtual:jahia-server"; */ export function AbsoluteArea({ name, + parent, areaView, + view, allowedTypes, + allowedNodeTypes, numberOfItems, - subNodesView, - path, readOnly = false, - level, areaType = "jnt:contentList", + nodeType = "jnt:contentList", parameters, }: Readonly<{ /** The name of the area. */ - name?: string; - /** The view to use for the area. */ + name: string; + /** Parent node where the area is stored in the JCR. The parent node must exist. */ + parent: JCRNodeWrapper; + + /** + * The view to use for the area. + * + * @deprecated Use {@link #view} instead + */ areaView?: string; - /** The allowed types for the area. */ + /** The view to use for the area. */ + view?: string; + /** + * The allowed types for the area. + * + * @deprecated Use {@link #allowedNodeTypes} instead + */ allowedTypes?: string[]; + /** The allowed types for the area. */ + allowedNodeTypes?: string[]; /** The number of items to display in the area. */ numberOfItems?: number; - /** The view to use for the subnodes. */ - subNodesView?: string; - /** Relative (to the current node) or absolute path to the node to include. */ - path?: string; + /** * Makes the area read-only. * @@ -42,15 +56,24 @@ export function AbsoluteArea({ * @default false */ readOnly?: boolean | "children"; - /** Ancestor level for absolute area - 0 is Home page, 1 first sub-pages, ... */ - level?: number; /** - * Content type to be used to create the area + * Content node type to be used to create the area (if the node does not exist yet) * + * @deprecated Use {@link #nodeType} instead * @default jnt:contentList */ areaType?: string; - /** The parameters to pass to the absolute area */ + /** + * Content node type to be used to create the area (if the node does not exist yet) + * + * @default jnt:contentList + */ + nodeType?: string; + /** + * Map of custom parameters that can be passed to the backend engine for advanced logic. + * + * @deprecated Not recommended + */ parameters?: Record; }>): React.JSX.Element { const { renderContext } = useServerContext(); @@ -61,14 +84,12 @@ export function AbsoluteArea({ __html: server.render.renderAbsoluteArea( { name, - areaView, - allowedTypes, + parent: parent, + view: view ? view : areaView, + allowedNodeTypes: allowedNodeTypes ?? allowedTypes, numberOfItems, - subNodesView, - path, + nodeType: nodeType ?? areaType, editable: readOnly !== true, - level, - areaType, limitedAbsoluteAreaEdit: readOnly === "children", parameters, }, diff --git a/javascript-modules-library/src/core/server/components/Area.tsx b/javascript-modules-library/src/core/server/components/Area.tsx index f932a385..3a544d0a 100644 --- a/javascript-modules-library/src/core/server/components/Area.tsx +++ b/javascript-modules-library/src/core/server/components/Area.tsx @@ -10,46 +10,62 @@ import server from "virtual:jahia-server"; export function Area({ name, areaView, + view, allowedTypes, + allowedNodeTypes, numberOfItems, - subNodesView, - path, readOnly = false, - areaAsSubNode, areaType = "jnt:contentList", + nodeType = "jnt:contentList", parameters, }: Readonly<{ /** The name of the area. */ - name?: string; - /** The view to use for the area. */ + name: string; + + /** + * The view to use for the area. + * + * @deprecated Use {@link #view} instead + */ areaView?: string; - /** The allowed types for the area. */ + /** The view to use for the area. */ + view?: string; + /** + * The allowed types for the area. + * + * @deprecated Use {@link #allowedNodeTypes} instead + */ allowedTypes?: string[]; + /** The allowed types for the area. */ + allowedNodeTypes?: string[]; /** The number of items to display in the area. */ numberOfItems?: number; - /** The view to use for the subnodes. */ - subNodesView?: string; - /** Relative (to the current node) or absolute path to the node to include. */ - path?: string; + /** * Makes the area read-only. * * @default false */ readOnly?: boolean; + /** - * Allow area to be stored as a subnode + * Content node type to be used to create the area (if the node does not exist yet) * - * @deprecated Use child node(s) and `` instead + * @deprecated Use {@link #nodeType} instead + * @default jnt:contentList */ - areaAsSubNode?: boolean; + areaType?: string; /** - * Content type to be used to create the area + * Content node type to be used to create the area (if the node does not exist yet) * * @default jnt:contentList */ - areaType?: string; - /** The parameters to pass to the area */ + nodeType?: string; + /** + * Map of custom parameters that can be passed to the backend engine for advanced logic. + * + * @deprecated Not recommended + */ parameters?: Record; }>): JSX.Element { const { renderContext } = useServerContext(); @@ -60,14 +76,11 @@ export function Area({ __html: server.render.renderArea( { name, - areaView, - allowedTypes, + view: view ?? areaView, + allowedNodeTypes: allowedNodeTypes ?? allowedTypes, numberOfItems, - subNodesView, - path, + nodeType: nodeType ?? areaType, editable: !readOnly, - areaAsSubNode, - areaType, parameters, }, renderContext,