diff --git a/app/build.gradle b/app/build.gradle index 9512e8d2..ef215520 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -29,6 +29,7 @@ android { dependencies { implementation project(':markwon') + implementation project(':markwon-html') implementation project(':markwon-image-gif') implementation project(':markwon-image-svg') implementation project(':markwon-syntax-highlight') diff --git a/app/src/main/java/ru/noties/markwon/GifProcessor.java b/app/src/main/java/ru/noties/markwon/GifProcessor.java index 7d2cd7c6..6c8bd35f 100644 --- a/app/src/main/java/ru/noties/markwon/GifProcessor.java +++ b/app/src/main/java/ru/noties/markwon/GifProcessor.java @@ -9,6 +9,7 @@ import android.view.View; import android.widget.TextView; import pl.droidsonroids.gif.GifDrawable; +import ru.noties.debug.Debug; import ru.noties.markwon.spans.AsyncDrawableSpan; public abstract class GifProcessor { @@ -25,16 +26,21 @@ public abstract class GifProcessor { @Override public void process(@NonNull final TextView textView) { + Debug.i("textView: %s", textView); + // here is what we will do additionally: // we query for all asyncDrawableSpans // we check if they are inside clickableSpan // if not we apply onGifListener final Spannable spannable = spannable(textView); + Debug.i(spannable); if (spannable == null) { return; } + Debug.i(spannable); + final AsyncDrawableSpan[] asyncDrawableSpans = spannable.getSpans(0, spannable.length(), AsyncDrawableSpan.class); if (asyncDrawableSpans == null @@ -42,6 +48,8 @@ public abstract class GifProcessor { return; } + Debug.i(asyncDrawableSpans); + int start; int end; ClickableSpan[] clickableSpans; @@ -51,6 +59,8 @@ public abstract class GifProcessor { start = spannable.getSpanStart(asyncDrawableSpan); end = spannable.getSpanEnd(asyncDrawableSpan); + Debug.i(asyncDrawableSpan, start, end); + if (start < 0 || end < 0) { continue; @@ -74,6 +84,7 @@ public abstract class GifProcessor { @Nullable private static Spannable spannable(@NonNull TextView textView) { final CharSequence charSequence = textView.getText(); + Debug.i("type: %s, spanned: %s, spannable: %s", charSequence.getClass().getName(), charSequence instanceof Spanned, charSequence instanceof Spannable); if (charSequence instanceof Spannable) { return (Spannable) charSequence; } @@ -85,6 +96,8 @@ public abstract class GifProcessor { @NonNull AsyncDrawableSpan span, @NonNull GifAwareAsyncDrawable drawable) { + Debug.i("textView: %s, span: %s, drawable: %s", textView, span, drawable); + // important thing here is to obtain new spannable from textView // as with each `setText()` new spannable is created and keeping reference // to an older one won't affect textView diff --git a/app/src/main/java/ru/noties/markwon/MarkdownRenderer.java b/app/src/main/java/ru/noties/markwon/MarkdownRenderer.java index fc6af948..bd09825f 100644 --- a/app/src/main/java/ru/noties/markwon/MarkdownRenderer.java +++ b/app/src/main/java/ru/noties/markwon/MarkdownRenderer.java @@ -14,6 +14,7 @@ import javax.inject.Inject; import ru.noties.debug.Debug; import ru.noties.markwon.core.CorePlugin; +import ru.noties.markwon.html.impl.HtmlPlugin; import ru.noties.markwon.image.ImagesPlugin; import ru.noties.markwon.image.gif.GifPlugin; import ru.noties.markwon.image.svg.SvgPlugin; @@ -21,6 +22,8 @@ import ru.noties.markwon.syntax.Prism4jTheme; import ru.noties.markwon.syntax.Prism4jThemeDarkula; import ru.noties.markwon.syntax.Prism4jThemeDefault; import ru.noties.markwon.syntax.SyntaxHighlightPlugin; +import ru.noties.markwon.table.TablePlugin; +import ru.noties.markwon.tasklist.TaskListPlugin; import ru.noties.prism4j.Prism4j; @ActivityScope @@ -84,6 +87,9 @@ public class MarkdownRenderer { .use(GifPlugin.create(false)) .use(SyntaxHighlightPlugin.create(prism4j, prism4jTheme)) .use(GifAwarePlugin.create(context)) + .use(TablePlugin.create(context)) + .use(TaskListPlugin.create(context)) + .use(HtmlPlugin.create()) .use(new AbstractMarkwonPlugin() { @Override public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { diff --git a/markwon-html/src/main/java/ru/noties/markwon/html/impl/HtmlPlugin.java b/markwon-html/src/main/java/ru/noties/markwon/html/impl/HtmlPlugin.java new file mode 100644 index 00000000..6e8935df --- /dev/null +++ b/markwon-html/src/main/java/ru/noties/markwon/html/impl/HtmlPlugin.java @@ -0,0 +1,85 @@ +package ru.noties.markwon.html.impl; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.commonmark.node.Document; +import org.commonmark.node.HtmlBlock; +import org.commonmark.node.HtmlInline; + +import ru.noties.markwon.AbstractMarkwonPlugin; +import ru.noties.markwon.MarkwonConfiguration; +import ru.noties.markwon.MarkwonVisitor; +import ru.noties.markwon.html.MarkwonHtmlParser; +import ru.noties.markwon.html.MarkwonHtmlRenderer; + +public class HtmlPlugin extends AbstractMarkwonPlugin { + + @NonNull + public static HtmlPlugin create() { + return create(MarkwonHtmlRendererImpl.create(), MarkwonHtmlParserImpl.create()); + } + + @NonNull + public static HtmlPlugin create(@NonNull MarkwonHtmlRenderer renderer) { + return create(renderer, MarkwonHtmlParserImpl.create()); + } + + @NonNull + public static HtmlPlugin create(@NonNull MarkwonHtmlParser parser) { + return create(MarkwonHtmlRendererImpl.create(), parser); + } + + @NonNull + public static HtmlPlugin create(@NonNull MarkwonHtmlRenderer renderer, @NonNull MarkwonHtmlParser parser) { + return new HtmlPlugin(renderer, parser); + } + + private final MarkwonHtmlRenderer renderer; + private final MarkwonHtmlParser parser; + + public HtmlPlugin(@NonNull MarkwonHtmlRenderer renderer, @NonNull MarkwonHtmlParser parser) { + this.renderer = renderer; + this.parser = parser; + } + + @Override + public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { + builder + .htmlParser(parser) + .htmlRenderer(renderer); + } + + @Override + public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { + builder + .on(Document.class, new MarkwonVisitor.NodeVisitor() { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull Document document) { + + visitor.visitChildren(document); + + final MarkwonConfiguration configuration = visitor.configuration(); + configuration.htmlRenderer().render(configuration, visitor.builder(), configuration.htmlParser()); + } + }) + .on(HtmlBlock.class, new MarkwonVisitor.NodeVisitor() { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull HtmlBlock htmlBlock) { + visitHtml(visitor, htmlBlock.getLiteral()); + } + }) + .on(HtmlInline.class, new MarkwonVisitor.NodeVisitor() { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull HtmlInline htmlInline) { + visitHtml(visitor, htmlInline.getLiteral()); + } + }); + } + + private void visitHtml(@NonNull MarkwonVisitor visitor, @Nullable String html) { + if (html != null) { + visitor.configuration().htmlParser().processFragment(visitor.builder(), html); + } + } +} diff --git a/markwon-html/src/main/java/ru/noties/markwon/html/impl/MarkwonHtmlRendererImpl.java b/markwon-html/src/main/java/ru/noties/markwon/html/impl/MarkwonHtmlRendererImpl.java index 6ec8f26b..b6b2851d 100644 --- a/markwon-html/src/main/java/ru/noties/markwon/html/impl/MarkwonHtmlRendererImpl.java +++ b/markwon-html/src/main/java/ru/noties/markwon/html/impl/MarkwonHtmlRendererImpl.java @@ -34,8 +34,24 @@ public class MarkwonHtmlRendererImpl extends MarkwonHtmlRenderer { return builderWithDefaults().build(); } + /** + * @since 3.0.0 + */ + @NonNull + public static MarkwonHtmlRendererImpl create(boolean allowNonClosedTags) { + return builderWithDefaults(allowNonClosedTags).build(); + } + @NonNull public static Builder builderWithDefaults() { + return builderWithDefaults(false); + } + + /** + * @since 3.0.0 + */ + @NonNull + public static Builder builderWithDefaults(boolean allowNonClosedTags) { final EmphasisHandler emphasisHandler = new EmphasisHandler(); final StrongEmphasisHandler strongEmphasisHandler = new StrongEmphasisHandler(); @@ -44,6 +60,7 @@ public class MarkwonHtmlRendererImpl extends MarkwonHtmlRenderer { final ListHandler listHandler = new ListHandler(); return builder() + .allowNonClosedTags(allowNonClosedTags) .handler("i", emphasisHandler) .handler("em", emphasisHandler) .handler("cite", emphasisHandler) @@ -77,9 +94,11 @@ public class MarkwonHtmlRendererImpl extends MarkwonHtmlRenderer { public static final float SCRIPT_DEF_TEXT_SIZE_RATIO = .75F; + private final boolean allowNonClosedTags; private final Map tagHandlers; - private MarkwonHtmlRendererImpl(@NonNull Map tagHandlers) { + private MarkwonHtmlRendererImpl(boolean allowNonClosedTags, @NonNull Map tagHandlers) { + this.allowNonClosedTags = allowNonClosedTags; this.tagHandlers = tagHandlers; } @@ -90,7 +109,7 @@ public class MarkwonHtmlRendererImpl extends MarkwonHtmlRenderer { @NonNull MarkwonHtmlParser parser) { final int end; - if (!configuration.htmlAllowNonClosedTags()) { + if (!allowNonClosedTags) { end = HtmlTag.NO_END; } else { end = builder.length(); @@ -152,6 +171,7 @@ public class MarkwonHtmlRendererImpl extends MarkwonHtmlRenderer { public static class Builder { private final Map tagHandlers = new HashMap<>(2); + private boolean allowNonClosedTags; @NonNull public Builder handler(@NonNull String tagName, @NonNull TagHandler tagHandler) { @@ -159,9 +179,23 @@ public class MarkwonHtmlRendererImpl extends MarkwonHtmlRenderer { return this; } + /** + * @param allowNonClosedTags that indicates if non-closed html tags should be rendered. + * If this argument is true then all non-closed HTML tags + * will be closed at the end of a document. Otherwise they will + * be delivered non-closed {@code HtmlTag#isClosed()} and thus not + * rendered at all + * @since 3.0.0 + */ + @NonNull + public Builder allowNonClosedTags(boolean allowNonClosedTags) { + this.allowNonClosedTags = allowNonClosedTags; + return this; + } + @NonNull public MarkwonHtmlRendererImpl build() { - return new MarkwonHtmlRendererImpl(Collections.unmodifiableMap(tagHandlers)); + return new MarkwonHtmlRendererImpl(allowNonClosedTags, Collections.unmodifiableMap(tagHandlers)); } } } diff --git a/markwon/src/main/java/ru/noties/markwon/MarkwonConfiguration.java b/markwon/src/main/java/ru/noties/markwon/MarkwonConfiguration.java index 7c9fad92..33be0601 100644 --- a/markwon/src/main/java/ru/noties/markwon/MarkwonConfiguration.java +++ b/markwon/src/main/java/ru/noties/markwon/MarkwonConfiguration.java @@ -39,7 +39,7 @@ public class MarkwonConfiguration { private final SpannableFactory factory; // @since 1.1.0 private final MarkwonHtmlParser htmlParser; // @since 2.0.0 private final MarkwonHtmlRenderer htmlRenderer; // @since 2.0.0 - private final boolean htmlAllowNonClosedTags; // @since 2.0.0 +// private final boolean htmlAllowNonClosedTags; // @since 2.0.0 private MarkwonConfiguration(@NonNull Builder builder) { this.theme = builder.theme; @@ -51,7 +51,7 @@ public class MarkwonConfiguration { this.factory = builder.factory; this.htmlParser = builder.htmlParser; this.htmlRenderer = builder.htmlRenderer; - this.htmlAllowNonClosedTags = builder.htmlAllowNonClosedTags; +// this.htmlAllowNonClosedTags = builder.htmlAllowNonClosedTags; } /** @@ -113,12 +113,12 @@ public class MarkwonConfiguration { return htmlRenderer; } - /** - * @since 2.0.0 - */ - public boolean htmlAllowNonClosedTags() { - return htmlAllowNonClosedTags; - } +// /** +// * @since 2.0.0 +// */ +// public boolean htmlAllowNonClosedTags() { +// return htmlAllowNonClosedTags; +// } @SuppressWarnings("unused") public static class Builder { @@ -134,7 +134,7 @@ public class MarkwonConfiguration { private SpannableFactory factory; // @since 1.1.0 private MarkwonHtmlParser htmlParser; // @since 2.0.0 private MarkwonHtmlRenderer htmlRenderer; // @since 2.0.0 - private boolean htmlAllowNonClosedTags; // @since 2.0.0 +// private boolean htmlAllowNonClosedTags; // @since 2.0.0 Builder(@NonNull Context context) { this.context = context; @@ -151,7 +151,7 @@ public class MarkwonConfiguration { this.factory = configuration.factory; this.htmlParser = configuration.htmlParser; this.htmlRenderer = configuration.htmlRenderer; - this.htmlAllowNonClosedTags = configuration.htmlAllowNonClosedTags; +// this.htmlAllowNonClosedTags = configuration.htmlAllowNonClosedTags; } @NonNull @@ -208,18 +208,18 @@ public class MarkwonConfiguration { return this; } - /** - * @param htmlAllowNonClosedTags that indicates if non-closed html tags should be rendered. - * If this argument is true then all non-closed HTML tags - * will be closed at the end of a document. Otherwise they will - * be delivered non-closed {@code HtmlTag#isClosed()} - * @since 2.0.0 - */ - @NonNull - public Builder htmlAllowNonClosedTags(boolean htmlAllowNonClosedTags) { - this.htmlAllowNonClosedTags = htmlAllowNonClosedTags; - return this; - } +// /** +// * @param htmlAllowNonClosedTags that indicates if non-closed html tags should be rendered. +// * If this argument is true then all non-closed HTML tags +// * will be closed at the end of a document. Otherwise they will +// * be delivered non-closed {@code HtmlTag#isClosed()} +// * @since 2.0.0 +// */ +// @NonNull +// public Builder htmlAllowNonClosedTags(boolean htmlAllowNonClosedTags) { +// this.htmlAllowNonClosedTags = htmlAllowNonClosedTags; +// return this; +// } @NonNull public MarkwonConfiguration build(@NonNull MarkwonTheme theme, @NonNull AsyncDrawableLoader asyncDrawableLoader) { diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java b/markwon/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java index 6a3a94e3..18d02c19 100644 --- a/markwon/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java +++ b/markwon/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java @@ -4,23 +4,16 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import org.commonmark.ext.gfm.strikethrough.Strikethrough; -import org.commonmark.ext.gfm.tables.TableBody; -import org.commonmark.ext.gfm.tables.TableCell; -import org.commonmark.ext.gfm.tables.TableHead; -import org.commonmark.ext.gfm.tables.TableRow; import org.commonmark.node.AbstractVisitor; import org.commonmark.node.BlockQuote; import org.commonmark.node.BulletList; import org.commonmark.node.Code; import org.commonmark.node.CustomBlock; import org.commonmark.node.CustomNode; -import org.commonmark.node.Document; import org.commonmark.node.Emphasis; import org.commonmark.node.FencedCodeBlock; import org.commonmark.node.HardLineBreak; import org.commonmark.node.Heading; -import org.commonmark.node.HtmlBlock; -import org.commonmark.node.HtmlInline; import org.commonmark.node.Image; import org.commonmark.node.IndentedCodeBlock; import org.commonmark.node.Link; @@ -29,29 +22,25 @@ import org.commonmark.node.ListItem; import org.commonmark.node.Node; import org.commonmark.node.OrderedList; import org.commonmark.node.Paragraph; -import org.commonmark.node.SoftLineBreak; import org.commonmark.node.StrongEmphasis; import org.commonmark.node.Text; import org.commonmark.node.ThematicBreak; -import java.util.ArrayList; import java.util.List; -import ru.noties.markwon.SpannableBuilder; import ru.noties.markwon.MarkwonConfiguration; +import ru.noties.markwon.SpannableBuilder; import ru.noties.markwon.SpannableFactory; -import ru.noties.markwon.html.api.MarkwonHtmlParser; import ru.noties.markwon.spans.MarkwonTheme; import ru.noties.markwon.table.TableRowSpan; import ru.noties.markwon.tasklist.TaskListBlock; -import ru.noties.markwon.tasklist.TaskListItem; @SuppressWarnings("WeakerAccess") public class SpannableMarkdownVisitor extends AbstractVisitor { private final MarkwonConfiguration configuration; private final SpannableBuilder builder; - private final MarkwonHtmlParser htmlParser; +// private final MarkwonHtmlParser htmlParser; private final MarkwonTheme theme; private final SpannableFactory factory; @@ -69,18 +58,18 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { ) { this.configuration = configuration; this.builder = builder; - this.htmlParser = configuration.htmlParser(); +// this.htmlParser = configuration.htmlParser(); this.theme = configuration.theme(); this.factory = configuration.factory(); } - @Override - public void visit(Document document) { - super.visit(document); - - configuration.htmlRenderer().render(configuration, builder, htmlParser); - } +// @Override +// public void visit(Document document) { +// super.visit(document); +// +// configuration.htmlRenderer().render(configuration, builder, htmlParser); +// } @Override public void visit(Text text) { @@ -276,15 +265,15 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { } } - @Override - public void visit(SoftLineBreak softLineBreak) { - // @since 1.1.1 there is an option to treat soft break as a hard break (thus adding new line) - if (configuration.softBreakAddsNewLine()) { - newLine(); - } else { - builder.append(' '); - } - } +// @Override +// public void visit(SoftLineBreak softLineBreak) { +// // @since 1.1.1 there is an option to treat soft break as a hard break (thus adding new line) +// if (configuration.softBreakAddsNewLine()) { +// newLine(); +// } else { +// builder.append(' '); +// } +// } @Override public void visit(HardLineBreak hardLineBreak) { @@ -463,21 +452,21 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { // user can open it in external viewer? } - @Override - public void visit(HtmlBlock htmlBlock) { - visitHtml(htmlBlock.getLiteral()); - } - - @Override - public void visit(HtmlInline htmlInline) { - visitHtml(htmlInline.getLiteral()); - } - - private void visitHtml(@Nullable String html) { - if (html != null) { - htmlParser.processFragment(builder, html); - } - } +// @Override +// public void visit(HtmlBlock htmlBlock) { +// visitHtml(htmlBlock.getLiteral()); +// } +// +// @Override +// public void visit(HtmlInline htmlInline) { +// visitHtml(htmlInline.getLiteral()); +// } +// +// private void visitHtml(@Nullable String html) { +// if (html != null) { +// htmlParser.processFragment(builder, html); +// } +// } @Override public void visit(Link link) {