Add reducer and nodeRenderer
This commit is contained in:
		
							parent
							
								
									12ef0b5703
								
							
						
					
					
						commit
						128612d5b2
					
				| @ -19,7 +19,7 @@ | ||||
|       @click="selectAll" | ||||
|     > | ||||
|       <div class="selected-artifact-script"> | ||||
|         <span class="token keyword">final def</span> markwon_version = <span class="token string">'latest_version'</span> | ||||
|         <span class="token keyword">final def</span> markwon_version = <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: { | ||||
|  | ||||
| @ -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) | ||||
|  | ||||
| @ -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; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -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(); | ||||
|  | ||||
| @ -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) { | ||||
|                     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<String> 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; | ||||
|         } | ||||
|  | ||||
| @ -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) { | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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> | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Dimitry Ivanov
						Dimitry Ivanov