Add reducer and nodeRenderer

This commit is contained in:
Dimitry Ivanov 2019-03-03 16:25:23 +03:00
parent 12ef0b5703
commit 128612d5b2
10 changed files with 280 additions and 93 deletions

View File

@ -19,7 +19,7 @@
@click="selectAll"
>
<div class="selected-artifact-script">
<span class="token keyword">final def</span>&nbsp;markwon_version =&nbsp;<span class="token string">'latest_version'</span>
<span class="token keyword">final def</span>&nbsp;markwon_version =&nbsp;<span class="token string">'{{latestVersion}}'</span>
</div>
<br>
<div class="selected-artifact-script" v-for="artifact in selectedArtifacts">
@ -42,7 +42,8 @@ export default {
data() {
return {
artifacts,
selected: ['core']
selected: ['core'],
latestVersion: 'latest_version'
};
},
methods: {

View File

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

View File

@ -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<N extends Node> {
/**
* 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<Node> 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<Node> defaultViewProvider;
private MarkwonReducer reducer;
private Map<Class<? extends Node>, ViewProvider<Node>> viewProviders;
private LayoutInflater inflater;
public Builder(@NonNull ViewProvider<Node> defaultViewProvider) {
this.defaultViewProvider = defaultViewProvider;
this.viewProviders = new HashMap<>(3);
}
@NonNull
public Builder reducer(@NonNull MarkwonReducer reducer) {
this.reducer = reducer;
return this;
}
@NonNull
public <N extends Node> Builder viewProvider(
@NonNull Class<N> type,
@NonNull ViewProvider<? super N> viewProvider) {
//noinspection unchecked
viewProviders.put(type, (ViewProvider<Node>) 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<Node> {
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<Class<? extends Node>, ViewProvider<Node>> viewProviders;
private final ViewProvider<Node> 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<Node> 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<Node> viewProvider(@NonNull Node node) {
// check for specific node view provider
final ViewProvider<Node> provider = viewProviders.get(node.getClass());
if (provider != null) {
return provider;
}
// if it's not present, then we can return a default one
return defaultViewProvider;
}
}
}

View File

@ -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<Node> reduce(@NonNull Node node);
static class DirectChildren extends MarkwonReducer {
@NonNull
@Override
public List<Node> reduce(@NonNull Node root) {
final List<Node> 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;
}
}
}

View File

@ -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<String> tagNames, @NonNull TagHandler tagHandler);
@NonNull
Builder removeHandler(@NonNull String tagName);
@NonNull
Builder removeHandlers(@NonNull String... tagNames);
Builder setHandler(@NonNull Collection<String> tagNames, @Nullable TagHandler tagHandler);
@NonNull
MarkwonHtmlRenderer build();

View File

@ -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<String> tagNames, @NonNull TagHandler tagHandler) {
for (String tagName : tagNames) {
if (tagName != null) {
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) {
public Builder setHandler(@NonNull Collection<String> tagNames, @Nullable TagHandler tagHandler) {
if (tagHandler == null) {
for (String tagName : tagNames) {
if (tagName != null) {
tagHandlers.remove(tagName);
}
} else {
for (String tagName : tagNames) {
tagHandlers.put(tagName, tagHandler);
}
}
return this;
}

View File

@ -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<TaskListBlock>() {
@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<TaskListItem>() {
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull TaskListItem taskListItem) {

View File

@ -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.Adapter<MarkwonAdapter
* @see #include(Class, Entry)
* @see #defaultEntry(int)
* @see #defaultEntry(Entry)
* @see #reducer(Reducer)
* @see #reducer(MarkwonReducer)
*/
public interface Builder {
@ -136,15 +137,15 @@ public abstract class MarkwonAdapter extends RecyclerView.Adapter<MarkwonAdapter
/**
* Specify how root Node will be <em>reduced</em> 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<MarkwonAdapter
void clear();
}
public interface Reducer {
@NonNull
List<Node> reduce(@NonNull Node root);
}
public abstract void setMarkdown(@NonNull Markwon markwon, @NonNull String markdown);
public abstract void setParsedMarkdown(@NonNull Markwon markwon, @NonNull Node document);

View File

@ -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<Entry<Node, Holder>> entries;
private final Entry<Node, Holder> defaultEntry;
private final Reducer reducer;
private final MarkwonReducer reducer;
private LayoutInflater layoutInflater;
@ -28,7 +28,7 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
MarkwonAdapterImpl(
@NonNull SparseArray<Entry<Node, Holder>> entries,
@NonNull Entry<Node, Holder> 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<Entry<Node, Holder>> entries = new SparseArray<>(3);
private Entry<Node, Holder> 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<Node> reduce(@NonNull Node root) {
final List<Node> 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;
}
}
}

View File

@ -2,12 +2,13 @@
<string name="app_name">MarkwonSample</string>
<!--do not indent (will be treated as code block otherwise)-->
<string name="input"><![CDATA[
# Hello! @ic-android-black-24\n\n
Home 36 black: @ic-home-black-36\n\n
Memory 48 black: @ic-memory-black-48\n\n
### I AM ANOTHER HEADER\n\n
Sentiment Satisfied 64 red: @ic-sentiment_satisfied-red-64
# Hello! @ic-android-black-24\n\n
Home 36 black: @ic-home-black-36\n\n
Memory 48 black: @ic-memory-black-48\n\n
### I AM ANOTHER HEADER\n\n
Sentiment Satisfied 64 red: @ic-sentiment_satisfied-red-64
]]>
</string>