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 | ||||
|  | ||||
| @ -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
	 Dimitry
						Dimitry