From 976dfa3162dbb33f920328bfb08a6f418e588abc Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Fri, 14 Feb 2020 12:53:35 +0300 Subject: [PATCH] MarkwonSpansFactory append-prepend methods --- CHANGELOG.md | 2 + docs/docs/v4/core/spans-factory.md | 23 +++++- .../noties/markwon/MarkwonSpansFactory.java | 24 +++++++ .../markwon/MarkwonSpansFactoryImpl.java | 25 +++++++ .../markwon/MarkwonSpansFactoryImplTest.java | 71 +++++++++++++++++++ .../noties/markwon/core/CorePluginTest.java | 12 ++++ .../sample/recycler/RecyclerActivity.java | 22 +++++- 7 files changed, 175 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8cc8ed5..6c719a9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,11 @@ # 4.2.2-SNAPSHOT * Fixed `AsyncDrawable` display when it has placeholder with empty bounds ([#189]) * Fixed `syntax-highlight` where code input is empty string ([#192]) +* Add `appendFactory`/`prependFactory` in `MarkwonSpansFactory.Builder` for more explicit `SpanFactory` ordering ([#193]) [#189]: https://github.com/noties/Markwon/issues/189 [#192]: https://github.com/noties/Markwon/issues/192 +[#193]: https://github.com/noties/Markwon/issues/193 # 4.2.1 * Fix SpannableBuilder `subSequence` method diff --git a/docs/docs/v4/core/spans-factory.md b/docs/docs/v4/core/spans-factory.md index 9567a788..e0b11aa1 100644 --- a/docs/docs/v4/core/spans-factory.md +++ b/docs/docs/v4/core/spans-factory.md @@ -62,7 +62,12 @@ builder.setFactory(Link.class, new SpanFactory() { --- -Since you can _add_ multiple `SpanFactory` for a single node: +:::warning +Deprecated in . Use `appendFactory` or `prependFactory` for +more explicit factories ordering. `addFactories` behaves like new `prependFactory` method. +::: + +Since you can _add_ multiple `SpanFactory` for a single node: ```java final Markwon markwon = Markwon.builder(context) @@ -81,6 +86,22 @@ final Markwon markwon = Markwon.builder(context) .build(); ``` +## appendFactory/prependFactory + +* use `appendFactory` if you wish to add a factory **after** original (can be used to post-process original factory) +* use `prependFactory` if you wish to add a factory **before** original (original factory will be applied after this one) + +```java +@Override +public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + // `RemoveUnderlineSpan` will be added AFTER original, thus it will remove underline applied by original + builder.appendFactory(Link.class, (configuration, props) -> new RemoveUnderlineSpan()); + + // will be added BEFORE origin (origin can override this) + builder.prependFactory(Link.class, (configuration, props) -> new AbsoluteSizeSpan(48, true)); +} +``` + --- If you wish to inspect existing factory you can use: diff --git a/markwon-core/src/main/java/io/noties/markwon/MarkwonSpansFactory.java b/markwon-core/src/main/java/io/noties/markwon/MarkwonSpansFactory.java index c4153075..1afb7212 100644 --- a/markwon-core/src/main/java/io/noties/markwon/MarkwonSpansFactory.java +++ b/markwon-core/src/main/java/io/noties/markwon/MarkwonSpansFactory.java @@ -39,10 +39,34 @@ public interface MarkwonSpansFactory { * {@link SpanFactory} with the specified one. * * @since 3.0.1 + * @deprecated 4.2.2-SNAPSHOT consider using {@link #appendFactory(Class, SpanFactory)} or + * {@link #prependFactory(Class, SpanFactory)} methods for more explicit factory ordering. + * `addFactory` behaved like {@link #prependFactory(Class, SpanFactory)}, so + * this method call can be replaced with it */ @NonNull + @Deprecated Builder addFactory(@NonNull Class node, @NonNull SpanFactory factory); + /** + * Append a factory to existing one (or make the first one for specified node). Specified factory + * will be called after original (if present) factory. Can be used to + * change behavior or original span factory. + * + * @since 4.2.2-SNAPSHOT + */ + @NonNull + Builder appendFactory(@NonNull Class node, @NonNull SpanFactory factory); + + /** + * Prepend a factory to existing one (or make the first one for specified node). Specified factory + * will be called before original (if present) factory. + * + * @since 4.2.2-SNAPSHOT + */ + @NonNull + Builder prependFactory(@NonNull Class node, @NonNull SpanFactory factory); + /** * Can be useful when enhancing an already defined SpanFactory with another one. */ diff --git a/markwon-core/src/main/java/io/noties/markwon/MarkwonSpansFactoryImpl.java b/markwon-core/src/main/java/io/noties/markwon/MarkwonSpansFactoryImpl.java index ab2c1f84..fd7a99d5 100644 --- a/markwon-core/src/main/java/io/noties/markwon/MarkwonSpansFactoryImpl.java +++ b/markwon-core/src/main/java/io/noties/markwon/MarkwonSpansFactoryImpl.java @@ -56,7 +56,32 @@ class MarkwonSpansFactoryImpl implements MarkwonSpansFactory { @NonNull @Override + @Deprecated public Builder addFactory(@NonNull Class node, @NonNull SpanFactory factory) { + return prependFactory(node, factory); + } + + @NonNull + @Override + public Builder appendFactory(@NonNull Class node, @NonNull SpanFactory factory) { + final SpanFactory existing = factories.get(node); + if (existing == null) { + factories.put(node, factory); + } else { + if (existing instanceof CompositeSpanFactory) { + ((CompositeSpanFactory) existing).factories.add(0, factory); + } else { + final CompositeSpanFactory compositeSpanFactory = + new CompositeSpanFactory(factory, existing); + factories.put(node, compositeSpanFactory); + } + } + return this; + } + + @NonNull + @Override + public Builder prependFactory(@NonNull Class node, @NonNull SpanFactory factory) { // if there is no factory registered for this node -> just add it final SpanFactory existing = factories.get(node); if (existing == null) { diff --git a/markwon-core/src/test/java/io/noties/markwon/MarkwonSpansFactoryImplTest.java b/markwon-core/src/test/java/io/noties/markwon/MarkwonSpansFactoryImplTest.java index ad43e6e6..998ec152 100644 --- a/markwon-core/src/test/java/io/noties/markwon/MarkwonSpansFactoryImplTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/MarkwonSpansFactoryImplTest.java @@ -113,6 +113,7 @@ public class MarkwonSpansFactoryImplTest { } @Test + @Deprecated public void builder_add_factory() { // here is what we should validate: // * if we call addFactory and there is none already -> supplied factory @@ -146,4 +147,74 @@ public class MarkwonSpansFactoryImplTest { assertEquals(compositeSpanFactory, builder.getFactory(node)); assertEquals(Arrays.asList(first, second, third), compositeSpanFactory.factories); } + + @Test + public void builder_prepend_factory() { + // here is what we should validate: + // * if we call prependFactory and there is none already -> supplied factory + // * if there is + // * * if not composite -> make composite + // * * if composite -> add to it + + final MarkwonSpansFactoryImpl.BuilderImpl builder = new MarkwonSpansFactoryImpl.BuilderImpl(); + + final SpanFactory first = mock(SpanFactory.class); + final SpanFactory second = mock(SpanFactory.class); + final SpanFactory third = mock(SpanFactory.class); + + final Class node = Node.class; + + // assert none yet + assertNull(builder.getFactory(node)); + + // add first, none yet -> it should be added without modifications + builder.prependFactory(node, first); + assertEquals(first, builder.getFactory(node)); + + // add second -> composite factory will be created + builder.prependFactory(node, second); + final MarkwonSpansFactoryImpl.CompositeSpanFactory compositeSpanFactory = + (MarkwonSpansFactoryImpl.CompositeSpanFactory) builder.getFactory(node); + assertNotNull(compositeSpanFactory); + assertEquals(Arrays.asList(first, second), compositeSpanFactory.factories); + + builder.prependFactory(node, third); + assertEquals(compositeSpanFactory, builder.getFactory(node)); + assertEquals(Arrays.asList(first, second, third), compositeSpanFactory.factories); + } + + @Test + public void builder_append_factory() { + // here is what we should validate: + // * if we call appendFactory and there is none already -> supplied factory + // * if there is + // * * if not composite -> make composite + // * * if composite -> add to it + + final MarkwonSpansFactoryImpl.BuilderImpl builder = new MarkwonSpansFactoryImpl.BuilderImpl(); + + final SpanFactory first = mock(SpanFactory.class); + final SpanFactory second = mock(SpanFactory.class); + final SpanFactory third = mock(SpanFactory.class); + + final Class node = Node.class; + + // assert none yet + assertNull(builder.getFactory(node)); + + // add first, none yet -> it should be added without modifications + builder.appendFactory(node, first); + assertEquals(first, builder.getFactory(node)); + + // add second -> composite factory will be created + builder.appendFactory(node, second); + final MarkwonSpansFactoryImpl.CompositeSpanFactory compositeSpanFactory = + (MarkwonSpansFactoryImpl.CompositeSpanFactory) builder.getFactory(node); + assertNotNull(compositeSpanFactory); + assertEquals(Arrays.asList(second, first), compositeSpanFactory.factories); + + builder.appendFactory(node, third); + assertEquals(compositeSpanFactory, builder.getFactory(node)); + assertEquals(Arrays.asList(third, second, first), compositeSpanFactory.factories); + } } \ No newline at end of file diff --git a/markwon-core/src/test/java/io/noties/markwon/core/CorePluginTest.java b/markwon-core/src/test/java/io/noties/markwon/core/CorePluginTest.java index 4137b83e..9cb95028 100644 --- a/markwon-core/src/test/java/io/noties/markwon/core/CorePluginTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/core/CorePluginTest.java @@ -166,6 +166,18 @@ public class CorePluginTest { throw new RuntimeException(); } + @NonNull + @Override + public MarkwonSpansFactory.Builder appendFactory(@NonNull Class node, @NonNull SpanFactory factory) { + throw new RuntimeException(); + } + + @NonNull + @Override + public MarkwonSpansFactory.Builder prependFactory(@NonNull Class node, @NonNull SpanFactory factory) { + throw new RuntimeException(); + } + @Nullable @Override public SpanFactory getFactory(@NonNull Class node) { diff --git a/sample/src/main/java/io/noties/markwon/sample/recycler/RecyclerActivity.java b/sample/src/main/java/io/noties/markwon/sample/recycler/RecyclerActivity.java index 3d86c683..84034ce9 100644 --- a/sample/src/main/java/io/noties/markwon/sample/recycler/RecyclerActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/recycler/RecyclerActivity.java @@ -3,10 +3,12 @@ package io.noties.markwon.sample.recycler; import android.app.Activity; import android.content.Context; import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; +import android.text.TextPaint; import android.text.TextUtils; +import android.text.style.CharacterStyle; +import android.text.style.UpdateAppearance; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -15,6 +17,7 @@ import androidx.recyclerview.widget.RecyclerView; import org.commonmark.ext.gfm.tables.TableBlock; import org.commonmark.node.FencedCodeBlock; +import org.commonmark.node.Link; import java.io.BufferedReader; import java.io.IOException; @@ -26,14 +29,13 @@ import io.noties.debug.Debug; import io.noties.markwon.AbstractMarkwonPlugin; import io.noties.markwon.Markwon; import io.noties.markwon.MarkwonConfiguration; +import io.noties.markwon.MarkwonSpansFactory; import io.noties.markwon.MarkwonVisitor; import io.noties.markwon.core.CorePlugin; import io.noties.markwon.html.HtmlPlugin; -import io.noties.markwon.image.AsyncDrawable; import io.noties.markwon.image.ImagesPlugin; import io.noties.markwon.image.file.FileSchemeHandler; import io.noties.markwon.image.network.OkHttpNetworkSchemeHandler; -import io.noties.markwon.image.picasso.PicassoImagesPlugin; import io.noties.markwon.image.svg.SvgMediaDecoder; import io.noties.markwon.recycler.MarkwonAdapter; import io.noties.markwon.recycler.SimpleEntry; @@ -114,10 +116,24 @@ public class RecyclerActivity extends Activity { visitor.builder().append(code); }); } + + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + // `RemoveUnderlineSpan` will be added AFTER original, thus it will remove underline applied by original + builder.appendFactory(Link.class, (configuration, props) -> new RemoveUnderlineSpan()); + } }) .build(); } + private static class RemoveUnderlineSpan extends CharacterStyle implements UpdateAppearance { + + @Override + public void updateDrawState(TextPaint tp) { + tp.setUnderlineText(false); + } + } + @NonNull private static String loadReadMe(@NonNull Context context) { InputStream stream = null;