From a885ac7361edda4dbe4119121cc4483c45b31b25 Mon Sep 17 00:00:00 2001 From: "daniel.solis" Date: Wed, 29 Apr 2026 14:40:59 -0600 Subject: [PATCH 1/3] fix(block-editor): render subscript and superscript marks as / in Story Block HTML output The renderMarks macro in VM_global_library.vm handled bold, italic, strike, underline, and link marks but silently dropped subscript and superscript marks even though the Block Editor (TipTap) registers both extensions and authors can produce them. Adds the two missing branches in both the opening and closing reverse-range loops so nested combinations (e.g. bold + superscript) close in the correct order, plus an integration test in StoryBlockMapTest covering each mark individually and a combined nesting case. Closes #35460 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../WEB-INF/velocity/VM_global_library.vm | 12 +++++++++ .../viewtools/content/StoryBlockMapTest.java | 27 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/dotCMS/src/main/webapp/WEB-INF/velocity/VM_global_library.vm b/dotCMS/src/main/webapp/WEB-INF/velocity/VM_global_library.vm index af4f61c080ee..2bac025d9b1a 100644 --- a/dotCMS/src/main/webapp/WEB-INF/velocity/VM_global_library.vm +++ b/dotCMS/src/main/webapp/WEB-INF/velocity/VM_global_library.vm @@ -26,6 +26,12 @@ *##if ($mark.type == "underline")#* *##* *##end#* + *##if ($mark.type == "subscript")#* + *##* + *##end#* + *##if ($mark.type == "superscript")#* + *##* + *##end#* *##if ($mark.type == "link")#* *##* *##end#* + *##if ($mark.type == "subscript")#* + *##* + *##end#* + *##if ($mark.type == "superscript")#* + *##* + *##end#* *##if($mark.type == "link")#* *##* *##end#* diff --git a/dotcms-integration/src/test/java/com/dotcms/rendering/velocity/viewtools/content/StoryBlockMapTest.java b/dotcms-integration/src/test/java/com/dotcms/rendering/velocity/viewtools/content/StoryBlockMapTest.java index 764264c83daa..194dc9f9fdbe 100644 --- a/dotcms-integration/src/test/java/com/dotcms/rendering/velocity/viewtools/content/StoryBlockMapTest.java +++ b/dotcms-integration/src/test/java/com/dotcms/rendering/velocity/viewtools/content/StoryBlockMapTest.java @@ -57,6 +57,15 @@ public class StoryBlockMapTest extends IntegrationTestBase { private static final String JSON_ULIST = "{\"type\":\"doc\",\"content\":[{\"type\":\"bulletList\",\"content\":[{\"type\":\"listItem\",\"attrs\":{\"textAlign\":\"left\"},\"content\":[{\"type\":\"paragraph\",\"attrs\":{\"textAlign\":\"left\"},\"content\":[{\"type\":\"text\",\"text\":\"1\"}]}]},{\"type\":\"listItem\",\"attrs\":{\"textAlign\":\"left\"},\"content\":[{\"type\":\"paragraph\",\"attrs\":{\"textAlign\":\"left\"},\"content\":[{\"type\":\"text\",\"text\":\"2\"}]}]},{\"type\":\"listItem\",\"attrs\":{\"textAlign\":\"left\"},\"content\":[{\"type\":\"paragraph\",\"attrs\":{\"textAlign\":\"left\"},\"content\":[{\"type\":\"text\",\"text\":\"3\"}]}]}]}]}"; + private static final String JSON_SUB_SUP = + "{\"type\":\"doc\",\"content\":[{\"type\":\"paragraph\",\"attrs\":{\"textAlign\":\"left\"},\"content\":[" + + "{\"type\":\"text\",\"marks\":[{\"type\":\"superscript\"}],\"text\":\"sup\"}," + + "{\"type\":\"text\",\"text\":\" and \"}," + + "{\"type\":\"text\",\"marks\":[{\"type\":\"subscript\"}],\"text\":\"sub\"}," + + "{\"type\":\"text\",\"text\":\" and \"}," + + "{\"type\":\"text\",\"marks\":[{\"type\":\"bold\"},{\"type\":\"superscript\"}],\"text\":\"bold-sup\"}" + + "]}]}"; + private static final String MACRO = "#macro(renderMarks $content)\n" + "\n" + " #if($content.marks)\n" + @@ -256,6 +265,24 @@ public void test_default_render_to_html() throws JSONException { } + /** + * Method to test: {@link StoryBlockMap#toHtml()} + * Given Scenario: A paragraph contains text with subscript, superscript, and a combined bold+superscript mark. + * ExpectedResult: The rendered HTML wraps each text run in {@code } / {@code } tags, and combined + * marks nest with the correct closing order (e.g. {@code bold-sup}). + */ + @Test + public void test_subscript_and_superscript_marks_render_to_html() throws JSONException { + + final StoryBlockMap storyBlockMap = new StoryBlockMap(JSON_SUB_SUP); + final String html = storyBlockMap.toHtml(); + + Assert.assertTrue(html + ": missing sup", html.contains("sup")); + Assert.assertTrue(html + ": missing sub", html.contains("sub")); + Assert.assertTrue(html + ": missing bold-sup", + html.contains("bold-sup")); + } + /** * Method to test: {@link StoryBlockMap#toHtml()} * Given Scenario: Will parse the json paragraph and render the custom html From f359f88903c7f0e08503f12f040a054510f40fae Mon Sep 17 00:00:00 2001 From: "daniel.solis" Date: Wed, 29 Apr 2026 15:05:07 -0600 Subject: [PATCH 2/3] test: keep MACRO copy in StoryBlockMapTest in sync with renderMarks Add subscript and superscript handling to the inline MACRO constant so the test file reflects the real renderMarks macro in VM_global_library.vm. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../viewtools/content/StoryBlockMapTest.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/dotcms-integration/src/test/java/com/dotcms/rendering/velocity/viewtools/content/StoryBlockMapTest.java b/dotcms-integration/src/test/java/com/dotcms/rendering/velocity/viewtools/content/StoryBlockMapTest.java index 194dc9f9fdbe..016e39a4b3e3 100644 --- a/dotcms-integration/src/test/java/com/dotcms/rendering/velocity/viewtools/content/StoryBlockMapTest.java +++ b/dotcms-integration/src/test/java/com/dotcms/rendering/velocity/viewtools/content/StoryBlockMapTest.java @@ -88,6 +88,12 @@ public class StoryBlockMapTest extends IntegrationTestBase { " #if ($mark.type == \"underline\")\n" + " \n" + " #end\n" + + " #if ($mark.type == \"subscript\")\n" + + " \n" + + " #end\n" + + " #if ($mark.type == \"superscript\")\n" + + " \n" + + " #end\n" + " #if ($mark.type == \"link\")\n" + " \n" + " #end\n" + @@ -112,6 +118,12 @@ public class StoryBlockMapTest extends IntegrationTestBase { " #if ($mark.type == \"underline\")\n" + " \n" + " #end\n" + + " #if ($mark.type == \"subscript\")\n" + + " \n" + + " #end\n" + + " #if ($mark.type == \"superscript\")\n" + + " \n" + + " #end\n" + " #if($mark.type == \"link\")\n" + " \n" + " #end\n" + From bc0ddc7f6c68b677619032f81e0ddd72ca87468a Mon Sep 17 00:00:00 2001 From: "daniel.solis" Date: Wed, 29 Apr 2026 16:27:31 -0600 Subject: [PATCH 3/3] test: normalize whitespace before asserting mark wrappings The inline MACRO registered in @Before pads tags with newlines and indent, so contains-checks for contiguous wrappings like sup never match even when the macro is correct. Strip whitespace before asserting so the test still verifies the wrapping order and nesting. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../viewtools/content/StoryBlockMapTest.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/dotcms-integration/src/test/java/com/dotcms/rendering/velocity/viewtools/content/StoryBlockMapTest.java b/dotcms-integration/src/test/java/com/dotcms/rendering/velocity/viewtools/content/StoryBlockMapTest.java index 016e39a4b3e3..d96ba36e34ab 100644 --- a/dotcms-integration/src/test/java/com/dotcms/rendering/velocity/viewtools/content/StoryBlockMapTest.java +++ b/dotcms-integration/src/test/java/com/dotcms/rendering/velocity/viewtools/content/StoryBlockMapTest.java @@ -288,11 +288,16 @@ public void test_subscript_and_superscript_marks_render_to_html() throws JSONExc final StoryBlockMap storyBlockMap = new StoryBlockMap(JSON_SUB_SUP); final String html = storyBlockMap.toHtml(); - - Assert.assertTrue(html + ": missing sup", html.contains("sup")); - Assert.assertTrue(html + ": missing sub", html.contains("sub")); + // The inline MACRO registered in @Before pads tags with newlines/indent, so + // strip whitespace before asserting on contiguous mark wrappings. + final String normalized = html.replaceAll("\\s+", ""); + + Assert.assertTrue(html + ": missing sup", + normalized.contains("sup")); + Assert.assertTrue(html + ": missing sub", + normalized.contains("sub")); Assert.assertTrue(html + ": missing bold-sup", - html.contains("bold-sup")); + normalized.contains("bold-sup")); } /**