Create HtmlPlugin

This commit is contained in:
Dimitry Ivanov 2018-11-26 15:51:16 +03:00
parent 27ed17aaff
commit 66bb33a76b
7 changed files with 197 additions and 69 deletions

View File

@ -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')

View File

@ -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

View File

@ -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) {

View File

@ -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<Document>() {
@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<HtmlBlock>() {
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull HtmlBlock htmlBlock) {
visitHtml(visitor, htmlBlock.getLiteral());
}
})
.on(HtmlInline.class, new MarkwonVisitor.NodeVisitor<HtmlInline>() {
@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);
}
}
}

View File

@ -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<String, TagHandler> tagHandlers;
private MarkwonHtmlRendererImpl(@NonNull Map<String, TagHandler> tagHandlers) {
private MarkwonHtmlRendererImpl(boolean allowNonClosedTags, @NonNull Map<String, TagHandler> 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<String, TagHandler> 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));
}
}
}

View File

@ -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) {

View File

@ -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) {