Merge pull request #105 from noties/v3.0.0
# 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.
@ -10,7 +10,7 @@ android:
|
|||||||
- tools
|
- tools
|
||||||
|
|
||||||
- build-tools-28.0.3
|
- build-tools-28.0.3
|
||||||
- android-27
|
- android-28
|
||||||
|
|
||||||
branches:
|
branches:
|
||||||
except:
|
except:
|
||||||
|
19
README.md
@ -2,11 +2,6 @@
|
|||||||
|
|
||||||
# Markwon
|
# Markwon
|
||||||
|
|
||||||
[](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%22markwon%22)
|
|
||||||
[](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%22markwon-image-loader%22)
|
|
||||||
[](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%22markwon-syntax-highlight%22)
|
|
||||||
[](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%22markwon-view%22)
|
|
||||||
|
|
||||||
[](https://travis-ci.org/noties/Markwon)
|
[](https://travis-ci.org/noties/Markwon)
|
||||||
|
|
||||||
**Markwon** is a markdown library for Android. It parses markdown
|
**Markwon** is a markdown library for Android. It parses markdown
|
||||||
@ -32,15 +27,20 @@ features listed in [commonmark-spec] are supported
|
|||||||
[sample-apk]: https://github.com/noties/Markwon/releases
|
[sample-apk]: https://github.com/noties/Markwon/releases
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
```groovy
|
```groovy
|
||||||
implementation "ru.noties:markwon:${markwonVersion}"
|
implementation "ru.noties.markwon:core:${markwonVersion}"
|
||||||
implementation "ru.noties:markwon-image-loader:${markwonVersion}" // optional
|
|
||||||
implementation "ru.noties:markwon-syntax-highlight:${markwonVersion}" // optional
|
|
||||||
implementation "ru.noties:markwon-view:${markwonVersion}" // optional
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Please visit [documentation] web-site for further reference
|
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
|
||||||
|
|
||||||
|
|
||||||
## Supported markdown features:
|
## Supported markdown features:
|
||||||
* Emphasis (`*`, `_`)
|
* Emphasis (`*`, `_`)
|
||||||
* Strong emphasis (`**`, `__`)
|
* Strong emphasis (`**`, `__`)
|
||||||
@ -55,6 +55,7 @@ Please visit [documentation] web-site for further reference
|
|||||||
* Code blocks
|
* Code blocks
|
||||||
* Tables (*with limitations*)
|
* Tables (*with limitations*)
|
||||||
* Syntax highlight
|
* Syntax highlight
|
||||||
|
* LaTeX formulas
|
||||||
* HTML
|
* HTML
|
||||||
* Emphasis (`<i>`, `<em>`, `<cite>`, `<dfn>`)
|
* Emphasis (`<i>`, `<em>`, `<cite>`, `<dfn>`)
|
||||||
* Strong emphasis (`<b>`, `<strong>`)
|
* Strong emphasis (`<b>`, `<strong>`)
|
||||||
|
@ -11,7 +11,7 @@ android {
|
|||||||
targetSdkVersion config['target-sdk']
|
targetSdkVersion config['target-sdk']
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName version
|
versionName version
|
||||||
setProperty("archivesBaseName", "markwon-sample-$versionName")
|
setProperty("archivesBaseName", "markwon-$versionName")
|
||||||
}
|
}
|
||||||
|
|
||||||
lintOptions {
|
lintOptions {
|
||||||
@ -28,8 +28,13 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
implementation project(':markwon')
|
implementation project(':markwon-core')
|
||||||
implementation project(':markwon-image-loader')
|
implementation project(':markwon-ext-strikethrough')
|
||||||
|
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-syntax-highlight')
|
implementation project(':markwon-syntax-highlight')
|
||||||
|
|
||||||
deps.with {
|
deps.with {
|
||||||
|
@ -9,7 +9,7 @@ import android.util.AttributeSet;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import ru.noties.markwon.R;
|
import ru.noties.markwon.R;
|
||||||
import ru.noties.markwon.spans.TaskListDrawable;
|
import ru.noties.markwon.ext.tasklist.TaskListDrawable;
|
||||||
|
|
||||||
public class DebugCheckboxDrawableView extends View {
|
public class DebugCheckboxDrawableView extends View {
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="ru.noties.markwon">
|
package="ru.noties.markwon">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
@ -10,8 +11,10 @@
|
|||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppThemeLight">
|
android:theme="@style/AppThemeLight"
|
||||||
|
tools:ignore="AllowBackup">
|
||||||
|
|
||||||
<activity android:name=".MainActivity">
|
<activity android:name=".MainActivity">
|
||||||
|
|
||||||
@ -38,21 +41,6 @@
|
|||||||
android:host="*"
|
android:host="*"
|
||||||
android:scheme="https" />
|
android:scheme="https" />
|
||||||
|
|
||||||
<!--<data-->
|
|
||||||
<!--android:host="*"-->
|
|
||||||
<!--android:scheme="http"-->
|
|
||||||
<!--android:mimeType="text/markdown"/>-->
|
|
||||||
|
|
||||||
<!--<data-->
|
|
||||||
<!--android:host="*"-->
|
|
||||||
<!--android:scheme="file"-->
|
|
||||||
<!--android:mimeType="text/markdown"/>-->
|
|
||||||
|
|
||||||
<!--<data-->
|
|
||||||
<!--android:host="*"-->
|
|
||||||
<!--android:scheme="https"-->
|
|
||||||
<!--android:mimeType="text/markdown"/>-->
|
|
||||||
|
|
||||||
<data android:pathPattern=".*\\.markdown" />
|
<data android:pathPattern=".*\\.markdown" />
|
||||||
<data android:pathPattern=".*\\.mdown" />
|
<data android:pathPattern=".*\\.mdown" />
|
||||||
<data android:pathPattern=".*\\.mkdn" />
|
<data android:pathPattern=".*\\.mkdn" />
|
||||||
|
@ -23,8 +23,8 @@ abstract class AppBarItem {
|
|||||||
final TextView subtitle;
|
final TextView subtitle;
|
||||||
|
|
||||||
Renderer(@NonNull View view, @NonNull View.OnClickListener themeChangeClicked) {
|
Renderer(@NonNull View view, @NonNull View.OnClickListener themeChangeClicked) {
|
||||||
this.title = Views.findView(view, R.id.app_bar_title);
|
this.title = view.findViewById(R.id.app_bar_title);
|
||||||
this.subtitle = Views.findView(view, R.id.app_bar_subtitle);
|
this.subtitle = view.findViewById(R.id.app_bar_subtitle);
|
||||||
view.findViewById(R.id.app_bar_theme_changer)
|
view.findViewById(R.id.app_bar_theme_changer)
|
||||||
.setOnClickListener(themeChangeClicked);
|
.setOnClickListener(themeChangeClicked);
|
||||||
}
|
}
|
||||||
|
@ -14,11 +14,6 @@ import dagger.Module;
|
|||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import okhttp3.Cache;
|
import okhttp3.Cache;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
import ru.noties.markwon.il.AsyncDrawableLoader;
|
|
||||||
import ru.noties.markwon.il.GifMediaDecoder;
|
|
||||||
import ru.noties.markwon.il.ImageMediaDecoder;
|
|
||||||
import ru.noties.markwon.il.SvgMediaDecoder;
|
|
||||||
import ru.noties.markwon.spans.AsyncDrawable;
|
|
||||||
import ru.noties.markwon.syntax.Prism4jThemeDarkula;
|
import ru.noties.markwon.syntax.Prism4jThemeDarkula;
|
||||||
import ru.noties.markwon.syntax.Prism4jThemeDefault;
|
import ru.noties.markwon.syntax.Prism4jThemeDefault;
|
||||||
import ru.noties.prism4j.Prism4j;
|
import ru.noties.prism4j.Prism4j;
|
||||||
@ -72,23 +67,6 @@ class AppModule {
|
|||||||
return new UriProcessorImpl();
|
return new UriProcessorImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
AsyncDrawable.Loader asyncDrawableLoader(
|
|
||||||
OkHttpClient client,
|
|
||||||
ExecutorService executorService,
|
|
||||||
Resources resources) {
|
|
||||||
return AsyncDrawableLoader.builder()
|
|
||||||
.client(client)
|
|
||||||
.executorService(executorService)
|
|
||||||
.resources(resources)
|
|
||||||
.mediaDecoders(
|
|
||||||
SvgMediaDecoder.create(resources),
|
|
||||||
GifMediaDecoder.create(false),
|
|
||||||
ImageMediaDecoder.create(resources)
|
|
||||||
)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
Prism4j prism4j() {
|
Prism4j prism4j() {
|
||||||
@ -104,12 +82,12 @@ class AppModule {
|
|||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
Prism4jThemeDarkula prism4jThemeDarkula() {
|
Prism4jThemeDarkula prism4jThemeDarkula() {
|
||||||
return Prism4jThemeDarkula.create();
|
return Prism4jThemeDarkula.create(0x0Fffffff);
|
||||||
}
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Provides
|
|
||||||
GifProcessor gifProcessor() {
|
|
||||||
return GifProcessor.create();
|
|
||||||
}
|
}
|
||||||
|
//
|
||||||
|
// @Singleton
|
||||||
|
// @Provides
|
||||||
|
// GifProcessor gifProcessor() {
|
||||||
|
// return GifProcessor.create();
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
package ru.noties.markwon;
|
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
|
|
||||||
import ru.noties.markwon.renderer.ImageSize;
|
|
||||||
import ru.noties.markwon.renderer.ImageSizeResolver;
|
|
||||||
import ru.noties.markwon.spans.AsyncDrawable;
|
|
||||||
import ru.noties.markwon.spans.AsyncDrawableSpan;
|
|
||||||
import ru.noties.markwon.spans.SpannableTheme;
|
|
||||||
|
|
||||||
public class GifAwareSpannableFactory extends SpannableFactoryDef {
|
|
||||||
|
|
||||||
private final GifPlaceholder gifPlaceholder;
|
|
||||||
|
|
||||||
public GifAwareSpannableFactory(@NonNull GifPlaceholder gifPlaceholder) {
|
|
||||||
this.gifPlaceholder = gifPlaceholder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public Object image(@NonNull SpannableTheme theme, @NonNull String destination, @NonNull AsyncDrawable.Loader loader, @NonNull ImageSizeResolver imageSizeResolver, @Nullable ImageSize imageSize, boolean replacementTextIsLink) {
|
|
||||||
return new AsyncDrawableSpan(
|
|
||||||
theme,
|
|
||||||
new GifAwareAsyncDrawable(
|
|
||||||
gifPlaceholder,
|
|
||||||
destination,
|
|
||||||
loader,
|
|
||||||
imageSizeResolver,
|
|
||||||
imageSize
|
|
||||||
),
|
|
||||||
AsyncDrawableSpan.ALIGN_BOTTOM,
|
|
||||||
replacementTextIsLink
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -6,6 +6,7 @@ import android.net.Uri;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
import android.text.Spanned;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
@ -27,9 +28,6 @@ public class MainActivity extends Activity {
|
|||||||
@Inject
|
@Inject
|
||||||
UriProcessor uriProcessor;
|
UriProcessor uriProcessor;
|
||||||
|
|
||||||
@Inject
|
|
||||||
GifProcessor gifProcessor;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(final Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@ -40,9 +38,6 @@ public class MainActivity extends Activity {
|
|||||||
|
|
||||||
themes.apply(this);
|
themes.apply(this);
|
||||||
|
|
||||||
// how can we obtain SpannableConfiguration after theme was applied?
|
|
||||||
// as we inject `themes` we won't be able to inject configuration, as it requires theme set
|
|
||||||
|
|
||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
// we process additionally github urls, as if url has in path `blob`, we won't receive
|
// we process additionally github urls, as if url has in path `blob`, we won't receive
|
||||||
@ -58,7 +53,7 @@ public class MainActivity extends Activity {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
final TextView textView = Views.findView(this, R.id.text);
|
final TextView textView = findViewById(R.id.text);
|
||||||
final View progress = findViewById(R.id.progress);
|
final View progress = findViewById(R.id.progress);
|
||||||
|
|
||||||
appBarRenderer.render(appBarState());
|
appBarRenderer.render(appBarState());
|
||||||
@ -68,11 +63,9 @@ public class MainActivity extends Activity {
|
|||||||
public void apply(final String text) {
|
public void apply(final String text) {
|
||||||
markdownRenderer.render(MainActivity.this, themes.isLight(), uri(), text, new MarkdownRenderer.MarkdownReadyListener() {
|
markdownRenderer.render(MainActivity.this, themes.isLight(), uri(), text, new MarkdownRenderer.MarkdownReadyListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onMarkdownReady(CharSequence markdown) {
|
public void onMarkdownReady(@NonNull Markwon markwon, Spanned markdown) {
|
||||||
|
|
||||||
Markwon.setText(textView, markdown);
|
markwon.setParsedMarkdown(textView, markdown);
|
||||||
|
|
||||||
gifProcessor.process(textView);
|
|
||||||
|
|
||||||
Views.setVisible(progress, false);
|
Views.setVisible(progress, false);
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import android.os.Handler;
|
|||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
import android.text.Spanned;
|
||||||
|
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
@ -13,24 +14,30 @@ import java.util.concurrent.Future;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import ru.noties.debug.Debug;
|
import ru.noties.debug.Debug;
|
||||||
import ru.noties.markwon.spans.AsyncDrawable;
|
import ru.noties.markwon.core.CorePlugin;
|
||||||
import ru.noties.markwon.spans.SpannableTheme;
|
import ru.noties.markwon.ext.strikethrough.StrikethroughPlugin;
|
||||||
import ru.noties.markwon.syntax.Prism4jSyntaxHighlight;
|
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.Prism4jTheme;
|
||||||
import ru.noties.markwon.syntax.Prism4jThemeDarkula;
|
import ru.noties.markwon.syntax.Prism4jThemeDarkula;
|
||||||
import ru.noties.markwon.syntax.Prism4jThemeDefault;
|
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 ru.noties.prism4j.Prism4j;
|
||||||
|
|
||||||
@ActivityScope
|
@ActivityScope
|
||||||
public class MarkdownRenderer {
|
public class MarkdownRenderer {
|
||||||
|
|
||||||
interface MarkdownReadyListener {
|
interface MarkdownReadyListener {
|
||||||
void onMarkdownReady(CharSequence markdown);
|
void onMarkdownReady(@NonNull Markwon markwon, Spanned markdown);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject
|
|
||||||
AsyncDrawable.Loader loader;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ExecutorService service;
|
ExecutorService service;
|
||||||
|
|
||||||
@ -64,9 +71,17 @@ public class MarkdownRenderer {
|
|||||||
cancel();
|
cancel();
|
||||||
|
|
||||||
task = service.submit(new Runnable() {
|
task = service.submit(new Runnable() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
try {
|
||||||
|
execute();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Debug.e(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void execute() {
|
||||||
final UrlProcessor urlProcessor;
|
final UrlProcessor urlProcessor;
|
||||||
if (uri == null) {
|
if (uri == null) {
|
||||||
urlProcessor = new UrlProcessorInitialReadme();
|
urlProcessor = new UrlProcessorInitialReadme();
|
||||||
@ -78,29 +93,28 @@ public class MarkdownRenderer {
|
|||||||
? prism4jThemeDefault
|
? prism4jThemeDefault
|
||||||
: prism4JThemeDarkula;
|
: prism4JThemeDarkula;
|
||||||
|
|
||||||
final int background = isLightTheme
|
final Markwon markwon = Markwon.builder(context)
|
||||||
? prism4jTheme.background()
|
.usePlugin(CorePlugin.create())
|
||||||
: 0x0Fffffff;
|
.usePlugin(ImagesPlugin.createWithAssets(context))
|
||||||
|
.usePlugin(SvgPlugin.create(context.getResources()))
|
||||||
final GifPlaceholder gifPlaceholder = new GifPlaceholder(
|
.usePlugin(GifPlugin.create(false))
|
||||||
context.getResources().getDrawable(R.drawable.ic_play_circle_filled_18dp_white),
|
.usePlugin(SyntaxHighlightPlugin.create(prism4j, prism4jTheme))
|
||||||
0x20000000
|
.usePlugin(GifAwarePlugin.create(context))
|
||||||
);
|
.usePlugin(TablePlugin.create(context))
|
||||||
|
.usePlugin(TaskListPlugin.create(context))
|
||||||
final SpannableConfiguration configuration = SpannableConfiguration.builder(context)
|
.usePlugin(StrikethroughPlugin.create())
|
||||||
.asyncDrawableLoader(loader)
|
.usePlugin(HtmlPlugin.create())
|
||||||
.urlProcessor(urlProcessor)
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
.syntaxHighlight(Prism4jSyntaxHighlight.create(prism4j, prism4jTheme))
|
@Override
|
||||||
.theme(SpannableTheme.builderWithDefaults(context)
|
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
|
||||||
.codeBackgroundColor(background)
|
builder.urlProcessor(urlProcessor);
|
||||||
.codeTextColor(prism4jTheme.textColor())
|
}
|
||||||
.build())
|
})
|
||||||
.factory(new GifAwareSpannableFactory(gifPlaceholder))
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
final long start = SystemClock.uptimeMillis();
|
final long start = SystemClock.uptimeMillis();
|
||||||
|
|
||||||
final CharSequence text = Markwon.markdown(configuration, markdown);
|
final Spanned text = markwon.toMarkdown(markdown);
|
||||||
|
|
||||||
final long end = SystemClock.uptimeMillis();
|
final long end = SystemClock.uptimeMillis();
|
||||||
|
|
||||||
@ -111,7 +125,7 @@ public class MarkdownRenderer {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (!isCancelled()) {
|
if (!isCancelled()) {
|
||||||
listener.onMarkdownReady(text);
|
listener.onMarkdownReady(markwon, text);
|
||||||
task = null;
|
task = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,9 @@ import android.net.Uri;
|
|||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import ru.noties.markwon.urlprocessor.UrlProcessor;
|
||||||
|
import ru.noties.markwon.urlprocessor.UrlProcessorRelativeToAbsolute;
|
||||||
|
|
||||||
class UrlProcessorInitialReadme implements UrlProcessor {
|
class UrlProcessorInitialReadme implements UrlProcessor {
|
||||||
|
|
||||||
private static final String GITHUB_BASE = "https://github.com/noties/Markwon/raw/master/";
|
private static final String GITHUB_BASE = "https://github.com/noties/Markwon/raw/master/";
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package ru.noties.markwon;
|
package ru.noties.markwon;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.support.annotation.IdRes;
|
|
||||||
import android.support.annotation.IntDef;
|
import android.support.annotation.IntDef;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@ -13,16 +11,6 @@ public abstract class Views {
|
|||||||
@interface NotVisible {
|
@interface NotVisible {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <V extends View> V findView(@NonNull View view, @IdRes int id) {
|
|
||||||
//noinspection unchecked
|
|
||||||
return (V) view.findViewById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <V extends View> V findView(@NonNull Activity activity, @IdRes int id) {
|
|
||||||
//noinspection unchecked
|
|
||||||
return (V) activity.findViewById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setVisible(@NonNull View view, boolean visible) {
|
public static void setVisible(@NonNull View view, boolean visible) {
|
||||||
setVisible(view, visible, View.GONE);
|
setVisible(view, visible, View.GONE);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package ru.noties.markwon;
|
package ru.noties.markwon.gif;
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
@ -6,9 +6,10 @@ import android.support.annotation.NonNull;
|
|||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import pl.droidsonroids.gif.GifDrawable;
|
import pl.droidsonroids.gif.GifDrawable;
|
||||||
import ru.noties.markwon.renderer.ImageSize;
|
import ru.noties.markwon.image.AsyncDrawableLoader;
|
||||||
import ru.noties.markwon.renderer.ImageSizeResolver;
|
import ru.noties.markwon.image.ImageSize;
|
||||||
import ru.noties.markwon.spans.AsyncDrawable;
|
import ru.noties.markwon.image.ImageSizeResolver;
|
||||||
|
import ru.noties.markwon.image.AsyncDrawable;
|
||||||
|
|
||||||
public class GifAwareAsyncDrawable extends AsyncDrawable {
|
public class GifAwareAsyncDrawable extends AsyncDrawable {
|
||||||
|
|
||||||
@ -23,7 +24,7 @@ public class GifAwareAsyncDrawable extends AsyncDrawable {
|
|||||||
public GifAwareAsyncDrawable(
|
public GifAwareAsyncDrawable(
|
||||||
@NonNull Drawable gifPlaceholder,
|
@NonNull Drawable gifPlaceholder,
|
||||||
@NonNull String destination,
|
@NonNull String destination,
|
||||||
@NonNull Loader loader,
|
@NonNull AsyncDrawableLoader loader,
|
||||||
@Nullable ImageSizeResolver imageSizeResolver,
|
@Nullable ImageSizeResolver imageSizeResolver,
|
||||||
@Nullable ImageSize imageSize) {
|
@Nullable ImageSize imageSize) {
|
||||||
super(destination, loader, imageSizeResolver, imageSize);
|
super(destination, loader, imageSizeResolver, imageSize);
|
72
app/src/main/java/ru/noties/markwon/gif/GifAwarePlugin.java
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package ru.noties.markwon.gif;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
public class GifAwarePlugin extends AbstractMarkwonPlugin {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static GifAwarePlugin create(@NonNull Context context) {
|
||||||
|
return new GifAwarePlugin(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final GifProcessor processor;
|
||||||
|
|
||||||
|
GifAwarePlugin(@NonNull Context context) {
|
||||||
|
this.context = context;
|
||||||
|
this.processor = GifProcessor.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
|
||||||
|
|
||||||
|
final GifPlaceholder gifPlaceholder = new GifPlaceholder(
|
||||||
|
context.getResources().getDrawable(R.drawable.ic_play_circle_filled_18dp_white),
|
||||||
|
0x20000000
|
||||||
|
);
|
||||||
|
|
||||||
|
builder.setFactory(Image.class, new SpanFactory() {
|
||||||
|
@Override
|
||||||
|
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
|
||||||
|
return new AsyncDrawableSpan(
|
||||||
|
configuration.theme(),
|
||||||
|
new GifAwareAsyncDrawable(
|
||||||
|
gifPlaceholder,
|
||||||
|
ImageProps.DESTINATION.require(props),
|
||||||
|
configuration.asyncDrawableLoader(),
|
||||||
|
configuration.imageSizeResolver(),
|
||||||
|
ImageProps.IMAGE_SIZE.get(props)
|
||||||
|
),
|
||||||
|
AsyncDrawableSpan.ALIGN_BOTTOM,
|
||||||
|
ImageProps.REPLACEMENT_TEXT_IS_LINK.get(props, false)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Priority priority() {
|
||||||
|
return Priority.after(ImagesPlugin.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterSetText(@NonNull TextView textView) {
|
||||||
|
processor.process(textView);
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package ru.noties.markwon;
|
package ru.noties.markwon.gif;
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.ColorFilter;
|
import android.graphics.ColorFilter;
|
@ -1,4 +1,4 @@
|
|||||||
package ru.noties.markwon;
|
package ru.noties.markwon.gif;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
@ -9,7 +9,7 @@ import android.view.View;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import pl.droidsonroids.gif.GifDrawable;
|
import pl.droidsonroids.gif.GifDrawable;
|
||||||
import ru.noties.markwon.spans.AsyncDrawableSpan;
|
import ru.noties.markwon.image.AsyncDrawableSpan;
|
||||||
|
|
||||||
public abstract class GifProcessor {
|
public abstract class GifProcessor {
|
||||||
|
|
||||||
@ -31,6 +31,7 @@ public abstract class GifProcessor {
|
|||||||
// if not we apply onGifListener
|
// if not we apply onGifListener
|
||||||
|
|
||||||
final Spannable spannable = spannable(textView);
|
final Spannable spannable = spannable(textView);
|
||||||
|
|
||||||
if (spannable == null) {
|
if (spannable == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -89,6 +90,7 @@ public abstract class GifProcessor {
|
|||||||
// as with each `setText()` new spannable is created and keeping reference
|
// as with each `setText()` new spannable is created and keeping reference
|
||||||
// to an older one won't affect textView
|
// to an older one won't affect textView
|
||||||
final Spannable spannable = spannable(textView);
|
final Spannable spannable = spannable(textView);
|
||||||
|
|
||||||
if (spannable == null) {
|
if (spannable == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -113,12 +115,13 @@ public abstract class GifProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View widget) {
|
public void onClick(@NonNull View widget) {
|
||||||
if (gifDrawable.isPlaying()) {
|
if (gifDrawable.isPlaying()) {
|
||||||
gifDrawable.pause();
|
gifDrawable.pause();
|
||||||
} else {
|
} else {
|
||||||
gifDrawable.start();
|
gifDrawable.start();
|
||||||
}
|
}
|
||||||
|
widget.invalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
24
app/src/main/res/drawable-v26/ic_launcher_background.xml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="512"
|
||||||
|
android:viewportHeight="512">
|
||||||
|
<path
|
||||||
|
android:pathData="M0,0h512v256h-512z"
|
||||||
|
android:strokeAlpha="0.94117647"
|
||||||
|
android:strokeWidth="0.40000001"
|
||||||
|
android:fillColor="#d7d7d7"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:fillAlpha="1"
|
||||||
|
android:strokeLineCap="butt"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M0,256h512v256h-512z"
|
||||||
|
android:strokeAlpha="0.94117647"
|
||||||
|
android:strokeWidth="0.40000001"
|
||||||
|
android:fillColor="#eeeeee"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:fillAlpha="1"
|
||||||
|
android:strokeLineCap="butt"/>
|
||||||
|
</vector>
|
24
app/src/main/res/drawable/ic_launcher_background.xml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="512"
|
||||||
|
android:viewportHeight="512">
|
||||||
|
<path
|
||||||
|
android:pathData="M0,0h512v256h-512z"
|
||||||
|
android:strokeAlpha="0.94117647"
|
||||||
|
android:strokeWidth="0.40000001"
|
||||||
|
android:fillColor="#d7d7d7"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:fillAlpha="1"
|
||||||
|
android:strokeLineCap="butt"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M0,256h512v256h-512z"
|
||||||
|
android:strokeAlpha="0.94117647"
|
||||||
|
android:strokeWidth="0.40000001"
|
||||||
|
android:fillColor="#eeeeee"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:fillAlpha="1"
|
||||||
|
android:strokeLineCap="butt"/>
|
||||||
|
</vector>
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background" />
|
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
BIN
app/src/main/res/mipmap-hdpi-v26/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 1.3 KiB |
BIN
app/src/main/res/mipmap-xhdpi-v26/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
BIN
app/src/main/res/mipmap-xxhdpi-v26/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 4.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi-v26/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 6.5 KiB |
97
art/markwon-icon-foreground.svg
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="512"
|
||||||
|
height="512"
|
||||||
|
viewBox="0 0 512.00001 512.00001"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="0.91 r13725"
|
||||||
|
inkscape:export-filename="/Users/di/text4169.png"
|
||||||
|
inkscape:export-xdpi="89.93"
|
||||||
|
inkscape:export-ydpi="89.93"
|
||||||
|
sodipodi:docname="markwon-icon-foreground.svg">
|
||||||
|
<defs
|
||||||
|
id="defs4" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="0.92578125"
|
||||||
|
inkscape:cx="163.7657"
|
||||||
|
inkscape:cy="186.451"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
units="px"
|
||||||
|
inkscape:window-width="1442"
|
||||||
|
inkscape:window-height="788"
|
||||||
|
inkscape:window-x="-1"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="0" />
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(0,-540.36216)">
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-weight:normal;font-size:34.96873856px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
x="106.24741"
|
||||||
|
y="908.6958"
|
||||||
|
id="text4136"
|
||||||
|
sodipodi:linespacing="125%"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan4138"
|
||||||
|
x="106.24741"
|
||||||
|
y="908.6958"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:314.71862793px;font-family:'Noto Serif';-inkscape-font-specification:'Noto Serif Bold'">M</tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-weight:normal;font-size:65.1031189px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#666666;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
|
||||||
|
x="109.9856"
|
||||||
|
y="780.45221"
|
||||||
|
id="text4140"
|
||||||
|
sodipodi:linespacing="125%"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan4142"
|
||||||
|
x="109.9856"
|
||||||
|
y="780.45221"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:293.75px;font-family:'Noto Serif';-inkscape-font-specification:'Noto Serif';fill:#666666;">**</tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-weight:normal;font-size:40.77807617px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#666666;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
|
||||||
|
x="109.9856"
|
||||||
|
y="1150.7955"
|
||||||
|
id="text4169"
|
||||||
|
sodipodi:linespacing="125%"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan4171"
|
||||||
|
x="109.9856"
|
||||||
|
y="1150.7955"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:293.75px;font-family:'Noto Serif';-inkscape-font-specification:'Noto Serif';fill:#666666;">**</tspan></text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.8 KiB |
97
art/sample-icon-foreground.svg
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="512"
|
||||||
|
height="512"
|
||||||
|
viewBox="0 0 512.00001 512.00001"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="0.91 r13725"
|
||||||
|
inkscape:export-filename="/Users/di/text4169.png"
|
||||||
|
inkscape:export-xdpi="89.93"
|
||||||
|
inkscape:export-ydpi="89.93"
|
||||||
|
sodipodi:docname="sample-icon-foreground.svg">
|
||||||
|
<defs
|
||||||
|
id="defs4" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="0.92578125"
|
||||||
|
inkscape:cx="163.7657"
|
||||||
|
inkscape:cy="186.451"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
units="px"
|
||||||
|
inkscape:window-width="1442"
|
||||||
|
inkscape:window-height="788"
|
||||||
|
inkscape:window-x="-1"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="0" />
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(0,-540.36216)">
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-weight:normal;font-size:34.96873856px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
|
||||||
|
x="106.24741"
|
||||||
|
y="908.6958"
|
||||||
|
id="text4136"
|
||||||
|
sodipodi:linespacing="125%"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan4138"
|
||||||
|
x="106.24741"
|
||||||
|
y="908.6958"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:314.71862793px;font-family:'Noto Serif';-inkscape-font-specification:'Noto Serif Bold';fill:#ffffff;">M</tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-weight:normal;font-size:65.1031189px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#999999;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
|
||||||
|
x="109.9856"
|
||||||
|
y="780.45221"
|
||||||
|
id="text4140"
|
||||||
|
sodipodi:linespacing="125%"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan4142"
|
||||||
|
x="109.9856"
|
||||||
|
y="780.45221"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:293.75px;font-family:'Noto Serif';-inkscape-font-specification:'Noto Serif';fill:#999999;">**</tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-weight:normal;font-size:40.77807617px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#999999;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
|
||||||
|
x="109.9856"
|
||||||
|
y="1150.7955"
|
||||||
|
id="text4169"
|
||||||
|
sodipodi:linespacing="125%"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan4171"
|
||||||
|
x="109.9856"
|
||||||
|
y="1150.7955"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:293.75px;font-family:'Noto Serif';-inkscape-font-specification:'Noto Serif';fill:#999999;">**</tspan></text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.9 KiB |
20
build.gradle
@ -4,7 +4,7 @@ buildscript {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.2.1'
|
classpath 'com.android.tools.build:gradle:3.3.2'
|
||||||
classpath 'com.github.ben-manes:gradle-versions-plugin:0.20.0'
|
classpath 'com.github.ben-manes:gradle-versions-plugin:0.20.0'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -19,6 +19,10 @@ allprojects {
|
|||||||
}
|
}
|
||||||
version = VERSION_NAME
|
version = VERSION_NAME
|
||||||
group = GROUP
|
group = GROUP
|
||||||
|
|
||||||
|
tasks.withType(Javadoc) {
|
||||||
|
options.addStringOption('Xdoclint:none', '-quiet')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task clean(type: Delete) {
|
task clean(type: Delete) {
|
||||||
@ -43,27 +47,30 @@ ext {
|
|||||||
// NB, updating build-tools or compile-sdk will require updating Travis config (.travis.yml)
|
// NB, updating build-tools or compile-sdk will require updating Travis config (.travis.yml)
|
||||||
config = [
|
config = [
|
||||||
'build-tools' : '28.0.3',
|
'build-tools' : '28.0.3',
|
||||||
'compile-sdk' : 27,
|
'compile-sdk' : 28,
|
||||||
'target-sdk' : 27,
|
'target-sdk' : 28,
|
||||||
'min-sdk' : 16,
|
'min-sdk' : 16,
|
||||||
'push-aar-gradle': 'https://raw.githubusercontent.com/noties/gradle-mvn-push/master/gradle-mvn-push-aar.gradle'
|
'push-aar-gradle': 'https://raw.githubusercontent.com/noties/gradle-mvn-push/master/gradle-mvn-push-aar.gradle'
|
||||||
]
|
]
|
||||||
|
|
||||||
final def supportVersion = '27.1.1'
|
final def supportVersion = '28.0.0'
|
||||||
final def commonMarkVersion = '0.12.1'
|
final def commonMarkVersion = '0.12.1'
|
||||||
final def daggerVersion = '2.10'
|
final def daggerVersion = '2.10'
|
||||||
|
|
||||||
deps = [
|
deps = [
|
||||||
'support-annotations' : "com.android.support:support-annotations:$supportVersion",
|
'support-annotations' : "com.android.support:support-annotations:$supportVersion",
|
||||||
'support-app-compat' : "com.android.support:appcompat-v7:$supportVersion",
|
'support-app-compat' : "com.android.support:appcompat-v7:$supportVersion",
|
||||||
|
'support-recycler-view' : "com.android.support:recyclerview-v7:$supportVersion",
|
||||||
'commonmark' : "com.atlassian.commonmark:commonmark:$commonMarkVersion",
|
'commonmark' : "com.atlassian.commonmark:commonmark:$commonMarkVersion",
|
||||||
'commonmark-strikethrough': "com.atlassian.commonmark:commonmark-ext-gfm-strikethrough:$commonMarkVersion",
|
'commonmark-strikethrough': "com.atlassian.commonmark:commonmark-ext-gfm-strikethrough:$commonMarkVersion",
|
||||||
'commonmark-table' : "com.atlassian.commonmark:commonmark-ext-gfm-tables:$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.2.1',
|
||||||
'android-gif' : 'pl.droidsonroids.gif:android-gif-drawable:1.2.14',
|
'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',
|
'okhttp' : 'com.squareup.okhttp3:okhttp:3.9.0',
|
||||||
'prism4j' : 'ru.noties:prism4j:1.1.0',
|
'prism4j' : 'ru.noties:prism4j:1.1.0',
|
||||||
'debug' : 'ru.noties:debug:3.0.0@jar',
|
'debug' : 'ru.noties:debug:3.0.0@jar',
|
||||||
|
'adapt' : 'ru.noties:adapt:1.1.0',
|
||||||
'dagger' : "com.google.dagger:dagger:$daggerVersion"
|
'dagger' : "com.google.dagger:dagger:$daggerVersion"
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -74,11 +81,8 @@ ext {
|
|||||||
|
|
||||||
deps['test'] = [
|
deps['test'] = [
|
||||||
'junit' : 'junit:junit:4.12',
|
'junit' : 'junit:junit:4.12',
|
||||||
'robolectric' : 'org.robolectric:robolectric:3.8',
|
'robolectric': 'org.robolectric:robolectric:3.8',
|
||||||
'ix-java' : 'com.github.akarnokd:ixjava:1.0.0',
|
'ix-java' : 'com.github.akarnokd:ixjava:1.0.0',
|
||||||
'jackson-yaml' : 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.9.0',
|
|
||||||
'jackson-databind': 'com.fasterxml.jackson.core:jackson-databind:2.9.6',
|
|
||||||
'gson' : 'com.google.code.gson:gson:2.8.5',
|
|
||||||
'commons-io' : 'commons-io:commons-io:2.6',
|
'commons-io' : 'commons-io:commons-io:2.6',
|
||||||
'mockito' : 'org.mockito:mockito-core:2.21.0'
|
'mockito' : 'org.mockito:mockito-core:2.21.0'
|
||||||
]
|
]
|
||||||
|
4
docs/.vuepress/.artifacts.js
Normal 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 };
|
105
docs/.vuepress/components/ArtifactPicker.vue
Normal 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> markwon_version = </span>
|
||||||
|
<span class="token string">'{{latestVersion}}'</span>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div class="selected-artifact-script" v-for="artifact in selectedArtifacts">
|
||||||
|
<span>implementation </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>
|
105
docs/.vuepress/components/CommonmarkSandbox.vue
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="container">
|
||||||
|
<div class="container-item">
|
||||||
|
<textarea @input="processMarkdown">{{markdownInput}}</textarea>
|
||||||
|
</div>
|
||||||
|
<div class="container-item display" v-html="markdownHtml"></div>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<!-- <p v-if="permalink">
|
||||||
|
Permalink: <span v-html="permalink"></span>
|
||||||
|
</p> -->
|
||||||
|
<p>
|
||||||
|
<em>
|
||||||
|
* Please note that this tool can be used to evaluate how commonmark
|
||||||
|
will react to certain markdown input. There is no guarantee that results
|
||||||
|
here and in an Android application that uses Markwon will be the same.
|
||||||
|
Especially if raw HTML is involved.
|
||||||
|
</em>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<em>
|
||||||
|
** For a more sophisticated commonmark sandbox editor
|
||||||
|
<a
|
||||||
|
href="https://spec.commonmark.org/dingus/"
|
||||||
|
>the dingus</a> can be used.
|
||||||
|
</em>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import commonmark from "commonmark";
|
||||||
|
|
||||||
|
const parser = new commonmark.Parser();
|
||||||
|
const writer = new commonmark.HtmlRenderer();
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "CommonmarkSandbox",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
markdownInput: this.initialMarkdown()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initialMarkdown() {
|
||||||
|
// const query = this.$route.query;
|
||||||
|
// if (query) {
|
||||||
|
// const md = query.md;
|
||||||
|
// if (md) {
|
||||||
|
// query.md = null;
|
||||||
|
// return md;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
return `# Header 1\n\n*Hello* __there!__`;
|
||||||
|
},
|
||||||
|
processMarkdown(e) {
|
||||||
|
this.markdownInput = e.target.value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
markdownHtml() {
|
||||||
|
return writer.render(parser.parse(this.markdownInput));
|
||||||
|
},
|
||||||
|
// permalink() {
|
||||||
|
// if (!this.markdownInput) {
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
// const url = `${window.location.href}?md=${encodeURIComponent(this.markdownInput)}`;
|
||||||
|
// return `<a href="#" title="${url}" onclick="">click to copy</a>`;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.container-item {
|
||||||
|
flex: 4;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
.container textarea {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 20em;
|
||||||
|
padding: 0px;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
.display {
|
||||||
|
flex: 5;
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
13
docs/.vuepress/components/LegacyWarning.vue
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<div class="warning custom-block">
|
||||||
|
<p class="custom-block-title">WARNING</p>
|
||||||
|
<p>This is documentation for <u>legacy 2.x.x</u> versions. For the most current version <a :href="$withBase('/')">click here.</a></p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'LegacyWarning'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<a :href="linkHref()" target="_blank" rel="noopener noreferrer">{{linkText()}}<OutboundLink/></a>
|
<a :href="linkHref()" target="_blank" rel="noopener noreferrer">
|
||||||
|
{{linkText()}}
|
||||||
|
<OutboundLink/>
|
||||||
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -25,14 +28,16 @@ var map = {
|
|||||||
href: "https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements"
|
href: "https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements"
|
||||||
},
|
},
|
||||||
"html-blocks": {
|
"html-blocks": {
|
||||||
href: "https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements"
|
href:
|
||||||
|
"https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements"
|
||||||
},
|
},
|
||||||
"jsoup": {
|
jsoup: {
|
||||||
displayName: "Jsoup",
|
displayName: "Jsoup",
|
||||||
href: "https://github.com/jhy/jsoup/"
|
href: "https://github.com/jhy/jsoup/"
|
||||||
},
|
},
|
||||||
"markwon-jsoup": {
|
"markwon-jsoup": {
|
||||||
href: "https://github.com/noties/Markwon/tree/master/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup"
|
href:
|
||||||
|
"https://github.com/noties/Markwon/tree/master/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup"
|
||||||
},
|
},
|
||||||
"commonmark-java": {
|
"commonmark-java": {
|
||||||
href: "https://github.com/atlassian/commonmark-java/",
|
href: "https://github.com/atlassian/commonmark-java/",
|
||||||
|
@ -1,17 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<a :href="mavenSearchUrl()"><img :src="shieldImgageUrl()" :alt="'' + artifact"></a>
|
<a :href="mavenSearchUrl()"><img :src="shieldImgageUrl()" :alt="displayLabel"></a>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'MavenBadge',
|
name: 'MavenBadge',
|
||||||
props: ['artifact'],
|
props: ['artifact', 'label'],
|
||||||
methods: {
|
methods: {
|
||||||
mavenSearchUrl: function() {
|
mavenSearchUrl: function() {
|
||||||
return 'http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%22' + this.artifact + '%22';
|
return `http://search.maven.org/#search|ga|1|g%3A%22ru.noties.markwon%22%20AND%20a%3A%22${this.artifact}%22`;
|
||||||
},
|
},
|
||||||
shieldImgageUrl: function() {
|
shieldImgageUrl: function() {
|
||||||
return 'https://img.shields.io/maven-central/v/ru.noties/' + this.artifact +'.svg?label=' + this.artifact;
|
return `https://img.shields.io/maven-central/v/ru.noties.markwon/${this.artifact}.svg?label=${this.displayLabel}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
displayLabel() {
|
||||||
|
return this.label || this.artifact;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
docs/.vuepress/components/MavenBadge2xx.vue
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<template>
|
||||||
|
<a :href="mavenSearchUrl()"><img :src="shieldImgageUrl()" :alt="displayLabel"></a>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'MavenBadge2xx',
|
||||||
|
props: ['artifact', 'label'],
|
||||||
|
methods: {
|
||||||
|
mavenSearchUrl: function() {
|
||||||
|
return `http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%22${this.artifact}%22`;
|
||||||
|
},
|
||||||
|
shieldImgageUrl: function() {
|
||||||
|
return `https://img.shields.io/maven-central/v/ru.noties/${this.artifact}.svg?label=${this.displayLabel}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
displayLabel() {
|
||||||
|
return this.label || this.artifact;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
20
docs/.vuepress/components/MavenBadges2xx.vue
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<MavenBadge2xx :artifact="'markwon'" />
|
||||||
|
<MavenBadge2xx :artifact="'markwon-image-loader'" />
|
||||||
|
<MavenBadge2xx :artifact="'markwon-syntax-highlight'"/>
|
||||||
|
<MavenBadge2xx :artifact="'markwon-view'"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import MavenBadge2xx from "./MavenBadge2xx.vue";
|
||||||
|
export default {
|
||||||
|
name: "MavenBadges2xx",
|
||||||
|
components: {
|
||||||
|
MavenBadge2xx
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
@ -1,37 +1,74 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
base: '/Markwon/',
|
base: '/Markwon/',
|
||||||
title: 'Markwon',
|
title: 'Markwon',
|
||||||
description: 'Android markdown library based on commonmark specification',
|
description: 'Android markdown library based on commonmark specification that renders markdown as system-native Spannables (no WebView)',
|
||||||
head: [
|
head: [
|
||||||
['link', {rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png?v=1'}],
|
['link', { rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png?v=1' }],
|
||||||
['link', {rel: 'icon', type: 'image/png', sizes: '16x16', href: '/favicon-16x16.png?v=1'}],
|
['link', { rel: 'icon', type: 'image/png', sizes: '16x16', href: '/favicon-16x16.png?v=1' }],
|
||||||
['link', {rel: 'icon', href: '/favicon.ico?v=1'}],
|
['link', { rel: 'icon', href: '/favicon.ico?v=1' }],
|
||||||
['link', {rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicon-32x32.png?v=1'}],
|
['link', { rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicon-32x32.png?v=1' }],
|
||||||
['link', {rel: 'manifest', href: '/manifest.json?v=1'}],
|
['link', { rel: 'manifest', href: '/manifest.json?v=1' }],
|
||||||
|
['meta', { name: 'keywords', content: 'android,markdown,library,spannable,markwon,commonmark' }]
|
||||||
],
|
],
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
nav: [
|
nav: [
|
||||||
{ text: 'Install', link: '/docs/install.md' },
|
{ text: 'Install', link: '/docs/v3/install.md' },
|
||||||
{ text: 'Changelog', link: '/CHANGELOG.md' },
|
{ text: 'Changelog', link: '/CHANGELOG.md' },
|
||||||
|
{
|
||||||
|
text: 'API Version',
|
||||||
|
items: [
|
||||||
|
{ text: 'Current (3.x.x)', link: '/' },
|
||||||
|
{ text: 'Legacy (2.x.x)', link: '/docs/v2/' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ text: 'Sandbox', link: '/sandbox.md' },
|
||||||
{ text: 'Github', link: 'https://github.com/noties/Markwon' }
|
{ text: 'Github', link: 'https://github.com/noties/Markwon' }
|
||||||
],
|
],
|
||||||
sidebar: [
|
sidebar: {
|
||||||
'/',
|
'/docs/v2': [
|
||||||
'/docs/getting-started.md',
|
'/docs/v2/getting-started.md',
|
||||||
'/docs/configure.md',
|
'/docs/v2/configure.md',
|
||||||
'/docs/theme.md',
|
'/docs/v2/theme.md',
|
||||||
'/docs/factory.md',
|
'/docs/v2/factory.md',
|
||||||
'/docs/image-loader.md',
|
'/docs/v2/image-loader.md',
|
||||||
'/docs/syntax-highlight.md',
|
'/docs/v2/syntax-highlight.md',
|
||||||
'/docs/html.md',
|
'/docs/v2/html.md',
|
||||||
'/docs/view.md'
|
'/docs/v2/view.md'
|
||||||
],
|
],
|
||||||
|
'/': [
|
||||||
|
'',
|
||||||
|
{
|
||||||
|
title: 'Core',
|
||||||
|
collapsable: false,
|
||||||
|
children: [
|
||||||
|
'/docs/v3/core/getting-started.md',
|
||||||
|
'/docs/v3/core/plugins.md',
|
||||||
|
'/docs/v3/core/theme.md',
|
||||||
|
'/docs/v3/core/images.md',
|
||||||
|
'/docs/v3/core/configuration.md',
|
||||||
|
'/docs/v3/core/visitor.md',
|
||||||
|
'/docs/v3/core/spans-factory.md',
|
||||||
|
'/docs/v3/core/html-renderer.md',
|
||||||
|
'/docs/v3/core/core-plugin.md',
|
||||||
|
'/docs/v3/core/movement-method-plugin.md',
|
||||||
|
'/docs/v3/core/render-props.md'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'/docs/v3/ext-latex/',
|
||||||
|
'/docs/v3/ext-strikethrough/',
|
||||||
|
'/docs/v3/ext-tables/',
|
||||||
|
'/docs/v3/ext-tasklist/',
|
||||||
|
'/docs/v3/html/',
|
||||||
|
'/docs/v3/image/gif.md',
|
||||||
|
'/docs/v3/image/okhttp.md',
|
||||||
|
'/docs/v3/image/svg.md',
|
||||||
|
'/docs/v3/recycler/',
|
||||||
|
'/docs/v3/recycler-table/',
|
||||||
|
'/docs/v3/syntax-highlight/',
|
||||||
|
'/docs/v3/migration-2-3.md'
|
||||||
|
]
|
||||||
|
},
|
||||||
sidebarDepth: 2,
|
sidebarDepth: 2,
|
||||||
lastUpdated: true
|
lastUpdated: true
|
||||||
},
|
|
||||||
markdown: {
|
|
||||||
config: md => {
|
|
||||||
md.use(require('markdown-it-task-lists'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,2 +1,23 @@
|
|||||||
$textColor = #000000
|
$textColor = #000000
|
||||||
$accentColor = #4CAF50
|
$accentColor = #4CAF50
|
||||||
|
|
||||||
|
a.sidebar-link {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-sub-headers a.sidebar-link {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-group a.sidebar-link {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-heading {
|
||||||
|
color: $textColor;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-heading.open, .sidebar-heading:hover {
|
||||||
|
color: $accentColor;
|
||||||
|
}
|
BIN
docs/.vuepress/public/assets/recycler-table-screenshot.png
Normal file
After Width: | Height: | Size: 77 KiB |
@ -0,0 +1,71 @@
|
|||||||
|
div[class~=language-gradle]:before {
|
||||||
|
content:"gradle"
|
||||||
|
}
|
||||||
|
|
||||||
|
div[class~=language-proguard]:before {
|
||||||
|
content:"proguard"
|
||||||
|
}
|
||||||
|
|
||||||
|
div[class~=language-groovy]:before {
|
||||||
|
content:"gradle"
|
||||||
|
}
|
||||||
|
|
||||||
|
div[class*="language-"] {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.comment, .token.prolog, .token.cdata {
|
||||||
|
color: #808080;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.delimiter, .token.boolean, .token.keyword, .token.selector, .token.important, .token.atrule {
|
||||||
|
color: #cc7832;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.operator, .token.punctuation, .token.attr-name {
|
||||||
|
color: #a9b7c6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.tag, .token.doctype, .token.builtin {
|
||||||
|
color: #e8bf6a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.entity, .token.number, .token.symbol {
|
||||||
|
color: #6897bb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.property, .token.constant, .token.variable {
|
||||||
|
color: #9876aa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.string, .token.char {
|
||||||
|
color: #6a8759;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.annotation {
|
||||||
|
color: #bbb438;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.attr-value {
|
||||||
|
color: #a5c261;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.url {
|
||||||
|
color: #287bde;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.function {
|
||||||
|
color: #ffc66d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.regex {
|
||||||
|
color: #364135;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.inserted {
|
||||||
|
color: #294436;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.deleted {
|
||||||
|
color: #484a4a;
|
||||||
|
}
|
@ -1,17 +1,54 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
# 2.0.1
|
# 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
|
* `SpannableMarkdownVisitor` Rename blockQuoteIndent to blockIndent
|
||||||
* Fixed block new lines logic for block quote and paragraph (#82)
|
* Fixed block new lines logic for block quote and paragraph (<GithubIssue id="82" />)
|
||||||
* AsyncDrawable fix no dimensions bug (#81)
|
* AsyncDrawable fix no dimensions bug (<GithubIssue id="81" />)
|
||||||
* Update SpannableTheme to use Px instead of Dimension annotation
|
* Update SpannableTheme to use Px instead of Dimension annotation
|
||||||
* Allow TaskListSpan isDone mutation
|
* Allow TaskListSpan isDone mutation
|
||||||
* Updated commonmark-java to 0.12.1
|
* Updated commonmark-java to 0.12.1
|
||||||
* Add OrderedListItemSpan measure utility method (#78)
|
* Add OrderedListItemSpan measure utility method (<GithubIssue id="78" />)
|
||||||
* Add SpannableBuilder#getSpans method
|
* Add SpannableBuilder#getSpans method
|
||||||
* Fix DataUri scheme handler in image-loader (#74)
|
* Fix DataUri scheme handler in image-loader (<GithubIssue id="74" />)
|
||||||
* Introduced a "copy" builder for SpannableThem
|
* Introduced a "copy" builder for SpannableThem <br>Thanks <GithubUser name="c-b-h" />
|
||||||
Thanks @c-b-h 🙌
|
|
||||||
|
|
||||||
## 2.0.0
|
## 2.0.0
|
||||||
* Add `html-parser-api` and `html-parser-impl` modules
|
* Add `html-parser-api` and `html-parser-impl` modules
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
---
|
---
|
||||||
title: 'Overview'
|
title: 'Introduction'
|
||||||
---
|
---
|
||||||
|
|
||||||
<img :src="$withBase('./art/markwon_logo.png')" alt="Markwon Logo" width="50%">
|
<img :src="$withBase('/art/markwon_logo.png')" alt="Markwon Logo" width="50%">
|
||||||
|
|
||||||
<br><br>
|
<br><br>
|
||||||
<MavenBadges/>
|
[](http://search.maven.org/#search|ga|1|g%3A%22ru.noties.markwon%22%20)
|
||||||
|
[](https://travis-ci.org/noties/Markwon)
|
||||||
|
|
||||||
**Markwon** is a markdown library for Android. It parses markdown following
|
**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
|
<Link name="commonmark-spec" /> with the help of amazing <Link name="commonmark-java" /> library
|
||||||
@ -20,22 +21,23 @@ but also gives all the means to tweak the appearance if desired. All markdown fe
|
|||||||
listed in <Link name="commonmark-spec" /> are supported (including support for **inlined/block HTML code**,
|
listed in <Link name="commonmark-spec" /> are supported (including support for **inlined/block HTML code**,
|
||||||
**markdown tables**, **images** and **syntax highlight**).
|
**markdown tables**, **images** and **syntax highlight**).
|
||||||
|
|
||||||
## Supported markdown features:
|
## Supported markdown features
|
||||||
|
|
||||||
* Emphasis (`*`, `_`)
|
* Emphasis (`*`, `_`)
|
||||||
* Strong emphasis (`**`, `__`)
|
* Strong emphasis (`**`, `__`)
|
||||||
* Strike-through (`~~`)
|
|
||||||
* Headers (`#{1,6}`)
|
* Headers (`#{1,6}`)
|
||||||
* Links (`[]()` && `[][]`)
|
* Links (`[]()` && `[][]`)
|
||||||
* [Images](/docs/image-loader.md)
|
* [Images](/docs/v3/core/images.md)
|
||||||
* Thematic break (`---`, `***`, `___`)
|
* Thematic break (`---`, `***`, `___`)
|
||||||
* Quotes & nested quotes (`>{1,}`)
|
* Quotes & nested quotes (`>{1,}`)
|
||||||
* Ordered & non-ordered lists & nested ones
|
* Ordered & non-ordered lists & nested ones
|
||||||
* Inline code
|
* Inline code
|
||||||
* Code blocks
|
* Code blocks
|
||||||
* Tables (*with limitations*)
|
* [Strike-through](/docs/v3/ext-strikethrough/) (`~~`)
|
||||||
* [Syntax highlight](/docs/syntax-highlight.md)
|
* [Tables](/docs/v3/ext-tables/) (*with limitations*)
|
||||||
* [HTML](/docs/html.md)
|
* [Syntax highlight](/docs/v3/syntax-highlight/)
|
||||||
|
* [LaTeX](/docs/v3/ext-latex/) formulas
|
||||||
|
* [HTML](/docs/v3/html/)
|
||||||
* Emphasis (`<i>`, `<em>`, `<cite>`, `<dfn>`)
|
* Emphasis (`<i>`, `<em>`, `<cite>`, `<dfn>`)
|
||||||
* Strong emphasis (`<b>`, `<strong>`)
|
* Strong emphasis (`<b>`, `<strong>`)
|
||||||
* SuperScript (`<sup>`)
|
* SuperScript (`<sup>`)
|
||||||
@ -48,11 +50,13 @@ listed in <Link name="commonmark-spec" /> are supported (including support for *
|
|||||||
* Blockquote (`blockquote`)
|
* Blockquote (`blockquote`)
|
||||||
* Heading (`h1`, `h2`, `h3`, `h4`, `h5`, `h6`)
|
* Heading (`h1`, `h2`, `h3`, `h4`, `h5`, `h6`)
|
||||||
* there is support to render any HTML tag, but it will require to create a special `TagHandler`,
|
* 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/html.md#custom-tag-handler)
|
more information can be found in [HTML section](/docs/v3/core/html-renderer.md)
|
||||||
* Task lists:
|
* [Task lists](/docs/v3/ext-tasklist/):
|
||||||
- [ ] Not _done_
|
<ul style="list-style-type: none; margin: 0; padding: 0;">
|
||||||
- [X] **Done** with `X`
|
<li><input type="checkbox" disabled>Not <i>done</i></li>
|
||||||
- [x] ~~and~~ **or** small `x`
|
<li><input type="checkbox" disabled checked><strong>Done</strong> with <code>X</code></li>
|
||||||
|
<li><input type="checkbox" disabled checked><del>and</del> <strong>or</strong> small <code>x</code></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
@ -68,3 +72,24 @@ Screenshots are taken from sample application. It is a generic markdown viewer
|
|||||||
with support to display markdown content via `http`, `https` & `file` schemes
|
with support to display markdown content via `http`, `https` & `file` schemes
|
||||||
and 2 themes included: Light & Dark. It can be downloaded from [releases](https://github.com/noties/Markwon/releases)
|
and 2 themes included: Light & Dark. It can be downloaded from [releases](https://github.com/noties/Markwon/releases)
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## Awesome Markwon
|
||||||
|
|
||||||
|
<u>Applications using Markwon</u>:
|
||||||
|
|
||||||
|
* [Partico](https://partiko.app/) - Partiko is a censorship free social network.
|
||||||
|
* [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.
|
||||||
|
|
||||||
|
|
||||||
|
<u>Extension/plugins</u>:
|
||||||
|
|
||||||
|
* [MarkwonCodeEx](https://github.com/kingideayou/MarkwonCodeEx) - Markwon extension support elegant code background.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[Help to improve][awesome_link] this section by submitting your application or library
|
||||||
|
that is using `Markwon`
|
||||||
|
|
||||||
|
|
||||||
|
[awesome_link]: https://github.com/noties/Markwon/issues/new?labels=awesome&body=Please%20provide%20the%20following%3A%0A*%20Project%20name%0A*%20Project%20URL%20(repository%2C%20store%20listing%2C%20web%20page)%0A*%20Optionally%20_brand_%20image%20URL%0A%0APlease%20make%20sure%20that%20there%20is%20the%20**awesome**%20label%20selected%20for%20this%20issue.%0A%0A%F0%9F%99%8C%20
|
||||||
|
50
docs/collectArtifacts.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const PROPERTIES_FILE_NAME = 'gradle.properties';
|
||||||
|
const PROP_GROUP = 'GROUP';
|
||||||
|
const PROP_DESCRIPTION = 'POM_DESCRIPTION';
|
||||||
|
const PROP_ARTIFACT_NAME = 'POM_NAME';
|
||||||
|
const PROP_ARTIFACT_ID = 'POM_ARTIFACT_ID';
|
||||||
|
|
||||||
|
const readProperties = (file) => fs.readFileSync(file, { encoding: 'utf-8' }, 'string')
|
||||||
|
.split('\n')
|
||||||
|
// filter-out empty lines
|
||||||
|
.filter(s => s)
|
||||||
|
.map(s => s.split('='))
|
||||||
|
.reduce((a, s) => {
|
||||||
|
a[s[0]] = s[1];
|
||||||
|
return a;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const listDirectories = (folder) => fs.readdirSync(folder)
|
||||||
|
.map(name => path.join(folder, name))
|
||||||
|
.filter(f => fs.lstatSync(f).isDirectory());
|
||||||
|
|
||||||
|
const projectDir = path.resolve(__dirname, '../');
|
||||||
|
|
||||||
|
const projectProperties = readProperties(path.join(projectDir, PROPERTIES_FILE_NAME));
|
||||||
|
|
||||||
|
const projectGroup = projectProperties[PROP_GROUP]
|
||||||
|
|
||||||
|
const artifacts = listDirectories(projectDir)
|
||||||
|
.map(dir => path.join(dir, PROPERTIES_FILE_NAME))
|
||||||
|
.filter(f => fs.existsSync(f))
|
||||||
|
.map(readProperties)
|
||||||
|
.map(props => {
|
||||||
|
return {
|
||||||
|
id: props[PROP_ARTIFACT_ID],
|
||||||
|
name: props[PROP_ARTIFACT_NAME],
|
||||||
|
group: projectGroup,
|
||||||
|
description: props[PROP_DESCRIPTION]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const artifactsFile = path.join(__dirname, '.vuepress', '.artifacts.js');
|
||||||
|
const artifactsJs = `
|
||||||
|
// this is a generated file, do not modify. To update it run 'collectArtifacts.js' script
|
||||||
|
const artifacts = ${JSON.stringify(artifacts)};
|
||||||
|
export { artifacts };
|
||||||
|
`
|
||||||
|
|
||||||
|
fs.writeFileSync(artifactsFile, artifactsJs);
|
70
docs/docs/v2/README.md
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
---
|
||||||
|
title: 'Overview'
|
||||||
|
---
|
||||||
|
|
||||||
|
<img :src="$withBase('/art/markwon_logo.png')" alt="Markwon Logo" width="50%">
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
<MavenBadges2xx/>
|
||||||
|
|
||||||
|
**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
|
||||||
|
as an intermediate step. <u>**No WebView** is required</u>. It's extremely fast,
|
||||||
|
feature-rich and extensible.
|
||||||
|
|
||||||
|
It gives ability to display markdown in all TextView widgets (**TextView**,
|
||||||
|
**Button**, **Switch**, **CheckBox**, etc), **Toasts** and all other places that accept
|
||||||
|
**Spanned content**. Library provides reasonable defaults to display style of a markdown content
|
||||||
|
but also gives all the means to tweak the appearance if desired. All markdown features
|
||||||
|
listed in <Link name="commonmark-spec" /> are supported (including support for **inlined/block HTML code**,
|
||||||
|
**markdown tables**, **images** and **syntax highlight**).
|
||||||
|
|
||||||
|
## Supported markdown features:
|
||||||
|
|
||||||
|
* Emphasis (`*`, `_`)
|
||||||
|
* Strong emphasis (`**`, `__`)
|
||||||
|
* Strike-through (`~~`)
|
||||||
|
* Headers (`#{1,6}`)
|
||||||
|
* Links (`[]()` && `[][]`)
|
||||||
|
* [Images](/docs/v2/image-loader.md)
|
||||||
|
* Thematic break (`---`, `***`, `___`)
|
||||||
|
* Quotes & nested quotes (`>{1,}`)
|
||||||
|
* Ordered & non-ordered lists & nested ones
|
||||||
|
* Inline code
|
||||||
|
* Code blocks
|
||||||
|
* Tables (*with limitations*)
|
||||||
|
* [Syntax highlight](/docs/v2/syntax-highlight.md)
|
||||||
|
* [HTML](/docs/v2/html.md)
|
||||||
|
* Emphasis (`<i>`, `<em>`, `<cite>`, `<dfn>`)
|
||||||
|
* Strong emphasis (`<b>`, `<strong>`)
|
||||||
|
* SuperScript (`<sup>`)
|
||||||
|
* SubScript (`<sub>`)
|
||||||
|
* Underline (`<u>`, `ins`)
|
||||||
|
* Strike-through (`<s>`, `<strike>`, `<del>`)
|
||||||
|
* Link (`a`)
|
||||||
|
* Lists (`ul`, `ol`)
|
||||||
|
* 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/v2/html.md#custom-tag-handler)
|
||||||
|
* Task lists:
|
||||||
|
- [ ] Not _done_
|
||||||
|
- [X] **Done** with `X`
|
||||||
|
- [x] ~~and~~ **or** small `x`
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
<img :src="$withBase('/art/mw_light_01.png')" alt="screenshot light #1" width="30%">
|
||||||
|
<img :src="$withBase('/art/mw_light_02.png')" alt="screenshot light #2" width="30%">
|
||||||
|
<img :src="$withBase('/art/mw_light_03.png')" alt="screenshot light #3" width="30%">
|
||||||
|
<img :src="$withBase('/art/mw_dark_01.png')" alt="screenshot dark #2" width="30%">
|
||||||
|
|
||||||
|
By default configuration uses TextView textColor for styling, so changing textColor changes style
|
||||||
|
|
||||||
|
:::tip Sample application
|
||||||
|
Screenshots are taken from sample application. It is a generic markdown viewer
|
||||||
|
with support to display markdown content via `http`, `https` & `file` schemes
|
||||||
|
and 2 themes included: Light & Dark. It can be downloaded from [releases](https://github.com/noties/Markwon/releases)
|
||||||
|
:::
|
@ -24,13 +24,13 @@ values as they will be applied automatically
|
|||||||
If you plan on using images inside your markdown/HTML, you will have to **explicitly**
|
If you plan on using images inside your markdown/HTML, you will have to **explicitly**
|
||||||
register an implementation of `AsyncDrawable.Loader` via `#asyncDrawableLoader` builder method.
|
register an implementation of `AsyncDrawable.Loader` via `#asyncDrawableLoader` builder method.
|
||||||
`Markwon` comes with ready implementation for that and it can be found in
|
`Markwon` comes with ready implementation for that and it can be found in
|
||||||
`markwon-image-loader` module. Refer to module [documentation](/docs/image-loader.md)
|
`markwon-image-loader` module. Refer to module [documentation](/docs/v2/image-loader.md)
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Theme
|
## Theme
|
||||||
|
|
||||||
`SpannableTheme` controls how markdown is rendered. It has pretty extensive number of
|
`SpannableTheme` controls how markdown is rendered. It has pretty extensive number of
|
||||||
options that can be found [here](/docs/theme.md)
|
options that can be found [here](/docs/v2/theme.md)
|
||||||
|
|
||||||
```java
|
```java
|
||||||
SpannableConfiguration.builder(context)
|
SpannableConfiguration.builder(context)
|
||||||
@ -56,7 +56,7 @@ If `AsyncDrawable.Loader` is not provided explicitly, default **no-op** implemen
|
|||||||
|
|
||||||
:::tip Implementation
|
:::tip Implementation
|
||||||
There are no restrictions on what implementation to use, but `Markwon` has artifact that can
|
There are no restrictions on what implementation to use, but `Markwon` has artifact that can
|
||||||
answer the most common needs of displaying SVG, GIF and other image formats. It can be found [here](/docs/image-loader.md)
|
answer the most common needs of displaying SVG, GIF and other image formats. It can be found [here](/docs/v2/image-loader.md)
|
||||||
:::
|
:::
|
||||||
|
|
||||||
### Size resolver <Badge text="1.0.1" />
|
### Size resolver <Badge text="1.0.1" />
|
||||||
@ -107,7 +107,7 @@ If not provided explicitly, default **no-op** implementation will be used.
|
|||||||
Although `SyntaxHighlight` interface was included with the very first version
|
Although `SyntaxHighlight` interface was included with the very first version
|
||||||
of `Markwon` there were no ready-to-use implementations. But starting with <Badge text="1.1.0" />
|
of `Markwon` there were no ready-to-use implementations. But starting with <Badge text="1.1.0" />
|
||||||
`Markwon` provides one. It can be found in `markwon-syntax-highlight` artifact. Refer
|
`Markwon` provides one. It can be found in `markwon-syntax-highlight` artifact. Refer
|
||||||
to module [documentation](/docs/syntax-highlight.md)
|
to module [documentation](/docs/v2/syntax-highlight.md)
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Link resolver
|
## Link resolver
|
||||||
@ -166,7 +166,7 @@ SpannableConfiguration.builder(context)
|
|||||||
```
|
```
|
||||||
|
|
||||||
If not provided explicitly, default `SpannableFactoryDef` implementation will be used. It is documented
|
If not provided explicitly, default `SpannableFactoryDef` implementation will be used. It is documented
|
||||||
in [this section](/docs/factory.md)
|
in [this section](/docs/v2/factory.md)
|
||||||
|
|
||||||
## Soft line break <Badge text="1.1.1" />
|
## Soft line break <Badge text="1.1.1" />
|
||||||
|
|
||||||
@ -197,7 +197,7 @@ SpannableConfiguration.builder(context)
|
|||||||
|
|
||||||
if not provided explicitly, default `MarkwonHtmlParserImpl` will be used
|
if not provided explicitly, default `MarkwonHtmlParserImpl` will be used
|
||||||
**if** it can be found in classpath, otherwise default **no-op** implementation
|
**if** it can be found in classpath, otherwise default **no-op** implementation
|
||||||
wiil be used. Refer to [HTML](/docs/html.md#parser) document for more information about this behavior.
|
wiil be used. Refer to [HTML](/docs/v2/html.md#parser) document for more information about this behavior.
|
||||||
|
|
||||||
### Renderer
|
### Renderer
|
||||||
|
|
||||||
@ -210,7 +210,7 @@ SpannableConfiguration.builder(context)
|
|||||||
```
|
```
|
||||||
|
|
||||||
If not provided explicitly, default `MarkwonHtmlRenderer` implementation will be used.
|
If not provided explicitly, default `MarkwonHtmlRenderer` implementation will be used.
|
||||||
It is documented [here](/docs/html.md#renderer)
|
It is documented [here](/docs/v2/html.md#renderer)
|
||||||
|
|
||||||
### HTML allow non-closed tags
|
### HTML allow non-closed tags
|
||||||
|
|
@ -1,10 +1,5 @@
|
|||||||
# Getting started
|
# Getting started
|
||||||
|
|
||||||
:::tip Installation
|
|
||||||
Please follow [installation](/docs/install.md) instructions
|
|
||||||
to learn how to add `Markwon` to your project
|
|
||||||
:::
|
|
||||||
|
|
||||||
## Quick one
|
## Quick one
|
||||||
|
|
||||||
This is the most simple way to set markdown to a `TextView` or any of its siblings:
|
This is the most simple way to set markdown to a `TextView` or any of its siblings:
|
||||||
@ -25,7 +20,7 @@ Toast.makeText(context, markdown, Toast.LENGTH_LONG).show();
|
|||||||
|
|
||||||
## Longer one
|
## Longer one
|
||||||
|
|
||||||
When you need to customize markdown parsing/rendering you can use [SpannableConfiguration](/docs/configure.md):
|
When you need to customize markdown parsing/rendering you can use [SpannableConfiguration](/docs/v2/configure.md):
|
||||||
|
|
||||||
```java
|
```java
|
||||||
final SpannableConfiguration configuration = SpannableConfiguration.builder(context)
|
final SpannableConfiguration configuration = SpannableConfiguration.builder(context)
|
@ -16,12 +16,12 @@ public interface Loader {
|
|||||||
|
|
||||||
## AsyncDrawableLoader
|
## AsyncDrawableLoader
|
||||||
|
|
||||||
<MavenBadge artifact="markwon-image-loader" />
|
<MavenBadge2xx artifact="markwon-image-loader" />
|
||||||
|
|
||||||
`AsyncDrawableLoader` from `markwon-image-loader` artifact can be used.
|
`AsyncDrawableLoader` from `markwon-image-loader` artifact can be used.
|
||||||
|
|
||||||
:::tip Install
|
:::tip Install
|
||||||
[Learn how to add](/docs/install.md#image-loader) `markwon-image-loader` to your project
|
[Learn how to add](/docs/v2/install.md#image-loader) `markwon-image-loader` to your project
|
||||||
:::
|
:::
|
||||||
|
|
||||||
Default instance of `AsyncDrawableLoader` can be obtain like this:
|
Default instance of `AsyncDrawableLoader` can be obtain like this:
|
@ -5,7 +5,7 @@ next: /docs/getting-started.md
|
|||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
<MavenBadges />
|
<MavenBadges2xx />
|
||||||
|
|
||||||
In order to start using `Markwon` add this to your dependencies block
|
In order to start using `Markwon` add this to your dependencies block
|
||||||
in your projects `build.gradle`:
|
in your projects `build.gradle`:
|
||||||
@ -39,7 +39,7 @@ Provides implementation of `AsyncDrawable.Loader` and comes with support for:
|
|||||||
* GIF
|
* GIF
|
||||||
* Other image formats
|
* Other image formats
|
||||||
|
|
||||||
Please refer to documentation for [image loader](/docs/image-loader.md) module
|
Please refer to documentation for [image loader](/docs/v2/image-loader.md) module
|
||||||
|
|
||||||
### Syntax highlight
|
### Syntax highlight
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ implementation "ru.noties:markwon-syntax-highlight:${markwonVersion}"
|
|||||||
|
|
||||||
Provides implementation of `SyntaxHighlight` and allows various syntax highlighting
|
Provides implementation of `SyntaxHighlight` and allows various syntax highlighting
|
||||||
in your markdown based Android applications. Comes with 2 ready-to-be-used themes: `light` and `dark`.
|
in your markdown based Android applications. Comes with 2 ready-to-be-used themes: `light` and `dark`.
|
||||||
Please refer to documentation for [syntax highlight](/docs/syntax-highlight.md) module
|
Please refer to documentation for [syntax highlight](/docs/v2/syntax-highlight.md) module
|
||||||
|
|
||||||
### View
|
### View
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ implementation "ru.noties:markwon-view:${markwonVersion}"
|
|||||||
|
|
||||||
Provides 2 widgets to display markdown: `MarkwonView` and `MarkwonViewCompat` (subclasses
|
Provides 2 widgets to display markdown: `MarkwonView` and `MarkwonViewCompat` (subclasses
|
||||||
of `TextView` and `AppCompatTextView` respectively).
|
of `TextView` and `AppCompatTextView` respectively).
|
||||||
Please refer to documentation for [view](/docs/view.md) module
|
Please refer to documentation for [view](/docs/v2/view.md) module
|
||||||
|
|
||||||
## Proguard
|
## Proguard
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
# Syntax highlight
|
# Syntax highlight
|
||||||
|
|
||||||
<MavenBadge artifact="markwon-syntax-highlight" />
|
<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.
|
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.
|
||||||
|
|
@ -1,10 +1,19 @@
|
|||||||
# Theme
|
# Theme
|
||||||
|
|
||||||
Here is the list of properties that can be configured via `SpannableTheme#builder` factory
|
Here is the list of properties that can be configured via `SpannableTheme`. If you wish to control what
|
||||||
method. If you wish to control what is out of this list, you can use [SpannableFactory](/docs/factory.md)
|
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.
|
abstraction which lets you to gather full control of Spans that are used to display markdown.
|
||||||
|
|
||||||
* factory methods
|
* `SpannableTheme#create(Context)` - creates a **default** instance of `SpannableBuilder (with _defaults_ registered)
|
||||||
|
* `SpannableTheme#builder` - creates **empty** builder with **no defaults registered**
|
||||||
|
* `SpannableTheme#builderWithDefaults(Context)` - create a **default** instance of builder (with default values registered)
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
`SpannbleTheme#builder` method has an unfortunate naming. It should've been `emptyBuilder`
|
||||||
|
or `builderNoDefaults` because `#builder` method returns a builder with <strong>no default
|
||||||
|
theme values registered</strong>. To create a builder **with** default values registered
|
||||||
|
use `SpannableBuilder#builderWithDefaults(Context)`
|
||||||
|
:::
|
||||||
|
|
||||||
## Link color
|
## Link color
|
||||||
|
|
||||||
@ -107,7 +116,7 @@ The color of background of code block text
|
|||||||
|
|
||||||
Leading margin for the block code content
|
Leading margin for the block code content
|
||||||
|
|
||||||
<ThemeProperty name="codeMultilineMargin" type="@Px int" defaults="Width of the space character" />
|
<ThemeProperty name="codeMultilineMargin" type="@Px int" defaults="8dip" />
|
||||||
|
|
||||||
### Code typeface
|
### Code typeface
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
# MarkwonView
|
# MarkwonView
|
||||||
|
|
||||||
<MavenBadge artifact="markwon-view" />
|
<MavenBadge2xx artifact="markwon-view" />
|
||||||
|
|
||||||
This is simple library containing 2 views that are able to display markdown:
|
This is simple library containing 2 views that are able to display markdown:
|
||||||
* MarkwonView - extends `android.view.TextView`
|
* MarkwonView - extends `android.view.TextView`
|
||||||
@ -27,7 +27,8 @@ public interface IMarkwonView {
|
|||||||
|
|
||||||
Both views support layout-preview in Android Studio (with some exceptions, for example, bold span is not rendered due to some limitations of layout preview).
|
Both views support layout-preview in Android Studio (with some exceptions, for example, bold span is not rendered due to some limitations of layout preview).
|
||||||
These are XML attributes:
|
These are XML attributes:
|
||||||
```
|
|
||||||
|
```xml
|
||||||
app:mv_markdown="string"
|
app:mv_markdown="string"
|
||||||
app:mv_configurationProvider="string"
|
app:mv_configurationProvider="string"
|
||||||
```
|
```
|
181
docs/docs/v3/core/configuration.md
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
# Configuration
|
||||||
|
|
||||||
|
`MarkwonConfiguration` class holds common Markwon functionality.
|
||||||
|
These are _configurable_ properties:
|
||||||
|
* `SyntaxHighlight`
|
||||||
|
* `LinkSpan.Resolver`
|
||||||
|
* `UrlProcessor`
|
||||||
|
* `ImageSizeResolver`
|
||||||
|
* `MarkwonHtmlParser`
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
Additionally `MarkwonConfiguration` holds:
|
||||||
|
* `MarkwonTheme`
|
||||||
|
* `AsyncDrawableLoader`
|
||||||
|
* `MarkwonHtmlRenderer`
|
||||||
|
* `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#configureImages`
|
||||||
|
* `Plugin#configureHtmlRenderer`
|
||||||
|
* `Plugin#configureSpansFactory`
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 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/v3/syntax-highlight/) to add syntax highlighting
|
||||||
|
to your application
|
||||||
|
:::
|
||||||
|
|
||||||
|
## LinkSpan.Resolver
|
||||||
|
|
||||||
|
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 LinkSpan.Resolver() {
|
||||||
|
@Override
|
||||||
|
public void resolve(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/v3/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: `` 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: `` 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.
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
In order to display an image from assets you still need to register `ImagesPlugin#createWithAssets(Context)`
|
||||||
|
plugin in resulting `Markwon` instance. As `UrlProcessorAndroidAssets` only
|
||||||
|
_processes_ URLs and doesn't take any part in displaying an image.
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## ImageSizeResolver
|
||||||
|
|
||||||
|
`ImageSizeResolver` controls the size of an image to be displayed. Currently it
|
||||||
|
handles only HTML images (specified via `img` tag).
|
||||||
|
|
||||||
|
```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(
|
||||||
|
@Nullable ImageSize imageSize,
|
||||||
|
@NonNull Rect imageBounds,
|
||||||
|
int canvasWidth,
|
||||||
|
float textSize) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.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 absolute sizes (for example width is specified
|
||||||
|
in pixels).
|
||||||
|
:::
|
||||||
|
|
||||||
|
## MarkwonHtmlParser
|
||||||
|
|
||||||
|
Specify which HTML parser to use. Default implementation is **no-op**.
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
One must explicitly use [HtmlPlugin](/docs/v3/html/) in order to display
|
||||||
|
HTML content in markdown. Without specified HTML parser **no HTML content
|
||||||
|
will be rendered**.
|
||||||
|
|
||||||
|
```java
|
||||||
|
Markwon.builder(context)
|
||||||
|
.usePlugin(HtmlPlugin.create())
|
||||||
|
```
|
||||||
|
|
||||||
|
Please note that adding `HtmlPlugin` will take care of initializing parser,
|
||||||
|
so after `HtmlPlugin` is used, no additional configuration steps are required.
|
||||||
|
:::
|
105
docs/docs/v3/core/core-plugin.md
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
# 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`
|
||||||
|
* `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/v3/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/v3/core/movement-method-plugin.md)
|
||||||
|
which accepts a MovementMethod as an argument.
|
||||||
|
:::
|
65
docs/docs/v3/core/getting-started.md
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# Getting started
|
||||||
|
|
||||||
|
:::tip Installation
|
||||||
|
Please follow [installation](/docs/v3/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();
|
||||||
|
```
|
||||||
|
|
||||||
|
:::warning 3.x.x migration
|
||||||
|
Starting with <Badge text="3.0.0" /> version Markwon no longer relies on static
|
||||||
|
utility methods. To learn more about migrating existing applications
|
||||||
|
refer to [migration](/docs/v3/migration-2-3.md) section.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 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();
|
||||||
|
```
|
||||||
|
|
||||||
|
## No magic one
|
||||||
|
|
||||||
|
This section is kept due to historical reasons. Starting with version <Badge text="3.0.0" />
|
||||||
|
the amount of magic is reduced. To leverage your `Markwon` usage a concept of `Plugin`
|
||||||
|
is introduced which helps to extend default behavior in a simple and _no-breaking-the-flow_ manner.
|
||||||
|
Head to the [next section](/docs/v3/core/plugins.md) to know more.
|
101
docs/docs/v3/core/html-renderer.md
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
# HTML Renderer
|
||||||
|
|
||||||
|
Starting with <Badge text="3.0.0" /> `MarkwonHtmlRenderer` controls how HTML
|
||||||
|
is rendered:
|
||||||
|
|
||||||
|
```java
|
||||||
|
Markwon.builder(context)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureHtmlRenderer(@NonNull MarkwonHtmlRenderer.Builder builder) {
|
||||||
|
builder.setHandler("a", new MyTagHandler());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
:::danger
|
||||||
|
Customizing `MarkwonHtmlRenderer` is not enough to include HTML content in your application.
|
||||||
|
You must explicitly include [markwon-html](/docs/v3/html/) artifact (includes HtmlParser)
|
||||||
|
to your project and register `HtmlPlugin`:
|
||||||
|
|
||||||
|
```java
|
||||||
|
Markwon.builder(context)
|
||||||
|
.usePlugin(HtmlPlugin.create())
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
For example, to create an `<a>` HTML tag handler:
|
||||||
|
|
||||||
|
```java
|
||||||
|
builder.setHandler("a", new SimpleTagHandler() {
|
||||||
|
@Override
|
||||||
|
public Object getSpans(
|
||||||
|
@NonNull MarkwonConfiguration configuration,
|
||||||
|
@NonNull RenderProps renderProps,
|
||||||
|
@NonNull HtmlTag tag) {
|
||||||
|
return new LinkSpan(
|
||||||
|
configuration.theme(),
|
||||||
|
tag.attributes().get("href"),
|
||||||
|
configuration.linkResolver());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
`SimpleTagHandler` can be used for simple cases when a tag does not require any special
|
||||||
|
handling (like visiting it's children)
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
One can return `null` a single span or an array of spans from `getSpans` method
|
||||||
|
:::
|
||||||
|
|
||||||
|
For a more advanced usage `TagHandler` can be used directly:
|
||||||
|
|
||||||
|
```java
|
||||||
|
builder.setHandler("a", new TagHandler() {
|
||||||
|
@Override
|
||||||
|
public void handle(@NonNull MarkwonVisitor visitor, @NonNull MarkwonHtmlRenderer renderer, @NonNull HtmlTag tag) {
|
||||||
|
|
||||||
|
// obtain default spanFactory for Link node
|
||||||
|
final SpanFactory factory = visitor.configuration().spansFactory().get(Link.class);
|
||||||
|
|
||||||
|
if (factory != null) {
|
||||||
|
|
||||||
|
// set destination property
|
||||||
|
CoreProps.LINK_DESTINATION.set(
|
||||||
|
visitor.renderProps(),
|
||||||
|
tag.attributes().get("href"));
|
||||||
|
|
||||||
|
// Obtain spans from the factory
|
||||||
|
final Object spans = factory.getSpans(
|
||||||
|
visitor.configuration(),
|
||||||
|
visitor.renderProps());
|
||||||
|
|
||||||
|
// apply spans to SpannableBuilder
|
||||||
|
SpannableBuilder.setSpans(
|
||||||
|
visitor.builder(),
|
||||||
|
spans,
|
||||||
|
tag.start(),
|
||||||
|
tag.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
Sometimes HTML content might include tags that are not closed (although
|
||||||
|
they are required to be by the spec, for example a `div`).
|
||||||
|
Markwon by default disallows such tags and ignores them. Still,
|
||||||
|
there is an option to allow them _explicitly_ via builder method:
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureHtmlRenderer(@NonNull MarkwonHtmlRenderer.Builder builder) {
|
||||||
|
builder.allowNonClosedTags(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
Please note that if `allowNonClosedTags=true` then all non-closed tags will be closed
|
||||||
|
at the end of a document.
|
||||||
|
:::
|
205
docs/docs/v3/core/images.md
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
# Images
|
||||||
|
|
||||||
|
Starting with <Badge text="3.0.0" /> `Markwon` comes with `ImagesPlugin`
|
||||||
|
which supports `http(s)`, `file` and `data` schemes and default media
|
||||||
|
decoder (for simple images, no [SVG](/docs/v3/image/svg.md) or [GIF](/docs/v3/image/gif.md) which
|
||||||
|
are defined in standalone modules).
|
||||||
|
|
||||||
|
## ImagesPlugin
|
||||||
|
|
||||||
|
`ImagePlugin` takes care of _obtaining_ image resource, decoding it and displaying it in a `TextView`.
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
Although `core` artifact contains `ImagesPlugin` one must
|
||||||
|
still **explicitly** register the `ImagesPlugin` on resulting `Markwon`
|
||||||
|
instance.
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(ImagesPlugin.create())
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
There are 2 factory methods to obtain `ImagesPlugin`:
|
||||||
|
* `ImagesPlugin#create(Context)`
|
||||||
|
* `ImagesPlugin#createWithAssets(Context)`
|
||||||
|
|
||||||
|
The first one `#create(Context)` configures:
|
||||||
|
* `FileSchemeHandler` that allows obtaining images from `file://` uris
|
||||||
|
* `DataUriSchemeHandler` that allows _inlining_ images with `data:`
|
||||||
|
scheme (`data:image/svg+xml;base64,MTIz`)
|
||||||
|
* `NetworkSchemeHandler` that allows obtaining images from `http://` and `https://` uris
|
||||||
|
(internally it uses `HttpURLConnection`)
|
||||||
|
* `ImageMediaDecoder` which _tries_ to decode all encountered images as regular ones (png, jpg, etc)
|
||||||
|
|
||||||
|
The second one `#createWithAssets(Context)` does the same but also adds support
|
||||||
|
for images that reside in `assets` folder of your application and
|
||||||
|
referenced by `file:///android_asset/{path}` uri.
|
||||||
|
|
||||||
|
`ImagesPlugin` also _prepares_ a TextView to display images. Due to asynchronous
|
||||||
|
nature of image loading, there must be a way to invalidate resulting Spanned
|
||||||
|
content after an image is loaded.
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
Images come with few limitations. For of all, they work with a **TextView only**.
|
||||||
|
This is due to the fact that there is no way to invalidate a `Spanned` content
|
||||||
|
by itself (without context in which it is displayed). So, if `Markwon` is used,
|
||||||
|
for example, to display a `Toast` with an image:
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Spanned spanned = markwon.toMarkdown("Hello ");
|
||||||
|
Toast.makeText(context, spanned, Toast.LENGTH_LONG).show();
|
||||||
|
```
|
||||||
|
|
||||||
|
Image _probably_ won't be displayed. As a workaround for `Toast` a custom `View`
|
||||||
|
can be used:
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Spanned spanned = markwon.toMarkdown("Hello ");
|
||||||
|
|
||||||
|
final View view = createToastView();
|
||||||
|
final TextView textView = view.findViewById(R.id.text_view);
|
||||||
|
markwon.setParsedMarkdown(textView, spanned);
|
||||||
|
|
||||||
|
final Toast toast = new Toast(context);
|
||||||
|
toast.setView(view);
|
||||||
|
// other Toast configurations
|
||||||
|
toast.show();
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
## SchemeHandler
|
||||||
|
|
||||||
|
To add support for different schemes (or customize provided) a `SchemeHandler` must be used.
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(ImagesPlugin.create(context))
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) {
|
||||||
|
// example only, Markwon doesn't come with a ftp scheme handler
|
||||||
|
builder.addSchemeHandler("ftp", new FtpSchemeHandler());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
It's a class to _convert_ an URI into an `InputStream`:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public abstract class SchemeHandler {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public abstract ImageItem handle(@NonNull String raw, @NonNull Uri uri);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`ImageItem` is a holder class for resulting `InputStream` and (optional)
|
||||||
|
content type:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class ImageItem {
|
||||||
|
|
||||||
|
private final String contentType;
|
||||||
|
private final InputStream inputStream;
|
||||||
|
|
||||||
|
/* rest omitted */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Based on `contentType` returned a corresponding `MediaDecoder` will be matched.
|
||||||
|
If no `MediaDecoder` can handle given `contentType` then a default media decoder will
|
||||||
|
be used.
|
||||||
|
|
||||||
|
## MediaDecoder
|
||||||
|
|
||||||
|
By default `core` artifact comes with _default image decoder_ only. It's called
|
||||||
|
`ImageMediaDecoder` and it can decode all the formats that `BitmapFactory#decodeStream(InputStream)`
|
||||||
|
can.
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(this)
|
||||||
|
.usePlugin(ImagesPlugin.create(this))
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) {
|
||||||
|
builder.addMediaDecoder("text/plain", new TextPlainMediaDecoder());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
`MediaDecoder` is a class to turn `InputStream` into a `Drawable`:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public abstract class MediaDecoder {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public abstract Drawable decode(@NonNull InputStream inputStream);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
If you want to display GIF or SVG images also, you can use [image-gif](/docs/v3/image/gif.md)
|
||||||
|
and [image-svg](/docs/v3/image/svg.md) modules.
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
If you are using [html](/docs/v3/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%">
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Placeholder drawable <Badge text="3.0.0" />
|
||||||
|
|
||||||
|
It's possible to provide a custom placeholder for an image (whilst it's loading).
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(ImagesPlugin.create(context))
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) {
|
||||||
|
builder.placeholderDrawableProvider(new AsyncDrawableLoader.DrawableProvider() {
|
||||||
|
@Override
|
||||||
|
public Drawable provide() {
|
||||||
|
// your custom placeholder drawable
|
||||||
|
return new PlaceholderDrawable();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error drawable <Badge text="3.0.0" />
|
||||||
|
|
||||||
|
To fallback in case of error whilst loading an image, an `error drawable` can be used:
|
||||||
|
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(ImagesPlugin.create(context))
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) {
|
||||||
|
builder.errorDrawableProvider(new AsyncDrawableLoader.DrawableProvider() {
|
||||||
|
@Override
|
||||||
|
public Drawable provide() {
|
||||||
|
// your custom error drawable
|
||||||
|
return new MyErrorDrawable();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
Before `3.0.0` `AsyncDrawableLoader` accepted a simple `Drawable` as error drawable
|
||||||
|
argument. Starting `3.0.0` it accepts a `DrawableProvider` instead.
|
||||||
|
:::
|
17
docs/docs/v3/core/movement-method-plugin.md
Normal 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.
|
||||||
|
:::
|
467
docs/docs/v3/core/plugins.md
Normal file
@ -0,0 +1,467 @@
|
|||||||
|
# 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)
|
||||||
|
.usePlugin(CorePlugin.create())
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
All the process of transforming _raw_ markdown into a styled text (Spanned)
|
||||||
|
will go through plugins. A plugin can:
|
||||||
|
|
||||||
|
* [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)
|
||||||
|
* [configure `MarkwonHtmlRenderer` (utility to properly display HTML in markdown)](#html-renderer)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
* [declare a dependency on another plugin (will be used as a runtime validator)](#priority)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
* [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.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Parser
|
||||||
|
|
||||||
|
For example, let's register a new commonmark-java Parser extension:
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(CorePlugin.create())
|
||||||
|
.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();
|
||||||
|
```
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
`CorePlugin` has special handling - it will be **implicitly** added
|
||||||
|
if a plugin declares dependency on it. This is why in previous example we haven't
|
||||||
|
added CorePlugin _explicitly_ as `AbstractMarkwonPlugin` declares a dependency on it.
|
||||||
|
If it's not desireable override `AbstractMarkwonPlugin#priority` method to specify own rules.
|
||||||
|
:::
|
||||||
|
|
||||||
|
More information about `MarkwonTheme` can be found [here](/docs/v3/core/theme.md).
|
||||||
|
|
||||||
|
|
||||||
|
## Images
|
||||||
|
|
||||||
|
Since <Badge text="3.0.0" /> core images functionality moved to the `core` module.
|
||||||
|
Now `Markwon` comes bundled with support for regular images (no `SVG` or `GIF`, they
|
||||||
|
defined in standalone modules now). And 3(4) schemes supported by default:
|
||||||
|
* http (+https; using system built-in `HttpURLConnection`)
|
||||||
|
* file (including Android assets)
|
||||||
|
* data (image inline, `data:image/svg+xml;base64,!@#$%^&*(`)
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(ImagesPlugin.create())
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) {
|
||||||
|
// sorry, these are not bundled with the library
|
||||||
|
builder
|
||||||
|
.addSchemeHandler("ftp", new FtpSchemeHandler("root", ""))
|
||||||
|
.addMediaDecoder("text/plain", new AnsiiMediaDecoder());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
Although `ImagesPlugin` is bundled with the `core` artifact, it is **not** used by default
|
||||||
|
and one must **explicitly** add it:
|
||||||
|
|
||||||
|
```java
|
||||||
|
Markwon.builder(context)
|
||||||
|
.usePlugin(ImagesPlugin.create(context));
|
||||||
|
```
|
||||||
|
|
||||||
|
Without explicit usage of `ImagesPlugin` all image configuration will be ignored (no-op'ed)
|
||||||
|
:::
|
||||||
|
|
||||||
|
More information about dealing with images can be found [here](/docs/v3/core/images.md)
|
||||||
|
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
`MarkwonConfiguration` is a set of common tools that are used by different parts
|
||||||
|
of `Markwon`. It allows configurations of these:
|
||||||
|
|
||||||
|
* `SyntaxHighlight` (highlighting code blocks)
|
||||||
|
* `LinkResolver` (opens links in markdown)
|
||||||
|
* `UrlProcessor` (process URLs in markdown for both links and images)
|
||||||
|
* `MarkwonHtmlParser` (HTML parser)
|
||||||
|
* `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) {
|
||||||
|
// MarkwonHtmlParserImpl is defined in `markwon-html` artifact
|
||||||
|
builder.htmlParser(MarkwonHtmlParserImpl.create());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
More information about `MarkwonConfiguration` can be found [here](/docs/v3/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) {
|
||||||
|
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,
|
||||||
|
we can disable `Heading` Node rendering:
|
||||||
|
|
||||||
|
```java
|
||||||
|
builder.on(Heading.class, null);
|
||||||
|
```
|
||||||
|
|
||||||
|
Please note that `Priority` plays nicely here to ensure that your
|
||||||
|
custom Node override/disable happens _after_ some plugin defines it.
|
||||||
|
:::
|
||||||
|
|
||||||
|
More information about `MarkwonVisitor` can be found [here](/docs/v3/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/v3/core/spans-factory.md)
|
||||||
|
|
||||||
|
|
||||||
|
## HTML Renderer
|
||||||
|
|
||||||
|
`MarkwonHtmlRenderer` controls how HTML is rendered in markdown.
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(HtmlPlugin.create())
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureHtmlRenderer(@NonNull MarkwonHtmlRenderer.Builder builder) {
|
||||||
|
// <center> tag handling (deprecated but valid in our case)
|
||||||
|
// can be any tag name, there is no connection with _real_ HTML tags,
|
||||||
|
// <just-try-to-not-go-crazy-and-remember-about-portability>
|
||||||
|
builder.addHandler("center", new SimpleTagHandler() {
|
||||||
|
@Override
|
||||||
|
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps renderProps, @NonNull HtmlTag tag) {
|
||||||
|
return new AlignmentSpan() {
|
||||||
|
@Override
|
||||||
|
public Layout.Alignment getAlignment() {
|
||||||
|
return Layout.Alignment.ALIGN_CENTER;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
:::danger
|
||||||
|
Although `MarkwonHtmlRenderer` is bundled with `core` artifact, actual
|
||||||
|
HTML parser is placed in a standalone artifact and must be added to your
|
||||||
|
project **explicitly** and then registered via `Markwon.Builder#usePlugin(HtmlPlugin.create())`.
|
||||||
|
If not done so, no HTML will be parsed nor rendered.
|
||||||
|
:::
|
||||||
|
|
||||||
|
More information about HTML rendering can be found [here](/docs/v3/core/html-renderer.md)
|
||||||
|
|
||||||
|
|
||||||
|
## Priority
|
||||||
|
|
||||||
|
`Priority` is an abstraction to _state_ dependency connection between plugins. It is
|
||||||
|
also used as a runtime graph validator. If a plugin defines a dependency on other, but
|
||||||
|
_other_ is not in resulting `Markwon` instance, then a runtime exception will be thrown.
|
||||||
|
`Priority` is also defines the order in which plugins will be placed. So, if a plugin `A`
|
||||||
|
states a plugin `B` as a dependency, then plugin `A` will come **after** plugin `B`.
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Priority priority() {
|
||||||
|
return Priority.after(CorePlugin.class);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
Please note that `AbstractMarkwonPlugin` _implicitly_ defines `CorePlugin`
|
||||||
|
as a dependency (`return Priority.after(CorePlugin.class);`). This will
|
||||||
|
also add `CorePlugin` to a `Markwon` instance, because it will be added
|
||||||
|
_implicitly_ if a plugin defines it as a dependency.
|
||||||
|
:::
|
||||||
|
|
||||||
|
Use one of the factory methods to create a `Priority` instance:
|
||||||
|
|
||||||
|
```java
|
||||||
|
// none
|
||||||
|
Priority.none();
|
||||||
|
|
||||||
|
// single dependency
|
||||||
|
Priority.after(CorePlugin.class);
|
||||||
|
|
||||||
|
// 2 dependencies
|
||||||
|
Priority.after(CorePlugin.class, ImagesPlugin.class);
|
||||||
|
|
||||||
|
// for a number >2, use #builder
|
||||||
|
Priority.builder()
|
||||||
|
.after(CorePlugin.class)
|
||||||
|
.after(ImagesPlugin.class)
|
||||||
|
.after(StrikethroughPlugin.class)
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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
|
||||||
|
// `Markwon#create` implicitly uses CorePlugin
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(CorePlugin.create())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
```
|
75
docs/docs/v3/core/render-props.md
Normal 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
|
61
docs/docs/v3/core/spans-factory.md
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
# 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/v3/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:
|
||||||
|
|
||||||
|
```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)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
187
docs/docs/v3/core/theme.md
Normal 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 & H2
|
||||||
|
|
||||||
|
<ThemeProperty name="headingBreakHeight" type="@Px int" defaults="Stroke width of context TextPaint" />
|
||||||
|
|
||||||
|
### Break color
|
||||||
|
|
||||||
|
The color of a brake under H1 & 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" />
|
73
docs/docs/v3/core/visitor.md
Normal 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/v3/core/configuration.md)
|
||||||
|
* `MarkwonVisitor#renderProps` - getter for current [RenderProps](/docs/v3/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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
46
docs/docs/v3/ext-latex/README.md
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# LaTeX extension
|
||||||
|
|
||||||
|
<MavenBadge :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(ImagesPlugin.create(context))
|
||||||
|
.use(JLatexMathPlugin.create(textSize))
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
This extension uses [jlatexmath-android](https://github.com/noties/jlatexmath-android) artifact to create LaTeX drawable. Then it
|
||||||
|
registers special `latex` image scheme handler and uses `AsyncDrawableLoader` to display
|
||||||
|
final result
|
||||||
|
|
||||||
|
## Config
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(ImagesPlugin.create(context))
|
||||||
|
.usePlugin(JLatexMathPlugin.create(textSize, new BuilderConfigure() {
|
||||||
|
@Override
|
||||||
|
public void configureBuilder(@NonNull Builder builder) {
|
||||||
|
builder
|
||||||
|
.background(backgroundDrawable)
|
||||||
|
.align(JLatexMathDrawable.ALIGN_CENTER)
|
||||||
|
.fitCanvas(true)
|
||||||
|
.padding(paddingPx);
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.build();
|
||||||
|
```
|
29
docs/docs/v3/ext-strikethrough/README.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Strikethrough extension
|
||||||
|
|
||||||
|
<MavenBadge :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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
99
docs/docs/v3/ext-tables/README.md
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
# Tables extension
|
||||||
|
|
||||||
|
<MavenBadge :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 the 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/v3/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" />
|
146
docs/docs/v3/ext-tasklist/README.md
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
# Task list extension
|
||||||
|
|
||||||
|
<MavenBadge :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();
|
||||||
|
```
|
63
docs/docs/v3/html/README.md
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# 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 & 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
|
||||||
|
:::
|
||||||
|
|
||||||
|
To learn more about defining own TagHandlers, please refer to [html-renderer docs](/docs/v3/core/html-renderer.md)
|
15
docs/docs/v3/image/gif.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Image GIF
|
||||||
|
|
||||||
|
<MavenBadge :artifact="'image-gif'" />
|
||||||
|
|
||||||
|
Adds support for GIF images inside markdown.
|
||||||
|
Relies on [android-gif-drawable library](https://github.com/koral--/android-gif-drawable)
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
// it's required to register ImagesPlugin
|
||||||
|
.usePlugin(ImagesPlugin.create(context))
|
||||||
|
// add GIF support for images
|
||||||
|
.usePlugin(GifPlugin.create())
|
||||||
|
.build();
|
||||||
|
```
|
28
docs/docs/v3/image/okhttp.md
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# Image OkHttp
|
||||||
|
|
||||||
|
<MavenBadge :artifact="'image-okhttp'" />
|
||||||
|
|
||||||
|
Uses [okhttp library](https://github.com/square/okhttp) as the network transport fro images. Since <Badge text="3.0.0" />
|
||||||
|
`Markwon` uses a system-native `HttpUrlConnection` and does not rely on any
|
||||||
|
3rd-party tool to download resources from network. It can answer the most common needs,
|
||||||
|
but if you would like to have a custom redirect policy or add an explicit caching
|
||||||
|
of downloaded resources OkHttp might be a better option.
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
// it's required to register ImagesPlugin
|
||||||
|
.usePlugin(ImagesPlugin.create(context))
|
||||||
|
|
||||||
|
// will create default instance of OkHttpClient
|
||||||
|
.usePlugin(OkHttpImagesPlugin.create())
|
||||||
|
|
||||||
|
// or accept a configured client
|
||||||
|
.usePlugin(OkHttpImagesPlugin.create(new OkHttpClient()))
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Proguard
|
||||||
|
```proguard
|
||||||
|
-dontwarn okhttp3.**
|
||||||
|
-dontwarn okio.**
|
||||||
|
```
|
25
docs/docs/v3/image/svg.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Image SVG
|
||||||
|
|
||||||
|
<MavenBadge :artifact="'image-svg'" />
|
||||||
|
|
||||||
|
Adds support for SVG images inside markdown.
|
||||||
|
Relies on [androidsvg library](https://github.com/BigBadaboom/androidsvg)
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
// it's required to register ImagesPlugin
|
||||||
|
.usePlugin(ImagesPlugin.create(context))
|
||||||
|
.usePlugin(SvgPlugin.create(context.getResources()))
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
`SvgPlugin` requires `Resources` in order to scale SVG media based on display density
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Proguard
|
||||||
|
|
||||||
|
```proguard
|
||||||
|
-keep class com.caverock.androidsvg.** { *; }
|
||||||
|
-dontwarn com.caverock.androidsvg.**
|
||||||
|
```
|
34
docs/docs/v3/install.md
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
prev: false
|
||||||
|
next: /docs/v3/core/getting-started.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
<ArtifactPicker />
|
||||||
|
|
||||||
|
## 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
|
||||||
|
:::
|
||||||
|
|
12
docs/docs/v3/migration-2-3.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# Migration 2.x.x -> 3.x.x
|
||||||
|
|
||||||
|
* strikethrough moved to standalone module
|
||||||
|
* tables moved to standalone module
|
||||||
|
* core functionality of `AsyncDrawableLoader` moved to `core` module
|
||||||
|
* * Handling of GIF and SVG media moved to standalone modules (`gif` and `svg` respectively)
|
||||||
|
* * OkHttpClient to download images moved to standalone module
|
||||||
|
* HTML no longer _implicitly_ added to core functionality, it must be specified __explicitly__ (as an artifact)
|
||||||
|
* removed `markwon-view` module
|
||||||
|
* changed Maven artifacts group to `ru.noties.markwon`
|
||||||
|
* removed `errorDrawable` in AsyncDrawableLoader in favor of a drawable provider
|
||||||
|
* added placeholder for AsyncDrawableProvider
|
92
docs/docs/v3/recycler-table/README.md
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
# Recycler Table <Badge text="3.0.0" />
|
||||||
|
|
||||||
|
<MavenBadge :artifact="'recycler-table'" />
|
||||||
|
|
||||||
|
Artifact that provides [MarkwonAdapter.Entry](/docs/v3/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>
|
||||||
|
```
|
153
docs/docs/v3/recycler/README.md
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
# Recycler <Badge text="3.0.0" />
|
||||||
|
|
||||||
|
<MavenBadge :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/v3/recycler-table/)
|
||||||
|
:::
|
69
docs/docs/v3/syntax-highlight/README.md
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# Syntax highlight
|
||||||
|
|
||||||
|
<MavenBadge :artifact="'syntax-highlight'" />
|
||||||
|
|
||||||
|
This is a simple module to add **syntax highlight** functionality to your markdown rendered with `Markwon` library. It is based on [Prism4j](https://github.com/noties/Prism4j) so lead there to understand how to configure `Prism4j` instance.
|
||||||
|
|
||||||
|
<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
|
||||||
|
:::
|
34
docs/package-lock.json
generated
@ -2471,6 +2471,24 @@
|
|||||||
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
||||||
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs="
|
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs="
|
||||||
},
|
},
|
||||||
|
"commonmark": {
|
||||||
|
"version": "0.28.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/commonmark/-/commonmark-0.28.1.tgz",
|
||||||
|
"integrity": "sha1-Buq41SM4uDn6Gi11rwCF7tGxvq4=",
|
||||||
|
"requires": {
|
||||||
|
"entities": "~ 1.1.1",
|
||||||
|
"mdurl": "~ 1.0.1",
|
||||||
|
"minimist": "~ 1.2.0",
|
||||||
|
"string.prototype.repeat": "^0.2.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"minimist": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||||
|
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"component-emitter": {
|
"component-emitter": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
|
||||||
@ -5465,9 +5483,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"linkify-it": {
|
"linkify-it": {
|
||||||
"version": "2.0.3",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.1.0.tgz",
|
||||||
"integrity": "sha1-2UpGSPmxwXnWT6lykSaL22zpQ08=",
|
"integrity": "sha512-4REs8/062kV2DSHxNfq5183zrqXMl7WP0WzABH9IeJI+NLm429FgE1PDecltYfnOoFDFlZGh2T8PfZn0r+GTRg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"uc.micro": "^1.0.1"
|
"uc.micro": "^1.0.1"
|
||||||
}
|
}
|
||||||
@ -5700,11 +5718,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/markdown-it-table-of-contents/-/markdown-it-table-of-contents-0.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/markdown-it-table-of-contents/-/markdown-it-table-of-contents-0.4.3.tgz",
|
||||||
"integrity": "sha512-x/OdaRzLYxAjmB+jIVlXuE3nX7tZTLDQxm58RkgjTLyQ+I290jYQvPS9cJjVN6SM3U6K6CHKYNgUtPNZmLblYQ=="
|
"integrity": "sha512-x/OdaRzLYxAjmB+jIVlXuE3nX7tZTLDQxm58RkgjTLyQ+I290jYQvPS9cJjVN6SM3U6K6CHKYNgUtPNZmLblYQ=="
|
||||||
},
|
},
|
||||||
"markdown-it-task-lists": {
|
|
||||||
"version": "2.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/markdown-it-task-lists/-/markdown-it-task-lists-2.1.1.tgz",
|
|
||||||
"integrity": "sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA=="
|
|
||||||
},
|
|
||||||
"math-expression-evaluator": {
|
"math-expression-evaluator": {
|
||||||
"version": "1.2.17",
|
"version": "1.2.17",
|
||||||
"resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz",
|
"resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz",
|
||||||
@ -9060,6 +9073,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"string.prototype.repeat": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-0.2.0.tgz",
|
||||||
|
"integrity": "sha1-q6Nt4I3O5qWjN9SbLqHaGyj8Ds8="
|
||||||
|
},
|
||||||
"string_decoder": {
|
"string_decoder": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"docs:build": "vuepress build"
|
"docs:build": "node ./collectArtifacts.js && vuepress build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"markdown-it-task-lists": "^2.1.1",
|
"commonmark": "^0.28.1",
|
||||||
"vuepress": "^0.14.2"
|
"vuepress": "^0.14.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
3
docs/sandbox.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Commonmark Sandbox
|
||||||
|
|
||||||
|
<CommonmarkSandbox />
|
@ -6,10 +6,10 @@ org.gradle.configureondemand=true
|
|||||||
android.enableBuildCache=true
|
android.enableBuildCache=true
|
||||||
android.buildCacheDir=build/pre-dex-cache
|
android.buildCacheDir=build/pre-dex-cache
|
||||||
|
|
||||||
VERSION_NAME=2.0.1
|
VERSION_NAME=3.0.0
|
||||||
|
|
||||||
GROUP=ru.noties
|
GROUP=ru.noties.markwon
|
||||||
POM_DESCRIPTION=Markwon
|
POM_DESCRIPTION=Markwon markdown for Android
|
||||||
POM_URL=https://github.com/noties/Markwon
|
POM_URL=https://github.com/noties/Markwon
|
||||||
POM_SCM_URL=https://github.com/noties/Markwon
|
POM_SCM_URL=https://github.com/noties/Markwon
|
||||||
POM_SCM_CONNECTION=scm:git:git://github.com/noties/Markwon.git
|
POM_SCM_CONNECTION=scm:git:git://github.com/noties/Markwon.git
|
||||||
|
@ -15,16 +15,20 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
api project(':markwon-html-parser-api')
|
|
||||||
|
|
||||||
deps.with {
|
deps.with {
|
||||||
api it['support-annotations']
|
api it['support-annotations']
|
||||||
api it['commonmark']
|
api it['commonmark']
|
||||||
}
|
}
|
||||||
|
|
||||||
deps.test.with {
|
deps['test'].with {
|
||||||
|
|
||||||
|
testImplementation project(':markwon-test-span')
|
||||||
|
|
||||||
testImplementation it['junit']
|
testImplementation it['junit']
|
||||||
testImplementation it['robolectric']
|
testImplementation it['robolectric']
|
||||||
|
testImplementation it['mockito']
|
||||||
|
|
||||||
|
testImplementation it['commons-io']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
4
markwon-core/gradle.properties
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
POM_NAME=Core
|
||||||
|
POM_ARTIFACT_ID=core
|
||||||
|
POM_DESCRIPTION=Core Markwon artifact that includes basic markdown parsing and rendering
|
||||||
|
POM_PACKAGING=aar
|
@ -0,0 +1,92 @@
|
|||||||
|
package ru.noties.markwon;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that extends {@link MarkwonPlugin} with all methods implemented (empty body)
|
||||||
|
* for easier plugin implementation. Only required methods can be overriden
|
||||||
|
*
|
||||||
|
* @see MarkwonPlugin
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
|
public abstract class AbstractMarkwonPlugin implements MarkwonPlugin {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureParser(@NonNull Parser.Builder builder) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureTheme(@NonNull MarkwonTheme.Builder builder) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@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) {
|
||||||
|
return markdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeRender(@NonNull Node node) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterRender(@NonNull Node node, @NonNull MarkwonVisitor visitor) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterSetText(@NonNull TextView textView) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -9,7 +9,7 @@ import android.support.annotation.NonNull;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import ru.noties.markwon.spans.LinkSpan;
|
import ru.noties.markwon.core.spans.LinkSpan;
|
||||||
|
|
||||||
public class LinkResolverDef implements LinkSpan.Resolver {
|
public class LinkResolverDef implements LinkSpan.Resolver {
|
||||||
@Override
|
@Override
|
136
markwon-core/src/main/java/ru/noties/markwon/Markwon.java
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
package ru.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 org.commonmark.node.Node;
|
||||||
|
|
||||||
|
import ru.noties.markwon.core.CorePlugin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to parse and render markdown. Since version 3.0.0 instance specific (previously consisted
|
||||||
|
* of static stateless methods). An instance of builder can be obtained via {@link #builder(Context)}
|
||||||
|
* method.
|
||||||
|
*
|
||||||
|
* @see #create(Context)
|
||||||
|
* @see #builder(Context)
|
||||||
|
* @see Builder
|
||||||
|
*/
|
||||||
|
public abstract class Markwon {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method to create a <em>minimally</em> functional {@link Markwon} instance. This
|
||||||
|
* instance will have <strong>only</strong> {@link CorePlugin} registered. If you wish
|
||||||
|
* to configure this instance more consider using {@link #builder(Context)} method.
|
||||||
|
*
|
||||||
|
* @return {@link Markwon} instance with only CorePlugin registered
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static Markwon create(@NonNull Context context) {
|
||||||
|
return builder(context)
|
||||||
|
.usePlugin(CorePlugin.create())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method to obtain an instance of {@link Builder}.
|
||||||
|
*
|
||||||
|
* @see Builder
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static Builder builder(@NonNull Context context) {
|
||||||
|
return new MarkwonBuilderImpl(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to parse markdown (without rendering)
|
||||||
|
*
|
||||||
|
* @param input markdown input to parse
|
||||||
|
* @return parsed via commonmark-java <code>org.commonmark.node.Node</code>
|
||||||
|
* @see #render(Node)
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public abstract Node parse(@NonNull String input);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create Spanned markdown from parsed Node (via {@link #parse(String)} call).
|
||||||
|
* <p>
|
||||||
|
* Please note that returned Spanned has few limitations. For example, images, tables
|
||||||
|
* and ordered lists require TextView to be properly displayed. This is why images and tables
|
||||||
|
* most likely won\'t work in this case. Ordered lists might have mis-measurements. Whenever
|
||||||
|
* possible use {@link #setMarkdown(TextView, String)} or {@link #setParsedMarkdown(TextView, Spanned)}
|
||||||
|
* as these methods will additionally call specific {@link MarkwonPlugin} methods to <em>prepare</em>
|
||||||
|
* proper display.
|
||||||
|
*
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public abstract Spanned render(@NonNull Node node);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will {@link #parse(String)} and {@link #render(Node)} supplied markdown. Returned
|
||||||
|
* Spanned has the same limitations as from {@link #render(Node)} method.
|
||||||
|
*
|
||||||
|
* @param input markdown input
|
||||||
|
* @see #parse(String)
|
||||||
|
* @see #render(Node)
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public abstract Spanned toMarkdown(@NonNull String input);
|
||||||
|
|
||||||
|
public abstract void setMarkdown(@NonNull TextView textView, @NonNull String markdown);
|
||||||
|
|
||||||
|
public abstract void setParsedMarkdown(@NonNull TextView textView, @NonNull Spanned markdown);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests information if certain plugin has been registered. Please note that this
|
||||||
|
* method will check for super classes also, so if supplied with {@code markwon.hasPlugin(MarkwonPlugin.class)}
|
||||||
|
* this method (if has at least one plugin) will return true. If for example a custom
|
||||||
|
* (subclassed) version of a {@link CorePlugin} has been registered and given name
|
||||||
|
* {@code CorePlugin2}, then both {@code markwon.hasPlugin(CorePlugin2.class)} and
|
||||||
|
* {@code markwon.hasPlugin(CorePlugin.class)} will return true.
|
||||||
|
*
|
||||||
|
* @param plugin type to query
|
||||||
|
* @return true if a plugin is used when configuring this {@link Markwon} instance
|
||||||
|
*/
|
||||||
|
public abstract boolean hasPlugin(@NonNull Class<? extends MarkwonPlugin> plugin);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public abstract <P extends MarkwonPlugin> P getPlugin(@NonNull Class<P> type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder for {@link Markwon}.
|
||||||
|
* <p>
|
||||||
|
* Please note that the order in which plugins are supplied is important as this order will be
|
||||||
|
* used through the whole usage of built Markwon instance
|
||||||
|
*
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
|
public interface Builder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify bufferType when applying text to a TextView {@code textView.setText(CharSequence,BufferType)}.
|
||||||
|
* By default `BufferType.SPANNABLE` is used
|
||||||
|
*
|
||||||
|
* @param bufferType BufferType
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
Builder bufferType(@NonNull TextView.BufferType bufferType);
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
Builder usePlugin(@NonNull MarkwonPlugin plugin);
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
Builder usePlugins(@NonNull Iterable<? extends MarkwonPlugin> plugins);
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
Markwon build();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,194 @@
|
|||||||
|
package ru.noties.markwon;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.VisibleForTesting;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.commonmark.parser.Parser;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
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.PriorityProcessor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
class MarkwonBuilderImpl implements Markwon.Builder {
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
private final List<MarkwonPlugin> plugins = new ArrayList<>(3);
|
||||||
|
|
||||||
|
private TextView.BufferType bufferType = TextView.BufferType.SPANNABLE;
|
||||||
|
|
||||||
|
private PriorityProcessor priorityProcessor;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("UnusedReturnValue")
|
||||||
|
@NonNull
|
||||||
|
public MarkwonBuilderImpl priorityProcessor(@NonNull PriorityProcessor priorityProcessor) {
|
||||||
|
this.priorityProcessor = priorityProcessor;
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
// this class will sort plugins to match a priority/dependency graph that we have
|
||||||
|
PriorityProcessor priorityProcessor = this.priorityProcessor;
|
||||||
|
if (priorityProcessor == null) {
|
||||||
|
// strictly speaking we do not need updating this field
|
||||||
|
// as we are not building this class to be reused between multiple `build` calls
|
||||||
|
priorityProcessor = this.priorityProcessor = PriorityProcessor.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(priorityProcessor, this.plugins);
|
||||||
|
|
||||||
|
final Parser.Builder parserBuilder = new Parser.Builder();
|
||||||
|
final MarkwonTheme.Builder themeBuilder = MarkwonTheme.builderWithDefaults(context);
|
||||||
|
final AsyncDrawableLoader.Builder asyncDrawableLoaderBuilder = new AsyncDrawableLoader.Builder();
|
||||||
|
final MarkwonConfiguration.Builder configurationBuilder = new MarkwonConfiguration.Builder();
|
||||||
|
final MarkwonVisitor.Builder visitorBuilder = new MarkwonVisitorImpl.BuilderImpl();
|
||||||
|
final MarkwonSpansFactory.Builder spanFactoryBuilder = new MarkwonSpansFactoryImpl.BuilderImpl();
|
||||||
|
final MarkwonHtmlRenderer.Builder htmlRendererBuilder = MarkwonHtmlRenderer.builder();
|
||||||
|
|
||||||
|
for (MarkwonPlugin plugin : plugins) {
|
||||||
|
plugin.configureParser(parserBuilder);
|
||||||
|
plugin.configureTheme(themeBuilder);
|
||||||
|
plugin.configureImages(asyncDrawableLoaderBuilder);
|
||||||
|
plugin.configureConfiguration(configurationBuilder);
|
||||||
|
plugin.configureVisitor(visitorBuilder);
|
||||||
|
plugin.configureSpansFactory(spanFactoryBuilder);
|
||||||
|
plugin.configureHtmlRenderer(htmlRendererBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
final MarkwonConfiguration configuration = configurationBuilder.build(
|
||||||
|
themeBuilder.build(),
|
||||||
|
asyncDrawableLoaderBuilder.build(),
|
||||||
|
htmlRendererBuilder.build(),
|
||||||
|
spanFactoryBuilder.build());
|
||||||
|
|
||||||
|
final RenderProps renderProps = new RenderPropsImpl();
|
||||||
|
|
||||||
|
return new MarkwonImpl(
|
||||||
|
bufferType,
|
||||||
|
parserBuilder.build(),
|
||||||
|
visitorBuilder.build(configuration, renderProps),
|
||||||
|
Collections.unmodifiableList(plugins)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
@NonNull
|
||||||
|
static List<MarkwonPlugin> preparePlugins(
|
||||||
|
@NonNull PriorityProcessor priorityProcessor,
|
||||||
|
@NonNull List<MarkwonPlugin> plugins) {
|
||||||
|
|
||||||
|
// with this method we will ensure that CorePlugin is added IF and ONLY IF
|
||||||
|
// there are plugins that depend on it. If CorePlugin is added, or there are
|
||||||
|
// no plugins that require it, CorePlugin won't be added
|
||||||
|
final List<MarkwonPlugin> out = ensureImplicitCoreIfHasDependents(plugins);
|
||||||
|
|
||||||
|
return priorityProcessor.process(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
// this method will _implicitly_ add CorePlugin if there is at least one plugin
|
||||||
|
// that depends on CorePlugin
|
||||||
|
@VisibleForTesting
|
||||||
|
@NonNull
|
||||||
|
static List<MarkwonPlugin> ensureImplicitCoreIfHasDependents(@NonNull List<MarkwonPlugin> plugins) {
|
||||||
|
// loop over plugins -> if CorePlugin is found -> break;
|
||||||
|
// iterate over all plugins and check if CorePlugin is requested
|
||||||
|
|
||||||
|
boolean hasCore = false;
|
||||||
|
boolean hasCoreDependents = false;
|
||||||
|
|
||||||
|
for (MarkwonPlugin plugin : plugins) {
|
||||||
|
|
||||||
|
// here we do not check for exact match (a user could've subclasses CorePlugin
|
||||||
|
// and supplied it. In this case we DO NOT implicitly add CorePlugin
|
||||||
|
//
|
||||||
|
// if core is present already we do not need to iterate anymore -> as nothing
|
||||||
|
// will be changed (and we actually do not care if there are any dependents of Core
|
||||||
|
// as it's present anyway)
|
||||||
|
if (CorePlugin.class.isAssignableFrom(plugin.getClass())) {
|
||||||
|
hasCore = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if plugin has CorePlugin in dependencies -> mark for addition
|
||||||
|
if (!hasCoreDependents) {
|
||||||
|
// here we check for direct CorePlugin, if it's not CorePlugin (exact, not a subclass
|
||||||
|
// or something -> ignore)
|
||||||
|
if (plugin.priority().after().contains(CorePlugin.class)) {
|
||||||
|
hasCoreDependents = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// important thing here is to check if corePlugin is added
|
||||||
|
// add it _only_ if it's not present
|
||||||
|
if (hasCoreDependents && !hasCore) {
|
||||||
|
final List<MarkwonPlugin> out = new ArrayList<>(plugins.size() + 1);
|
||||||
|
// add default instance of CorePlugin
|
||||||
|
out.add(CorePlugin.create());
|
||||||
|
out.addAll(plugins);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
return plugins;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,185 @@
|
|||||||
|
package ru.noties.markwon;
|
||||||
|
|
||||||
|
import android.support.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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* since 3.0.0 renamed `SpannableConfiguration` -> `MarkwonConfiguration`
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
public class MarkwonConfiguration {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static Builder builder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final MarkwonTheme theme;
|
||||||
|
private final AsyncDrawableLoader asyncDrawableLoader;
|
||||||
|
private final SyntaxHighlight syntaxHighlight;
|
||||||
|
private final LinkSpan.Resolver 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;
|
||||||
|
|
||||||
|
private MarkwonConfiguration(@NonNull Builder builder) {
|
||||||
|
this.theme = builder.theme;
|
||||||
|
this.asyncDrawableLoader = builder.asyncDrawableLoader;
|
||||||
|
this.syntaxHighlight = builder.syntaxHighlight;
|
||||||
|
this.linkResolver = builder.linkResolver;
|
||||||
|
this.urlProcessor = builder.urlProcessor;
|
||||||
|
this.imageSizeResolver = builder.imageSizeResolver;
|
||||||
|
this.spansFactory = builder.spansFactory;
|
||||||
|
this.htmlParser = builder.htmlParser;
|
||||||
|
this.htmlRenderer = builder.htmlRenderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public MarkwonTheme theme() {
|
||||||
|
return theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public AsyncDrawableLoader asyncDrawableLoader() {
|
||||||
|
return asyncDrawableLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public SyntaxHighlight syntaxHighlight() {
|
||||||
|
return syntaxHighlight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public LinkSpan.Resolver linkResolver() {
|
||||||
|
return linkResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public UrlProcessor urlProcessor() {
|
||||||
|
return urlProcessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public ImageSizeResolver imageSizeResolver() {
|
||||||
|
return imageSizeResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public MarkwonHtmlParser htmlParser() {
|
||||||
|
return htmlParser;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public MarkwonHtmlRenderer htmlRenderer() {
|
||||||
|
return htmlRenderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public MarkwonSpansFactory spansFactory() {
|
||||||
|
return spansFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"unused", "UnusedReturnValue"})
|
||||||
|
public static class Builder {
|
||||||
|
|
||||||
|
private MarkwonTheme theme;
|
||||||
|
private AsyncDrawableLoader asyncDrawableLoader;
|
||||||
|
private SyntaxHighlight syntaxHighlight;
|
||||||
|
private LinkSpan.Resolver linkResolver;
|
||||||
|
private UrlProcessor urlProcessor;
|
||||||
|
private ImageSizeResolver imageSizeResolver;
|
||||||
|
private MarkwonHtmlParser htmlParser;
|
||||||
|
private MarkwonHtmlRenderer htmlRenderer;
|
||||||
|
private MarkwonSpansFactory spansFactory;
|
||||||
|
|
||||||
|
Builder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder syntaxHighlight(@NonNull SyntaxHighlight syntaxHighlight) {
|
||||||
|
this.syntaxHighlight = syntaxHighlight;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder linkResolver(@NonNull LinkSpan.Resolver linkResolver) {
|
||||||
|
this.linkResolver = linkResolver;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder urlProcessor(@NonNull UrlProcessor urlProcessor) {
|
||||||
|
this.urlProcessor = urlProcessor;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder htmlParser(@NonNull MarkwonHtmlParser htmlParser) {
|
||||||
|
this.htmlParser = htmlParser;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 1.0.1
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public Builder imageSizeResolver(@NonNull ImageSizeResolver imageSizeResolver) {
|
||||||
|
this.imageSizeResolver = imageSizeResolver;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
|
||||||
|
if (syntaxHighlight == null) {
|
||||||
|
syntaxHighlight = new SyntaxHighlightNoOp();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (linkResolver == null) {
|
||||||
|
linkResolver = new LinkResolverDef();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (urlProcessor == null) {
|
||||||
|
urlProcessor = new UrlProcessorNoOp();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageSizeResolver == null) {
|
||||||
|
imageSizeResolver = new ImageSizeResolverDef();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (htmlParser == null) {
|
||||||
|
htmlParser = MarkwonHtmlParser.noOp();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MarkwonConfiguration(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
110
markwon-core/src/main/java/ru/noties/markwon/MarkwonImpl.java
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
package ru.noties.markwon;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.commonmark.node.Node;
|
||||||
|
import org.commonmark.parser.Parser;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
|
class MarkwonImpl extends Markwon {
|
||||||
|
|
||||||
|
private final TextView.BufferType bufferType;
|
||||||
|
private final Parser parser;
|
||||||
|
private final MarkwonVisitor visitor;
|
||||||
|
private final List<MarkwonPlugin> plugins;
|
||||||
|
|
||||||
|
MarkwonImpl(
|
||||||
|
@NonNull TextView.BufferType bufferType,
|
||||||
|
@NonNull Parser parser,
|
||||||
|
@NonNull MarkwonVisitor visitor,
|
||||||
|
@NonNull List<MarkwonPlugin> plugins) {
|
||||||
|
this.bufferType = bufferType;
|
||||||
|
this.parser = parser;
|
||||||
|
this.visitor = visitor;
|
||||||
|
this.plugins = plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Node parse(@NonNull String input) {
|
||||||
|
|
||||||
|
// make sure that all plugins are called `processMarkdown` before parsing
|
||||||
|
for (MarkwonPlugin plugin : plugins) {
|
||||||
|
input = plugin.processMarkdown(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parser.parse(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Spanned render(@NonNull Node node) {
|
||||||
|
|
||||||
|
for (MarkwonPlugin plugin : plugins) {
|
||||||
|
plugin.beforeRender(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
node.accept(visitor);
|
||||||
|
|
||||||
|
for (MarkwonPlugin plugin : plugins) {
|
||||||
|
plugin.afterRender(node, visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Spanned spanned = visitor.builder().spannableStringBuilder();
|
||||||
|
|
||||||
|
// clear render props and builder after rendering
|
||||||
|
visitor.clear();
|
||||||
|
|
||||||
|
return spanned;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Spanned toMarkdown(@NonNull String input) {
|
||||||
|
return render(parse(input));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMarkdown(@NonNull TextView textView, @NonNull String markdown) {
|
||||||
|
setParsedMarkdown(textView, toMarkdown(markdown));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setParsedMarkdown(@NonNull TextView textView, @NonNull Spanned markdown) {
|
||||||
|
|
||||||
|
for (MarkwonPlugin plugin : plugins) {
|
||||||
|
plugin.beforeSetText(textView, markdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
textView.setText(markdown, bufferType);
|
||||||
|
|
||||||
|
for (MarkwonPlugin plugin : plugins) {
|
||||||
|
plugin.afterSetText(textView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasPlugin(@NonNull Class<? extends MarkwonPlugin> type) {
|
||||||
|
return getPlugin(type) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public <P extends MarkwonPlugin> P getPlugin(@NonNull Class<P> type) {
|
||||||
|
MarkwonPlugin out = null;
|
||||||
|
for (MarkwonPlugin plugin : plugins) {
|
||||||
|
if (type.isAssignableFrom(plugin.getClass())) {
|
||||||
|
out = plugin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//noinspection unchecked
|
||||||
|
return (P) out;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,184 @@
|
|||||||
|
package ru.noties.markwon;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.annotation.IdRes;
|
||||||
|
import android.support.annotation.LayoutRes;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.commonmark.node.Node;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
|
public abstract class MarkwonNodeRenderer {
|
||||||
|
|
||||||
|
public interface ViewProvider<N extends Node> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Please note that you should not attach created View to specified group. It will be done
|
||||||
|
* automatically.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
View provide(
|
||||||
|
@NonNull LayoutInflater inflater,
|
||||||
|
@NonNull ViewGroup group,
|
||||||
|
@NonNull Markwon markwon,
|
||||||
|
@NonNull N n);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static Builder builder(@NonNull ViewProvider<Node> defaultViewProvider) {
|
||||||
|
return new Builder(defaultViewProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param defaultViewProviderLayoutResId layout resource id to be used in default view provider
|
||||||
|
* @param defaultViewProviderTextViewId id of a TextView in specified layout
|
||||||
|
* @return Builder
|
||||||
|
* @see SimpleTextViewProvider
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static Builder builder(
|
||||||
|
@LayoutRes int defaultViewProviderLayoutResId,
|
||||||
|
@IdRes int defaultViewProviderTextViewId) {
|
||||||
|
return new Builder(new SimpleTextViewProvider(
|
||||||
|
defaultViewProviderLayoutResId,
|
||||||
|
defaultViewProviderTextViewId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void render(@NonNull ViewGroup group, @NonNull Markwon markwon, @NonNull String markdown);
|
||||||
|
|
||||||
|
public abstract void render(@NonNull ViewGroup group, @NonNull Markwon markwon, @NonNull Node root);
|
||||||
|
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
|
||||||
|
private final ViewProvider<Node> defaultViewProvider;
|
||||||
|
|
||||||
|
private MarkwonReducer reducer;
|
||||||
|
private Map<Class<? extends Node>, ViewProvider<Node>> viewProviders;
|
||||||
|
private LayoutInflater inflater;
|
||||||
|
|
||||||
|
public Builder(@NonNull ViewProvider<Node> defaultViewProvider) {
|
||||||
|
this.defaultViewProvider = defaultViewProvider;
|
||||||
|
this.viewProviders = new HashMap<>(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder reducer(@NonNull MarkwonReducer reducer) {
|
||||||
|
this.reducer = reducer;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public <N extends Node> Builder viewProvider(
|
||||||
|
@NonNull Class<N> type,
|
||||||
|
@NonNull ViewProvider<? super N> viewProvider) {
|
||||||
|
//noinspection unchecked
|
||||||
|
viewProviders.put(type, (ViewProvider<Node>) viewProvider);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder inflater(@NonNull LayoutInflater inflater) {
|
||||||
|
this.inflater = inflater;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public MarkwonNodeRenderer build() {
|
||||||
|
if (reducer == null) {
|
||||||
|
reducer = MarkwonReducer.directChildren();
|
||||||
|
}
|
||||||
|
return new Impl(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SimpleTextViewProvider implements ViewProvider<Node> {
|
||||||
|
|
||||||
|
private final int layoutResId;
|
||||||
|
private final int textViewId;
|
||||||
|
|
||||||
|
public SimpleTextViewProvider(@LayoutRes int layoutResId, @IdRes int textViewId) {
|
||||||
|
this.layoutResId = layoutResId;
|
||||||
|
this.textViewId = textViewId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public View provide(
|
||||||
|
@NonNull LayoutInflater inflater,
|
||||||
|
@NonNull ViewGroup group,
|
||||||
|
@NonNull Markwon markwon,
|
||||||
|
@NonNull Node node) {
|
||||||
|
final View view = inflater.inflate(layoutResId, group, false);
|
||||||
|
final TextView textView = view.findViewById(textViewId);
|
||||||
|
markwon.setParsedMarkdown(textView, markwon.render(node));
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Impl extends MarkwonNodeRenderer {
|
||||||
|
|
||||||
|
private final MarkwonReducer reducer;
|
||||||
|
private final Map<Class<? extends Node>, ViewProvider<Node>> viewProviders;
|
||||||
|
private final ViewProvider<Node> defaultViewProvider;
|
||||||
|
|
||||||
|
private LayoutInflater inflater;
|
||||||
|
|
||||||
|
Impl(@NonNull Builder builder) {
|
||||||
|
this.reducer = builder.reducer;
|
||||||
|
this.viewProviders = builder.viewProviders;
|
||||||
|
this.defaultViewProvider = builder.defaultViewProvider;
|
||||||
|
this.inflater = builder.inflater;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(@NonNull ViewGroup group, @NonNull Markwon markwon, @NonNull String markdown) {
|
||||||
|
render(group, markwon, markwon.parse(markdown));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(@NonNull ViewGroup group, @NonNull Markwon markwon, @NonNull Node root) {
|
||||||
|
|
||||||
|
final LayoutInflater inflater = ensureLayoutInflater(group.getContext());
|
||||||
|
|
||||||
|
ViewProvider<Node> viewProvider;
|
||||||
|
|
||||||
|
for (Node node : reducer.reduce(root)) {
|
||||||
|
viewProvider = viewProvider(node);
|
||||||
|
group.addView(viewProvider.provide(inflater, group, markwon, node));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private LayoutInflater ensureLayoutInflater(@NonNull Context context) {
|
||||||
|
LayoutInflater inflater = this.inflater;
|
||||||
|
if (inflater == null) {
|
||||||
|
inflater = this.inflater = LayoutInflater.from(context);
|
||||||
|
}
|
||||||
|
return inflater;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private ViewProvider<Node> viewProvider(@NonNull Node node) {
|
||||||
|
|
||||||
|
// check for specific node view provider
|
||||||
|
final ViewProvider<Node> provider = viewProviders.get(node.getClass());
|
||||||
|
if (provider != null) {
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if it's not present, then we can return a default one
|
||||||
|
return defaultViewProvider;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
143
markwon-core/src/main/java/ru/noties/markwon/MarkwonPlugin.java
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
package ru.noties.markwon;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
|
public interface MarkwonPlugin {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to configure <code>org.commonmark.parser.Parser</code> (for example register custom
|
||||||
|
* extension, etc).
|
||||||
|
*/
|
||||||
|
void configureParser(@NonNull Parser.Builder builder);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modify {@link MarkwonTheme} that is used for rendering of markdown.
|
||||||
|
*
|
||||||
|
* @see MarkwonTheme
|
||||||
|
* @see MarkwonTheme.Builder
|
||||||
|
*/
|
||||||
|
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}
|
||||||
|
*
|
||||||
|
* @see MarkwonConfiguration
|
||||||
|
* @see MarkwonConfiguration.Builder
|
||||||
|
*/
|
||||||
|
void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure {@link MarkwonVisitor} to accept new node types or override already registered nodes.
|
||||||
|
*
|
||||||
|
* @see MarkwonVisitor
|
||||||
|
* @see MarkwonVisitor.Builder
|
||||||
|
*/
|
||||||
|
void configureVisitor(@NonNull MarkwonVisitor.Builder builder);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure {@link MarkwonSpansFactory} to change what spans are used for certain node types.
|
||||||
|
*
|
||||||
|
* @see MarkwonSpansFactory
|
||||||
|
* @see MarkwonSpansFactory.Builder
|
||||||
|
*/
|
||||||
|
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.
|
||||||
|
*
|
||||||
|
* @param markdown String to process
|
||||||
|
* @return processed markdown String
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
String processMarkdown(@NonNull String markdown);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will be called <strong>before</strong> rendering will occur thus making possible
|
||||||
|
* to <code>post-process</code> parsed node (make changes for example).
|
||||||
|
*
|
||||||
|
* @param node root parsed org.commonmark.node.Node
|
||||||
|
*/
|
||||||
|
void beforeRender(@NonNull Node node);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will be called <strong>after</strong> rendering (but before applying markdown to a
|
||||||
|
* TextView, if such action will happen). It can be used to clean some
|
||||||
|
* internal state, or trigger certain action. Please note that modifying <code>node</code> won\'t
|
||||||
|
* have any effect as it has been already <i>visited</i> at this stage.
|
||||||
|
*
|
||||||
|
* @param node root parsed org.commonmark.node.Node
|
||||||
|
* @param visitor {@link MarkwonVisitor} instance used to render markdown
|
||||||
|
*/
|
||||||
|
void afterRender(@NonNull Node node, @NonNull MarkwonVisitor visitor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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}
|
||||||
|
* which measures ordered list numbers
|
||||||
|
*
|
||||||
|
* @param textView TextView to which <code>markdown</code> will be applied
|
||||||
|
* @param markdown Parsed markdown
|
||||||
|
*/
|
||||||
|
void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* asynchronously loading images.
|
||||||
|
* <p>
|
||||||
|
* Unlike {@link #beforeSetText(TextView, Spanned)} this method does not receive parsed markdown
|
||||||
|
* as at this point spans must be queried by calling <code>TextView#getText#getSpans</code>.
|
||||||
|
*
|
||||||
|
* @param textView TextView to which markdown was applied
|
||||||
|
*/
|
||||||
|
void afterSetText(@NonNull TextView textView);
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
package ru.noties.markwon;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.commonmark.node.Node;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
|
public abstract class MarkwonReducer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return direct children of supplied Node. In the most usual case
|
||||||
|
* will return all BlockNodes of a Document
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static MarkwonReducer directChildren() {
|
||||||
|
return new DirectChildren();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public abstract List<Node> reduce(@NonNull Node node);
|
||||||
|
|
||||||
|
|
||||||
|
static class DirectChildren extends MarkwonReducer {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public List<Node> reduce(@NonNull Node root) {
|
||||||
|
|
||||||
|
final List<Node> list;
|
||||||
|
|
||||||
|
// we will extract all blocks that are direct children of Document
|
||||||
|
Node node = root.getFirstChild();
|
||||||
|
|
||||||
|
// please note, that if there are no children -> we will return a list with
|
||||||
|
// single element (which was supplied)
|
||||||
|
if (node == null) {
|
||||||
|
list = Collections.singletonList(root);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
list = new ArrayList<>();
|
||||||
|
|
||||||
|
Node temp;
|
||||||
|
|
||||||
|
while (node != null) {
|
||||||
|
list.add(node);
|
||||||
|
temp = node.getNext();
|
||||||
|
node.unlink();
|
||||||
|
node = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package ru.noties.markwon;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.commonmark.node.Node;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that controls what spans are used for certain Nodes.
|
||||||
|
*
|
||||||
|
* @see SpanFactory
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
|
public interface MarkwonSpansFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns registered {@link SpanFactory} or <code>null</code> if a factory for this node type
|
||||||
|
* is not registered. There is {@link #require(Class)} method that will throw an exception
|
||||||
|
* if required {@link SpanFactory} is not registered, thus making return type <code>non-null</code>
|
||||||
|
*
|
||||||
|
* @param node type of the node
|
||||||
|
* @return registered {@link SpanFactory} or null if it\'s not registered
|
||||||
|
* @see #require(Class)
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
<N extends Node> SpanFactory get(@NonNull Class<N> node);
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
<N extends Node> SpanFactory require(@NonNull Class<N> node);
|
||||||
|
|
||||||
|
|
||||||
|
interface Builder {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
<N extends Node> Builder setFactory(@NonNull Class<N> node, @Nullable SpanFactory factory);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be useful when <em>enhancing</em> an already defined SpanFactory with another one.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
<N extends Node> SpanFactory getFactory(@NonNull Class<N> node);
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
MarkwonSpansFactory build();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
package ru.noties.markwon;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.commonmark.node.Node;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
|
class MarkwonSpansFactoryImpl implements MarkwonSpansFactory {
|
||||||
|
|
||||||
|
private final Map<Class<? extends Node>, SpanFactory> factories;
|
||||||
|
|
||||||
|
MarkwonSpansFactoryImpl(@NonNull Map<Class<? extends Node>, SpanFactory> factories) {
|
||||||
|
this.factories = factories;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public <N extends Node> SpanFactory get(@NonNull Class<N> node) {
|
||||||
|
return factories.get(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public <N extends Node> SpanFactory require(@NonNull Class<N> node) {
|
||||||
|
final SpanFactory f = get(node);
|
||||||
|
if (f == null) {
|
||||||
|
throw new NullPointerException(node.getName());
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class BuilderImpl implements Builder {
|
||||||
|
|
||||||
|
private final Map<Class<? extends Node>, SpanFactory> factories =
|
||||||
|
new HashMap<>(3);
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public <N extends Node> Builder setFactory(@NonNull Class<N> node, @Nullable SpanFactory factory) {
|
||||||
|
if (factory == null) {
|
||||||
|
factories.remove(node);
|
||||||
|
} else {
|
||||||
|
factories.put(node, factory);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public <N extends Node> SpanFactory getFactory(@NonNull Class<N> node) {
|
||||||
|
return factories.get(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public MarkwonSpansFactory build() {
|
||||||
|
return new MarkwonSpansFactoryImpl(Collections.unmodifiableMap(factories));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
136
markwon-core/src/main/java/ru/noties/markwon/MarkwonVisitor.java
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
package ru.noties.markwon;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.commonmark.node.Node;
|
||||||
|
import org.commonmark.node.Visitor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configurable visitor of parsed markdown. Allows visiting certain (registered) nodes without
|
||||||
|
* need to create own instance of this class.
|
||||||
|
*
|
||||||
|
* @see Builder#on(Class, NodeVisitor)
|
||||||
|
* @see MarkwonPlugin#configureVisitor(Builder)
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
|
public interface MarkwonVisitor extends Visitor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see Builder#on(Class, NodeVisitor)
|
||||||
|
*/
|
||||||
|
interface NodeVisitor<N extends Node> {
|
||||||
|
void visit(@NonNull MarkwonVisitor visitor, @NonNull N n);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Builder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param node to register
|
||||||
|
* @param nodeVisitor {@link NodeVisitor} to be used or null to ignore previously registered
|
||||||
|
* visitor for this node
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
<N extends Node> Builder on(@NonNull Class<N> node, @Nullable NodeVisitor<? super N> nodeVisitor);
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
MarkwonVisitor build(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps renderProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
MarkwonConfiguration configuration();
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
RenderProps renderProps();
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
SpannableBuilder builder();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visits all children of supplied node.
|
||||||
|
*
|
||||||
|
* @param node to visit
|
||||||
|
*/
|
||||||
|
void visitChildren(@NonNull Node node);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a check if there is further content available.
|
||||||
|
*
|
||||||
|
* @param node to check
|
||||||
|
* @return boolean indicating if there are more nodes after supplied one
|
||||||
|
*/
|
||||||
|
boolean hasNext(@NonNull Node node);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method <strong>ensures</strong> that further content will start at a new line. If current
|
||||||
|
* last character is already a new line, then it won\'t do anything.
|
||||||
|
*/
|
||||||
|
void ensureNewLine();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method inserts a new line without any condition checking (unlike {@link #ensureNewLine()}).
|
||||||
|
*/
|
||||||
|
void forceNewLine();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to call <code>builder().length()</code>
|
||||||
|
*
|
||||||
|
* @return current length of underlying {@link SpannableBuilder}
|
||||||
|
*/
|
||||||
|
int length();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears state of visitor (both {@link RenderProps} and {@link SpannableBuilder} will be cleared
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets <code>spans</code> to underlying {@link SpannableBuilder} from <em>start</em>
|
||||||
|
* to <em>{@link SpannableBuilder#length()}</em>.
|
||||||
|
*
|
||||||
|
* @param start start position of spans
|
||||||
|
* @param spans to apply
|
||||||
|
*/
|
||||||
|
void setSpans(int start, @Nullable Object spans);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to obtain and apply spans for supplied Node. Internally queries {@link SpanFactory}
|
||||||
|
* for the node (via {@link MarkwonSpansFactory#require(Class)} thus throwing an exception
|
||||||
|
* if there is no {@link SpanFactory} registered for the node).
|
||||||
|
*
|
||||||
|
* @param node to retrieve {@link SpanFactory} for
|
||||||
|
* @param start start position for further {@link #setSpans(int, Object)} call
|
||||||
|
* @see #setSpansForNodeOptional(Node, int)
|
||||||
|
*/
|
||||||
|
<N extends Node> void setSpansForNode(@NonNull N node, int start);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The same as {@link #setSpansForNode(Node, int)} but can be used in situations when there is
|
||||||
|
* no access to a Node instance (for example in HTML rendering which doesn\'t have markdown Nodes).
|
||||||
|
*
|
||||||
|
* @see #setSpansForNode(Node, int)
|
||||||
|
*/
|
||||||
|
<N extends Node> void setSpansForNode(@NonNull Class<N> node, int start);
|
||||||
|
|
||||||
|
// does not throw if there is no SpanFactory registered for this node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to apply spans from a {@link SpanFactory} <b>if</b> it\'s registered in
|
||||||
|
* {@link MarkwonSpansFactory} instance. Otherwise ignores this call (no spans will be applied).
|
||||||
|
* If there is a need to ensure that specified <code>node</code> has a {@link SpanFactory} registered,
|
||||||
|
* then {@link #setSpansForNode(Node, int)} can be used. {@link #setSpansForNode(Node, int)} internally
|
||||||
|
* uses {@link MarkwonSpansFactory#require(Class)}. This method uses {@link MarkwonSpansFactory#get(Class)}.
|
||||||
|
*
|
||||||
|
* @see #setSpansForNode(Node, int)
|
||||||
|
*/
|
||||||
|
<N extends Node> void setSpansForNodeOptional(@NonNull N node, int start);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The same as {@link #setSpansForNodeOptional(Node, int)} but can be used in situations when
|
||||||
|
* there is no access to a Node instance (for example in HTML rendering).
|
||||||
|
*
|
||||||
|
* @see #setSpansForNodeOptional(Node, int)
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
<N extends Node> void setSpansForNodeOptional(@NonNull Class<N> node, int start);
|
||||||
|
}
|
@ -0,0 +1,292 @@
|
|||||||
|
package ru.noties.markwon;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.commonmark.node.BlockQuote;
|
||||||
|
import org.commonmark.node.BulletList;
|
||||||
|
import org.commonmark.node.Code;
|
||||||
|
import org.commonmark.node.CustomBlock;
|
||||||
|
import org.commonmark.node.CustomNode;
|
||||||
|
import org.commonmark.node.Document;
|
||||||
|
import org.commonmark.node.Emphasis;
|
||||||
|
import org.commonmark.node.FencedCodeBlock;
|
||||||
|
import org.commonmark.node.HardLineBreak;
|
||||||
|
import org.commonmark.node.Heading;
|
||||||
|
import org.commonmark.node.HtmlBlock;
|
||||||
|
import org.commonmark.node.HtmlInline;
|
||||||
|
import org.commonmark.node.Image;
|
||||||
|
import org.commonmark.node.IndentedCodeBlock;
|
||||||
|
import org.commonmark.node.Link;
|
||||||
|
import org.commonmark.node.ListItem;
|
||||||
|
import org.commonmark.node.Node;
|
||||||
|
import org.commonmark.node.OrderedList;
|
||||||
|
import org.commonmark.node.Paragraph;
|
||||||
|
import org.commonmark.node.SoftLineBreak;
|
||||||
|
import org.commonmark.node.StrongEmphasis;
|
||||||
|
import org.commonmark.node.Text;
|
||||||
|
import org.commonmark.node.ThematicBreak;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
|
class MarkwonVisitorImpl implements MarkwonVisitor {
|
||||||
|
|
||||||
|
private final MarkwonConfiguration configuration;
|
||||||
|
|
||||||
|
private final RenderProps renderProps;
|
||||||
|
|
||||||
|
private final SpannableBuilder builder;
|
||||||
|
|
||||||
|
private final Map<Class<? extends Node>, NodeVisitor<? extends Node>> nodes;
|
||||||
|
|
||||||
|
MarkwonVisitorImpl(
|
||||||
|
@NonNull MarkwonConfiguration configuration,
|
||||||
|
@NonNull RenderProps renderProps,
|
||||||
|
@NonNull SpannableBuilder builder,
|
||||||
|
@NonNull Map<Class<? extends Node>, NodeVisitor<? extends Node>> nodes) {
|
||||||
|
this.configuration = configuration;
|
||||||
|
this.renderProps = renderProps;
|
||||||
|
this.builder = builder;
|
||||||
|
this.nodes = nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(BlockQuote blockQuote) {
|
||||||
|
visit((Node) blockQuote);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(BulletList bulletList) {
|
||||||
|
visit((Node) bulletList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(Code code) {
|
||||||
|
visit((Node) code);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(Document document) {
|
||||||
|
visit((Node) document);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(Emphasis emphasis) {
|
||||||
|
visit((Node) emphasis);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(FencedCodeBlock fencedCodeBlock) {
|
||||||
|
visit((Node) fencedCodeBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(HardLineBreak hardLineBreak) {
|
||||||
|
visit((Node) hardLineBreak);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(Heading heading) {
|
||||||
|
visit((Node) heading);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(ThematicBreak thematicBreak) {
|
||||||
|
visit((Node) thematicBreak);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(HtmlInline htmlInline) {
|
||||||
|
visit((Node) htmlInline);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(HtmlBlock htmlBlock) {
|
||||||
|
visit((Node) htmlBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(Image image) {
|
||||||
|
visit((Node) image);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(IndentedCodeBlock indentedCodeBlock) {
|
||||||
|
visit((Node) indentedCodeBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(Link link) {
|
||||||
|
visit((Node) link);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(ListItem listItem) {
|
||||||
|
visit((Node) listItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(OrderedList orderedList) {
|
||||||
|
visit((Node) orderedList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(Paragraph paragraph) {
|
||||||
|
visit((Node) paragraph);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(SoftLineBreak softLineBreak) {
|
||||||
|
visit((Node) softLineBreak);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(StrongEmphasis strongEmphasis) {
|
||||||
|
visit((Node) strongEmphasis);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(Text text) {
|
||||||
|
visit((Node) text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(CustomBlock customBlock) {
|
||||||
|
visit((Node) customBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(CustomNode customNode) {
|
||||||
|
visit((Node) customNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void visit(@NonNull Node node) {
|
||||||
|
//noinspection unchecked
|
||||||
|
final NodeVisitor<Node> nodeVisitor = (NodeVisitor<Node>) nodes.get(node.getClass());
|
||||||
|
if (nodeVisitor != null) {
|
||||||
|
nodeVisitor.visit(this, node);
|
||||||
|
} else {
|
||||||
|
visitChildren(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public MarkwonConfiguration configuration() {
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public RenderProps renderProps() {
|
||||||
|
return renderProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public SpannableBuilder builder() {
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitChildren(@NonNull Node parent) {
|
||||||
|
Node node = parent.getFirstChild();
|
||||||
|
while (node != null) {
|
||||||
|
// A subclass of this visitor might modify the node, resulting in getNext returning a different node or no
|
||||||
|
// node after visiting it. So get the next node before visiting.
|
||||||
|
Node next = node.getNext();
|
||||||
|
node.accept(this);
|
||||||
|
node = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext(@NonNull Node node) {
|
||||||
|
return node.getNext() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void ensureNewLine() {
|
||||||
|
if (builder.length() > 0
|
||||||
|
&& '\n' != builder.lastChar()) {
|
||||||
|
builder.append('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void forceNewLine() {
|
||||||
|
builder.append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int length() {
|
||||||
|
return builder.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSpans(int start, @Nullable Object spans) {
|
||||||
|
SpannableBuilder.setSpans(builder, spans, start, builder.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
renderProps.clearAll();
|
||||||
|
builder.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <N extends Node> void setSpansForNode(@NonNull N node, int start) {
|
||||||
|
setSpansForNode(node.getClass(), start);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <N extends Node> void setSpansForNode(@NonNull Class<N> node, int start) {
|
||||||
|
setSpans(start, configuration.spansFactory().require(node).getSpans(configuration, renderProps));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <N extends Node> void setSpansForNodeOptional(@NonNull N node, int start) {
|
||||||
|
setSpansForNodeOptional(node.getClass(), start);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <N extends Node> void setSpansForNodeOptional(@NonNull Class<N> node, int start) {
|
||||||
|
final SpanFactory factory = configuration.spansFactory().get(node);
|
||||||
|
if (factory != null) {
|
||||||
|
setSpans(start, factory.getSpans(configuration, renderProps));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class BuilderImpl implements Builder {
|
||||||
|
|
||||||
|
private final Map<Class<? extends Node>, NodeVisitor<? extends Node>> nodes = new HashMap<>();
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public <N extends Node> Builder on(@NonNull Class<N> node, @Nullable NodeVisitor<? super N> nodeVisitor) {
|
||||||
|
// we should allow `null` to exclude node from being visited (for example to disable
|
||||||
|
// some functionality)
|
||||||
|
if (nodeVisitor == null) {
|
||||||
|
nodes.remove(node);
|
||||||
|
} else {
|
||||||
|
nodes.put(node, nodeVisitor);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public MarkwonVisitor build(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps renderProps) {
|
||||||
|
return new MarkwonVisitorImpl(
|
||||||
|
configuration,
|
||||||
|
renderProps,
|
||||||
|
new SpannableBuilder(),
|
||||||
|
Collections.unmodifiableMap(nodes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|