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:
commit
4348555b75
20
.github/workflows/release.yml
vendored
Normal file
20
.github/workflows/release.yml
vendored
Normal 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
20
.github/workflows/snapshot.yml
vendored
Normal 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
|
11
CHANGELOG.md
11
CHANGELOG.md
@ -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`
|
||||
|
@ -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'
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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/',
|
||||
|
@ -1,5 +1,7 @@
|
||||
# Configuration
|
||||
|
||||
<LegacyWarning />
|
||||
|
||||
`MarkwonConfiguration` class holds common Markwon functionality.
|
||||
These are _configurable_ properties:
|
||||
* `SyntaxHighlight`
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -1,5 +1,7 @@
|
||||
# HTML Renderer
|
||||
|
||||
<LegacyWarning />
|
||||
|
||||
Starting with <Badge text="3.0.0" /> `MarkwonHtmlRenderer` controls how HTML
|
||||
is rendered:
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
# Spans Factory
|
||||
|
||||
<LegacyWarning />
|
||||
|
||||
Starting with <Badge text="3.0.0" /> `MarkwonSpansFactory` controls what spans are displayed
|
||||
for markdown nodes.
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
# Theme
|
||||
|
||||
<LegacyWarning />
|
||||
|
||||
Here is the list of properties that can be configured via `MarkwonTheme.Builder` class.
|
||||
|
||||
:::tip
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -1,5 +1,7 @@
|
||||
# Strikethrough extension
|
||||
|
||||
<LegacyWarning />
|
||||
|
||||
<MavenBadge :artifact="'ext-strikethrough'" />
|
||||
|
||||
This module adds `strikethrough` functionality to `Markwon` via `StrikethroughPlugin`:
|
||||
|
@ -1,5 +1,7 @@
|
||||
# Tables extension
|
||||
|
||||
<LegacyWarning />
|
||||
|
||||
<MavenBadge :artifact="'ext-tables'" />
|
||||
|
||||
This extension adds support for GFM tables.
|
||||
|
@ -1,5 +1,7 @@
|
||||
# Task list extension
|
||||
|
||||
<LegacyWarning />
|
||||
|
||||
<MavenBadge :artifact="'ext-tasklist'" />
|
||||
|
||||
Adds support for GFM (Github-flavored markdown) task-lists:
|
||||
|
@ -1,5 +1,7 @@
|
||||
# HTML
|
||||
|
||||
<LegacyWarning />
|
||||
|
||||
This artifact encapsulates HTML parsing from the core artifact and provides
|
||||
few predefined `TagHandlers`
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
# Image GIF
|
||||
|
||||
<LegacyWarning />
|
||||
|
||||
<MavenBadge :artifact="'image-gif'" />
|
||||
|
||||
Adds support for GIF images inside markdown.
|
||||
|
@ -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" />
|
||||
|
@ -1,5 +1,7 @@
|
||||
# Image SVG
|
||||
|
||||
<LegacyWarning />
|
||||
|
||||
<MavenBadge :artifact="'image-svg'" />
|
||||
|
||||
Adds support for SVG images inside markdown.
|
||||
|
@ -3,6 +3,8 @@ prev: false
|
||||
next: /docs/v3/core/getting-started.md
|
||||
---
|
||||
|
||||
<LegacyWarning />
|
||||
|
||||
# Installation
|
||||
|
||||

|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
40
docs/docs/v4/core/text-setter.md
Normal file
40
docs/docs/v4/core/text-setter.md
Normal 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
|
@ -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
|
||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
@ -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}.
|
||||
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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)) {
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user