replace commonMark to flexMark

This commit is contained in:
shenliming 2023-04-07 10:49:34 +08:00
parent 2ea148c30a
commit b6135ecc56
138 changed files with 815 additions and 4348 deletions

View File

@ -44,12 +44,8 @@ android {
}
compileOptions {
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
targetCompatibility JavaVersion.VERSION_11
sourceCompatibility JavaVersion.VERSION_11
}
sourceSets {
@ -58,48 +54,6 @@ android {
}
}
// do not sign in CI
if (!project.hasProperty('CI')) {
signingConfigs {
config {
final def keystoreFile = project.file('keystore.jks')
final def keystoreFilePassword = 'MARKWON_KEYSTORE_FILE_PASSWORD'
final def keystoreAlias = 'MARKWON_KEY_ALIAS'
final def keystoreAliasPassword = 'MARKWON_KEY_ALIAS_PASSWORD'
final def properties = [
keystoreFilePassword,
keystoreAlias,
keystoreAliasPassword
]
if (!keystoreFile.exists()) {
throw new IllegalStateException("No '${keystoreFile.name}' file is found.")
}
final def missingProperties = properties.findAll { !project.hasProperty(it) }
if (!missingProperties.isEmpty()) {
throw new IllegalStateException("Missing required signing properties: $missingProperties")
}
storeFile keystoreFile
storePassword project[keystoreFilePassword]
keyAlias project[keystoreAlias]
keyPassword project[keystoreAliasPassword]
}
}
buildTypes {
debug {
signingConfig signingConfigs.config
}
release {
signingConfig signingConfigs.config
}
}
}
}
kapt {
@ -122,7 +76,7 @@ dependencies {
kapt it['prism4j-bundler']
}
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation project(':markwon-core')
implementation project(':markwon-editor')
@ -132,7 +86,6 @@ dependencies {
implementation project(':markwon-ext-tasklist')
implementation project(':markwon-html')
implementation project(':markwon-image')
implementation project(':markwon-inline-parser')
implementation project(':markwon-linkify')
implementation project(':markwon-recycler')
implementation project(':markwon-recycler-table')

View File

@ -651,18 +651,6 @@
"parsing"
]
},
{
"javaClassName": "io.noties.markwon.app.samples.DelimiterProcessorSample",
"id": "20200630194017",
"title": "Custom delimiter processor",
"description": "Custom parsing delimiter processor with `?` character",
"artifacts": [
"CORE"
],
"tags": [
"parsing"
]
},
{
"javaClassName": "io.noties.markwon.app.samples.html.HtmlDisableSanitizeSample",
"id": "20200630171424",
@ -957,31 +945,6 @@
"editor"
]
},
{
"javaClassName": "io.noties.markwon.app.samples.NoParsingSample",
"id": "20200629171212",
"title": "No parsing",
"description": "All commonmark parsing is disabled (both inlines and blocks)",
"artifacts": [
"CORE"
],
"tags": [
"parsing",
"rendering"
]
},
{
"javaClassName": "io.noties.markwon.app.samples.InlinePluginNoDefaultsSample",
"id": "20200629170857",
"title": "Inline parsing without defaults",
"description": "Configure inline parser plugin to **not** have any **inline** parsing",
"artifacts": [
"INLINE_PARSER"
],
"tags": [
"parsing"
]
},
{
"javaClassName": "io.noties.markwon.app.samples.editor.EditorNewLineContinuationSample",
"id": "20200629170348",
@ -1076,23 +1039,6 @@
"editor"
]
},
{
"javaClassName": "io.noties.markwon.app.samples.CustomExtensionSample",
"id": "20200629163248",
"title": "Custom extension",
"description": "Custom extension that adds an icon from resources and renders it as image with `@ic-name` syntax",
"artifacts": [
"CORE"
],
"tags": [
"parsing",
"plugin",
"rendering",
"image",
"extension",
"span"
]
},
{
"javaClassName": "io.noties.markwon.app.samples.GithubUserIssueOnTextAddedSample",
"id": "20200629162024",
@ -1107,21 +1053,6 @@
"textAddedListener"
]
},
{
"javaClassName": "io.noties.markwon.app.samples.GithubUserIssueInlineParsingSample",
"id": "20200629162023",
"title": "User mention and issue (via text)",
"description": "Github-like user mention and issue rendering via `CorePlugin.OnTextAddedListener`",
"artifacts": [
"CORE",
"INLINE_PARSER"
],
"tags": [
"parsing",
"rendering",
"textAddedListener"
]
},
{
"javaClassName": "io.noties.markwon.app.samples.ReadMorePluginSample",
"id": "20200629161505",
@ -1410,20 +1341,6 @@
"defaults"
]
},
{
"javaClassName": "io.noties.markwon.app.samples.EnabledBlockTypesSample",
"id": "20200627075012",
"title": "Enabled markdown blocks",
"description": "Modify/inspect enabled by `CorePlugin` block types. Disable quotes or other blocks from being parsed",
"artifacts": [
"CORE"
],
"tags": [
"parsing",
"block",
"plugin"
]
},
{
"javaClassName": "io.noties.markwon.app.samples.ToastDynamicContentSample",
"id": "20200627074017",

View File

@ -15,7 +15,9 @@
android:theme="@style/AppTheme"
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
<activity android:name=".sample.MainActivity">
<activity
android:name=".sample.MainActivity"
android:exported="true">
<!-- launcher intent -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -42,7 +44,7 @@
android:host="noties.io"
android:scheme="https" />
<data android:pathPrefix="/Markwon/app"/>
<data android:pathPrefix="/Markwon/app" />
<data android:pathPattern="sample/.*" />
<data android:pathPattern="search" />

View File

@ -2,7 +2,7 @@ package io.noties.markwon.app.samples;
import androidx.annotation.NonNull;
import org.commonmark.node.Heading;
import com.vladsch.flexmark.ast.Heading;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.Markwon;

View File

@ -2,7 +2,7 @@ package io.noties.markwon.app.samples;
import androidx.annotation.NonNull;
import org.commonmark.node.Node;
import com.vladsch.flexmark.util.ast.Node;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.BlockHandlerDef;

View File

@ -2,7 +2,7 @@ package io.noties.markwon.app.samples;
import androidx.annotation.NonNull;
import org.commonmark.node.Node;
import com.vladsch.flexmark.util.ast.Node;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.Markwon;

View File

@ -4,7 +4,8 @@ import android.text.style.BulletSpan;
import androidx.annotation.NonNull;
import org.commonmark.node.ListItem;
import com.vladsch.flexmark.ast.ListItem;
import io.noties.debug.Debug;
import io.noties.markwon.AbstractMarkwonPlugin;

View File

@ -10,6 +10,7 @@ import android.text.style.ClickableSpan
import android.text.style.LeadingMarginSpan
import android.view.View
import android.widget.TextView
import com.vladsch.flexmark.ast.FencedCodeBlock
import io.noties.debug.Debug
import io.noties.markwon.AbstractMarkwonPlugin
import io.noties.markwon.Markwon
@ -20,19 +21,18 @@ import io.noties.markwon.sample.annotations.MarkwonArtifact
import io.noties.markwon.sample.annotations.MarkwonSampleInfo
import io.noties.markwon.sample.annotations.Tag
import io.noties.markwon.utils.LeadingMarginUtils
import org.commonmark.node.FencedCodeBlock
@MarkwonSampleInfo(
id = "20210315112847",
title = "Copy code block",
description = "Copy contents of fenced code blocks",
artifacts = [MarkwonArtifact.CORE],
tags = [Tag.rendering, Tag.block, Tag.spanFactory, Tag.span]
id = "20210315112847",
title = "Copy code block",
description = "Copy contents of fenced code blocks",
artifacts = [MarkwonArtifact.CORE],
tags = [Tag.rendering, Tag.block, Tag.spanFactory, Tag.span]
)
class CopyCodeBlockSample : MarkwonTextViewSample() {
override fun render() {
val md = """
override fun render() {
val md = """
# Hello code blocks!
```java
final int i = 0;
@ -43,77 +43,77 @@ class CopyCodeBlockSample : MarkwonTextViewSample() {
bye bye!
""".trimIndent()
val markwon = Markwon.builder(context)
.usePlugin(object : AbstractMarkwonPlugin() {
override fun configureSpansFactory(builder: MarkwonSpansFactory.Builder) {
builder.appendFactory(FencedCodeBlock::class.java) { _, _ ->
CopyContentsSpan()
}
builder.appendFactory(FencedCodeBlock::class.java) { _, _ ->
CopyIconSpan(context.getDrawable(R.drawable.ic_code_white_24dp)!!)
}
}
})
.build()
val markwon = Markwon.builder(context)
.usePlugin(object : AbstractMarkwonPlugin() {
override fun configureSpansFactory(builder: MarkwonSpansFactory.Builder) {
builder.appendFactory(FencedCodeBlock::class.java) { _, _ ->
CopyContentsSpan()
}
builder.appendFactory(FencedCodeBlock::class.java) { _, _ ->
CopyIconSpan(context.getDrawable(R.drawable.ic_code_white_24dp)!!)
}
}
})
.build()
markwon.setMarkdown(textView, md)
markwon.setMarkdown(textView, md)
}
class CopyContentsSpan : ClickableSpan() {
override fun onClick(widget: View) {
val spanned = (widget as? TextView)?.text as? Spanned ?: return
val start = spanned.getSpanStart(this)
val end = spanned.getSpanEnd(this)
// by default code blocks have new lines before and after content
val contents = spanned.subSequence(start, end).toString().trim()
// copy code here
Debug.i(contents)
}
class CopyContentsSpan : ClickableSpan() {
override fun onClick(widget: View) {
val spanned = (widget as? TextView)?.text as? Spanned ?: return
val start = spanned.getSpanStart(this)
val end = spanned.getSpanEnd(this)
// by default code blocks have new lines before and after content
val contents = spanned.subSequence(start, end).toString().trim()
// copy code here
Debug.i(contents)
}
override fun updateDrawState(ds: TextPaint) {
// do not apply link styling
}
}
override fun updateDrawState(ds: TextPaint) {
// do not apply link styling
}
class CopyIconSpan(val icon: Drawable) : LeadingMarginSpan {
init {
if (icon.bounds.isEmpty) {
icon.setBounds(0, 0, icon.intrinsicWidth, icon.intrinsicHeight)
}
}
class CopyIconSpan(val icon: Drawable) : LeadingMarginSpan {
override fun getLeadingMargin(first: Boolean): Int = 0
init {
if (icon.bounds.isEmpty) {
icon.setBounds(0, 0, icon.intrinsicWidth, icon.intrinsicHeight)
}
}
override fun drawLeadingMargin(
c: Canvas,
p: Paint,
x: Int,
dir: Int,
top: Int,
baseline: Int,
bottom: Int,
text: CharSequence,
start: Int,
end: Int,
first: Boolean,
layout: Layout
) {
override fun getLeadingMargin(first: Boolean): Int = 0
// called for each line of text, we are interested only in first one
if (!LeadingMarginUtils.selfStart(start, text, this)) return
override fun drawLeadingMargin(
c: Canvas,
p: Paint,
x: Int,
dir: Int,
top: Int,
baseline: Int,
bottom: Int,
text: CharSequence,
start: Int,
end: Int,
first: Boolean,
layout: Layout
) {
// called for each line of text, we are interested only in first one
if (!LeadingMarginUtils.selfStart(start, text, this)) return
val save = c.save()
try {
// horizontal position for icon
val w = icon.bounds.width().toFloat()
// minus quarter width as padding
val left = layout.width - w - (w / 4F)
c.translate(left, top.toFloat())
icon.draw(c)
} finally {
c.restoreToCount(save)
}
}
val save = c.save()
try {
// horizontal position for icon
val w = icon.bounds.width().toFloat()
// minus quarter width as padding
val left = layout.width - w - (w / 4F)
c.translate(left, top.toFloat())
icon.draw(c)
} finally {
c.restoreToCount(save)
}
}
}
}

View File

@ -1,436 +0,0 @@
package io.noties.markwon.app.samples;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.text.style.ReplacementSpan;
import androidx.annotation.DrawableRes;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.commonmark.node.CustomNode;
import org.commonmark.node.Delimited;
import org.commonmark.node.Node;
import org.commonmark.node.Text;
import org.commonmark.parser.Parser;
import org.commonmark.parser.delimiter.DelimiterProcessor;
import org.commonmark.parser.delimiter.DelimiterRun;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.Markwon;
import io.noties.markwon.MarkwonVisitor;
import io.noties.markwon.app.sample.ui.MarkwonTextViewSample;
import io.noties.markwon.sample.annotations.MarkwonArtifact;
import io.noties.markwon.sample.annotations.MarkwonSampleInfo;
import io.noties.markwon.sample.annotations.Tag;
@MarkwonSampleInfo(
id = "20200629163248",
title = "Custom extension",
description = "Custom extension that adds an " +
"icon from resources and renders it as image with " +
"`@ic-name` syntax",
artifacts = MarkwonArtifact.CORE,
tags = {Tag.parsing, Tag.rendering, Tag.plugin, Tag.image, Tag.extension, Tag.span}
)
public class CustomExtensionSample extends MarkwonTextViewSample {
@Override
public void render() {
final String md = "" +
"# Hello! @ic-android-black-24\n\n" +
"" +
"Home 36 black: @ic-home-black-36\n\n" +
"" +
"Memory 48 black: @ic-memory-black-48\n\n" +
"" +
"### I AM ANOTHER HEADER\n\n" +
"" +
"Sentiment Satisfied 64 red: @ic-sentiment_satisfied-red-64" +
"";
// note that we haven't registered CorePlugin, as it's the only one that can be
// implicitly deducted and added automatically. All other plugins require explicit
// `usePlugin` call
final Markwon markwon = Markwon.builder(context)
.usePlugin(IconPlugin.create(IconSpanProvider.create(context, 0)))
.build();
markwon.setMarkdown(textView, md);
}
}
class IconPlugin extends AbstractMarkwonPlugin {
@NonNull
public static IconPlugin create(@NonNull IconSpanProvider iconSpanProvider) {
return new IconPlugin(iconSpanProvider);
}
private final IconSpanProvider iconSpanProvider;
IconPlugin(@NonNull IconSpanProvider iconSpanProvider) {
this.iconSpanProvider = iconSpanProvider;
}
@Override
public void configureParser(@NonNull Parser.Builder builder) {
builder.customDelimiterProcessor(IconProcessor.create());
}
@Override
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
builder.on(IconNode.class, (visitor, iconNode) -> {
final String name = iconNode.name();
final String color = iconNode.color();
final String size = iconNode.size();
if (!TextUtils.isEmpty(name)
&& !TextUtils.isEmpty(color)
&& !TextUtils.isEmpty(size)) {
final int length = visitor.length();
visitor.builder().append(name);
visitor.setSpans(length, iconSpanProvider.provide(name, color, size));
visitor.builder().append(' ');
}
});
}
@NonNull
@Override
public String processMarkdown(@NonNull String markdown) {
return IconProcessor.prepare(markdown);
}
}
abstract class IconSpanProvider {
@SuppressWarnings("SameParameterValue")
@NonNull
public static IconSpanProvider create(@NonNull Context context, @DrawableRes int fallBack) {
return new Impl(context, fallBack);
}
@NonNull
public abstract IconSpan provide(@NonNull String name, @NonNull String color, @NonNull String size);
private static class Impl extends IconSpanProvider {
private final Context context;
private final Resources resources;
private final int fallBack;
Impl(@NonNull Context context, @DrawableRes int fallBack) {
this.context = context;
this.resources = context.getResources();
this.fallBack = fallBack;
}
@NonNull
@Override
public IconSpan provide(@NonNull String name, @NonNull String color, @NonNull String size) {
final String resName = iconName(name, color, size);
int resId = resources.getIdentifier(resName, "drawable", context.getPackageName());
if (resId == 0) {
resId = fallBack;
}
return new IconSpan(getDrawable(resId), IconSpan.ALIGN_CENTER);
}
@NonNull
private static String iconName(@NonNull String name, @NonNull String color, @NonNull String size) {
return "ic_" + name + "_" + color + "_" + size + "dp";
}
@NonNull
private Drawable getDrawable(int resId) {
//noinspection ConstantConditions
return context.getDrawable(resId);
}
}
}
class IconSpan extends ReplacementSpan {
@IntDef({ALIGN_BOTTOM, ALIGN_BASELINE, ALIGN_CENTER})
@Retention(RetentionPolicy.CLASS)
@interface Alignment {
}
public static final int ALIGN_BOTTOM = 0;
public static final int ALIGN_BASELINE = 1;
public static final int ALIGN_CENTER = 2; // will only center if drawable height is less than text line height
private final Drawable drawable;
private final int alignment;
public IconSpan(@NonNull Drawable drawable, @Alignment int alignment) {
this.drawable = drawable;
this.alignment = alignment;
if (drawable.getBounds().isEmpty()) {
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
}
}
@Override
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fm) {
final Rect rect = drawable.getBounds();
if (fm != null) {
fm.ascent = -rect.bottom;
fm.descent = 0;
fm.top = fm.ascent;
fm.bottom = 0;
}
return rect.right;
}
@Override
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
final int b = bottom - drawable.getBounds().bottom;
final int save = canvas.save();
try {
final int translationY;
if (ALIGN_CENTER == alignment) {
translationY = b - ((bottom - top - drawable.getBounds().height()) / 2);
} else if (ALIGN_BASELINE == alignment) {
translationY = b - paint.getFontMetricsInt().descent;
} else {
translationY = b;
}
canvas.translate(x, translationY);
drawable.draw(canvas);
} finally {
canvas.restoreToCount(save);
}
}
}
class IconProcessor implements DelimiterProcessor {
@NonNull
public static IconProcessor create() {
return new IconProcessor();
}
// ic-home-black-24
private static final Pattern PATTERN = Pattern.compile("ic-(\\w+)-(\\w+)-(\\d+)");
private static final String TO_FIND = IconNode.DELIMITER_STRING + "ic-";
/**
* Should be used when input string does not wrap icon definition with `@` from both ends.
* So, `@ic-home-white-24` would become `@ic-home-white-24@`. This way parsing is easier
* and more predictable (cannot specify multiple ending delimiters, as we would require them:
* space, newline, end of a document, and a lot of more)
*
* @param input to process
* @return processed string
* @see #prepare(StringBuilder)
*/
@NonNull
public static String prepare(@NonNull String input) {
final StringBuilder builder = new StringBuilder(input);
prepare(builder);
return builder.toString();
}
public static void prepare(@NonNull StringBuilder builder) {
int start = builder.indexOf(TO_FIND);
int end;
while (start > -1) {
end = iconDefinitionEnd(start + TO_FIND.length(), builder);
// if we match our pattern, append `@` else ignore
if (iconDefinitionValid(builder.subSequence(start + 1, end))) {
builder.insert(end, '@');
}
// move to next
start = builder.indexOf(TO_FIND, end);
}
}
@Override
public char getOpeningCharacter() {
return IconNode.DELIMITER;
}
@Override
public char getClosingCharacter() {
return IconNode.DELIMITER;
}
@Override
public int getMinLength() {
return 1;
}
@Override
public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) {
return opener.length() >= 1 && closer.length() >= 1 ? 1 : 0;
}
@Override
public void process(Text opener, Text closer, int delimiterUse) {
final IconGroupNode iconGroupNode = new IconGroupNode();
final Node next = opener.getNext();
boolean handled = false;
// process only if we have exactly one Text node
if (next instanceof Text && next.getNext() == closer) {
final String text = ((Text) next).getLiteral();
if (!TextUtils.isEmpty(text)) {
// attempt to match
final Matcher matcher = PATTERN.matcher(text);
if (matcher.matches()) {
final IconNode iconNode = new IconNode(
matcher.group(1),
matcher.group(2),
matcher.group(3)
);
iconGroupNode.appendChild(iconNode);
next.unlink();
handled = true;
}
}
}
if (!handled) {
// restore delimiters if we didn't match
iconGroupNode.appendChild(new Text(IconNode.DELIMITER_STRING));
Node node;
for (Node tmp = opener.getNext(); tmp != null && tmp != closer; tmp = node) {
node = tmp.getNext();
// append a child anyway
iconGroupNode.appendChild(tmp);
}
iconGroupNode.appendChild(new Text(IconNode.DELIMITER_STRING));
}
opener.insertBefore(iconGroupNode);
}
private static int iconDefinitionEnd(int index, @NonNull StringBuilder builder) {
// all spaces, new lines, non-words or digits,
char c;
int end = -1;
for (int i = index; i < builder.length(); i++) {
c = builder.charAt(i);
if (Character.isWhitespace(c)
|| !(Character.isLetterOrDigit(c) || c == '-' || c == '_')) {
end = i;
break;
}
}
if (end == -1) {
end = builder.length();
}
return end;
}
private static boolean iconDefinitionValid(@NonNull CharSequence cs) {
final Matcher matcher = PATTERN.matcher(cs);
return matcher.matches();
}
}
class IconNode extends CustomNode implements Delimited {
public static final char DELIMITER = '@';
public static final String DELIMITER_STRING = "" + DELIMITER;
private final String name;
private final String color;
private final String size;
public IconNode(@NonNull String name, @NonNull String color, @NonNull String size) {
this.name = name;
this.color = color;
this.size = size;
}
@NonNull
public String name() {
return name;
}
@NonNull
public String color() {
return color;
}
@NonNull
public String size() {
return size;
}
@Override
public String getOpeningDelimiter() {
return DELIMITER_STRING;
}
@Override
public String getClosingDelimiter() {
return DELIMITER_STRING;
}
@Override
@NonNull
public String toString() {
return "IconNode{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
", size='" + size + '\'' +
'}';
}
}
class IconGroupNode extends CustomNode {
}

View File

@ -1,83 +0,0 @@
package io.noties.markwon.app.samples;
import androidx.annotation.NonNull;
import org.commonmark.node.Emphasis;
import org.commonmark.node.Node;
import org.commonmark.node.Text;
import org.commonmark.parser.Parser;
import org.commonmark.parser.delimiter.DelimiterProcessor;
import org.commonmark.parser.delimiter.DelimiterRun;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.Markwon;
import io.noties.markwon.app.sample.ui.MarkwonTextViewSample;
import io.noties.markwon.sample.annotations.MarkwonArtifact;
import io.noties.markwon.sample.annotations.MarkwonSampleInfo;
import io.noties.markwon.sample.annotations.Tag;
@MarkwonSampleInfo(
id = "20200630194017",
title = "Custom delimiter processor",
description = "Custom parsing delimiter processor with `?` character",
artifacts = MarkwonArtifact.CORE,
tags = Tag.parsing
)
public class DelimiterProcessorSample extends MarkwonTextViewSample {
@Override
public void render() {
final String md = "" +
"?hello? there!";
final Markwon markwon = Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureParser(@NonNull Parser.Builder builder) {
builder.customDelimiterProcessor(new QuestionDelimiterProcessor());
}
})
.build();
markwon.setMarkdown(textView, md);
}
}
class QuestionDelimiterProcessor implements DelimiterProcessor {
@Override
public char getOpeningCharacter() {
return '?';
}
@Override
public char getClosingCharacter() {
return '?';
}
@Override
public int getMinLength() {
return 1;
}
@Override
public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) {
if (opener.length() >= 1 && closer.length() >= 1) {
return 1;
}
return 0;
}
@Override
public void process(Text opener, Text closer, int delimiterUse) {
final Node node = new Emphasis();
Node tmp = opener.getNext();
while (tmp != null && tmp != closer) {
Node next = tmp.getNext();
node.appendChild(tmp);
tmp = next;
}
opener.insertAfter(node);
}
}

View File

@ -2,7 +2,7 @@ package io.noties.markwon.app.samples;
import androidx.annotation.NonNull;
import org.commonmark.node.Heading;
import com.vladsch.flexmark.ast.Heading;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.Markwon;

View File

@ -1,45 +0,0 @@
package io.noties.markwon.app.samples
import io.noties.markwon.AbstractMarkwonPlugin
import io.noties.markwon.Markwon
import io.noties.markwon.app.sample.ui.MarkwonTextViewSample
import io.noties.markwon.core.CorePlugin
import io.noties.markwon.sample.annotations.MarkwonArtifact
import io.noties.markwon.sample.annotations.MarkwonSampleInfo
import io.noties.markwon.sample.annotations.Tag
import org.commonmark.node.BlockQuote
import org.commonmark.parser.Parser
@MarkwonSampleInfo(
id = "20200627075012",
title = "Enabled markdown blocks",
description = "Modify/inspect enabled by `CorePlugin` block types. " +
"Disable quotes or other blocks from being parsed",
artifacts = [MarkwonArtifact.CORE],
tags = [Tag.parsing, Tag.block, Tag.plugin]
)
class EnabledBlockTypesSample : MarkwonTextViewSample() {
override fun render() {
val md = """
# Heading
## Second level
> Quote is not handled
""".trimIndent()
val markwon = Markwon.builder(context)
.usePlugin(object : AbstractMarkwonPlugin() {
override fun configureParser(builder: Parser.Builder) {
// obtain all enabled block types
val enabledBlockTypes = CorePlugin.enabledBlockTypes()
// it is safe to modify returned collection
// remove quotes
enabledBlockTypes.remove(BlockQuote::class.java)
builder.enabledBlockTypes(enabledBlockTypes)
}
})
.build()
markwon.setMarkdown(textView, md)
}
}

View File

@ -16,7 +16,7 @@ import java.util.regex.Pattern
artifacts = [MarkwonArtifact.CORE],
tags = [Tag.parsing]
)
class ExcludeFromParsingSample : MarkwonTextViewSample() {
public class ExcludeFromParsingSample : MarkwonTextViewSample() {
override fun render() {
// cannot have continuous markdown between parts (so a node started in one part and ended in other)

View File

@ -1,110 +0,0 @@
package io.noties.markwon.app.samples;
import androidx.annotation.NonNull;
import org.commonmark.node.Link;
import org.commonmark.node.Node;
import org.commonmark.parser.InlineParserFactory;
import org.commonmark.parser.Parser;
import java.util.regex.Pattern;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.Markwon;
import io.noties.markwon.app.BuildConfig;
import io.noties.markwon.app.sample.ui.MarkwonTextViewSample;
import io.noties.markwon.inlineparser.InlineProcessor;
import io.noties.markwon.inlineparser.MarkwonInlineParser;
import io.noties.markwon.sample.annotations.MarkwonArtifact;
import io.noties.markwon.sample.annotations.MarkwonSampleInfo;
import io.noties.markwon.sample.annotations.Tag;
@MarkwonSampleInfo(
id = "20200629162023",
title = "User mention and issue (via text)",
description = "Github-like user mention and issue " +
"rendering via `CorePlugin.OnTextAddedListener`",
artifacts = {MarkwonArtifact.CORE, MarkwonArtifact.INLINE_PARSER},
tags = {Tag.parsing, Tag.textAddedListener, Tag.rendering}
)
public class GithubUserIssueInlineParsingSample extends MarkwonTextViewSample {
@Override
public void render() {
final String md = "" +
"# Custom Extension 2\n" +
"\n" +
"This is an issue #1\n" +
"Done by @noties and other @dude";
final InlineParserFactory inlineParserFactory = MarkwonInlineParser.factoryBuilder()
// include all current defaults (otherwise will be empty - contain only our inline-processors)
// included by default, to create factory-builder without defaults call `factoryBuilderNoDefaults`
// .includeDefaults()
.addInlineProcessor(new IssueInlineProcessor())
.addInlineProcessor(new UserInlineProcessor())
.build();
final Markwon markwon = Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureParser(@NonNull Parser.Builder builder) {
builder.inlineParserFactory(inlineParserFactory);
}
})
.build();
markwon.setMarkdown(textView, md);
}
}
class IssueInlineProcessor extends InlineProcessor {
private static final Pattern RE = Pattern.compile("\\d+");
@Override
public char specialCharacter() {
return '#';
}
@Override
protected Node parse() {
final String id = match(RE);
if (id != null) {
final Link link = new Link(createIssueOrPullRequestLinkDestination(id), null);
link.appendChild(text("#" + id));
return link;
}
return null;
}
@NonNull
private static String createIssueOrPullRequestLinkDestination(@NonNull String id) {
return BuildConfig.GIT_REPOSITORY + "/issues/" + id;
}
}
class UserInlineProcessor extends InlineProcessor {
private static final Pattern RE = Pattern.compile("\\w+");
@Override
public char specialCharacter() {
return '@';
}
@Override
protected Node parse() {
final String user = match(RE);
if (user != null) {
final Link link = new Link(createUserLinkDestination(user), null);
link.appendChild(text("@" + user));
return link;
}
return null;
}
@NonNull
private static String createUserLinkDestination(@NonNull String user) {
return "https://github.com/" + user;
}
}

View File

@ -2,7 +2,7 @@ package io.noties.markwon.app.samples;
import androidx.annotation.NonNull;
import org.commonmark.node.Link;
import com.vladsch.flexmark.ast.Link;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

View File

@ -5,7 +5,7 @@ import android.text.style.ForegroundColorSpan;
import androidx.annotation.NonNull;
import org.commonmark.node.Heading;
import com.vladsch.flexmark.ast.Heading;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.Markwon;

View File

@ -2,8 +2,8 @@ package io.noties.markwon.app.samples;
import androidx.annotation.NonNull;
import org.commonmark.node.Heading;
import org.commonmark.node.Node;
import com.vladsch.flexmark.ast.Heading;
import com.vladsch.flexmark.util.ast.Node;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.BlockHandlerDef;

View File

@ -2,7 +2,7 @@ package io.noties.markwon.app.samples;
import androidx.annotation.NonNull;
import org.commonmark.node.Heading;
import com.vladsch.flexmark.ast.Heading;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.Markwon;

View File

@ -1,35 +0,0 @@
package io.noties.markwon.app.samples;
import io.noties.markwon.Markwon;
import io.noties.markwon.app.sample.ui.MarkwonTextViewSample;
import io.noties.markwon.inlineparser.MarkwonInlineParser;
import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin;
import io.noties.markwon.sample.annotations.MarkwonArtifact;
import io.noties.markwon.sample.annotations.MarkwonSampleInfo;
import io.noties.markwon.sample.annotations.Tag;
@MarkwonSampleInfo(
id = "20200629170857",
title = "Inline parsing without defaults",
description = "Configure inline parser plugin to **not** have any **inline** parsing",
artifacts = {MarkwonArtifact.INLINE_PARSER},
tags = {Tag.parsing}
)
public class InlinePluginNoDefaultsSample extends MarkwonTextViewSample {
@Override
public void render() {
final String md = "" +
"# Heading\n" +
"`code` inlined and **bold** here";
final Markwon markwon = Markwon.builder(context)
.usePlugin(MarkwonInlineParserPlugin.create(MarkwonInlineParser.factoryBuilderNoDefaults()))
// .usePlugin(MarkwonInlineParserPlugin.create(MarkwonInlineParser.factoryBuilderNoDefaults(), factoryBuilder -> {
// // if anything, they can be included here
//// factoryBuilder.includeDefaults()
// }))
.build();
markwon.setMarkdown(textView, md);
}
}

View File

@ -5,10 +5,10 @@ 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 com.vladsch.flexmark.ast.BulletList;
import com.vladsch.flexmark.ast.ListItem;
import com.vladsch.flexmark.ast.OrderedList;
import com.vladsch.flexmark.util.ast.Node;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.Markwon;

View File

@ -6,7 +6,7 @@ import android.text.style.UpdateAppearance;
import androidx.annotation.NonNull;
import org.commonmark.node.Link;
import com.vladsch.flexmark.ast.Link;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.Markwon;

View File

@ -8,7 +8,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.commonmark.node.Link;
import com.vladsch.flexmark.ast.Link;
import java.util.Locale;

View File

@ -1,49 +0,0 @@
package io.noties.markwon.app.samples;
import androidx.annotation.NonNull;
import org.commonmark.parser.Parser;
import java.util.Collections;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.Markwon;
import io.noties.markwon.app.sample.ui.MarkwonTextViewSample;
import io.noties.markwon.inlineparser.MarkwonInlineParser;
import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin;
import io.noties.markwon.sample.annotations.MarkwonArtifact;
import io.noties.markwon.sample.annotations.MarkwonSampleInfo;
import io.noties.markwon.sample.annotations.Tag;
@MarkwonSampleInfo(
id = "20200629171212",
title = "No parsing",
description = "All commonmark parsing is disabled (both inlines and blocks)",
artifacts = MarkwonArtifact.CORE,
tags = {Tag.parsing, Tag.rendering}
)
public class NoParsingSample extends MarkwonTextViewSample {
@Override
public void render() {
final String md = "" +
"# Heading\n" +
"[link](#) was _here_ and `then` and it was:\n" +
"> a quote\n" +
"```java\n" +
"final int someJavaCode = 0;\n" +
"```\n";
final Markwon markwon = Markwon.builder(context)
// disable inline parsing
.usePlugin(MarkwonInlineParserPlugin.create(MarkwonInlineParser.factoryBuilderNoDefaults()))
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureParser(@NonNull Parser.Builder builder) {
builder.enabledBlockTypes(Collections.emptySet());
}
})
.build();
markwon.setMarkdown(textView, md);
}
}

