diff --git a/flexmark-ext-admonition/src/main/java/com/vladsch/flexmark/ext/admonition/AdmonitionBlock.java b/flexmark-ext-admonition/src/main/java/com/vladsch/flexmark/ext/admonition/AdmonitionBlock.java index 854149bf67..68305b6c3e 100644 --- a/flexmark-ext-admonition/src/main/java/com/vladsch/flexmark/ext/admonition/AdmonitionBlock.java +++ b/flexmark-ext-admonition/src/main/java/com/vladsch/flexmark/ext/admonition/AdmonitionBlock.java @@ -3,8 +3,10 @@ import com.vladsch.flexmark.ast.Paragraph; import com.vladsch.flexmark.ast.ParagraphContainer; import com.vladsch.flexmark.util.ast.Block; +import com.vladsch.flexmark.util.ast.Node; import com.vladsch.flexmark.util.sequence.BasedSequence; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.List; @@ -14,31 +16,13 @@ public class AdmonitionBlock extends Block implements ParagraphContainer { private BasedSequence openingMarker = BasedSequence.NULL; private BasedSequence info = BasedSequence.NULL; - protected BasedSequence titleOpeningMarker = BasedSequence.NULL; - protected BasedSequence title = BasedSequence.NULL; - protected BasedSequence titleClosingMarker = BasedSequence.NULL; @NotNull @Override public BasedSequence[] getSegments() { return new BasedSequence[] { openingMarker, - info, - titleOpeningMarker, - title, - titleClosingMarker, - }; - } - - @NotNull - @Override - public BasedSequence[] getSegmentsForChars() { - return new BasedSequence[] { - openingMarker, - info, - titleOpeningMarker, - title, - titleClosingMarker, + info }; } @@ -46,7 +30,6 @@ public BasedSequence[] getSegmentsForChars() { public void getAstExtra(@NotNull StringBuilder out) { segmentSpanChars(out, openingMarker, "open"); segmentSpanChars(out, info, "info"); - delimitedSegmentSpanChars(out, titleOpeningMarker, title, titleClosingMarker, "title"); } public AdmonitionBlock() { @@ -78,45 +61,32 @@ public BasedSequence getInfo() { return info; } - public BasedSequence getTitle() { - return title; - } - - public BasedSequence getTitleOpeningMarker() { - return titleOpeningMarker; + @Nullable + public AdmonitionTitle getTitleNode() { + Node child = getFirstChild(); + if (child instanceof AdmonitionTitle) + return (AdmonitionTitle) child; + return null; } - public void setTitleOpeningMarker(BasedSequence titleOpeningMarker) { - this.titleOpeningMarker = titleOpeningMarker; + public BasedSequence getTitle() { + AdmonitionTitle title = getTitleNode(); + return title == null ? BasedSequence.NULL : title.getText(); } - public void setTitle(BasedSequence title) { - this.title = title; + public BasedSequence getTitleOpeningMarker() { + AdmonitionTitle title = getTitleNode(); + return title == null ? BasedSequence.NULL : title.getOpeningMarker(); } public BasedSequence getTitleClosingMarker() { - return titleClosingMarker; - } - - public void setTitleClosingMarker(BasedSequence titleClosingMarker) { - this.titleClosingMarker = titleClosingMarker; + AdmonitionTitle title = getTitleNode(); + return title == null ? BasedSequence.NULL : title.getClosingMarker(); } public BasedSequence getTitleChars() { - return spanningChars(titleOpeningMarker, title, titleClosingMarker); - } - - public void setTitleChars(BasedSequence titleChars) { - if (titleChars != null && titleChars != BasedSequence.NULL) { - int titleCharsLength = titleChars.length(); - titleOpeningMarker = titleChars.subSequence(0, 1); - title = titleChars.subSequence(1, titleCharsLength - 1); - titleClosingMarker = titleChars.subSequence(titleCharsLength - 1, titleCharsLength); - } else { - titleOpeningMarker = BasedSequence.NULL; - title = BasedSequence.NULL; - titleClosingMarker = BasedSequence.NULL; - } + AdmonitionTitle title = getTitleNode(); + return title == null ? BasedSequence.NULL : title.getChars(); } @Override diff --git a/flexmark-ext-admonition/src/main/java/com/vladsch/flexmark/ext/admonition/AdmonitionTitle.java b/flexmark-ext-admonition/src/main/java/com/vladsch/flexmark/ext/admonition/AdmonitionTitle.java new file mode 100755 index 0000000000..da81763b1b --- /dev/null +++ b/flexmark-ext-admonition/src/main/java/com/vladsch/flexmark/ext/admonition/AdmonitionTitle.java @@ -0,0 +1,6 @@ +package com.vladsch.flexmark.ext.admonition; + +import com.vladsch.flexmark.ast.DelimitedNodeImpl; + +public class AdmonitionTitle extends DelimitedNodeImpl { +} diff --git a/flexmark-ext-admonition/src/main/java/com/vladsch/flexmark/ext/admonition/internal/AdmonitionBlockParser.java b/flexmark-ext-admonition/src/main/java/com/vladsch/flexmark/ext/admonition/internal/AdmonitionBlockParser.java index 6a1ccb8d48..9e5cf7cfc9 100644 --- a/flexmark-ext-admonition/src/main/java/com/vladsch/flexmark/ext/admonition/internal/AdmonitionBlockParser.java +++ b/flexmark-ext-admonition/src/main/java/com/vladsch/flexmark/ext/admonition/internal/AdmonitionBlockParser.java @@ -3,6 +3,8 @@ import com.vladsch.flexmark.ast.ListItem; import com.vladsch.flexmark.ast.util.Parsing; import com.vladsch.flexmark.ext.admonition.AdmonitionBlock; +import com.vladsch.flexmark.ext.admonition.AdmonitionTitle; +import com.vladsch.flexmark.parser.InlineParser; import com.vladsch.flexmark.parser.block.*; import com.vladsch.flexmark.util.ast.Block; import com.vladsch.flexmark.util.data.DataHolder; @@ -71,6 +73,15 @@ public void closeBlock(ParserState state) { block.setCharsFromContent(); } + @Override + public void parseInlines(InlineParser inlineParser) { + super.parseInlines(inlineParser); + AdmonitionTitle title = block.getTitleNode(); + if (title != null) { + inlineParser.parse(title.getText(), title); + } + } + public static class Factory implements CustomBlockParserFactory { @Nullable @Override @@ -185,7 +196,14 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar AdmonitionBlockParser admonitionBlockParser = new AdmonitionBlockParser(options, contentOffset); admonitionBlockParser.block.setOpeningMarker(openingMarker); admonitionBlockParser.block.setInfo(info); - admonitionBlockParser.block.setTitleChars(titleChars); + if (titleChars.isNotNull()) { + AdmonitionTitle title = new AdmonitionTitle(); + title.setOpeningMarker(titleChars.subSequence(0, 1)); + title.setText(titleChars.subSequence(1, titleChars.length() - 1)); + title.setClosingMarker(titleChars.subSequence(titleChars.length() - 1)); + title.setCharsFromSegments(); + admonitionBlockParser.block.prependChild(title); + } return BlockStart.of(admonitionBlockParser) .atIndex(line.length()); diff --git a/flexmark-ext-admonition/src/main/java/com/vladsch/flexmark/ext/admonition/internal/AdmonitionNodeFormatter.java b/flexmark-ext-admonition/src/main/java/com/vladsch/flexmark/ext/admonition/internal/AdmonitionNodeFormatter.java index faad336564..31084bd1e5 100644 --- a/flexmark-ext-admonition/src/main/java/com/vladsch/flexmark/ext/admonition/internal/AdmonitionNodeFormatter.java +++ b/flexmark-ext-admonition/src/main/java/com/vladsch/flexmark/ext/admonition/internal/AdmonitionNodeFormatter.java @@ -1,14 +1,14 @@ package com.vladsch.flexmark.ext.admonition.internal; import com.vladsch.flexmark.ext.admonition.AdmonitionBlock; +import com.vladsch.flexmark.ext.admonition.AdmonitionTitle; import com.vladsch.flexmark.formatter.*; +import com.vladsch.flexmark.util.ast.Node; import com.vladsch.flexmark.util.data.DataHolder; import com.vladsch.flexmark.util.sequence.RepeatedSequence; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Collections; -import java.util.HashSet; import java.util.Set; public class AdmonitionNodeFormatter implements NodeFormatter { @@ -27,21 +27,35 @@ public Set> getNodeClasses() { @Nullable @Override public Set> getNodeFormattingHandlers() { - return new HashSet<>(Collections.singletonList( - new NodeFormattingHandler<>(AdmonitionBlock.class, AdmonitionNodeFormatter.this::render) - )); + return Set.of( + new NodeFormattingHandler<>(AdmonitionBlock.class, AdmonitionNodeFormatter.this::render), + new NodeFormattingHandler<>(AdmonitionTitle.class, AdmonitionNodeFormatter.this::render) + ); + } + + private void render(AdmonitionTitle node, NodeFormatterContext context, MarkdownWriter markdown) { + markdown.append(node.getOpeningMarker()); + context.renderChildren(node); + markdown.append(node.getClosingMarker()); } private void render(AdmonitionBlock node, NodeFormatterContext context, MarkdownWriter markdown) { markdown.blankLine(); markdown.append(node.getOpeningMarker()).append(' '); markdown.appendNonTranslating(node.getInfo()); - if (node.getTitle().isNotNull()) { - markdown.append(' ').append('"').appendTranslating(node.getTitle()).append('"'); + AdmonitionTitle title = node.getTitleNode(); + if (title != null) { + markdown.append(' '); + context.render(title); } markdown.line(); markdown.pushPrefix().addPrefix(RepeatedSequence.repeatOf(" ", options.contentIndent).toString()); - context.renderChildren(node); + Node next = title == null ? node.getFirstChild() : title.getNext(); + while (next != null) { + Node child = next; + next = child.getNext(); + context.render(child); + } markdown.blankLine(); markdown.popPrefix(); } diff --git a/flexmark-ext-admonition/src/main/java/com/vladsch/flexmark/ext/admonition/internal/AdmonitionNodeRenderer.java b/flexmark-ext-admonition/src/main/java/com/vladsch/flexmark/ext/admonition/internal/AdmonitionNodeRenderer.java index 2b83b26e1a..41c9e592f9 100644 --- a/flexmark-ext-admonition/src/main/java/com/vladsch/flexmark/ext/admonition/internal/AdmonitionNodeRenderer.java +++ b/flexmark-ext-admonition/src/main/java/com/vladsch/flexmark/ext/admonition/internal/AdmonitionNodeRenderer.java @@ -1,9 +1,11 @@ package com.vladsch.flexmark.ext.admonition.internal; import com.vladsch.flexmark.ext.admonition.AdmonitionBlock; +import com.vladsch.flexmark.ext.admonition.AdmonitionTitle; import com.vladsch.flexmark.html.HtmlWriter; import com.vladsch.flexmark.html.renderer.*; import com.vladsch.flexmark.util.ast.Document; +import com.vladsch.flexmark.util.ast.Node; import com.vladsch.flexmark.util.data.DataHolder; import com.vladsch.flexmark.util.html.Attribute; import org.jetbrains.annotations.NotNull; @@ -31,6 +33,7 @@ public AdmonitionNodeRenderer(DataHolder options) { public Set> getNodeRenderingHandlers() { Set> set = new HashSet<>(); set.add(new NodeRenderingHandler<>(AdmonitionBlock.class, this::render)); + set.add(new NodeRenderingHandler<>(AdmonitionTitle.class, this::render)); return set; } @@ -71,6 +74,23 @@ public void renderDocument(@NotNull NodeRendererContext context, @NotNull HtmlWr } } + private static void outputTitle(String type, HtmlWriter html, Runnable output) { + html.attr(Attribute.CLASS_ATTR, "adm-heading").withAttr(ADMONITION_HEADING_PART).tag("div").line(); + html.attr(Attribute.CLASS_ATTR, "adm-icon").withAttr(ADMONITION_ICON_PART).tag("svg").raw("").closeTag("svg"); + html.withAttr(ADMONITION_TITLE_PART).tag("span", output); + html.closeTag("div").line(); + } + + private void render(AdmonitionTitle node, NodeRendererContext context, HtmlWriter html) { + if (node.getText().isEmpty()) return; + String type = options.unresolvedQualifier; + if (node.getParent() instanceof AdmonitionBlock) { + type = ((AdmonitionBlock) node.getParent()).getInfo().toString().toLowerCase(); + type = this.options.qualifierTypeMap.getOrDefault(type, options.unresolvedQualifier); + } + outputTitle(type, html, () -> context.renderChildren(node)); + } + private void render(AdmonitionBlock node, NodeRendererContext context, HtmlWriter html) { String info = node.getInfo().toString().toLowerCase(); String type = this.options.qualifierTypeMap.get(info); @@ -78,51 +98,41 @@ private void render(AdmonitionBlock node, NodeRendererContext context, HtmlWrite type = options.unresolvedQualifier; } - String title; - if (node.getTitle().isNull()) { - title = this.options.qualifierTitleMap.get(info); - if (title == null) { - title = info.substring(0, 1).toUpperCase() + info.substring(1); - } - } else { - title = node.getTitle().toString(); - } - String openClose; if (node.getOpeningMarker().equals("???")) openClose = " adm-collapsed"; else if (node.getOpeningMarker().equals("???+")) openClose = "adm-open"; else openClose = null; - if (title.isEmpty()) { - html.srcPos(node.getChars()).withAttr() - .attr(Attribute.CLASS_ATTR, "adm-block") - .attr(Attribute.CLASS_ATTR, "adm-" + type) - .tag("div", false).line(); - - html.attr(Attribute.CLASS_ATTR, "adm-body").withAttr(ADMONITION_BODY_PART).tag("div").indent().line(); - - context.renderChildren(node); - - html.unIndent().closeTag("div").line(); - html.closeTag("div").line(); - } else { - html.srcPos(node.getChars()) - .attr(Attribute.CLASS_ATTR, "adm-block").attr(Attribute.CLASS_ATTR, "adm-" + type); - - if (openClose != null) { - html.attr(Attribute.CLASS_ATTR, openClose).attr(Attribute.CLASS_ATTR, "adm-" + type); - } - - html.withAttr().tag("div", false).line(); - html.attr(Attribute.CLASS_ATTR, "adm-heading").withAttr(ADMONITION_HEADING_PART).tag("div").line(); - html.attr(Attribute.CLASS_ATTR, "adm-icon").withAttr(ADMONITION_ICON_PART).tag("svg").raw("").closeTag("svg"); - html.withAttr(ADMONITION_TITLE_PART).tag("span").text(title).closeTag("span").line(); - html.closeTag("div").line(); - - html.attr(Attribute.CLASS_ATTR, "adm-body").withAttr(ADMONITION_BODY_PART).withCondIndent().tagLine("div", () -> context.renderChildren(node)); + html.srcPos(node.getChars()).withAttr() + .attr(Attribute.CLASS_ATTR, "adm-block") + .attr(Attribute.CLASS_ATTR, "adm-" + type); + if (openClose != null) { + html.attr(Attribute.CLASS_ATTR, openClose); + } + html.tag("div", false).line(); + + AdmonitionTitle title = node.getTitleNode(); + + if (title == null) { // standard title + outputTitle(type, html, () -> { + String automaticTitle = this.options.qualifierTitleMap.get(info); + if (automaticTitle == null) + automaticTitle = info.substring(0, 1).toUpperCase() + info.substring(1); + html.text(automaticTitle); + }); + } else if (!title.getText().isEmpty()) { + outputTitle(type, html, () -> context.renderChildren(title)); + } - html.closeTag("div").line(); + html.attr(Attribute.CLASS_ATTR, "adm-body").withAttr(ADMONITION_BODY_PART).tag("div").indent().line(); + Node next = title == null ? node.getFirstChild() : title.getNext(); + while (next != null) { + Node child = next; + next = child.getNext(); + context.render(child); } + html.unIndent().closeTag("div").line(); + html.closeTag("div").line(); } public static class Factory implements NodeRendererFactory {