Working with documentation

This commit is contained in:
Dimitry Ivanov 2019-01-20 16:04:28 +03:00
parent d91f367e0a
commit 18fd56a97c
20 changed files with 217 additions and 121 deletions

View File

@ -49,17 +49,20 @@ module.exports = {
'/docs/core/configuration.md', '/docs/core/configuration.md',
'/docs/core/visitor.md', '/docs/core/visitor.md',
'/docs/core/spans-factory.md', '/docs/core/spans-factory.md',
'/docs/core/html-renderer.md' '/docs/core/html-renderer.md',
'/docs/core/core-plugin.md',
'/docs/core/movement-method-plugin.md'
] ]
}, },
'/docs/ext-latex/', '/docs/ext-latex/',
'/docs/ext-strikethrough/strikethrough.md', '/docs/ext-strikethrough/',
'/docs/ext-tables/tables.md', '/docs/ext-tables/',
'/docs/ext-tasklist/tasklist.md', '/docs/ext-tasklist/',
{ {
title: 'HTML', title: 'HTML',
children: [ children: [
'/docs/html/html.md' '/docs/html/',
'/docs/html/custom-tag-handler.md'
] ]
}, },
{ {
@ -70,8 +73,8 @@ module.exports = {
'/docs/image/svg.md' '/docs/image/svg.md'
] ]
}, },
'/docs/recycler/recycler.md', '/docs/recycler/',
'/docs/syntax-highlight/syntax-highlight.md', '/docs/syntax-highlight/',
'/docs/migration-2-3.md' '/docs/migration-2-3.md'
] ]
}, },

View File