View File

@ -5,7 +5,7 @@ import android.text.style.ForegroundColorSpan;
import androidx.annotation.NonNull;
import org.commonmark.node.Paragraph;
import com.vladsch.flexmark.ast.Paragraph;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.Markwon;

View File

@ -3,8 +3,8 @@ package io.noties.markwon.app.samples;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import org.commonmark.ext.gfm.tables.TableBlock;
import org.commonmark.node.FencedCodeBlock;
import com.vladsch.flexmark.ast.FencedCodeBlock;
import com.vladsch.flexmark.ext.tables.TableBlock;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.Markwon;
@ -59,7 +59,7 @@ public class RecyclerSample extends MarkwonRecyclerViewSample {
// 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());
.highlight(fencedCodeBlock.getInfo().unescape(), fencedCodeBlock.toAstString(false).trim());
visitor.builder().append(code);
});
}

View File

@ -2,8 +2,8 @@ package io.noties.markwon.app.samples;
import androidx.annotation.NonNull;
import org.commonmark.node.Node;
import org.commonmark.node.ThematicBreak;
import com.vladsch.flexmark.ast.ThematicBreak;
import com.vladsch.flexmark.util.ast.Node;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.BlockHandlerDef;

View File

@ -19,7 +19,7 @@ import io.noties.markwon.sample.annotations.Tag
artifacts = [MarkwonArtifact.CORE, MarkwonArtifact.IMAGE],
tags = [Tag.toast, Tag.hack]
)
class ToastDynamicContentSample : MarkwonTextViewSample() {
public class ToastDynamicContentSample : MarkwonTextViewSample() {
override fun render() {
val md = """
# Head!

View File

@ -1,13 +1,13 @@
package io.noties.markwon.app.samples.basics
import android.text.Spanned
import com.vladsch.flexmark.util.ast.Node
import io.noties.markwon.Markwon
import io.noties.markwon.app.sample.ui.MarkwonTextViewSample
import io.noties.markwon.core.CorePlugin
import io.noties.markwon.sample.annotations.MarkwonArtifact
import io.noties.markwon.sample.annotations.MarkwonSampleInfo
import io.noties.markwon.sample.annotations.Tag
import org.commonmark.node.Node
@MarkwonSampleInfo(
id = "20200626153426",

View File

@ -15,8 +15,9 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.commonmark.node.CustomNode;
import org.commonmark.node.Node;
import com.vladsch.flexmark.parser.InlineParser;
import com.vladsch.flexmark.parser.internal.InlineParserImpl;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -26,8 +27,6 @@ import io.noties.markwon.Markwon;
import io.noties.markwon.MarkwonVisitor;
import io.noties.markwon.app.sample.ui.MarkwonTextViewSample;
import io.noties.markwon.image.ImagesPlugin;
import io.noties.markwon.inlineparser.InlineProcessor;
import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin;
import io.noties.markwon.sample.annotations.MarkwonArtifact;
import io.noties.markwon.sample.annotations.MarkwonSampleInfo;
import io.noties.markwon.sample.annotations.Tag;
@ -79,7 +78,7 @@ public class InlineParsingTooltipSample extends MarkwonTextViewSample {
}
}
class TooltipInlineProcessor extends InlineProcessor {
class TooltipInlineProcessor extends InlineParserImpl {
// NB! without bang
// `\\{` is required (although marked as redundant), without it - runtime crash

View File

@ -1,37 +0,0 @@
package io.noties.markwon.app.samples.latex;
import io.noties.markwon.Markwon;
import io.noties.markwon.app.sample.ui.MarkwonTextViewSample;
import io.noties.markwon.app.samples.latex.shared.LatexHolder;
import io.noties.markwon.ext.latex.JLatexMathPlugin;
import io.noties.markwon.sample.annotations.MarkwonArtifact;
import io.noties.markwon.sample.annotations.MarkwonSampleInfo;
import io.noties.markwon.sample.annotations.Tag;
@MarkwonSampleInfo(
id = "20200701090335",
title = "LaTeX blocks in legacy mode",
description = "Sample using _legacy_ LaTeX block parsing (pre `4.3.0` Markwon version)",
artifacts = MarkwonArtifact.EXT_LATEX,
tags = Tag.rendering
)
public class LatexLegacySample extends MarkwonTextViewSample {
@Override
public void render() {
final String md = "" +
"# LaTeX legacy\n" +
"There are no inlines in previous versions, only blocks:\n" +
"$$\n" +
"" + LatexHolder.LATEX_BOXES + "\n" +
"$$\n" +
"yeah";
final Markwon markwon = Markwon.builder(context)
.usePlugin(JLatexMathPlugin.create(textView.getTextSize(), builder -> {
builder.blocksLegacy(true);
}))
.build();
markwon.setMarkdown(textView, md);
}
}

View File

@ -3,7 +3,6 @@ package io.noties.markwon.app.samples.latex;
import io.noties.markwon.Markwon;
import io.noties.markwon.app.sample.ui.MarkwonTextViewSample;
import io.noties.markwon.ext.latex.JLatexMathPlugin;
import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin;
import io.noties.markwon.sample.annotations.MarkwonArtifact;
import io.noties.markwon.sample.annotations.MarkwonSampleInfo;
import io.noties.markwon.sample.annotations.Tag;
@ -27,7 +26,6 @@ public class LatexOmegaSample extends MarkwonTextViewSample {
"$$\\Omega$$";
final Markwon markwon = Markwon.builder(context)
.usePlugin(MarkwonInlineParserPlugin.create())
.usePlugin(JLatexMathPlugin.create(textView.getTextSize(), builder -> {
builder.inlinesEnabled(true);
}))

View File

@ -8,7 +8,6 @@ import io.noties.markwon.app.sample.ui.MarkwonTextViewSample;
import io.noties.markwon.app.samples.latex.shared.LatexHolder;
import io.noties.markwon.ext.latex.JLatexMathPlugin;
import io.noties.markwon.ext.latex.JLatexMathTheme;
import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin;
import io.noties.markwon.sample.annotations.MarkwonArtifact;
import io.noties.markwon.sample.annotations.MarkwonSampleInfo;
import io.noties.markwon.sample.annotations.Tag;
@ -35,7 +34,6 @@ public class LatexThemeSample extends MarkwonTextViewSample {
final int blockPadding = (int) (16 * context.getResources().getDisplayMetrics().density + 0.5F);
final Markwon markwon = Markwon.builder(context)
.usePlugin(MarkwonInlineParserPlugin.create())
.usePlugin(JLatexMathPlugin.create(textView.getTextSize(), builder -> {
builder.inlinesEnabled(true);
builder.theme()

View File

@ -13,13 +13,13 @@ import android.widget.RemoteViews;
import androidx.annotation.NonNull;
import org.commonmark.ext.gfm.strikethrough.Strikethrough;
import org.commonmark.node.BlockQuote;
import org.commonmark.node.Code;
import org.commonmark.node.Emphasis;
import org.commonmark.node.Heading;
import org.commonmark.node.ListItem;
import org.commonmark.node.StrongEmphasis;
import com.vladsch.flexmark.ast.BlockQuote;
import com.vladsch.flexmark.ast.Code;
import com.vladsch.flexmark.ast.Emphasis;
import com.vladsch.flexmark.ast.Heading;
import com.vladsch.flexmark.ast.ListItem;
import com.vladsch.flexmark.ast.StrongEmphasis;
import com.vladsch.flexmark.ext.gfm.strikethrough.Strikethrough;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.Markwon;

View File

@ -1,47 +0,0 @@
package io.noties.markwon.app.samples.parser
import io.noties.markwon.AbstractMarkwonPlugin
import io.noties.markwon.Markwon
import io.noties.markwon.app.sample.ui.MarkwonTextViewSample
import io.noties.markwon.core.CorePlugin
import io.noties.markwon.sample.annotations.MarkwonArtifact
import io.noties.markwon.sample.annotations.MarkwonSampleInfo
import io.noties.markwon.sample.annotations.Tag
import org.commonmark.node.Heading
import org.commonmark.parser.Parser
import org.commonmark.parser.block.BlockParserFactory
import org.commonmark.parser.block.BlockStart
import org.commonmark.parser.block.MatchedBlockParser
import org.commonmark.parser.block.ParserState
@MarkwonSampleInfo(
id = "20201111221207",
title = "Custom heading parser",
description = "Custom heading block parser. Actual parser is not implemented",
artifacts = [MarkwonArtifact.CORE],
tags = [Tag.parsing, Tag.heading]
)
class CustomHeadingParserSample : MarkwonTextViewSample() {
override fun render() {
val md = "#Head"
val markwon = Markwon.builder(context)
.usePlugin(object : AbstractMarkwonPlugin() {
override fun configureParser(builder: Parser.Builder) {
val enabled = CorePlugin.enabledBlockTypes()
.filter { it != Heading::class.java }
.toSet()
builder.enabledBlockTypes(enabled)
builder.customBlockParserFactory(MyHeadingBlockParserFactory)
}
})
.build()
markwon.setMarkdown(textView, md)
}
object MyHeadingBlockParserFactory : BlockParserFactory {
override fun tryStart(state: ParserState, matchedBlockParser: MatchedBlockParser): BlockStart? {
// TODO("Not yet implemented")
return null
}
}
}

View File

@ -1,156 +0,0 @@
package io.noties.markwon.app.samples.plugins;
import androidx.annotation.NonNull;
import org.commonmark.node.AbstractVisitor;
import org.commonmark.node.BulletList;
import org.commonmark.node.CustomBlock;
import org.commonmark.node.Heading;
import org.commonmark.node.Link;
import org.commonmark.node.ListItem;
import org.commonmark.node.Node;
import org.commonmark.node.Text;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.Markwon;
import io.noties.markwon.MarkwonVisitor;
import io.noties.markwon.app.R;
import io.noties.markwon.app.sample.ui.MarkwonTextViewSample;
import io.noties.markwon.app.samples.plugins.shared.AnchorHeadingPlugin;
import io.noties.markwon.core.SimpleBlockNodeVisitor;
import io.noties.markwon.sample.annotations.MarkwonArtifact;
import io.noties.markwon.sample.annotations.MarkwonSampleInfo;
import io.noties.markwon.sample.annotations.Tag;
@MarkwonSampleInfo(
id = "20200629161226",
title = "Table of contents",
description = "Sample plugin that adds a table of contents header",
artifacts = MarkwonArtifact.CORE,
tags = {Tag.rendering, Tag.plugin}
)
public class TableOfContentsSample extends MarkwonTextViewSample {
@Override
public void render() {
final String lorem = context.getString(R.string.lorem);
final String md = "" +
"# First\n" +
"" + lorem + "\n\n" +
"# Second\n" +
"" + lorem + "\n\n" +
"## Second level\n\n" +
"" + lorem + "\n\n" +
"### Level 3\n\n" +
"" + lorem + "\n\n" +
"# First again\n" +
"" + lorem + "\n\n";
final Markwon markwon = Markwon.builder(context)
.usePlugin(new TableOfContentsPlugin())
// NB! plugin is defined in `AnchorSample` file
.usePlugin(new AnchorHeadingPlugin((view, top) -> scrollView.smoothScrollTo(0, top)))
.build();
markwon.setMarkdown(textView, md);
}
}
class TableOfContentsPlugin extends AbstractMarkwonPlugin {
@Override
public void configure(@NonNull Registry registry) {
// just to make it explicit
registry.require(AnchorHeadingPlugin.class);
}
@Override
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
builder.on(TableOfContentsBlock.class, new SimpleBlockNodeVisitor());
}
@Override
public void beforeRender(@NonNull Node node) {
// custom block to hold TOC
final TableOfContentsBlock block = new TableOfContentsBlock();
// create TOC title
{
final Text text = new Text("Table of contents");
final Heading heading = new Heading();
// important one - set TOC heading level
heading.setLevel(1);
heading.appendChild(text);
block.appendChild(heading);
}
final HeadingVisitor visitor = new HeadingVisitor(block);
node.accept(visitor);
// make it the very first node in rendered markdown
node.prependChild(block);
}
private static class HeadingVisitor extends AbstractVisitor {
private final BulletList bulletList = new BulletList();
private final StringBuilder builder = new StringBuilder();
private boolean isInsideHeading;
HeadingVisitor(@NonNull Node node) {
node.appendChild(bulletList);
}
@Override
public void visit(Heading heading) {
this.isInsideHeading = true;
try {
// reset build from previous content
builder.setLength(0);
// obtain level (can additionally filter by level, to skip lower ones)
final int level = heading.getLevel();
// build heading title
visitChildren(heading);
// initial list item
final ListItem listItem = new ListItem();
Node parent = listItem;
Node node = listItem;
for (int i = 1; i < level; i++) {
final ListItem li = new ListItem();
final BulletList bulletList = new BulletList();
bulletList.appendChild(li);
parent.appendChild(bulletList);
parent = li;
node = li;
}
final String content = builder.toString();
final Link link = new Link("#" + AnchorHeadingPlugin.createAnchor(content), null);
final Text text = new Text(content);
link.appendChild(text);
node.appendChild(link);
bulletList.appendChild(listItem);
} finally {
isInsideHeading = false;
}
}
@Override
public void visit(Text text) {
// can additionally check if we are building heading (to skip all other texts)
if (isInsideHeading) {
builder.append(text.getLiteral());
}
}
}
private static class TableOfContentsBlock extends CustomBlock {
}
}

View File

@ -5,7 +5,6 @@ import io.noties.markwon.app.sample.ui.MarkwonTextViewSample;
import io.noties.markwon.ext.latex.JLatexMathPlugin;
import io.noties.markwon.ext.tables.TablePlugin;
import io.noties.markwon.image.ImagesPlugin;
import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin;
import io.noties.markwon.sample.annotations.MarkwonArtifact;
import io.noties.markwon.sample.annotations.MarkwonSampleInfo;
import io.noties.markwon.sample.annotations.Tag;
@ -34,7 +33,6 @@ public class TableLatexSample extends MarkwonTextViewSample {
"\n";
final Markwon markwon = Markwon.builder(context)
.usePlugin(MarkwonInlineParserPlugin.create())
.usePlugin(ImagesPlugin.create())
.usePlugin(JLatexMathPlugin.create(textView.getTextSize(), builder -> builder.inlinesEnabled(true)))
.usePlugin(TablePlugin.create(context))

View File

@ -1,157 +0,0 @@
package io.noties.markwon.app.samples.tasklist
import android.text.style.ClickableSpan
import android.view.View
import io.noties.debug.Debug
import io.noties.markwon.AbstractMarkwonPlugin
import io.noties.markwon.Markwon
import io.noties.markwon.MarkwonVisitor
import io.noties.markwon.SoftBreakAddsNewLinePlugin
import io.noties.markwon.SpannableBuilder
import io.noties.markwon.app.sample.ui.MarkwonTextViewSample
import io.noties.markwon.ext.tasklist.TaskListItem
import io.noties.markwon.ext.tasklist.TaskListPlugin
import io.noties.markwon.ext.tasklist.TaskListProps
import io.noties.markwon.ext.tasklist.TaskListSpan
import io.noties.markwon.sample.annotations.MarkwonArtifact
import io.noties.markwon.sample.annotations.MarkwonSampleInfo
import io.noties.markwon.sample.annotations.Tag
import org.commonmark.node.AbstractVisitor
import org.commonmark.node.Block
import org.commonmark.node.HardLineBreak
import org.commonmark.node.Node
import org.commonmark.node.Paragraph
import org.commonmark.node.SoftLineBreak
import org.commonmark.node.Text
@MarkwonSampleInfo(
id = "20201228120444",
title = "Task list mutate nested",
description = "Task list mutation with nested items",
artifacts = [MarkwonArtifact.EXT_TASKLIST],
tags = [Tag.plugin]
)
class TaskListMutateNestedSample : MarkwonTextViewSample() {
override fun render() {
val md = """
# Task list
- [ ] not done
- [X] done
- [ ] nested not done
and text and textand text and text
- [X] nested done
""".trimIndent()
val markwon = Markwon.builder(context)
.usePlugin(TaskListPlugin.create(context))
.usePlugin(SoftBreakAddsNewLinePlugin.create())
.usePlugin(object : AbstractMarkwonPlugin() {
override fun configureVisitor(builder: MarkwonVisitor.Builder) {
builder.on(TaskListItem::class.java) { visitor, node ->
val length = visitor.length()
visitor.visitChildren(node)
TaskListProps.DONE.set(visitor.renderProps(), node.isDone)
val spans = visitor.configuration()
.spansFactory()
.get(TaskListItem::class.java)
?.getSpans(visitor.configuration(), visitor.renderProps())
if (spans != null) {
val taskListSpan = if (spans is Array<*>) {
spans.first { it is TaskListSpan } as? TaskListSpan
} else {
spans as? TaskListSpan
}
Debug.i("#### ${visitor.builder().substring(length, length + 3)}")
val content = TaskListContextVisitor.contentLength(node)
Debug.i("#### content: $content, '${visitor.builder().subSequence(length, length + content)}'")
if (content > 0 && taskListSpan != null) {
// maybe additionally identify this task list (for persistence)
visitor.builder().setSpan(
ToggleTaskListSpan(taskListSpan, visitor.builder().substring(length, length + content)),
length,
length + content
)
}
}
SpannableBuilder.setSpans(
visitor.builder(),
spans,
length,
visitor.length()
)
if (visitor.hasNext(node)) {
visitor.ensureNewLine()
}
}
}
})
.build()
markwon.setMarkdown(textView, md)
}
class TaskListContextVisitor : AbstractVisitor() {
companion object {
fun contentLength(node: Node): Int {
val visitor = TaskListContextVisitor()
visitor.visitChildren(node)
return visitor.contentLength
}
}
var contentLength: Int = 0
override fun visit(text: Text) {
super.visit(text)
contentLength += text.literal.length
}
// NB! if count both soft and hard breaks as having length of 1
override fun visit(softLineBreak: SoftLineBreak?) {
super.visit(softLineBreak)
contentLength += 1
}
// NB! if count both soft and hard breaks as having length of 1
override fun visit(hardLineBreak: HardLineBreak?) {
super.visit(hardLineBreak)
contentLength += 1
}
override fun visitChildren(parent: Node) {
var node = parent.firstChild
while (node != null) {
// A subclass of this visitor might modify the node, resulting in getNext returning a different node or no
// node after visiting it. So get the next node before visiting.
val next = node.next
if (node is Block && node !is Paragraph) {
break
}
node.accept(this)
node = next
}
}
}
class ToggleTaskListSpan(
val span: TaskListSpan,
val content: String
) : ClickableSpan() {
override fun onClick(widget: View) {
span.isDone = !span.isDone
widget.invalidate()
Debug.i("task-list click, isDone: ${span.isDone}, content: '$content'")
}
}
}

View File

@ -14,7 +14,6 @@ import io.noties.markwon.Markwon;
import io.noties.markwon.MarkwonSpansFactory;
import io.noties.markwon.SpanFactory;
import io.noties.markwon.app.sample.ui.MarkwonTextViewSample;
import io.noties.markwon.ext.tasklist.TaskListItem;
import io.noties.markwon.ext.tasklist.TaskListPlugin;
import io.noties.markwon.ext.tasklist.TaskListSpan;
import io.noties.markwon.sample.annotations.MarkwonArtifact;
@ -23,6 +22,8 @@ import io.noties.markwon.sample.annotations.Tag;
import static io.noties.markwon.app.samples.tasklist.shared.TaskListHolder.MD;
import com.vladsch.flexmark.ext.gfm.tasklist.TaskListItem;
@MarkwonSampleInfo(
id = "20200702140901",
title = "GFM task list mutate",

View File

@ -1,13 +1,14 @@
buildscript {
ext.kotlin_version = '1.4.10'
ext.kotlin_version = '1.7.20'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.2'
classpath 'com.android.tools.build:gradle:7.4.0'
classpath 'com.github.ben-manes:gradle-versions-plugin:0.28.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
}
}
@ -27,16 +28,23 @@ allprojects {
tasks.withType(Javadoc) {
enabled = false
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions {
// Treat all Kotlin warnings as errors
// allWarningsAsErrors = true
freeCompilerArgs += '-opt-in=kotlin.RequiresOptIn'
// Set JVM target to 11
jvmTarget = JavaVersion.VERSION_11
}
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
wrapper {
gradleVersion '6.1.1'
distributionType 'all'
}
if (hasProperty('local')) {
if (!hasProperty('LOCAL_MAVEN_URL')) {
@ -49,10 +57,10 @@ if (hasProperty('local')) {
ext {
config = [
'build-tools' : '29.0.3',
'compile-sdk' : 29,
'target-sdk' : 29,
'min-sdk' : 16,
'build-tools' : '33.0.1',
'compile-sdk' : 33,
'target-sdk' : 33,
'min-sdk' : 21,
'push-aar-gradle': 'https://raw.githubusercontent.com/noties/gradle-mvn-push/master/gradle-mvn-push-aar.gradle'
]
@ -72,6 +80,7 @@ ext {
'x-appcompat' : 'androidx.appcompat:appcompat:1.1.0',
'x-cardview' : 'androidx.cardview:cardview:1.0.0',
'x-fragment' : 'androidx.fragment:fragment:1.0.0',
'flexmark' : 'com.vladsch.flexmark:flexmark-all:0.64.0',
'commonmark' : "com.atlassian.commonmark:commonmark:$commonMarkVersion",
'commonmark-strikethrough': "com.atlassian.commonmark:commonmark-ext-gfm-strikethrough:$commonMarkVersion",
'commonmark-table' : "com.atlassian.commonmark:commonmark-ext-gfm-tables:$commonMarkVersion",

View File

@ -5,19 +5,21 @@ org.gradle.configureondemand=true
android.useAndroidX=true
android.enableJetifier=true
android.enableBuildCache=true
android.buildCacheDir=build/pre-dex-cache
VERSION_NAME=4.6.2
VERSION_NAME=5.0.0
GROUP=io.noties.markwon
POM_DESCRIPTION=Markwon markdown for Android
POM_URL=https://github.com/noties/Markwon
POM_SCM_URL=https://github.com/noties/Markwon
POM_SCM_CONNECTION=scm:git:git://github.com/noties/Markwon.git
POM_SCM_DEV_CONNECTION=scm:git:git://github.com/noties/Markwon.git
POM_URL=https://github.com/cpacm/Markwon
POM_SCM_URL=https://github.com/cpacm/Markwon
POM_SCM_CONNECTION=scm:git:git://github.com/cpacm/Markwon.git
POM_SCM_DEV_CONNECTION=scm:git:git://github.com/cpacm/Markwon.git
POM_LICENCE_NAME=The Apache Software License, Version 2.0
POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
POM_LICENCE_DIST=repo
POM_DEVELOPER_ID=noties
POM_DEVELOPER_NAME=Dimitry Ivanov
POM_DEVELOPER_ID=cpacm
POM_DEVELOPER_NAME=cpacm
POM_DEVELOPER_EMAIL=cpacm@8bgm.com
POM_COMPANY=Github
POM_OFFICIAL_WEBSITE=https://github.com/cpacm/Markwon
POM_SCM_ISSUES=https://github.com/cpacm/Markwon/issues

View File

@ -0,0 +1,20 @@
#!/bin/bash
./gradlew clean \
&& ./gradlew :markwon-core:publishToMavenLocal \
&& ./gradlew :markwon-ext-latex:publishToMavenLocal \
&& ./gradlew :markwon-ext-strikethrough:publishToMavenLocal \
&& ./gradlew :markwon-ext-tables:publishToMavenLocal \
&& ./gradlew :markwon-ext-tasklist:publishToMavenLocal \
&& ./gradlew :markwon-html:publishToMavenLocal \
&& ./gradlew :markwon-image:publishToMavenLocal \
&& ./gradlew :markwon-image-coil:publishToMavenLocal \
&& ./gradlew :markwon-image-glide:publishToMavenLocal \
&& ./gradlew :markwon-image-picasso:publishToMavenLocal \
&& ./gradlew :markwon-linkify:publishToMavenLocal \
&& ./gradlew :markwon-recycler:publishToMavenLocal \
&& ./gradlew :markwon-recycler-table:publishToMavenLocal \
&& ./gradlew :markwon-simple-ext:publishToMavenLocal \
&& ./gradlew :markwon-syntax-highlight:publishToMavenLocal \
&& ./gradlew :markwon-editor:publishToMavenLocal \
&& ./gradlew clean

240
gradle/publishMaven.gradle Normal file
View File

@ -0,0 +1,240 @@
/*
* Copyright 2021 Cpacm
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
* Based on: https://github.com/mcxiaoke/gradle-mvn-push/blob/master/gradle-mvn-push.gradle.
*
* To install in a local maven repo:
* 1. In the project you want to test, add mavenLocal() to the repositories list.
* 2. In Project, run: ./gradlew publishToMavenLocal
*
* For faster runs add: -x check when building.
*/
apply plugin: 'maven-publish'
apply plugin: 'signing'
version = VERSION_NAME
group = GROUP
@SuppressWarnings("GrMethodMayBeStatic")
def isReleaseBuild() {
return !version.contains("SNAPSHOT")
}
def getReleaseRepositoryUrl() {
return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
: 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/'
}
def getSnapshotRepositoryUrl() {
return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
: 'https://s01.oss.sonatype.org/content/repositories/snapshots/'
}
def getRepositoryUsername() {
return hasProperty('USERNAME') ? USERNAME : (hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : '')
}
def getRepositoryPassword() {
return hasProperty('PASSWORD') ? PASSWORD : (hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : '')
}
def configurePom(pom) {
pom.name = POM_NAME
pom.packaging = POM_PACKAGING
pom.description = POM_DESCRIPTION
pom.url = POM_URL
pom.scm {
url = POM_SCM_URL
connection = POM_SCM_CONNECTION
developerConnection = POM_SCM_DEV_CONNECTION
}
pom.licenses {
license {
name = POM_LICENCE_NAME
url = POM_LICENCE_URL
distribution = POM_LICENCE_DIST
}
}
pom.issueManagement {
system = 'GitHub Issues'
url = POM_SCM_ISSUES
}
pom.developers {
developer {
id = POM_DEVELOPER_ID
name = POM_DEVELOPER_NAME
email = POM_DEVELOPER_EMAIL
}
}
}
afterEvaluate { project ->
def isAndroidProject = project.plugins.hasPlugin('com.android.application') || project.plugins.hasPlugin('com.android.library')
publishing {
repositories {
maven {
def releasesRepoUrl = getReleaseRepositoryUrl()
def snapshotsRepoUrl = getSnapshotRepositoryUrl()
url = isReleaseBuild() ? releasesRepoUrl : snapshotsRepoUrl
credentials(PasswordCredentials) {
username = getRepositoryUsername()
password = getRepositoryPassword()
}
}
}
}
if (isAndroidProject) {
task androidJavadocs(type: Javadoc, dependsOn: assembleDebug) {
source = android.sourceSets.main.java.source
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
// include generated file
classpath += project.files("${buildDir}/generated/source/buildConfig/debug")
classpath += project.files("${buildDir}/generated/ap_generated_sources/debug/out")
excludes = ['**/*.kt']
}
task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
classifier = 'javadoc'
from androidJavadocs.destinationDir
}
task androidSourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.source
}
android.libraryVariants.all { variant ->
tasks.androidJavadocs.doFirst {
classpath += files(variant.javaCompileProvider.get().classpath.files.join(File.pathSeparator))
}
}
} else {
task sourcesJar(type: Jar, dependsOn: classes) {
classifier = 'sources'
from sourceSets.main.allSource
}
task javadocsJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
artifacts {
archives sourcesJar
archives javadocsJar
}
}
if (JavaVersion.current().isJava8Compatible()) {
allprojects {
tasks.withType(Javadoc) {
options.addStringOption('Xdoclint:none', '-quiet')
}
}
}
if (JavaVersion.current().isJava9Compatible()) {
allprojects {
tasks.withType(Javadoc) {
options.addBooleanOption('html5', true)
}
}
}
artifacts {
if (isAndroidProject) {
archives androidSourcesJar
archives androidJavadocsJar
archives project.tasks.bundleDebugAar
}
}
publishing {
publications {
mavenAgent(MavenPublication) {
groupId GROUP
artifactId POM_ARTIFACT_ID
version version
configurePom(pom)
if (isAndroidProject) {
artifact bundleReleaseAar
artifact androidSourcesJar
pom.withXml {
def dependenciesNode = asNode().appendNode('dependencies')
project.configurations.all { configuration ->
def name = configuration.name
// api will duplicate with implementation
if (name == 'releaseImplementation' || name == 'implementation') {
configuration.allDependencies.each {
if (it.name != "unspecified" && it.version != "unspecified") {
def groupId = it.group
def artifactId = it.name
if (it instanceof ProjectDependency) {
// skip eg:implementation project(:module)
// def properties = it.getDependencyProject().getProperties()
// groupId = properties.get("GROUP")
// artifactId = properties.get("POM_ARTIFACT_ID")
// if (!artifactId.equals("annotation")) {return}
return
}
println "dependencies:" + groupId + ":" + artifactId + ":" + it.version
def dependencyNode = dependenciesNode.appendNode('dependency')
dependencyNode.appendNode('groupId', groupId)
dependencyNode.appendNode('artifactId', artifactId)
dependencyNode.appendNode('version', it.version)
dependencyNode.appendNode('scope', 'compile')
}
}
}
}
}
} else {
from components.java
artifact sourcesJar
artifact javadocsJar
}
}
if (project.plugins.hasPlugin('java-gradle-plugin')) {
pluginMaven(MavenPublication) {
groupId GROUP
artifactId POM_ARTIFACT_ID
version version
configurePom(pom)
}
}
}
}
}
signing {
required { isReleaseBuild() && gradle.taskGraph.hasTask("publish") }
publishing.publications.all { publication ->
sign publication
}
}

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -17,7 +17,7 @@ dependencies {
deps.with {
api it['x-annotations']
api it['commonmark']
api it['flexmark']
// @since 4.1.0 to allow PrecomputedTextSetterCompat
// note that this dependency must be added on a client side explicitly
@ -35,4 +35,6 @@ dependencies {
}
}
registerArtifact(this)
registerArtifact(this)
apply from: "${rootProject.projectDir}/gradle/publishMaven.gradle"

View File

@ -5,8 +5,8 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.ast.Node;
import io.noties.markwon.core.MarkwonTheme;

View File

@ -2,7 +2,7 @@ package io.noties.markwon;
import androidx.annotation.NonNull;
import org.commonmark.node.Node;
import com.vladsch.flexmark.util.ast.Node;
/**
* @since 4.3.0

View File

@ -7,7 +7,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.commonmark.node.Node;
import com.vladsch.flexmark.util.ast.Node;
import java.util.List;

View File

@ -5,7 +5,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import org.commonmark.parser.Parser;
import com.vladsch.flexmark.parser.Parser;
import java.util.ArrayList;
import java.util.Collections;

View File

@ -8,8 +8,14 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
import com.vladsch.flexmark.ast.Code;
import com.vladsch.flexmark.ast.Text;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.ast.Document;
import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.util.ast.NodeVisitor;
import com.vladsch.flexmark.util.ast.VisitHandler;
import java.util.Collections;
import java.util.List;
@ -74,7 +80,7 @@ class MarkwonImpl extends Markwon {
// @since 4.1.1 obtain visitor via factory
final MarkwonVisitor visitor = visitorFactory.create();
node.accept(visitor);
visitor.visit(node);
for (MarkwonPlugin plugin : plugins) {
plugin.afterRender(node, visitor);

View File

@ -5,8 +5,8 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.ast.Node;
import io.noties.markwon.core.CorePlugin;
import io.noties.markwon.core.MarkwonTheme;

View File

@ -2,8 +2,8 @@ package io.noties.markwon;
import androidx.annotation.NonNull;
import org.commonmark.node.LinkReferenceDefinition;
import org.commonmark.node.Node;
import com.vladsch.flexmark.ast.Reference;
import com.vladsch.flexmark.util.ast.Node;
import java.util.ArrayList;
import java.util.Collections;
@ -51,7 +51,7 @@ public abstract class MarkwonReducer {
while (node != null) {
// @since 4.5.0 do not include LinkReferenceDefinition node (would result
// in empty textView if rendered in recycler-view)
if (!(node instanceof LinkReferenceDefinition)) {
if (!(node instanceof Reference)) {
list.add(node);
}
temp = node.getNext();

View File

@ -3,7 +3,7 @@ package io.noties.markwon;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.commonmark.node.Node;
import com.vladsch.flexmark.util.ast.Node;
/**
* Class that controls what spans are used for certain Nodes.

View File

@ -3,7 +3,7 @@ package io.noties.markwon;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.commonmark.node.Node;
import com.vladsch.flexmark.util.ast.Node;
import java.util.ArrayList;
import java.util.Collections;

View File

@ -3,8 +3,12 @@ package io.noties.markwon;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.commonmark.node.Node;
import org.commonmark.node.Visitor;
import com.vladsch.flexmark.ast.Heading;
import com.vladsch.flexmark.ast.util.BlockVisitor;
import com.vladsch.flexmark.ast.util.InlineVisitor;
import com.vladsch.flexmark.util.ast.Node;
import org.jetbrains.annotations.NotNull;
/**
* Configurable visitor of parsed markdown. Allows visiting certain (registered) nodes without
@ -14,7 +18,7 @@ import org.commonmark.node.Visitor;
* @see MarkwonPlugin#configureVisitor(Builder)
* @since 3.0.0
*/
public interface MarkwonVisitor extends Visitor {
public interface MarkwonVisitor extends BlockVisitor, InlineVisitor {
/**
* @see Builder#on(Class, NodeVisitor)
@ -69,13 +73,6 @@ public interface MarkwonVisitor extends Visitor {
@NonNull
SpannableBuilder builder();
/**
* Visits all children of supplied node.
*
* @param node to visit
*/
void visitChildren(@NonNull Node node);
/**
* Executes a check if there is further content available.
*
@ -166,4 +163,8 @@ public interface MarkwonVisitor extends Visitor {
* @since 4.3.0
*/
void blockEnd(@NonNull Node node);
void visit(@NotNull Node node);
void visitChildren(Node node);
}

View File

@ -3,30 +3,39 @@ package io.noties.markwon;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
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.LinkReferenceDefinition;
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 com.vladsch.flexmark.ast.AutoLink;
import com.vladsch.flexmark.ast.BlockQuote;
import com.vladsch.flexmark.ast.BulletList;
import com.vladsch.flexmark.ast.BulletListItem;
import com.vladsch.flexmark.ast.Code;
import com.vladsch.flexmark.ast.Emphasis;
import com.vladsch.flexmark.ast.FencedCodeBlock;
import com.vladsch.flexmark.ast.HardLineBreak;
import com.vladsch.flexmark.ast.Heading;
import com.vladsch.flexmark.ast.HtmlBlock;
import com.vladsch.flexmark.ast.HtmlCommentBlock;
import com.vladsch.flexmark.ast.HtmlEntity;
import com.vladsch.flexmark.ast.HtmlInline;
import com.vladsch.flexmark.ast.HtmlInlineComment;
import com.vladsch.flexmark.ast.Image;
import com.vladsch.flexmark.ast.ImageRef;
import com.vladsch.flexmark.ast.IndentedCodeBlock;
import com.vladsch.flexmark.ast.Link;
import com.vladsch.flexmark.ast.LinkRef;
import com.vladsch.flexmark.ast.ListItem;
import com.vladsch.flexmark.ast.MailLink;
import com.vladsch.flexmark.ast.OrderedList;
import com.vladsch.flexmark.ast.OrderedListItem;
import com.vladsch.flexmark.ast.Paragraph;
import com.vladsch.flexmark.ast.Reference;
import com.vladsch.flexmark.ast.SoftLineBreak;
import com.vladsch.flexmark.ast.StrongEmphasis;
import com.vladsch.flexmark.ast.Text;
import com.vladsch.flexmark.ast.ThematicBreak;
import com.vladsch.flexmark.ast.util.BlockVisitorExt;
import com.vladsch.flexmark.ast.util.InlineVisitorExt;
import com.vladsch.flexmark.util.ast.Document;
import com.vladsch.flexmark.util.ast.Node;
import java.util.Collections;
import java.util.HashMap;
@ -35,7 +44,7 @@ import java.util.Map;
/**
* @since 3.0.0
*/
class MarkwonVisitorImpl implements MarkwonVisitor {
class MarkwonVisitorImpl extends com.vladsch.flexmark.util.ast.NodeVisitor implements MarkwonVisitor {
private final MarkwonConfiguration configuration;
@ -59,124 +68,169 @@ class MarkwonVisitorImpl implements MarkwonVisitor {
this.builder = builder;
this.nodes = nodes;
this.blockHandler = blockHandler;
addHandlers(BlockVisitorExt.VISIT_HANDLERS(this));
addHandlers(InlineVisitorExt.VISIT_HANDLERS(this));
}
@Override
public void visit(BlockQuote blockQuote) {
visit((Node) blockQuote);
visitImpl(blockQuote);
}
@Override
public void visit(BulletList bulletList) {
visit((Node) bulletList);
visitImpl(bulletList);
}
@Override
public void visit(AutoLink autoLink) {
visitImpl(autoLink);
}
@Override
public void visit(Code code) {
visit((Node) code);
visitImpl(code);
}
@Override
public void visit(Document document) {
visit((Node) document);
visitImpl(document);
}
@Override
public void visit(Emphasis emphasis) {
visit((Node) emphasis);
visitImpl(emphasis);
}
@Override
public void visit(FencedCodeBlock fencedCodeBlock) {
visit((Node) fencedCodeBlock);
visitImpl(fencedCodeBlock);
}
public void visit(HardLineBreak hardLineBreak) {
visitImpl(hardLineBreak);
}
@Override
public void visit(HardLineBreak hardLineBreak) {
visit((Node) hardLineBreak);
public void visit(HtmlEntity node) {
visitImpl(node);
}
@Override
public void visit(Heading heading) {
visit((Node) heading);
visitImpl(heading);
}
@Override
public void visit(ThematicBreak thematicBreak) {
visit((Node) thematicBreak);
visitImpl(thematicBreak);
}
@Override
public void visit(HtmlInline htmlInline) {
visit((Node) htmlInline);
visitImpl(htmlInline);
}
@Override
public void visit(HtmlInlineComment node) {
visitImpl(node);
}
@Override
public void visit(HtmlBlock htmlBlock) {
visit((Node) htmlBlock);
visitImpl(htmlBlock);
}
@Override
public void visit(HtmlCommentBlock node) {
visitImpl(node);
}
/**
* ![Alt text](/path/to/img.jpg "Optional title")
*/
@Override
public void visit(Image image) {
visit((Node) image);
visitImpl(image);
}
/**
* This is a ![foo][bar] image.
* [bar]: /url/of/bar.jpg "optional title attribute"
*/
@Override
public void visit(ImageRef imageRef) {
visitImpl(imageRef);
}
@Override
public void visit(IndentedCodeBlock indentedCodeBlock) {
visit((Node) indentedCodeBlock);
visitImpl(indentedCodeBlock);
}
// @Override
// public void visit(ListItem listItem) {
// visitImpl(listItem);
// }
@Override
public void visit(BulletListItem listItem) {
visitImpl(listItem);
}
@Override
public void visit(OrderedListItem listItem) {
visitImpl(listItem);
}
@Override
public void visit(Link link) {
visit((Node) link);
visitImpl(link);
}
@Override
public void visit(ListItem listItem) {
visit((Node) listItem);
public void visit(LinkRef node) {
visitImpl(node);
}
@Override
public void visit(MailLink node) {
visitImpl(node);
}
@Override
public void visit(OrderedList orderedList) {
visit((Node) orderedList);
visitImpl(orderedList);
}
@Override
public void visit(Paragraph paragraph) {
visit((Node) paragraph);
visitImpl(paragraph);
}
@Override
public void visit(Reference node) {
// Reference 需要和 ImageRef,LinkRef配合使用因为这些都是指向 Reference,再由Reference指向真正的地址
}
@Override
public void visit(SoftLineBreak softLineBreak) {
visit((Node) softLineBreak);
visitImpl(softLineBreak);
}
@Override
public void visit(StrongEmphasis strongEmphasis) {
visit((Node) strongEmphasis);
visitImpl(strongEmphasis);
}
@Override
public void visit(Text text) {
visit((Node) text);
visitImpl(text);
}
@Override
public void visit(LinkReferenceDefinition linkReferenceDefinition) {
visit((Node) linkReferenceDefinition);
}
@Override
public void visit(CustomBlock customBlock) {
visit((Node) customBlock);
}
@Override
public void visit(CustomNode customNode) {
visit((Node) customNode);
}
private void visit(@NonNull Node node) {
private void visitImpl(@NonNull Node node) {
//noinspection unchecked
final NodeVisitor<Node> nodeVisitor = (NodeVisitor<Node>) nodes.get(node.getClass());
if (nodeVisitor != null) {
@ -204,17 +258,6 @@ class MarkwonVisitorImpl implements MarkwonVisitor {
return builder;
}
@Override
public void visitChildren(@NonNull Node parent) {
Node node = parent.getFirstChild();
while (node != null) {
// A subclass of this visitor might modify the node, resulting in getNext returning a different node or no
// node after visiting it. So get the next node before visiting.
Node next = node.getNext();
node.accept(this);
node = next;
}
}
@Override
public boolean hasNext(@NonNull Node node) {

View File

@ -2,7 +2,7 @@ package io.noties.markwon;
import androidx.annotation.NonNull;
import org.commonmark.node.SoftLineBreak;
import com.vladsch.flexmark.ast.SoftLineBreak;
/**
* @since 4.3.0

View File

@ -9,27 +9,27 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import org.commonmark.node.Block;
import org.commonmark.node.BlockQuote;
import org.commonmark.node.BulletList;
import org.commonmark.node.Code;
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.Image;
import org.commonmark.node.IndentedCodeBlock;
import org.commonmark.node.Link;
import org.commonmark.node.ListBlock;
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 com.vladsch.flexmark.ast.BlockQuote;
import com.vladsch.flexmark.ast.BulletList;
import com.vladsch.flexmark.ast.Code;
import com.vladsch.flexmark.ast.Emphasis;
import com.vladsch.flexmark.ast.FencedCodeBlock;
import com.vladsch.flexmark.ast.HardLineBreak;
import com.vladsch.flexmark.ast.Heading;
import com.vladsch.flexmark.ast.HtmlBlock;
import com.vladsch.flexmark.ast.Image;
import com.vladsch.flexmark.ast.IndentedCodeBlock;
import com.vladsch.flexmark.ast.Link;
import com.vladsch.flexmark.ast.ListBlock;
import com.vladsch.flexmark.ast.ListItem;
import com.vladsch.flexmark.ast.OrderedList;
import com.vladsch.flexmark.ast.Paragraph;
import com.vladsch.flexmark.ast.SoftLineBreak;
import com.vladsch.flexmark.ast.StrongEmphasis;
import com.vladsch.flexmark.ast.Text;
import com.vladsch.flexmark.ast.ThematicBreak;
import com.vladsch.flexmark.util.ast.Block;
import com.vladsch.flexmark.util.ast.Node;
import java.util.ArrayList;
import java.util.Arrays;
@ -212,8 +212,7 @@ public class CorePlugin extends AbstractMarkwonPlugin {
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull Text text) {
final String literal = text.getLiteral();
String literal = text.toAstString(true);
visitor.builder().append(literal);
// @since 4.0.0
@ -278,7 +277,7 @@ public class CorePlugin extends AbstractMarkwonPlugin {
// unfortunately we cannot use this for multiline code as we cannot control where a new line break will be inserted
visitor.builder()
.append('\u00a0')
.append(code.getLiteral())
.append(code.toAstString(true))
.append('\u00a0');
visitor.setSpansForNodeOptional(code, length);
@ -290,7 +289,7 @@ public class CorePlugin extends AbstractMarkwonPlugin {
builder.on(FencedCodeBlock.class, new MarkwonVisitor.NodeVisitor<FencedCodeBlock>() {
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull FencedCodeBlock fencedCodeBlock) {
visitCodeBlock(visitor, fencedCodeBlock.getInfo(), fencedCodeBlock.getLiteral(), fencedCodeBlock);
visitCodeBlock(visitor, fencedCodeBlock.getInfo().unescape(), fencedCodeBlock.toAstString(true), fencedCodeBlock);
}
});
}
@ -299,7 +298,7 @@ public class CorePlugin extends AbstractMarkwonPlugin {
builder.on(IndentedCodeBlock.class, new MarkwonVisitor.NodeVisitor<IndentedCodeBlock>() {
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull IndentedCodeBlock indentedCodeBlock) {
visitCodeBlock(visitor, null, indentedCodeBlock.getLiteral(), indentedCodeBlock);
visitCodeBlock(visitor, null, indentedCodeBlock.toAstString(true), indentedCodeBlock);
}
});
}
@ -335,7 +334,7 @@ public class CorePlugin extends AbstractMarkwonPlugin {
final String destination = configuration
.imageDestinationProcessor()
.process(image.getDestination());
.process(image.getUrl().unescape());
final RenderProps props = visitor.renderProps();
@ -538,7 +537,7 @@ public class CorePlugin extends AbstractMarkwonPlugin {
final int length = visitor.length();
visitor.visitChildren(link);
final String destination = link.getDestination();
final String destination = link.getUrl().unescape();
CoreProps.LINK_DESTINATION.set(visitor.renderProps(), destination);

View File

@ -2,7 +2,7 @@ package io.noties.markwon.core;
import androidx.annotation.NonNull;
import org.commonmark.node.Node;
import com.vladsch.flexmark.util.ast.Node;
import io.noties.markwon.MarkwonVisitor;

View File

@ -3,6 +3,7 @@ package io.noties.markwon.syntax;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@SuppressWarnings("WeakerAccess")
public interface SyntaxHighlight {

View File

@ -4,8 +4,8 @@ import androidx.annotation.CheckResult;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.commonmark.node.Node;
import org.commonmark.node.Visitor;
import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.util.ast.Visitor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
@ -70,7 +70,8 @@ public abstract class DumpNodes {
return null;
}
});
node.accept(visitor);
visitor.visit(node);
//node.accept(visitor);
return builder.toString();
}
@ -104,7 +105,7 @@ public abstract class DumpNodes {
// A subclass of this visitor might modify the node, resulting in getNext returning a different node or no
// node after visiting it. So get the next node before visiting.
Node next = node.getNext();
node.accept(visitor);
visitor.visit(node);
node = next;
}
}

View File

@ -2,7 +2,7 @@ package io.noties.markwon.utils;
import androidx.annotation.NonNull;
import org.commonmark.node.Node;
import com.vladsch.flexmark.util.ast.Node;
/**
* @since 4.6.0

View File

@ -28,4 +28,5 @@ dependencies {
}
}
registerArtifact(this)
registerArtifact(this)
apply from: "${rootProject.projectDir}/gradle/publishMaven.gradle"

View File

@ -16,7 +16,6 @@ android {
dependencies {
api project(':markwon-core')
api project(':markwon-inline-parser')
api deps['jlatexmath-android']
@ -27,4 +26,5 @@ dependencies {
}
}
registerArtifact(this)
registerArtifact(this)
apply from: "${rootProject.projectDir}/gradle/publishMaven.gradle"

View File

@ -1,16 +0,0 @@
package io.noties.markwon.ext.latex;
import org.commonmark.node.CustomBlock;
public class JLatexMathBlock extends CustomBlock {
private String latex;
public String latex() {
return latex;
}
public void latex(String latex) {
this.latex = latex;
}
}

View File

@ -1,119 +0,0 @@
package io.noties.markwon.ext.latex;
import androidx.annotation.NonNull;
import org.commonmark.internal.util.Parsing;
import org.commonmark.node.Block;
import org.commonmark.parser.block.AbstractBlockParser;
import org.commonmark.parser.block.AbstractBlockParserFactory;
import org.commonmark.parser.block.BlockContinue;
import org.commonmark.parser.block.BlockStart;
import org.commonmark.parser.block.MatchedBlockParser;
import org.commonmark.parser.block.ParserState;
/**
* @since 4.3.0 (although there was a class with the same name,
* which is renamed now to {@link JLatexMathBlockParserLegacy})
*/
class JLatexMathBlockParser extends AbstractBlockParser {
private static final char DOLLAR = '$';
private static final char SPACE = ' ';
private final JLatexMathBlock block = new JLatexMathBlock();
private final StringBuilder builder = new StringBuilder();
private final int signs;
JLatexMathBlockParser(int signs) {
this.signs = signs;
}
@Override
public Block getBlock() {
return block;
}
@Override
public BlockContinue tryContinue(ParserState parserState) {
final int nextNonSpaceIndex = parserState.getNextNonSpaceIndex();
final CharSequence line = parserState.getLine();
final int length = line.length();
// check for closing
if (parserState.getIndent() < Parsing.CODE_BLOCK_INDENT) {
if (consume(DOLLAR, line, nextNonSpaceIndex, length) == signs) {
// okay, we have our number of signs
// let's consume spaces until the end
if (Parsing.skip(SPACE, line, nextNonSpaceIndex + signs, length) == length) {
return BlockContinue.finished();
}
}
}
return BlockContinue.atIndex(parserState.getIndex());
}
@Override
public void addLine(CharSequence line) {
builder.append(line);
builder.append('\n');
}
@Override
public void closeBlock() {
block.latex(builder.toString());
}
public static class Factory extends AbstractBlockParserFactory {
@Override
public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) {
// let's define the spec:
// * 0-3 spaces before are allowed (Parsing.CODE_BLOCK_INDENT = 4)
// * 2+ subsequent `$` signs
// * any optional amount of spaces
// * new line
// * block is closed when the same amount of opening signs is met
final int indent = state.getIndent();
// check if it's an indented code block
if (indent >= Parsing.CODE_BLOCK_INDENT) {
return BlockStart.none();
}
final int nextNonSpaceIndex = state.getNextNonSpaceIndex();
final CharSequence line = state.getLine();
final int length = line.length();
final int signs = consume(DOLLAR, line, nextNonSpaceIndex, length);
// 2 is minimum
if (signs < 2) {
return BlockStart.none();
}
// consume spaces until the end of the line, if any other content is found -> NONE
if (Parsing.skip(SPACE, line, nextNonSpaceIndex + signs, length) != length) {
return BlockStart.none();
}
return BlockStart.of(new JLatexMathBlockParser(signs))
.atIndex(length + 1);
}
}
@SuppressWarnings("SameParameterValue")
private static int consume(char c, @NonNull CharSequence line, int start, int end) {
for (int i = start; i < end; i++) {
if (c != line.charAt(i)) {
return i - start;
}
}
// all consumed
return end - start;
}
}

View File

@ -1,82 +0,0 @@
package io.noties.markwon.ext.latex;
import org.commonmark.node.Block;
import org.commonmark.parser.block.AbstractBlockParser;
import org.commonmark.parser.block.AbstractBlockParserFactory;
import org.commonmark.parser.block.BlockContinue;
import org.commonmark.parser.block.BlockStart;
import org.commonmark.parser.block.MatchedBlockParser;
import org.commonmark.parser.block.ParserState;
/**
* @since 4.3.0 (although it is just renamed parser from previous versions)
*/
class JLatexMathBlockParserLegacy extends AbstractBlockParser {
private final JLatexMathBlock block = new JLatexMathBlock();
private final StringBuilder builder = new StringBuilder();
private boolean isClosed;
@Override
public Block getBlock() {
return block;
}
@Override
public BlockContinue tryContinue(ParserState parserState) {
if (isClosed) {
return BlockContinue.finished();
}
return BlockContinue.atIndex(parserState.getIndex());
}
@Override
public void addLine(CharSequence line) {
if (builder.length() > 0) {
builder.append('\n');
}
builder.append(line);
final int length = builder.length();
if (length > 1) {
isClosed = '$' == builder.charAt(length - 1)
&& '$' == builder.charAt(length - 2);
if (isClosed) {
builder.replace(length - 2, length, "");
}
}
}
@Override
public void closeBlock() {
block.latex(builder.toString());
}
public static class Factory extends AbstractBlockParserFactory {
@Override
public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) {
final CharSequence line = state.getLine();
final int length = line != null
? line.length()
: 0;
if (length > 1) {
if ('$' == line.charAt(0)
&& '$' == line.charAt(1)) {
return BlockStart.of(new JLatexMathBlockParserLegacy())
.atIndex(state.getIndex() + 2);
}
}
return BlockStart.none();
}
}
}

View File

@ -1,36 +0,0 @@
package io.noties.markwon.ext.latex;
import androidx.annotation.Nullable;
import org.commonmark.node.Node;
import java.util.regex.Pattern;
import io.noties.markwon.inlineparser.InlineProcessor;
/**
* @since 4.3.0
*/
class JLatexMathInlineProcessor extends InlineProcessor {
private static final Pattern RE = Pattern.compile("(\\${2})([\\s\\S]+?)\\1");
@Override
public char specialCharacter() {
return '$';
}
@Nullable
@Override
protected Node parse() {
final String latex = match(RE);
if (latex == null) {
return null;
}
final JLatexMathNode node = new JLatexMathNode();
node.latex(latex.substring(2, latex.length() - 2));
return node;
}
}

View File

@ -1,19 +0,0 @@
package io.noties.markwon.ext.latex;
import org.commonmark.node.CustomNode;
/**
* @since 4.3.0
*/
public class JLatexMathNode extends CustomNode {
private String latex;
public String latex() {
return latex;
}
public void latex(String latex) {
this.latex = latex;
}
}

View File

@ -14,8 +14,11 @@ import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.annotation.VisibleForTesting;
import org.commonmark.parser.Parser;
import com.vladsch.flexmark.ext.gitlab.GitLabExtension;
import com.vladsch.flexmark.ext.gitlab.GitLabInlineMath;
import com.vladsch.flexmark.parser.Parser;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
@ -31,7 +34,6 @@ import io.noties.markwon.image.AsyncDrawableScheduler;
import io.noties.markwon.image.AsyncDrawableSpan;
import io.noties.markwon.image.DrawableUtils;
import io.noties.markwon.image.ImageSizeResolver;
import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin;
import ru.noties.jlatexmath.JLatexMathDrawable;
/**
@ -117,8 +119,6 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
// @since 4.3.0
final boolean blocksEnabled;
final boolean blocksLegacy;
final boolean inlinesEnabled;
// @since 4.3.0
final ErrorHandler errorHandler;
@ -128,8 +128,6 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
Config(@NonNull Builder builder) {
this.theme = builder.theme.build();
this.blocksEnabled = builder.blocksEnabled;
this.blocksLegacy = builder.blocksLegacy;
this.inlinesEnabled = builder.inlinesEnabled;
this.errorHandler = builder.errorHandler;
// @since 4.0.0
ExecutorService executorService = builder.executorService;
@ -155,59 +153,34 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
this.inlineImageSizeResolver = new InlineImageSizeResolver();
}
@Override
public void configure(@NonNull Registry registry) {
if (config.inlinesEnabled) {
registry.require(MarkwonInlineParserPlugin.class)
.factoryBuilder()
.addInlineProcessor(new JLatexMathInlineProcessor());
}
}
@Override
public void configureParser(@NonNull Parser.Builder builder) {
// @since 4.3.0
builder.extensions(Collections.singleton(GitLabExtension.create()));
if (config.blocksEnabled) {
if (config.blocksLegacy) {
builder.customBlockParserFactory(new JLatexMathBlockParserLegacy.Factory());
} else {
builder.customBlockParserFactory(new JLatexMathBlockParser.Factory());
}
builder.set(GitLabExtension.RENDER_BLOCK_MATH, true);
}
}
@Override
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
addBlockVisitor(builder);
addInlineVisitor(builder);
}
private void addBlockVisitor(@NonNull MarkwonVisitor.Builder builder) {
if (!config.blocksEnabled) {
return;
}
builder.on(JLatexMathBlock.class, new MarkwonVisitor.NodeVisitor<JLatexMathBlock>() {
builder.on(GitLabInlineMath.class, new MarkwonVisitor.NodeVisitor<GitLabInlineMath>() {
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull JLatexMathBlock jLatexMathBlock) {
visitor.blockStart(jLatexMathBlock);
final String latex = jLatexMathBlock.latex();
public void visit(@NonNull MarkwonVisitor visitor, @NonNull GitLabInlineMath gitLabInlineMath) {
if (config.blocksEnabled) visitor.blockStart(gitLabInlineMath);
final String tex = gitLabInlineMath.getText().unescape();
final int length = visitor.length();
// @since 4.0.2 we cannot append _raw_ latex as a placeholder-text,
// because Android will draw formula for each line of text, thus
// leading to formula duplicated (drawn on each line of text)
visitor.builder().append(prepareLatexTextPlaceholder(latex));
visitor.builder().append(prepareLatexTextPlaceholder(tex));
final MarkwonConfiguration configuration = visitor.configuration();
final AsyncDrawableSpan span = new JLatexAsyncDrawableSpan(
configuration.theme(),
new JLatextAsyncDrawable(
latex,
tex,
jLatextAsyncDrawableLoader,
jLatexBlockImageSizeResolver,
null,
@ -217,43 +190,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
visitor.setSpans(length, span);
visitor.blockEnd(jLatexMathBlock);
}
});
}
private void addInlineVisitor(@NonNull MarkwonVisitor.Builder builder) {
if (!config.inlinesEnabled) {
return;
}
builder.on(JLatexMathNode.class, new MarkwonVisitor.NodeVisitor<JLatexMathNode>() {
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull JLatexMathNode jLatexMathNode) {
final String latex = jLatexMathNode.latex();
final int length = visitor.length();
// @since 4.0.2 we cannot append _raw_ latex as a placeholder-text,
// because Android will draw formula for each line of text, thus
// leading to formula duplicated (drawn on each line of text)
visitor.builder().append(prepareLatexTextPlaceholder(latex));
final MarkwonConfiguration configuration = visitor.configuration();
final AsyncDrawableSpan span = new JLatexInlineAsyncDrawableSpan(
configuration.theme(),
new JLatextAsyncDrawable(
latex,
jLatextAsyncDrawableLoader,
inlineImageSizeResolver,
null,
false),
config.theme.inlineTextColor()
);
visitor.setSpans(length, span);
if (config.blocksEnabled) visitor.blockEnd(gitLabInlineMath);
}
});
}
@ -310,16 +247,6 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
return this;
}
/**
* @param blocksLegacy indicates if blocks should be handled in legacy mode ({@code pre 4.3.0})
* @since 4.3.0
*/
@NonNull
public Builder blocksLegacy(boolean blocksLegacy) {
this.blocksLegacy = blocksLegacy;
return this;
}
/**
* @param inlinesEnabled indicates if inline parsing should be enabled.
* NB, this requires `MarkwonInlineParserPlugin` to be used when creating `MarkwonInstance`

View File

@ -1,173 +0,0 @@
package io.noties.markwon.ext.latex;
import androidx.annotation.NonNull;
import org.commonmark.internal.BlockContinueImpl;
import org.commonmark.internal.BlockStartImpl;
import org.commonmark.internal.util.Parsing;
import org.commonmark.parser.block.BlockStart;
import org.commonmark.parser.block.ParserState;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class JLatexMathBlockParserTest {
private static final String[] NO_MATCH = {
" ",
" ",
" ",
"$ ",
" $ $",
"-$$",
" -$$",
"$$-",
" $$ -",
" $$ -",
"$$$ -"
};
private static final String[] MATCH = {
"$$",
" $$",
" $$",
" $$",
"$$ ",
" $$ ",
" $$ ",
" $$ ",
"$$$",
" $$$",
" $$$",
"$$$$",
" $$$$",
"$$$$$$$$$$$$$$$$$$$$$",
" $$$$$$$$$$$$$$$$$$$$$",
" $$$$$$$$$$$$$$$$$$$$$",
" $$$$$$$$$$$$$$$$$$$$$"
};
private JLatexMathBlockParser.Factory factory;
@Before
public void before() {
factory = new JLatexMathBlockParser.Factory();
}
@Test
public void factory_indentBlock() {
// when state indent is greater than block -> nono
final ParserState state = mock(ParserState.class);
when(state.getIndent()).thenReturn(Parsing.CODE_BLOCK_INDENT);
// hm, interesting, `BlockStart.none()` actually returns null
final BlockStart start = factory.tryStart(state, null);
assertNull(start);
}
@Test
public void factory_noMatch() {
for (String line : NO_MATCH) {
final ParserState state = createState(line);
assertNull(factory.tryStart(state, null));
}
}
@Test
public void factory_match() {
for (String line : MATCH) {
final ParserState state = createState(line);
final BlockStart start = factory.tryStart(state, null);
assertNotNull(start);
// hm...
final BlockStartImpl impl = (BlockStartImpl) start;
assertEquals(quote(line), line.length() + 1, impl.getNewIndex());
}
}
@Test
public void finish() {
for (String line : MATCH) {
final ParserState state = createState(line);
// we will have 2 checks here:
// * must pass for correct length
// * must fail for incorrect
final int count = countDollarSigns(line);
// pass
{
final JLatexMathBlockParser parser = new JLatexMathBlockParser(count);
final BlockContinueImpl impl = (BlockContinueImpl) parser.tryContinue(state);
assertTrue(quote(line), impl.isFinalize());
}
// fail (in terms of closing, not failing test)
{
final JLatexMathBlockParser parser = new JLatexMathBlockParser(count + 1);
final BlockContinueImpl impl = (BlockContinueImpl) parser.tryContinue(state);
assertFalse(quote(line), impl.isFinalize());
}
}
}
@Test
public void finish_noMatch() {
for (String line : NO_MATCH) {
final ParserState state = createState(line);
// doesn't matter
final int count = 2;
final JLatexMathBlockParser parser = new JLatexMathBlockParser(count);
final BlockContinueImpl impl = (BlockContinueImpl) parser.tryContinue(state);
assertFalse(quote(line), impl.isFinalize());
}
}
@NonNull
private static ParserState createState(@NonNull String line) {
final ParserState state = mock(ParserState.class);
int i = 0;
for (int length = line.length(); i < length; i++) {
if (' ' != line.charAt(i)) {
// previous is the last space
break;
}
}
when(state.getIndent()).thenReturn(i);
when(state.getNextNonSpaceIndex()).thenReturn(i);
when(state.getLine()).thenReturn(line);
return state;
}
private static int countDollarSigns(@NonNull String line) {
int count = 0;
for (int i = 0, length = line.length(); i < length; i++) {
if ('$' == line.charAt(i)) count += 1;
}
return count;
}
@NonNull
private static String quote(@NonNull String s) {
return '\'' + s + '\'';
}
}

View File

@ -1,230 +0,0 @@
package io.noties.markwon.ext.latex;
import androidx.annotation.NonNull;
import org.commonmark.parser.Parser;
import org.commonmark.parser.block.BlockParserFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.util.List;
import java.util.concurrent.ExecutorService;
import io.noties.markwon.MarkwonConfiguration;
import io.noties.markwon.MarkwonPlugin;
import io.noties.markwon.MarkwonVisitor;
import io.noties.markwon.SpannableBuilder;
import io.noties.markwon.inlineparser.InlineProcessor;
import io.noties.markwon.inlineparser.MarkwonInlineParser;
import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class JLatexMathPluginTest {
@Test
public void latex_text_placeholder() {
// text placeholder cannot have new-line characters and should be trimmed from ends
final String[] in = {
"hello",
"he\nllo",
" hello\n\n",
"\n\nhello\n\n",
"\n",
" \nhello\n "
};
for (String latex : in) {
final String placeholder = JLatexMathPlugin.prepareLatexTextPlaceholder(latex);
assertTrue(placeholder, placeholder.indexOf('\n') < 0);
if (placeholder.length() > 0) {
assertFalse(placeholder, Character.isWhitespace(placeholder.charAt(0)));
assertFalse(placeholder, Character.isWhitespace(placeholder.charAt(placeholder.length() - 1)));
}
}
}
@Test
public void block_parser_registered() {
final JLatexMathPlugin plugin = JLatexMathPlugin.create(0);
final Parser.Builder builder = mock(Parser.Builder.class);
plugin.configureParser(builder);
verify(builder, times(1)).customBlockParserFactory(any(BlockParserFactory.class));
}
@Test
public void visitor_registered() {
final JLatexMathPlugin plugin = JLatexMathPlugin.create(0);
final MarkwonVisitor.Builder builder = mock(MarkwonVisitor.Builder.class);
plugin.configureVisitor(builder);
//noinspection unchecked
verify(builder, times(1))
.on(eq(JLatexMathBlock.class), any(MarkwonVisitor.NodeVisitor.class));
}
@Test
public void visit() {
final JLatexMathPlugin plugin = JLatexMathPlugin.create(0, new JLatexMathPlugin.BuilderConfigure() {
@Override
public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) {
// no async in test (nooped for this test)
builder.executorService(mock(ExecutorService.class));
}
});
final MarkwonVisitor.Builder builder = mock(MarkwonVisitor.Builder.class);
final ArgumentCaptor<MarkwonVisitor.NodeVisitor> captor =
ArgumentCaptor.forClass(MarkwonVisitor.NodeVisitor.class);
plugin.configureVisitor(builder);
//noinspection unchecked
verify(builder, times(1))
.on(eq(JLatexMathBlock.class), captor.capture());
final MarkwonVisitor.NodeVisitor nodeVisitor = captor.getValue();
final MarkwonVisitor visitor = mock(MarkwonVisitor.class);
final JLatexMathBlock block = mock(JLatexMathBlock.class);
when(block.latex()).thenReturn(" first\nsecond\n ");
final SpannableBuilder spannableBuilder = mock(SpannableBuilder.class);
when(visitor.builder()).thenReturn(spannableBuilder);
when(visitor.configuration()).thenReturn(mock(MarkwonConfiguration.class));
//noinspection unchecked
nodeVisitor.visit(visitor, block);
verify(block, times(1)).latex();
verify(visitor, times(1)).length();
final ArgumentCaptor<String> stringArgumentCaptor = ArgumentCaptor.forClass(String.class);
verify(spannableBuilder, times(1)).append(stringArgumentCaptor.capture());
final String placeholder = stringArgumentCaptor.getValue();
assertTrue(placeholder, placeholder.indexOf('\n') < 0);
verify(visitor, times(1)).setSpans(eq(0), any());
}
@Test
public void legacy() {
// if render mode is legacy:
// - no inline plugin is required,
// - parser has legacy block parser factory
// - no inline node is registered (node)
final JLatexMathPlugin plugin = JLatexMathPlugin.create(1, new JLatexMathPlugin.BuilderConfigure() {
@Override
public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) {
builder.blocksLegacy(true);
builder.inlinesEnabled(false);
}
});
// registry
{
final MarkwonPlugin.Registry registry = mock(MarkwonPlugin.Registry.class);
plugin.configure(registry);
verify(registry, never()).require(any(Class.class));
}
// parser
{
final Parser.Builder builder = mock(Parser.Builder.class);
plugin.configureParser(builder);
final ArgumentCaptor<BlockParserFactory> captor =
ArgumentCaptor.forClass(BlockParserFactory.class);
verify(builder, times(1)).customBlockParserFactory(captor.capture());
final BlockParserFactory factory = captor.getValue();
assertTrue(factory.getClass().getName(), factory instanceof JLatexMathBlockParserLegacy.Factory);
}
// visitor
{
final MarkwonVisitor.Builder builder = mock(MarkwonVisitor.Builder.class);
plugin.configureVisitor(builder);
final ArgumentCaptor<Class> captor = ArgumentCaptor.forClass(Class.class);
verify(builder, times(1)).on(captor.capture(), any(MarkwonVisitor.NodeVisitor.class));
assertEquals(JLatexMathBlock.class, captor.getValue());
}
}
@Test
public void blocks_inlines_implicit() {
final JLatexMathPlugin plugin = JLatexMathPlugin.create(1);
final JLatexMathPlugin.Config config = plugin.config;
assertTrue("blocksEnabled", config.blocksEnabled);
assertFalse("blocksLegacy", config.blocksLegacy);
assertFalse("inlinesEnabled", config.inlinesEnabled);
}
@Test
public void blocks_inlines() {
final JLatexMathPlugin plugin = JLatexMathPlugin.create(12, new JLatexMathPlugin.BuilderConfigure() {
@Override
public void configureBuilder(@NonNull JLatexMathPlugin.Builder builder) {
builder.inlinesEnabled(true);
}
});
// registry
{
final MarkwonInlineParser.FactoryBuilder factoryBuilder = mock(MarkwonInlineParser.FactoryBuilder.class);
final MarkwonInlineParserPlugin inlineParserPlugin = mock(MarkwonInlineParserPlugin.class);
final MarkwonPlugin.Registry registry = mock(MarkwonPlugin.Registry.class);
when(inlineParserPlugin.factoryBuilder()).thenReturn(factoryBuilder);
when(registry.require(eq(MarkwonInlineParserPlugin.class))).thenReturn(inlineParserPlugin);
plugin.configure(registry);
verify(registry, times(1)).require(eq(MarkwonInlineParserPlugin.class));
verify(inlineParserPlugin, times(1)).factoryBuilder();
final ArgumentCaptor<InlineProcessor> captor = ArgumentCaptor.forClass(InlineProcessor.class);
verify(factoryBuilder, times(1)).addInlineProcessor(captor.capture());
final InlineProcessor inlineProcessor = captor.getValue();
assertTrue(inlineParserPlugin.getClass().getName(), inlineProcessor instanceof JLatexMathInlineProcessor);
}
// parser
{
final Parser.Builder builder = mock(Parser.Builder.class);
plugin.configureParser(builder);
final ArgumentCaptor<BlockParserFactory> captor =
ArgumentCaptor.forClass(BlockParserFactory.class);
verify(builder, times(1)).customBlockParserFactory(captor.capture());
final BlockParserFactory factory = captor.getValue();
assertTrue(factory.getClass().getName(), factory instanceof JLatexMathBlockParser.Factory);
}
// visitor
{
final MarkwonVisitor.Builder builder = mock(MarkwonVisitor.Builder.class);
plugin.configureVisitor(builder);
final ArgumentCaptor<Class> captor = ArgumentCaptor.forClass(Class.class);
verify(builder, times(2)).on(captor.capture(), any(MarkwonVisitor.NodeVisitor.class));
final List<Class> nodes = captor.getAllValues();
assertEquals(2, nodes.size());
assertTrue(nodes.toString(), nodes.contains(JLatexMathNode.class));
assertTrue(nodes.toString(), nodes.contains(JLatexMathBlock.class));
}
}
}

View File

@ -18,8 +18,6 @@ dependencies {
api project(':markwon-core')
deps.with {
api it['commonmark-strikethrough']
// NB! ix-java dependency to be used in tests
testImplementation it['ix-java']
}
@ -32,4 +30,5 @@ dependencies {
}
}
registerArtifact(this)
registerArtifact(this)
apply from: "${rootProject.projectDir}/gradle/publishMaven.gradle"

View File

@ -4,9 +4,9 @@ import android.text.style.StrikethroughSpan;
import androidx.annotation.NonNull;
import org.commonmark.ext.gfm.strikethrough.Strikethrough;
import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension;
import org.commonmark.parser.Parser;
import com.vladsch.flexmark.ext.gfm.strikethrough.Strikethrough;
import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension;
import com.vladsch.flexmark.parser.Parser;
import java.util.Collections;
@ -18,8 +18,8 @@ import io.noties.markwon.RenderProps;
import io.noties.markwon.SpanFactory;
/**
* Plugin to add strikethrough markdown feature. This plugin will extend commonmark-java.Parser
* with strikethrough extension, add SpanFactory and register commonmark-java.Strikethrough node
* Plugin to add strikethrough markdown feature. This plugin will extend flexmark.Parser
* with strikethrough extension, add SpanFactory and register flexmark.Strikethrough node
* visitor
*
* @see #create()

View File

@ -11,15 +11,16 @@ android {
versionCode 1
versionName version
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
}
dependencies {
api project(':markwon-core')
deps.with {
api it['commonmark-table']
}
}
registerArtifact(this)
registerArtifact(this)
apply from: "${rootProject.projectDir}/gradle/publishMaven.gradle"

View File

@ -5,12 +5,16 @@ import android.text.Spanned;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
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 com.vladsch.flexmark.ast.BlockQuote;
import com.vladsch.flexmark.ast.util.BlockVisitorExt;
import com.vladsch.flexmark.ast.util.InlineVisitorExt;
import com.vladsch.flexmark.ext.tables.TableBlock;
import com.vladsch.flexmark.ext.tables.TableCell;
import com.vladsch.flexmark.ext.tables.TableHead;
import com.vladsch.flexmark.ext.tables.TableRow;
import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.util.ast.NodeVisitor;
import com.vladsch.flexmark.util.ast.VisitHandler;
import java.util.ArrayList;
import java.util.List;
@ -40,7 +44,8 @@ public class Table {
final Table table;
final ParseVisitor visitor = new ParseVisitor(markwon);
tableBlock.accept(visitor);
visitor.visit(tableBlock);
final List<Row> rows = visitor.rows();
if (rows == null) {
@ -135,7 +140,7 @@ public class Table {
'}';
}
static class ParseVisitor extends AbstractVisitor {
static class ParseVisitor extends NodeVisitor implements TableVisitor{
private final Markwon markwon;
@ -146,6 +151,7 @@ public class Table {
ParseVisitor(@NonNull Markwon markwon) {
this.markwon = markwon;
addHandlers(TableVisitor.VISIT_HANDLERS(this));
}
@Nullable
@ -154,44 +160,47 @@ public class Table {
}
@Override
public void visit(CustomNode customNode) {
if (customNode instanceof TableCell) {
final TableCell cell = (TableCell) customNode;
if (pendingRow == null) {
pendingRow = new ArrayList<>(2);
}
pendingRow.add(new Table.Column(alignment(cell.getAlignment()), markwon.render(cell)));
pendingRowIsHeader = cell.isHeader();
return;
public void visit(TableCell cell) {
if (pendingRow == null) {
pendingRow = new ArrayList<>(2);
}
if (customNode instanceof TableHead
|| customNode instanceof TableRow) {
pendingRow.add(new Table.Column(alignment(cell.getAlignment()), markwon.render(cell)));
pendingRowIsHeader = cell.isHeader();
}
visitChildren(customNode);
@Override
public void visit(TableHead head) {
visitChildren(head);
// 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));
// this can happen, ignore such row
if (pendingRow != null && pendingRow.size() > 0) {
if (rows == null) {
rows = new ArrayList<>(2);
}
pendingRow = null;
pendingRowIsHeader = false;
return;
rows.add(new Table.Row(pendingRowIsHeader, pendingRow));
}
visitChildren(customNode);
pendingRow = null;
pendingRowIsHeader = false;
}
@Override
public void visit(TableRow row) {
visitChildren(row);
// 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;
}
@NonNull
@ -206,5 +215,6 @@ public class Table {
}
return out;
}
}
}

View File

@ -6,14 +6,14 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import org.commonmark.ext.gfm.tables.TableBlock;
import org.commonmark.ext.gfm.tables.TableBody;
import org.commonmark.ext.gfm.tables.TableCell;
import org.commonmark.ext.gfm.tables.TableHead;
import org.commonmark.ext.gfm.tables.TableRow;
import org.commonmark.ext.gfm.tables.TablesExtension;
import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
import com.vladsch.flexmark.ext.tables.TableBlock;
import com.vladsch.flexmark.ext.tables.TableBody;
import com.vladsch.flexmark.ext.tables.TableCell;
import com.vladsch.flexmark.ext.tables.TableHead;
import com.vladsch.flexmark.ext.tables.TableRow;
import com.vladsch.flexmark.ext.tables.TablesExtension;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.ast.Node;
import java.util.ArrayList;
import java.util.Collections;

View File

@ -0,0 +1,28 @@
package io.noties.markwon.ext.tables;
import com.vladsch.flexmark.ext.tables.TableCell;
import com.vladsch.flexmark.ext.tables.TableHead;
import com.vladsch.flexmark.ext.tables.TableRow;
import com.vladsch.flexmark.util.ast.VisitHandler;
/**
* <p>
*
* @author cpacm 2023/4/4
*/
public interface TableVisitor {
void visit(TableCell node);
void visit(TableHead head);
void visit(TableRow row);
public static <V extends TableVisitor> VisitHandler<?>[] VISIT_HANDLERS(V visitor) {
return new VisitHandler<?>[]{
new VisitHandler<>(TableCell.class, visitor::visit),
new VisitHandler<>(TableHead.class, visitor::visit),
new VisitHandler<>(TableRow.class, visitor::visit),
};
}
}

View File

@ -27,4 +27,5 @@ dependencies {
}
}
registerArtifact(this)
registerArtifact(this)
apply from: "${rootProject.projectDir}/gradle/publishMaven.gradle"

View File

@ -1,30 +0,0 @@
package io.noties.markwon.ext.tasklist;
import androidx.annotation.NonNull;
import org.commonmark.node.CustomBlock;
/**
* @since 1.0.1
*/
@SuppressWarnings("WeakerAccess")
public class TaskListItem extends CustomBlock {
private final boolean isDone;
public TaskListItem(boolean isDone) {
this.isDone = isDone;
}
public boolean isDone() {
return isDone;
}
@Override
@NonNull
public String toString() {
return "TaskListItem{" +
"isDone=" + isDone +
'}';
}
}

View File

@ -9,12 +9,15 @@ import androidx.annotation.AttrRes;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import org.commonmark.parser.Parser;
import com.vladsch.flexmark.ext.gfm.tasklist.TaskListExtension;
import com.vladsch.flexmark.ext.gfm.tasklist.TaskListItem;
import com.vladsch.flexmark.parser.Parser;
import java.util.Collections;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.MarkwonSpansFactory;
import io.noties.markwon.MarkwonVisitor;
import io.noties.markwon.core.SimpleBlockNodeVisitor;
/**
* @since 3.0.0
@ -62,20 +65,19 @@ public class TaskListPlugin extends AbstractMarkwonPlugin {
this.drawable = drawable;
}
@Override
public void configureParser(@NonNull Parser.Builder builder) {
builder.postProcessor(new TaskListPostProcessor());
}
@Override
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
builder.setFactory(TaskListItem.class, new TaskListSpanFactory(drawable));
}
@Override
public void configureParser(@NonNull Parser.Builder builder) {
builder.extensions(Collections.singleton(TaskListExtension.create()));
}
@Override
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
builder
.on(TaskListItem.class, new MarkwonVisitor.NodeVisitor<TaskListItem>() {
builder.on(TaskListItem.class, new MarkwonVisitor.NodeVisitor<TaskListItem>() {
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull TaskListItem taskListItem) {
@ -83,7 +85,7 @@ public class TaskListPlugin extends AbstractMarkwonPlugin {
visitor.visitChildren(taskListItem);
TaskListProps.DONE.set(visitor.renderProps(), taskListItem.isDone());
TaskListProps.DONE.set(visitor.renderProps(), taskListItem.isItemDoneMarker());
visitor.setSpansForNode(taskListItem, length);

View File

@ -1,82 +0,0 @@
package io.noties.markwon.ext.tasklist;
import android.text.TextUtils;
import org.commonmark.node.AbstractVisitor;
import org.commonmark.node.ListItem;
import org.commonmark.node.Node;
import org.commonmark.node.Paragraph;
import org.commonmark.node.Text;
import org.commonmark.parser.PostProcessor;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import io.noties.markwon.utils.ParserUtils;
// @since 4.6.0
// Hint taken from commonmark-ext-task-list-items artifact
class TaskListPostProcessor implements PostProcessor {
@Override
public Node process(Node node) {
final TaskListVisitor visitor = new TaskListVisitor();
node.accept(visitor);
return node;
}
private static class TaskListVisitor extends AbstractVisitor {
private static final Pattern REGEX_TASK_LIST_ITEM = Pattern.compile("^\\[([xX\\s])]\\s+(.*)");
@Override
public void visit(ListItem listItem) {
// Takes first child and checks if it is Text (we are looking for exact `[xX\s]` without any formatting)
final Node child = listItem.getFirstChild();
// check if it is paragraph (can contain text)
if (child instanceof Paragraph) {
final Node node = child.getFirstChild();
if (node instanceof Text) {
final Text textNode = (Text) node;
final Matcher matcher = REGEX_TASK_LIST_ITEM.matcher(textNode.getLiteral());
if (matcher.matches()) {
final String checked = matcher.group(1);
final boolean isChecked = "x".equals(checked) || "X".equals(checked);
final TaskListItem taskListItem = new TaskListItem(isChecked);
final Paragraph paragraph = new Paragraph();
// insert before list item (directly before inside parent)
listItem.insertBefore(taskListItem);
// append the rest of matched text (can be empty)
final String restMatchedText = matcher.group(2);
if (!TextUtils.isEmpty(restMatchedText)) {
paragraph.appendChild(new Text(restMatchedText));
}
// move all the rest children (from the first paragraph)
ParserUtils.moveChildren(paragraph, node);
// append our created paragraph
taskListItem.appendChild(paragraph);
// move all the rest children from the listItem (further nested lists, etc)
ParserUtils.moveChildren(taskListItem, child);
// remove list item from node
listItem.unlink();
// visit taskListItem children
visitChildren(taskListItem);
return;
}
}
}
visitChildren(listItem);
}
}
}

View File

@ -22,7 +22,6 @@ dependencies {
// we will try to obtain a SpanFactory for a Strikethrough node and use
// it to be consistent with markdown (please note that we do not use markwon plugin
// for that in case if different implementation is used)
compileOnly it['commonmark-strikethrough']
testImplementation it['ix-java']
}
@ -34,3 +33,4 @@ dependencies {
}
registerArtifact(this)
apply from: "${rootProject.projectDir}/gradle/publishMaven.gradle"

View File

@ -3,9 +3,9 @@ package io.noties.markwon.html;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.commonmark.node.HtmlBlock;
import org.commonmark.node.HtmlInline;
import org.commonmark.node.Node;
import com.vladsch.flexmark.ast.HtmlBlock;
import com.vladsch.flexmark.ast.HtmlInline;
import com.vladsch.flexmark.util.ast.Node;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.MarkwonConfiguration;
@ -161,13 +161,13 @@ public class HtmlPlugin extends AbstractMarkwonPlugin {
.on(HtmlBlock.class, new MarkwonVisitor.NodeVisitor<HtmlBlock>() {
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull HtmlBlock htmlBlock) {
visitHtml(visitor, htmlBlock.getLiteral());
visitHtml(visitor, htmlBlock.toAstString(false));
}
})
.on(HtmlInline.class, new MarkwonVisitor.NodeVisitor<HtmlInline>() {
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull HtmlInline htmlInline) {
visitHtml(visitor, htmlInline.getLiteral());
visitHtml(visitor, htmlInline.toAstString(false));
}
});
}

View File

@ -2,7 +2,7 @@ package io.noties.markwon.html.jsoup.nodes;
import androidx.annotation.NonNull;
import org.commonmark.internal.util.Html5Entities;
import com.vladsch.flexmark.util.sequence.Html5Entities;
import java.lang.reflect.Field;
import java.util.Collections;

View File

@ -2,7 +2,7 @@ package io.noties.markwon.html.tag;
import androidx.annotation.NonNull;
import org.commonmark.node.BlockQuote;
import com.vladsch.flexmark.ast.BlockQuote;
import java.util.Collection;
import java.util.Collections;

View File

@ -3,7 +3,7 @@ package io.noties.markwon.html.tag;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.commonmark.node.Emphasis;
import com.vladsch.flexmark.ast.Emphasis;
import java.util.Arrays;
import java.util.Collection;

View File

@ -3,7 +3,7 @@ package io.noties.markwon.html.tag;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.commonmark.node.Heading;
import com.vladsch.flexmark.ast.Heading;
import java.util.Arrays;
import java.util.Collection;

View File

@ -5,7 +5,7 @@ import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.commonmark.node.Image;
import com.vladsch.flexmark.ast.Image;
import java.util.Collection;
import java.util.Collections;

View File

@ -5,7 +5,7 @@ import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.commonmark.node.Link;
import com.vladsch.flexmark.ast.Link;
import java.util.Collection;
import java.util.Collections;

View File

@ -2,7 +2,7 @@ package io.noties.markwon.html.tag;
import androidx.annotation.NonNull;
import org.commonmark.node.ListItem;
import com.vladsch.flexmark.ast.ListItem;
import java.util.Arrays;
import java.util.Collection;

View File

@ -5,6 +5,8 @@ import android.text.style.StrikethroughSpan;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.vladsch.flexmark.ext.gfm.strikethrough.Strikethrough;
import java.util.Arrays;
import java.util.Collection;
@ -63,7 +65,7 @@ public class StrikeHandler extends TagHandler {
private static Object getMarkdownSpans(@NonNull MarkwonVisitor visitor) {
final MarkwonConfiguration configuration = visitor.configuration();
final SpanFactory spanFactory = configuration.spansFactory()
.get(org.commonmark.ext.gfm.strikethrough.Strikethrough.class);
.get(Strikethrough.class);
if (spanFactory == null) {
return null;
}

View File

@ -3,7 +3,7 @@ package io.noties.markwon.html.tag;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.commonmark.node.StrongEmphasis;
import com.vladsch.flexmark.ast.StrongEmphasis;
import java.util.Arrays;
import java.util.Collection;

View File

@ -23,3 +23,4 @@ dependencies {
}
registerArtifact(this)
apply from: "${rootProject.projectDir}/gradle/publishMaven.gradle"

View File

@ -8,7 +8,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.commonmark.node.Image;
import com.vladsch.flexmark.ast.Image;
import java.util.HashMap;
import java.util.Map;

View File

@ -18,4 +18,5 @@ dependencies {
api deps['glide']
}
registerArtifact(this)
registerArtifact(this)
apply from: "${rootProject.projectDir}/gradle/publishMaven.gradle"

View File

@ -14,8 +14,7 @@ import com.bumptech.glide.RequestManager;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.request.transition.Transition;
import org.commonmark.node.Image;
import com.vladsch.flexmark.ast.Image;
import java.util.HashMap;
import java.util.Map;

View File

@ -18,4 +18,5 @@ dependencies {
api deps['picasso']
}
registerArtifact(this)
registerArtifact(this)
apply from: "${rootProject.projectDir}/gradle/publishMaven.gradle"

View File

@ -14,8 +14,7 @@ import androidx.annotation.Nullable;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.RequestCreator;
import com.squareup.picasso.Target;
import org.commonmark.node.Image;
import com.vladsch.flexmark.ast.Image;
import java.util.HashMap;
import java.util.Map;

View File

@ -34,4 +34,5 @@ dependencies {
}
}
registerArtifact(this)
registerArtifact(this)
apply from: "${rootProject.projectDir}/gradle/publishMaven.gradle"

View File

@ -8,7 +8,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import org.commonmark.node.Image;
import com.vladsch.flexmark.ast.Image;
import java.util.concurrent.ExecutorService;

Some files were not shown because too many files have changed in this diff Show More