From 128612d5b26fa02d10ca56fd194f98c582122c02 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Sun, 3 Mar 2019 16:25:23 +0300 Subject: [PATCH] Add reducer and nodeRenderer --- docs/.vuepress/components/ArtifactPicker.vue | 5 +- docs/docs/v3/core/html-renderer.md | 4 +- .../noties/markwon/MarkwonNodeRenderer.java | 184 ++++++++++++++++++ .../ru/noties/markwon/MarkwonReducer.java | 57 ++++++ .../markwon/html/MarkwonHtmlRenderer.java | 10 +- .../markwon/html/MarkwonHtmlRendererImpl.java | 34 ++-- .../markwon/ext/tasklist/TaskListPlugin.java | 16 +- .../markwon/recycler/MarkwonAdapter.java | 17 +- .../markwon/recycler/MarkwonAdapterImpl.java | 35 +--- sample/src/main/res/values/strings.xml | 11 +- 10 files changed, 280 insertions(+), 93 deletions(-) create mode 100644 markwon-core/src/main/java/ru/noties/markwon/MarkwonNodeRenderer.java create mode 100644 markwon-core/src/main/java/ru/noties/markwon/MarkwonReducer.java 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 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 +