@ -25,18 +25,19 @@ listed in <Link name="commonmark-spec" /> are supported (including support for *
* Emphasis (`*`, `_`) * Emphasis (`*`, `_`)
* Strong emphasis (`**`, `__`) * Strong emphasis (`**`, `__`)
* Strike-through (`~~`)
* Headers (`#{1,6}`) * Headers (`#{1,6}`)
* Links (`[]()` && `[][]`) * Links (`[]()` && `[][]`)
* [Images](/docs/image-loader.md) * [Images](/docs/core/images.md)
* Thematic break (`---`, `***`, `___`) * Thematic break (`---`, `***`, `___`)
* Quotes & nested quotes (`>{1,}`) * Quotes & nested quotes (`>{1,}`)
* Ordered & non-ordered lists & nested ones * Ordered & non-ordered lists & nested ones
* Inline code * Inline code
* Code blocks * Code blocks
* Tables (*with limitations*) * [Strike-through](/docs/ext-strikethrough/) (`~~`)
* [Syntax highlight](/docs/syntax-highlight.md) * [Tables](/docs/ext-tables/) (*with limitations*)
* [HTML](/docs/html.md) * [Syntax highlight](/docs/syntax-highlight/)
* [LaTeX](/docs/ext-latex/) formulas
* [HTML](/docs/html/)
* Emphasis (`<i>`, `<em>`, `<cite>`, `<dfn>`) * Emphasis (`<i>`, `<em>`, `<cite>`, `<dfn>`)
* Strong emphasis (`<b>`, `<strong>`) * Strong emphasis (`<b>`, `<strong>`)
* SuperScript (`<sup>`) * SuperScript (`<sup>`)
@ -49,8 +50,8 @@ listed in <Link name="commonmark-spec" /> are supported (including support for *
* Blockquote (`blockquote`) * Blockquote (`blockquote`)
* Heading (`h1`, `h2`, `h3`, `h4`, `h5`, `h6`) * Heading (`h1`, `h2`, `h3`, `h4`, `h5`, `h6`)
* there is support to render any HTML tag, but it will require to create a special `TagHandler`, * there is support to render any HTML tag, but it will require to create a special `TagHandler`,
more information can be found in [HTML section](/docs/html.md#custom-tag-handler) more information can be found in [HTML section](/docs/html/custom-tag-handler.md)
* Task lists: * [Task lists](/docs/ext-tasklist/):
- [ ] Not _done_ - [ ] Not _done_
- [X] **Done** with `X` - [X] **Done** with `X`
- [x] ~~and~~ **or** small `x` - [x] ~~and~~ **or** small `x`

View File

@ -0,0 +1,103 @@
# Core plugin <Badge text="3.0.0" />
Since <Badge text="3.0.0" /> with introduction of _plugins_, Markwon
**core** functionality was moved to a dedicated plugin.
```java
CorePlugin.create();
```
## Node visitors
`CorePlugin` registers these `commonmark-java` node visitors:
* `Text`
* `StrongEmphasis`
* `Emphasis`
* `BlockQuote`
* `Code`
* `FencedCodeBlock`
* `IndentedCodeBlock`
* `BulletList`
* `OrderedList`
* `ListItem`
* `ThematicBreak`
* `Heading`
* `SoftLineBreak`
* `HardLineBreak`
* `Paragraph`
* `Link`
## Span factories
`CorePlugin` adds these `SpanFactory`s:
* `StrongEmphasis`
* `Emphasis`
* `BlockQuote`
* `Code`
* `FencedCodeBlock`
* `IndentedCodeBlock`
* `ListItem`
* `Heading`
* `Link`
* `ThematicBreak`
:::tip
By default `CorePlugin` does not register a `Paragraph` `SpanFactory` but
this can be done in your custom plugin:
```java
Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
builder.setFactory(Paragraph.class, (configuration, props) ->
new ForegroundColorSpan(Color.RED));
}
})
```
:::
## Props
These props are exported by `CorePlugin` and can be found in `CoreProps`:
* `Prop<ListItemType> LIST_ITEM_TYPE` (BULLET | ORDERED)
* `Prop<Integer> BULLET_LIST_ITEM_LEVEL`
* `Prop<Integer> ORDERED_LIST_ITEM_NUMBER`
* `Prop<Integer> HEADING_LEVEL`
* `Prop<String> LINK_DESTINATION`
* `Prop<Boolean> PARAGRAPH_IS_IN_TIGHT_LIST`
:::warning List item type
Before <Badge text="3.0.0" /> `Markwon` had 2 distinct lists (bullet and ordered).
Since <Badge text="3.0.0" /> a single `SpanFactory` is used, which internally checks
for `Prop<ListItemType> LIST_ITEM_TYPE`.
Beware of this if you would like to override only one of the list types. This is
done to correspond to `commonmark-java` implementation.
:::
---
:::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/core/movement-method-plugin.md)
which accepts a MovementMethod as an argument.
:::

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

@ -24,6 +24,19 @@ final Markwon markwon = Markwon.builder(context)
.usePlugin(TablePlugin.create(tableTheme)) .usePlugin(TablePlugin.create(tableTheme))
``` ```
```java
Markwon.builder(context)
.usePlugin(TablePlugin.create(builder ->
builder
.tableBorderColor(Color.RED)
.tableBorderWidth(0)
.tableCellPadding(0)
.tableHeaderRowBackgroundColor(Color.BLACK)
.tableEvenRowBackgroundColor(Color.GREEN)
.tableOddRowBackgroundColor(Color.YELLOW)
))
```
Please note, that _by default_ tables have limitations. For example, there is no support Please note, that _by default_ tables have limitations. For example, there is no support
for images inside table cells. And table contents won't be copied to clipboard if a TextView for images inside table cells. And table contents won't be copied to clipboard if a TextView
has such functionality. Table will always take full width of a TextView in which it is displayed. has such functionality. Table will always take full width of a TextView in which it is displayed.

View File

@ -0,0 +1 @@
# HTML custom tag handler

View File

@ -50,18 +50,10 @@ public class CorePlugin extends AbstractMarkwonPlugin {
@NonNull @NonNull
public static CorePlugin create() { public static CorePlugin create() {
return create(false); return new CorePlugin();
} }
@NonNull protected CorePlugin() {
public static CorePlugin create(boolean softBreakAddsNewLine) {
return new CorePlugin(softBreakAddsNewLine);
}
private final boolean softBreakAddsNewLine;
protected CorePlugin(boolean softBreakAddsNewLine) {
this.softBreakAddsNewLine = softBreakAddsNewLine;
} }
@Override @Override
@ -78,7 +70,7 @@ public class CorePlugin extends AbstractMarkwonPlugin {
listItem(builder); listItem(builder);
thematicBreak(builder); thematicBreak(builder);
heading(builder); heading(builder);
softLineBreak(builder, softBreakAddsNewLine); softLineBreak(builder);
hardLineBreak(builder); hardLineBreak(builder);
paragraph(builder); paragraph(builder);
link(builder); link(builder);
@ -341,15 +333,11 @@ public class CorePlugin extends AbstractMarkwonPlugin {
}); });
} }
private static void softLineBreak(@NonNull MarkwonVisitor.Builder builder, final boolean softBreakAddsNewLine) { private static void softLineBreak(@NonNull MarkwonVisitor.Builder builder) {
builder.on(SoftLineBreak.class, new MarkwonVisitor.NodeVisitor<SoftLineBreak>() { builder.on(SoftLineBreak.class, new MarkwonVisitor.NodeVisitor<SoftLineBreak>() {
@Override @Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull SoftLineBreak softLineBreak) { public void visit(@NonNull MarkwonVisitor visitor, @NonNull SoftLineBreak softLineBreak) {
if (softBreakAddsNewLine) { visitor.builder().append(' ');
visitor.ensureNewLine();
} else {
visitor.builder().append(' ');
}
} }
}); });
} }

