text-added-listener for core-plugin and linkify module

This commit is contained in:
Dimitry Ivanov 2019-06-02 22:12:40 +03:00
parent 2e35ef53bb
commit cedb3971a0
10 changed files with 185 additions and 7 deletions

View File

@ -4,3 +4,6 @@
also accepts a ExecutorService (optional, by default cachedThreadPool is used)
* AsyncDrawableScheduler now can be called by multiple plugins without penalty
internally caches latest state and skips scheduling if drawables are already processed
* configure with registry
* removed priority
* images-plugin moved to standalone again

View File

@ -34,6 +34,7 @@ dependencies {
implementation project(':markwon-ext-tasklist')
implementation project(':markwon-html')
implementation project(':markwon-image')
implementation project(':markwon-linkify')
implementation project(':markwon-syntax-highlight')
deps.with {

View File

@ -25,6 +25,7 @@ import ru.noties.markwon.image.file.FileSchemeHandler;
import ru.noties.markwon.image.gif.GifMediaDecoder;
import ru.noties.markwon.image.network.OkHttpNetworkSchemeHandler;
import ru.noties.markwon.image.svg.SvgMediaDecoder;
import ru.noties.markwon.linkify.LinkifyPlugin;
import ru.noties.markwon.syntax.Prism4jTheme;
import ru.noties.markwon.syntax.Prism4jThemeDarkula;
import ru.noties.markwon.syntax.Prism4jThemeDefault;
@ -107,6 +108,7 @@ public class MarkdownRenderer {
.addMediaDecoder(SvgMediaDecoder.create());
}
}))
.usePlugin(LinkifyPlugin.create())
.usePlugin(SyntaxHighlightPlugin.create(prism4j, prism4jTheme))
.usePlugin(GifAwarePlugin.create(context))
.usePlugin(TablePlugin.create(context))

View File

@ -8,11 +8,11 @@
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dip"
android:clipToPadding="false"
android:layout_marginTop="?android:attr/actionBarSize"
android:clipChildren="false"
android:scrollbarStyle="outsideOverlay"
android:layout_marginTop="?android:attr/actionBarSize">
android:clipToPadding="false"
android:padding="16dip"
android:scrollbarStyle="outsideOverlay">
<TextView
android:id="@+id/text"

View File

@ -27,6 +27,9 @@ import org.commonmark.node.StrongEmphasis;
import org.commonmark.node.Text;
import org.commonmark.node.ThematicBreak;
import java.util.ArrayList;
import java.util.List;
import ru.noties.markwon.AbstractMarkwonPlugin;
import ru.noties.markwon.MarkwonConfiguration;
import ru.noties.markwon.MarkwonSpansFactory;
@ -51,14 +54,57 @@ import ru.noties.markwon.image.ImageProps;
*/
public class CorePlugin extends AbstractMarkwonPlugin {
/**
* @see #addOnTextAddedListener(OnTextAddedListener)
* @since 4.0.0-SNAPSHOT
*/
public interface OnTextAddedListener {
/**
* Will be called when new text is added to resulting {@link ru.noties.markwon.SpannableBuilder}.
* Please note that only text represented by {@link Text} node will trigger this callback
* (text inside code and code-blocks won\'t trigger it).
* <p>
* Please note that if you wish to add spans you must use {@code start} parameter
* in order to place spans correctly ({@code start} represents the index at which {@code text}
* was added). So, to set a span for the whole length of the text added one should use:
* <p>
* {@code
* visitor.builder().setSpan(new MySpan(), start, start + text.length(), 0);
* }
*
* @param visitor {@link MarkwonVisitor}
* @param text literal that had been added
* @param start index in {@code visitor} as which text had been added
* @see #addOnTextAddedListener(OnTextAddedListener)
*/
void onTextAdded(@NonNull MarkwonVisitor visitor, @NonNull String text, int start);
}
@NonNull
public static CorePlugin create() {
return new CorePlugin();
}
// @since 4.0.0-SNAPSHOT
private final List<OnTextAddedListener> onTextAddedListeners = new ArrayList<>(0);
protected CorePlugin() {
}
/**
* Can be useful to post-process text added. For example for auto-linking capabilities.
*
* @see OnTextAddedListener
* @since 4.0.0-SNAPSHOT
*/
@SuppressWarnings("UnusedReturnValue")
@NonNull
public CorePlugin addOnTextAddedListener(@NonNull OnTextAddedListener onTextAddedListener) {
onTextAddedListeners.add(onTextAddedListener);
return this;
}
@Override
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
text(builder);
@ -114,11 +160,20 @@ public class CorePlugin extends AbstractMarkwonPlugin {
}
}
private static void text(@NonNull MarkwonVisitor.Builder builder) {
private void text(@NonNull MarkwonVisitor.Builder builder) {
builder.on(Text.class, new MarkwonVisitor.NodeVisitor<Text>() {
@Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull Text text) {
visitor.builder().append(text.getLiteral());
final int length = visitor.length();
final String literal = text.getLiteral();
visitor.builder().append(literal);
// @since 4.0.0-SNAPSHOT
for (OnTextAddedListener onTextAddedListener : onTextAddedListeners) {
onTextAddedListener.onTextAdded(visitor, literal, length);
}
}
});
}

