+ builder.addHandler("center", new SimpleTagHandler() {
+ @Override
+ public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps renderProps, @NonNull HtmlTag tag) {
+ return new AlignmentSpan() {
+ @Override
+ public Layout.Alignment getAlignment() {
+ return Layout.Alignment.ALIGN_CENTER;
+ }
+ };
+ }
+ });
+ }
+ })
+ .build();
+```
+
+:::danger
+Although `MarkwonHtmlRenderer` is bundled with `core` artifact, actual
+HTML parser is placed in a standalone artifact and must be added to your
+project **explicitly** and then registered via `Markwon.Builder#usePlugin(HtmlPlugin.create())`.
+If not done so, no HTML will be parsed nor rendered.
+:::
+
+More information about HTML rendering can be found [here](/docs/core/html-renderer.md)
+
+
+## Priority
+
+`Priority` is an abstraction to _state_ dependency connection between plugins. It is
+also used as a runtime graph validator. If a plugin defines a dependency on other, but
+_other_ is not in resulting `Markwon` instance, then a runtime exception will be thrown.
+`Priority` is also defines the order in which plugins will be placed. So, if a plugin `A`
+states a plugin `B` as a dependency, then plugin `A` will come **after** plugin `B`.
+
+```java
+final Markwon markwon = Markwon.builder(context)
+ .usePlugin(new AbstractMarkwonPlugin() {
+ @NonNull
+ @Override
+ public Priority priority() {
+ return Priority.after(CorePlugin.class);
+ }
+ })
+ .build();
+```
+
+:::warning
+Please note that `AbstractMarkwonPlugin` _implicitly_ defines `CorePlugin`
+as a dependency (`return Priority.after(CorePlugin.class);`). This will
+also add `CorePlugin` to a `Markwon` instance, because it will be added
+_implicitly_ if a plugin defines it as a dependency.
+:::
+
+Use one of the factory methods to create a `Priority` instance:
+
+```java
+// none
+Priority.none();
+
+// single dependency
+Priority.after(CorePlugin.class);
+
+// 2 dependencies
+Priority.after(CorePlugin.class, ImagesPlugin.class);
+
+// for a number >2, use #builder
+Priority.builder()
+ .after(CorePlugin.class)
+ .after(ImagesPlugin.class)
+ .after(StrikethroughPlugin.class)
+ .build();
+```
+
+## Process markdown
+
+A plugin can be used to _pre-process_ input markdown (this will be called before _parsing_):
+
+```java
+final Markwon markwon = Markwon.builder(context)
+ .usePlugin(new AbstractMarkwonPlugin() {
+ @NonNull
+ @Override
+ public String processMarkdown(@NonNull String markdown) {
+ return markdown.replaceAll("foo", "bar");
+ }
+ })
+ .build();
+```
+
+## Inspect/modify Node
+
+A plugin can inspect/modify commonmark-java Node _before_ it's being rendered.
+
+```java
+final Markwon markwon = Markwon.builder(context)
+ .usePlugin(new AbstractMarkwonPlugin() {
+ @Override
+ public void beforeRender(@NonNull Node node) {
+
+ // for example inspect it with custom visitor
+ node.accept(new MyVisitor());
+
+ // or modify (you know what you are doing, right?)
+ node.appendChild(new Text("Appended"));
+ }
+ })
+ .build();
+```
+
+## Inspect Node after render
+
+A plugin can inspect commonmark-java Node after it's been rendered.
+Modifying Node at this point makes not much sense (it's already been
+rendered and all modifications won't change anything). But this method can be used,
+for example, to clean-up some internal state (after rendering). Generally
+speaking, a plugin must be stateless, but if it cannot, then this method is
+the best place to clean-up.
+
+```java
+final Markwon markwon = Markwon.builder(context)
+ .usePlugin(new AbstractMarkwonPlugin() {
+ @Override
+ public void afterRender(@NonNull Node node, @NonNull MarkwonVisitor visitor) {
+ cleanUp();
+ }
+ })
+ .build();
+```
+
+## Prepare TextView
+
+A plugin can _prepare_ a TextView before markdown is applied. For example `images`
+unschedules all previously scheduled `AsyncDrawableSpans` (if any) here. This way
+when new markdown (and set of Spannables) arrives, previous set won't be kept in
+memory and could be garbage-collected.
+
+```java
+final Markwon markwon = Markwon.builder(context)
+ .usePlugin(new AbstractMarkwonPlugin() {
+ @Override
+ public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
+ // clean-up previous
+ AsyncDrawableScheduler.unschedule(textView);
+ }
+ })
+ .build();
+```
+
+## TextView after markdown applied
+
+A plugin will receive a callback _after_ markdown is applied to a TextView.
+For example `images` uses this callback to schedule new set of Spannables.
+
+```java
+final Markwon markwon = Markwon.builder(context)
+ .usePlugin(new AbstractMarkwonPlugin() {
+ @Override
+ public void afterSetText(@NonNull TextView textView) {
+ AsyncDrawableScheduler.schedule(textView);
+ }
+ })
+ .build();
+```
+
+:::tip
+Please note that unlike `#beforeSetText`, `#afterSetText` won't receive
+`Spanned` markdown. This happens because at this point spans must be
+queried directly from a TextView.
+:::
\ No newline at end of file
diff --git a/docs/docs/core/spans-factory.md b/docs/docs/core/spans-factory.md
new file mode 100644
index 00000000..3b326736
--- /dev/null
+++ b/docs/docs/core/spans-factory.md
@@ -0,0 +1 @@
+# Spans Factory
\ No newline at end of file
diff --git a/docs/docs/core/visitor.md b/docs/docs/core/visitor.md
new file mode 100644
index 00000000..3e8ba057
--- /dev/null
+++ b/docs/docs/core/visitor.md
@@ -0,0 +1 @@
+# Visitor
\ No newline at end of file
diff --git a/docs/docs/factory.md b/docs/docs/factory.md
deleted file mode 100644
index edf4a018..00000000
--- a/docs/docs/factory.md
+++ /dev/null
@@ -1,61 +0,0 @@
-# Factory
-
-`SpannableFactory` is used to create Span implementations.
-
-```java
-SpannableConfiguration.builder(context)
- .factory(SpannableFactory)
- .build();
-```
-
-`Markwon` provides default `SpannableFactoryDef` implementation that is
-used by default.
-
-Spans:
-* `strongEmphasis`
-* `emphasis`
-* `blockQuote`
-* `code`
-* `orderedListItem`
-* `bulletListItem`
-* `thematicBreak`
-* `heading`
-* `strikethrough`
-* `taskListItem`
-* `tableRow`
-* `paragraph`
-* `image`
-* `link`
-* `superScript` (HTML content only)
-* `subScript` (HTML content only)
-* `underline` (HTML content only)
-
-:::tip
-`SpannableFactory` can be used to ignore some kinds of text markup. If, for example,
-you do not wish to apply _emphasis_ styling to your final result, just return `null`
-from `emphasis` factory method:
-```java
-@Nullable
-@Override
-public Object emphasis() {
- return null;
-}
-```
-:::
-
-:::tip
-All factory methods in `SpannableFactory` return an `Object`, but you can actually
-return an **array of Objects** if you wish to apply multiple Spans to a single styling node.
-For example, let's make all _emphasis_ also red:
-
-```java
-@Nullable
-@Override
-public Object emphasis() {
- return new Object[] {
- super.emphasis(),
- new ForegroundColorSpan(Color.RED)
- };
-}
-```
-:::
\ No newline at end of file
diff --git a/docs/docs/migration-2-3.md b/docs/docs/migration-2-3.md
index c7543794..8ca12b36 100644
--- a/docs/docs/migration-2-3.md
+++ b/docs/docs/migration-2-3.md
@@ -1 +1,8 @@
-# Migration 2.x.x -> 3.x.x
\ No newline at end of file
+# Migration 2.x.x -> 3.x.x
+
+* strikethrough moved to standalone module
+* tables moved to standalone module
+* core functionality of `AsyncDrawableLoader` moved to `core` module
+* * Handling of GIF and SVG media moved to standalone modules (`gif` and `svg` respectively)
+* * OkHttpClient to download images moved to standalone module
+* HTML no longer _implicitly_ added to core functionality, it must be specified __explicitly__ (as an artifact)
\ No newline at end of file
diff --git a/docs/docs/v2/install.md b/docs/docs/v2/README.md
similarity index 100%
rename from docs/docs/v2/install.md
rename to docs/docs/v2/README.md
diff --git a/docs/donate.md b/docs/donate.md
new file mode 100644
index 00000000..12b79839
--- /dev/null
+++ b/docs/donate.md
@@ -0,0 +1,7 @@
+# Donations
+
+If you find this library useful consider making a donation
+to your local [environmental](https://en.wikipedia.org/wiki/List_of_environmental_organizations)
+or [human rights](https://en.wikipedia.org/wiki/List_of_human_rights_organisations) organization.
+
+Thank you
\ No newline at end of file
diff --git a/markwon-core/src/main/java/ru/noties/markwon/AbstractMarkwonPlugin.java b/markwon-core/src/main/java/ru/noties/markwon/AbstractMarkwonPlugin.java
index 6f6573c3..5492d4d0 100644
--- a/markwon-core/src/main/java/ru/noties/markwon/AbstractMarkwonPlugin.java
+++ b/markwon-core/src/main/java/ru/noties/markwon/AbstractMarkwonPlugin.java
@@ -57,11 +57,6 @@ public abstract class AbstractMarkwonPlugin implements MarkwonPlugin {
}
- @Override
- public void configureRenderProps(@NonNull RenderProps renderProps) {
-
- }
-
@NonNull
@Override
public Priority priority() {
diff --git a/markwon-core/src/main/java/ru/noties/markwon/MarkwonImpl.java b/markwon-core/src/main/java/ru/noties/markwon/MarkwonImpl.java
index 8d5211d8..a51bd62a 100644
--- a/markwon-core/src/main/java/ru/noties/markwon/MarkwonImpl.java
+++ b/markwon-core/src/main/java/ru/noties/markwon/MarkwonImpl.java
@@ -46,14 +46,7 @@ class MarkwonImpl extends Markwon {
@Override
public Spanned render(@NonNull Node node) {
- final RenderProps renderProps = visitor.renderProps();
-
for (MarkwonPlugin plugin : plugins) {
-
- // let plugins apply render properties before rendering (as we will clear
- // renderProps after rendering)
- plugin.configureRenderProps(renderProps);
-
plugin.beforeRender(node);
}
diff --git a/markwon-core/src/main/java/ru/noties/markwon/MarkwonPlugin.java b/markwon-core/src/main/java/ru/noties/markwon/MarkwonPlugin.java
index d887e619..0804848b 100644
--- a/markwon-core/src/main/java/ru/noties/markwon/MarkwonPlugin.java
+++ b/markwon-core/src/main/java/ru/noties/markwon/MarkwonPlugin.java
@@ -82,19 +82,6 @@ public interface MarkwonPlugin {
*/
void configureHtmlRenderer(@NonNull MarkwonHtmlRenderer.Builder builder);
- /**
- * A method to store some arbitrary data in {@link RenderProps}. Although it won\'t make
- * much sense to use existing {@link Prop} keys for {@link SpanFactory}, it can be helpful
- * to establish a communication channel between multiple plugins in decoupled way (provide
- * some initial properties for example or indicate that certain plugin is registered).
- *
- * This method will be called before each rendering step (after rendering {@link RenderProps}
- * will be cleared. This method won\'t be called during initialization stage.
- *
- * @see RenderProps
- */
- void configureRenderProps(@NonNull RenderProps renderProps);
-
@NonNull
Priority priority();
diff --git a/markwon-core/src/main/java/ru/noties/markwon/html/MarkwonHtmlRenderer.java b/markwon-core/src/main/java/ru/noties/markwon/html/MarkwonHtmlRenderer.java
index 9d641826..931fd092 100644
--- a/markwon-core/src/main/java/ru/noties/markwon/html/MarkwonHtmlRenderer.java
+++ b/markwon-core/src/main/java/ru/noties/markwon/html/MarkwonHtmlRenderer.java
@@ -3,6 +3,8 @@ package ru.noties.markwon.html;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import java.util.Collection;
+
import ru.noties.markwon.MarkwonVisitor;
/**
@@ -39,19 +41,11 @@ public abstract class MarkwonHtmlRenderer {
@NonNull
Builder allowNonClosedTags(boolean allowNonClosedTags);
- /**
- * Please note that if there is already a {@link TagHandler} registered with specified
- * {@code tagName} it will be replaced with newly supplied one.
- *
- * @param tagHandler {@link TagHandler}
- * @param tagName name of a tag
- * @return self
- */
@NonNull
- Builder addHandler(@NonNull TagHandler tagHandler, @NonNull String tagName);
+ Builder addHandler(@NonNull String tagName, @NonNull TagHandler tagHandler);
@NonNull
- Builder addHandler(@NonNull TagHandler tagHandler, String... tagNames);
+ Builder addHandler(@NonNull Collection tagNames, @NonNull TagHandler tagHandler);
@NonNull
Builder removeHandler(@NonNull String tagName);
diff --git a/markwon-core/src/main/java/ru/noties/markwon/html/MarkwonHtmlRendererImpl.java b/markwon-core/src/main/java/ru/noties/markwon/html/MarkwonHtmlRendererImpl.java
index be78caec..ca94f902 100644
--- a/markwon-core/src/main/java/ru/noties/markwon/html/MarkwonHtmlRendererImpl.java
+++ b/markwon-core/src/main/java/ru/noties/markwon/html/MarkwonHtmlRendererImpl.java
@@ -3,6 +3,7 @@ package ru.noties.markwon.html;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -99,14 +100,14 @@ class MarkwonHtmlRendererImpl extends MarkwonHtmlRenderer {
@NonNull
@Override
- public Builder addHandler(@NonNull TagHandler tagHandler, @NonNull String tagName) {
+ public Builder addHandler(@NonNull String tagName, @NonNull TagHandler tagHandler) {
tagHandlers.put(tagName, tagHandler);
return this;
}
@NonNull
@Override
- public Builder addHandler(@NonNull TagHandler tagHandler, String... tagNames) {
+ public Builder addHandler(@NonNull Collection tagNames, @NonNull TagHandler tagHandler) {
for (String tagName : tagNames) {
if (tagName != null) {
tagHandlers.put(tagName, tagHandler);
diff --git a/markwon-core/src/test/java/ru/noties/markwon/MarkwonBuilderImplTest.java b/markwon-core/src/test/java/ru/noties/markwon/MarkwonBuilderImplTest.java
index e451f74e..108c43b8 100644
--- a/markwon-core/src/test/java/ru/noties/markwon/MarkwonBuilderImplTest.java
+++ b/markwon-core/src/test/java/ru/noties/markwon/MarkwonBuilderImplTest.java
@@ -223,7 +223,6 @@ public class MarkwonBuilderImplTest {
verify(plugin, atLeast(1)).priority();
// note, no render props -> they must be configured on render stage
- verify(plugin, times(0)).configureRenderProps(any(RenderProps.class));
verify(plugin, times(0)).processMarkdown(anyString());
verify(plugin, times(0)).beforeRender(any(Node.class));
verify(plugin, times(0)).afterRender(any(Node.class), any(MarkwonVisitor.class));
diff --git a/markwon-core/src/test/java/ru/noties/markwon/MarkwonImplTest.java b/markwon-core/src/test/java/ru/noties/markwon/MarkwonImplTest.java
index f0771efa..d80cb765 100644
--- a/markwon-core/src/test/java/ru/noties/markwon/MarkwonImplTest.java
+++ b/markwon-core/src/test/java/ru/noties/markwon/MarkwonImplTest.java
@@ -107,8 +107,6 @@ public class MarkwonImplTest {
// mark this flag (we must ensure that this method body is executed)
flag.set(true);
- //noinspection ConstantConditions
- verify(plugin, times(1)).configureRenderProps(null);
verify(plugin, times(1)).beforeRender(eq(node));
verify(plugin, times(0)).afterRender(any(Node.class), any(MarkwonVisitor.class));
@@ -175,8 +173,6 @@ public class MarkwonImplTest {
flag.set(true);
- verify(visitor, times(1)).renderProps();
- verify(plugin, times(1)).configureRenderProps(eq(renderProps));
verify(renderProps, times(0)).clearAll();
return null;
diff --git a/markwon-core/src/test/java/ru/noties/markwon/core/CorePluginTest.java b/markwon-core/src/test/java/ru/noties/markwon/core/CorePluginTest.java
index c0481ff2..64c266b8 100644
--- a/markwon-core/src/test/java/ru/noties/markwon/core/CorePluginTest.java
+++ b/markwon-core/src/test/java/ru/noties/markwon/core/CorePluginTest.java
@@ -186,6 +186,7 @@ public class CorePluginTest {
add("configureVisitor");
add("configureSpansFactory");
add("beforeSetText");
+ add("afterSetText");
add("priority");
}};
diff --git a/markwon-html/src/main/java/ru/noties/markwon/html/HtmlPlugin.java b/markwon-html/src/main/java/ru/noties/markwon/html/HtmlPlugin.java
index f69b2368..032fd68e 100644
--- a/markwon-html/src/main/java/ru/noties/markwon/html/HtmlPlugin.java
+++ b/markwon-html/src/main/java/ru/noties/markwon/html/HtmlPlugin.java
@@ -22,6 +22,8 @@ import ru.noties.markwon.html.tag.SubScriptHandler;
import ru.noties.markwon.html.tag.SuperScriptHandler;
import ru.noties.markwon.html.tag.UnderlineHandler;
+import static java.util.Arrays.asList;
+
/**
* @since 3.0.0
*/
@@ -41,18 +43,41 @@ public class HtmlPlugin extends AbstractMarkwonPlugin {
@Override
public void configureHtmlRenderer(@NonNull MarkwonHtmlRenderer.Builder builder) {
+
builder
- .addHandler(new EmphasisHandler(), "i", "em", "cite", "dfn")
- .addHandler(new StrongEmphasisHandler(), "b", "strong")
- .addHandler(new SuperScriptHandler(), "sup")
- .addHandler(new SubScriptHandler(), "sub")
- .addHandler(new UnderlineHandler(), "u", "ins")
- .addHandler(new StrikeHandler(), "s", "del")
- .addHandler(new LinkHandler(), "a")
- .addHandler(new ListHandler(), "ul", "ol")
- .addHandler(ImageHandler.create(), "img")
- .addHandler(new BlockquoteHandler(), "blockquote")
- .addHandler(new HeadingHandler(), "h1", "h2", "h3", "h4", "h5", "h6");
+ .addHandler(
+ "img",
+ ImageHandler.create())
+ .addHandler(
+ "a",
+ new LinkHandler())
+ .addHandler(
+ "blockquote",
+ new BlockquoteHandler())
+ .addHandler(
+ "sub",
+ new SubScriptHandler())
+ .addHandler(
+ "sup",
+ new SuperScriptHandler())
+ .addHandler(
+ asList("b", "strong"),
+ new StrongEmphasisHandler())
+ .addHandler(
+ asList("s", "del"),
+ new StrikeHandler())
+ .addHandler(
+ asList("u", "ins"),
+ new UnderlineHandler())
+ .addHandler(
+ asList("ul", "ol"),
+ new ListHandler())
+ .addHandler(
+ asList("i", "em", "cite", "dfn"),
+ new EmphasisHandler())
+ .addHandler(
+ asList("h1", "h2", "h3", "h4", "h5", "h6"),
+ new HeadingHandler());
}
@Override