View File

@ -126,7 +126,7 @@ public class MarkwonBuilderImplTest {
}; };
// our subclass // our subclass
final CorePlugin corePlugin = new CorePlugin(false) { final CorePlugin corePlugin = new CorePlugin() {
}; };

View File

@ -2,6 +2,8 @@ package ru.noties.markwon.core;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.text.method.MovementMethod;
import android.widget.TextView;
import org.commonmark.node.BlockQuote; import org.commonmark.node.BlockQuote;
import org.commonmark.node.BulletList; import org.commonmark.node.BulletList;
@ -46,6 +48,7 @@ import ru.noties.markwon.SpannableBuilder;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
@ -223,26 +226,9 @@ public class CorePluginTest {
} }
@Test @Test
public void softbreak_adds_new_line_default() { public void softbreak() {
// default is false
softbreak_adds_new_line(CorePlugin.create(), false);
}
@Test final CorePlugin plugin = CorePlugin.create();
public void softbreak_adds_new_line_false() {
// a space character will be added
softbreak_adds_new_line(CorePlugin.create(false), false);
}
@Test
public void softbreak_adds_new_line_true() {
// a new line will be added
softbreak_adds_new_line(CorePlugin.create(true), true);
}
private static void softbreak_adds_new_line(
@NonNull CorePlugin plugin,
boolean softBreakAddsNewLine) {
final MarkwonVisitor.Builder builder = mock(MarkwonVisitor.Builder.class); final MarkwonVisitor.Builder builder = mock(MarkwonVisitor.Builder.class);
when(builder.on(any(Class.class), any(MarkwonVisitor.NodeVisitor.class))).thenReturn(builder); when(builder.on(any(Class.class), any(MarkwonVisitor.NodeVisitor.class))).thenReturn(builder);
@ -259,21 +245,44 @@ public class CorePluginTest {
final MarkwonVisitor.NodeVisitor<SoftLineBreak> nodeVisitor = captor.getValue(); final MarkwonVisitor.NodeVisitor<SoftLineBreak> nodeVisitor = captor.getValue();
final MarkwonVisitor visitor = mock(MarkwonVisitor.class); final MarkwonVisitor visitor = mock(MarkwonVisitor.class);
if (!softBreakAddsNewLine) { // we must mock SpannableBuilder and verify that it has a space character appended
final SpannableBuilder spannableBuilder = mock(SpannableBuilder.class);
when(visitor.builder()).thenReturn(spannableBuilder);
nodeVisitor.visit(visitor, mock(SoftLineBreak.class));
// we must mock SpannableBuilder and verify that it has a space character appended verify(visitor, times(1)).builder();
final SpannableBuilder spannableBuilder = mock(SpannableBuilder.class); verify(spannableBuilder, times(1)).append(eq(' '));
when(visitor.builder()).thenReturn(spannableBuilder); }
nodeVisitor.visit(visitor, mock(SoftLineBreak.class));
verify(visitor, times(1)).builder(); @Test
verify(spannableBuilder, times(1)).append(eq(' ')); public void implicit_movement_method_after_set_text_added() {
// validate that CorePlugin will implicitly add LinkMovementMethod if one is missing
final TextView textView = mock(TextView.class);
when(textView.getMovementMethod()).thenReturn(null);
} else { final CorePlugin plugin = CorePlugin.create();
nodeVisitor.visit(visitor, mock(SoftLineBreak.class)); assertNull(textView.getMovementMethod());
verify(visitor, times(1)).ensureNewLine(); plugin.afterSetText(textView);
}
final ArgumentCaptor<MovementMethod> captor = ArgumentCaptor.forClass(MovementMethod.class);
verify(textView, times(1)).setMovementMethod(captor.capture());
assertNotNull(captor.getValue());
}
@Test
public void implicit_movement_method_after_set_text_no_op() {
// validate that CorePlugin won't change movement method if one is present on a TextView
final TextView textView = mock(TextView.class);
when(textView.getMovementMethod()).thenReturn(mock(MovementMethod.class));
final CorePlugin plugin = CorePlugin.create();
plugin.afterSetText(textView);
verify(textView, times(0)).setMovementMethod(any(MovementMethod.class));
} }
} }

