Working on documentation. Table rendering sample
This commit is contained in:
		
							parent
							
								
									cc75a92c7f
								
							
						
					
					
						commit
						96ca96fa70
					
				| @ -1 +1,125 @@ | |||||||
| # Images | # Images | ||||||
|  | 
 | ||||||
|  | Starting with <Badge text="3.0.0" /> `Markwon` comes with `ImagesPlugin` | ||||||
|  | which supports `http(s)`, `file` and `data` schemes and default media | ||||||
|  | decoder (for simple images, no [SVG](/docs/image/svg.md) or [GIF](/docs/image/gif.md) which | ||||||
|  | are defined in standalone modules). | ||||||
|  | 
 | ||||||
|  | ## ImagesPlugin | ||||||
|  | 
 | ||||||
|  | `ImagePlugin` takes care of _obtaining_ image resource, decoding it and displaying it in a `TextView`. | ||||||
|  | 
 | ||||||
|  | :::warning | ||||||
|  | Although `core` artifact contains `ImagesPlugin` one must  | ||||||
|  | still **explicitly** register the `ImagesPlugin` on resulting `Markwon` | ||||||
|  | instance. | ||||||
|  | ```java | ||||||
|  | final Markwon markwon = Markwon.builder(context) | ||||||
|  |         .usePlugin(ImagesPlugin.create()) | ||||||
|  | ``` | ||||||
|  | ::: | ||||||
|  | 
 | ||||||
|  | There are 2 factory methods to obtain `ImagesPlugin`: | ||||||
|  | * `ImagesPlugin#create(Context)` | ||||||
|  | * `ImagesPlugin#createWithAssets(Context)` | ||||||
|  | 
 | ||||||
|  | The first one `#create(Context)` configures: | ||||||
|  | * `FileSchemeHandler` that allows obtaining images from `file://` uris | ||||||
|  | * `DataUriSchemeHandler` that allows _inlining_ images with `data:`  | ||||||
|  |   scheme (`data:image/svg+xml;base64,MTIz`) | ||||||
|  | * `NetworkSchemeHandler` that allows obtaining images from `http://` and `https://` uris | ||||||
|  |   (internally it uses `HttpURLConnection`) | ||||||
|  | * `ImageMediaDecoder` which _tries_ to decode all encountered images as regular ones (png, jpg, etc) | ||||||
|  | 
 | ||||||
|  | The second one `#createWithAssets(Context)` does the same but also adds support | ||||||
|  | for images that reside in `assets` folder of your application and | ||||||
|  | referenced by `file:///android_asset/{path}` uri. | ||||||
|  | 
 | ||||||
|  | `ImagesPlugin` also _prepares_ a TextView to display images. Due to asynchronous | ||||||
|  | nature of image loading, there must be a way to invalidate resulting Spanned  | ||||||
|  | content after an image is loaded. | ||||||
|  | 
 | ||||||
|  | :::warning | ||||||
|  | Images come with few limitations. For of all, they work with a **TextView only**. | ||||||
|  | This is due to the fact that there is no way to invalidate a `Spanned` content | ||||||
|  | by itself (without context in which it is displayed). So, if `Markwon` is used, | ||||||
|  | for example, to display a `Toast` with an image: | ||||||
|  | 
 | ||||||
|  | ```java | ||||||
|  | final Spanned spanned = markwon.toMarkdown("Hello "); | ||||||
|  | Toast.makeText(context, spanned, Toast.LENGTH_LONG).show(); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Image _probably_ won't be displayed. As a workaround for `Toast` a custom `View` | ||||||
|  | can be used: | ||||||
|  | 
 | ||||||
|  | ```java | ||||||
|  | final Spanned spanned = markwon.toMarkdown("Hello "); | ||||||
|  | 
 | ||||||
|  | final View view = createToastView(); | ||||||
|  | final TextView textView = view.findViewById(R.id.text_view); | ||||||
|  | markwon.setParsedMarkdown(textView, spanned); | ||||||
|  | 
 | ||||||
