diff --git a/app/src/main/java/ru/noties/markwon/MarkdownRenderer.java b/app/src/main/java/ru/noties/markwon/MarkdownRenderer.java index df1c0b5d..5c8658e7 100644 --- a/app/src/main/java/ru/noties/markwon/MarkdownRenderer.java +++ b/app/src/main/java/ru/noties/markwon/MarkdownRenderer.java @@ -85,17 +85,17 @@ public class MarkdownRenderer { : prism4JThemeDarkula; final Markwon markwon = Markwon.builder(context) - .use(CorePlugin.create()) - .use(ImagesPlugin.createWithAssets(context)) - .use(SvgPlugin.create(context.getResources())) - .use(GifPlugin.create(false)) - .use(SyntaxHighlightPlugin.create(prism4j, prism4jTheme)) - .use(GifAwarePlugin.create(context)) - .use(TablePlugin.create(context)) - .use(TaskListPlugin.create(context)) - .use(StrikethroughPlugin.create()) - .use(HtmlPlugin.create()) - .use(new AbstractMarkwonPlugin() { + .usePlugin(CorePlugin.create()) + .usePlugin(ImagesPlugin.createWithAssets(context)) + .usePlugin(SvgPlugin.create(context.getResources())) + .usePlugin(GifPlugin.create(false)) + .usePlugin(SyntaxHighlightPlugin.create(prism4j, prism4jTheme)) + .usePlugin(GifAwarePlugin.create(context)) + .usePlugin(TablePlugin.create(context)) + .usePlugin(TaskListPlugin.create(context)) + .usePlugin(StrikethroughPlugin.create()) + .usePlugin(HtmlPlugin.create()) + .usePlugin(new AbstractMarkwonPlugin() { @Override public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { builder.urlProcessor(urlProcessor); diff --git a/app/src/main/java/ru/noties/markwon/gif/GifAwarePlugin.java b/app/src/main/java/ru/noties/markwon/gif/GifAwarePlugin.java index 6bc6682b..ab69ceb7 100644 --- a/app/src/main/java/ru/noties/markwon/gif/GifAwarePlugin.java +++ b/app/src/main/java/ru/noties/markwon/gif/GifAwarePlugin.java @@ -4,9 +4,16 @@ import android.content.Context; import android.support.annotation.NonNull; import android.widget.TextView; +import org.commonmark.node.Image; + import ru.noties.markwon.AbstractMarkwonPlugin; import ru.noties.markwon.MarkwonConfiguration; +import ru.noties.markwon.MarkwonSpansFactory; import ru.noties.markwon.R; +import ru.noties.markwon.RenderProps; +import ru.noties.markwon.SpanFactory; +import ru.noties.markwon.image.AsyncDrawableSpan; +import ru.noties.markwon.image.ImageProps; public class GifAwarePlugin extends AbstractMarkwonPlugin { @@ -18,18 +25,36 @@ public class GifAwarePlugin extends AbstractMarkwonPlugin { private final Context context; private final GifProcessor processor; - public GifAwarePlugin(@NonNull Context context) { + GifAwarePlugin(@NonNull Context context) { this.context = context; this.processor = GifProcessor.create(); } @Override - public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + final GifPlaceholder gifPlaceholder = new GifPlaceholder( context.getResources().getDrawable(R.drawable.ic_play_circle_filled_18dp_white), 0x20000000 ); - builder.factory(new GifAwareSpannableFactory(gifPlaceholder)); + + builder.setFactory(Image.class, new SpanFactory() { + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps context) { + return new AsyncDrawableSpan( + configuration.theme(), + new GifAwareAsyncDrawable( + gifPlaceholder, + ImageProps.DESTINATION.require(context), + configuration.asyncDrawableLoader(), + configuration.imageSizeResolver(), + ImageProps.IMAGE_SIZE.get(context) + ), + AsyncDrawableSpan.ALIGN_BOTTOM, + ImageProps.REPLACEMENT_TEXT_IS_LINK.get(context, false) + ); + } + }); } @Override diff --git a/app/src/main/java/ru/noties/markwon/gif/GifAwareSpannableFactory.java b/app/src/main/java/ru/noties/markwon/gif/GifAwareSpannableFactory.java deleted file mode 100644 index 8d9e14fb..00000000 --- a/app/src/main/java/ru/noties/markwon/gif/GifAwareSpannableFactory.java +++ /dev/null @@ -1,37 +0,0 @@ -package ru.noties.markwon.gif; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import ru.noties.markwon.core.MarkwonTheme; -import ru.noties.markwon.core.MarkwonSpannableFactoryDef; -import ru.noties.markwon.image.AsyncDrawableLoader; -import ru.noties.markwon.image.ImageSize; -import ru.noties.markwon.image.ImageSizeResolver; -import ru.noties.markwon.core.spans.AsyncDrawableSpan; - -public class GifAwareSpannableFactory extends MarkwonSpannableFactoryDef { - - private final GifPlaceholder gifPlaceholder; - - public GifAwareSpannableFactory(@NonNull GifPlaceholder gifPlaceholder) { - this.gifPlaceholder = gifPlaceholder; - } - - @Nullable - @Override - public Object image(@NonNull MarkwonTheme theme, @NonNull String destination, @NonNull AsyncDrawableLoader loader, @NonNull ImageSizeResolver imageSizeResolver, @Nullable ImageSize imageSize, boolean replacementTextIsLink) { - return new AsyncDrawableSpan( - theme, - new GifAwareAsyncDrawable( - gifPlaceholder, - destination, - loader, - imageSizeResolver, - imageSize - ), - AsyncDrawableSpan.ALIGN_BOTTOM, - replacementTextIsLink - ); - } -} diff --git a/app/src/main/java/ru/noties/markwon/gif/GifProcessor.java b/app/src/main/java/ru/noties/markwon/gif/GifProcessor.java index 6a297f8b..fcec2e17 100644 --- a/app/src/main/java/ru/noties/markwon/gif/GifProcessor.java +++ b/app/src/main/java/ru/noties/markwon/gif/GifProcessor.java @@ -9,7 +9,7 @@ import android.view.View; import android.widget.TextView; import pl.droidsonroids.gif.GifDrawable; -import ru.noties.markwon.core.spans.AsyncDrawableSpan; +import ru.noties.markwon.image.AsyncDrawableSpan; public abstract class GifProcessor { diff --git a/markwon-ext-tasklist/src/main/java/ru/noties/markwon/ext/tasklist/TaskListPlugin.java b/markwon-ext-tasklist/src/main/java/ru/noties/markwon/ext/tasklist/TaskListPlugin.java index 31a61a1b..5a787dc9 100644 --- a/markwon-ext-tasklist/src/main/java/ru/noties/markwon/ext/tasklist/TaskListPlugin.java +++ b/markwon-ext-tasklist/src/main/java/ru/noties/markwon/ext/tasklist/TaskListPlugin.java @@ -12,8 +12,13 @@ import org.commonmark.node.Node; import org.commonmark.parser.Parser; import ru.noties.markwon.AbstractMarkwonPlugin; +import ru.noties.markwon.MarkwonSpansFactory; import ru.noties.markwon.MarkwonVisitor; +import ru.noties.markwon.RenderProps; +/** + * @since 3.0.0 + */ public class TaskListPlugin extends AbstractMarkwonPlugin { /** @@ -62,6 +67,11 @@ public class TaskListPlugin extends AbstractMarkwonPlugin { builder.customBlockParserFactory(new TaskListBlockParser.Factory()); } + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + builder.setFactory(TaskListItem.class, new TaskListSpanFactory(drawable)); + } + @Override public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { builder @@ -86,11 +96,13 @@ public class TaskListPlugin extends AbstractMarkwonPlugin { final int length = visitor.length(); visitor.visitChildren(taskListItem); - visitor.setSpans(length, new TaskListSpan( - visitor.theme(), - drawable, - indent(taskListItem) + taskListItem.indent(), - taskListItem.done())); + + final RenderProps context = visitor.renderProps(); + + TaskListProps.BLOCK_INDENT.set(context, indent(taskListItem) + taskListItem.indent()); + TaskListProps.DONE.set(context, taskListItem.done()); + + visitor.setSpansForNode(taskListItem, length); if (visitor.hasNext(taskListItem)) { visitor.ensureNewLine(); @@ -99,7 +111,7 @@ public class TaskListPlugin extends AbstractMarkwonPlugin { }); } - private static int resolve(Context context, @AttrRes int attr) { + private static int resolve(@NonNull Context context, @AttrRes int attr) { final TypedValue typedValue = new TypedValue(); final int attrs[] = new int[]{attr}; final TypedArray typedArray = context.obtainStyledAttributes(typedValue.data, attrs); diff --git a/markwon-ext-tasklist/src/main/java/ru/noties/markwon/ext/tasklist/TaskListProps.java b/markwon-ext-tasklist/src/main/java/ru/noties/markwon/ext/tasklist/TaskListProps.java new file mode 100644 index 00000000..7a0b6ca7 --- /dev/null +++ b/markwon-ext-tasklist/src/main/java/ru/noties/markwon/ext/tasklist/TaskListProps.java @@ -0,0 +1,16 @@ +package ru.noties.markwon.ext.tasklist; + +import ru.noties.markwon.Prop; + +/** + * @since 3.0.0 + */ +public abstract class TaskListProps { + + public static final Prop BLOCK_INDENT = Prop.of("task-list-block-indent"); + + public static final Prop DONE = Prop.of("task-list-done"); + + private TaskListProps() { + } +} diff --git a/markwon-ext-tasklist/src/main/java/ru/noties/markwon/ext/tasklist/TaskListSpan.java b/markwon-ext-tasklist/src/main/java/ru/noties/markwon/ext/tasklist/TaskListSpan.java index b339360f..b851f382 100644 --- a/markwon-ext-tasklist/src/main/java/ru/noties/markwon/ext/tasklist/TaskListSpan.java +++ b/markwon-ext-tasklist/src/main/java/ru/noties/markwon/ext/tasklist/TaskListSpan.java @@ -26,13 +26,6 @@ public class TaskListSpan implements LeadingMarginSpan { // @since 2.0.1 field is NOT final (to allow mutation) private boolean isDone; - @Deprecated - public TaskListSpan(@NonNull MarkwonTheme theme, int blockIndent, boolean isDone) { - this.theme = theme; - this.drawable = null; - this.blockIndent = blockIndent; - this.isDone = isDone; - } public TaskListSpan(@NonNull MarkwonTheme theme, @NonNull Drawable drawable, int blockIndent, boolean isDone) { this.theme = theme; @@ -71,11 +64,6 @@ public class TaskListSpan implements LeadingMarginSpan { return; } -// final Drawable drawable = theme.getTaskListDrawable(); -// if (drawable == null) { -// return; -// } - final int save = c.save(); try { diff --git a/markwon-ext-tasklist/src/main/java/ru/noties/markwon/ext/tasklist/TaskListSpanFactory.java b/markwon-ext-tasklist/src/main/java/ru/noties/markwon/ext/tasklist/TaskListSpanFactory.java new file mode 100644 index 00000000..b0f4fb20 --- /dev/null +++ b/markwon-ext-tasklist/src/main/java/ru/noties/markwon/ext/tasklist/TaskListSpanFactory.java @@ -0,0 +1,29 @@ +package ru.noties.markwon.ext.tasklist; + +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import ru.noties.markwon.MarkwonConfiguration; +import ru.noties.markwon.RenderProps; +import ru.noties.markwon.SpanFactory; + +public class TaskListSpanFactory implements SpanFactory { + + private final Drawable drawable; + + public TaskListSpanFactory(@NonNull Drawable drawable) { + this.drawable = drawable; + } + + @Nullable + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps context) { + return new TaskListSpan( + configuration.theme(), + drawable, + TaskListProps.BLOCK_INDENT.get(context, 0), + TaskListProps.DONE.get(context, false) + ); + } +} diff --git a/markwon-html/src/main/java/ru/noties/markwon/html/tag/ImageHandler.java b/markwon-html/src/main/java/ru/noties/markwon/html/tag/ImageHandler.java index 2faf4826..8554a645 100644 --- a/markwon-html/src/main/java/ru/noties/markwon/html/tag/ImageHandler.java +++ b/markwon-html/src/main/java/ru/noties/markwon/html/tag/ImageHandler.java @@ -47,13 +47,15 @@ public class ImageHandler extends SimpleTagHandler { // but we can look and see if we are inside a LinkSpan (will have to extend TagHandler // to obtain an instance SpannableBuilder for inspection) - return configuration.factory().image( - configuration.theme(), - destination, - configuration.asyncDrawableLoader(), - configuration.imageSizeResolver(), - imageSizeParser.parse(tag.attributes()), - false - ); + return null; + +// return configuration.factory().image( +// configuration.theme(), +// destination, +// configuration.asyncDrawableLoader(), +// configuration.imageSizeResolver(), +// imageSizeParser.parse(tag.attributes()), +// false +// ); } } diff --git a/markwon/src/main/java/ru/noties/markwon/AbstractMarkwonPlugin.java b/markwon/src/main/java/ru/noties/markwon/AbstractMarkwonPlugin.java index 9820f18f..28767521 100644 --- a/markwon/src/main/java/ru/noties/markwon/AbstractMarkwonPlugin.java +++ b/markwon/src/main/java/ru/noties/markwon/AbstractMarkwonPlugin.java @@ -35,6 +35,16 @@ public abstract class AbstractMarkwonPlugin implements MarkwonPlugin { } + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + + } + + @Override + public void configureRenderProps(@NonNull RenderProps renderProps) { + + } + @NonNull @Override public String processMarkdown(@NonNull String markdown) { diff --git a/markwon/src/main/java/ru/noties/markwon/Markwon.java b/markwon/src/main/java/ru/noties/markwon/Markwon.java index 6214fc16..6c4e4bea 100644 --- a/markwon/src/main/java/ru/noties/markwon/Markwon.java +++ b/markwon/src/main/java/ru/noties/markwon/Markwon.java @@ -9,6 +9,9 @@ import org.commonmark.node.Node; public abstract class Markwon { + /** + * @since 3.0.0 + */ @NonNull public static Builder builder(@NonNull Context context) { return new MarkwonBuilderImpl(context); @@ -28,6 +31,9 @@ public abstract class Markwon { public abstract void setParsedMarkdown(@NonNull TextView textView, @NonNull CharSequence markdown); + /** + * @since 3.0.0 + */ public interface Builder { /** @@ -40,7 +46,10 @@ public abstract class Markwon { Builder bufferType(@NonNull TextView.BufferType bufferType); @NonNull - Builder use(@NonNull MarkwonPlugin plugin); + Builder usePlugin(@NonNull MarkwonPlugin plugin); + + @NonNull + Builder usePlugins(@NonNull Iterable plugins); @NonNull Markwon build(); diff --git a/markwon/src/main/java/ru/noties/markwon/MarkwonBuilderImpl.java b/markwon/src/main/java/ru/noties/markwon/MarkwonBuilderImpl.java index 641f9691..6098155a 100644 --- a/markwon/src/main/java/ru/noties/markwon/MarkwonBuilderImpl.java +++ b/markwon/src/main/java/ru/noties/markwon/MarkwonBuilderImpl.java @@ -8,11 +8,15 @@ import org.commonmark.parser.Parser; import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; import java.util.List; import ru.noties.markwon.core.MarkwonTheme; import ru.noties.markwon.image.AsyncDrawableLoader; +/** + * @since 3.0.0 + */ class MarkwonBuilderImpl implements Markwon.Builder { private final Context context; @@ -33,11 +37,30 @@ class MarkwonBuilderImpl implements Markwon.Builder { @NonNull @Override - public Markwon.Builder use(@NonNull MarkwonPlugin plugin) { + public Markwon.Builder usePlugin(@NonNull MarkwonPlugin plugin) { plugins.add(plugin); return this; } + @NonNull + @Override + public Markwon.Builder usePlugins(@NonNull Iterable plugins) { + + final Iterator iterator = plugins.iterator(); + + MarkwonPlugin plugin; + + while (iterator.hasNext()) { + plugin = iterator.next(); + if (plugin == null) { + throw new NullPointerException(); + } + this.plugins.add(plugin); + } + + return this; + } + @NonNull @Override public Markwon build() { @@ -45,8 +68,10 @@ class MarkwonBuilderImpl implements Markwon.Builder { final Parser.Builder parserBuilder = new Parser.Builder(); final MarkwonTheme.Builder themeBuilder = MarkwonTheme.builderWithDefaults(context); final AsyncDrawableLoader.Builder asyncDrawableLoaderBuilder = new AsyncDrawableLoader.Builder(); - final MarkwonConfiguration.Builder configurationBuilder = new MarkwonConfiguration.Builder(context); + final MarkwonConfiguration.Builder configurationBuilder = new MarkwonConfiguration.Builder(); final MarkwonVisitor.Builder visitorBuilder = new MarkwonVisitorImpl.BuilderImpl(); + final MarkwonSpansFactory.Builder spanFactoryBuilder = new MarkwonSpansFactoryImpl.BuilderImpl(); + final RenderProps renderProps = new RenderPropsImpl(); for (MarkwonPlugin plugin : plugins) { plugin.configureParser(parserBuilder); @@ -54,16 +79,19 @@ class MarkwonBuilderImpl implements Markwon.Builder { plugin.configureImages(asyncDrawableLoaderBuilder); plugin.configureConfiguration(configurationBuilder); plugin.configureVisitor(visitorBuilder); + plugin.configureSpansFactory(spanFactoryBuilder); + plugin.configureRenderProps(renderProps); } final MarkwonConfiguration configuration = configurationBuilder.build( themeBuilder.build(), - asyncDrawableLoaderBuilder.build()); + asyncDrawableLoaderBuilder.build(), + spanFactoryBuilder.build()); return new MarkwonImpl( bufferType, parserBuilder.build(), - visitorBuilder.build(configuration), + visitorBuilder.build(configuration, renderProps), Collections.unmodifiableList(plugins) ); } diff --git a/markwon/src/main/java/ru/noties/markwon/MarkwonConfiguration.java b/markwon/src/main/java/ru/noties/markwon/MarkwonConfiguration.java index b55c1df5..b5266c1d 100644 --- a/markwon/src/main/java/ru/noties/markwon/MarkwonConfiguration.java +++ b/markwon/src/main/java/ru/noties/markwon/MarkwonConfiguration.java @@ -1,11 +1,8 @@ package ru.noties.markwon; -import android.content.Context; import android.support.annotation.NonNull; import ru.noties.markwon.core.MarkwonTheme; -import ru.noties.markwon.core.MarkwonSpannableFactory; -import ru.noties.markwon.core.MarkwonSpannableFactoryDef; import ru.noties.markwon.core.spans.LinkSpan; import ru.noties.markwon.image.AsyncDrawableLoader; import ru.noties.markwon.image.ImageSizeResolver; @@ -21,16 +18,9 @@ import ru.noties.markwon.urlprocessor.UrlProcessorNoOp; @SuppressWarnings("WeakerAccess") public class MarkwonConfiguration { - // creates default configuration @NonNull - @Deprecated - public static MarkwonConfiguration create(@NonNull Context context) { - return new Builder(context).build(MarkwonTheme.create(context), AsyncDrawableLoader.noOp()); - } - - @NonNull - public static Builder builder(@NonNull Context context) { - return new Builder(context); + public static Builder builder() { + return new Builder(); } private final MarkwonTheme theme; @@ -39,7 +29,9 @@ public class MarkwonConfiguration { private final LinkSpan.Resolver linkResolver; private final UrlProcessor urlProcessor; private final ImageSizeResolver imageSizeResolver; - private final MarkwonSpannableFactory factory; // @since 1.1.0 + + // @since 3.0.0 + private final MarkwonSpansFactory spansFactory; private MarkwonConfiguration(@NonNull Builder builder) { this.theme = builder.theme; @@ -48,7 +40,7 @@ public class MarkwonConfiguration { this.linkResolver = builder.linkResolver; this.urlProcessor = builder.urlProcessor; this.imageSizeResolver = builder.imageSizeResolver; - this.factory = builder.factory; + this.spansFactory = builder.spansFactory; } @NonNull @@ -81,26 +73,26 @@ public class MarkwonConfiguration { return imageSizeResolver; } + /** + * @since 3.0.0 + */ @NonNull - public MarkwonSpannableFactory factory() { - return factory; + public MarkwonSpansFactory spansFactory() { + return spansFactory; } - @SuppressWarnings("unused") + @SuppressWarnings({"unused", "UnusedReturnValue"}) public static class Builder { - private final Context context; - private MarkwonTheme theme; private AsyncDrawableLoader asyncDrawableLoader; private SyntaxHighlight syntaxHighlight; private LinkSpan.Resolver linkResolver; private UrlProcessor urlProcessor; private ImageSizeResolver imageSizeResolver; - private MarkwonSpannableFactory factory; // @since 1.1.0 + private MarkwonSpansFactory spansFactory; - Builder(@NonNull Context context) { - this.context = context; + Builder() { } @NonNull @@ -130,20 +122,15 @@ public class MarkwonConfiguration { return this; } - /** - * @since 1.1.0 - */ @NonNull - public Builder factory(@NonNull MarkwonSpannableFactory factory) { - this.factory = factory; - return this; - } - - @NonNull - public MarkwonConfiguration build(@NonNull MarkwonTheme theme, @NonNull AsyncDrawableLoader asyncDrawableLoader) { + public MarkwonConfiguration build( + @NonNull MarkwonTheme theme, + @NonNull AsyncDrawableLoader asyncDrawableLoader, + @NonNull MarkwonSpansFactory spansFactory) { this.theme = theme; this.asyncDrawableLoader = asyncDrawableLoader; + this.spansFactory = spansFactory; if (syntaxHighlight == null) { syntaxHighlight = new SyntaxHighlightNoOp(); @@ -161,11 +148,6 @@ public class MarkwonConfiguration { imageSizeResolver = new ImageSizeResolverDef(); } - // @since 1.1.0 - if (factory == null) { - factory = MarkwonSpannableFactoryDef.create(); - } - return new MarkwonConfiguration(this); } } diff --git a/markwon/src/main/java/ru/noties/markwon/MarkwonPlugin.java b/markwon/src/main/java/ru/noties/markwon/MarkwonPlugin.java index fc0cf4ca..910b044e 100644 --- a/markwon/src/main/java/ru/noties/markwon/MarkwonPlugin.java +++ b/markwon/src/main/java/ru/noties/markwon/MarkwonPlugin.java @@ -9,6 +9,9 @@ import org.commonmark.parser.Parser; import ru.noties.markwon.core.MarkwonTheme; import ru.noties.markwon.image.AsyncDrawableLoader; +/** + * @since 3.0.0 + */ public interface MarkwonPlugin { void configureParser(@NonNull Parser.Builder builder); @@ -21,6 +24,11 @@ public interface MarkwonPlugin { void configureVisitor(@NonNull MarkwonVisitor.Builder builder); + void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder); + + // can be used to configure own properties and use between plugins + void configureRenderProps(@NonNull RenderProps renderProps); + @NonNull String processMarkdown(@NonNull String markdown); diff --git a/markwon/src/main/java/ru/noties/markwon/MarkwonSpansFactory.java b/markwon/src/main/java/ru/noties/markwon/MarkwonSpansFactory.java new file mode 100644 index 00000000..748ebd57 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/MarkwonSpansFactory.java @@ -0,0 +1,34 @@ +package ru.noties.markwon; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.commonmark.node.Node; + +/** + * @since 3.0.0 + */ +public interface MarkwonSpansFactory { + + @Nullable + F get(@NonNull Class node); + + @Nullable + F get(@NonNull N node); + + @NonNull + F require(@NonNull Class node); + + @NonNull + F require(@NonNull N node); + + + interface Builder { + + @NonNull + Builder setFactory(@NonNull Class node, @NonNull F factory); + + @NonNull + MarkwonSpansFactory build(); + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/MarkwonSpansFactoryImpl.java b/markwon/src/main/java/ru/noties/markwon/MarkwonSpansFactoryImpl.java new file mode 100644 index 00000000..7bbc8ca8 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/MarkwonSpansFactoryImpl.java @@ -0,0 +1,74 @@ +package ru.noties.markwon; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.commonmark.node.Node; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * @since 3.0.0 + */ +class MarkwonSpansFactoryImpl implements MarkwonSpansFactory { + + private final Map, SpanFactory> factories; + + private MarkwonSpansFactoryImpl(@NonNull Map, SpanFactory> factories) { + this.factories = factories; + } + + @Nullable + @Override + public F get(@NonNull Class node) { + //noinspection unchecked + return (F) factories.get(node); + } + + @Nullable + @Override + public F get(@NonNull N node) { + return get(node.getClass()); + } + + @NonNull + @Override + public F require(@NonNull Class node) { + final F f = get(node); + if (f == null) { + throw new NullPointerException(); + } + return f; + } + + @NonNull + @Override + public F require(@NonNull N node) { + final F f = get(node); + if (f == null) { + throw new NullPointerException(); + } + return f; + } + + static class BuilderImpl implements Builder { + + private final Map, SpanFactory> factories = + new HashMap<>(3); + + @NonNull + @Override + public Builder setFactory(@NonNull Class node, @NonNull F factory) { + factories.put(node, factory); + return this; + } + + @NonNull + @Override + public MarkwonSpansFactory build() { + return new MarkwonSpansFactoryImpl(Collections.unmodifiableMap(factories)); + } + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/MarkwonVisitor.java b/markwon/src/main/java/ru/noties/markwon/MarkwonVisitor.java index 88ae1b5d..f5e67c1f 100644 --- a/markwon/src/main/java/ru/noties/markwon/MarkwonVisitor.java +++ b/markwon/src/main/java/ru/noties/markwon/MarkwonVisitor.java @@ -6,9 +6,9 @@ import android.support.annotation.Nullable; import org.commonmark.node.Node; import org.commonmark.node.Visitor; -import ru.noties.markwon.core.MarkwonTheme; -import ru.noties.markwon.core.MarkwonSpannableFactory; - +/** + * @since 3.0.0 + */ public interface MarkwonVisitor extends Visitor { interface NodeVisitor { @@ -21,17 +21,14 @@ public interface MarkwonVisitor extends Visitor { Builder on(@NonNull Class node, @Nullable NodeVisitor nodeVisitor); @NonNull - MarkwonVisitor build(@NonNull MarkwonConfiguration configuration); + MarkwonVisitor build(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps renderProps); } @NonNull MarkwonConfiguration configuration(); @NonNull - MarkwonTheme theme(); - - @NonNull - MarkwonSpannableFactory factory(); + RenderProps renderProps(); @NonNull SpannableBuilder builder(); @@ -48,6 +45,10 @@ public interface MarkwonVisitor extends Visitor { void setSpans(int start, @Nullable Object spans); - @Nullable - NodeVisitor nodeVisitor(@NonNull Class node); + // will automatically obtain SpanFactory instance and use it, it no SpanFactory is registered, + // will throw, if not desired use setSpansForNodeOptional + void setSpansForNode(@NonNull N node, int start); + + // does not throw if there is no SpanFactory registered for this node + void setSpansForNodeOptional(@NonNull N node, int start); } diff --git a/markwon/src/main/java/ru/noties/markwon/MarkwonVisitorImpl.java b/markwon/src/main/java/ru/noties/markwon/MarkwonVisitorImpl.java index 6982b74a..0421f9e3 100644 --- a/markwon/src/main/java/ru/noties/markwon/MarkwonVisitorImpl.java +++ b/markwon/src/main/java/ru/noties/markwon/MarkwonVisitorImpl.java @@ -31,25 +31,25 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import ru.noties.markwon.core.MarkwonTheme; -import ru.noties.markwon.core.MarkwonSpannableFactory; - +/** + * @since 3.0.0 + */ class MarkwonVisitorImpl implements MarkwonVisitor { private final Map, NodeVisitor> nodes; private final MarkwonConfiguration configuration; - private final MarkwonTheme theme; - private final MarkwonSpannableFactory factory; + + private final RenderProps renderProps; private final SpannableBuilder builder = new SpannableBuilder(); MarkwonVisitorImpl( @NonNull MarkwonConfiguration configuration, + @NonNull RenderProps renderProps, @NonNull Map, NodeVisitor> nodes) { this.configuration = configuration; - this.theme = configuration.theme(); - this.factory = configuration.factory(); + this.renderProps = renderProps; this.nodes = nodes; } @@ -181,14 +181,8 @@ class MarkwonVisitorImpl implements MarkwonVisitor { @NonNull @Override - public MarkwonTheme theme() { - return theme; - } - - @NonNull - @Override - public MarkwonSpannableFactory factory() { - return factory; + public RenderProps renderProps() { + return renderProps; } @NonNull @@ -237,17 +231,22 @@ class MarkwonVisitorImpl implements MarkwonVisitor { SpannableBuilder.setSpans(builder, spans, start, builder.length()); } - @Nullable @Override - public NodeVisitor nodeVisitor(@NonNull Class node) { - //noinspection unchecked - return (NodeVisitor) nodes.get(node); + public void setSpansForNode(@NonNull N node, int start) { + setSpans(start, configuration.spansFactory().require(node).getSpans(configuration, renderProps)); + } + + @Override + public void setSpansForNodeOptional(@NonNull N node, int start) { + final SpanFactory factory = configuration.spansFactory().get(node); + if (factory != null) { + setSpans(start, factory.getSpans(configuration, renderProps)); + } } static class BuilderImpl implements Builder { - private final Map, NodeVisitor> nodes = - new HashMap<>(3); + private final Map, NodeVisitor> nodes = new HashMap<>(); @NonNull @Override @@ -264,9 +263,10 @@ class MarkwonVisitorImpl implements MarkwonVisitor { @NonNull @Override - public MarkwonVisitor build(@NonNull MarkwonConfiguration configuration) { + public MarkwonVisitor build(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps renderProps) { return new MarkwonVisitorImpl( configuration, + renderProps, Collections.unmodifiableMap(nodes)); } } diff --git a/markwon/src/main/java/ru/noties/markwon/Prop.java b/markwon/src/main/java/ru/noties/markwon/Prop.java new file mode 100644 index 00000000..a930288f --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/Prop.java @@ -0,0 +1,84 @@ +package ru.noties.markwon; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +/** + * Class to hold data in {@link RenderProps} + * + * @param represents the type that this instance holds + * @since 3.0.0 + */ +public final class Prop { + + @SuppressWarnings("unused") + @NonNull + public static Prop of(@NonNull Class type, @NonNull String name) { + return new Prop<>(name); + } + + @NonNull + public static Prop of(@NonNull String name) { + return new Prop<>(name); + } + + private final String name; + + private Prop(@NonNull String name) { + this.name = name; + } + + @NonNull + public String name() { + return name; + } + + @Nullable + public T get(@NonNull RenderProps context) { + return context.get(this); + } + + @NonNull + public T get(@NonNull RenderProps context, @NonNull T defValue) { + return context.get(this, defValue); + } + + @NonNull + public T require(@NonNull RenderProps context) { + final T t = get(context); + if (t == null) { + throw new NullPointerException(); + } + return t; + } + + public void set(@NonNull RenderProps context, @Nullable T value) { + context.set(this, value); + } + + public void clear(@NonNull RenderProps context) { + context.clear(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Prop prop = (Prop) o; + + return name.equals(prop.name); + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public String toString() { + return "Prop{" + + "name='" + name + '\'' + + '}'; + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/RenderProps.java b/markwon/src/main/java/ru/noties/markwon/RenderProps.java new file mode 100644 index 00000000..b939acc4 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/RenderProps.java @@ -0,0 +1,20 @@ +package ru.noties.markwon; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +/** + * @since 3.0.0 + */ +public interface RenderProps { + + @Nullable + T get(@NonNull Prop prop); + + @NonNull + T get(@NonNull Prop prop, @NonNull T defValue); + + void set(@NonNull Prop prop, @Nullable T value); + + void clear(@NonNull Prop prop); +} diff --git a/markwon/src/main/java/ru/noties/markwon/RenderPropsImpl.java b/markwon/src/main/java/ru/noties/markwon/RenderPropsImpl.java new file mode 100644 index 00000000..81605ee9 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/RenderPropsImpl.java @@ -0,0 +1,51 @@ +package ru.noties.markwon; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.HashMap; +import java.util.Map; + +/** + * @since 3.0.0 + */ +public class RenderPropsImpl implements RenderProps { + + private final Map values = new HashMap<>(3); + + @Nullable + @Override + public T get(@NonNull Prop prop) { + //noinspection unchecked + return (T) values.get(prop); + } + + @NonNull + @Override + public T get(@NonNull Prop prop, @NonNull T defValue) { + Object value = values.get(prop); + if (value != null) { + //noinspection unchecked + return (T) value; + } + return defValue; + } + + @Override + public void set(@NonNull Prop prop, @Nullable T value) { + if (value == null) { + values.remove(prop); + } else { + values.put(prop, value); + } + } + + @Override + public void clear(@NonNull Prop prop) { + values.remove(prop); + } + + public void clearAll() { + values.clear(); + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/SpanFactory.java b/markwon/src/main/java/ru/noties/markwon/SpanFactory.java new file mode 100644 index 00000000..62780776 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/SpanFactory.java @@ -0,0 +1,15 @@ +package ru.noties.markwon; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +/** + * @since 3.0.0 + */ +public interface SpanFactory { + + @Nullable + Object getSpans( + @NonNull MarkwonConfiguration configuration, + @NonNull RenderProps context); +} diff --git a/markwon/src/main/java/ru/noties/markwon/core/CorePlugin.java b/markwon/src/main/java/ru/noties/markwon/core/CorePlugin.java index c6e6c1bb..4d1ff29b 100644 --- a/markwon/src/main/java/ru/noties/markwon/core/CorePlugin.java +++ b/markwon/src/main/java/ru/noties/markwon/core/CorePlugin.java @@ -1,6 +1,7 @@ package ru.noties.markwon.core; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.widget.TextView; import org.commonmark.node.BlockQuote; @@ -12,7 +13,9 @@ import org.commonmark.node.HardLineBreak; import org.commonmark.node.Heading; import org.commonmark.node.IndentedCodeBlock; import org.commonmark.node.Link; +import org.commonmark.node.ListBlock; 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; @@ -21,23 +24,23 @@ import org.commonmark.node.Text; import org.commonmark.node.ThematicBreak; import ru.noties.markwon.AbstractMarkwonPlugin; +import ru.noties.markwon.MarkwonConfiguration; +import ru.noties.markwon.MarkwonSpansFactory; import ru.noties.markwon.MarkwonVisitor; -import ru.noties.markwon.core.visitor.BlockQuoteNodeVisitor; -import ru.noties.markwon.core.visitor.CodeBlockNodeVisitor; -import ru.noties.markwon.core.visitor.CodeNodeVisitor; -import ru.noties.markwon.core.visitor.EmphasisNodeVisitor; -import ru.noties.markwon.core.visitor.HardLineBreakNodeVisitor; -import ru.noties.markwon.core.visitor.HeadingNodeVisitor; -import ru.noties.markwon.core.visitor.LinkNodeVisitor; -import ru.noties.markwon.core.visitor.ListBlockNodeVisitor; -import ru.noties.markwon.core.visitor.ListItemNodeVisitor; -import ru.noties.markwon.core.visitor.ParagraphNodeVisitor; -import ru.noties.markwon.core.visitor.SoftLineBreakNodeVisitor; -import ru.noties.markwon.core.visitor.StrongEmphasisNodeVisitor; -import ru.noties.markwon.core.visitor.TextNodeVisitor; -import ru.noties.markwon.core.visitor.ThematicBreakNodeVisitor; +import ru.noties.markwon.core.factory.BlockQuoteSpanFactory; +import ru.noties.markwon.core.factory.CodeBlockSpanFactory; +import ru.noties.markwon.core.factory.CodeSpanFactory; +import ru.noties.markwon.core.factory.EmphasisSpanFactory; +import ru.noties.markwon.core.factory.HeadingSpanFactory; +import ru.noties.markwon.core.factory.LinkSpanFactory; +import ru.noties.markwon.core.factory.ListItemSpanFactory; +import ru.noties.markwon.core.factory.StrongEmphasisSpanFactory; +import ru.noties.markwon.core.factory.ThematicBreakSpanFactory; import ru.noties.markwon.core.spans.OrderedListItemSpan; +/** + * @since 3.0.0 + */ public class CorePlugin extends AbstractMarkwonPlugin { @NonNull @@ -50,8 +53,12 @@ public class CorePlugin extends AbstractMarkwonPlugin { return new CorePlugin(softBreakAddsNewLine); } + // todo: can we make it configurable somewhere else? + // even possibility of options that require creating factory method for each configuration... meh private final boolean softBreakAddsNewLine; + // todo: test that visitors are registered for all expected nodes + protected CorePlugin(boolean softBreakAddsNewLine) { this.softBreakAddsNewLine = softBreakAddsNewLine; } @@ -70,78 +77,315 @@ public class CorePlugin extends AbstractMarkwonPlugin { listItem(builder); thematicBreak(builder); heading(builder); - softLineBreak(builder); + softLineBreak(builder, softBreakAddsNewLine); hardLineBreak(builder); paragraph(builder); link(builder); } + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + + // reuse this one for both code-blocks (indent & fenced) + final CodeBlockSpanFactory codeBlockSpanFactory = new CodeBlockSpanFactory(); + + builder + .setFactory(StrongEmphasis.class, new StrongEmphasisSpanFactory()) + .setFactory(Emphasis.class, new EmphasisSpanFactory()) + .setFactory(BlockQuote.class, new BlockQuoteSpanFactory()) + .setFactory(Code.class, new CodeSpanFactory()) + .setFactory(FencedCodeBlock.class, codeBlockSpanFactory) + .setFactory(IndentedCodeBlock.class, codeBlockSpanFactory) + .setFactory(ListItem.class, new ListItemSpanFactory()) + .setFactory(Heading.class, new HeadingSpanFactory()) + .setFactory(Link.class, new LinkSpanFactory()) + .setFactory(ThematicBreak.class, new ThematicBreakSpanFactory()); + } + @Override public void beforeSetText(@NonNull TextView textView, @NonNull CharSequence markdown) { OrderedListItemSpan.measure(textView, markdown); } - protected void text(@NonNull MarkwonVisitor.Builder builder) { - builder.on(Text.class, new TextNodeVisitor()); + private static void text(@NonNull MarkwonVisitor.Builder builder) { + builder.on(Text.class, new MarkwonVisitor.NodeVisitor() { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull Text text) { + visitor.builder().append(text.getLiteral()); + } + }); } - protected void strongEmphasis(@NonNull MarkwonVisitor.Builder builder) { - builder.on(StrongEmphasis.class, new StrongEmphasisNodeVisitor()); + private static void strongEmphasis(@NonNull MarkwonVisitor.Builder builder) { + builder.on(StrongEmphasis.class, new MarkwonVisitor.NodeVisitor() { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull StrongEmphasis strongEmphasis) { + final int length = visitor.length(); + visitor.visitChildren(strongEmphasis); + visitor.setSpansForNode(strongEmphasis, length); + } + }); } - protected void emphasis(@NonNull MarkwonVisitor.Builder builder) { - builder.on(Emphasis.class, new EmphasisNodeVisitor()); + private static void emphasis(@NonNull MarkwonVisitor.Builder builder) { + builder.on(Emphasis.class, new MarkwonVisitor.NodeVisitor() { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull Emphasis emphasis) { + final int length = visitor.length(); + visitor.visitChildren(emphasis); + visitor.setSpansForNode(emphasis, length); + } + }); } - protected void blockQuote(@NonNull MarkwonVisitor.Builder builder) { - builder.on(BlockQuote.class, new BlockQuoteNodeVisitor()); + private static void blockQuote(@NonNull MarkwonVisitor.Builder builder) { + builder.on(BlockQuote.class, new MarkwonVisitor.NodeVisitor
() { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull BlockQuote blockQuote) { + final int length = visitor.length(); + visitor.visitChildren(blockQuote); + visitor.setSpansForNode(blockQuote, length); + } + }); } - protected void code(@NonNull MarkwonVisitor.Builder builder) { - builder.on(Code.class, new CodeNodeVisitor()); + private static void code(@NonNull MarkwonVisitor.Builder builder) { + builder.on(Code.class, new MarkwonVisitor.NodeVisitor() { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull Code code) { + + final int length = visitor.length(); + + // NB, in order to provide a _padding_ feeling code is wrapped inside two unbreakable spaces + // unfortunately we cannot use this for multiline code as we cannot control where a new line break will be inserted + visitor.builder() + .append('\u00a0') + .append(code.getLiteral()) + .append('\u00a0'); + + visitor.setSpansForNode(code, length); + } + }); } - protected void fencedCodeBlock(@NonNull MarkwonVisitor.Builder builder) { - builder.on(FencedCodeBlock.class, new CodeBlockNodeVisitor.Fenced()); + private static void fencedCodeBlock(@NonNull MarkwonVisitor.Builder builder) { + builder.on(FencedCodeBlock.class, new MarkwonVisitor.NodeVisitor() { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull FencedCodeBlock fencedCodeBlock) { + visitCodeBlock(visitor, fencedCodeBlock.getInfo(), fencedCodeBlock.getLiteral(), fencedCodeBlock); + } + }); } - protected void indentedCodeBlock(@NonNull MarkwonVisitor.Builder builder) { - builder.on(IndentedCodeBlock.class, new CodeBlockNodeVisitor.Indented()); + private static void indentedCodeBlock(@NonNull MarkwonVisitor.Builder builder) { + builder.on(IndentedCodeBlock.class, new MarkwonVisitor.NodeVisitor() { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull IndentedCodeBlock indentedCodeBlock) { + visitCodeBlock(visitor, null, indentedCodeBlock.getLiteral(), indentedCodeBlock); + } + }); } - protected void bulletList(@NonNull MarkwonVisitor.Builder builder) { - builder.on(BulletList.class, new ListBlockNodeVisitor()); + private static void visitCodeBlock( + @NonNull MarkwonVisitor visitor, + @Nullable String info, + @NonNull String code, + @NonNull Node node) { + + visitor.ensureNewLine(); + + final int length = visitor.length(); + + visitor.builder() + .append('\u00a0').append('\n') + .append(visitor.configuration().syntaxHighlight().highlight(info, code)); + + visitor.ensureNewLine(); + + visitor.builder().append('\u00a0'); + + visitor.setSpansForNode(node, length); + + if (visitor.hasNext(node)) { + visitor.ensureNewLine(); + visitor.forceNewLine(); + } } - protected void orderedList(@NonNull MarkwonVisitor.Builder builder) { - builder.on(OrderedList.class, new ListBlockNodeVisitor()); + private static void bulletList(@NonNull MarkwonVisitor.Builder builder) { + builder.on(BulletList.class, new SimpleBlockNodeVisitor()); } - protected void listItem(@NonNull MarkwonVisitor.Builder builder) { - builder.on(ListItem.class, new ListItemNodeVisitor()); + private static void orderedList(@NonNull MarkwonVisitor.Builder builder) { + builder.on(OrderedList.class, new SimpleBlockNodeVisitor()); } - protected void thematicBreak(@NonNull MarkwonVisitor.Builder builder) { - builder.on(ThematicBreak.class, new ThematicBreakNodeVisitor()); + private static void listItem(@NonNull MarkwonVisitor.Builder builder) { + builder.on(ListItem.class, new MarkwonVisitor.NodeVisitor() { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull ListItem listItem) { + + final int length = visitor.length(); + + final Node parent = listItem.getParent(); + if (parent instanceof OrderedList) { + + final int start = ((OrderedList) parent).getStartNumber(); + + CoreProps.LIST_ITEM_TYPE.set(visitor.renderProps(), CoreProps.ListItemType.ORDERED); + CoreProps.ORDERED_LIST_ITEM_NUMBER.set(visitor.renderProps(), start); + + // after we have visited the children increment start number + final OrderedList orderedList = (OrderedList) parent; + orderedList.setStartNumber(orderedList.getStartNumber() + 1); + + } else { + CoreProps.LIST_ITEM_TYPE.set(visitor.renderProps(), CoreProps.ListItemType.BULLET); + CoreProps.BULLET_LIST_ITEM_LEVEL.set(visitor.renderProps(), listLevel(listItem)); + } + + visitor.visitChildren(listItem); + visitor.setSpansForNode(listItem, length); + + if (visitor.hasNext(listItem)) { + visitor.ensureNewLine(); + } + } + }); } - protected void heading(@NonNull MarkwonVisitor.Builder builder) { - builder.on(Heading.class, new HeadingNodeVisitor()); + private static int listLevel(@NonNull Node node) { + int level = 0; + Node parent = node.getParent(); + while (parent != null) { + if (parent instanceof ListItem) { + level += 1; + } + parent = parent.getParent(); + } + return level; } - protected void softLineBreak(@NonNull MarkwonVisitor.Builder builder) { - builder.on(SoftLineBreak.class, new SoftLineBreakNodeVisitor(softBreakAddsNewLine)); + private static void thematicBreak(@NonNull MarkwonVisitor.Builder builder) { + builder.on(ThematicBreak.class, new MarkwonVisitor.NodeVisitor() { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull ThematicBreak thematicBreak) { + + visitor.ensureNewLine(); + + final int length = visitor.length(); + + // without space it won't render + visitor.builder().append('\u00a0'); + + visitor.setSpansForNode(thematicBreak, length); + + if (visitor.hasNext(thematicBreak)) { + visitor.ensureNewLine(); + visitor.forceNewLine(); + } + } + }); } - protected void hardLineBreak(@NonNull MarkwonVisitor.Builder builder) { - builder.on(HardLineBreak.class, new HardLineBreakNodeVisitor()); + private static void heading(@NonNull MarkwonVisitor.Builder builder) { + builder.on(Heading.class, new MarkwonVisitor.NodeVisitor() { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull Heading heading) { + + visitor.ensureNewLine(); + + final int length = visitor.length(); + visitor.visitChildren(heading); + + CoreProps.HEADING_LEVEL.set(visitor.renderProps(), heading.getLevel()); + + visitor.setSpansForNode(heading, length); + + if (visitor.hasNext(heading)) { + visitor.ensureNewLine(); + visitor.forceNewLine(); + } + } + }); } - protected void paragraph(@NonNull MarkwonVisitor.Builder builder) { - builder.on(Paragraph.class, new ParagraphNodeVisitor()); + private static void softLineBreak(@NonNull MarkwonVisitor.Builder builder, final boolean softBreakAddsNewLine) { + builder.on(SoftLineBreak.class, new MarkwonVisitor.NodeVisitor() { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull SoftLineBreak softLineBreak) { + if (softBreakAddsNewLine) { + visitor.ensureNewLine(); + } else { + visitor.builder().append(' '); + } + } + }); } - protected void link(@NonNull MarkwonVisitor.Builder builder) { - builder.on(Link.class, new LinkNodeVisitor()); + private static void hardLineBreak(@NonNull MarkwonVisitor.Builder builder) { + builder.on(HardLineBreak.class, new MarkwonVisitor.NodeVisitor() { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull HardLineBreak hardLineBreak) { + visitor.ensureNewLine(); + } + }); + } + + private static void paragraph(@NonNull MarkwonVisitor.Builder builder) { + builder.on(Paragraph.class, new MarkwonVisitor.NodeVisitor() { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull Paragraph paragraph) { + + final boolean inTightList = isInTightList(paragraph); + + if (!inTightList) { + visitor.ensureNewLine(); + } + + final int length = visitor.length(); + visitor.visitChildren(paragraph); + + CoreProps.PARAGRAPH_IS_IN_TIGHT_LIST.set(visitor.renderProps(), inTightList); + + // @since 1.1.1 apply paragraph span + visitor.setSpansForNodeOptional(paragraph, length); + + if (!inTightList && visitor.hasNext(paragraph)) { + visitor.ensureNewLine(); + visitor.forceNewLine(); + } + } + }); + } + + private static boolean isInTightList(@NonNull Paragraph paragraph) { + final Node parent = paragraph.getParent(); + if (parent != null) { + final Node gramps = parent.getParent(); + if (gramps instanceof ListBlock) { + ListBlock list = (ListBlock) gramps; + return list.isTight(); + } + } + return false; + } + + private static void link(@NonNull MarkwonVisitor.Builder builder) { + builder.on(Link.class, new MarkwonVisitor.NodeVisitor() { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull Link link) { + + final int length = visitor.length(); + visitor.visitChildren(link); + + final MarkwonConfiguration configuration = visitor.configuration(); + final String destination = configuration.urlProcessor().process(link.getDestination()); + + CoreProps.LINK_DESTINATION.set(visitor.renderProps(), destination); + + visitor.setSpansForNode(link, length); + } + }); } } diff --git a/markwon/src/main/java/ru/noties/markwon/core/CoreProps.java b/markwon/src/main/java/ru/noties/markwon/core/CoreProps.java new file mode 100644 index 00000000..158c5802 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/core/CoreProps.java @@ -0,0 +1,26 @@ +package ru.noties.markwon.core; + +import ru.noties.markwon.Prop; + +public abstract class CoreProps { + + public static final Prop LIST_ITEM_TYPE = Prop.of("list-item-type"); + + public static final Prop BULLET_LIST_ITEM_LEVEL = Prop.of("bullet-list-item-level"); + + public static final Prop ORDERED_LIST_ITEM_NUMBER = Prop.of("ordered-list-item-number"); + + public static final Prop HEADING_LEVEL = Prop.of("heading-level"); + + public static final Prop LINK_DESTINATION = Prop.of("link-destination"); + + public static final Prop PARAGRAPH_IS_IN_TIGHT_LIST = Prop.of("paragraph-is-in-tight-list"); + + public enum ListItemType { + BULLET, + ORDERED + } + + private CoreProps() { + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/core/MarkwonSpannableFactory.java b/markwon/src/main/java/ru/noties/markwon/core/MarkwonSpannableFactory.java index ccdb8a4c..1e440591 100644 --- a/markwon/src/main/java/ru/noties/markwon/core/MarkwonSpannableFactory.java +++ b/markwon/src/main/java/ru/noties/markwon/core/MarkwonSpannableFactory.java @@ -10,6 +10,7 @@ import ru.noties.markwon.core.spans.LinkSpan; * * @since 1.1.0 */ +@Deprecated public interface MarkwonSpannableFactory { @Nullable diff --git a/markwon/src/main/java/ru/noties/markwon/core/MarkwonSpannableFactoryDef.java b/markwon/src/main/java/ru/noties/markwon/core/MarkwonSpannableFactoryDef.java index 8b0a6461..5765c7b4 100644 --- a/markwon/src/main/java/ru/noties/markwon/core/MarkwonSpannableFactoryDef.java +++ b/markwon/src/main/java/ru/noties/markwon/core/MarkwonSpannableFactoryDef.java @@ -16,6 +16,7 @@ import ru.noties.markwon.core.spans.ThematicBreakSpan; /** * @since 1.1.0 */ +@Deprecated public class MarkwonSpannableFactoryDef implements MarkwonSpannableFactory { @NonNull diff --git a/markwon/src/main/java/ru/noties/markwon/core/visitor/ListBlockNodeVisitor.java b/markwon/src/main/java/ru/noties/markwon/core/SimpleBlockNodeVisitor.java similarity index 52% rename from markwon/src/main/java/ru/noties/markwon/core/visitor/ListBlockNodeVisitor.java rename to markwon/src/main/java/ru/noties/markwon/core/SimpleBlockNodeVisitor.java index f06d324b..1cc2fa02 100644 --- a/markwon/src/main/java/ru/noties/markwon/core/visitor/ListBlockNodeVisitor.java +++ b/markwon/src/main/java/ru/noties/markwon/core/SimpleBlockNodeVisitor.java @@ -1,4 +1,4 @@ -package ru.noties.markwon.core.visitor; +package ru.noties.markwon.core; import android.support.annotation.NonNull; @@ -6,7 +6,14 @@ import org.commonmark.node.Node; import ru.noties.markwon.MarkwonVisitor; -public class ListBlockNodeVisitor implements MarkwonVisitor.NodeVisitor { +/** + * A {@link ru.noties.markwon.MarkwonVisitor.NodeVisitor} that ensures that a markdown + * block starts with a new line, all children are visited and if further content available + * ensures a new line after self. Does not render any spans + * + * @since 3.0.0 + */ +public class SimpleBlockNodeVisitor implements MarkwonVisitor.NodeVisitor { @Override public void visit(@NonNull MarkwonVisitor visitor, @NonNull Node node) { diff --git a/markwon/src/main/java/ru/noties/markwon/core/factory/BlockQuoteSpanFactory.java b/markwon/src/main/java/ru/noties/markwon/core/factory/BlockQuoteSpanFactory.java new file mode 100644 index 00000000..437c0028 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/core/factory/BlockQuoteSpanFactory.java @@ -0,0 +1,17 @@ +package ru.noties.markwon.core.factory; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import ru.noties.markwon.MarkwonConfiguration; +import ru.noties.markwon.RenderProps; +import ru.noties.markwon.SpanFactory; +import ru.noties.markwon.core.spans.BlockQuoteSpan; + +public class BlockQuoteSpanFactory implements SpanFactory { + @Nullable + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps context) { + return new BlockQuoteSpan(configuration.theme()); + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/core/factory/CodeBlockSpanFactory.java b/markwon/src/main/java/ru/noties/markwon/core/factory/CodeBlockSpanFactory.java new file mode 100644 index 00000000..472bf620 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/core/factory/CodeBlockSpanFactory.java @@ -0,0 +1,17 @@ +package ru.noties.markwon.core.factory; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import ru.noties.markwon.MarkwonConfiguration; +import ru.noties.markwon.RenderProps; +import ru.noties.markwon.SpanFactory; +import ru.noties.markwon.core.spans.CodeSpan; + +public class CodeBlockSpanFactory implements SpanFactory { + @Nullable + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps context) { + return new CodeSpan(configuration.theme(), true); + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/core/factory/CodeSpanFactory.java b/markwon/src/main/java/ru/noties/markwon/core/factory/CodeSpanFactory.java new file mode 100644 index 00000000..6850ff44 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/core/factory/CodeSpanFactory.java @@ -0,0 +1,17 @@ +package ru.noties.markwon.core.factory; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import ru.noties.markwon.MarkwonConfiguration; +import ru.noties.markwon.RenderProps; +import ru.noties.markwon.SpanFactory; +import ru.noties.markwon.core.spans.CodeSpan; + +public class CodeSpanFactory implements SpanFactory { + @Nullable + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps context) { + return new CodeSpan(configuration.theme(), false); + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/core/factory/EmphasisSpanFactory.java b/markwon/src/main/java/ru/noties/markwon/core/factory/EmphasisSpanFactory.java new file mode 100644 index 00000000..eb49a906 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/core/factory/EmphasisSpanFactory.java @@ -0,0 +1,17 @@ +package ru.noties.markwon.core.factory; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import ru.noties.markwon.MarkwonConfiguration; +import ru.noties.markwon.RenderProps; +import ru.noties.markwon.SpanFactory; +import ru.noties.markwon.core.spans.EmphasisSpan; + +public class EmphasisSpanFactory implements SpanFactory { + @Nullable + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps context) { + return new EmphasisSpan(); + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/core/factory/HeadingSpanFactory.java b/markwon/src/main/java/ru/noties/markwon/core/factory/HeadingSpanFactory.java new file mode 100644 index 00000000..e0b1b517 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/core/factory/HeadingSpanFactory.java @@ -0,0 +1,21 @@ +package ru.noties.markwon.core.factory; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import ru.noties.markwon.MarkwonConfiguration; +import ru.noties.markwon.RenderProps; +import ru.noties.markwon.SpanFactory; +import ru.noties.markwon.core.CoreProps; +import ru.noties.markwon.core.spans.HeadingSpan; + +public class HeadingSpanFactory implements SpanFactory { + @Nullable + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps context) { + return new HeadingSpan( + configuration.theme(), + CoreProps.HEADING_LEVEL.require(context) + ); + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/core/factory/LinkSpanFactory.java b/markwon/src/main/java/ru/noties/markwon/core/factory/LinkSpanFactory.java new file mode 100644 index 00000000..e0a39dd2 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/core/factory/LinkSpanFactory.java @@ -0,0 +1,22 @@ +package ru.noties.markwon.core.factory; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import ru.noties.markwon.MarkwonConfiguration; +import ru.noties.markwon.RenderProps; +import ru.noties.markwon.SpanFactory; +import ru.noties.markwon.core.CoreProps; +import ru.noties.markwon.core.spans.LinkSpan; + +public class LinkSpanFactory implements SpanFactory { + @Nullable + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps context) { + return new LinkSpan( + configuration.theme(), + CoreProps.LINK_DESTINATION.require(context), + configuration.linkResolver() + ); + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/core/factory/ListItemSpanFactory.java b/markwon/src/main/java/ru/noties/markwon/core/factory/ListItemSpanFactory.java new file mode 100644 index 00000000..87bd4e4e --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/core/factory/ListItemSpanFactory.java @@ -0,0 +1,43 @@ +package ru.noties.markwon.core.factory; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import ru.noties.markwon.MarkwonConfiguration; +import ru.noties.markwon.RenderProps; +import ru.noties.markwon.SpanFactory; +import ru.noties.markwon.core.CoreProps; +import ru.noties.markwon.core.spans.BulletListItemSpan; +import ru.noties.markwon.core.spans.OrderedListItemSpan; + +public class ListItemSpanFactory implements SpanFactory { + + @Nullable + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps context) { + + // type of list item + // bullet : level + // ordered: number + final Object spans; + + if (CoreProps.ListItemType.BULLET == CoreProps.LIST_ITEM_TYPE.require(context)) { + spans = new BulletListItemSpan( + configuration.theme(), + CoreProps.BULLET_LIST_ITEM_LEVEL.require(context) + ); + } else { + + // todo| in order to provide real RTL experience there must be a way to provide this string + final String number = String.valueOf(CoreProps.ORDERED_LIST_ITEM_NUMBER.require(context)) + + "." + '\u00a0'; + + spans = new OrderedListItemSpan( + configuration.theme(), + number + ); + } + + return spans; + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/core/factory/StrongEmphasisSpanFactory.java b/markwon/src/main/java/ru/noties/markwon/core/factory/StrongEmphasisSpanFactory.java new file mode 100644 index 00000000..e1905e05 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/core/factory/StrongEmphasisSpanFactory.java @@ -0,0 +1,17 @@ +package ru.noties.markwon.core.factory; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import ru.noties.markwon.MarkwonConfiguration; +import ru.noties.markwon.RenderProps; +import ru.noties.markwon.SpanFactory; +import ru.noties.markwon.core.spans.StrongEmphasisSpan; + +public class StrongEmphasisSpanFactory implements SpanFactory { + @Nullable + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps context) { + return new StrongEmphasisSpan(); + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/core/factory/ThematicBreakSpanFactory.java b/markwon/src/main/java/ru/noties/markwon/core/factory/ThematicBreakSpanFactory.java new file mode 100644 index 00000000..82c1dea8 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/core/factory/ThematicBreakSpanFactory.java @@ -0,0 +1,17 @@ +package ru.noties.markwon.core.factory; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import ru.noties.markwon.MarkwonConfiguration; +import ru.noties.markwon.RenderProps; +import ru.noties.markwon.SpanFactory; +import ru.noties.markwon.core.spans.ThematicBreakSpan; + +public class ThematicBreakSpanFactory implements SpanFactory { + @Nullable + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps context) { + return new ThematicBreakSpan(configuration.theme()); + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/core/visitor/BlockQuoteNodeVisitor.java b/markwon/src/main/java/ru/noties/markwon/core/visitor/BlockQuoteNodeVisitor.java deleted file mode 100644 index 1c236c42..00000000 --- a/markwon/src/main/java/ru/noties/markwon/core/visitor/BlockQuoteNodeVisitor.java +++ /dev/null @@ -1,25 +0,0 @@ -package ru.noties.markwon.core.visitor; - -import android.support.annotation.NonNull; - -import org.commonmark.node.BlockQuote; - -import ru.noties.markwon.MarkwonVisitor; - -public class BlockQuoteNodeVisitor implements MarkwonVisitor.NodeVisitor
{ - @Override - public void visit(@NonNull MarkwonVisitor visitor, @NonNull BlockQuote blockQuote) { - - visitor.ensureNewLine(); - - final int length = visitor.length(); - - visitor.visitChildren(blockQuote); - visitor.setSpans(length, visitor.factory().blockQuote(visitor.theme())); - - if (visitor.hasNext(blockQuote)) { - visitor.ensureNewLine(); - visitor.forceNewLine(); - } - } -} diff --git a/markwon/src/main/java/ru/noties/markwon/core/visitor/CodeBlockNodeVisitor.java b/markwon/src/main/java/ru/noties/markwon/core/visitor/CodeBlockNodeVisitor.java deleted file mode 100644 index 491da0e8..00000000 --- a/markwon/src/main/java/ru/noties/markwon/core/visitor/CodeBlockNodeVisitor.java +++ /dev/null @@ -1,56 +0,0 @@ -package ru.noties.markwon.core.visitor; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import org.commonmark.node.FencedCodeBlock; -import org.commonmark.node.IndentedCodeBlock; -import org.commonmark.node.Node; - -import ru.noties.markwon.MarkwonVisitor; - -public abstract class CodeBlockNodeVisitor { - - public static class Fenced implements MarkwonVisitor.NodeVisitor { - - @Override - public void visit(@NonNull MarkwonVisitor visitor, @NonNull FencedCodeBlock fencedCodeBlock) { - visitCodeBlock(visitor, fencedCodeBlock.getInfo(), fencedCodeBlock.getLiteral(), fencedCodeBlock); - } - } - - public static class Indented implements MarkwonVisitor.NodeVisitor { - - @Override - public void visit(@NonNull MarkwonVisitor visitor, @NonNull IndentedCodeBlock indentedCodeBlock) { - visitCodeBlock(visitor, null, indentedCodeBlock.getLiteral(), indentedCodeBlock); - } - } - - - public static void visitCodeBlock( - @NonNull MarkwonVisitor visitor, - @Nullable String info, - @NonNull String code, - @NonNull Node node) { - - visitor.ensureNewLine(); - - final int length = visitor.length(); - - visitor.builder() - .append('\u00a0').append('\n') - .append(visitor.configuration().syntaxHighlight().highlight(info, code)); - - visitor.ensureNewLine(); - - visitor.builder().append('\u00a0'); - - visitor.setSpans(length, visitor.factory().code(visitor.theme(), true)); - - if (visitor.hasNext(node)) { - visitor.ensureNewLine(); - visitor.forceNewLine(); - } - } -} diff --git a/markwon/src/main/java/ru/noties/markwon/core/visitor/CodeNodeVisitor.java b/markwon/src/main/java/ru/noties/markwon/core/visitor/CodeNodeVisitor.java deleted file mode 100644 index fb893839..00000000 --- a/markwon/src/main/java/ru/noties/markwon/core/visitor/CodeNodeVisitor.java +++ /dev/null @@ -1,24 +0,0 @@ -package ru.noties.markwon.core.visitor; - -import android.support.annotation.NonNull; - -import org.commonmark.node.Code; - -import ru.noties.markwon.MarkwonVisitor; - -public class CodeNodeVisitor implements MarkwonVisitor.NodeVisitor { - @Override - public void visit(@NonNull MarkwonVisitor visitor, @NonNull Code code) { - - final int length = visitor.length(); - - // NB, in order to provide a _padding_ feeling code is wrapped inside two unbreakable spaces - // unfortunately we cannot use this for multiline code as we cannot control where a new line break will be inserted - visitor.builder() - .append('\u00a0') - .append(code.getLiteral()) - .append('\u00a0'); - - visitor.setSpans(length, visitor.factory().code(visitor.theme(), false)); - } -} diff --git a/markwon/src/main/java/ru/noties/markwon/core/visitor/EmphasisNodeVisitor.java b/markwon/src/main/java/ru/noties/markwon/core/visitor/EmphasisNodeVisitor.java deleted file mode 100644 index f4010b32..00000000 --- a/markwon/src/main/java/ru/noties/markwon/core/visitor/EmphasisNodeVisitor.java +++ /dev/null @@ -1,16 +0,0 @@ -package ru.noties.markwon.core.visitor; - -import android.support.annotation.NonNull; - -import org.commonmark.node.Emphasis; - -import ru.noties.markwon.MarkwonVisitor; - -public class EmphasisNodeVisitor implements MarkwonVisitor.NodeVisitor { - @Override - public void visit(@NonNull MarkwonVisitor visitor, @NonNull Emphasis emphasis) { - final int length = visitor.length(); - visitor.visitChildren(emphasis); - visitor.setSpans(length, visitor.factory().emphasis()); - } -} diff --git a/markwon/src/main/java/ru/noties/markwon/core/visitor/HardLineBreakNodeVisitor.java b/markwon/src/main/java/ru/noties/markwon/core/visitor/HardLineBreakNodeVisitor.java deleted file mode 100644 index 809638b7..00000000 --- a/markwon/src/main/java/ru/noties/markwon/core/visitor/HardLineBreakNodeVisitor.java +++ /dev/null @@ -1,14 +0,0 @@ -package ru.noties.markwon.core.visitor; - -import android.support.annotation.NonNull; - -import org.commonmark.node.HardLineBreak; - -import ru.noties.markwon.MarkwonVisitor; - -public class HardLineBreakNodeVisitor implements MarkwonVisitor.NodeVisitor { - @Override - public void visit(@NonNull MarkwonVisitor visitor, @NonNull HardLineBreak hardLineBreak) { - visitor.ensureNewLine(); - } -} diff --git a/markwon/src/main/java/ru/noties/markwon/core/visitor/HeadingNodeVisitor.java b/markwon/src/main/java/ru/noties/markwon/core/visitor/HeadingNodeVisitor.java deleted file mode 100644 index c82d51fe..00000000 --- a/markwon/src/main/java/ru/noties/markwon/core/visitor/HeadingNodeVisitor.java +++ /dev/null @@ -1,24 +0,0 @@ -package ru.noties.markwon.core.visitor; - -import android.support.annotation.NonNull; - -import org.commonmark.node.Heading; - -import ru.noties.markwon.MarkwonVisitor; - -public class HeadingNodeVisitor implements MarkwonVisitor.NodeVisitor { - @Override - public void visit(@NonNull MarkwonVisitor visitor, @NonNull Heading heading) { - - visitor.ensureNewLine(); - - final int length = visitor.length(); - visitor.visitChildren(heading); - visitor.setSpans(length, visitor.factory().heading(visitor.theme(), heading.getLevel())); - - if (visitor.hasNext(heading)) { - visitor.ensureNewLine(); - visitor.forceNewLine(); - } - } -} diff --git a/markwon/src/main/java/ru/noties/markwon/core/visitor/LinkNodeVisitor.java b/markwon/src/main/java/ru/noties/markwon/core/visitor/LinkNodeVisitor.java deleted file mode 100644 index cc4dfd6d..00000000 --- a/markwon/src/main/java/ru/noties/markwon/core/visitor/LinkNodeVisitor.java +++ /dev/null @@ -1,19 +0,0 @@ -package ru.noties.markwon.core.visitor; - -import android.support.annotation.NonNull; - -import org.commonmark.node.Link; - -import ru.noties.markwon.MarkwonConfiguration; -import ru.noties.markwon.MarkwonVisitor; - -public class LinkNodeVisitor implements MarkwonVisitor.NodeVisitor { - @Override - public void visit(@NonNull MarkwonVisitor visitor, @NonNull Link link) { - final int length = visitor.length(); - visitor.visitChildren(link); - final MarkwonConfiguration configuration = visitor.configuration(); - final String destination = configuration.urlProcessor().process(link.getDestination()); - visitor.setSpans(length, visitor.factory().link(visitor.theme(), destination, configuration.linkResolver())); - } -} diff --git a/markwon/src/main/java/ru/noties/markwon/core/visitor/ListItemNodeVisitor.java b/markwon/src/main/java/ru/noties/markwon/core/visitor/ListItemNodeVisitor.java deleted file mode 100644 index f362c3ba..00000000 --- a/markwon/src/main/java/ru/noties/markwon/core/visitor/ListItemNodeVisitor.java +++ /dev/null @@ -1,54 +0,0 @@ -package ru.noties.markwon.core.visitor; - -import android.support.annotation.NonNull; - -import org.commonmark.node.ListItem; -import org.commonmark.node.Node; -import org.commonmark.node.OrderedList; - -import ru.noties.markwon.MarkwonVisitor; - -public class ListItemNodeVisitor implements MarkwonVisitor.NodeVisitor { - - @Override - public void visit(@NonNull MarkwonVisitor visitor, @NonNull ListItem listItem) { - - final int length = visitor.length(); - - final Node parent = listItem.getParent(); - if (parent instanceof OrderedList) { - - final int start = ((OrderedList) parent).getStartNumber(); - - visitor.visitChildren(listItem); - visitor.setSpans(length, visitor.factory().orderedListItem(visitor.theme(), start)); - - - // after we have visited the children increment start number - final OrderedList orderedList = (OrderedList) parent; - orderedList.setStartNumber(orderedList.getStartNumber() + 1); - - } else { - - visitor.visitChildren(listItem); - visitor.setSpans(length, visitor.factory().bulletListItem(visitor.theme(), listLevel(listItem))); - - } - - if (visitor.hasNext(listItem)) { - visitor.ensureNewLine(); - } - } - - private static int listLevel(@NonNull Node node) { - int level = 0; - Node parent = node.getParent(); - while (parent != null) { - if (parent instanceof ListItem) { - level += 1; - } - parent = parent.getParent(); - } - return level; - } -} diff --git a/markwon/src/main/java/ru/noties/markwon/core/visitor/ParagraphNodeVisitor.java b/markwon/src/main/java/ru/noties/markwon/core/visitor/ParagraphNodeVisitor.java deleted file mode 100644 index b945c501..00000000 --- a/markwon/src/main/java/ru/noties/markwon/core/visitor/ParagraphNodeVisitor.java +++ /dev/null @@ -1,44 +0,0 @@ -package ru.noties.markwon.core.visitor; - -import android.support.annotation.NonNull; - -import org.commonmark.node.ListBlock; -import org.commonmark.node.Node; -import org.commonmark.node.Paragraph; - -import ru.noties.markwon.MarkwonVisitor; - -public class ParagraphNodeVisitor implements MarkwonVisitor.NodeVisitor { - @Override - public void visit(@NonNull MarkwonVisitor visitor, @NonNull Paragraph paragraph) { - - final boolean inTightList = isInTightList(paragraph); - - if (!inTightList) { - visitor.ensureNewLine(); - } - - final int length = visitor.length(); - visitor.visitChildren(paragraph); - - // @since 1.1.1 apply paragraph span - visitor.setSpans(length, visitor.factory().paragraph(inTightList)); - - if (!inTightList && visitor.hasNext(paragraph)) { - visitor.ensureNewLine(); - visitor.forceNewLine(); - } - } - - private static boolean isInTightList(@NonNull Paragraph paragraph) { - final Node parent = paragraph.getParent(); - if (parent != null) { - final Node gramps = parent.getParent(); - if (gramps instanceof ListBlock) { - ListBlock list = (ListBlock) gramps; - return list.isTight(); - } - } - return false; - } -} diff --git a/markwon/src/main/java/ru/noties/markwon/core/visitor/SoftLineBreakNodeVisitor.java b/markwon/src/main/java/ru/noties/markwon/core/visitor/SoftLineBreakNodeVisitor.java deleted file mode 100644 index 79880bb2..00000000 --- a/markwon/src/main/java/ru/noties/markwon/core/visitor/SoftLineBreakNodeVisitor.java +++ /dev/null @@ -1,25 +0,0 @@ -package ru.noties.markwon.core.visitor; - -import android.support.annotation.NonNull; - -import org.commonmark.node.SoftLineBreak; - -import ru.noties.markwon.MarkwonVisitor; - -public class SoftLineBreakNodeVisitor implements MarkwonVisitor.NodeVisitor { - - private final boolean softBreakAddsNewLine; - - public SoftLineBreakNodeVisitor(boolean softBreakAddsNewLine) { - this.softBreakAddsNewLine = softBreakAddsNewLine; - } - - @Override - public void visit(@NonNull MarkwonVisitor visitor, @NonNull SoftLineBreak softLineBreak) { - if (softBreakAddsNewLine) { - visitor.ensureNewLine(); - } else { - visitor.builder().append(' '); - } - } -} diff --git a/markwon/src/main/java/ru/noties/markwon/core/visitor/StrongEmphasisNodeVisitor.java b/markwon/src/main/java/ru/noties/markwon/core/visitor/StrongEmphasisNodeVisitor.java deleted file mode 100644 index ae3c701d..00000000 --- a/markwon/src/main/java/ru/noties/markwon/core/visitor/StrongEmphasisNodeVisitor.java +++ /dev/null @@ -1,16 +0,0 @@ -package ru.noties.markwon.core.visitor; - -import android.support.annotation.NonNull; - -import org.commonmark.node.StrongEmphasis; - -import ru.noties.markwon.MarkwonVisitor; - -public class StrongEmphasisNodeVisitor implements MarkwonVisitor.NodeVisitor { - @Override - public void visit(@NonNull MarkwonVisitor visitor, @NonNull StrongEmphasis strongEmphasis) { - final int length = visitor.length(); - visitor.visitChildren(strongEmphasis); - visitor.setSpans(length, visitor.factory().strongEmphasis()); - } -} diff --git a/markwon/src/main/java/ru/noties/markwon/core/visitor/TextNodeVisitor.java b/markwon/src/main/java/ru/noties/markwon/core/visitor/TextNodeVisitor.java deleted file mode 100644 index 17ebfbb0..00000000 --- a/markwon/src/main/java/ru/noties/markwon/core/visitor/TextNodeVisitor.java +++ /dev/null @@ -1,14 +0,0 @@ -package ru.noties.markwon.core.visitor; - -import android.support.annotation.NonNull; - -import org.commonmark.node.Text; - -import ru.noties.markwon.MarkwonVisitor; - -public class TextNodeVisitor implements MarkwonVisitor.NodeVisitor { - @Override - public void visit(@NonNull MarkwonVisitor visitor, @NonNull Text text) { - visitor.builder().append(text.getLiteral()); - } -} diff --git a/markwon/src/main/java/ru/noties/markwon/core/visitor/ThematicBreakNodeVisitor.java b/markwon/src/main/java/ru/noties/markwon/core/visitor/ThematicBreakNodeVisitor.java deleted file mode 100644 index 2bc2886a..00000000 --- a/markwon/src/main/java/ru/noties/markwon/core/visitor/ThematicBreakNodeVisitor.java +++ /dev/null @@ -1,27 +0,0 @@ -package ru.noties.markwon.core.visitor; - -import android.support.annotation.NonNull; - -import org.commonmark.node.ThematicBreak; - -import ru.noties.markwon.MarkwonVisitor; - -public class ThematicBreakNodeVisitor implements MarkwonVisitor.NodeVisitor { - @Override - public void visit(@NonNull MarkwonVisitor visitor, @NonNull ThematicBreak thematicBreak) { - - visitor.ensureNewLine(); - - final int length = visitor.length(); - - // without space it won't render - visitor.builder().append('\u00a0'); - - visitor.setSpans(length, visitor.factory().thematicBreak(visitor.theme())); - - if (visitor.hasNext(thematicBreak)) { - visitor.ensureNewLine(); - visitor.forceNewLine(); - } - } -} diff --git a/markwon/src/main/java/ru/noties/markwon/image/AsyncDrawableScheduler.java b/markwon/src/main/java/ru/noties/markwon/image/AsyncDrawableScheduler.java index 27348a76..0093abed 100644 --- a/markwon/src/main/java/ru/noties/markwon/image/AsyncDrawableScheduler.java +++ b/markwon/src/main/java/ru/noties/markwon/image/AsyncDrawableScheduler.java @@ -15,7 +15,6 @@ import java.util.Collections; import java.util.List; import ru.noties.markwon.renderer.R; -import ru.noties.markwon.core.spans.AsyncDrawableSpan; public abstract class AsyncDrawableScheduler { diff --git a/markwon/src/main/java/ru/noties/markwon/core/spans/AsyncDrawableSpan.java b/markwon/src/main/java/ru/noties/markwon/image/AsyncDrawableSpan.java similarity index 91% rename from markwon/src/main/java/ru/noties/markwon/core/spans/AsyncDrawableSpan.java rename to markwon/src/main/java/ru/noties/markwon/image/AsyncDrawableSpan.java index b06b2348..a268ef19 100644 --- a/markwon/src/main/java/ru/noties/markwon/core/spans/AsyncDrawableSpan.java +++ b/markwon/src/main/java/ru/noties/markwon/image/AsyncDrawableSpan.java @@ -1,4 +1,4 @@ -package ru.noties.markwon.core.spans; +package ru.noties.markwon.image; import android.graphics.Canvas; import android.graphics.Paint; @@ -13,7 +13,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import ru.noties.markwon.core.MarkwonTheme; -import ru.noties.markwon.image.AsyncDrawable; @SuppressWarnings("WeakerAccess") public class AsyncDrawableSpan extends ReplacementSpan { @@ -32,17 +31,6 @@ public class AsyncDrawableSpan extends ReplacementSpan { private final int alignment; private final boolean replacementTextIsLink; -// public AsyncDrawableSpan(@NonNull MarkwonTheme theme, @NonNull AsyncDrawable drawable) { -// this(theme, drawable, ALIGN_BOTTOM); -// } - -// public AsyncDrawableSpan( -// @NonNull MarkwonTheme theme, -// @NonNull AsyncDrawable drawable, -// @Alignment int alignment) { -// this(theme, drawable, alignment, false); -// } - public AsyncDrawableSpan( @NonNull MarkwonTheme theme, @NonNull AsyncDrawable drawable, @@ -150,6 +138,7 @@ public class AsyncDrawableSpan extends ReplacementSpan { } } + @NonNull public AsyncDrawable getDrawable() { return drawable; } diff --git a/markwon/src/main/java/ru/noties/markwon/image/ImageProps.java b/markwon/src/main/java/ru/noties/markwon/image/ImageProps.java new file mode 100644 index 00000000..7bb9bfd1 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/image/ImageProps.java @@ -0,0 +1,20 @@ +package ru.noties.markwon.image; + +import ru.noties.markwon.Prop; + +/** + * @since 3.0.0 + */ +public abstract class ImageProps { + + public static final Prop DESTINATION = Prop.of("image-destination"); + + public static final Prop REPLACEMENT_TEXT_IS_LINK = + Prop.of("image-replacement-text-is-link"); + + public static final Prop IMAGE_SIZE = Prop.of("image-size"); + + + private ImageProps() { + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/image/ImageSpanFactory.java b/markwon/src/main/java/ru/noties/markwon/image/ImageSpanFactory.java new file mode 100644 index 00000000..ba73d047 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/image/ImageSpanFactory.java @@ -0,0 +1,26 @@ +package ru.noties.markwon.image; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import ru.noties.markwon.MarkwonConfiguration; +import ru.noties.markwon.RenderProps; +import ru.noties.markwon.SpanFactory; + +public class ImageSpanFactory implements SpanFactory { + @Nullable + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps context) { + return new AsyncDrawableSpan( + configuration.theme(), + new AsyncDrawable( + ImageProps.DESTINATION.require(context), + configuration.asyncDrawableLoader(), + configuration.imageSizeResolver(), + ImageProps.IMAGE_SIZE.get(context) + ), + AsyncDrawableSpan.ALIGN_BOTTOM, + ImageProps.REPLACEMENT_TEXT_IS_LINK.get(context, false) + ); + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/image/ImagesPlugin.java b/markwon/src/main/java/ru/noties/markwon/image/ImagesPlugin.java index 2be7763f..7e91f288 100644 --- a/markwon/src/main/java/ru/noties/markwon/image/ImagesPlugin.java +++ b/markwon/src/main/java/ru/noties/markwon/image/ImagesPlugin.java @@ -2,7 +2,6 @@ package ru.noties.markwon.image; import android.content.Context; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.widget.TextView; import org.commonmark.node.Image; @@ -13,9 +12,9 @@ import java.util.Arrays; import ru.noties.markwon.AbstractMarkwonPlugin; import ru.noties.markwon.MarkwonConfiguration; +import ru.noties.markwon.MarkwonSpansFactory; import ru.noties.markwon.MarkwonVisitor; -import ru.noties.markwon.core.MarkwonTheme; -import ru.noties.markwon.core.spans.AsyncDrawableSpan; +import ru.noties.markwon.RenderProps; import ru.noties.markwon.image.data.DataUriSchemeHandler; import ru.noties.markwon.image.file.FileSchemeHandler; import ru.noties.markwon.image.network.NetworkSchemeHandler; @@ -35,7 +34,7 @@ public class ImagesPlugin extends AbstractMarkwonPlugin { private final Context context; private final boolean useAssets; - private ImagesPlugin(Context context, boolean useAssets) { + protected ImagesPlugin(Context context, boolean useAssets) { this.context = context; this.useAssets = useAssets; } @@ -58,6 +57,11 @@ public class ImagesPlugin extends AbstractMarkwonPlugin { .defaultMediaDecoder(ImageMediaDecoder.create(context.getResources())); } + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + builder.setFactory(Image.class, new ImageSpanFactory()); + } + @Override public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { builder.on(Image.class, new MarkwonVisitor.NodeVisitor() { @@ -77,18 +81,21 @@ public class ImagesPlugin extends AbstractMarkwonPlugin { final Node parent = image.getParent(); final boolean link = parent instanceof Link; + final String destination = configuration .urlProcessor() .process(image.getDestination()); - final Object spans = imageSpan( - visitor.theme(), - destination, - configuration.asyncDrawableLoader(), - configuration.imageSizeResolver(), - link); + final RenderProps context = visitor.renderProps(); - visitor.setSpans(length, spans); + // apply image properties + // Please note that we explicitly set IMAGE_SIZE to null as we do not clear + // properties after we applied span (we could though) + ImageProps.DESTINATION.set(context, destination); + ImageProps.REPLACEMENT_TEXT_IS_LINK.set(context, link); + ImageProps.IMAGE_SIZE.set(context, null); + + visitor.setSpansForNode(image, length); } }); } @@ -102,24 +109,4 @@ public class ImagesPlugin extends AbstractMarkwonPlugin { public void afterSetText(@NonNull TextView textView) { AsyncDrawableScheduler.schedule(textView); } - - @Nullable - protected Object imageSpan( - @NonNull MarkwonTheme theme, - @NonNull String destination, - @NonNull AsyncDrawableLoader loader, - @NonNull ImageSizeResolver imageSizeResolver, - boolean replacementTextIsLink) { - return new AsyncDrawableSpan( - theme, - new AsyncDrawable( - destination, - loader, - imageSizeResolver, - null - ), - AsyncDrawableSpan.ALIGN_BOTTOM, - replacementTextIsLink - ); - } } diff --git a/markwon/src/test/java/ru/noties/markwon/core/CoreTest.java b/markwon/src/test/java/ru/noties/markwon/core/CoreTest.java index e417267a..17ee51db 100644 --- a/markwon/src/test/java/ru/noties/markwon/core/CoreTest.java +++ b/markwon/src/test/java/ru/noties/markwon/core/CoreTest.java @@ -32,8 +32,8 @@ public class CoreTest { span("italic", text("bold italic")))); final Spanned spanned = (Spanned) Markwon.builder(RuntimeEnvironment.application) - .use(CorePlugin.create()) - .use(new AbstractMarkwonPlugin() { + .usePlugin(CorePlugin.create()) + .usePlugin(new AbstractMarkwonPlugin() { @Override public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { builder.factory(new MarkwonSpannableFactoryDef() { diff --git a/markwon/src/test/java/ru/noties/markwon/core/suite/BaseSuiteTest.java b/markwon/src/test/java/ru/noties/markwon/core/suite/BaseSuiteTest.java index 73c05bdf..65555352 100644 --- a/markwon/src/test/java/ru/noties/markwon/core/suite/BaseSuiteTest.java +++ b/markwon/src/test/java/ru/noties/markwon/core/suite/BaseSuiteTest.java @@ -33,8 +33,8 @@ abstract class BaseSuiteTest { @NonNull Markwon markwon() { return Markwon.builder(RuntimeEnvironment.application) - .use(CorePlugin.create(softBreakAddsNewLine())) - .use(new AbstractMarkwonPlugin() { + .usePlugin(CorePlugin.create(softBreakAddsNewLine())) + .usePlugin(new AbstractMarkwonPlugin() { @Override public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { builder.factory(new TestFactory(useParagraphs())); diff --git a/markwon/src/test/java/ru/noties/markwon/image/ImageTest.java b/markwon/src/test/java/ru/noties/markwon/image/ImageTest.java new file mode 100644 index 00000000..48cd3227 --- /dev/null +++ b/markwon/src/test/java/ru/noties/markwon/image/ImageTest.java @@ -0,0 +1,49 @@ +package ru.noties.markwon.image; + +import android.content.Context; +import android.support.annotation.NonNull; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import ru.noties.markwon.Markwon; +import ru.noties.markwon.core.CorePlugin; +import ru.noties.markwon.core.MarkwonTheme; +import ru.noties.markwon.test.TestSpan.Document; +import ru.noties.markwon.test.TestSpanMatcher; + +import static ru.noties.markwon.test.TestSpan.args; +import static ru.noties.markwon.test.TestSpan.document; +import static ru.noties.markwon.test.TestSpan.span; +import static ru.noties.markwon.test.TestSpan.text; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class ImageTest { + + @Test + public void test() { + + final String markdown = "![alt](#href)"; + + final Context context = RuntimeEnvironment.application; + final Markwon markwon = Markwon.builder(context) + .usePlugin(CorePlugin.create()) + .usePlugin(new ImagesPlugin(context, false) { + @Override + protected Object imageSpan(@NonNull MarkwonTheme theme, @NonNull String destination, @NonNull AsyncDrawableLoader loader, @NonNull ImageSizeResolver imageSizeResolver, boolean replacementTextIsLink) { + return span("image", args("href", destination)); + } + }) + .build(); + + final Document document = document( + span("image", args("href", "#href"), text("alt")) + ); + + TestSpanMatcher.matches(markwon.toMarkdown(markdown), document); + } +} diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/SpannableMarkdownVisitorTest.java b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/SpannableMarkdownVisitorTest.java deleted file mode 100644 index 07890959..00000000 --- a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/SpannableMarkdownVisitorTest.java +++ /dev/null @@ -1,83 +0,0 @@ -package ru.noties.markwon.renderer.visitor; - -import android.content.Context; -import android.support.annotation.NonNull; -import android.text.SpannableStringBuilder; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.ParameterizedRobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; - -import java.util.Arrays; -import java.util.Collection; - -import ru.noties.markwon.AbstractMarkwonPlugin; -import ru.noties.markwon.Markwon; -import ru.noties.markwon.MarkwonConfiguration; -import ru.noties.markwon.core.CorePlugin; -import ru.noties.markwon.image.ImagesPlugin; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; - -@RunWith(ParameterizedRobolectricTestRunner.class) -@Config(manifest = Config.NONE) -public class SpannableMarkdownVisitorTest { - - @ParameterizedRobolectricTestRunner.Parameters(name = "{0}") - public static Collection parameters() { - return TestDataReader.testFiles(); - } - - private final String file; - - public SpannableMarkdownVisitorTest(@NonNull String file) { - this.file = file; - } - - @Test - public void test() { - - final TestData data = TestDataReader.readTest(file); - - final Markwon markwon = markwon(data.config()); - - // okay we must thing about it... casting? - final SpannableStringBuilder stringBuilder = (SpannableStringBuilder) markwon.toMarkdown(data.input()); - - final TestValidator validator = TestValidator.create(file); - - int index = 0; - - for (TestNode testNode : data.output()) { - index = validator.validate(stringBuilder, index, testNode); - } - - // assert that the whole thing is processed - assertEquals("`" + stringBuilder + "`", stringBuilder.length(), index); - - final Object[] spans = stringBuilder.getSpans(0, stringBuilder.length(), Object.class); - final int length = spans != null - ? spans.length - : 0; - - assertEquals(Arrays.toString(spans), validator.processedSpanNodesCount(), length); - } - - - @NonNull - private Markwon markwon(@NonNull final TestConfig config) { - return Markwon.builder(RuntimeEnvironment.application) - .use(CorePlugin.create(config.hasOption(TestConfig.SOFT_BREAK_ADDS_NEW_LINE))) - .use(ImagesPlugin.create(mock(Context.class))) - .use(new AbstractMarkwonPlugin() { - @Override - public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { - builder.factory(new TestFactory(config.hasOption(TestConfig.USE_PARAGRAPHS))); - } - }) - .build(); - } -} \ No newline at end of file diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestConfig.java b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestConfig.java deleted file mode 100644 index 367f8a43..00000000 --- a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestConfig.java +++ /dev/null @@ -1,31 +0,0 @@ -package ru.noties.markwon.renderer.visitor; - -import android.support.annotation.NonNull; - -import java.util.Map; - -class TestConfig { - - static final String USE_PARAGRAPHS = "use-paragraphs"; -// static final String USE_HTML = "use-html"; - static final String SOFT_BREAK_ADDS_NEW_LINE = "soft-break-adds-new-line"; -// static final String HTML_ALLOW_NON_CLOSED_TAGS = "html-allow-non-closed-tags"; - - private final Map map; - - TestConfig(@NonNull Map map) { - this.map = map; - } - - boolean hasOption(@NonNull String option) { - final Boolean value = map.get(option); - return value != null && value; - } - - @Override - public String toString() { - return "TestConfig{" + - "map=" + map + - '}'; - } -} diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestData.java b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestData.java deleted file mode 100644 index 67807202..00000000 --- a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestData.java +++ /dev/null @@ -1,45 +0,0 @@ -package ru.noties.markwon.renderer.visitor; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.util.List; - -class TestData { - - private final String description; - private final String input; - private final TestConfig config; - private final List output; - - TestData( - @Nullable String description, - @NonNull String input, - @NonNull TestConfig config, - @NonNull List output) { - this.description = description; - this.input = input; - this.config = config; - this.output = output; - } - - @Nullable - public String description() { - return description; - } - - @NonNull - public String input() { - return input; - } - - @NonNull - public TestConfig config() { - return config; - } - - @NonNull - public List output() { - return output; - } -} diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestDataReader.java b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestDataReader.java deleted file mode 100644 index 0aa5e998..00000000 --- a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestDataReader.java +++ /dev/null @@ -1,341 +0,0 @@ -package ru.noties.markwon.renderer.visitor; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.text.TextUtils; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; - -import org.apache.commons.io.IOUtils; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import ix.Ix; -import ix.IxFunction; -import ix.IxPredicate; - -import static ru.noties.markwon.renderer.visitor.TestSpan.BLOCK_QUOTE; -import static ru.noties.markwon.renderer.visitor.TestSpan.BULLET_LIST; -import static ru.noties.markwon.renderer.visitor.TestSpan.CODE; -import static ru.noties.markwon.renderer.visitor.TestSpan.CODE_BLOCK; -import static ru.noties.markwon.renderer.visitor.TestSpan.EMPHASIS; -import static ru.noties.markwon.renderer.visitor.TestSpan.HEADING; -import static ru.noties.markwon.renderer.visitor.TestSpan.IMAGE; -import static ru.noties.markwon.renderer.visitor.TestSpan.LINK; -import static ru.noties.markwon.renderer.visitor.TestSpan.ORDERED_LIST; -import static ru.noties.markwon.renderer.visitor.TestSpan.PARAGRAPH; -import static ru.noties.markwon.renderer.visitor.TestSpan.STRONG_EMPHASIS; -import static ru.noties.markwon.renderer.visitor.TestSpan.THEMATIC_BREAK; - -abstract class TestDataReader { - - private static final String FOLDER = "tests/"; - - @NonNull - static Collection testFiles() { - - final InputStream in = TestDataReader.class.getClassLoader().getResourceAsStream(FOLDER); - if (in == null) { - throw new RuntimeException("Cannot access test cases folder"); - } - - try { - //noinspection unchecked - return (Collection) Ix.from(IOUtils.readLines(in, StandardCharsets.UTF_8)) - .filter(new IxPredicate() { - @Override - public boolean test(String s) { - return s.endsWith(".yaml"); - } - }) - .map(new IxFunction() { - @Override - public String apply(String s) { - return FOLDER + s; - } - }) - .map(new IxFunction() { - @Override - public Object[] apply(String s) { - return new Object[]{ - s - }; - } - }) - .toList(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @NonNull - static TestData readTest(@NonNull String file) { - return new Reader(file).read(); - } - - private TestDataReader() { - } - - static class Reader { - - private static final String TEXT = "text"; -// private static final String CELLS = "cells"; - - private static final Set TAGS; - - static { - TAGS = new HashSet<>(Arrays.asList( - STRONG_EMPHASIS, - EMPHASIS, - BLOCK_QUOTE, - CODE, - CODE_BLOCK, - ORDERED_LIST, - BULLET_LIST, - THEMATIC_BREAK, - HEADING, - PARAGRAPH, - IMAGE, - LINK, - HEADING + "1", - HEADING + "2", - HEADING + "3", - HEADING + "4", - HEADING + "5", - HEADING + "6", - TEXT - )); - } - - private final String file; - - Reader(@NonNull String file) { - this.file = file; - } - - @NonNull - TestData read() { - return testData(jsonObject()); - } - - @NonNull - private JsonObject jsonObject() { - try { - final String input = IOUtils.resourceToString(file, StandardCharsets.UTF_8, TestDataReader.class.getClassLoader()); - final ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory()); - final Object object = objectMapper.readValue(input, Object.class); - final ObjectMapper jsonWriter = new ObjectMapper(); - final String json = jsonWriter.writeValueAsString(object); - return new Gson().fromJson(json, JsonObject.class); - } catch (Throwable t) { - throw new RuntimeException(t); - } - } - - @NonNull - private TestData testData(@NonNull JsonObject jsonObject) { - - final String description; - { - final JsonElement element = jsonObject.get("description"); - if (element != null - && element.isJsonPrimitive()) { - description = element.getAsString(); - } else { - description = null; - } - } - - final String input = jsonObject.get("input").getAsString(); - if (TextUtils.isEmpty(input)) { - throw new RuntimeException(String.format("Test case file `%s` is missing " + - "input parameter", file)); - } - - final TestConfig testConfig = testConfig(jsonObject.get("config")); - - final List testNodes = testNodes(jsonObject.get("output").getAsJsonArray()); - if (testNodes.size() == 0) { - throw new RuntimeException(String.format("Test case file `%s` has no " + - "output specified", file)); - } - - return new TestData( - description, - input, - testConfig, - testNodes - ); - } - - @NonNull - private List testNodes(@NonNull JsonArray array) { - return testNodes(null, array); - } - - @NonNull - private List testNodes(@Nullable TestNode parent, @NonNull JsonArray array) { - - // an item in array is a JsonObject - - // it can be "b": "bold" -> means Span(name="b", children=[Text(bold)] - // or b: - // - text: "bold" -> which is the same as above - - // it can additionally contain "attrs" key which is the attributes - // b: - // - text: "bold" - // href: "my-href" - - final int size = array.size(); - - final List testNodes = new ArrayList<>(size); - - for (int i = 0; i < size; i++) { - - // if element is a string (or a json primitive) let's just add a text node - // right away, this way we will not have to provide text with `text: "my-text"` - // (we still can though) - final JsonElement jsonElement = array.get(i); - if (jsonElement.isJsonPrimitive()) { - testNodes.add(new TestNode.Text(parent, jsonElement.getAsString())); - continue; - } - - final JsonObject object = jsonElement.getAsJsonObject(); - - String name = null; - Map attributes = new HashMap<>(0); - - for (String key : object.keySet()) { - if (TAGS.contains(key)) { - if (name == null) { - name = key; - } else { - throw new RuntimeException("Unexpected key in object: " + object); - } - } else { - // fill attribute map with it - final String value; - final JsonElement valueElement = object.get(key); - if (valueElement.isJsonNull()) { - value = null; - } else { - value = valueElement.getAsString(); - } -// else { -// // another special case: table cell -// // this is not so good -// if (CELLS.equals(key)) { -// final JsonArray cells = valueElement.getAsJsonArray(); -// final int length = cells.size(); -// final List list = new ArrayList<>(length); -// for (int k = 0; k < length; k++) { -// final JsonObject cell = cells.get(k).getAsJsonObject(); -// list.add(new TableRowSpan.Cell( -// cell.get("alignment").getAsInt(), -// cell.get("text").getAsString() -// )); -// } -// value = list.toString(); -// } else { -// value = valueElement.getAsString(); -// } -// } - attributes.put(key, value); - } - } - - if (name == null) { - throw new RuntimeException("Object is missing tag name: " + object); - } - - final JsonElement element = object.get(name); - - if (TEXT.equals(name)) { - testNodes.add(new TestNode.Text(parent, element.getAsString())); - } else { - - final List children = new ArrayList<>(1); - final TestNode.Span span = new TestNode.Span(parent, name, children, attributes); - - // if it's primitive string -> just append text node - if (element.isJsonPrimitive()) { - children.add(new TestNode.Text(span, element.getAsString())); - } else if (element.isJsonArray()) { - children.addAll(testNodes(span, element.getAsJsonArray())); - } else { - throw new RuntimeException("Unexpected element: " + object); - } - - testNodes.add(span); - } - } - - return testNodes; - } - - @NonNull - private TestConfig testConfig(@Nullable JsonElement element) { - - final JsonObject object = element != null && element.isJsonObject() - ? element.getAsJsonObject() - : null; - - final Map map; - - if (object != null) { - - map = new HashMap<>(object.size()); - - for (String key : object.keySet()) { - - final JsonElement value = object.get(key); - - if (value.isJsonPrimitive()) { - - final JsonPrimitive jsonPrimitive = value.getAsJsonPrimitive(); - - Boolean b = null; - - if (jsonPrimitive.isBoolean()) { - b = jsonPrimitive.getAsBoolean(); - } else if (jsonPrimitive.isString()) { - final String s = jsonPrimitive.getAsString(); - if ("true".equalsIgnoreCase(s)) { - b = Boolean.TRUE; - } else if ("false".equalsIgnoreCase(s)) { - b = Boolean.FALSE; - } - } - - if (b != null) { - map.put(key, b); - } - } - } - } else { - map = Collections.emptyMap(); - } - - return new TestConfig(map); - } - } -} diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestFactory.java b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestFactory.java deleted file mode 100644 index 2171c1c1..00000000 --- a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestFactory.java +++ /dev/null @@ -1,142 +0,0 @@ -package ru.noties.markwon.renderer.visitor; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import ru.noties.markwon.core.MarkwonTheme; -import ru.noties.markwon.core.MarkwonSpannableFactory; -import ru.noties.markwon.core.spans.LinkSpan; -import ru.noties.markwon.image.AsyncDrawableLoader; -import ru.noties.markwon.image.ImageSize; -import ru.noties.markwon.image.ImageSizeResolver; - -import static ru.noties.markwon.renderer.visitor.TestSpan.BLOCK_QUOTE; -import static ru.noties.markwon.renderer.visitor.TestSpan.BULLET_LIST; -import static ru.noties.markwon.renderer.visitor.TestSpan.CODE; -import static ru.noties.markwon.renderer.visitor.TestSpan.CODE_BLOCK; -import static ru.noties.markwon.renderer.visitor.TestSpan.EMPHASIS; -import static ru.noties.markwon.renderer.visitor.TestSpan.HEADING; -import static ru.noties.markwon.renderer.visitor.TestSpan.IMAGE; -import static ru.noties.markwon.renderer.visitor.TestSpan.LINK; -import static ru.noties.markwon.renderer.visitor.TestSpan.ORDERED_LIST; -import static ru.noties.markwon.renderer.visitor.TestSpan.PARAGRAPH; -import static ru.noties.markwon.renderer.visitor.TestSpan.STRONG_EMPHASIS; -import static ru.noties.markwon.renderer.visitor.TestSpan.THEMATIC_BREAK; - -class TestFactory implements MarkwonSpannableFactory { - - private final boolean useParagraphs; - - TestFactory(boolean useParagraphs) { - this.useParagraphs = useParagraphs; - } - - @Nullable - @Override - public Object strongEmphasis() { - return new TestSpan(STRONG_EMPHASIS); - } - - @Nullable - @Override - public Object emphasis() { - return new TestSpan(EMPHASIS); - } - - @Nullable - @Override - public Object blockQuote(@NonNull MarkwonTheme theme) { - return new TestSpan(BLOCK_QUOTE); - } - - @Nullable - @Override - public Object code(@NonNull MarkwonTheme theme, boolean multiline) { - final String name = multiline - ? CODE_BLOCK - : CODE; - return new TestSpan(name); - } - - @Nullable - @Override - public Object orderedListItem(@NonNull MarkwonTheme theme, int startNumber) { - return new TestSpan(ORDERED_LIST, map("start", startNumber)); - } - - @Nullable - @Override - public Object bulletListItem(@NonNull MarkwonTheme theme, int level) { - return new TestSpan(BULLET_LIST, map("level", level)); - } - - @Nullable - @Override - public Object thematicBreak(@NonNull MarkwonTheme theme) { - return new TestSpan(THEMATIC_BREAK); - } - - @Nullable - @Override - public Object heading(@NonNull MarkwonTheme theme, int level) { - return new TestSpan(HEADING + level); - } - - @Nullable - @Override - public Object paragraph(boolean inTightList) { - return !useParagraphs - ? null - : new TestSpan(PARAGRAPH); - } - - @Nullable - @Override - public Object image(@NonNull MarkwonTheme theme, @NonNull String destination, @NonNull AsyncDrawableLoader loader, @NonNull ImageSizeResolver imageSizeResolver, @Nullable ImageSize imageSize, boolean replacementTextIsLink) { - return new TestSpan(IMAGE, map( - Pair.of("src", destination), - Pair.of("imageSize", imageSize), - Pair.of("replacementTextIsLink", replacementTextIsLink) - )); - } - - @Nullable - @Override - public Object link(@NonNull MarkwonTheme theme, @NonNull String destination, @NonNull LinkSpan.Resolver resolver) { - return new TestSpan(LINK, map("href", destination)); - } - - @NonNull - private static Map map(@NonNull String key, @Nullable Object value) { - return Collections.singletonMap(key, String.valueOf(value)); - } - - private static class Pair { - - static Pair of(@NonNull String key, @Nullable Object value) { - return new Pair(key, value); - } - - final String key; - final Object value; - - Pair(@NonNull String key, @Nullable Object value) { - this.key = key; - this.value = value; - } - } - - @NonNull - private static Map map(Pair... pairs) { - final int length = pairs.length; - final Map map = new HashMap<>(length); - for (Pair pair : pairs) { - map.put(pair.key, pair.value == null ? null : String.valueOf(pair.value)); - } - return map; - } -} diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestNode.java b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestNode.java deleted file mode 100644 index 9124fd59..00000000 --- a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestNode.java +++ /dev/null @@ -1,140 +0,0 @@ -package ru.noties.markwon.renderer.visitor; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.util.List; -import java.util.Map; - -abstract class TestNode { - - private final TestNode parent; - - TestNode(@Nullable TestNode parent) { - this.parent = parent; - } - - @Nullable - public TestNode parent() { - return parent; - } - - abstract boolean isText(); - - abstract boolean isSpan(); - - @NonNull - abstract Text getAsText(); - - @NonNull - abstract Span getAsSpan(); - - - static class Text extends TestNode { - - private final String text; - - Text(@Nullable TestNode parent, @NonNull String text) { - super(parent); - this.text = text; - } - - @NonNull - public String text() { - return text; - } - - @Override - boolean isText() { - return true; - } - - @Override - boolean isSpan() { - return false; - } - - @NonNull - @Override - Text getAsText() { - return this; - } - - @NonNull - @Override - Span getAsSpan() { - throw new ClassCastException(); - } - - @Override - public String toString() { - return "Text{" + - "text='" + text + '\'' + - '}'; - } - } - - static class Span extends TestNode { - - private final String name; - private final List children; - private final Map attributes; - - Span( - @Nullable TestNode parent, - @NonNull String name, - @NonNull List children, - @NonNull Map attributes) { - super(parent); - this.name = name; - this.children = children; - this.attributes = attributes; - } - - @NonNull - public String name() { - return name; - } - - @NonNull - public List children() { - return children; - } - - @NonNull - public Map attributes() { - return attributes; - } - - @Override - boolean isText() { - return false; - } - - @Override - boolean isSpan() { - return true; - } - - @NonNull - @Override - Text getAsText() { - throw new ClassCastException(); - } - - @NonNull - @Override - Span getAsSpan() { - return this; - } - - @Override - public String toString() { - return "Span{" + - "name='" + name + '\'' + - ", children=" + children + - ", attributes=" + attributes + - '}'; - } - } -} diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestSpan.java b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestSpan.java deleted file mode 100644 index 1740ba3d..00000000 --- a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestSpan.java +++ /dev/null @@ -1,59 +0,0 @@ -package ru.noties.markwon.renderer.visitor; - -import android.support.annotation.NonNull; - -import java.util.Collections; -import java.util.Map; - -class TestSpan { - - static final String STRONG_EMPHASIS = "b"; - static final String EMPHASIS = "i"; - static final String BLOCK_QUOTE = "blockquote"; - static final String CODE = "code"; - static final String CODE_BLOCK = "code-block"; - static final String ORDERED_LIST = "ol"; - static final String BULLET_LIST = "ul"; - static final String THEMATIC_BREAK = "hr"; - static final String HEADING = "h"; -// static final String STRIKE_THROUGH = "s"; -// static final String TASK_LIST = "task-list"; -// static final String TABLE_ROW = "tr"; - static final String PARAGRAPH = "p"; - static final String IMAGE = "img"; - static final String LINK = "a"; -// static final String SUPER_SCRIPT = "sup"; -// static final String SUB_SCRIPT = "sub"; -// static final String UNDERLINE = "u"; - - - private final String name; - private final Map attributes; - - TestSpan(@NonNull String name) { - this(name, Collections.emptyMap()); - } - - TestSpan(@NonNull String name, @NonNull Map attributes) { - this.name = name; - this.attributes = attributes; - } - - @NonNull - public String name() { - return name; - } - - @NonNull - public Map attributes() { - return attributes; - } - - @Override - public String toString() { - return "TestSpan{" + - "name='" + name + '\'' + - ", attributes=" + attributes + - '}'; - } -} diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestValidator.java b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestValidator.java deleted file mode 100644 index 59c000fe..00000000 --- a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestValidator.java +++ /dev/null @@ -1,199 +0,0 @@ -package ru.noties.markwon.renderer.visitor; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.text.SpannableStringBuilder; -import android.text.Spanned; - -import java.util.Map; - -import ix.Ix; -import ix.IxPredicate; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -abstract class TestValidator { - - abstract int validate( - @NonNull SpannableStringBuilder builder, - int index, - @NonNull TestNode node); - - abstract int processedSpanNodesCount(); - - - @NonNull - static TestValidator create(@NonNull String id) { - return new Impl(id); - } - - static class Impl extends TestValidator { - - private final String id; - - private int processedCount; - - Impl(@NonNull String id) { - this.id = id; - } - - @Override - int validate( - @NonNull final SpannableStringBuilder builder, - final int index, - @NonNull TestNode node) { - - if (node.isText()) { - - final String text; - { - final String content = node.getAsText().text(); - - // code is a special case as we wrap it around non-breakable spaces - final TestNode parent = node.parent(); - if (parent != null) { - final TestNode.Span span = parent.getAsSpan(); - if (TestSpan.CODE.equals(span.name())) { - text = "\u00a0" + content + "\u00a0"; - } else if (TestSpan.CODE_BLOCK.equals(span.name())) { - text = "\u00a0\n" + content + "\n\u00a0"; - } else { - text = content; - } - } else { - text = content; - } - } - - assertEquals( - String.format("text: %s, position: {%d-%d}", text, index, index + text.length()), - text, - builder.subSequence(index, index + text.length()).toString()); - - return index + text.length(); - } - - final TestNode.Span span = node.getAsSpan(); - processedCount += 1; - - int out = index; - - for (TestNode child : span.children()) { - out = validate(builder, out, child); - } - - final int end = out; - - // we can possibly have parent spans here, should filter them - final Object[] spans = builder.getSpans(index, out, Object.class); - - // expected span{name, attributes} at position{start-end}, with text: `%s`, spans: [] - - - assertTrue( - message(span, index, end, builder, spans), - spans != null - ); - - final TestSpan testSpan = Ix.fromArray(spans) - .filter(new IxPredicate() { - @Override - public boolean test(Object o) { - return o instanceof TestSpan; - } - }) - .cast(TestSpan.class) - .filter(new IxPredicate() { - @Override - public boolean test(TestSpan testSpan) { - - // in case of nested spans with the same name (lists) - // we also must validate attributes - // and thus we are moving most of assertions to this filter method - return span.name().equals(testSpan.name()) - && index == builder.getSpanStart(testSpan) - && end == builder.getSpanEnd(testSpan) - && mapEquals(span.attributes(), testSpan.attributes()); - } - }) - .first(null); - - assertNotNull( - message(span, index, end, builder, spans), - testSpan - ); - - return out; - } - - @Override - int processedSpanNodesCount() { - return processedCount; - } - - private static boolean mapEquals( - @NonNull Map expected, - @NonNull Map actual) { - - if (expected.size() != actual.size()) { - return false; - } - - boolean result = true; - - for (Map.Entry entry : expected.entrySet()) { - if (!actual.containsKey(entry.getKey()) - || !equals(entry.getValue(), actual.get(entry.getKey()))) { - result = false; - break; - } - } - - return result; - } - - private static boolean equals(@Nullable Object o1, @Nullable Object o2) { - return o1 != null - ? o1.equals(o2) - : o2 == null; - } - - @NonNull - private static String message( - @NonNull TestNode.Span span, - int start, - int end, - @NonNull Spanned text, - @Nullable Object[] spans) { - final String spansText; - if (spans == null - || spans.length == 0) { - spansText = "[]"; - } else { - final StringBuilder builder = new StringBuilder(); - for (Object o : spans) { - final TestSpan testSpan = (TestSpan) o; - if (builder.length() > 0) { - builder.append(", "); - } - - builder - .append("{name: '").append(testSpan.name()).append('\'') - .append(", position{").append(start).append(", ").append(end).append('}'); - - if (testSpan.attributes().size() > 0) { - builder.append(", attributes: ").append(testSpan.attributes()); - } - - builder.append('}'); - } - spansText = builder.toString(); - } - return String.format("Expected span: %s at position{%d-%d} with text `%s`, spans: %s", - span, start, end, text.subSequence(start, end), spansText - ); - } - } -} diff --git a/markwon/src/test/java/ru/noties/markwon/syntax/SyntaxHighlightTest.java b/markwon/src/test/java/ru/noties/markwon/syntax/SyntaxHighlightTest.java index 2f2d99b7..c15426d2 100644 --- a/markwon/src/test/java/ru/noties/markwon/syntax/SyntaxHighlightTest.java +++ b/markwon/src/test/java/ru/noties/markwon/syntax/SyntaxHighlightTest.java @@ -23,7 +23,6 @@ import ru.noties.markwon.MarkwonVisitor; import ru.noties.markwon.SpannableBuilder; import ru.noties.markwon.core.MarkwonTheme; import ru.noties.markwon.core.MarkwonSpannableFactory; -import ru.noties.markwon.core.visitor.CodeBlockNodeVisitor; import ru.noties.markwon.image.AsyncDrawableLoader; import static org.junit.Assert.assertEquals; diff --git a/markwon/src/test/resources/tests/single-img.yaml b/markwon/src/test/resources/tests/single-img.yaml deleted file mode 100644 index 9c55a2f6..00000000 --- a/markwon/src/test/resources/tests/single-img.yaml +++ /dev/null @@ -1,7 +0,0 @@ -input: "![image](#href)" - -output: - - img: "image" - src: "#href" - imageSize: null - replacementTextIsLink: false \ No newline at end of file diff --git a/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconPlugin.java b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconPlugin.java index eb821000..e03f8a86 100644 --- a/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconPlugin.java +++ b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconPlugin.java @@ -24,6 +24,7 @@ public class IconPlugin extends AbstractMarkwonPlugin { @Override public void configureParser(@NonNull Parser.Builder builder) { builder.customDelimiterProcessor(IconProcessor.create()); + builder.postProcessor() } @Override diff --git a/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/MainActivity.java b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/MainActivity.java index 29ad6c51..4fa0bac7 100644 --- a/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/MainActivity.java +++ b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/MainActivity.java @@ -21,8 +21,8 @@ public class MainActivity extends Activity { final TextView textView = findViewById(R.id.text_view); final Markwon markwon = Markwon.builder(this) - .use(IconPlugin.create(IconSpanProvider.create(this, 0))) - .use(new AbstractMarkwonPlugin() { + .usePlugin(IconPlugin.create(IconSpanProvider.create(this, 0))) + .usePlugin(new AbstractMarkwonPlugin() { @Override public void configureTheme(@NonNull MarkwonTheme.Builder builder) { final float[] textSizeMultipliers = new float[]{3f, 2f, 1.5f, 1f, .5f, .25f}; diff --git a/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/MainActivity.java b/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/MainActivity.java index 01fb2718..7eeb8006 100644 --- a/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/MainActivity.java +++ b/sample-latex-math/src/main/java/ru/noties/markwon/sample/jlatexmath/MainActivity.java @@ -46,11 +46,11 @@ public class MainActivity extends Activity { + latex + "$$\n\n something like **this**"; final Markwon markwon = Markwon.builder(this) - .use(CorePlugin.create()) + .usePlugin(CorePlugin.create()) // strictly speaking this one is not required as long as JLatexMathPlugin schedules // drawables on it's own - .use(ImagesPlugin.create(this)) - .use(JLatexMathPlugin.create(config)) + .usePlugin(ImagesPlugin.create(this)) + .usePlugin(JLatexMathPlugin.create(config)) .build(); markwon.setMarkdown(textView, markdown);