View File

@ -73,7 +73,7 @@ abstract class BaseSuiteTest {
@NonNull @NonNull
Markwon markwon() { Markwon markwon() {
return Markwon.builder(RuntimeEnvironment.application) return Markwon.builder(RuntimeEnvironment.application)
.usePlugin(CorePlugin.create(softBreakAddsNewLine())) .usePlugin(CorePlugin.create())
.usePlugin(new AbstractMarkwonPlugin() { .usePlugin(new AbstractMarkwonPlugin() {
@Override @Override
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
@ -99,10 +99,6 @@ abstract class BaseSuiteTest {
return false; return false;
} }
boolean softBreakAddsNewLine() {
return false;
}
private static final Map<Class<? extends Node>, SpanFactory> CORE_NODES; private static final Map<Class<? extends Node>, SpanFactory> CORE_NODES;
static { static {

View File

@ -1,39 +0,0 @@
package ru.noties.markwon.core.suite;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import ru.noties.markwon.test.TestSpan.Document;
import static ru.noties.markwon.test.TestSpan.document;
import static ru.noties.markwon.test.TestSpan.text;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class SoftBreakAddsNewLineTest extends BaseSuiteTest {
/*
hello there!
this one is on the next line
hard break to the full extend
*/
@Test
public void test() {
final Document document = document(
text("hello there!\n"),
text("this one is on the next line\n"),
text("hard break to the full extend")
);
matchInput("soft-break-adds-new-line.md", document);
}
@Override
boolean softBreakAddsNewLine() {
return true;
}
}

View File

@ -14,20 +14,15 @@ import static ru.noties.markwon.test.TestSpan.text;
@Config(manifest = Config.NONE) @Config(manifest = Config.NONE)
public class SoftBreakTest extends BaseSuiteTest { public class SoftBreakTest extends BaseSuiteTest {
@Test @Test
public void test() { public void test() {
final Document document = document( final Document document = document(
text("First line "), text("First line "),
text("same line but with space between "), text("same line but with space between "),
text("this is also the first line") text("this is also the first line")
); );
matchInput("soft-break.md", document); matchInput("soft-break.md", document);
} }
@Override
boolean softBreakAddsNewLine() {
return false;
}
} }

View File

@ -109,7 +109,7 @@ public class PriorityProcessorTest {
public void subclass_found() { public void subclass_found() {
// when a plugin comes after another, but _another_ was subclassed and placed in the list // when a plugin comes after another, but _another_ was subclassed and placed in the list
final MarkwonPlugin core = new CorePlugin(false) { final MarkwonPlugin core = new CorePlugin() {
}; };
final MarkwonPlugin plugin = new AbstractMarkwonPlugin() { final MarkwonPlugin plugin = new AbstractMarkwonPlugin() {
@NonNull @NonNull

View File

@ -30,6 +30,13 @@ public class TablePlugin extends AbstractMarkwonPlugin {
void configureTheme(@NonNull TableTheme.Builder builder); void configureTheme(@NonNull TableTheme.Builder builder);
} }
/**
* Factory method to create a {@link TablePlugin} with default {@link TableTheme} instance
* (obtained via {@link TableTheme#create(Context)} method)
*
* @see #create(TableTheme)
* @see #create(ThemeConfigure)
*/
@NonNull @NonNull
public static TablePlugin create(@NonNull Context context) { public static TablePlugin create(@NonNull Context context) {
return new TablePlugin(TableTheme.create(context)); return new TablePlugin(TableTheme.create(context));

View File

@ -81,6 +81,8 @@ public class RecyclerActivity extends Activity {
.usePlugin(CorePlugin.create()) .usePlugin(CorePlugin.create())
.usePlugin(ImagesPlugin.createWithAssets(context)) .usePlugin(ImagesPlugin.createWithAssets(context))
.usePlugin(SvgPlugin.create(context.getResources())) .usePlugin(SvgPlugin.create(context.getResources()))
// although we will be rendering table differently we still need
// to register commonmark-java tables extension (which TablePlugin does)
.usePlugin(TablePlugin.create(context)) .usePlugin(TablePlugin.create(context))
.usePlugin(HtmlPlugin.create()) .usePlugin(HtmlPlugin.create())
.usePlugin(new AbstractMarkwonPlugin() { .usePlugin(new AbstractMarkwonPlugin() {