Merge pull request #162 from noties/develop

* `markwon-ext-tables`: fix padding between subsequent table blocks ([#159])
* `markwon-images`: print a single warning instead full stacktrace in case when SVG or GIF 
are not present in the classpath ([#160])
* Make `Markwon` instance thread-safe by using a single `MarkwonVisitor` for each `render` call ([#157])
* Add `CoreProps.CODE_BLOCK_INFO` with code-block info (language)

[#159]: https://github.com/noties/Markwon/issues/159
[#160]: https://github.com/noties/Markwon/issues/160
[#157]: https://github.com/noties/Markwon/issues/157
This commit is contained in:
Dimitry 2019-08-29 14:30:12 +03:00 committed by GitHub
commit 4348555b75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 349 additions and 60 deletions

20
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,20 @@
name: Release
on:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build with Gradle
run: ./gradlew build

20
.github/workflows/snapshot.yml vendored Normal file
View File

@ -0,0 +1,20 @@
name: Snapshot
on:
push:
branches:
- develop
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build with Gradle
run: ./gradlew build

View File

@ -1,5 +1,16 @@
# Changelog
# 4.1.1
* `markwon-ext-tables`: fix padding between subsequent table blocks ([#159])
* `markwon-images`: print a single warning instead full stacktrace in case when SVG or GIF
are not present in the classpath ([#160])
* Make `Markwon` instance thread-safe by using a single `MarkwonVisitor` for each `render` call ([#157])
* Add `CoreProps.CODE_BLOCK_INFO` with code-block info (language)
[#159]: https://github.com/noties/Markwon/issues/159
[#160]: https://github.com/noties/Markwon/issues/160
[#157]: https://github.com/noties/Markwon/issues/157
# 4.1.0
* Add `Markwon.TextSetter` interface to be able to use PrecomputedText/PrecomputedTextCompat
* Add `PrecomputedTextSetterCompat` and `compileOnly` dependency on `androidx.core:core`

View File

@ -4,7 +4,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.1'
classpath 'com.android.tools.build:gradle:3.5.0'
classpath 'com.github.ben-manes:gradle-versions-plugin:0.21.0'
}
}
@ -30,7 +30,7 @@ task clean(type: Delete) {
}
wrapper {
gradleVersion '5.1.1'
gradleVersion '5.5.1'
distributionType 'all'
}

View File

@ -1,7 +1,7 @@
<template>
<div class="warning custom-block">
<p class="custom-block-title">WARNING</p>
<p>This is documentation for <u>legacy 2.x.x</u> versions. For the most current version <a :href="$withBase('/')">click here.</a></p>
<p>This is documentation for <u>legacy</u> versions. For the most current version <a :href="$withBase('/')">click here.</a></p>
</div>
</template>

View File

@ -16,9 +16,17 @@ module.exports = {
{
text: 'API Version',
items: [
{ text: 'Current (4.x.x)', link: '/' },
{ text: 'Legacy (3.x.x)', link: '/docs/v3/install.md' },
{ text: 'Legacy (2.x.x)', link: '/docs/v2/' }
{
text: 'Latest', items: [
{ text: '4.x.x', link: '/' },
]
},
{
text: 'Legacy', items: [
{ text: '3.x.x', link: '/docs/v3/install.md' },
{ text: '2.x.x', link: '/docs/v2/' }
]
}
]
},
{ text: 'Changelog', link: 'https://github.com/noties/Markwon/blob/master/CHANGELOG.md' },
@ -83,7 +91,8 @@ module.exports = {
'/docs/v4/core/spans-factory.md',
'/docs/v4/core/core-plugin.md',
'/docs/v4/core/movement-method-plugin.md',
'/docs/v4/core/render-props.md'
'/docs/v4/core/render-props.md',
'/docs/v4/core/text-setter.md'
]
},
'/docs/v4/ext-latex/',

View File

@ -1,5 +1,7 @@
# Configuration
<LegacyWarning />
`MarkwonConfiguration` class holds common Markwon functionality.
These are _configurable_ properties:
* `SyntaxHighlight`

View File

@ -1,5 +1,7 @@
# Core plugin <Badge text="3.0.0" />
<LegacyWarning />
Since <Badge text="3.0.0" /> with introduction of _plugins_, Markwon
**core** functionality was moved to a dedicated plugin.

View File

@ -1,5 +1,7 @@
# Getting started
<LegacyWarning />
:::tip Installation
Please follow [installation](/docs/v3/install.md) instructions
to learn how to add `Markwon` to your project

View File

@ -1,5 +1,7 @@
# HTML Renderer
<LegacyWarning />
Starting with <Badge text="3.0.0" /> `MarkwonHtmlRenderer` controls how HTML
is rendered:

View File

@ -1,5 +1,7 @@
# Images
<LegacyWarning />
Starting with <Badge text="3.0.0" /> `Markwon` comes with `ImagesPlugin`
which supports `http(s)`, `file` and `data` schemes and default media
decoder (for simple images, no [SVG](/docs/v3/image/svg.md) or [GIF](/docs/v3/image/gif.md) which

View File

@ -1,5 +1,7 @@
# Movement method plugin
<LegacyWarning />
`MovementMethodPlugin` can be used to apply a `MovementMethod` to a TextView
(important if you have links inside your markdown). By default `CorePlugin`
will set a `LinkMovementMethod` on a TextView if one is missing. If you have

View File

@ -1,5 +1,7 @@
# Plugins <Badge text="3.0.0" />
<LegacyWarning />
Since <Badge text="3.0.0" /> `MarkwonPlugin` takes the key role in
processing and rendering markdown. Even **core** functionaly is abstracted
into a `CorePlugin`. So it's still possible to use `Markwon` with a completely

View File

@ -1,5 +1,7 @@
# RenderProps <Badge text="3.0.0" />
<LegacyWarning />
`RenderProps` encapsulates passing arguments from a node visitor to a node renderer.
Without hardcoding arguments into an API method calls.

View File

@ -1,5 +1,7 @@
# Spans Factory
<LegacyWarning />
Starting with <Badge text="3.0.0" /> `MarkwonSpansFactory` controls what spans are displayed
for markdown nodes.

View File

@ -1,5 +1,7 @@
# Theme
<LegacyWarning />
Here is the list of properties that can be configured via `MarkwonTheme.Builder` class.
:::tip

View File

@ -1,5 +1,7 @@
# Visitor
<LegacyWarning />
Starting with <Badge text="3.0.0" /> _visiting_ of parsed markdown
nodes does not require creating own instance of commonmark-java `Visitor`,
instead a composable/configurable `MarkwonVisitor` is used.

View File

@ -1,5 +1,7 @@
# LaTeX extension
<LegacyWarning />
<MavenBadge :artifact="'ext-latex'" />
This is an extension that will help you display LaTeX formulas in your markdown.

View File

@ -1,5 +1,7 @@
# Strikethrough extension
<LegacyWarning />
<MavenBadge :artifact="'ext-strikethrough'" />
This module adds `strikethrough` functionality to `Markwon` via `StrikethroughPlugin`:

View File

@ -1,5 +1,7 @@
# Tables extension
<LegacyWarning />
<MavenBadge :artifact="'ext-tables'" />
This extension adds support for GFM tables.

View File

@ -1,5 +1,7 @@
# Task list extension
<LegacyWarning />
<MavenBadge :artifact="'ext-tasklist'" />
Adds support for GFM (Github-flavored markdown) task-lists:

View File

@ -1,5 +1,7 @@
# HTML
<LegacyWarning />
This artifact encapsulates HTML parsing from the core artifact and provides
few predefined `TagHandlers`

View File

@ -1,5 +1,7 @@
# Image GIF
<LegacyWarning />
<MavenBadge :artifact="'image-gif'" />
Adds support for GIF images inside markdown.

View File

@ -1,5 +1,7 @@
# Image OkHttp
<LegacyWarning />
<MavenBadge :artifact="'image-okhttp'" />
Uses [okhttp library](https://github.com/square/okhttp) as the network transport fro images. Since <Badge text="3.0.0" />

View File

@ -1,5 +1,7 @@
# Image SVG
<LegacyWarning />
<MavenBadge :artifact="'image-svg'" />
Adds support for SVG images inside markdown.

View File

@ -3,6 +3,8 @@ prev: false
next: /docs/v3/core/getting-started.md
---
<LegacyWarning />
# Installation
![stable](https://img.shields.io/maven-central/v/ru.noties.markwon/core.svg?label=stable)

View File

@ -1,5 +1,7 @@
# Migration 2.x.x -> 3.x.x
<LegacyWarning />
* strikethrough moved to standalone module
* tables moved to standalone module
* core functionality of `AsyncDrawableLoader` moved to `core` module

View File

@ -1,5 +1,7 @@
# Recycler Table <Badge text="3.0.0" />
<LegacyWarning />
<MavenBadge :artifact="'recycler-table'" />
Artifact that provides [MarkwonAdapter.Entry](/docs/v3/recycler/) to render `TableBlock` inside

View File

@ -1,5 +1,7 @@
# Recycler <Badge text="3.0.0" />
<LegacyWarning />
<MavenBadge :artifact="'recycler'" />
This artifact allows displaying markdown in a set of Android widgets

View File

@ -1,5 +1,7 @@
# Syntax highlight
<LegacyWarning />
<MavenBadge :artifact="'syntax-highlight'" />
This is a simple module to add **syntax highlight** functionality to your markdown rendered with `Markwon` library. It is based on [Prism4j](https://github.com/noties/Prism4j) so lead there to understand how to configure `Prism4j` instance.

View File

@ -0,0 +1,40 @@
# TextSetter <Badge text="4.1.0" />
Since <Badge text="4.1.0" /> it is possible to control how text is applied to a `TextView`.
This is done via `Markwon.TextSetter` interface.
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(/**/)
.textSetter(PrecomputedTextSetterCompat.create(Executors.newCachedThreadPool()))
.build();
```
```java
public interface TextSetter {
/**
* @param textView TextView
* @param markdown prepared markdown
* @param bufferType BufferType specified when building {@link Markwon} instance
* via {@link Builder#bufferType(TextView.BufferType)}
* @param onComplete action to run when set-text is finished (required to call in order
* to execute {@link MarkwonPlugin#afterSetText(TextView)})
*/
void setText(
@NonNull TextView textView,
@NonNull Spanned markdown,
@NonNull TextView.BufferType bufferType,
@NonNull Runnable onComplete);
}
```
Primary target for this functionality is to use [PrecomputedText] and [PrecomputedTextCompat].
`Markwon` comes with `PrecomputedTextSetterCompat` implementation.
:::tip Note
Please note that `PrecomputedTextCompat` belongs to the `androidx.core:core` artifact. Make
sure that you have it in your project's dependencies (explicitly or implicitly)
:::
[PrecomputedText]: https://developer.android.com/reference/android/text/PrecomputedText
[PrecomputedTextCompat]: https://developer.android.com/reference/androidx/core/text/PrecomputedTextCompat

View File

@ -8,7 +8,7 @@ android.enableJetifier=true
android.enableBuildCache=true
android.buildCacheDir=build/pre-dex-cache
VERSION_NAME=4.1.0
VERSION_NAME=4.1.1
GROUP=io.noties.markwon
POM_DESCRIPTION=Markwon markdown for Android

View File

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

View File

@ -141,21 +141,21 @@ public abstract class Markwon {
* @see PrecomputedTextSetterCompat
* @since 4.1.0
*/
public interface TextSetter {
/**
* @param textView TextView
* @param markdown prepared markdown
* @param bufferType BufferType specified when building {@link Markwon} instance
* via {@link Builder#bufferType(TextView.BufferType)}
* @param onComplete action to run when set-text is finished (required to call in order
* to execute {@link MarkwonPlugin#afterSetText(TextView)})
*/
void setText(
@NonNull TextView textView,
@NonNull Spanned markdown,
@NonNull TextView.BufferType bufferType,
@NonNull Runnable onComplete);
}
public interface TextSetter {
/**
* @param textView TextView
* @param markdown prepared markdown
* @param bufferType BufferType specified when building {@link Markwon} instance
* via {@link Builder#bufferType(TextView.BufferType)}
* @param onComplete action to run when set-text is finished (required to call in order
* to execute {@link MarkwonPlugin#afterSetText(TextView)})
*/
void setText(
@NonNull TextView textView,
@NonNull Spanned markdown,
@NonNull TextView.BufferType bufferType,
@NonNull Runnable onComplete);
}
/**
* Builder for {@link Markwon}.

View File

@ -104,11 +104,17 @@ class MarkwonBuilderImpl implements Markwon.Builder {
final RenderProps renderProps = new RenderPropsImpl();
// @since 4.1.1
final MarkwonVisitorFactory visitorFactory = MarkwonVisitorFactory.create(
visitorBuilder,
configuration,
renderProps);
return new MarkwonImpl(
bufferType,
textSetter,
parserBuilder.build(),
visitorBuilder.build(configuration, renderProps),
visitorFactory,
Collections.unmodifiableList(plugins)
);
}

View File

@ -20,7 +20,7 @@ class MarkwonImpl extends Markwon {
private final TextView.BufferType bufferType;
private final Parser parser;
private final MarkwonVisitor visitor;
private final MarkwonVisitorFactory visitorFactory; // @since 4.1.1
private final List<MarkwonPlugin> plugins;
// @since 4.1.0
@ -31,12 +31,12 @@ class MarkwonImpl extends Markwon {
@NonNull TextView.BufferType bufferType,
@Nullable TextSetter textSetter,
@NonNull Parser parser,
@NonNull MarkwonVisitor visitor,
@NonNull MarkwonVisitorFactory visitorFactory,
@NonNull List<MarkwonPlugin> plugins) {
this.bufferType = bufferType;
this.textSetter = textSetter;
this.parser = parser;
this.visitor = visitor;
this.visitorFactory = visitorFactory;
this.plugins = plugins;
}
@ -60,16 +60,22 @@ class MarkwonImpl extends Markwon {
plugin.beforeRender(node);
}
// @since 4.1.1 obtain visitor via factory
final MarkwonVisitor visitor = visitorFactory.create();
node.accept(visitor);
for (MarkwonPlugin plugin : plugins) {
plugin.afterRender(node, visitor);
}
//noinspection UnnecessaryLocalVariable
final Spanned spanned = visitor.builder().spannableStringBuilder();
// clear render props and builder after rendering
visitor.clear();
// @since 4.1.1 as we no longer reuse visitor - there is no need to clean it
// we might still do it if we introduce a thread-local storage though
// visitor.clear();
return spanned;
}

View File

@ -0,0 +1,26 @@
package io.noties.markwon;
import androidx.annotation.NonNull;
/**
* @since 4.1.1
*/
abstract class MarkwonVisitorFactory {
@NonNull
abstract MarkwonVisitor create();
@NonNull
static MarkwonVisitorFactory create(
@NonNull final MarkwonVisitorImpl.Builder builder,
@NonNull final MarkwonConfiguration configuration,
@NonNull final RenderProps renderProps) {
return new MarkwonVisitorFactory() {
@NonNull
@Override
MarkwonVisitor create() {
return builder.build(configuration, renderProps);
}
};
}
}

View File

@ -269,6 +269,11 @@ class MarkwonVisitorImpl implements MarkwonVisitor {
@NonNull
@Override
public <N extends Node> Builder on(@NonNull Class<N> node, @Nullable NodeVisitor<? super N> nodeVisitor) {
// @since 4.1.1 we might actually introduce a local flag to check if it's been built
// and throw an exception here if some modification is requested
// NB, as we might be built from different threads this flag must be synchronized
// we should allow `null` to exclude node from being visited (for example to disable
// some functionality)
if (nodeVisitor == null) {

View File

@ -328,6 +328,9 @@ public class CorePlugin extends AbstractMarkwonPlugin {
visitor.builder().append('\u00a0');
// @since 4.1.1
CoreProps.CODE_BLOCK_INFO.set(visitor.renderProps(), info);
visitor.setSpansForNodeOptional(node, length);
if (visitor.hasNext(node)) {

View File

@ -19,6 +19,11 @@ public abstract class CoreProps {
public static final Prop<Boolean> PARAGRAPH_IS_IN_TIGHT_LIST = Prop.of("paragraph-is-in-tight-list");
/**
* @since 4.1.1
*/
public static final Prop<String> CODE_BLOCK_INFO = Prop.of("code-block-info");
public enum ListItemType {
BULLET,
ORDERED

View File

@ -31,6 +31,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.RETURNS_MOCKS;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ -47,7 +48,7 @@ public class MarkwonImplTest {
TextView.BufferType.SPANNABLE,
null,
mock(Parser.class),
mock(MarkwonVisitor.class),
mock(MarkwonVisitorFactory.class),
Collections.singletonList(plugin));
impl.parse("whatever");
@ -70,7 +71,7 @@ public class MarkwonImplTest {
TextView.BufferType.SPANNABLE,
null,
parser,
mock(MarkwonVisitor.class),
mock(MarkwonVisitorFactory.class),
Arrays.asList(first, second));
impl.parse("zero");
@ -89,6 +90,7 @@ public class MarkwonImplTest {
final MarkwonPlugin plugin = mock(MarkwonPlugin.class);
final MarkwonVisitorFactory visitorFactory = mock(MarkwonVisitorFactory.class);
final MarkwonVisitor visitor = mock(MarkwonVisitor.class);
final SpannableBuilder builder = mock(SpannableBuilder.class);
@ -96,9 +98,10 @@ public class MarkwonImplTest {
TextView.BufferType.SPANNABLE,
null,
mock(Parser.class),
visitor,
visitorFactory,
Collections.singletonList(plugin));
when(visitorFactory.create()).thenReturn(visitor);
when(visitor.builder()).thenReturn(builder);
final Node node = mock(Node.class);
@ -132,24 +135,30 @@ public class MarkwonImplTest {
public void render_clears_visitor() {
// each render call should have empty-state visitor (no previous rendering info)
final MarkwonVisitorFactory visitorFactory = mock(MarkwonVisitorFactory.class);
final MarkwonVisitor visitor = mock(MarkwonVisitor.class, RETURNS_MOCKS);
when(visitorFactory.create()).thenReturn(visitor);
final MarkwonImpl impl = new MarkwonImpl(
TextView.BufferType.SPANNABLE,
null,
mock(Parser.class),
visitor,
visitorFactory,
Collections.<MarkwonPlugin>emptyList());
impl.render(mock(Node.class));
verify(visitor, times(1)).clear();
// obsolete starting with 4.1.1
// verify(visitor, times(1)).clear();
verify(visitor, never()).clear();
}
@Test
public void render_props() {
// render props are configured properly and cleared after render function
final MarkwonVisitorFactory visitorFactory = mock(MarkwonVisitorFactory.class);
final MarkwonVisitor visitor = mock(MarkwonVisitor.class, RETURNS_MOCKS);
final RenderProps renderProps = mock(RenderProps.class);
@ -161,6 +170,7 @@ public class MarkwonImplTest {
}
}).when(visitor).clear();
when(visitorFactory.create()).thenReturn(visitor);
when(visitor.renderProps()).thenReturn(renderProps);
final MarkwonPlugin plugin = mock(MarkwonPlugin.class);
@ -169,7 +179,7 @@ public class MarkwonImplTest {
TextView.BufferType.SPANNABLE,
null,
mock(Parser.class),
visitor,
visitorFactory,
Collections.singletonList(plugin));
final AtomicBoolean flag = new AtomicBoolean(false);
@ -191,7 +201,9 @@ public class MarkwonImplTest {
assertTrue(flag.get());
verify(renderProps, times(1)).clearAll();
// obsolete starting with 4.1.1
// verify(renderProps, times(1)).clearAll();
verify(renderProps, never()).clearAll();
}
@Test
@ -205,7 +217,7 @@ public class MarkwonImplTest {
TextView.BufferType.EDITABLE,
null,
mock(Parser.class),
mock(MarkwonVisitor.class, RETURNS_MOCKS),
mock(MarkwonVisitorFactory.class, RETURNS_MOCKS),
Collections.singletonList(plugin));
final TextView textView = mock(TextView.class);
@ -252,7 +264,7 @@ public class MarkwonImplTest {
TextView.BufferType.SPANNABLE,
null,
mock(Parser.class),
mock(MarkwonVisitor.class),
mock(MarkwonVisitorFactory.class),
plugins);
assertTrue("First", impl.hasPlugin(First.class));
@ -274,7 +286,7 @@ public class MarkwonImplTest {
TextView.BufferType.EDITABLE,
textSetter,
mock(Parser.class),
mock(MarkwonVisitor.class),
mock(MarkwonVisitorFactory.class),
Collections.singletonList(plugin));
final TextView textView = mock(TextView.class);
@ -317,7 +329,8 @@ public class MarkwonImplTest {
TextView.BufferType.SPANNABLE,
null,
mock(Parser.class),
mock(MarkwonVisitor.class), plugins);
mock(MarkwonVisitorFactory.class),
plugins);
// should be returned
assertNotNull(impl.requirePlugin(MarkwonPlugin.class));
@ -346,7 +359,7 @@ public class MarkwonImplTest {
TextView.BufferType.SPANNABLE,
null,
mock(Parser.class),
mock(MarkwonVisitor.class),
mock(MarkwonVisitorFactory.class),
plugins);
final List<? extends MarkwonPlugin> list = impl.getPlugins();

View File

@ -1,10 +1,10 @@
package io.noties.markwon.core;
import android.text.method.MovementMethod;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.text.method.MovementMethod;
import android.widget.ImageView;
import android.widget.TextView;
import org.commonmark.node.BlockQuote;
import org.commonmark.node.BulletList;
@ -38,15 +38,15 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import ix.Ix;
import ix.IxFunction;
import ix.IxPredicate;
import io.noties.markwon.MarkwonConfiguration;
import io.noties.markwon.MarkwonSpansFactory;
import io.noties.markwon.MarkwonVisitor;
import io.noties.markwon.RenderProps;
import io.noties.markwon.SpanFactory;
import io.noties.markwon.SpannableBuilder;
import ix.Ix;
import ix.IxFunction;
import ix.IxPredicate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@ -54,6 +54,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.RETURNS_MOCKS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@ -300,4 +301,45 @@ public class CorePluginTest {
verify(textView, times(0)).setMovementMethod(any(MovementMethod.class));
}
@Test
public void code_block_info_prop() {
final CorePlugin plugin = CorePlugin.create();
final MarkwonVisitor.Builder builder = mock(MarkwonVisitor.Builder.class);
plugin.configureVisitor(builder);
final ArgumentCaptor<MarkwonVisitor.NodeVisitor> fencedCaptor =
ArgumentCaptor.forClass(MarkwonVisitor.NodeVisitor.class);
final ArgumentCaptor<MarkwonVisitor.NodeVisitor> indendedCaptor =
ArgumentCaptor.forClass(MarkwonVisitor.NodeVisitor.class);
//noinspection unchecked
verify(builder, times(1)).on(eq(FencedCodeBlock.class), fencedCaptor.capture());
//noinspection unchecked
verify(builder, times(1)).on(eq(IndentedCodeBlock.class), indendedCaptor.capture());
final RenderProps renderProps = mock(RenderProps.class);
final MarkwonVisitor visitor = mock(MarkwonVisitor.class, RETURNS_MOCKS);
when(visitor.renderProps()).thenReturn(renderProps);
// fenced
{
final FencedCodeBlock block = new FencedCodeBlock();
block.setInfo("testing-fenced");
//noinspection unchecked
fencedCaptor.getValue().visit(visitor, block);
verify(renderProps, times(1)).set(eq(CoreProps.CODE_BLOCK_INFO), eq("testing-fenced"));
}
// indended
{
final IndentedCodeBlock block = new IndentedCodeBlock();
//noinspection unchecked
indendedCaptor.getValue().visit(visitor, block);
verify(renderProps, times(1)).set(eq(CoreProps.CODE_BLOCK_INFO), eq((String) null));
}
}
}

View File

@ -6,6 +6,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import org.commonmark.ext.gfm.tables.TableBlock;
import org.commonmark.ext.gfm.tables.TableBody;
import org.commonmark.ext.gfm.tables.TableCell;
import org.commonmark.ext.gfm.tables.TableHead;
@ -115,19 +116,26 @@ public class TablePlugin extends AbstractMarkwonPlugin {
void configure(@NonNull MarkwonVisitor.Builder builder) {
builder
.on(TableBody.class, new MarkwonVisitor.NodeVisitor<TableBody>() {
// @since 4.1.1 we use TableBlock instead of TableBody to add new lines
.on(TableBlock.class, new MarkwonVisitor.NodeVisitor<TableBlock>() {
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull TableBody tableBody) {
public void visit(@NonNull MarkwonVisitor visitor, @NonNull TableBlock tableBlock) {
visitor.visitChildren(tableBody);
tableRows = 0;
visitor.visitChildren(tableBlock);
if (visitor.hasNext(tableBody)) {
if (visitor.hasNext(tableBlock)) {
visitor.ensureNewLine();
visitor.forceNewLine();
}
}
})
.on(TableBody.class, new MarkwonVisitor.NodeVisitor<TableBody>() {
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull TableBody tableBody) {
visitor.visitChildren(tableBody);
tableRows = 0;
}
})
.on(TableRow.class, new MarkwonVisitor.NodeVisitor<TableRow>() {
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull TableRow tableRow) {

View File

@ -11,7 +11,6 @@ import java.io.InputStream;
import java.util.Collection;
import java.util.Collections;
import io.noties.markwon.image.DrawableUtils;
import io.noties.markwon.image.MediaDecoder;
import pl.droidsonroids.gif.GifDrawable;
@ -97,9 +96,7 @@ public class GifMediaDecoder extends MediaDecoder {
private static void validate() {
if (!GifSupport.hasGifSupport()) {
throw new IllegalStateException("`pl.droidsonroids.gif:android-gif-drawable:*` " +
"dependency is missing, please add to your project explicitly if you " +
"wish to use GIF media decoder");
throw new IllegalStateException(GifSupport.missingMessage());
}
}
}

View File

@ -1,5 +1,9 @@
package io.noties.markwon.image.gif;
import android.util.Log;
import androidx.annotation.NonNull;
/**
* @since 4.0.0
*/
@ -13,7 +17,9 @@ public abstract class GifSupport {
pl.droidsonroids.gif.GifDrawable.class.getName();
result = true;
} catch (Throwable t) {
t.printStackTrace();
// @since 4.1.1 instead of printing full stacktrace of the exception,
// just print a warning to the console
Log.w("MarkwonImagesPlugin", missingMessage());
result = false;
}
HAS_GIF = result;
@ -23,6 +29,16 @@ public abstract class GifSupport {
return HAS_GIF;
}
/**
* @since 4.1.1
*/
@NonNull
static String missingMessage() {
return "`pl.droidsonroids.gif:android-gif-drawable:*` " +
"dependency is missing, please add to your project explicitly if you " +
"wish to use GIF media-decoder";
}
private GifSupport() {
}
}

View File

@ -83,8 +83,7 @@ public class SvgMediaDecoder extends MediaDecoder {
private static void validate() {
if (!SvgSupport.hasSvgSupport()) {
throw new IllegalStateException("`com.caverock:androidsvg:*` dependency is missing, " +
"please add to your project explicitly if you wish to use SVG media decoder");
throw new IllegalStateException(SvgSupport.missingMessage());
}
}
}

View File

@ -1,5 +1,9 @@
package io.noties.markwon.image.svg;
import android.util.Log;
import androidx.annotation.NonNull;
/**
* @since 4.0.0
*/
@ -13,7 +17,9 @@ public abstract class SvgSupport {
com.caverock.androidsvg.SVG.class.getName();
result = true;
} catch (Throwable t) {
t.printStackTrace();
// @since 4.1.1 instead of printing full stacktrace of the exception,
// just print a warning to the console
Log.w("MarkwonImagesPlugin", missingMessage());
result = false;
}
HAS_SVG = result;
@ -23,6 +29,15 @@ public abstract class SvgSupport {
return HAS_SVG;
}
/**
* @since 4.1.1
*/
@NonNull
static String missingMessage() {
return "`com.caverock:androidsvg:*` dependency is missing, " +
"please add to your project explicitly if you wish to use SVG media-decoder";
}
private SvgSupport() {
}
}