Started with v4 documentation

This commit is contained in:
Dimitry Ivanov 2019-06-12 18:45:28 +03:00
parent 14591508b5
commit 8b0edc32c3
35 changed files with 1454 additions and 432 deletions

View File

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

View File

@ -10,3 +10,4 @@
* removed MarkwonPlugin#configureHtmlRenderer -> now part of HtmlPlugin
* TagHandler now has `supportedTags()` method
* html is moved completely to html-plugin
* OnTextAddedListener

View File

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

View File

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

View File

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

View File

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

View File

@ -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'
],
'/': [
'',
{

View 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: `![img](./art/image.JPG)` and `UrlProcessorRelativeToAbsolute`
is created with `https://github.com/noties/Markwon/raw/master/` as the base:
`new UrlProcessorRelativeToAbsolute("https://github.com/noties/Markwon/raw/master/")`,
then final image will have `https://github.com/noties/Markwon/raw/master/art/image.JPG`
as the destination.
### UrlProcessorAndroidAssets
`UrlProcessorAndroidAssets` can be used to make processed links to point to Android assets folder.
So an image: `![img](./art/image.JPG)` will have `file:///android_asset/art/image.JPG` as the
destination.
:::tip
Please note that `UrlProcessorAndroidAssets` will process only URLs that have no `scheme` information,
so a `./art/image.png` will become `file:///android_asset/art/image.JPG` whilst `https://so.me/where.png`
will be kept as-is.
:::
## ImageSizeResolver
`ImageSizeResolver` controls the size of an image to be displayed. 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).
:::

View File

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

View File

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

View File

@ -0,0 +1,17 @@
# Movement method plugin
`MovementMethodPlugin` can be used to apply a `MovementMethod` to a TextView
(important if you have links inside your markdown). By default `CorePlugin`
will set a `LinkMovementMethod` on a TextView if one is missing. If you have
specific needs for a `MovementMethod` and `LinkMovementMethod` doesn't answer
your needs use `MovementMethodPlugin`:
```java
Markwon.builder(context)
.usePlugin(MovementMethodPlugin.create(ScrollingMovementMethod.getInstance()))
```
:::tip
If you are having trouble with system `LinkMovementMethod` as an alternative
[BetterLinkMovementMethod](https://github.com/saket/Better-Link-Movement-Method) library can be used.
:::

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

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

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

View File

@ -0,0 +1,3 @@
# Migration 3.x.x -> 4.x.x
todo

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

@ -0,0 +1,3 @@
# Recipes
todo

View File

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

View File

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

View File

@ -1 +0,0 @@
<manifest package="ru.noties.markwon.image.gif" />

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

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

View File

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

View File

@ -1 +0,0 @@
<manifest package="ru.noties.markwon.image.okhttp" />

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

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

View File

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

View File

@ -1 +0,0 @@
<manifest package="ru.noties.markwon.image.svg" />

View File

@ -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;
}
}

View File

@ -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);
}
}