diff --git a/_CHANGES.md b/_CHANGES.md index 3a63fbbe..4695759f 100644 --- a/_CHANGES.md +++ b/_CHANGES.md @@ -3,4 +3,7 @@ * `JLatex` plugin now is not dependent on ImagesPlugin 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 \ No newline at end of file + internally caches latest state and skips scheduling if drawables are already processed +* configure with registry +* removed priority +* images-plugin moved to standalone again \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 9d95baaf..ffa43c25 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 { diff --git a/app/src/main/java/ru/noties/markwon/MarkdownRenderer.java b/app/src/main/java/ru/noties/markwon/MarkdownRenderer.java index a55d82dc..73adc541 100644 --- a/app/src/main/java/ru/noties/markwon/MarkdownRenderer.java +++ b/app/src/main/java/ru/noties/markwon/MarkdownRenderer.java @@ -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)) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 97c5fb1b..0bd1c6ac 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -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"> + * 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: + *

+ * {@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 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() { @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); + } } }); } diff --git a/markwon-linkify/build.gradle b/markwon-linkify/build.gradle new file mode 100644 index 00000000..764113ab --- /dev/null +++ b/markwon-linkify/build.gradle @@ -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) \ No newline at end of file diff --git a/markwon-linkify/gradle.properties b/markwon-linkify/gradle.properties new file mode 100644 index 00000000..405e6449 --- /dev/null +++ b/markwon-linkify/gradle.properties @@ -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 \ No newline at end of file diff --git a/markwon-linkify/src/main/AndroidManifest.xml b/markwon-linkify/src/main/AndroidManifest.xml new file mode 100644 index 00000000..3a9ba865 --- /dev/null +++ b/markwon-linkify/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/markwon-linkify/src/main/java/ru/noties/markwon/linkify/LinkifyPlugin.java b/markwon-linkify/src/main/java/ru/noties/markwon/linkify/LinkifyPlugin.java new file mode 100644 index 00000000..ff29c1a1 --- /dev/null +++ b/markwon-linkify/src/main/java/ru/noties/markwon/linkify/LinkifyPlugin.java @@ -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() { + @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)); + } + } + } + } + } +} diff --git a/settings.gradle b/settings.gradle index da0bcd9e..38372eba 100644 --- a/settings.gradle +++ b/settings.gradle @@ -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',