Started with v4 documentation
This commit is contained in:
parent
14591508b5
commit
8b0edc32c3
@ -97,15 +97,6 @@ Please visit [documentation] web-site for reference
|
||||
|
||||
[documentation]: https://noties.github.io/Markwon
|
||||
|
||||
---
|
||||
|
||||
## Applications using Markwon
|
||||
|
||||
* [Partiko](https://partiko.app)
|
||||
* [FairNote Notepad](https://play.google.com/store/apps/details?id=com.rgiskard.fairnote)
|
||||
* [Boxcryptor](https://www.boxcryptor.com)
|
||||
|
||||
|
||||
---
|
||||
|
||||
# Demo
|
||||
|
@ -10,3 +10,4 @@
|
||||
* removed MarkwonPlugin#configureHtmlRenderer -> now part of HtmlPlugin
|
||||
* TagHandler now has `supportedTags()` method
|
||||
* html is moved completely to html-plugin
|
||||
* OnTextAddedListener
|
@ -1,4 +1,4 @@
|
||||
|
||||
// this is a generated file, do not modify. To update it run 'collectArtifacts.js' script
|
||||
const artifacts = [{"id":"core","name":"Core","group":"ru.noties.markwon","description":"Core Markwon artifact that includes basic markdown parsing and rendering"},{"id":"ext-latex","name":"LaTeX","group":"ru.noties.markwon","description":"Extension to add LaTeX formulas to Markwon markdown"},{"id":"ext-strikethrough","name":"Strikethrough","group":"ru.noties.markwon","description":"Extension to add strikethrough markup to Markwon markdown"},{"id":"ext-tables","name":"Tables","group":"ru.noties.markwon","description":"Extension to add tables markup (GFM) to Markwon markdown"},{"id":"ext-tasklist","name":"Task List","group":"ru.noties.markwon","description":"Extension to add task lists (GFM) to Markwon markdown"},{"id":"html","name":"HTML","group":"ru.noties.markwon","description":"Provides HTML parsing functionality"},{"id":"image-gif","name":"Image GIF","group":"ru.noties.markwon","description":"Adds GIF media support to Markwon markdown"},{"id":"image-okhttp","name":"Image OkHttp","group":"ru.noties.markwon","description":"Adds OkHttp client to retrieve images data from network"},{"id":"image-svg","name":"Image SVG","group":"ru.noties.markwon","description":"Adds SVG media support to Markwon markdown"},{"id":"recycler","name":"Recycler","group":"ru.noties.markwon","description":"Provides RecyclerView.Adapter to display Markwon markdown"},{"id":"recycler-table","name":"Recycler Table","group":"ru.noties.markwon","description":"Provides MarkwonAdapter.Entry to render TableBlocks inside Android-native TableLayout widget"},{"id":"syntax-highlight","name":"Syntax Highlight","group":"ru.noties.markwon","description":"Add syntax highlight to Markwon markdown via Prism4j library"}];
|
||||
const artifacts = [{"id":"core","name":"Core","group":"io.noties.markwon","description":"Core Markwon artifact that includes basic markdown parsing and rendering"},{"id":"ext-latex","name":"LaTeX","group":"io.noties.markwon","description":"Extension to add LaTeX formulas to Markwon markdown"},{"id":"ext-strikethrough","name":"Strikethrough","group":"io.noties.markwon","description":"Extension to add strikethrough markup to Markwon markdown"},{"id":"ext-tables","name":"Tables","group":"io.noties.markwon","description":"Extension to add tables markup (GFM) to Markwon markdown"},{"id":"ext-tasklist","name":"Task List","group":"io.noties.markwon","description":"Extension to add task lists (GFM) to Markwon markdown"},{"id":"html","name":"HTML","group":"io.noties.markwon","description":"Provides HTML parsing functionality"},{"id":"image","name":"Image","group":"io.noties.markwon","description":"Markwon image loading module (with optional GIF and SVG support)"},{"id":"image-gif","name":"Image GIF","group":"io.noties.markwon","description":"Adds GIF media support to Markwon markdown"},{"id":"image-glide","name":"Image Glide","group":"io.noties.markwon","description":"Markwon image loading module (based on Glide library)"},{"id":"image-okhttp","name":"Image OkHttp","group":"io.noties.markwon","description":"Adds OkHttp client to retrieve images data from network"},{"id":"image-picasso","name":"Image Picasso","group":"io.noties.markwon","description":"Markwon image loading module (based on Picasso library)"},{"id":"image-svg","name":"Image SVG","group":"io.noties.markwon","description":"Adds SVG media support to Markwon markdown"},{"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":"syntax-highlight","name":"Syntax Highlight","group":"io.noties.markwon","description":"Add syntax highlight to Markwon markdown via Prism4j library"}];
|
||||
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>
|
||||
|
||||
<script>
|
||||
import { artifacts } from "../.artifacts.js";
|
||||
import { artifacts } from "../.artifacts.v3.js";
|
||||
|
||||
if (!artifacts) {
|
||||
throw "Artifacts not found. Use `collectArtifacts.js` script to obtain artifacts metadata.";
|
||||
|
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>
|
@ -18,10 +18,10 @@ module.exports = {
|
||||
text: 'API Version',
|
||||
items: [
|
||||
{ text: 'Current (3.x.x)', link: '/' },
|
||||
{ text: 'Beta (4.x.x)', link: '/docs/v4/install.md' },
|
||||
{ text: 'Legacy (2.x.x)', link: '/docs/v2/' }
|
||||
]
|
||||
},
|
||||
{ text: 'Sandbox', link: '/sandbox.md' },
|
||||
{ text: 'Github', link: 'https://github.com/noties/Markwon' }
|
||||
],
|
||||
sidebar: {
|
||||
@ -36,6 +36,27 @@ module.exports = {
|
||||
'/docs/v2/html.md',
|
||||
'/docs/v2/view.md'
|
||||
],
|
||||
'/docs/v4': [
|
||||
'/docs/v4/install.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/recipes.md',
|
||||
'/docs/v4/migration-3-4.md'
|
||||
],
|
||||
'/': [
|
||||
'',
|
||||
{
|
||||
|
173
docs/docs/v4/core/configuration.md
Normal file
173
docs/docs/v4/core/configuration.md
Normal file
@ -0,0 +1,173 @@
|
||||
# Configuration
|
||||
|
||||
`MarkwonConfiguration` class holds common Markwon functionality.
|
||||
These are _configurable_ properties:
|
||||
* `AsyncDrawableLoader` (back here since <Badge text="4.0.0" />)
|
||||
* `SyntaxHighlight`
|
||||
* `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:
|
||||
* [own implementation](/docs/v4/image.md) with SVG, GIF, data uri and android_assets support
|
||||
* [based on Picasso](/docs/v4/image-picasso.md)
|
||||
* [based on Glide](/docs/v4/image-glide.md)
|
||||
|
||||
## 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
|
||||
:::
|
||||
|
||||
## LinkSpan.Resolver
|
||||
|
||||
React to a link click event. By default `LinkResolverDef` is used,
|
||||
which tries to start an Activity given the `link` argument. If no
|
||||
Activity can handle `link` `LinkResolverDef` silently ignores click event
|
||||
|
||||
```java
|
||||
final Markwon markwon = Markwon.builder(this)
|
||||
.usePlugin(new AbstractMarkwonPlugin() {
|
||||
@Override
|
||||
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
|
||||
builder.linkResolver(new LinkSpan.Resolver() {
|
||||
@Override
|
||||
public void resolve(View view, @NonNull String link) {
|
||||
// react to link click here
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.build();
|
||||
```
|
||||
|
||||
:::tip
|
||||
Please note that `Markwon` will apply `LinkMovementMethod` to a resulting TextView
|
||||
if there is none registered. if you wish to register own instance of a `MovementMethod`
|
||||
apply it directly to a TextView or use [MovementMethodPlugin](/docs/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. Currently it
|
||||
handles only HTML images (specified via `img` tag).
|
||||
|
||||
```java
|
||||
final Markwon markwon = Markwon.builder(this)
|
||||
.usePlugin(new AbstractMarkwonPlugin() {
|
||||
@Override
|
||||
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
|
||||
builder.imageSizeResolver(new ImageSizeResolver() {
|
||||
@NonNull
|
||||
@Override
|
||||
public Rect resolveImageSize(
|
||||
@Nullable ImageSize imageSize,
|
||||
@NonNull Rect imageBounds,
|
||||
int canvasWidth,
|
||||
float textSize) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.build();
|
||||
```
|
||||
|
||||
If not provided explicitly, default `ImageSizeResolverDef` implementation will be used.
|
||||
It handles 3 dimension units:
|
||||
* `%` (percent, relative to Canvas width)
|
||||
* `em` (relative to text size)
|
||||
* `px` (absolute size, every dimension that is not `%` or `em` is considered to be _absolute_)
|
||||
|
||||
```html
|
||||
<img width="100%">
|
||||
<img width="2em" height="10px">
|
||||
<img style="{width: 100%; height: 8em;}">
|
||||
```
|
||||
|
||||
`ImageSizeResolverDef` keeps the ratio of original image if one of the dimensions is missing.
|
||||
|
||||
:::warning Height%
|
||||
There is no support for `%` units for `height` dimension. This is due to the fact that
|
||||
height of an TextView in which markdown is displayed is non-stable and changes with time
|
||||
(for example when image is loaded and applied to a TextView it will _increase_ TextView's height),
|
||||
so we will have no point-of-reference from which to _calculate_ image height.
|
||||
:::
|
||||
|
||||
:::tip
|
||||
`ImageSizeResolverDef` also takes care for an image to **not** exceed
|
||||
canvas width. If an image has greater width than a TextView Canvas, then
|
||||
image will be _scaled-down_ to fit the canvas. Please note that this rule
|
||||
applies only if image has no absolute sizes (for example width is specified
|
||||
in pixels).
|
||||
:::
|
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.md) 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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
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
|
||||
:::
|
||||
|
3
docs/docs/v4/migration-3-4.md
Normal file
3
docs/docs/v4/migration-3-4.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Migration 3.x.x -> 4.x.x
|
||||
|
||||
todo
|
3
docs/docs/v4/recipes.md
Normal file
3
docs/docs/v4/recipes.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Recipes
|
||||
|
||||
todo
|
@ -1,25 +0,0 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
|
||||
compileSdkVersion config['compile-sdk']
|
||||
buildToolsVersion config['build-tools']
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion config['min-sdk']
|
||||
targetSdkVersion config['target-sdk']
|
||||
versionCode 1
|
||||
versionName version
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
api project(':markwon-core')
|
||||
|
||||
deps.with {
|
||||
api it['android-gif']
|
||||
}
|
||||
}
|
||||
|
||||
registerArtifact(this)
|
@ -1,4 +0,0 @@
|
||||
POM_NAME=Image GIF
|
||||
POM_ARTIFACT_ID=image-gif
|
||||
POM_DESCRIPTION=Adds GIF media support to Markwon markdown
|
||||
POM_PACKAGING=aar
|
@ -1 +0,0 @@
|
||||
<manifest package="ru.noties.markwon.image.gif" />
|
@ -1,81 +0,0 @@
|
||||
package ru.noties.markwon.image.gif;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import pl.droidsonroids.gif.GifDrawable;
|
||||
import ru.noties.markwon.image.DrawableUtils;
|
||||
|
||||
/**
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class GifMediaDecoder extends MediaDecoder {
|
||||
|
||||
public static final String CONTENT_TYPE = "image/gif";
|
||||
|
||||
@NonNull
|
||||
public static GifMediaDecoder create(boolean autoPlayGif) {
|
||||
return new GifMediaDecoder(autoPlayGif);
|
||||
}
|
||||
|
||||
private final boolean autoPlayGif;
|
||||
|
||||
protected GifMediaDecoder(boolean autoPlayGif) {
|
||||
this.autoPlayGif = autoPlayGif;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Drawable decode(@NonNull InputStream inputStream) {
|
||||
|
||||
Drawable out = null;
|
||||
|
||||
final byte[] bytes = readBytes(inputStream);
|
||||
if (bytes != null) {
|
||||
try {
|
||||
out = newGifDrawable(bytes);
|
||||
DrawableUtils.applyIntrinsicBounds(out);
|
||||
|
||||
if (!autoPlayGif) {
|
||||
((GifDrawable) out).pause();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected Drawable newGifDrawable(@NonNull byte[] bytes) throws IOException {
|
||||
return new GifDrawable(bytes);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected static byte[] readBytes(@NonNull InputStream stream) {
|
||||
|
||||
byte[] out = null;
|
||||
|
||||
try {
|
||||
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
final int length = 1024 * 8;
|
||||
final byte[] buffer = new byte[length];
|
||||
int read;
|
||||
while ((read = stream.read(buffer, 0, length)) != -1) {
|
||||
outputStream.write(buffer, 0, read);
|
||||
}
|
||||
out = outputStream.toByteArray();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package ru.noties.markwon.image.gif;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import ru.noties.markwon.AbstractMarkwonPlugin;
|
||||
import ru.noties.markwon.image.AsyncDrawableLoader;
|
||||
import ru.noties.markwon.priority.Priority;
|
||||
|
||||
public class GifPlugin extends AbstractMarkwonPlugin {
|
||||
|
||||
@NonNull
|
||||
public static GifPlugin create() {
|
||||
return create(true);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static GifPlugin create(boolean autoPlay) {
|
||||
return new GifPlugin(autoPlay);
|
||||
}
|
||||
|
||||
private final boolean autoPlay;
|
||||
|
||||
public GifPlugin(boolean autoPlay) {
|
||||
this.autoPlay = autoPlay;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) {
|
||||
builder.addMediaDecoder(GifMediaDecoder.CONTENT_TYPE, GifMediaDecoder.create(autoPlay));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Priority priority() {
|
||||
return Priority.after(ImagesPlugin.class);
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
|
||||
compileSdkVersion config['compile-sdk']
|
||||
buildToolsVersion config['build-tools']
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion config['min-sdk']
|
||||
targetSdkVersion config['target-sdk']
|
||||
versionCode 1
|
||||
versionName version
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
api project(':markwon-core')
|
||||
|
||||
deps.with {
|
||||
api it['okhttp']
|
||||
}
|
||||
}
|
||||
|
||||
registerArtifact(this)
|
@ -1,4 +0,0 @@
|
||||
POM_NAME=Image OkHttp
|
||||
POM_ARTIFACT_ID=image-okhttp
|
||||
POM_DESCRIPTION=Adds OkHttp client to retrieve images data from network
|
||||
POM_PACKAGING=aar
|
@ -1 +0,0 @@
|
||||
<manifest package="ru.noties.markwon.image.okhttp" />
|
@ -1,51 +0,0 @@
|
||||
package ru.noties.markwon.image.okhttp;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import ru.noties.markwon.AbstractMarkwonPlugin;
|
||||
import ru.noties.markwon.image.AsyncDrawableLoader;
|
||||
import ru.noties.markwon.priority.Priority;
|
||||
|
||||
/**
|
||||
* Plugin to use OkHttpClient to obtain images from network (http and https schemes)
|
||||
*
|
||||
* @see #create()
|
||||
* @see #create(OkHttpClient)
|
||||
* @since 3.0.0
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class OkHttpImagesPlugin extends AbstractMarkwonPlugin {
|
||||
|
||||
@NonNull
|
||||
public static OkHttpImagesPlugin create() {
|
||||
return new OkHttpImagesPlugin(new OkHttpClient());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static OkHttpImagesPlugin create(@NonNull OkHttpClient okHttpClient) {
|
||||
return new OkHttpImagesPlugin(okHttpClient);
|
||||
}
|
||||
|
||||
private final OkHttpClient client;
|
||||
|
||||
OkHttpImagesPlugin(@NonNull OkHttpClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) {
|
||||
builder.addSchemeHandler(
|
||||
Arrays.asList(NetworkSchemeHandler.SCHEME_HTTP, NetworkSchemeHandler.SCHEME_HTTPS),
|
||||
new OkHttpSchemeHandler(client)
|
||||
);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Priority priority() {
|
||||
return Priority.after(ImagesPlugin.class);
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
package ru.noties.markwon.image.okhttp;
|
||||
|
||||
import android.net.Uri;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
class OkHttpSchemeHandler extends SchemeHandler {
|
||||
|
||||
private static final String HEADER_CONTENT_TYPE = "Content-Type";
|
||||
|
||||
private final OkHttpClient client;
|
||||
|
||||
OkHttpSchemeHandler(@NonNull OkHttpClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ImageItem handle(@NonNull String raw, @NonNull Uri uri) {
|
||||
ImageItem out = null;
|
||||
|
||||
final Request request = new Request.Builder()
|
||||
.url(raw)
|
||||
.tag(raw)
|
||||
.build();
|
||||
|
||||
Response response = null;
|
||||
try {
|
||||
response = client.newCall(request).execute();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (response != null) {
|
||||
final ResponseBody body = response.body();
|
||||
if (body != null) {
|
||||
final InputStream inputStream = body.byteStream();
|
||||
if (inputStream != null) {
|
||||
final String contentType = response.header(HEADER_CONTENT_TYPE);
|
||||
out = new ImageItem(contentType, inputStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
|
||||
compileSdkVersion config['compile-sdk']
|
||||
buildToolsVersion config['build-tools']
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion config['min-sdk']
|
||||
targetSdkVersion config['target-sdk']
|
||||
versionCode 1
|
||||
versionName version
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
api project(':markwon-core')
|
||||
|
||||
deps.with {
|
||||
api it['android-svg']
|
||||
}
|
||||
}
|
||||
|
||||
registerArtifact(this)
|
@ -1,4 +0,0 @@
|
||||
POM_NAME=Image SVG
|
||||
POM_ARTIFACT_ID=image-svg
|
||||
POM_DESCRIPTION=Adds SVG media support to Markwon markdown
|
||||
POM_PACKAGING=aar
|
@ -1 +0,0 @@
|
||||
<manifest package="ru.noties.markwon.image.svg" />
|
@ -1,72 +0,0 @@
|
||||
package ru.noties.markwon.image.svg;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.caverock.androidsvg.SVG;
|
||||
import com.caverock.androidsvg.SVGParseException;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import ru.noties.markwon.image.DrawableUtils;
|
||||
|
||||
/**
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class SvgMediaDecoder extends MediaDecoder {
|
||||
|
||||
public static final String CONTENT_TYPE = "image/svg+xml";
|
||||
|
||||
@NonNull
|
||||
public static SvgMediaDecoder create(@NonNull Resources resources) {
|
||||
return new SvgMediaDecoder(resources);
|
||||
}
|
||||
|
||||
private final Resources resources;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
SvgMediaDecoder(Resources resources) {
|
||||
this.resources = resources;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Drawable decode(@NonNull InputStream inputStream) {
|
||||
|
||||
final Drawable out;
|
||||
|
||||
SVG svg = null;
|
||||
try {
|
||||
svg = SVG.getFromInputStream(inputStream);
|
||||
} catch (SVGParseException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (svg == null) {
|
||||
out = null;
|
||||
} else {
|
||||
|
||||
final float w = svg.getDocumentWidth();
|
||||
final float h = svg.getDocumentHeight();
|
||||
final float density = resources.getDisplayMetrics().density;
|
||||
|
||||
final int width = (int) (w * density + .5F);
|
||||
final int height = (int) (h * density + .5F);
|
||||
|
||||
final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);
|
||||
final Canvas canvas = new Canvas(bitmap);
|
||||
canvas.scale(density, density);
|
||||
svg.renderToCanvas(canvas);
|
||||
|
||||
out = new BitmapDrawable(resources, bitmap);
|
||||
DrawableUtils.applyIntrinsicBounds(out);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package ru.noties.markwon.image.svg;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import ru.noties.markwon.AbstractMarkwonPlugin;
|
||||
import ru.noties.markwon.image.AsyncDrawableLoader;
|
||||
import ru.noties.markwon.priority.Priority;
|
||||
|
||||
public class SvgPlugin extends AbstractMarkwonPlugin {
|
||||
|
||||
@NonNull
|
||||
public static SvgPlugin create(@NonNull Resources resources) {
|
||||
return new SvgPlugin(resources);
|
||||
}
|
||||
|
||||
private final Resources resources;
|
||||
|
||||
public SvgPlugin(@NonNull Resources resources) {
|
||||
this.resources = resources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) {
|
||||
builder.addMediaDecoder(SvgMediaDecoder.CONTENT_TYPE, SvgMediaDecoder.create(resources));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Priority priority() {
|
||||
return Priority.after(ImagesPlugin.class);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user