diff --git a/README.md b/README.md index db19a195..0041cbeb 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ and [3.x.x](https://github.com/noties/Markwon/tree/3.x.x) branches ## Screenshots -Taken with default configuration (except for image loading): +Taken with default configuration (except for image loading) in [sample app](./app-sample/): diff --git a/app-sample/samples.json b/app-sample/samples.json index 1e04d195..d24eb2a7 100644 --- a/app-sample/samples.json +++ b/app-sample/samples.json @@ -1,4 +1,16 @@ [ + { + "javaClassName": "io.noties.markwon.app.samples.RedditSpoilerSample", + "id": "20200813145316", + "title": "Reddit spoiler", + "description": "An attempt to implement Reddit spoiler syntax `\u003e! !\u003c`", + "artifacts": [ + "CORE" + ], + "tags": [ + "parsing" + ] + }, { "javaClassName": "io.noties.markwon.app.samples.image.CoilRecyclerViewSample", "id": "20200803132053", diff --git a/app-sample/src/main/java/io/noties/markwon/app/samples/RedditSpoilerSample.java b/app-sample/src/main/java/io/noties/markwon/app/samples/RedditSpoilerSample.java new file mode 100644 index 00000000..e51ffabd --- /dev/null +++ b/app-sample/src/main/java/io/noties/markwon/app/samples/RedditSpoilerSample.java @@ -0,0 +1,126 @@ +package io.noties.markwon.app.samples; + +import android.graphics.Color; +import android.text.Spannable; +import android.text.Spanned; +import android.text.TextPaint; +import android.text.style.CharacterStyle; +import android.text.style.ClickableSpan; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +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.sample.annotations.MarkwonArtifact; +import io.noties.markwon.sample.annotations.MarkwonSampleInfo; +import io.noties.markwon.utils.ColorUtils; + +@MarkwonSampleInfo( + id = "20200813145316", + title = "Reddit spoiler", + description = "An attempt to implement Reddit spoiler syntax `>! !<`", + artifacts = MarkwonArtifact.CORE, + tags = Tags.parsing +) +public class RedditSpoilerSample extends MarkwonTextViewSample { + @Override + public void render() { + final String md = "" + + "# Reddit spolier\n\n" + + "Hello >!ugly so **ugly** !<, how are you?\n\n" + + ">!a blockquote?!< should not be >!present!< yeah" + + ""; + + final Markwon markwon = Markwon.builder(context) + .usePlugin(new RedditSpoilerPlugin()) + .build(); + + markwon.setMarkdown(textView, md); + } +} + +class RedditSpoilerPlugin extends AbstractMarkwonPlugin { + + private static final Pattern RE = Pattern.compile(">!.+?!<"); + + @NonNull + @Override + public String processMarkdown(@NonNull String markdown) { + // replace all `>!` with `>!` so no blockquote would be parsed (when spoiler starts at new line) + return markdown.replaceAll(">!", ">!"); + } + + @Override + public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) { + applySpoilerSpans((Spannable) markdown); + } + + private static void applySpoilerSpans(@NonNull Spannable spannable) { + final String text = spannable.toString(); + final Matcher matcher = RE.matcher(text); + + while (matcher.find()) { + + final RedditSpoilerSpan spoilerSpan = new RedditSpoilerSpan(); + final ClickableSpan clickableSpan = new ClickableSpan() { + @Override + public void onClick(@NonNull View widget) { + spoilerSpan.setRevealed(true); + widget.postInvalidateOnAnimation(); + } + + @Override + public void updateDrawState(@NonNull TextPaint ds) { + // no op + } + }; + + final int s = matcher.start(); + final int e = matcher.end(); + spannable.setSpan(spoilerSpan, s, e, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + spannable.setSpan(clickableSpan, s, e, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + // we also can hide original syntax + spannable.setSpan(new HideSpoilerSyntaxSpan(), s, s + 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + spannable.setSpan(new HideSpoilerSyntaxSpan(), e - 2, e, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + + private static class RedditSpoilerSpan extends CharacterStyle { + + private boolean revealed; + + @Override + public void updateDrawState(TextPaint tp) { + if (!revealed) { + // use the same text color + tp.bgColor = Color.BLACK; + tp.setColor(Color.BLACK); + } else { + // for example keep a bit of black background to remind that it is a spoiler + tp.bgColor = ColorUtils.applyAlpha(Color.BLACK, 25); + } + } + + public void setRevealed(boolean revealed) { + this.revealed = revealed; + } + } + + // we also could make text size smaller (but then MetricAffectingSpan should be used) + private static class HideSpoilerSyntaxSpan extends CharacterStyle { + + @Override + public void updateDrawState(TextPaint tp) { + // set transparent color + tp.setColor(0); + } + } +} \ No newline at end of file