diff --git a/.gitignore b/.gitignore index b7d48b1d3..e0fe28144 100644 --- a/.gitignore +++ b/.gitignore @@ -75,4 +75,5 @@ cms-content/src/test/resources/site/data integration-tests/hosts/test/data /modules/example-module/src/main/ts/node_modules/ /modules/example-module/src/main/ts/pnpm-lock.yaml -*.jfr \ No newline at end of file +*.jfr +/modules/ui-module/src/main/ts/node_modules \ No newline at end of file diff --git a/cms-api/src/main/java/com/condation/cms/api/Constants.java b/cms-api/src/main/java/com/condation/cms/api/Constants.java index 88da35848..5de9d24eb 100644 --- a/cms-api/src/main/java/com/condation/cms/api/Constants.java +++ b/cms-api/src/main/java/com/condation/cms/api/Constants.java @@ -95,19 +95,19 @@ public static class ContentTypes { public static final Pattern TAXONOMY_VALUE = Pattern.compile("taxonomy\\.([a-zA-Z0-9-]+)\\.yaml"); - public static final Pattern SECTION_PATTERN = Pattern.compile("\\w+\\.(?
[a-zA-Z0-9-]+)\\.md"); + public static final Pattern SLOT_ITEM_PATTERN = Pattern.compile("\\w+\\.(?[a-zA-Z0-9-]+)\\.md"); - public static final Function SECTION_OF_PATTERN = (fileName) -> { - return Pattern.compile("%s\\.(?
[a-zA-Z0-9-]+)\\.md".formatted(Pattern.quote(fileName))); + public static final Function SLOT_ITEM_OF_PATTERN = (fileName) -> { + return Pattern.compile("%s\\.(?[a-zA-Z0-9-]+)\\.md".formatted(Pattern.quote(fileName))); }; - public static final Pattern SECTION_NAMED_PATTERN = Pattern.compile("[\\w-]+\\.(?
[a-zA-Z0-9-]+)\\.(?[\\w-]+)\\.md"); + public static final Pattern SLOT_ITEM_NAMED_PATTERN = Pattern.compile("[\\w-]+\\.(?[a-zA-Z0-9-]+)\\.(?[\\w-]+)\\.md"); - public static final Function SECTION_NAMED_OF_PATTERN = (fileName) -> { + public static final Function SLOT_ITEM_NAMED_OF_PATTERN = (fileName) -> { return Pattern.compile("%s\\.[a-zA-Z0-9-]+\\.[a-zA-Z0-9-]+\\.md".formatted(Pattern.quote(fileName))); }; - public static final int DEFAULT_SECTION_LAYOUT_ORDER = 0; + public static final int DEFAULT_SLOT_ITEM_LAYOUT_ORDER = 0; public static final double DEFAULT_MENU_POSITION = 1000f; public static final boolean DEFAULT_MENU_VISIBILITY = true; public static final int DEFAULT_EXCERPT_LENGTH = 200; diff --git a/cms-api/src/main/java/com/condation/cms/api/content/MapAccess.java b/cms-api/src/main/java/com/condation/cms/api/content/MapAccess.java index 2259574ae..32a5f70c6 100644 --- a/cms-api/src/main/java/com/condation/cms/api/content/MapAccess.java +++ b/cms-api/src/main/java/com/condation/cms/api/content/MapAccess.java @@ -26,6 +26,7 @@ import java.util.Set; import com.condation.cms.api.utils.MapUtil; +import com.google.common.base.Strings; import lombok.RequiredArgsConstructor; @@ -53,6 +54,16 @@ public boolean containsKey(Object key) { return MapUtil.getValue(wrapped, (String)key) != null; } + public boolean empty(Object key) { + var value = MapUtil.getValue(wrapped, (String)key); + + if (value instanceof String stringValue) { + return Strings.isNullOrEmpty(stringValue); + } + + return value == null; + } + @Override public boolean containsValue(Object value) { throw new UnsupportedOperationException("Unimplemented method 'containsValue'"); diff --git a/cms-api/src/main/java/com/condation/cms/api/db/Content.java b/cms-api/src/main/java/com/condation/cms/api/db/Content.java index 1aa111af9..658afd26b 100644 --- a/cms-api/src/main/java/com/condation/cms/api/db/Content.java +++ b/cms-api/src/main/java/com/condation/cms/api/db/Content.java @@ -36,7 +36,7 @@ public interface Content { boolean isVisible (ContentNode node); - List listSections(final ReadOnlyFile contentFile); + List listSlotItems(final ReadOnlyFile contentFile); List listContent(final ReadOnlyFile base, final String start); diff --git a/cms-api/src/main/java/com/condation/cms/api/db/ContentNode.java b/cms-api/src/main/java/com/condation/cms/api/db/ContentNode.java index 0b8c2dec4..608e8bde9 100644 --- a/cms-api/src/main/java/com/condation/cms/api/db/ContentNode.java +++ b/cms-api/src/main/java/com/condation/cms/api/db/ContentNode.java @@ -28,7 +28,7 @@ import com.condation.cms.api.request.RequestContextScope; import com.condation.cms.api.utils.DateRange; import com.condation.cms.api.utils.MapUtil; -import com.condation.cms.api.utils.SectionUtil; +import com.condation.cms.api.utils.SlotUtil; import java.io.Serializable; import java.time.Instant; import java.time.LocalDate; @@ -124,8 +124,8 @@ public boolean isVisible() { return DateRange.isNowWithin(publish_date, unpublish_date); } - public boolean isSection() { - return SectionUtil.isSection(name); + public boolean isSlotItem() { + return SlotUtil.isSlotItem(name); } public boolean isRedirect() { diff --git a/cms-api/src/main/java/com/condation/cms/api/db/DB.java b/cms-api/src/main/java/com/condation/cms/api/db/DB.java index ffa1446f8..b7eb9c1ee 100644 --- a/cms-api/src/main/java/com/condation/cms/api/db/DB.java +++ b/cms-api/src/main/java/com/condation/cms/api/db/DB.java @@ -21,8 +21,8 @@ * #L% */ -import com.condation.cms.api.db.cms.ReadyOnlyFileSystem; import com.condation.cms.api.db.taxonomy.Taxonomies; +import com.condation.cms.api.db.cms.ReadOnlyFileSystem; /** @@ -33,7 +33,7 @@ public interface DB extends AutoCloseable{ public DBFileSystem getFileSystem(); - public ReadyOnlyFileSystem getReadOnlyFileSystem(); + public ReadOnlyFileSystem getReadOnlyFileSystem(); public Content getContent(); diff --git a/cms-api/src/main/java/com/condation/cms/api/db/cms/ReadyOnlyFileSystem.java b/cms-api/src/main/java/com/condation/cms/api/db/cms/ReadOnlyFileSystem.java similarity index 96% rename from cms-api/src/main/java/com/condation/cms/api/db/cms/ReadyOnlyFileSystem.java rename to cms-api/src/main/java/com/condation/cms/api/db/cms/ReadOnlyFileSystem.java index 8d218c512..aa2062c5c 100644 --- a/cms-api/src/main/java/com/condation/cms/api/db/cms/ReadyOnlyFileSystem.java +++ b/cms-api/src/main/java/com/condation/cms/api/db/cms/ReadOnlyFileSystem.java @@ -27,7 +27,7 @@ * * @author t.marx */ -public interface ReadyOnlyFileSystem { +public interface ReadOnlyFileSystem { /** * Resolves a file if it is a child of the host base directory diff --git a/cms-api/src/main/java/com/condation/cms/api/db/cms/WrappedReadOnlyFileSystem.java b/cms-api/src/main/java/com/condation/cms/api/db/cms/WrappedReadOnlyFileSystem.java index c7ec1e0e6..141b66f9d 100644 --- a/cms-api/src/main/java/com/condation/cms/api/db/cms/WrappedReadOnlyFileSystem.java +++ b/cms-api/src/main/java/com/condation/cms/api/db/cms/WrappedReadOnlyFileSystem.java @@ -34,7 +34,7 @@ * @author t.marx */ @RequiredArgsConstructor -public class WrappedReadOnlyFileSystem implements ReadyOnlyFileSystem { +public class WrappedReadOnlyFileSystem implements ReadOnlyFileSystem { private final DBFileSystem dbFileSytem; diff --git a/cms-api/src/main/java/com/condation/cms/api/ui/elements/ContentTypes.java b/cms-api/src/main/java/com/condation/cms/api/ui/elements/ContentTypes.java index 460404aab..2803b204c 100644 --- a/cms-api/src/main/java/com/condation/cms/api/ui/elements/ContentTypes.java +++ b/cms-api/src/main/java/com/condation/cms/api/ui/elements/ContentTypes.java @@ -35,7 +35,7 @@ public class ContentTypes { public Set pageTemplates = new HashSet(); - public Set sectionTemplates = new HashSet<>(); + public Set slotItemTemplates = new HashSet<>(); public Set listItemTypes = new HashSet<>(); @@ -55,21 +55,21 @@ public void registerPageTemplate(Map pageTemplate) { pageTemplates.add(new PageTemplate(pageTemplate)); } - public void registerSectionTemplate(Map sectionTempate) { - sectionTemplates.add(new SectionTemplate(sectionTempate)); + public void registerSlotItemTemplate(Map slotItemTemplate) { + slotItemTemplates.add(new SlotItemTemplate(slotItemTemplate)); } public Set getPageTemplates () { return new HashSet<>(pageTemplates); } - public Set getSectionTemplates (String section) { - return sectionTemplates.stream() - .filter(template -> template.section().equals(section)) + public Set getSlotItemTemplates (String slot) { + return slotItemTemplates.stream() + .filter(template -> template.slot().equals(slot)) .collect(Collectors.toSet()); } - public Set getSectionTemplates () { - return new HashSet<>(sectionTemplates); + public Set getSlotItemTemplates () { + return new HashSet<>(slotItemTemplates); } public static record PageTemplate(String name, String template, Map data) { @@ -85,19 +85,26 @@ public Map getForm (String name) { var forms = (Map)data.getOrDefault("forms", Collections.emptyMap()); return (Map)forms.getOrDefault(name, Collections.emptyMap()); } + + public String getContentFolder () { + return (String) data.getOrDefault("contentFolder", ""); + } + public boolean addCreateButton () { + return (boolean) data.getOrDefault("createButton", true); + } } - public static record SectionTemplate(String name, String template, Map data) { + public static record SlotItemTemplate(String name, String template, Map data) { - public SectionTemplate (Map data) { + public SlotItemTemplate (Map data) { this( (String) data.getOrDefault("name", ""), (String) data.getOrDefault("template", ""), data); } - public String section() { - return (String) data.getOrDefault("section", ""); + public String slot() { + return (String) data.getOrDefault("slot", ""); } } diff --git a/cms-api/src/main/java/com/condation/cms/api/utils/SectionUtil.java b/cms-api/src/main/java/com/condation/cms/api/utils/SlotUtil.java similarity index 60% rename from cms-api/src/main/java/com/condation/cms/api/utils/SectionUtil.java rename to cms-api/src/main/java/com/condation/cms/api/utils/SlotUtil.java index af0271d5b..85f71624b 100644 --- a/cms-api/src/main/java/com/condation/cms/api/utils/SectionUtil.java +++ b/cms-api/src/main/java/com/condation/cms/api/utils/SlotUtil.java @@ -27,26 +27,26 @@ * * @author t.marx */ -public class SectionUtil { +public class SlotUtil { - public static boolean isNamedSection(final String name) { - return Constants.SECTION_NAMED_PATTERN.matcher(name).matches(); + public static boolean isNamedSlotItem(final String name) { + return Constants.SLOT_ITEM_NAMED_PATTERN.matcher(name).matches(); } - public static String getSectionName(final String name) { - if (isNamedSection(name)) { - var matcher = Constants.SECTION_NAMED_PATTERN.matcher(name); + public static String getSlotItemName(final String name) { + if (isNamedSlotItem(name)) { + var matcher = Constants.SLOT_ITEM_NAMED_PATTERN.matcher(name); matcher.matches(); - return matcher.group("section"); + return matcher.group("slot"); } else { - var matcher = Constants.SECTION_PATTERN.matcher(name); + var matcher = Constants.SLOT_ITEM_PATTERN.matcher(name); matcher.matches(); - return matcher.group("section"); + return matcher.group("slot"); } } - public static boolean isSection(final String name) { - return Constants.SECTION_PATTERN.matcher(name).matches() - || Constants.SECTION_NAMED_PATTERN.matcher(name).matches(); + public static boolean isSlotItem(final String name) { + return Constants.SLOT_ITEM_PATTERN.matcher(name).matches() + || Constants.SLOT_ITEM_NAMED_PATTERN.matcher(name).matches(); } } diff --git a/cms-api/src/test/java/com/condation/cms/api/ConstantsNGTest.java b/cms-api/src/test/java/com/condation/cms/api/ConstantsNGTest.java index 018f5223e..ce2e4d030 100644 --- a/cms-api/src/test/java/com/condation/cms/api/ConstantsNGTest.java +++ b/cms-api/src/test/java/com/condation/cms/api/ConstantsNGTest.java @@ -36,53 +36,53 @@ public class ConstantsNGTest { @Test - public void test_section_pattern() { - Assertions.assertThat(Constants.SECTION_PATTERN.matcher("index.md").matches()).isFalse(); - Assertions.assertThat(Constants.SECTION_PATTERN.matcher(".section.md").matches()).isFalse(); + public void test_slot_pattern() { + Assertions.assertThat(Constants.SLOT_ITEM_PATTERN.matcher("index.md").matches()).isFalse(); + Assertions.assertThat(Constants.SLOT_ITEM_PATTERN.matcher(".section.md").matches()).isFalse(); - Matcher matcher = Constants.SECTION_PATTERN.matcher("page.section.md"); + Matcher matcher = Constants.SLOT_ITEM_PATTERN.matcher("page.section.md"); Assertions.assertThat(matcher.matches()).isTrue(); Assertions.assertThat(matcher.group(1)).isEqualTo("section"); - Assertions.assertThat(matcher.group("section")).isEqualTo("section"); + Assertions.assertThat(matcher.group("slot")).isEqualTo("section"); - matcher = Constants.SECTION_PATTERN.matcher("index.card.md"); + matcher = Constants.SLOT_ITEM_PATTERN.matcher("index.card.md"); Assertions.assertThat(matcher.matches()).isTrue(); Assertions.assertThat(matcher.group(1)).isEqualTo("card"); - Assertions.assertThat(matcher.group("section")).isEqualTo("card"); + Assertions.assertThat(matcher.group("slot")).isEqualTo("card"); } @Test public void test_named_sections_pattern() { - Assertions.assertThat(Constants.SECTION_NAMED_PATTERN.matcher("index.md").matches()).isFalse(); - Assertions.assertThat(Constants.SECTION_NAMED_PATTERN.matcher(".section.md").matches()).isFalse(); + Assertions.assertThat(Constants.SLOT_ITEM_NAMED_PATTERN.matcher("index.md").matches()).isFalse(); + Assertions.assertThat(Constants.SLOT_ITEM_NAMED_PATTERN.matcher(".section.md").matches()).isFalse(); - Matcher matcher = Constants.SECTION_NAMED_PATTERN.matcher("page.section.md"); + Matcher matcher = Constants.SLOT_ITEM_NAMED_PATTERN.matcher("page.section.md"); Assertions.assertThat(matcher.matches()).isFalse(); - matcher = Constants.SECTION_NAMED_PATTERN.matcher("page.section..md"); + matcher = Constants.SLOT_ITEM_NAMED_PATTERN.matcher("page.section..md"); Assertions.assertThat(matcher.matches()).isFalse(); - matcher = Constants.SECTION_NAMED_PATTERN.matcher("index.card.1.md"); + matcher = Constants.SLOT_ITEM_NAMED_PATTERN.matcher("index.card.1.md"); Assertions.assertThat(matcher.matches()).isTrue(); - Assertions.assertThat(matcher.group("section")).isEqualTo("card"); + Assertions.assertThat(matcher.group("slot")).isEqualTo("card"); Assertions.assertThat(matcher.group("id")).isEqualTo("1"); - matcher = Constants.SECTION_NAMED_PATTERN.matcher("index.card.10.md"); + matcher = Constants.SLOT_ITEM_NAMED_PATTERN.matcher("index.card.10.md"); Assertions.assertThat(matcher.matches()).isTrue(); - Assertions.assertThat(matcher.group("section")).isEqualTo("card"); + Assertions.assertThat(matcher.group("slot")).isEqualTo("card"); Assertions.assertThat(matcher.group("id")).isEqualTo("10"); } @Test public void test_named_section_of() { - var pattern = Constants.SECTION_NAMED_OF_PATTERN.apply("page"); + var pattern = Constants.SLOT_ITEM_NAMED_OF_PATTERN.apply("page"); var matcher = pattern.matcher("page.left.10.md"); Assertions.assertThat(matcher.matches()).isTrue(); - pattern = Constants.SECTION_NAMED_OF_PATTERN.apply("other"); + pattern = Constants.SLOT_ITEM_NAMED_OF_PATTERN.apply("other"); matcher = pattern.matcher("page.left.10.md"); Assertions.assertThat(matcher.matches()).isFalse(); diff --git a/cms-api/src/test/java/com/condation/cms/api/utils/SectionUtilTest.java b/cms-api/src/test/java/com/condation/cms/api/utils/SlotUtilTest.java similarity index 85% rename from cms-api/src/test/java/com/condation/cms/api/utils/SectionUtilTest.java rename to cms-api/src/test/java/com/condation/cms/api/utils/SlotUtilTest.java index a3404290a..0df397ba3 100644 --- a/cms-api/src/test/java/com/condation/cms/api/utils/SectionUtilTest.java +++ b/cms-api/src/test/java/com/condation/cms/api/utils/SlotUtilTest.java @@ -29,7 +29,7 @@ * * @author thorstenmarx */ -public class SectionUtilTest { +public class SlotUtilTest { @ParameterizedTest @CsvSource({ @@ -37,7 +37,7 @@ public class SectionUtilTest { "index.asection.1.md, asection", "index.asection.blabla.md, asection" }) - public void test_getSectionName(String filename, String sectionname) { - Assertions.assertThat(SectionUtil.getSectionName(filename)).isEqualTo(sectionname); + public void test_getSlotName(String filename, String slotItemName) { + Assertions.assertThat(SlotUtil.getSlotItemName(filename)).isEqualTo(slotItemName); } } diff --git a/cms-content/src/main/java/com/condation/cms/content/ContentRenderer.java b/cms-content/src/main/java/com/condation/cms/content/ContentRenderer.java index c68fbe2e8..b9c7b96ba 100644 --- a/cms-content/src/main/java/com/condation/cms/content/ContentRenderer.java +++ b/cms-content/src/main/java/com/condation/cms/content/ContentRenderer.java @@ -43,13 +43,13 @@ public interface ContentRenderer { String render(final ReadOnlyFile contentFile, final RequestContext context) throws IOException; - String render(final ReadOnlyFile contentFile, final RequestContext context, final Map> sections) throws IOException; + String render(final ReadOnlyFile contentFile, final RequestContext context, final Map> slotItems) throws IOException; - String render(final ReadOnlyFile contentFile, final RequestContext context, final Map> sections, final Map meta, final String markdownContent, final Consumer modelExtending) throws IOException; + String render(final ReadOnlyFile contentFile, final RequestContext context, final Map> slotItems, final Map meta, final String markdownContent, final Consumer modelExtending) throws IOException; - Map> renderSections(final List sectionNodes, final RequestContext context) throws IOException; + Map> renderSlotItems(final List slotItemNodes, final RequestContext context) throws IOException; - String renderTaxonomy(final Optional contentFileOpt, final Taxonomy taxonomy, Optional taxonomyValue, final RequestContext context, final Map meta, final Page page, Map> sections) throws IOException; + String renderTaxonomy(final Optional contentFileOpt, final Taxonomy taxonomy, Optional taxonomyValue, final RequestContext context, final Map meta, final Page page, Map> slotItems) throws IOException; String renderView(final ReadOnlyFile viewFile, final View view, final ContentNode contentNode, final RequestContext requestContext, final Page page) throws IOException; diff --git a/cms-content/src/main/java/com/condation/cms/content/ContentResolver.java b/cms-content/src/main/java/com/condation/cms/content/ContentResolver.java index 548e8fbba..69e2d410e 100644 --- a/cms-content/src/main/java/com/condation/cms/content/ContentResolver.java +++ b/cms-content/src/main/java/com/condation/cms/content/ContentResolver.java @@ -115,11 +115,11 @@ private Optional getContent(final RequestContext context, boole try { - List sections = db.getContent().listSections(contentFile); + List slotItems = db.getContent().listSlotItems(contentFile); - Map> renderedSections = contentRenderer.renderSections(sections, context); + Map> renderedSlotItems = contentRenderer.renderSlotItems(slotItems, context); - var content = contentRenderer.render(contentFile, context, renderedSections); + var content = contentRenderer.render(contentFile, context, renderedSlotItems); var contentType = contentNode.contentType(); diff --git a/cms-content/src/main/java/com/condation/cms/content/DefaultContentRenderer.java b/cms-content/src/main/java/com/condation/cms/content/DefaultContentRenderer.java index bd1cda18f..0583ee5e5 100644 --- a/cms-content/src/main/java/com/condation/cms/content/DefaultContentRenderer.java +++ b/cms-content/src/main/java/com/condation/cms/content/DefaultContentRenderer.java @@ -45,7 +45,7 @@ import com.condation.cms.api.request.RequestContext; import com.condation.cms.api.template.TemplateEngine; import com.condation.cms.api.utils.PathUtil; -import com.condation.cms.api.utils.SectionUtil; +import com.condation.cms.api.utils.SlotUtil; import com.condation.cms.content.pipeline.ContentPipelineFactory; import com.condation.cms.content.views.model.View; import com.condation.cms.api.content.MapAccess; @@ -93,10 +93,10 @@ public String render(final ReadOnlyFile contentFile, final RequestContext contex } @Override - public String render(final ReadOnlyFile contentFile, final RequestContext context, final Map> sections) throws IOException { + public String render(final ReadOnlyFile contentFile, final RequestContext context, final Map> slotItems) throws IOException { var content = contentParser.parse(contentFile); - return render(contentFile, context, sections, content.meta(), content.content(), (model) -> { + return render(contentFile, context, slotItems, content.meta(), content.content(), (model) -> { }); } @@ -104,13 +104,13 @@ public String render(final ReadOnlyFile contentFile, final RequestContext contex public String renderTaxonomy( final Optional contentFileOpt, final Taxonomy taxonomy, Optional taxonomyValue, final RequestContext context, - final Map meta, final Page page, final Map> sections) throws IOException { + final Map meta, final Page page, final Map> slotItems) throws IOException { var contentFile = contentFileOpt.orElseGet(() -> db.getReadOnlyFileSystem().contentBase().resolve("index.md")); var content = contentFileOpt.isPresent() ? contentParser.parse(contentFileOpt.get()).content() : ""; - return render(contentFile, context, sections, meta, content, (model) -> { + return render(contentFile, context, slotItems, meta, content, (model) -> { model.values.put("taxonomy", taxonomy); model.values.put("taxonomy_values", db.getTaxonomies().values(taxonomy)); if (taxonomyValue.isPresent()) { @@ -137,7 +137,7 @@ private String renderContent(final String rawContent, final RequestContext conte @Override public String render(final ReadOnlyFile contentFile, final RequestContext context, - final Map> sections, + final Map> slotItems, final Map meta, final String rawContent, final Consumer modelExtending ) throws IOException { var uri = PathUtil.toRelativeFile(contentFile, db.getReadOnlyFileSystem().contentBase()); @@ -154,7 +154,9 @@ public String render(final ReadOnlyFile contentFile, final RequestContext contex Namespace namespace = new Namespace(); namespace.add(Constants.TemplateNamespaces.NODE, "meta", new MapAccess(meta)); - namespace.add(Constants.TemplateNamespaces.NODE, "sections", sections); + // sections will be removed + namespace.add(Constants.TemplateNamespaces.NODE, "sections", slotItems); + namespace.add(Constants.TemplateNamespaces.NODE, "slots", slotItems); namespace.add(Constants.TemplateNamespaces.NODE, "uri", uri); namespace.add(Constants.TemplateNamespaces.NODE, "translation", new NodeTranslations(contentNode.orElse(null), siteProperties)); @@ -277,36 +279,36 @@ private void extendModel(final TemplateEngine.Model model, Namespace namespace) } @Override - public Map> renderSections(final List sectionNodes, final RequestContext context) throws IOException { + public Map> renderSlotItems(final List slotItemNodes, final RequestContext context) throws IOException { - if (sectionNodes.isEmpty()) { + if (slotItemNodes.isEmpty()) { return Collections.emptyMap(); } - Map> sections = new HashMap<>(); + Map> slotItems = new HashMap<>(); final ReadOnlyFile contentBase = db.getReadOnlyFileSystem().contentBase(); - sectionNodes.forEach(node -> { + slotItemNodes.forEach(node -> { try { - var sectionPath = contentBase.resolve(node.uri()); - var content = render(sectionPath, context); - var name = SectionUtil.getSectionName(node.name()); - var index = node.getMetaValue(Constants.MetaFields.LAYOUT_ORDER, Constants.DEFAULT_SECTION_LAYOUT_ORDER); + var slotItemPath = contentBase.resolve(node.uri()); + var content = render(slotItemPath, context); + var name = SlotUtil.getSlotItemName(node.name()); + var index = node.getMetaValue(Constants.MetaFields.LAYOUT_ORDER, Constants.DEFAULT_SLOT_ITEM_LAYOUT_ORDER); - if (!sections.containsKey(name)) { - sections.put(name, new ArrayList<>()); + if (!slotItems.containsKey(name)) { + slotItems.put(name, new ArrayList<>()); } - sections.get(name).add(new Section(name, index, content, node.data())); + slotItems.get(name).add(new SlotItem(name, index, content, node.data())); } catch (Exception ex) { - log.error("error render section", ex); + log.error("error render slotItems", ex); } }); - sections.values().forEach(list -> list.sort((s1, s2) -> Integer.compare(s1.index(), s2.index()))); + slotItems.values().forEach(list -> list.sort((s1, s2) -> Integer.compare(s1.index(), s2.index()))); - return sections; + return slotItems; } } diff --git a/cms-content/src/main/java/com/condation/cms/content/Section.java b/cms-content/src/main/java/com/condation/cms/content/SlotItem.java similarity index 73% rename from cms-content/src/main/java/com/condation/cms/content/Section.java rename to cms-content/src/main/java/com/condation/cms/content/SlotItem.java index b0e9c7d79..67891b40a 100644 --- a/cms-content/src/main/java/com/condation/cms/content/Section.java +++ b/cms-content/src/main/java/com/condation/cms/content/SlotItem.java @@ -30,13 +30,13 @@ * * @author t.marx */ -public record Section(String name, int index, String content, Map data, String uri) { +public record SlotItem(String name, int index, String content, Map data, String uri) { - public Section(String name, int index, String content, Map data) { + public SlotItem(String name, int index, String content, Map data) { this(name, index, content, data, null); } - public Section(String name, String content, Map data) { - this(name, Constants.DEFAULT_SECTION_LAYOUT_ORDER, content, data, null); + public SlotItem(String name, String content, Map data) { + this(name, Constants.DEFAULT_SLOT_ITEM_LAYOUT_ORDER, content, data, null); } } diff --git a/cms-content/src/main/java/com/condation/cms/content/TaxonomyResolver.java b/cms-content/src/main/java/com/condation/cms/content/TaxonomyResolver.java index 89ab57219..c4a57e767 100644 --- a/cms-content/src/main/java/com/condation/cms/content/TaxonomyResolver.java +++ b/cms-content/src/main/java/com/condation/cms/content/TaxonomyResolver.java @@ -119,7 +119,7 @@ public Optional getTaxonomyResponse(final RequestContext conte Optional contentFileOpt = ContentResolvingStrategy.resolve(context.get(RequestFeature.class).uri(), db); - Map> sections = Collections.emptyMap(); + Map> slotItems = Collections.emptyMap(); if (contentFileOpt.isPresent()) { var contentFile = contentFileOpt.get(); @@ -128,12 +128,12 @@ public Optional getTaxonomyResponse(final RequestContext conte meta.putAll(content.meta()); - List sectionList = db.getContent().listSections(contentFile); + List slotItemList = db.getContent().listSlotItems(contentFile); - sections = contentRenderer.renderSections(sectionList, context); + slotItems = contentRenderer.renderSlotItems(slotItemList, context); } - String content = contentRenderer.renderTaxonomy(contentFileOpt, taxonomy, value, context, meta, resultPage, sections); + String content = contentRenderer.renderTaxonomy(contentFileOpt, taxonomy, value, context, meta, resultPage, slotItems); return Optional.of(new TaxonomyResponse(content, taxonomy)); } catch (Exception ex) { diff --git a/cms-content/src/main/java/com/condation/cms/content/template/functions/translation/NodeTranslations.java b/cms-content/src/main/java/com/condation/cms/content/template/functions/translation/NodeTranslations.java index 333c880c2..b59c32b9d 100644 --- a/cms-content/src/main/java/com/condation/cms/content/template/functions/translation/NodeTranslations.java +++ b/cms-content/src/main/java/com/condation/cms/content/template/functions/translation/NodeTranslations.java @@ -32,6 +32,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; /** * @@ -65,8 +66,12 @@ public List translations () { locale = siteProperties.locale().getCountry().toLowerCase(); } + if (locale == null) { + return null; + } + return new TranslationDto(mapping.language(), locale, mapping.language().equals(siteProperties.language()), url); - }).toList(); + }).filter(Objects::nonNull).toList(); } public static record TranslationDto (String lang, String locale, boolean current, String url) { diff --git a/cms-content/src/test/java/com/condation/cms/content/template/navigation/NavigationFunctionTest.java b/cms-content/src/test/java/com/condation/cms/content/template/navigation/NavigationFunctionTest.java index 64013be80..4ad7edff6 100644 --- a/cms-content/src/test/java/com/condation/cms/content/template/navigation/NavigationFunctionTest.java +++ b/cms-content/src/test/java/com/condation/cms/content/template/navigation/NavigationFunctionTest.java @@ -28,7 +28,7 @@ import com.condation.cms.api.db.DB; import com.condation.cms.api.db.DBFileSystem; import com.condation.cms.api.db.cms.NIOReadOnlyFile; -import com.condation.cms.api.db.cms.ReadyOnlyFileSystem; +import com.condation.cms.api.db.cms.ReadOnlyFileSystem; import com.condation.cms.api.feature.features.ContentNodeMapperFeature; import com.condation.cms.api.feature.features.ContentParserFeature; import com.condation.cms.api.feature.features.HookSystemFeature; @@ -74,7 +74,7 @@ public class NavigationFunctionTest { @Mock ContentNodeMapper contentNodeMapper; @Mock - ReadyOnlyFileSystem cmsFileSystem; + ReadOnlyFileSystem cmsFileSystem; NavigationFunction sut; diff --git a/cms-core/src/main/java/com/condation/cms/core/eventbus/MessagingEventBus.java b/cms-core/src/main/java/com/condation/cms/core/eventbus/MessagingEventBus.java index 0f7b02a94..d2d13fe47 100644 --- a/cms-core/src/main/java/com/condation/cms/core/eventbus/MessagingEventBus.java +++ b/cms-core/src/main/java/com/condation/cms/core/eventbus/MessagingEventBus.java @@ -25,6 +25,7 @@ import com.condation.cms.api.eventbus.EventBus; import com.condation.cms.api.eventbus.EventListener; import com.condation.cms.api.messaging.Messaging; +import com.condation.cms.api.messaging.Topic; import com.google.inject.Inject; import lombok.extern.slf4j.Slf4j; @@ -56,6 +57,6 @@ public void publish(final T event) { @Override public void syncPublish(T event) { - publish(event); + messaging.topic(event.getClass().getName()).publish(event, Topic.Mode.SYNC); } } diff --git a/cms-filesystem/src/main/java/com/condation/cms/filesystem/FileContent.java b/cms-filesystem/src/main/java/com/condation/cms/filesystem/FileContent.java index 262a9077c..b2a721c98 100644 --- a/cms-filesystem/src/main/java/com/condation/cms/filesystem/FileContent.java +++ b/cms-filesystem/src/main/java/com/condation/cms/filesystem/FileContent.java @@ -25,13 +25,13 @@ import com.condation.cms.api.db.ContentNode; import com.condation.cms.api.db.ContentQuery; import com.condation.cms.api.db.cms.ReadOnlyFile; -import com.condation.cms.api.db.cms.ReadyOnlyFileSystem; import com.condation.cms.api.utils.PathUtil; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.BiFunction; import lombok.RequiredArgsConstructor; +import com.condation.cms.api.db.cms.ReadOnlyFileSystem; /** * @@ -41,7 +41,7 @@ public class FileContent implements Content { private final FileSystem fileSystem; - private final ReadyOnlyFileSystem cmsFileSystem; + private final ReadOnlyFileSystem cmsFileSystem; @Override public boolean isVisible(String uri) { @@ -54,12 +54,12 @@ public boolean isVisible(ContentNode node) { } @Override - public List listSections(ReadOnlyFile contentFile) { + public List listSlotItems(ReadOnlyFile contentFile) { String folder = PathUtil.toRelativePath(contentFile, cmsFileSystem.contentBase()); String filename = contentFile.getFileName(); filename = filename.substring(0, filename.length() - 3); - return fileSystem.listSections(filename, folder); + return fileSystem.listSlotItems(filename, folder); } @Override diff --git a/cms-filesystem/src/main/java/com/condation/cms/filesystem/FileDB.java b/cms-filesystem/src/main/java/com/condation/cms/filesystem/FileDB.java index 28f4fe940..29a17a8d9 100644 --- a/cms-filesystem/src/main/java/com/condation/cms/filesystem/FileDB.java +++ b/cms-filesystem/src/main/java/com/condation/cms/filesystem/FileDB.java @@ -27,7 +27,6 @@ import com.condation.cms.api.db.Content; import com.condation.cms.api.db.DB; import com.condation.cms.api.db.DBFileSystem; -import com.condation.cms.api.db.cms.ReadyOnlyFileSystem; import com.condation.cms.api.db.cms.WrappedReadOnlyFileSystem; import com.condation.cms.api.db.taxonomy.Taxonomies; import com.condation.cms.api.eventbus.EventBus; @@ -37,6 +36,7 @@ import java.util.Map; import java.util.function.Function; import lombok.RequiredArgsConstructor; +import com.condation.cms.api.db.cms.ReadOnlyFileSystem; /** * @@ -52,7 +52,7 @@ public class FileDB implements DB { private FileSystem fileSystem; private FileContent content; - private ReadyOnlyFileSystem readOnlyFileSystem; + private ReadOnlyFileSystem readOnlyFileSystem; private FileTaxonomies taxonomies; @@ -73,7 +73,7 @@ public void init (MetaData.Type metaDataType) throws IOException { } @Override - public ReadyOnlyFileSystem getReadOnlyFileSystem() { + public ReadOnlyFileSystem getReadOnlyFileSystem() { return readOnlyFileSystem; } diff --git a/cms-filesystem/src/main/java/com/condation/cms/filesystem/FileSystem.java b/cms-filesystem/src/main/java/com/condation/cms/filesystem/FileSystem.java index 017829c35..21dce3f55 100644 --- a/cms-filesystem/src/main/java/com/condation/cms/filesystem/FileSystem.java +++ b/cms-filesystem/src/main/java/com/condation/cms/filesystem/FileSystem.java @@ -35,6 +35,7 @@ import com.condation.cms.api.utils.PathUtil; import com.condation.cms.core.utils.MdcScope; import com.condation.cms.filesystem.metadata.AbstractMetaData; +import com.condation.cms.filesystem.metadata.PageMetaData; import com.condation.cms.filesystem.metadata.memory.MemoryMetaData; import com.condation.cms.filesystem.metadata.persistent.PersistentMetaData; import java.io.IOException; @@ -96,7 +97,7 @@ public boolean isVisible(final String uri) { return false; } var n = node.get(); - return AbstractMetaData.isVisible(n); + return PageMetaData.isVisible(n); } @Override @@ -215,28 +216,28 @@ public List listContent(final String folder) { } - public List listSections(final Path contentFile) { + public List listSlotItems(final Path contentFile) { String folder = PathUtil.toRelativePath(contentFile, contentBase); String filename = contentFile.getFileName().toString(); filename = filename.substring(0, filename.length() - 3); - return listSections(filename, folder); + return FileSystem.this.listSlotItems(filename, folder); } - public List listSections(final String filename, String folder) { + public List listSlotItems(final String filename, String folder) { List nodes = new ArrayList<>(); - final Pattern isSectionOf = Constants.SECTION_OF_PATTERN.apply(filename); - final Pattern isNamedSectionOf = Constants.SECTION_NAMED_OF_PATTERN.apply(filename); + final Pattern isSlotItemOf = Constants.SLOT_ITEM_OF_PATTERN.apply(filename); + final Pattern isNamedSlotItemOf = Constants.SLOT_ITEM_NAMED_OF_PATTERN.apply(filename); if ("".equals(folder)) { metaData.getTree().values() .stream() .filter(node -> !node.isHidden()) .filter(node -> node.isVisible()) - .filter(node -> node.isSection()) + .filter(node -> node.isSlotItem()) .filter(node -> { - return isSectionOf.matcher(node.name()).matches() || isNamedSectionOf.matcher(node.name()).matches(); + return isSlotItemOf.matcher(node.name()).matches() || isNamedSlotItemOf.matcher(node.name()).matches(); }) .forEach((node) -> { nodes.add(node); @@ -248,9 +249,9 @@ public List listSections(final String filename, String folder) { .stream() .filter(node -> !node.isHidden()) .filter(node -> node.isVisible()) - .filter(node -> node.isSection()) + .filter(node -> node.isSlotItem()) .filter(node - -> isSectionOf.matcher(node.name()).matches() || isNamedSectionOf.matcher(node.name()).matches() + -> isSlotItemOf.matcher(node.name()).matches() || isNamedSlotItemOf.matcher(node.name()).matches() ) .forEach((node) -> { nodes.add(node); diff --git a/cms-filesystem/src/main/java/com/condation/cms/filesystem/metadata/AbstractMetaData.java b/cms-filesystem/src/main/java/com/condation/cms/filesystem/metadata/AbstractMetaData.java index a08f673a6..1454b5cac 100644 --- a/cms-filesystem/src/main/java/com/condation/cms/filesystem/metadata/AbstractMetaData.java +++ b/cms-filesystem/src/main/java/com/condation/cms/filesystem/metadata/AbstractMetaData.java @@ -72,26 +72,6 @@ public ConcurrentMap getTree() { return new ConcurrentHashMap<>(tree); } - public static boolean isVisible (ContentNode node) { - - if (RequestContextScope.REQUEST_CONTEXT.isBound() - && RequestContextScope.REQUEST_CONTEXT.get().has(IsPreviewFeature.class) - && RequestContextScope.REQUEST_CONTEXT.get().get(IsPreviewFeature.class).mode().equals(com.condation.cms.api.feature.features.IsPreviewFeature.Mode.MANAGER) - ) { - return true; - } - - if (node == null || node.isSection()) { - return false; - } - - if (node.isParentPathHidden()) { - return false; - } - - return node.isVisible() && !node.isHidden(); - } - @Override public Optional byUri(String uri) { if (!nodes.containsKey(uri)) { @@ -154,7 +134,8 @@ public List listChildren(String uri) { .filter(node -> !node.isHidden()) .map(this::mapToIndex) .filter(node -> node != null) - .filter(AbstractMetaData::isVisible) + .filter(PageMetaData::isPage) + .filter(PageMetaData::isVisible) .toList(); } else { @@ -165,7 +146,8 @@ public List listChildren(String uri) { .filter(node -> !node.isHidden()) .map(this::mapToIndex) .filter(node -> node != null) - .filter(AbstractMetaData::isVisible) + .filter(PageMetaData::isPage) + .filter(PageMetaData::isVisible) .toList(); } } diff --git a/cms-filesystem/src/main/java/com/condation/cms/filesystem/metadata/PageMetaData.java b/cms-filesystem/src/main/java/com/condation/cms/filesystem/metadata/PageMetaData.java new file mode 100644 index 000000000..3dd414714 --- /dev/null +++ b/cms-filesystem/src/main/java/com/condation/cms/filesystem/metadata/PageMetaData.java @@ -0,0 +1,57 @@ +package com.condation.cms.filesystem.metadata; + +/*- + * #%L + * CMS FileSystem + * %% + * Copyright (C) 2023 - 2026 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * #L% + */ + +import com.condation.cms.api.db.ContentNode; +import com.condation.cms.api.feature.features.IsPreviewFeature; +import com.condation.cms.api.request.RequestContextScope; + +/** + * + * @author thorstenmarx + */ +public abstract class PageMetaData { + + public static boolean isPage (ContentNode node) { + return !node.isSlotItem(); + } + + public static boolean isVisible (ContentNode node) { + + if (node == null || !isPage(node)) { + return false; + } + + if (RequestContextScope.REQUEST_CONTEXT.isBound() + && RequestContextScope.REQUEST_CONTEXT.get().has(IsPreviewFeature.class) + && RequestContextScope.REQUEST_CONTEXT.get().get(IsPreviewFeature.class).mode().equals(com.condation.cms.api.feature.features.IsPreviewFeature.Mode.MANAGER) + ) { + return true; + } + + if (node.isParentPathHidden()) { + return false; + } + + return node.isVisible() && !node.isHidden(); + } +} diff --git a/cms-filesystem/src/main/java/com/condation/cms/filesystem/metadata/memory/MemoryQuery.java b/cms-filesystem/src/main/java/com/condation/cms/filesystem/metadata/memory/MemoryQuery.java index 32a9877a5..e57c38835 100644 --- a/cms-filesystem/src/main/java/com/condation/cms/filesystem/metadata/memory/MemoryQuery.java +++ b/cms-filesystem/src/main/java/com/condation/cms/filesystem/metadata/memory/MemoryQuery.java @@ -27,6 +27,7 @@ import com.condation.cms.api.db.Page; import com.condation.cms.api.utils.NodeUtil; import com.condation.cms.filesystem.metadata.AbstractMetaData; +import com.condation.cms.filesystem.metadata.PageMetaData; import static com.condation.cms.filesystem.metadata.memory.QueryUtil.filtered; import static com.condation.cms.filesystem.metadata.memory.QueryUtil.sorted; import com.condation.cms.filesystem.metadata.query.ExcerptMapperFunction; @@ -144,7 +145,8 @@ public List get() { return context.getNodes() .filter(NodeUtil.contentTypeFiler(context.getContentType())) .filter(node -> !node.isDirectory()) - .filter(AbstractMetaData::isVisible) + .filter(PageMetaData::isPage) + .filter(PageMetaData::isVisible) .map(context.getNodeMapper()) .toList(); } @@ -172,7 +174,8 @@ public Page page(final long page, final long pageSize) { var filteredNodes = context.getNodes() .filter(NodeUtil.contentTypeFiler(context.getContentType())) .filter(node -> !node.isDirectory()) - .filter(AbstractMetaData::isVisible) + .filter(PageMetaData::isPage) + .filter(PageMetaData::isVisible) .toList(); var totalItems = filteredNodes.size(); diff --git a/cms-filesystem/src/main/java/com/condation/cms/filesystem/metadata/persistent/LuceneQuery.java b/cms-filesystem/src/main/java/com/condation/cms/filesystem/metadata/persistent/LuceneQuery.java index 4f43b1f51..730dc7a40 100644 --- a/cms-filesystem/src/main/java/com/condation/cms/filesystem/metadata/persistent/LuceneQuery.java +++ b/cms-filesystem/src/main/java/com/condation/cms/filesystem/metadata/persistent/LuceneQuery.java @@ -27,6 +27,7 @@ import com.condation.cms.api.db.Page; import com.condation.cms.filesystem.MetaData; import com.condation.cms.filesystem.metadata.AbstractMetaData; +import com.condation.cms.filesystem.metadata.PageMetaData; import com.condation.cms.filesystem.metadata.memory.QueryUtil; import com.condation.cms.filesystem.metadata.query.ExcerptMapperFunction; import com.condation.cms.filesystem.metadata.query.ExtendableQuery; @@ -164,7 +165,8 @@ private List queryContentNodes() { .filter(Optional::isPresent) .map(Optional::get) .filter(node -> !node.isDirectory()) - .filter(AbstractMetaData::isVisible) + .filter(PageMetaData::isPage) + .filter(PageMetaData::isVisible) .toList(); if (!extensionOperations.isEmpty()) { diff --git a/cms-filesystem/src/test/resources/content/index.slot.item.md b/cms-filesystem/src/test/resources/content/index.slot.item.md new file mode 100644 index 000000000..51b3cd6f3 --- /dev/null +++ b/cms-filesystem/src/test/resources/content/index.slot.item.md @@ -0,0 +1,5 @@ +featured: true +publish_date: 2024-09-23 +name: start +keywords: ["eins", "zwei"] +published: true \ No newline at end of file diff --git a/cms-server/src/main/java/com/condation/cms/server/host/VHost.java b/cms-server/src/main/java/com/condation/cms/server/host/VHost.java index d13a2bdc7..2fb09bafd 100644 --- a/cms-server/src/main/java/com/condation/cms/server/host/VHost.java +++ b/cms-server/src/main/java/com/condation/cms/server/host/VHost.java @@ -283,18 +283,12 @@ public Handler buildHttpHandler() { log.debug("create assets handler for site"); ResourceHandler assetsHandler = injector.getInstance(Key.get(ResourceHandler.class, Names.named("site.assets"))); - ResourceHandler faviconHandler = new ResourceHandler(); - faviconHandler.setDirAllowed(false); - var assetBase = this.injector.getInstance(Key.get(Path.class, Names.named("assets"))); - faviconHandler.setBaseResource(new FileFolderPathResource(assetBase.resolve("favicon.ico"))); - PathMappingsHandler pathMappingsHandler = new PathMappingsHandler(); pathMappingsHandler.addMapping( PathSpec.from("/"), defaultHandlerSequence ); pathMappingsHandler.addMapping(PathSpec.from("/assets/*"), assetsHandler); - pathMappingsHandler.addMapping(PathSpec.from("/favicon.ico"), faviconHandler); var assetsMediaManager = this.injector.getInstance(SiteMediaManager.class); injector.getInstance(EventBus.class).register(ConfigurationReloadEvent.class, assetsMediaManager); diff --git a/cms-templates/src/main/java/com/condation/cms/templates/TemplateEngineFactory.java b/cms-templates/src/main/java/com/condation/cms/templates/TemplateEngineFactory.java index c5723e567..f4dad1f9e 100644 --- a/cms-templates/src/main/java/com/condation/cms/templates/TemplateEngineFactory.java +++ b/cms-templates/src/main/java/com/condation/cms/templates/TemplateEngineFactory.java @@ -25,6 +25,8 @@ import com.condation.cms.templates.filter.impl.DateFilter; import com.condation.cms.templates.filter.impl.DefaultFilter; import com.condation.cms.templates.filter.impl.MarkdownFilter; +import com.condation.cms.templates.filter.impl.MinusFilter; +import com.condation.cms.templates.filter.impl.PlusFilter; import com.condation.cms.templates.filter.impl.RawFilter; import com.condation.cms.templates.filter.impl.UpperFilter; import com.condation.cms.templates.tags.ElseIfTag; @@ -94,6 +96,8 @@ public TemplateEngineFactory defaultTags() { public TemplateEngineFactory defaultFilters() { configuration + .registerFilter(MinusFilter.NAME, new MinusFilter()) + .registerFilter(PlusFilter.NAME, new PlusFilter()) .registerFilter(DateFilter.NAME, new DateFilter()) .registerFilter(UpperFilter.NAME, new UpperFilter()) .registerFilter(RawFilter.NAME, new RawFilter()) diff --git a/cms-templates/src/main/java/com/condation/cms/templates/components/TemplateFunctions.java b/cms-templates/src/main/java/com/condation/cms/templates/components/TemplateFunctions.java index bcb7e0703..3362485b8 100644 --- a/cms-templates/src/main/java/com/condation/cms/templates/components/TemplateFunctions.java +++ b/cms-templates/src/main/java/com/condation/cms/templates/components/TemplateFunctions.java @@ -95,11 +95,11 @@ public void register(Object handler) { for (var entry : annotations) { String name = entry.annotation().value(); - String namespace = entry.annotation().value(); + String namespace = entry.annotation().namespace(); functionMap.put(namespace, name, param -> { try { - return entry.invoke(null); + return entry.invoke((Object[]) null); } catch (Exception e) { throw new RuntimeException("Error calling component: " + name, e); } diff --git a/cms-templates/src/main/java/com/condation/cms/templates/filter/impl/MinusFilter.java b/cms-templates/src/main/java/com/condation/cms/templates/filter/impl/MinusFilter.java new file mode 100644 index 000000000..f2ef05df4 --- /dev/null +++ b/cms-templates/src/main/java/com/condation/cms/templates/filter/impl/MinusFilter.java @@ -0,0 +1,54 @@ +package com.condation.cms.templates.filter.impl; + +/*- + * #%L + * CMS Templates + * %% + * Copyright (C) 2023 - 2026 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * #L% + */ + +import com.condation.cms.templates.filter.Filter; +import com.condation.cms.templates.utils.NumberUtils; + +public class MinusFilter implements Filter { + + public static final String NAME = "minus"; + + @Override + public Object apply(Object input, Object... params) { + if (input == null) { + return input; + } + + Object value = 1; + if (params.length > 0) { + value = params[0]; + } + + if (input instanceof Long longInput) { + return longInput - NumberUtils.castToNumber(value, 1L); + } else if (input instanceof Integer intInput) { + return intInput - NumberUtils.castToNumber(value, 1); + } else if (input instanceof Double doubleInput) { + return doubleInput - NumberUtils.castToNumber(value, 1D); + } else if (input instanceof Float floatInput) { + return floatInput - NumberUtils.castToNumber(value, 1f); + } + return input; + } + +} diff --git a/cms-templates/src/main/java/com/condation/cms/templates/filter/impl/PlusFilter.java b/cms-templates/src/main/java/com/condation/cms/templates/filter/impl/PlusFilter.java new file mode 100644 index 000000000..74b5fdc42 --- /dev/null +++ b/cms-templates/src/main/java/com/condation/cms/templates/filter/impl/PlusFilter.java @@ -0,0 +1,54 @@ +package com.condation.cms.templates.filter.impl; + +/*- + * #%L + * CMS Templates + * %% + * Copyright (C) 2023 - 2026 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * #L% + */ + +import com.condation.cms.templates.filter.Filter; +import com.condation.cms.templates.utils.NumberUtils; + +public class PlusFilter implements Filter { + + public static final String NAME = "plus"; + + @Override + public Object apply(Object input, Object... params) { + if (input == null) { + return input; + } + + Object value = 1; + if (params.length > 0) { + value = params[0]; + } + + if (input instanceof Long longInput) { + return longInput + NumberUtils.castToNumber(value, 1L); + } else if (input instanceof Integer intInput) { + return intInput + NumberUtils.castToNumber(value, 1); + } else if (input instanceof Double doubleInput) { + return doubleInput + NumberUtils.castToNumber(value, 1D); + } else if (input instanceof Float floatInput) { + return floatInput + NumberUtils.castToNumber(value, 1f); + } + return input; + } + +} diff --git a/cms-templates/src/main/java/com/condation/cms/templates/functions/impl/NodeFunction.java b/cms-templates/src/main/java/com/condation/cms/templates/functions/impl/NodeFunction.java index 0c173fcf2..042f78b2f 100644 --- a/cms-templates/src/main/java/com/condation/cms/templates/functions/impl/NodeFunction.java +++ b/cms-templates/src/main/java/com/condation/cms/templates/functions/impl/NodeFunction.java @@ -27,7 +27,7 @@ import com.condation.cms.api.feature.features.InjectorFeature; import com.condation.cms.api.request.RequestContext; import com.condation.cms.content.ContentRenderer; -import com.condation.cms.content.Section; +import com.condation.cms.content.SlotItem; import java.io.IOException; import java.util.List; import java.util.Map; @@ -51,10 +51,10 @@ protected void extendMap(Map node, ReadOnlyFile contentFile) { try { var db = requestContext.get(InjectorFeature.class).injector().getInstance(DB.class); var contentRenderer = requestContext.get(InjectorFeature.class).injector().getInstance(ContentRenderer.class); - List sections = db.getContent().listSections(contentFile); + List slotItemList = db.getContent().listSlotItems(contentFile); - Map> renderedSections = contentRenderer.renderSections(sections, requestContext); - node.put("sections", renderedSections); + Map> slotItems = contentRenderer.renderSlotItems(slotItemList, requestContext); + node.put("slots", slotItems); } catch (IOException iOException) { log.error("error loading sections", iOException); } diff --git a/cms-templates/src/main/java/com/condation/cms/templates/utils/NumberUtils.java b/cms-templates/src/main/java/com/condation/cms/templates/utils/NumberUtils.java new file mode 100644 index 000000000..d9c74f09a --- /dev/null +++ b/cms-templates/src/main/java/com/condation/cms/templates/utils/NumberUtils.java @@ -0,0 +1,71 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package com.condation.cms.templates.utils; + +/*- + * #%L + * CMS Templates + * %% + * Copyright (C) 2023 - 2026 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * #L% + */ + +public class NumberUtils { + + /** + * Konvertiert ein Objekt in eine Zahl (int oder long). + * Falls das Objekt null ist oder keine Zahl darstellt, wird der Default-Wert zurückgegeben. + * + * @param obj Das zu prüfende Objekt + * @param defaultValue Der Standardwert (bestimmt auch den Rückgabetyp) + * @return Das konvertierte Objekt oder der Default-Wert + */ + @SuppressWarnings("unchecked") + public static T castToNumber(Object obj, T defaultValue) { + if (obj == null) { + return defaultValue; + } + + // Fall 1: Das Objekt ist bereits eine Instanz einer Zahl (Integer, Long, Double, etc.) + if (obj instanceof Number) { + Number number = (Number) obj; + if (defaultValue instanceof Integer) { + return (T) Integer.valueOf(number.intValue()); + } else if (defaultValue instanceof Long) { + return (T) Long.valueOf(number.longValue()); + } + } + + // Fall 2: Das Objekt ist ein String (z.B. "123"), der geparsed werden kann + if (obj instanceof String) { + try { + String str = (String) obj; + if (defaultValue instanceof Integer) { + return (T) Integer.valueOf(Integer.parseInt(str)); + } else if (defaultValue instanceof Long) { + return (T) Long.valueOf(Long.parseLong(str)); + } + } catch (NumberFormatException e) { + // String war keine gültige Zahl -> ignoriert, liefert Default-Wert + } + } + + // Fall 3: Weder Zahl noch passender String -> Default-Wert + return defaultValue; + } +} diff --git a/cms-templates/src/test/java/com/condation/cms/templates/TemplateEngineFilterTest.java b/cms-templates/src/test/java/com/condation/cms/templates/TemplateEngineFilterTest.java index 68511686e..43780095a 100644 --- a/cms-templates/src/test/java/com/condation/cms/templates/TemplateEngineFilterTest.java +++ b/cms-templates/src/test/java/com/condation/cms/templates/TemplateEngineFilterTest.java @@ -39,9 +39,10 @@ public TemplateLoader getLoader() { return new StringTemplateLoader() .add("var_default", "{{ content | default('the default text') }}") .add("simple", "{{ meta['date'] | date }}") + .add("minus", "{{ meta['year'] | minus(1) }}") + .add("plus", "{{ meta['year'] | plus(1) }}") .add("month_year", "{{ meta['date'] | date('MM/yyyy')}}") - .add("format_issue", "{{ meta['date'] | date('MMM d, yyyy')}}") - ; + .add("format_issue", "{{ meta['date'] | date('MMM d, yyyy')}}"); } @@ -80,7 +81,7 @@ public void test_month_year() throws IOException { Assertions.assertThat(result).isEqualTo(format.format(date)); } - + @Test public void test_default_filter() throws IOException { @@ -91,21 +92,21 @@ public void test_default_filter() throws IOException { Map.of("content", "<p></p>") ); - Assertions.assertThat(result).isEqualTo("the default text"); - + Assertions.assertThat(result).isEqualTo("the default text"); + result = simpleTemplate.evaluate( Map.of("content", "

") ); Assertions.assertThat(result).isEqualTo("the default text"); - + result = simpleTemplate.evaluate( Map.of("content", "") ); Assertions.assertThat(result).isEqualTo("the default text"); } - + @Test public void issue_format() throws IOException { @@ -123,4 +124,34 @@ public void issue_format() throws IOException { Assertions.assertThat(result).isEqualTo(format.format(date)); } + + @Test + public void test_minus() throws IOException { + + Template simpleTemplate = SUT.getTemplate("minus"); + Assertions.assertThat(simpleTemplate).isNotNull(); + + Map context = Map.of("meta", Map.of( + "year", 2026 + )); + + var result = simpleTemplate.evaluate(context); + + Assertions.assertThat(result).isEqualTo("2025"); + } + + @Test + public void issue_plus() throws IOException { + + Template simpleTemplate = SUT.getTemplate("plus"); + Assertions.assertThat(simpleTemplate).isNotNull(); + + Map context = Map.of("meta", Map.of( + "year", 2026 + )); + + var result = simpleTemplate.evaluate(context); + + Assertions.assertThat(result).isEqualTo("2027"); + } } diff --git a/integration-tests/src/test/java/com/condation/cms/SectionsTest.java b/integration-tests/src/test/java/com/condation/cms/SlotItemsTest.java similarity index 82% rename from integration-tests/src/test/java/com/condation/cms/SectionsTest.java rename to integration-tests/src/test/java/com/condation/cms/SlotItemsTest.java index 89e9bfe35..118034a0c 100644 --- a/integration-tests/src/test/java/com/condation/cms/SectionsTest.java +++ b/integration-tests/src/test/java/com/condation/cms/SlotItemsTest.java @@ -33,7 +33,7 @@ import com.condation.cms.api.template.TemplateEngine; import com.condation.cms.content.DefaultContentParser; import com.condation.cms.content.DefaultContentRenderer; -import com.condation.cms.content.Section; +import com.condation.cms.content.SlotItem; import com.condation.cms.core.eventbus.DefaultEventBus; import com.condation.cms.filesystem.FileDB; import com.condation.cms.template.TemplateEngineTest; @@ -52,7 +52,7 @@ * * @author t.marx */ -public class SectionsTest extends TemplateEngineTest { +public class SlotItemsTest extends TemplateEngineTest { static DefaultContentRenderer contentRenderer; static MarkdownRenderer markdownRenderer; @@ -93,20 +93,20 @@ public static void beforeClass() throws IOException { } @Test - public void test_sections() throws IOException { - List listSections = db.getContent().listSections(db.getReadOnlyFileSystem().contentBase().resolve("page.md")); - Assertions.assertThat(listSections).hasSize(4); + public void test_slot_items() throws IOException { + List listSlotItems = db.getContent().listSlotItems(db.getReadOnlyFileSystem().contentBase().resolve("page.md")); + Assertions.assertThat(listSlotItems).hasSize(4); - Map> renderSections = contentRenderer.renderSections(listSections, TestHelper.requestContext()); + Map> renderedSlotItems = contentRenderer.renderSlotItems(listSlotItems, TestHelper.requestContext()); - Assertions.assertThat(renderSections) + Assertions.assertThat(renderedSlotItems) .hasSize(1) .containsKey("left"); - Assertions.assertThat(renderSections.get("left")) + Assertions.assertThat(renderedSlotItems.get("left")) .hasSize(4); - var sectionIndexes = renderSections.get("left").stream().map(section -> section.index()).collect(Collectors.toList()); - Assertions.assertThat(sectionIndexes).containsExactly(0, 1, 2, 10); + var slotItemIndexes = renderedSlotItems.get("left").stream().map(item -> item.index()).collect(Collectors.toList()); + Assertions.assertThat(slotItemIndexes).containsExactly(0, 1, 2, 10); } } diff --git a/modules/api-module/src/main/java/com/condation/cms/modules/system/api/helpers/NodeHelper.java b/modules/api-module/src/main/java/com/condation/cms/modules/system/api/helpers/NodeHelper.java index 4bde9a186..2eb4f414b 100644 --- a/modules/api-module/src/main/java/com/condation/cms/modules/system/api/helpers/NodeHelper.java +++ b/modules/api-module/src/main/java/com/condation/cms/modules/system/api/helpers/NodeHelper.java @@ -23,10 +23,9 @@ import com.condation.cms.api.configuration.configs.SiteConfiguration; import com.condation.cms.api.db.ContentNode; import com.condation.cms.api.feature.features.ConfigurationFeature; -import com.condation.cms.api.feature.features.IsPreviewFeature; import com.condation.cms.api.request.RequestContext; import com.condation.cms.api.utils.HTTPUtil; -import com.condation.cms.filesystem.metadata.AbstractMetaData; +import com.condation.cms.filesystem.metadata.PageMetaData; import java.util.Collections; import java.util.Map; import org.eclipse.jetty.server.Request; @@ -41,7 +40,7 @@ private NodeHelper() { } public static Map getLinks(ContentNode node, Request request) { - if (!AbstractMetaData.isVisible(node)) { + if (!PageMetaData.isVisible(node) || !PageMetaData.isPage(node)) { return Collections.emptyMap(); } return getLinks(node.uri(), request); diff --git a/modules/ui-module/src/main/java/com/condation/cms/modules/ui/extensionpoints/MenuHookExtension.java b/modules/ui-module/src/main/java/com/condation/cms/modules/ui/extensionpoints/MenuHookExtension.java index cd76a7cae..8876925c4 100644 --- a/modules/ui-module/src/main/java/com/condation/cms/modules/ui/extensionpoints/MenuHookExtension.java +++ b/modules/ui-module/src/main/java/com/condation/cms/modules/ui/extensionpoints/MenuHookExtension.java @@ -28,7 +28,6 @@ import com.condation.cms.api.ui.action.UIScriptAction; import com.condation.cms.api.ui.elements.Menu; import com.condation.cms.api.ui.elements.MenuEntry; -import com.condation.modules.api.annotation.Extension; import java.util.ArrayList; import java.util.List; import java.util.Map; diff --git a/modules/ui-module/src/main/java/com/condation/cms/modules/ui/extensionpoints/SiteMenuExtension.java b/modules/ui-module/src/main/java/com/condation/cms/modules/ui/extensionpoints/SiteMenuExtension.java index 6ebdf5a78..feea51604 100644 --- a/modules/ui-module/src/main/java/com/condation/cms/modules/ui/extensionpoints/SiteMenuExtension.java +++ b/modules/ui-module/src/main/java/com/condation/cms/modules/ui/extensionpoints/SiteMenuExtension.java @@ -23,11 +23,11 @@ import com.condation.cms.api.auth.Permissions; import com.condation.cms.api.extensions.AbstractExtensionPoint; import com.condation.cms.api.feature.features.InjectorFeature; -import com.condation.cms.api.site.Site; import com.condation.cms.api.site.SiteService; import com.condation.cms.api.ui.action.UIScriptAction; import com.condation.cms.api.ui.annotations.HookAction; import com.condation.cms.api.ui.annotations.MenuEntry; +import com.condation.cms.api.ui.annotations.ScriptAction; import com.condation.cms.api.ui.annotations.ShortCut; import com.condation.cms.api.ui.elements.Menu; import com.condation.cms.api.ui.extensions.UIActionsExtensionPoint; @@ -140,4 +140,22 @@ private List siteMenus() { .build(); }).toList()); } + + @MenuEntry( + parent = "toolMenu", + id = "list_unpublished_pages", + name = "Unpublished pages", + permissions = {Permissions.CONTENT_EDIT}, + position = 4, + scriptAction = @ScriptAction(module = "/manager/actions/page/list-unpublished-pages") + ) + @ShortCut( + id = "list_unpublished_pages", + title = "Unpublished pages", + permissions = {Permissions.CACHE_INVALIDATE}, + section = "tools", + scriptAction = @ScriptAction(module = "/manager/actions/page/list-unpublished-pages") + ) + public void list_unpublished_pages() { + } } diff --git a/modules/ui-module/src/main/java/com/condation/cms/modules/ui/extensionpoints/remotemethods/RemoteContentEndpointsExtension.java b/modules/ui-module/src/main/java/com/condation/cms/modules/ui/extensionpoints/remotemethods/RemoteContentEndpointsExtension.java index caf4d43f9..59d50bb86 100644 --- a/modules/ui-module/src/main/java/com/condation/cms/modules/ui/extensionpoints/remotemethods/RemoteContentEndpointsExtension.java +++ b/modules/ui-module/src/main/java/com/condation/cms/modules/ui/extensionpoints/remotemethods/RemoteContentEndpointsExtension.java @@ -33,8 +33,6 @@ import com.condation.cms.api.feature.features.RequestFeature; import com.condation.cms.api.ui.extensions.UIRemoteMethodExtensionPoint; import com.condation.cms.api.utils.PathUtil; -import com.condation.cms.api.utils.SectionUtil; -import com.condation.cms.content.Section; import com.condation.cms.core.content.io.ContentFileParser; import com.condation.cms.core.content.io.YamlHeaderUpdater; import com.condation.modules.api.annotation.Extension; @@ -46,6 +44,8 @@ import java.util.Map; import lombok.extern.slf4j.Slf4j; import com.condation.cms.api.ui.annotations.RemoteMethod; +import com.condation.cms.api.utils.SlotUtil; +import com.condation.cms.content.SlotItem; import com.condation.cms.modules.ui.utils.FormHelper; import com.condation.cms.modules.ui.utils.MetaConverter; import com.condation.cms.modules.ui.utils.UIFileNameUtil; @@ -194,8 +194,8 @@ public Object setMetaBatch(Map parameters) { return result; } - @RemoteMethod(name = "content.section.delete", permissions = {Permissions.CONTENT_EDIT}) - public Object deleteSection(Map parameters) { + @RemoteMethod(name = "content.slotItem.delete", permissions = {Permissions.CONTENT_EDIT}) + public Object deleteSlotItem(Map parameters) { final DB db = getContext().get(DBFeature.class).db(); var uri = (String) parameters.get("uri"); final Path contentBase = db.getFileSystem().resolve(Constants.Folders.CONTENT); @@ -221,21 +221,21 @@ public Object deleteSection(Map parameters) { return result; } - @RemoteMethod(name = "content.section.add", permissions = {Permissions.CONTENT_EDIT}) - public Object addSection(Map parameters) { + @RemoteMethod(name = "content.slotItem.add", permissions = {Permissions.CONTENT_EDIT}) + public Object addSlotItem(Map parameters) { final DB db = getContext().get(DBFeature.class).db(); var contentBase = db.getReadOnlyFileSystem().resolve(Constants.Folders.CONTENT); var content = (String) parameters.getOrDefault("content", ""); var parentUri = (String) parameters.get("parentUri"); - var parentSectionName = (String) parameters.get("parentSectionName"); - var sectionItemName = (String) parameters.get("sectionItemName"); + var slot = (String) parameters.get("slot"); + var sectionItemName = (String) parameters.get("slotItemName"); var template = (String) parameters.get("template"); var title = sectionItemName; sectionItemName = UIPathUtil.slugify(sectionItemName); - var uri = UIFileNameUtil.createSectionFileName(parentUri, parentSectionName, sectionItemName); + var uri = UIFileNameUtil.createSlotItemFileName(parentUri, slot, sectionItemName); var contentFile = contentBase.resolve(uri); @@ -305,17 +305,17 @@ public Object getContentNode (Map parameters) { if (contentFile != null) { result.put("uri", PathUtil.toRelativeFile(contentFile, contentBase)); - var sections = db.getContent().listSections(contentFile); - Map> sectionMap = new HashMap<>(); - sections.forEach(section -> { - String uri = section.uri(); - String name = SectionUtil.getSectionName(section.name()); - var index = section.getMetaValue(Constants.MetaFields.LAYOUT_ORDER, Constants.DEFAULT_SECTION_LAYOUT_ORDER); + var slotItems = db.getContent().listSlotItems(contentFile); + Map> sectionMap = new HashMap<>(); + slotItems.forEach(slotItem -> { + String uri = slotItem.uri(); + String name = SlotUtil.getSlotItemName(slotItem.name()); + var index = slotItem.getMetaValue(Constants.MetaFields.LAYOUT_ORDER, Constants.DEFAULT_SLOT_ITEM_LAYOUT_ORDER); sectionMap.computeIfAbsent(name, k -> new ArrayList<>()) - .add(new Section(section.name(), index, "", section.data(), uri)); + .add(new SlotItem(slotItem.name(), index, "", slotItem.data(), uri)); }); - result.put("sections", sectionMap); + result.put("slots", sectionMap); } return result; diff --git a/modules/ui-module/src/main/java/com/condation/cms/modules/ui/extensionpoints/remotemethods/RemoteFileEnpoints.java b/modules/ui-module/src/main/java/com/condation/cms/modules/ui/extensionpoints/remotemethods/RemoteFileEnpoints.java index b24f52b09..6d30f042b 100644 --- a/modules/ui-module/src/main/java/com/condation/cms/modules/ui/extensionpoints/remotemethods/RemoteFileEnpoints.java +++ b/modules/ui-module/src/main/java/com/condation/cms/modules/ui/extensionpoints/remotemethods/RemoteFileEnpoints.java @@ -25,10 +25,9 @@ import com.condation.cms.api.db.DB; import com.condation.cms.api.db.DBFileSystem; import com.condation.cms.api.db.cms.ReadOnlyFile; -import com.condation.cms.api.db.cms.ReadyOnlyFileSystem; import com.condation.cms.api.ui.extensions.UIRemoteMethodExtensionPoint; import com.condation.cms.api.utils.FileUtils; -import com.condation.cms.api.utils.SectionUtil; +import com.condation.cms.api.utils.SlotUtil; import com.condation.modules.api.annotation.Extension; import java.io.IOException; import java.nio.file.Files; @@ -42,6 +41,7 @@ import com.condation.cms.api.utils.PathUtil; import com.condation.cms.modules.ui.utils.UIPathUtil; import java.nio.file.Path; +import com.condation.cms.api.db.cms.ReadOnlyFileSystem; /** * @@ -51,7 +51,7 @@ @Extension(UIRemoteMethodExtensionPoint.class) public class RemoteFileEnpoints extends AbstractRemoteMethodeExtension { - private static ReadOnlyFile getBase(ReadyOnlyFileSystem fileSystem, String type) { + private static ReadOnlyFile getBase(ReadOnlyFileSystem fileSystem, String type) { return switch (type) { case "content" -> fileSystem.contentBase(); @@ -100,7 +100,7 @@ public Object list(Map parameters) { files.add(new Directory("..", parent.uri())); } contentFile.children().stream() - .filter(child -> !SectionUtil.isSection(child.getFileName())) + .filter(child -> !SlotUtil.isSlotItem(child.getFileName())) .map(this::map) .forEach(files::add); } catch (IOException ex) { @@ -144,7 +144,7 @@ public Object delete(Map parameters) throws RPCException { } else if ("assets".equals(type)) { Files.deleteIfExists(writableBase.resolve(uri).resolve(name)); } else { - var sections = db.getContent().listSections(contentFile); + var sections = db.getContent().listSlotItems(contentFile); Files.deleteIfExists(writableBase.resolve(uri).resolve(name)); sections.forEach(node -> { try { @@ -202,7 +202,7 @@ public Object renameFile(Map parameters) throws RPCException { if (!"assets".equals(type) && !Files.isDirectory(targetPath)) { var contentFile = contentBase.resolve(uri).resolve(name); - var sections = db.getContent().listSections(contentFile); + var sections = db.getContent().listSlotItems(contentFile); for (var node : sections) { var sourceSectionPath = writableBase.resolve(node.uri()); diff --git a/modules/ui-module/src/main/java/com/condation/cms/modules/ui/extensionpoints/remotemethods/RemoteManagerEnpoints.java b/modules/ui-module/src/main/java/com/condation/cms/modules/ui/extensionpoints/remotemethods/RemoteManagerEnpoints.java index a29e06b00..342dcb47c 100644 --- a/modules/ui-module/src/main/java/com/condation/cms/modules/ui/extensionpoints/remotemethods/RemoteManagerEnpoints.java +++ b/modules/ui-module/src/main/java/com/condation/cms/modules/ui/extensionpoints/remotemethods/RemoteManagerEnpoints.java @@ -65,14 +65,14 @@ public Object getMediaFormats (Map parameters) throws RPCExcepti return configuration.get(MediaConfiguration.class).getFormats(); } - @RemoteMethod(name = "manager.contentTypes.sections", permissions = {Permissions.CONTENT_EDIT}) + @RemoteMethod(name = "manager.contentTypes.slotItems", permissions = {Permissions.CONTENT_EDIT}) public Object getSectionTemplates(Map parameters) throws RPCException { try { - var section = (String) parameters.getOrDefault("section", ""); - if (!Strings.isNullOrEmpty(section)) { - return uiHooks().contentTypes().getSectionTemplates(section); + var slot = (String) parameters.getOrDefault("slot", ""); + if (!Strings.isNullOrEmpty(slot)) { + return uiHooks().contentTypes().getSlotItemTemplates(slot); } else { - return uiHooks().contentTypes().getSectionTemplates(); + return uiHooks().contentTypes().getSlotItemTemplates(); } } catch (Exception e) { log.error("", e); diff --git a/modules/ui-module/src/main/java/com/condation/cms/modules/ui/extensionpoints/remotemethods/RemotePageEnpoints.java b/modules/ui-module/src/main/java/com/condation/cms/modules/ui/extensionpoints/remotemethods/RemotePageEnpoints.java index 01e4358b5..e35fea5b9 100644 --- a/modules/ui-module/src/main/java/com/condation/cms/modules/ui/extensionpoints/remotemethods/RemotePageEnpoints.java +++ b/modules/ui-module/src/main/java/com/condation/cms/modules/ui/extensionpoints/remotemethods/RemotePageEnpoints.java @@ -23,6 +23,10 @@ import com.condation.cms.api.Constants; import com.condation.cms.api.auth.Permissions; import com.condation.cms.api.db.DB; +import com.condation.cms.api.db.Page; +import com.condation.cms.api.eventbus.events.ContentChangedEvent; +import com.condation.cms.api.eventbus.events.ReIndexContentMetaDataEvent; +import com.condation.cms.api.feature.features.EventBusFeature; import com.condation.cms.api.ui.extensions.UIRemoteMethodExtensionPoint; import com.condation.cms.api.utils.FileUtils; import com.condation.modules.api.annotation.Extension; @@ -33,12 +37,17 @@ import lombok.extern.slf4j.Slf4j; import com.condation.cms.api.ui.annotations.RemoteMethod; import com.condation.cms.api.ui.rpc.RPCException; +import com.condation.cms.api.utils.HTTPUtil; +import com.condation.cms.api.utils.PathUtil; import com.condation.cms.modules.ui.utils.UIPathUtil; import com.condation.cms.core.content.io.YamlHeaderUpdater; +import com.condation.cms.modules.ui.model.NodeDTO; +import com.condation.cms.modules.ui.utils.NumberUtils; import com.google.common.base.Strings; import java.nio.file.Path; import java.time.Instant; import java.util.Date; +import java.util.List; /** * @@ -48,6 +57,69 @@ @Extension(UIRemoteMethodExtensionPoint.class) public class RemotePageEnpoints extends AbstractRemoteMethodeExtension { + @RemoteMethod(name = "pages.filter", permissions = {Permissions.CONTENT_EDIT}) + public Object filterPages (Map parameters) throws RPCException { + + final DB db = getDB(parameters); + + var query = db.getContent().query((node, length) -> node); + + if (parameters.containsKey("contentType") && parameters.get("contentType") != null) { + query.contentType(parameters.get("contentType").toString()); + } + + if (parameters.containsKey("query") && parameters.get("query") != null) { + query.expression(parameters.get("query").toString()); + } + + if (parameters.containsKey("excerpt") && parameters.get("excerpt") != null) { + try { + long excerpt = Long.parseLong(parameters.get("excerpt").toString()); + query.excerpt(excerpt); + } catch (NumberFormatException e) { + log.error("Error parsing excerpt", e); + } + } + + if (parameters.get("where") instanceof List whereClauses) { + for (Object clauseObj : whereClauses) { + if (clauseObj instanceof Map clause) { + String field = (String) clause.get("field"); + Object value = clause.get("value"); + String operator = (String) clause.getOrDefault("operator", "="); + query.where(field, operator, value); + } + } + } + + if (parameters.containsKey("orderby") && parameters.get("orderby") != null) { + String field = parameters.get("orderby").toString(); + String direction = (String) parameters.getOrDefault("order", "asc"); + if ("desc".equalsIgnoreCase(direction)) { + query.orderby(field).desc(); + } else { + query.orderby(field).asc(); + } + } + + long page = NumberUtils.toLong(parameters.getOrDefault("page", 1l)); + long size = NumberUtils.toLong(parameters.getOrDefault("size", 5l)); + + var pageList = query.page(page, size); + + var contentBase = db.getReadOnlyFileSystem().contentBase(); + return new Page<>(pageList.getTotalItems(), pageList.getPageSize(), pageList.getTotalPages(), pageList.getPage(), + pageList.getItems().stream().map(node -> { + var temp_path = contentBase.resolve(node.uri()); + var url = PathUtil.toURL(temp_path, contentBase); + + url = HTTPUtil.modifyUrl(url, context); + + return new NodeDTO(url, node.data()); + }).toList() + ); + } + @RemoteMethod(name = "page.delete", permissions = {Permissions.CONTENT_EDIT}) public Object deletePage(Map parameters) throws RPCException { final DB db = getDB(parameters); @@ -66,7 +138,7 @@ public Object deletePage(Map parameters) throws RPCException { var contentFile = contentBase.resolve(uri).resolve(name); log.debug("deleting file {}", contentFile.uri()); - var sections = db.getContent().listSections(contentFile); + var sections = db.getContent().listSlotItems(contentFile); Files.deleteIfExists(db.getFileSystem().resolve(Constants.Folders.CONTENT).resolve(uri).resolve(name)); sections.forEach(node -> { try { @@ -134,8 +206,14 @@ public Object createPage(Map parameters) throws RPCException { } Files.createDirectories(newFile.getParent()); Files.createFile(newFile); + var newURI = PathUtil.toRelativeFile(newFile, contentBase); + getContext().get(EventBusFeature.class).eventBus() + .syncPublish(new ReIndexContentMetaDataEvent(newURI)); YamlHeaderUpdater.saveMarkdownFileWithHeader(newFile, meta, ""); + + String url = PathUtil.toURL(newFile, contentBase); + result.put("uri", url); } catch (Exception e) { log.error("", e); throw new RPCException(0, e.getMessage()); diff --git a/modules/ui-module/src/main/java/com/condation/cms/modules/ui/http/ResourceHandler.java b/modules/ui-module/src/main/java/com/condation/cms/modules/ui/http/ResourceHandler.java index 0c28a36fd..b8b5e7e61 100644 --- a/modules/ui-module/src/main/java/com/condation/cms/modules/ui/http/ResourceHandler.java +++ b/modules/ui-module/src/main/java/com/condation/cms/modules/ui/http/ResourceHandler.java @@ -20,7 +20,6 @@ * along with this program. If not, see . * #L% */ -import com.condation.cms.api.SiteProperties; import com.condation.cms.api.configuration.configs.ServerConfiguration; import com.condation.cms.api.configuration.configs.SiteConfiguration; import com.condation.cms.api.feature.features.ConfigurationFeature; @@ -64,8 +63,8 @@ public boolean handle(Request request, Response response, Callback callback) thr var hookSystem = requestContext.get(HookSystemFeature.class).hookSystem(); var moduleManager = context.get(ModuleManagerFeature.class).moduleManager(); - - var actionFactory = new ActionFactory(hookSystem, moduleManager, getUser(request, context, requestContext).get()); + var siteProperties = context.get(ConfigurationFeature.class).configuration().get(SiteConfiguration.class).siteProperties(); + var actionFactory = new ActionFactory(context, siteProperties, hookSystem, moduleManager, getUser(request, context, requestContext).get()); var resource = request.getHttpURI().getPath().replaceFirst( managerURL("/manager/", requestContext), ""); @@ -77,7 +76,6 @@ public boolean handle(Request request, Response response, Callback callback) thr if (resource.endsWith(".html")) { try { var secret = context.get(ConfigurationFeature.class).configuration().get(ServerConfiguration.class).serverProperties().secret(); - final SiteProperties siteProperties = context.get(ConfigurationFeature.class).configuration().get(SiteConfiguration.class).siteProperties(); String content = UILifecycleExtension.getInstance(context).getTemplateEngine().render(resource, Map.of( "actionFactory", actionFactory, diff --git a/modules/ui-module/src/main/java/com/condation/cms/modules/ui/model/NodeDTO.java b/modules/ui-module/src/main/java/com/condation/cms/modules/ui/model/NodeDTO.java new file mode 100644 index 000000000..750e40f66 --- /dev/null +++ b/modules/ui-module/src/main/java/com/condation/cms/modules/ui/model/NodeDTO.java @@ -0,0 +1,31 @@ +package com.condation.cms.modules.ui.model; + +/*- + * #%L + * UI Module + * %% + * Copyright (C) 2023 - 2026 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * #L% + */ + +import java.util.Map; + +/** + * + * @author thorstenmarx + */ +public record NodeDTO (String uri, Map meta) { +} diff --git a/modules/ui-module/src/main/java/com/condation/cms/modules/ui/utils/ActionFactory.java b/modules/ui-module/src/main/java/com/condation/cms/modules/ui/utils/ActionFactory.java index 9c0233b48..df9a4e29c 100644 --- a/modules/ui-module/src/main/java/com/condation/cms/modules/ui/utils/ActionFactory.java +++ b/modules/ui-module/src/main/java/com/condation/cms/modules/ui/utils/ActionFactory.java @@ -20,8 +20,10 @@ * along with this program. If not, see . * #L% */ +import com.condation.cms.api.SiteProperties; import com.condation.cms.api.annotations.Action; import com.condation.cms.api.hooks.HookSystem; +import com.condation.cms.api.module.SiteModuleContext; import com.condation.cms.api.ui.action.UIHookAction; import com.condation.cms.api.ui.action.UIAction; import com.condation.cms.api.ui.action.UIScriptAction; @@ -37,6 +39,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import com.condation.cms.api.ui.extensions.UIActionsExtensionPoint; +import com.condation.cms.api.utils.HTTPUtil; import com.condation.cms.api.utils.JSONUtil; import com.condation.cms.auth.services.AuthorizationService; import com.condation.cms.auth.services.User; @@ -50,224 +53,251 @@ @RequiredArgsConstructor public class ActionFactory { - private final HookSystem hookSystem; - private final ModuleManager moduleManager; - private final User user; - - AuthorizationService authService = new AuthorizationService(); - - public List createShortCuts() { - List shortCuts = new ArrayList<>(); - moduleManager.extensions(UIActionsExtensionPoint.class).forEach(extension -> { - shortCuts.addAll(scanShortCuts(extension)); - }); - - return shortCuts; - } - - public Menu createMenu() { - UIHooks uiHooks = new UIHooks(hookSystem); - var menu = uiHooks.menu(); - - moduleManager.extensions(UIActionsExtensionPoint.class).forEach(extension -> { - try { - extension.addMenuItems(menu); - } catch (Exception e) { - log.error("", e); - } - }); - - - List entries = new ArrayList<>(); - moduleManager.extensions(UIActionsExtensionPoint.class).forEach(extension -> { - try { - entries.addAll(scanMenuEntries(extension)); - } catch (Exception e) { - log.error("", e); - } - }); - - insertEntriesIntoMenu(menu, entries); - - var filteredMenu = new Menu(); - var menuEntries = menu.entries(); - menuEntries.stream() - .filter(entry -> authService.hasAllPermissions(user, entry.getPermissions().toArray(new String[0]))) - .forEach(filteredMenu::addMenuEntry); - - return filteredMenu; - } - - private List scanShortCuts(Object moduleInstance) { - List shortCuts = new ArrayList<>(); - - for (Method method : moduleInstance.getClass().getMethods()) { - var shortcutAnnotation = method.getAnnotation(com.condation.cms.api.ui.annotations.ShortCut.class); - if (shortcutAnnotation == null) { - continue; - } - - method.setAccessible(true); - UIAction menuAction = null; - - // 1. Methode hat @Action? - Action actionAnn = method.getAnnotation(Action.class); - if (actionAnn != null) { - menuAction = new UIHookAction(actionAnn.value(), Map.of()); - } // 2. @Hook in @MenuEntry - else if (!shortcutAnnotation.hookAction().value().isEmpty()) { - menuAction = new UIHookAction(shortcutAnnotation.hookAction().value(), Map.of()); - } // 3. @ScriptAction in @MenuEntry - else if (!shortcutAnnotation.scriptAction().module().isEmpty()) { - menuAction = new UIScriptAction(shortcutAnnotation.scriptAction().module(), shortcutAnnotation.scriptAction().function(), Map.of()); - } - - if (menuAction == null) { - var menuAnn = method.getAnnotation(com.condation.cms.api.ui.annotations.MenuEntry.class); - if (menuAnn != null) { - if (!menuAnn.hookAction().value().isEmpty()) { - menuAction = new UIHookAction(menuAnn.hookAction().value(), Map.of()); - } // 3. @ScriptAction in @MenuEntry - else if (!menuAnn.scriptAction().module().isEmpty()) { - menuAction = new UIScriptAction(menuAnn.scriptAction().module(), menuAnn.scriptAction().function(), Map.of()); - } - } - } - - if (menuAction != null) { - shortCuts.add(new ShortCutHolder( - shortcutAnnotation.id(), - shortcutAnnotation.title(), - shortcutAnnotation.icon(), - shortcutAnnotation.hotkey(), - shortcutAnnotation.parent(), - shortcutAnnotation.section(), - menuAction, - shortcutAnnotation.permissions())); - } - - } - - return shortCuts; - } - - private List scanMenuEntries(Object moduleInstance) { - - List entries = new ArrayList<>(); - - for (Method method : moduleInstance.getClass().getMethods()) { - var menuAnn = method.getAnnotation(com.condation.cms.api.ui.annotations.MenuEntry.class); - if (menuAnn == null) { - continue; - } - - method.setAccessible(true); - UIAction menuAction = null; - - // 1. Methode hat @Action? - Action actionAnn = method.getAnnotation(Action.class); - if (actionAnn != null) { - menuAction = new UIHookAction(actionAnn.value(), Map.of()); - } // 2. @Hook in @MenuEntry - else if (!menuAnn.hookAction().value().isEmpty()) { - menuAction = new UIHookAction(menuAnn.hookAction().value(), Map.of()); - } // 3. @ScriptAction in @MenuEntry - else if (!menuAnn.scriptAction().module().isEmpty()) { - menuAction = new UIScriptAction(menuAnn.scriptAction().module(), menuAnn.scriptAction().function(), Map.of()); - } - - var entry = MenuEntry.builder() - .id(menuAnn.id()) - .name(menuAnn.name()) - .divider(menuAnn.divider()) - .position(menuAnn.position()) - .action(menuAction) - .children(new ArrayList<>()) - .permissions(Arrays.asList(menuAnn.permissions())) - .build(); - - entries.add(new EntryHolder(menuAnn.parent(), entry)); - } - - return entries; - } - - private void insertEntriesIntoMenu(Menu menu, List entries) { - Map index = new HashMap<>(); - entries.forEach(holder -> index.put(holder.entry().getId(), holder.entry())); - - // füge alle mit parent == "" oder null in die Wurzel ein - for (EntryHolder holder : entries) { - String parentId = holder.parent(); - MenuEntry entry = holder.entry(); - - if (parentId == null || parentId.isBlank()) { - menu.addMenuEntry(entry); - } else { - // Versuche Parent in fertigem Menü zu finden - Optional parentInMenu = findEntryById(menu, parentId); - - if (parentInMenu.isEmpty()) { - // Versuche in den noch nicht eingefügten Entries - MenuEntry parentInBatch = index.get(parentId); - if (parentInBatch != null) { - parentInBatch.getChildren().add(entry); - } else { - log.warn("Parent entry with ID '" + parentId + "' not found for menu entry '" + entry.getId() + "'"); - } - } else { - parentInMenu.get().addChildren(entry); - } - } - } - - // Jetzt alle "Wurzel"-Einträge, die nicht direkt im Menü sind, einfügen - for (EntryHolder holder : entries) { - String parentId = holder.parent(); - if (parentId == null || parentId.isBlank()) { - menu.addMenuEntry(holder.entry()); - } - } - } - - private Optional findEntryById(Menu menu, String id) { - if (menu.entries() == null) { - return Optional.empty(); - } - for (MenuEntry entry : menu.entries()) { - Optional result = findEntryByIdRecursive(entry, id); - if (result.isPresent()) { - return result; - } - } - return Optional.empty(); - } - - private Optional findEntryByIdRecursive(MenuEntry entry, String id) { - if (entry.getId().equals(id)) { - return Optional.of(entry); - } - if (entry.getChildren() == null) { - return Optional.empty(); - } - for (MenuEntry child : entry.getChildren()) { - Optional result = findEntryByIdRecursive(child, id); - if (result.isPresent()) { - return result; - } - } - return Optional.empty(); - } - - private record EntryHolder(String parent, MenuEntry entry) { - - } - - public record ShortCutHolder(String id, String title, String icon, String hotkey, String parent, String section, UIAction action, String[] permissions) { - - public String getActionDefinition() { - return action != null ? JSONUtil.toJson(action) : ""; - } - } + private final SiteModuleContext context; + private final SiteProperties siteProperties; + private final HookSystem hookSystem; + private final ModuleManager moduleManager; + private final User user; + + AuthorizationService authService = new AuthorizationService(); + + public List createShortCuts() { + List shortCuts = new ArrayList<>(); + moduleManager.extensions(UIActionsExtensionPoint.class).forEach(extension -> { + shortCuts.addAll(scanShortCuts(extension)); + }); + + return shortCuts; + } + + public Menu createContentTypeMenu() { + UIHooks uiHooks = new UIHooks(hookSystem); + var contentTypes = uiHooks.contentTypes(); + + Menu menu = new Menu(); + + contentTypes.getPageTemplates().stream().map(pt -> { + return MenuEntry.builder() + .id(pt.name()) + .name(pt.name()) + .action(new UIScriptAction(HTTPUtil.prependContext("/manager/actions/page/create-node", siteProperties), + Map.of( + "name", pt.name(), + "folder", pt.getContentFolder(), + "template", pt.template(), + "contentType", pt.name() + ) + )) + .children(new ArrayList<>()) + .build(); + }).forEach(menu::addMenuEntry); + + return menu; + } + + public Menu createMenu() { + UIHooks uiHooks = new UIHooks(hookSystem); + var menu = uiHooks.menu(); + + moduleManager.extensions(UIActionsExtensionPoint.class).forEach(extension -> { + try { + extension.addMenuItems(menu); + } catch (Exception e) { + log.error("", e); + } + }); + + List entries = new ArrayList<>(); + moduleManager.extensions(UIActionsExtensionPoint.class).forEach(extension -> { + try { + entries.addAll(scanMenuEntries(extension)); + } catch (Exception e) { + log.error("", e); + } + }); + + insertEntriesIntoMenu(menu, entries); + + var filteredMenu = new Menu(); + var menuEntries = menu.entries(); + menuEntries.stream() + .filter(entry -> authService.hasAllPermissions(user, entry.getPermissions().toArray(new String[0]))) + .forEach(filteredMenu::addMenuEntry); + + return filteredMenu; + } + + private List scanShortCuts(Object moduleInstance) { + List shortCuts = new ArrayList<>(); + + for (Method method : moduleInstance.getClass().getMethods()) { + var shortcutAnnotation = method.getAnnotation(com.condation.cms.api.ui.annotations.ShortCut.class); + if (shortcutAnnotation == null) { + continue; + } + + method.setAccessible(true); + UIAction menuAction = null; + + // 1. Methode hat @Action? + Action actionAnn = method.getAnnotation(Action.class); + if (actionAnn != null) { + menuAction = new UIHookAction(actionAnn.value(), Map.of()); + } // 2. @Hook in @MenuEntry + else if (!shortcutAnnotation.hookAction().value().isEmpty()) { + menuAction = new UIHookAction(shortcutAnnotation.hookAction().value(), Map.of()); + } // 3. @ScriptAction in @MenuEntry + else if (!shortcutAnnotation.scriptAction().module().isEmpty()) { + menuAction = new UIScriptAction(shortcutAnnotation.scriptAction().module(), shortcutAnnotation.scriptAction().function(), Map.of()); + } + + if (menuAction == null) { + var menuAnn = method.getAnnotation(com.condation.cms.api.ui.annotations.MenuEntry.class); + if (menuAnn != null) { + if (!menuAnn.hookAction().value().isEmpty()) { + menuAction = new UIHookAction(menuAnn.hookAction().value(), Map.of()); + } // 3. @ScriptAction in @MenuEntry + else if (!menuAnn.scriptAction().module().isEmpty()) { + menuAction = new UIScriptAction(menuAnn.scriptAction().module(), menuAnn.scriptAction().function(), Map.of()); + } + } + } + + if (menuAction != null) { + shortCuts.add(new ShortCutHolder( + shortcutAnnotation.id(), + shortcutAnnotation.title(), + shortcutAnnotation.icon(), + shortcutAnnotation.hotkey(), + shortcutAnnotation.parent(), + shortcutAnnotation.section(), + menuAction, + shortcutAnnotation.permissions())); + } + + } + + return shortCuts; + } + + private List scanMenuEntries(Object moduleInstance) { + + List entries = new ArrayList<>(); + + for (Method method : moduleInstance.getClass().getMethods()) { + var menuAnn = method.getAnnotation(com.condation.cms.api.ui.annotations.MenuEntry.class); + if (menuAnn == null) { + continue; + } + + method.setAccessible(true); + UIAction menuAction = null; + + // 1. Methode hat @Action? + Action actionAnn = method.getAnnotation(Action.class); + if (actionAnn != null) { + menuAction = new UIHookAction(actionAnn.value(), Map.of()); + } // 2. @Hook in @MenuEntry + else if (!menuAnn.hookAction().value().isEmpty()) { + menuAction = new UIHookAction(menuAnn.hookAction().value(), Map.of()); + } // 3. @ScriptAction in @MenuEntry + else if (!menuAnn.scriptAction().module().isEmpty()) { + var moduleWithContext = HTTPUtil.modifyUrl(menuAnn.scriptAction().module(), context); + menuAction = new UIScriptAction(moduleWithContext, menuAnn.scriptAction().function(), Map.of()); + } + + var entry = MenuEntry.builder() + .id(menuAnn.id()) + .name(menuAnn.name()) + .divider(menuAnn.divider()) + .position(menuAnn.position()) + .action(menuAction) + .children(new ArrayList<>()) + .permissions(Arrays.asList(menuAnn.permissions())) + .build(); + + entries.add(new EntryHolder(menuAnn.parent(), entry)); + } + + return entries; + } + + private void insertEntriesIntoMenu(Menu menu, List entries) { + Map index = new HashMap<>(); + entries.forEach(holder -> index.put(holder.entry().getId(), holder.entry())); + + // füge alle mit parent == "" oder null in die Wurzel ein + for (EntryHolder holder : entries) { + String parentId = holder.parent(); + MenuEntry entry = holder.entry(); + + if (parentId == null || parentId.isBlank()) { + menu.addMenuEntry(entry); + } else { + // Versuche Parent in fertigem Menü zu finden + Optional parentInMenu = findEntryById(menu, parentId); + + if (parentInMenu.isEmpty()) { + // Versuche in den noch nicht eingefügten Entries + MenuEntry parentInBatch = index.get(parentId); + if (parentInBatch != null) { + parentInBatch.getChildren().add(entry); + } else { + log.warn("Parent entry with ID '" + parentId + "' not found for menu entry '" + entry.getId() + "'"); + } + } else { + parentInMenu.get().addChildren(entry); + } + } + } + + // Jetzt alle "Wurzel"-Einträge, die nicht direkt im Menü sind, einfügen + for (EntryHolder holder : entries) { + String parentId = holder.parent(); + if (parentId == null || parentId.isBlank()) { + menu.addMenuEntry(holder.entry()); + } + } + } + + private Optional findEntryById(Menu menu, String id) { + if (menu.entries() == null) { + return Optional.empty(); + } + for (MenuEntry entry : menu.entries()) { + Optional result = findEntryByIdRecursive(entry, id); + if (result.isPresent()) { + return result; + } + } + return Optional.empty(); + } + + private Optional findEntryByIdRecursive(MenuEntry entry, String id) { + if (entry.getId().equals(id)) { + return Optional.of(entry); + } + if (entry.getChildren() == null) { + return Optional.empty(); + } + for (MenuEntry child : entry.getChildren()) { + Optional result = findEntryByIdRecursive(child, id); + if (result.isPresent()) { + return result; + } + } + return Optional.empty(); + } + + private record EntryHolder(String parent, MenuEntry entry) { + + } + + public record ShortCutHolder(String id, String title, String icon, String hotkey, String parent, String section, UIAction action, String[] permissions) { + + public String getActionDefinition() { + return action != null ? JSONUtil.toJson(action) : ""; + } + } ; } diff --git a/modules/ui-module/src/main/java/com/condation/cms/modules/ui/utils/NumberUtils.java b/modules/ui-module/src/main/java/com/condation/cms/modules/ui/utils/NumberUtils.java new file mode 100644 index 000000000..b5e063ec7 --- /dev/null +++ b/modules/ui-module/src/main/java/com/condation/cms/modules/ui/utils/NumberUtils.java @@ -0,0 +1,42 @@ +package com.condation.cms.modules.ui.utils; + +/*- + * #%L + * UI Module + * %% + * Copyright (C) 2023 - 2026 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * #L% + */ + +/** + * + * @author thorstenmarx + */ +public class NumberUtils { + + public static long toLong(Object value) { + return switch (value) { + case null -> + 1L; + case Number n -> + n.longValue(); + case String s -> + Long.parseLong(s); + default -> + throw new IllegalArgumentException("Invalid page value: " + value); + }; + } +} diff --git a/modules/ui-module/src/main/java/com/condation/cms/modules/ui/utils/UIFileNameUtil.java b/modules/ui-module/src/main/java/com/condation/cms/modules/ui/utils/UIFileNameUtil.java index 5f4dba5fe..f58b0a351 100644 --- a/modules/ui-module/src/main/java/com/condation/cms/modules/ui/utils/UIFileNameUtil.java +++ b/modules/ui-module/src/main/java/com/condation/cms/modules/ui/utils/UIFileNameUtil.java @@ -29,7 +29,7 @@ */ public class UIFileNameUtil { - public static String createSectionFileName(String parentUri, String section, String sectionItem) { + public static String createSlotItemFileName(String parentUri, String slot, String slotItem) { // Pfadteile per "/" splitten String[] parts = parentUri.split("/"); @@ -49,7 +49,7 @@ public static String createSectionFileName(String parentUri, String section, Str } // Neuen Dateinamen erstellen - String newFileName = baseName + "." + section + "." + sectionItem + extension; + String newFileName = baseName + "." + slot + "." + slotItem + extension; // Pfad wieder zusammenbauen if (parts.length > 1) { diff --git a/modules/ui-module/src/main/resources/manager/actions/media/edit-focal-point.js b/modules/ui-module/src/main/resources/manager/actions/media/edit-focal-point.js index 6e3df3240..6c4a63928 100644 --- a/modules/ui-module/src/main/resources/manager/actions/media/edit-focal-point.js +++ b/modules/ui-module/src/main/resources/manager/actions/media/edit-focal-point.js @@ -8,12 +8,12 @@ * it under the terms of the GNU Affero 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 General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * #L% diff --git a/modules/ui-module/src/main/resources/manager/actions/media/edit-media-form.js b/modules/ui-module/src/main/resources/manager/actions/media/edit-media-form.js index cc8a70930..de852933c 100644 --- a/modules/ui-module/src/main/resources/manager/actions/media/edit-media-form.js +++ b/modules/ui-module/src/main/resources/manager/actions/media/edit-media-form.js @@ -8,12 +8,12 @@ * it under the terms of the GNU Affero 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 General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * #L% diff --git a/modules/ui-module/src/main/resources/manager/actions/media/select-media.js b/modules/ui-module/src/main/resources/manager/actions/media/select-media.js index e59d393f5..e0753be54 100644 --- a/modules/ui-module/src/main/resources/manager/actions/media/select-media.js +++ b/modules/ui-module/src/main/resources/manager/actions/media/select-media.js @@ -8,12 +8,12 @@ * it under the terms of the GNU Affero 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 General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * #L% diff --git a/modules/ui-module/src/main/resources/manager/actions/page/add-section.js b/modules/ui-module/src/main/resources/manager/actions/page/add-section.js index b8be5ac16..925958828 100644 --- a/modules/ui-module/src/main/resources/manager/actions/page/add-section.js +++ b/modules/ui-module/src/main/resources/manager/actions/page/add-section.js @@ -8,12 +8,12 @@ * it under the terms of the GNU Affero 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 General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * #L% @@ -24,40 +24,40 @@ import { addSection, getContentNode } from '@cms/modules/rpc/rpc-content.js'; import { getPreviewUrl, reloadPreview } from '@cms/modules/preview.utils.js'; import Handlebars from 'https://cdn.jsdelivr.net/npm/handlebars@4.7.8/+esm'; import { i18n } from '@cms/modules/localization.js'; -import { getSectionTemplates } from '@cms/modules/rpc/rpc-manager.js'; +import { getSlotItemTemplates } from '@cms/modules/rpc/rpc-manager.js'; export async function runAction(params) { const contentNode = await getContentNode({ url: getPreviewUrl() }); var template = Handlebars.compile(`
- - + +
- {{#each templates}} {{/each}} `); - var sectionsResponse = await getSectionTemplates({ - section: params.sectionName + var sectionsResponse = await getSlotItemTemplates({ + slot: params.slot }); openModal({ - title: i18n.t("addsection.titles.modal", 'Add section'), + title: i18n.t("addsection.titles.modal", 'Add item'), body: template({ templates: sectionsResponse.result, }), fullscreen: false, onCancel: (event) => { }, - validate: () => validate(contentNode, params.sectionName), + validate: () => validate(contentNode, params.slot), onOk: async (event) => { - var result = await createSection(contentNode.result.uri, params.sectionName); + var result = await createSection(contentNode.result.uri, params.slot); if (result) { showToast({ - title: i18n.t("manager.actions.addsection.titles.alert", "Create section"), - message: i18n.t("manager.actions.addsection.alerts.success.message", "Section successfuly created."), + title: i18n.t("manager.actions.addsection.titles.alert", "Create Item"), + message: i18n.t("manager.actions.addsection.alerts.success.message", "Item successfuly created."), type: 'success', // optional: info | success | warning | error timeout: 3000 }); @@ -66,8 +66,8 @@ export async function runAction(params) { } else { showToast({ - title: i18n.t("manager.actions.addsection.titles.alert", 'Create section'), - message: i18n.t("manager.actions.addsection.alerts.error.message", "Section not created."), + title: i18n.t("manager.actions.addsection.titles.alert", 'Create Item'), + message: i18n.t("manager.actions.addsection.alerts.error.message", "Item not created."), type: 'warning', // optional: info | success | warning | error timeout: 3000 }); @@ -75,25 +75,25 @@ export async function runAction(params) { } }); } -const getSectionItemName = () => { +const getSlotItemName = () => { return document.getElementById("cms-section-name").value; }; -const validate = (contentNode, targetSectionName) => { +const validate = (contentNode, targetSlot) => { const template = document.getElementById("cms-section-template-selection").value; if (template === "000") { showToast({ - title: i18n.t("manager.actions.addsection.titles.alert", 'Create section'), + title: i18n.t("manager.actions.addsection.titles.alert", 'Create Item'), message: i18n.t("manager.actions.addsection.alerts.notemplate.message", "No template selected."), type: 'error', // optional: info | success | warning | error timeout: 3000 }); return false; } - const sectionItemName = getSectionItemName(); - if (sectionItemName === "" || sectionItemName === null) { + const slotItemName = getSlotItemName(); + if (slotItemName === "" || slotItemName === null) { showToast({ - title: i18n.t("manager.actions.addsection.titles.alert", 'Create section'), - message: i18n.t("manager.actions.addsection.alerts.noname.message", "No section name provided."), + title: i18n.t("manager.actions.addsection.titles.alert", 'Create Item'), + message: i18n.t("manager.actions.addsection.alerts.noname.message", "No Item name provided."), type: 'error', timeout: 3000 }); @@ -101,14 +101,14 @@ const validate = (contentNode, targetSectionName) => { } return true; }; -function isUriInSection(data, sectionKey, targetUri) { +function isUriInSection(data, slotItemKey, targetUri) { if (!data || !data.result || - !data.result.sections || - typeof data.result.sections !== 'object') { + !data.result.slots || + typeof data.result.slots !== 'object') { return false; } - const sectionArray = data.result.sections[sectionKey]; + const sectionArray = data.result.slots[slotItemKey]; if (!Array.isArray(sectionArray)) { return false; } @@ -121,8 +121,8 @@ const createSection = async (parentUri, parentSectionName) => { } await addSection({ parentUri: parentUri, - sectionItemName: getSectionItemName(), - parentSectionName: parentSectionName, + slotItemName: getSlotItemName(), + slot: parentSectionName, template: template }); return true; diff --git a/modules/ui-module/src/main/resources/manager/actions/page/create-content.js b/modules/ui-module/src/main/resources/manager/actions/page/create-content.js new file mode 100644 index 000000000..f465c740b --- /dev/null +++ b/modules/ui-module/src/main/resources/manager/actions/page/create-content.js @@ -0,0 +1,29 @@ +/*- + * #%L + * UI Module + * %% + * Copyright (C) 2023 - 2026 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * #L% + */ +import { openFileBrowser } from '@cms/modules/filebrowser.js'; +// hook.js +export async function runAction(params) { + openFileBrowser({ + type: "content", + uri: params.folder, + template : params.template, + }); +} diff --git a/modules/ui-module/src/main/resources/manager/actions/page/create-node.d.ts b/modules/ui-module/src/main/resources/manager/actions/page/create-node.d.ts new file mode 100644 index 000000000..1dc39e464 --- /dev/null +++ b/modules/ui-module/src/main/resources/manager/actions/page/create-node.d.ts @@ -0,0 +1,21 @@ +/*- + * #%L + * UI Module + * %% + * Copyright (C) 2023 - 2026 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * #L% + */ +export function runAction(params: any): Promise; diff --git a/modules/ui-module/src/main/resources/manager/actions/page/create-node.js b/modules/ui-module/src/main/resources/manager/actions/page/create-node.js new file mode 100644 index 000000000..a06f8a9f0 --- /dev/null +++ b/modules/ui-module/src/main/resources/manager/actions/page/create-node.js @@ -0,0 +1,38 @@ +/*- + * #%L + * UI Module + * %% + * Copyright (C) 2023 - 2026 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * #L% + */ +import { i18n } from '@cms/modules/localization'; +import { openCreateContentBrowser } from '@cms/modules/filebrowser.create.js'; +import { loadPreview } from '@cms/modules/preview.utils'; +import { patchPathWithContext } from '@cms/js/manager-globals'; +// hook.js +export async function runAction(params) { + openCreateContentBrowser({ + type: "content", + template: params.template, + contentType: params.contentType, + uri: params.folder, + title: i18n.t("filebrowser.createContent.title", "Create Node"), + onCreate: (newContent) => { + console.log("Created new content:", newContent); + loadPreview(patchPathWithContext(newContent.uri)); // Reload the preview to reflect the new content + } + }); +} diff --git a/modules/ui-module/src/main/resources/manager/actions/page/create-page.js b/modules/ui-module/src/main/resources/manager/actions/page/create-page.js index 19414ae0d..a0e42d021 100644 --- a/modules/ui-module/src/main/resources/manager/actions/page/create-page.js +++ b/modules/ui-module/src/main/resources/manager/actions/page/create-page.js @@ -8,12 +8,12 @@ * it under the terms of the GNU Affero 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 General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * #L% diff --git a/modules/ui-module/src/main/resources/manager/actions/page/delete-section.js b/modules/ui-module/src/main/resources/manager/actions/page/delete-section.js index df7bbab2a..e27099bdb 100644 --- a/modules/ui-module/src/main/resources/manager/actions/page/delete-section.js +++ b/modules/ui-module/src/main/resources/manager/actions/page/delete-section.js @@ -8,12 +8,12 @@ * it under the terms of the GNU Affero 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 General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * #L% @@ -39,16 +39,16 @@ export async function runAction(params) { }).then((response) => { if (response.error) { showToast({ - title: i18n.t("manager.actions.section.delete.error.title", "Error deleting section"), - message: i18n.t("manager.actions.section.delete.error.message", "Error deleting section"), + title: i18n.t("manager.actions.section.delete.error.title", "Error deleting item"), + message: i18n.t("manager.actions.section.delete.error.message", "Error deleting item"), type: 'error', timeout: 3000 }); } else { showToast({ - title: i18n.t("manager.actions.section.delete.success.title", "Delete section"), - message: i18n.t("manager.actions.section.delete.success.message", "Section deleted successfully"), + title: i18n.t("manager.actions.section.delete.success.title", "Delete item"), + message: i18n.t("manager.actions.section.delete.success.message", "Item deleted successfully"), type: 'success', timeout: 3000 }); diff --git a/modules/ui-module/src/main/resources/manager/actions/page/edit-content.js b/modules/ui-module/src/main/resources/manager/actions/page/edit-content.js index 58522a0aa..6d816262a 100644 --- a/modules/ui-module/src/main/resources/manager/actions/page/edit-content.js +++ b/modules/ui-module/src/main/resources/manager/actions/page/edit-content.js @@ -8,12 +8,12 @@ * it under the terms of the GNU Affero 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 General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * #L% diff --git a/modules/ui-module/src/main/resources/manager/actions/page/edit-metaattribute-form.js b/modules/ui-module/src/main/resources/manager/actions/page/edit-metaattribute-form.js index 0424bb3bb..c5fce0f9e 100644 --- a/modules/ui-module/src/main/resources/manager/actions/page/edit-metaattribute-form.js +++ b/modules/ui-module/src/main/resources/manager/actions/page/edit-metaattribute-form.js @@ -8,12 +8,12 @@ * it under the terms of the GNU Affero 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 General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * #L% @@ -25,7 +25,7 @@ import { buildValuesFromFields, getValueByPath } from '@cms/modules/node.js'; import { getContentNode, getContent, setMeta } from '@cms/modules/rpc/rpc-content.js'; import { i18n } from '@cms/modules/localization.js'; import { openSidebar } from '@cms/modules/sidebar.js'; -import { getPageTemplates, getSectionTemplates } from '@cms/modules/rpc/rpc-manager'; +import { getPageTemplates, getSlotItemTemplates } from '@cms/modules/rpc/rpc-manager'; // hook.js export async function runAction(params) { var uri = null; @@ -42,8 +42,8 @@ export async function runAction(params) { uri: uri }); var templates = null; - if (params.type === "section") { - templates = (await getSectionTemplates()).result; + if (params.type === "slotItem") { + templates = (await getSlotItemTemplates()).result; } else { templates = (await getPageTemplates()).result; diff --git a/modules/ui-module/src/main/resources/manager/actions/page/edit-metaattribute-list.js b/modules/ui-module/src/main/resources/manager/actions/page/edit-metaattribute-list.js index 39eefea21..388a3764f 100644 --- a/modules/ui-module/src/main/resources/manager/actions/page/edit-metaattribute-list.js +++ b/modules/ui-module/src/main/resources/manager/actions/page/edit-metaattribute-list.js @@ -8,12 +8,12 @@ * it under the terms of the GNU Affero 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 General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * #L% diff --git a/modules/ui-module/src/main/resources/manager/actions/page/edit-metaattribute.js b/modules/ui-module/src/main/resources/manager/actions/page/edit-metaattribute.js index a6e7c812c..b87ff053b 100644 --- a/modules/ui-module/src/main/resources/manager/actions/page/edit-metaattribute.js +++ b/modules/ui-module/src/main/resources/manager/actions/page/edit-metaattribute.js @@ -8,12 +8,12 @@ * it under the terms of the GNU Affero 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 General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * #L% diff --git a/modules/ui-module/src/main/resources/manager/actions/page/edit-page-settings.js b/modules/ui-module/src/main/resources/manager/actions/page/edit-page-settings.js index 4326499d8..4ff4d5a32 100644 --- a/modules/ui-module/src/main/resources/manager/actions/page/edit-page-settings.js +++ b/modules/ui-module/src/main/resources/manager/actions/page/edit-page-settings.js @@ -8,12 +8,12 @@ * it under the terms of the GNU Affero 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 General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * #L% diff --git a/modules/ui-module/src/main/resources/manager/actions/page/edit-sections.js b/modules/ui-module/src/main/resources/manager/actions/page/edit-sections.js index ef953003e..f2eec5573 100644 --- a/modules/ui-module/src/main/resources/manager/actions/page/edit-sections.js +++ b/modules/ui-module/src/main/resources/manager/actions/page/edit-sections.js @@ -8,12 +8,12 @@ * it under the terms of the GNU Affero 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 General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * #L% @@ -31,7 +31,7 @@ export async function runAction(params) { }); var template = Handlebars.compile(`
    - {{#each sections}} + {{#each slotItems}}
  • {{#if data.title}} {{data.title}} @@ -42,21 +42,21 @@ export async function runAction(params) { {{/each}}
`); - var sections = []; - if (contentNode.result.sections[params.sectionName]) { - var sections = contentNode.result.sections[params.sectionName]; + var slotItems = []; + if (contentNode.result.slots[params.slot]) { + var slotItems = contentNode.result.slots[params.slot]; } - sections = sections.sort((a, b) => a.index - b.index); + slotItems = slotItems.sort((a, b) => a.index - b.index); openModal({ - title: 'Edit Sections', - body: template({ sections: sections }), + title: 'Edit items', + body: template({ slotItems: slotItems }), fullscreen: false, onCancel: (event) => { }, onOk: async (event) => { await saveSections(); showToast({ - title: 'Sections saved', - message: 'Sections successfuly saved.', + title: 'Items saved', + message: 'Items successfuly saved.', type: 'success', // optional: info | success | warning | error timeout: 3000 }); diff --git a/modules/ui-module/src/main/resources/manager/actions/page/list-unpublished-pages.d.ts b/modules/ui-module/src/main/resources/manager/actions/page/list-unpublished-pages.d.ts new file mode 100644 index 000000000..44d1f6c2a --- /dev/null +++ b/modules/ui-module/src/main/resources/manager/actions/page/list-unpublished-pages.d.ts @@ -0,0 +1,25 @@ +/*- + * #%L + * UI Module + * %% + * Copyright (C) 2023 - 2026 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * #L% + */ +interface ListUnpublishedPagesOptions { + page?: number; +} +export declare const runAction: (options?: ListUnpublishedPagesOptions) => Promise; +export {}; diff --git a/modules/ui-module/src/main/resources/manager/actions/page/list-unpublished-pages.js b/modules/ui-module/src/main/resources/manager/actions/page/list-unpublished-pages.js new file mode 100644 index 000000000..a5ecece14 --- /dev/null +++ b/modules/ui-module/src/main/resources/manager/actions/page/list-unpublished-pages.js @@ -0,0 +1,122 @@ +/*- + * #%L + * UI Module + * %% + * Copyright (C) 2023 - 2026 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * #L% + */ +import { openModal } from '@cms/modules/modal.js'; +import { i18n } from '@cms/modules/localization.js'; +import { filterPages } from '@cms/modules/rpc/rpc-page'; +import { loadPreview } from '@cms/modules/preview.utils'; +const ITEMS_PER_PAGE = 5; // Feste Seitengröße wie angefordert +const renderPageListHtml = (pages, currentPage, totalPages) => { + let pageItemsHtml = ''; + if (pages.length === 0) { + pageItemsHtml = `

${i18n.t('page.unpublished.noPages', 'No unpublished pages found.')}

`; + } + else { + pageItemsHtml = ` + + `; + } + const paginationHtml = ` + + `; + return ` +
+ ${pageItemsHtml} + ${paginationHtml} +
+ `; +}; +const state = { + modal: null +}; +const updateDialog = async (pageNumber) => { + const filterOptions = { + where: [ + { + field: "published", + operator: "=", + value: false + } + ], + page: pageNumber, + size: ITEMS_PER_PAGE + }; + const response = await filterPages(filterOptions); + var pageData = response.result; + const modalBodyHtml = renderPageListHtml(pageData.items, pageData.page, pageData.totalPages); + var modalElement = document.getElementById('cms-unpublished-pages-modal-body'); + if (modalElement) { + modalElement.innerHTML = modalBodyHtml; + modalElement.querySelectorAll('.page-link').forEach(link => { + link.addEventListener('click', (e) => { + e.preventDefault(); + const newPage = parseInt(e.target.dataset.page || '1'); + if (newPage >= 1 && newPage <= pageData.totalPages) { + updateDialog(newPage); + } + }); + }); + modalElement.querySelectorAll('a[data-cms-page-uri]').forEach((link) => { + link.addEventListener('click', (e) => { + e.preventDefault(); + state.modal.hide(); + loadPreview(link.dataset.cmsPageUri || ''); + }); + }); + } +}; +export const runAction = async (options = {}) => { + let currentPage = options.page || 1; + state.modal = openModal({ + title: i18n.t('page.unpublished.title', 'Unpublished Pages'), + body: "
", + fullscreen: false, + onCancel: () => { }, + onOk: () => { }, + onShow: (modalElement) => { + updateDialog(currentPage); + } + }); +}; diff --git a/modules/ui-module/src/main/resources/manager/actions/page/manage-assets.js b/modules/ui-module/src/main/resources/manager/actions/page/manage-assets.js index 26d3b4abf..e9d22d620 100644 --- a/modules/ui-module/src/main/resources/manager/actions/page/manage-assets.js +++ b/modules/ui-module/src/main/resources/manager/actions/page/manage-assets.js @@ -8,12 +8,12 @@ * it under the terms of the GNU Affero 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 General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * #L% diff --git a/modules/ui-module/src/main/resources/manager/actions/page/section-set-published.js b/modules/ui-module/src/main/resources/manager/actions/page/section-set-published.js index 4d49e3bc8..7a408e61d 100644 --- a/modules/ui-module/src/main/resources/manager/actions/page/section-set-published.js +++ b/modules/ui-module/src/main/resources/manager/actions/page/section-set-published.js @@ -8,12 +8,12 @@ * it under the terms of the GNU Affero 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 General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * #L% diff --git a/modules/ui-module/src/main/resources/manager/actions/page/translations.js b/modules/ui-module/src/main/resources/manager/actions/page/translations.js index 423c6e9bb..7745258f6 100644 --- a/modules/ui-module/src/main/resources/manager/actions/page/translations.js +++ b/modules/ui-module/src/main/resources/manager/actions/page/translations.js @@ -8,12 +8,12 @@ * it under the terms of the GNU Affero 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 General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * #L% diff --git a/modules/ui-module/src/main/resources/manager/actions/reload-preview.js b/modules/ui-module/src/main/resources/manager/actions/reload-preview.js index adc035a23..b46cabed9 100644 --- a/modules/ui-module/src/main/resources/manager/actions/reload-preview.js +++ b/modules/ui-module/src/main/resources/manager/actions/reload-preview.js @@ -8,12 +8,12 @@ * it under the terms of the GNU Affero 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 General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * #L% diff --git a/modules/ui-module/src/main/resources/manager/actions/site-change.js b/modules/ui-module/src/main/resources/manager/actions/site-change.js index ba95c51f8..f76f45d62 100644 --- a/modules/ui-module/src/main/resources/manager/actions/site-change.js +++ b/modules/ui-module/src/main/resources/manager/actions/site-change.js @@ -8,12 +8,12 @@ * it under the terms of the GNU Affero 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 General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * #L% diff --git a/modules/ui-module/src/main/resources/manager/actions/test-command.js b/modules/ui-module/src/main/resources/manager/actions/test-command.js index aaf0835e9..ab354e896 100644 --- a/modules/ui-module/src/main/resources/manager/actions/test-command.js +++ b/modules/ui-module/src/main/resources/manager/actions/test-command.js @@ -8,12 +8,12 @@ * it under the terms of the GNU Affero 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 General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * #L% diff --git a/modules/ui-module/src/main/resources/manager/index.html b/modules/ui-module/src/main/resources/manager/index.html index 169f7a338..878d7ac6d 100644 --- a/modules/ui-module/src/main/resources/manager/index.html +++ b/modules/ui-module/src/main/resources/manager/index.html @@ -120,6 +120,25 @@ + {% assign contentTypeMenu = actionFactory.createContentTypeMenu() %} + {% if contentTypeMenu.entries().size() > 0 %} + + {% /if %}