Working on core module documentation
This commit is contained in:
parent
02e7539881
commit
d91f367e0a
@ -17,7 +17,7 @@
|
||||
</p>
|
||||
<p>
|
||||
<em>
|
||||
** For a little more sophisticated commonmark sandbox editor
|
||||
** For a more sophisticated commonmark sandbox editor
|
||||
<a href="https://spec.commonmark.org/dingus/">the dingus</a> can be used.
|
||||
</em>
|
||||
</p>
|
||||
|
@ -1,24 +1,33 @@
|
||||
module.exports = {
|
||||
base: '/Markwon/',
|
||||
title: 'Markwon',
|
||||
description: 'Android markdown library based on commonmark specification',
|
||||
description: 'Android markdown library based on commonmark specification that renders markdown as system-native Spannables (no WebView)',
|
||||
head: [
|
||||
['link', { rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png?v=1' }],
|
||||
['link', { rel: 'icon', type: 'image/png', sizes: '16x16', href: '/favicon-16x16.png?v=1' }],
|
||||
['link', { rel: 'icon', href: '/favicon.ico?v=1' }],
|
||||
['link', { rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicon-32x32.png?v=1' }],
|
||||
['link', { rel: 'manifest', href: '/manifest.json?v=1' }],
|
||||
['meta', { name: 'keywords', content: 'android,markdown,library,spannable,markwon,commonmark' }]
|
||||
],
|
||||
themeConfig: {
|
||||
nav: [
|
||||
{ text: 'Install', link: '/docs/install.md' },
|
||||
{ text: 'Changelog', link: '/CHANGELOG.md' },
|
||||
{
|
||||
text: 'API Version',
|
||||
items: [
|
||||
{ text: 'Current (3.x.x)', link: '/' },
|
||||
{ text: 'Legacy (2.x.x)', link: '/docs/v2/' }
|
||||
]
|
||||
},
|
||||
{ text: 'Sandbox', link: '/sandbox.md' },
|
||||
{ text: 'Donate', link: '/donate.md' },
|
||||
{ text: 'Github', link: 'https://github.com/noties/Markwon' }
|
||||
],
|
||||
sidebar: {
|
||||
'/docs/v2/': [
|
||||
'install.md',
|
||||
'',
|
||||
'getting-started.md',
|
||||
'configure.md',
|
||||
'theme.md',
|
||||
@ -34,7 +43,13 @@ module.exports = {
|
||||
title: 'Core',
|
||||
children: [
|
||||
'/docs/core/getting-started.md',
|
||||
'/docs/core/theme.md'
|
||||
'/docs/core/plugins.md',
|
||||
'/docs/core/theme.md',
|
||||
'/docs/core/images.md',
|
||||
'/docs/core/configuration.md',
|
||||
'/docs/core/visitor.md',
|
||||
'/docs/core/spans-factory.md',
|
||||
'/docs/core/html-renderer.md'
|
||||
]
|
||||
},
|
||||
'/docs/ext-latex/',
|
||||
@ -57,8 +72,7 @@ module.exports = {
|
||||
},
|
||||
'/docs/recycler/recycler.md',
|
||||
'/docs/syntax-highlight/syntax-highlight.md',
|
||||
'/docs/migration-2-3.md',
|
||||
['/docs/v2/install.md', 'Legacy 2.x.x documentation']
|
||||
'/docs/migration-2-3.md'
|
||||
]
|
||||
},
|
||||
sidebarDepth: 2,
|
||||
|
@ -1,226 +0,0 @@
|
||||
# Configuration
|
||||
|
||||
`SpannableConfiguration` is the core component that controls how markdown is parsed and rendered.
|
||||
It can be obtained via factory methods:
|
||||
|
||||
```java
|
||||
// creates default implementation
|
||||
final SpannableConfiguration configuration = SpannableConfiguration.create(context);
|
||||
```
|
||||
|
||||
```java
|
||||
// creates configurablable instance via `#builder` method
|
||||
final SpannableConfiguration configuration = SpannableConfiguration.builder(context)
|
||||
.asyncDrawableLoader(AsyncDrawableLoader.create())
|
||||
.build();
|
||||
```
|
||||
|
||||
:::tip Note
|
||||
If `#builder` factory method is used, you do not need to specify default
|
||||
values as they will be applied automatically
|
||||
:::
|
||||
|
||||
:::warning Images
|
||||
If you plan on using images inside your markdown/HTML, you will have to **explicitly**
|
||||
register an implementation of `AsyncDrawable.Loader` via `#asyncDrawableLoader` builder method.
|
||||
`Markwon` comes with ready implementation for that and it can be found in
|
||||
`markwon-image-loader` module. Refer to module [documentation](/docs/image-loader.md)
|
||||
:::
|
||||
|
||||
## Theme
|
||||
|
||||
`SpannableTheme` controls how markdown is rendered. It has pretty extensive number of
|
||||
options that can be found [here](/docs/theme.md)
|
||||
|
||||
```java
|
||||
SpannableConfiguration.builder(context)
|
||||
.theme(SpannableTheme)
|
||||
.build();
|
||||
```
|
||||
|
||||
If `SpannableTheme` is not provided explicitly, `SpannableTheme.create(context)` will be used
|
||||
|
||||
## Images
|
||||
|
||||
### Async loader
|
||||
|
||||
`AsyncDrawable.Loader` handles images in your markdown and HTML
|
||||
|
||||
```java
|
||||
SpannableConfiguration.builder(context)
|
||||
.asyncDrawableLoader(AsyncDrawable.Loader)
|
||||
.build();
|
||||
```
|
||||
|
||||
If `AsyncDrawable.Loader` is not provided explicitly, default **no-op** implementation will be used.
|
||||
|
||||
:::tip Implementation
|
||||
There are no restrictions on what implementation to use, but `Markwon` has artifact that can
|
||||
answer the most common needs of displaying SVG, GIF and other image formats. It can be found [here](/docs/image-loader.md)
|
||||
:::
|
||||
|
||||
### Size resolver <Badge text="1.0.1" />
|
||||
|
||||
`ImageSizeResolver` controls the size of an image to be displayed. Currently it
|
||||
handles only HTML images (specified via `img` tag).
|
||||
|
||||
```java
|
||||
SpannableConfiguration.builder(context)
|
||||
.imageSizeResolver(ImageSizeResolver)
|
||||
.build();
|
||||
```
|
||||
|
||||
If not provided explicitly, default `ImageSizeResolverDef` implementation will be used.
|
||||
It handles 3 dimention units:
|
||||
* `%` (percent)
|
||||
* `em` (relative to text size)
|
||||
* `px` (absolute size, every dimention 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 dimentions is missing.
|
||||
|
||||
:::warning Height%
|
||||
There is no support for `%` units for `height` dimention. 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-refence from which to _calculate_ image height.
|
||||
:::
|
||||
|
||||
## Syntax highlight
|
||||
|
||||
`SyntaxHighlight` controls the syntax highlight for code blocks (in markdown).
|
||||
|
||||
```java
|
||||
SpannableConfiguration.builder(context)
|
||||
.syntaxHighlight(SyntaxHighlight)
|
||||
.build();
|
||||
```
|
||||
|
||||
If not provided explicitly, default **no-op** implementation will be used.
|
||||
|
||||
:::tip Syntax highlight
|
||||
Although `SyntaxHighlight` interface was included with the very first version
|
||||
of `Markwon` there were no ready-to-use implementations. But starting with <Badge text="1.1.0" />
|
||||
`Markwon` provides one. It can be found in `markwon-syntax-highlight` artifact. Refer
|
||||
to module [documentation](/docs/syntax-highlight.md)
|
||||
:::
|
||||
|
||||
## Link resolver
|
||||
|
||||
`LinkSpan.Resolver` is triggered when a link is clicked in markdown/HTML.
|
||||
|
||||
```java
|
||||
SpannableConfiguration.builder(context)
|
||||
.linkResolver(LinkSpan.Resolver)
|
||||
.build();
|
||||
```
|
||||
|
||||
If not provided explicitly, default `LinkResolverDef` implementation will be used.
|
||||
Underneath it constructs an `Intent` and _tries_ to start an Activity associated with it.
|
||||
It no Activity is found, it will silently fail (no runtime exceptions)
|
||||
|
||||
## URL processor
|
||||
|
||||
`UrlProcessor` is used to process found URLs in markdown/HTML.
|
||||
|
||||
```java
|
||||
SpannableConfiguration.builder(context)
|
||||
.urlProcessor(UrlProcessor)
|
||||
.build();
|
||||
```
|
||||
|
||||
If not provided explicitly, default **no-op** implementation will be used.
|
||||
|
||||
`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
|
||||
|
||||
## Factory <Badge text="1.1.0" />
|
||||
|
||||
`SpannableFactory` is used to control _what_ span implementations to be used
|
||||
|
||||
```java
|
||||
SpannableConfiguration.builder(context)
|
||||
.factory(SpannableFactory)
|
||||
.build();
|
||||
```
|
||||
|
||||
If not provided explicitly, default `SpannableFactoryDef` implementation will be used. It is documented
|
||||
in [this section](/docs/factory.md)
|
||||
|
||||
## Soft line break <Badge text="1.1.1" />
|
||||
|
||||
`softBreakAddsNewLine` option controls how _soft breaks_ are treated in the final result.
|
||||
If `true` -> soft break will add a new line, else it will add a ` ` (space) char.
|
||||
|
||||
```java
|
||||
SpannableConfiguration.builder(context)
|
||||
.softBreakAddsNewLine(boolean)
|
||||
.build();
|
||||
```
|
||||
|
||||
If not provided explicitly, default `false` value will be used.
|
||||
|
||||
<Link name="commonmark-spec#soft-break" displayName="Commonmark specification" />
|
||||
|
||||
## HTML <Badge text="2.0.0" />
|
||||
|
||||
### Parser
|
||||
|
||||
`MarkwonHtmlParser` is used to parse HTML content
|
||||
|
||||
```java
|
||||
SpannableConfiguration.builder(context)
|
||||
.htmlParser(MarkwonHtmlParser)
|
||||
.build();
|
||||
```
|
||||
|
||||
if not provided explicitly, default `MarkwonHtmlParserImpl` will be used
|
||||
**if** it can be found in classpath, otherwise default **no-op** implementation
|
||||
wiil be used. Refer to [HTML](/docs/html.md#parser) document for more information about this behavior.
|
||||
|
||||
### Renderer
|
||||
|
||||
`MarkwonHtmlRenderer` controls how parsed HTML content will be rendered.
|
||||
|
||||
```java
|
||||
SpannableConfiguration.builder(context)
|
||||
.htmlRenderer(MarkwonHtmlRenderer)
|
||||
.build();
|
||||
```
|
||||
|
||||
If not provided explicitly, default `MarkwonHtmlRenderer` implementation will be used.
|
||||
It is documented [here](/docs/html.md#renderer)
|
||||
|
||||
### HTML allow non-closed tags
|
||||
|
||||
`htmlAllowNonClosedTags` option is used to control whether or not to
|
||||
render non-closed HTML tags
|
||||
|
||||
```java
|
||||
SpannableConfiguration.builder(context)
|
||||
.htmlAllowNonClosedTags(boolean)
|
||||
.build();
|
||||
```
|
||||
|
||||
If not provided explicitly, default value `false` will be used (non-closed tags **won't** be rendered).
|
1
docs/docs/core/configuration.md
Normal file
1
docs/docs/core/configuration.md
Normal file
@ -0,0 +1 @@
|
||||
# Configuration
|
@ -81,39 +81,28 @@ rawInput = plugins.reduce(rawInput, (input, plugin) -> plugin.processMarkdown(in
|
||||
// 1. after input is processed it's being parsed to a Node
|
||||
node = parser.parse(rawInput);
|
||||
|
||||
// 2. each plugin will configure RenderProps
|
||||
plugins.forEach(plugin -> plugin.configureRenderProps(renderProps));
|
||||
|
||||
// 3. each plugin will be able to inspect or manipulate resulting Node
|
||||
// 2. each plugin will be able to inspect or manipulate resulting Node
|
||||
// before rendering
|
||||
plugins.forEach(plugin -> plugin.beforeRender(node));
|
||||
|
||||
// 4. node is being visited by a visitor
|
||||
// 3. node is being visited by a visitor
|
||||
node.accept(visitor);
|
||||
|
||||
// 5. each plugin will be called after node is being visited (aka rendered)
|
||||
// 4. each plugin will be called after node is being visited (aka rendered)
|
||||
plugins.forEach(plugin -> plugin.afterRender(node, visitor));
|
||||
|
||||
// 6. styled markdown ready at this point
|
||||
// 5. styled markdown ready at this point
|
||||
final Spanned markdown = visitor.markdown();
|
||||
|
||||
// 7. each plugin will be called before styled markdown is applied to a TextView
|
||||
// 6. each plugin will be called before styled markdown is applied to a TextView
|
||||
plugins.forEach(plugin -> plugin.beforeSetText(textView, markdown));
|
||||
|
||||
// 8. markdown is applied to a TextView
|
||||
// 7. markdown is applied to a TextView
|
||||
textView.setText(markdown);
|
||||
|
||||
// 9. each plugin will be called after markdown is applied to a TextView
|
||||
// 8. each plugin will be called after markdown is applied to a TextView
|
||||
plugins.forEach(plugin -> plugin.afterSetText(textView));
|
||||
```
|
||||
|
||||
As you can see a `plugin` is what lifts the most weight. We will cover
|
||||
plugins next.
|
||||
|
||||
:::tip Note
|
||||
If you are having trouble with `LinkMovementMethod` you can use
|
||||
`Markwon.setText(textView, markdown, movementMethod)` method <Badge text="1.0.6" /> to specify _no_ movement
|
||||
method (aka `null`) or own implementation. As an alternative to the system `LinkMovementMethod`
|
||||
you can use [Better-Link-Movement-Method](https://github.com/saket/Better-Link-Movement-Method).
|
||||
Please note that `Markwon.setText` method expects _parsed_ markdown as the second argument.
|
||||
:::
|
1
docs/docs/core/html-renderer.md
Normal file
1
docs/docs/core/html-renderer.md
Normal file
@ -0,0 +1 @@
|
||||
# HTML Renderer
|
1
docs/docs/core/images.md
Normal file
1
docs/docs/core/images.md
Normal file
@ -0,0 +1 @@
|
||||
# Images
|
424
docs/docs/core/plugins.md
Normal file
424
docs/docs/core/plugins.md
Normal file
@ -0,0 +1,424 @@
|
||||
# Plugins <Badge text="3.0.0" />
|
||||
|
||||
Since <Badge text="3.0.0" /> `MarkwonPlugin` takes the key role in
|
||||
processing and rendering markdown. Even **core** functionaly is abstracted
|
||||
into a `CorePlugin`. So it's still possible to use `Markwon` with a completely
|
||||
own set of plugins.
|
||||
|
||||
To register a plugin `Markwon.Builder` must be used:
|
||||
|
||||
```java
|
||||
Markwon.builder(context)
|
||||
.usePlugin(CorePlugin.create())
|
||||
.build();
|
||||
```
|
||||
|
||||
All the process of transforming _raw_ markdown into a styled text (Spanned)
|
||||
will go through plugins. A plugin can:
|
||||
|
||||
* [configure commonmark-java `Parser`](#parser)
|
||||
* [configure `MarkwonTheme`](#markwontheme)
|
||||
* [configure `AsyncDrawableLoader` (used to display images in markdown)](#images)
|
||||
* [configure `MarkwonConfiguration`](#configuration)
|
||||
* [configure `MarkwonVisitor` (extensible commonmark-java Node visitor)](#visitor)
|
||||
* [configure `MarkwonSpansFactory` (factory to hold spans information for each Node)](#spans-factory)
|
||||
* [configure `MarkwonHtmlRenderer` (utility to properly display HTML in markdown)](#html-renderer)
|
||||
|
||||
---
|
||||
|
||||
* [declare a dependency on another plugin (will be used as a runtime validator)](#priority)
|
||||
|
||||
---
|
||||
|
||||
* [process raw input markdown before parsing it](#process-markdown)
|
||||
* [inspect/modify commonmark-java Node after it's been parsed, but before rendering](#inspect-modify-node)
|
||||
* [inspect commonmark-java Node after it's been rendered](#inspect-node-after-render)
|
||||
* [prepare TextView to display markdown _before_ markdown is applied to a TextView](#prepare-textview)
|
||||
* [post-process TextView _after_ markdown was applied](#textview-after-markdown-applied)
|
||||
|
||||
:::tip
|
||||
if you need to override only few methods of `MarkwonPlugin` (since it is an interface),
|
||||
`AbstractMarkwonPlugin` can be used.
|
||||
:::
|
||||
|
||||
## Parser
|
||||
|
||||
For example, let's register a new commonmark-java Parser extension:
|
||||
|
||||
```java
|
||||
final Markwon markwon = Markwon.builder(context)
|
||||
.usePlugin(CorePlugin.create())
|
||||
.usePlugin(new AbstractMarkwonPlugin() {
|
||||
@Override
|
||||
public void configureParser(@NonNull Parser.Builder builder) {
|
||||
// no need to call `super.configureParser(builder)`
|
||||
builder.extensions(Collections.singleton(StrikethroughExtension.create()));
|
||||
}
|
||||
})
|
||||
.build();
|
||||
```
|
||||
|
||||
There are no limitations on what to do with commonmark-java Parser. For more info
|
||||
_what_ can be done please refer to <Link name="commonmark-java" displayName="commonmark-java documentation" />.
|
||||
|
||||
## MarkwonTheme
|
||||
|
||||
Starting <Badge text="3.0.0" /> `MarkwonTheme` represents _core_ theme. Aka theme for
|
||||
things core module knows of. For example it doesn't know anything about `strikethrough`
|
||||
or `tables` (as they belong to different modules).
|
||||
|
||||
```java
|
||||
final Markwon markwon = Markwon.builder(context)
|
||||
.usePlugin(new AbstractMarkwonPlugin() {
|
||||
@Override
|
||||
public void configureTheme(@NonNull MarkwonTheme.Builder builder) {
|
||||
builder
|
||||
.codeTextColor(Color.BLACK)
|
||||
.codeBackgroundColor(Color.GREEN);
|
||||
}
|
||||
})
|
||||
.build();
|
||||
```
|
||||
|
||||
:::warning
|
||||
`CorePlugin` has special handling - it will be **implicitly** added
|
||||
if a plugin declares dependency on it. This is why in previous example we haven't
|
||||
added CorePlugin _explicitly_ as `AbstractMarkwonPlugin` declares a dependency on it.
|
||||
If it's not desireable override `AbstractMarkwonPlugin#priority` method to specify own rules.
|
||||
:::
|
||||
|
||||
More information about `MarkwonTheme` can be found [here](/docs/core/theme.md).
|
||||
|
||||
|
||||
## Images
|
||||
|
||||
Since <Badge text="3.0.0" /> core images functionality moved to the `core` module.
|
||||
Now `Markwon` comes bundled with support for regular images (no `SVG` or `GIF`, they
|
||||
defined in standalone modules now). And 3(4) schemes supported by default:
|
||||
* http (+https; using system built-in `HttpURLConnection`)
|
||||
* file (including Android assets)
|
||||
* data (image inline, `data:image/svg+xml;base64,!@#$%^&*(`)
|
||||
|
||||
```java
|
||||
final Markwon markwon = Markwon.builder(context)
|
||||
.usePlugin(ImagesPlugin.create())
|
||||
.usePlugin(new AbstractMarkwonPlugin() {
|
||||
@Override
|
||||
public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) {
|
||||
// sorry, these are not bundled with the library
|
||||
builder
|
||||
.addSchemeHandler("ftp", new FtpSchemeHandler("root", ""))
|
||||
.addMediaDecoder("text/plain", new AnsiiMediaDecoder());
|
||||
}
|
||||
})
|
||||
.build();
|
||||
```
|
||||
|
||||
:::warning
|
||||
Although `ImagesPlugin` is bundled with the `core` artifact, it is **not** used by default
|
||||
and one must **explicitly** add it:
|
||||
|
||||
```java
|
||||
Markwon.builder(context)
|
||||
.usePlugin(ImagesPlugin.create(context));
|
||||
```
|
||||
|
||||
Without explicit usage of `ImagesPlugin` all image configuration will be ignored (no-op'ed)
|
||||
:::
|
||||
|
||||
More information about dealing with images can be found [here](/docs/core/images.md)
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
`MarkwonConfiguration` is a set of common tools that are used by different parts
|
||||
of `Markwon`. It allows configurations of these:
|
||||
|
||||
* `SyntaxHighlight` (highlighting code blocks)
|
||||
* `LinkResolver` (opens links in markdown)
|
||||
* `UrlProcessor` (process URLs in markdown for both links and images)
|
||||
* `MarkwonHtmlParser` (HTML parser)
|
||||
* `ImageSizeResolver` (resolve image sizes, like `fit-to-canvas`, etc)
|
||||
|
||||
```java
|
||||
final Markwon markwon = Markwon.builder(context)
|
||||
.usePlugin(new AbstractMarkwonPlugin() {
|
||||
@Override
|
||||
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
|
||||
// MarkwonHtmlParserImpl is defined in `markwon-html` artifact
|
||||
builder.htmlParser(MarkwonHtmlParserImpl.create());
|
||||
}
|
||||
})
|
||||
.build();
|
||||
```
|
||||
|
||||
More information about `MarkwonConfiguration` can be found [here](/docs/core/configuration.md)
|
||||
|
||||
|
||||
## Visitor
|
||||
|
||||
`MarkwonVisitor` <Badge text="3.0.0" /> is commonmark-java Visitor that allows
|
||||
configuration of how each Node is visited. There is no longer need to create
|
||||
own subclass of Visitor and override required methods (like in `2.x.x` versions).
|
||||
`MarkwonVisitor` also allows registration of Nodes, that `core` module knows
|
||||
nothing about (instead of relying on `visit(CustomNode)` method)).
|
||||
|
||||
For example, let's add `strikethrough` Node visitor:
|
||||
|
||||
```java
|
||||
final Markwon markwon = Markwon.builder(context)
|
||||
.usePlugin(new AbstractMarkwonPlugin() {
|
||||
@Override
|
||||
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
|
||||
builder
|
||||
.on(Strikethrough.class, new MarkwonVisitor.NodeVisitor<Strikethrough>() {
|
||||
@Override
|
||||
public void visit(@NonNull MarkwonVisitor visitor, @NonNull Strikethrough strikethrough) {
|
||||
final int length = visitor.length();
|
||||
visitor.visitChildren(strikethrough);
|
||||
visitor.setSpansForNodeOptional(strikethrough, length);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.build();
|
||||
```
|
||||
|
||||
:::tip
|
||||
`MarkwonVisitor` also allows _overriding_ already registered nodes. For example,
|
||||
we can disable `Heading` Node rendering:
|
||||
|
||||
```java
|
||||
builder.on(Heading.class, null);
|
||||
```
|
||||
|
||||
Please note that `Priority` plays nicely here to ensure that your
|
||||
custom Node override/disable happens _after_ some plugin defines it.
|
||||
:::
|
||||
|
||||
More information about `MarkwonVisitor` can be found [here](/docs/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/core/spans-factory.md)
|
||||
|
||||
|
||||
## HTML Renderer
|
||||
|
||||
`MarkwonHtmlRenderer` controls how HTML is rendered in markdown.
|
||||
|
||||
```java
|
||||
final Markwon markwon = Markwon.builder(context)
|
||||
.usePlugin(HtmlPlugin.create())
|
||||
.usePlugin(new AbstractMarkwonPlugin() {
|
||||
@Override
|
||||
public void configureHtmlRenderer(@NonNull MarkwonHtmlRenderer.Builder builder) {
|
||||
// <center> tag handling (deprecated but valid in our case)
|
||||
// can be any tag name, there is no connection with _real_ HTML tags,
|
||||
// <just-try-to-not-go-crazy-and-remember-about-portability>
|
||||
builder.addHandler("center", new SimpleTagHandler() {
|
||||
@Override
|
||||
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps renderProps, @NonNull HtmlTag tag) {
|
||||
return new AlignmentSpan() {
|
||||
@Override
|
||||
public Layout.Alignment getAlignment() {
|
||||
return Layout.Alignment.ALIGN_CENTER;
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.build();
|
||||
```
|
||||
|
||||
:::danger
|
||||
Although `MarkwonHtmlRenderer` is bundled with `core` artifact, actual
|
||||
HTML parser is placed in a standalone artifact and must be added to your
|
||||
project **explicitly** and then registered via `Markwon.Builder#usePlugin(HtmlPlugin.create())`.
|
||||
If not done so, no HTML will be parsed nor rendered.
|
||||
:::
|
||||
|
||||
More information about HTML rendering can be found [here](/docs/core/html-renderer.md)
|
||||
|
||||
|
||||
## Priority
|
||||
|
||||
`Priority` is an abstraction to _state_ dependency connection between plugins. It is
|
||||
also used as a runtime graph validator. If a plugin defines a dependency on other, but
|
||||
_other_ is not in resulting `Markwon` instance, then a runtime exception will be thrown.
|
||||
`Priority` is also defines the order in which plugins will be placed. So, if a plugin `A`
|
||||
states a plugin `B` as a dependency, then plugin `A` will come **after** plugin `B`.
|
||||
|
||||
```java
|
||||
final Markwon markwon = Markwon.builder(context)
|
||||
.usePlugin(new AbstractMarkwonPlugin() {
|
||||
@NonNull
|
||||
@Override
|
||||
public Priority priority() {
|
||||
return Priority.after(CorePlugin.class);
|
||||
}
|
||||
})
|
||||
.build();
|
||||
```
|
||||
|
||||
:::warning
|
||||
Please note that `AbstractMarkwonPlugin` _implicitly_ defines `CorePlugin`
|
||||
as a dependency (`return Priority.after(CorePlugin.class);`). This will
|
||||
also add `CorePlugin` to a `Markwon` instance, because it will be added
|
||||
_implicitly_ if a plugin defines it as a dependency.
|
||||
:::
|
||||
|
||||
Use one of the factory methods to create a `Priority` instance:
|
||||
|
||||
```java
|
||||
// none
|
||||
Priority.none();
|
||||
|
||||
// single dependency
|
||||
Priority.after(CorePlugin.class);
|
||||
|
||||
// 2 dependencies
|
||||
Priority.after(CorePlugin.class, ImagesPlugin.class);
|
||||
|
||||
// for a number >2, use #builder
|
||||
Priority.builder()
|
||||
.after(CorePlugin.class)
|
||||
.after(ImagesPlugin.class)
|
||||
.after(StrikethroughPlugin.class)
|
||||
.build();
|
||||
```
|
||||
|
||||
## Process markdown
|
||||
|
||||
A plugin can be used to _pre-process_ input markdown (this will be called before _parsing_):
|
||||
|
||||
```java
|
||||
final Markwon markwon = Markwon.builder(context)
|
||||
.usePlugin(new AbstractMarkwonPlugin() {
|
||||
@NonNull
|
||||
@Override
|
||||
public String processMarkdown(@NonNull String markdown) {
|
||||
return markdown.replaceAll("foo", "bar");
|
||||
}
|
||||
})
|
||||
.build();
|
||||
```
|
||||
|
||||
## Inspect/modify Node
|
||||
|
||||
A plugin can inspect/modify commonmark-java Node _before_ it's being rendered.
|
||||
|
||||
```java
|
||||
final Markwon markwon = Markwon.builder(context)
|
||||
.usePlugin(new AbstractMarkwonPlugin() {
|
||||
@Override
|
||||
public void beforeRender(@NonNull Node node) {
|
||||
|
||||
// for example inspect it with custom visitor
|
||||
node.accept(new MyVisitor());
|
||||
|
||||
// or modify (you know what you are doing, right?)
|
||||
node.appendChild(new Text("Appended"));
|
||||
}
|
||||
})
|
||||
.build();
|
||||
```
|
||||
|
||||
## Inspect Node after render
|
||||
|
||||
A plugin can inspect commonmark-java Node after it's been rendered.
|
||||
Modifying Node at this point makes not much sense (it's already been
|
||||
rendered and all modifications won't change anything). But this method can be used,
|
||||
for example, to clean-up some internal state (after rendering). Generally
|
||||
speaking, a plugin must be stateless, but if it cannot, then this method is
|
||||
the best place to clean-up.
|
||||
|
||||
```java
|
||||
final Markwon markwon = Markwon.builder(context)
|
||||
.usePlugin(new AbstractMarkwonPlugin() {
|
||||
@Override
|
||||
public void afterRender(@NonNull Node node, @NonNull MarkwonVisitor visitor) {
|
||||
cleanUp();
|
||||
}
|
||||
})
|
||||
.build();
|
||||
```
|
||||
|
||||
## Prepare TextView
|
||||
|
||||
A plugin can _prepare_ a TextView before markdown is applied. For example `images`
|
||||
unschedules all previously scheduled `AsyncDrawableSpans` (if any) here. This way
|
||||
when new markdown (and set of Spannables) arrives, previous set won't be kept in
|
||||
memory and could be garbage-collected.
|
||||
|
||||
```java
|
||||
final Markwon markwon = Markwon.builder(context)
|
||||
.usePlugin(new AbstractMarkwonPlugin() {
|
||||
@Override
|
||||
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
|
||||
// clean-up previous
|
||||
AsyncDrawableScheduler.unschedule(textView);
|
||||
}
|
||||
})
|
||||
.build();
|
||||
```
|
||||
|
||||
## TextView after markdown applied
|
||||
|
||||
A plugin will receive a callback _after_ markdown is applied to a TextView.
|
||||
For example `images` uses this callback to schedule new set of Spannables.
|
||||
|
||||
```java
|
||||
final Markwon markwon = Markwon.builder(context)
|
||||
.usePlugin(new AbstractMarkwonPlugin() {
|
||||
@Override
|
||||
public void afterSetText(@NonNull TextView textView) {
|
||||
AsyncDrawableScheduler.schedule(textView);
|
||||
}
|
||||
})
|
||||
.build();
|
||||
```
|
||||
|
||||
:::tip
|
||||
Please note that unlike `#beforeSetText`, `#afterSetText` won't receive
|
||||
`Spanned` markdown. This happens because at this point spans must be
|
||||
queried directly from a TextView.
|
||||
:::
|
1
docs/docs/core/spans-factory.md
Normal file
1
docs/docs/core/spans-factory.md
Normal file
@ -0,0 +1 @@
|
||||
# Spans Factory
|
1
docs/docs/core/visitor.md
Normal file
1
docs/docs/core/visitor.md
Normal file
@ -0,0 +1 @@
|
||||
# Visitor
|
@ -1,61 +0,0 @@
|
||||
# Factory <Badge text="1.1.0" />
|
||||
|
||||
`SpannableFactory` is used to create Span implementations.
|
||||
|
||||
```java
|
||||
SpannableConfiguration.builder(context)
|
||||
.factory(SpannableFactory)
|
||||
.build();
|
||||
```
|
||||
|
||||
`Markwon` provides default `SpannableFactoryDef` implementation that is
|
||||
used by default.
|
||||
|
||||
Spans:
|
||||
* `strongEmphasis`
|
||||
* `emphasis`
|
||||
* `blockQuote`
|
||||
* `code`
|
||||
* `orderedListItem`
|
||||
* `bulletListItem`
|
||||
* `thematicBreak`
|
||||
* `heading`
|
||||
* `strikethrough`
|
||||
* `taskListItem`
|
||||
* `tableRow`
|
||||
* `paragraph` <Badge text="1.1.1" />
|
||||
* `image`
|
||||
* `link`
|
||||
* `superScript` (HTML content only)
|
||||
* `subScript` (HTML content only)
|
||||
* `underline` (HTML content only)
|
||||
|
||||
:::tip
|
||||
`SpannableFactory` can be used to ignore some kinds of text markup. If, for example,
|
||||
you do not wish to apply _emphasis_ styling to your final result, just return `null`
|
||||
from `emphasis` factory method:
|
||||
```java
|
||||
@Nullable
|
||||
@Override
|
||||
public Object emphasis() {
|
||||
return null;
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
:::tip
|
||||
All factory methods in `SpannableFactory` return an `Object`, but you can actually
|
||||
return an **array of Objects** if you wish to apply multiple Spans to a single styling node.
|
||||
For example, let's make all _emphasis_ also <span :style="{color: '#F00'}">red</span>:
|
||||
|
||||
```java
|
||||
@Nullable
|
||||
@Override
|
||||
public Object emphasis() {
|
||||
return new Object[] {
|
||||
super.emphasis(),
|
||||
new ForegroundColorSpan(Color.RED)
|
||||
};
|
||||
}
|
||||
```
|
||||
:::
|
@ -1 +1,8 @@
|
||||
# Migration 2.x.x -> 3.x.x
|
||||
|
||||
* strikethrough moved to standalone module
|
||||
* tables moved to standalone module
|
||||
* core functionality of `AsyncDrawableLoader` moved to `core` module
|
||||
* * Handling of GIF and SVG media moved to standalone modules (`gif` and `svg` respectively)
|
||||
* * OkHttpClient to download images moved to standalone module
|
||||
* HTML no longer _implicitly_ added to core functionality, it must be specified __explicitly__ (as an artifact)
|
7
docs/donate.md
Normal file
7
docs/donate.md
Normal file
@ -0,0 +1,7 @@
|
||||
# Donations
|
||||
|
||||
If you find this library useful consider making a donation
|
||||
to your local [environmental](https://en.wikipedia.org/wiki/List_of_environmental_organizations)
|
||||
or [human rights](https://en.wikipedia.org/wiki/List_of_human_rights_organisations) organization.
|
||||
|
||||
Thank you
|
@ -57,11 +57,6 @@ public abstract class AbstractMarkwonPlugin implements MarkwonPlugin {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureRenderProps(@NonNull RenderProps renderProps) {
|
||||
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Priority priority() {
|
||||
|
@ -46,14 +46,7 @@ class MarkwonImpl extends Markwon {
|
||||
@Override
|
||||
public Spanned render(@NonNull Node node) {
|
||||
|
||||
final RenderProps renderProps = visitor.renderProps();
|
||||
|
||||
for (MarkwonPlugin plugin : plugins) {
|
||||
|
||||
// let plugins apply render properties before rendering (as we will clear
|
||||
// renderProps after rendering)
|
||||
plugin.configureRenderProps(renderProps);
|
||||
|
||||
plugin.beforeRender(node);
|
||||
}
|
||||
|
||||
|
@ -82,19 +82,6 @@ public interface MarkwonPlugin {
|
||||
*/
|
||||
void configureHtmlRenderer(@NonNull MarkwonHtmlRenderer.Builder builder);
|
||||
|
||||
/**
|
||||
* A method to store some arbitrary data in {@link RenderProps}. Although it won\'t make
|
||||
* much sense to use existing {@link Prop} keys for {@link SpanFactory}, it can be helpful
|
||||
* to establish a communication channel between multiple plugins in decoupled way (provide
|
||||
* some initial properties for example or indicate that certain plugin is registered).
|
||||
* <p>
|
||||
* This method will be called before <em>each</em> rendering step (after rendering {@link RenderProps}
|
||||
* will be cleared. This method <strong>won\'t</strong> be called during initialization stage.
|
||||
*
|
||||
* @see RenderProps
|
||||
*/
|
||||
void configureRenderProps(@NonNull RenderProps renderProps);
|
||||
|
||||
@NonNull
|
||||
Priority priority();
|
||||
|
||||
|
@ -3,6 +3,8 @@ package ru.noties.markwon.html;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import ru.noties.markwon.MarkwonVisitor;
|
||||
|
||||
/**
|
||||
@ -39,19 +41,11 @@ public abstract class MarkwonHtmlRenderer {
|
||||
@NonNull
|
||||
Builder allowNonClosedTags(boolean allowNonClosedTags);
|
||||
|
||||
/**
|
||||
* Please note that if there is already a {@link TagHandler} registered with specified
|
||||
* {@code tagName} it will be replaced with newly supplied one.
|
||||
*
|
||||
* @param tagHandler {@link TagHandler}
|
||||
* @param tagName name of a tag
|
||||
* @return self
|
||||
*/
|
||||
@NonNull
|
||||
Builder addHandler(@NonNull TagHandler tagHandler, @NonNull String tagName);
|
||||
Builder addHandler(@NonNull String tagName, @NonNull TagHandler tagHandler);
|
||||
|
||||
@NonNull
|
||||
Builder addHandler(@NonNull TagHandler tagHandler, String... tagNames);
|
||||
Builder addHandler(@NonNull Collection<String> tagNames, @NonNull TagHandler tagHandler);
|
||||
|
||||
@NonNull
|
||||
Builder removeHandler(@NonNull String tagName);
|
||||
|
@ -3,6 +3,7 @@ package ru.noties.markwon.html;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@ -99,14 +100,14 @@ class MarkwonHtmlRendererImpl extends MarkwonHtmlRenderer {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Builder addHandler(@NonNull TagHandler tagHandler, @NonNull String tagName) {
|
||||
public Builder addHandler(@NonNull String tagName, @NonNull TagHandler tagHandler) {
|
||||
tagHandlers.put(tagName, tagHandler);
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Builder addHandler(@NonNull TagHandler tagHandler, String... tagNames) {
|
||||
public Builder addHandler(@NonNull Collection<String> tagNames, @NonNull TagHandler tagHandler) {
|
||||
for (String tagName : tagNames) {
|
||||
if (tagName != null) {
|
||||
tagHandlers.put(tagName, tagHandler);
|
||||
|
@ -223,7 +223,6 @@ public class MarkwonBuilderImplTest {
|
||||
verify(plugin, atLeast(1)).priority();
|
||||
|
||||
// note, no render props -> they must be configured on render stage
|
||||
verify(plugin, times(0)).configureRenderProps(any(RenderProps.class));
|
||||
verify(plugin, times(0)).processMarkdown(anyString());
|
||||
verify(plugin, times(0)).beforeRender(any(Node.class));
|
||||
verify(plugin, times(0)).afterRender(any(Node.class), any(MarkwonVisitor.class));
|
||||
|
@ -107,8 +107,6 @@ public class MarkwonImplTest {
|
||||
// mark this flag (we must ensure that this method body is executed)
|
||||
flag.set(true);
|
||||
|
||||
//noinspection ConstantConditions
|
||||
verify(plugin, times(1)).configureRenderProps(null);
|
||||
verify(plugin, times(1)).beforeRender(eq(node));
|
||||
verify(plugin, times(0)).afterRender(any(Node.class), any(MarkwonVisitor.class));
|
||||
|
||||
@ -175,8 +173,6 @@ public class MarkwonImplTest {
|
||||
|
||||
flag.set(true);
|
||||
|
||||
verify(visitor, times(1)).renderProps();
|
||||
verify(plugin, times(1)).configureRenderProps(eq(renderProps));
|
||||
verify(renderProps, times(0)).clearAll();
|
||||
|
||||
return null;
|
||||
|
@ -186,6 +186,7 @@ public class CorePluginTest {
|
||||
add("configureVisitor");
|
||||
add("configureSpansFactory");
|
||||
add("beforeSetText");
|
||||
add("afterSetText");
|
||||
add("priority");
|
||||
}};
|
||||
|
||||
|
@ -22,6 +22,8 @@ import ru.noties.markwon.html.tag.SubScriptHandler;
|
||||
import ru.noties.markwon.html.tag.SuperScriptHandler;
|
||||
import ru.noties.markwon.html.tag.UnderlineHandler;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
/**
|
||||
* @since 3.0.0
|
||||
*/
|
||||
@ -41,18 +43,41 @@ public class HtmlPlugin extends AbstractMarkwonPlugin {
|
||||
|
||||
@Override
|
||||
public void configureHtmlRenderer(@NonNull MarkwonHtmlRenderer.Builder builder) {
|
||||
|
||||
builder
|
||||
.addHandler(new EmphasisHandler(), "i", "em", "cite", "dfn")
|
||||
.addHandler(new StrongEmphasisHandler(), "b", "strong")
|
||||
.addHandler(new SuperScriptHandler(), "sup")
|
||||
.addHandler(new SubScriptHandler(), "sub")
|
||||
.addHandler(new UnderlineHandler(), "u", "ins")
|
||||
.addHandler(new StrikeHandler(), "s", "del")
|
||||
.addHandler(new LinkHandler(), "a")
|
||||
.addHandler(new ListHandler(), "ul", "ol")
|
||||
.addHandler(ImageHandler.create(), "img")
|
||||
.addHandler(new BlockquoteHandler(), "blockquote")
|
||||
.addHandler(new HeadingHandler(), "h1", "h2", "h3", "h4", "h5", "h6");
|
||||
.addHandler(
|
||||
"img",
|
||||
ImageHandler.create())
|
||||
.addHandler(
|
||||
"a",
|
||||
new LinkHandler())
|
||||
.addHandler(
|
||||
"blockquote",
|
||||
new BlockquoteHandler())
|
||||
.addHandler(
|
||||
"sub",
|
||||
new SubScriptHandler())
|
||||
.addHandler(
|
||||
"sup",
|
||||
new SuperScriptHandler())
|
||||
.addHandler(
|
||||
asList("b", "strong"),
|
||||
new StrongEmphasisHandler())
|
||||
.addHandler(
|
||||
asList("s", "del"),
|
||||
new StrikeHandler())
|
||||
.addHandler(
|
||||
asList("u", "ins"),
|
||||
new UnderlineHandler())
|
||||
.addHandler(
|
||||
asList("ul", "ol"),
|
||||
new ListHandler())
|
||||
.addHandler(
|
||||
asList("i", "em", "cite", "dfn"),
|
||||
new EmphasisHandler())
|
||||
.addHandler(
|
||||
asList("h1", "h2", "h3", "h4", "h5", "h6"),
|
||||
new HeadingHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
Loading…
x
Reference in New Issue
Block a user