Table parsing and improvements for recycler artifact
This commit is contained in:
parent
f6ac3fde68
commit
24b95e2ffb
@ -0,0 +1,220 @@
|
|||||||
|
package ru.noties.markwon.ext.tables;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.text.Spanned;
|
||||||
|
|
||||||
|
import org.commonmark.ext.gfm.tables.TableBlock;
|
||||||
|
import org.commonmark.ext.gfm.tables.TableCell;
|
||||||
|
import org.commonmark.ext.gfm.tables.TableHead;
|
||||||
|
import org.commonmark.ext.gfm.tables.TableRow;
|
||||||
|
import org.commonmark.node.AbstractVisitor;
|
||||||
|
import org.commonmark.node.CustomNode;
|
||||||
|
import org.commonmark.node.Node;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import ru.noties.markwon.Markwon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class to parse <code>TableBlock</code> and return a data-structure that is not dependent
|
||||||
|
* on commonmark-java table extension. Can be useful when rendering tables require special
|
||||||
|
* handling (multiple views, specific table view) for example when used with `markwon-recycler` artifact
|
||||||
|
*
|
||||||
|
* @see #parse(Markwon, TableBlock)
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
|
public class Table {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method to obtain an instance of {@link Table}
|
||||||
|
*
|
||||||
|
* @param markwon Markwon
|
||||||
|
* @param tableBlock TableBlock to parse
|
||||||
|
* @return parsed {@link Table} or null
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static Table parse(@NonNull Markwon markwon, @NonNull TableBlock tableBlock) {
|
||||||
|
|
||||||
|
final Table table;
|
||||||
|
|
||||||
|
final ParseVisitor visitor = new ParseVisitor(markwon);
|
||||||
|
tableBlock.accept(visitor);
|
||||||
|
final List<Row> rows = visitor.rows();
|
||||||
|
|
||||||
|
if (rows == null) {
|
||||||
|
table = null;
|
||||||
|
} else {
|
||||||
|
table = new Table(rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Row {
|
||||||
|
|
||||||
|
private final boolean isHeader;
|
||||||
|
private final List<Column> columns;
|
||||||
|
|
||||||
|
public Row(
|
||||||
|
boolean isHeader,
|
||||||
|
@NonNull List<Column> columns) {
|
||||||
|
this.isHeader = isHeader;
|
||||||
|
this.columns = columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean header() {
|
||||||
|
return isHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public List<Column> columns() {
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Row{" +
|
||||||
|
"isHeader=" + isHeader +
|
||||||
|
", columns=" + columns +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Column {
|
||||||
|
|
||||||
|
private final Alignment alignment;
|
||||||
|
private final Spanned content;
|
||||||
|
|
||||||
|
public Column(@NonNull Alignment alignment, @NonNull Spanned content) {
|
||||||
|
this.alignment = alignment;
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Alignment alignment() {
|
||||||
|
return alignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Spanned content() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Column{" +
|
||||||
|
"alignment=" + alignment +
|
||||||
|
", content=" + content +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Alignment {
|
||||||
|
LEFT,
|
||||||
|
CENTER,
|
||||||
|
RIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
private final List<Row> rows;
|
||||||
|
|
||||||
|
public Table(@NonNull List<Row> rows) {
|
||||||
|
this.rows = rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public List<Row> rows() {
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Table{" +
|
||||||
|
"rows=" + rows +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ParseVisitor extends AbstractVisitor {
|
||||||
|
|
||||||
|
private final Markwon markwon;
|
||||||
|
|
||||||
|
private List<Row> rows;
|
||||||
|
|
||||||
|
private List<Column> pendingRow;
|
||||||
|
private boolean pendingRowIsHeader;
|
||||||
|
|
||||||
|
ParseVisitor(@NonNull Markwon markwon) {
|
||||||
|
this.markwon = markwon;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public List<Row> rows() {
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(CustomNode customNode) {
|
||||||
|
|
||||||
|
if (customNode instanceof TableCell) {
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customNode instanceof TableHead
|
||||||
|
|| customNode instanceof TableRow) {
|
||||||
|
|
||||||
|
visitChildren(customNode);
|
||||||
|
|
||||||
|
// this can happen, ignore such row
|
||||||
|
if (pendingRow != null && pendingRow.size() > 0) {
|
||||||
|
|
||||||
|
if (rows == null) {
|
||||||
|
rows = new ArrayList<>(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.add(new Table.Row(pendingRowIsHeader, pendingRow));
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingRow = null;
|
||||||
|
pendingRowIsHeader = false;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitChildren(customNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static Table.Alignment alignment(@NonNull TableCell.Alignment alignment) {
|
||||||
|
final Table.Alignment out;
|
||||||
|
if (TableCell.Alignment.RIGHT == alignment) {
|
||||||
|
out = Table.Alignment.RIGHT;
|
||||||
|
} else if (TableCell.Alignment.CENTER == alignment) {
|
||||||
|
out = Table.Alignment.CENTER;
|
||||||
|
} else {
|
||||||
|
out = Table.Alignment.LEFT;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -33,10 +33,11 @@ public class TablePlugin extends AbstractMarkwonPlugin {
|
|||||||
return new TablePlugin(tableTheme);
|
return new TablePlugin(tableTheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final TableTheme tableTheme;
|
private final TableVisitor visitor;
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
TablePlugin(@NonNull TableTheme tableTheme) {
|
TablePlugin(@NonNull TableTheme tableTheme) {
|
||||||
this.tableTheme = tableTheme;
|
this.visitor = new TableVisitor(tableTheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -46,7 +47,12 @@ public class TablePlugin extends AbstractMarkwonPlugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
|
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
|
||||||
TableVisitor.configure(tableTheme, builder);
|
visitor.configure(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeRender(@NonNull Node node) {
|
||||||
|
visitor.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -61,18 +67,23 @@ public class TablePlugin extends AbstractMarkwonPlugin {
|
|||||||
|
|
||||||
private static class TableVisitor {
|
private static class TableVisitor {
|
||||||
|
|
||||||
static void configure(@NonNull TableTheme tableTheme, @NonNull MarkwonVisitor.Builder builder) {
|
|
||||||
new TableVisitor(tableTheme, builder);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final TableTheme tableTheme;
|
private final TableTheme tableTheme;
|
||||||
|
|
||||||
private List<TableRowSpan.Cell> pendingTableRow;
|
private List<TableRowSpan.Cell> pendingTableRow;
|
||||||
private boolean tableRowIsHeader;
|
private boolean tableRowIsHeader;
|
||||||
private int tableRows;
|
private int tableRows;
|
||||||
|
|
||||||
private TableVisitor(@NonNull TableTheme tableTheme, @NonNull MarkwonVisitor.Builder builder) {
|
TableVisitor(@NonNull TableTheme tableTheme) {
|
||||||
this.tableTheme = tableTheme;
|
this.tableTheme = tableTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
pendingTableRow = null;
|
||||||
|
tableRowIsHeader = false;
|
||||||
|
tableRows = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void configure(@NonNull MarkwonVisitor.Builder builder) {
|
||||||
builder
|
builder
|
||||||
.on(TableBody.class, new MarkwonVisitor.NodeVisitor<TableBody>() {
|
.on(TableBody.class, new MarkwonVisitor.NodeVisitor<TableBody>() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -15,7 +15,25 @@ import java.util.List;
|
|||||||
|
|
||||||
import ru.noties.markwon.Markwon;
|
import ru.noties.markwon.Markwon;
|
||||||
|
|
||||||
// each node block will be rendered by a simple TextView, but we can provide own entries for each block
|
/**
|
||||||
|
* Class to display markdown in a RecyclerView. It is done by extracting root blocks from a
|
||||||
|
* parsed markdown document and rendering each block in a standalone RecyclerView entry. Provides
|
||||||
|
* ability to customize rendering of blocks. For example display certain blocks in a horizontal
|
||||||
|
* scrolling container or display tables in a specific widget designed for it ({@link Builder#include(Class, Entry)}).
|
||||||
|
* <p>
|
||||||
|
* By default each node will be rendered in a TextView provided by this artifact. It has no styling
|
||||||
|
* information and thus must be replaced with your own layout ({@link Builder#defaultEntry(int)} or
|
||||||
|
* {@link Builder#defaultEntry(Entry)}).
|
||||||
|
*
|
||||||
|
* @see #builder()
|
||||||
|
* @see #create()
|
||||||
|
* @see #create(int)
|
||||||
|
* @see #create(Entry)
|
||||||
|
* @see #setMarkdown(Markwon, String)
|
||||||
|
* @see #setParsedMarkdown(Markwon, Node)
|
||||||
|
* @see #setParsedMarkdown(Markwon, List)
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
public abstract class MarkwonAdapter extends RecyclerView.Adapter<MarkwonAdapter.Holder> {
|
public abstract class MarkwonAdapter extends RecyclerView.Adapter<MarkwonAdapter.Holder> {
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -59,6 +77,9 @@ public abstract class MarkwonAdapter extends RecyclerView.Adapter<MarkwonAdapter
|
|||||||
MarkwonAdapter build();
|
MarkwonAdapter build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see SimpleEntry
|
||||||
|
*/
|
||||||
public interface Entry<H extends Holder, N extends Node> {
|
public interface Entry<H extends Holder, N extends Node> {
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package ru.noties.markwon.recycler;
|
package ru.noties.markwon.recycler;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.util.Log;
|
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@ -25,6 +24,7 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
|
|||||||
private Markwon markwon;
|
private Markwon markwon;
|
||||||
private List<Node> nodes;
|
private List<Node> nodes;
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
MarkwonAdapterImpl(
|
MarkwonAdapterImpl(
|
||||||
@NonNull SparseArray<Entry<Holder, Node>> entries,
|
@NonNull SparseArray<Entry<Holder, Node>> entries,
|
||||||
@NonNull Entry<Holder, Node> defaultEntry,
|
@NonNull Entry<Holder, Node> defaultEntry,
|
||||||
@ -67,9 +67,7 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
|
|||||||
layoutInflater = LayoutInflater.from(parent.getContext());
|
layoutInflater = LayoutInflater.from(parent.getContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
final Entry<Holder, Node> entry = viewType == 0
|
final Entry<Holder, Node> entry = getEntry(viewType);
|
||||||
? defaultEntry
|
|
||||||
: entries.get(viewType);
|
|
||||||
|
|
||||||
return entry.createHolder(layoutInflater, parent);
|
return entry.createHolder(layoutInflater, parent);
|
||||||
}
|
}
|
||||||
@ -80,9 +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 = viewType == 0
|
final Entry<Holder, Node> entry = getEntry(viewType);
|
||||||
? defaultEntry
|
|
||||||
: entries.get(viewType);
|
|
||||||
|
|
||||||
entry.bindHolder(markwon, holder, node);
|
entry.bindHolder(markwon, holder, node);
|
||||||
}
|
}
|
||||||
@ -94,6 +90,7 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
|
|||||||
: 0;
|
: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
@NonNull
|
@NonNull
|
||||||
public List<Node> getItems() {
|
public List<Node> getItems() {
|
||||||
return nodes != null
|
return nodes != null
|
||||||
@ -110,12 +107,11 @@ 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 = type == 0
|
final Entry<Holder, Node> entry = getEntry(type);
|
||||||
? defaultEntry
|
|
||||||
: entries.get(type);
|
|
||||||
return entry.id(node);
|
return entry.id(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
public int getNodeViewType(@NonNull Class<? extends Node> node) {
|
public int getNodeViewType(@NonNull Class<? extends Node> node) {
|
||||||
// if has registered -> then return it, else 0
|
// if has registered -> then return it, else 0
|
||||||
final int hash = node.hashCode();
|
final int hash = node.hashCode();
|
||||||
@ -125,6 +121,13 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private Entry<Holder, Node> getEntry(int viewType) {
|
||||||
|
return viewType == 0
|
||||||
|
? defaultEntry
|
||||||
|
: entries.get(viewType);
|
||||||
|
}
|
||||||
|
|
||||||
static class BuilderImpl implements Builder {
|
static class BuilderImpl implements Builder {
|
||||||
|
|
||||||
private final SparseArray<Entry<Holder, Node>> entries = new SparseArray<>(3);
|
private final SparseArray<Entry<Holder, Node>> entries = new SparseArray<>(3);
|
||||||
@ -154,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 SimpleNodeEntry(layoutResId);
|
this.defaultEntry = (Entry<Holder, Node>) (Entry) new SimpleEntry(layoutResId);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,7 +174,7 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
|
|||||||
|
|
||||||
if (defaultEntry == null) {
|
if (defaultEntry == null) {
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
defaultEntry = (Entry<Holder, Node>) (Entry) new SimpleNodeEntry();
|
defaultEntry = (Entry<Holder, Node>) (Entry) new SimpleEntry();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reducer == null) {
|
if (reducer == null) {
|
||||||
@ -190,7 +193,7 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
|
|||||||
|
|
||||||
final List<Node> list = new ArrayList<>();
|
final List<Node> list = new ArrayList<>();
|
||||||
|
|
||||||
// // we will extract all blocks that are direct children of Document
|
// we will extract all blocks that are direct children of Document
|
||||||
Node node = root.getFirstChild();
|
Node node = root.getFirstChild();
|
||||||
Node temp;
|
Node temp;
|
||||||
|
|
||||||
@ -201,8 +204,6 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
|
|||||||
node = temp;
|
node = temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.e("NODES", list.toString());
|
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
package ru.noties.markwon.recycler;
|
|
||||||
|
|
||||||
public class MarkwonRecycler {
|
|
||||||
}
|
|
@ -17,7 +17,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
import ru.noties.markwon.Markwon;
|
import ru.noties.markwon.Markwon;
|
||||||
|
|
||||||
public class SimpleNodeEntry implements MarkwonAdapter.Entry<SimpleNodeEntry.Holder, Node> {
|
public class SimpleEntry implements MarkwonAdapter.Entry<SimpleEntry.Holder, Node> {
|
||||||
|
|
||||||
private static final NoCopySpannableFactory FACTORY = new NoCopySpannableFactory();
|
private static final NoCopySpannableFactory FACTORY = new NoCopySpannableFactory();
|
||||||
|
|
||||||
@ -26,11 +26,11 @@ public class SimpleNodeEntry implements MarkwonAdapter.Entry<SimpleNodeEntry.Hol
|
|||||||
|
|
||||||
private final int layoutResId;
|
private final int layoutResId;
|
||||||
|
|
||||||
public SimpleNodeEntry() {
|
public SimpleEntry() {
|
||||||
this(R.layout.adapter_simple_entry);
|
this(R.layout.markwon_adapter_simple_entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SimpleNodeEntry(@LayoutRes int layoutResId) {
|
public SimpleEntry(@LayoutRes int layoutResId) {
|
||||||
this.layoutResId = layoutResId;
|
this.layoutResId = layoutResId;
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:lineSpacingExtra="2dip"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
tools:text="# Hello there!" />
|
@ -65,7 +65,7 @@ class MarkwonImpl extends Markwon {
|
|||||||
|
|
||||||
final Spanned spanned = visitor.builder().spannableStringBuilder();
|
final Spanned spanned = visitor.builder().spannableStringBuilder();
|
||||||
|
|
||||||
// clear render props and builder after rending
|
// clear render props and builder after rendering
|
||||||
visitor.clear();
|
visitor.clear();
|
||||||
|
|
||||||
return spanned;
|
return spanned;
|
||||||
|
@ -29,7 +29,7 @@ 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.SimpleNodeEntry;
|
import ru.noties.markwon.recycler.SimpleEntry;
|
||||||
import ru.noties.markwon.sample.extension.R;
|
import ru.noties.markwon.sample.extension.R;
|
||||||
import ru.noties.markwon.urlprocessor.UrlProcessor;
|
import ru.noties.markwon.urlprocessor.UrlProcessor;
|
||||||
import ru.noties.markwon.urlprocessor.UrlProcessorRelativeToAbsolute;
|
import ru.noties.markwon.urlprocessor.UrlProcessorRelativeToAbsolute;
|
||||||
@ -48,8 +48,9 @@ public class MarkwonRecyclerActivity extends Activity {
|
|||||||
setContentView(R.layout.activity_recycler);
|
setContentView(R.layout.activity_recycler);
|
||||||
|
|
||||||
final MarkwonAdapter adapter = MarkwonAdapter.builder()
|
final MarkwonAdapter adapter = MarkwonAdapter.builder()
|
||||||
.include(FencedCodeBlock.class, new SimpleNodeEntry(R.layout.adapter_fenced_code_block))
|
.include(FencedCodeBlock.class, new SimpleEntry(R.layout.adapter_fenced_code_block))
|
||||||
.include(TableBlock.class, new TableNodeEntry())
|
.include(TableBlock.class, new TableNodeEntry())
|
||||||
|
.defaultEntry(new SimpleEntry(R.layout.adapter_default_entry))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
final RecyclerView recyclerView = findViewById(R.id.recycler_view);
|
final RecyclerView recyclerView = findViewById(R.id.recycler_view);
|
||||||
|
@ -1,48 +1,26 @@
|
|||||||
package ru.noties.markwon.sample.extension.recycler;
|
package ru.noties.markwon.sample.extension.recycler;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.TableLayout;
|
import android.widget.TableLayout;
|
||||||
import android.widget.TableRow;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import org.commonmark.ext.gfm.tables.TableBlock;
|
import org.commonmark.ext.gfm.tables.TableBlock;
|
||||||
import org.commonmark.node.AbstractVisitor;
|
|
||||||
import org.commonmark.node.BlockQuote;
|
|
||||||
import org.commonmark.node.BulletList;
|
|
||||||
import org.commonmark.node.Code;
|
|
||||||
import org.commonmark.node.CustomBlock;
|
|
||||||
import org.commonmark.node.CustomNode;
|
|
||||||
import org.commonmark.node.Document;
|
|
||||||
import org.commonmark.node.Emphasis;
|
|
||||||
import org.commonmark.node.FencedCodeBlock;
|
|
||||||
import org.commonmark.node.HardLineBreak;
|
|
||||||
import org.commonmark.node.Heading;
|
|
||||||
import org.commonmark.node.HtmlBlock;
|
|
||||||
import org.commonmark.node.HtmlInline;
|
|
||||||
import org.commonmark.node.Image;
|
|
||||||
import org.commonmark.node.IndentedCodeBlock;
|
|
||||||
import org.commonmark.node.Link;
|
|
||||||
import org.commonmark.node.ListItem;
|
|
||||||
import org.commonmark.node.Node;
|
|
||||||
import org.commonmark.node.OrderedList;
|
|
||||||
import org.commonmark.node.Paragraph;
|
|
||||||
import org.commonmark.node.SoftLineBreak;
|
|
||||||
import org.commonmark.node.StrongEmphasis;
|
|
||||||
import org.commonmark.node.Text;
|
|
||||||
import org.commonmark.node.ThematicBreak;
|
|
||||||
|
|
||||||
import ru.noties.debug.Debug;
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import ru.noties.markwon.Markwon;
|
import ru.noties.markwon.Markwon;
|
||||||
|
import ru.noties.markwon.ext.tables.Table;
|
||||||
import ru.noties.markwon.recycler.MarkwonAdapter;
|
import ru.noties.markwon.recycler.MarkwonAdapter;
|
||||||
import ru.noties.markwon.sample.extension.R;
|
import ru.noties.markwon.sample.extension.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 TableNodeEntry implements MarkwonAdapter.Entry<TableNodeEntry.TableNodeHolder, TableBlock> {
|
public class TableNodeEntry implements MarkwonAdapter.Entry<TableNodeEntry.TableNodeHolder, TableBlock> {
|
||||||
|
|
||||||
|
private final Map<TableBlock, Table> cache = new HashMap<>(2);
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public TableNodeHolder createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
|
public TableNodeHolder createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
|
||||||
@ -52,77 +30,23 @@ public class TableNodeEntry implements MarkwonAdapter.Entry<TableNodeEntry.Table
|
|||||||
@Override
|
@Override
|
||||||
public void bindHolder(@NonNull Markwon markwon, @NonNull TableNodeHolder holder, @NonNull TableBlock node) {
|
public void bindHolder(@NonNull Markwon markwon, @NonNull TableNodeHolder holder, @NonNull TableBlock node) {
|
||||||
|
|
||||||
if (true) {
|
Table table = cache.get(node);
|
||||||
Debug.e("###############");
|
if (table == null) {
|
||||||
Debug.e("NODE: %s", node);
|
table = Table.parse(markwon, node);
|
||||||
node.accept(new PrintVisitor());
|
cache.put(node, table);
|
||||||
Debug.e("NODE: %s", node);
|
|
||||||
Debug.e("###############");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final TableLayout layout = holder.layout;
|
if (table != null) {
|
||||||
layout.removeAllViews();
|
// render table
|
||||||
|
renderTable(markwon, holder, table);
|
||||||
// each child represents a row (head or regular)
|
} // we need to do something with null table...
|
||||||
// first direct child is TableHead or TableBody
|
|
||||||
Node child = node.getFirstChild().getFirstChild();
|
|
||||||
Node temp;
|
|
||||||
|
|
||||||
while (child != null) {
|
|
||||||
Log.e("BIND-ROWS", String.valueOf(child));
|
|
||||||
temp = child.getNext();
|
|
||||||
addRow(markwon, layout, child);
|
|
||||||
child = temp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.e("BIND", String.valueOf(layout.getChildCount()));
|
private void renderTable(
|
||||||
if (true) {
|
@NonNull Markwon markwon,
|
||||||
final ViewGroup group = (ViewGroup) layout.getChildAt(0);
|
@NonNull TableNodeHolder holder,
|
||||||
Log.e("BIND-GROUP", String.valueOf(group.getChildCount()));
|
@NonNull Table table) {
|
||||||
for (int i = 0; i < group.getChildCount(); i++) {
|
|
||||||
Log.e("BIND-CHILD-" + i, String.valueOf(group.getChildAt(i)) + ", " + ((TextView) group.getChildAt(i)).getText());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
layout.requestLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addRow(@NonNull Markwon markwon, @NonNull TableLayout layout, @NonNull Node node) {
|
|
||||||
|
|
||||||
final TableRow tableRow = new TableRow(layout.getContext());
|
|
||||||
// final TableRow.LayoutParams params = new TableRow.LayoutParams(100, 100);
|
|
||||||
tableRow.setBackgroundColor(0x80ff0000);
|
|
||||||
|
|
||||||
TextView textView;
|
|
||||||
RenderNode renderNode;
|
|
||||||
Node temp;
|
|
||||||
|
|
||||||
// each child in a row represents a cell
|
|
||||||
Node child = node.getFirstChild();
|
|
||||||
while (child != null) {
|
|
||||||
Log.e("BIND-CELL", String.valueOf(child));
|
|
||||||
textView = new TextView(layout.getContext());
|
|
||||||
textView.setLayoutParams(new TableRow.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
|
||||||
renderNode = new RenderNode();
|
|
||||||
temp = child.getNext();
|
|
||||||
copy(child, renderNode);
|
|
||||||
tableRow.addView(textView);
|
|
||||||
markwon.setParsedMarkdown(textView, markwon.render(renderNode));
|
|
||||||
child = temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
layout.addView(tableRow);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void copy(@NonNull Node from, @NonNull Node to) {
|
|
||||||
Node child = from.getFirstChild();
|
|
||||||
Node temp;
|
|
||||||
while (child != null) {
|
|
||||||
Log.e("BIND-COPY", String.valueOf(child));
|
|
||||||
temp = child.getNext();
|
|
||||||
to.appendChild(child);
|
|
||||||
child = temp;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -132,7 +56,7 @@ public class TableNodeEntry implements MarkwonAdapter.Entry<TableNodeEntry.Table
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
|
cache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
static class TableNodeHolder extends MarkwonAdapter.Holder {
|
static class TableNodeHolder extends MarkwonAdapter.Holder {
|
||||||
@ -145,145 +69,4 @@ public class TableNodeEntry implements MarkwonAdapter.Entry<TableNodeEntry.Table
|
|||||||
this.layout = requireView(R.id.table_layout);
|
this.layout = requireView(R.id.table_layout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class RenderNode extends CustomBlock {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class PrintVisitor extends AbstractVisitor {
|
|
||||||
|
|
||||||
private final RenderNode renderNode = new RenderNode();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(BlockQuote blockQuote) {
|
|
||||||
Debug.i("blockQuote: %s", blockQuote);
|
|
||||||
super.visit(blockQuote);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(BulletList bulletList) {
|
|
||||||
Debug.i("bulletList: %s", bulletList);
|
|
||||||
super.visit(bulletList);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(Code code) {
|
|
||||||
Debug.i("code: %s", code);
|
|
||||||
super.visit(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(Document document) {
|
|
||||||
Debug.i("document: %s", document);
|
|
||||||
super.visit(document);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(Emphasis emphasis) {
|
|
||||||
Debug.i("emphasis: %s", emphasis);
|
|
||||||
super.visit(emphasis);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(FencedCodeBlock fencedCodeBlock) {
|
|
||||||
Debug.i("fencedCodeBlock: %s", fencedCodeBlock);
|
|
||||||
super.visit(fencedCodeBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(HardLineBreak hardLineBreak) {
|
|
||||||
Debug.i("hardLineBreak: %s", hardLineBreak);
|
|
||||||
super.visit(hardLineBreak);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(Heading heading) {
|
|
||||||
Debug.i("heading: %s", heading);
|
|
||||||
super.visit(heading);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(ThematicBreak thematicBreak) {
|
|
||||||
Debug.i("thematicBreak: %s", thematicBreak);
|
|
||||||
super.visit(thematicBreak);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(HtmlInline htmlInline) {
|
|
||||||
Debug.i("htmlInline: %s", htmlInline);
|
|
||||||
super.visit(htmlInline);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(HtmlBlock htmlBlock) {
|
|
||||||
Debug.i("htmlBlock: %s", htmlBlock);
|
|
||||||
super.visit(htmlBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(Image image) {
|
|
||||||
Debug.i("image: %s", image);
|
|
||||||
super.visit(image);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(IndentedCodeBlock indentedCodeBlock) {
|
|
||||||
Debug.i("indentedCodeBlock: %s", indentedCodeBlock);
|
|
||||||
super.visit(indentedCodeBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(Link link) {
|
|
||||||
Debug.i("link: %s", link);
|
|
||||||
super.visit(link);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(ListItem listItem) {
|
|
||||||
Debug.i("listItem: %s", listItem);
|
|
||||||
super.visit(listItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(OrderedList orderedList) {
|
|
||||||
Debug.i("orderedList: %s", orderedList);
|
|
||||||
super.visit(orderedList);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(Paragraph paragraph) {
|
|
||||||
Debug.i("paragraph: %s", paragraph);
|
|
||||||
super.visit(paragraph);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(SoftLineBreak softLineBreak) {
|
|
||||||
Debug.i("softLineBreak: %s", softLineBreak);
|
|
||||||
super.visit(softLineBreak);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(StrongEmphasis strongEmphasis) {
|
|
||||||
Debug.i("strongEmphasis: %s", strongEmphasis);
|
|
||||||
super.visit(strongEmphasis);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(Text text) {
|
|
||||||
Debug.i("text: %s", text);
|
|
||||||
super.visit(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(CustomBlock customBlock) {
|
|
||||||
Debug.i("customBlock: %s", customBlock);
|
|
||||||
super.visit(customBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(CustomNode customNode) {
|
|
||||||
Debug.i("customNode: %s", customNode);
|
|
||||||
super.visit(customNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<?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,5 +9,5 @@
|
|||||||
android:paddingTop="8dip"
|
android:paddingTop="8dip"
|
||||||
android:paddingBottom="8dip"
|
android:paddingBottom="8dip"
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
android:textSize="16sp"
|
android:textColor="#000"
|
||||||
tools:text="# Hello there!" />
|
android:textSize="16sp" />
|
Loading…
x
Reference in New Issue
Block a user