Task lists implementation
This commit is contained in:
parent
f95918104b
commit
bf5a8142f5
@ -6,9 +6,6 @@
|
||||
[](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%markwon-image-loader%22)
|
||||
[](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%markwon-view%22)
|
||||
|
||||
- [ ] one **one** _one_
|
||||
- [X] ~~two~~
|
||||
|
||||
**Markwon** is a library for Android that renders markdown as system-native Spannables. It gives ability to display markdown in all TextView widgets (**TextView**, **Button**, **Switch**, **CheckBox**, etc), **Notifications**, **Toasts**, etc. <u>**No WebView is required**</u>. Library provides reasonable defaults for display style of markdown but also gives all the means to tweak the appearance if desired. All markdown features are supported (including limited support for inlined HTML code, markdown tables and images).
|
||||
|
||||
<sup>*</sup>*This file is displayed by default in the [sample-apk] application. Which is a generic markdown viewer with support to display markdown via `http`, `https` & `file` schemes and 2 themes included: Light & Dark*
|
||||
@ -41,6 +38,11 @@ compile 'ru.noties:markwon-view:1.0.0' // optional
|
||||
* * Underline (`<u>`)
|
||||
* * Strike-through (`<s>`, `<strike>`, `<del>`)
|
||||
* other inline html is rendered via (`Html.fromHtml(...)`)
|
||||
* Task lists:
|
||||
|
||||
- [ ] Not _done_
|
||||
- [X] **Done** with `X`
|
||||
- [x] ~~and~~ **or** small `x`
|
||||
|
||||
---
|
||||
|
||||
|
@ -105,6 +105,16 @@ public Builder tableBorderWidth(@Dimension int tableBorderWidth);
|
||||
public Builder tableOddRowBackgroundColor(@ColorInt int tableOddRowBackgroundColor);
|
||||
```
|
||||
|
||||
#### Task lists
|
||||
|
||||
Task lists are supported but with some limitations. First of all, task list cannot be nested
|
||||
(in a list, quote, etc). By default (if used factory method `builderWithDefaults`) TaskListDrawable
|
||||
will be used with `linkColor` as the primary color and `windowBackground` as the checkMarkColor.
|
||||
|
||||
```java
|
||||
public Builder taskListDrawable(@NonNull Drawable taskListDrawable);
|
||||
```
|
||||
|
||||
|
||||
### Contents
|
||||
|
||||
|
@ -1,10 +0,0 @@
|
||||
apply plugin: 'java'
|
||||
|
||||
sourceCompatibility = "1.7"
|
||||
targetCompatibility = "1.7"
|
||||
|
||||
dependencies {
|
||||
compile SUPPORT_ANNOTATIONS
|
||||
compile COMMON_MARK
|
||||
compile 'ru.noties:debug:3.0.0@jar'
|
||||
}
|
@ -18,8 +18,6 @@ dependencies {
|
||||
compile COMMON_MARK
|
||||
compile COMMON_MARK_STRIKETHROUGHT
|
||||
compile COMMON_MARK_TABLE
|
||||
|
||||
compile project(':library-task-list')
|
||||
}
|
||||
|
||||
if (project.hasProperty('release')) {
|
||||
|
@ -17,7 +17,7 @@ import java.util.Arrays;
|
||||
import ru.noties.markwon.renderer.SpannableRenderer;
|
||||
import ru.noties.markwon.tasklist.TaskListExtension;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||
public abstract class Markwon {
|
||||
|
||||
/**
|
||||
|
@ -299,11 +299,25 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
|
||||
if (customNode instanceof TaskListItem) {
|
||||
|
||||
final TaskListItem listItem = (TaskListItem) customNode;
|
||||
|
||||
final int length = builder.length();
|
||||
|
||||
blockQuoteIndent += listItem.indent();
|
||||
|
||||
visitChildren(customNode);
|
||||
setSpan(length, new TaskListSpan(configuration.theme(), blockQuoteIndent, length, ((TaskListItem) customNode).done()));
|
||||
|
||||
setSpan(length, new TaskListSpan(
|
||||
configuration.theme(),
|
||||
blockQuoteIndent,
|
||||
length,
|
||||
listItem.done()
|
||||
));
|
||||
|
||||
newLine();
|
||||
|
||||
blockQuoteIndent -= listItem.indent();
|
||||
|
||||
} else {
|
||||
super.visit(customNode);
|
||||
}
|
||||
|
@ -8,16 +8,9 @@ import org.commonmark.node.Node;
|
||||
|
||||
import ru.noties.markwon.SpannableConfiguration;
|
||||
|
||||
// please note that this class does not implement Renderer in order to return CharSequence (instead of String)
|
||||
public class SpannableRenderer {
|
||||
|
||||
// todo
|
||||
// * LinkDrawableSpan, that draws link whilst image is still loading (it must be clickable...)
|
||||
// * Common interface for images (in markdown & inline-html)
|
||||
// * util method to properly copy markdown (with lists/links, etc)
|
||||
// * util to apply empty line height
|
||||
// * transform relative urls to absolute ones...
|
||||
|
||||
@Nullable
|
||||
public CharSequence render(@NonNull SpannableConfiguration configuration, @Nullable Node node) {
|
||||
final CharSequence out;
|
||||
if (node == null) {
|
||||
|
@ -11,22 +11,13 @@ import android.support.annotation.Dimension;
|
||||
import android.support.annotation.FloatRange;
|
||||
import android.support.annotation.IntRange;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextPaint;
|
||||
import android.util.TypedValue;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class SpannableTheme {
|
||||
//
|
||||
// // this method should be used if TextView is known beforehand
|
||||
// // it will correctly measure the `space` char and set it as `codeMultilineMargin`
|
||||
// // otherwise this value must be set explicitly
|
||||
// public static SpannableTheme create(@NonNull TextView textView) {
|
||||
// return builderWithDefaults(textView.getContext())
|
||||
// .codeMultilineMargin((int) (textView.getPaint().measureText("\u00a0") + .5F))
|
||||
// .build();
|
||||
// }
|
||||
|
||||
// this create default theme (except for `codeMultilineMargin` property)
|
||||
public static SpannableTheme create(@NonNull Context context) {
|
||||
return builderWithDefaults(context).build();
|
||||
}
|
||||
@ -41,6 +32,8 @@ public class SpannableTheme {
|
||||
|
||||
public static Builder builderWithDefaults(@NonNull Context context) {
|
||||
|
||||
// by default we will be using link color for the checkbox color
|
||||
// & window background as a checkMark color
|
||||
final int linkColor = resolve(context, android.R.attr.textColorLink);
|
||||
final int backgroundColor = resolve(context, android.R.attr.colorBackground);
|
||||
|
||||
@ -153,6 +146,8 @@ public class SpannableTheme {
|
||||
// by default paint.color * TABLE_ODD_ROW_DEF_ALPHA
|
||||
protected final int tableOddRowBackgroundColor;
|
||||
|
||||
// drawable that will be used to render checkbox (should be stateful)
|
||||
// TaskListDrawable can be used
|
||||
protected final Drawable taskListDrawable;
|
||||
|
||||
protected SpannableTheme(@NonNull Builder builder) {
|
||||
@ -252,10 +247,16 @@ public class SpannableTheme {
|
||||
|
||||
// custom typeface was set
|
||||
if (codeTypeface != null) {
|
||||
|
||||
paint.setTypeface(codeTypeface);
|
||||
|
||||
// please note that we won't be calculating textSize
|
||||
// (like we do when no Typeface is provided), if it's some specific typeface
|
||||
// we would confuse users about textSize
|
||||
if (codeTextSize != 0) {
|
||||
paint.setTextSize(codeTextSize);
|
||||
}
|
||||
|
||||
} else {
|
||||
paint.setTypeface(Typeface.MONOSPACE);
|
||||
final float textSize;
|
||||
@ -372,7 +373,7 @@ public class SpannableTheme {
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Nullable
|
||||
public Drawable getTaskListDrawable() {
|
||||
return taskListDrawable;
|
||||
}
|
||||
@ -546,6 +547,7 @@ public class SpannableTheme {
|
||||
}
|
||||
|
||||
private static class Dip {
|
||||
|
||||
private final float density;
|
||||
|
||||
Dip(@NonNull Context context) {
|
||||
|
@ -46,7 +46,7 @@ public class TaskListDrawable extends Drawable {
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
super.onBoundsChange(bounds);
|
||||
|
||||
// we should exclude stroke with from final bounds (half of the strokeWidth from both sides)
|
||||
// we should exclude stroke with from final bounds (half of the strokeWidth from all sides)
|
||||
|
||||
// we should have square shape
|
||||
final float min = Math.min(bounds.width(), bounds.height());
|
||||
|
@ -23,7 +23,7 @@ public class TaskListSpan implements LeadingMarginSpan {
|
||||
|
||||
@Override
|
||||
public int getLeadingMargin(boolean first) {
|
||||
return theme.getBlockMargin();
|
||||
return theme.getBlockMargin() * blockIndent;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -33,14 +33,17 @@ public class TaskListSpan implements LeadingMarginSpan {
|
||||
return;
|
||||
}
|
||||
|
||||
final Drawable drawable = theme.getTaskListDrawable();
|
||||
if (drawable == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int save = c.save();
|
||||
try {
|
||||
|
||||
final int width = theme.getBlockMargin();
|
||||
final int height = bottom - top;
|
||||
|
||||
final Drawable drawable = theme.getTaskListDrawable();
|
||||
|
||||
final int w = (int) (width * .75F + .5F);
|
||||
final int h = (int) (height * .75F + .5F);
|
||||
|
||||
|
@ -17,20 +17,20 @@ import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import ru.noties.debug.Debug;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
class TaskListBlockParser extends AbstractBlockParser {
|
||||
|
||||
private static final Pattern PATTERN = Pattern.compile("\\s*-\\s+\\[(x|X|\\s)\\]\\s+(.*)");
|
||||
// private static final Pattern PATTERN_2 = Pattern.compile("^\\s*-\\s+\\[(x|X|\\s)\\]\\s+(.*)");
|
||||
|
||||
private final TaskListBlock block = new TaskListBlock();
|
||||
|
||||
private final List<String> lines;
|
||||
private final List<Item> items = new ArrayList<>(3);
|
||||
|
||||
TaskListBlockParser(@NonNull String startLine) {
|
||||
this.lines = new ArrayList<>(3);
|
||||
this.lines.add(startLine);
|
||||
private int indent = 0;
|
||||
|
||||
TaskListBlockParser(@NonNull String startLine, int startIndent) {
|
||||
items.add(new Item(startLine, startIndent));
|
||||
indent = startIndent;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -45,16 +45,18 @@ class TaskListBlockParser extends AbstractBlockParser {
|
||||
|
||||
final String line = line(parserState);
|
||||
|
||||
// Debug.i("line: %s, find: %s", line, PATTERN.matcher(line).find());
|
||||
Debug.i("isBlank: %s, line: `%s`", parserState.isBlank(), line);
|
||||
final int currentIndent = parserState.getIndent();
|
||||
if (currentIndent > indent) {
|
||||
indent += 2;
|
||||
} else if (currentIndent < indent && indent > 1) {
|
||||
indent -= 2;
|
||||
}
|
||||
|
||||
if (line != null
|
||||
&& line.length() > 0
|
||||
&& PATTERN.matcher(line).matches()) {
|
||||
Debug.e();
|
||||
blockContinue = BlockContinue.atIndex(parserState.getIndex());
|
||||
} else {
|
||||
Debug.e();
|
||||
blockContinue = BlockContinue.finished();
|
||||
}
|
||||
|
||||
@ -63,54 +65,29 @@ class TaskListBlockParser extends AbstractBlockParser {
|
||||
|
||||
@Override
|
||||
public void addLine(CharSequence line) {
|
||||
Debug.i("line: %s", line);
|
||||
if (line != null
|
||||
&& line.length() > 0) {
|
||||
lines.add(line.toString());
|
||||
if (length(line) > 0) {
|
||||
items.add(new Item(line.toString(), indent));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseInlines(InlineParser inlineParser) {
|
||||
|
||||
Debug.i(lines);
|
||||
|
||||
Matcher matcher;
|
||||
|
||||
TaskListItem item;
|
||||
|
||||
for (String line : lines) {
|
||||
|
||||
matcher = PATTERN.matcher(line);
|
||||
TaskListItem listItem;
|
||||
|
||||
for (Item item : items) {
|
||||
matcher = PATTERN.matcher(item.line);
|
||||
if (!matcher.matches()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
item = new TaskListItem().done(isDone(matcher.group(1)));
|
||||
|
||||
inlineParser.parse(matcher.group(2), item);
|
||||
|
||||
block.appendChild(item);
|
||||
listItem = new TaskListItem()
|
||||
.done(isDone(matcher.group(1)))
|
||||
.indent(item.indent / 2);
|
||||
inlineParser.parse(matcher.group(2), listItem);
|
||||
block.appendChild(listItem);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isContainer() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canContain(Block block) {
|
||||
Debug.i("block: %s", block);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeBlock() {
|
||||
Debug.e(block);
|
||||
Debug.trace();
|
||||
}
|
||||
|
||||
static class Factory extends AbstractBlockParserFactory {
|
||||
@ -124,8 +101,14 @@ class TaskListBlockParser extends AbstractBlockParser {
|
||||
&& line.length() > 0
|
||||
&& PATTERN.matcher(line).matches()) {
|
||||
|
||||
return BlockStart.of(new TaskListBlockParser(line))
|
||||
.atIndex(state.getIndex() + line.length());
|
||||
final int length = line.length();
|
||||
final int index = state.getIndex();
|
||||
final int atIndex = index != 0
|
||||
? index + (length - index)
|
||||
: length;
|
||||
|
||||
return BlockStart.of(new TaskListBlockParser(line, state.getIndent()))
|
||||
.atIndex(atIndex);
|
||||
}
|
||||
|
||||
return BlockStart.none();
|
||||
@ -140,8 +123,25 @@ class TaskListBlockParser extends AbstractBlockParser {
|
||||
: null;
|
||||
}
|
||||
|
||||
private static int length(@Nullable CharSequence text) {
|
||||
return text != null
|
||||
? text.length()
|
||||
: 0;
|
||||
}
|
||||
|
||||
private static boolean isDone(@NonNull String value) {
|
||||
return "X".equals(value)
|
||||
|| "x".equals(value);
|
||||
}
|
||||
|
||||
private static class Item {
|
||||
|
||||
final String line;
|
||||
final int indent;
|
||||
|
||||
Item(@NonNull String line, int indent) {
|
||||
this.line = line;
|
||||
this.indent = indent;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,9 +2,11 @@ package ru.noties.markwon.tasklist;
|
||||
|
||||
import org.commonmark.node.CustomNode;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class TaskListItem extends CustomNode {
|
||||
|
||||
private boolean done;
|
||||
private int indent;
|
||||
|
||||
public boolean done() {
|
||||
return done;
|
||||
@ -14,4 +16,13 @@ public class TaskListItem extends CustomNode {
|
||||
this.done = done;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int indent() {
|
||||
return indent;
|
||||
}
|
||||
|
||||
public TaskListItem indent(int indent) {
|
||||
this.indent = indent;
|
||||
return this;
|
||||
}
|
||||
}
|
@ -1 +1 @@
|
||||
include ':app', ':library', ':library-image-loader', ':library-view', ':library-task-list'
|
||||
include ':app', ':library', ':library-image-loader', ':library-view'
|
||||
|
Loading…
x
Reference in New Issue
Block a user