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) also accepts a ExecutorService (optional, by default cachedThreadPool is used)
* AsyncDrawableScheduler now can be called by multiple plugins without penalty * AsyncDrawableScheduler now can be called by multiple plugins without penalty
internally caches latest state and skips scheduling if drawables are already processed 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-ext-tasklist')
implementation project(':markwon-html') implementation project(':markwon-html')
implementation project(':markwon-image') implementation project(':markwon-image')
implementation project(':markwon-linkify')
implementation project(':markwon-syntax-highlight') implementation project(':markwon-syntax-highlight')
deps.with { 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.gif.GifMediaDecoder;
import ru.noties.markwon.image.network.OkHttpNetworkSchemeHandler; import ru.noties.markwon.image.network.OkHttpNetworkSchemeHandler;
import ru.noties.markwon.image.svg.SvgMediaDecoder; import ru.noties.markwon.image.svg.SvgMediaDecoder;
import ru.noties.markwon.linkify.LinkifyPlugin;
import ru.noties.markwon.syntax.Prism4jTheme; import ru.noties.markwon.syntax.Prism4jTheme;
import ru.noties.markwon.syntax.Prism4jThemeDarkula; import ru.noties.markwon.syntax.Prism4jThemeDarkula;
import ru.noties.markwon.syntax.Prism4jThemeDefault; import ru.noties.markwon.syntax.Prism4jThemeDefault;
@ -107,6 +108,7 @@ public class MarkdownRenderer {
.addMediaDecoder(SvgMediaDecoder.create()); .addMediaDecoder(SvgMediaDecoder.create());
} }
})) }))
.usePlugin(LinkifyPlugin.create())
.usePlugin(SyntaxHighlightPlugin.create(prism4j, prism4jTheme)) .usePlugin(SyntaxHighlightPlugin.create(prism4j, prism4jTheme))
.usePlugin(GifAwarePlugin.create(context)) .usePlugin(GifAwarePlugin.create(context))
.usePlugin(TablePlugin.create(context)) .usePlugin(TablePlugin.create(context))

View File

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

View File

@ -27,6 +27,9 @@ import org.commonmark.node.StrongEmphasis;
import org.commonmark.node.Text; import org.commonmark.node.Text;
import org.commonmark.node.ThematicBreak; import org.commonmark.node.ThematicBreak;
import java.util.ArrayList;
import java.util.List;
import ru.noties.markwon.AbstractMarkwonPlugin; import ru.noties.markwon.AbstractMarkwonPlugin;
import ru.noties.markwon.MarkwonConfiguration; import ru.noties.markwon.MarkwonConfiguration;
import ru.noties.markwon.MarkwonSpansFactory; import ru.noties.markwon.MarkwonSpansFactory;
@ -51,14 +54,57 @@ import ru.noties.markwon.image.ImageProps;
*/ */
public class CorePlugin extends AbstractMarkwonPlugin { 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 @NonNull
public static CorePlugin create() { public static CorePlugin create() {
return new CorePlugin(); return new CorePlugin();
} }
// @since 4.0.0-SNAPSHOT
private final List<OnTextAddedListener> onTextAddedListeners = new ArrayList<>(0);
protected CorePlugin() { 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 @Override
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
text(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>() { builder.on(Text.class, new MarkwonVisitor.NodeVisitor<Text>() {
@Override @Override
public void visit(@NonNull MarkwonVisitor visitor, @NonNull Text text) { 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',
':markwon-image-glide', ':markwon-image-glide',
':markwon-image-picasso', ':markwon-image-picasso',
':markwon-linkify',
':markwon-recycler', ':markwon-recycler',
':markwon-recycler-table', ':markwon-recycler-table',
':markwon-syntax-highlight', ':markwon-syntax-highlight',