diff --git a/docs/.vuepress/components/ArtifactPicker.vue b/docs/.vuepress/components/ArtifactPicker.vue
index a19ec829..463d230a 100644
--- a/docs/.vuepress/components/ArtifactPicker.vue
+++ b/docs/.vuepress/components/ArtifactPicker.vue
@@ -19,7 +19,7 @@
@click="selectAll"
>
- final def markwon_version = 'latest_version'
+ final def markwon_version = '{{latestVersion}}'
@@ -42,7 +42,8 @@ export default {
data() {
return {
artifacts,
- selected: ['core']
+ selected: ['core'],
+ latestVersion: 'latest_version'
};
},
methods: {
diff --git a/docs/docs/v3/core/html-renderer.md b/docs/docs/v3/core/html-renderer.md
index 70b8598e..d0756b12 100644
--- a/docs/docs/v3/core/html-renderer.md
+++ b/docs/docs/v3/core/html-renderer.md
@@ -15,8 +15,8 @@ Markwon.builder(context)
:::danger
Customizing `MarkwonHtmlRenderer` is not enough to include HTML content in your application.
-You must explicitly include [markwon-html](/docs/v3/html/) artifact (include HtmlParser)
-to you project and register `HtmlPlugin`:
+You must explicitly include [markwon-html](/docs/v3/html/) artifact (includes HtmlParser)
+to your project and register `HtmlPlugin`:
```java
Markwon.builder(context)
diff --git a/markwon-core/src/main/java/ru/noties/markwon/MarkwonNodeRenderer.java b/markwon-core/src/main/java/ru/noties/markwon/MarkwonNodeRenderer.java
new file mode 100644
index 00000000..94de3c20
--- /dev/null
+++ b/markwon-core/src/main/java/ru/noties/markwon/MarkwonNodeRenderer.java
@@ -0,0 +1,184 @@
+package ru.noties.markwon;
+
+import android.content.Context;
+import android.support.annotation.IdRes;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.NonNull;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.commonmark.node.Node;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @since 3.0.0
+ */
+public abstract class MarkwonNodeRenderer {
+
+ public interface ViewProvider {
+
+ /**
+ * Please note that you should not attach created View to specified group. It will be done
+ * automatically.
+ */
+ @NonNull
+ View provide(
+ @NonNull LayoutInflater inflater,
+ @NonNull ViewGroup group,
+ @NonNull Markwon markwon,
+ @NonNull N n);
+ }
+
+ @NonNull
+ public static Builder builder(@NonNull ViewProvider defaultViewProvider) {
+ return new Builder(defaultViewProvider);
+ }
+
+ /**
+ * @param defaultViewProviderLayoutResId layout resource id to be used in default view provider
+ * @param defaultViewProviderTextViewId id of a TextView in specified layout
+ * @return Builder
+ * @see SimpleTextViewProvider
+ */
+ @NonNull
+ public static Builder builder(
+ @LayoutRes int defaultViewProviderLayoutResId,
+ @IdRes int defaultViewProviderTextViewId) {
+ return new Builder(new SimpleTextViewProvider(
+ defaultViewProviderLayoutResId,
+ defaultViewProviderTextViewId));
+ }
+
+ public abstract void render(@NonNull ViewGroup group, @NonNull Markwon markwon, @NonNull String markdown);
+
+ public abstract void render(@NonNull ViewGroup group, @NonNull Markwon markwon, @NonNull Node root);
+
+
+ public static class Builder {
+
+ private final ViewProvider defaultViewProvider;
+
+ private MarkwonReducer reducer;
+ private Map, ViewProvider> viewProviders;
+ private LayoutInflater inflater;
+
+ public Builder(@NonNull ViewProvider defaultViewProvider) {
+ this.defaultViewProvider = defaultViewProvider;
+ this.viewProviders = new HashMap<>(3);
+ }
+
+ @NonNull
+ public Builder reducer(@NonNull MarkwonReducer reducer) {
+ this.reducer = reducer;
+ return this;
+ }
+
+ @NonNull
+ public Builder viewProvider(
+ @NonNull Class type,
+ @NonNull ViewProvider super N> viewProvider) {
+ //noinspection unchecked
+ viewProviders.put(type, (ViewProvider) viewProvider);
+ return this;
+ }
+
+ @NonNull
+ public Builder inflater(@NonNull LayoutInflater inflater) {
+ this.inflater = inflater;
+ return this;
+ }
+
+ @NonNull
+ public MarkwonNodeRenderer build() {
+ if (reducer == null) {
+ reducer = MarkwonReducer.directChildren();
+ }
+ return new Impl(this);
+ }
+ }
+
+ public static class SimpleTextViewProvider implements ViewProvider {
+
+ private final int layoutResId;
+ private final int textViewId;
+
+ public SimpleTextViewProvider(@LayoutRes int layoutResId, @IdRes int textViewId) {
+ this.layoutResId = layoutResId;
+ this.textViewId = textViewId;
+ }
+
+ @NonNull
+ @Override
+ public View provide(
+ @NonNull LayoutInflater inflater,
+ @NonNull ViewGroup group,
+ @NonNull Markwon markwon,
+ @NonNull Node node) {
+ final View view = inflater.inflate(layoutResId, group, false);
+ final TextView textView = view.findViewById(textViewId);
+ markwon.setParsedMarkdown(textView, markwon.render(node));
+ return view;
+ }
+ }
+
+ static class Impl extends MarkwonNodeRenderer {
+
+ private final MarkwonReducer reducer;
+ private final Map, ViewProvider> viewProviders;
+ private final ViewProvider defaultViewProvider;
+
+ private LayoutInflater inflater;
+
+ Impl(@NonNull Builder builder) {
+ this.reducer = builder.reducer;
+ this.viewProviders = builder.viewProviders;
+ this.defaultViewProvider = builder.defaultViewProvider;
+ this.inflater = builder.inflater;
+ }
+
+ @Override
+ public void render(@NonNull ViewGroup group, @NonNull Markwon markwon, @NonNull String markdown) {
+ render(group, markwon, markwon.parse(markdown));
+ }
+
+ @Override
+ public void render(@NonNull ViewGroup group, @NonNull Markwon markwon, @NonNull Node root) {
+
+ final LayoutInflater inflater = ensureLayoutInflater(group.getContext());
+
+ ViewProvider viewProvider;
+
+ for (Node node : reducer.reduce(root)) {
+ viewProvider = viewProvider(node);
+ group.addView(viewProvider.provide(inflater, group, markwon, node));
+ }
+ }
+
+ @NonNull
+ private LayoutInflater ensureLayoutInflater(@NonNull Context context) {
+ LayoutInflater inflater = this.inflater;
+ if (inflater == null) {
+ inflater = this.inflater = LayoutInflater.from(context);
+ }
+ return inflater;
+ }
+
+ @NonNull
+ private ViewProvider viewProvider(@NonNull Node node) {
+
+ // check for specific node view provider
+ final ViewProvider provider = viewProviders.get(node.getClass());
+ if (provider != null) {
+ return provider;
+ }
+
+ // if it's not present, then we can return a default one
+ return defaultViewProvider;
+ }
+ }
+
+}
diff --git a/markwon-core/src/main/java/ru/noties/markwon/MarkwonReducer.java b/markwon-core/src/main/java/ru/noties/markwon/MarkwonReducer.java
new file mode 100644
index 00000000..251cf1fe
--- /dev/null
+++ b/markwon-core/src/main/java/ru/noties/markwon/MarkwonReducer.java
@@ -0,0 +1,57 @@
+package ru.noties.markwon;
+
+import android.support.annotation.NonNull;
+
+import org.commonmark.node.Node;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @since 3.0.0
+ */
+public abstract class MarkwonReducer {
+
+ @NonNull
+ public static MarkwonReducer directChildren() {
+ return new DirectChildren();
+ }
+
+ @NonNull
+ public abstract List reduce(@NonNull Node node);
+
+
+ static class DirectChildren extends MarkwonReducer {
+
+ @NonNull
+ @Override
+ public List reduce(@NonNull Node root) {
+
+ final List list;
+
+ // we will extract all blocks that are direct children of Document
+ Node node = root.getFirstChild();
+
+ // please note, that if there are no children -> we will return a list with
+ // single element (which was supplied)
+ if (node == null) {
+ list = Collections.singletonList(root);
+ } else {
+
+ list = new ArrayList<>();
+
+ Node temp;
+
+ while (node != null) {
+ list.add(node);
+ temp = node.getNext();
+ node.unlink();
+ node = temp;
+ }
+ }
+
+ return list;
+ }
+ }
+}
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 234d53ac..19d355f0 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
@@ -42,16 +42,10 @@ public abstract class MarkwonHtmlRenderer {
Builder allowNonClosedTags(boolean allowNonClosedTags);
@NonNull
- Builder setHandler(@NonNull String tagName, @NonNull TagHandler tagHandler);
+ Builder setHandler(@NonNull String tagName, @Nullable TagHandler tagHandler);
@NonNull
- Builder setHandler(@NonNull Collection tagNames, @NonNull TagHandler tagHandler);
-
- @NonNull
- Builder removeHandler(@NonNull String tagName);
-
- @NonNull
- Builder removeHandlers(@NonNull String... tagNames);
+ Builder setHandler(@NonNull Collection tagNames, @Nullable TagHandler tagHandler);
@NonNull
MarkwonHtmlRenderer build();
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 7547e1cc..973342e2 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
@@ -100,36 +100,26 @@ class MarkwonHtmlRendererImpl extends MarkwonHtmlRenderer {
@NonNull
@Override
- public Builder setHandler(@NonNull String tagName, @NonNull TagHandler tagHandler) {
- tagHandlers.put(tagName, tagHandler);
- return this;
- }
-
- @NonNull
- @Override
- public Builder setHandler(@NonNull Collection tagNames, @NonNull TagHandler tagHandler) {
- for (String tagName : tagNames) {
- if (tagName != null) {
- tagHandlers.put(tagName, tagHandler);
- }
+ public Builder setHandler(@NonNull String tagName, @Nullable TagHandler tagHandler) {
+ if (tagHandler == null) {
+ tagHandlers.remove(tagName);
+ } else {
+ tagHandlers.put(tagName, tagHandler);
}
return this;
}
@NonNull
@Override
- public Builder removeHandler(@NonNull String tagName) {
- tagHandlers.remove(tagName);
- return this;
- }
-
- @NonNull
- @Override
- public Builder removeHandlers(@NonNull String... tagNames) {
- for (String tagName : tagNames) {
- if (tagName != null) {
+ public Builder setHandler(@NonNull Collection tagNames, @Nullable TagHandler tagHandler) {
+ if (tagHandler == null) {
+ for (String tagName : tagNames) {
tagHandlers.remove(tagName);
}
+ } else {
+ for (String tagName : tagNames) {
+ tagHandlers.put(tagName, tagHandler);
+ }
}
return this;
}
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 5a787dc9..9f74c206 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
@@ -15,6 +15,7 @@ import ru.noties.markwon.AbstractMarkwonPlugin;
import ru.noties.markwon.MarkwonSpansFactory;
import ru.noties.markwon.MarkwonVisitor;
import ru.noties.markwon.RenderProps;
+import ru.noties.markwon.core.SimpleBlockNodeVisitor;
/**
* @since 3.0.0
@@ -75,20 +76,7 @@ public class TaskListPlugin extends AbstractMarkwonPlugin {
@Override
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
builder
- .on(TaskListBlock.class, new MarkwonVisitor.NodeVisitor() {
- @Override
- public void visit(@NonNull MarkwonVisitor visitor, @NonNull TaskListBlock taskListBlock) {
-
- visitor.ensureNewLine();
-
- visitor.visitChildren(taskListBlock);
-
- if (visitor.hasNext(taskListBlock)) {
- visitor.ensureNewLine();
- visitor.forceNewLine();
- }
- }
- })
+ .on(TaskListBlock.class, new SimpleBlockNodeVisitor())
.on(TaskListItem.class, new MarkwonVisitor.NodeVisitor() {
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull TaskListItem taskListItem) {
diff --git a/markwon-recycler/src/main/java/ru/noties/markwon/recycler/MarkwonAdapter.java b/markwon-recycler/src/main/java/ru/noties/markwon/recycler/MarkwonAdapter.java
index 5d6ddc8b..d288a44c 100644
--- a/markwon-recycler/src/main/java/ru/noties/markwon/recycler/MarkwonAdapter.java
+++ b/markwon-recycler/src/main/java/ru/noties/markwon/recycler/MarkwonAdapter.java
@@ -14,6 +14,7 @@ import org.commonmark.node.Node;
import java.util.List;
import ru.noties.markwon.Markwon;
+import ru.noties.markwon.MarkwonReducer;
/**
* Adapter to display markdown in a RecyclerView. It is done by extracting root blocks from a
@@ -92,7 +93,7 @@ public abstract class MarkwonAdapter extends RecyclerView.Adapterreduced to a list of nodes. There is a default
- * {@link Reducer} that will be used if not provided explicitly (there is no need to
+ * {@link MarkwonReducer} that will be used if not provided explicitly (there is no need to
* register your own unless you require it).
*
- * @param reducer {@link Reducer}
+ * @param reducer {@link MarkwonReducer}
* @return self
- * @see Reducer
+ * @see MarkwonReducer
*/
@NonNull
- Builder reducer(@NonNull Reducer reducer);
+ Builder reducer(@NonNull MarkwonReducer reducer);
/**
* @return {@link MarkwonAdapter}
@@ -169,12 +170,6 @@ public abstract class MarkwonAdapter extends RecyclerView.Adapter reduce(@NonNull Node root);
- }
-
public abstract void setMarkdown(@NonNull Markwon markwon, @NonNull String markdown);
public abstract void setParsedMarkdown(@NonNull Markwon markwon, @NonNull Node document);
diff --git a/markwon-recycler/src/main/java/ru/noties/markwon/recycler/MarkwonAdapterImpl.java b/markwon-recycler/src/main/java/ru/noties/markwon/recycler/MarkwonAdapterImpl.java
index 0e29c2d4..70406e78 100644
--- a/markwon-recycler/src/main/java/ru/noties/markwon/recycler/MarkwonAdapterImpl.java
+++ b/markwon-recycler/src/main/java/ru/noties/markwon/recycler/MarkwonAdapterImpl.java
@@ -7,17 +7,17 @@ import android.view.ViewGroup;
import org.commonmark.node.Node;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import ru.noties.markwon.Markwon;
+import ru.noties.markwon.MarkwonReducer;
class MarkwonAdapterImpl extends MarkwonAdapter {
private final SparseArray> entries;
private final Entry defaultEntry;
- private final Reducer reducer;
+ private final MarkwonReducer reducer;
private LayoutInflater layoutInflater;
@@ -28,7 +28,7 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
MarkwonAdapterImpl(
@NonNull SparseArray> entries,
@NonNull Entry defaultEntry,
- @NonNull Reducer reducer) {
+ @NonNull MarkwonReducer reducer) {
this.entries = entries;
this.defaultEntry = defaultEntry;
this.reducer = reducer;
@@ -133,7 +133,7 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
private final SparseArray> entries = new SparseArray<>(3);
private Entry defaultEntry;
- private Reducer reducer;
+ private MarkwonReducer reducer;
@NonNull
@Override
@@ -163,7 +163,7 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
@NonNull
@Override
- public Builder reducer(@NonNull Reducer reducer) {
+ public Builder reducer(@NonNull MarkwonReducer reducer) {
this.reducer = reducer;
return this;
}
@@ -178,33 +178,10 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
}
if (reducer == null) {
- reducer = new ReducerImpl();
+ reducer = MarkwonReducer.directChildren();
}
return new MarkwonAdapterImpl(entries, defaultEntry, reducer);
}
}
-
- static class ReducerImpl implements Reducer {
-
- @NonNull
- @Override
- public List reduce(@NonNull Node root) {
-
- final List list = new ArrayList<>();
-
- // we will extract all blocks that are direct children of Document
- Node node = root.getFirstChild();
- Node temp;
-
- while (node != null) {
- list.add(node);
- temp = node.getNext();
- node.unlink();
- node = temp;
- }
-
- return list;
- }
- }
}
diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml
index d1ec3ddc..5505eb0c 100644
--- a/sample/src/main/res/values/strings.xml
+++ b/sample/src/main/res/values/strings.xml
@@ -2,12 +2,13 @@
MarkwonSample
+