View File

@ -0,0 +1,20 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion config['compile-sdk']
buildToolsVersion config['build-tools']
defaultConfig {
minSdkVersion config['min-sdk']
targetSdkVersion config['target-sdk']
versionCode 1
versionName version
}
}
dependencies {
api project(':markwon-core')
}
registerArtifact(this)

View File

@ -0,0 +1,4 @@
POM_NAME=Linkify
POM_ARTIFACT_ID=linkify
POM_DESCRIPTION=Markwon plugin to linkify text (based on Android Linkify)
POM_PACKAGING=aar

View File

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

View File

@ -0,0 +1,91 @@
package ru.noties.markwon.linkify;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.text.SpannableStringBuilder;
import android.text.util.Linkify;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import ru.noties.markwon.AbstractMarkwonPlugin;
import ru.noties.markwon.MarkwonVisitor;
import ru.noties.markwon.SpannableBuilder;
import ru.noties.markwon.core.CorePlugin;
public class LinkifyPlugin extends AbstractMarkwonPlugin {
@IntDef(flag = true, value = {
Linkify.EMAIL_ADDRESSES,
Linkify.PHONE_NUMBERS,
Linkify.WEB_URLS,
Linkify.ALL
})
@Retention(RetentionPolicy.SOURCE)
@interface LinkifyMask {
}
@NonNull
public static LinkifyPlugin create() {
return create(Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS | Linkify.WEB_URLS);
}
@NonNull
public static LinkifyPlugin create(@LinkifyMask int mask) {
return new LinkifyPlugin(mask);
}
private final int mask;
@SuppressWarnings("WeakerAccess")
LinkifyPlugin(@LinkifyMask int mask) {
this.mask = mask;
}
@Override
public void configure(@NonNull Registry registry) {
registry.require(CorePlugin.class, new Action<CorePlugin>() {
@Override
public void apply(@NonNull CorePlugin corePlugin) {
corePlugin.addOnTextAddedListener(new LinkifyTextAddedListener(mask));
}
});
}
private static class LinkifyTextAddedListener implements CorePlugin.OnTextAddedListener {
private final int mask;
private final SpannableStringBuilder builder;
LinkifyTextAddedListener(int mask) {
this.mask = mask;
this.builder = new SpannableStringBuilder();
}
@Override
public void onTextAdded(@NonNull MarkwonVisitor visitor, @NonNull String text, int start) {
// clear previous state
builder.clear();
builder.clearSpans();
// append text to process
builder.append(text);
if (Linkify.addLinks(builder, mask)) {
final Object[] spans = builder.getSpans(0, builder.length(), Object.class);
if (spans != null
&& spans.length > 0) {
final SpannableBuilder spannableBuilder = visitor.builder();
for (Object span : spans) {
spannableBuilder.setSpan(
span,
start + builder.getSpanStart(span),
start + builder.getSpanEnd(span),
builder.getSpanFlags(span));
}
}
}
}
}
}

View File

@ -9,6 +9,7 @@ include ':app', ':sample',
':markwon-image',
':markwon-image-glide',
':markwon-image-picasso',
':markwon-linkify',
':markwon-recycler',
':markwon-recycler-table',
':markwon-syntax-highlight',