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

|

|
||||||

|

|
||||||
|
|
||||||
```groovy
|
```kotlin
|
||||||
implementation "ru.noties.markwon:core:${markwonVersion}"
|
implementation "io.noties.markwon:core:${markwonVersion}"
|
||||||
```
|
```
|
||||||
|
|
||||||
Full list of available artifacts is present in the [install section](https://noties.github.io/Markwon/docs/v3/install.html)
|
Full list of available artifacts is present in the [install section](https://noties.github.io/Markwon/docs/v4/install.html)
|
||||||
of the [documentation] web-site.
|
of the [documentation] web-site.
|
||||||
|
|
||||||
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
|
> You can find previous version of Markwon in [2.x.x](https://github.com/noties/Markwon/tree/2.x.x)
|
||||||
|
and [3.x.x](https://github.com/noties/Markwon/tree/3.x.x) branches
|
||||||
|
|
||||||
|
|
||||||
## Supported markdown features:
|
## Supported markdown features:
|
||||||
@ -97,15 +98,6 @@ Please visit [documentation] web-site for reference
|
|||||||
|
|
||||||
[documentation]: https://noties.github.io/Markwon
|
[documentation]: https://noties.github.io/Markwon
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Applications using Markwon
|
|
||||||
|
|
||||||
* [Partiko](https://partiko.app)
|
|
||||||
* [FairNote Notepad](https://play.google.com/store/apps/details?id=com.rgiskard.fairnote)
|
|
||||||
* [Boxcryptor](https://www.boxcryptor.com)
|
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Demo
|
# Demo
|
||||||
@ -217,7 +209,6 @@ public static Parser createParser() {
|
|||||||
android:layout_margin="16dip"
|
android:layout_margin="16dip"
|
||||||
android:lineSpacingExtra="2dip"
|
android:lineSpacingExtra="2dip"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
tools:context="ru.noties.markwon.MainActivity"
|
|
||||||
tools:text="yo\nman" />
|
tools:text="yo\nman" />
|
||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
@ -296,7 +287,7 @@ Underscores (`_`)
|
|||||||
## License
|
## License
|
||||||
|
|
||||||
```
|
```
|
||||||
Copyright 2017 Dimitry Ivanov (mail@dimitryivanov.ru)
|
Copyright 2019 Dimitry Ivanov (legal@noties.io)
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -6,7 +6,7 @@ android {
|
|||||||
buildToolsVersion config['build-tools']
|
buildToolsVersion config['build-tools']
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "ru.noties.markwon"
|
applicationId "io.noties.markwon"
|
||||||
minSdkVersion config['min-sdk']
|
minSdkVersion config['min-sdk']
|
||||||
targetSdkVersion config['target-sdk']
|
targetSdkVersion config['target-sdk']
|
||||||
versionCode 1
|
versionCode 1
|
||||||
@ -33,8 +33,7 @@ dependencies {
|
|||||||
implementation project(':markwon-ext-tables')
|
implementation project(':markwon-ext-tables')
|
||||||
implementation project(':markwon-ext-tasklist')
|
implementation project(':markwon-ext-tasklist')
|
||||||
implementation project(':markwon-html')
|
implementation project(':markwon-html')
|
||||||
implementation project(':markwon-image-gif')
|
implementation project(':markwon-image')
|
||||||
implementation project(':markwon-image-svg')
|
|
||||||
implementation project(':markwon-syntax-highlight')
|
implementation project(':markwon-syntax-highlight')
|
||||||
|
|
||||||
deps.with {
|
deps.with {
|
||||||
@ -42,6 +41,8 @@ dependencies {
|
|||||||
implementation it['prism4j']
|
implementation it['prism4j']
|
||||||
implementation it['debug']
|
implementation it['debug']
|
||||||
implementation it['dagger']
|
implementation it['dagger']
|
||||||
|
implementation it['android-svg']
|
||||||
|
implementation it['android-gif']
|
||||||
}
|
}
|
||||||
|
|
||||||
deps['annotationProcessor'].with {
|
deps['annotationProcessor'].with {
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
package ru.noties.markwon.debug;
|
package io.noties.markwon.debug;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.support.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import ru.noties.markwon.R;
|
import io.noties.markwon.app.R;
|
||||||
import ru.noties.markwon.ext.tasklist.TaskListDrawable;
|
import io.noties.markwon.ext.tasklist.TaskListDrawable;
|
||||||
|
|
||||||
public class DebugCheckboxDrawableView extends View {
|
public class DebugCheckboxDrawableView extends View {
|
||||||
|
|
@ -4,7 +4,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<ru.noties.markwon.debug.DebugCheckboxDrawableView
|
<io.noties.markwon.debug.DebugCheckboxDrawableView
|
||||||
android:layout_width="128dip"
|
android:layout_width="128dip"
|
||||||
android:layout_height="128dip"
|
android:layout_height="128dip"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?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"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="ru.noties.markwon">
|
package="io.noties.markwon.app">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_ALL_DOWNLOADS" />
|
<uses-permission android:name="android.permission.ACCESS_ALL_DOWNLOADS" />
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon.app;
|
||||||
|
|
||||||
import javax.inject.Scope;
|
import javax.inject.Scope;
|
||||||
|
|
@ -1,11 +1,12 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon.app;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
|
||||||
import ru.noties.debug.AndroidLogDebugOutput;
|
import androidx.annotation.NonNull;
|
||||||
import ru.noties.debug.Debug;
|
|
||||||
|
import io.noties.debug.AndroidLogDebugOutput;
|
||||||
|
import io.noties.debug.Debug;
|
||||||
|
|
||||||
public class App extends Application {
|
public class App extends Application {
|
||||||
|
|
@ -1,10 +1,11 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon.app;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
abstract class AppBarItem {
|
abstract class AppBarItem {
|
||||||
|
|
||||||
static class State {
|
static class State {
|
@ -1,4 +1,4 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon.app;
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon.app;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
@ -12,12 +12,12 @@ import javax.inject.Singleton;
|
|||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
import io.noties.markwon.syntax.Prism4jThemeDarkula;
|
||||||
|
import io.noties.markwon.syntax.Prism4jThemeDefault;
|
||||||
import okhttp3.Cache;
|
import okhttp3.Cache;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
import ru.noties.markwon.syntax.Prism4jThemeDarkula;
|
import io.noties.prism4j.Prism4j;
|
||||||
import ru.noties.markwon.syntax.Prism4jThemeDefault;
|
import io.noties.prism4j.annotations.PrismBundle;
|
||||||
import ru.noties.prism4j.Prism4j;
|
|
||||||
import ru.noties.prism4j.annotations.PrismBundle;
|
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@PrismBundle(includeAll = true)
|
@PrismBundle(includeAll = true)
|
@ -1,18 +1,20 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon.app;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import ru.noties.debug.Debug;
|
import io.noties.debug.Debug;
|
||||||
|
import io.noties.markwon.Markwon;
|
||||||
|
|
||||||
public class MainActivity extends Activity {
|
public class MainActivity extends Activity {
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon.app;
|
||||||
|
|
||||||
import dagger.Subcomponent;
|
import dagger.Subcomponent;
|
||||||
|
|
@ -1,13 +1,14 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon.app;
|
||||||
|
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
@ -20,11 +21,11 @@ import java.util.concurrent.Future;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import io.noties.debug.Debug;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
import okhttp3.ResponseBody;
|
import okhttp3.ResponseBody;
|
||||||
import ru.noties.debug.Debug;
|
|
||||||
|
|
||||||
@ActivityScope
|
@ActivityScope
|
||||||
public class MarkdownLoader {
|
public class MarkdownLoader {
|
@ -1,35 +1,38 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon.app;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import ru.noties.debug.Debug;
|
import io.noties.debug.Debug;
|
||||||
import ru.noties.markwon.core.CorePlugin;
|
import io.noties.markwon.AbstractMarkwonPlugin;
|
||||||
import ru.noties.markwon.ext.strikethrough.StrikethroughPlugin;
|
import io.noties.markwon.Markwon;
|
||||||
import ru.noties.markwon.ext.tables.TablePlugin;
|
import io.noties.markwon.MarkwonConfiguration;
|
||||||
import ru.noties.markwon.ext.tasklist.TaskListPlugin;
|
import io.noties.markwon.app.gif.GifAwarePlugin;
|
||||||
import ru.noties.markwon.gif.GifAwarePlugin;
|
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin;
|
||||||
import ru.noties.markwon.html.HtmlPlugin;
|
import io.noties.markwon.ext.tables.TablePlugin;
|
||||||
import ru.noties.markwon.image.ImagesPlugin;
|
import io.noties.markwon.ext.tasklist.TaskListPlugin;
|
||||||
import ru.noties.markwon.image.gif.GifPlugin;
|
import io.noties.markwon.html.HtmlPlugin;
|
||||||
import ru.noties.markwon.image.svg.SvgPlugin;
|
import io.noties.markwon.image.ImagesPlugin;
|
||||||
import ru.noties.markwon.syntax.Prism4jTheme;
|
import io.noties.markwon.image.file.FileSchemeHandler;
|
||||||
import ru.noties.markwon.syntax.Prism4jThemeDarkula;
|
import io.noties.markwon.image.network.OkHttpNetworkSchemeHandler;
|
||||||
import ru.noties.markwon.syntax.Prism4jThemeDefault;
|
import io.noties.markwon.syntax.Prism4jTheme;
|
||||||
import ru.noties.markwon.syntax.SyntaxHighlightPlugin;
|
import io.noties.markwon.syntax.Prism4jThemeDarkula;
|
||||||
import ru.noties.markwon.urlprocessor.UrlProcessor;
|
import io.noties.markwon.syntax.Prism4jThemeDefault;
|
||||||
import ru.noties.markwon.urlprocessor.UrlProcessorRelativeToAbsolute;
|
import io.noties.markwon.syntax.SyntaxHighlightPlugin;
|
||||||
import ru.noties.prism4j.Prism4j;
|
import io.noties.markwon.urlprocessor.UrlProcessor;
|
||||||
|
import io.noties.markwon.urlprocessor.UrlProcessorRelativeToAbsolute;
|
||||||
|
import io.noties.prism4j.Prism4j;
|
||||||
|
|
||||||
@ActivityScope
|
@ActivityScope
|
||||||
public class MarkdownRenderer {
|
public class MarkdownRenderer {
|
||||||
@ -94,10 +97,17 @@ public class MarkdownRenderer {
|
|||||||
: prism4JThemeDarkula;
|
: prism4JThemeDarkula;
|
||||||
|
|
||||||
final Markwon markwon = Markwon.builder(context)
|
final Markwon markwon = Markwon.builder(context)
|
||||||
.usePlugin(CorePlugin.create())
|
.usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
|
||||||
.usePlugin(ImagesPlugin.createWithAssets(context))
|
@Override
|
||||||
.usePlugin(SvgPlugin.create(context.getResources()))
|
public void configureImages(@NonNull ImagesPlugin plugin) {
|
||||||
.usePlugin(GifPlugin.create(false))
|
// data uri scheme handler is added automatically
|
||||||
|
// SVG & GIF will be added if required dependencies are present in the classpath
|
||||||
|
// default-media-decoder is also added automatically
|
||||||
|
plugin
|
||||||
|
.addSchemeHandler(OkHttpNetworkSchemeHandler.create())
|
||||||
|
.addSchemeHandler(FileSchemeHandler.createWithAssets(context.getAssets()));
|
||||||
|
}
|
||||||
|
}))
|
||||||
.usePlugin(SyntaxHighlightPlugin.create(prism4j, prism4jTheme))
|
.usePlugin(SyntaxHighlightPlugin.create(prism4j, prism4jTheme))
|
||||||
.usePlugin(GifAwarePlugin.create(context))
|
.usePlugin(GifAwarePlugin.create(context))
|
||||||
.usePlugin(TablePlugin.create(context))
|
.usePlugin(TablePlugin.create(context))
|
@ -1,8 +1,9 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon.app;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
@ -1,7 +1,8 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon.app;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
public interface UriProcessor {
|
public interface UriProcessor {
|
@ -1,7 +1,8 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon.app;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -1,11 +1,12 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon.app;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import ru.noties.markwon.urlprocessor.UrlProcessor;
|
import androidx.annotation.NonNull;
|
||||||
import ru.noties.markwon.urlprocessor.UrlProcessorRelativeToAbsolute;
|
|
||||||
|
import io.noties.markwon.urlprocessor.UrlProcessor;
|
||||||
|
import io.noties.markwon.urlprocessor.UrlProcessorRelativeToAbsolute;
|
||||||
|
|
||||||
class UrlProcessorInitialReadme implements UrlProcessor {
|
class UrlProcessorInitialReadme implements UrlProcessor {
|
||||||
|
|
@ -1,9 +1,10 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon.app;
|
||||||
|
|
||||||
import android.support.annotation.IntDef;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.IntDef;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
public abstract class Views {
|
public abstract class Views {
|
||||||
|
|
@ -1,15 +1,16 @@
|
|||||||
package ru.noties.markwon.gif;
|
package io.noties.markwon.app.gif;
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import io.noties.markwon.image.AsyncDrawable;
|
||||||
|
import io.noties.markwon.image.AsyncDrawableLoader;
|
||||||
|
import io.noties.markwon.image.ImageSize;
|
||||||
|
import io.noties.markwon.image.ImageSizeResolver;
|
||||||
import pl.droidsonroids.gif.GifDrawable;
|
import pl.droidsonroids.gif.GifDrawable;
|
||||||
import ru.noties.markwon.image.AsyncDrawableLoader;
|
|
||||||
import ru.noties.markwon.image.ImageSize;
|
|
||||||
import ru.noties.markwon.image.ImageSizeResolver;
|
|
||||||
import ru.noties.markwon.image.AsyncDrawable;
|
|
||||||
|
|
||||||
public class GifAwareAsyncDrawable extends AsyncDrawable {
|
public class GifAwareAsyncDrawable extends AsyncDrawable {
|
||||||
|
|
@ -1,21 +1,20 @@
|
|||||||
package ru.noties.markwon.gif;
|
package io.noties.markwon.app.gif;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.commonmark.node.Image;
|
import org.commonmark.node.Image;
|
||||||
|
|
||||||
import ru.noties.markwon.AbstractMarkwonPlugin;
|
import io.noties.markwon.AbstractMarkwonPlugin;
|
||||||
import ru.noties.markwon.MarkwonConfiguration;
|
import io.noties.markwon.MarkwonConfiguration;
|
||||||
import ru.noties.markwon.MarkwonSpansFactory;
|
import io.noties.markwon.MarkwonSpansFactory;
|
||||||
import ru.noties.markwon.R;
|
import io.noties.markwon.RenderProps;
|
||||||
import ru.noties.markwon.RenderProps;
|
import io.noties.markwon.SpanFactory;
|
||||||
import ru.noties.markwon.SpanFactory;
|
import io.noties.markwon.app.R;
|
||||||
import ru.noties.markwon.image.AsyncDrawableSpan;
|
import io.noties.markwon.image.AsyncDrawableSpan;
|
||||||
import ru.noties.markwon.image.ImageProps;
|
import io.noties.markwon.image.ImageProps;
|
||||||
import ru.noties.markwon.image.ImagesPlugin;
|
|
||||||
import ru.noties.markwon.priority.Priority;
|
|
||||||
|
|
||||||
public class GifAwarePlugin extends AbstractMarkwonPlugin {
|
public class GifAwarePlugin extends AbstractMarkwonPlugin {
|
||||||
|
|
||||||
@ -59,12 +58,6 @@ public class GifAwarePlugin extends AbstractMarkwonPlugin {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Priority priority() {
|
|
||||||
return Priority.after(ImagesPlugin.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterSetText(@NonNull TextView textView) {
|
public void afterSetText(@NonNull TextView textView) {
|
||||||
processor.process(textView);
|
processor.process(textView);
|
@ -1,4 +1,4 @@
|
|||||||
package ru.noties.markwon.gif;
|
package io.noties.markwon.app.gif;
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.ColorFilter;
|
import android.graphics.ColorFilter;
|
||||||
@ -6,9 +6,10 @@ import android.graphics.Paint;
|
|||||||
import android.graphics.PixelFormat;
|
import android.graphics.PixelFormat;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.support.annotation.ColorInt;
|
|
||||||
import android.support.annotation.NonNull;
|
import androidx.annotation.ColorInt;
|
||||||
import android.support.annotation.Nullable;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
public class GifPlaceholder extends Drawable {
|
public class GifPlaceholder extends Drawable {
|
||||||
|
|
@ -1,15 +1,16 @@
|
|||||||
package ru.noties.markwon.gif;
|
package io.noties.markwon.app.gif;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.style.ClickableSpan;
|
import android.text.style.ClickableSpan;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import io.noties.markwon.image.AsyncDrawableSpan;
|
||||||
import pl.droidsonroids.gif.GifDrawable;
|
import pl.droidsonroids.gif.GifDrawable;
|
||||||
import ru.noties.markwon.image.AsyncDrawableSpan;
|
|
||||||
|
|
||||||
public abstract class GifProcessor {
|
public abstract class GifProcessor {
|
||||||
|
|
@ -8,11 +8,11 @@
|
|||||||
android:id="@+id/scroll_view"
|
android:id="@+id/scroll_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:padding="16dip"
|
android:layout_marginTop="?android:attr/actionBarSize"
|
||||||
android:clipToPadding="false"
|
|
||||||
android:clipChildren="false"
|
android:clipChildren="false"
|
||||||
android:scrollbarStyle="outsideOverlay"
|
android:clipToPadding="false"
|
||||||
android:layout_marginTop="?android:attr/actionBarSize">
|
android:padding="16dip"
|
||||||
|
android:scrollbarStyle="outsideOverlay">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/text"
|
android:id="@+id/text"
|
||||||
@ -20,7 +20,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:lineSpacingExtra="2dip"
|
android:lineSpacingExtra="2dip"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
tools:context="ru.noties.markwon.MainActivity"
|
|
||||||
tools:text="yo\nman" />
|
tools:text="yo\nman" />
|
||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
28
build.gradle
28
build.gradle
@ -4,8 +4,8 @@ buildscript {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.3.2'
|
classpath 'com.android.tools.build:gradle:3.4.1'
|
||||||
classpath 'com.github.ben-manes:gradle-versions-plugin:0.20.0'
|
classpath 'com.github.ben-manes:gradle-versions-plugin:0.21.0'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,8 +29,8 @@ task clean(type: Delete) {
|
|||||||
delete rootProject.buildDir
|
delete rootProject.buildDir
|
||||||
}
|
}
|
||||||
|
|
||||||
task wrapper(type: Wrapper) {
|
wrapper {
|
||||||
gradleVersion '4.10.2'
|
gradleVersion '5.1.1'
|
||||||
distributionType 'all'
|
distributionType 'all'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,29 +53,29 @@ ext {
|
|||||||
'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 = '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",
|
'x-annotations' : 'androidx.annotation:annotation:1.1.0',
|
||||||
'support-app-compat' : "com.android.support:appcompat-v7:$supportVersion",
|
'x-recycler-view' : 'androidx.recyclerview:recyclerview:1.0.0',
|
||||||
'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.4',
|
||||||
'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',
|
'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' : 'io.noties:prism4j:2.0.0',
|
||||||
'debug' : 'ru.noties:debug:3.0.0@jar',
|
'debug' : 'io.noties:debug:5.0.0@jar',
|
||||||
'adapt' : 'ru.noties:adapt:1.1.0',
|
'adapt' : 'io.noties:adapt:2.0.0',
|
||||||
'dagger' : "com.google.dagger:dagger:$daggerVersion"
|
'dagger' : "com.google.dagger:dagger:$daggerVersion",
|
||||||
|
'picasso' : 'com.squareup.picasso:picasso:2.71828',
|
||||||
|
'glide' : 'com.github.bumptech.glide:glide:4.9.0'
|
||||||
]
|
]
|
||||||
|
|
||||||
deps['annotationProcessor'] = [
|
deps['annotationProcessor'] = [
|
||||||
'prism4j-bundler': 'ru.noties:prism4j-bundler:1.1.0',
|
'prism4j-bundler': 'io.noties:prism4j-bundler:2.0.0',
|
||||||
'dagger-compiler': "com.google.dagger:dagger-compiler:$daggerVersion"
|
'dagger-compiler': "com.google.dagger:dagger-compiler:$daggerVersion"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
|
|
||||||
// this is a generated file, do not modify. To update it run 'collectArtifacts.js' script
|
// this is a generated file, do not modify. To update it run 'collectArtifacts.js' script
|
||||||
const artifacts = [{"id":"core","name":"Core","group":"ru.noties.markwon","description":"Core Markwon artifact that includes basic markdown parsing and rendering"},{"id":"ext-latex","name":"LaTeX","group":"ru.noties.markwon","description":"Extension to add LaTeX formulas to Markwon markdown"},{"id":"ext-strikethrough","name":"Strikethrough","group":"ru.noties.markwon","description":"Extension to add strikethrough markup to Markwon markdown"},{"id":"ext-tables","name":"Tables","group":"ru.noties.markwon","description":"Extension to add tables markup (GFM) to Markwon markdown"},{"id":"ext-tasklist","name":"Task List","group":"ru.noties.markwon","description":"Extension to add task lists (GFM) to Markwon markdown"},{"id":"html","name":"HTML","group":"ru.noties.markwon","description":"Provides HTML parsing functionality"},{"id":"image-gif","name":"Image GIF","group":"ru.noties.markwon","description":"Adds GIF media support to Markwon markdown"},{"id":"image-okhttp","name":"Image OkHttp","group":"ru.noties.markwon","description":"Adds OkHttp client to retrieve images data from network"},{"id":"image-svg","name":"Image SVG","group":"ru.noties.markwon","description":"Adds SVG media support to Markwon markdown"},{"id":"recycler","name":"Recycler","group":"ru.noties.markwon","description":"Provides RecyclerView.Adapter to display Markwon markdown"},{"id":"recycler-table","name":"Recycler Table","group":"ru.noties.markwon","description":"Provides MarkwonAdapter.Entry to render TableBlocks inside Android-native TableLayout widget"},{"id":"syntax-highlight","name":"Syntax Highlight","group":"ru.noties.markwon","description":"Add syntax highlight to Markwon markdown via Prism4j library"}];
|
const artifacts = [{"id":"core","name":"Core","group":"io.noties.markwon","description":"Core Markwon artifact that includes basic markdown parsing and rendering"},{"id":"ext-latex","name":"LaTeX","group":"io.noties.markwon","description":"Extension to add LaTeX formulas to Markwon markdown"},{"id":"ext-strikethrough","name":"Strikethrough","group":"io.noties.markwon","description":"Extension to add strikethrough markup to Markwon markdown"},{"id":"ext-tables","name":"Tables","group":"io.noties.markwon","description":"Extension to add tables markup (GFM) to Markwon markdown"},{"id":"ext-tasklist","name":"Task List","group":"io.noties.markwon","description":"Extension to add task lists (GFM) to Markwon markdown"},{"id":"html","name":"HTML","group":"io.noties.markwon","description":"Provides HTML parsing functionality"},{"id":"image","name":"Image","group":"io.noties.markwon","description":"Markwon image loading module (with optional GIF and SVG support)"},{"id":"image-glide","name":"Image Glide","group":"io.noties.markwon","description":"Markwon image loading module (based on Glide library)"},{"id":"image-picasso","name":"Image Picasso","group":"io.noties.markwon","description":"Markwon image loading module (based on Picasso library)"},{"id":"linkify","name":"Linkify","group":"io.noties.markwon","description":"Markwon plugin to linkify text (based on Android Linkify)"},{"id":"recycler","name":"Recycler","group":"io.noties.markwon","description":"Provides RecyclerView.Adapter to display Markwon markdown"},{"id":"recycler-table","name":"Recycler Table","group":"io.noties.markwon","description":"Provides MarkwonAdapter.Entry to render TableBlocks inside Android-native TableLayout widget"},{"id":"simple-ext","name":"Simple Extension","group":"io.noties.markwon","description":"Custom extension based on simple delimiter usage"},{"id":"syntax-highlight","name":"Syntax Highlight","group":"io.noties.markwon","description":"Add syntax highlight to Markwon markdown via Prism4j library"}];
|
||||||
export { artifacts };
|
export { artifacts };
|
||||||
|
4
docs/.vuepress/.artifacts.v3.js
Normal file
4
docs/.vuepress/.artifacts.v3.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 };
|
@ -29,7 +29,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { artifacts } from "../.artifacts.js";
|
import { artifacts } from "../.artifacts.v3.js";
|
||||||
|
|
||||||
if (!artifacts) {
|
if (!artifacts) {
|
||||||
throw "Artifacts not found. Use `collectArtifacts.js` script to obtain artifacts metadata.";
|
throw "Artifacts not found. Use `collectArtifacts.js` script to obtain artifacts metadata.";
|
||||||
|
105
docs/.vuepress/components/ArtifactPicker4.vue
Normal file
105
docs/.vuepress/components/ArtifactPicker4.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>
|
57
docs/.vuepress/components/AwesomeGroup.vue
Normal file
57
docs/.vuepress/components/AwesomeGroup.vue
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<template>
|
||||||
|
<div class="awesome-group">
|
||||||
|
<div v-for="app in apps" class="awesome-app">
|
||||||
|
<a :href="app.link" class="awesome-app-name" target="_blank" rel="noopener noreferrer">{{app.name}} <OutboundLink/></a>
|
||||||
|
<img class="awesome-app-image" :src="app.image">
|
||||||
|
<span v-if="app.description" class="awesome-app-description">{{ app.description }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import Link from './Link.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "AwesomeGroup",
|
||||||
|
props: ["apps"],
|
||||||
|
components: {
|
||||||
|
Link
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.awesome-group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.awesome-app {
|
||||||
|
padding: 1em;
|
||||||
|
background-color: #fff;
|
||||||
|
margin: 0.25em;
|
||||||
|
border-radius: 0.25em;
|
||||||
|
box-shadow: 0 0 0.1em 0.1em #eee;
|
||||||
|
max-width: 30%;
|
||||||
|
min-width: 100px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.awesome-app-name {
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
.awesome-app-image {
|
||||||
|
width: 96px;
|
||||||
|
height: 96px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.awesome-app-description {
|
||||||
|
margin-top: 1em;
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
24
docs/.vuepress/components/MavenBadge4.vue
Normal file
24
docs/.vuepress/components/MavenBadge4.vue
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<template>
|
||||||
|
<a :href="mavenSearchUrl()"><img :src="shieldImgageUrl()" :alt="displayLabel"></a>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'MavenBadge',
|
||||||
|
props: ['artifact', 'label'],
|
||||||
|
methods: {
|
||||||
|
mavenSearchUrl: function() {
|
||||||
|
return `http://search.maven.org/#search|ga|1|g%3A%22io.noties.markwon%22%20AND%20a%3A%22${this.artifact}%22`;
|
||||||
|
},
|
||||||
|
shieldImgageUrl: function() {
|
||||||
|
return `https://img.shields.io/maven-central/v/io.noties.markwon/${this.artifact}.svg?label=${this.displayLabel}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
displayLabel() {
|
||||||
|
return this.label || this.artifact;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -12,16 +12,16 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
nav: [
|
nav: [
|
||||||
{ text: 'Install', link: '/docs/v3/install.md' },
|
{ text: 'Install', link: '/docs/v4/install.md' },
|
||||||
{ text: 'Changelog', link: '/CHANGELOG.md' },
|
|
||||||
{
|
{
|
||||||
text: 'API Version',
|
text: 'API Version',
|
||||||
items: [
|
items: [
|
||||||
{ text: 'Current (3.x.x)', link: '/' },
|
{ text: 'Current (4.x.x)', link: '/' },
|
||||||
|
{ text: 'Legacy (3.x.x)', link: '/docs/v3/install.md' },
|
||||||
{ text: 'Legacy (2.x.x)', link: '/docs/v2/' }
|
{ text: 'Legacy (2.x.x)', link: '/docs/v2/' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ text: 'Sandbox', link: '/sandbox.md' },
|
{ text: 'Changelog', link: 'https://github.com/noties/Markwon/blob/master/CHANGELOG.md' },
|
||||||
{ text: 'Github', link: 'https://github.com/noties/Markwon' }
|
{ text: 'Github', link: 'https://github.com/noties/Markwon' }
|
||||||
],
|
],
|
||||||
sidebar: {
|
sidebar: {
|
||||||
@ -36,8 +36,8 @@ module.exports = {
|
|||||||
'/docs/v2/html.md',
|
'/docs/v2/html.md',
|
||||||
'/docs/v2/view.md'
|
'/docs/v2/view.md'
|
||||||
],
|
],
|
||||||
'/': [
|
'/docs/v3': [
|
||||||
'',
|
'/docs/v3/install.md',
|
||||||
{
|
{
|
||||||
title: 'Core',
|
title: 'Core',
|
||||||
collapsable: false,
|
collapsable: false,
|
||||||
@ -67,8 +67,41 @@ module.exports = {
|
|||||||
'/docs/v3/recycler-table/',
|
'/docs/v3/recycler-table/',
|
||||||
'/docs/v3/syntax-highlight/',
|
'/docs/v3/syntax-highlight/',
|
||||||
'/docs/v3/migration-2-3.md'
|
'/docs/v3/migration-2-3.md'
|
||||||
|
],
|
||||||
|
'/': [
|
||||||
|
'',
|
||||||
|
{
|
||||||
|
title: 'Core',
|
||||||
|
collapsable: false,
|
||||||
|
children: [
|
||||||
|
'/docs/v4/core/getting-started.md',
|
||||||
|
'/docs/v4/core/plugins.md',
|
||||||
|
'/docs/v4/core/registry.md',
|
||||||
|
'/docs/v4/core/theme.md',
|
||||||
|
'/docs/v4/core/configuration.md',
|
||||||
|
'/docs/v4/core/visitor.md',
|
||||||
|
'/docs/v4/core/spans-factory.md',
|
||||||
|
'/docs/v4/core/core-plugin.md',
|
||||||
|
'/docs/v4/core/movement-method-plugin.md',
|
||||||
|
'/docs/v4/core/render-props.md'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
'/docs/v4/ext-latex/',
|
||||||
|
'/docs/v4/ext-strikethrough/',
|
||||||
|
'/docs/v4/ext-tables/',
|
||||||
|
'/docs/v4/ext-tasklist/',
|
||||||
|
'/docs/v4/html/',
|
||||||
|
'/docs/v4/image/',
|
||||||
|
'/docs/v4/image-glide/',
|
||||||
|
'/docs/v4/image-picasso/',
|
||||||
|
'/docs/v4/linkify/',
|
||||||
|
'/docs/v4/recycler/',
|
||||||
|
'/docs/v4/recycler-table/',
|
||||||
|
'/docs/v4/simple-ext/',
|
||||||
|
'/docs/v4/syntax-highlight/',
|
||||||
|
'/docs/v4/recipes.md'
|
||||||
|
],
|
||||||
|
},
|
||||||
sidebarDepth: 2,
|
sidebarDepth: 2,
|
||||||
lastUpdated: true
|
lastUpdated: true
|
||||||
}
|
}
|
||||||
|
@ -1,132 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
# 3.0.1
|
|
||||||
* Add `AsyncDrawableLoader.Builder#implementation` method (<GithubIssue id="109" />)
|
|
||||||
* AsyncDrawable allow placeholder to have independent size (<GithubIssue id="115" />)
|
|
||||||
* `addFactory` method for MarkwonSpansFactory
|
|
||||||
* Add optional spans for list blocks (bullet and ordered)
|
|
||||||
* AsyncDrawable placeholder bounds fix
|
|
||||||
* SpannableBuilder setSpans allow array of arrays
|
|
||||||
* Add `requireFactory` method to MarkwonSpansFactory
|
|
||||||
* Add DrawableUtils
|
|
||||||
|
|
||||||
## 3.0.0
|
|
||||||
* Plugins, plugins, plugins
|
|
||||||
* Split basic functionality blocks into standalone modules
|
|
||||||
* Maven artifacts group changed to `ru.noties.markwon` (previously had been `ru.noties`)
|
|
||||||
* removed `markwon`, `markwon-image-loader`, `markwon-html-pareser-api`, `markwon-html-parser-impl`, `markwon-view` modules
|
|
||||||
* new module system: `core`, `ext-latex`, `ext-strikethrough`, `ext-tables`, `ext-tasklist`, `html`, `image-gif`, `image-okhttp`, `image-svg`, `recycler`, `recycler-table`, `syntax-highlight`
|
|
||||||
* Add BufferType option for Markwon configuration
|
|
||||||
* Fix typo in AsyncDrawable waitingForDimensions
|
|
||||||
* New tests format
|
|
||||||
* `Markwon.render` returns `Spanned` instance of generic `CharSequence`
|
|
||||||
* LinkMovementMethod is applied implicitly if not set on a TextView explicitly
|
|
||||||
* Split code and codeBlock spans and factories
|
|
||||||
* Add CustomTypefaceSpan
|
|
||||||
* Add NoCopySpansFactory
|
|
||||||
* Add placeholder to image loading
|
|
||||||
|
|
||||||
Generally speaking there are a lot of changes. Most of them are not backwards-compatible.
|
|
||||||
The main point of this release is the `Plugin` system that allows more fluent configuration
|
|
||||||
and opens the possibility of extending `Markwon` with 3rd party functionality in a simple
|
|
||||||
and intuitive fashion. Please refer to the [documentation web-site](https://noties.github.io/Markwon)
|
|
||||||
that has information on how to start migration.
|
|
||||||
|
|
||||||
The shortest excerpt of this release can be expressed like this:
|
|
||||||
|
|
||||||
```java
|
|
||||||
// previous v2.x.x way
|
|
||||||
Markwon.setMarkdown(textView, "**Hello there!**");
|
|
||||||
```
|
|
||||||
|
|
||||||
```java
|
|
||||||
// 3.x.x
|
|
||||||
Markwon.create(context)
|
|
||||||
.setMarkdown(textView, "**Hello there!**");
|
|
||||||
```
|
|
||||||
|
|
||||||
But there is much more to it, please visit documentation web-site
|
|
||||||
to get the full picture of latest changes.
|
|
||||||
|
|
||||||
## 2.0.1
|
|
||||||
* `SpannableMarkdownVisitor` Rename blockQuoteIndent to blockIndent
|
|
||||||
* Fixed block new lines logic for block quote and paragraph (<GithubIssue id="82" />)
|
|
||||||
* AsyncDrawable fix no dimensions bug (<GithubIssue id="81" />)
|
|
||||||
* Update SpannableTheme to use Px instead of Dimension annotation
|
|
||||||
* Allow TaskListSpan isDone mutation
|
|
||||||
* Updated commonmark-java to 0.12.1
|
|
||||||
* Add OrderedListItemSpan measure utility method (<GithubIssue id="78" />)
|
|
||||||
* Add SpannableBuilder#getSpans method
|
|
||||||
* Fix DataUri scheme handler in image-loader (<GithubIssue id="74" />)
|
|
||||||
* Introduced a "copy" builder for SpannableThem <br>Thanks <GithubUser name="c-b-h" />
|
|
||||||
|
|
||||||
## 2.0.0
|
|
||||||
* Add `html-parser-api` and `html-parser-impl` modules
|
|
||||||
* Add `HtmlEmptyTagReplacement`
|
|
||||||
* Implement Appendable and CharSequence in SpannableBuilder
|
|
||||||
* Renamed library modules to reflect maven artifact names
|
|
||||||
* Rename `markwon-syntax` to `markwon-syntax-highlight`
|
|
||||||
* Add HtmlRenderer asbtraction
|
|
||||||
* Add CssInlineStyleParser
|
|
||||||
* Fix Theme#listItemColor and OL
|
|
||||||
* Fix task list block parser to revert parsing state when line is not matching
|
|
||||||
* Defined test format files
|
|
||||||
* image-loader add datauri parser
|
|
||||||
* image-loader add support for inline data uri image references
|
|
||||||
* Add travis configuration
|
|
||||||
* Fix image with width greater than canvas scaled
|
|
||||||
* Fix blockquote span
|
|
||||||
* Dealing with white spaces at the end of a document
|
|
||||||
* image-loader add SchemeHandler abstraction
|
|
||||||
* Add sample-latex-math module
|
|
||||||
|
|
||||||
## v1.1.1
|
|
||||||
* Fix OrderedListItemSpan text position (baseline) (<GithubIssue id="55" />)
|
|
||||||
* Add softBreakAddsNewLine option for SpannableConfiguration (<GithubIssue id="54" />)
|
|
||||||
* Paragraph text can now explicitly be spanned (<GithubPull id="58" />)<br>Thanks to <GithubUser name="c-b-h" />
|
|
||||||
* Fix table border color if odd background is specified (<GithubIssue id="56" />)
|
|
||||||
* Add table customizations (even and header rows)
|
|
||||||
|
|
||||||
## v1.1.0
|
|
||||||
* Update commonmark to 0.11.0 and android-gif to 1.2.14
|
|
||||||
* Add syntax highlight functionality (`library-syntax` module and `markwon-syntax` artifact)
|
|
||||||
* Add headingTypeface, headingTextSizes to SpannableTheme<br>Thanks to <GithubUser name="edenman" />
|
|
||||||
* Introduce `MediaDecoder` abstraction to `image-loader` module
|
|
||||||
* Introduce `SpannableFactory`<br>Thanks for idea to <GithubUser name="c-b-h" />
|
|
||||||
* Update sample application to use syntax-highlight
|
|
||||||
* Update sample application to use clickable placeholder for GIF media
|
|
||||||
|
|
||||||
## v1.0.6
|
|
||||||
* Fix bullet list item size (depend on text size and not top-bottom arguments)
|
|
||||||
* Add ability to specify MovementMethod when applying markdown to a TextView
|
|
||||||
* Markdown images size is also resolved via ImageSizeResolver
|
|
||||||
* Moved `ImageSize`, `ImageSizeResolver` and `ImageSizeResolverDef`
|
|
||||||
to `ru.noties.markwon.renderer` package (one level up, previously `ru.noties.markwon.renderer.html`)
|
|
||||||
|
|
||||||
## v1.0.5
|
|
||||||
* Change LinkSpan to extend URLSpan. Allow default linkColor (if not set explicitly)
|
|
||||||
* Fit an image without dimensions to canvas width (and keep ratio)
|
|
||||||
* Add support for separate color for code blocks (<GithubPull id="37" />)<br>Thanks to <GithubUser name="Arcnor" />
|
|
||||||
|
|
||||||
## v1.0.4
|
|
||||||
* Fixes <GithubIssue id="28"/> (tables are not rendered when at the end of the markdown)
|
|
||||||
* Adds support for `indented code blocks`<br>Thanks to <GithubUser name="dlew"/>
|
|
||||||
|
|
||||||
## v1.0.3
|
|
||||||
* Fixed ordered lists (when number width is greater than block margin)
|
|
||||||
|
|
||||||
## v1.0.2
|
|
||||||
* Fixed additional white spaces at the end of parsed markdown
|
|
||||||
* Fixed headings with no underline (levels 1 & 2)
|
|
||||||
* Tables can have no borders
|
|
||||||
|
|
||||||
## v1.0.1
|
|
||||||
* Support for task-lists (<GithubIssue id="2" />)
|
|
||||||
* Spans now are applied in reverse order (<GithubIssue id="5" /> <GithubIssue id="10" />)
|
|
||||||
* Added `SpannableBuilder` to follow the reverse order of spans
|
|
||||||
* Updated `commonmark-java` to `0.10.0`
|
|
||||||
* Fixes <GithubIssue id="1" />
|
|
||||||
|
|
||||||
## v1.0.0
|
|
||||||
|
|
||||||
Initial release
|
|
@ -5,7 +5,7 @@ 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>
|
||||||
[](http://search.maven.org/#search|ga|1|g%3A%22ru.noties.markwon%22%20)
|
[](http://search.maven.org/#search|ga|1|g%3A%22io.noties.markwon%22%20)
|
||||||
[](https://travis-ci.org/noties/Markwon)
|
[](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
|
||||||
@ -27,17 +27,17 @@ listed in <Link name="commonmark-spec" /> are supported (including support for *
|
|||||||
* Strong emphasis (`**`, `__`)
|
* Strong emphasis (`**`, `__`)
|
||||||
* Headers (`#{1,6}`)
|
* Headers (`#{1,6}`)
|
||||||
* Links (`[]()` && `[][]`)
|
* Links (`[]()` && `[][]`)
|
||||||
* [Images](/docs/v3/core/images.md)
|
* [Images](/docs/v4/image/)
|
||||||
* 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
|
||||||
* [Strike-through](/docs/v3/ext-strikethrough/) (`~~`)
|
* [Strike-through](/docs/v4/ext-strikethrough/) (`~~`)
|
||||||
* [Tables](/docs/v3/ext-tables/) (*with limitations*)
|
* [Tables](/docs/v4/ext-tables/) (*with limitations*)
|
||||||
* [Syntax highlight](/docs/v3/syntax-highlight/)
|
* [Syntax highlight](/docs/v4/syntax-highlight/)
|
||||||
* [LaTeX](/docs/v3/ext-latex/) formulas
|
* [LaTeX](/docs/v4/ext-latex/) formulas
|
||||||
* [HTML](/docs/v3/html/)
|
* [HTML](/docs/v4/html/)
|
||||||
* Emphasis (`<i>`, `<em>`, `<cite>`, `<dfn>`)
|
* Emphasis (`<i>`, `<em>`, `<cite>`, `<dfn>`)
|
||||||
* Strong emphasis (`<b>`, `<strong>`)
|
* Strong emphasis (`<b>`, `<strong>`)
|
||||||
* SuperScript (`<sup>`)
|
* SuperScript (`<sup>`)
|
||||||
@ -49,9 +49,9 @@ listed in <Link name="commonmark-spec" /> are supported (including support for *
|
|||||||
* Images (`img` will require configured image loader)
|
* Images (`img` will require configured image loader)
|
||||||
* 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/XML tag, but it will require to create a special `TagHandler`,
|
||||||
more information can be found in [HTML section](/docs/v3/core/html-renderer.md)
|
more information can be found in [HTML section](/docs/v4/html/#taghandler)
|
||||||
* [Task lists](/docs/v3/ext-tasklist/):
|
* [Task lists](/docs/v4/ext-tasklist/):
|
||||||
<ul style="list-style-type: none; margin: 0; padding: 0;">
|
<ul style="list-style-type: none; margin: 0; padding: 0;">
|
||||||
<li><input type="checkbox" disabled>Not <i>done</i></li>
|
<li><input type="checkbox" disabled>Not <i>done</i></li>
|
||||||
<li><input type="checkbox" disabled checked><strong>Done</strong> with <code>X</code></li>
|
<li><input type="checkbox" disabled checked><strong>Done</strong> with <code>X</code></li>
|
||||||
@ -82,6 +82,9 @@ and 2 themes included: Light & Dark. It can be downloaded from [releases](ht
|
|||||||
* [FairNote](https://play.google.com/store/apps/details?id=com.rgiskard.fairnote) - simple and intuitive notepad app. It gives you speed and efficiency when you write notes, to-do lists, e-mails, or jot down quick ideas.
|
* [FairNote](https://play.google.com/store/apps/details?id=com.rgiskard.fairnote) - simple and intuitive notepad app. It gives you speed and efficiency when you write notes, to-do lists, e-mails, or jot down quick ideas.
|
||||||
* [Boxcryptor](https://www.boxcryptor.com) - A software that adds AES-256 and RSA encryption to Dropbox, Google Drive, OneDrive and many other clouds.
|
* [Boxcryptor](https://www.boxcryptor.com) - A software that adds AES-256 and RSA encryption to Dropbox, Google Drive, OneDrive and many other clouds.
|
||||||
|
|
||||||
|
<AwesomeGroup :apps="[
|
||||||
|
{name: 'Cinopsys: Movies and Shows', image: 'http://drive.google.com/uc?export=view&id=1rD0HLd8tDUCe8QcVEG_iGvsJbFyozRhC', link: 'https://play.google.com/store/apps/details?id=com.cinopsys.movieshows'}
|
||||||
|
]" />
|
||||||
|
|
||||||
<u>Extension/plugins</u>:
|
<u>Extension/plugins</u>:
|
||||||
|
|
||||||
|
@ -7,6 +7,8 @@ title: 'Overview'
|
|||||||
<br><br>
|
<br><br>
|
||||||
<MavenBadges2xx/>
|
<MavenBadges2xx/>
|
||||||
|
|
||||||
|
<LegacyWarning />
|
||||||
|
|
||||||
**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
|
||||||
and renders result as _Android-native_ Spannables. **No HTML** is involved
|
and renders result as _Android-native_ Spannables. **No HTML** is involved
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# Configuration
|
# Configuration
|
||||||
|
|
||||||
|
<LegacyWarning />
|
||||||
|
|
||||||
`SpannableConfiguration` is the core component that controls how markdown is parsed and rendered.
|
`SpannableConfiguration` is the core component that controls how markdown is parsed and rendered.
|
||||||
It can be obtained via factory methods:
|
It can be obtained via factory methods:
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# Factory <Badge text="1.1.0" />
|
# Factory <Badge text="1.1.0" />
|
||||||
|
|
||||||
|
<LegacyWarning />
|
||||||
|
|
||||||
`SpannableFactory` is used to create Span implementations.
|
`SpannableFactory` is used to create Span implementations.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# Getting started
|
# Getting started
|
||||||
|
|
||||||
|
<LegacyWarning />
|
||||||
|
|
||||||
## 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:
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# HTML <Badge text="2.0.0" />
|
# HTML <Badge text="2.0.0" />
|
||||||
|
|
||||||
|
<LegacyWarning />
|
||||||
|
|
||||||
Starting with version `2.0.0` `Markwon` brings the whole HTML parsing/rendering
|
Starting with version `2.0.0` `Markwon` brings the whole HTML parsing/rendering
|
||||||
stack _on-site_. The main reason for this are _special_ definitions of HTML nodes
|
stack _on-site_. The main reason for this are _special_ definitions of HTML nodes
|
||||||
by <Link name="commonmark-spec" />. More specifically: <Link name="commonmark-spec#inline" displayName="inline" />
|
by <Link name="commonmark-spec" />. More specifically: <Link name="commonmark-spec#inline" displayName="inline" />
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# Images
|
# Images
|
||||||
|
|
||||||
|
<LegacyWarning />
|
||||||
|
|
||||||
By default `Markwon` doesn't handle images. Although `AsyncDrawable.Loader` is
|
By default `Markwon` doesn't handle images. Although `AsyncDrawable.Loader` is
|
||||||
defined in main artifact, it does not provide implementation.
|
defined in main artifact, it does not provide implementation.
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
|
<LegacyWarning />
|
||||||
|
|
||||||
<MavenBadges2xx />
|
<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
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# Syntax highlight
|
# Syntax highlight
|
||||||
|
|
||||||
|
<LegacyWarning />
|
||||||
|
|
||||||
<MavenBadge2xx 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,5 +1,7 @@
|
|||||||
# Theme
|
# Theme
|
||||||
|
|
||||||
|
<LegacyWarning />
|
||||||
|
|
||||||
Here is the list of properties that can be configured via `SpannableTheme`. If you wish to control what
|
Here is the list of properties that can be configured via `SpannableTheme`. If you wish to control what
|
||||||
is out of this list, you can use [SpannableFactory](/docs/v2/factory.md)
|
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.
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# MarkwonView
|
# MarkwonView
|
||||||
|
|
||||||
|
<LegacyWarning />
|
||||||
|
|
||||||
<MavenBadge2xx 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:
|
||||||
|
@ -56,7 +56,7 @@ myTableWidget.setTable(table);
|
|||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
To take advantage of this functionality and render tables without limitations (including
|
To take advantage of this functionality and render tables without limitations (including
|
||||||
horizontally scrollable layout when its contents exceed screen width), refer to [recycler-table](/docs/v3/recycler-table/)
|
horizontally scrollable layout when its contents exceed screen width), refer to [recycler-table](/docs/v4/recycler-table/)
|
||||||
module documentation that adds support for rendering `TableBlock` markdown node inside Android-native `TableLayout` widget.
|
module documentation that adds support for rendering `TableBlock` markdown node inside Android-native `TableLayout` widget.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
168
docs/docs/v4/core/configuration.md
Normal file
168
docs/docs/v4/core/configuration.md
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
# Configuration
|
||||||
|
|
||||||
|
`MarkwonConfiguration` class holds common Markwon functionality.
|
||||||
|
These are _configurable_ properties:
|
||||||
|
* `AsyncDrawableLoader` (back here since <Badge text="4.0.0" />)
|
||||||
|
* `SyntaxHighlight`
|
||||||
|
* `LinkResolver` (since <Badge text="4.0.0" />, before — `LinkSpan.Resolver`)
|
||||||
|
* `UrlProcessor`
|
||||||
|
* `ImageSizeResolver`
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
Additionally `MarkwonConfiguration` holds:
|
||||||
|
* `MarkwonTheme`
|
||||||
|
* `MarkwonSpansFactory`
|
||||||
|
|
||||||
|
Please note that these values can be retrieved from `MarkwonConfiguration`
|
||||||
|
instance, but their _configuration_ must be done by a `Plugin` by overriding
|
||||||
|
one of the methods:
|
||||||
|
* `Plugin#configureTheme`
|
||||||
|
* `Plugin#configureSpansFactory`
|
||||||
|
:::
|
||||||
|
|
||||||
|
## AsyncDrawableLoader
|
||||||
|
|
||||||
|
Allows loading and displaying of images in markdown. Please note that if one is not specified
|
||||||
|
directly (or via plugin) no images will be displayed.
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
|
||||||
|
builder.asyncDrawableLoader(AsyncDrawableLoader.noOp());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
Currently `Markwon` provides 3 implementations for loading images:
|
||||||
|
* [markwon implementation](/docs/v4/image/) with SVG, GIF, data uri and android_assets support
|
||||||
|
* [based on Picasso](/docs/v4/image-picasso/)
|
||||||
|
* [based on Glide](/docs/v4/image-glide/)
|
||||||
|
|
||||||
|
## SyntaxHighlight
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(this)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
|
||||||
|
builder.syntaxHighlight(new SyntaxHighlightNoOp());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
Use [syntax-highlight](/docs/v4/syntax-highlight/) to add syntax highlighting
|
||||||
|
to your application
|
||||||
|
:::
|
||||||
|
|
||||||
|
## LinkResolver
|
||||||
|
|
||||||
|
React to a link click event. By default `LinkResolverDef` is used,
|
||||||
|
which tries to start an Activity given the `link` argument. If no
|
||||||
|
Activity can handle `link` `LinkResolverDef` silently ignores click event
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(this)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
|
||||||
|
builder.linkResolver(new LinkResolver() {
|
||||||
|
@Override
|
||||||
|
public void resolve(@NonNull View view, @NonNull String link) {
|
||||||
|
// react to link click here
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
Please note that `Markwon` will apply `LinkMovementMethod` to a resulting TextView
|
||||||
|
if there is none registered. if you wish to register own instance of a `MovementMethod`
|
||||||
|
apply it directly to a TextView or use [MovementMethodPlugin](/docs/v4/core/movement-method-plugin.md)
|
||||||
|
:::
|
||||||
|
|
||||||
|
## UrlProcessor
|
||||||
|
|
||||||
|
Process URLs in your markdown (for links and images). If not provided explicitly,
|
||||||
|
default **no-op** implementation will be used, which does not modify URLs (keeping them as-is).
|
||||||
|
|
||||||
|
`Markwon` provides 2 implementations of `UrlProcessor`:
|
||||||
|
* `UrlProcessorRelativeToAbsolute`
|
||||||
|
* `UrlProcessorAndroidAssets`
|
||||||
|
|
||||||
|
### UrlProcessorRelativeToAbsolute
|
||||||
|
|
||||||
|
`UrlProcessorRelativeToAbsolute` can be used to make relative URL absolute. For example if an image is
|
||||||
|
defined like this: `` 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.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## ImageSizeResolver
|
||||||
|
|
||||||
|
`ImageSizeResolver` controls the size of an image to be displayed.
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(this)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
|
||||||
|
builder.imageSizeResolver(new ImageSizeResolver() {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Rect resolveImageSize(@NonNull AsyncDrawable drawable) {
|
||||||
|
final ImageSize imageSize = drawable.getImageSize();
|
||||||
|
return drawable.getResult().getBounds();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
If not provided explicitly, default `ImageSizeResolverDef` implementation will be used.
|
||||||
|
It handles 3 dimension units:
|
||||||
|
* `%` (percent, relative to Canvas width)
|
||||||
|
* `em` (relative to text size)
|
||||||
|
* `px` (absolute size, every dimension that is not `%` or `em` is considered to be _absolute_)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<img width="100%">
|
||||||
|
<img width="2em" height="10px">
|
||||||
|
<img style="{width: 100%; height: 8em;}">
|
||||||
|
```
|
||||||
|
|
||||||
|
`ImageSizeResolverDef` keeps the ratio of original image if one of the dimensions is missing.
|
||||||
|
|
||||||
|
:::warning Height%
|
||||||
|
There is no support for `%` units for `height` dimension. This is due to the fact that
|
||||||
|
height of an TextView in which markdown is displayed is non-stable and changes with time
|
||||||
|
(for example when image is loaded and applied to a TextView it will _increase_ TextView's height),
|
||||||
|
so we will have no point-of-reference from which to _calculate_ image height.
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
`ImageSizeResolverDef` also takes care for an image to **not** exceed
|
||||||
|
canvas width. If an image has greater width than a TextView Canvas, then
|
||||||
|
image will be _scaled-down_ to fit the canvas. Please note that this rule
|
||||||
|
applies only if image has no sizes specified (`ImageSize == null`).
|
||||||
|
:::
|
141
docs/docs/v4/core/core-plugin.md
Normal file
141
docs/docs/v4/core/core-plugin.md
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
# Core plugin <Badge text="3.0.0" />
|
||||||
|
|
||||||
|
Since <Badge text="3.0.0" /> with introduction of _plugins_, Markwon
|
||||||
|
**core** functionality was moved to a dedicated plugin.
|
||||||
|
|
||||||
|
```java
|
||||||
|
CorePlugin.create();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Node visitors
|
||||||
|
|
||||||
|
`CorePlugin` registers these `commonmark-java` node visitors:
|
||||||
|
* `Text`
|
||||||
|
* `StrongEmphasis`
|
||||||
|
* `Emphasis`
|
||||||
|
* `BlockQuote`
|
||||||
|
* `Code`
|
||||||
|
* `Image`
|
||||||
|
* `FencedCodeBlock`
|
||||||
|
* `IndentedCodeBlock`
|
||||||
|
* `BulletList`
|
||||||
|
* `OrderedList`
|
||||||
|
* `ListItem`
|
||||||
|
* `ThematicBreak`
|
||||||
|
* `Heading`
|
||||||
|
* `SoftLineBreak`
|
||||||
|
* `HardLineBreak`
|
||||||
|
* `Paragraph`
|
||||||
|
* `Link`
|
||||||
|
|
||||||
|
## Span factories
|
||||||
|
|
||||||
|
`CorePlugin` adds these `SpanFactory`s:
|
||||||
|
* `StrongEmphasis`
|
||||||
|
* `Emphasis`
|
||||||
|
* `BlockQuote`
|
||||||
|
* `Code`
|
||||||
|
* `FencedCodeBlock`
|
||||||
|
* `IndentedCodeBlock`
|
||||||
|
* `ListItem`
|
||||||
|
* `Heading`
|
||||||
|
* `Link`
|
||||||
|
* `ThematicBreak`
|
||||||
|
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
By default `CorePlugin` does not register a `Paragraph` `SpanFactory` but
|
||||||
|
this can be done in your custom plugin:
|
||||||
|
|
||||||
|
```java
|
||||||
|
Markwon.builder(context)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
|
||||||
|
builder.setFactory(Paragraph.class, (configuration, props) ->
|
||||||
|
new ForegroundColorSpan(Color.RED));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Props
|
||||||
|
These props are exported by `CorePlugin` and can be found in `CoreProps`:
|
||||||
|
* `Prop<ListItemType> LIST_ITEM_TYPE` (BULLET | ORDERED)
|
||||||
|
* `Prop<Integer> BULLET_LIST_ITEM_LEVEL`
|
||||||
|
* `Prop<Integer> ORDERED_LIST_ITEM_NUMBER`
|
||||||
|
* `Prop<Integer> HEADING_LEVEL`
|
||||||
|
* `Prop<String> LINK_DESTINATION`
|
||||||
|
* `Prop<Boolean> PARAGRAPH_IS_IN_TIGHT_LIST`
|
||||||
|
|
||||||
|
:::warning List item type
|
||||||
|
Before <Badge text="3.0.0" /> `Markwon` had 2 distinct lists (bullet and ordered).
|
||||||
|
Since <Badge text="3.0.0" /> a single `SpanFactory` is used, which internally checks
|
||||||
|
for `Prop<ListItemType> LIST_ITEM_TYPE`.
|
||||||
|
Beware of this if you would like to override only one of the list types. This is
|
||||||
|
done to correspond to `commonmark-java` implementation.
|
||||||
|
:::
|
||||||
|
|
||||||
|
More information about props can be found [here](/docs/v4/core/render-props.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
:::tip Soft line break
|
||||||
|
Since <Badge text="3.0.0" /> Markwon core does not give an option to
|
||||||
|
insert a new line when there is a soft line break in markdown. Instead a
|
||||||
|
custom plugin can be used:
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(this)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
|
||||||
|
builder.on(SoftLineBreak.class, (visitor, softLineBreak) ->
|
||||||
|
visitor.forceNewLine());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
Please note that `CorePlugin` will implicitly set a `LinkMovementMethod` on a TextView
|
||||||
|
if one is not present. If you wish to customize a MovementMethod that is used, apply
|
||||||
|
one manually to a TextView (before applying markdown) or use the [MovementMethodPlugin](/docs/v4/core/movement-method-plugin.md)
|
||||||
|
which accepts a MovementMethod as an argument.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## OnTextAddedListener <Badge text="4.0.0"/>
|
||||||
|
|
||||||
|
Since `4.0.0` `CorePlugin` provides ability to receive text-added event. This can
|
||||||
|
be useful in order to process raw text (for example to [linkify](/docs/v4/linkify/) it):
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configure(@NonNull Registry registry) {
|
||||||
|
registry.require(CorePlugin.class, new Action<CorePlugin>() {
|
||||||
|
@Override
|
||||||
|
public void apply(@NonNull CorePlugin corePlugin) {
|
||||||
|
corePlugin.addOnTextAddedListener(new CorePlugin.OnTextAddedListener() {
|
||||||
|
@Override
|
||||||
|
public void onTextAdded(@NonNull MarkwonVisitor visitor, @NonNull String text, int start) {
|
||||||
|
|
||||||
|
// NB text is already added and you are __strongly__ adviced not to
|
||||||
|
// modify visitor here, but only add spans
|
||||||
|
//
|
||||||
|
// this will make all text BLUE
|
||||||
|
visitor.builder().setSpan(
|
||||||
|
new ForegroundColorSpan(Color.BLUE),
|
||||||
|
start,
|
||||||
|
visitor.length()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
52
docs/docs/v4/core/getting-started.md
Normal file
52
docs/docs/v4/core/getting-started.md
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# Getting started
|
||||||
|
|
||||||
|
:::tip Installation
|
||||||
|
Please follow [installation](/docs/v4/install.md) instructions
|
||||||
|
to learn how to add `Markwon` to your project
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Quick one
|
||||||
|
|
||||||
|
This is the most simple way to set markdown to a `TextView` or any of its siblings:
|
||||||
|
|
||||||
|
```java
|
||||||
|
// obtain an instance of Markwon
|
||||||
|
final Markwon markwon = Markwon.create(context);
|
||||||
|
|
||||||
|
// set markdown
|
||||||
|
markwon.setMarkdown(textView, "**Hello there!**");
|
||||||
|
```
|
||||||
|
|
||||||
|
The most simple way to obtain markdown to be applied _somewhere_ else:
|
||||||
|
|
||||||
|
```java
|
||||||
|
// obtain an instance of Markwon
|
||||||
|
final Markwon markwon = Markwon.create(context);
|
||||||
|
|
||||||
|
// parse markdown and create styled text
|
||||||
|
final Spanned markdown = markwon.toMarkdown("**Hello there!**");
|
||||||
|
|
||||||
|
// use it
|
||||||
|
Toast.makeText(context, markdown, Toast.LENGTH_LONG).show();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Longer one
|
||||||
|
|
||||||
|
With explicit `parse` and `render` methods:
|
||||||
|
|
||||||
|
```java
|
||||||
|
// obtain an instance of Markwon
|
||||||
|
final Markwon markwon = Markwon.create(context);
|
||||||
|
|
||||||
|
// parse markdown to commonmark-java Node
|
||||||
|
final Node node = markwon.parse("Are **you** still there?");
|
||||||
|
|
||||||
|
// create styled text from parsed Node
|
||||||
|
final Spanned markdown = markwon.render(node);
|
||||||
|
|
||||||
|
// use it on a TextView
|
||||||
|
markwon.setParsedMarkdown(textView, markdown);
|
||||||
|
|
||||||
|
// or a Toast
|
||||||
|
Toast.makeText(context, markdown, Toast.LENGTH_LONG).show();
|
||||||
|
```
|
17
docs/docs/v4/core/movement-method-plugin.md
Normal file
17
docs/docs/v4/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.
|
||||||
|
:::
|
361
docs/docs/v4/core/plugins.md
Normal file
361
docs/docs/v4/core/plugins.md
Normal file
@ -0,0 +1,361 @@
|
|||||||
|
# Plugins <Badge text="3.0.0" />
|
||||||
|
|
||||||
|
Since <Badge text="3.0.0" /> `MarkwonPlugin` takes the key role in
|
||||||
|
processing and rendering markdown. Even **core** functionaly is abstracted
|
||||||
|
into a `CorePlugin`. So it's still possible to use `Markwon` with a completely
|
||||||
|
own set of plugins.
|
||||||
|
|
||||||
|
To register a plugin `Markwon.Builder` must be used:
|
||||||
|
|
||||||
|
```java
|
||||||
|
Markwon.builder(context)
|
||||||
|
// @since 4.0.0 there is no need to register CorePlugin, as it's registered automatically
|
||||||
|
// .usePlugin(CorePlugin.create())
|
||||||
|
.usePlugin(MyPlugin.create())
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
All the process of transforming _raw_ markdown into a styled text (Spanned)
|
||||||
|
will go through plugins. A plugin can:
|
||||||
|
|
||||||
|
* [configure plugin registry](#registry)
|
||||||
|
* [configure commonmark-java `Parser`](#parser)
|
||||||
|
* [configure `MarkwonTheme`](#markwontheme)
|
||||||
|
* [configure `AsyncDrawableLoader` (used to display images in markdown)](#images)
|
||||||
|
* [configure `MarkwonConfiguration`](#configuration)
|
||||||
|
* [configure `MarkwonVisitor` (extensible commonmark-java Node visitor)](#visitor)
|
||||||
|
* [configure `MarkwonSpansFactory` (factory to hold spans information for each Node)](#spans-factory)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
* [process raw input markdown before parsing it](#process-markdown)
|
||||||
|
* [inspect/modify commonmark-java Node after it's been parsed, but before rendering](#inspect-modify-node)
|
||||||
|
* [inspect commonmark-java Node after it's been rendered](#inspect-node-after-render)
|
||||||
|
* [prepare TextView to display markdown _before_ markdown is applied to a TextView](#prepare-textview)
|
||||||
|
* [post-process TextView _after_ markdown was applied](#textview-after-markdown-applied)
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
if you need to override only few methods of `MarkwonPlugin` (since it is an interface),
|
||||||
|
`AbstractMarkwonPlugin` can be used.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Registry <Badge text="4.0.0" />
|
||||||
|
|
||||||
|
Registry is a special step to pre-configure all registered plugins. It is also
|
||||||
|
used to determine the order of plugins inside `Markwon` instance.
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configure(@NonNull Registry registry) {
|
||||||
|
|
||||||
|
final CorePlugin corePlugin = registry.require(CorePlugin.class);
|
||||||
|
|
||||||
|
// or
|
||||||
|
registry.require(CorePlugin.class, new Action<CorePlugin>() {
|
||||||
|
@Override
|
||||||
|
public void apply(@NonNull CorePlugin corePlugin) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
More information about registry can be found [here](/docs/v4/core/registry.md)
|
||||||
|
|
||||||
|
## Parser
|
||||||
|
|
||||||
|
For example, let's register a new commonmark-java Parser extension:
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureParser(@NonNull Parser.Builder builder) {
|
||||||
|
// no need to call `super.configureParser(builder)`
|
||||||
|
builder.extensions(Collections.singleton(StrikethroughExtension.create()));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
There are no limitations on what to do with commonmark-java Parser. For more info
|
||||||
|
_what_ can be done please refer to <Link name="commonmark-java" displayName="commonmark-java documentation" />.
|
||||||
|
|
||||||
|
## MarkwonTheme
|
||||||
|
|
||||||
|
Starting <Badge text="3.0.0" /> `MarkwonTheme` represents _core_ theme. Aka theme for
|
||||||
|
things core module knows of. For example it doesn't know anything about `strikethrough`
|
||||||
|
or `tables` (as they belong to different modules).
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureTheme(@NonNull MarkwonTheme.Builder builder) {
|
||||||
|
builder
|
||||||
|
.codeTextColor(Color.BLACK)
|
||||||
|
.codeBackgroundColor(Color.GREEN);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
`CorePlugin` has special handling - it will be added automatically
|
||||||
|
when `Markwon.builder(Context)` method is used. If you wish to create
|
||||||
|
Markwon instance _without_ CorePlugin registered -
|
||||||
|
use `Markwon.builderNoCore(Context)` method instead
|
||||||
|
:::
|
||||||
|
|
||||||
|
More information about `MarkwonTheme` can be found [here](/docs/v4/core/theme.md).
|
||||||
|
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
`MarkwonConfiguration` is a set of common tools that are used by different parts
|
||||||
|
of `Markwon`. It allows configurations of these:
|
||||||
|
|
||||||
|
* `AsyncDrawableLoader` (image loading)
|
||||||
|
* `SyntaxHighlight` (highlighting code blocks)
|
||||||
|
* `LinkResolver` (opens links in markdown)
|
||||||
|
* `UrlProcessor` (process URLs in markdown for both links and images)
|
||||||
|
* `ImageSizeResolver` (resolve image sizes, like `fit-to-canvas`, etc)
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
|
||||||
|
builder.linkResolver(new LinkResolverDef());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
More information about `MarkwonConfiguration` can be found [here](/docs/v4/core/configuration.md)
|
||||||
|
|
||||||
|
|
||||||
|
## Visitor
|
||||||
|
|
||||||
|
`MarkwonVisitor` <Badge text="3.0.0" /> is commonmark-java Visitor that allows
|
||||||
|
configuration of how each Node is visited. There is no longer need to create
|
||||||
|
own subclass of Visitor and override required methods (like in `2.x.x` versions).
|
||||||
|
`MarkwonVisitor` also allows registration of Nodes, that `core` module knows
|
||||||
|
nothing about (instead of relying on `visit(CustomNode)` method)).
|
||||||
|
|
||||||
|
For example, let's add `strikethrough` Node visitor:
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
|
||||||
|
// please note that strike-through parser extension must be registered
|
||||||
|
// in order to receive such callback
|
||||||
|
builder
|
||||||
|
.on(Strikethrough.class, new MarkwonVisitor.NodeVisitor<Strikethrough>() {
|
||||||
|
@Override
|
||||||
|
public void visit(@NonNull MarkwonVisitor visitor, @NonNull Strikethrough strikethrough) {
|
||||||
|
final int length = visitor.length();
|
||||||
|
visitor.visitChildren(strikethrough);
|
||||||
|
visitor.setSpansForNodeOptional(strikethrough, length);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
`MarkwonVisitor` also allows _overriding_ already registered nodes. For example,
|
||||||
|
you can disable `Heading` Node rendering:
|
||||||
|
|
||||||
|
```java
|
||||||
|
builder.on(Heading.class, null);
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
More information about `MarkwonVisitor` can be found [here](/docs/v4/core/visitor.md)
|
||||||
|
|
||||||
|
|
||||||
|
## Spans Factory
|
||||||
|
|
||||||
|
`MarkwonSpansFactory` <Badge text="3.0.0" /> is an abstract factory (factory that produces other factories)
|
||||||
|
for spans that `Markwon` uses. It controls what spans to use for certain Nodes.
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
|
||||||
|
// override emphasis factory to make all emphasis nodes underlined
|
||||||
|
builder.setFactory(Emphasis.class, new SpanFactory() {
|
||||||
|
@Override
|
||||||
|
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
|
||||||
|
return new UnderlineSpan();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
`SpanFactory` allows to return an _array_ of spans to apply multiple spans
|
||||||
|
for a Node:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Override
|
||||||
|
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
|
||||||
|
// make underlined and set text color to red
|
||||||
|
return new Object[]{
|
||||||
|
new UnderlineSpan(),
|
||||||
|
new ForegroundColorSpan(Color.RED)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
More information about spans factory can be found [here](/docs/v4/core/spans-factory.md)
|
||||||
|
|
||||||
|
|
||||||
|
## Process markdown
|
||||||
|
|
||||||
|
A plugin can be used to _pre-process_ input markdown (this will be called before _parsing_):
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String processMarkdown(@NonNull String markdown) {
|
||||||
|
return markdown.replaceAll("foo", "bar");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Inspect/modify Node
|
||||||
|
|
||||||
|
A plugin can inspect/modify commonmark-java Node _before_ it's being rendered.
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void beforeRender(@NonNull Node node) {
|
||||||
|
|
||||||
|
// for example inspect it with custom visitor
|
||||||
|
node.accept(new MyVisitor());
|
||||||
|
|
||||||
|
// or modify (you know what you are doing, right?)
|
||||||
|
node.appendChild(new Text("Appended"));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Inspect Node after render
|
||||||
|
|
||||||
|
A plugin can inspect commonmark-java Node after it's been rendered.
|
||||||
|
Modifying Node at this point makes not much sense (it's already been
|
||||||
|
rendered and all modifications won't change anything). But this method can be used,
|
||||||
|
for example, to clean-up some internal state (after rendering). Generally
|
||||||
|
speaking, a plugin must be stateless, but if it cannot, then this method is
|
||||||
|
the best place to clean-up.
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void afterRender(@NonNull Node node, @NonNull MarkwonVisitor visitor) {
|
||||||
|
cleanUp();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Prepare TextView
|
||||||
|
|
||||||
|
A plugin can _prepare_ a TextView before markdown is applied. For example `images`
|
||||||
|
unschedules all previously scheduled `AsyncDrawableSpans` (if any) here. This way
|
||||||
|
when new markdown (and set of Spannables) arrives, previous set won't be kept in
|
||||||
|
memory and could be garbage-collected.
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
|
||||||
|
// clean-up previous
|
||||||
|
AsyncDrawableScheduler.unschedule(textView);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
## TextView after markdown applied
|
||||||
|
|
||||||
|
A plugin will receive a callback _after_ markdown is applied to a TextView.
|
||||||
|
For example `images` uses this callback to schedule new set of Spannables.
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void afterSetText(@NonNull TextView textView) {
|
||||||
|
AsyncDrawableScheduler.schedule(textView);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
Please note that unlike `#beforeSetText`, `#afterSetText` won't receive
|
||||||
|
`Spanned` markdown. This happens because at this point spans must be
|
||||||
|
queried directly from a TextView.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## What happens underneath
|
||||||
|
|
||||||
|
Here is what happens inside `Markwon` when `setMarkdown` method is called:
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.create(context);
|
||||||
|
|
||||||
|
// warning: pseudo-code
|
||||||
|
|
||||||
|
// 0. each plugin will be called to _pre-process_ raw input markdown
|
||||||
|
rawInput = plugins.reduce(rawInput, (input, plugin) -> plugin.processMarkdown(input));
|
||||||
|
|
||||||
|
// 1. after input is processed it's being parsed to a Node
|
||||||
|
node = parser.parse(rawInput);
|
||||||
|
|
||||||
|
// 2. each plugin will be able to inspect or manipulate resulting Node
|
||||||
|
// before rendering
|
||||||
|
plugins.forEach(plugin -> plugin.beforeRender(node));
|
||||||
|
|
||||||
|
// 3. node is being visited by a visitor
|
||||||
|
node.accept(visitor);
|
||||||
|
|
||||||
|
// 4. each plugin will be called after node is being visited (aka rendered)
|
||||||
|
plugins.forEach(plugin -> plugin.afterRender(node, visitor));
|
||||||
|
|
||||||
|
// 5. styled markdown ready at this point
|
||||||
|
final Spanned markdown = visitor.markdown();
|
||||||
|
|
||||||
|
// NB, points 6-8 are applied **only** if markdown is set to a TextView
|
||||||
|
|
||||||
|
// 6. each plugin will be called before styled markdown is applied to a TextView
|
||||||
|
plugins.forEach(plugin -> plugin.beforeSetText(textView, markdown));
|
||||||
|
|
||||||
|
// 7. markdown is applied to a TextView
|
||||||
|
textView.setText(markdown);
|
||||||
|
|
||||||
|
// 8. each plugin will be called after markdown is applied to a TextView
|
||||||
|
plugins.forEach(plugin -> plugin.afterSetText(textView));
|
||||||
|
```
|
97
docs/docs/v4/core/registry.md
Normal file
97
docs/docs/v4/core/registry.md
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
# Registry <Badge text="4.0.0" />
|
||||||
|
|
||||||
|
`Registry` allows to pre-configure other plugins and/or declare a dependency on a plugin,
|
||||||
|
which also will modify internal order of plugins inside a `Markwon` instance.
|
||||||
|
|
||||||
|
For example, you have a configurable plugin:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class MyPlugin extends AbstractMarkwonPlugin {
|
||||||
|
|
||||||
|
private boolean enabled;
|
||||||
|
|
||||||
|
public boolean enabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public MyPlugin enabled(boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
{...}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
and other plugin that needs to access `MyPlugin` or modify/configure it:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class MyOtherPlugin extends AbstractMarkwonPlugin {
|
||||||
|
@Override
|
||||||
|
public void configure(@NonNull Registry registry) {
|
||||||
|
registry.require(MyPlugin.class, new Action<MyPlugin>() {
|
||||||
|
@Override
|
||||||
|
public void apply(@NonNull MyPlugin myPlugin) {
|
||||||
|
myPlugin.enabled(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(new MyOtherPlugin())
|
||||||
|
.usePlugin(new MyPlugin())
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
_Internal_ plugins order (in this case) will be:
|
||||||
|
* `CorePlugin` (added automatically and always the first one)
|
||||||
|
* `MyPlugin` (was required by `MyOtherPlugin`)
|
||||||
|
* `MyOtherPlugin`
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
There is no need to _require_ `CorePlugin` as it will be the first one inside
|
||||||
|
`Markwon` instance.
|
||||||
|
:::
|
||||||
|
|
||||||
|
The order matters if you want to _override_ some plugin. For example, `CoolPlugin`
|
||||||
|
adds a `SpanFactory` for a `Cool` markdown node. Other `NotCoolPlugin` wants to
|
||||||
|
use a different `SpanFactory`, then:
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(CoolPlugin.create())
|
||||||
|
.usePlugin(new NotCoolPlugin() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(@NonNull MarkwonPlugin.Registry registry) {
|
||||||
|
registry.require(CoolPlugin.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
|
||||||
|
builder.setFactory(Cool.class, new NotCoolSpanFactory());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
All `require` calls to the `Registry` will also validate at runtime that
|
||||||
|
_required_ plugins are registered.
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configure(@NonNull Registry registry) {
|
||||||
|
// will throw an exception if `NotPresentPlugin` is not present
|
||||||
|
registry.require(NotPresentPlugin.class);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
75
docs/docs/v4/core/render-props.md
Normal file
75
docs/docs/v4/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
|
103
docs/docs/v4/core/spans-factory.md
Normal file
103
docs/docs/v4/core/spans-factory.md
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
# Spans Factory
|
||||||
|
|
||||||
|
Starting with <Badge text="3.0.0" /> `MarkwonSpansFactory` controls what spans are displayed
|
||||||
|
for markdown nodes.
|
||||||
|
|
||||||
|
```java
|
||||||
|
Markwon.builder(context)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
|
||||||
|
// passing null as second argument will remove previously added
|
||||||
|
// factory for the Link node
|
||||||
|
builder.setFactory(Link.class, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## SpanFactory
|
||||||
|
|
||||||
|
In order to create a _generic_ interface for all possible Nodes, a `SpanFactory`
|
||||||
|
was added:
|
||||||
|
|
||||||
|
```java
|
||||||
|
builder.setFactory(Link.class, new SpanFactory() {
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
All possible arguments are passed via [RenderProps](/docs/v4/core/render-props.md):
|
||||||
|
|
||||||
|
```java
|
||||||
|
builder.setFactory(Link.class, new SpanFactory() {
|
||||||
|
@Override
|
||||||
|
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
|
||||||
|
final String href = CoreProps.LINK_DESTINATION.require(props);
|
||||||
|
return new LinkSpan(configuration.theme(), href, configuration.linkResolver());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
`SpanFactory` allows returning `null` for a certain span (no span will be applied).
|
||||||
|
Or an array of spans (you _can_ go deeper):
|
||||||
|
|
||||||
|
```java
|
||||||
|
builder.setFactory(Link.class, new SpanFactory() {
|
||||||
|
@Override
|
||||||
|
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
|
||||||
|
return new Object[]{
|
||||||
|
new LinkSpan(
|
||||||
|
configuration.theme(),
|
||||||
|
CoreProps.LINK_DESTINATION.require(props),
|
||||||
|
configuration.linkResolver()),
|
||||||
|
new ForegroundColorSpan(Color.RED)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Since <Badge text="3.0.1" /> you can _add_ multiple `SpanFactory` for a single node:
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
|
||||||
|
// this factory will be used _along_ with all other factories for specified node
|
||||||
|
builder.addFactory(Code.class, new SpanFactory() {
|
||||||
|
@Override
|
||||||
|
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
|
||||||
|
return new ForegroundColorSpan(Color.GREEN);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
If you wish to inspect existing factory you can use:
|
||||||
|
* `builder#getFactory()` -> returns registered factory or `null`
|
||||||
|
* `builder#requireFactory()` -> returns registered factory or throws <Badge text="3.0.1" />
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
|
||||||
|
final SpanFactory codeFactory = builder.requireFactory(Code.class);
|
||||||
|
final SpanFactory linkFactory = builder.getFactory(Link.class);
|
||||||
|
if (linkFactory != null) {
|
||||||
|
{...}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
187
docs/docs/v4/core/theme.md
Normal file
187
docs/docs/v4/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/v4/core/visitor.md
Normal file
73
docs/docs/v4/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/v4/core/configuration.md)
|
||||||
|
* `MarkwonVisitor#renderProps` - getter for current [RenderProps](/docs/v4/core/render-props.md)
|
||||||
|
* `MarkwonVisitor#builder` - getter for current `SpannableBuilder`
|
||||||
|
|
||||||
|
It contains also a number of utility functions:
|
||||||
|
* `visitChildren(Node)` - will visit all children of supplied Node
|
||||||
|
* `hasNext(Node)` - utility function to check if supplied Node has a Node after it (useful for white-space management, so there should be no blank new line after last BlockNode)
|
||||||
|
* `ensureNewLine` - will insert a new line at current `SpannableBuilder` position only if current (last) character is not a new-line
|
||||||
|
* `forceNewLine` - will insert a new line character without any condition checking
|
||||||
|
* `length` - helper function to call `visitor.builder().length()`, returns current length of `SpannableBuilder`
|
||||||
|
* `clear` - will clear state for `RenderProps` and `SpannableBuilder`, this is done by `Markwon` automatically after each render call
|
||||||
|
|
||||||
|
And some utility functions to control the spans:
|
||||||
|
* `setSpans(int start, Object spans)` - will apply supplied `spans` on `SpannableBuilder` starting at `start` position and ending at `SpannableBuilder#length`. `spans` can be `null` (no spans will be applied) or an array of spans (each span of this array will be applied)
|
||||||
|
* `setSpansForNodeOptional(N node, int start)` - helper method to set spans for specified `node` (internally obtains `SpanFactory` for that node and uses it to apply spans)
|
||||||
|
* `setSpansForNode(N node, int start)` - almost the same as `setSpansForNodeOptional` but instead of silently ignoring call if none `SpanFactory` is registered, this method will throw an exception.
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Override
|
||||||
|
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
|
||||||
|
builder.on(Heading.class, new MarkwonVisitor.NodeVisitor<Heading>() {
|
||||||
|
@Override
|
||||||
|
public void visit(@NonNull MarkwonVisitor visitor, @NonNull Heading heading) {
|
||||||
|
|
||||||
|
// or just `visitor.length()`
|
||||||
|
final int start = visitor.builder().length();
|
||||||
|
|
||||||
|
visitor.visitChildren(heading);
|
||||||
|
|
||||||
|
// or just `visitor.setSpansForNodeOptional(heading, start)`
|
||||||
|
final SpanFactory factory = visitor.configuration().spansFactory().get(heading.getClass());
|
||||||
|
if (factory != null) {
|
||||||
|
visitor.setSpans(start, factory.getSpans(visitor.configuration(), visitor.renderProps()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visitor.hasNext(heading)) {
|
||||||
|
visitor.ensureNewLine();
|
||||||
|
visitor.forceNewLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
52
docs/docs/v4/ext-latex/README.md
Normal file
52
docs/docs/v4/ext-latex/README.md
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# LaTeX extension
|
||||||
|
|
||||||
|
<MavenBadge4 :artifact="'ext-latex'" />
|
||||||
|
|
||||||
|
This is an extension that will help you display LaTeX formulas in your markdown.
|
||||||
|
Syntax is pretty simple: pre-fix and post-fix your latex with `$$` (double dollar sign).
|
||||||
|
`$$` should be the first characters in a line.
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
$$
|
||||||
|
\\text{A long division \\longdiv{12345}{13}
|
||||||
|
$$
|
||||||
|
```
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
$$\\text{A long division \\longdiv{12345}{13}$$
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
Markwon.builder(context)
|
||||||
|
.use(JLatexMathPlugin.create(textSize))
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
This extension uses [jlatexmath-android](https://github.com/noties/jlatexmath-android) artifact to create LaTeX drawable.
|
||||||
|
|
||||||
|
## Config
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(JLatexMathPlugin.create(textSize, new BuilderConfigure() {
|
||||||
|
@Override
|
||||||
|
public void configureBuilder(@NonNull Builder builder) {
|
||||||
|
builder
|
||||||
|
.align(JLatexMathDrawable.ALIGN_CENTER)
|
||||||
|
.fitCanvas(true)
|
||||||
|
.padding(paddingPx)
|
||||||
|
// @since 4.0.0 - horizontal and vertical padding
|
||||||
|
.padding(paddingHorizontalPx, paddingVerticalPx)
|
||||||
|
// @since 4.0.0 - change to provider
|
||||||
|
.backgroundProvider(() -> new MyDrawable()))
|
||||||
|
// @since 4.0.0 - optional, by default cached-thread-pool will be used
|
||||||
|
.executorService(Executors.newCachedThreadPool());
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
Since <Badge text="4.0.0" /> `JLatexMathPlugin` operates independently of `ImagesPlugin`
|
||||||
|
:::
|
29
docs/docs/v4/ext-strikethrough/README.md
Normal file
29
docs/docs/v4/ext-strikethrough/README.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Strikethrough extension
|
||||||
|
|
||||||
|
<MavenBadge4 :artifact="'ext-strikethrough'" />
|
||||||
|
|
||||||
|
This module adds `strikethrough` functionality to `Markwon` via `StrikethroughPlugin`:
|
||||||
|
|
||||||
|
```java
|
||||||
|
Markwon.builder(context)
|
||||||
|
.usePlugin(StrikethroughPlugin.create())
|
||||||
|
```
|
||||||
|
|
||||||
|
This plugin registers `SpanFactory` for `Strikethrough` node, so it's possible to customize Strikethrough Span that is used in rendering:
|
||||||
|
|
||||||
|
```java
|
||||||
|
Markwon.builder(context)
|
||||||
|
.usePlugin(StrikethroughPlugin.create())
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
|
||||||
|
builder.setFactory(Strikethrough.class, new SpanFactory() {
|
||||||
|
@Override
|
||||||
|
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
|
||||||
|
// will use Underline span instead of Strikethrough
|
||||||
|
return new UnderlineSpan();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
99
docs/docs/v4/ext-tables/README.md
Normal file
99
docs/docs/v4/ext-tables/README.md
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
# Tables extension
|
||||||
|
|
||||||
|
<MavenBadge4 :artifact="'ext-tables'" />
|
||||||
|
|
||||||
|
This extension adds support for GFM tables.
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
// create default instance of TablePlugin
|
||||||
|
.usePlugin(TablePlugin.create(context))
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
final TableTheme tableTheme = TableTheme.builder()
|
||||||
|
.tableBorderColor(Color.RED)
|
||||||
|
.tableBorderWidth(0)
|
||||||
|
.tableCellPadding(0)
|
||||||
|
.tableHeaderRowBackgroundColor(Color.BLACK)
|
||||||
|
.tableEvenRowBackgroundColor(Color.GREEN)
|
||||||
|
.tableOddRowBackgroundColor(Color.YELLOW)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(TablePlugin.create(tableTheme))
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
Markwon.builder(context)
|
||||||
|
.usePlugin(TablePlugin.create(builder ->
|
||||||
|
builder
|
||||||
|
.tableBorderColor(Color.RED)
|
||||||
|
.tableBorderWidth(0)
|
||||||
|
.tableCellPadding(0)
|
||||||
|
.tableHeaderRowBackgroundColor(Color.BLACK)
|
||||||
|
.tableEvenRowBackgroundColor(Color.GREEN)
|
||||||
|
.tableOddRowBackgroundColor(Color.YELLOW)
|
||||||
|
))
|
||||||
|
```
|
||||||
|
|
||||||
|
Please note, that _by default_ tables have limitations. For example, there is no support
|
||||||
|
for images inside table cells. And table contents won't be copied to clipboard if a TextView
|
||||||
|
has such functionality. Table will always take full width of a TextView in which it is displayed.
|
||||||
|
All columns will always be of the same width. So, _default_ implementation provides basic
|
||||||
|
functionality which can answer some needs. These all come from the limited nature of the TextView
|
||||||
|
to display such content.
|
||||||
|
|
||||||
|
In order to provide full-fledged experience, tables must be displayed in a special widget.
|
||||||
|
Since version `3.0.0` Markwon provides a special artifact `markwon-recycler` that allows
|
||||||
|
to render markdown in a set of widgets in a RecyclerView. It also gives ability to change
|
||||||
|
display widget form TextView to any other.
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Table table = Table.parse(Markwon, TableBlock);
|
||||||
|
myTableWidget.setTable(table);
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
To take advantage of this functionality and render tables without limitations (including
|
||||||
|
horizontally scrollable layout when its contents exceed screen width), refer to [recycler-table](/docs/v4/recycler-table/)
|
||||||
|
module documentation that adds support for rendering `TableBlock` markdown node inside Android-native `TableLayout` widget.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Theme
|
||||||
|
|
||||||
|
### Cell padding
|
||||||
|
|
||||||
|
Padding inside a table cell
|
||||||
|
|
||||||
|
<ThemeProperty name="tableCellPadding" type="@Px int" defaults="0" />
|
||||||
|
|
||||||
|
### Border color
|
||||||
|
|
||||||
|
The color of table borders
|
||||||
|
|
||||||
|
<ThemeProperty name="tableBorderColor" type="@ColorInt int" defaults="(text color) with 75 (0-255) alpha" />
|
||||||
|
|
||||||
|
### Border width
|
||||||
|
|
||||||
|
The width of table borders
|
||||||
|
|
||||||
|
<ThemeProperty name="tableBorderWidth" type="@Px int" defaults="Stroke with of context TextPaint" />
|
||||||
|
|
||||||
|
### Odd row background
|
||||||
|
|
||||||
|
Background of an odd table row
|
||||||
|
|
||||||
|
<ThemeProperty name="tableOddRowBackgroundColor" type="@ColorInt int" defaults="(text color) with 22 (0-255) alpha" />
|
||||||
|
|
||||||
|
### Even row background <Badge text="1.1.1" />
|
||||||
|
|
||||||
|
Background of an even table row
|
||||||
|
|
||||||
|
<ThemeProperty name="tableEventRowBackgroundColor" type="@ColorInt int" defaults="0" />
|
||||||
|
|
||||||
|
### Header row background <Badge text="1.1.1" />
|
||||||
|
|
||||||
|
Background of header table row
|
||||||
|
|
||||||
|
<ThemeProperty name="tableHeaderRowBackgroundColor" type="@ColorInt int" defaults="0" />
|
146
docs/docs/v4/ext-tasklist/README.md
Normal file
146
docs/docs/v4/ext-tasklist/README.md
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
# Task list extension
|
||||||
|
|
||||||
|
<MavenBadge4 :artifact="'ext-tasklist'" />
|
||||||
|
|
||||||
|
Adds support for GFM (Github-flavored markdown) task-lists:
|
||||||
|
|
||||||
|
```java
|
||||||
|
Markwon.builder(context)
|
||||||
|
.usePlugin(TaskListPlugin.create(context));
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Create a default instance of `TaskListPlugin` with `TaskListDrawable` initialized to use
|
||||||
|
`android.R.attr.textColorLink` as primary color and `android.R.attr.colorBackground` as background
|
||||||
|
```java
|
||||||
|
TaskListPlugin.create(context);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Create an instance of `TaskListPlugin` with exact color values to use:
|
||||||
|
```java
|
||||||
|
// obtain color values
|
||||||
|
final int checkedFillColor = /* */;
|
||||||
|
final int normalOutlineColor = /* */;
|
||||||
|
final int checkMarkColor = /* */;
|
||||||
|
|
||||||
|
TaskListPlugin.create(checkedFillColor, normalOutlineColor, checkMarkColor);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Specify own drawable for a task list item:
|
||||||
|
|
||||||
|
```java
|
||||||
|
// obtain drawable
|
||||||
|
final Drawable drawable = /* */;
|
||||||
|
|
||||||
|
TaskListPlugin.create(drawable);
|
||||||
|
```
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
Please note that custom drawable for a task list item must correctly handle state
|
||||||
|
in order to display done/not-done:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class MyTaskListDrawable extends Drawable {
|
||||||
|
|
||||||
|
private boolean isChecked;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(@NonNull Canvas canvas) {
|
||||||
|
// draw accordingly to the isChecked value
|
||||||
|
}
|
||||||
|
|
||||||
|
/* implementation omitted */
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean onStateChange(int[] state) {
|
||||||
|
final boolean isChecked = contains(state, android.R.attr.state_checked);
|
||||||
|
final boolean result = this.isChecked != isChecked;
|
||||||
|
if (result) {
|
||||||
|
this.isChecked = isChecked;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean contains(@Nullable int[] states, int value) {
|
||||||
|
if (states != null) {
|
||||||
|
for (int state : states) {
|
||||||
|
if (state == value) {
|
||||||
|
// NB return here
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Task list mutation
|
||||||
|
|
||||||
|
It is possible to mutate task list item state (toggle done/not-done). But note
|
||||||
|
that `Markwon` won't handle state change internally by any means and this change
|
||||||
|
is merely a visual one. If you need to persist state of a task list
|
||||||
|
item change you have to implement it yourself. This should get your started:
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(TaskListPlugin.create(context))
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
|
||||||
|
|
||||||
|
// obtain original SpanFactory set by TaskListPlugin
|
||||||
|
final SpanFactory origin = builder.getFactory(TaskListItem.class);
|
||||||
|
if (origin == null) {
|
||||||
|
// or throw, as it's a bit weird state and we expect
|
||||||
|
// this factory to be present
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.setFactory(TaskListItem.class, new SpanFactory() {
|
||||||
|
@Override
|
||||||
|
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
|
||||||
|
// it's a bit non-secure behavior and we should validate
|
||||||
|
// the type of returned span first, but for the sake of brevity
|
||||||
|
// we skip this step
|
||||||
|
final TaskListSpan span = (TaskListSpan) origin.getSpans(configuration, props);
|
||||||
|
|
||||||
|
if (span == null) {
|
||||||
|
// or throw
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return an array of spans
|
||||||
|
return new Object[]{
|
||||||
|
span,
|
||||||
|
new ClickableSpan() {
|
||||||
|
@Override
|
||||||
|
public void onClick(@NonNull View widget) {
|
||||||
|
// toggle VISUAL state
|
||||||
|
span.setDone(!span.isDone());
|
||||||
|
|
||||||
|
// do not forget to invalidate widget
|
||||||
|
widget.invalidate();
|
||||||
|
|
||||||
|
// execute your persistence logic
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateDrawState(@NonNull TextPaint ds) {
|
||||||
|
// no-op, so appearance is not changed (otherwise
|
||||||
|
// task list item will look like a link)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
235
docs/docs/v4/html/README.md
Normal file
235
docs/docs/v4/html/README.md
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
# HTML
|
||||||
|
|
||||||
|
<MavenBadge4 :artifact="'html'" />
|
||||||
|
|
||||||
|
This artifact encapsulates HTML parsing from the core artifact and provides
|
||||||
|
few predefined `TagHandlers`
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(HtmlPlugin.create())
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
As this artifact brings modified [jsoup](https://github.com/jhy/jsoup) library
|
||||||
|
it was moved to a standalone module in order to minimize dependencies and unused code
|
||||||
|
in applications that does not require HTML render capabilities.
|
||||||
|
|
||||||
|
Before <Badge text="2.0.0" /> `Markwon` used android `Html` class for parsing and
|
||||||
|
rendering. Unfortunately, according to markdown specification, markdown can contain
|
||||||
|
HTML in _unpredictable_ way if rendered _outside_ of browser. For example:
|
||||||
|
|
||||||
|
```markdown{4}
|
||||||
|
<i>
|
||||||
|
Hello from italics tag
|
||||||
|
|
||||||
|
</i><b>bold></b>
|
||||||
|
```
|
||||||
|
|
||||||
|
This snippet could be represented as:
|
||||||
|
* HtmlBlock (`<i>\nHello from italics tag`)
|
||||||
|
* HtmlInline (`<i>`)
|
||||||
|
* HtmlInline (`<b>`)
|
||||||
|
* Text (`bold`)
|
||||||
|
* HtmlInline (`</b>`)
|
||||||
|
|
||||||
|
:::tip A bit of background
|
||||||
|
<br>
|
||||||
|
<GithubIssue id="52" displayName="This issue" /> had brought attention to differences between HTML & commonmark implementations. <br><br>
|
||||||
|
:::
|
||||||
|
|
||||||
|
Unfortunately Android `HTML` class cannot parse a _fragment_ of HTML to later
|
||||||
|
be included in a bigger set of content. This is why the decision was made to bring
|
||||||
|
HTML parsing _in-markwon-house_
|
||||||
|
|
||||||
|
## Predefined TagHandlers
|
||||||
|
* `<img>`
|
||||||
|
* `<a>`
|
||||||
|
* `<blockquote>`
|
||||||
|
* `<sub>`
|
||||||
|
* `<sup>`
|
||||||
|
* `<b>, <strong>`
|
||||||
|
* `<s>, <del>`
|
||||||
|
* `<u>, <ins>`
|
||||||
|
* `<ul>, <ol>`
|
||||||
|
* `<i>, <cite>, <em>, <dfn>`
|
||||||
|
* `<h1>, <h2>, <h3>, <h4>, <h5>, <h6>`
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
All predefined tag handlers will use styling spans for native markdown content.
|
||||||
|
So, if your `Markwon` instance was configured to, for example, render Emphasis
|
||||||
|
nodes as a <span style="color: #FF0000">red text</span> then HTML tag handler will
|
||||||
|
use the same span. This includes images, links, UrlResolver, LinkProcessor, etc
|
||||||
|
:::
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Staring with <Badge text="4.0.0" /> you can exclude all default tag handlers:
|
||||||
|
|
||||||
|
```java
|
||||||
|
.usePlugin(HtmlPlugin.create(new HtmlPlugin.HtmlConfigure() {
|
||||||
|
@Override
|
||||||
|
public void configureHtml(@NonNull HtmlPlugin plugin) {
|
||||||
|
plugin.excludeDefaults(true);
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
|
or via plugin:
|
||||||
|
|
||||||
|
```java
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configure(@NonNull Registry registry) {
|
||||||
|
registry.require(HtmlPlugin.class, new Action<HtmlPlugin>() {
|
||||||
|
@Override
|
||||||
|
public void apply(@NonNull HtmlPlugin htmlPlugin) {
|
||||||
|
htmlPlugin.excludeDefaults(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
If you wish to exclude some of them `TagHandlerNoOp` can be used:
|
||||||
|
|
||||||
|
```java
|
||||||
|
.usePlugin(HtmlPlugin.create(new HtmlPlugin.HtmlConfigure() {
|
||||||
|
@Override
|
||||||
|
public void configureHtml(@NonNull HtmlPlugin plugin) {
|
||||||
|
plugin.addHandler(TagHandlerNoOp.create("h4", "h5", "h6", "img"));
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
|
## TagHandler
|
||||||
|
|
||||||
|
To define a tag-handler that applies style for the whole tag content (from start to end),
|
||||||
|
a `SimpleTagHandler` can be used. For example, let's define `<align>` tag, which can be used
|
||||||
|
like this:
|
||||||
|
|
||||||
|
* `<align center>centered text</align>`
|
||||||
|
* `<align end>this should be aligned at the end (right for LTR locales)</align>`
|
||||||
|
* `<align>regular alignment</align>`
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class AlignTagHandler extends SimpleTagHandler {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Object getSpans(
|
||||||
|
@NonNull MarkwonConfiguration configuration,
|
||||||
|
@NonNull RenderProps renderProps,
|
||||||
|
@NonNull HtmlTag tag) {
|
||||||
|
|
||||||
|
final Layout.Alignment alignment;
|
||||||
|
|
||||||
|
// html attribute without value, <align center></align>
|
||||||
|
if (tag.attributes().containsKey("center")) {
|
||||||
|
alignment = Layout.Alignment.ALIGN_CENTER;
|
||||||
|
} else if (tag.attributes().containsKey("end")) {
|
||||||
|
alignment = Layout.Alignment.ALIGN_OPPOSITE;
|
||||||
|
} else {
|
||||||
|
// empty value or any other will make regular alignment
|
||||||
|
alignment = Layout.Alignment.ALIGN_NORMAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AlignmentSpan.Standard(alignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Collection<String> supportedTags() {
|
||||||
|
return Collections.singleton("align");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
`SimpleTagHandler` can return an array of spans from `getSpans` method
|
||||||
|
:::
|
||||||
|
|
||||||
|
Then register `AlignTagHandler`:
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(this)
|
||||||
|
.usePlugin(HtmlPlugin.create())
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configure(@NonNull Registry registry) {
|
||||||
|
registry.require(HtmlPlugin.class, htmlPlugin -> htmlPlugin
|
||||||
|
.addHandler(new AlignTagHandler());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
or directly on `HtmlPlugin`:
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(this)
|
||||||
|
.usePlugin(HtmlPlugin.create(plugin -> plugin.addHandler(new AlignTagHandler())))
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
If a tag requires special handling `TagHandler` can be used directly. For example
|
||||||
|
let's define an `<enhance>` tag with `start` and `end` arguments, that will mark
|
||||||
|
start and end positions of the text that needs to be enlarged:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<enhance start="5" end="12">This is text that must be enhanced, at least a part of it</enhance>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class EnhanceTagHandler extends TagHandler {
|
||||||
|
|
||||||
|
private final int enhanceTextSize;
|
||||||
|
|
||||||
|
EnhanceTagHandler(@Px int enhanceTextSize) {
|
||||||
|
this.enhanceTextSize = enhanceTextSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(
|
||||||
|
@NonNull MarkwonVisitor visitor,
|
||||||
|
@NonNull MarkwonHtmlRenderer renderer,
|
||||||
|
@NonNull HtmlTag tag) {
|
||||||
|
|
||||||
|
// we require start and end to be present
|
||||||
|
final int start = parsePosition(tag.attributes().get("start"));
|
||||||
|
final int end = parsePosition(tag.attributes().get("end"));
|
||||||
|
|
||||||
|
if (start > -1 && end > -1) {
|
||||||
|
visitor.builder().setSpan(
|
||||||
|
new AbsoluteSizeSpan(enhanceTextSize),
|
||||||
|
tag.start() + start,
|
||||||
|
tag.start() + end
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Collection<String> supportedTags() {
|
||||||
|
return Collections.singleton("enhance");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int parsePosition(@Nullable String value) {
|
||||||
|
int position;
|
||||||
|
if (!TextUtils.isEmpty(value)) {
|
||||||
|
try {
|
||||||
|
position = Integer.parseInt(value);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
position = -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
position = -1;
|
||||||
|
}
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
27
docs/docs/v4/image-glide/README.md
Normal file
27
docs/docs/v4/image-glide/README.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Image Glide
|
||||||
|
|
||||||
|
<MavenBadge4 :artifact="'image-glide'" />
|
||||||
|
|
||||||
|
Image loading based on `Glide` library
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
// automatically create Glide instance
|
||||||
|
.usePlugin(GlideImagesPlugin.create(context))
|
||||||
|
// use supplied Glide instance
|
||||||
|
.usePlugin(GlideImagesPlugin.create(Glide.with(context)))
|
||||||
|
// if you need more control
|
||||||
|
.usePlugin(GlideImagesPlugin.create(new GlideImagesPlugin.GlideStore() {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public RequestBuilder<Drawable> load(@NonNull AsyncDrawable drawable) {
|
||||||
|
return Glide.with(context).load(drawable.getDestination());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancel(@NonNull Target<?> target) {
|
||||||
|
Glide.with(context).clear(target);
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.build();
|
||||||
|
```
|
32
docs/docs/v4/image-picasso/README.md
Normal file
32
docs/docs/v4/image-picasso/README.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# Image Picasso
|
||||||
|
|
||||||
|
<MavenBadge4 :artifact="'image-picasso'" />
|
||||||
|
|
||||||
|
Image loading based on `Picasso` library
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
// automatically create Picasso instance
|
||||||
|
.usePlugin(PicassoImagesPlugin.create(context))
|
||||||
|
// use provided picasso instance
|
||||||
|
.usePlugin(PicassoImagesPlugin.create(Picasso.get()))
|
||||||
|
// if you need more control
|
||||||
|
.usePlugin(PicassoImagesPlugin.create(new PicassoImagesPlugin.PicassoStore() {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public RequestCreator load(@NonNull AsyncDrawable drawable) {
|
||||||
|
return Picasso.get()
|
||||||
|
.load(drawable.getDestination())
|
||||||
|
// please note that drawable should be used as tag (not a destination)
|
||||||
|
// otherwise there won't be support for multiple images with the same URL
|
||||||
|
.tag(drawable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancel(@NonNull AsyncDrawable drawable) {
|
||||||
|
Picasso.get()
|
||||||
|
.cancelTag(drawable);
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.build();
|
||||||
|
```
|
354
docs/docs/v4/image/README.md
Normal file
354
docs/docs/v4/image/README.md
Normal file
@ -0,0 +1,354 @@
|
|||||||
|
# Image
|
||||||
|
|
||||||
|
<MavenBadge4 :artifact="'image'" />
|
||||||
|
|
||||||
|
In order to display images in your markdown `ImagesPlugin` can be used.
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(ImagesPlugin.create())
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
There are also modules that add image loading capabilities to markdown
|
||||||
|
based on image-loading libraries: [image-glide](/docs/v4/image-glide/) and
|
||||||
|
[image-picasso](/docs/v4/image-picasso/)
|
||||||
|
:::
|
||||||
|
|
||||||
|
`ImagesPlugin` splits the image-loading into 2 parts: scheme-handling and media-decoding.
|
||||||
|
|
||||||
|
## SchemeHandler
|
||||||
|
|
||||||
|
To add a scheme-handler to `ImagesPlugin`:
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(ImagesPlugin.create())
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configure(@NonNull Registry registry) {
|
||||||
|
registry.require(ImagesPlugin.class, new Action<ImagesPlugin>() {
|
||||||
|
@Override
|
||||||
|
public void apply(@NonNull ImagesPlugin imagesPlugin) {
|
||||||
|
imagesPlugin.addSchemeHandler(DataUriSchemeHandler.create());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
|
||||||
|
@Override
|
||||||
|
public void configureImages(@NonNull ImagesPlugin plugin) {
|
||||||
|
plugin.addSchemeHandler(DataUriSchemeHandler.create());
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
|
`ImagesPlugin` comes with a set of predefined scheme-handlers:
|
||||||
|
* `FileSchemeHandler` - `file://`
|
||||||
|
* `DataUriSchemeHandler` - `data:`
|
||||||
|
* `NetworkSchemeHandler` - `http`, `https`
|
||||||
|
* `OkHttpNetworkSchemeHandler` - `http`, `https`
|
||||||
|
|
||||||
|
### FileSchemeHandler
|
||||||
|
|
||||||
|
Loads images via `file://` scheme. Allows loading images from `assets` folder.
|
||||||
|
|
||||||
|
```java
|
||||||
|
// default implementation, no assets handling
|
||||||
|
FileSchemeHandler.create();
|
||||||
|
|
||||||
|
// assets loading
|
||||||
|
FileSchemeHandler.createWithAssets(context);
|
||||||
|
```
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
Assets loading will work when your URL will include `android_asset` in the path,
|
||||||
|
for example: `file:///android_asset/image.png` (mind the 3 slashes `///`). If you wish
|
||||||
|
to _assume_ all images without proper scheme to point to assets folder, then you can use
|
||||||
|
[UrlProcessorAndroidAssets](/docs/v4/core/configuration.html#urlprocessorandroidassets)
|
||||||
|
:::
|
||||||
|
|
||||||
|
By default `ImagesPlugin` includes _plain_ `FileSchemeHandler` (without assets support),
|
||||||
|
so if you wish to change that you can explicitly specify it:
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
|
||||||
|
@Override
|
||||||
|
public void configureImages(@NonNull ImagesPlugin plugin) {
|
||||||
|
plugin.addSchemeHandler(FileSchemeHandler.createWithAssets(context));
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
|
### DataUriSchemeHandler
|
||||||
|
`DataUriSchemeHandler` allows _inlining_ images with `data:` scheme (`data:image/svg+xml;base64,MTIz`).
|
||||||
|
This scheme-handler is registered by default, so you do not need to add it explicitly.
|
||||||
|
|
||||||
|
### NetworkSchemeHandler
|
||||||
|
`NetworkSchemeHandler` allows obtaining images from `http://` and `https://` uris
|
||||||
|
(internally it uses `HttpURLConnection`). This scheme-handler is registered by default
|
||||||
|
|
||||||
|
### OkHttpNetworkSchemeHandler
|
||||||
|
`OkHttpNetworkSchemeHandler` allows obtaining images from `http://` and `https://` uris
|
||||||
|
via [okhttp library](https://github.com/square/okhttp). Please note that in order to use
|
||||||
|
this scheme-handler you must explicitly add `okhttp` library to your project.
|
||||||
|
|
||||||
|
```java
|
||||||
|
// default instance
|
||||||
|
OkHttpNetworkSchemeHandler.create();
|
||||||
|
|
||||||
|
// specify OkHttpClient to use
|
||||||
|
OkHttpNetworkSchemeHandler.create(new OkHttpClient());
|
||||||
|
|
||||||
|
// @since 4.0.0
|
||||||
|
OkHttpNetworkSchemeHandler.create(Call.Factory);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom SchemeHandler
|
||||||
|
|
||||||
|
```java
|
||||||
|
public abstract class SchemeHandler {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public abstract ImageItem handle(@NonNull String raw, @NonNull Uri uri);
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public abstract Collection<String> supportedSchemes();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Starting with <Badge text="4.0.0" /> `SchemeHandler` can return a result (when no
|
||||||
|
further decoding is required):
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
|
||||||
|
@Override
|
||||||
|
public void configureImages(@NonNull ImagesPlugin plugin) {
|
||||||
|
// for example to return a drawable resource
|
||||||
|
plugin.addSchemeHandler(new SchemeHandler() {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ImageItem handle(@NonNull String raw, @NonNull Uri uri) {
|
||||||
|
|
||||||
|
// will handle URLs like `drawable://ic_account_24dp_white`
|
||||||
|
final int resourceId = context.getResources().getIdentifier(
|
||||||
|
raw.substring("drawable://".length()),
|
||||||
|
"drawable",
|
||||||
|
context.getPackageName());
|
||||||
|
|
||||||
|
// it's fine if it throws, async-image-loader will catch exception
|
||||||
|
final Drawable drawable = context.getDrawable(resourceId);
|
||||||
|
|
||||||
|
return ImageItem.withResult(drawable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Collection<String> supportedSchemes() {
|
||||||
|
return Collections.singleton("drawable");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
|
Otherwise `SchemeHandler` must return an `InputStream` with proper `content-type` information
|
||||||
|
for further processing by a `MediaDecoder`:
|
||||||
|
|
||||||
|
```java
|
||||||
|
imagesPlugin.addSchemeHandler(new SchemeHandler() {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ImageItem handle(@NonNull String raw, @NonNull Uri uri) {
|
||||||
|
return ImageItem.withDecodingNeeded("image/png", load(raw));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private InputStream load(@NonNull String raw) {...}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## MediaDecoder
|
||||||
|
|
||||||
|
`ImagesPlugin` comes with predefined media-decoders:
|
||||||
|
* `GifMediaDecoder` adds support for GIF
|
||||||
|
* `SvgMediaDecoder` adds support for SVG
|
||||||
|
* `DefaultMediaDecoder`
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
If you wish to add support for **SVG** or **GIF** you must explicitly add these dependencies
|
||||||
|
to your project:
|
||||||
|
* for `SVG`: `com.caverock:androidsvg:1.4`
|
||||||
|
* for `GIF`: `pl.droidsonroids.gif:android-gif-drawable:1.2.14`
|
||||||
|
|
||||||
|
You can try more recent versions of these libraries, but make sure that they doesn't
|
||||||
|
introduce any unexpected behavior.
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
### GifMediaDecoder
|
||||||
|
|
||||||
|
Adds support for GIF media in markdown. If `pl.droidsonroids.gif:android-gif-drawable:*` dependency
|
||||||
|
is found in the classpath, then registration will happen automatically.
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
|
||||||
|
@Override
|
||||||
|
public void configureImages(@NonNull ImagesPlugin plugin) {
|
||||||
|
// autoplayGif controls if GIF should be automatically started
|
||||||
|
plugin.addMediaDecoder(GifMediaDecoder.create(/*autoplayGif*/false));
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
### SvgMediaDecoder
|
||||||
|
|
||||||
|
Adds support for SVG media in markdown. If `com.caverock:androidsvg:*` dependency is found
|
||||||
|
in the classpath, then registration will happen automatically.
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
|
||||||
|
@Override
|
||||||
|
public void configureImages(@NonNull ImagesPlugin plugin) {
|
||||||
|
|
||||||
|
// uses supplied Resources
|
||||||
|
plugin.addMediaDecoder(SvgMediaDecoder.create(context.getResources()));
|
||||||
|
|
||||||
|
// uses Resources.getSystem()
|
||||||
|
plugin.addMediaDecoder(SvgMediaDecoder.create());
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
### DefaultMediaDecoder
|
||||||
|
|
||||||
|
`DefaultMediaDecoder` _tries_ to decode supplied InputStream
|
||||||
|
as Bitmap (via `BitmapFactory.decodeStream(inputStream)`). This decoder is registered automatically.
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
|
||||||
|
@Override
|
||||||
|
public void configureImages(@NonNull ImagesPlugin plugin) {
|
||||||
|
|
||||||
|
// uses supplied Resources
|
||||||
|
plugin.defaultMediaDecoder(DefaultMediaDecoder.create(context.getResources()));
|
||||||
|
|
||||||
|
// uses Resources.getSystem()
|
||||||
|
plugin.defaultMediaDecoder(DefaultMediaDecoder.create());
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
## AsyncDrawableScheduler
|
||||||
|
|
||||||
|
`AsyncDrawableScheduler` is used in order to give `AsyncDrawable` a way to invalidate `TextView`
|
||||||
|
that is holding it. A plugin that is dealing with `AsyncDrawable` should always call these methods:
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
|
||||||
|
AsyncDrawableScheduler.unschedule(textView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterSetText(@NonNull TextView textView) {
|
||||||
|
AsyncDrawableScheduler.schedule(textView);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
Starting with <Badge text="4.0.0" /> multiple plugins can call `AsyncDrawableScheduler#schedule`
|
||||||
|
method without the penalty to process `AsyncDrawable` callbacks multiple times (internally caches
|
||||||
|
state which ensures that a `TextView` is processed only once the text has changed).
|
||||||
|
:::
|
||||||
|
|
||||||
|
## ErrorHandler
|
||||||
|
|
||||||
|
An `ErrorHandler` can be used to receive an error that has happened during image loading
|
||||||
|
and (optionally) return an error drawable to be displayed instead
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
|
||||||
|
@Override
|
||||||
|
public void configureImages(@NonNull ImagesPlugin plugin) {
|
||||||
|
plugin.errorHandler(new ImagesPlugin.ErrorHandler() {
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Drawable handleError(@NonNull String url, @NonNull Throwable throwable) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
## PlaceholderProvider
|
||||||
|
|
||||||
|
To display a placeholder during image loading `PlaceholderProvider` can be used:
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
|
||||||
|
@Override
|
||||||
|
public void configureImages(@NonNull ImagesPlugin plugin) {
|
||||||
|
plugin.placeholderProvider(new ImagesPlugin.PlaceholderProvider() {
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Drawable providePlaceholder(@NonNull AsyncDrawable drawable) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
If your placeholder drawable has _specific_ size which is not the same an image that is being loaded,
|
||||||
|
you can manually assign bounds to the placeholder:
|
||||||
|
|
||||||
|
```java
|
||||||
|
plugin.placeholderProvider(new ImagesPlugin.PlaceholderProvider() {
|
||||||
|
@Override
|
||||||
|
public Drawable providePlaceholder(@NonNull AsyncDrawable drawable) {
|
||||||
|
final ColorDrawable placeholder = new ColorDrawable(Color.BLUE);
|
||||||
|
// these bounds will be used to display a placeholder,
|
||||||
|
// so even if loading image has size `width=100%`, placeholder
|
||||||
|
// bounds won't be affected by it
|
||||||
|
placeholder.setBounds(0, 0, 48, 48);
|
||||||
|
return placeholder;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
If you are using [html](/docs/v4/html/) you do not have to additionally setup
|
||||||
|
images displayed via `<img>` tag, as `HtmlPlugin` automatically uses configured
|
||||||
|
image loader. But images referenced in HTML come with additional support for
|
||||||
|
sizes, which is not supported natively by markdown, allowing absolute or relative sizes:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<img src="./assets/my-image" width="100%">
|
||||||
|
```
|
||||||
|
:::
|
34
docs/docs/v4/install.md
Normal file
34
docs/docs/v4/install.md
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
prev: false
|
||||||
|
next: /docs/v4/core/getting-started.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
<ArtifactPicker4 />
|
||||||
|
|
||||||
|
## Snapshot
|
||||||
|
|
||||||
|
In order to use latest `SNAPSHOT` version add snapshot repository
|
||||||
|
to your root project's `build.gradle` file:
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
jcenter()
|
||||||
|
google()
|
||||||
|
// this one 👇
|
||||||
|
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } // 👈 this one
|
||||||
|
// this one 👆
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip Info
|
||||||
|
All official artifacts share the same version number and all
|
||||||
|
are uploaded to **release** and **snapshot** repositories
|
||||||
|
:::
|
||||||
|
|
31
docs/docs/v4/linkify/README.md
Normal file
31
docs/docs/v4/linkify/README.md
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# Linkify
|
||||||
|
|
||||||
|
<MavenBadge4 :artifact="'linkify'" />
|
||||||
|
|
||||||
|
A plugin to automatically add links to your markdown. Currently autolinking works for:
|
||||||
|
* email (`me@web.com`)
|
||||||
|
* phone numbers (`+10000000`)
|
||||||
|
* web URLS
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
`Linkify` plugin is based on `android.text.util.Linkify` which can lead to significant performance
|
||||||
|
drop due to its implementation based on regex.
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::danger
|
||||||
|
Do not use `autolink` XML attribute on your `TextView` as it will remove
|
||||||
|
all links except autolinked ones ¯\\\_(ツ)_/¯
|
||||||
|
:::
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
// will autolink all supported types
|
||||||
|
.usePlugin(LinkifyPlugin.create())
|
||||||
|
// the same as above
|
||||||
|
.usePlugin(LinkifyPlugin.create(
|
||||||
|
Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS | Linkify.WEB_URLS
|
||||||
|
))
|
||||||
|
// only emails
|
||||||
|
.usePlugin(LinkifyPlugin.create(Linkify.EMAIL_ADDRESSES))
|
||||||
|
.build();
|
||||||
|
```
|
96
docs/docs/v4/recipes.md
Normal file
96
docs/docs/v4/recipes.md
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# Recipes
|
||||||
|
|
||||||
|
|
||||||
|
## SpannableFactory
|
||||||
|
|
||||||
|
Consider using `NoCopySpannableFactory` when a `TextView` will be used to display markdown
|
||||||
|
multiple times (for example in a `RecyclerView`):
|
||||||
|
|
||||||
|
```java
|
||||||
|
// call after inflation and before setting markdown
|
||||||
|
textView.setSpannableFactory(NoCopySpannableFactory.getInstance());
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Autolink
|
||||||
|
|
||||||
|
Do not use `autolink` XML attribute on your `TextView` as it will remove all links except autolinked ones.
|
||||||
|
Consider using [linkify plugin](/docs/v4/linkify/) or commonmark-java [autolink extension](https://github.com/atlassian/commonmark-java)
|
||||||
|
|
||||||
|
|
||||||
|
## List item spacing
|
||||||
|
|
||||||
|
If your list items, task list items or paragraphs need special space between them
|
||||||
|
(increasing spacing between them, but keeping the original line height),
|
||||||
|
`LastLineSpacingSpan` <Badge text="4.0.0" /> can be used:
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
|
||||||
|
// or Paragraph, or TaskListItem
|
||||||
|
builder.addFactory(ListItem.class, new SpanFactory() {
|
||||||
|
@Override
|
||||||
|
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
|
||||||
|
return new LastLineSpacingSpan(spacingPx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Softbreak new-line
|
||||||
|
|
||||||
|
If you want to add a new line when a `softbreak` is used:
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
|
||||||
|
builder.on(SoftLineBreak.class, new MarkwonVisitor.NodeVisitor<SoftLineBreak>() {
|
||||||
|
@Override
|
||||||
|
public void visit(@NonNull MarkwonVisitor visitor, @NonNull SoftLineBreak softLineBreak) {
|
||||||
|
visitor.forceNewLine();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Custom typeface
|
||||||
|
|
||||||
|
When using a custom typeface on a `TextView` you might find that **bold** and *italic* nodes
|
||||||
|
are displayed incorrectly. Consider registering own `SpanFactories` for `StrongEmphasis` and `Emphasis` nodes:
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
|
||||||
|
builder
|
||||||
|
.setFactory(StrongEmphasis.class, new SpanFactory() {
|
||||||
|
@Override
|
||||||
|
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
|
||||||
|
return new StyleSpan(Typeface.BOLD);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setFactory(Emphasis.class, new SpanFactory() {
|
||||||
|
@Override
|
||||||
|
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
|
||||||
|
return new StyleSpan(Typeface.ITALIC);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
Please check that `StyleSpan` works for you. If it doesn't consider
|
||||||
|
using `CustomTypefaceSpan` with your typeface directly.
|
||||||
|
|
||||||
|
|
92
docs/docs/v4/recycler-table/README.md
Normal file
92
docs/docs/v4/recycler-table/README.md
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
# Recycler Table <Badge text="3.0.0" />
|
||||||
|
|
||||||
|
<MavenBadge4 :artifact="'recycler-table'" />
|
||||||
|
|
||||||
|
Artifact that provides [MarkwonAdapter.Entry](/docs/v4/recycler/) to render `TableBlock` inside
|
||||||
|
Android-native `TableLayout` widget.
|
||||||
|
|
||||||
|
<img :src="$withBase('/assets/recycler-table-screenshot.png')" alt="screenshot" width="45%">
|
||||||
|
<br>
|
||||||
|
<small><em><sup>*</sup> It's possible to wrap `TableLayout` inside a `HorizontalScrollView` to include all table content</em></small>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Register instance of `TableEntry` with `MarkwonAdapter` to render TableBlocks:
|
||||||
|
```java
|
||||||
|
final MarkwonAdapter adapter = MarkwonAdapter.builder(R.layout.adapter_default_entry, R.id.text)
|
||||||
|
.include(TableBlock.class, TableEntry.create(builder -> builder
|
||||||
|
.tableLayout(R.layout.adapter_table_block, R.id.table_layout)
|
||||||
|
.textLayoutIsRoot(R.layout.view_table_entry_cell)))
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
`TableEntry` requires at least 2 arguments:
|
||||||
|
* `tableLayout` - layout with `TableLayout` inside
|
||||||
|
* `textLayout` - layout with `TextView` inside (represents independent table cell)
|
||||||
|
|
||||||
|
In case when required view is the root of layout specific builder methods can be used:
|
||||||
|
* `tableLayoutIsRoot(int)`
|
||||||
|
* `textLayoutIsRoot(int)`
|
||||||
|
|
||||||
|
If your layouts have different structure (for example wrap a `TableView` inside a `HorizontalScrollView`)
|
||||||
|
then you should use methods that accept ID of required view inside layout:
|
||||||
|
* `tableLayout(int, int)`
|
||||||
|
* `textLayout(int, int)`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
To display `TableBlock` as a `TableLayout` specific `MarkwonPlugin` must be used: `TableEntryPlugin`.
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
Do not use `TablePlugin` if you wish to display markdown tables via `TableEntry`. Use **TableEntryPlugin** instead
|
||||||
|
:::
|
||||||
|
|
||||||
|
`TableEntryPlugin` can reuse existing `TablePlugin` to make appearance of tables the same in both contexts:
|
||||||
|
when rendering _natively_ in a TextView and when rendering in RecyclerView with TableEntry.
|
||||||
|
|
||||||
|
* `TableEntryPlugin.create(Context)` - creates plugin with default `TableTheme`
|
||||||
|
* `TableEntryPlugin.create(TableTheme)` - creates plugin with provided `TableTheme`
|
||||||
|
* `TableEntryPlugin.create(TablePlugin.ThemeConfigure)` - creates plugin with theme configured by `ThemeConfigure`
|
||||||
|
* `TableEntryPlugin.create(TablePlugin)` - creates plugin with `TableTheme` used in provided `TablePlugin`
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(TableEntryPlugin.create(context))
|
||||||
|
// other plugins
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(TableEntryPlugin.create(builder -> builder
|
||||||
|
.tableBorderWidth(0)
|
||||||
|
.tableHeaderRowBackgroundColor(Color.RED)))
|
||||||
|
// other plugins
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Table with scrollable content
|
||||||
|
|
||||||
|
To stretch table columns to fit the width of screen or to make table scrollable when content exceeds screen width
|
||||||
|
this layout can be used:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clipChildren="false"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingLeft="16dip"
|
||||||
|
android:paddingTop="8dip"
|
||||||
|
android:paddingRight="16dip"
|
||||||
|
android:paddingBottom="8dip"
|
||||||
|
android:scrollbarStyle="outsideInset">
|
||||||
|
|
||||||
|
<TableLayout
|
||||||
|
android:id="@+id/table_layout"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:stretchColumns="*" />
|
||||||
|
|
||||||
|
</HorizontalScrollView>
|
||||||
|
```
|
153
docs/docs/v4/recycler/README.md
Normal file
153
docs/docs/v4/recycler/README.md
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
# Recycler <Badge text="3.0.0" />
|
||||||
|
|
||||||
|
<MavenBadge4 :artifact="'recycler'" />
|
||||||
|
|
||||||
|
This artifact allows displaying markdown in a set of Android widgets
|
||||||
|
inside a RecyclerView. Can be useful when displaying lengthy markdown
|
||||||
|
content or **displaying certain markdown blocks inside specific widgets**.
|
||||||
|
|
||||||
|
```java
|
||||||
|
// create an adapter that will use a TextView for each block of markdown
|
||||||
|
// `createTextViewIsRoot` accepts a layout in which TextView is the root view
|
||||||
|
final MarkwonAdapter adapter =
|
||||||
|
MarkwonAdapter.createTextViewIsRoot(R.layout.adapter_default_entry);
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
// `create` method accepts a layout with TextView and ID of a TextView
|
||||||
|
// which allows wrapping a TextView inside another widget or combine with other widgets
|
||||||
|
final MarkwonAdapter adapter =
|
||||||
|
MarkwonAdapter.create(R.layout.adapter_default_entry, R.id.text_view);
|
||||||
|
|
||||||
|
// initialize RecyclerView (LayoutManager, Decorations, etc)
|
||||||
|
final RecyclerView recyclerView = obtainRecyclerView();
|
||||||
|
|
||||||
|
// set adapter
|
||||||
|
recyclerView.setAdapter(adapter);
|
||||||
|
|
||||||
|
// obtain an instance of Markwon (register all required plugins)
|
||||||
|
final Markwon markwon = obtainMarkwon();
|
||||||
|
|
||||||
|
// set markdown to be displayed
|
||||||
|
adapter.setMarkdown(markwon, "# This is markdown!");
|
||||||
|
|
||||||
|
// NB, adapter does not handle updates on its own, please use
|
||||||
|
// whatever method appropriate for you.
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
```
|
||||||
|
|
||||||
|
Initialized adapter above will use a TextView for each markdown block.
|
||||||
|
In order to tell adapter to render certain blocks differently a `builder` can be used.
|
||||||
|
For example, let's render `FencedCodeBlock` inside a `HorizontalScrollView`:
|
||||||
|
|
||||||
|
```java
|
||||||
|
// we still need to have a _default_ entry
|
||||||
|
final MarkwonAdapter adapter =
|
||||||
|
MarkwonAdapter.builderTextViewIsRoot(R.layout.adapter_default_entry)
|
||||||
|
.include(FencedCodeBlock.class, new FencedCodeBlockEntry())
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
where `FencedCodeBlockEntry` is:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class FencedCodeBlockEntry extends MarkwonAdapter.Entry<FencedCodeBlock, FencedCodeBlockEntry.Holder> {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Holder createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
|
||||||
|
return new Holder(inflater.inflate(R.layout.adapter_fenced_code_block, parent, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bindHolder(@NonNull Markwon markwon, @NonNull Holder holder, @NonNull FencedCodeBlock node) {
|
||||||
|
markwon.setParsedMarkdown(holder.textView, markwon.render(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Holder extends MarkwonAdapter.Holder {
|
||||||
|
|
||||||
|
final TextView textView;
|
||||||
|
|
||||||
|
public Holder(@NonNull View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
|
||||||
|
this.textView = requireView(R.id.text_view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
and its layout (`R.layout.adapter_fenced_code_block`):
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clipChildren="false"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:fillViewport="true"
|
||||||
|
android:paddingLeft="16dip"
|
||||||
|
android:paddingRight="16dip"
|
||||||
|
android:scrollbarStyle="outsideInset">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="#0f000000"
|
||||||
|
android:fontFamily="monospace"
|
||||||
|
android:lineSpacingExtra="2dip"
|
||||||
|
android:paddingLeft="16dip"
|
||||||
|
android:paddingTop="8dip"
|
||||||
|
android:paddingRight="16dip"
|
||||||
|
android:paddingBottom="8dip"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
</HorizontalScrollView>
|
||||||
|
```
|
||||||
|
|
||||||
|
As we apply styling to `FencedCodeBlock` _manually_, we no longer need
|
||||||
|
`Markwon` to apply styling spans for us, so `Markwon` initialization could be:
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
// your other plugins
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
|
||||||
|
builder.on(FencedCodeBlock.class, (visitor, fencedCodeBlock) -> {
|
||||||
|
// we actually won't be applying code spans here, as our custom view will
|
||||||
|
// draw background and apply mono typeface
|
||||||
|
//
|
||||||
|
// NB the `trim` operation on literal (as code will have a new line at the end)
|
||||||
|
final CharSequence code = visitor.configuration()
|
||||||
|
.syntaxHighlight()
|
||||||
|
.highlight(fencedCodeBlock.getInfo(), fencedCodeBlock.getLiteral().trim());
|
||||||
|
visitor.builder().append(code);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
Previously we have created a `FencedCodeBlockEntry` but all it does is apply markdown to a TextView.
|
||||||
|
For such a case there is a `SimpleEntry` that could be used instead:
|
||||||
|
|
||||||
|
```java
|
||||||
|
final MarkwonAdapter adapter =
|
||||||
|
MarkwonAdapter.builderTextViewIsRoot(R.layout.adapter_default_entry)
|
||||||
|
.include(FencedCodeBlock.class, SimpleEntry.create(R.layout.adapter_fenced_code_block, R.id.text_view))
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
`SimpleEntry` also takes care of _caching_ parsed markdown. So each node will be
|
||||||
|
parsed only once and each subsequent adapter binding call will reuse previously cached markdown.
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::tip Tables
|
||||||
|
There is a standalone artifact that adds support for displaying markdown tables
|
||||||
|
natively via `TableLayout`. Please refer to its [documentation](/docs/v4/recycler-table/)
|
||||||
|
:::
|
70
docs/docs/v4/simple-ext/README.md
Normal file
70
docs/docs/v4/simple-ext/README.md
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# Simple Extension <Badge text="4.0.0" />
|
||||||
|
|
||||||
|
<MavenBadge4 :artifact="'simple-ext'" />
|
||||||
|
|
||||||
|
`SimpleExtPlugin` allows creating simple _delimited_ extensions, for example:
|
||||||
|
|
||||||
|
```md
|
||||||
|
+this is text surrounded by `+`+
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(this)
|
||||||
|
.usePlugin(SimpleExtPlugin.create(plugin -> plugin
|
||||||
|
// +sometext+
|
||||||
|
.addExtension(1, '+', new SpanFactory() {
|
||||||
|
@Override
|
||||||
|
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
|
||||||
|
return new EmphasisSpan();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(this)
|
||||||
|
.usePlugin(SimpleExtPlugin.create())
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configure(@NonNull Registry registry) {
|
||||||
|
registry.require(SimpleExtPlugin.class, new Action<SimpleExtPlugin>() {
|
||||||
|
@Override
|
||||||
|
public void apply(@NonNull SimpleExtPlugin plugin) {
|
||||||
|
plugin.addExtension(1, '+', new SpanFactory() {
|
||||||
|
@Override
|
||||||
|
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
|
||||||
|
return new EmphasisSpan();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
If opening and closing characters are different another method can be used:
|
||||||
|
|
||||||
|
```java
|
||||||
|
plugin.addExtension(
|
||||||
|
/*length*/2,
|
||||||
|
/*openingCharacter*/'@',
|
||||||
|
/*closingCharacter*/'$',
|
||||||
|
/*spanFactory*/(configuration, props) -> new ForegroundColorSpan(Color.RED))))
|
||||||
|
```
|
||||||
|
|
||||||
|
This extension will be applied to a text like this:
|
||||||
|
|
||||||
|
```md
|
||||||
|
@@we are inside different delimiter characters$$
|
||||||
|
```
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
Space character cannot be used as a delimiter (from either side). So,
|
||||||
|
```java
|
||||||
|
plugin.addExtension(1, '@', ' ', /*spanFactory*/);
|
||||||
|
```
|
||||||
|
won't work for `@some-text ` text
|
||||||
|
:::
|
74
docs/docs/v4/syntax-highlight/README.md
Normal file
74
docs/docs/v4/syntax-highlight/README.md
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# Syntax highlight
|
||||||
|
|
||||||
|
<MavenBadge4 :artifact="'syntax-highlight'" />
|
||||||
|
|
||||||
|
This is a module to add **syntax highlight** functionality to your markdown rendered with `Markwon` library. It is based on [Prism4j](https://github.com/noties/Prism4j) so lead there to understand how to configure `Prism4j` instance.
|
||||||
|
|
||||||
|
<img :src="$withBase('/art/markwon-syntax-default.png')" alt="theme-default" width="80%">
|
||||||
|
|
||||||
|
|
||||||
|
<img :src="$withBase('/art/markwon-syntax-darkula.png')" alt="theme-darkula" width="80%">
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
First, we need to obtain an instance of `Prism4jSyntaxHighlight` which implements Markwon's `SyntaxHighlight`:
|
||||||
|
|
||||||
|
```java
|
||||||
|
final SyntaxHighlight highlight =
|
||||||
|
Prism4jSyntaxHighlight.create(Prism4j, Prism4jTheme);
|
||||||
|
```
|
||||||
|
|
||||||
|
we also can obtain an instance of `Prism4jSyntaxHighlight` that has a _fallback_ option (if a language is not defined in `Prism4j` instance, fallback language can be used):
|
||||||
|
|
||||||
|
```java
|
||||||
|
final SyntaxHighlight highlight =
|
||||||
|
Prism4jSyntaxHighlight.create(Prism4j, Prism4jTheme, String);
|
||||||
|
```
|
||||||
|
|
||||||
|
Generally obtaining a `Prism4j` instance is pretty easy:
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Prism4j prism4j = new Prism4j(new GrammarLocatorDef());
|
||||||
|
```
|
||||||
|
|
||||||
|
Where `GrammarLocatorDef` is a generated grammar locator (if you use `prism4j-bundler` annotation processor)
|
||||||
|
|
||||||
|
`Prism4jTheme` is a specific type that is defined in this module (`prism4j` doesn't know anything about rendering). It has 2 implementations:
|
||||||
|
|
||||||
|
* `Prism4jThemeDefault`
|
||||||
|
* `Prism4jThemeDarkula`
|
||||||
|
|
||||||
|
Both of them can be obtained via factory method `create`:
|
||||||
|
|
||||||
|
* `Prism4jThemeDefault.create()`
|
||||||
|
* `Prism4jThemeDarkula.create()`
|
||||||
|
|
||||||
|
But of cause nothing is stopping you from defining your own theme:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public interface Prism4jTheme {
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
int background();
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
int textColor();
|
||||||
|
|
||||||
|
void apply(
|
||||||
|
@NonNull String language,
|
||||||
|
@NonNull Prism4j.Syntax syntax,
|
||||||
|
@NonNull SpannableStringBuilder builder,
|
||||||
|
int start,
|
||||||
|
int end
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
You can extend `Prism4jThemeBase` which has some helper methods
|
||||||
|
:::
|
||||||
|
|
||||||
|
```java
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(SyntaxHighlightPlugin.create(prism4j, prism4jTheme))
|
||||||
|
```
|
@ -3,12 +3,14 @@ org.gradle.jvmargs=-Xmx5g -Dfile.encoding=UTF-8
|
|||||||
#org.gradle.parallel=true
|
#org.gradle.parallel=true
|
||||||
org.gradle.configureondemand=true
|
org.gradle.configureondemand=true
|
||||||
|
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=true
|
||||||
android.enableBuildCache=true
|
android.enableBuildCache=true
|
||||||
android.buildCacheDir=build/pre-dex-cache
|
android.buildCacheDir=build/pre-dex-cache
|
||||||
|
|
||||||
VERSION_NAME=3.0.1
|
VERSION_NAME=4.0.0
|
||||||
|
|
||||||
GROUP=ru.noties.markwon
|
GROUP=io.noties.markwon
|
||||||
POM_DESCRIPTION=Markwon markdown for Android
|
POM_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
|
||||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
2
gradlew
vendored
2
gradlew
vendored
@ -28,7 +28,7 @@ APP_NAME="Gradle"
|
|||||||
APP_BASE_NAME=`basename "$0"`
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
DEFAULT_JVM_OPTS=""
|
DEFAULT_JVM_OPTS='"-Xmx64m"'
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD="maximum"
|
MAX_FD="maximum"
|
||||||
|
2
gradlew.bat
vendored
2
gradlew.bat
vendored
@ -14,7 +14,7 @@ set APP_BASE_NAME=%~n0
|
|||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
set DEFAULT_JVM_OPTS=
|
set DEFAULT_JVM_OPTS="-Xmx64m"
|
||||||
|
|
||||||
@rem Find java.exe
|
@rem Find java.exe
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
3
markwon-core/README.md
Normal file
3
markwon-core/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Markwon Core
|
||||||
|
|
||||||
|
https://noties.io/Markwon/docs/v4/core/getting-started.html
|
@ -16,7 +16,7 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
deps.with {
|
deps.with {
|
||||||
api it['support-annotations']
|
api it['x-annotations']
|
||||||
api it['commonmark']
|
api it['commonmark']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1 +1 @@
|
|||||||
<manifest package="ru.noties.markwon.renderer" />
|
<manifest package="io.noties.markwon" />
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.commonmark.node.Node;
|
import org.commonmark.node.Node;
|
||||||
import org.commonmark.parser.Parser;
|
import org.commonmark.parser.Parser;
|
||||||
|
|
||||||
import ru.noties.markwon.core.CorePlugin;
|
import io.noties.markwon.core.MarkwonTheme;
|
||||||
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)
|
* Class that extends {@link MarkwonPlugin} with all methods implemented (empty body)
|
||||||
@ -22,6 +19,11 @@ import ru.noties.markwon.priority.Priority;
|
|||||||
*/
|
*/
|
||||||
public abstract class AbstractMarkwonPlugin implements MarkwonPlugin {
|
public abstract class AbstractMarkwonPlugin implements MarkwonPlugin {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(@NonNull Registry registry) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configureParser(@NonNull Parser.Builder builder) {
|
public void configureParser(@NonNull Parser.Builder builder) {
|
||||||
|
|
||||||
@ -32,11 +34,6 @@ public abstract class AbstractMarkwonPlugin implements MarkwonPlugin {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
|
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
|
||||||
|
|
||||||
@ -52,18 +49,6 @@ public abstract class AbstractMarkwonPlugin implements MarkwonPlugin {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configureHtmlRenderer(@NonNull MarkwonHtmlRenderer.Builder builder) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Priority priority() {
|
|
||||||
// by default all come after CorePlugin
|
|
||||||
return Priority.after(CorePlugin.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String processMarkdown(@NonNull String markdown) {
|
public String processMarkdown(@NonNull String markdown) {
|
@ -0,0 +1,14 @@
|
|||||||
|
package io.noties.markwon;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see LinkResolverDef
|
||||||
|
* @see MarkwonConfiguration.Builder#linkResolver(LinkResolver)
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
public interface LinkResolver {
|
||||||
|
void resolve(@NonNull View view, @NonNull String link);
|
||||||
|
}
|
@ -1,19 +1,18 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon;
|
||||||
|
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.provider.Browser;
|
import android.provider.Browser;
|
||||||
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.core.spans.LinkSpan;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
public class LinkResolverDef implements LinkSpan.Resolver {
|
public class LinkResolverDef implements LinkResolver {
|
||||||
@Override
|
@Override
|
||||||
public void resolve(View view, @NonNull String link) {
|
public void resolve(@NonNull View view, @NonNull String link) {
|
||||||
final Uri uri = Uri.parse(link);
|
final Uri uri = Uri.parse(link);
|
||||||
final Context context = view.getContext();
|
final Context context = view.getContext();
|
||||||
final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
@ -1,14 +1,15 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.commonmark.node.Node;
|
import org.commonmark.node.Node;
|
||||||
|
|
||||||
import ru.noties.markwon.core.CorePlugin;
|
import io.noties.markwon.core.CorePlugin;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to parse and render markdown. Since version 3.0.0 instance specific (previously consisted
|
* Class to parse and render markdown. Since version 3.0.0 instance specific (previously consisted
|
||||||
@ -37,13 +38,26 @@ public abstract class Markwon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory method to obtain an instance of {@link Builder}.
|
* Factory method to obtain an instance of {@link Builder} with {@link CorePlugin} added.
|
||||||
*
|
*
|
||||||
* @see Builder
|
* @see Builder
|
||||||
|
* @see #builderNoCore(Context)
|
||||||
* @since 3.0.0
|
* @since 3.0.0
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public static Builder builder(@NonNull Context context) {
|
public static Builder builder(@NonNull Context context) {
|
||||||
|
return new MarkwonBuilderImpl(context)
|
||||||
|
// @since 4.0.0 add CorePlugin
|
||||||
|
.usePlugin(CorePlugin.create());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method to obtain an instance of {@link Builder} without {@link CorePlugin}.
|
||||||
|
*
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static Builder builderNoCore(@NonNull Context context) {
|
||||||
return new MarkwonBuilderImpl(context);
|
return new MarkwonBuilderImpl(context);
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,110 @@
|
|||||||
|
package io.noties.markwon;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.commonmark.parser.Parser;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.noties.markwon.core.MarkwonTheme;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
|
class MarkwonBuilderImpl implements Markwon.Builder {
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
private final List<MarkwonPlugin> plugins = new ArrayList<>(3);
|
||||||
|
|
||||||
|
private TextView.BufferType bufferType = TextView.BufferType.SPANNABLE;
|
||||||
|
|
||||||
|
MarkwonBuilderImpl(@NonNull Context context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Markwon.Builder bufferType(@NonNull TextView.BufferType bufferType) {
|
||||||
|
this.bufferType = bufferType;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Markwon.Builder usePlugin(@NonNull MarkwonPlugin plugin) {
|
||||||
|
plugins.add(plugin);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Markwon.Builder usePlugins(@NonNull Iterable<? extends MarkwonPlugin> plugins) {
|
||||||
|
|
||||||
|
final Iterator<? extends MarkwonPlugin> iterator = plugins.iterator();
|
||||||
|
|
||||||
|
MarkwonPlugin plugin;
|
||||||
|
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
plugin = iterator.next();
|
||||||
|
if (plugin == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
this.plugins.add(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Markwon build() {
|
||||||
|
|
||||||
|
if (plugins.isEmpty()) {
|
||||||
|
throw new IllegalStateException("No plugins were added to this builder. Use #usePlugin " +
|
||||||
|
"method to add them");
|
||||||
|
}
|
||||||
|
|
||||||
|
// please note that this method must not modify supplied collection
|
||||||
|
// if nothing should be done -> the same collection can be returned
|
||||||
|
final List<MarkwonPlugin> plugins = preparePlugins(this.plugins);
|
||||||
|
|
||||||
|
final Parser.Builder parserBuilder = new Parser.Builder();
|
||||||
|
final MarkwonTheme.Builder themeBuilder = MarkwonTheme.builderWithDefaults(context);
|
||||||
|
final MarkwonConfiguration.Builder configurationBuilder = new MarkwonConfiguration.Builder();
|
||||||
|
final MarkwonVisitor.Builder visitorBuilder = new MarkwonVisitorImpl.BuilderImpl();
|
||||||
|
final MarkwonSpansFactory.Builder spanFactoryBuilder = new MarkwonSpansFactoryImpl.BuilderImpl();
|
||||||
|
|
||||||
|
for (MarkwonPlugin plugin : plugins) {
|
||||||
|
plugin.configureParser(parserBuilder);
|
||||||
|
plugin.configureTheme(themeBuilder);
|
||||||
|
plugin.configureConfiguration(configurationBuilder);
|
||||||
|
plugin.configureVisitor(visitorBuilder);
|
||||||
|
plugin.configureSpansFactory(spanFactoryBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
final MarkwonConfiguration configuration = configurationBuilder.build(
|
||||||
|
themeBuilder.build(),
|
||||||
|
spanFactoryBuilder.build());
|
||||||
|
|
||||||
|
final RenderProps renderProps = new RenderPropsImpl();
|
||||||
|
|
||||||
|
return new MarkwonImpl(
|
||||||
|
bufferType,
|
||||||
|
parserBuilder.build(),
|
||||||
|
visitorBuilder.build(configuration, renderProps),
|
||||||
|
Collections.unmodifiableList(plugins)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static List<MarkwonPlugin> preparePlugins(@NonNull List<MarkwonPlugin> plugins) {
|
||||||
|
return new RegistryImpl(plugins).process();
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +1,15 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import ru.noties.markwon.core.MarkwonTheme;
|
import io.noties.markwon.core.MarkwonTheme;
|
||||||
import ru.noties.markwon.core.spans.LinkSpan;
|
import io.noties.markwon.image.AsyncDrawableLoader;
|
||||||
import ru.noties.markwon.html.MarkwonHtmlParser;
|
import io.noties.markwon.image.ImageSizeResolver;
|
||||||
import ru.noties.markwon.html.MarkwonHtmlRenderer;
|
import io.noties.markwon.image.ImageSizeResolverDef;
|
||||||
import ru.noties.markwon.image.AsyncDrawableLoader;
|
import io.noties.markwon.syntax.SyntaxHighlight;
|
||||||
import ru.noties.markwon.image.ImageSizeResolver;
|
import io.noties.markwon.syntax.SyntaxHighlightNoOp;
|
||||||
import ru.noties.markwon.image.ImageSizeResolverDef;
|
import io.noties.markwon.urlprocessor.UrlProcessor;
|
||||||
import ru.noties.markwon.syntax.SyntaxHighlight;
|
import io.noties.markwon.urlprocessor.UrlProcessorNoOp;
|
||||||
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`
|
* since 3.0.0 renamed `SpannableConfiguration` -> `MarkwonConfiguration`
|
||||||
@ -28,11 +25,9 @@ public class MarkwonConfiguration {
|
|||||||
private final MarkwonTheme theme;
|
private final MarkwonTheme theme;
|
||||||
private final AsyncDrawableLoader asyncDrawableLoader;
|
private final AsyncDrawableLoader asyncDrawableLoader;
|
||||||
private final SyntaxHighlight syntaxHighlight;
|
private final SyntaxHighlight syntaxHighlight;
|
||||||
private final LinkSpan.Resolver linkResolver;
|
private final LinkResolver linkResolver;
|
||||||
private final UrlProcessor urlProcessor;
|
private final UrlProcessor urlProcessor;
|
||||||
private final ImageSizeResolver imageSizeResolver;
|
private final ImageSizeResolver imageSizeResolver;
|
||||||
private final MarkwonHtmlParser htmlParser;
|
|
||||||
private final MarkwonHtmlRenderer htmlRenderer;
|
|
||||||
|
|
||||||
// @since 3.0.0
|
// @since 3.0.0
|
||||||
private final MarkwonSpansFactory spansFactory;
|
private final MarkwonSpansFactory spansFactory;
|
||||||
@ -45,8 +40,6 @@ public class MarkwonConfiguration {
|
|||||||
this.urlProcessor = builder.urlProcessor;
|
this.urlProcessor = builder.urlProcessor;
|
||||||
this.imageSizeResolver = builder.imageSizeResolver;
|
this.imageSizeResolver = builder.imageSizeResolver;
|
||||||
this.spansFactory = builder.spansFactory;
|
this.spansFactory = builder.spansFactory;
|
||||||
this.htmlParser = builder.htmlParser;
|
|
||||||
this.htmlRenderer = builder.htmlRenderer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -65,7 +58,7 @@ public class MarkwonConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public LinkSpan.Resolver linkResolver() {
|
public LinkResolver linkResolver() {
|
||||||
return linkResolver;
|
return linkResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,16 +72,6 @@ public class MarkwonConfiguration {
|
|||||||
return imageSizeResolver;
|
return imageSizeResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public MarkwonHtmlParser htmlParser() {
|
|
||||||
return htmlParser;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public MarkwonHtmlRenderer htmlRenderer() {
|
|
||||||
return htmlRenderer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 3.0.0
|
* @since 3.0.0
|
||||||
*/
|
*/
|
||||||
@ -103,16 +86,23 @@ public class MarkwonConfiguration {
|
|||||||
private MarkwonTheme theme;
|
private MarkwonTheme theme;
|
||||||
private AsyncDrawableLoader asyncDrawableLoader;
|
private AsyncDrawableLoader asyncDrawableLoader;
|
||||||
private SyntaxHighlight syntaxHighlight;
|
private SyntaxHighlight syntaxHighlight;
|
||||||
private LinkSpan.Resolver linkResolver;
|
private LinkResolver linkResolver;
|
||||||
private UrlProcessor urlProcessor;
|
private UrlProcessor urlProcessor;
|
||||||
private ImageSizeResolver imageSizeResolver;
|
private ImageSizeResolver imageSizeResolver;
|
||||||
private MarkwonHtmlParser htmlParser;
|
|
||||||
private MarkwonHtmlRenderer htmlRenderer;
|
|
||||||
private MarkwonSpansFactory spansFactory;
|
private MarkwonSpansFactory spansFactory;
|
||||||
|
|
||||||
Builder() {
|
Builder() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public Builder asyncDrawableLoader(@NonNull AsyncDrawableLoader asyncDrawableLoader) {
|
||||||
|
this.asyncDrawableLoader = asyncDrawableLoader;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public Builder syntaxHighlight(@NonNull SyntaxHighlight syntaxHighlight) {
|
public Builder syntaxHighlight(@NonNull SyntaxHighlight syntaxHighlight) {
|
||||||
this.syntaxHighlight = syntaxHighlight;
|
this.syntaxHighlight = syntaxHighlight;
|
||||||
@ -120,7 +110,7 @@ public class MarkwonConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public Builder linkResolver(@NonNull LinkSpan.Resolver linkResolver) {
|
public Builder linkResolver(@NonNull LinkResolver linkResolver) {
|
||||||
this.linkResolver = linkResolver;
|
this.linkResolver = linkResolver;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -131,12 +121,6 @@ public class MarkwonConfiguration {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public Builder htmlParser(@NonNull MarkwonHtmlParser htmlParser) {
|
|
||||||
this.htmlParser = htmlParser;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 1.0.1
|
* @since 1.0.1
|
||||||
*/
|
*/
|
||||||
@ -149,15 +133,16 @@ public class MarkwonConfiguration {
|
|||||||
@NonNull
|
@NonNull
|
||||||
public MarkwonConfiguration build(
|
public MarkwonConfiguration build(
|
||||||
@NonNull MarkwonTheme theme,
|
@NonNull MarkwonTheme theme,
|
||||||
@NonNull AsyncDrawableLoader asyncDrawableLoader,
|
|
||||||
@NonNull MarkwonHtmlRenderer htmlRenderer,
|
|
||||||
@NonNull MarkwonSpansFactory spansFactory) {
|
@NonNull MarkwonSpansFactory spansFactory) {
|
||||||
|
|
||||||
this.theme = theme;
|
this.theme = theme;
|
||||||
this.asyncDrawableLoader = asyncDrawableLoader;
|
|
||||||
this.htmlRenderer = htmlRenderer;
|
|
||||||
this.spansFactory = spansFactory;
|
this.spansFactory = spansFactory;
|
||||||
|
|
||||||
|
// @since 4.0.0
|
||||||
|
if (asyncDrawableLoader == null) {
|
||||||
|
asyncDrawableLoader = AsyncDrawableLoader.noOp();
|
||||||
|
}
|
||||||
|
|
||||||
if (syntaxHighlight == null) {
|
if (syntaxHighlight == null) {
|
||||||
syntaxHighlight = new SyntaxHighlightNoOp();
|
syntaxHighlight = new SyntaxHighlightNoOp();
|
||||||
}
|
}
|
||||||
@ -174,10 +159,6 @@ public class MarkwonConfiguration {
|
|||||||
imageSizeResolver = new ImageSizeResolverDef();
|
imageSizeResolver = new ImageSizeResolverDef();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (htmlParser == null) {
|
|
||||||
htmlParser = MarkwonHtmlParser.noOp();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new MarkwonConfiguration(this);
|
return new MarkwonConfiguration(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,10 +1,11 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.commonmark.node.Node;
|
import org.commonmark.node.Node;
|
||||||
import org.commonmark.parser.Parser;
|
import org.commonmark.parser.Parser;
|
||||||
|
|
@ -1,30 +1,58 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.commonmark.node.Node;
|
import org.commonmark.node.Node;
|
||||||
import org.commonmark.parser.Parser;
|
import org.commonmark.parser.Parser;
|
||||||
|
|
||||||
import ru.noties.markwon.core.MarkwonTheme;
|
import io.noties.markwon.core.CorePlugin;
|
||||||
import ru.noties.markwon.html.MarkwonHtmlRenderer;
|
import io.noties.markwon.core.MarkwonTheme;
|
||||||
import ru.noties.markwon.image.AsyncDrawableLoader;
|
import io.noties.markwon.image.AsyncDrawableSpan;
|
||||||
import ru.noties.markwon.image.MediaDecoder;
|
import io.noties.markwon.movement.MovementMethodPlugin;
|
||||||
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
|
* Class represents a plugin (extension) to Markwon to configure how parsing and rendering
|
||||||
* of markdown is carried on.
|
* of markdown is carried on.
|
||||||
*
|
*
|
||||||
* @see AbstractMarkwonPlugin
|
* @see AbstractMarkwonPlugin
|
||||||
* @see ru.noties.markwon.core.CorePlugin
|
* @see CorePlugin
|
||||||
* @see ru.noties.markwon.image.ImagesPlugin
|
* @see MovementMethodPlugin
|
||||||
* @since 3.0.0
|
* @since 3.0.0
|
||||||
*/
|
*/
|
||||||
public interface MarkwonPlugin {
|
public interface MarkwonPlugin {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see Registry#require(Class, Action)
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
interface Action<P extends MarkwonPlugin> {
|
||||||
|
void apply(@NonNull P p);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see #configure(Registry)
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
interface Registry {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
<P extends MarkwonPlugin> P require(@NonNull Class<P> plugin);
|
||||||
|
|
||||||
|
<P extends MarkwonPlugin> void require(
|
||||||
|
@NonNull Class<P> plugin,
|
||||||
|
@NonNull Action<? super P> action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will be called before any other during {@link Markwon} instance construction.
|
||||||
|
*
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
void configure(@NonNull Registry registry);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to configure <code>org.commonmark.parser.Parser</code> (for example register custom
|
* Method to configure <code>org.commonmark.parser.Parser</code> (for example register custom
|
||||||
* extension, etc).
|
* extension, etc).
|
||||||
@ -39,17 +67,6 @@ public interface MarkwonPlugin {
|
|||||||
*/
|
*/
|
||||||
void configureTheme(@NonNull MarkwonTheme.Builder 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}
|
* Configure {@link MarkwonConfiguration}
|
||||||
*
|
*
|
||||||
@ -74,17 +91,6 @@ public interface MarkwonPlugin {
|
|||||||
*/
|
*/
|
||||||
void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder 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.
|
* 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.
|
* Can be described as <code>pre-processing</code> of markdown String.
|
||||||
@ -117,9 +123,9 @@ public interface MarkwonPlugin {
|
|||||||
/**
|
/**
|
||||||
* This method will be called <strong>before</strong> calling <code>TextView#setText</code>.
|
* This method will be called <strong>before</strong> calling <code>TextView#setText</code>.
|
||||||
* <p>
|
* <p>
|
||||||
* It can be useful to prepare a TextView for markdown. For example {@link ru.noties.markwon.image.ImagesPlugin}
|
* It can be useful to prepare a TextView for markdown. For example {@code ru.noties.markwon.image.ImagesPlugin}
|
||||||
* uses this method to unregister previously registered {@link ru.noties.markwon.image.AsyncDrawableSpan}
|
* uses this method to unregister previously registered {@link AsyncDrawableSpan}
|
||||||
* (if there are such spans in this TextView at this point). Or {@link ru.noties.markwon.core.CorePlugin}
|
* (if there are such spans in this TextView at this point). Or {@link CorePlugin}
|
||||||
* which measures ordered list numbers
|
* which measures ordered list numbers
|
||||||
*
|
*
|
||||||
* @param textView TextView to which <code>markdown</code> will be applied
|
* @param textView TextView to which <code>markdown</code> will be applied
|
||||||
@ -130,8 +136,8 @@ public interface MarkwonPlugin {
|
|||||||
/**
|
/**
|
||||||
* This method will be called <strong>after</strong> markdown was applied.
|
* This method will be called <strong>after</strong> markdown was applied.
|
||||||
* <p>
|
* <p>
|
||||||
* It can be useful to trigger certain action on spans/textView. For example {@link ru.noties.markwon.image.ImagesPlugin}
|
* It can be useful to trigger certain action on spans/textView. For example {@code ru.noties.markwon.image.ImagesPlugin}
|
||||||
* uses this method to register {@link ru.noties.markwon.image.AsyncDrawableSpan} and start
|
* uses this method to register {@link AsyncDrawableSpan} and start
|
||||||
* asynchronously loading images.
|
* asynchronously loading images.
|
||||||
* <p>
|
* <p>
|
||||||
* Unlike {@link #beforeSetText(TextView, Spanned)} this method does not receive parsed markdown
|
* Unlike {@link #beforeSetText(TextView, Spanned)} this method does not receive parsed markdown
|
@ -1,6 +1,6 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.commonmark.node.Node;
|
import org.commonmark.node.Node;
|
||||||
|
|
@ -1,7 +1,7 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.commonmark.node.Node;
|
import org.commonmark.node.Node;
|
||||||
|
|
@ -1,7 +1,7 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.commonmark.node.Node;
|
import org.commonmark.node.Node;
|
||||||
|
|
@ -1,7 +1,7 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.commonmark.node.Node;
|
import org.commonmark.node.Node;
|
||||||
import org.commonmark.node.Visitor;
|
import org.commonmark.node.Visitor;
|
@ -1,7 +1,7 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.commonmark.node.BlockQuote;
|
import org.commonmark.node.BlockQuote;
|
||||||
import org.commonmark.node.BulletList;
|
import org.commonmark.node.BulletList;
|
@ -1,7 +1,7 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to hold data in {@link RenderProps}. Represents a certain <em>property</em>.
|
* Class to hold data in {@link RenderProps}. Represents a certain <em>property</em>.
|
116
markwon-core/src/main/java/io/noties/markwon/RegistryImpl.java
Normal file
116
markwon-core/src/main/java/io/noties/markwon/RegistryImpl.java
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
package io.noties.markwon;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import io.noties.markwon.core.CorePlugin;
|
||||||
|
|
||||||
|
// @since 4.0.0
|
||||||
|
class RegistryImpl implements MarkwonPlugin.Registry {
|
||||||
|
|
||||||
|
private final List<MarkwonPlugin> origin;
|
||||||
|
private final List<MarkwonPlugin> plugins;
|
||||||
|
private final Set<MarkwonPlugin> pending;
|
||||||
|
|
||||||
|
RegistryImpl(@NonNull List<MarkwonPlugin> origin) {
|
||||||
|
this.origin = origin;
|
||||||
|
this.plugins = new ArrayList<>(origin.size());
|
||||||
|
this.pending = new HashSet<>(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public <P extends MarkwonPlugin> P require(@NonNull Class<P> plugin) {
|
||||||
|
return get(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <P extends MarkwonPlugin> void require(
|
||||||
|
@NonNull Class<P> plugin,
|
||||||
|
@NonNull MarkwonPlugin.Action<? super P> action) {
|
||||||
|
action.apply(get(plugin));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
List<MarkwonPlugin> process() {
|
||||||
|
for (MarkwonPlugin plugin : origin) {
|
||||||
|
configure(plugin);
|
||||||
|
}
|
||||||
|
return plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void configure(@NonNull MarkwonPlugin plugin) {
|
||||||
|
|
||||||
|
// important -> check if it's in plugins
|
||||||
|
// if it is -> no need to configure (already configured)
|
||||||
|
|
||||||
|
if (!plugins.contains(plugin)) {
|
||||||
|
|
||||||
|
if (pending.contains(plugin)) {
|
||||||
|
throw new IllegalStateException("Cyclic dependency chain found: " + pending);
|
||||||
|
}
|
||||||
|
|
||||||
|
// start tracking plugins that are pending for configuration
|
||||||
|
pending.add(plugin);
|
||||||
|
|
||||||
|
plugin.configure(this);
|
||||||
|
|
||||||
|
// stop pending tracking
|
||||||
|
pending.remove(plugin);
|
||||||
|
|
||||||
|
// check again if it's included (a child might've configured it already)
|
||||||
|
// add to out-collection if not already present
|
||||||
|
// this is a bit different from `find` method as it does check for exact instance
|
||||||
|
// and not a sub-type
|
||||||
|
if (!plugins.contains(plugin)) {
|
||||||
|
// core-plugin must always be the first one (if it's present)
|
||||||
|
if (CorePlugin.class.isAssignableFrom(plugin.getClass())) {
|
||||||
|
plugins.add(0, plugin);
|
||||||
|
} else {
|
||||||
|
plugins.add(plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private <P extends MarkwonPlugin> P get(@NonNull Class<P> type) {
|
||||||
|
|
||||||
|
// check if present already in plugins
|
||||||
|
// find in origin, if not found -> throw, else add to out-plugins
|
||||||
|
|
||||||
|
P plugin = find(plugins, type);
|
||||||
|
|
||||||
|
if (plugin == null) {
|
||||||
|
|
||||||
|
plugin = find(origin, type);
|
||||||
|
|
||||||
|
if (plugin == null) {
|
||||||
|
throw new IllegalStateException("Requested plugin is not added: " +
|
||||||
|
"" + type.getName() + ", plugins: " + origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
configure(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
return plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static <P extends MarkwonPlugin> P find(
|
||||||
|
@NonNull List<MarkwonPlugin> plugins,
|
||||||
|
@NonNull Class<P> type) {
|
||||||
|
for (MarkwonPlugin plugin : plugins) {
|
||||||
|
if (type.isAssignableFrom(plugin.getClass())) {
|
||||||
|
//noinspection unchecked
|
||||||
|
return (P) plugin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 3.0.0
|
* @since 3.0.0
|
@ -1,7 +1,7 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
@ -1,7 +1,7 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 3.0.0
|
* @since 3.0.0
|
@ -1,11 +1,12 @@
|
|||||||
package ru.noties.markwon;
|
package io.noties.markwon;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.annotation.VisibleForTesting;
|
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
@ -1,12 +1,13 @@
|
|||||||
package ru.noties.markwon.core;
|
package io.noties.markwon.core;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.annotation.VisibleForTesting;
|
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.method.LinkMovementMethod;
|
import android.text.method.LinkMovementMethod;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import org.commonmark.node.BlockQuote;
|
import org.commonmark.node.BlockQuote;
|
||||||
import org.commonmark.node.BulletList;
|
import org.commonmark.node.BulletList;
|
||||||
import org.commonmark.node.Code;
|
import org.commonmark.node.Code;
|
||||||
@ -14,6 +15,7 @@ import org.commonmark.node.Emphasis;
|
|||||||
import org.commonmark.node.FencedCodeBlock;
|
import org.commonmark.node.FencedCodeBlock;
|
||||||
import org.commonmark.node.HardLineBreak;
|
import org.commonmark.node.HardLineBreak;
|
||||||
import org.commonmark.node.Heading;
|
import org.commonmark.node.Heading;
|
||||||
|
import org.commonmark.node.Image;
|
||||||
import org.commonmark.node.IndentedCodeBlock;
|
import org.commonmark.node.IndentedCodeBlock;
|
||||||
import org.commonmark.node.Link;
|
import org.commonmark.node.Link;
|
||||||
import org.commonmark.node.ListBlock;
|
import org.commonmark.node.ListBlock;
|
||||||
@ -26,21 +28,27 @@ import org.commonmark.node.StrongEmphasis;
|
|||||||
import org.commonmark.node.Text;
|
import org.commonmark.node.Text;
|
||||||
import org.commonmark.node.ThematicBreak;
|
import org.commonmark.node.ThematicBreak;
|
||||||
|
|
||||||
import ru.noties.markwon.AbstractMarkwonPlugin;
|
import java.util.ArrayList;
|
||||||
import ru.noties.markwon.MarkwonConfiguration;
|
import java.util.List;
|
||||||
import ru.noties.markwon.MarkwonSpansFactory;
|
|
||||||
import ru.noties.markwon.MarkwonVisitor;
|
import io.noties.markwon.AbstractMarkwonPlugin;
|
||||||
import ru.noties.markwon.core.factory.BlockQuoteSpanFactory;
|
import io.noties.markwon.MarkwonConfiguration;
|
||||||
import ru.noties.markwon.core.factory.CodeBlockSpanFactory;
|
import io.noties.markwon.MarkwonSpansFactory;
|
||||||
import ru.noties.markwon.core.factory.CodeSpanFactory;
|
import io.noties.markwon.MarkwonVisitor;
|
||||||
import ru.noties.markwon.core.factory.EmphasisSpanFactory;
|
import io.noties.markwon.RenderProps;
|
||||||
import ru.noties.markwon.core.factory.HeadingSpanFactory;
|
import io.noties.markwon.SpanFactory;
|
||||||
import ru.noties.markwon.core.factory.LinkSpanFactory;
|
import io.noties.markwon.SpannableBuilder;
|
||||||
import ru.noties.markwon.core.factory.ListItemSpanFactory;
|
import io.noties.markwon.core.factory.BlockQuoteSpanFactory;
|
||||||
import ru.noties.markwon.core.factory.StrongEmphasisSpanFactory;
|
import io.noties.markwon.core.factory.CodeBlockSpanFactory;
|
||||||
import ru.noties.markwon.core.factory.ThematicBreakSpanFactory;
|
import io.noties.markwon.core.factory.CodeSpanFactory;
|
||||||
import ru.noties.markwon.core.spans.OrderedListItemSpan;
|
import io.noties.markwon.core.factory.EmphasisSpanFactory;
|
||||||
import ru.noties.markwon.priority.Priority;
|
import io.noties.markwon.core.factory.HeadingSpanFactory;
|
||||||
|
import io.noties.markwon.core.factory.LinkSpanFactory;
|
||||||
|
import io.noties.markwon.core.factory.ListItemSpanFactory;
|
||||||
|
import io.noties.markwon.core.factory.StrongEmphasisSpanFactory;
|
||||||
|
import io.noties.markwon.core.factory.ThematicBreakSpanFactory;
|
||||||
|
import io.noties.markwon.core.spans.OrderedListItemSpan;
|
||||||
|
import io.noties.markwon.image.ImageProps;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see CoreProps
|
* @see CoreProps
|
||||||
@ -48,14 +56,57 @@ import ru.noties.markwon.priority.Priority;
|
|||||||
*/
|
*/
|
||||||
public class CorePlugin extends AbstractMarkwonPlugin {
|
public class CorePlugin extends AbstractMarkwonPlugin {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see #addOnTextAddedListener(OnTextAddedListener)
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
public interface OnTextAddedListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will be called when new text is added to resulting {@link SpannableBuilder}.
|
||||||
|
* Please note that only text represented by {@link Text} node will trigger this callback
|
||||||
|
* (text inside code and code-blocks won\'t trigger it).
|
||||||
|
* <p>
|
||||||
|
* Please note that if you wish to add spans you must use {@code start} parameter
|
||||||
|
* in order to place spans correctly ({@code start} represents the index at which {@code text}
|
||||||
|
* was added). So, to set a span for the whole length of the text added one should use:
|
||||||
|
* <p>
|
||||||
|
* {@code
|
||||||
|
* visitor.builder().setSpan(new MySpan(), start, start + text.length(), 0);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @param visitor {@link MarkwonVisitor}
|
||||||
|
* @param text literal that had been added
|
||||||
|
* @param start index in {@code visitor} as which text had been added
|
||||||
|
* @see #addOnTextAddedListener(OnTextAddedListener)
|
||||||
|
*/
|
||||||
|
void onTextAdded(@NonNull MarkwonVisitor visitor, @NonNull String text, int start);
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static CorePlugin create() {
|
public static CorePlugin create() {
|
||||||
return new CorePlugin();
|
return new CorePlugin();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @since 4.0.0
|
||||||
|
private final List<OnTextAddedListener> onTextAddedListeners = new ArrayList<>(0);
|
||||||
|
|
||||||
protected CorePlugin() {
|
protected CorePlugin() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be useful to post-process text added. For example for auto-linking capabilities.
|
||||||
|
*
|
||||||
|
* @see OnTextAddedListener
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("UnusedReturnValue")
|
||||||
|
@NonNull
|
||||||
|
public CorePlugin addOnTextAddedListener(@NonNull OnTextAddedListener onTextAddedListener) {
|
||||||
|
onTextAddedListeners.add(onTextAddedListener);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
|
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
|
||||||
text(builder);
|
text(builder);
|
||||||
@ -65,6 +116,7 @@ public class CorePlugin extends AbstractMarkwonPlugin {
|
|||||||
code(builder);
|
code(builder);
|
||||||
fencedCodeBlock(builder);
|
fencedCodeBlock(builder);
|
||||||
indentedCodeBlock(builder);
|
indentedCodeBlock(builder);
|
||||||
|
image(builder);
|
||||||
bulletList(builder);
|
bulletList(builder);
|
||||||
orderedList(builder);
|
orderedList(builder);
|
||||||
listItem(builder);
|
listItem(builder);
|
||||||
@ -95,12 +147,6 @@ public class CorePlugin extends AbstractMarkwonPlugin {
|
|||||||
.setFactory(ThematicBreak.class, new ThematicBreakSpanFactory());
|
.setFactory(ThematicBreak.class, new ThematicBreakSpanFactory());
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Priority priority() {
|
|
||||||
return Priority.none();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
|
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
|
||||||
OrderedListItemSpan.measure(textView, markdown);
|
OrderedListItemSpan.measure(textView, markdown);
|
||||||
@ -116,11 +162,23 @@ public class CorePlugin extends AbstractMarkwonPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void text(@NonNull MarkwonVisitor.Builder builder) {
|
private void text(@NonNull MarkwonVisitor.Builder builder) {
|
||||||
builder.on(Text.class, new MarkwonVisitor.NodeVisitor<Text>() {
|
builder.on(Text.class, new MarkwonVisitor.NodeVisitor<Text>() {
|
||||||
@Override
|
@Override
|
||||||
public void visit(@NonNull MarkwonVisitor visitor, @NonNull Text text) {
|
public void visit(@NonNull MarkwonVisitor visitor, @NonNull Text text) {
|
||||||
visitor.builder().append(text.getLiteral());
|
|
||||||
|
final String literal = text.getLiteral();
|
||||||
|
|
||||||
|
visitor.builder().append(literal);
|
||||||
|
|
||||||
|
// @since 4.0.0
|
||||||
|
if (!onTextAddedListeners.isEmpty()) {
|
||||||
|
// calculate the start position
|
||||||
|
final int length = visitor.length() - literal.length();
|
||||||
|
for (OnTextAddedListener onTextAddedListener : onTextAddedListeners) {
|
||||||
|
onTextAddedListener.onTextAdded(visitor, literal, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -204,6 +262,53 @@ public class CorePlugin extends AbstractMarkwonPlugin {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @since 4.0.0
|
||||||
|
// his method is moved from ImagesPlugin. Alternative implementations must set SpanFactory
|
||||||
|
// for Image node in order for this visitor to function
|
||||||
|
private static void image(MarkwonVisitor.Builder builder) {
|
||||||
|
builder.on(Image.class, new MarkwonVisitor.NodeVisitor<Image>() {
|
||||||
|
@Override
|
||||||
|
public void visit(@NonNull MarkwonVisitor visitor, @NonNull Image image) {
|
||||||
|
|
||||||
|
// if there is no image spanFactory, ignore
|
||||||
|
final SpanFactory spanFactory = visitor.configuration().spansFactory().get(Image.class);
|
||||||
|
if (spanFactory == null) {
|
||||||
|
visitor.visitChildren(image);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int length = visitor.length();
|
||||||
|
|
||||||
|
visitor.visitChildren(image);
|
||||||
|
|
||||||
|
// we must check if anything _was_ added, as we need at least one char to render
|
||||||
|
if (length == visitor.length()) {
|
||||||
|
visitor.builder().append('\uFFFC');
|
||||||
|
}
|
||||||
|
|
||||||
|
final MarkwonConfiguration configuration = visitor.configuration();
|
||||||
|
|
||||||
|
final Node parent = image.getParent();
|
||||||
|
final boolean link = parent instanceof Link;
|
||||||
|
|
||||||
|
final String destination = configuration
|
||||||
|
.urlProcessor()
|
||||||
|
.process(image.getDestination());
|
||||||
|
|
||||||
|
final RenderProps props = visitor.renderProps();
|
||||||
|
|
||||||
|
// apply image properties
|
||||||
|
// Please note that we explicitly set IMAGE_SIZE to null as we do not clear
|
||||||
|
// properties after we applied span (we could though)
|
||||||
|
ImageProps.DESTINATION.set(props, destination);
|
||||||
|
ImageProps.REPLACEMENT_TEXT_IS_LINK.set(props, link);
|
||||||
|
ImageProps.IMAGE_SIZE.set(props, null);
|
||||||
|
|
||||||
|
visitor.setSpans(length, spanFactory.getSpans(configuration, props));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static void visitCodeBlock(
|
static void visitCodeBlock(
|
||||||
@NonNull MarkwonVisitor visitor,
|
@NonNull MarkwonVisitor visitor,
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user