From 9c469be1761020c0df2497abc2824a85f4620017 Mon Sep 17 00:00:00 2001 From: Francesco Cervone Date: Thu, 19 Mar 2020 16:12:36 +0100 Subject: [PATCH 01/14] Fix DexGuard optimization issue --- .../src/main/java/io/noties/markwon/html/tag/StrikeHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/markwon-html/src/main/java/io/noties/markwon/html/tag/StrikeHandler.java b/markwon-html/src/main/java/io/noties/markwon/html/tag/StrikeHandler.java index 6c01e31f..0ed1ef2b 100644 --- a/markwon-html/src/main/java/io/noties/markwon/html/tag/StrikeHandler.java +++ b/markwon-html/src/main/java/io/noties/markwon/html/tag/StrikeHandler.java @@ -25,7 +25,7 @@ public class StrikeHandler extends TagHandler { static { boolean hasMarkdownImplementation; try { - org.commonmark.ext.gfm.strikethrough.Strikethrough.class.getName(); + Class.forName("org.commonmark.ext.gfm.strikethrough.Strikethrough"); hasMarkdownImplementation = true; } catch (Throwable t) { hasMarkdownImplementation = false; From d1479dba8d36d9515bf97b1831db1a299d73d56e Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Mon, 23 Mar 2020 12:17:04 +0300 Subject: [PATCH 02/14] Begin 4.3.1-SNAPSHOT --- CHANGELOG.md | 3 +-- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a536f2c1..28931d6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,7 @@ dependency (must be explicitly added to `Markwon` whilst configuring) * `LinkResolverDef` defaults to `https` when a link does not have scheme information ([#75]) * add `option` abstraction for `sample` module allowing switching of multiple cases in runtime via menu * non-empty bounds for `AsyncDrawable` when no dimensions are not yet available ([#189]) -* `linkify` - option to use `LinkifyCompat` in `LinkifyPlugin` ([#201]) -
Thanks to [@drakeet] +* `linkify` - option to use `LinkifyCompat` in `LinkifyPlugin` ([#201])
Thanks to [@drakeet] * `MarkwonVisitor.BlockHandler` and `BlockHandlerDef` implementation to control how blocks insert new lines after them diff --git a/gradle.properties b/gradle.properties index f74f7775..ca3e59a6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ android.enableJetifier=true android.enableBuildCache=true android.buildCacheDir=build/pre-dex-cache -VERSION_NAME=4.3.0 +VERSION_NAME=4.3.1-SNAPSHOT GROUP=io.noties.markwon POM_DESCRIPTION=Markwon markdown for Android From c4a2bb94e2e322052193aab37b2e3a28241633cd Mon Sep 17 00:00:00 2001 From: luca Date: Mon, 23 Mar 2020 13:24:28 +0100 Subject: [PATCH 03/14] added PrecomputedFutureTextSetterCompat with tests in the sample app --- build.gradle | 1 + markwon-core/build.gradle | 1 + .../PrecomputedFutureTextSetterCompat.java | 61 ++++++++++++ sample/build.gradle | 1 + sample/src/main/AndroidManifest.xml | 1 + .../noties/markwon/sample/MainActivity.java | 5 + .../java/io/noties/markwon/sample/Sample.java | 2 + .../PrecomputedFutureActivity.java | 95 +++++++++++++++++++ .../adapter_appcompat_default_entry.xml | 16 ++++ .../src/main/res/values/strings-samples.xml | 2 + 10 files changed, 185 insertions(+) create mode 100644 markwon-core/src/main/java/io/noties/markwon/PrecomputedFutureTextSetterCompat.java create mode 100644 sample/src/main/java/io/noties/markwon/sample/precomputed/PrecomputedFutureActivity.java create mode 100644 sample/src/main/res/layout/adapter_appcompat_default_entry.xml diff --git a/build.gradle b/build.gradle index 43348c63..51aa9d6c 100644 --- a/build.gradle +++ b/build.gradle @@ -66,6 +66,7 @@ ext { 'x-annotations' : 'androidx.annotation:annotation:1.1.0', 'x-recycler-view' : 'androidx.recyclerview:recyclerview:1.0.0', 'x-core' : 'androidx.core:core:1.0.2', + 'x-appcompat' : 'androidx.appcompat:appcompat:1.1.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", diff --git a/markwon-core/build.gradle b/markwon-core/build.gradle index 6ff67d77..8b2f1be6 100644 --- a/markwon-core/build.gradle +++ b/markwon-core/build.gradle @@ -22,6 +22,7 @@ dependencies { // @since 4.1.0 to allow PrecomputedTextSetterCompat // note that this dependency must be added on a client side explicitly compileOnly it['x-core'] + compileOnly it['x-appcompat'] } deps['test'].with { diff --git a/markwon-core/src/main/java/io/noties/markwon/PrecomputedFutureTextSetterCompat.java b/markwon-core/src/main/java/io/noties/markwon/PrecomputedFutureTextSetterCompat.java new file mode 100644 index 00000000..66efe187 --- /dev/null +++ b/markwon-core/src/main/java/io/noties/markwon/PrecomputedFutureTextSetterCompat.java @@ -0,0 +1,61 @@ +package io.noties.markwon; + +import android.text.Spanned; +import android.util.Log; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatTextView; +import androidx.core.text.PrecomputedTextCompat; + +import java.util.concurrent.Executor; + +/** + * Please note this class requires `androidx.core:core` artifact being explicitly added to your dependencies. + * This is intended to be used in a RecyclerView. + * + * @see io.noties.markwon.Markwon.TextSetter + */ +public class PrecomputedFutureTextSetterCompat implements Markwon.TextSetter { + + /** + * @param executor for background execution of text pre-computation, + * if not provided the standard, single threaded one will be used. + */ + @NonNull + public static PrecomputedFutureTextSetterCompat create(@Nullable Executor executor) { + return new PrecomputedFutureTextSetterCompat(executor); + } + + @NonNull + public static PrecomputedFutureTextSetterCompat create() { + return new PrecomputedFutureTextSetterCompat(null); + } + + @Nullable + private final Executor executor; + + @SuppressWarnings("WeakerAccess") + PrecomputedFutureTextSetterCompat(@Nullable Executor executor) { + this.executor = executor; + } + + @Override + public void setText( + @NonNull TextView textView, + @NonNull Spanned markdown, + @NonNull TextView.BufferType bufferType, + @NonNull Runnable onComplete) { + + if (textView instanceof AppCompatTextView) { + ((AppCompatTextView) textView).setTextFuture(PrecomputedTextCompat.getTextFuture( + markdown, ((AppCompatTextView) textView).getTextMetricsParamsCompat(), executor + )); + + onComplete.run(); + } else { + Log.w("Markwon, no-op", "PrecomputedFutureTextSetterCompat: textView provided is not an AppCompatTextView!"); + } + } +} diff --git a/sample/build.gradle b/sample/build.gradle index 595fd54b..b87cab5e 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -54,6 +54,7 @@ dependencies { deps.with { implementation it['x-recycler-view'] implementation it['x-core'] // for precomputedTextCompat + implementation it['x-appcompat'] // for setTextFuture implementation it['okhttp'] implementation it['prism4j'] implementation it['debug'] diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index f85d8750..6b148bbc 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -28,6 +28,7 @@ + + \ No newline at end of file diff --git a/sample/src/main/res/values/strings-samples.xml b/sample/src/main/res/values/strings-samples.xml index 0305b471..1743aa35 100644 --- a/sample/src/main/res/values/strings-samples.xml +++ b/sample/src/main/res/values/strings-samples.xml @@ -25,6 +25,8 @@ # \# PrecomputedText\n\nUsage of TextSetter and PrecomputedTextCompat + # \# PrecomputedFutureText\n\nUsage of TextSetter and PrecomputedFutureTextSetterCompat + # \# Editor\n\n`MarkwonEditor` sample usage to highlight user input in EditText # \# Inline Parser\n\nUsage of custom inline parser From abeb5044afb37d57df86e4e57a7c8af2b37de033 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Tue, 24 Mar 2020 08:58:39 +0300 Subject: [PATCH 04/14] Update CHANGELOG --- CHANGELOG.md | 6 ++++++ .../main/java/io/noties/markwon/html/tag/StrikeHandler.java | 2 ++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28931d6f..aa38599f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +# 4.3.1-SNAPSHOT +* Fix DexGuard optimization issue ([#216])
Thanks [@francescocervone] + +[#216]: https://github.com/noties/Markwon/pull/216 +[@francescocervone]: https://github.com/francescocervone + # 4.3.0 * add `MarkwonInlineParserPlugin` in `inline-parser` module * `JLatexMathPlugin` now supports inline LaTeX structures via `MarkwonInlineParserPlugin` diff --git a/markwon-html/src/main/java/io/noties/markwon/html/tag/StrikeHandler.java b/markwon-html/src/main/java/io/noties/markwon/html/tag/StrikeHandler.java index 0ed1ef2b..37b3168d 100644 --- a/markwon-html/src/main/java/io/noties/markwon/html/tag/StrikeHandler.java +++ b/markwon-html/src/main/java/io/noties/markwon/html/tag/StrikeHandler.java @@ -25,6 +25,8 @@ public class StrikeHandler extends TagHandler { static { boolean hasMarkdownImplementation; try { + // @since $nap; we class Class.forName instead of trying + // to access the class by full qualified name (which caused issues with DexGuard) Class.forName("org.commonmark.ext.gfm.strikethrough.Strikethrough"); hasMarkdownImplementation = true; } catch (Throwable t) { From 392333806a4d762c17172ba94db047e18fe6db51 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Tue, 24 Mar 2020 09:26:36 +0300 Subject: [PATCH 05/14] image, GifSupport and SvgSupport Class.forName --- CHANGELOG.md | 1 + .../src/main/java/io/noties/markwon/image/gif/GifSupport.java | 3 ++- .../src/main/java/io/noties/markwon/image/svg/SvgSupport.java | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa38599f..5c0f5148 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ # 4.3.1-SNAPSHOT * Fix DexGuard optimization issue ([#216])
Thanks [@francescocervone] +* module `images`: `GifSupport` and `SvgSupport` use `Class.forName` instead access to full qualified class name [#216]: https://github.com/noties/Markwon/pull/216 [@francescocervone]: https://github.com/francescocervone diff --git a/markwon-image/src/main/java/io/noties/markwon/image/gif/GifSupport.java b/markwon-image/src/main/java/io/noties/markwon/image/gif/GifSupport.java index 70a143fc..b5c68d44 100644 --- a/markwon-image/src/main/java/io/noties/markwon/image/gif/GifSupport.java +++ b/markwon-image/src/main/java/io/noties/markwon/image/gif/GifSupport.java @@ -14,7 +14,8 @@ public abstract class GifSupport { static { boolean result; try { - pl.droidsonroids.gif.GifDrawable.class.getName(); + // @since $nap; + Class.forName("pl.droidsonroids.gif.GifDrawable"); result = true; } catch (Throwable t) { // @since 4.1.1 instead of printing full stacktrace of the exception, diff --git a/markwon-image/src/main/java/io/noties/markwon/image/svg/SvgSupport.java b/markwon-image/src/main/java/io/noties/markwon/image/svg/SvgSupport.java index 4afe2506..aea6ffb5 100644 --- a/markwon-image/src/main/java/io/noties/markwon/image/svg/SvgSupport.java +++ b/markwon-image/src/main/java/io/noties/markwon/image/svg/SvgSupport.java @@ -14,7 +14,7 @@ public abstract class SvgSupport { static { boolean result; try { - com.caverock.androidsvg.SVG.class.getName(); + Class.forName("com.caverock.androidsvg.SVG"); result = true; } catch (Throwable t) { // @since 4.1.1 instead of printing full stacktrace of the exception, From 54e5b27d59a3391bb010feecc5976e94b621950e Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Tue, 24 Mar 2020 09:55:28 +0300 Subject: [PATCH 06/14] Update sample --- .../basicplugins/BasicPluginsActivity.java | 133 +++++++++++++++++- 1 file changed, 132 insertions(+), 1 deletion(-) diff --git a/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java b/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java index e8bb4761..fa9ea9ca 100644 --- a/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java @@ -14,8 +14,11 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.commonmark.node.BulletList; import org.commonmark.node.Heading; +import org.commonmark.node.ListItem; import org.commonmark.node.Node; +import org.commonmark.node.OrderedList; import org.commonmark.node.Paragraph; import java.util.Collection; @@ -28,11 +31,16 @@ import io.noties.markwon.Markwon; import io.noties.markwon.MarkwonConfiguration; import io.noties.markwon.MarkwonSpansFactory; import io.noties.markwon.MarkwonVisitor; +import io.noties.markwon.Prop; +import io.noties.markwon.RenderProps; import io.noties.markwon.SoftBreakAddsNewLinePlugin; +import io.noties.markwon.SpanFactory; import io.noties.markwon.core.CoreProps; import io.noties.markwon.core.MarkwonTheme; +import io.noties.markwon.core.spans.BulletListItemSpan; import io.noties.markwon.core.spans.HeadingSpan; import io.noties.markwon.core.spans.LastLineSpacingSpan; +import io.noties.markwon.core.spans.OrderedListItemSpan; import io.noties.markwon.image.ImageItem; import io.noties.markwon.image.ImagesPlugin; import io.noties.markwon.image.SchemeHandler; @@ -62,7 +70,8 @@ public class BasicPluginsActivity extends ActivityWithMenuOptions { .add("headingNoSpace", this::headingNoSpace) .add("headingNoSpaceBlockHandler", this::headingNoSpaceBlockHandler) .add("allBlocksNoForcedLine", this::allBlocksNoForcedLine) - .add("anchor", this::anchor); + .add("anchor", this::anchor) + .add("letterOrderedList", this::letterOrderedList); } @Override @@ -502,4 +511,126 @@ final Markwon markwon = Markwon.builder(this) markwon.setMarkdown(textView, md); } + + private static final Prop ORDERED_IS_NESTED = Prop.of("my-ordered-is-nested"); + + private void letterOrderedList() { + // first level of ordered-list is still number, + // other ordered-list levels start with `A` + final String md = "" + + "1. Hello there!\n" + + "1. And here is how:\n" + + " 1. First\n" + + " 2. Second\n" + + " 3. Third\n" + + " 1. And first here\n\n"; + + final Markwon markwon = Markwon.builder(this) + .usePlugin(new AbstractMarkwonPlugin() { + @Override + public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { + builder.on(ListItem.class, new MarkwonVisitor.NodeVisitor() { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull ListItem listItem) { + final int length = visitor.length(); + + // it's important to visit children before applying render props ( + // we can have nested children, who are list items also, thus they will + // override out props (if we set them before visiting children) + visitor.visitChildren(listItem); + + final Node parent = listItem.getParent(); + if (parent instanceof OrderedList) { + + final int start = ((OrderedList) parent).getStartNumber(); + + CoreProps.LIST_ITEM_TYPE.set(visitor.renderProps(), CoreProps.ListItemType.ORDERED); + CoreProps.ORDERED_LIST_ITEM_NUMBER.set(visitor.renderProps(), start); + ORDERED_IS_NESTED.set(visitor.renderProps(), isOrderedListNested(parent)); + + // after we have visited the children increment start number + final OrderedList orderedList = (OrderedList) parent; + orderedList.setStartNumber(orderedList.getStartNumber() + 1); + + } else { + CoreProps.LIST_ITEM_TYPE.set(visitor.renderProps(), CoreProps.ListItemType.BULLET); + CoreProps.BULLET_LIST_ITEM_LEVEL.set(visitor.renderProps(), listLevel(listItem)); + } + + visitor.setSpansForNodeOptional(listItem, length); + + if (visitor.hasNext(listItem)) { + visitor.ensureNewLine(); + } + } + }); + } + + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + builder.setFactory(ListItem.class, new SpanFactory() { + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { + + // type of list item + // bullet : level + // ordered: number + final Object spans; + + if (CoreProps.ListItemType.BULLET == CoreProps.LIST_ITEM_TYPE.require(props)) { + spans = new BulletListItemSpan( + configuration.theme(), + CoreProps.BULLET_LIST_ITEM_LEVEL.require(props) + ); + } else { + final int number = CoreProps.ORDERED_LIST_ITEM_NUMBER.require(props); + final String text; + if (ORDERED_IS_NESTED.get(props, false)) { + // or `a`, or any other logic + text = ((char)('A' + number - 1)) + ".\u00a0"; + } else { + text = number + ".\u00a0"; + } + + spans = new OrderedListItemSpan( + configuration.theme(), + text + ); + } + + return spans; + } + }); + } + }) + .build(); + + markwon.setMarkdown(textView, md); + } + + private static int listLevel(@NonNull Node node) { + int level = 0; + Node parent = node.getParent(); + while (parent != null) { + if (parent instanceof ListItem) { + level += 1; + } + parent = parent.getParent(); + } + return level; + } + + private static boolean isOrderedListNested(@NonNull Node node) { + node = node.getParent(); + while (node != null) { + if (node instanceof OrderedList) { + return true; + } + if (node instanceof BulletList) { + return false; + } + node = node.getParent(); + } + return false; + } } From 3ee62a724cbda63f428d5990c15bba43e0984c44 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Tue, 24 Mar 2020 12:16:03 +0300 Subject: [PATCH 07/14] Update sample with anchor and bullet-letter plugins --- .../basicplugins/AnchorHeadingPlugin.java | 97 +++++++ .../basicplugins/BasicPluginsActivity.java | 268 ++---------------- ...tIsOrderedWithLettersWhenNestedPlugin.java | 156 ++++++++++ 3 files changed, 276 insertions(+), 245 deletions(-) create mode 100644 sample/src/main/java/io/noties/markwon/sample/basicplugins/AnchorHeadingPlugin.java create mode 100644 sample/src/main/java/io/noties/markwon/sample/basicplugins/BulletListIsOrderedWithLettersWhenNestedPlugin.java diff --git a/sample/src/main/java/io/noties/markwon/sample/basicplugins/AnchorHeadingPlugin.java b/sample/src/main/java/io/noties/markwon/sample/basicplugins/AnchorHeadingPlugin.java new file mode 100644 index 00000000..bc76e93f --- /dev/null +++ b/sample/src/main/java/io/noties/markwon/sample/basicplugins/AnchorHeadingPlugin.java @@ -0,0 +1,97 @@ +package io.noties.markwon.sample.basicplugins; + +import android.text.Spannable; +import android.text.Spanned; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import io.noties.markwon.AbstractMarkwonPlugin; +import io.noties.markwon.LinkResolverDef; +import io.noties.markwon.MarkwonConfiguration; +import io.noties.markwon.core.spans.HeadingSpan; + +public class AnchorHeadingPlugin extends AbstractMarkwonPlugin { + + public interface ScrollTo { + void scrollTo(@NonNull TextView view, int top); + } + + private final ScrollTo scrollTo; + + AnchorHeadingPlugin(@NonNull ScrollTo scrollTo) { + this.scrollTo = scrollTo; + } + + @Override + public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { + builder.linkResolver(new AnchorLinkResolver(scrollTo)); + } + + @Override + public void afterSetText(@NonNull TextView textView) { + final Spannable spannable = (Spannable) textView.getText(); + // obtain heading spans + final HeadingSpan[] spans = spannable.getSpans(0, spannable.length(), HeadingSpan.class); + if (spans != null) { + for (HeadingSpan span : spans) { + final int start = spannable.getSpanStart(span); + final int end = spannable.getSpanEnd(span); + final int flags = spannable.getSpanFlags(span); + spannable.setSpan( + new AnchorSpan(createAnchor(spannable.subSequence(start, end))), + start, + end, + flags + ); + } + } + } + + private static class AnchorLinkResolver extends LinkResolverDef { + + private final ScrollTo scrollTo; + + AnchorLinkResolver(@NonNull ScrollTo scrollTo) { + this.scrollTo = scrollTo; + } + + @Override + public void resolve(@NonNull View view, @NonNull String link) { + if (link.startsWith("#")) { + final TextView textView = (TextView) view; + final Spanned spanned = (Spannable) textView.getText(); + final AnchorSpan[] spans = spanned.getSpans(0, spanned.length(), AnchorSpan.class); + if (spans != null) { + final String anchor = link.substring(1); + for (AnchorSpan span : spans) { + if (anchor.equals(span.anchor)) { + final int start = spanned.getSpanStart(span); + final int line = textView.getLayout().getLineForOffset(start); + final int top = textView.getLayout().getLineTop(line); + scrollTo.scrollTo(textView, top); + return; + } + } + } + } + super.resolve(view, link); + } + } + + private static class AnchorSpan { + final String anchor; + + AnchorSpan(@NonNull String anchor) { + this.anchor = anchor; + } + } + + @NonNull + private static String createAnchor(@NonNull CharSequence content) { + return String.valueOf(content) + .replaceAll("[^\\w]", "") + .toLowerCase(); + } +} diff --git a/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java b/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java index fa9ea9ca..d6f9fef0 100644 --- a/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java @@ -3,22 +3,16 @@ package io.noties.markwon.sample.basicplugins; import android.graphics.Color; import android.net.Uri; import android.os.Bundle; -import android.text.Spannable; -import android.text.Spanned; import android.text.TextUtils; import android.text.style.ForegroundColorSpan; -import android.view.View; import android.widget.ScrollView; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import org.commonmark.node.BulletList; import org.commonmark.node.Heading; -import org.commonmark.node.ListItem; import org.commonmark.node.Node; -import org.commonmark.node.OrderedList; import org.commonmark.node.Paragraph; import java.util.Collection; @@ -26,21 +20,14 @@ import java.util.Collections; import io.noties.markwon.AbstractMarkwonPlugin; import io.noties.markwon.BlockHandlerDef; -import io.noties.markwon.LinkResolverDef; import io.noties.markwon.Markwon; import io.noties.markwon.MarkwonConfiguration; import io.noties.markwon.MarkwonSpansFactory; import io.noties.markwon.MarkwonVisitor; -import io.noties.markwon.Prop; -import io.noties.markwon.RenderProps; import io.noties.markwon.SoftBreakAddsNewLinePlugin; -import io.noties.markwon.SpanFactory; import io.noties.markwon.core.CoreProps; import io.noties.markwon.core.MarkwonTheme; -import io.noties.markwon.core.spans.BulletListItemSpan; -import io.noties.markwon.core.spans.HeadingSpan; import io.noties.markwon.core.spans.LastLineSpacingSpan; -import io.noties.markwon.core.spans.OrderedListItemSpan; import io.noties.markwon.image.ImageItem; import io.noties.markwon.image.ImagesPlugin; import io.noties.markwon.image.SchemeHandler; @@ -332,26 +319,26 @@ public class BasicPluginsActivity extends ActivityWithMenuOptions { } private void headingNoSpaceBlockHandler() { -final Markwon markwon = Markwon.builder(this) - .usePlugin(new AbstractMarkwonPlugin() { - @Override - public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { - builder.blockHandler(new BlockHandlerDef() { + final Markwon markwon = Markwon.builder(this) + .usePlugin(new AbstractMarkwonPlugin() { @Override - public void blockEnd(@NonNull MarkwonVisitor visitor, @NonNull Node node) { - if (node instanceof Heading) { - if (visitor.hasNext(node)) { - visitor.ensureNewLine(); - // ensure new line but do not force insert one + public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { + builder.blockHandler(new BlockHandlerDef() { + @Override + public void blockEnd(@NonNull MarkwonVisitor visitor, @NonNull Node node) { + if (node instanceof Heading) { + if (visitor.hasNext(node)) { + visitor.ensureNewLine(); + // ensure new line but do not force insert one + } + } else { + super.blockEnd(visitor, node); + } } - } else { - super.blockEnd(visitor, node); - } + }); } - }); - } - }) - .build(); + }) + .build(); final String md = "" + "# Title title title title title title title title title title \n\ntext text text text"; @@ -393,85 +380,6 @@ final Markwon markwon = Markwon.builder(this) markwon.setMarkdown(textView, md); } -// public void step_6() { -// -// final Markwon markwon = Markwon.builder(this) -// .usePlugin(HtmlPlugin.create()) -// .usePlugin(new AbstractMarkwonPlugin() { -// @Override -// public void configure(@NonNull Registry registry) { -// registry.require(HtmlPlugin.class, plugin -> plugin.addHandler(new SimpleTagHandler() { -// @Override -// public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps renderProps, @NonNull HtmlTag tag) { -// return new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER); -// } -// -// @NonNull -// @Override -// public Collection supportedTags() { -// return Collections.singleton("center"); -// } -// })); -// } -// }) -// .build(); -// } - - // text lifecycle (after/before) - // rendering lifecycle (before/after) - // renderProps - // process - - private static class AnchorSpan { - final String anchor; - - AnchorSpan(@NonNull String anchor) { - this.anchor = anchor; - } - } - - @NonNull - private String createAnchor(@NonNull CharSequence content) { - return String.valueOf(content) - .replaceAll("[^\\w]", "") - .toLowerCase(); - } - - private static class AnchorLinkResolver extends LinkResolverDef { - - interface ScrollTo { - void scrollTo(@NonNull View view, int top); - } - - private final ScrollTo scrollTo; - - AnchorLinkResolver(@NonNull ScrollTo scrollTo) { - this.scrollTo = scrollTo; - } - - @Override - public void resolve(@NonNull View view, @NonNull String link) { - if (link.startsWith("#")) { - final TextView textView = (TextView) view; - final Spanned spanned = (Spannable) textView.getText(); - final AnchorSpan[] spans = spanned.getSpans(0, spanned.length(), AnchorSpan.class); - if (spans != null) { - final String anchor = link.substring(1); - for (AnchorSpan span: spans) { - if (anchor.equals(span.anchor)) { - final int start = spanned.getSpanStart(span); - final int line = textView.getLayout().getLineForOffset(start); - final int top = textView.getLayout().getLineTop(line); - scrollTo.scrollTo(textView, top); - return; - } - } - } - } - super.resolve(view, link); - } - } - private void anchor() { final String lorem = getString(R.string.lorem); final String md = "" + @@ -481,156 +389,26 @@ final Markwon markwon = Markwon.builder(this) lorem; final Markwon markwon = Markwon.builder(this) - .usePlugin(new AbstractMarkwonPlugin() { - @Override - public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { - builder.linkResolver(new AnchorLinkResolver((view, top) -> scrollView.smoothScrollTo(0, top))); - } - - @Override - public void afterSetText(@NonNull TextView textView) { - final Spannable spannable = (Spannable) textView.getText(); - // obtain heading spans - final HeadingSpan[] spans = spannable.getSpans(0, spannable.length(), HeadingSpan.class); - if (spans != null) { - for (HeadingSpan span : spans) { - final int start = spannable.getSpanStart(span); - final int end = spannable.getSpanEnd(span); - final int flags = spannable.getSpanFlags(span); - spannable.setSpan( - new AnchorSpan(createAnchor(spannable.subSequence(start, end))), - start, - end, - flags - ); - } - } - } - }) + .usePlugin(new AnchorHeadingPlugin((view, top) -> scrollView.smoothScrollTo(0, top))) .build(); markwon.setMarkdown(textView, md); } - private static final Prop ORDERED_IS_NESTED = Prop.of("my-ordered-is-nested"); - private void letterOrderedList() { - // first level of ordered-list is still number, - // other ordered-list levels start with `A` + // bullet list nested in ordered list renders letters instead of bullets final String md = "" + "1. Hello there!\n" + "1. And here is how:\n" + - " 1. First\n" + - " 2. Second\n" + - " 3. Third\n" + + " - First\n" + + " - Second\n" + + " - Third\n" + " 1. And first here\n\n"; final Markwon markwon = Markwon.builder(this) - .usePlugin(new AbstractMarkwonPlugin() { - @Override - public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { - builder.on(ListItem.class, new MarkwonVisitor.NodeVisitor() { - @Override - public void visit(@NonNull MarkwonVisitor visitor, @NonNull ListItem listItem) { - final int length = visitor.length(); - - // it's important to visit children before applying render props ( - // we can have nested children, who are list items also, thus they will - // override out props (if we set them before visiting children) - visitor.visitChildren(listItem); - - final Node parent = listItem.getParent(); - if (parent instanceof OrderedList) { - - final int start = ((OrderedList) parent).getStartNumber(); - - CoreProps.LIST_ITEM_TYPE.set(visitor.renderProps(), CoreProps.ListItemType.ORDERED); - CoreProps.ORDERED_LIST_ITEM_NUMBER.set(visitor.renderProps(), start); - ORDERED_IS_NESTED.set(visitor.renderProps(), isOrderedListNested(parent)); - - // after we have visited the children increment start number - final OrderedList orderedList = (OrderedList) parent; - orderedList.setStartNumber(orderedList.getStartNumber() + 1); - - } else { - CoreProps.LIST_ITEM_TYPE.set(visitor.renderProps(), CoreProps.ListItemType.BULLET); - CoreProps.BULLET_LIST_ITEM_LEVEL.set(visitor.renderProps(), listLevel(listItem)); - } - - visitor.setSpansForNodeOptional(listItem, length); - - if (visitor.hasNext(listItem)) { - visitor.ensureNewLine(); - } - } - }); - } - - @Override - public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { - builder.setFactory(ListItem.class, new SpanFactory() { - @Override - public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { - - // type of list item - // bullet : level - // ordered: number - final Object spans; - - if (CoreProps.ListItemType.BULLET == CoreProps.LIST_ITEM_TYPE.require(props)) { - spans = new BulletListItemSpan( - configuration.theme(), - CoreProps.BULLET_LIST_ITEM_LEVEL.require(props) - ); - } else { - final int number = CoreProps.ORDERED_LIST_ITEM_NUMBER.require(props); - final String text; - if (ORDERED_IS_NESTED.get(props, false)) { - // or `a`, or any other logic - text = ((char)('A' + number - 1)) + ".\u00a0"; - } else { - text = number + ".\u00a0"; - } - - spans = new OrderedListItemSpan( - configuration.theme(), - text - ); - } - - return spans; - } - }); - } - }) + .usePlugin(new BulletListIsOrderedWithLettersWhenNestedPlugin()) .build(); markwon.setMarkdown(textView, md); } - - private static int listLevel(@NonNull Node node) { - int level = 0; - Node parent = node.getParent(); - while (parent != null) { - if (parent instanceof ListItem) { - level += 1; - } - parent = parent.getParent(); - } - return level; - } - - private static boolean isOrderedListNested(@NonNull Node node) { - node = node.getParent(); - while (node != null) { - if (node instanceof OrderedList) { - return true; - } - if (node instanceof BulletList) { - return false; - } - node = node.getParent(); - } - return false; - } } diff --git a/sample/src/main/java/io/noties/markwon/sample/basicplugins/BulletListIsOrderedWithLettersWhenNestedPlugin.java b/sample/src/main/java/io/noties/markwon/sample/basicplugins/BulletListIsOrderedWithLettersWhenNestedPlugin.java new file mode 100644 index 00000000..26712de0 --- /dev/null +++ b/sample/src/main/java/io/noties/markwon/sample/basicplugins/BulletListIsOrderedWithLettersWhenNestedPlugin.java @@ -0,0 +1,156 @@ +package io.noties.markwon.sample.basicplugins; + +import android.text.TextUtils; +import android.util.SparseIntArray; + +import androidx.annotation.NonNull; + +import org.commonmark.node.BulletList; +import org.commonmark.node.ListItem; +import org.commonmark.node.Node; +import org.commonmark.node.OrderedList; + +import io.noties.markwon.AbstractMarkwonPlugin; +import io.noties.markwon.MarkwonSpansFactory; +import io.noties.markwon.MarkwonVisitor; +import io.noties.markwon.Prop; +import io.noties.markwon.core.CoreProps; +import io.noties.markwon.core.spans.BulletListItemSpan; +import io.noties.markwon.core.spans.OrderedListItemSpan; + +public class BulletListIsOrderedWithLettersWhenNestedPlugin extends AbstractMarkwonPlugin { + + private static final Prop BULLET_LETTER = Prop.of("my-bullet-letter"); + + // or introduce some kind of synchronization if planning to use from multiple threads, + // for example via ThreadLocal + private final SparseIntArray bulletCounter = new SparseIntArray(); + + @Override + public void afterRender(@NonNull Node node, @NonNull MarkwonVisitor visitor) { + // clear counter after render + bulletCounter.clear(); + } + + @Override + public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { + // NB that both ordered and bullet lists are represented + // by ListItem (must inspect parent to detect the type) + builder.on(ListItem.class, (visitor, listItem) -> { + // mimic original behaviour (copy-pasta from CorePlugin) + + final int length = visitor.length(); + + visitor.visitChildren(listItem); + + final Node parent = listItem.getParent(); + if (parent instanceof OrderedList) { + + final int start = ((OrderedList) parent).getStartNumber(); + + CoreProps.LIST_ITEM_TYPE.set(visitor.renderProps(), CoreProps.ListItemType.ORDERED); + CoreProps.ORDERED_LIST_ITEM_NUMBER.set(visitor.renderProps(), start); + + // after we have visited the children increment start number + final OrderedList orderedList = (OrderedList) parent; + orderedList.setStartNumber(orderedList.getStartNumber() + 1); + + } else { + CoreProps.LIST_ITEM_TYPE.set(visitor.renderProps(), CoreProps.ListItemType.BULLET); + + if (isBulletOrdered(parent)) { + // obtain current count value + final int count = currentBulletCountIn(parent); + BULLET_LETTER.set(visitor.renderProps(), createBulletLetter(count)); + // update current count value + setCurrentBulletCountIn(parent, count + 1); + } else { + CoreProps.BULLET_LIST_ITEM_LEVEL.set(visitor.renderProps(), listLevel(listItem)); + // clear letter info when regular bullet list is used + BULLET_LETTER.clear(visitor.renderProps()); + } + } + + visitor.setSpansForNodeOptional(listItem, length); + + if (visitor.hasNext(listItem)) { + visitor.ensureNewLine(); + } + }); + } + + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + builder.setFactory(ListItem.class, (configuration, props) -> { + final Object spans; + + if (CoreProps.ListItemType.BULLET == CoreProps.LIST_ITEM_TYPE.require(props)) { + final String letter = BULLET_LETTER.get(props); + if (!TextUtils.isEmpty(letter)) { + // NB, we are using OrderedListItemSpan here! + spans = new OrderedListItemSpan( + configuration.theme(), + letter + ); + } else { + spans = new BulletListItemSpan( + configuration.theme(), + CoreProps.BULLET_LIST_ITEM_LEVEL.require(props) + ); + } + } else { + + final String number = String.valueOf(CoreProps.ORDERED_LIST_ITEM_NUMBER.require(props)) + + "." + '\u00a0'; + + spans = new OrderedListItemSpan( + configuration.theme(), + number + ); + } + + return spans; + }); + } + + private int currentBulletCountIn(@NonNull Node parent) { + return bulletCounter.get(parent.hashCode(), 0); + } + + private void setCurrentBulletCountIn(@NonNull Node parent, int count) { + bulletCounter.put(parent.hashCode(), count); + } + + @NonNull + private static String createBulletLetter(int count) { + // or lower `a` + // `'u00a0` is non-breakable space char + return ((char) ('A' + count)) + ".\u00a0"; + } + + private static int listLevel(@NonNull Node node) { + int level = 0; + Node parent = node.getParent(); + while (parent != null) { + if (parent instanceof ListItem) { + level += 1; + } + parent = parent.getParent(); + } + return level; + } + + private static boolean isBulletOrdered(@NonNull Node node) { + node = node.getParent(); + while (node != null) { + if (node instanceof OrderedList) { + return true; + } + if (node instanceof BulletList) { + return false; + } + node = node.getParent(); + } + return false; + } +} From 3ef41b1b81f570fe9abec7e446c5972f8e1d8186 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Wed, 25 Mar 2020 21:15:15 +0300 Subject: [PATCH 08/14] Update sample (toc plugin) --- .../basicplugins/AnchorHeadingPlugin.java | 2 +- .../basicplugins/BasicPluginsActivity.java | 25 +++- .../basicplugins/TableOfContentsPlugin.java | 115 ++++++++++++++++++ 3 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 sample/src/main/java/io/noties/markwon/sample/basicplugins/TableOfContentsPlugin.java diff --git a/sample/src/main/java/io/noties/markwon/sample/basicplugins/AnchorHeadingPlugin.java b/sample/src/main/java/io/noties/markwon/sample/basicplugins/AnchorHeadingPlugin.java index bc76e93f..2471cd4f 100644 --- a/sample/src/main/java/io/noties/markwon/sample/basicplugins/AnchorHeadingPlugin.java +++ b/sample/src/main/java/io/noties/markwon/sample/basicplugins/AnchorHeadingPlugin.java @@ -89,7 +89,7 @@ public class AnchorHeadingPlugin extends AbstractMarkwonPlugin { } @NonNull - private static String createAnchor(@NonNull CharSequence content) { + public static String createAnchor(@NonNull CharSequence content) { return String.valueOf(content) .replaceAll("[^\\w]", "") .toLowerCase(); diff --git a/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java b/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java index d6f9fef0..9b9ffdde 100644 --- a/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/basicplugins/BasicPluginsActivity.java @@ -58,7 +58,8 @@ public class BasicPluginsActivity extends ActivityWithMenuOptions { .add("headingNoSpaceBlockHandler", this::headingNoSpaceBlockHandler) .add("allBlocksNoForcedLine", this::allBlocksNoForcedLine) .add("anchor", this::anchor) - .add("letterOrderedList", this::letterOrderedList); + .add("letterOrderedList", this::letterOrderedList) + .add("tableOfContents", this::tableOfContents); } @Override @@ -411,4 +412,26 @@ public class BasicPluginsActivity extends ActivityWithMenuOptions { markwon.setMarkdown(textView, md); } + + private void tableOfContents() { + final String lorem = 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(this) + .usePlugin(new TableOfContentsPlugin()) + .usePlugin(new AnchorHeadingPlugin((view, top) -> scrollView.smoothScrollTo(0, top))) + .build(); + + markwon.setMarkdown(textView, md); + } } diff --git a/sample/src/main/java/io/noties/markwon/sample/basicplugins/TableOfContentsPlugin.java b/sample/src/main/java/io/noties/markwon/sample/basicplugins/TableOfContentsPlugin.java new file mode 100644 index 00000000..c172ec7e --- /dev/null +++ b/sample/src/main/java/io/noties/markwon/sample/basicplugins/TableOfContentsPlugin.java @@ -0,0 +1,115 @@ +package io.noties.markwon.sample.basicplugins; + +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.MarkwonVisitor; +import io.noties.markwon.core.SimpleBlockNodeVisitor; + +public 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 { + } +} From f47124a2acdf3e3557f098e5bb5e9d17d66ccb22 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Thu, 26 Mar 2020 16:16:54 +0300 Subject: [PATCH 09/14] Sample, add heading handler --- .../markwon/sample/editor/EditorActivity.java | 13 +++- .../sample/editor/HeadingEditHandler.java | 78 +++++++++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 sample/src/main/java/io/noties/markwon/sample/editor/HeadingEditHandler.java diff --git a/sample/src/main/java/io/noties/markwon/sample/editor/EditorActivity.java b/sample/src/main/java/io/noties/markwon/sample/editor/EditorActivity.java index e1181a7f..31a4370e 100644 --- a/sample/src/main/java/io/noties/markwon/sample/editor/EditorActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/editor/EditorActivity.java @@ -64,7 +64,8 @@ public class EditorActivity extends ActivityWithMenuOptions { .add("multipleEditSpans", this::multiple_edit_spans) .add("multipleEditSpansPlugin", this::multiple_edit_spans_plugin) .add("pluginRequire", this::plugin_require) - .add("pluginNoDefaults", this::plugin_no_defaults); + .add("pluginNoDefaults", this::plugin_no_defaults) + .add("heading", this::heading); } @Override @@ -317,6 +318,16 @@ public class EditorActivity extends ActivityWithMenuOptions { editor, Executors.newSingleThreadExecutor(), editText)); } + private void heading() { + final Markwon markwon = Markwon.create(this); + final MarkwonEditor editor = MarkwonEditor.builder(markwon) + .useEditHandler(new HeadingEditHandler()) + .build(); + + editText.addTextChangedListener(MarkwonEditorTextWatcher.withPreRender( + editor, Executors.newSingleThreadExecutor(), editText)); + } + private void initBottomBar() { // all except block-quote wraps if have selection, or inserts at current cursor position diff --git a/sample/src/main/java/io/noties/markwon/sample/editor/HeadingEditHandler.java b/sample/src/main/java/io/noties/markwon/sample/editor/HeadingEditHandler.java new file mode 100644 index 00000000..f76499db --- /dev/null +++ b/sample/src/main/java/io/noties/markwon/sample/editor/HeadingEditHandler.java @@ -0,0 +1,78 @@ +package io.noties.markwon.sample.editor; + +import android.text.Editable; +import android.text.Spanned; + +import androidx.annotation.NonNull; + +import io.noties.markwon.Markwon; +import io.noties.markwon.core.MarkwonTheme; +import io.noties.markwon.core.spans.HeadingSpan; +import io.noties.markwon.editor.EditHandler; +import io.noties.markwon.editor.PersistedSpans; + +public class HeadingEditHandler implements EditHandler { + + private MarkwonTheme theme; + + @Override + public void init(@NonNull Markwon markwon) { + this.theme = markwon.configuration().theme(); + } + + @Override + public void configurePersistedSpans(@NonNull PersistedSpans.Builder builder) { + builder + .persistSpan(Head1.class, () -> new Head1(theme)) + .persistSpan(Head2.class, () -> new Head2(theme)); + } + + @Override + public void handleMarkdownSpan( + @NonNull PersistedSpans persistedSpans, + @NonNull Editable editable, + @NonNull String input, + @NonNull HeadingSpan span, + int spanStart, + int spanTextLength + ) { + final Class type; + switch (span.getLevel()) { + case 1: type = Head1.class; break; + case 2: type = Head2.class; break; + default: + type = null; + } + + if (type != null) { + final int index = input.indexOf('\n', spanStart + spanTextLength); + final int end = index < 0 + ? input.length() + : index; + editable.setSpan( + persistedSpans.get(type), + spanStart, + end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ); + } + } + + @NonNull + @Override + public Class markdownSpanType() { + return HeadingSpan.class; + } + + private static class Head1 extends HeadingSpan { + Head1(@NonNull MarkwonTheme theme) { + super(theme, 1); + } + } + + private static class Head2 extends HeadingSpan { + Head2(@NonNull MarkwonTheme theme) { + super(theme, 2); + } + } +} From a6bd102e8219c3c2692e879186bf140858c43659 Mon Sep 17 00:00:00 2001 From: luca Date: Sat, 28 Mar 2020 17:51:06 +0100 Subject: [PATCH 10/14] changed noop on PrecomputedFutureTextSetterCompat to error throw --- .../io/noties/markwon/PrecomputedFutureTextSetterCompat.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/markwon-core/src/main/java/io/noties/markwon/PrecomputedFutureTextSetterCompat.java b/markwon-core/src/main/java/io/noties/markwon/PrecomputedFutureTextSetterCompat.java index 66efe187..7e164a20 100644 --- a/markwon-core/src/main/java/io/noties/markwon/PrecomputedFutureTextSetterCompat.java +++ b/markwon-core/src/main/java/io/noties/markwon/PrecomputedFutureTextSetterCompat.java @@ -55,7 +55,8 @@ public class PrecomputedFutureTextSetterCompat implements Markwon.TextSetter { onComplete.run(); } else { - Log.w("Markwon, no-op", "PrecomputedFutureTextSetterCompat: textView provided is not an AppCompatTextView!"); + + throw new IllegalStateException("TextView provided is not a child of AppCompatTextView, cannot call setTextFuture()."); } } } From 33f0dcb841cb28fb22aa046422defc7301aaf739 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Sun, 29 Mar 2020 10:36:51 +0300 Subject: [PATCH 11/14] Fix links in ext-table --- .../markwon/ext/tables/TablePlugin.java | 4 -- .../markwon/ext/tables/TableRowSpan.java | 8 ++- sample/src/main/AndroidManifest.xml | 1 + .../noties/markwon/sample/MainActivity.java | 5 ++ .../java/io/noties/markwon/sample/Sample.java | 4 +- .../markwon/sample/table/TableActivity.java | 57 +++++++++++++++++++ .../src/main/res/values/strings-samples.xml | 1 + 7 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 sample/src/main/java/io/noties/markwon/sample/table/TableActivity.java diff --git a/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TablePlugin.java b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TablePlugin.java index 0cbce8b8..93dccd83 100644 --- a/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TablePlugin.java +++ b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TablePlugin.java @@ -125,10 +125,6 @@ public class TablePlugin extends AbstractMarkwonPlugin { visitor.visitChildren(tableBlock); -// if (visitor.hasNext(tableBlock)) { -// visitor.ensureNewLine(); -// visitor.forceNewLine(); -// } visitor.blockEnd(tableBlock); } }) diff --git a/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableRowSpan.java b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableRowSpan.java index 6cbdcae4..a90818fb 100644 --- a/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableRowSpan.java +++ b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableRowSpan.java @@ -143,7 +143,13 @@ public class TableRowSpan extends ReplacementSpan { if (recreateLayouts(canvas.getWidth())) { width = canvas.getWidth(); - textPaint.set(paint); + // @since $nap; it's important to cast to TextPaint in order to display links, etc + if (paint instanceof TextPaint) { + // there must be a reason why this method receives Paint instead of TextPaint... + textPaint.set((TextPaint) paint); + } else { + textPaint.set(paint); + } makeNewLayouts(); } diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index f85d8750..04e2bc6d 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -38,6 +38,7 @@ + diff --git a/sample/src/main/java/io/noties/markwon/sample/MainActivity.java b/sample/src/main/java/io/noties/markwon/sample/MainActivity.java index 2ac55d99..0f1d903e 100644 --- a/sample/src/main/java/io/noties/markwon/sample/MainActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/MainActivity.java @@ -32,6 +32,7 @@ import io.noties.markwon.sample.notification.NotificationActivity; import io.noties.markwon.sample.precomputed.PrecomputedActivity; import io.noties.markwon.sample.recycler.RecyclerActivity; import io.noties.markwon.sample.simpleext.SimpleExtActivity; +import io.noties.markwon.sample.table.TableActivity; import io.noties.markwon.sample.tasklist.TaskListActivity; public class MainActivity extends Activity { @@ -147,6 +148,10 @@ public class MainActivity extends Activity { activity = NotificationActivity.class; break; + case TABLE: + activity = TableActivity.class; + break; + default: throw new IllegalStateException("No Activity is associated with sample-item: " + item); } diff --git a/sample/src/main/java/io/noties/markwon/sample/Sample.java b/sample/src/main/java/io/noties/markwon/sample/Sample.java index f18ed25b..e8c2c73d 100644 --- a/sample/src/main/java/io/noties/markwon/sample/Sample.java +++ b/sample/src/main/java/io/noties/markwon/sample/Sample.java @@ -33,7 +33,9 @@ public enum Sample { IMAGES(R.string.sample_images), - REMOTE_VIEWS(R.string.sample_remote_views); + REMOTE_VIEWS(R.string.sample_remote_views), + + TABLE(R.string.sample_table); private final int textResId; diff --git a/sample/src/main/java/io/noties/markwon/sample/table/TableActivity.java b/sample/src/main/java/io/noties/markwon/sample/table/TableActivity.java new file mode 100644 index 00000000..a47a13e2 --- /dev/null +++ b/sample/src/main/java/io/noties/markwon/sample/table/TableActivity.java @@ -0,0 +1,57 @@ +package io.noties.markwon.sample.table; + +import android.os.Bundle; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import io.noties.markwon.Markwon; +import io.noties.markwon.ext.tables.TablePlugin; +import io.noties.markwon.linkify.LinkifyPlugin; +import io.noties.markwon.sample.ActivityWithMenuOptions; +import io.noties.markwon.sample.MenuOptions; +import io.noties.markwon.sample.R; + +public class TableActivity extends ActivityWithMenuOptions { + + @NonNull + @Override + public MenuOptions menuOptions() { + return MenuOptions.create() + .add("tableAndLinkify", this::tableAndLinkify); + } + + private TextView textView; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_text_view); + textView = findViewById(R.id.text_view); + + tableAndLinkify(); + } + + private void tableAndLinkify() { + final String md = "" + + "| HEADER | HEADER | HEADER |\n" + + "|:----:|:----:|:----:|\n" + + "| 测试 | 测试 | 测试 |\n" + + "| 测试 | 测试 | 测测测12345试测试测试 |\n" + + "| 测试 | 测试 | 123445 |\n" + + "| 测试 | 测试 | (650) 555-1212 |\n" + + "| 测试 | 测试 | [link](#) |\n" + + "\n" + + "测试\n" + + "\n" + + "[https://www.baidu.com](https://www.baidu.com)"; + + final Markwon markwon = Markwon.builder(this) + .usePlugin(LinkifyPlugin.create()) + .usePlugin(TablePlugin.create(this)) + .build(); + + markwon.setMarkdown(textView, md); + } +} diff --git a/sample/src/main/res/values/strings-samples.xml b/sample/src/main/res/values/strings-samples.xml index 0305b471..9da9300a 100644 --- a/sample/src/main/res/values/strings-samples.xml +++ b/sample/src/main/res/values/strings-samples.xml @@ -37,4 +37,5 @@ # \# Notification\n\nExample usage in notifications and other remote views + # \# Table\n\nUsage of tables in a `TextView` \ No newline at end of file From ddfa9c98b839053f36242cc88d976d071f89b4c0 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Sun, 29 Mar 2020 15:54:44 +0300 Subject: [PATCH 12/14] Table plugin, better borders --- CHANGELOG.md | 6 +- .../noties/markwon/debug/ColorBlendView.java | 59 +++++++++++++ .../debug/res/layout/debug_color_blend.xml | 34 ++++++++ app/src/debug/res/values/attrs.xml | 5 ++ .../io/noties/markwon/utils/ColorUtils.java | 24 +++++- .../markwon/ext/tables/TablePlugin.java | 5 ++ .../markwon/ext/tables/TableRowSpan.java | 84 +++++++++++++++---- .../noties/markwon/ext/tables/TableSpan.java | 7 ++ .../noties/markwon/ext/tables/TableTheme.java | 4 +- .../markwon/sample/table/TableActivity.java | 34 +++++++- 10 files changed, 240 insertions(+), 22 deletions(-) create mode 100644 app/src/debug/java/io/noties/markwon/debug/ColorBlendView.java create mode 100644 app/src/debug/res/layout/debug_color_blend.xml create mode 100644 markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableSpan.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c0f5148..31fc43d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,16 @@ # Changelog -# 4.3.1-SNAPSHOT +# $nap; * Fix DexGuard optimization issue ([#216])
Thanks [@francescocervone] * module `images`: `GifSupport` and `SvgSupport` use `Class.forName` instead access to full qualified class name +* `ext-table`: fix links in tables ([#224]) +* `ext-table`: proper borders (equal for all sides) [#216]: https://github.com/noties/Markwon/pull/216 +[#224]: https://github.com/noties/Markwon/issues/224 [@francescocervone]: https://github.com/francescocervone + # 4.3.0 * add `MarkwonInlineParserPlugin` in `inline-parser` module * `JLatexMathPlugin` now supports inline LaTeX structures via `MarkwonInlineParserPlugin` diff --git a/app/src/debug/java/io/noties/markwon/debug/ColorBlendView.java b/app/src/debug/java/io/noties/markwon/debug/ColorBlendView.java new file mode 100644 index 00000000..baf5e92b --- /dev/null +++ b/app/src/debug/java/io/noties/markwon/debug/ColorBlendView.java @@ -0,0 +1,59 @@ +package io.noties.markwon.debug; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.View; + +import androidx.annotation.Nullable; + +import io.noties.markwon.app.R; +import io.noties.markwon.utils.ColorUtils; + +public class ColorBlendView extends View { + + private final Rect rect = new Rect(); + private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + + private int background; + private int foreground; + + public ColorBlendView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + + if (attrs != null) { + final TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ColorBlendView); + try { + background = array.getColor(R.styleable.ColorBlendView_cbv_background, 0); + foreground = array.getColor(R.styleable.ColorBlendView_cbv_foreground, 0); + } finally { + array.recycle(); + } + } + + paint.setStyle(Paint.Style.FILL); + + setWillNotDraw(false); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + final int side = getWidth() / 11; + + rect.set(0, 0, side, getHeight()); + + canvas.translate(getPaddingLeft(), 0F); + + for (int i = 0; i < 11; i++) { + final float alpha = i / 10F; + paint.setColor(ColorUtils.blend(foreground, background, alpha)); + canvas.drawRect(rect, paint); + canvas.translate(side, 0F); + } + } +} diff --git a/app/src/debug/res/layout/debug_color_blend.xml b/app/src/debug/res/layout/debug_color_blend.xml new file mode 100644 index 00000000..cacc73fa --- /dev/null +++ b/app/src/debug/res/layout/debug_color_blend.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/debug/res/values/attrs.xml b/app/src/debug/res/values/attrs.xml index 161f7806..0636e107 100644 --- a/app/src/debug/res/values/attrs.xml +++ b/app/src/debug/res/values/attrs.xml @@ -8,4 +8,9 @@ + + + + + \ No newline at end of file diff --git a/markwon-core/src/main/java/io/noties/markwon/utils/ColorUtils.java b/markwon-core/src/main/java/io/noties/markwon/utils/ColorUtils.java index d2a8bc6e..d5d9703d 100644 --- a/markwon-core/src/main/java/io/noties/markwon/utils/ColorUtils.java +++ b/markwon-core/src/main/java/io/noties/markwon/utils/ColorUtils.java @@ -1,11 +1,33 @@ package io.noties.markwon.utils; +import android.graphics.Color; + +import androidx.annotation.ColorInt; +import androidx.annotation.FloatRange; +import androidx.annotation.IntRange; + public abstract class ColorUtils { - public static int applyAlpha(int color, int alpha) { + @ColorInt + public static int applyAlpha( + @ColorInt int color, + @IntRange(from = 0, to = 255) int alpha) { return (color & 0x00FFFFFF) | (alpha << 24); } + // blend two colors w/ specified ratio, resulting color won't have alpha channel + @ColorInt + public static int blend( + @ColorInt int foreground, + @ColorInt int background, + @FloatRange(from = 0.0F, to = 1.0F) float ratio) { + return Color.rgb( + (int) (((1F - ratio) * Color.red(foreground)) + (ratio * Color.red(background))), + (int) (((1F - ratio) * Color.green(foreground)) + (ratio * Color.green(background))), + (int) (((1F - ratio) * Color.blue(foreground)) + (ratio * Color.blue(background))) + ); + } + private ColorUtils() { } } diff --git a/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TablePlugin.java b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TablePlugin.java index 93dccd83..8e624e01 100644 --- a/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TablePlugin.java +++ b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TablePlugin.java @@ -123,8 +123,13 @@ public class TablePlugin extends AbstractMarkwonPlugin { visitor.blockStart(tableBlock); + final int length = visitor.length(); + visitor.visitChildren(tableBlock); + // @since $nap; apply table span for the full table + visitor.setSpans(length, new TableSpan()); + visitor.blockEnd(tableBlock); } }) diff --git a/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableRowSpan.java b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableRowSpan.java index a90818fb..f6653cf8 100644 --- a/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableRowSpan.java +++ b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableRowSpan.java @@ -5,6 +5,7 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.text.Layout; +import android.text.Spanned; import android.text.StaticLayout; import android.text.TextPaint; import android.text.style.ReplacementSpan; @@ -19,6 +20,8 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; +import io.noties.markwon.utils.LeadingMarginUtils; + public class TableRowSpan extends ReplacementSpan { public static final int ALIGN_LEFT = 0; @@ -139,16 +142,16 @@ public class TableRowSpan extends ReplacementSpan { int top, int y, int bottom, - @NonNull Paint paint) { + @NonNull Paint p) { if (recreateLayouts(canvas.getWidth())) { width = canvas.getWidth(); // @since $nap; it's important to cast to TextPaint in order to display links, etc - if (paint instanceof TextPaint) { + if (p instanceof TextPaint) { // there must be a reason why this method receives Paint instead of TextPaint... - textPaint.set((TextPaint) paint); + textPaint.set((TextPaint) p); } else { - textPaint.set(paint); + textPaint.set(p); } makeNewLayouts(); } @@ -161,28 +164,25 @@ public class TableRowSpan extends ReplacementSpan { final int w = width / size; - // feels like magic... - final int heightDiff = (bottom - top - height) / 4; - // @since 1.1.1 // draw backgrounds { if (header) { - theme.applyTableHeaderRowStyle(this.paint); + theme.applyTableHeaderRowStyle(paint); } else if (odd) { - theme.applyTableOddRowStyle(this.paint); + theme.applyTableOddRowStyle(paint); } else { // even - theme.applyTableEvenRowStyle(this.paint); + theme.applyTableEvenRowStyle(paint); } // if present (0 is transparent) - if (this.paint.getColor() != 0) { + if (paint.getColor() != 0) { final int save = canvas.save(); try { rect.set(0, 0, width, bottom - top); - canvas.translate(x, top - heightDiff); - canvas.drawRect(rect, this.paint); + canvas.translate(x, top); + canvas.drawRect(rect, paint); } finally { canvas.restoreToCount(save); } @@ -192,25 +192,73 @@ public class TableRowSpan extends ReplacementSpan { // @since 1.1.1 reset after applying background color // as background changes color attribute and if not specific tableBorderColor // is specified then after this row all borders will have color of this row (plus alpha) - this.paint.set(paint); - theme.applyTableBorderStyle(this.paint); + paint.set(p); + theme.applyTableBorderStyle(paint); final int borderWidth = theme.tableBorderWidth(paint); final boolean drawBorder = borderWidth > 0; + + // why divided by 4 gives a more or less good result is still not clear (shouldn't it be 2?) + final int heightDiff = (bottom - top - height) / 4; + + // required for borderTop calculation + final boolean isFirstTableRow; + + // @since $nap; if (drawBorder) { - rect.set(0, 0, w, bottom - top); + boolean first = false; + // only if first draw the line + { + final Spanned spanned = (Spanned) text; + final TableSpan[] spans = spanned.getSpans(start, end, TableSpan.class); + if (spans != null && spans.length > 0) { + final TableSpan span = spans[0]; + if (LeadingMarginUtils.selfStart(start, text, span)) { + first = true; + rect.set((int) x, top, width, top + borderWidth); + canvas.drawRect(rect, paint); + } + } + } + + // draw the line at the bottom + rect.set((int) x, bottom - borderWidth, width, bottom); + canvas.drawRect(rect, paint); + + isFirstTableRow = first; + } else { + isFirstTableRow = false; } + final int borderWidthHalf = borderWidth / 2; + + // to NOT overlap borders inset top and bottom + final int borderTop = isFirstTableRow ? borderWidth : 0; + final int borderBottom = bottom - top - borderWidth; + StaticLayout layout; for (int i = 0; i < size; i++) { layout = layouts.get(i); final int save = canvas.save(); try { - canvas.translate(x + (i * w), top - heightDiff); + canvas.translate(x + (i * w), top); + // @since $nap; if (drawBorder) { - canvas.drawRect(rect, this.paint); + // first vertical border will have full width (it cannot exceed canvas) + if (i == 0) { + rect.set(0, borderTop, borderWidth, borderBottom); + } else { + rect.set(-borderWidthHalf, borderTop, borderWidthHalf, borderBottom); + } + + canvas.drawRect(rect, paint); + + if (i == (size - 1)) { + rect.set(w - borderWidth, borderTop, w, borderBottom); + canvas.drawRect(rect, paint); + } } canvas.translate(padding, padding + heightDiff); diff --git a/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableSpan.java b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableSpan.java new file mode 100644 index 00000000..bba40d76 --- /dev/null +++ b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableSpan.java @@ -0,0 +1,7 @@ +package io.noties.markwon.ext.tables; + +/** + * @since $nap; + */ +public class TableSpan { +} diff --git a/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableTheme.java b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableTheme.java index f4a1c5cc..48655220 100644 --- a/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableTheme.java +++ b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableTheme.java @@ -10,6 +10,7 @@ import androidx.annotation.Px; import io.noties.markwon.utils.ColorUtils; import io.noties.markwon.utils.Dip; +@SuppressWarnings("WeakerAccess") public class TableTheme { @NonNull @@ -101,7 +102,8 @@ public class TableTheme { } paint.setColor(color); - paint.setStyle(Paint.Style.STROKE); + // @since $nap; before it was STROKE... change to FILL as we draw border differently + paint.setStyle(Paint.Style.FILL); } public void applyTableOddRowStyle(@NonNull Paint paint) { diff --git a/sample/src/main/java/io/noties/markwon/sample/table/TableActivity.java b/sample/src/main/java/io/noties/markwon/sample/table/TableActivity.java index a47a13e2..d7153ec6 100644 --- a/sample/src/main/java/io/noties/markwon/sample/table/TableActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/table/TableActivity.java @@ -1,17 +1,22 @@ package io.noties.markwon.sample.table; +import android.graphics.Color; import android.os.Bundle; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import io.noties.debug.Debug; import io.noties.markwon.Markwon; import io.noties.markwon.ext.tables.TablePlugin; +import io.noties.markwon.ext.tables.TableTheme; import io.noties.markwon.linkify.LinkifyPlugin; import io.noties.markwon.sample.ActivityWithMenuOptions; import io.noties.markwon.sample.MenuOptions; import io.noties.markwon.sample.R; +import io.noties.markwon.utils.ColorUtils; +import io.noties.markwon.utils.Dip; public class TableActivity extends ActivityWithMenuOptions { @@ -19,6 +24,7 @@ public class TableActivity extends ActivityWithMenuOptions { @Override public MenuOptions menuOptions() { return MenuOptions.create() + .add("customize", this::customize) .add("tableAndLinkify", this::tableAndLinkify); } @@ -33,6 +39,32 @@ public class TableActivity extends ActivityWithMenuOptions { tableAndLinkify(); } + private void customize() { + final String md = "" + + "| HEADER | HEADER | HEADER |\n" + + "|:----:|:----:|:----:|\n" + + "| 测试 | 测试 | 测试 |\n" + + "| 测试 | 测试 | 测测测12345试测试测试 |\n" + + "| 测试 | 测试 | 123445 |\n" + + "| 测试 | 测试 | (650) 555-1212 |\n" + + "| 测试 | 测试 | [link](#) |\n"; + + final Markwon markwon = Markwon.builder(this) + .usePlugin(TablePlugin.create(builder -> { + final Dip dip = Dip.create(this); + builder + .tableBorderWidth(dip.toPx(2)) + .tableBorderColor(Color.YELLOW) + .tableCellPadding(dip.toPx(4)) + .tableHeaderRowBackgroundColor(ColorUtils.applyAlpha(Color.RED, 80)) + .tableEvenRowBackgroundColor(ColorUtils.applyAlpha(Color.GREEN, 80)) + .tableOddRowBackgroundColor(ColorUtils.applyAlpha(Color.BLUE, 80)); + })) + .build(); + + markwon.setMarkdown(textView, md); + } + private void tableAndLinkify() { final String md = "" + "| HEADER | HEADER | HEADER |\n" + @@ -45,7 +77,7 @@ public class TableActivity extends ActivityWithMenuOptions { "\n" + "测试\n" + "\n" + - "[https://www.baidu.com](https://www.baidu.com)"; + "[link link](https://link.link)"; final Markwon markwon = Markwon.builder(this) .usePlugin(LinkifyPlugin.create()) From 0ae3a3d66e3e955b9ea93f9d03207950f97df17c Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Tue, 31 Mar 2020 23:41:10 +0300 Subject: [PATCH 13/14] Update changelog --- CHANGELOG.md | 2 ++ .../PrecomputedFutureTextSetterCompat.java | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31fc43d1..8dfd5358 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,12 @@ * module `images`: `GifSupport` and `SvgSupport` use `Class.forName` instead access to full qualified class name * `ext-table`: fix links in tables ([#224]) * `ext-table`: proper borders (equal for all sides) +* module `core`: Add `PrecomputedFutureTextSetterCompat`
Thanks [@KirkBushman] [#216]: https://github.com/noties/Markwon/pull/216 [#224]: https://github.com/noties/Markwon/issues/224 [@francescocervone]: https://github.com/francescocervone +[@KirkBushman]: https://github.com/KirkBushman # 4.3.0 diff --git a/markwon-core/src/main/java/io/noties/markwon/PrecomputedFutureTextSetterCompat.java b/markwon-core/src/main/java/io/noties/markwon/PrecomputedFutureTextSetterCompat.java index 7e164a20..e107341c 100644 --- a/markwon-core/src/main/java/io/noties/markwon/PrecomputedFutureTextSetterCompat.java +++ b/markwon-core/src/main/java/io/noties/markwon/PrecomputedFutureTextSetterCompat.java @@ -1,7 +1,6 @@ package io.noties.markwon; import android.text.Spanned; -import android.util.Log; import android.widget.TextView; import androidx.annotation.NonNull; @@ -10,12 +9,14 @@ import androidx.appcompat.widget.AppCompatTextView; import androidx.core.text.PrecomputedTextCompat; import java.util.concurrent.Executor; +import java.util.concurrent.Future; /** * Please note this class requires `androidx.core:core` artifact being explicitly added to your dependencies. * This is intended to be used in a RecyclerView. * * @see io.noties.markwon.Markwon.TextSetter + * @since $nap; */ public class PrecomputedFutureTextSetterCompat implements Markwon.TextSetter { @@ -47,16 +48,18 @@ public class PrecomputedFutureTextSetterCompat implements Markwon.TextSetter { @NonNull Spanned markdown, @NonNull TextView.BufferType bufferType, @NonNull Runnable onComplete) { - if (textView instanceof AppCompatTextView) { - ((AppCompatTextView) textView).setTextFuture(PrecomputedTextCompat.getTextFuture( - markdown, ((AppCompatTextView) textView).getTextMetricsParamsCompat(), executor - )); - + final AppCompatTextView appCompatTextView = (AppCompatTextView) textView; + final Future future = PrecomputedTextCompat.getTextFuture( + markdown, + appCompatTextView.getTextMetricsParamsCompat(), + executor); + appCompatTextView.setTextFuture(future); + // `setTextFuture` is actually a synchronous call, so we should call onComplete now onComplete.run(); } else { - - throw new IllegalStateException("TextView provided is not a child of AppCompatTextView, cannot call setTextFuture()."); + throw new IllegalStateException("TextView provided is not an instance of AppCompatTextView, " + + "cannot call setTextFuture(), textView: " + textView); } } } From 0f968662a816e38669c221866a4d93a8bbd98c53 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Wed, 1 Apr 2020 09:48:37 +0300 Subject: [PATCH 14/14] Prepare 4.3.1 release --- CHANGELOG.md | 2 +- gradle.properties | 2 +- .../noties/markwon/PrecomputedFutureTextSetterCompat.java | 2 +- .../main/java/io/noties/markwon/ext/tables/TablePlugin.java | 2 +- .../java/io/noties/markwon/ext/tables/TableRowSpan.java | 6 +++--- .../main/java/io/noties/markwon/ext/tables/TableSpan.java | 2 +- .../main/java/io/noties/markwon/ext/tables/TableTheme.java | 2 +- .../main/java/io/noties/markwon/html/tag/StrikeHandler.java | 2 +- .../main/java/io/noties/markwon/image/gif/GifSupport.java | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dfd5358..886bd169 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -# $nap; +# 4.3.1 * Fix DexGuard optimization issue ([#216])
Thanks [@francescocervone] * module `images`: `GifSupport` and `SvgSupport` use `Class.forName` instead access to full qualified class name * `ext-table`: fix links in tables ([#224]) diff --git a/gradle.properties b/gradle.properties index ca3e59a6..0e222c26 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ android.enableJetifier=true android.enableBuildCache=true android.buildCacheDir=build/pre-dex-cache -VERSION_NAME=4.3.1-SNAPSHOT +VERSION_NAME=4.3.1 GROUP=io.noties.markwon POM_DESCRIPTION=Markwon markdown for Android diff --git a/markwon-core/src/main/java/io/noties/markwon/PrecomputedFutureTextSetterCompat.java b/markwon-core/src/main/java/io/noties/markwon/PrecomputedFutureTextSetterCompat.java index e107341c..f4601286 100644 --- a/markwon-core/src/main/java/io/noties/markwon/PrecomputedFutureTextSetterCompat.java +++ b/markwon-core/src/main/java/io/noties/markwon/PrecomputedFutureTextSetterCompat.java @@ -16,7 +16,7 @@ import java.util.concurrent.Future; * This is intended to be used in a RecyclerView. * * @see io.noties.markwon.Markwon.TextSetter - * @since $nap; + * @since 4.3.1 */ public class PrecomputedFutureTextSetterCompat implements Markwon.TextSetter { diff --git a/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TablePlugin.java b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TablePlugin.java index 8e624e01..d5d0f74f 100644 --- a/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TablePlugin.java +++ b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TablePlugin.java @@ -127,7 +127,7 @@ public class TablePlugin extends AbstractMarkwonPlugin { visitor.visitChildren(tableBlock); - // @since $nap; apply table span for the full table + // @since 4.3.1 apply table span for the full table visitor.setSpans(length, new TableSpan()); visitor.blockEnd(tableBlock); diff --git a/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableRowSpan.java b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableRowSpan.java index f6653cf8..0248c4ef 100644 --- a/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableRowSpan.java +++ b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableRowSpan.java @@ -146,7 +146,7 @@ public class TableRowSpan extends ReplacementSpan { if (recreateLayouts(canvas.getWidth())) { width = canvas.getWidth(); - // @since $nap; it's important to cast to TextPaint in order to display links, etc + // @since 4.3.1 it's important to cast to TextPaint in order to display links, etc if (p instanceof TextPaint) { // there must be a reason why this method receives Paint instead of TextPaint... textPaint.set((TextPaint) p); @@ -204,7 +204,7 @@ public class TableRowSpan extends ReplacementSpan { // required for borderTop calculation final boolean isFirstTableRow; - // @since $nap; + // @since 4.3.1 if (drawBorder) { boolean first = false; // only if first draw the line @@ -244,7 +244,7 @@ public class TableRowSpan extends ReplacementSpan { canvas.translate(x + (i * w), top); - // @since $nap; + // @since 4.3.1 if (drawBorder) { // first vertical border will have full width (it cannot exceed canvas) if (i == 0) { diff --git a/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableSpan.java b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableSpan.java index bba40d76..4f7f1aee 100644 --- a/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableSpan.java +++ b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableSpan.java @@ -1,7 +1,7 @@ package io.noties.markwon.ext.tables; /** - * @since $nap; + * @since 4.3.1 */ public class TableSpan { } diff --git a/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableTheme.java b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableTheme.java index 48655220..1f1c4dd1 100644 --- a/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableTheme.java +++ b/markwon-ext-tables/src/main/java/io/noties/markwon/ext/tables/TableTheme.java @@ -102,7 +102,7 @@ public class TableTheme { } paint.setColor(color); - // @since $nap; before it was STROKE... change to FILL as we draw border differently + // @since 4.3.1 before it was STROKE... change to FILL as we draw border differently paint.setStyle(Paint.Style.FILL); } diff --git a/markwon-html/src/main/java/io/noties/markwon/html/tag/StrikeHandler.java b/markwon-html/src/main/java/io/noties/markwon/html/tag/StrikeHandler.java index 37b3168d..03a0e952 100644 --- a/markwon-html/src/main/java/io/noties/markwon/html/tag/StrikeHandler.java +++ b/markwon-html/src/main/java/io/noties/markwon/html/tag/StrikeHandler.java @@ -25,7 +25,7 @@ public class StrikeHandler extends TagHandler { static { boolean hasMarkdownImplementation; try { - // @since $nap; we class Class.forName instead of trying + // @since 4.3.1 we class Class.forName instead of trying // to access the class by full qualified name (which caused issues with DexGuard) Class.forName("org.commonmark.ext.gfm.strikethrough.Strikethrough"); hasMarkdownImplementation = true; diff --git a/markwon-image/src/main/java/io/noties/markwon/image/gif/GifSupport.java b/markwon-image/src/main/java/io/noties/markwon/image/gif/GifSupport.java index b5c68d44..2e9406ba 100644 --- a/markwon-image/src/main/java/io/noties/markwon/image/gif/GifSupport.java +++ b/markwon-image/src/main/java/io/noties/markwon/image/gif/GifSupport.java @@ -14,7 +14,7 @@ public abstract class GifSupport { static { boolean result; try { - // @since $nap; + // @since 4.3.1 Class.forName("pl.droidsonroids.gif.GifDrawable"); result = true; } catch (Throwable t) {