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,20 +159,12 @@ public class Table {
|
|||||||
|
|
||||||
final TableCell cell = (TableCell) customNode;
|
final TableCell cell = (TableCell) customNode;
|
||||||
|
|
||||||
final Node firstChild = cell.getFirstChild();
|
|
||||||
|
|
||||||
// need to investigate why... (most likely initial node is modified by someone)
|
|
||||||
if (firstChild != null) {
|
|
||||||
|
|
||||||
if (pendingRow == null) {
|
if (pendingRow == null) {
|
||||||
pendingRow = new ArrayList<>(2);
|
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(cell)));
|
||||||
|
|
||||||
pendingRow.add(new Table.Column(alignment(cell.getAlignment()), markwon.render(firstChild)));
|
|
||||||
pendingRowIsHeader = cell.isHeader();
|
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