Update sample with anchor and bullet-letter plugins
This commit is contained in:
parent
54e5b27d59
commit
3ee62a724c
@ -0,0 +1,97 @@
|
|||||||
|
package io.noties.markwon.sample.basicplugins;
|
||||||
|
|
||||||
|
import android.text.Spannable;
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import io.noties.markwon.AbstractMarkwonPlugin;
|
||||||
|
import io.noties.markwon.LinkResolverDef;
|
||||||
|
import io.noties.markwon.MarkwonConfiguration;
|
||||||
|
import io.noties.markwon.core.spans.HeadingSpan;
|
||||||
|
|
||||||
|
public class AnchorHeadingPlugin extends AbstractMarkwonPlugin {
|
||||||
|
|
||||||
|
public interface ScrollTo {
|
||||||
|
void scrollTo(@NonNull TextView view, int top);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ScrollTo scrollTo;
|
||||||
|
|
||||||
|
AnchorHeadingPlugin(@NonNull ScrollTo scrollTo) {
|
||||||
|
this.scrollTo = scrollTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
|
||||||
|
builder.linkResolver(new AnchorLinkResolver(scrollTo));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterSetText(@NonNull TextView textView) {
|
||||||
|
final Spannable spannable = (Spannable) textView.getText();
|
||||||
|
// obtain heading spans
|
||||||
|
final HeadingSpan[] spans = spannable.getSpans(0, spannable.length(), HeadingSpan.class);
|
||||||
|
if (spans != null) {
|
||||||
|
for (HeadingSpan span : spans) {
|
||||||
|
final int start = spannable.getSpanStart(span);
|
||||||
|
final int end = spannable.getSpanEnd(span);
|
||||||
|
final int flags = spannable.getSpanFlags(span);
|
||||||
|
spannable.setSpan(
|
||||||
|
new AnchorSpan(createAnchor(spannable.subSequence(start, end))),
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
flags
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class AnchorLinkResolver extends LinkResolverDef {
|
||||||
|
|
||||||
|
private final ScrollTo scrollTo;
|
||||||
|
|
||||||
|
AnchorLinkResolver(@NonNull ScrollTo scrollTo) {
|
||||||
|
this.scrollTo = scrollTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resolve(@NonNull View view, @NonNull String link) {
|
||||||
|
if (link.startsWith("#")) {
|
||||||
|
final TextView textView = (TextView) view;
|
||||||
|
final Spanned spanned = (Spannable) textView.getText();
|
||||||
|
final AnchorSpan[] spans = spanned.getSpans(0, spanned.length(), AnchorSpan.class);
|
||||||
|
if (spans != null) {
|
||||||
|
final String anchor = link.substring(1);
|
||||||
|
for (AnchorSpan span : spans) {
|
||||||
|
if (anchor.equals(span.anchor)) {
|
||||||
|
final int start = spanned.getSpanStart(span);
|
||||||
|
final int line = textView.getLayout().getLineForOffset(start);
|
||||||
|
final int top = textView.getLayout().getLineTop(line);
|
||||||
|
scrollTo.scrollTo(textView, top);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.resolve(view, link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class AnchorSpan {
|
||||||
|
final String anchor;
|
||||||
|
|
||||||
|
AnchorSpan(@NonNull String anchor) {
|
||||||
|
this.anchor = anchor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static String createAnchor(@NonNull CharSequence content) {
|
||||||
|
return String.valueOf(content)
|
||||||
|
.replaceAll("[^\\w]", "")
|
||||||
|
.toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
@ -3,22 +3,16 @@ package io.noties.markwon.sample.basicplugins;
|
|||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Spannable;
|
|
||||||
import android.text.Spanned;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.style.ForegroundColorSpan;
|
import android.text.style.ForegroundColorSpan;
|
||||||
import android.view.View;
|
|
||||||
import android.widget.ScrollView;
|
import android.widget.ScrollView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.commonmark.node.BulletList;
|
|
||||||
import org.commonmark.node.Heading;
|
import org.commonmark.node.Heading;
|
||||||
import org.commonmark.node.ListItem;
|
|
||||||
import org.commonmark.node.Node;
|
import org.commonmark.node.Node;
|
||||||
import org.commonmark.node.OrderedList;
|
|
||||||
import org.commonmark.node.Paragraph;
|
import org.commonmark.node.Paragraph;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@ -26,21 +20,14 @@ import java.util.Collections;
|
|||||||
|
|
||||||
import io.noties.markwon.AbstractMarkwonPlugin;
|
import io.noties.markwon.AbstractMarkwonPlugin;
|
||||||
import io.noties.markwon.BlockHandlerDef;
|
import io.noties.markwon.BlockHandlerDef;
|
||||||
import io.noties.markwon.LinkResolverDef;
|
|
||||||
import io.noties.markwon.Markwon;
|
import io.noties.markwon.Markwon;
|
||||||
import io.noties.markwon.MarkwonConfiguration;
|
import io.noties.markwon.MarkwonConfiguration;
|
||||||
import io.noties.markwon.MarkwonSpansFactory;
|
import io.noties.markwon.MarkwonSpansFactory;
|
||||||
import io.noties.markwon.MarkwonVisitor;
|
import io.noties.markwon.MarkwonVisitor;
|
||||||
import io.noties.markwon.Prop;
|
|
||||||
import io.noties.markwon.RenderProps;
|
|
||||||
import io.noties.markwon.SoftBreakAddsNewLinePlugin;
|
import io.noties.markwon.SoftBreakAddsNewLinePlugin;
|
||||||
import io.noties.markwon.SpanFactory;
|
|
||||||
import io.noties.markwon.core.CoreProps;
|
import io.noties.markwon.core.CoreProps;
|
||||||
import io.noties.markwon.core.MarkwonTheme;
|
import io.noties.markwon.core.MarkwonTheme;
|
||||||
import io.noties.markwon.core.spans.BulletListItemSpan;
|
|
||||||
import io.noties.markwon.core.spans.HeadingSpan;
|
|
||||||
import io.noties.markwon.core.spans.LastLineSpacingSpan;
|
import io.noties.markwon.core.spans.LastLineSpacingSpan;
|
||||||
import io.noties.markwon.core.spans.OrderedListItemSpan;
|
|
||||||
import io.noties.markwon.image.ImageItem;
|
import io.noties.markwon.image.ImageItem;
|
||||||
import io.noties.markwon.image.ImagesPlugin;
|
import io.noties.markwon.image.ImagesPlugin;
|
||||||
import io.noties.markwon.image.SchemeHandler;
|
import io.noties.markwon.image.SchemeHandler;
|
||||||
@ -393,85 +380,6 @@ final Markwon markwon = Markwon.builder(this)
|
|||||||
markwon.setMarkdown(textView, md);
|
markwon.setMarkdown(textView, md);
|
||||||
}
|
}
|
||||||
|
|
||||||
// public void step_6() {
|
|
||||||
//
|
|
||||||
// final Markwon markwon = Markwon.builder(this)
|
|
||||||
// .usePlugin(HtmlPlugin.create())
|
|
||||||
// .usePlugin(new AbstractMarkwonPlugin() {
|
|
||||||
// @Override
|
|
||||||
// public void configure(@NonNull Registry registry) {
|
|
||||||
// registry.require(HtmlPlugin.class, plugin -> plugin.addHandler(new SimpleTagHandler() {
|
|
||||||
// @Override
|
|
||||||
// public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps renderProps, @NonNull HtmlTag tag) {
|
|
||||||
// return new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @NonNull
|
|
||||||
// @Override
|
|
||||||
// public Collection<String> supportedTags() {
|
|
||||||
// return Collections.singleton("center");
|
|
||||||
// }
|
|
||||||
// }));
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// .build();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// text lifecycle (after/before)
|
|
||||||
// rendering lifecycle (before/after)
|
|
||||||
// renderProps
|
|
||||||
// process
|
|
||||||
|
|
||||||
private static class AnchorSpan {
|
|
||||||
final String anchor;
|
|
||||||
|
|
||||||
AnchorSpan(@NonNull String anchor) {
|
|
||||||
this.anchor = anchor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private String createAnchor(@NonNull CharSequence content) {
|
|
||||||
return String.valueOf(content)
|
|
||||||
.replaceAll("[^\\w]", "")
|
|
||||||
.toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class AnchorLinkResolver extends LinkResolverDef {
|
|
||||||
|
|
||||||
interface ScrollTo {
|
|
||||||
void scrollTo(@NonNull View view, int top);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final ScrollTo scrollTo;
|
|
||||||
|
|
||||||
AnchorLinkResolver(@NonNull ScrollTo scrollTo) {
|
|
||||||
this.scrollTo = scrollTo;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void resolve(@NonNull View view, @NonNull String link) {
|
|
||||||
if (link.startsWith("#")) {
|
|
||||||
final TextView textView = (TextView) view;
|
|
||||||
final Spanned spanned = (Spannable) textView.getText();
|
|
||||||
final AnchorSpan[] spans = spanned.getSpans(0, spanned.length(), AnchorSpan.class);
|
|
||||||
if (spans != null) {
|
|
||||||
final String anchor = link.substring(1);
|
|
||||||
for (AnchorSpan span: spans) {
|
|
||||||
if (anchor.equals(span.anchor)) {
|
|
||||||
final int start = spanned.getSpanStart(span);
|
|
||||||
final int line = textView.getLayout().getLineForOffset(start);
|
|
||||||
final int top = textView.getLayout().getLineTop(line);
|
|
||||||
scrollTo.scrollTo(textView, top);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super.resolve(view, link);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void anchor() {
|
private void anchor() {
|
||||||
final String lorem = getString(R.string.lorem);
|
final String lorem = getString(R.string.lorem);
|
||||||
final String md = "" +
|
final String md = "" +
|
||||||
@ -481,156 +389,26 @@ final Markwon markwon = Markwon.builder(this)
|
|||||||
lorem;
|
lorem;
|
||||||
|
|
||||||
final Markwon markwon = Markwon.builder(this)
|
final Markwon markwon = Markwon.builder(this)
|
||||||
.usePlugin(new AbstractMarkwonPlugin() {
|
.usePlugin(new AnchorHeadingPlugin((view, top) -> scrollView.smoothScrollTo(0, top)))
|
||||||
@Override
|
|
||||||
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
|
|
||||||
builder.linkResolver(new AnchorLinkResolver((view, top) -> scrollView.smoothScrollTo(0, top)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterSetText(@NonNull TextView textView) {
|
|
||||||
final Spannable spannable = (Spannable) textView.getText();
|
|
||||||
// obtain heading spans
|
|
||||||
final HeadingSpan[] spans = spannable.getSpans(0, spannable.length(), HeadingSpan.class);
|
|
||||||
if (spans != null) {
|
|
||||||
for (HeadingSpan span : spans) {
|
|
||||||
final int start = spannable.getSpanStart(span);
|
|
||||||
final int end = spannable.getSpanEnd(span);
|
|
||||||
final int flags = spannable.getSpanFlags(span);
|
|
||||||
spannable.setSpan(
|
|
||||||
new AnchorSpan(createAnchor(spannable.subSequence(start, end))),
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
flags
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
markwon.setMarkdown(textView, md);
|
markwon.setMarkdown(textView, md);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Prop<Boolean> ORDERED_IS_NESTED = Prop.of("my-ordered-is-nested");
|
|
||||||
|
|
||||||
private void letterOrderedList() {
|
private void letterOrderedList() {
|
||||||
// first level of ordered-list is still number,
|
// bullet list nested in ordered list renders letters instead of bullets
|
||||||
// other ordered-list levels start with `A`
|
|
||||||
final String md = "" +
|
final String md = "" +
|
||||||
"1. Hello there!\n" +
|
"1. Hello there!\n" +
|
||||||
"1. And here is how:\n" +
|
"1. And here is how:\n" +
|
||||||
" 1. First\n" +
|
" - First\n" +
|
||||||
" 2. Second\n" +
|
" - Second\n" +
|
||||||
" 3. Third\n" +
|
" - Third\n" +
|
||||||
" 1. And first here\n\n";
|
" 1. And first here\n\n";
|
||||||
|
|
||||||
final Markwon markwon = Markwon.builder(this)
|
final Markwon markwon = Markwon.builder(this)
|
||||||
.usePlugin(new AbstractMarkwonPlugin() {
|
.usePlugin(new BulletListIsOrderedWithLettersWhenNestedPlugin())
|
||||||
@Override
|
|
||||||
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
|
|
||||||
builder.on(ListItem.class, new MarkwonVisitor.NodeVisitor<ListItem>() {
|
|
||||||
@Override
|
|
||||||
public void visit(@NonNull MarkwonVisitor visitor, @NonNull ListItem listItem) {
|
|
||||||
final int length = visitor.length();
|
|
||||||
|
|
||||||
// it's important to visit children before applying render props (
|
|
||||||
// we can have nested children, who are list items also, thus they will
|
|
||||||
// override out props (if we set them before visiting children)
|
|
||||||
visitor.visitChildren(listItem);
|
|
||||||
|
|
||||||
final Node parent = listItem.getParent();
|
|
||||||
if (parent instanceof OrderedList) {
|
|
||||||
|
|
||||||
final int start = ((OrderedList) parent).getStartNumber();
|
|
||||||
|
|
||||||
CoreProps.LIST_ITEM_TYPE.set(visitor.renderProps(), CoreProps.ListItemType.ORDERED);
|
|
||||||
CoreProps.ORDERED_LIST_ITEM_NUMBER.set(visitor.renderProps(), start);
|
|
||||||
ORDERED_IS_NESTED.set(visitor.renderProps(), isOrderedListNested(parent));
|
|
||||||
|
|
||||||
// after we have visited the children increment start number
|
|
||||||
final OrderedList orderedList = (OrderedList) parent;
|
|
||||||
orderedList.setStartNumber(orderedList.getStartNumber() + 1);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
CoreProps.LIST_ITEM_TYPE.set(visitor.renderProps(), CoreProps.ListItemType.BULLET);
|
|
||||||
CoreProps.BULLET_LIST_ITEM_LEVEL.set(visitor.renderProps(), listLevel(listItem));
|
|
||||||
}
|
|
||||||
|
|
||||||
visitor.setSpansForNodeOptional(listItem, length);
|
|
||||||
|
|
||||||
if (visitor.hasNext(listItem)) {
|
|
||||||
visitor.ensureNewLine();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
|
|
||||||
builder.setFactory(ListItem.class, new SpanFactory() {
|
|
||||||
@Override
|
|
||||||
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
|
|
||||||
|
|
||||||
// type of list item
|
|
||||||
// bullet : level
|
|
||||||
// ordered: number
|
|
||||||
final Object spans;
|
|
||||||
|
|
||||||
if (CoreProps.ListItemType.BULLET == CoreProps.LIST_ITEM_TYPE.require(props)) {
|
|
||||||
spans = new BulletListItemSpan(
|
|
||||||
configuration.theme(),
|
|
||||||
CoreProps.BULLET_LIST_ITEM_LEVEL.require(props)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
final int number = CoreProps.ORDERED_LIST_ITEM_NUMBER.require(props);
|
|
||||||
final String text;
|
|
||||||
if (ORDERED_IS_NESTED.get(props, false)) {
|
|
||||||
// or `a`, or any other logic
|
|
||||||
text = ((char)('A' + number - 1)) + ".\u00a0";
|
|
||||||
} else {
|
|
||||||
text = number + ".\u00a0";
|
|
||||||
}
|
|
||||||
|
|
||||||
spans = new OrderedListItemSpan(
|
|
||||||
configuration.theme(),
|
|
||||||
text
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return spans;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
markwon.setMarkdown(textView, md);
|
markwon.setMarkdown(textView, md);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int listLevel(@NonNull Node node) {
|
|
||||||
int level = 0;
|
|
||||||
Node parent = node.getParent();
|
|
||||||
while (parent != null) {
|
|
||||||
if (parent instanceof ListItem) {
|
|
||||||
level += 1;
|
|
||||||
}
|
|
||||||
parent = parent.getParent();
|
|
||||||
}
|
|
||||||
return level;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isOrderedListNested(@NonNull Node node) {
|
|
||||||
node = node.getParent();
|
|
||||||
while (node != null) {
|
|
||||||
if (node instanceof OrderedList) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (node instanceof BulletList) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
node = node.getParent();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,156 @@
|
|||||||
|
package io.noties.markwon.sample.basicplugins;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.SparseIntArray;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.commonmark.node.BulletList;
|
||||||
|
import org.commonmark.node.ListItem;
|
||||||
|
import org.commonmark.node.Node;
|
||||||
|
import org.commonmark.node.OrderedList;
|
||||||
|
|
||||||
|
import io.noties.markwon.AbstractMarkwonPlugin;
|
||||||
|
import io.noties.markwon.MarkwonSpansFactory;
|
||||||
|
import io.noties.markwon.MarkwonVisitor;
|
||||||
|
import io.noties.markwon.Prop;
|
||||||
|
import io.noties.markwon.core.CoreProps;
|
||||||
|
import io.noties.markwon.core.spans.BulletListItemSpan;
|
||||||
|
import io.noties.markwon.core.spans.OrderedListItemSpan;
|
||||||
|
|
||||||
|
public class BulletListIsOrderedWithLettersWhenNestedPlugin extends AbstractMarkwonPlugin {
|
||||||
|
|
||||||
|
private static final Prop<String> BULLET_LETTER = Prop.of("my-bullet-letter");
|
||||||
|
|
||||||
|
// or introduce some kind of synchronization if planning to use from multiple threads,
|
||||||
|
// for example via ThreadLocal
|
||||||
|
private final SparseIntArray bulletCounter = new SparseIntArray();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterRender(@NonNull Node node, @NonNull MarkwonVisitor visitor) {
|
||||||
|
// clear counter after render
|
||||||
|
bulletCounter.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
|
||||||
|
// NB that both ordered and bullet lists are represented
|
||||||
|
// by ListItem (must inspect parent to detect the type)
|
||||||
|
builder.on(ListItem.class, (visitor, listItem) -> {
|
||||||
|
// mimic original behaviour (copy-pasta from CorePlugin)
|
||||||
|
|
||||||
|
final int length = visitor.length();
|
||||||
|
|
||||||
|
visitor.visitChildren(listItem);
|
||||||
|
|
||||||
|
final Node parent = listItem.getParent();
|
||||||
|
if (parent instanceof OrderedList) {
|
||||||
|
|
||||||
|
final int start = ((OrderedList) parent).getStartNumber();
|
||||||
|
|
||||||
|
CoreProps.LIST_ITEM_TYPE.set(visitor.renderProps(), CoreProps.ListItemType.ORDERED);
|
||||||
|
CoreProps.ORDERED_LIST_ITEM_NUMBER.set(visitor.renderProps(), start);
|
||||||
|
|
||||||
|
// after we have visited the children increment start number
|
||||||
|
final OrderedList orderedList = (OrderedList) parent;
|
||||||
|
orderedList.setStartNumber(orderedList.getStartNumber() + 1);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
CoreProps.LIST_ITEM_TYPE.set(visitor.renderProps(), CoreProps.ListItemType.BULLET);
|
||||||
|
|
||||||
|
if (isBulletOrdered(parent)) {
|
||||||
|
// obtain current count value
|
||||||
|
final int count = currentBulletCountIn(parent);
|
||||||
|
BULLET_LETTER.set(visitor.renderProps(), createBulletLetter(count));
|
||||||
|
// update current count value
|
||||||
|
setCurrentBulletCountIn(parent, count + 1);
|
||||||
|
} else {
|
||||||
|
CoreProps.BULLET_LIST_ITEM_LEVEL.set(visitor.renderProps(), listLevel(listItem));
|
||||||
|
// clear letter info when regular bullet list is used
|
||||||
|
BULLET_LETTER.clear(visitor.renderProps());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
visitor.setSpansForNodeOptional(listItem, length);
|
||||||
|
|
||||||
|
if (visitor.hasNext(listItem)) {
|
||||||
|
visitor.ensureNewLine();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
|
||||||
|
builder.setFactory(ListItem.class, (configuration, props) -> {
|
||||||
|
final Object spans;
|
||||||
|
|
||||||
|
if (CoreProps.ListItemType.BULLET == CoreProps.LIST_ITEM_TYPE.require(props)) {
|
||||||
|
final String letter = BULLET_LETTER.get(props);
|
||||||
|
if (!TextUtils.isEmpty(letter)) {
|
||||||
|
// NB, we are using OrderedListItemSpan here!
|
||||||
|
spans = new OrderedListItemSpan(
|
||||||
|
configuration.theme(),
|
||||||
|
letter
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
spans = new BulletListItemSpan(
|
||||||
|
configuration.theme(),
|
||||||
|
CoreProps.BULLET_LIST_ITEM_LEVEL.require(props)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
final String number = String.valueOf(CoreProps.ORDERED_LIST_ITEM_NUMBER.require(props))
|
||||||
|
+ "." + '\u00a0';
|
||||||
|
|
||||||
|
spans = new OrderedListItemSpan(
|
||||||
|
configuration.theme(),
|
||||||
|
number
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return spans;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private int currentBulletCountIn(@NonNull Node parent) {
|
||||||
|
return bulletCounter.get(parent.hashCode(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setCurrentBulletCountIn(@NonNull Node parent, int count) {
|
||||||
|
bulletCounter.put(parent.hashCode(), count);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static String createBulletLetter(int count) {
|
||||||
|
// or lower `a`
|
||||||
|
// `'u00a0` is non-breakable space char
|
||||||
|
return ((char) ('A' + count)) + ".\u00a0";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int listLevel(@NonNull Node node) {
|
||||||
|
int level = 0;
|
||||||
|
Node parent = node.getParent();
|
||||||
|
while (parent != null) {
|
||||||
|
if (parent instanceof ListItem) {
|
||||||
|
level += 1;
|
||||||
|
}
|
||||||
|
parent = parent.getParent();
|
||||||
|
}
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isBulletOrdered(@NonNull Node node) {
|
||||||
|
node = node.getParent();
|
||||||
|
while (node != null) {
|
||||||
|
if (node instanceof OrderedList) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (node instanceof BulletList) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
node = node.getParent();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user