Add markwon-recycler module (work in progress)

This commit is contained in:
Dimitry Ivanov 2018-12-24 18:16:17 +03:00
parent c58c31d5fd
commit f6ac3fde68
34 changed files with 1266 additions and 222 deletions

View File

@ -49,6 +49,8 @@ ext {
'push-aar-gradle': 'https://raw.githubusercontent.com/noties/gradle-mvn-push/master/gradle-mvn-push-aar.gradle' 'push-aar-gradle': 'https://raw.githubusercontent.com/noties/gradle-mvn-push/master/gradle-mvn-push-aar.gradle'
] ]
// for now 27.1.1 is used because it's the last one distributed with source files
// next version with sources is androidx one (we wait until migration)
final def supportVersion = '27.1.1' final def supportVersion = '27.1.1'
final def commonMarkVersion = '0.12.1' final def commonMarkVersion = '0.12.1'
final def daggerVersion = '2.10' final def daggerVersion = '2.10'
@ -56,6 +58,7 @@ ext {
deps = [ deps = [
'support-annotations' : "com.android.support:support-annotations:$supportVersion", 'support-annotations' : "com.android.support:support-annotations:$supportVersion",
'support-app-compat' : "com.android.support:appcompat-v7:$supportVersion", 'support-app-compat' : "com.android.support:appcompat-v7:$supportVersion",
'support-recycler-view' : "com.android.support:recyclerview-v7:$supportVersion",
'commonmark' : "com.atlassian.commonmark:commonmark:$commonMarkVersion", 'commonmark' : "com.atlassian.commonmark:commonmark:$commonMarkVersion",
'commonmark-strikethrough': "com.atlassian.commonmark:commonmark-ext-gfm-strikethrough:$commonMarkVersion", 'commonmark-strikethrough': "com.atlassian.commonmark:commonmark-ext-gfm-strikethrough:$commonMarkVersion",
'commonmark-table' : "com.atlassian.commonmark:commonmark-ext-gfm-tables:$commonMarkVersion", 'commonmark-table' : "com.atlassian.commonmark:commonmark-ext-gfm-tables:$commonMarkVersion",

View File

@ -7,8 +7,6 @@ import android.text.TextUtils;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import ru.noties.markwon.renderer.R;
abstract class TableRowsScheduler { abstract class TableRowsScheduler {
static void schedule(@NonNull final TextView view) { static void schedule(@NonNull final TextView view) {

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="markwon_tables_scheduler" type="id" />
</resources>

View File

@ -0,0 +1,25 @@
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')
deps.with {
api it['support-recycler-view']
}
}
registerArtifact(this)

View File

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

View File

@ -0,0 +1,110 @@
package ru.noties.markwon.recycler;
import android.support.annotation.IdRes;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.commonmark.node.Node;
import java.util.List;
import ru.noties.markwon.Markwon;
// each node block will be rendered by a simple TextView, but we can provide own entries for each block
public abstract class MarkwonAdapter extends RecyclerView.Adapter<MarkwonAdapter.Holder> {
@NonNull
public static Builder builder() {
return new MarkwonAdapterImpl.BuilderImpl();
}
@NonNull
public static MarkwonAdapter create() {
return new MarkwonAdapterImpl.BuilderImpl().build();
}
// for an adapter with only one entry (all blocks are rendered the same with this entry)
@NonNull
public static MarkwonAdapter create(@NonNull Entry<? extends Holder, ? extends Node> defaultEntry) {
return new MarkwonAdapterImpl.BuilderImpl().defaultEntry(defaultEntry).build();
}
@NonNull
public static MarkwonAdapter create(@LayoutRes int layoutResId) {
return new MarkwonAdapterImpl.BuilderImpl().defaultEntry(layoutResId).build();
}
public interface Builder {
@NonNull
<N extends Node> Builder include(
@NonNull Class<N> node,
@NonNull Entry<? extends Holder, ? super N> entry);
@NonNull
Builder defaultEntry(@NonNull Entry<? extends Holder, ? extends Node> defaultEntry);
@NonNull
Builder defaultEntry(@LayoutRes int layoutResId);
@NonNull
Builder reducer(@NonNull Reducer reducer);
@NonNull
MarkwonAdapter build();
}
public interface Entry<H extends Holder, N extends Node> {
@NonNull
H createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent);
void bindHolder(@NonNull Markwon markwon, @NonNull H holder, @NonNull N node);
long id(@NonNull N node);
// will be called when new content is available (clear internal cache if any)
void clear();
}
public interface Reducer {
@NonNull
List<Node> reduce(@NonNull Node root);
}
public abstract void setMarkdown(@NonNull Markwon markwon, @NonNull String markdown);
public abstract void setParsedMarkdown(@NonNull Markwon markwon, @NonNull Node document);
public abstract void setParsedMarkdown(@NonNull Markwon markwon, @NonNull List<Node> nodes);
@SuppressWarnings("WeakerAccess")
public static class Holder extends RecyclerView.ViewHolder {
public Holder(@NonNull View itemView) {
super(itemView);
}
// please note that this method should be called after constructor
@Nullable
protected <V extends View> V findView(@IdRes int id) {
return itemView.findViewById(id);
}
// please note that this method should be called after constructor
@NonNull
protected <V extends View> V requireView(@IdRes int id) {
final V v = itemView.findViewById(id);
if (v == null) {
throw new NullPointerException();
}
return v;
}
}
}

View File

@ -0,0 +1,209 @@
package ru.noties.markwon.recycler;
import android.support.annotation.NonNull;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import org.commonmark.node.Node;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import ru.noties.markwon.Markwon;
class MarkwonAdapterImpl extends MarkwonAdapter {
private final SparseArray<Entry<Holder, Node>> entries;
private final Entry<Holder, Node> defaultEntry;
private final Reducer reducer;
private LayoutInflater layoutInflater;
private Markwon markwon;
private List<Node> nodes;
MarkwonAdapterImpl(
@NonNull SparseArray<Entry<Holder, Node>> entries,
@NonNull Entry<Holder, Node> defaultEntry,
@NonNull Reducer reducer) {
this.entries = entries;
this.defaultEntry = defaultEntry;
this.reducer = reducer;
setHasStableIds(true);
}
@Override
public void setMarkdown(@NonNull Markwon markwon, @NonNull String markdown) {
setParsedMarkdown(markwon, markwon.parse(markdown));
}
@Override
public void setParsedMarkdown(@NonNull Markwon markwon, @NonNull Node document) {
setParsedMarkdown(markwon, reducer.reduce(document));
}
@Override
public void setParsedMarkdown(@NonNull Markwon markwon, @NonNull List<Node> nodes) {
// clear all entries before applying
defaultEntry.clear();
for (int i = 0, size = entries.size(); i < size; i++) {
entries.valueAt(i).clear();
}
this.markwon = markwon;
this.nodes = nodes;
}
@NonNull
@Override
public Holder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (layoutInflater == null) {
layoutInflater = LayoutInflater.from(parent.getContext());
}
final Entry<Holder, Node> entry = viewType == 0
? defaultEntry
: entries.get(viewType);
return entry.createHolder(layoutInflater, parent);
}
@Override
public void onBindViewHolder(@NonNull Holder holder, int position) {
final Node node = nodes.get(position);
final int viewType = getNodeViewType(node.getClass());
final Entry<Holder, Node> entry = viewType == 0
? defaultEntry
: entries.get(viewType);
entry.bindHolder(markwon, holder, node);
}
@Override
public int getItemCount() {
return nodes != null
? nodes.size()
: 0;
}
@NonNull
public List<Node> getItems() {
return nodes != null
? Collections.unmodifiableList(nodes)
: Collections.<Node>emptyList();
}
@Override
public int getItemViewType(int position) {
return getNodeViewType(nodes.get(position).getClass());
}
@Override
public long getItemId(int position) {
final Node node = nodes.get(position);
final int type = getNodeViewType(node.getClass());
final Entry<Holder, Node> entry = type == 0
? defaultEntry
: entries.get(type);
return entry.id(node);
}
public int getNodeViewType(@NonNull Class<? extends Node> node) {
// if has registered -> then return it, else 0
final int hash = node.hashCode();
if (entries.indexOfKey(hash) > -1) {
return hash;
}
return 0;
}
static class BuilderImpl implements Builder {
private final SparseArray<Entry<Holder, Node>> entries = new SparseArray<>(3);
private Entry<Holder, Node> defaultEntry;
private Reducer reducer;
@NonNull
@Override
public <N extends Node> Builder include(
@NonNull Class<N> node,
@NonNull Entry<? extends Holder, ? super N> entry) {
//noinspection unchecked
entries.append(node.hashCode(), (Entry<Holder, Node>) entry);
return this;
}
@NonNull
@Override
public Builder defaultEntry(@NonNull Entry<? extends Holder, ? extends Node> defaultEntry) {
//noinspection unchecked
this.defaultEntry = (Entry<Holder, Node>) defaultEntry;
return this;
}
@NonNull
@Override
public Builder defaultEntry(int layoutResId) {
//noinspection unchecked
this.defaultEntry = (Entry<Holder, Node>) (Entry) new SimpleNodeEntry(layoutResId);
return this;
}
@NonNull
@Override
public Builder reducer(@NonNull Reducer reducer) {
this.reducer = reducer;
return this;
}
@NonNull
@Override
public MarkwonAdapter build() {
if (defaultEntry == null) {
//noinspection unchecked
defaultEntry = (Entry<Holder, Node>) (Entry) new SimpleNodeEntry();
}
if (reducer == null) {
reducer = new ReducerImpl();
}
return new MarkwonAdapterImpl(entries, defaultEntry, reducer);
}
}
static class ReducerImpl implements Reducer {
@NonNull
@Override
public List<Node> reduce(@NonNull Node root) {
final List<Node> list = new ArrayList<>();
// // we will extract all blocks that are direct children of Document
Node node = root.getFirstChild();
Node temp;
while (node != null) {
list.add(node);
temp = node.getNext();
node.unlink();
node = temp;
}
Log.e("NODES", list.toString());
return list;
}
}
}

View File

@ -0,0 +1,4 @@
package ru.noties.markwon.recycler;
public class MarkwonRecycler {
}

View File

@ -0,0 +1,84 @@
package ru.noties.markwon.recycler;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.commonmark.node.Node;
import java.util.HashMap;
import java.util.Map;
import ru.noties.markwon.Markwon;
public class SimpleNodeEntry implements MarkwonAdapter.Entry<SimpleNodeEntry.Holder, Node> {
private static final NoCopySpannableFactory FACTORY = new NoCopySpannableFactory();
// small cache, maybe add pre-compute of text, also spannableFactory (so no copying of spans)
private final Map<Node, Spanned> cache = new HashMap<>();
private final int layoutResId;
public SimpleNodeEntry() {
this(R.layout.adapter_simple_entry);
}
public SimpleNodeEntry(@LayoutRes int layoutResId) {
this.layoutResId = layoutResId;
}
@NonNull
@Override
public Holder createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
return new Holder(inflater.inflate(layoutResId, parent, false));
}
@Override
public void bindHolder(@NonNull Markwon markwon, @NonNull Holder holder, @NonNull Node node) {
Spanned spanned = cache.get(node);
if (spanned == null) {
spanned = markwon.render(node);
cache.put(node, spanned);
}
markwon.setParsedMarkdown(holder.textView, spanned);
}
@Override
public long id(@NonNull Node node) {
return node.hashCode();
}
@Override
public void clear() {
cache.clear();
}
static class Holder extends MarkwonAdapter.Holder {
final TextView textView;
Holder(@NonNull View itemView) {
super(itemView);
this.textView = requireView(R.id.text);
this.textView.setSpannableFactory(FACTORY);
}
}
private static class NoCopySpannableFactory extends Spannable.Factory {
@Override
public Spannable newSpannable(CharSequence source) {
return source instanceof Spannable
? (Spannable) source
: new SpannableString(source);
}
}
}

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dip"
android:layout_marginRight="16dip"
android:lineSpacingExtra="2dip"
android:paddingTop="8dip"
android:paddingBottom="8dip"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="16sp"
tools:text="# Hello there!" />

View File

@ -2,6 +2,5 @@
<resources> <resources>
<item name="markwon_drawables_scheduler" type="id" /> <item name="markwon_drawables_scheduler" type="id" />
<item name="markwon_tables_scheduler" type="id" />
</resources> </resources>

View File

@ -19,4 +19,11 @@ android {
dependencies { dependencies {
implementation project(':markwon') implementation project(':markwon')
implementation project(':markwon-image-svg')
implementation project(':markwon-recycler')
implementation project(':markwon-ext-tables')
implementation project(':markwon-html')
implementation deps['debug']
} }

View File

@ -1,14 +1,16 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="ru.noties.markwon.sample.extension"> package="ru.noties.markwon.sample.extension">
<uses-permission android:name="android.permission.INTERNET" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme"
tools:ignore="AllowBackup,GoogleAppIndexingWarning,MissingApplicationIcon">
<activity android:name=".MainActivity"> <activity android:name=".MainActivity">
<intent-filter> <intent-filter>
@ -16,6 +18,11 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".recycler.MarkwonRecyclerActivity"
android:exported="true" />
</application> </application>
</manifest> </manifest>

View File

@ -0,0 +1,313 @@
![logo](./art/markwon_logo.png)
# Markwon
[![markwon](https://img.shields.io/maven-central/v/ru.noties/markwon.svg?label=markwon)](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%22markwon%22)
[![markwon-image-loader](https://img.shields.io/maven-central/v/ru.noties/markwon-image-loader.svg?label=markwon-image-loader)](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%22markwon-image-loader%22)
[![markwon-syntax-highlight](https://img.shields.io/maven-central/v/ru.noties/markwon-syntax-highlight.svg?label=markwon-syntax-highlight)](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%22markwon-syntax-highlight%22)
[![markwon-view](https://img.shields.io/maven-central/v/ru.noties/markwon-view.svg?label=markwon-view)](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%22markwon-view%22)
[![Build Status](https://travis-ci.org/noties/Markwon.svg?branch=master)](https://travis-ci.org/noties/Markwon)
**Markwon** is a markdown library for Android. It parses markdown
following [commonmark-spec] with the help of amazing [commonmark-java]
library and renders result as _Android-native_ Spannables. **No HTML**
is involved as an intermediate step. <u>**No WebView** is required</u>.
It's extremely fast, feature-rich and extensible.
It gives ability to display markdown in all TextView widgets
(**TextView**, **Button**, **Switch**, **CheckBox**, etc), **Toasts**
and all other places that accept **Spanned content**. Library provides
reasonable defaults to display style of a markdown content but also
gives all the means to tweak the appearance if desired. All markdown
features listed in [commonmark-spec] are supported
(including support for **inlined/block HTML code**, **markdown tables**,
**images** and **syntax highlight**).
[commonmark-spec]: https://spec.commonmark.org/0.28/
[commonmark-java]: https://github.com/atlassian/commonmark-java/blob/master/README.md
<sup>*</sup>*This file is displayed by default in the [sample-apk] (`markwon-sample-{latest-version}-debug.apk`) application. Which is a generic markdown viewer with support to display markdown via `http`, `https` & `file` schemes and 2 themes included: Light & Dark*
[sample-apk]: https://github.com/noties/Markwon/releases
## Installation
```groovy
implementation "ru.noties:markwon:${markwonVersion}"
implementation "ru.noties:markwon-image-loader:${markwonVersion}" // optional
implementation "ru.noties:markwon-syntax-highlight:${markwonVersion}" // optional
implementation "ru.noties:markwon-view:${markwonVersion}" // optional
```
Please visit [documentation] web-site for further reference
## Supported markdown features:
* Emphasis (`*`, `_`)
* Strong emphasis (`**`, `__`)
* Strike-through (`~~`)
* Headers (`#{1,6}`)
* Links (`[]()` && `[][]`)
* Images
* Thematic break (`---`, `***`, `___`)
* Quotes & nested quotes (`>{1,}`)
* Ordered & non-ordered lists & nested ones
* Inline code
* Code blocks
* Tables (*with limitations*)
* Syntax highlight
* HTML
* Emphasis (`<i>`, `<em>`, `<cite>`, `<dfn>`)
* Strong emphasis (`<b>`, `<strong>`)
* SuperScript (`<sup>`)
* SubScript (`<sub>`)
* Underline (`<u>`, `ins`)
* Strike-through (`<s>`, `<strike>`, `<del>`)
* Link (`a`)
* Lists (`ul`, `ol`)
* Images (`img` will require configured image loader)
* Blockquote (`blockquote`)
* Heading (`h1`, `h2`, `h3`, `h4`, `h5`, `h6`)
* there is support to render any HTML tag
* Task lists:
- [ ] Not _done_
- [X] **Done** with `X`
- [x] ~~and~~ **or** small `x`
---
## Screenshots
Taken with default configuration (except for image loading):
<a href="./art/mw_light_01.png"><img src="./art/mw_light_01.png" width="30%" /></a>
<a href="./art/mw_light_02.png"><img src="./art/mw_light_02.png" width="30%" /></a>
<a href="./art/mw_light_03.png"><img src="./art/mw_light_03.png" width="30%" /></a>
<a href="./art/mw_dark_01.png"><img src="./art/mw_dark_01.png" width="30%" /></a>
By default configuration uses TextView textColor for styling, so changing textColor changes style
---
## Documentation
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)
---
# Demo
Based on [this cheatsheet][cheatsheet]
---
## Headers
---
# Header 1
## Header 2
### Header 3
#### Header 4
##### Header 5
###### Header 6
---
## Emphasis
Emphasis, aka italics, with *asterisks* or _underscores_.
Strong emphasis, aka bold, with **asterisks** or __underscores__.
Combined emphasis with **asterisks and _underscores_**.
Strikethrough uses two tildes. ~~Scratch this.~~
---
## Lists
1. First ordered list item
2. Another item
* Unordered sub-list.
1. Actual numbers don't matter, just that it's a number
1. Ordered sub-list
4. And another item.
You can have properly indented paragraphs within list items. Notice the blank line above, and the leading spaces (at least one, but we'll use three here to also align the raw Markdown).
To have a line break without a paragraph, you will need to use two trailing spaces.
Note that this line is separate, but within the same paragraph.
(This is contrary to the typical GFM line break behaviour, where trailing spaces are not required.)
* Unordered list can use asterisks
- Or minuses
+ Or pluses
---
## Links
[I'm an inline-style link](https://www.google.com)
[I'm a reference-style link][Arbitrary case-insensitive reference text]
[I'm a relative reference to a repository file](../blob/master/LICENSE)
[You can use numbers for reference-style link definitions][1]
Or leave it empty and use the [link text itself].
---
## Code
Inline `code` has `back-ticks around` it.
```javascript
var s = "JavaScript syntax highlighting";
alert(s);
```
```python
s = "Python syntax highlighting"
print s
```
```java
/**
* Helper method to obtain a Parser with registered strike-through &amp; table extensions
* &amp; task lists (added in 1.0.1)
*
* @return a Parser instance that is supported by this library
* @since 1.0.0
*/
@NonNull
public static Parser createParser() {
return new Parser.Builder()
.extensions(Arrays.asList(
StrikethroughExtension.create(),
TablesExtension.create(),
TaskListExtension.create()
))
.build();
}
```
```xml
<ScrollView
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="?android:attr/actionBarSize">
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dip"
android:lineSpacingExtra="2dip"
android:textSize="16sp"
tools:context="ru.noties.markwon.MainActivity"
tools:text="yo\nman" />
</ScrollView>
```
```
No language indicated, so no syntax highlighting.
But let's throw in a <b>tag</b>.
```
---
## Tables
Colons can be used to align columns.
| Tables | Are | Cool |
| ------------- |:-------------:| -----:|
| col 3 is | right-aligned | $1600 |
| col 2 is | centered | $12 |
| zebra stripes | are neat | $1 |
There must be at least 3 dashes separating each header cell.
The outer pipes (|) are optional, and you don't need to make the
raw Markdown line up prettily. You can also use inline Markdown.
Markdown | Less | Pretty
--- | --- | ---
*Still* | `renders` | **nicely**
1 | 2 | 3
---
## Blockquotes
> Blockquotes are very handy in email to emulate reply text.
> This line is part of the same quote.
Quote break.
> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote.
Nested quotes
> Hello!
>> And to you!
---
## Inline HTML
```html
<u><i>H<sup>T<sub>M</sub></sup><b><s>L</s></b></i></u>
```
<u><i>H<sup>T<sub>M</sub></sup><b><s>L</s></b></i></u>
---
## Horizontal Rule
Three or more...
---
Hyphens (`-`)
***
Asterisks (`*`)
___
Underscores (`_`)
## License
```
Copyright 2017 Dimitry Ivanov (mail@dimitryivanov.ru)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```
[cheatsheet]: https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet
[arbitrary case-insensitive reference text]: https://www.mozilla.org
[1]: http://slashdot.org
[link text itself]: http://www.reddit.com

View File

@ -0,0 +1,143 @@
package ru.noties.markwon.sample.extension.recycler;
import android.app.Activity;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import org.commonmark.ext.gfm.tables.TableBlock;
import org.commonmark.node.FencedCodeBlock;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import ru.noties.debug.AndroidLogDebugOutput;
import ru.noties.debug.Debug;
import ru.noties.markwon.AbstractMarkwonPlugin;
import ru.noties.markwon.Markwon;
import ru.noties.markwon.MarkwonConfiguration;
import ru.noties.markwon.core.CorePlugin;
import ru.noties.markwon.ext.tables.TablePlugin;
import ru.noties.markwon.html.HtmlPlugin;
import ru.noties.markwon.image.ImagesPlugin;
import ru.noties.markwon.image.svg.SvgPlugin;
import ru.noties.markwon.recycler.MarkwonAdapter;
import ru.noties.markwon.recycler.SimpleNodeEntry;
import ru.noties.markwon.sample.extension.R;
import ru.noties.markwon.urlprocessor.UrlProcessor;
import ru.noties.markwon.urlprocessor.UrlProcessorRelativeToAbsolute;
// we will create a standalone `sample` module with all these samples, right now this
// module has unrelated things
public class MarkwonRecyclerActivity extends Activity {
static {
Debug.init(new AndroidLogDebugOutput(true));
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recycler);
final MarkwonAdapter adapter = MarkwonAdapter.builder()
.include(FencedCodeBlock.class, new SimpleNodeEntry(R.layout.adapter_fenced_code_block))
.include(TableBlock.class, new TableNodeEntry())
.build();
final RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setHasFixedSize(true);
recyclerView.setAdapter(adapter);
final Markwon markwon = markwon(this);
adapter.setMarkdown(markwon, loadReadMe(this));
adapter.notifyDataSetChanged();
}
@NonNull
private static Markwon markwon(@NonNull Context context) {
return Markwon.builder(context)
.usePlugin(CorePlugin.create())
.usePlugin(ImagesPlugin.createWithAssets(context))
.usePlugin(SvgPlugin.create(context.getResources()))
.usePlugin(TablePlugin.create(context))
.usePlugin(HtmlPlugin.create())
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
builder.urlProcessor(new UrlProcessorInitialReadme());
}
})
.build();
}
private static String loadReadMe(@NonNull Context context) {
InputStream stream = null;
try {
stream = context.getAssets().open("README.md");
} catch (IOException e) {
e.printStackTrace();
}
return readStream(stream);
}
private static String readStream(@Nullable InputStream inputStream) {
String out = null;
if (inputStream != null) {
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(inputStream));
final StringBuilder builder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
builder.append(line)
.append('\n');
}
out = builder.toString();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
// no op
}
}
}
}
return out;
}
private static class UrlProcessorInitialReadme implements UrlProcessor {
private static final String GITHUB_BASE = "https://github.com/noties/Markwon/raw/master/";
private final UrlProcessorRelativeToAbsolute processor
= new UrlProcessorRelativeToAbsolute(GITHUB_BASE);
@NonNull
@Override
public String process(@NonNull String destination) {
String out;
final Uri uri = Uri.parse(destination);
if (TextUtils.isEmpty(uri.getScheme())) {
out = processor.process(destination);
} else {
out = destination;
}
return out;
}
}
}

View File

@ -0,0 +1,289 @@
package ru.noties.markwon.sample.extension.recycler;
import android.support.annotation.NonNull;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
import org.commonmark.ext.gfm.tables.TableBlock;
import org.commonmark.node.AbstractVisitor;
import org.commonmark.node.BlockQuote;
import org.commonmark.node.BulletList;
import org.commonmark.node.Code;
import org.commonmark.node.CustomBlock;
import org.commonmark.node.CustomNode;
import org.commonmark.node.Document;
import org.commonmark.node.Emphasis;
import org.commonmark.node.FencedCodeBlock;
import org.commonmark.node.HardLineBreak;
import org.commonmark.node.Heading;
import org.commonmark.node.HtmlBlock;
import org.commonmark.node.HtmlInline;
import org.commonmark.node.Image;
import org.commonmark.node.IndentedCodeBlock;
import org.commonmark.node.Link;
import org.commonmark.node.ListItem;
import org.commonmark.node.Node;
import org.commonmark.node.OrderedList;
import org.commonmark.node.Paragraph;
import org.commonmark.node.SoftLineBreak;
import org.commonmark.node.StrongEmphasis;
import org.commonmark.node.Text;
import org.commonmark.node.ThematicBreak;
import ru.noties.debug.Debug;
import ru.noties.markwon.Markwon;
import ru.noties.markwon.recycler.MarkwonAdapter;
import ru.noties.markwon.sample.extension.R;
// do not use in real applications, this is just a showcase
public class TableNodeEntry implements MarkwonAdapter.Entry<TableNodeEntry.TableNodeHolder, TableBlock> {
@NonNull
@Override
public TableNodeHolder createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
return new TableNodeHolder(inflater.inflate(R.layout.adapter_table_block, parent, false));
}
@Override
public void bindHolder(@NonNull Markwon markwon, @NonNull TableNodeHolder holder, @NonNull TableBlock node) {
if (true) {
Debug.e("###############");
Debug.e("NODE: %s", node);
node.accept(new PrintVisitor());
Debug.e("NODE: %s", node);
Debug.e("###############");
}
final TableLayout layout = holder.layout;
layout.removeAllViews();
// each child represents a row (head or regular)
// first direct child is TableHead or TableBody
Node child = node.getFirstChild().getFirstChild();
Node temp;
while (child != null) {
Log.e("BIND-ROWS", String.valueOf(child));
temp = child.getNext();
addRow(markwon, layout, child);
child = temp;
}
Log.e("BIND", String.valueOf(layout.getChildCount()));
if (true) {
final ViewGroup group = (ViewGroup) layout.getChildAt(0);
Log.e("BIND-GROUP", String.valueOf(group.getChildCount()));
for (int i = 0; i < group.getChildCount(); i++) {
Log.e("BIND-CHILD-" + i, String.valueOf(group.getChildAt(i)) + ", " + ((TextView) group.getChildAt(i)).getText());
}
}
layout.requestLayout();
}
private void addRow(@NonNull Markwon markwon, @NonNull TableLayout layout, @NonNull Node node) {
final TableRow tableRow = new TableRow(layout.getContext());
// final TableRow.LayoutParams params = new TableRow.LayoutParams(100, 100);
tableRow.setBackgroundColor(0x80ff0000);
TextView textView;
RenderNode renderNode;
Node temp;
// each child in a row represents a cell
Node child = node.getFirstChild();
while (child != null) {
Log.e("BIND-CELL", String.valueOf(child));
textView = new TextView(layout.getContext());
textView.setLayoutParams(new TableRow.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
renderNode = new RenderNode();
temp = child.getNext();
copy(child, renderNode);
tableRow.addView(textView);
markwon.setParsedMarkdown(textView, markwon.render(renderNode));
child = temp;
}
layout.addView(tableRow);
}
private static void copy(@NonNull Node from, @NonNull Node to) {
Node child = from.getFirstChild();
Node temp;
while (child != null) {
Log.e("BIND-COPY", String.valueOf(child));
temp = child.getNext();
to.appendChild(child);
child = temp;
}
}
@Override
public long id(@NonNull TableBlock node) {
return node.hashCode();
}
@Override
public void clear() {
}
static class TableNodeHolder extends MarkwonAdapter.Holder {
final TableLayout layout;
TableNodeHolder(@NonNull View itemView) {
super(itemView);
this.layout = requireView(R.id.table_layout);
}
}
private static class RenderNode extends CustomBlock {
}
private static class PrintVisitor extends AbstractVisitor {
private final RenderNode renderNode = new RenderNode();
@Override
public void visit(BlockQuote blockQuote) {
Debug.i("blockQuote: %s", blockQuote);
super.visit(blockQuote);
}
@Override
public void visit(BulletList bulletList) {
Debug.i("bulletList: %s", bulletList);
super.visit(bulletList);
}
@Override
public void visit(Code code) {
Debug.i("code: %s", code);
super.visit(code);
}
@Override
public void visit(Document document) {
Debug.i("document: %s", document);
super.visit(document);
}
@Override
public void visit(Emphasis emphasis) {
Debug.i("emphasis: %s", emphasis);
super.visit(emphasis);
}
@Override
public void visit(FencedCodeBlock fencedCodeBlock) {
Debug.i("fencedCodeBlock: %s", fencedCodeBlock);
super.visit(fencedCodeBlock);
}
@Override
public void visit(HardLineBreak hardLineBreak) {
Debug.i("hardLineBreak: %s", hardLineBreak);
super.visit(hardLineBreak);
}
@Override
public void visit(Heading heading) {
Debug.i("heading: %s", heading);
super.visit(heading);
}
@Override
public void visit(ThematicBreak thematicBreak) {
Debug.i("thematicBreak: %s", thematicBreak);
super.visit(thematicBreak);
}
@Override
public void visit(HtmlInline htmlInline) {
Debug.i("htmlInline: %s", htmlInline);
super.visit(htmlInline);
}
@Override
public void visit(HtmlBlock htmlBlock) {
Debug.i("htmlBlock: %s", htmlBlock);
super.visit(htmlBlock);
}
@Override
public void visit(Image image) {
Debug.i("image: %s", image);
super.visit(image);
}
@Override
public void visit(IndentedCodeBlock indentedCodeBlock) {
Debug.i("indentedCodeBlock: %s", indentedCodeBlock);
super.visit(indentedCodeBlock);
}
@Override
public void visit(Link link) {
Debug.i("link: %s", link);
super.visit(link);
}
@Override
public void visit(ListItem listItem) {
Debug.i("listItem: %s", listItem);
super.visit(listItem);
}
@Override
public void visit(OrderedList orderedList) {
Debug.i("orderedList: %s", orderedList);
super.visit(orderedList);
}
@Override
public void visit(Paragraph paragraph) {
Debug.i("paragraph: %s", paragraph);
super.visit(paragraph);
}
@Override
public void visit(SoftLineBreak softLineBreak) {
Debug.i("softLineBreak: %s", softLineBreak);
super.visit(softLineBreak);
}
@Override
public void visit(StrongEmphasis strongEmphasis) {
Debug.i("strongEmphasis: %s", strongEmphasis);
super.visit(strongEmphasis);
}
@Override
public void visit(Text text) {
Debug.i("text: %s", text);
super.visit(text);
}
@Override
public void visit(CustomBlock customBlock) {
Debug.i("customBlock: %s", customBlock);
super.visit(customBlock);
}
@Override
public void visit(CustomNode customNode) {
Debug.i("customNode: %s", customNode);
super.visit(customNode);
}
}
}

View File

@ -1,34 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</vector>

View File

@ -1,170 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillColor="#26A69A"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
</vector>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:fillViewport="true"
android:paddingLeft="16dip"
android:paddingRight="16dip"
android:scrollbarStyle="outsideInset">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:lineSpacingExtra="2dip"
android:paddingTop="8dip"
android:paddingBottom="8dip"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="16sp"
tools:text="# Hello there! and taskjs" />
</HorizontalScrollView>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:fillViewport="true"
android:paddingLeft="16dip"
android:paddingRight="16dip">
<TableLayout
android:id="@+id/table_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</HorizontalScrollView>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@ -5,11 +5,12 @@ include ':app',
':markwon-ext-strikethrough', ':markwon-ext-strikethrough',
':markwon-ext-tables', ':markwon-ext-tables',
':markwon-ext-tasklist', ':markwon-ext-tasklist',
':markwon-html',
':markwon-image-gif',
':markwon-image-okhttp', ':markwon-image-okhttp',
':markwon-image-svg', ':markwon-image-svg',
':markwon-image-gif', ':markwon-recycler',
':markwon-syntax-highlight', ':markwon-syntax-highlight',
':markwon-html',
':markwon-test-span', ':markwon-test-span',
':sample-custom-extension', ':sample-custom-extension',
':sample-latex-math' ':sample-latex-math'