diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..962e76ed --- /dev/null +++ b/.github/workflows/release.yml @@ -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 diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml new file mode 100644 index 00000000..7d38df2b --- /dev/null +++ b/.github/workflows/snapshot.yml @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 60e2c357..e8992853 100644 --- a/CHANGELOG.md +++ b/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` diff --git a/build.gradle b/build.gradle index 57cc1bab..cfb9c01e 100644 --- a/build.gradle +++ b/build.gradle @@ -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' } diff --git a/docs/.vuepress/components/LegacyWarning.vue b/docs/.vuepress/components/LegacyWarning.vue index b84e9cea..5f1d585f 100644 --- a/docs/.vuepress/components/LegacyWarning.vue +++ b/docs/.vuepress/components/LegacyWarning.vue @@ -1,7 +1,7 @@ WARNING - This is documentation for legacy 2.x.x versions. For the most current version click here. + This is documentation for legacy versions. For the most current version click here. diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index de897e67..cd58b64c 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -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/', diff --git a/docs/docs/v3/core/configuration.md b/docs/docs/v3/core/configuration.md index 245b3310..db5d3f96 100644 --- a/docs/docs/v3/core/configuration.md +++ b/docs/docs/v3/core/configuration.md @@ -1,5 +1,7 @@ # Configuration + + `MarkwonConfiguration` class holds common Markwon functionality. These are _configurable_ properties: * `SyntaxHighlight` diff --git a/docs/docs/v3/core/core-plugin.md b/docs/docs/v3/core/core-plugin.md index 3a63c2c5..41c92a45 100644 --- a/docs/docs/v3/core/core-plugin.md +++ b/docs/docs/v3/core/core-plugin.md @@ -1,5 +1,7 @@ # Core plugin + + Since with introduction of _plugins_, Markwon **core** functionality was moved to a dedicated plugin. diff --git a/docs/docs/v3/core/getting-started.md b/docs/docs/v3/core/getting-started.md index 652cfb7c..60dc0451 100644 --- a/docs/docs/v3/core/getting-started.md +++ b/docs/docs/v3/core/getting-started.md @@ -1,5 +1,7 @@ # Getting started + + :::tip Installation Please follow [installation](/docs/v3/install.md) instructions to learn how to add `Markwon` to your project diff --git a/docs/docs/v3/core/html-renderer.md b/docs/docs/v3/core/html-renderer.md index 17ef63e3..1b5e4761 100644 --- a/docs/docs/v3/core/html-renderer.md +++ b/docs/docs/v3/core/html-renderer.md @@ -1,5 +1,7 @@ # HTML Renderer + + Starting with `MarkwonHtmlRenderer` controls how HTML is rendered: diff --git a/docs/docs/v3/core/images.md b/docs/docs/v3/core/images.md index 4aa675e2..75361d86 100644 --- a/docs/docs/v3/core/images.md +++ b/docs/docs/v3/core/images.md @@ -1,5 +1,7 @@ # Images + + Starting with `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 diff --git a/docs/docs/v3/core/movement-method-plugin.md b/docs/docs/v3/core/movement-method-plugin.md index 6bb50c87..86fe43d2 100644 --- a/docs/docs/v3/core/movement-method-plugin.md +++ b/docs/docs/v3/core/movement-method-plugin.md @@ -1,5 +1,7 @@ # Movement method plugin + + `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 diff --git a/docs/docs/v3/core/plugins.md b/docs/docs/v3/core/plugins.md index dd83ab76..62052c32 100644 --- a/docs/docs/v3/core/plugins.md +++ b/docs/docs/v3/core/plugins.md @@ -1,5 +1,7 @@ # Plugins + + Since `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 diff --git a/docs/docs/v3/core/render-props.md b/docs/docs/v3/core/render-props.md index 9dd18004..fb75479d 100644 --- a/docs/docs/v3/core/render-props.md +++ b/docs/docs/v3/core/render-props.md @@ -1,5 +1,7 @@ # RenderProps + + `RenderProps` encapsulates passing arguments from a node visitor to a node renderer. Without hardcoding arguments into an API method calls. diff --git a/docs/docs/v3/core/spans-factory.md b/docs/docs/v3/core/spans-factory.md index 6044de9a..e6536dd0 100644 --- a/docs/docs/v3/core/spans-factory.md +++ b/docs/docs/v3/core/spans-factory.md @@ -1,5 +1,7 @@ # Spans Factory + + Starting with `MarkwonSpansFactory` controls what spans are displayed for markdown nodes. diff --git a/docs/docs/v3/core/theme.md b/docs/docs/v3/core/theme.md index 672babc6..1b7d524f 100644 --- a/docs/docs/v3/core/theme.md +++ b/docs/docs/v3/core/theme.md @@ -1,5 +1,7 @@ # Theme + + Here is the list of properties that can be configured via `MarkwonTheme.Builder` class. :::tip diff --git a/docs/docs/v3/core/visitor.md b/docs/docs/v3/core/visitor.md index f0cce0f2..ef0ffee6 100644 --- a/docs/docs/v3/core/visitor.md +++ b/docs/docs/v3/core/visitor.md @@ -1,5 +1,7 @@ # Visitor + + Starting with _visiting_ of parsed markdown nodes does not require creating own instance of commonmark-java `Visitor`, instead a composable/configurable `MarkwonVisitor` is used. diff --git a/docs/docs/v3/ext-latex/README.md b/docs/docs/v3/ext-latex/README.md index ea1256f0..a0f1b601 100644 --- a/docs/docs/v3/ext-latex/README.md +++ b/docs/docs/v3/ext-latex/README.md @@ -1,5 +1,7 @@ # LaTeX extension + + This is an extension that will help you display LaTeX formulas in your markdown. diff --git a/docs/docs/v3/ext-strikethrough/README.md b/docs/docs/v3/ext-strikethrough/README.md index 9bbfc0e5..ce0d9f4d 100644 --- a/docs/docs/v3/ext-strikethrough/README.md +++ b/docs/docs/v3/ext-strikethrough/README.md @@ -1,5 +1,7 @@ # Strikethrough extension + + This module adds `strikethrough` functionality to `Markwon` via `StrikethroughPlugin`: diff --git a/docs/docs/v3/ext-tables/README.md b/docs/docs/v3/ext-tables/README.md index 49282a05..dc0813b0 100644 --- a/docs/docs/v3/ext-tables/README.md +++ b/docs/docs/v3/ext-tables/README.md @@ -1,5 +1,7 @@ # Tables extension + + This extension adds support for GFM tables. diff --git a/docs/docs/v3/ext-tasklist/README.md b/docs/docs/v3/ext-tasklist/README.md index 3fb332f4..cda66aa5 100644 --- a/docs/docs/v3/ext-tasklist/README.md +++ b/docs/docs/v3/ext-tasklist/README.md @@ -1,5 +1,7 @@ # Task list extension + + Adds support for GFM (Github-flavored markdown) task-lists: diff --git a/docs/docs/v3/html/README.md b/docs/docs/v3/html/README.md index 12f66400..36e08abc 100644 --- a/docs/docs/v3/html/README.md +++ b/docs/docs/v3/html/README.md @@ -1,5 +1,7 @@ # HTML + + This artifact encapsulates HTML parsing from the core artifact and provides few predefined `TagHandlers` diff --git a/docs/docs/v3/image/gif.md b/docs/docs/v3/image/gif.md index 56ce081c..f3d3b5a6 100644 --- a/docs/docs/v3/image/gif.md +++ b/docs/docs/v3/image/gif.md @@ -1,5 +1,7 @@ # Image GIF + + Adds support for GIF images inside markdown. diff --git a/docs/docs/v3/image/okhttp.md b/docs/docs/v3/image/okhttp.md index 4a7c159d..fc61b1d9 100644 --- a/docs/docs/v3/image/okhttp.md +++ b/docs/docs/v3/image/okhttp.md @@ -1,5 +1,7 @@ # Image OkHttp + + Uses [okhttp library](https://github.com/square/okhttp) as the network transport fro images. Since diff --git a/docs/docs/v3/image/svg.md b/docs/docs/v3/image/svg.md index 538c524c..cf060251 100644 --- a/docs/docs/v3/image/svg.md +++ b/docs/docs/v3/image/svg.md @@ -1,5 +1,7 @@ # Image SVG + + Adds support for SVG images inside markdown. diff --git a/docs/docs/v3/install.md b/docs/docs/v3/install.md index 19c28cba..1d76b5c2 100644 --- a/docs/docs/v3/install.md +++ b/docs/docs/v3/install.md @@ -3,6 +3,8 @@ prev: false next: /docs/v3/core/getting-started.md --- + + # Installation  diff --git a/docs/docs/v3/migration-2-3.md b/docs/docs/v3/migration-2-3.md index aeb045c6..2fdcd884 100644 --- a/docs/docs/v3/migration-2-3.md +++ b/docs/docs/v3/migration-2-3.md @@ -1,5 +1,7 @@ # Migration 2.x.x -> 3.x.x + + * strikethrough moved to standalone module * tables moved to standalone module * core functionality of `AsyncDrawableLoader` moved to `core` module diff --git a/docs/docs/v3/recycler-table/README.md b/docs/docs/v3/recycler-table/README.md index e7287649..a377b041 100644 --- a/docs/docs/v3/recycler-table/README.md +++ b/docs/docs/v3/recycler-table/README.md @@ -1,5 +1,7 @@ # Recycler Table + + Artifact that provides [MarkwonAdapter.Entry](/docs/v3/recycler/) to render `TableBlock` inside diff --git a/docs/docs/v3/recycler/README.md b/docs/docs/v3/recycler/README.md index 73b05826..635fbcf4 100644 --- a/docs/docs/v3/recycler/README.md +++ b/docs/docs/v3/recycler/README.md @@ -1,5 +1,7 @@ # Recycler + + This artifact allows displaying markdown in a set of Android widgets diff --git a/docs/docs/v3/syntax-highlight/README.md b/docs/docs/v3/syntax-highlight/README.md index a5203cf6..cc47f6f5 100644 --- a/docs/docs/v3/syntax-highlight/README.md +++ b/docs/docs/v3/syntax-highlight/README.md @@ -1,5 +1,7 @@ # 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. diff --git a/docs/docs/v4/core/text-setter.md b/docs/docs/v4/core/text-setter.md new file mode 100644 index 00000000..c69d0e58 --- /dev/null +++ b/docs/docs/v4/core/text-setter.md @@ -0,0 +1,40 @@ +# TextSetter + +Since 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 \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index b76c9e11..bc71ace9 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.1.0 +VERSION_NAME=4.1.1 GROUP=io.noties.markwon POM_DESCRIPTION=Markwon markdown for Android diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c4486d47..430dfabc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/markwon-core/src/main/java/io/noties/markwon/Markwon.java b/markwon-core/src/main/java/io/noties/markwon/Markwon.java index 9277688b..ce8f956a 100644 --- a/markwon-core/src/main/java/io/noties/markwon/Markwon.java +++ b/markwon-core/src/main/java/io/noties/markwon/Markwon.java @@ -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}. diff --git a/markwon-core/src/main/java/io/noties/markwon/MarkwonBuilderImpl.java b/markwon-core/src/main/java/io/noties/markwon/MarkwonBuilderImpl.java index 77de961f..a14002f1 100644 --- a/markwon-core/src/main/java/io/noties/markwon/MarkwonBuilderImpl.java +++ b/markwon-core/src/main/java/io/noties/markwon/MarkwonBuilderImpl.java @@ -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) ); } diff --git a/markwon-core/src/main/java/io/noties/markwon/MarkwonImpl.java b/markwon-core/src/main/java/io/noties/markwon/MarkwonImpl.java index 9080527f..3f0ee18c 100644 --- a/markwon-core/src/main/java/io/noties/markwon/MarkwonImpl.java +++ b/markwon-core/src/main/java/io/noties/markwon/MarkwonImpl.java @@ -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 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 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; } diff --git a/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitorFactory.java b/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitorFactory.java new file mode 100644 index 00000000..98b0187e --- /dev/null +++ b/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitorFactory.java @@ -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); + } + }; + } +} diff --git a/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitorImpl.java b/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitorImpl.java index 42db74ac..c6361a00 100644 --- a/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitorImpl.java +++ b/markwon-core/src/main/java/io/noties/markwon/MarkwonVisitorImpl.java @@ -269,6 +269,11 @@ class MarkwonVisitorImpl implements MarkwonVisitor { @NonNull @Override public Builder on(@NonNull Class 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) { diff --git a/markwon-core/src/main/java/io/noties/markwon/core/CorePlugin.java b/markwon-core/src/main/java/io/noties/markwon/core/CorePlugin.java index 194882e3..dffa215d 100644 --- a/markwon-core/src/main/java/io/noties/markwon/core/CorePlugin.java +++ b/markwon-core/src/main/java/io/noties/markwon/core/CorePlugin.java @@ -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)) { diff --git a/markwon-core/src/main/java/io/noties/markwon/core/CoreProps.java b/markwon-core/src/main/java/io/noties/markwon/core/CoreProps.java index 1da57e24..23d8d374 100644 --- a/markwon-core/src/main/java/io/noties/markwon/core/CoreProps.java +++ b/markwon-core/src/main/java/io/noties/markwon/core/CoreProps.java @@ -19,6 +19,11 @@ public abstract class CoreProps { public static final Prop PARAGRAPH_IS_IN_TIGHT_LIST = Prop.of("paragraph-is-in-tight-list"); + /** + * @since 4.1.1 + */ + public static final Prop CODE_BLOCK_INFO = Prop.of("code-block-info"); + public enum ListItemType { BULLET, ORDERED diff --git a/markwon-core/src/test/java/io/noties/markwon/MarkwonImplTest.java b/markwon-core/src/test/java/io/noties/markwon/MarkwonImplTest.java index 8e24e578..2eb4f3f8 100644 --- a/markwon-core/src/test/java/io/noties/markwon/MarkwonImplTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/MarkwonImplTest.java @@ -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.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(); 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 e21a8ded..4137b83e 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 @@ -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 fencedCaptor = + ArgumentCaptor.forClass(MarkwonVisitor.NodeVisitor.class); + final ArgumentCaptor 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)); + } + } } \ No newline at end of file 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 4d931e9a..086afd75 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 @@ -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() { + // @since 4.1.1 we use TableBlock instead of TableBody to add new lines + .on(TableBlock.class, new MarkwonVisitor.NodeVisitor() { @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() { + @Override + public void visit(@NonNull MarkwonVisitor visitor, @NonNull TableBody tableBody) { + visitor.visitChildren(tableBody); + tableRows = 0; + } + }) .on(TableRow.class, new MarkwonVisitor.NodeVisitor() { @Override public void visit(@NonNull MarkwonVisitor visitor, @NonNull TableRow tableRow) { diff --git a/markwon-image/src/main/java/io/noties/markwon/image/gif/GifMediaDecoder.java b/markwon-image/src/main/java/io/noties/markwon/image/gif/GifMediaDecoder.java index e8ac616e..2efd9f93 100644 --- a/markwon-image/src/main/java/io/noties/markwon/image/gif/GifMediaDecoder.java +++ b/markwon-image/src/main/java/io/noties/markwon/image/gif/GifMediaDecoder.java @@ -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()); } } } 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 47624d40..70a143fc 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 @@ -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() { } } diff --git a/markwon-image/src/main/java/io/noties/markwon/image/svg/SvgMediaDecoder.java b/markwon-image/src/main/java/io/noties/markwon/image/svg/SvgMediaDecoder.java index b307a105..fc5da090 100644 --- a/markwon-image/src/main/java/io/noties/markwon/image/svg/SvgMediaDecoder.java +++ b/markwon-image/src/main/java/io/noties/markwon/image/svg/SvgMediaDecoder.java @@ -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()); } } } 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 70293570..4afe2506 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 @@ -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() { } }
WARNING
This is documentation for legacy 2.x.x versions. For the most current version click here.
This is documentation for legacy versions. For the most current version click here.