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.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.Spannable;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.view.View;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.commonmark.node.BulletList;
|
||||
import org.commonmark.node.Heading;
|
||||
import org.commonmark.node.ListItem;
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.node.OrderedList;
|
||||
import org.commonmark.node.Paragraph;
|
||||
|
||||
import java.util.Collection;
|
||||
@ -26,21 +20,14 @@ import java.util.Collections;
|
||||
|
||||
import io.noties.markwon.AbstractMarkwonPlugin;
|
||||
import io.noties.markwon.BlockHandlerDef;
|
||||
import io.noties.markwon.LinkResolverDef;
|
||||
import io.noties.markwon.Markwon;
|
||||
import io.noties.markwon.MarkwonConfiguration;
|
||||
import io.noties.markwon.MarkwonSpansFactory;
|
||||
import io.noties.markwon.MarkwonVisitor;
|
||||
import io.noties.markwon.Prop;
|
||||
import io.noties.markwon.RenderProps;
|
||||
import io.noties.markwon.SoftBreakAddsNewLinePlugin;
|
||||
import io.noties.markwon.SpanFactory;
|
||||
import io.noties.markwon.core.CoreProps;
|
||||
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.OrderedListItemSpan;
|
||||
import io.noties.markwon.image.ImageItem;
|
||||
import io.noties.markwon.image.ImagesPlugin;
|
||||
import io.noties.markwon.image.SchemeHandler;
|
||||
@ -332,26 +319,26 @@ public class BasicPluginsActivity extends ActivityWithMenuOptions {
|
||||
}
|
||||
|
||||
private void headingNoSpaceBlockHandler() {
|
||||
final Markwon markwon = Markwon.builder(this)
|
||||
.usePlugin(new AbstractMarkwonPlugin() {
|
||||
@Override
|
||||
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
|
||||
builder.blockHandler(new BlockHandlerDef() {
|
||||
final Markwon markwon = Markwon.builder(this)
|
||||
.usePlugin(new AbstractMarkwonPlugin() {
|
||||
@Override
|
||||
public void blockEnd(@NonNull MarkwonVisitor visitor, @NonNull Node node) {
|
||||
if (node instanceof Heading) {
|
||||
if (visitor.hasNext(node)) {
|
||||
visitor.ensureNewLine();
|
||||
// ensure new line but do not force insert one
|
||||
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
|
||||
builder.blockHandler(new BlockHandlerDef() {
|
||||
@Override
|
||||
public void blockEnd(@NonNull MarkwonVisitor visitor, @NonNull Node node) {
|
||||
if (node instanceof Heading) {
|
||||
if (visitor.hasNext(node)) {
|
||||
visitor.ensureNewLine();
|
||||
// ensure new line but do not force insert one
|
||||
}
|
||||
} else {
|
||||
super.blockEnd(visitor, node);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
super.blockEnd(visitor, node);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.build();
|
||||
})
|
||||
.build();
|
||||
|
||||
final String md = "" +
|
||||
"# Title title title title title title title title title title \n\ntext text text text";
|
||||
@ -393,85 +380,6 @@ final Markwon markwon = Markwon.builder(this)
|
||||
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() {
|
||||
final String lorem = getString(R.string.lorem);
|
||||
final String md = "" +
|
||||
@ -481,156 +389,26 @@ final Markwon markwon = Markwon.builder(this)
|
||||
lorem;
|
||||
|
||||
final Markwon markwon = Markwon.builder(this)
|
||||
.usePlugin(new AbstractMarkwonPlugin() {
|
||||
@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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.usePlugin(new AnchorHeadingPlugin((view, top) -> scrollView.smoothScrollTo(0, top)))
|
||||
.build();
|
||||
|
||||
markwon.setMarkdown(textView, md);
|
||||
}
|
||||
|
||||
private static final Prop<Boolean> ORDERED_IS_NESTED = Prop.of("my-ordered-is-nested");
|
||||
|
||||
private void letterOrderedList() {
|
||||
// first level of ordered-list is still number,
|
||||
// other ordered-list levels start with `A`
|
||||
// bullet list nested in ordered list renders letters instead of bullets
|
||||
final String md = "" +
|
||||
"1. Hello there!\n" +
|
||||
"1. And here is how:\n" +
|
||||
" 1. First\n" +
|
||||
" 2. Second\n" +
|
||||
" 3. Third\n" +
|
||||
" - First\n" +
|
||||
" - Second\n" +
|
||||
" - Third\n" +
|
||||
" 1. And first here\n\n";
|
||||
|
||||
final Markwon markwon = Markwon.builder(this)
|
||||
.usePlugin(new AbstractMarkwonPlugin() {
|
||||
@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;
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.usePlugin(new BulletListIsOrderedWithLettersWhenNestedPlugin())
|
||||
.build();
|
||||
|
||||
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