diff --git a/CHANGELOG.md b/CHANGELOG.md index b9a246ba..845d8b01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ #### Added * `core` - `MovementMethodPlugin.none()`, `MovementMethodPlugin.link()` factory methods -* `core` - `CorePlugin` `hasExplicitMovementMethodPlugin` configuration method to **not** add implicit `LinkMovementMethod` in `afterSetText` +* `core` - `CorePlugin` `hasExplicitMovementMethod` configuration method to **not** add implicit `LinkMovementMethod` in `afterSetText` #### Changed * `html` - `SimpleTagHandler` visits children tags if supplied tag is block one ([#235]) diff --git a/app-sample/samples.json b/app-sample/samples.json index aa01906b..4b274493 100644 --- a/app-sample/samples.json +++ b/app-sample/samples.json @@ -1,4 +1,99 @@ [ + { + "javaClassName": "io.noties.markwon.app.samples.movementmethod.MovementMethodPluginSample", + "id": "202006179081631", + "title": "MovementMethodPlugin", + "description": "Plugin to control movement method", + "artifacts": [ + "CORE" + ], + "tags": [ + "links", + "movement-method", + "plugin" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.movementmethod.DisableImplicitMovementMethodSample", + "id": "202006179081256", + "title": "Disable implicit movement method", + "description": "Configure `Markwon` to **not** apply implicit movement method", + "artifacts": [ + "CORE" + ], + "tags": [ + "links", + "movement-method", + "plugin", + "recycler-view" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.movementmethod.ExplicitMovementMethodSample", + "id": "202006179080007", + "title": "Explicit movement method", + "description": "When a movement method already applied to a `TextView``Markwon` won\u0027t try to apply own (implicit) one", + "artifacts": [ + "CORE" + ], + "tags": [ + "links", + "movement-method" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.movementmethod.ImplicitMovementMethodSample", + "id": "202006179075524", + "title": "Implicit movement method", + "description": "By default movement method is applied for links to be clickable", + "artifacts": [ + "CORE" + ], + "tags": [ + "links", + "movement-method" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.EnabledBlockTypesSample", + "id": "202006179075012", + "title": "Enabled markdown blocks", + "description": "Modify/inspect enabled by `CorePlugin` block types. Disable quotes or other blocks from being parsed", + "artifacts": [ + "CORE" + ], + "tags": [ + "block", + "parsing", + "plugin" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.ToastDynamicContentSample", + "id": "202006179074017", + "title": "Markdown in Toast (with dynamic content)", + "description": "Display markdown in a `android.widget.Toast` with dynamic content (image)", + "artifacts": [ + "CORE", + "IMAGE" + ], + "tags": [ + "hack", + "toast" + ] + }, + { + "javaClassName": "io.noties.markwon.app.samples.ToastSample", + "id": "202006179072642", + "title": "Markdown in Toast", + "description": "Display _static_ markdown content in a `android.widget.Toast`", + "artifacts": [ + "CORE" + ], + "tags": [ + "toast" + ] + }, { "javaClassName": "io.noties.markwon.app.samples.basics.SimpleWalkthrough", "id": "202006178153426", diff --git a/app-sample/src/main/java/io/noties/markwon/app/sample/Tags.kt b/app-sample/src/main/java/io/noties/markwon/app/sample/Tags.kt index cfa5b91b..748fe16a 100644 --- a/app-sample/src/main/java/io/noties/markwon/app/sample/Tags.kt +++ b/app-sample/src/main/java/io/noties/markwon/app/sample/Tags.kt @@ -2,4 +2,12 @@ package io.noties.markwon.app.sample object Tags { const val basics = "basics" + const val toast = "toast" + const val hack = "hack" + const val parsing = "parsing" + const val block = "block" + const val movementMethod = "movement-method" + const val links = "links" + const val plugin = "plugin" + const val recyclerView = "recycler-view" } \ No newline at end of file diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/EnabledBlockTypesSample.kt b/app-sample/src/main/java/io/noties/markwon/app/samples/EnabledBlockTypesSample.kt new file mode 100644 index 00000000..e68eb486 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/EnabledBlockTypesSample.kt @@ -0,0 +1,45 @@ +package io.noties.markwon.app.samples + +import io.noties.markwon.AbstractMarkwonPlugin +import io.noties.markwon.Markwon +import io.noties.markwon.app.sample.Tags +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample +import io.noties.markwon.core.CorePlugin +import io.noties.markwon.sample.annotations.MarkwonArtifact +import io.noties.markwon.sample.annotations.MarkwonSampleInfo +import org.commonmark.node.BlockQuote +import org.commonmark.parser.Parser + +@MarkwonSampleInfo( + id = "202006179075012", + title = "Enabled markdown blocks", + description = "Modify/inspect enabled by `CorePlugin` block types. " + + "Disable quotes or other blocks from being parsed", + artifacts = [MarkwonArtifact.CORE], + tags = [Tags.parsing, Tags.block, Tags.plugin] +) +class EnabledBlockTypesSample : MarkwonTextViewSample() { + override fun render() { + val md = """ + # Heading + ## Second level + > Quote is not handled + """.trimIndent() + + val markwon = Markwon.builder(context) + .usePlugin(object : AbstractMarkwonPlugin() { + override fun configureParser(builder: Parser.Builder) { + // obtain all enabled block types + val enabledBlockTypes = CorePlugin.enabledBlockTypes() + // it is safe to modify returned collection + // remove quotes + enabledBlockTypes.remove(BlockQuote::class.java) + + builder.enabledBlockTypes(enabledBlockTypes) + } + }) + .build() + + markwon.setMarkdown(textView, md) + } +} \ No newline at end of file diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/ToastDynamicContentSample.kt b/app-sample/src/main/java/io/noties/markwon/app/samples/ToastDynamicContentSample.kt new file mode 100644 index 00000000..41212db2 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/ToastDynamicContentSample.kt @@ -0,0 +1,73 @@ +package io.noties.markwon.app.samples + +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import android.widget.Toast +import io.noties.markwon.Markwon +import io.noties.markwon.app.sample.Tags +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample +import io.noties.markwon.image.ImagesPlugin +import io.noties.markwon.sample.annotations.MarkwonArtifact +import io.noties.markwon.sample.annotations.MarkwonSampleInfo + +@MarkwonSampleInfo( + id = "202006179074017", + title = "Markdown in Toast (with dynamic content)", + description = "Display markdown in a `android.widget.Toast` with dynamic content (image)", + artifacts = [MarkwonArtifact.CORE, MarkwonArtifact.IMAGE], + tags = [Tags.toast, Tags.hack] +) +class ToastDynamicContentSample : MarkwonTextViewSample() { + override fun render() { + val md = """ + # Head! + + ![alt](https://github.com/noties/Markwon/raw/master/art/markwon_logo.png) + + Do you see an image? ☝️ + """.trimIndent() + + val markwon = Markwon.builder(context) + .usePlugin(ImagesPlugin.create()) + .build() + + val markdown = markwon.toMarkdown(md) + + val toast = Toast.makeText(context, markdown, Toast.LENGTH_LONG) + + // try to obtain textView + val textView = toast.textView + if (textView != null) { + markwon.setParsedMarkdown(textView, markdown) + } + + // finally show toast (at this point, if we didn't find TextView it will still + // present markdown, just without dynamic content (image)) + toast.show() + } +} + +private val Toast.textView: TextView? + get() { + + fun find(view: View?): TextView? { + + if (view is TextView) { + return view + } + + if (view is ViewGroup) { + for (i in 0 until view.childCount) { + val textView = find(view.getChildAt(i)) + if (textView != null) { + return textView + } + } + } + + return null + } + + return find(view) + } \ No newline at end of file diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/ToastSample.kt b/app-sample/src/main/java/io/noties/markwon/app/samples/ToastSample.kt new file mode 100644 index 00000000..89f6e978 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/ToastSample.kt @@ -0,0 +1,38 @@ +package io.noties.markwon.app.samples + +import android.widget.Toast +import io.noties.markwon.Markwon +import io.noties.markwon.app.sample.Tags +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample +import io.noties.markwon.sample.annotations.MarkwonArtifact +import io.noties.markwon.sample.annotations.MarkwonSampleInfo + +@MarkwonSampleInfo( + id = "202006179072642", + title = "Markdown in Toast", + description = "Display _static_ markdown content in a `android.widget.Toast`", + artifacts = [MarkwonArtifact.CORE], + tags = [Tags.toast] +) +class ToastSample : MarkwonTextViewSample() { + override fun render() { + // NB! only _static_ content is going to be displayed, + // so, no images, tables or latex in a Toast + val md = """ + # Heading is fine + > Even quote if **fine** + ``` + finally code works; + ``` + _italic_ to put an end to it + """.trimIndent() + + val markwon = Markwon.create(context) + + // render raw input to styled markdown + val markdown = markwon.toMarkdown(md) + + // Toast accepts CharSequence and allows styling via spans + Toast.makeText(context, markdown, Toast.LENGTH_LONG).show() + } +} \ No newline at end of file diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/movementmethod/DisableImplicitMovementMethodSample.kt b/app-sample/src/main/java/io/noties/markwon/app/samples/movementmethod/DisableImplicitMovementMethodSample.kt new file mode 100644 index 00000000..82116b06 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/movementmethod/DisableImplicitMovementMethodSample.kt @@ -0,0 +1,43 @@ +package io.noties.markwon.app.samples.movementmethod + +import io.noties.markwon.AbstractMarkwonPlugin +import io.noties.markwon.Markwon +import io.noties.markwon.MarkwonPlugin +import io.noties.markwon.app.sample.Tags +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample +import io.noties.markwon.core.CorePlugin +import io.noties.markwon.sample.annotations.MarkwonArtifact +import io.noties.markwon.sample.annotations.MarkwonSampleInfo + +@MarkwonSampleInfo( + id = "202006179081256", + title = "Disable implicit movement method", + description = "Configure `Markwon` to **not** apply implicit movement method", + artifacts = [MarkwonArtifact.CORE], + tags = [Tags.plugin, Tags.movementMethod, Tags.links, Tags.recyclerView] +) +class DisableImplicitMovementMethodSample : MarkwonTextViewSample() { + override fun render() { + val md = """ + # Disable implicit movement method + Sometimes it is required to stop `Markwon` from applying _implicit_ + movement method (for example when used inside in a `RecyclerView` + in order to make the whole itemView clickable). `Markwon` inspects + `TextView` and applies implicit movement method if `getMovementMethod()` + returns `null`. No [links](https://github.com) will be clickable in this + markdown + """.trimIndent() + + val markwon = Markwon.builder(context) + .usePlugin(object : AbstractMarkwonPlugin() { + override fun configure(registry: MarkwonPlugin.Registry) { + registry.require(CorePlugin::class.java) + // this flag will make sure that CorePlugin won't apply any movement method + .hasExplicitMovementMethod(true) + } + }) + .build() + + markwon.setMarkdown(textView, md) + } +} \ No newline at end of file diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/movementmethod/ExplicitMovementMethodSample.kt b/app-sample/src/main/java/io/noties/markwon/app/samples/movementmethod/ExplicitMovementMethodSample.kt new file mode 100644 index 00000000..224e4714 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/movementmethod/ExplicitMovementMethodSample.kt @@ -0,0 +1,37 @@ +package io.noties.markwon.app.samples.movementmethod + +import android.text.method.ScrollingMovementMethod +import io.noties.markwon.Markwon +import io.noties.markwon.app.sample.Tags +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample +import io.noties.markwon.sample.annotations.MarkwonArtifact +import io.noties.markwon.sample.annotations.MarkwonSampleInfo + +@MarkwonSampleInfo( + id = "202006179080007", + title = "Explicit movement method", + description = "When a movement method already applied to a `TextView`" + + "`Markwon` won't try to apply own (implicit) one", + artifacts = [MarkwonArtifact.CORE], + tags = [Tags.movementMethod, Tags.links] +) +class ExplicitMovementMethodSample : MarkwonTextViewSample() { + override fun render() { + val md = """ + # Explicit movement method + If `TextView` already has a movement method specified, then `Markwon` + won't be applying a default one. You can specify movement + method via call to `setMovementMethod`. If your movement method can + handle [links](https://github.com/noties/Markwon) then link would be + _clickable_ + """.trimIndent() + + val markwon = Markwon.create(context) + + // own movement method that does not handle clicks would still be used + // (no default aka implicit method would be applied by Markwon) + textView.movementMethod = ScrollingMovementMethod.getInstance() + + markwon.setMarkdown(textView, md) + } +} \ No newline at end of file diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/movementmethod/ImplicitMovementMethodSample.kt b/app-sample/src/main/java/io/noties/markwon/app/samples/movementmethod/ImplicitMovementMethodSample.kt new file mode 100644 index 00000000..e0f1da49 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/movementmethod/ImplicitMovementMethodSample.kt @@ -0,0 +1,33 @@ +package io.noties.markwon.app.samples.movementmethod + +import io.noties.markwon.Markwon +import io.noties.markwon.app.sample.Tags +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample +import io.noties.markwon.sample.annotations.MarkwonArtifact +import io.noties.markwon.sample.annotations.MarkwonSampleInfo + +@MarkwonSampleInfo( + id = "202006179075524", + title = "Implicit movement method", + description = "By default movement method is applied for links to be clickable", + artifacts = [MarkwonArtifact.CORE], + tags = [Tags.movementMethod, Tags.links] +) +class ImplicitMovementMethodSample : MarkwonTextViewSample() { + override fun render() { + val md = """ + # Implicit movement method + By default `Markwon` applies `LinkMovementMethod` if it is missing, + so in order for [links](https://github.com/noties/Markwon) to be clickable + nothing special should be done + """.trimIndent() + + // by default Markwon will apply a `LinkMovementMethod` if + // it is missing. So, in order for links to be clickable + // nothing should be done + + val markwon = Markwon.create(context) + + markwon.setMarkdown(textView, md) + } +} \ No newline at end of file diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/movementmethod/MovementMethodPluginSample.kt b/app-sample/src/main/java/io/noties/markwon/app/samples/movementmethod/MovementMethodPluginSample.kt new file mode 100644 index 00000000..f8d22647 --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/movementmethod/MovementMethodPluginSample.kt @@ -0,0 +1,33 @@ +package io.noties.markwon.app.samples.movementmethod + +import io.noties.markwon.Markwon +import io.noties.markwon.app.sample.Tags +import io.noties.markwon.app.sample.ui.MarkwonTextViewSample +import io.noties.markwon.movement.MovementMethodPlugin +import io.noties.markwon.sample.annotations.MarkwonArtifact +import io.noties.markwon.sample.annotations.MarkwonSampleInfo + +@MarkwonSampleInfo( + id = "202006179081631", + title = "MovementMethodPlugin", + description = "Plugin to control movement method", + artifacts = [MarkwonArtifact.CORE], + tags = [Tags.movementMethod, Tags.links, Tags.plugin] +) +class MovementMethodPluginSample : MarkwonTextViewSample() { + override fun render() { + val md = """ + # MovementMethodPlugin + `MovementMethodPlugin` can be used to apply movement method + explicitly. Including specific case to disable implicit movement + method which is applied when `TextView.getMovementMethod()` + returns `null`. A [link](https://github.com) + """.trimIndent() + + val markwon = Markwon.builder(context) + .usePlugin(MovementMethodPlugin.link()) + .build() + + markwon.setMarkdown(textView, md) + } +} \ No newline at end of file diff --git a/art/sample-app.epgz b/art/sample-app.epgz index 43d2f596..d5d47b94 100644 Binary files a/art/sample-app.epgz and b/art/sample-app.epgz differ diff --git a/markwon-core/src/main/java/io/noties/markwon/core/CorePlugin.java b/markwon-core/src/main/java/io/noties/markwon/core/CorePlugin.java index d9c2a3dd..9622e56b 100644 --- a/markwon-core/src/main/java/io/noties/markwon/core/CorePlugin.java +++ b/markwon-core/src/main/java/io/noties/markwon/core/CorePlugin.java @@ -116,7 +116,7 @@ public class CorePlugin extends AbstractMarkwonPlugin { private final List onTextAddedListeners = new ArrayList<>(0); // @since $nap; - private boolean hasExplicitMovementMethodPlugin; + private boolean hasExplicitMovementMethod; protected CorePlugin() { } @@ -126,8 +126,8 @@ public class CorePlugin extends AbstractMarkwonPlugin { */ @SuppressWarnings("UnusedReturnValue") @NonNull - public CorePlugin hasExplicitMovementMethodPlugin(boolean hasExplicitMovementMethodPlugin) { - this.hasExplicitMovementMethodPlugin = hasExplicitMovementMethodPlugin; + public CorePlugin hasExplicitMovementMethod(boolean hasExplicitMovementMethod) { + this.hasExplicitMovementMethod = hasExplicitMovementMethod; return this; } @@ -202,7 +202,7 @@ public class CorePlugin extends AbstractMarkwonPlugin { // we do it `afterSetText` so any user-defined movement method won't be // replaced (it should be done in `beforeSetText` or manually on a TextView) // @since $nap; we additionally check if we should apply _implicit_ movement method - if (!hasExplicitMovementMethodPlugin && textView.getMovementMethod() == null) { + if (!hasExplicitMovementMethod && textView.getMovementMethod() == null) { textView.setMovementMethod(LinkMovementMethod.getInstance()); } } diff --git a/markwon-core/src/main/java/io/noties/markwon/movement/MovementMethodPlugin.java b/markwon-core/src/main/java/io/noties/markwon/movement/MovementMethodPlugin.java index b16af95f..b672cb6e 100644 --- a/markwon-core/src/main/java/io/noties/markwon/movement/MovementMethodPlugin.java +++ b/markwon-core/src/main/java/io/noties/markwon/movement/MovementMethodPlugin.java @@ -68,7 +68,7 @@ public class MovementMethodPlugin extends AbstractMarkwonPlugin { @Override public void configure(@NonNull Registry registry) { registry.require(CorePlugin.class) - .hasExplicitMovementMethodPlugin(true); + .hasExplicitMovementMethod(true); } @Override diff --git a/markwon-core/src/test/java/io/noties/markwon/core/CorePluginTest.java b/markwon-core/src/test/java/io/noties/markwon/core/CorePluginTest.java index 02dfbf82..8c0eae91 100644 --- a/markwon-core/src/test/java/io/noties/markwon/core/CorePluginTest.java +++ b/markwon-core/src/test/java/io/noties/markwon/core/CorePluginTest.java @@ -30,13 +30,8 @@ import org.mockito.ArgumentCaptor; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; import java.util.HashMap; -import java.util.HashSet; -import java.util.List; import java.util.Map; -import java.util.Set; import io.noties.markwon.MarkwonConfiguration; import io.noties.markwon.MarkwonSpansFactory; @@ -44,9 +39,6 @@ import io.noties.markwon.MarkwonVisitor; import io.noties.markwon.RenderProps; import io.noties.markwon.SpanFactory; import io.noties.markwon.SpannableBuilder; -import ix.Ix; -import ix.IxFunction; -import ix.IxPredicate; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -279,7 +271,7 @@ public class CorePluginTest { public void explicit_movement_method() { final TextView textView = mock(TextView.class); final CorePlugin plugin = CorePlugin.create() - .hasExplicitMovementMethodPlugin(true); + .hasExplicitMovementMethod(true); plugin.afterSetText(textView); verify(textView, never()).setMovementMethod(any(MovementMethod.class)); } diff --git a/sample/src/main/java/io/noties/markwon/sample/core/CoreActivity.java b/sample/src/main/java/io/noties/markwon/sample/core/CoreActivity.java index a8625d59..6251181e 100644 --- a/sample/src/main/java/io/noties/markwon/sample/core/CoreActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/core/CoreActivity.java @@ -192,7 +192,7 @@ public class CoreActivity extends ActivityWithMenuOptions { textView.setMovementMethod(null); // by default Markwon will set a LinkMovementMethod on a TextView if it is missing - // to control that `hasExplicitMovementMethodPlugin` can be used + // to control that `hasExplicitMovementMethod` can be used final String md = "[1 link](#) here"; final Markwon markwon = Markwon.builder(this) @@ -202,7 +202,7 @@ public class CoreActivity extends ActivityWithMenuOptions { // Markwon **won't** set implicit movement method // thus making the link in markdown input not clickable registry.require(CorePlugin.class) - .hasExplicitMovementMethodPlugin(true); + .hasExplicitMovementMethod(true); } }) .build(); @@ -211,7 +211,7 @@ public class CoreActivity extends ActivityWithMenuOptions { } private void explicitMovementMethodPlugin() { - // additionally special MovementMethodPlugin.none() can be used to control `hasExplicitMovementMethodPlugin` + // additionally special MovementMethodPlugin.none() can be used to control `hasExplicitMovementMethod` final String md = "[2 link](#) here";