replace commonMark to flexMark
This commit is contained in:
parent
2ea148c30a
commit
b6135ecc56
@ -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')
|
||||
|
@ -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",
|
||||
|
@ -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" />
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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!
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}))
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
}
|
||||
}
|
@ -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))
|
||||
|
@ -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'")
|
||||
}
|
||||
}
|
||||
}
|
@ -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",
|
||||
|
29
build.gradle
29
build.gradle
@ -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",
|
||||
|
@ -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
|
20
gradle/publishAllToMavenLocal.sh
Executable file
20
gradle/publishAllToMavenLocal.sh
Executable 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
240
gradle/publishMaven.gradle
Normal 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
|
||||
}
|
||||
}
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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
|
||||
|
@ -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"
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 
|
||||
*/
|
||||
@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) {
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -3,6 +3,7 @@ package io.noties.markwon.syntax;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public interface SyntaxHighlight {
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -28,4 +28,5 @@ dependencies {
|
||||
}
|
||||
}
|
||||
|
||||
registerArtifact(this)
|
||||
registerArtifact(this)
|
||||
apply from: "${rootProject.projectDir}/gradle/publishMaven.gradle"
|
@ -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"
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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`
|
||||
|
@ -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 + '\'';
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
@ -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()
|
||||
|
@ -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"
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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),
|
||||
};
|
||||
}
|
||||
}
|
@ -27,4 +27,5 @@ dependencies {
|
||||
}
|
||||
}
|
||||
|
||||
registerArtifact(this)
|
||||
registerArtifact(this)
|
||||
apply from: "${rootProject.projectDir}/gradle/publishMaven.gradle"
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -23,3 +23,4 @@ dependencies {
|
||||
}
|
||||
|
||||
registerArtifact(this)
|
||||
apply from: "${rootProject.projectDir}/gradle/publishMaven.gradle"
|
@ -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;
|
||||
|
@ -18,4 +18,5 @@ dependencies {
|
||||
api deps['glide']
|
||||
}
|
||||
|
||||
registerArtifact(this)
|
||||
registerArtifact(this)
|
||||
apply from: "${rootProject.projectDir}/gradle/publishMaven.gradle"
|
@ -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;
|
||||
|
@ -18,4 +18,5 @@ dependencies {
|
||||
api deps['picasso']
|
||||
}
|
||||
|
||||
registerArtifact(this)
|
||||
registerArtifact(this)
|
||||
apply from: "${rootProject.projectDir}/gradle/publishMaven.gradle"
|
@ -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;
|
||||
|
@ -34,4 +34,5 @@ dependencies {
|
||||
}
|
||||
}
|
||||
|
||||
registerArtifact(this)
|
||||
registerArtifact(this)
|
||||
apply from: "${rootProject.projectDir}/gradle/publishMaven.gradle"
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user