Add fallbackToRawInputWhenEmpty configuration

This commit is contained in:
Dimitry Ivanov 2020-05-12 14:41:24 +03:00
parent c450765ab4
commit d42ae41409
5 changed files with 116 additions and 14 deletions

View File

@ -10,10 +10,12 @@
* Update `jlatexmath-android` dependency ([#225]) * Update `jlatexmath-android` dependency ([#225])
* Update `image-coil` module (Coil version `0.10.1`) ([#244])<br>Thanks to [@tylerbwong] * Update `image-coil` module (Coil version `0.10.1`) ([#244])<br>Thanks to [@tylerbwong]
* Rename `UrlProcessor` to `ImageDestinationProcessor` (`io.noties.markwon.urlprocessor` -&gt; `io.noties.markwon.image.destination`) and limit its usage to process **only** destination URL of images (was used to also process links before) * Rename `UrlProcessor` to `ImageDestinationProcessor` (`io.noties.markwon.urlprocessor` -&gt; `io.noties.markwon.image.destination`) and limit its usage to process **only** destination URL of images (was used to also process links before)
* `fallbackToRawInputWhenEmpty` `Markwon.Builder` configuration to fallback to raw input if rendered markdown is empty ([#242])
[#235]: https://github.com/noties/Markwon/issues/235 [#235]: https://github.com/noties/Markwon/issues/235
[#225]: https://github.com/noties/Markwon/issues/225 [#225]: https://github.com/noties/Markwon/issues/225
[#244]: https://github.com/noties/Markwon/pull/244 [#244]: https://github.com/noties/Markwon/pull/244
[#242]: https://github.com/noties/Markwon/issues/242
[@tylerbwong]: https://github.com/tylerbwong [@tylerbwong]: https://github.com/tylerbwong

View File

@ -192,6 +192,17 @@ public abstract class Markwon {
@NonNull @NonNull
Builder usePlugins(@NonNull Iterable<? extends MarkwonPlugin> plugins); Builder usePlugins(@NonNull Iterable<? extends MarkwonPlugin> plugins);
/**
* Control if small chunks of non-finished markdown sentences (for example, a single `*` character)
* should be displayed/rendered as raw input instead of an empty string.
* <p>
* Since $nap; {@code true} by default, versions prior - {@code false}
*
* @since $nap;
*/
@NonNull
Builder fallbackToRawInputWhenEmpty(boolean fallbackToRawInputWhenEmpty);
@NonNull @NonNull
Markwon build(); Markwon build();
} }

View File

@ -27,6 +27,9 @@ class MarkwonBuilderImpl implements Markwon.Builder {
private Markwon.TextSetter textSetter; private Markwon.TextSetter textSetter;
// @since $nap;
private boolean fallbackToRawInputWhenEmpty = true;
MarkwonBuilderImpl(@NonNull Context context) { MarkwonBuilderImpl(@NonNull Context context) {
this.context = context; this.context = context;
} }
@ -71,6 +74,13 @@ class MarkwonBuilderImpl implements Markwon.Builder {
return this; return this;
} }
@NonNull
@Override
public Markwon.Builder fallbackToRawInputWhenEmpty(boolean fallbackToRawInputWhenEmpty) {
this.fallbackToRawInputWhenEmpty = fallbackToRawInputWhenEmpty;
return this;
}
@NonNull @NonNull
@Override @Override
public Markwon build() { public Markwon build() {
@ -114,7 +124,8 @@ class MarkwonBuilderImpl implements Markwon.Builder {
parserBuilder.build(), parserBuilder.build(),
visitorFactory, visitorFactory,
configuration, configuration,
Collections.unmodifiableList(plugins) Collections.unmodifiableList(plugins),
fallbackToRawInputWhenEmpty
); );
} }

View File

@ -1,6 +1,8 @@
package io.noties.markwon; package io.noties.markwon;
import android.text.SpannableStringBuilder;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextUtils;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -28,19 +30,25 @@ class MarkwonImpl extends Markwon {
@Nullable @Nullable
private final TextSetter textSetter; private final TextSetter textSetter;
// @since $nap;
private final boolean fallbackToRawInputWhenEmpty;
MarkwonImpl( MarkwonImpl(
@NonNull TextView.BufferType bufferType, @NonNull TextView.BufferType bufferType,
@Nullable TextSetter textSetter, @Nullable TextSetter textSetter,
@NonNull Parser parser, @NonNull Parser parser,
@NonNull MarkwonVisitorFactory visitorFactory, @NonNull MarkwonVisitorFactory visitorFactory,
@NonNull MarkwonConfiguration configuration, @NonNull MarkwonConfiguration configuration,
@NonNull List<MarkwonPlugin> plugins) { @NonNull List<MarkwonPlugin> plugins,
boolean fallbackToRawInputWhenEmpty
) {
this.bufferType = bufferType; this.bufferType = bufferType;
this.textSetter = textSetter; this.textSetter = textSetter;
this.parser = parser; this.parser = parser;
this.visitorFactory = visitorFactory; this.visitorFactory = visitorFactory;
this.configuration = configuration; this.configuration = configuration;
this.plugins = plugins; this.plugins = plugins;
this.fallbackToRawInputWhenEmpty = fallbackToRawInputWhenEmpty;
} }
@NonNull @NonNull
@ -86,7 +94,18 @@ class MarkwonImpl extends Markwon {
@NonNull @NonNull
@Override @Override
public Spanned toMarkdown(@NonNull String input) { public Spanned toMarkdown(@NonNull String input) {
return render(parse(input)); final Spanned spanned = render(parse(input));
// @since $nap;
// if spanned is empty, we are configured to use raw input and input is not empty
if (TextUtils.isEmpty(spanned)
&& fallbackToRawInputWhenEmpty
&& !TextUtils.isEmpty(input)) {
// let's use SpannableStringBuilder in order to keep backward-compatibility
return new SpannableStringBuilder(input);
}
return spanned;
} }
@Override @Override

View File

@ -1,6 +1,7 @@
package io.noties.markwon; package io.noties.markwon;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextUtils;
import android.widget.TextView; import android.widget.TextView;
import org.commonmark.node.Node; import org.commonmark.node.Node;
@ -50,7 +51,9 @@ public class MarkwonImplTest {
mock(Parser.class), mock(Parser.class),
mock(MarkwonVisitorFactory.class), mock(MarkwonVisitorFactory.class),
mock(MarkwonConfiguration.class), mock(MarkwonConfiguration.class),
Collections.singletonList(plugin)); Collections.singletonList(plugin),
true
);
impl.parse("whatever"); impl.parse("whatever");
@ -74,7 +77,9 @@ public class MarkwonImplTest {
parser, parser,
mock(MarkwonVisitorFactory.class), mock(MarkwonVisitorFactory.class),
mock(MarkwonConfiguration.class), mock(MarkwonConfiguration.class),
Arrays.asList(first, second)); Arrays.asList(first, second),
true
);
impl.parse("zero"); impl.parse("zero");
@ -102,7 +107,9 @@ public class MarkwonImplTest {
mock(Parser.class), mock(Parser.class),
visitorFactory, visitorFactory,
mock(MarkwonConfiguration.class), mock(MarkwonConfiguration.class),
Collections.singletonList(plugin)); Collections.singletonList(plugin),
true
);
when(visitorFactory.create()).thenReturn(visitor); when(visitorFactory.create()).thenReturn(visitor);
when(visitor.builder()).thenReturn(builder); when(visitor.builder()).thenReturn(builder);
@ -149,7 +156,9 @@ public class MarkwonImplTest {
mock(Parser.class), mock(Parser.class),
visitorFactory, visitorFactory,
mock(MarkwonConfiguration.class), mock(MarkwonConfiguration.class),
Collections.<MarkwonPlugin>emptyList()); Collections.<MarkwonPlugin>emptyList(),
true
);
impl.render(mock(Node.class)); impl.render(mock(Node.class));
@ -185,7 +194,9 @@ public class MarkwonImplTest {
mock(Parser.class), mock(Parser.class),
visitorFactory, visitorFactory,
mock(MarkwonConfiguration.class), mock(MarkwonConfiguration.class),
Collections.singletonList(plugin)); Collections.singletonList(plugin),
true
);
final AtomicBoolean flag = new AtomicBoolean(false); final AtomicBoolean flag = new AtomicBoolean(false);
final Node node = mock(Node.class); final Node node = mock(Node.class);
@ -224,7 +235,9 @@ public class MarkwonImplTest {
mock(Parser.class), mock(Parser.class),
mock(MarkwonVisitorFactory.class, RETURNS_MOCKS), mock(MarkwonVisitorFactory.class, RETURNS_MOCKS),
mock(MarkwonConfiguration.class), mock(MarkwonConfiguration.class),
Collections.singletonList(plugin)); Collections.singletonList(plugin),
true
);
final TextView textView = mock(TextView.class); final TextView textView = mock(TextView.class);
final AtomicBoolean flag = new AtomicBoolean(false); final AtomicBoolean flag = new AtomicBoolean(false);
@ -272,7 +285,9 @@ public class MarkwonImplTest {
mock(Parser.class), mock(Parser.class),
mock(MarkwonVisitorFactory.class), mock(MarkwonVisitorFactory.class),
mock(MarkwonConfiguration.class), mock(MarkwonConfiguration.class),
plugins); plugins,
true
);
assertTrue("First", impl.hasPlugin(First.class)); assertTrue("First", impl.hasPlugin(First.class));
assertFalse("Second", impl.hasPlugin(Second.class)); assertFalse("Second", impl.hasPlugin(Second.class));
@ -295,7 +310,9 @@ public class MarkwonImplTest {
mock(Parser.class), mock(Parser.class),
mock(MarkwonVisitorFactory.class), mock(MarkwonVisitorFactory.class),
mock(MarkwonConfiguration.class), mock(MarkwonConfiguration.class),
Collections.singletonList(plugin)); Collections.singletonList(plugin),
true
);
final TextView textView = mock(TextView.class); final TextView textView = mock(TextView.class);
final Spanned spanned = mock(Spanned.class); final Spanned spanned = mock(Spanned.class);
@ -339,7 +356,9 @@ public class MarkwonImplTest {
mock(Parser.class), mock(Parser.class),
mock(MarkwonVisitorFactory.class), mock(MarkwonVisitorFactory.class),
mock(MarkwonConfiguration.class), mock(MarkwonConfiguration.class),
plugins); plugins,
true
);
// should be returned // should be returned
assertNotNull(impl.requirePlugin(MarkwonPlugin.class)); assertNotNull(impl.requirePlugin(MarkwonPlugin.class));
@ -370,7 +389,9 @@ public class MarkwonImplTest {
mock(Parser.class), mock(Parser.class),
mock(MarkwonVisitorFactory.class), mock(MarkwonVisitorFactory.class),
mock(MarkwonConfiguration.class), mock(MarkwonConfiguration.class),
plugins); plugins,
true
);
final List<? extends MarkwonPlugin> list = impl.getPlugins(); final List<? extends MarkwonPlugin> list = impl.getPlugins();
@ -385,4 +406,42 @@ public class MarkwonImplTest {
assertTrue(e.getMessage(), true); assertTrue(e.getMessage(), true);
} }
} }
@Test
public void fallback_to_raw() {
final String md = "*";
final MarkwonImpl impl = new MarkwonImpl(
TextView.BufferType.SPANNABLE,
null,
mock(Parser.class, RETURNS_MOCKS),
// it must be sufficient to just return mocks and thus empty rendering result
mock(MarkwonVisitorFactory.class, RETURNS_MOCKS),
mock(MarkwonConfiguration.class),
Collections.<MarkwonPlugin>emptyList(),
true
);
final Spanned spanned = impl.toMarkdown(md);
assertEquals(md, spanned.toString());
}
@Test
public void fallback_to_raw_false() {
final String md = "*";
final MarkwonImpl impl = new MarkwonImpl(
TextView.BufferType.SPANNABLE,
null,
mock(Parser.class, RETURNS_MOCKS),
// it must be sufficient to just return mocks and thus empty rendering result
mock(MarkwonVisitorFactory.class, RETURNS_MOCKS),
mock(MarkwonConfiguration.class),
Collections.<MarkwonPlugin>emptyList(),
false
);
final Spanned spanned = impl.toMarkdown(md);
assertTrue(spanned.toString(), TextUtils.isEmpty(spanned));
}
} }