Merge pull request #146 from noties/v4.0.0

V4.0.0
This commit is contained in:
Dimitry 2019-07-01 20:03:17 +03:00 committed by GitHub
commit 24151dff7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
420 changed files with 10009 additions and 4709 deletions

View File

@ -1,5 +1,9 @@
# https://docs.travis-ci.com/user/languages/android/
language: android
# so, out of blue travis requires this now (without it build would not even execute, immediate failure when downloading jdk)
dist: trusty
jdk: openjdk8
sudo: false

219
CHANGELOG.md Normal file
View File

@ -0,0 +1,219 @@
# Changelog
# 4.0.0
* maven group-id change to `io.noties.markwon` (was `ru.noties.markwon`)
* package name change to `io.notier.markwon.*` (was `ru.noties.markwon.*`)
* androidx artifacts ([#76])
* `Markwon#builder` does not require explicit `CorePlugin` (added automatically),
use `Markwon#builderNoCore()` to obtain a builder without `CorePlugin`
* Removed `Priority` abstraction and `MarkwonPlugin#priority` (use `MarkwonPlugin.Registry`)
* Removed `MarkwonPlugin#configureHtmlRenderer` (for configuration use `HtmlPlugin` directly)
* Removed `MarkwonPlugin#configureImages` (for configuration use `ImagesPlugin` directly)
* Added `MarkwonPlugin.Registry` and `MarkwonPlugin#configure(Registry)` method
* `CorePlugin#addOnTextAddedListener` (process raw text added)
* `ImageSizeResolver` signature change (accept `AsyncDrawable`)
* `LinkResolver` is now an independent entity (previously part of `LinkSpan`)
* `AsyncDrawableScheduler` can now be called multiple times without performance penalty
* `AsyncDrawable` now exposes its destination, image-size, last known dimensions (canvas, text-size)
* `AsyncDrawableLoader` signature change (accept `AsyncDrawable`)
* Add `LastLineSpacingSpan`
* Add `MarkwonConfiguration.Builder#asyncDrawableLoader` method
* `ImagesPlugin` removed from `core` artifact
(also removed `images-gif`, `images-okhttp` and `images-svg` artifacts and their plugins)
* `ImagesPlugin` exposes configuration (adding scheme-handler, media-decoder, etc)
* `ImagesPlugin` allows multiple images with the same source (URL)
* Add `PlaceholderProvider` and `ErrorHandler` to `ImagesPlugin`
* `GIF` and `SVG` media-decoders are automatically added to `ImagesPlugin` if required libraries are found in the classpath
* `ImageItem` is now abstract, has 2 implementations: `withResult`, `withDecodingNeeded`
* Add `images-glide`, `images-picasso`, `linkify`, `simple-ext` modules
* `JLatexMathPlugin` is now independent of `ImagesPlugin`
* Fix wrong `JLatexMathPlugin` formulas sizes ([#138])
* `JLatexMathPlugin` has `backgroundProvider`, `executorService` configuration
* `HtmlPlugin` is self-contained (all configuration is moved in the plugin itself)
[#76]: https://github.com/noties/Markwon/issues/76
[#138]: https://github.com/noties/Markwon/issues/138
## 3.0.2
* Fix `latex` plugin ([#136])
* Add `#create(Call.Factory)` factory method to `OkHttpImagesPlugin` ([#129])
<br>Thanks to [@ZacSweers]
[#136]: https://github.com/noties/Markwon/issues/136
[#129]: https://github.com/noties/Markwon/issues/129
[@ZacSweers]: https://github.com/ZacSweers
## 3.0.1
* Add `AsyncDrawableLoader.Builder#implementation` method ([#109])
* AsyncDrawable allow placeholder to have independent size ([#115])
* `addFactory` method for MarkwonSpansFactory
* Add optional spans for list blocks (bullet and ordered)
* AsyncDrawable placeholder bounds fix
* SpannableBuilder setSpans allow array of arrays
* Add `requireFactory` method to MarkwonSpansFactory
* Add DrawableUtils
[#109]: https://github.com/noties/Markwon/issues/109
[#115]: https://github.com/noties/Markwon/issues/115
## 3.0.0
* Plugins, plugins, plugins
* Split basic functionality blocks into standalone modules
* Maven artifacts group changed to `ru.noties.markwon` (previously had been `ru.noties`)
* removed `markwon`, `markwon-image-loader`, `markwon-html-pareser-api`, `markwon-html-parser-impl`, `markwon-view` modules
* new module system: `core`, `ext-latex`, `ext-strikethrough`, `ext-tables`, `ext-tasklist`, `html`, `image-gif`, `image-okhttp`, `image-svg`, `recycler`, `recycler-table`, `syntax-highlight`
* Add BufferType option for Markwon configuration
* Fix typo in AsyncDrawable waitingForDimensions
* New tests format
* `Markwon.render` returns `Spanned` instance of generic `CharSequence`
* LinkMovementMethod is applied implicitly if not set on a TextView explicitly
* Split code and codeBlock spans and factories
* Add CustomTypefaceSpan
* Add NoCopySpansFactory
* Add placeholder to image loading
Generally speaking there are a lot of changes. Most of them are not backwards-compatible.
The main point of this release is the `Plugin` system that allows more fluent configuration
and opens the possibility of extending `Markwon` with 3rd party functionality in a simple
and intuitive fashion. Please refer to the [documentation web-site](https://noties.github.io/Markwon)
that has information on how to start migration.
The shortest excerpt of this release can be expressed like this:
```java
// previous v2.x.x way
Markwon.setMarkdown(textView, "**Hello there!**");
```
```java
// 3.x.x
Markwon.create(context)
.setMarkdown(textView, "**Hello there!**");
```
But there is much more to it, please visit documentation web-site
to get the full picture of latest changes.
## 2.0.1
* `SpannableMarkdownVisitor` Rename blockQuoteIndent to blockIndent
* Fixed block new lines logic for block quote and paragraph ([#82])
* AsyncDrawable fix no dimensions bug ([#81])
* Update SpannableTheme to use Px instead of Dimension annotation
* Allow TaskListSpan isDone mutation
* Updated commonmark-java to 0.12.1
* Add OrderedListItemSpan measure utility method ([#78])
* Add SpannableBuilder#getSpans method
* Fix DataUri scheme handler in image-loader ([#74])
* Introduced a "copy" builder for SpannableThem
<br>Thanks [@c-b-h]
[#82]: https://github.com/noties/Markwon/issues/82
[#81]: https://github.com/noties/Markwon/issues/81
[#78]: https://github.com/noties/Markwon/issues/78
[#74]: https://github.com/noties/Markwon/issues/74
[@c-b-h]: https://github.com/c-b-h
## 2.0.0
* Add `html-parser-api` and `html-parser-impl` modules
* Add `HtmlEmptyTagReplacement`
* Implement Appendable and CharSequence in SpannableBuilder
* Renamed library modules to reflect maven artifact names
* Rename `markwon-syntax` to `markwon-syntax-highlight`
* Add HtmlRenderer asbtraction
* Add CssInlineStyleParser
* Fix Theme#listItemColor and OL
* Fix task list block parser to revert parsing state when line is not matching
* Defined test format files
* image-loader add datauri parser
* image-loader add support for inline data uri image references
* Add travis configuration
* Fix image with width greater than canvas scaled
* Fix blockquote span
* Dealing with white spaces at the end of a document
* image-loader add SchemeHandler abstraction
* Add sample-latex-math module
## v1.1.1
* Fix OrderedListItemSpan text position (baseline) ([#55])
* Add softBreakAddsNewLine option for SpannableConfiguration ([#54])
* Paragraph text can now explicitly be spanned ([#58])
<br>Thanks to [@c-b-h]
* Fix table border color if odd background is specified ([#56])
* Add table customizations (even and header rows)
[#55]: https://github.com/noties/Markwon/issues/55
[#54]: https://github.com/noties/Markwon/issues/54
[#58]: https://github.com/noties/Markwon/issues/58
[#56]: https://github.com/noties/Markwon/issues/56
[@c-b-h]: https://github.com/c-b-h
## v1.1.0
* Update commonmark to 0.11.0 and android-gif to 1.2.14
* Add syntax highlight functionality (`library-syntax` module and `markwon-syntax` artifact)
* Add headingTypeface, headingTextSizes to SpannableTheme
<br>Thanks to [@edenman]
* Introduce `MediaDecoder` abstraction to `image-loader` module
* Introduce `SpannableFactory`
<br>Thanks for idea to [@c-b-h]
* Update sample application to use syntax-highlight
* Update sample application to use clickable placeholder for GIF media
[@edenman]: https://github.com/edenman
[@c-b-h]: https://github.com/c-b-h
## v1.0.6
* Fix bullet list item size (depend on text size and not top-bottom arguments)
* Add ability to specify MovementMethod when applying markdown to a TextView
* Markdown images size is also resolved via ImageSizeResolver
* Moved `ImageSize`, `ImageSizeResolver` and `ImageSizeResolverDef`
to `ru.noties.markwon.renderer` package (one level up, previously `ru.noties.markwon.renderer.html`)
## v1.0.5
* Change LinkSpan to extend URLSpan. Allow default linkColor (if not set explicitly)
* Fit an image without dimensions to canvas width (and keep ratio)
* Add support for separate color for code blocks ([#37])
<br>Thanks to [@Arcnor]
[#37]: https://github.com/noties/Markwon/issues/37
[@Arcnor]: https://github.com/Arcnor
## v1.0.4
* Fixes [#28] (tables are not rendered when at the end of the markdown)
* Adds support for `indented code blocks`
<br>Thanks to [@dlew]
[#28]: https://github.com/noties/Markwon/issues/
[@dlew]: https://github.com/dlew
## v1.0.3
* Fixed ordered lists (when number width is greater than block margin)
## v1.0.2
* Fixed additional white spaces at the end of parsed markdown
* Fixed headings with no underline (levels 1 &amp; 2)
* Tables can have no borders
## v1.0.1
* Support for task-lists ([#2])
* Spans now are applied in reverse order ([#5] [#10])
* Added `SpannableBuilder` to follow the reverse order of spans
* Updated `commonmark-java` to `0.10.0`
* Fixes [#1]
[#1]: https://github.com/noties/Markwon/issues/1
[#2]: https://github.com/noties/Markwon/issues/2
[#5]: https://github.com/noties/Markwon/issues/5
[#10]: https://github.com/noties/Markwon/issues/10
## v1.0.0
Initial release

View File

@ -28,20 +28,21 @@ features listed in [commonmark-spec] are supported
## Installation
![stable](https://img.shields.io/maven-central/v/ru.noties.markwon/core.svg?label=stable)
![snapshot](https://img.shields.io/nexus/s/https/oss.sonatype.org/ru.noties.markwon/core.svg?label=snapshot)
![stable](https://img.shields.io/maven-central/v/io.noties.markwon/core.svg?label=stable)
![snapshot](https://img.shields.io/nexus/s/https/oss.sonatype.org/io.noties.markwon/core.svg?label=snapshot)
```groovy
implementation "ru.noties.markwon:core:${markwonVersion}"
```kotlin
implementation "io.noties.markwon:core:${markwonVersion}"
```
Full list of available artifacts is present in the [install section](https://noties.github.io/Markwon/docs/v3/install.html)
Full list of available artifacts is present in the [install section](https://noties.github.io/Markwon/docs/v4/install.html)
of the [documentation] web-site.
Please visit [documentation] web-site for further reference.
> You can find previous version of Markwon in [2.x.x](https://github.com/noties/Markwon/tree/2.x.x) branch
> You can find previous version of Markwon in [2.x.x](https://github.com/noties/Markwon/tree/2.x.x)
and [3.x.x](https://github.com/noties/Markwon/tree/3.x.x) branches
## Supported markdown features:
@ -97,15 +98,6 @@ Please visit [documentation] web-site for reference
[documentation]: https://noties.github.io/Markwon
---
## Applications using Markwon
* [Partiko](https://partiko.app)
* [FairNote Notepad](https://play.google.com/store/apps/details?id=com.rgiskard.fairnote)
* [Boxcryptor](https://www.boxcryptor.com)
---
# Demo
@ -217,7 +209,6 @@ public static Parser createParser() {
android:layout_margin="16dip"
android:lineSpacingExtra="2dip"
android:textSize="16sp"
tools:context="ru.noties.markwon.MainActivity"
tools:text="yo\nman" />
</ScrollView>
@ -296,7 +287,7 @@ Underscores (`_`)
## License
```
Copyright 2017 Dimitry Ivanov (mail@dimitryivanov.ru)
Copyright 2019 Dimitry Ivanov (legal@noties.io)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -6,7 +6,7 @@ android {
buildToolsVersion config['build-tools']
defaultConfig {
applicationId "ru.noties.markwon"
applicationId "io.noties.markwon"
minSdkVersion config['min-sdk']
targetSdkVersion config['target-sdk']
versionCode 1
@ -33,8 +33,7 @@ dependencies {
implementation project(':markwon-ext-tables')
implementation project(':markwon-ext-tasklist')
implementation project(':markwon-html')
implementation project(':markwon-image-gif')
implementation project(':markwon-image-svg')
implementation project(':markwon-image')
implementation project(':markwon-syntax-highlight')
deps.with {
@ -42,6 +41,8 @@ dependencies {
implementation it['prism4j']
implementation it['debug']
implementation it['dagger']
implementation it['android-svg']
implementation it['android-gif']
}
deps['annotationProcessor'].with {

View File

@ -1,15 +1,15 @@
package ru.noties.markwon.debug;
package io.noties.markwon.debug;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
import androidx.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import ru.noties.markwon.R;
import ru.noties.markwon.ext.tasklist.TaskListDrawable;
import io.noties.markwon.app.R;
import io.noties.markwon.ext.tasklist.TaskListDrawable;
public class DebugCheckboxDrawableView extends View {

View File

@ -4,7 +4,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<ru.noties.markwon.debug.DebugCheckboxDrawableView
<io.noties.markwon.debug.DebugCheckboxDrawableView
android:layout_width="128dip"
android:layout_height="128dip"
android:layout_gravity="center"

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="ru.noties.markwon">
package="io.noties.markwon.app">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_ALL_DOWNLOADS" />

View File

@ -1,4 +1,4 @@
package ru.noties.markwon;
package io.noties.markwon.app;
import javax.inject.Scope;

View File

@ -1,11 +1,12 @@
package ru.noties.markwon;
package io.noties.markwon.app;
import android.app.Application;
import android.content.Context;
import android.support.annotation.NonNull;
import ru.noties.debug.AndroidLogDebugOutput;
import ru.noties.debug.Debug;
import androidx.annotation.NonNull;
import io.noties.debug.AndroidLogDebugOutput;
import io.noties.debug.Debug;
public class App extends Application {

View File

@ -1,10 +1,11 @@
package ru.noties.markwon;
package io.noties.markwon.app;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
abstract class AppBarItem {
static class State {

View File

@ -1,4 +1,4 @@
package ru.noties.markwon;
package io.noties.markwon.app;
import javax.inject.Singleton;

View File

@ -1,4 +1,4 @@
package ru.noties.markwon;
package io.noties.markwon.app;
import android.content.Context;
import android.content.res.Resources;
@ -12,12 +12,12 @@ import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import io.noties.markwon.syntax.Prism4jThemeDarkula;
import io.noties.markwon.syntax.Prism4jThemeDefault;
import okhttp3.Cache;
import okhttp3.OkHttpClient;
import ru.noties.markwon.syntax.Prism4jThemeDarkula;
import ru.noties.markwon.syntax.Prism4jThemeDefault;
import ru.noties.prism4j.Prism4j;
import ru.noties.prism4j.annotations.PrismBundle;
import io.noties.prism4j.Prism4j;
import io.noties.prism4j.annotations.PrismBundle;
@Module
@PrismBundle(includeAll = true)

View File

@ -1,18 +1,20 @@
package ru.noties.markwon;
package io.noties.markwon.app;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.Spanned;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import javax.inject.Inject;
import ru.noties.debug.Debug;
import io.noties.debug.Debug;
import io.noties.markwon.Markwon;
public class MainActivity extends Activity {

View File

@ -1,4 +1,4 @@
package ru.noties.markwon;
package io.noties.markwon.app;
import dagger.Subcomponent;

View File

@ -1,13 +1,14 @@
package ru.noties.markwon;
package io.noties.markwon.app;
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
@ -20,11 +21,11 @@ import java.util.concurrent.Future;
import javax.inject.Inject;
import io.noties.debug.Debug;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import ru.noties.debug.Debug;
@ActivityScope
public class MarkdownLoader {

View File

@ -1,35 +1,38 @@
package ru.noties.markwon;
package io.noties.markwon.app;
import android.content.Context;
import android.net.Uri;
import android.os.Handler;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.Spanned;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import javax.inject.Inject;
import ru.noties.debug.Debug;
import ru.noties.markwon.core.CorePlugin;
import ru.noties.markwon.ext.strikethrough.StrikethroughPlugin;
import ru.noties.markwon.ext.tables.TablePlugin;
import ru.noties.markwon.ext.tasklist.TaskListPlugin;
import ru.noties.markwon.gif.GifAwarePlugin;
import ru.noties.markwon.html.HtmlPlugin;
import ru.noties.markwon.image.ImagesPlugin;
import ru.noties.markwon.image.gif.GifPlugin;
import ru.noties.markwon.image.svg.SvgPlugin;
import ru.noties.markwon.syntax.Prism4jTheme;
import ru.noties.markwon.syntax.Prism4jThemeDarkula;
import ru.noties.markwon.syntax.Prism4jThemeDefault;
import ru.noties.markwon.syntax.SyntaxHighlightPlugin;
import ru.noties.markwon.urlprocessor.UrlProcessor;
import ru.noties.markwon.urlprocessor.UrlProcessorRelativeToAbsolute;
import ru.noties.prism4j.Prism4j;
import io.noties.debug.Debug;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.Markwon;
import io.noties.markwon.MarkwonConfiguration;
import io.noties.markwon.app.gif.GifAwarePlugin;
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin;
import io.noties.markwon.ext.tables.TablePlugin;
import io.noties.markwon.ext.tasklist.TaskListPlugin;
import io.noties.markwon.html.HtmlPlugin;
import io.noties.markwon.image.ImagesPlugin;
import io.noties.markwon.image.file.FileSchemeHandler;
import io.noties.markwon.image.network.OkHttpNetworkSchemeHandler;
import io.noties.markwon.syntax.Prism4jTheme;
import io.noties.markwon.syntax.Prism4jThemeDarkula;
import io.noties.markwon.syntax.Prism4jThemeDefault;
import io.noties.markwon.syntax.SyntaxHighlightPlugin;
import io.noties.markwon.urlprocessor.UrlProcessor;
import io.noties.markwon.urlprocessor.UrlProcessorRelativeToAbsolute;
import io.noties.prism4j.Prism4j;
@ActivityScope
public class MarkdownRenderer {
@ -94,10 +97,17 @@ public class MarkdownRenderer {
: prism4JThemeDarkula;
final Markwon markwon = Markwon.builder(context)
.usePlugin(CorePlugin.create())
.usePlugin(ImagesPlugin.createWithAssets(context))
.usePlugin(SvgPlugin.create(context.getResources()))
.usePlugin(GifPlugin.create(false))
.usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
@Override
public void configureImages(@NonNull ImagesPlugin plugin) {
// data uri scheme handler is added automatically
// SVG & GIF will be added if required dependencies are present in the classpath
// default-media-decoder is also added automatically
plugin
.addSchemeHandler(OkHttpNetworkSchemeHandler.create())
.addSchemeHandler(FileSchemeHandler.createWithAssets(context.getAssets()));
}
}))
.usePlugin(SyntaxHighlightPlugin.create(prism4j, prism4jTheme))
.usePlugin(GifAwarePlugin.create(context))
.usePlugin(TablePlugin.create(context))

View File

@ -1,8 +1,9 @@
package ru.noties.markwon;
package io.noties.markwon.app;
import android.content.Context;
import android.content.SharedPreferences;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import javax.inject.Inject;
import javax.inject.Singleton;

View File

@ -1,7 +1,8 @@
package ru.noties.markwon;
package io.noties.markwon.app;
import android.net.Uri;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
@SuppressWarnings("WeakerAccess")
public interface UriProcessor {

View File

@ -1,7 +1,8 @@
package ru.noties.markwon;
package io.noties.markwon.app;
import android.net.Uri;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import java.util.List;

View File

@ -1,11 +1,12 @@
package ru.noties.markwon;
package io.noties.markwon.app;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import ru.noties.markwon.urlprocessor.UrlProcessor;
import ru.noties.markwon.urlprocessor.UrlProcessorRelativeToAbsolute;
import androidx.annotation.NonNull;
import io.noties.markwon.urlprocessor.UrlProcessor;
import io.noties.markwon.urlprocessor.UrlProcessorRelativeToAbsolute;
class UrlProcessorInitialReadme implements UrlProcessor {

View File

@ -1,9 +1,10 @@
package ru.noties.markwon;
package io.noties.markwon.app;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.view.View;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
@SuppressWarnings("WeakerAccess")
public abstract class Views {

View File

@ -1,15 +1,16 @@
package ru.noties.markwon.gif;
package io.noties.markwon.app.gif;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.noties.markwon.image.AsyncDrawable;
import io.noties.markwon.image.AsyncDrawableLoader;
import io.noties.markwon.image.ImageSize;
import io.noties.markwon.image.ImageSizeResolver;
import pl.droidsonroids.gif.GifDrawable;
import ru.noties.markwon.image.AsyncDrawableLoader;
import ru.noties.markwon.image.ImageSize;
import ru.noties.markwon.image.ImageSizeResolver;
import ru.noties.markwon.image.AsyncDrawable;
public class GifAwareAsyncDrawable extends AsyncDrawable {

View File

@ -1,21 +1,20 @@
package ru.noties.markwon.gif;
package io.noties.markwon.app.gif;
import android.content.Context;
import android.support.annotation.NonNull;
import android.widget.TextView;
import androidx.annotation.NonNull;
import org.commonmark.node.Image;
import ru.noties.markwon.AbstractMarkwonPlugin;
import ru.noties.markwon.MarkwonConfiguration;
import ru.noties.markwon.MarkwonSpansFactory;
import ru.noties.markwon.R;
import ru.noties.markwon.RenderProps;
import ru.noties.markwon.SpanFactory;
import ru.noties.markwon.image.AsyncDrawableSpan;
import ru.noties.markwon.image.ImageProps;
import ru.noties.markwon.image.ImagesPlugin;
import ru.noties.markwon.priority.Priority;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.MarkwonConfiguration;
import io.noties.markwon.MarkwonSpansFactory;
import io.noties.markwon.RenderProps;
import io.noties.markwon.SpanFactory;
import io.noties.markwon.app.R;
import io.noties.markwon.image.AsyncDrawableSpan;
import io.noties.markwon.image.ImageProps;
public class GifAwarePlugin extends AbstractMarkwonPlugin {
@ -59,12 +58,6 @@ public class GifAwarePlugin extends AbstractMarkwonPlugin {
});
}
@NonNull
@Override
public Priority priority() {
return Priority.after(ImagesPlugin.class);
}
@Override
public void afterSetText(@NonNull TextView textView) {
processor.process(textView);

View File

@ -1,4 +1,4 @@
package ru.noties.markwon.gif;
package io.noties.markwon.app.gif;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
@ -6,9 +6,10 @@ import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class GifPlaceholder extends Drawable {

View File

@ -1,15 +1,16 @@
package ru.noties.markwon.gif;
package io.noties.markwon.app.gif;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.Spannable;
import android.text.Spanned;
import android.text.style.ClickableSpan;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.noties.markwon.image.AsyncDrawableSpan;
import pl.droidsonroids.gif.GifDrawable;
import ru.noties.markwon.image.AsyncDrawableSpan;
public abstract class GifProcessor {

View File

@ -8,11 +8,11 @@
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dip"
android:clipToPadding="false"
android:layout_marginTop="?android:attr/actionBarSize"
android:clipChildren="false"
android:scrollbarStyle="outsideOverlay"
android:layout_marginTop="?android:attr/actionBarSize">
android:clipToPadding="false"
android:padding="16dip"
android:scrollbarStyle="outsideOverlay">
<TextView
android:id="@+id/text"
@ -20,7 +20,6 @@
android:layout_height="wrap_content"
android:lineSpacingExtra="2dip"
android:textSize="16sp"
tools:context="ru.noties.markwon.MainActivity"
tools:text="yo\nman" />
</ScrollView>

View File

@ -4,8 +4,8 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.2'
classpath 'com.github.ben-manes:gradle-versions-plugin:0.20.0'
classpath 'com.android.tools.build:gradle:3.4.1'
classpath 'com.github.ben-manes:gradle-versions-plugin:0.21.0'
}
}
@ -29,8 +29,8 @@ task clean(type: Delete) {
delete rootProject.buildDir
}
task wrapper(type: Wrapper) {
gradleVersion '4.10.2'
wrapper {
gradleVersion '5.1.1'
distributionType 'all'
}
@ -53,29 +53,29 @@ ext {
'push-aar-gradle': 'https://raw.githubusercontent.com/noties/gradle-mvn-push/master/gradle-mvn-push-aar.gradle'
]
final def supportVersion = '28.0.0'
final def commonMarkVersion = '0.12.1'
final def daggerVersion = '2.10'
deps = [
'support-annotations' : "com.android.support:support-annotations:$supportVersion",
'support-app-compat' : "com.android.support:appcompat-v7:$supportVersion",
'support-recycler-view' : "com.android.support:recyclerview-v7:$supportVersion",
'x-annotations' : 'androidx.annotation:annotation:1.1.0',
'x-recycler-view' : 'androidx.recyclerview:recyclerview:1.0.0',
'commonmark' : "com.atlassian.commonmark:commonmark:$commonMarkVersion",
'commonmark-strikethrough': "com.atlassian.commonmark:commonmark-ext-gfm-strikethrough:$commonMarkVersion",
'commonmark-table' : "com.atlassian.commonmark:commonmark-ext-gfm-tables:$commonMarkVersion",
'android-svg' : 'com.caverock:androidsvg:1.2.1',
'android-svg' : 'com.caverock:androidsvg:1.4',
'android-gif' : 'pl.droidsonroids.gif:android-gif-drawable:1.2.14',
'jlatexmath-android' : 'ru.noties:jlatexmath-android:0.1.0',
'okhttp' : 'com.squareup.okhttp3:okhttp:3.9.0',
'prism4j' : 'ru.noties:prism4j:1.1.0',
'debug' : 'ru.noties:debug:3.0.0@jar',
'adapt' : 'ru.noties:adapt:1.1.0',
'dagger' : "com.google.dagger:dagger:$daggerVersion"
'prism4j' : 'io.noties:prism4j:2.0.0',
'debug' : 'io.noties:debug:5.0.0@jar',
'adapt' : 'io.noties:adapt:2.0.0',
'dagger' : "com.google.dagger:dagger:$daggerVersion",
'picasso' : 'com.squareup.picasso:picasso:2.71828',
'glide' : 'com.github.bumptech.glide:glide:4.9.0'
]
deps['annotationProcessor'] = [
'prism4j-bundler': 'ru.noties:prism4j-bundler:1.1.0',
'prism4j-bundler': 'io.noties:prism4j-bundler:2.0.0',
'dagger-compiler': "com.google.dagger:dagger-compiler:$daggerVersion"
]

View File

@ -1,4 +1,4 @@
// this is a generated file, do not modify. To update it run 'collectArtifacts.js' script
const artifacts = [{"id":"core","name":"Core","group":"ru.noties.markwon","description":"Core Markwon artifact that includes basic markdown parsing and rendering"},{"id":"ext-latex","name":"LaTeX","group":"ru.noties.markwon","description":"Extension to add LaTeX formulas to Markwon markdown"},{"id":"ext-strikethrough","name":"Strikethrough","group":"ru.noties.markwon","description":"Extension to add strikethrough markup to Markwon markdown"},{"id":"ext-tables","name":"Tables","group":"ru.noties.markwon","description":"Extension to add tables markup (GFM) to Markwon markdown"},{"id":"ext-tasklist","name":"Task List","group":"ru.noties.markwon","description":"Extension to add task lists (GFM) to Markwon markdown"},{"id":"html","name":"HTML","group":"ru.noties.markwon","description":"Provides HTML parsing functionality"},{"id":"image-gif","name":"Image GIF","group":"ru.noties.markwon","description":"Adds GIF media support to Markwon markdown"},{"id":"image-okhttp","name":"Image OkHttp","group":"ru.noties.markwon","description":"Adds OkHttp client to retrieve images data from network"},{"id":"image-svg","name":"Image SVG","group":"ru.noties.markwon","description":"Adds SVG media support to Markwon markdown"},{"id":"recycler","name":"Recycler","group":"ru.noties.markwon","description":"Provides RecyclerView.Adapter to display Markwon markdown"},{"id":"recycler-table","name":"Recycler Table","group":"ru.noties.markwon","description":"Provides MarkwonAdapter.Entry to render TableBlocks inside Android-native TableLayout widget"},{"id":"syntax-highlight","name":"Syntax Highlight","group":"ru.noties.markwon","description":"Add syntax highlight to Markwon markdown via Prism4j library"}];
const artifacts = [{"id":"core","name":"Core","group":"io.noties.markwon","description":"Core Markwon artifact that includes basic markdown parsing and rendering"},{"id":"ext-latex","name":"LaTeX","group":"io.noties.markwon","description":"Extension to add LaTeX formulas to Markwon markdown"},{"id":"ext-strikethrough","name":"Strikethrough","group":"io.noties.markwon","description":"Extension to add strikethrough markup to Markwon markdown"},{"id":"ext-tables","name":"Tables","group":"io.noties.markwon","description":"Extension to add tables markup (GFM) to Markwon markdown"},{"id":"ext-tasklist","name":"Task List","group":"io.noties.markwon","description":"Extension to add task lists (GFM) to Markwon markdown"},{"id":"html","name":"HTML","group":"io.noties.markwon","description":"Provides HTML parsing functionality"},{"id":"image","name":"Image","group":"io.noties.markwon","description":"Markwon image loading module (with optional GIF and SVG support)"},{"id":"image-glide","name":"Image Glide","group":"io.noties.markwon","description":"Markwon image loading module (based on Glide library)"},{"id":"image-picasso","name":"Image Picasso","group":"io.noties.markwon","description":"Markwon image loading module (based on Picasso library)"},{"id":"linkify","name":"Linkify","group":"io.noties.markwon","description":"Markwon plugin to linkify text (based on Android Linkify)"},{"id":"recycler","name":"Recycler","group":"io.noties.markwon","description":"Provides RecyclerView.Adapter to display Markwon markdown"},{"id":"recycler-table","name":"Recycler Table","group":"io.noties.markwon","description":"Provides MarkwonAdapter.Entry to render TableBlocks inside Android-native TableLayout widget"},{"id":"simple-ext","name":"Simple Extension","group":"io.noties.markwon","description":"Custom extension based on simple delimiter usage"},{"id":"syntax-highlight","name":"Syntax Highlight","group":"io.noties.markwon","description":"Add syntax highlight to Markwon markdown via Prism4j library"}];
export { artifacts };

View File

@ -0,0 +1,4 @@
// this is a generated file, do not modify. To update it run 'collectArtifacts.js' script
const artifacts = [{"id":"core","name":"Core","group":"ru.noties.markwon","description":"Core Markwon artifact that includes basic markdown parsing and rendering"},{"id":"ext-latex","name":"LaTeX","group":"ru.noties.markwon","description":"Extension to add LaTeX formulas to Markwon markdown"},{"id":"ext-strikethrough","name":"Strikethrough","group":"ru.noties.markwon","description":"Extension to add strikethrough markup to Markwon markdown"},{"id":"ext-tables","name":"Tables","group":"ru.noties.markwon","description":"Extension to add tables markup (GFM) to Markwon markdown"},{"id":"ext-tasklist","name":"Task List","group":"ru.noties.markwon","description":"Extension to add task lists (GFM) to Markwon markdown"},{"id":"html","name":"HTML","group":"ru.noties.markwon","description":"Provides HTML parsing functionality"},{"id":"image-gif","name":"Image GIF","group":"ru.noties.markwon","description":"Adds GIF media support to Markwon markdown"},{"id":"image-okhttp","name":"Image OkHttp","group":"ru.noties.markwon","description":"Adds OkHttp client to retrieve images data from network"},{"id":"image-svg","name":"Image SVG","group":"ru.noties.markwon","description":"Adds SVG media support to Markwon markdown"},{"id":"recycler","name":"Recycler","group":"ru.noties.markwon","description":"Provides RecyclerView.Adapter to display Markwon markdown"},{"id":"recycler-table","name":"Recycler Table","group":"ru.noties.markwon","description":"Provides MarkwonAdapter.Entry to render TableBlocks inside Android-native TableLayout widget"},{"id":"syntax-highlight","name":"Syntax Highlight","group":"ru.noties.markwon","description":"Add syntax highlight to Markwon markdown via Prism4j library"}];
export { artifacts };

View File

@ -29,7 +29,7 @@
</template>
<script>
import { artifacts } from "../.artifacts.js";
import { artifacts } from "../.artifacts.v3.js";
if (!artifacts) {
throw "Artifacts not found. Use `collectArtifacts.js` script to obtain artifacts metadata.";

View File

@ -0,0 +1,105 @@
<template>
<div>
<div class="artifact-container">
<div v-for="artifact in artifacts" class="artifact" @click="toggleSelection(artifact)">
<div class="artifact-header">
<input type="checkbox" v-model="selected" :value="artifact.id" :id="artifact.id">
<strong>
<label :for="artifact.id">{{artifact.name}}</label>
</strong>
</div>
<div class="artifact-description" v-if="artifact.description">{{artifact.description}}</div>
</div>
</div>
<div class="extra-class language-gradle selected-artifacts" v-if="selected.length > 0">
<div class="selected-artifact-script">
<span class="token keyword">final def</span>
<span>&nbsp;markwon_version =&nbsp;</span>
<span class="token string">'{{latestVersion}}'</span>
</div>
<br>
<div class="selected-artifact-script" v-for="artifact in selectedArtifacts">
<span>implementation&nbsp;</span>
<span class="token string">"{{artifact.group}}:{{artifact.id}}:</span>
<span>$markwon_version</span>
<span class="token string">"</span>
</div>
</div>
</div>
</template>
<script>
import { artifacts } from "../.artifacts.js";
if (!artifacts) {
throw "Artifacts not found. Use `collectArtifacts.js` script to obtain artifacts metadata.";
}
export default {
name: "ArtifactPicker",
data() {
return {
artifacts,
selected: ["core"],
latestVersion: "latest_version"
};
},
methods: {
toggleSelection(artifact) {
const index = this.selected.indexOf(artifact.id);
if (index < 0) {
this.selected.push(artifact.id);
} else {
this.selected.splice(index, 1);
}
}
},
computed: {
selectedArtifacts() {
return this.artifacts.filter(a => this.selected.indexOf(a.id) >= 0);
}
}
};
</script>
<style scoped>
.artifact-container {
display: flex;
flex-wrap: wrap;
flex-direction: row;
margin-top: 0.5em;
}
.artifact {
flex: 1;
border: 1px #ccc solid;
background-color: #fafafa;
padding: 0.5em;
margin: 0.2em;
border-radius: 0.25em;
min-width: 10em;
max-width: 10em;
}
.artifact-description {
font-size: 0.85em;
margin-top: 0.5em;
}
.selected-artifacts {
color: white;
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
padding: 16px;
text-align: left;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
hyphens: none;
font-size: 0.85em;
margin-top: 0.5em;
}
.selected-artifact-script {
display: flex;
flex-wrap: wrap;
}
</style>

View File

@ -0,0 +1,57 @@
<template>
<div class="awesome-group">
<div v-for="app in apps" class="awesome-app">
<a :href="app.link" class="awesome-app-name" target="_blank" rel="noopener noreferrer">{{app.name}}&nbsp;<OutboundLink/></a>
<img class="awesome-app-image" :src="app.image">
<span v-if="app.description" class="awesome-app-description">{{ app.description }}</span>
</div>
</div>
</template>
<script>
import Link from './Link.vue'
export default {
name: "AwesomeGroup",
props: ["apps"],
components: {
Link
}
};
</script>
<style scoped>
.awesome-group {
display: flex;
flex-wrap: wrap;
}
.awesome-app {
padding: 1em;
background-color: #fff;
margin: 0.25em;
border-radius: 0.25em;
box-shadow: 0 0 0.1em 0.1em #eee;
max-width: 30%;
min-width: 100px;
display: flex;
align-items: center;
flex-direction: column;
}
.awesome-app-name {
font-size: 1em;
font-weight: 500;
margin-bottom: 0.5em;
}
.awesome-app-image {
width: 96px;
height: 96px;
display: block;
}
.awesome-app-description {
margin-top: 1em;
font-size: 0.85em;
}
</style>

View File

@ -0,0 +1,24 @@
<template>
<a :href="mavenSearchUrl()"><img :src="shieldImgageUrl()" :alt="displayLabel"></a>
</template>
<script>
export default {
name: 'MavenBadge',
props: ['artifact', 'label'],
methods: {
mavenSearchUrl: function() {
return `http://search.maven.org/#search|ga|1|g%3A%22io.noties.markwon%22%20AND%20a%3A%22${this.artifact}%22`;
},
shieldImgageUrl: function() {
return `https://img.shields.io/maven-central/v/io.noties.markwon/${this.artifact}.svg?label=${this.displayLabel}`;
}
},
computed: {
displayLabel() {
return this.label || this.artifact;
}
}
}
</script>

View File

@ -12,16 +12,16 @@ module.exports = {
],
themeConfig: {
nav: [
{ text: 'Install', link: '/docs/v3/install.md' },
{ text: 'Changelog', link: '/CHANGELOG.md' },
{ text: 'Install', link: '/docs/v4/install.md' },
{
text: 'API Version',
items: [
{ text: 'Current (3.x.x)', link: '/' },
{ 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: 'Sandbox', link: '/sandbox.md' },
{ text: 'Changelog', link: 'https://github.com/noties/Markwon/blob/master/CHANGELOG.md' },
{ text: 'Github', link: 'https://github.com/noties/Markwon' }
],
sidebar: {
@ -36,8 +36,8 @@ module.exports = {
'/docs/v2/html.md',
'/docs/v2/view.md'
],
'/': [
'',
'/docs/v3': [
'/docs/v3/install.md',
{
title: 'Core',
collapsable: false,
@ -67,8 +67,41 @@ module.exports = {
'/docs/v3/recycler-table/',
'/docs/v3/syntax-highlight/',
'/docs/v3/migration-2-3.md'
],
'/': [
'',
{
title: 'Core',
collapsable: false,
children: [
'/docs/v4/core/getting-started.md',
'/docs/v4/core/plugins.md',
'/docs/v4/core/registry.md',
'/docs/v4/core/theme.md',
'/docs/v4/core/configuration.md',
'/docs/v4/core/visitor.md',
'/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/ext-latex/',
'/docs/v4/ext-strikethrough/',
'/docs/v4/ext-tables/',
'/docs/v4/ext-tasklist/',
'/docs/v4/html/',
'/docs/v4/image/',
'/docs/v4/image-glide/',
'/docs/v4/image-picasso/',
'/docs/v4/linkify/',
'/docs/v4/recycler/',
'/docs/v4/recycler-table/',
'/docs/v4/simple-ext/',
'/docs/v4/syntax-highlight/',
'/docs/v4/recipes.md'
],
},
sidebarDepth: 2,
lastUpdated: true
}

View File

@ -1,132 +0,0 @@
# Changelog
# 3.0.1
* Add `AsyncDrawableLoader.Builder#implementation` method (<GithubIssue id="109" />)
* AsyncDrawable allow placeholder to have independent size (<GithubIssue id="115" />)
* `addFactory` method for MarkwonSpansFactory
* Add optional spans for list blocks (bullet and ordered)
* AsyncDrawable placeholder bounds fix
* SpannableBuilder setSpans allow array of arrays
* Add `requireFactory` method to MarkwonSpansFactory
* Add DrawableUtils
## 3.0.0
* Plugins, plugins, plugins
* Split basic functionality blocks into standalone modules
* Maven artifacts group changed to `ru.noties.markwon` (previously had been `ru.noties`)
* removed `markwon`, `markwon-image-loader`, `markwon-html-pareser-api`, `markwon-html-parser-impl`, `markwon-view` modules
* new module system: `core`, `ext-latex`, `ext-strikethrough`, `ext-tables`, `ext-tasklist`, `html`, `image-gif`, `image-okhttp`, `image-svg`, `recycler`, `recycler-table`, `syntax-highlight`
* Add BufferType option for Markwon configuration
* Fix typo in AsyncDrawable waitingForDimensions
* New tests format
* `Markwon.render` returns `Spanned` instance of generic `CharSequence`
* LinkMovementMethod is applied implicitly if not set on a TextView explicitly
* Split code and codeBlock spans and factories
* Add CustomTypefaceSpan
* Add NoCopySpansFactory
* Add placeholder to image loading
Generally speaking there are a lot of changes. Most of them are not backwards-compatible.
The main point of this release is the `Plugin` system that allows more fluent configuration
and opens the possibility of extending `Markwon` with 3rd party functionality in a simple
and intuitive fashion. Please refer to the [documentation web-site](https://noties.github.io/Markwon)
that has information on how to start migration.
The shortest excerpt of this release can be expressed like this:
```java
// previous v2.x.x way
Markwon.setMarkdown(textView, "**Hello there!**");
```
```java
// 3.x.x
Markwon.create(context)
.setMarkdown(textView, "**Hello there!**");
```
But there is much more to it, please visit documentation web-site
to get the full picture of latest changes.
## 2.0.1
* `SpannableMarkdownVisitor` Rename blockQuoteIndent to blockIndent
* Fixed block new lines logic for block quote and paragraph (<GithubIssue id="82" />)
* AsyncDrawable fix no dimensions bug (<GithubIssue id="81" />)
* Update SpannableTheme to use Px instead of Dimension annotation
* Allow TaskListSpan isDone mutation
* Updated commonmark-java to 0.12.1
* Add OrderedListItemSpan measure utility method (<GithubIssue id="78" />)
* Add SpannableBuilder#getSpans method
* Fix DataUri scheme handler in image-loader (<GithubIssue id="74" />)
* Introduced a "copy" builder for SpannableThem <br>Thanks <GithubUser name="c-b-h" />
## 2.0.0
* Add `html-parser-api` and `html-parser-impl` modules
* Add `HtmlEmptyTagReplacement`
* Implement Appendable and CharSequence in SpannableBuilder
* Renamed library modules to reflect maven artifact names
* Rename `markwon-syntax` to `markwon-syntax-highlight`
* Add HtmlRenderer asbtraction
* Add CssInlineStyleParser
* Fix Theme#listItemColor and OL
* Fix task list block parser to revert parsing state when line is not matching
* Defined test format files
* image-loader add datauri parser
* image-loader add support for inline data uri image references
* Add travis configuration
* Fix image with width greater than canvas scaled
* Fix blockquote span
* Dealing with white spaces at the end of a document
* image-loader add SchemeHandler abstraction
* Add sample-latex-math module
## v1.1.1
* Fix OrderedListItemSpan text position (baseline) (<GithubIssue id="55" />)
* Add softBreakAddsNewLine option for SpannableConfiguration (<GithubIssue id="54" />)
* Paragraph text can now explicitly be spanned (<GithubPull id="58" />)<br>Thanks to <GithubUser name="c-b-h" />
* Fix table border color if odd background is specified (<GithubIssue id="56" />)
* Add table customizations (even and header rows)
## v1.1.0
* Update commonmark to 0.11.0 and android-gif to 1.2.14
* Add syntax highlight functionality (`library-syntax` module and `markwon-syntax` artifact)
* Add headingTypeface, headingTextSizes to SpannableTheme<br>Thanks to <GithubUser name="edenman" />
* Introduce `MediaDecoder` abstraction to `image-loader` module
* Introduce `SpannableFactory`<br>Thanks for idea to <GithubUser name="c-b-h" />
* Update sample application to use syntax-highlight
* Update sample application to use clickable placeholder for GIF media
## v1.0.6
* Fix bullet list item size (depend on text size and not top-bottom arguments)
* Add ability to specify MovementMethod when applying markdown to a TextView
* Markdown images size is also resolved via ImageSizeResolver
* Moved `ImageSize`, `ImageSizeResolver` and `ImageSizeResolverDef`
to `ru.noties.markwon.renderer` package (one level up, previously `ru.noties.markwon.renderer.html`)
## v1.0.5
* Change LinkSpan to extend URLSpan. Allow default linkColor (if not set explicitly)
* Fit an image without dimensions to canvas width (and keep ratio)
* Add support for separate color for code blocks (<GithubPull id="37" />)<br>Thanks to <GithubUser name="Arcnor" />
## v1.0.4
* Fixes <GithubIssue id="28"/> (tables are not rendered when at the end of the markdown)
* Adds support for `indented code blocks`<br>Thanks to <GithubUser name="dlew"/>
## v1.0.3
* Fixed ordered lists (when number width is greater than block margin)
## v1.0.2
* Fixed additional white spaces at the end of parsed markdown
* Fixed headings with no underline (levels 1 &amp; 2)
* Tables can have no borders
## v1.0.1
* Support for task-lists (<GithubIssue id="2" />)
* Spans now are applied in reverse order (<GithubIssue id="5" /> <GithubIssue id="10" />)
* Added `SpannableBuilder` to follow the reverse order of spans
* Updated `commonmark-java` to `0.10.0`
* Fixes <GithubIssue id="1" />
## v1.0.0
Initial release

View File

@ -5,7 +5,7 @@ title: 'Introduction'
<img :src="$withBase('/art/markwon_logo.png')" alt="Markwon Logo" width="50%">
<br><br>
[![markwon](https://img.shields.io/maven-central/v/ru.noties.markwon/core.svg?label=markwon)](http://search.maven.org/#search|ga|1|g%3A%22ru.noties.markwon%22%20)
[![markwon](https://img.shields.io/maven-central/v/io.noties.markwon/core.svg?label=markwon)](http://search.maven.org/#search|ga|1|g%3A%22io.noties.markwon%22%20)
[![Build Status](https://travis-ci.org/noties/Markwon.svg?branch=master)](https://travis-ci.org/noties/Markwon)
**Markwon** is a markdown library for Android. It parses markdown following
@ -27,17 +27,17 @@ listed in <Link name="commonmark-spec" /> are supported (including support for *
* Strong emphasis (`**`, `__`)
* Headers (`#{1,6}`)
* Links (`[]()` && `[][]`)
* [Images](/docs/v3/core/images.md)
* [Images](/docs/v4/image/)
* Thematic break (`---`, `***`, `___`)
* Quotes & nested quotes (`>{1,}`)
* Ordered & non-ordered lists & nested ones
* Inline code
* Code blocks
* [Strike-through](/docs/v3/ext-strikethrough/) (`~~`)
* [Tables](/docs/v3/ext-tables/) (*with limitations*)
* [Syntax highlight](/docs/v3/syntax-highlight/)
* [LaTeX](/docs/v3/ext-latex/) formulas
* [HTML](/docs/v3/html/)
* [Strike-through](/docs/v4/ext-strikethrough/) (`~~`)
* [Tables](/docs/v4/ext-tables/) (*with limitations*)
* [Syntax highlight](/docs/v4/syntax-highlight/)
* [LaTeX](/docs/v4/ext-latex/) formulas
* [HTML](/docs/v4/html/)
* Emphasis (`<i>`, `<em>`, `<cite>`, `<dfn>`)
* Strong emphasis (`<b>`, `<strong>`)
* SuperScript (`<sup>`)
@ -49,9 +49,9 @@ listed in <Link name="commonmark-spec" /> are supported (including support for *
* Images (`img` will require configured image loader)
* Blockquote (`blockquote`)
* Heading (`h1`, `h2`, `h3`, `h4`, `h5`, `h6`)
* there is support to render any HTML tag, but it will require to create a special `TagHandler`,
more information can be found in [HTML section](/docs/v3/core/html-renderer.md)
* [Task lists](/docs/v3/ext-tasklist/):
* there is support to render any HTML/XML tag, but it will require to create a special `TagHandler`,
more information can be found in [HTML section](/docs/v4/html/#taghandler)
* [Task lists](/docs/v4/ext-tasklist/):
<ul style="list-style-type: none; margin: 0; padding: 0;">
<li><input type="checkbox" disabled>Not <i>done</i></li>
<li><input type="checkbox" disabled checked><strong>Done</strong> with <code>X</code></li>
@ -82,6 +82,9 @@ and 2 themes included: Light &amp; Dark. It can be downloaded from [releases](ht
* [FairNote](https://play.google.com/store/apps/details?id=com.rgiskard.fairnote) - simple and intuitive notepad app. It gives you speed and efficiency when you write notes, to-do lists, e-mails, or jot down quick ideas.
* [Boxcryptor](https://www.boxcryptor.com) - A software that adds AES-256 and RSA encryption to Dropbox, Google Drive, OneDrive and many other clouds.
<AwesomeGroup :apps="[
{name: 'Cinopsys: Movies and Shows', image: 'http://drive.google.com/uc?export=view&id=1rD0HLd8tDUCe8QcVEG_iGvsJbFyozRhC', link: 'https://play.google.com/store/apps/details?id=com.cinopsys.movieshows'}
]" />
<u>Extension/plugins</u>:

View File

@ -7,6 +7,8 @@ title: 'Overview'
<br><br>
<MavenBadges2xx/>
<LegacyWarning />
**Markwon** is a markdown library for Android. It parses markdown following
<Link name="commonmark-spec" /> with the help of amazing <Link name="commonmark-java" /> library
and renders result as _Android-native_ Spannables. **No HTML** is involved

View File

@ -1,5 +1,7 @@
# Configuration
<LegacyWarning />
`SpannableConfiguration` is the core component that controls how markdown is parsed and rendered.
It can be obtained via factory methods:

View File

@ -1,5 +1,7 @@
# Factory <Badge text="1.1.0" />
<LegacyWarning />
`SpannableFactory` is used to create Span implementations.
```java

View File

@ -1,5 +1,7 @@
# Getting started
<LegacyWarning />
## Quick one
This is the most simple way to set markdown to a `TextView` or any of its siblings:

View File

@ -1,5 +1,7 @@
# HTML <Badge text="2.0.0" />
<LegacyWarning />
Starting with version `2.0.0` `Markwon` brings the whole HTML parsing/rendering
stack _on-site_. The main reason for this are _special_ definitions of HTML nodes
by <Link name="commonmark-spec" />. More specifically: <Link name="commonmark-spec#inline" displayName="inline" />

View File

@ -1,5 +1,7 @@
# Images
<LegacyWarning />
By default `Markwon` doesn't handle images. Although `AsyncDrawable.Loader` is
defined in main artifact, it does not provide implementation.

View File

@ -1,5 +1,7 @@
# Installation
<LegacyWarning />
<MavenBadges2xx />
In order to start using `Markwon` add this to your dependencies block

View File

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

View File

@ -1,5 +1,7 @@
# Theme
<LegacyWarning />
Here is the list of properties that can be configured via `SpannableTheme`. If you wish to control what
is out of this list, you can use [SpannableFactory](/docs/v2/factory.md)
abstraction which lets you to gather full control of Spans that are used to display markdown.

View File

@ -1,5 +1,7 @@
# MarkwonView
<LegacyWarning />
<MavenBadge2xx artifact="markwon-view" />
This is simple library containing 2 views that are able to display markdown:

View File

@ -56,7 +56,7 @@ myTableWidget.setTable(table);
:::tip
To take advantage of this functionality and render tables without limitations (including
horizontally scrollable layout when its contents exceed screen width), refer to [recycler-table](/docs/v3/recycler-table/)
horizontally scrollable layout when its contents exceed screen width), refer to [recycler-table](/docs/v4/recycler-table/)
module documentation that adds support for rendering `TableBlock` markdown node inside Android-native `TableLayout` widget.
:::

View File

@ -0,0 +1,168 @@
# Configuration
`MarkwonConfiguration` class holds common Markwon functionality.
These are _configurable_ properties:
* `AsyncDrawableLoader` (back here since <Badge text="4.0.0" />)
* `SyntaxHighlight`
* `LinkResolver` (since <Badge text="4.0.0" />, before &mdash; `LinkSpan.Resolver`)
* `UrlProcessor`
* `ImageSizeResolver`
:::tip
Additionally `MarkwonConfiguration` holds:
* `MarkwonTheme`
* `MarkwonSpansFactory`
Please note that these values can be retrieved from `MarkwonConfiguration`
instance, but their _configuration_ must be done by a `Plugin` by overriding
one of the methods:
* `Plugin#configureTheme`
* `Plugin#configureSpansFactory`
:::
## AsyncDrawableLoader
Allows loading and displaying of images in markdown. Please note that if one is not specified
directly (or via plugin) no images will be displayed.
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
builder.asyncDrawableLoader(AsyncDrawableLoader.noOp());
}
})
.build();
```
Currently `Markwon` provides 3 implementations for loading images:
* [markwon implementation](/docs/v4/image/) with SVG, GIF, data uri and android_assets support
* [based on Picasso](/docs/v4/image-picasso/)
* [based on Glide](/docs/v4/image-glide/)
## SyntaxHighlight
```java
final Markwon markwon = Markwon.builder(this)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
builder.syntaxHighlight(new SyntaxHighlightNoOp());
}
})
.build();
```
:::tip
Use [syntax-highlight](/docs/v4/syntax-highlight/) to add syntax highlighting
to your application
:::
## LinkResolver
React to a link click event. By default `LinkResolverDef` is used,
which tries to start an Activity given the `link` argument. If no
Activity can handle `link` `LinkResolverDef` silently ignores click event
```java
final Markwon markwon = Markwon.builder(this)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
builder.linkResolver(new LinkResolver() {
@Override
public void resolve(@NonNull View view, @NonNull String link) {
// react to link click here
}
});
}
})
.build();
```
:::tip
Please note that `Markwon` will apply `LinkMovementMethod` to a resulting TextView
if there is none registered. if you wish to register own instance of a `MovementMethod`
apply it directly to a TextView or use [MovementMethodPlugin](/docs/v4/core/movement-method-plugin.md)
:::
## UrlProcessor
Process URLs in your markdown (for links and images). If not provided explicitly,
default **no-op** implementation will be used, which does not modify URLs (keeping them as-is).
`Markwon` provides 2 implementations of `UrlProcessor`:
* `UrlProcessorRelativeToAbsolute`
* `UrlProcessorAndroidAssets`
### UrlProcessorRelativeToAbsolute
`UrlProcessorRelativeToAbsolute` can be used to make relative URL absolute. For example if an image is
defined like this: `![img](./art/image.JPG)` and `UrlProcessorRelativeToAbsolute`
is created with `https://github.com/noties/Markwon/raw/master/` as the base:
`new UrlProcessorRelativeToAbsolute("https://github.com/noties/Markwon/raw/master/")`,
then final image will have `https://github.com/noties/Markwon/raw/master/art/image.JPG`
as the destination.
### UrlProcessorAndroidAssets
`UrlProcessorAndroidAssets` can be used to make processed links to point to Android assets folder.
So an image: `![img](./art/image.JPG)` will have `file:///android_asset/art/image.JPG` as the
destination.
:::tip
Please note that `UrlProcessorAndroidAssets` will process only URLs that have no `scheme` information,
so a `./art/image.png` will become `file:///android_asset/art/image.JPG` whilst `https://so.me/where.png`
will be kept as-is.
:::
## ImageSizeResolver
`ImageSizeResolver` controls the size of an image to be displayed.
```java
final Markwon markwon = Markwon.builder(this)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
builder.imageSizeResolver(new ImageSizeResolver() {
@NonNull
@Override
public Rect resolveImageSize(@NonNull AsyncDrawable drawable) {
final ImageSize imageSize = drawable.getImageSize();
return drawable.getResult().getBounds();
}
});
}
})
.build();
```
If not provided explicitly, default `ImageSizeResolverDef` implementation will be used.
It handles 3 dimension units:
* `%` (percent, relative to Canvas width)
* `em` (relative to text size)
* `px` (absolute size, every dimension that is not `%` or `em` is considered to be _absolute_)
```html
<img width="100%">
<img width="2em" height="10px">
<img style="{width: 100%; height: 8em;}">
```
`ImageSizeResolverDef` keeps the ratio of original image if one of the dimensions is missing.
:::warning Height%
There is no support for `%` units for `height` dimension. This is due to the fact that
height of an TextView in which markdown is displayed is non-stable and changes with time
(for example when image is loaded and applied to a TextView it will _increase_ TextView's height),
so we will have no point-of-reference from which to _calculate_ image height.
:::
:::tip
`ImageSizeResolverDef` also takes care for an image to **not** exceed
canvas width. If an image has greater width than a TextView Canvas, then
image will be _scaled-down_ to fit the canvas. Please note that this rule
applies only if image has no sizes specified (`ImageSize == null`).
:::

View File

@ -0,0 +1,141 @@
# Core plugin <Badge text="3.0.0" />
Since <Badge text="3.0.0" /> with introduction of _plugins_, Markwon
**core** functionality was moved to a dedicated plugin.
```java
CorePlugin.create();
```
## Node visitors
`CorePlugin` registers these `commonmark-java` node visitors:
* `Text`
* `StrongEmphasis`
* `Emphasis`
* `BlockQuote`
* `Code`
* `Image`
* `FencedCodeBlock`
* `IndentedCodeBlock`
* `BulletList`
* `OrderedList`
* `ListItem`
* `ThematicBreak`
* `Heading`
* `SoftLineBreak`
* `HardLineBreak`
* `Paragraph`
* `Link`
## Span factories
`CorePlugin` adds these `SpanFactory`s:
* `StrongEmphasis`
* `Emphasis`
* `BlockQuote`
* `Code`
* `FencedCodeBlock`
* `IndentedCodeBlock`
* `ListItem`
* `Heading`
* `Link`
* `ThematicBreak`
:::tip
By default `CorePlugin` does not register a `Paragraph` `SpanFactory` but
this can be done in your custom plugin:
```java
Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
builder.setFactory(Paragraph.class, (configuration, props) ->
new ForegroundColorSpan(Color.RED));
}
})
```
:::
## Props
These props are exported by `CorePlugin` and can be found in `CoreProps`:
* `Prop<ListItemType> LIST_ITEM_TYPE` (BULLET | ORDERED)
* `Prop<Integer> BULLET_LIST_ITEM_LEVEL`
* `Prop<Integer> ORDERED_LIST_ITEM_NUMBER`
* `Prop<Integer> HEADING_LEVEL`
* `Prop<String> LINK_DESTINATION`
* `Prop<Boolean> PARAGRAPH_IS_IN_TIGHT_LIST`
:::warning List item type
Before <Badge text="3.0.0" /> `Markwon` had 2 distinct lists (bullet and ordered).
Since <Badge text="3.0.0" /> a single `SpanFactory` is used, which internally checks
for `Prop<ListItemType> LIST_ITEM_TYPE`.
Beware of this if you would like to override only one of the list types. This is
done to correspond to `commonmark-java` implementation.
:::
More information about props can be found [here](/docs/v4/core/render-props.md)
---
:::tip Soft line break
Since <Badge text="3.0.0" /> Markwon core does not give an option to
insert a new line when there is a soft line break in markdown. Instead a
custom plugin can be used:
```java
final Markwon markwon = Markwon.builder(this)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
builder.on(SoftLineBreak.class, (visitor, softLineBreak) ->
visitor.forceNewLine());
}
})
.build();
```
:::
:::warning
Please note that `CorePlugin` will implicitly set a `LinkMovementMethod` on a TextView
if one is not present. If you wish to customize a MovementMethod that is used, apply
one manually to a TextView (before applying markdown) or use the [MovementMethodPlugin](/docs/v4/core/movement-method-plugin.md)
which accepts a MovementMethod as an argument.
:::
## OnTextAddedListener <Badge text="4.0.0"/>
Since `4.0.0` `CorePlugin` provides ability to receive text-added event. This can
be useful in order to process raw text (for example to [linkify](/docs/v4/linkify/) it):
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configure(@NonNull Registry registry) {
registry.require(CorePlugin.class, new Action<CorePlugin>() {
@Override
public void apply(@NonNull CorePlugin corePlugin) {
corePlugin.addOnTextAddedListener(new CorePlugin.OnTextAddedListener() {
@Override
public void onTextAdded(@NonNull MarkwonVisitor visitor, @NonNull String text, int start) {
// NB text is already added and you are __strongly__ adviced not to
// modify visitor here, but only add spans
//
// this will make all text BLUE
visitor.builder().setSpan(
new ForegroundColorSpan(Color.BLUE),
start,
visitor.length()
);
}
});
}
});
}
})
.build();
```

View File

@ -0,0 +1,52 @@
# Getting started
:::tip Installation
Please follow [installation](/docs/v4/install.md) instructions
to learn how to add `Markwon` to your project
:::
## Quick one
This is the most simple way to set markdown to a `TextView` or any of its siblings:
```java
// obtain an instance of Markwon
final Markwon markwon = Markwon.create(context);
// set markdown
markwon.setMarkdown(textView, "**Hello there!**");
```
The most simple way to obtain markdown to be applied _somewhere_ else:
```java
// obtain an instance of Markwon
final Markwon markwon = Markwon.create(context);
// parse markdown and create styled text
final Spanned markdown = markwon.toMarkdown("**Hello there!**");
// use it
Toast.makeText(context, markdown, Toast.LENGTH_LONG).show();
```
## Longer one
With explicit `parse` and `render` methods:
```java
// obtain an instance of Markwon
final Markwon markwon = Markwon.create(context);
// parse markdown to commonmark-java Node
final Node node = markwon.parse("Are **you** still there?");
// create styled text from parsed Node
final Spanned markdown = markwon.render(node);
// use it on a TextView
markwon.setParsedMarkdown(textView, markdown);
// or a Toast
Toast.makeText(context, markdown, Toast.LENGTH_LONG).show();
```

View File

@ -0,0 +1,17 @@
# 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
specific needs for a `MovementMethod` and `LinkMovementMethod` doesn't answer
your needs use `MovementMethodPlugin`:
```java
Markwon.builder(context)
.usePlugin(MovementMethodPlugin.create(ScrollingMovementMethod.getInstance()))
```
:::tip
If you are having trouble with system `LinkMovementMethod` as an alternative
[BetterLinkMovementMethod](https://github.com/saket/Better-Link-Movement-Method) library can be used.
:::

View File

@ -0,0 +1,361 @@
# Plugins <Badge text="3.0.0" />
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
own set of plugins.
To register a plugin `Markwon.Builder` must be used:
```java
Markwon.builder(context)
// @since 4.0.0 there is no need to register CorePlugin, as it's registered automatically
// .usePlugin(CorePlugin.create())
.usePlugin(MyPlugin.create())
.build();
```
All the process of transforming _raw_ markdown into a styled text (Spanned)
will go through plugins. A plugin can:
* [configure plugin registry](#registry)
* [configure commonmark-java `Parser`](#parser)
* [configure `MarkwonTheme`](#markwontheme)
* [configure `AsyncDrawableLoader` (used to display images in markdown)](#images)
* [configure `MarkwonConfiguration`](#configuration)
* [configure `MarkwonVisitor` (extensible commonmark-java Node visitor)](#visitor)
* [configure `MarkwonSpansFactory` (factory to hold spans information for each Node)](#spans-factory)
---
* [process raw input markdown before parsing it](#process-markdown)
* [inspect/modify commonmark-java Node after it's been parsed, but before rendering](#inspect-modify-node)
* [inspect commonmark-java Node after it's been rendered](#inspect-node-after-render)
* [prepare TextView to display markdown _before_ markdown is applied to a TextView](#prepare-textview)
* [post-process TextView _after_ markdown was applied](#textview-after-markdown-applied)
:::tip
if you need to override only few methods of `MarkwonPlugin` (since it is an interface),
`AbstractMarkwonPlugin` can be used.
:::
## Registry <Badge text="4.0.0" />
Registry is a special step to pre-configure all registered plugins. It is also
used to determine the order of plugins inside `Markwon` instance.
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configure(@NonNull Registry registry) {
final CorePlugin corePlugin = registry.require(CorePlugin.class);
// or
registry.require(CorePlugin.class, new Action<CorePlugin>() {
@Override
public void apply(@NonNull CorePlugin corePlugin) {
}
});
}
})
.build();
```
More information about registry can be found [here](/docs/v4/core/registry.md)
## Parser
For example, let's register a new commonmark-java Parser extension:
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureParser(@NonNull Parser.Builder builder) {
// no need to call `super.configureParser(builder)`
builder.extensions(Collections.singleton(StrikethroughExtension.create()));
}
})
.build();
```
There are no limitations on what to do with commonmark-java Parser. For more info
_what_ can be done please refer to <Link name="commonmark-java" displayName="commonmark-java documentation" />.
## MarkwonTheme
Starting <Badge text="3.0.0" /> `MarkwonTheme` represents _core_ theme. Aka theme for
things core module knows of. For example it doesn't know anything about `strikethrough`
or `tables` (as they belong to different modules).
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureTheme(@NonNull MarkwonTheme.Builder builder) {
builder
.codeTextColor(Color.BLACK)
.codeBackgroundColor(Color.GREEN);
}
})
.build();
```
:::tip
`CorePlugin` has special handling - it will be added automatically
when `Markwon.builder(Context)` method is used. If you wish to create
Markwon instance _without_ CorePlugin registered -
use `Markwon.builderNoCore(Context)` method instead
:::
More information about `MarkwonTheme` can be found [here](/docs/v4/core/theme.md).
## Configuration
`MarkwonConfiguration` is a set of common tools that are used by different parts
of `Markwon`. It allows configurations of these:
* `AsyncDrawableLoader` (image loading)
* `SyntaxHighlight` (highlighting code blocks)
* `LinkResolver` (opens links in markdown)
* `UrlProcessor` (process URLs in markdown for both links and images)
* `ImageSizeResolver` (resolve image sizes, like `fit-to-canvas`, etc)
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
builder.linkResolver(new LinkResolverDef());
}
})
.build();
```
More information about `MarkwonConfiguration` can be found [here](/docs/v4/core/configuration.md)
## Visitor
`MarkwonVisitor` <Badge text="3.0.0" /> is commonmark-java Visitor that allows
configuration of how each Node is visited. There is no longer need to create
own subclass of Visitor and override required methods (like in `2.x.x` versions).
`MarkwonVisitor` also allows registration of Nodes, that `core` module knows
nothing about (instead of relying on `visit(CustomNode)` method)).
For example, let's add `strikethrough` Node visitor:
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
// please note that strike-through parser extension must be registered
// in order to receive such callback
builder
.on(Strikethrough.class, new MarkwonVisitor.NodeVisitor<Strikethrough>() {
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull Strikethrough strikethrough) {
final int length = visitor.length();
visitor.visitChildren(strikethrough);
visitor.setSpansForNodeOptional(strikethrough, length);
}
});
}
})
.build();
```
:::tip
`MarkwonVisitor` also allows _overriding_ already registered nodes. For example,
you can disable `Heading` Node rendering:
```java
builder.on(Heading.class, null);
```
:::
More information about `MarkwonVisitor` can be found [here](/docs/v4/core/visitor.md)
## Spans Factory
`MarkwonSpansFactory` <Badge text="3.0.0" /> is an abstract factory (factory that produces other factories)
for spans that `Markwon` uses. It controls what spans to use for certain Nodes.
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
// override emphasis factory to make all emphasis nodes underlined
builder.setFactory(Emphasis.class, new SpanFactory() {
@Override
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
return new UnderlineSpan();
}
});
}
})
.build();
```
:::tip
`SpanFactory` allows to return an _array_ of spans to apply multiple spans
for a Node:
```java
@Override
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
// make underlined and set text color to red
return new Object[]{
new UnderlineSpan(),
new ForegroundColorSpan(Color.RED)
};
}
```
:::
More information about spans factory can be found [here](/docs/v4/core/spans-factory.md)
## Process markdown
A plugin can be used to _pre-process_ input markdown (this will be called before _parsing_):
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@NonNull
@Override
public String processMarkdown(@NonNull String markdown) {
return markdown.replaceAll("foo", "bar");
}
})
.build();
```
## Inspect/modify Node
A plugin can inspect/modify commonmark-java Node _before_ it's being rendered.
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void beforeRender(@NonNull Node node) {
// for example inspect it with custom visitor
node.accept(new MyVisitor());
// or modify (you know what you are doing, right?)
node.appendChild(new Text("Appended"));
}
})
.build();
```
## Inspect Node after render
A plugin can inspect commonmark-java Node after it's been rendered.
Modifying Node at this point makes not much sense (it's already been
rendered and all modifications won't change anything). But this method can be used,
for example, to clean-up some internal state (after rendering). Generally
speaking, a plugin must be stateless, but if it cannot, then this method is
the best place to clean-up.
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void afterRender(@NonNull Node node, @NonNull MarkwonVisitor visitor) {
cleanUp();
}
})
.build();
```
## Prepare TextView
A plugin can _prepare_ a TextView before markdown is applied. For example `images`
unschedules all previously scheduled `AsyncDrawableSpans` (if any) here. This way
when new markdown (and set of Spannables) arrives, previous set won't be kept in
memory and could be garbage-collected.
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
// clean-up previous
AsyncDrawableScheduler.unschedule(textView);
}
})
.build();
```
## TextView after markdown applied
A plugin will receive a callback _after_ markdown is applied to a TextView.
For example `images` uses this callback to schedule new set of Spannables.
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void afterSetText(@NonNull TextView textView) {
AsyncDrawableScheduler.schedule(textView);
}
})
.build();
```
:::tip
Please note that unlike `#beforeSetText`, `#afterSetText` won't receive
`Spanned` markdown. This happens because at this point spans must be
queried directly from a TextView.
:::
## What happens underneath
Here is what happens inside `Markwon` when `setMarkdown` method is called:
```java
final Markwon markwon = Markwon.create(context);
// warning: pseudo-code
// 0. each plugin will be called to _pre-process_ raw input markdown
rawInput = plugins.reduce(rawInput, (input, plugin) -> plugin.processMarkdown(input));
// 1. after input is processed it's being parsed to a Node
node = parser.parse(rawInput);
// 2. each plugin will be able to inspect or manipulate resulting Node
// before rendering
plugins.forEach(plugin -> plugin.beforeRender(node));
// 3. node is being visited by a visitor
node.accept(visitor);
// 4. each plugin will be called after node is being visited (aka rendered)
plugins.forEach(plugin -> plugin.afterRender(node, visitor));
// 5. styled markdown ready at this point
final Spanned markdown = visitor.markdown();
// NB, points 6-8 are applied **only** if markdown is set to a TextView
// 6. each plugin will be called before styled markdown is applied to a TextView
plugins.forEach(plugin -> plugin.beforeSetText(textView, markdown));
// 7. markdown is applied to a TextView
textView.setText(markdown);
// 8. each plugin will be called after markdown is applied to a TextView
plugins.forEach(plugin -> plugin.afterSetText(textView));
```

View File

@ -0,0 +1,97 @@
# Registry <Badge text="4.0.0" />
`Registry` allows to pre-configure other plugins and/or declare a dependency on a plugin,
which also will modify internal order of plugins inside a `Markwon` instance.
For example, you have a configurable plugin:
```java
public class MyPlugin extends AbstractMarkwonPlugin {
private boolean enabled;
public boolean enabled() {
return enabled;
}
@NonNull
public MyPlugin enabled(boolean enabled) {
this.enabled = enabled;
return this;
}
{...}
}
```
and other plugin that needs to access `MyPlugin` or modify/configure it:
```java
public class MyOtherPlugin extends AbstractMarkwonPlugin {
@Override
public void configure(@NonNull Registry registry) {
registry.require(MyPlugin.class, new Action<MyPlugin>() {
@Override
public void apply(@NonNull MyPlugin myPlugin) {
myPlugin.enabled(false);
}
});
}
}
```
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(new MyOtherPlugin())
.usePlugin(new MyPlugin())
.build();
```
_Internal_ plugins order (in this case) will be:
* `CorePlugin` (added automatically and always the first one)
* `MyPlugin` (was required by `MyOtherPlugin`)
* `MyOtherPlugin`
:::tip
There is no need to _require_ `CorePlugin` as it will be the first one inside
`Markwon` instance.
:::
The order matters if you want to _override_ some plugin. For example, `CoolPlugin`
adds a `SpanFactory` for a `Cool` markdown node. Other `NotCoolPlugin` wants to
use a different `SpanFactory`, then:
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(CoolPlugin.create())
.usePlugin(new NotCoolPlugin() {
@Override
public void configure(@NonNull MarkwonPlugin.Registry registry) {
registry.require(CoolPlugin.class);
}
@Override
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
builder.setFactory(Cool.class, new NotCoolSpanFactory());
}
})
.build();
```
---
All `require` calls to the `Registry` will also validate at runtime that
_required_ plugins are registered.
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configure(@NonNull Registry registry) {
// will throw an exception if `NotPresentPlugin` is not present
registry.require(NotPresentPlugin.class);
}
})
.build();
```

View File

@ -0,0 +1,75 @@
# RenderProps <Badge text="3.0.0" />
`RenderProps` encapsulates passing arguments from a node visitor to a node renderer.
Without hardcoding arguments into an API method calls.
`RenderProps` is the state collection for `Props` that are set by a node visitor and
retrieved by a node renderer.
```java
public class Prop<T> {
@NonNull
public static <T> Prop<T> of(@NonNull String name) {
return new Prop<>(name);
}
/* ... */
}
```
For example `CorePlugin` defines a _Heading level_ prop (inside `CoreProps` class):
```java
public static final Prop<Integer> HEADING_LEVEL = Prop.of("heading-level");
```
Then CorePlugin registers a `Heading` node visitor and applies heading value:
```java
@Override
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
builder.on(Heading.class, new MarkwonVisitor.NodeVisitor<Heading>() {
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull Heading heading) {
/* Heading node handling logic */
// set heading level
CoreProps.HEADING_LEVEL.set(visitor.renderProps(), heading.getLevel());
// a helper method to apply span(s) for a node
// (internally obtains a SpanFactory for Heading or silently ignores
// this call if no factory for a Heading is registered)
visitor.setSpansForNodeOptional(heading, start);
/* Heading node handling logic */
}
});
}
```
And finally `HeadingSpanFactory` (which is also registered by `CorePlugin`):
```java
public class HeadingSpanFactory implements SpanFactory {
@Nullable
@Override
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
return new HeadingSpan(
configuration.theme(),
CoreProps.HEADING_LEVEL.require(props)
);
}
}
```
---
`Prop<T>` has these methods:
* `@Nullable T get(RenderProps)` - returns value stored in RenderProps or `null` if none is present
* `@NonNull T get(RenderProps, @NonNull T defValue)` - returns value stored in RenderProps or default value (this method always return non-null value)
* `@NonNull T require(RenderProps)` - returns value stored in RenderProps or _throws an exception_ if none is present
* `void set(RenderProps, @Nullable T value)` - updates value stored in RenderProps, passing `null` as value is the same as calling `clear`
* `void clear(RenderProps)` - clears value stored in RenderProps

View File

@ -0,0 +1,103 @@
# Spans Factory
Starting with <Badge text="3.0.0" /> `MarkwonSpansFactory` controls what spans are displayed
for markdown nodes.
```java
Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
// passing null as second argument will remove previously added
// factory for the Link node
builder.setFactory(Link.class, null);
}
});
```
## SpanFactory
In order to create a _generic_ interface for all possible Nodes, a `SpanFactory`
was added:
```java
builder.setFactory(Link.class, new SpanFactory() {
@Nullable
@Override
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
return null;
}
});
```
All possible arguments are passed via [RenderProps](/docs/v4/core/render-props.md):
```java
builder.setFactory(Link.class, new SpanFactory() {
@Override
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
final String href = CoreProps.LINK_DESTINATION.require(props);
return new LinkSpan(configuration.theme(), href, configuration.linkResolver());
}
});
```
`SpanFactory` allows returning `null` for a certain span (no span will be applied).
Or an array of spans (you _can_ go deeper):
```java
builder.setFactory(Link.class, new SpanFactory() {
@Override
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
return new Object[]{
new LinkSpan(
configuration.theme(),
CoreProps.LINK_DESTINATION.require(props),
configuration.linkResolver()),
new ForegroundColorSpan(Color.RED)
};
}
});
```
---
Since <Badge text="3.0.1" /> you can _add_ multiple `SpanFactory` for a single node:
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
// this factory will be used _along_ with all other factories for specified node
builder.addFactory(Code.class, new SpanFactory() {
@Override
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
return new ForegroundColorSpan(Color.GREEN);
}
});
}
})
.build();
```
---
If you wish to inspect existing factory you can use:
* `builder#getFactory()` -> returns registered factory or `null`
* `builder#requireFactory()` -> returns registered factory or throws <Badge text="3.0.1" />
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
final SpanFactory codeFactory = builder.requireFactory(Code.class);
final SpanFactory linkFactory = builder.getFactory(Link.class);
if (linkFactory != null) {
{...}
}
}
})
.build();
```

187
docs/docs/v4/core/theme.md Normal file
View File

@ -0,0 +1,187 @@
# Theme
Here is the list of properties that can be configured via `MarkwonTheme.Builder` class.
:::tip
Starting with <Badge text="3.0.0" /> there is no need to manually construct a `MarkwonTheme`.
Instead a `Plugin` should be used:
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureTheme(@NonNull MarkwonTheme.Builder builder) {
builder
.codeTextColor(Color.BLACK)
.codeBackgroundColor(Color.GREEN);
}
})
.build();
```
:::
## Link color
Controls the color of a [link](#)
<ThemeProperty name="linkColor" type="@ColorInt int" defaults="Default link color of a context where markdown is displayed <sup>*</sup>" />
<sup>*</sup> `TextPaint#linkColor` will be used to determine linkColor of a context
## Block margin
Starting margin before text content for the:
* lists
* blockquotes
* task lists
<ThemeProperty name="blockMargin" type="@Px int" defaults="24dp" />
## Block quote
Customizations for the `blockquote` stripe
> Quote
### Stripe width
Width of a blockquote stripe
<ThemeProperty name="blockQuoteWidth" type="@Px int" defaults="1/4 of the <a href='#block-margin'>block margin</a>" />
### Stripe color
Color of a blockquote stripe
<ThemeProperty name="blockQuoteColor" type="@ColorInt int" defaults="textColor with <code>25</code> (0-255) alpha value" />
## List
### List item color
Controls the color of a list item. For ordered list: leading number,
for unordered list: bullet.
* UL
1. OL
<ThemeProperty name="listItemColor" type="@ColorInt int" defaults="Text color" />
### Bullet item stroke width
Border width of a bullet list item (level 2)
* First
* * Second
* * * Third
<ThemeProperty name="bulletListItemStrokeWidth" type="@Px int" defaults="Stroke width of TextPaint" />
### Bullet width
The width of the bullet item
* First
* Second
* Third
<ThemeProperty name="bulletWidth" type="@Px int" defaults="min(<a href='#block-margin'>blockMargin</a>, lineHeight) / 2" />
## Code
### Inline code text color
The color of the `code` content
<ThemeProperty name="codeTextColor" type="@ColorInt int" defaults="Content text color" />
### Inline code background color
The color of `background` of a code content
<ThemeProperty name="codeBackgroundColor" type="@ColorInt int" defaults="<a href='#inline-code-text-color'>inline code text color</a> with 25 (0-255) alpha" />
### Block code text color
```
The color of code block text
```
<ThemeProperty name="codeBlockTextColor" type="@ColorInt int" defaults="<a href='#inline-code-text-color'>inline code text color</a>" />
### Block code background color
```
The color of background of code block text
```
<ThemeProperty name="codeBlockBackgroundColor" type="@ColorInt int" defaults="<a href='#inline-code-background-color'>inline code background color</a>" />
### Block code leading margin
Leading margin for the block code content
<ThemeProperty name="codeMultilineMargin" type="@Px int" defaults="8dip" />
### Code typeface
Typeface of code content
<ThemeProperty name="codeTypeface" type="android.graphics.Typeface" defaults="Typeface.MONOSPACE" />
### Block code typeface <Badge text="3.0.0" />
Typeface of block code content
<ThemeProperty name="codeBlockTypeface" type="android.graphics.Typeface" defaults="<code>codeTypeface</code> if set or Typeface.MONOSPACE" />
### Code text size
Text size of code content
<ThemeProperty name="codeTextSize" type="@Px int" defaults="(Content text size) * 0.87 if no custom <a href='#code-typeface'>Typeface</a> was set, otherwise (content text size)" />
### Block code text size <Badge text="3.0.0" />
Text size of block code content
<ThemeProperty name="codeBlockTextSize" type="@Px int" defaults="<code>codeTextSize</code> if set or (content text size) * 0.87 if no custom <a href='#code-typeface'>Typeface</a> was set, otherwise (content text size)" />
## Heading
### Break height
The height of a brake under H1 &amp; H2
<ThemeProperty name="headingBreakHeight" type="@Px int" defaults="Stroke width of context TextPaint" />
### Break color
The color of a brake under H1 &amp; H2
<ThemeProperty name="headingBreakColor" type="@ColorInt int" defaults="(text color) with 75 (0-255) alpha" />
### Typeface <Badge text="1.1.0" />
The typeface of heading elements
<ThemeProperty name="headingTypeface" type="android.graphics.Typeface" defaults="default text Typeface" />
### Text size <Badge text="1.1.0" />
Array of heading text sizes _ratio_ that is applied to text size
<ThemeProperty name="headingTextSizeMultipliers" type="float[]" defaults="<code>{2.F, 1.5F, 1.17F, 1.F, .83F, .67F}</code> (HTML spec)" />
## Thematic break
### Color
Color of a thematic break
<ThemeProperty name="thematicBreakColor" type="@ColorInt int" defaults="(text color) with 25 (0-255) alpha" />
### Height
Height of a thematic break
<ThemeProperty name="thematicBreakHeight" type="@Px int" defaults="Stroke width of context TextPaint" />

View File

@ -0,0 +1,73 @@
# Visitor
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.
## Visitor.Builder
There is no need to create own instance of `MarkwonVisitor.Builder` as
it is done by `Markwon` itself. One still can configure it as one wishes:
```java
final Markwon markwon = Markwon.builder(contex)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
builder.on(SoftLineBreak.class, new MarkwonVisitor.NodeVisitor<SoftLineBreak>() {
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull SoftLineBreak softLineBreak) {
visitor.forceNewLine();
}
});
}
});
```
---
`MarkwonVisitor` encapsulates most of the functionality of rendering parsed markdown.
It holds rendering configuration:
* `MarkwonVisitor#configuration` - getter for current [MarkwonConfiguration](/docs/v4/core/configuration.md)
* `MarkwonVisitor#renderProps` - getter for current [RenderProps](/docs/v4/core/render-props.md)
* `MarkwonVisitor#builder` - getter for current `SpannableBuilder`
It contains also a number of utility functions:
* `visitChildren(Node)` - will visit all children of supplied Node
* `hasNext(Node)` - utility function to check if supplied Node has a Node after it (useful for white-space management, so there should be no blank new line after last BlockNode)
* `ensureNewLine` - will insert a new line at current `SpannableBuilder` position only if current (last) character is not a new-line
* `forceNewLine` - will insert a new line character without any condition checking
* `length` - helper function to call `visitor.builder().length()`, returns current length of `SpannableBuilder`
* `clear` - will clear state for `RenderProps` and `SpannableBuilder`, this is done by `Markwon` automatically after each render call
And some utility functions to control the spans:
* `setSpans(int start, Object spans)` - will apply supplied `spans` on `SpannableBuilder` starting at `start` position and ending at `SpannableBuilder#length`. `spans` can be `null` (no spans will be applied) or an array of spans (each span of this array will be applied)
* `setSpansForNodeOptional(N node, int start)` - helper method to set spans for specified `node` (internally obtains `SpanFactory` for that node and uses it to apply spans)
* `setSpansForNode(N node, int start)` - almost the same as `setSpansForNodeOptional` but instead of silently ignoring call if none `SpanFactory` is registered, this method will throw an exception.
```java
@Override
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
builder.on(Heading.class, new MarkwonVisitor.NodeVisitor<Heading>() {
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull Heading heading) {
// or just `visitor.length()`
final int start = visitor.builder().length();
visitor.visitChildren(heading);
// or just `visitor.setSpansForNodeOptional(heading, start)`
final SpanFactory factory = visitor.configuration().spansFactory().get(heading.getClass());
if (factory != null) {
visitor.setSpans(start, factory.getSpans(visitor.configuration(), visitor.renderProps()));
}
if (visitor.hasNext(heading)) {
visitor.ensureNewLine();
visitor.forceNewLine();
}
}
});
}
```

View File

@ -0,0 +1,52 @@
# LaTeX extension
<MavenBadge4 :artifact="'ext-latex'" />
This is an extension that will help you display LaTeX formulas in your markdown.
Syntax is pretty simple: pre-fix and post-fix your latex with `$$` (double dollar sign).
`$$` should be the first characters in a line.
```markdown
$$
\\text{A long division \\longdiv{12345}{13}
$$
```
```markdown
$$\\text{A long division \\longdiv{12345}{13}$$
```
```java
Markwon.builder(context)
.use(JLatexMathPlugin.create(textSize))
.build();
```
This extension uses [jlatexmath-android](https://github.com/noties/jlatexmath-android) artifact to create LaTeX drawable.
## Config
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(JLatexMathPlugin.create(textSize, new BuilderConfigure() {
@Override
public void configureBuilder(@NonNull Builder builder) {
builder
.align(JLatexMathDrawable.ALIGN_CENTER)
.fitCanvas(true)
.padding(paddingPx)
// @since 4.0.0 - horizontal and vertical padding
.padding(paddingHorizontalPx, paddingVerticalPx)
// @since 4.0.0 - change to provider
.backgroundProvider(() -> new MyDrawable()))
// @since 4.0.0 - optional, by default cached-thread-pool will be used
.executorService(Executors.newCachedThreadPool());
}
}))
.build();
```
:::tip
Since <Badge text="4.0.0" /> `JLatexMathPlugin` operates independently of `ImagesPlugin`
:::

View File

@ -0,0 +1,29 @@
# Strikethrough extension
<MavenBadge4 :artifact="'ext-strikethrough'" />
This module adds `strikethrough` functionality to `Markwon` via `StrikethroughPlugin`:
```java
Markwon.builder(context)
.usePlugin(StrikethroughPlugin.create())
```
This plugin registers `SpanFactory` for `Strikethrough` node, so it's possible to customize Strikethrough Span that is used in rendering:
```java
Markwon.builder(context)
.usePlugin(StrikethroughPlugin.create())
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
builder.setFactory(Strikethrough.class, new SpanFactory() {
@Override
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
// will use Underline span instead of Strikethrough
return new UnderlineSpan();
}
});
}
})
```

View File

@ -0,0 +1,99 @@
# Tables extension
<MavenBadge4 :artifact="'ext-tables'" />
This extension adds support for GFM tables.
```java
final Markwon markwon = Markwon.builder(context)
// create default instance of TablePlugin
.usePlugin(TablePlugin.create(context))
```
```java
final TableTheme tableTheme = TableTheme.builder()
.tableBorderColor(Color.RED)
.tableBorderWidth(0)
.tableCellPadding(0)
.tableHeaderRowBackgroundColor(Color.BLACK)
.tableEvenRowBackgroundColor(Color.GREEN)
.tableOddRowBackgroundColor(Color.YELLOW)
.build();
final Markwon markwon = Markwon.builder(context)
.usePlugin(TablePlugin.create(tableTheme))
```
```java
Markwon.builder(context)
.usePlugin(TablePlugin.create(builder ->
builder
.tableBorderColor(Color.RED)
.tableBorderWidth(0)
.tableCellPadding(0)
.tableHeaderRowBackgroundColor(Color.BLACK)
.tableEvenRowBackgroundColor(Color.GREEN)
.tableOddRowBackgroundColor(Color.YELLOW)
))
```
Please note, that _by default_ tables have limitations. For example, there is no support
for images inside table cells. And table contents won't be copied to clipboard if a TextView
has such functionality. Table will always take full width of a TextView in which it is displayed.
All columns will always be of the same width. So, _default_ implementation provides basic
functionality which can answer some needs. These all come from the limited nature of the TextView
to display such content.
In order to provide full-fledged experience, tables must be displayed in a special widget.
Since version `3.0.0` Markwon provides a special artifact `markwon-recycler` that allows
to render markdown in a set of widgets in a RecyclerView. It also gives ability to change
display widget form TextView to any other.
```java
final Table table = Table.parse(Markwon, TableBlock);
myTableWidget.setTable(table);
```
:::tip
To take advantage of this functionality and render tables without limitations (including
horizontally scrollable layout when its contents exceed screen width), refer to [recycler-table](/docs/v4/recycler-table/)
module documentation that adds support for rendering `TableBlock` markdown node inside Android-native `TableLayout` widget.
:::
## Theme
### Cell padding
Padding inside a table cell
<ThemeProperty name="tableCellPadding" type="@Px int" defaults="0" />
### Border color
The color of table borders
<ThemeProperty name="tableBorderColor" type="@ColorInt int" defaults="(text color) with 75 (0-255) alpha" />
### Border width
The width of table borders
<ThemeProperty name="tableBorderWidth" type="@Px int" defaults="Stroke with of context TextPaint" />
### Odd row background
Background of an odd table row
<ThemeProperty name="tableOddRowBackgroundColor" type="@ColorInt int" defaults="(text color) with 22 (0-255) alpha" />
### Even row background <Badge text="1.1.1" />
Background of an even table row
<ThemeProperty name="tableEventRowBackgroundColor" type="@ColorInt int" defaults="0" />
### Header row background <Badge text="1.1.1" />
Background of header table row
<ThemeProperty name="tableHeaderRowBackgroundColor" type="@ColorInt int" defaults="0" />

View File

@ -0,0 +1,146 @@
# Task list extension
<MavenBadge4 :artifact="'ext-tasklist'" />
Adds support for GFM (Github-flavored markdown) task-lists:
```java
Markwon.builder(context)
.usePlugin(TaskListPlugin.create(context));
```
---
Create a default instance of `TaskListPlugin` with `TaskListDrawable` initialized to use
`android.R.attr.textColorLink` as primary color and `android.R.attr.colorBackground` as background
```java
TaskListPlugin.create(context);
```
---
Create an instance of `TaskListPlugin` with exact color values to use:
```java
// obtain color values
final int checkedFillColor = /* */;
final int normalOutlineColor = /* */;
final int checkMarkColor = /* */;
TaskListPlugin.create(checkedFillColor, normalOutlineColor, checkMarkColor);
```
---
Specify own drawable for a task list item:
```java
// obtain drawable
final Drawable drawable = /* */;
TaskListPlugin.create(drawable);
```
:::warning
Please note that custom drawable for a task list item must correctly handle state
in order to display done/not-done:
```java
public class MyTaskListDrawable extends Drawable {
private boolean isChecked;
@Override
public void draw(@NonNull Canvas canvas) {
// draw accordingly to the isChecked value
}
/* implementation omitted */
@Override
protected boolean onStateChange(int[] state) {
final boolean isChecked = contains(state, android.R.attr.state_checked);
final boolean result = this.isChecked != isChecked;
if (result) {
this.isChecked = isChecked;
}
return result;
}
private static boolean contains(@Nullable int[] states, int value) {
if (states != null) {
for (int state : states) {
if (state == value) {
// NB return here
return true;
}
}
}
return false;
}
}
```
:::
## Task list mutation
It is possible to mutate task list item state (toggle done/not-done). But note
that `Markwon` won't handle state change internally by any means and this change
is merely a visual one. If you need to persist state of a task list
item change you have to implement it yourself. This should get your started:
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(TaskListPlugin.create(context))
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
// obtain original SpanFactory set by TaskListPlugin
final SpanFactory origin = builder.getFactory(TaskListItem.class);
if (origin == null) {
// or throw, as it's a bit weird state and we expect
// this factory to be present
return;
}
builder.setFactory(TaskListItem.class, new SpanFactory() {
@Override
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
// it's a bit non-secure behavior and we should validate
// the type of returned span first, but for the sake of brevity
// we skip this step
final TaskListSpan span = (TaskListSpan) origin.getSpans(configuration, props);
if (span == null) {
// or throw
return null;
}
// return an array of spans
return new Object[]{
span,
new ClickableSpan() {
@Override
public void onClick(@NonNull View widget) {
// toggle VISUAL state
span.setDone(!span.isDone());
// do not forget to invalidate widget
widget.invalidate();
// execute your persistence logic
}
@Override
public void updateDrawState(@NonNull TextPaint ds) {
// no-op, so appearance is not changed (otherwise
// task list item will look like a link)
}
}
};
}
});
}
})
.build();
```

235
docs/docs/v4/html/README.md Normal file
View File

@ -0,0 +1,235 @@
# HTML
<MavenBadge4 :artifact="'html'" />
This artifact encapsulates HTML parsing from the core artifact and provides
few predefined `TagHandlers`
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(HtmlPlugin.create())
.build();
```
As this artifact brings modified [jsoup](https://github.com/jhy/jsoup) library
it was moved to a standalone module in order to minimize dependencies and unused code
in applications that does not require HTML render capabilities.
Before <Badge text="2.0.0" /> `Markwon` used android `Html` class for parsing and
rendering. Unfortunately, according to markdown specification, markdown can contain
HTML in _unpredictable_ way if rendered _outside_ of browser. For example:
```markdown{4}
<i>
Hello from italics tag
</i><b>bold></b>
```
This snippet could be represented as:
* HtmlBlock (`<i>\nHello from italics tag`)
* HtmlInline (`<i>`)
* HtmlInline (`<b>`)
* Text (`bold`)
* HtmlInline (`</b>`)
:::tip A bit of background
<br>
<GithubIssue id="52" displayName="This issue" /> had brought attention to differences between HTML &amp; commonmark implementations. <br><br>
:::
Unfortunately Android `HTML` class cannot parse a _fragment_ of HTML to later
be included in a bigger set of content. This is why the decision was made to bring
HTML parsing _in-markwon-house_
## Predefined TagHandlers
* `<img>`
* `<a>`
* `<blockquote>`
* `<sub>`
* `<sup>`
* `<b>, <strong>`
* `<s>, <del>`
* `<u>, <ins>`
* `<ul>, <ol>`
* `<i>, <cite>, <em>, <dfn>`
* `<h1>, <h2>, <h3>, <h4>, <h5>, <h6>`
:::tip
All predefined tag handlers will use styling spans for native markdown content.
So, if your `Markwon` instance was configured to, for example, render Emphasis
nodes as a <span style="color: #FF0000">red text</span> then HTML tag handler will
use the same span. This includes images, links, UrlResolver, LinkProcessor, etc
:::
---
Staring with <Badge text="4.0.0" /> you can exclude all default tag handlers:
```java
.usePlugin(HtmlPlugin.create(new HtmlPlugin.HtmlConfigure() {
@Override
public void configureHtml(@NonNull HtmlPlugin plugin) {
plugin.excludeDefaults(true);
}
}))
```
or via plugin:
```java
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configure(@NonNull Registry registry) {
registry.require(HtmlPlugin.class, new Action<HtmlPlugin>() {
@Override
public void apply(@NonNull HtmlPlugin htmlPlugin) {
htmlPlugin.excludeDefaults(true);
}
});
}
})
```
If you wish to exclude some of them `TagHandlerNoOp` can be used:
```java
.usePlugin(HtmlPlugin.create(new HtmlPlugin.HtmlConfigure() {
@Override
public void configureHtml(@NonNull HtmlPlugin plugin) {
plugin.addHandler(TagHandlerNoOp.create("h4", "h5", "h6", "img"));
}
}))
```
## TagHandler
To define a tag-handler that applies style for the whole tag content (from start to end),
a `SimpleTagHandler` can be used. For example, let's define `<align>` tag, which can be used
like this:
* `<align center>centered text</align>`
* `<align end>this should be aligned at the end (right for LTR locales)</align>`
* `<align>regular alignment</align>`
```java
public class AlignTagHandler extends SimpleTagHandler {
@Nullable
@Override
public Object getSpans(
@NonNull MarkwonConfiguration configuration,
@NonNull RenderProps renderProps,
@NonNull HtmlTag tag) {
final Layout.Alignment alignment;
// html attribute without value, <align center></align>
if (tag.attributes().containsKey("center")) {
alignment = Layout.Alignment.ALIGN_CENTER;
} else if (tag.attributes().containsKey("end")) {
alignment = Layout.Alignment.ALIGN_OPPOSITE;
} else {
// empty value or any other will make regular alignment
alignment = Layout.Alignment.ALIGN_NORMAL;
}
return new AlignmentSpan.Standard(alignment);
}
@NonNull
@Override
public Collection<String> supportedTags() {
return Collections.singleton("align");
}
}
```
:::tip
`SimpleTagHandler` can return an array of spans from `getSpans` method
:::
Then register `AlignTagHandler`:
```java
final Markwon markwon = Markwon.builder(this)
.usePlugin(HtmlPlugin.create())
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configure(@NonNull Registry registry) {
registry.require(HtmlPlugin.class, htmlPlugin -> htmlPlugin
.addHandler(new AlignTagHandler());
}
})
.build();
```
or directly on `HtmlPlugin`:
```java
final Markwon markwon = Markwon.builder(this)
.usePlugin(HtmlPlugin.create(plugin -> plugin.addHandler(new AlignTagHandler())))
.build();
```
---
If a tag requires special handling `TagHandler` can be used directly. For example
let's define an `<enhance>` tag with `start` and `end` arguments, that will mark
start and end positions of the text that needs to be enlarged:
```html
<enhance start="5" end="12">This is text that must be enhanced, at least a part of it</enhance>
```
```java
public class EnhanceTagHandler extends TagHandler {
private final int enhanceTextSize;
EnhanceTagHandler(@Px int enhanceTextSize) {
this.enhanceTextSize = enhanceTextSize;
}
@Override
public void handle(
@NonNull MarkwonVisitor visitor,
@NonNull MarkwonHtmlRenderer renderer,
@NonNull HtmlTag tag) {
// we require start and end to be present
final int start = parsePosition(tag.attributes().get("start"));
final int end = parsePosition(tag.attributes().get("end"));
if (start > -1 && end > -1) {
visitor.builder().setSpan(
new AbsoluteSizeSpan(enhanceTextSize),
tag.start() + start,
tag.start() + end
);
}
}
@NonNull
@Override
public Collection<String> supportedTags() {
return Collections.singleton("enhance");
}
private static int parsePosition(@Nullable String value) {
int position;
if (!TextUtils.isEmpty(value)) {
try {
position = Integer.parseInt(value);
} catch (NumberFormatException e) {
e.printStackTrace();
position = -1;
}
} else {
position = -1;
}
return position;
}
}
```

View File

@ -0,0 +1,27 @@
# Image Glide
<MavenBadge4 :artifact="'image-glide'" />
Image loading based on `Glide` library
```java
final Markwon markwon = Markwon.builder(context)
// automatically create Glide instance
.usePlugin(GlideImagesPlugin.create(context))
// use supplied Glide instance
.usePlugin(GlideImagesPlugin.create(Glide.with(context)))
// if you need more control
.usePlugin(GlideImagesPlugin.create(new GlideImagesPlugin.GlideStore() {
@NonNull
@Override
public RequestBuilder<Drawable> load(@NonNull AsyncDrawable drawable) {
return Glide.with(context).load(drawable.getDestination());
}
@Override
public void cancel(@NonNull Target<?> target) {
Glide.with(context).clear(target);
}
}))
.build();
```

View File

@ -0,0 +1,32 @@
# Image Picasso
<MavenBadge4 :artifact="'image-picasso'" />
Image loading based on `Picasso` library
```java
final Markwon markwon = Markwon.builder(context)
// automatically create Picasso instance
.usePlugin(PicassoImagesPlugin.create(context))
// use provided picasso instance
.usePlugin(PicassoImagesPlugin.create(Picasso.get()))
// if you need more control
.usePlugin(PicassoImagesPlugin.create(new PicassoImagesPlugin.PicassoStore() {
@NonNull
@Override
public RequestCreator load(@NonNull AsyncDrawable drawable) {
return Picasso.get()
.load(drawable.getDestination())
// please note that drawable should be used as tag (not a destination)
// otherwise there won't be support for multiple images with the same URL
.tag(drawable);
}
@Override
public void cancel(@NonNull AsyncDrawable drawable) {
Picasso.get()
.cancelTag(drawable);
}
}))
.build();
```

View File

@ -0,0 +1,354 @@
# Image
<MavenBadge4 :artifact="'image'" />
In order to display images in your markdown `ImagesPlugin` can be used.
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(ImagesPlugin.create())
```
:::tip
There are also modules that add image loading capabilities to markdown
based on image-loading libraries: [image-glide](/docs/v4/image-glide/) and
[image-picasso](/docs/v4/image-picasso/)
:::
`ImagesPlugin` splits the image-loading into 2 parts: scheme-handling and media-decoding.
## SchemeHandler
To add a scheme-handler to `ImagesPlugin`:
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(ImagesPlugin.create())
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configure(@NonNull Registry registry) {
registry.require(ImagesPlugin.class, new Action<ImagesPlugin>() {
@Override
public void apply(@NonNull ImagesPlugin imagesPlugin) {
imagesPlugin.addSchemeHandler(DataUriSchemeHandler.create());
}
});
}
})
```
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
@Override
public void configureImages(@NonNull ImagesPlugin plugin) {
plugin.addSchemeHandler(DataUriSchemeHandler.create());
}
}))
```
`ImagesPlugin` comes with a set of predefined scheme-handlers:
* `FileSchemeHandler` - `file://`
* `DataUriSchemeHandler` - `data:`
* `NetworkSchemeHandler` - `http`, `https`
* `OkHttpNetworkSchemeHandler` - `http`, `https`
### FileSchemeHandler
Loads images via `file://` scheme. Allows loading images from `assets` folder.
```java
// default implementation, no assets handling
FileSchemeHandler.create();
// assets loading
FileSchemeHandler.createWithAssets(context);
```
:::warning
Assets loading will work when your URL will include `android_asset` in the path,
for example: `file:///android_asset/image.png` (mind the 3 slashes `///`). If you wish
to _assume_ all images without proper scheme to point to assets folder, then you can use
[UrlProcessorAndroidAssets](/docs/v4/core/configuration.html#urlprocessorandroidassets)
:::
By default `ImagesPlugin` includes _plain_ `FileSchemeHandler` (without assets support),
so if you wish to change that you can explicitly specify it:
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
@Override
public void configureImages(@NonNull ImagesPlugin plugin) {
plugin.addSchemeHandler(FileSchemeHandler.createWithAssets(context));
}
}))
```
### DataUriSchemeHandler
`DataUriSchemeHandler` allows _inlining_ images with `data:` scheme (`data:image/svg+xml;base64,MTIz`).
This scheme-handler is registered by default, so you do not need to add it explicitly.
### NetworkSchemeHandler
`NetworkSchemeHandler` allows obtaining images from `http://` and `https://` uris
(internally it uses `HttpURLConnection`). This scheme-handler is registered by default
### OkHttpNetworkSchemeHandler
`OkHttpNetworkSchemeHandler` allows obtaining images from `http://` and `https://` uris
via [okhttp library](https://github.com/square/okhttp). Please note that in order to use
this scheme-handler you must explicitly add `okhttp` library to your project.
```java
// default instance
OkHttpNetworkSchemeHandler.create();
// specify OkHttpClient to use
OkHttpNetworkSchemeHandler.create(new OkHttpClient());
// @since 4.0.0
OkHttpNetworkSchemeHandler.create(Call.Factory);
```
### Custom SchemeHandler
```java
public abstract class SchemeHandler {
@NonNull
public abstract ImageItem handle(@NonNull String raw, @NonNull Uri uri);
@NonNull
public abstract Collection<String> supportedSchemes();
}
```
Starting with <Badge text="4.0.0" /> `SchemeHandler` can return a result (when no
further decoding is required):
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
@Override
public void configureImages(@NonNull ImagesPlugin plugin) {
// for example to return a drawable resource
plugin.addSchemeHandler(new SchemeHandler() {
@NonNull
@Override
public ImageItem handle(@NonNull String raw, @NonNull Uri uri) {
// will handle URLs like `drawable://ic_account_24dp_white`
final int resourceId = context.getResources().getIdentifier(
raw.substring("drawable://".length()),
"drawable",
context.getPackageName());
// it's fine if it throws, async-image-loader will catch exception
final Drawable drawable = context.getDrawable(resourceId);
return ImageItem.withResult(drawable);
}
@NonNull
@Override
public Collection<String> supportedSchemes() {
return Collections.singleton("drawable");
}
});
}
}))
```
Otherwise `SchemeHandler` must return an `InputStream` with proper `content-type` information
for further processing by a `MediaDecoder`:
```java
imagesPlugin.addSchemeHandler(new SchemeHandler() {
@NonNull
@Override
public ImageItem handle(@NonNull String raw, @NonNull Uri uri) {
return ImageItem.withDecodingNeeded("image/png", load(raw));
}
@NonNull
private InputStream load(@NonNull String raw) {...}
});
```
## MediaDecoder
`ImagesPlugin` comes with predefined media-decoders:
* `GifMediaDecoder` adds support for GIF
* `SvgMediaDecoder` adds support for SVG
* `DefaultMediaDecoder`
:::warning
If you wish to add support for **SVG** or **GIF** you must explicitly add these dependencies
to your project:
* for `SVG`: `com.caverock:androidsvg:1.4`
* for `GIF`: `pl.droidsonroids.gif:android-gif-drawable:1.2.14`
You can try more recent versions of these libraries, but make sure that they doesn't
introduce any unexpected behavior.
:::
### GifMediaDecoder
Adds support for GIF media in markdown. If `pl.droidsonroids.gif:android-gif-drawable:*` dependency
is found in the classpath, then registration will happen automatically.
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
@Override
public void configureImages(@NonNull ImagesPlugin plugin) {
// autoplayGif controls if GIF should be automatically started
plugin.addMediaDecoder(GifMediaDecoder.create(/*autoplayGif*/false));
}
}))
.build();
```
### SvgMediaDecoder
Adds support for SVG media in markdown. If `com.caverock:androidsvg:*` dependency is found
in the classpath, then registration will happen automatically.
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
@Override
public void configureImages(@NonNull ImagesPlugin plugin) {
// uses supplied Resources
plugin.addMediaDecoder(SvgMediaDecoder.create(context.getResources()));
// uses Resources.getSystem()
plugin.addMediaDecoder(SvgMediaDecoder.create());
}
}))
.build();
```
### DefaultMediaDecoder
`DefaultMediaDecoder` _tries_ to decode supplied InputStream
as Bitmap (via `BitmapFactory.decodeStream(inputStream)`). This decoder is registered automatically.
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
@Override
public void configureImages(@NonNull ImagesPlugin plugin) {
// uses supplied Resources
plugin.defaultMediaDecoder(DefaultMediaDecoder.create(context.getResources()));
// uses Resources.getSystem()
plugin.defaultMediaDecoder(DefaultMediaDecoder.create());
}
}))
.build();
```
## AsyncDrawableScheduler
`AsyncDrawableScheduler` is used in order to give `AsyncDrawable` a way to invalidate `TextView`
that is holding it. A plugin that is dealing with `AsyncDrawable` should always call these methods:
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
AsyncDrawableScheduler.unschedule(textView);
}
@Override
public void afterSetText(@NonNull TextView textView) {
AsyncDrawableScheduler.schedule(textView);
}
})
.build();
```
:::tip
Starting with <Badge text="4.0.0" /> multiple plugins can call `AsyncDrawableScheduler#schedule`
method without the penalty to process `AsyncDrawable` callbacks multiple times (internally caches
state which ensures that a `TextView` is processed only once the text has changed).
:::
## ErrorHandler
An `ErrorHandler` can be used to receive an error that has happened during image loading
and (optionally) return an error drawable to be displayed instead
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
@Override
public void configureImages(@NonNull ImagesPlugin plugin) {
plugin.errorHandler(new ImagesPlugin.ErrorHandler() {
@Nullable
@Override
public Drawable handleError(@NonNull String url, @NonNull Throwable throwable) {
return null;
}
});
}
}))
.build();
```
## PlaceholderProvider
To display a placeholder during image loading `PlaceholderProvider` can be used:
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
@Override
public void configureImages(@NonNull ImagesPlugin plugin) {
plugin.placeholderProvider(new ImagesPlugin.PlaceholderProvider() {
@Nullable
@Override
public Drawable providePlaceholder(@NonNull AsyncDrawable drawable) {
return null;
}
});
}
}))
.build();
```
:::tip
If your placeholder drawable has _specific_ size which is not the same an image that is being loaded,
you can manually assign bounds to the placeholder:
```java
plugin.placeholderProvider(new ImagesPlugin.PlaceholderProvider() {
@Override
public Drawable providePlaceholder(@NonNull AsyncDrawable drawable) {
final ColorDrawable placeholder = new ColorDrawable(Color.BLUE);
// these bounds will be used to display a placeholder,
// so even if loading image has size `width=100%`, placeholder
// bounds won't be affected by it
placeholder.setBounds(0, 0, 48, 48);
return placeholder;
}
});
```
:::
---
:::tip
If you are using [html](/docs/v4/html/) you do not have to additionally setup
images displayed via `<img>` tag, as `HtmlPlugin` automatically uses configured
image loader. But images referenced in HTML come with additional support for
sizes, which is not supported natively by markdown, allowing absolute or relative sizes:
```html
<img src="./assets/my-image" width="100%">
```
:::

34
docs/docs/v4/install.md Normal file
View File

@ -0,0 +1,34 @@
---
prev: false
next: /docs/v4/core/getting-started.md
---
# Installation
![stable](https://img.shields.io/maven-central/v/io.noties.markwon/core.svg?label=stable)
![snapshot](https://img.shields.io/nexus/s/https/oss.sonatype.org/io.noties.markwon/core.svg?label=snapshot)
<ArtifactPicker4 />
## Snapshot
In order to use latest `SNAPSHOT` version add snapshot repository
to your root project's `build.gradle` file:
```groovy
allprojects {
repositories {
jcenter()
google()
// this one 👇
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } // 👈 this one
// this one 👆
}
}
```
:::tip Info
All official artifacts share the same version number and all
are uploaded to **release** and **snapshot** repositories
:::

View File

@ -0,0 +1,31 @@
# Linkify
<MavenBadge4 :artifact="'linkify'" />
A plugin to automatically add links to your markdown. Currently autolinking works for:
* email (`me@web.com`)
* phone numbers (`+10000000`)
* web URLS
:::warning
`Linkify` plugin is based on `android.text.util.Linkify` which can lead to significant performance
drop due to its implementation based on regex.
:::
:::danger
Do not use `autolink` XML attribute on your `TextView` as it will remove
all links except autolinked ones ¯\\\_(ツ)_/¯
:::
```java
final Markwon markwon = Markwon.builder(context)
// will autolink all supported types
.usePlugin(LinkifyPlugin.create())
// the same as above
.usePlugin(LinkifyPlugin.create(
Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS | Linkify.WEB_URLS
))
// only emails
.usePlugin(LinkifyPlugin.create(Linkify.EMAIL_ADDRESSES))
.build();
```

96
docs/docs/v4/recipes.md Normal file
View File

@ -0,0 +1,96 @@
# Recipes
## SpannableFactory
Consider using `NoCopySpannableFactory` when a `TextView` will be used to display markdown
multiple times (for example in a `RecyclerView`):
```java
// call after inflation and before setting markdown
textView.setSpannableFactory(NoCopySpannableFactory.getInstance());
```
## Autolink
Do not use `autolink` XML attribute on your `TextView` as it will remove all links except autolinked ones.
Consider using [linkify plugin](/docs/v4/linkify/) or commonmark-java [autolink extension](https://github.com/atlassian/commonmark-java)
## List item spacing
If your list items, task list items or paragraphs need special space between them
(increasing spacing between them, but keeping the original line height),
`LastLineSpacingSpan` <Badge text="4.0.0" /> can be used:
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
// or Paragraph, or TaskListItem
builder.addFactory(ListItem.class, new SpanFactory() {
@Override
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
return new LastLineSpacingSpan(spacingPx);
}
});
}
})
.build();
```
## Softbreak new-line
If you want to add a new line when a `softbreak` is used:
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
builder.on(SoftLineBreak.class, new MarkwonVisitor.NodeVisitor<SoftLineBreak>() {
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull SoftLineBreak softLineBreak) {
visitor.forceNewLine();
}
});
}
})
.build();
```
## Custom typeface
When using a custom typeface on a `TextView` you might find that **bold** and *italic* nodes
are displayed incorrectly. Consider registering own `SpanFactories` for `StrongEmphasis` and `Emphasis` nodes:
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
builder
.setFactory(StrongEmphasis.class, new SpanFactory() {
@Override
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
return new StyleSpan(Typeface.BOLD);
}
})
.setFactory(Emphasis.class, new SpanFactory() {
@Override
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
return new StyleSpan(Typeface.ITALIC);
}
});
}
})
.build();
```
Please check that `StyleSpan` works for you. If it doesn't consider
using `CustomTypefaceSpan` with your typeface directly.

View File

@ -0,0 +1,92 @@
# Recycler Table <Badge text="3.0.0" />
<MavenBadge4 :artifact="'recycler-table'" />
Artifact that provides [MarkwonAdapter.Entry](/docs/v4/recycler/) to render `TableBlock` inside
Android-native `TableLayout` widget.
<img :src="$withBase('/assets/recycler-table-screenshot.png')" alt="screenshot" width="45%">
<br>
<small><em><sup>*</sup> It's possible to wrap `TableLayout` inside a `HorizontalScrollView` to include all table content</em></small>
---
Register instance of `TableEntry` with `MarkwonAdapter` to render TableBlocks:
```java
final MarkwonAdapter adapter = MarkwonAdapter.builder(R.layout.adapter_default_entry, R.id.text)
.include(TableBlock.class, TableEntry.create(builder -> builder
.tableLayout(R.layout.adapter_table_block, R.id.table_layout)
.textLayoutIsRoot(R.layout.view_table_entry_cell)))
.build();
```
`TableEntry` requires at least 2 arguments:
* `tableLayout` - layout with `TableLayout` inside
* `textLayout` - layout with `TextView` inside (represents independent table cell)
In case when required view is the root of layout specific builder methods can be used:
* `tableLayoutIsRoot(int)`
* `textLayoutIsRoot(int)`
If your layouts have different structure (for example wrap a `TableView` inside a `HorizontalScrollView`)
then you should use methods that accept ID of required view inside layout:
* `tableLayout(int, int)`
* `textLayout(int, int)`
---
To display `TableBlock` as a `TableLayout` specific `MarkwonPlugin` must be used: `TableEntryPlugin`.
:::warning
Do not use `TablePlugin` if you wish to display markdown tables via `TableEntry`. Use **TableEntryPlugin** instead
:::
`TableEntryPlugin` can reuse existing `TablePlugin` to make appearance of tables the same in both contexts:
when rendering _natively_ in a TextView and when rendering in RecyclerView with TableEntry.
* `TableEntryPlugin.create(Context)` - creates plugin with default `TableTheme`
* `TableEntryPlugin.create(TableTheme)` - creates plugin with provided `TableTheme`
* `TableEntryPlugin.create(TablePlugin.ThemeConfigure)` - creates plugin with theme configured by `ThemeConfigure`
* `TableEntryPlugin.create(TablePlugin)` - creates plugin with `TableTheme` used in provided `TablePlugin`
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(TableEntryPlugin.create(context))
// other plugins
.build();
```
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(TableEntryPlugin.create(builder -> builder
.tableBorderWidth(0)
.tableHeaderRowBackgroundColor(Color.RED)))
// other plugins
.build();
```
## Table with scrollable content
To stretch table columns to fit the width of screen or to make table scrollable when content exceeds screen width
this layout can be used:
```xml
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingLeft="16dip"
android:paddingTop="8dip"
android:paddingRight="16dip"
android:paddingBottom="8dip"
android:scrollbarStyle="outsideInset">
<TableLayout
android:id="@+id/table_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:stretchColumns="*" />
</HorizontalScrollView>
```

View File

@ -0,0 +1,153 @@
# Recycler <Badge text="3.0.0" />
<MavenBadge4 :artifact="'recycler'" />
This artifact allows displaying markdown in a set of Android widgets
inside a RecyclerView. Can be useful when displaying lengthy markdown
content or **displaying certain markdown blocks inside specific widgets**.
```java
// create an adapter that will use a TextView for each block of markdown
// `createTextViewIsRoot` accepts a layout in which TextView is the root view
final MarkwonAdapter adapter =
MarkwonAdapter.createTextViewIsRoot(R.layout.adapter_default_entry);
```
```java
// `create` method accepts a layout with TextView and ID of a TextView
// which allows wrapping a TextView inside another widget or combine with other widgets
final MarkwonAdapter adapter =
MarkwonAdapter.create(R.layout.adapter_default_entry, R.id.text_view);
// initialize RecyclerView (LayoutManager, Decorations, etc)
final RecyclerView recyclerView = obtainRecyclerView();
// set adapter
recyclerView.setAdapter(adapter);
// obtain an instance of Markwon (register all required plugins)
final Markwon markwon = obtainMarkwon();
// set markdown to be displayed
adapter.setMarkdown(markwon, "# This is markdown!");
// NB, adapter does not handle updates on its own, please use
// whatever method appropriate for you.
adapter.notifyDataSetChanged();
```
Initialized adapter above will use a TextView for each markdown block.
In order to tell adapter to render certain blocks differently a `builder` can be used.
For example, let's render `FencedCodeBlock` inside a `HorizontalScrollView`:
```java
// we still need to have a _default_ entry
final MarkwonAdapter adapter =
MarkwonAdapter.builderTextViewIsRoot(R.layout.adapter_default_entry)
.include(FencedCodeBlock.class, new FencedCodeBlockEntry())
.build();
```
where `FencedCodeBlockEntry` is:
```java
public class FencedCodeBlockEntry extends MarkwonAdapter.Entry<FencedCodeBlock, FencedCodeBlockEntry.Holder> {
@NonNull
@Override
public Holder createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
return new Holder(inflater.inflate(R.layout.adapter_fenced_code_block, parent, false));
}
@Override
public void bindHolder(@NonNull Markwon markwon, @NonNull Holder holder, @NonNull FencedCodeBlock node) {
markwon.setParsedMarkdown(holder.textView, markwon.render(node));
}
public static class Holder extends MarkwonAdapter.Holder {
final TextView textView;
public Holder(@NonNull View itemView) {
super(itemView);
this.textView = requireView(R.id.text_view);
}
}
}
```
and its layout (`R.layout.adapter_fenced_code_block`):
```xml
<?xml version="1.0" encoding="utf-8"?>
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:fillViewport="true"
android:paddingLeft="16dip"
android:paddingRight="16dip"
android:scrollbarStyle="outsideInset">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#0f000000"
android:fontFamily="monospace"
android:lineSpacingExtra="2dip"
android:paddingLeft="16dip"
android:paddingTop="8dip"
android:paddingRight="16dip"
android:paddingBottom="8dip"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="14sp" />
</HorizontalScrollView>
```
As we apply styling to `FencedCodeBlock` _manually_, we no longer need
`Markwon` to apply styling spans for us, so `Markwon` initialization could be:
```java
final Markwon markwon = Markwon.builder(context)
// your other plugins
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
builder.on(FencedCodeBlock.class, (visitor, fencedCodeBlock) -> {
// we actually won't be applying code spans here, as our custom view will
// draw background and apply mono typeface
//
// NB the `trim` operation on literal (as code will have a new line at the end)
final CharSequence code = visitor.configuration()
.syntaxHighlight()
.highlight(fencedCodeBlock.getInfo(), fencedCodeBlock.getLiteral().trim());
visitor.builder().append(code);
});
}
})
.build();
```
Previously we have created a `FencedCodeBlockEntry` but all it does is apply markdown to a TextView.
For such a case there is a `SimpleEntry` that could be used instead:
```java
final MarkwonAdapter adapter =
MarkwonAdapter.builderTextViewIsRoot(R.layout.adapter_default_entry)
.include(FencedCodeBlock.class, SimpleEntry.create(R.layout.adapter_fenced_code_block, R.id.text_view))
.build();
```
:::tip
`SimpleEntry` also takes care of _caching_ parsed markdown. So each node will be
parsed only once and each subsequent adapter binding call will reuse previously cached markdown.
:::
:::tip Tables
There is a standalone artifact that adds support for displaying markdown tables
natively via `TableLayout`. Please refer to its [documentation](/docs/v4/recycler-table/)
:::

View File

@ -0,0 +1,70 @@
# Simple Extension <Badge text="4.0.0" />
<MavenBadge4 :artifact="'simple-ext'" />
`SimpleExtPlugin` allows creating simple _delimited_ extensions, for example:
```md
+this is text surrounded by `+`+
```
```java
final Markwon markwon = Markwon.builder(this)
.usePlugin(SimpleExtPlugin.create(plugin -> plugin
// +sometext+
.addExtension(1, '+', new SpanFactory() {
@Override
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
return new EmphasisSpan();
}
})
.build();
```
or
```java
final Markwon markwon = Markwon.builder(this)
.usePlugin(SimpleExtPlugin.create())
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configure(@NonNull Registry registry) {
registry.require(SimpleExtPlugin.class, new Action<SimpleExtPlugin>() {
@Override
public void apply(@NonNull SimpleExtPlugin plugin) {
plugin.addExtension(1, '+', new SpanFactory() {
@Override
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
return new EmphasisSpan();
}
})
}
});
}
})
.build();
```
If opening and closing characters are different another method can be used:
```java
plugin.addExtension(
/*length*/2,
/*openingCharacter*/'@',
/*closingCharacter*/'$',
/*spanFactory*/(configuration, props) -> new ForegroundColorSpan(Color.RED))))
```
This extension will be applied to a text like this:
```md
@@we are inside different delimiter characters$$
```
:::warning
Space character cannot be used as a delimiter (from either side). So,
```java
plugin.addExtension(1, '@', ' ', /*spanFactory*/);
```
won't work for `@some-text ` text
:::

View File

@ -0,0 +1,74 @@
# Syntax highlight
<MavenBadge4 :artifact="'syntax-highlight'" />
This is a 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.
<img :src="$withBase('/art/markwon-syntax-default.png')" alt="theme-default" width="80%">
<img :src="$withBase('/art/markwon-syntax-darkula.png')" alt="theme-darkula" width="80%">
---
First, we need to obtain an instance of `Prism4jSyntaxHighlight` which implements Markwon's `SyntaxHighlight`:
```java
final SyntaxHighlight highlight =
Prism4jSyntaxHighlight.create(Prism4j, Prism4jTheme);
```
we also can obtain an instance of `Prism4jSyntaxHighlight` that has a _fallback_ option (if a language is not defined in `Prism4j` instance, fallback language can be used):
```java
final SyntaxHighlight highlight =
Prism4jSyntaxHighlight.create(Prism4j, Prism4jTheme, String);
```
Generally obtaining a `Prism4j` instance is pretty easy:
```java
final Prism4j prism4j = new Prism4j(new GrammarLocatorDef());
```
Where `GrammarLocatorDef` is a generated grammar locator (if you use `prism4j-bundler` annotation processor)
`Prism4jTheme` is a specific type that is defined in this module (`prism4j` doesn't know anything about rendering). It has 2 implementations:
* `Prism4jThemeDefault`
* `Prism4jThemeDarkula`
Both of them can be obtained via factory method `create`:
* `Prism4jThemeDefault.create()`
* `Prism4jThemeDarkula.create()`
But of cause nothing is stopping you from defining your own theme:
```java
public interface Prism4jTheme {
@ColorInt
int background();
@ColorInt
int textColor();
void apply(
@NonNull String language,
@NonNull Prism4j.Syntax syntax,
@NonNull SpannableStringBuilder builder,
int start,
int end
);
}
```
:::tip
You can extend `Prism4jThemeBase` which has some helper methods
:::
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(SyntaxHighlightPlugin.create(prism4j, prism4jTheme))
```

View File

@ -3,12 +3,14 @@ org.gradle.jvmargs=-Xmx5g -Dfile.encoding=UTF-8
#org.gradle.parallel=true
org.gradle.configureondemand=true
android.useAndroidX=true
android.enableJetifier=true
android.enableBuildCache=true
android.buildCacheDir=build/pre-dex-cache
VERSION_NAME=3.0.1
VERSION_NAME=4.0.0
GROUP=ru.noties.markwon
GROUP=io.noties.markwon
POM_DESCRIPTION=Markwon markdown for Android
POM_URL=https://github.com/noties/Markwon
POM_SCM_URL=https://github.com/noties/Markwon

Binary file not shown.

View File

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

2
gradlew vendored
View File

@ -28,7 +28,7 @@ APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
DEFAULT_JVM_OPTS='"-Xmx64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

2
gradlew.bat vendored
View File

@ -14,7 +14,7 @@ set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DEFAULT_JVM_OPTS="-Xmx64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

3
markwon-core/README.md Normal file
View File

@ -0,0 +1,3 @@
# Markwon Core
https://noties.io/Markwon/docs/v4/core/getting-started.html

View File

@ -16,7 +16,7 @@ android {
dependencies {
deps.with {
api it['support-annotations']
api it['x-annotations']
api it['commonmark']
}

View File

@ -1 +1 @@
<manifest package="ru.noties.markwon.renderer" />
<manifest package="io.noties.markwon" />

View File

@ -1,17 +1,14 @@
package ru.noties.markwon;
package io.noties.markwon;
import android.support.annotation.NonNull;
import android.text.Spanned;
import android.widget.TextView;
import androidx.annotation.NonNull;
import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
import ru.noties.markwon.core.CorePlugin;
import ru.noties.markwon.core.MarkwonTheme;
import ru.noties.markwon.html.MarkwonHtmlRenderer;
import ru.noties.markwon.image.AsyncDrawableLoader;
import ru.noties.markwon.priority.Priority;
import io.noties.markwon.core.MarkwonTheme;
/**
* Class that extends {@link MarkwonPlugin} with all methods implemented (empty body)
@ -22,6 +19,11 @@ import ru.noties.markwon.priority.Priority;
*/
public abstract class AbstractMarkwonPlugin implements MarkwonPlugin {
@Override
public void configure(@NonNull Registry registry) {
}
@Override
public void configureParser(@NonNull Parser.Builder builder) {
@ -32,11 +34,6 @@ public abstract class AbstractMarkwonPlugin implements MarkwonPlugin {
}
@Override
public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) {
}
@Override
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
@ -52,18 +49,6 @@ public abstract class AbstractMarkwonPlugin implements MarkwonPlugin {
}
@Override
public void configureHtmlRenderer(@NonNull MarkwonHtmlRenderer.Builder builder) {
}
@NonNull
@Override
public Priority priority() {
// by default all come after CorePlugin
return Priority.after(CorePlugin.class);
}
@NonNull
@Override
public String processMarkdown(@NonNull String markdown) {

View File

@ -0,0 +1,14 @@
package io.noties.markwon;
import android.view.View;
import androidx.annotation.NonNull;
/**
* @see LinkResolverDef
* @see MarkwonConfiguration.Builder#linkResolver(LinkResolver)
* @since 4.0.0
*/
public interface LinkResolver {
void resolve(@NonNull View view, @NonNull String link);
}

View File

@ -1,19 +1,18 @@
package ru.noties.markwon;
package io.noties.markwon;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.provider.Browser;
import android.support.annotation.NonNull;
import android.util.Log;
import android.view.View;
import ru.noties.markwon.core.spans.LinkSpan;
import androidx.annotation.NonNull;
public class LinkResolverDef implements LinkSpan.Resolver {
public class LinkResolverDef implements LinkResolver {
@Override
public void resolve(View view, @NonNull String link) {
public void resolve(@NonNull View view, @NonNull String link) {
final Uri uri = Uri.parse(link);
final Context context = view.getContext();
final Intent intent = new Intent(Intent.ACTION_VIEW, uri);

View File

@ -1,14 +1,15 @@
package ru.noties.markwon;
package io.noties.markwon;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.Spanned;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.commonmark.node.Node;
import ru.noties.markwon.core.CorePlugin;
import io.noties.markwon.core.CorePlugin;
/**
* Class to parse and render markdown. Since version 3.0.0 instance specific (previously consisted
@ -37,13 +38,26 @@ public abstract class Markwon {
}
/**
* Factory method to obtain an instance of {@link Builder}.
* Factory method to obtain an instance of {@link Builder} with {@link CorePlugin} added.
*
* @see Builder
* @see #builderNoCore(Context)
* @since 3.0.0
*/
@NonNull
public static Builder builder(@NonNull Context context) {
return new MarkwonBuilderImpl(context)
// @since 4.0.0 add CorePlugin
.usePlugin(CorePlugin.create());
}
/**
* Factory method to obtain an instance of {@link Builder} without {@link CorePlugin}.
*
* @since 4.0.0
*/
@NonNull
public static Builder builderNoCore(@NonNull Context context) {
return new MarkwonBuilderImpl(context);
}

View File

@ -0,0 +1,110 @@
package io.noties.markwon;
import android.content.Context;
import android.widget.TextView;
import androidx.annotation.NonNull;
import org.commonmark.parser.Parser;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import io.noties.markwon.core.MarkwonTheme;
/**
* @since 3.0.0
*/
class MarkwonBuilderImpl implements Markwon.Builder {
private final Context context;
private final List<MarkwonPlugin> plugins = new ArrayList<>(3);
private TextView.BufferType bufferType = TextView.BufferType.SPANNABLE;
MarkwonBuilderImpl(@NonNull Context context) {
this.context = context;
}
@NonNull
@Override
public Markwon.Builder bufferType(@NonNull TextView.BufferType bufferType) {
this.bufferType = bufferType;
return this;
}
@NonNull
@Override
public Markwon.Builder usePlugin(@NonNull MarkwonPlugin plugin) {
plugins.add(plugin);
return this;
}
@NonNull
@Override
public Markwon.Builder usePlugins(@NonNull Iterable<? extends MarkwonPlugin> plugins) {
final Iterator<? extends MarkwonPlugin> iterator = plugins.iterator();
MarkwonPlugin plugin;
while (iterator.hasNext()) {
plugin = iterator.next();
if (plugin == null) {
throw new NullPointerException();
}
this.plugins.add(plugin);
}
return this;
}
@NonNull
@Override
public Markwon build() {
if (plugins.isEmpty()) {
throw new IllegalStateException("No plugins were added to this builder. Use #usePlugin " +
"method to add them");
}
// please note that this method must not modify supplied collection
// if nothing should be done -> the same collection can be returned
final List<MarkwonPlugin> plugins = preparePlugins(this.plugins);
final Parser.Builder parserBuilder = new Parser.Builder();
final MarkwonTheme.Builder themeBuilder = MarkwonTheme.builderWithDefaults(context);
final MarkwonConfiguration.Builder configurationBuilder = new MarkwonConfiguration.Builder();
final MarkwonVisitor.Builder visitorBuilder = new MarkwonVisitorImpl.BuilderImpl();
final MarkwonSpansFactory.Builder spanFactoryBuilder = new MarkwonSpansFactoryImpl.BuilderImpl();
for (MarkwonPlugin plugin : plugins) {
plugin.configureParser(parserBuilder);
plugin.configureTheme(themeBuilder);
plugin.configureConfiguration(configurationBuilder);
plugin.configureVisitor(visitorBuilder);
plugin.configureSpansFactory(spanFactoryBuilder);
}
final MarkwonConfiguration configuration = configurationBuilder.build(
themeBuilder.build(),
spanFactoryBuilder.build());
final RenderProps renderProps = new RenderPropsImpl();
return new MarkwonImpl(
bufferType,
parserBuilder.build(),
visitorBuilder.build(configuration, renderProps),
Collections.unmodifiableList(plugins)
);
}
@NonNull
private static List<MarkwonPlugin> preparePlugins(@NonNull List<MarkwonPlugin> plugins) {
return new RegistryImpl(plugins).process();
}
}

View File

@ -1,18 +1,15 @@
package ru.noties.markwon;
package io.noties.markwon;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import ru.noties.markwon.core.MarkwonTheme;
import ru.noties.markwon.core.spans.LinkSpan;
import ru.noties.markwon.html.MarkwonHtmlParser;
import ru.noties.markwon.html.MarkwonHtmlRenderer;
import ru.noties.markwon.image.AsyncDrawableLoader;
import ru.noties.markwon.image.ImageSizeResolver;
import ru.noties.markwon.image.ImageSizeResolverDef;
import ru.noties.markwon.syntax.SyntaxHighlight;
import ru.noties.markwon.syntax.SyntaxHighlightNoOp;
import ru.noties.markwon.urlprocessor.UrlProcessor;
import ru.noties.markwon.urlprocessor.UrlProcessorNoOp;
import io.noties.markwon.core.MarkwonTheme;
import io.noties.markwon.image.AsyncDrawableLoader;
import io.noties.markwon.image.ImageSizeResolver;
import io.noties.markwon.image.ImageSizeResolverDef;
import io.noties.markwon.syntax.SyntaxHighlight;
import io.noties.markwon.syntax.SyntaxHighlightNoOp;
import io.noties.markwon.urlprocessor.UrlProcessor;
import io.noties.markwon.urlprocessor.UrlProcessorNoOp;
/**
* since 3.0.0 renamed `SpannableConfiguration` -&gt; `MarkwonConfiguration`
@ -28,11 +25,9 @@ public class MarkwonConfiguration {
private final MarkwonTheme theme;
private final AsyncDrawableLoader asyncDrawableLoader;
private final SyntaxHighlight syntaxHighlight;
private final LinkSpan.Resolver linkResolver;
private final LinkResolver linkResolver;
private final UrlProcessor urlProcessor;
private final ImageSizeResolver imageSizeResolver;
private final MarkwonHtmlParser htmlParser;
private final MarkwonHtmlRenderer htmlRenderer;
// @since 3.0.0
private final MarkwonSpansFactory spansFactory;
@ -45,8 +40,6 @@ public class MarkwonConfiguration {
this.urlProcessor = builder.urlProcessor;
this.imageSizeResolver = builder.imageSizeResolver;
this.spansFactory = builder.spansFactory;
this.htmlParser = builder.htmlParser;
this.htmlRenderer = builder.htmlRenderer;
}
@NonNull
@ -65,7 +58,7 @@ public class MarkwonConfiguration {
}
@NonNull
public LinkSpan.Resolver linkResolver() {
public LinkResolver linkResolver() {
return linkResolver;
}
@ -79,16 +72,6 @@ public class MarkwonConfiguration {
return imageSizeResolver;
}
@NonNull
public MarkwonHtmlParser htmlParser() {
return htmlParser;
}
@NonNull
public MarkwonHtmlRenderer htmlRenderer() {
return htmlRenderer;
}
/**
* @since 3.0.0
*/
@ -103,16 +86,23 @@ public class MarkwonConfiguration {
private MarkwonTheme theme;
private AsyncDrawableLoader asyncDrawableLoader;
private SyntaxHighlight syntaxHighlight;
private LinkSpan.Resolver linkResolver;
private LinkResolver linkResolver;
private UrlProcessor urlProcessor;
private ImageSizeResolver imageSizeResolver;
private MarkwonHtmlParser htmlParser;
private MarkwonHtmlRenderer htmlRenderer;
private MarkwonSpansFactory spansFactory;
Builder() {
}
/**
* @since 4.0.0
*/
@NonNull
public Builder asyncDrawableLoader(@NonNull AsyncDrawableLoader asyncDrawableLoader) {
this.asyncDrawableLoader = asyncDrawableLoader;
return this;
}
@NonNull
public Builder syntaxHighlight(@NonNull SyntaxHighlight syntaxHighlight) {
this.syntaxHighlight = syntaxHighlight;
@ -120,7 +110,7 @@ public class MarkwonConfiguration {
}
@NonNull
public Builder linkResolver(@NonNull LinkSpan.Resolver linkResolver) {
public Builder linkResolver(@NonNull LinkResolver linkResolver) {
this.linkResolver = linkResolver;
return this;
}
@ -131,12 +121,6 @@ public class MarkwonConfiguration {
return this;
}
@NonNull
public Builder htmlParser(@NonNull MarkwonHtmlParser htmlParser) {
this.htmlParser = htmlParser;
return this;
}
/**
* @since 1.0.1
*/
@ -149,15 +133,16 @@ public class MarkwonConfiguration {
@NonNull
public MarkwonConfiguration build(
@NonNull MarkwonTheme theme,
@NonNull AsyncDrawableLoader asyncDrawableLoader,
@NonNull MarkwonHtmlRenderer htmlRenderer,
@NonNull MarkwonSpansFactory spansFactory) {
this.theme = theme;
this.asyncDrawableLoader = asyncDrawableLoader;
this.htmlRenderer = htmlRenderer;
this.spansFactory = spansFactory;
// @since 4.0.0
if (asyncDrawableLoader == null) {
asyncDrawableLoader = AsyncDrawableLoader.noOp();
}
if (syntaxHighlight == null) {
syntaxHighlight = new SyntaxHighlightNoOp();
}
@ -174,10 +159,6 @@ public class MarkwonConfiguration {
imageSizeResolver = new ImageSizeResolverDef();
}
if (htmlParser == null) {
htmlParser = MarkwonHtmlParser.noOp();
}
return new MarkwonConfiguration(this);
}
}

View File

@ -1,10 +1,11 @@
package ru.noties.markwon;
package io.noties.markwon;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.Spanned;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.commonmark.node.Node;
import org.commonmark.parser.Parser;

View File

@ -1,30 +1,58 @@
package ru.noties.markwon;
package io.noties.markwon;
import android.support.annotation.NonNull;
import android.text.Spanned;
import android.widget.TextView;
import androidx.annotation.NonNull;
import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
import ru.noties.markwon.core.MarkwonTheme;
import ru.noties.markwon.html.MarkwonHtmlRenderer;
import ru.noties.markwon.image.AsyncDrawableLoader;
import ru.noties.markwon.image.MediaDecoder;
import ru.noties.markwon.image.SchemeHandler;
import ru.noties.markwon.priority.Priority;
import io.noties.markwon.core.CorePlugin;
import io.noties.markwon.core.MarkwonTheme;
import io.noties.markwon.image.AsyncDrawableSpan;
import io.noties.markwon.movement.MovementMethodPlugin;
/**
* Class represents a plugin (extension) to Markwon to configure how parsing and rendering
* of markdown is carried on.
*
* @see AbstractMarkwonPlugin
* @see ru.noties.markwon.core.CorePlugin
* @see ru.noties.markwon.image.ImagesPlugin
* @see CorePlugin
* @see MovementMethodPlugin
* @since 3.0.0
*/
public interface MarkwonPlugin {
/**
* @see Registry#require(Class, Action)
* @since 4.0.0
*/
interface Action<P extends MarkwonPlugin> {
void apply(@NonNull P p);
}
/**
* @see #configure(Registry)
* @since 4.0.0
*/
interface Registry {
@NonNull
<P extends MarkwonPlugin> P require(@NonNull Class<P> plugin);
<P extends MarkwonPlugin> void require(
@NonNull Class<P> plugin,
@NonNull Action<? super P> action);
}
/**
* This method will be called before any other during {@link Markwon} instance construction.
*
* @since 4.0.0
*/
void configure(@NonNull Registry registry);
/**
* Method to configure <code>org.commonmark.parser.Parser</code> (for example register custom
* extension, etc).
@ -39,17 +67,6 @@ public interface MarkwonPlugin {
*/
void configureTheme(@NonNull MarkwonTheme.Builder builder);
/**
* Configure image loading functionality. For example add new content-types
* {@link AsyncDrawableLoader.Builder#addMediaDecoder(String, MediaDecoder)}, a transport
* layer (network, file, etc) {@link AsyncDrawableLoader.Builder#addSchemeHandler(String, SchemeHandler)}
* or modify existing properties.
*
* @see AsyncDrawableLoader
* @see AsyncDrawableLoader.Builder
*/
void configureImages(@NonNull AsyncDrawableLoader.Builder builder);
/**
* Configure {@link MarkwonConfiguration}
*
@ -74,17 +91,6 @@ public interface MarkwonPlugin {
*/
void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder);
/**
* Configure {@link MarkwonHtmlRenderer} to add or remove HTML {@link ru.noties.markwon.html.TagHandler}s
*
* @see MarkwonHtmlRenderer
* @see MarkwonHtmlRenderer.Builder
*/
void configureHtmlRenderer(@NonNull MarkwonHtmlRenderer.Builder builder);
@NonNull
Priority priority();
/**
* Process input markdown and return new string to be used in parsing stage further.
* Can be described as <code>pre-processing</code> of markdown String.
@ -117,9 +123,9 @@ public interface MarkwonPlugin {
/**
* This method will be called <strong>before</strong> calling <code>TextView#setText</code>.
* <p>
* It can be useful to prepare a TextView for markdown. For example {@link ru.noties.markwon.image.ImagesPlugin}
* uses this method to unregister previously registered {@link ru.noties.markwon.image.AsyncDrawableSpan}
* (if there are such spans in this TextView at this point). Or {@link ru.noties.markwon.core.CorePlugin}
* It can be useful to prepare a TextView for markdown. For example {@code ru.noties.markwon.image.ImagesPlugin}
* uses this method to unregister previously registered {@link AsyncDrawableSpan}
* (if there are such spans in this TextView at this point). Or {@link CorePlugin}
* which measures ordered list numbers
*
* @param textView TextView to which <code>markdown</code> will be applied
@ -130,8 +136,8 @@ public interface MarkwonPlugin {
/**
* This method will be called <strong>after</strong> markdown was applied.
* <p>
* It can be useful to trigger certain action on spans/textView. For example {@link ru.noties.markwon.image.ImagesPlugin}
* uses this method to register {@link ru.noties.markwon.image.AsyncDrawableSpan} and start
* It can be useful to trigger certain action on spans/textView. For example {@code ru.noties.markwon.image.ImagesPlugin}
* uses this method to register {@link AsyncDrawableSpan} and start
* asynchronously loading images.
* <p>
* Unlike {@link #beforeSetText(TextView, Spanned)} this method does not receive parsed markdown

View File

@ -1,6 +1,6 @@
package ru.noties.markwon;
package io.noties.markwon;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import org.commonmark.node.Node;

View File

@ -1,7 +1,7 @@
package ru.noties.markwon;
package io.noties.markwon;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.commonmark.node.Node;

View File

@ -1,7 +1,7 @@
package ru.noties.markwon;
package io.noties.markwon;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.commonmark.node.Node;

View File

@ -1,7 +1,7 @@
package ru.noties.markwon;
package io.noties.markwon;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.commonmark.node.Node;
import org.commonmark.node.Visitor;

View File

@ -1,7 +1,7 @@
package ru.noties.markwon;
package io.noties.markwon;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.commonmark.node.BlockQuote;
import org.commonmark.node.BulletList;

View File

@ -1,7 +1,7 @@
package ru.noties.markwon;
package io.noties.markwon;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* Class to hold data in {@link RenderProps}. Represents a certain <em>property</em>.

View File

@ -0,0 +1,116 @@
package io.noties.markwon;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import io.noties.markwon.core.CorePlugin;
// @since 4.0.0
class RegistryImpl implements MarkwonPlugin.Registry {
private final List<MarkwonPlugin> origin;
private final List<MarkwonPlugin> plugins;
private final Set<MarkwonPlugin> pending;
RegistryImpl(@NonNull List<MarkwonPlugin> origin) {
this.origin = origin;
this.plugins = new ArrayList<>(origin.size());
this.pending = new HashSet<>(3);
}
@NonNull
@Override
public <P extends MarkwonPlugin> P require(@NonNull Class<P> plugin) {
return get(plugin);
}
@Override
public <P extends MarkwonPlugin> void require(
@NonNull Class<P> plugin,
@NonNull MarkwonPlugin.Action<? super P> action) {
action.apply(get(plugin));
}
@NonNull
List<MarkwonPlugin> process() {
for (MarkwonPlugin plugin : origin) {
configure(plugin);
}
return plugins;
}
private void configure(@NonNull MarkwonPlugin plugin) {
// important -> check if it's in plugins
// if it is -> no need to configure (already configured)
if (!plugins.contains(plugin)) {
if (pending.contains(plugin)) {
throw new IllegalStateException("Cyclic dependency chain found: " + pending);
}
// start tracking plugins that are pending for configuration
pending.add(plugin);
plugin.configure(this);
// stop pending tracking
pending.remove(plugin);
// check again if it's included (a child might've configured it already)
// add to out-collection if not already present
// this is a bit different from `find` method as it does check for exact instance
// and not a sub-type
if (!plugins.contains(plugin)) {
// core-plugin must always be the first one (if it's present)
if (CorePlugin.class.isAssignableFrom(plugin.getClass())) {
plugins.add(0, plugin);
} else {
plugins.add(plugin);
}
}
}
}
@NonNull
private <P extends MarkwonPlugin> P get(@NonNull Class<P> type) {
// check if present already in plugins
// find in origin, if not found -> throw, else add to out-plugins
P plugin = find(plugins, type);
if (plugin == null) {
plugin = find(origin, type);
if (plugin == null) {
throw new IllegalStateException("Requested plugin is not added: " +
"" + type.getName() + ", plugins: " + origin);
}
configure(plugin);
}
return plugin;
}
@Nullable
private static <P extends MarkwonPlugin> P find(
@NonNull List<MarkwonPlugin> plugins,
@NonNull Class<P> type) {
for (MarkwonPlugin plugin : plugins) {
if (type.isAssignableFrom(plugin.getClass())) {
//noinspection unchecked
return (P) plugin;
}
}
return null;
}
}

View File

@ -1,7 +1,7 @@
package ru.noties.markwon;
package io.noties.markwon;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* @since 3.0.0

View File

@ -1,7 +1,7 @@
package ru.noties.markwon;
package io.noties.markwon;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;

View File

@ -1,7 +1,7 @@
package ru.noties.markwon;
package io.noties.markwon;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* @since 3.0.0

View File

@ -1,11 +1,12 @@
package ru.noties.markwon;
package io.noties.markwon;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;

View File

@ -1,12 +1,13 @@
package ru.noties.markwon.core;
package io.noties.markwon.core;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import org.commonmark.node.BlockQuote;
import org.commonmark.node.BulletList;
import org.commonmark.node.Code;
@ -14,6 +15,7 @@ import org.commonmark.node.Emphasis;
import org.commonmark.node.FencedCodeBlock;
import org.commonmark.node.HardLineBreak;
import org.commonmark.node.Heading;
import org.commonmark.node.Image;
import org.commonmark.node.IndentedCodeBlock;
import org.commonmark.node.Link;
import org.commonmark.node.ListBlock;
@ -26,21 +28,27 @@ import org.commonmark.node.StrongEmphasis;
import org.commonmark.node.Text;
import org.commonmark.node.ThematicBreak;
import ru.noties.markwon.AbstractMarkwonPlugin;
import ru.noties.markwon.MarkwonConfiguration;
import ru.noties.markwon.MarkwonSpansFactory;
import ru.noties.markwon.MarkwonVisitor;
import ru.noties.markwon.core.factory.BlockQuoteSpanFactory;
import ru.noties.markwon.core.factory.CodeBlockSpanFactory;
import ru.noties.markwon.core.factory.CodeSpanFactory;
import ru.noties.markwon.core.factory.EmphasisSpanFactory;
import ru.noties.markwon.core.factory.HeadingSpanFactory;
import ru.noties.markwon.core.factory.LinkSpanFactory;
import ru.noties.markwon.core.factory.ListItemSpanFactory;
import ru.noties.markwon.core.factory.StrongEmphasisSpanFactory;
import ru.noties.markwon.core.factory.ThematicBreakSpanFactory;
import ru.noties.markwon.core.spans.OrderedListItemSpan;
import ru.noties.markwon.priority.Priority;
import java.util.ArrayList;
import java.util.List;
import io.noties.markwon.AbstractMarkwonPlugin;
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 io.noties.markwon.core.factory.BlockQuoteSpanFactory;
import io.noties.markwon.core.factory.CodeBlockSpanFactory;
import io.noties.markwon.core.factory.CodeSpanFactory;
import io.noties.markwon.core.factory.EmphasisSpanFactory;
import io.noties.markwon.core.factory.HeadingSpanFactory;
import io.noties.markwon.core.factory.LinkSpanFactory;
import io.noties.markwon.core.factory.ListItemSpanFactory;
import io.noties.markwon.core.factory.StrongEmphasisSpanFactory;
import io.noties.markwon.core.factory.ThematicBreakSpanFactory;
import io.noties.markwon.core.spans.OrderedListItemSpan;
import io.noties.markwon.image.ImageProps;
/**
* @see CoreProps
@ -48,14 +56,57 @@ import ru.noties.markwon.priority.Priority;
*/
public class CorePlugin extends AbstractMarkwonPlugin {
/**
* @see #addOnTextAddedListener(OnTextAddedListener)
* @since 4.0.0
*/
public interface OnTextAddedListener {
/**
* Will be called when new text is added to resulting {@link SpannableBuilder}.
* Please note that only text represented by {@link Text} node will trigger this callback
* (text inside code and code-blocks won\'t trigger it).
* <p>
* Please note that if you wish to add spans you must use {@code start} parameter
* in order to place spans correctly ({@code start} represents the index at which {@code text}
* was added). So, to set a span for the whole length of the text added one should use:
* <p>
* {@code
* visitor.builder().setSpan(new MySpan(), start, start + text.length(), 0);
* }
*
* @param visitor {@link MarkwonVisitor}
* @param text literal that had been added
* @param start index in {@code visitor} as which text had been added
* @see #addOnTextAddedListener(OnTextAddedListener)
*/
void onTextAdded(@NonNull MarkwonVisitor visitor, @NonNull String text, int start);
}
@NonNull
public static CorePlugin create() {
return new CorePlugin();
}
// @since 4.0.0
private final List<OnTextAddedListener> onTextAddedListeners = new ArrayList<>(0);
protected CorePlugin() {
}
/**
* Can be useful to post-process text added. For example for auto-linking capabilities.
*
* @see OnTextAddedListener
* @since 4.0.0
*/
@SuppressWarnings("UnusedReturnValue")
@NonNull
public CorePlugin addOnTextAddedListener(@NonNull OnTextAddedListener onTextAddedListener) {
onTextAddedListeners.add(onTextAddedListener);
return this;
}
@Override
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
text(builder);
@ -65,6 +116,7 @@ public class CorePlugin extends AbstractMarkwonPlugin {
code(builder);
fencedCodeBlock(builder);
indentedCodeBlock(builder);
image(builder);
bulletList(builder);
orderedList(builder);
listItem(builder);
@ -95,12 +147,6 @@ public class CorePlugin extends AbstractMarkwonPlugin {
.setFactory(ThematicBreak.class, new ThematicBreakSpanFactory());
}
@NonNull
@Override
public Priority priority() {
return Priority.none();
}
@Override
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
OrderedListItemSpan.measure(textView, markdown);
@ -116,11 +162,23 @@ public class CorePlugin extends AbstractMarkwonPlugin {
}
}
private static void text(@NonNull MarkwonVisitor.Builder builder) {
private void text(@NonNull MarkwonVisitor.Builder builder) {
builder.on(Text.class, new MarkwonVisitor.NodeVisitor<Text>() {
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull Text text) {
visitor.builder().append(text.getLiteral());
final String literal = text.getLiteral();
visitor.builder().append(literal);
// @since 4.0.0
if (!onTextAddedListeners.isEmpty()) {
// calculate the start position
final int length = visitor.length() - literal.length();
for (OnTextAddedListener onTextAddedListener : onTextAddedListeners) {
onTextAddedListener.onTextAdded(visitor, literal, length);
}
}
}
});
}
@ -204,6 +262,53 @@ public class CorePlugin extends AbstractMarkwonPlugin {
});
}
// @since 4.0.0
// his method is moved from ImagesPlugin. Alternative implementations must set SpanFactory
// for Image node in order for this visitor to function
private static void image(MarkwonVisitor.Builder builder) {
builder.on(Image.class, new MarkwonVisitor.NodeVisitor<Image>() {
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull Image image) {
// if there is no image spanFactory, ignore
final SpanFactory spanFactory = visitor.configuration().spansFactory().get(Image.class);
if (spanFactory == null) {
visitor.visitChildren(image);
return;
}
final int length = visitor.length();
visitor.visitChildren(image);
// we must check if anything _was_ added, as we need at least one char to render
if (length == visitor.length()) {
visitor.builder().append('\uFFFC');
}
final MarkwonConfiguration configuration = visitor.configuration();
final Node parent = image.getParent();
final boolean link = parent instanceof Link;
final String destination = configuration
.urlProcessor()
.process(image.getDestination());
final RenderProps props = visitor.renderProps();
// apply image properties
// Please note that we explicitly set IMAGE_SIZE to null as we do not clear
// properties after we applied span (we could though)
ImageProps.DESTINATION.set(props, destination);
ImageProps.REPLACEMENT_TEXT_IS_LINK.set(props, link);
ImageProps.IMAGE_SIZE.set(props, null);
visitor.setSpans(length, spanFactory.getSpans(configuration, props));
}
});
}
@VisibleForTesting
static void visitCodeBlock(
@NonNull MarkwonVisitor visitor,

Some files were not shown because too many files have changed in this diff Show More