|  | final Toast toast = new Toast(context); | ||||||
|  | toast.setView(view); | ||||||
|  | // other Toast configurations | ||||||
|  | toast.show(); | ||||||
|  | ``` | ||||||
|  | ::: | ||||||
|  | 
 | ||||||
|  | ## SchemeHandler | ||||||
|  | 
 | ||||||
|  | To add support for different schemes (or customize provided) a `SchemeHandler` must be used. | ||||||
|  | 
 | ||||||
|  | ```java | ||||||
|  | final Markwon markwon = Markwon.builder(context) | ||||||
|  |         .usePlugin(ImagesPlugin.create(context)) | ||||||
|  |         .usePlugin(new AbstractMarkwonPlugin() { | ||||||
|  |             @Override | ||||||
|  |             public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) { | ||||||
|  |                 // example only, Markwon doesn't come with a ftp scheme handler | ||||||
|  |                 builder.addSchemeHandler("ftp", new FtpSchemeHandler()); | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |         .build(); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | It's a class to _convert_ an URI into an `InputStream`: | ||||||
|  | 
 | ||||||
|  | ```java | ||||||
|  | public abstract class SchemeHandler { | ||||||
|  | 
 | ||||||
|  |     @Nullable | ||||||
|  |     public abstract ImageItem handle(@NonNull String raw, @NonNull Uri uri); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | `ImageItem` is a holder class for resulting `InputStream` and (optional) | ||||||
|  | content type: | ||||||
|  | 
 | ||||||
|  | ```java | ||||||
|  | public class ImageItem { | ||||||
|  | 
 | ||||||
|  |     private final String contentType; | ||||||
|  |     private final InputStream inputStream; | ||||||
|  | 
 | ||||||
|  |     /* rest omitted */ | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Based on `contentType` returned a corresponding `MediaDecoder` will be matched. | ||||||
|  | If no `MediaDecoder` can handle given `contentType` then a default media decoder will | ||||||
|  | be used. | ||||||
|  | 
 | ||||||
|  | ## MediaDecoder | ||||||
|  | 
 | ||||||
|  | :::tip | ||||||
|  | If you are using [html](/docs/html/) you do not have to additionally setup | ||||||
|  | images displayed via `<img>` tag, as `HtmlPlugin` automatically uses configured | ||||||
|  | image loader. But images referenced in HTML come with additional support for | ||||||
|  | sizes, which is not supported natively by markdown, allowing absolute or relative sizes: | ||||||
|  | 
 | ||||||
|  | ```html | ||||||
|  | <img src="./assets/my-image" width="100%"> | ||||||
|  | ``` | ||||||
|  | ::: | ||||||
| @ -2,6 +2,23 @@ | |||||||
| 
 | 
 | ||||||
| Here is the list of properties that can be configured via `MarkwonTheme.Builder` class.  | Here is the list of properties that can be configured via `MarkwonTheme.Builder` class.  | ||||||
| 
 | 
 | ||||||
|  | :::tip | ||||||
|  | Starting with <Badge text="3.0.0" /> there is no need to manually construct a `MarkwonTheme`. | ||||||
|  | Instead a `Plugin` should be used: | ||||||
|  | ```java | ||||||
|  | final Markwon markwon = Markwon.builder(context) | ||||||
|  |         .usePlugin(new AbstractMarkwonPlugin() { | ||||||
|  |             @Override | ||||||
|  |             public void configureTheme(@NonNull MarkwonTheme.Builder builder) { | ||||||
|  |                 builder | ||||||
|  |                         .codeTextColor(Color.BLACK) | ||||||
|  |                         .codeBackgroundColor(Color.GREEN); | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |         .build(); | ||||||
|  | ``` | ||||||
|  | ::: | ||||||
|  | 
 | ||||||
| ## Link color | ## Link color | ||||||
| 
 | 
 | ||||||
| Controls the color of a [link](#) | Controls the color of a [link](#) | ||||||
|  | |||||||
| @ -10,7 +10,6 @@ import org.commonmark.ext.gfm.tables.TableHead; | |||||||
| import org.commonmark.ext.gfm.tables.TableRow; | import org.commonmark.ext.gfm.tables.TableRow; | ||||||
| import org.commonmark.node.AbstractVisitor; | import org.commonmark.node.AbstractVisitor; | ||||||
| import org.commonmark.node.CustomNode; | import org.commonmark.node.CustomNode; | ||||||
| import org.commonmark.node.Node; |  | ||||||
| 
 | 
 | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| @ -160,21 +159,13 @@ public class Table { | |||||||
| 
 | 
 | ||||||
|                 final TableCell cell = (TableCell) customNode; |                 final TableCell cell = (TableCell) customNode; | ||||||
| 
 | 
 | ||||||
|                 final Node firstChild = cell.getFirstChild(); |                 if (pendingRow == null) { | ||||||
| 
 |                     pendingRow = new ArrayList<>(2); | ||||||
|                 // need to investigate why... (most likely initial node is modified by someone) |  | ||||||
|                 if (firstChild != null) { |  | ||||||
| 
 |  | ||||||
|                     if (pendingRow == null) { |  | ||||||
|                         pendingRow = new ArrayList<>(2); |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     // let's TRY to not visit this node but instead try to render its first child |  | ||||||
| 
 |  | ||||||
|                     pendingRow.add(new Table.Column(alignment(cell.getAlignment()), markwon.render(firstChild))); |  | ||||||
|                     pendingRowIsHeader = cell.isHeader(); |  | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|  |                 pendingRow.add(new Table.Column(alignment(cell.getAlignment()), markwon.render(cell))); | ||||||
|  |                 pendingRowIsHeader = cell.isHeader(); | ||||||
|  | 
 | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
| @ -215,6 +206,4 @@ public class Table { | |||||||
|             return out; |             return out; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -67,7 +67,7 @@ public abstract class MarkwonAdapter extends RecyclerView.Adapter<MarkwonAdapter | |||||||
|      * @see SimpleEntry |      * @see SimpleEntry | ||||||
|      */ |      */ | ||||||
|     @NonNull |     @NonNull | ||||||
|     public static MarkwonAdapter create(@NonNull Entry<? extends Holder, ? extends Node> defaultEntry) { |     public static MarkwonAdapter create(@NonNull Entry<? extends Node, ? extends Holder> defaultEntry) { | ||||||
|         return new MarkwonAdapterImpl.BuilderImpl().defaultEntry(defaultEntry).build(); |         return new MarkwonAdapterImpl.BuilderImpl().defaultEntry(defaultEntry).build(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -109,7 +109,7 @@ public abstract class MarkwonAdapter extends RecyclerView.Adapter<MarkwonAdapter | |||||||
|         @NonNull |         @NonNull | ||||||
|         <N extends Node> Builder include( |         <N extends Node> Builder include( | ||||||
|                 @NonNull Class<N> node, |                 @NonNull Class<N> node, | ||||||
|                 @NonNull Entry<? extends Holder, ? super N> entry); |                 @NonNull Entry<? super N, ? extends Holder> entry); | ||||||
| 
 | 
 | ||||||
|         /** |         /** | ||||||
|          * Specify which {@link Entry} to use for all non-explicitly registered nodes |          * Specify which {@link Entry} to use for all non-explicitly registered nodes | ||||||
| @ -119,7 +119,7 @@ public abstract class MarkwonAdapter extends RecyclerView.Adapter<MarkwonAdapter | |||||||
|          * @see SimpleEntry |          * @see SimpleEntry | ||||||
|          */ |          */ | ||||||
|         @NonNull |         @NonNull | ||||||
|         Builder defaultEntry(@NonNull Entry<? extends Holder, ? extends Node> defaultEntry); |         Builder defaultEntry(@NonNull Entry<? extends Node, ? extends Holder> defaultEntry); | ||||||
| 
 | 
 | ||||||
|         /** |         /** | ||||||
|          * Specify which layout {@link SimpleEntry} will use to render all non-explicitly |          * Specify which layout {@link SimpleEntry} will use to render all non-explicitly | ||||||
| @ -156,7 +156,7 @@ public abstract class MarkwonAdapter extends RecyclerView.Adapter<MarkwonAdapter | |||||||
|     /** |     /** | ||||||
|      * @see SimpleEntry |      * @see SimpleEntry | ||||||
|      */ |      */ | ||||||
|     public interface Entry<H extends Holder, N extends Node> { |     public interface Entry<N extends Node, H extends Holder> { | ||||||
| 
 | 
 | ||||||
|         @NonNull |         @NonNull | ||||||
|         H createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent); |         H createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent); | ||||||
|  | |||||||
| @ -15,8 +15,8 @@ import ru.noties.markwon.Markwon; | |||||||
| 
 | 
 | ||||||
| class MarkwonAdapterImpl extends MarkwonAdapter { | class MarkwonAdapterImpl extends MarkwonAdapter { | ||||||
| 
 | 
 | ||||||
|     private final SparseArray<Entry<Holder, Node>> entries; |     private final SparseArray<Entry<Node, Holder>> entries; | ||||||
|     private final Entry<Holder, Node> defaultEntry; |     private final Entry<Node, Holder> defaultEntry; | ||||||
|     private final Reducer reducer; |     private final Reducer reducer; | ||||||
| 
 | 
 | ||||||
|     private LayoutInflater layoutInflater; |     private LayoutInflater layoutInflater; | ||||||
| @ -26,8 +26,8 @@ class MarkwonAdapterImpl extends MarkwonAdapter { | |||||||
| 
 | 
 | ||||||
|     @SuppressWarnings("WeakerAccess") |     @SuppressWarnings("WeakerAccess") | ||||||
|     MarkwonAdapterImpl( |     MarkwonAdapterImpl( | ||||||
|             @NonNull SparseArray<Entry<Holder, Node>> entries, |             @NonNull SparseArray<Entry<Node, Holder>> entries, | ||||||
|             @NonNull Entry<Holder, Node> defaultEntry, |             @NonNull Entry<Node, Holder> defaultEntry, | ||||||
|             @NonNull Reducer reducer) { |             @NonNull Reducer reducer) { | ||||||
|         this.entries = entries; |         this.entries = entries; | ||||||
|         this.defaultEntry = defaultEntry; |         this.defaultEntry = defaultEntry; | ||||||
| @ -67,7 +67,7 @@ class MarkwonAdapterImpl extends MarkwonAdapter { | |||||||
|             layoutInflater = LayoutInflater.from(parent.getContext()); |             layoutInflater = LayoutInflater.from(parent.getContext()); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         final Entry<Holder, Node> entry = getEntry(viewType); |         final Entry<Node, Holder> entry = getEntry(viewType); | ||||||
| 
 | 
 | ||||||
|         return entry.createHolder(layoutInflater, parent); |         return entry.createHolder(layoutInflater, parent); | ||||||
|     } |     } | ||||||
| @ -78,7 +78,7 @@ class MarkwonAdapterImpl extends MarkwonAdapter { | |||||||
|         final Node node = nodes.get(position); |         final Node node = nodes.get(position); | ||||||
|         final int viewType = getNodeViewType(node.getClass()); |         final int viewType = getNodeViewType(node.getClass()); | ||||||
| 
 | 
 | ||||||
|         final Entry<Holder, Node> entry = getEntry(viewType); |         final Entry<Node, Holder> entry = getEntry(viewType); | ||||||
| 
 | 
 | ||||||
|         entry.bindHolder(markwon, holder, node); |         entry.bindHolder(markwon, holder, node); | ||||||
|     } |     } | ||||||
| @ -107,7 +107,7 @@ class MarkwonAdapterImpl extends MarkwonAdapter { | |||||||
|     public long getItemId(int position) { |     public long getItemId(int position) { | ||||||
|         final Node node = nodes.get(position); |         final Node node = nodes.get(position); | ||||||
|         final int type = getNodeViewType(node.getClass()); |         final int type = getNodeViewType(node.getClass()); | ||||||
|         final Entry<Holder, Node> entry = getEntry(type); |         final Entry<Node, Holder> entry = getEntry(type); | ||||||
|         return entry.id(node); |         return entry.id(node); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -122,7 +122,7 @@ class MarkwonAdapterImpl extends MarkwonAdapter { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @NonNull |     @NonNull | ||||||
|     private Entry<Holder, Node> getEntry(int viewType) { |     private Entry<Node, Holder> getEntry(int viewType) { | ||||||
|         return viewType == 0 |         return viewType == 0 | ||||||
|                 ? defaultEntry |                 ? defaultEntry | ||||||
|                 : entries.get(viewType); |                 : entries.get(viewType); | ||||||
| @ -130,26 +130,26 @@ class MarkwonAdapterImpl extends MarkwonAdapter { | |||||||
| 
 | 
 | ||||||
|     static class BuilderImpl implements Builder { |     static class BuilderImpl implements Builder { | ||||||
| 
 | 
 | ||||||
|         private final SparseArray<Entry<Holder, Node>> entries = new SparseArray<>(3); |         private final SparseArray<Entry<Node, Holder>> entries = new SparseArray<>(3); | ||||||
| 
 | 
 | ||||||
|         private Entry<Holder, Node> defaultEntry; |         private Entry<Node, Holder> defaultEntry; | ||||||
|         private Reducer reducer; |         private Reducer reducer; | ||||||
| 
 | 
 | ||||||
|         @NonNull |         @NonNull | ||||||
|         @Override |         @Override | ||||||
|         public <N extends Node> Builder include( |         public <N extends Node> Builder include( | ||||||
|                 @NonNull Class<N> node, |                 @NonNull Class<N> node, | ||||||
|                 @NonNull Entry<? extends Holder, ? super N> entry) { |                 @NonNull Entry<? super N, ? extends Holder> entry) { | ||||||
|             //noinspection unchecked |             //noinspection unchecked | ||||||
|             entries.append(node.hashCode(), (Entry<Holder, Node>) entry); |             entries.append(node.hashCode(), (Entry<Node, Holder>) entry); | ||||||
|             return this; |             return this; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         @NonNull |         @NonNull | ||||||
|         @Override |         @Override | ||||||
|         public Builder defaultEntry(@NonNull Entry<? extends Holder, ? extends Node> defaultEntry) { |         public Builder defaultEntry(@NonNull Entry<? extends Node, ? extends Holder> defaultEntry) { | ||||||
|             //noinspection unchecked |             //noinspection unchecked | ||||||
|             this.defaultEntry = (Entry<Holder, Node>) defaultEntry; |             this.defaultEntry = (Entry<Node, Holder>) defaultEntry; | ||||||
|             return this; |             return this; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -157,7 +157,7 @@ class MarkwonAdapterImpl extends MarkwonAdapter { | |||||||
|         @Override |         @Override | ||||||
|         public Builder defaultEntry(int layoutResId) { |         public Builder defaultEntry(int layoutResId) { | ||||||
|             //noinspection unchecked |             //noinspection unchecked | ||||||
|             this.defaultEntry = (Entry<Holder, Node>) (Entry) new SimpleEntry(layoutResId); |             this.defaultEntry = (Entry<Node, Holder>) (Entry) new SimpleEntry(layoutResId); | ||||||
|             return this; |             return this; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -174,7 +174,7 @@ class MarkwonAdapterImpl extends MarkwonAdapter { | |||||||
| 
 | 
 | ||||||
|             if (defaultEntry == null) { |             if (defaultEntry == null) { | ||||||
|                 //noinspection unchecked |                 //noinspection unchecked | ||||||
|                 defaultEntry = (Entry<Holder, Node>) (Entry) new SimpleEntry(); |                 defaultEntry = (Entry<Node, Holder>) (Entry) new SimpleEntry(); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if (reducer == null) { |             if (reducer == null) { | ||||||
|  | |||||||
| @ -21,7 +21,7 @@ import ru.noties.markwon.Markwon; | |||||||
|  * @since 3.0.0 |  * @since 3.0.0 | ||||||
|  */ |  */ | ||||||
| @SuppressWarnings("WeakerAccess") | @SuppressWarnings("WeakerAccess") | ||||||
| public class SimpleEntry implements MarkwonAdapter.Entry<SimpleEntry.Holder, Node> { | public class SimpleEntry implements MarkwonAdapter.Entry<Node, SimpleEntry.Holder> { | ||||||
| 
 | 
 | ||||||
|     public static final Spannable.Factory NO_COPY_SPANNABLE_FACTORY = new NoCopySpannableFactory(); |     public static final Spannable.Factory NO_COPY_SPANNABLE_FACTORY = new NoCopySpannableFactory(); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -8,8 +8,8 @@ | |||||||
|     <application |     <application | ||||||
|         android:allowBackup="true" |         android:allowBackup="true" | ||||||
|         android:icon="@mipmap/ic_launcher" |         android:icon="@mipmap/ic_launcher" | ||||||
|         android:roundIcon="@mipmap/ic_launcher" |  | ||||||
|         android:label="@string/app_name" |         android:label="@string/app_name" | ||||||
|  |         android:roundIcon="@mipmap/ic_launcher" | ||||||
|         android:theme="@style/AppTheme" |         android:theme="@style/AppTheme" | ||||||
|         tools:ignore="AllowBackup,GoogleAppIndexingWarning"> |         tools:ignore="AllowBackup,GoogleAppIndexingWarning"> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -11,27 +11,29 @@ import android.support.v7.widget.RecyclerView; | |||||||
| import android.text.TextUtils; | import android.text.TextUtils; | ||||||
| 
 | 
 | ||||||
| import org.commonmark.ext.gfm.tables.TableBlock; | import org.commonmark.ext.gfm.tables.TableBlock; | ||||||
|  | import org.commonmark.ext.gfm.tables.TablesExtension; | ||||||
| import org.commonmark.node.FencedCodeBlock; | import org.commonmark.node.FencedCodeBlock; | ||||||
|  | import org.commonmark.parser.Parser; | ||||||
| 
 | 
 | ||||||
| import java.io.BufferedReader; | import java.io.BufferedReader; | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.io.InputStream; | import java.io.InputStream; | ||||||
| import java.io.InputStreamReader; | import java.io.InputStreamReader; | ||||||
|  | import java.util.Collections; | ||||||
| 
 | 
 | ||||||
| import ru.noties.debug.AndroidLogDebugOutput; | import ru.noties.debug.AndroidLogDebugOutput; | ||||||
| import ru.noties.debug.Debug; | import ru.noties.debug.Debug; | ||||||
| import ru.noties.markwon.AbstractMarkwonPlugin; | import ru.noties.markwon.AbstractMarkwonPlugin; | ||||||
| import ru.noties.markwon.Markwon; | import ru.noties.markwon.Markwon; | ||||||
| import ru.noties.markwon.MarkwonConfiguration; | import ru.noties.markwon.MarkwonConfiguration; | ||||||
|  | import ru.noties.markwon.MarkwonVisitor; | ||||||
| import ru.noties.markwon.core.CorePlugin; | import ru.noties.markwon.core.CorePlugin; | ||||||
| import ru.noties.markwon.ext.tables.TablePlugin; |  | ||||||
| import ru.noties.markwon.html.HtmlPlugin; | import ru.noties.markwon.html.HtmlPlugin; | ||||||
| import ru.noties.markwon.image.ImagesPlugin; | import ru.noties.markwon.image.ImagesPlugin; | ||||||
| import ru.noties.markwon.image.svg.SvgPlugin; | import ru.noties.markwon.image.svg.SvgPlugin; | ||||||
| import ru.noties.markwon.recycler.MarkwonAdapter; | import ru.noties.markwon.recycler.MarkwonAdapter; | ||||||
| import ru.noties.markwon.recycler.SimpleEntry; | import ru.noties.markwon.recycler.SimpleEntry; | ||||||
| import ru.noties.markwon.sample.R; | import ru.noties.markwon.sample.R; | ||||||
| import ru.noties.markwon.syntax.SyntaxHighlightPlugin; |  | ||||||
| import ru.noties.markwon.urlprocessor.UrlProcessor; | import ru.noties.markwon.urlprocessor.UrlProcessor; | ||||||
| import ru.noties.markwon.urlprocessor.UrlProcessorRelativeToAbsolute; | import ru.noties.markwon.urlprocessor.UrlProcessorRelativeToAbsolute; | ||||||
| 
 | 
 | ||||||
| @ -54,7 +56,7 @@ public class RecyclerActivity extends Activity { | |||||||
|                 // with `@+id/text` id |                 // with `@+id/text` id | ||||||
|                 .include(FencedCodeBlock.class, new SimpleEntry(R.layout.adapter_fenced_code_block)) |                 .include(FencedCodeBlock.class, new SimpleEntry(R.layout.adapter_fenced_code_block)) | ||||||
|                 // create own implementation of entry for different rendering |                 // create own implementation of entry for different rendering | ||||||
|                 .include(TableBlock.class, new TableEntry()) |                 .include(TableBlock.class, new TableEntry2()) | ||||||
|                 // specify default entry (for all other blocks) |                 // specify default entry (for all other blocks) | ||||||
|                 .defaultEntry(new SimpleEntry(R.layout.adapter_default_entry)) |                 .defaultEntry(new SimpleEntry(R.layout.adapter_default_entry)) | ||||||
|                 .build(); |                 .build(); | ||||||
| @ -81,15 +83,35 @@ public class RecyclerActivity extends Activity { | |||||||
|                 .usePlugin(CorePlugin.create()) |                 .usePlugin(CorePlugin.create()) | ||||||
|                 .usePlugin(ImagesPlugin.createWithAssets(context)) |                 .usePlugin(ImagesPlugin.createWithAssets(context)) | ||||||
|                 .usePlugin(SvgPlugin.create(context.getResources())) |                 .usePlugin(SvgPlugin.create(context.getResources())) | ||||||
|                 // although we will be rendering table differently we still need |                 .usePlugin(new AbstractMarkwonPlugin() { | ||||||
|                 // to register commonmark-java tables extension (which TablePlugin does) |                     @Override | ||||||
|                 .usePlugin(TablePlugin.create(context)) |                     public void configureParser(@NonNull Parser.Builder builder) { | ||||||
|  |                         // it's important NOT to use TablePlugin | ||||||
|  |                         // the only thing we want from it is commonmark-java parser extension | ||||||
|  |                         builder.extensions(Collections.singleton(TablesExtension.create())); | ||||||
|  |                     } | ||||||
|  |                 }) | ||||||
|                 .usePlugin(HtmlPlugin.create()) |                 .usePlugin(HtmlPlugin.create()) | ||||||
|  | //                .usePlugin(SyntaxHighlightPlugin.create()) | ||||||
|                 .usePlugin(new AbstractMarkwonPlugin() { |                 .usePlugin(new AbstractMarkwonPlugin() { | ||||||
|                     @Override |                     @Override | ||||||
|                     public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { |                     public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { | ||||||
|                         builder.urlProcessor(new UrlProcessorInitialReadme()); |                         builder.urlProcessor(new UrlProcessorInitialReadme()); | ||||||
|                     } |                     } | ||||||
|  | 
 | ||||||
|  |                     @Override | ||||||
|  |                     public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { | ||||||
|  |                         builder.on(FencedCodeBlock.class, (visitor, fencedCodeBlock) -> { | ||||||
|  |                             // we actually won't be applying code spans here, as our custom view will | ||||||
|  |                             // draw background and apply mono typeface | ||||||
|  |                             // | ||||||
|  |                             // NB the `trim` operation on literal (as code will have a new line at the end) | ||||||
|  |                             final CharSequence code = visitor.configuration() | ||||||
|  |                                     .syntaxHighlight() | ||||||
|  |                                     .highlight(fencedCodeBlock.getInfo(), fencedCodeBlock.getLiteral().trim()); | ||||||
|  |                             visitor.builder().append(code); | ||||||
|  |                         }); | ||||||
|  |                     } | ||||||
|                 }) |                 }) | ||||||
|                 .build(); |                 .build(); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -17,7 +17,7 @@ import ru.noties.markwon.recycler.MarkwonAdapter; | |||||||
| import ru.noties.markwon.sample.R; | import ru.noties.markwon.sample.R; | ||||||
| 
 | 
 | ||||||
| // do not use in real applications, this is just a showcase | // do not use in real applications, this is just a showcase | ||||||
| public class TableEntry implements MarkwonAdapter.Entry<TableEntry.TableNodeHolder, TableBlock> { | public class TableEntry implements MarkwonAdapter.Entry<TableBlock, TableEntry.TableNodeHolder> { | ||||||
| 
 | 
 | ||||||
|     private final Map<TableBlock, Table> cache = new HashMap<>(2); |     private final Map<TableBlock, Table> cache = new HashMap<>(2); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -0,0 +1,121 @@ | |||||||
|  | package ru.noties.markwon.sample.recycler; | ||||||
|  | 
 | ||||||
|  | import android.annotation.SuppressLint; | ||||||
|  | import android.content.Context; | ||||||
|  | import android.support.annotation.NonNull; | ||||||
|  | import android.view.Gravity; | ||||||
|  | import android.view.LayoutInflater; | ||||||
|  | import android.view.View; | ||||||
|  | import android.view.ViewGroup; | ||||||
|  | import android.widget.TableLayout; | ||||||
|  | import android.widget.TableRow; | ||||||
|  | import android.widget.TextView; | ||||||
|  | 
 | ||||||
|  | import org.commonmark.ext.gfm.tables.TableBlock; | ||||||
|  | 
 | ||||||
|  | import java.util.HashMap; | ||||||
|  | import java.util.Map; | ||||||
|  | 
 | ||||||
|  | import ru.noties.markwon.Markwon; | ||||||
|  | import ru.noties.markwon.ext.tables.Table; | ||||||
|  | import ru.noties.markwon.recycler.MarkwonAdapter; | ||||||
|  | import ru.noties.markwon.sample.R; | ||||||
|  | 
 | ||||||
|  | public class TableEntry2 implements MarkwonAdapter.Entry<TableBlock, TableEntry2.TableHolder> { | ||||||
|  | 
 | ||||||
|  |     private final Map<TableBlock, Table> map = new HashMap<>(3); | ||||||
|  | 
 | ||||||
|  |     @NonNull | ||||||
|  |     @Override | ||||||
|  |     public TableHolder createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) { | ||||||
|  |         return new TableHolder(inflater.inflate(R.layout.adapter_table_block_2, parent, false)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void bindHolder(@NonNull Markwon markwon, @NonNull TableHolder holder, @NonNull TableBlock node) { | ||||||
|  | 
 | ||||||
|  |         Table table = map.get(node); | ||||||
|  |         if (table == null) { | ||||||
|  |             table = Table.parse(markwon, node); | ||||||
|  |             map.put(node, table); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // check if this exact TableBlock was already | ||||||
|  |         final TableLayout layout = holder.layout; | ||||||
|  |         if (table == null | ||||||
|  |                 || table == layout.getTag(R.id.table_layout)) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         layout.setTag(R.id.table_layout, table); | ||||||
|  |         layout.removeAllViews(); | ||||||
|  |         layout.setBackgroundResource(R.drawable.bg_table_cell); | ||||||
|  | 
 | ||||||
|  |         final Context context = layout.getContext(); | ||||||
|  |         final LayoutInflater inflater = LayoutInflater.from(context); | ||||||
|  | 
 | ||||||
|  |         TableRow tableRow; | ||||||
|  |         TextView textView; | ||||||
|  | 
 | ||||||
|  |         for (Table.Row row : table.rows()) { | ||||||
|  |             tableRow = new TableRow(context); | ||||||
|  |             for (Table.Column column : row.columns()) { | ||||||
|  |                 textView = (TextView) inflater.inflate(R.layout.view_table_entry_cell, tableRow, false); | ||||||
|  |                 textView.setGravity(textGravity(column.alignment())); | ||||||
|  |                 markwon.setParsedMarkdown(textView, column.content()); | ||||||
|  |                 textView.getPaint().setFakeBoldText(row.header()); | ||||||
|  |                 textView.setBackgroundResource(R.drawable.bg_table_cell); | ||||||
|  |                 tableRow.addView(textView); | ||||||
|  |             } | ||||||
|  |             layout.addView(tableRow); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public long id(@NonNull TableBlock node) { | ||||||
|  |         return node.hashCode(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void clear() { | ||||||
|  |         map.clear(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     static class TableHolder extends MarkwonAdapter.Holder { | ||||||
|  | 
 | ||||||
|  |         final TableLayout layout; | ||||||
|  | 
 | ||||||
|  |         TableHolder(@NonNull View itemView) { | ||||||
|  |             super(itemView); | ||||||
|  | 
 | ||||||
|  |             this.layout = requireView(R.id.table_layout); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // we will use gravity instead of textAlignment because min sdk is 16 (textAlignment starts at 17) | ||||||
|  |     @SuppressLint("RtlHardcoded") | ||||||
|  |     private static int textGravity(@NonNull Table.Alignment alignment) { | ||||||
|  | 
 | ||||||
|  |         final int gravity; | ||||||
|  | 
 | ||||||
|  |         switch (alignment) { | ||||||
|  | 
 | ||||||
|  |             case LEFT: | ||||||
|  |                 gravity = Gravity.LEFT; | ||||||
|  |                 break; | ||||||
|  | 
 | ||||||
|  |             case CENTER: | ||||||
|  |                 gravity = Gravity.CENTER_HORIZONTAL; | ||||||
|  |                 break; | ||||||
|  | 
 | ||||||
|  |             case RIGHT: | ||||||
|  |                 gravity = Gravity.RIGHT; | ||||||
|  |                 break; | ||||||
|  | 
 | ||||||
|  |             default: | ||||||
|  |                 throw new IllegalStateException("Unknown table alignment: " + alignment); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return gravity | Gravity.CENTER_VERTICAL; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -54,7 +54,6 @@ public class TableEntryView extends LinearLayout { | |||||||
| 
 | 
 | ||||||
|                 rowEvenBackgroundColor = array.getColor(R.styleable.TableEntryView_tev_rowEvenBackgroundColor, 0); |                 rowEvenBackgroundColor = array.getColor(R.styleable.TableEntryView_tev_rowEvenBackgroundColor, 0); | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|                 final int stroke = array.getDimensionPixelSize(R.styleable.TableEntryView_tev_borderWidth, 0); |                 final int stroke = array.getDimensionPixelSize(R.styleable.TableEntryView_tev_borderWidth, 0); | ||||||
| 
 | 
 | ||||||
|                 // half of requested |                 // half of requested | ||||||
| @ -124,6 +123,8 @@ public class TableEntryView extends LinearLayout { | |||||||
|             textView.setText(column.content()); |             textView.setText(column.content()); | ||||||
|             textView.getPaint().setFakeBoldText(row.header()); |             textView.getPaint().setFakeBoldText(row.header()); | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         group.requestLayout(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @NonNull |     @NonNull | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||||
| <TextView xmlns:android="http://schemas.android.com/apk/res/android" | <TextView xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     xmlns:tools="http://schemas.android.com/tools" | ||||||
|     android:id="@+id/text" |     android:id="@+id/text" | ||||||
|     android:layout_width="match_parent" |     android:layout_width="match_parent" | ||||||
|     android:layout_height="wrap_content" |     android:layout_height="wrap_content" | ||||||
| @ -10,4 +11,5 @@ | |||||||
|     android:paddingBottom="8dip" |     android:paddingBottom="8dip" | ||||||
|     android:textAppearance="?android:attr/textAppearanceMedium" |     android:textAppearance="?android:attr/textAppearanceMedium" | ||||||
|     android:textColor="#000" |     android:textColor="#000" | ||||||
|     android:textSize="16sp" /> |     android:textSize="16sp" | ||||||
|  |     tools:text="Hello" /> | ||||||
| @ -14,11 +14,15 @@ | |||||||
|         android:id="@+id/text" |         android:id="@+id/text" | ||||||
|         android:layout_width="wrap_content" |         android:layout_width="wrap_content" | ||||||
|         android:layout_height="wrap_content" |         android:layout_height="wrap_content" | ||||||
|  |         android:background="#0f000000" | ||||||
|  |         android:fontFamily="monospace" | ||||||
|         android:lineSpacingExtra="2dip" |         android:lineSpacingExtra="2dip" | ||||||
|  |         android:paddingLeft="16dip" | ||||||
|         android:paddingTop="8dip" |         android:paddingTop="8dip" | ||||||
|  |         android:paddingRight="16dip" | ||||||
|         android:paddingBottom="8dip" |         android:paddingBottom="8dip" | ||||||
|         android:textAppearance="?android:attr/textAppearanceMedium" |         android:textAppearance="?android:attr/textAppearanceMedium" | ||||||
|         android:textSize="16sp" |         android:textSize="14sp" | ||||||
|         tools:text="# Hello there! and taskjs" /> |         tools:text="# Hello there! and tasks" /> | ||||||
| 
 | 
 | ||||||
| </HorizontalScrollView> | </HorizontalScrollView> | ||||||
| @ -5,7 +5,6 @@ | |||||||
|     android:layout_height="wrap_content" |     android:layout_height="wrap_content" | ||||||
|     android:clipChildren="false" |     android:clipChildren="false" | ||||||
|     android:clipToPadding="false" |     android:clipToPadding="false" | ||||||
|     android:fillViewport="true" |  | ||||||
|     android:paddingLeft="16dip" |     android:paddingLeft="16dip" | ||||||
|     android:paddingTop="8dip" |     android:paddingTop="8dip" | ||||||
|     android:paddingRight="16dip" |     android:paddingRight="16dip" | ||||||
|  | |||||||
| @ -0,0 +1,18 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     android:layout_width="match_parent" | ||||||
|  |     android:layout_height="wrap_content" | ||||||
|  |     android:clipChildren="false" | ||||||
|  |     android:clipToPadding="false" | ||||||
|  |     android:paddingLeft="16dip" | ||||||
|  |     android:paddingTop="8dip" | ||||||
|  |     android:paddingRight="16dip" | ||||||
|  |     android:paddingBottom="8dip" | ||||||
|  |     android:scrollbarStyle="outsideInset"> | ||||||
|  | 
 | ||||||
|  |     <TableLayout | ||||||
|  |         android:id="@+id/table_layout" | ||||||
|  |         android:layout_width="wrap_content" | ||||||
|  |         android:layout_height="wrap_content" /> | ||||||
|  | 
 | ||||||
|  | </HorizontalScrollView> | ||||||
| @ -1,10 +1,10 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||||
| <TextView xmlns:android="http://schemas.android.com/apk/res/android" | <TextView xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|     xmlns:tools="http://schemas.android.com/tools" |     xmlns:tools="http://schemas.android.com/tools" | ||||||
|     android:layout_width="0px" |     android:layout_width="wrap_content" | ||||||
|     android:layout_height="match_parent" |     android:layout_height="match_parent" | ||||||
|     android:layout_weight="1" |     android:gravity="center_vertical" | ||||||
|     android:padding="4dip" |     android:padding="8dip" | ||||||
|     android:textAppearance="?android:attr/textAppearanceMedium" |     android:textAppearance="?android:attr/textAppearanceMedium" | ||||||
|     android:textColor="#000" |     android:textColor="#000" | ||||||
|     android:textSize="16sp" |     android:textSize="16sp" | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|     android:layout_width="match_parent" |     android:layout_width="wrap_content" | ||||||
|     android:layout_height="wrap_content" |     android:layout_height="wrap_content" | ||||||
|     android:orientation="horizontal" /> |     android:orientation="horizontal" /> | ||||||
							
								
								
									
										6
									
								
								sample/src/main/res/drawable/bg_table_cell.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								sample/src/main/res/drawable/bg_table_cell.xml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <shape xmlns:android="http://schemas.android.com/apk/res/android"> | ||||||
|  |     <stroke | ||||||
|  |         android:width="0.5dip" | ||||||
|  |         android:color="#000" /> | ||||||
|  | </shape> | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Dimitry Ivanov
						Dimitry Ivanov