From d5770599f56f8ba1b7119de3b193d1daca7bb766 Mon Sep 17 00:00:00 2001 From: scria1000 <91804886+scria1000@users.noreply.github.com> Date: Tue, 16 Nov 2021 22:53:56 +0300 Subject: Refactor spoiler parsing into its own class Reuse spoiler pattern otherwise we match markdown like: `>!` lots of new lines and text "); + mCommentContent = getIntent().getStringExtra(EXTRA_CONTENT).replaceAll(">!",">!"); contentEditText.setText(mCommentContent); if (savedInstanceState != null) { diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/EditPostActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/EditPostActivity.java index 16e91ebc..2de2a47b 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/EditPostActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/EditPostActivity.java @@ -141,7 +141,7 @@ public class EditPostActivity extends BaseActivity implements UploadImageEnabled mFullName = getIntent().getStringExtra(EXTRA_FULLNAME); mAccessToken = mCurrentAccountSharedPreferences.getString(SharedPreferencesUtils.ACCESS_TOKEN, null); titleTextView.setText(getIntent().getStringExtra(EXTRA_TITLE)); - mPostContent = getIntent().getStringExtra(EXTRA_CONTENT).replaceAll(">",">");; + mPostContent = getIntent().getStringExtra(EXTRA_CONTENT).replaceAll(">!",">!");; contentEditText.setText(mPostContent); if (savedInstanceState != null) { diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/FullMarkdownActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/FullMarkdownActivity.java index 32a5f452..acd1713c 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/FullMarkdownActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/FullMarkdownActivity.java @@ -61,8 +61,10 @@ import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper; import ml.docilealligator.infinityforreddit.customviews.LinearLayoutManagerBugFixed; import ml.docilealligator.infinityforreddit.customviews.MarkwonLinearLayoutManager; import ml.docilealligator.infinityforreddit.events.SwitchAccountEvent; +import ml.docilealligator.infinityforreddit.markdown.SpoilerParserPlugin; +import ml.docilealligator.infinityforreddit.markdown.SpoilerSpan; import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; -import ml.docilealligator.infinityforreddit.utils.SuperscriptInlineProcessor; +import ml.docilealligator.infinityforreddit.markdown.SuperscriptInlineProcessor; import ml.docilealligator.infinityforreddit.utils.Utils; public class FullMarkdownActivity extends BaseActivity { @@ -150,51 +152,6 @@ public class FullMarkdownActivity extends BaseActivity { public String processMarkdown(@NonNull String markdown) { return super.processMarkdown(Utils.fixSuperScript(markdown)); } - - @Override - public void afterSetText(@NonNull TextView textView) { - textView.setHighlightColor(Color.TRANSPARENT); - SpannableStringBuilder markdownStringBuilder = new SpannableStringBuilder(textView.getText()); - Pattern spoilerPattern = Pattern.compile(">![\\S\\s]+?!<"); - Matcher matcher = spoilerPattern.matcher(markdownStringBuilder); - int start = 0; - boolean find = false; - while (matcher.find(start)) { - if (markdownStringBuilder.length() < 4 - || matcher.start() < 0 - || matcher.end() > markdownStringBuilder.length()) { - break; - } - find = true; - markdownStringBuilder.delete(matcher.end() - 2, matcher.end()); - markdownStringBuilder.delete(matcher.start(), matcher.start() + 2); - ClickableSpan clickableSpan = new ClickableSpan() { - private boolean isShowing = false; - @Override - public void updateDrawState(@NonNull TextPaint ds) { - if (isShowing) { - super.updateDrawState(ds); - ds.setColor(markdownColor); - } else { - ds.bgColor = spoilerBackgroundColor; - ds.setColor(markdownColor); - } - ds.setUnderlineText(false); - } - - @Override - public void onClick(@NonNull View view) { - isShowing = !isShowing; - view.invalidate(); - } - }; - markdownStringBuilder.setSpan(clickableSpan, matcher.start(), matcher.end() - 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - start = matcher.end() - 4; - } - if (find) { - textView.setText(markdownStringBuilder); - } - } @Override public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) { @@ -217,6 +174,7 @@ public class FullMarkdownActivity extends BaseActivity { builder.linkColor(linkColor); } }) + .usePlugin(SpoilerParserPlugin.create(markdownColor, spoilerBackgroundColor)) .usePlugin(StrikethroughPlugin.create()) .usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS)) .usePlugin(TableEntryPlugin.create(this)) diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/WikiActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/WikiActivity.java index 1a353a7f..b44f5d4f 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/WikiActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/WikiActivity.java @@ -66,9 +66,11 @@ import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper; import ml.docilealligator.infinityforreddit.customviews.LinearLayoutManagerBugFixed; import ml.docilealligator.infinityforreddit.customviews.MarkwonLinearLayoutManager; import ml.docilealligator.infinityforreddit.events.SwitchAccountEvent; +import ml.docilealligator.infinityforreddit.markdown.SpoilerParserPlugin; +import ml.docilealligator.infinityforreddit.markdown.SpoilerSpan; import ml.docilealligator.infinityforreddit.utils.JSONUtils; import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; -import ml.docilealligator.infinityforreddit.utils.SuperscriptInlineProcessor; +import ml.docilealligator.infinityforreddit.markdown.SuperscriptInlineProcessor; import ml.docilealligator.infinityforreddit.utils.Utils; import retrofit2.Call; import retrofit2.Callback; @@ -176,51 +178,6 @@ public class WikiActivity extends BaseActivity { return super.processMarkdown(Utils.fixSuperScript(markdown)); } - @Override - public void afterSetText(@NonNull TextView textView) { - textView.setHighlightColor(Color.TRANSPARENT); - SpannableStringBuilder markdownStringBuilder = new SpannableStringBuilder(textView.getText()); - Pattern spoilerPattern = Pattern.compile(">![\\S\\s]+?!<"); - Matcher matcher = spoilerPattern.matcher(markdownStringBuilder); - int start = 0; - boolean find = false; - while (matcher.find(start)) { - if (markdownStringBuilder.length() < 4 - || matcher.start() < 0 - || matcher.end() > markdownStringBuilder.length()) { - break; - } - find = true; - markdownStringBuilder.delete(matcher.end() - 2, matcher.end()); - markdownStringBuilder.delete(matcher.start(), matcher.start() + 2); - ClickableSpan clickableSpan = new ClickableSpan() { - private boolean isShowing = false; - @Override - public void updateDrawState(@NonNull TextPaint ds) { - if (isShowing) { - super.updateDrawState(ds); - ds.setColor(markdownColor); - } else { - ds.bgColor = spoilerBackgroundColor; - ds.setColor(markdownColor); - } - ds.setUnderlineText(false); - } - - @Override - public void onClick(@NonNull View view) { - isShowing = !isShowing; - view.invalidate(); - } - }; - markdownStringBuilder.setSpan(clickableSpan, matcher.start(), matcher.end() - 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - start = matcher.end() - 4; - } - if (find) { - textView.setText(markdownStringBuilder); - } - } - @Override public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) { textView.setTextColor(markdownColor); @@ -241,6 +198,7 @@ public class WikiActivity extends BaseActivity { builder.linkColor(linkColor); } }) + .usePlugin(SpoilerParserPlugin.create(markdownColor, spoilerBackgroundColor)) .usePlugin(StrikethroughPlugin.create()) .usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS)) .usePlugin(TableEntryPlugin.create(this)) diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/CommentsListingRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/CommentsListingRecyclerViewAdapter.java index 616b9c1b..40bce0d6 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/CommentsListingRecyclerViewAdapter.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/CommentsListingRecyclerViewAdapter.java @@ -63,9 +63,11 @@ import ml.docilealligator.infinityforreddit.comment.Comment; import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper; import ml.docilealligator.infinityforreddit.customviews.CommentIndentationView; import ml.docilealligator.infinityforreddit.customviews.SpoilerOnClickTextView; +import ml.docilealligator.infinityforreddit.markdown.SpoilerParserPlugin; +import ml.docilealligator.infinityforreddit.markdown.SpoilerSpan; import ml.docilealligator.infinityforreddit.utils.APIUtils; import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; -import ml.docilealligator.infinityforreddit.utils.SuperscriptInlineProcessor; +import ml.docilealligator.infinityforreddit.markdown.SuperscriptInlineProcessor; import ml.docilealligator.infinityforreddit.utils.Utils; import retrofit2.Retrofit; @@ -136,56 +138,6 @@ public class CommentsListingRecyclerViewAdapter extends PagedListAdapter![\\S\\s]+?!<"); - Matcher matcher = spoilerPattern.matcher(markdownStringBuilder); - int start = 0; - boolean find = false; - while (matcher.find(start)) { - if (markdownStringBuilder.length() < 4 - || matcher.start() < 0 - || matcher.end() > markdownStringBuilder.length()) { - break; - } - find = true; - markdownStringBuilder.delete(matcher.end() - 2, matcher.end()); - markdownStringBuilder.delete(matcher.start(), matcher.start() + 2); - int matcherStart = matcher.start(); - int matcherEnd = matcher.end(); - ClickableSpan clickableSpan = new ClickableSpan() { - private boolean isShowing = false; - @Override - public void updateDrawState(@NonNull TextPaint ds) { - if (isShowing) { - super.updateDrawState(ds); - ds.setColor(mCommentColor); - } else { - ds.bgColor = commentSpoilerBackgroundColor; - ds.setColor(mCommentColor); - } - ds.setUnderlineText(false); - } - - @Override - public void onClick(@NonNull View view) { - if (textView instanceof SpoilerOnClickTextView) { - ((SpoilerOnClickTextView) textView).setSpoilerOnClick(true); - } - isShowing = !isShowing; - view.invalidate(); - } - }; - markdownStringBuilder.setSpan(clickableSpan, matcherStart, matcherEnd - 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - start = matcherEnd - 4; - } - if (find) { - textView.setText(markdownStringBuilder); - } - } - @Override public void configureTheme(@NonNull MarkwonTheme.Builder builder) { builder.linkColor(customThemeWrapper.getLinkColor()); @@ -202,6 +154,7 @@ public class CommentsListingRecyclerViewAdapter extends PagedListAdapter![\\S\\s]+?!<"); - Matcher matcher = spoilerPattern.matcher(markdownStringBuilder); - int start = 0; - boolean find = false; - while (matcher.find(start)) { - if (markdownStringBuilder.length() < 4 - || matcher.start() < 0 - || matcher.end() > markdownStringBuilder.length()) { - break; - } - find = true; - markdownStringBuilder.delete(matcher.end() - 2, matcher.end()); - markdownStringBuilder.delete(matcher.start(), matcher.start() + 2); - ClickableSpan clickableSpan = new ClickableSpan() { - private boolean isShowing = false; - @Override - public void updateDrawState(@NonNull TextPaint ds) { - if (isShowing) { - super.updateDrawState(ds); - } else { - ds.bgColor = commentSpoilerBackgroundColor; - } - ds.setColor(mCommentTextColor); - ds.setUnderlineText(false); - } - - @Override - public void onClick(@NonNull View view) { - if (textView instanceof SpoilerOnClickTextView) { - ((SpoilerOnClickTextView) textView).setSpoilerOnClick(true); - } - isShowing = !isShowing; - view.invalidate(); - } - }; - markdownStringBuilder.setSpan(clickableSpan, matcher.start(), matcher.end() - 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - start = matcher.end() - 4; - } - if (find) { - textView.setText(markdownStringBuilder); - } - } - @Override public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { builder.linkResolver((view, link) -> { @@ -243,6 +197,7 @@ public class CommentsRecyclerViewAdapter extends RecyclerView.Adapter { if (!activity.isDestroyed() && !activity.isFinishing()) { diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/MessageRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/MessageRecyclerViewAdapter.java index 095a36f7..7560aab4 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/MessageRecyclerViewAdapter.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/MessageRecyclerViewAdapter.java @@ -46,10 +46,12 @@ import ml.docilealligator.infinityforreddit.activities.LinkResolverActivity; import ml.docilealligator.infinityforreddit.activities.ViewPrivateMessagesActivity; import ml.docilealligator.infinityforreddit.activities.ViewUserDetailActivity; import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper; +import ml.docilealligator.infinityforreddit.markdown.SpoilerParserPlugin; +import ml.docilealligator.infinityforreddit.markdown.SpoilerSpan; import ml.docilealligator.infinityforreddit.message.FetchMessage; import ml.docilealligator.infinityforreddit.message.Message; import ml.docilealligator.infinityforreddit.message.ReadMessage; -import ml.docilealligator.infinityforreddit.utils.SuperscriptInlineProcessor; +import ml.docilealligator.infinityforreddit.markdown.SuperscriptInlineProcessor; import ml.docilealligator.infinityforreddit.utils.Utils; import retrofit2.Retrofit; @@ -120,53 +122,6 @@ public class MessageRecyclerViewAdapter extends PagedListAdapter![\\S\\s]+?!<"); - Matcher matcher = spoilerPattern.matcher(markdownStringBuilder); - int start = 0; - boolean find = false; - while (matcher.find(start)) { - if (markdownStringBuilder.length() < 4 - || matcher.start() < 0 - || matcher.end() > markdownStringBuilder.length()) { - break; - } - find = true; - markdownStringBuilder.delete(matcher.end() - 2, matcher.end()); - markdownStringBuilder.delete(matcher.start(), matcher.start() + 2); - int matcherStart = matcher.start(); - int matcherEnd = matcher.end(); - ClickableSpan clickableSpan = new ClickableSpan() { - private boolean isShowing = false; - @Override - public void updateDrawState(@NonNull TextPaint ds) { - if (isShowing) { - super.updateDrawState(ds); - ds.setColor(mSecondaryTextColor); - } else { - ds.bgColor = spoilerBackgroundColor; - ds.setColor(mSecondaryTextColor); - } - ds.setUnderlineText(false); - } - - @Override - public void onClick(@NonNull View view) { - isShowing = !isShowing; - view.invalidate(); - } - }; - markdownStringBuilder.setSpan(clickableSpan, matcherStart, matcherEnd - 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - start = matcherEnd - 4; - } - if (find) { - textView.setText(markdownStringBuilder); - } - } - @Override public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { builder.linkResolver((view, link) -> { @@ -182,6 +137,7 @@ public class MessageRecyclerViewAdapter extends PagedListAdapter![\\S\\s]+?!<"); - Matcher matcher = spoilerPattern.matcher(markdownStringBuilder); - int start = 0; - boolean find = false; - while (matcher.find(start)) { - if (markdownStringBuilder.length() < 4 - || matcher.start() < 0 - || matcher.end() > markdownStringBuilder.length()) { - break; - } - find = true; - markdownStringBuilder.delete(matcher.end() - 2, matcher.end()); - markdownStringBuilder.delete(matcher.start(), matcher.start() + 2); - ClickableSpan clickableSpan = new ClickableSpan() { - private boolean isShowing = false; - @Override - public void updateDrawState(@NonNull TextPaint ds) { - if (isShowing) { - super.updateDrawState(ds); - ds.setColor(markdownColor); - } else { - ds.bgColor = postSpoilerBackgroundColor; - ds.setColor(markdownColor); - } - ds.setUnderlineText(false); - } - - @Override - public void onClick(@NonNull View view) { - isShowing = !isShowing; - view.invalidate(); - } - }; - markdownStringBuilder.setSpan(clickableSpan, matcher.start(), matcher.end() - 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - start = matcher.end() - 4; - } - if (find) { - textView.setText(markdownStringBuilder); - } - } - @Override public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) { textView.setTextColor(markdownColor); @@ -323,6 +280,7 @@ public class PostDetailRecyclerViewAdapter extends RecyclerView.Adapter { if (activity != null && !activity.isDestroyed() && !activity.isFinishing()) { diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PrivateMessagesDetailRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PrivateMessagesDetailRecyclerViewAdapter.java index e357771b..180b74be 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PrivateMessagesDetailRecyclerViewAdapter.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PrivateMessagesDetailRecyclerViewAdapter.java @@ -41,7 +41,7 @@ import ml.docilealligator.infinityforreddit.activities.ViewUserDetailActivity; import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper; import ml.docilealligator.infinityforreddit.message.Message; import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; -import ml.docilealligator.infinityforreddit.utils.SuperscriptInlineProcessor; +import ml.docilealligator.infinityforreddit.markdown.SuperscriptInlineProcessor; import ml.docilealligator.infinityforreddit.utils.Utils; public class PrivateMessagesDetailRecyclerViewAdapter extends RecyclerView.Adapter { diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/RulesRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/RulesRecyclerViewAdapter.java index 294733a1..d82fcf8d 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/RulesRecyclerViewAdapter.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/RulesRecyclerViewAdapter.java @@ -35,7 +35,7 @@ import ml.docilealligator.infinityforreddit.Rule; import ml.docilealligator.infinityforreddit.activities.LinkResolverActivity; import ml.docilealligator.infinityforreddit.bottomsheetfragments.UrlMenuBottomSheetFragment; import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper; -import ml.docilealligator.infinityforreddit.utils.SuperscriptInlineProcessor; +import ml.docilealligator.infinityforreddit.markdown.SuperscriptInlineProcessor; import ml.docilealligator.infinityforreddit.utils.Utils; public class RulesRecyclerViewAdapter extends RecyclerView.Adapter { diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/bottomsheetfragments/CopyTextBottomSheetFragment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/bottomsheetfragments/CopyTextBottomSheetFragment.java index 4264f36a..1e458fc7 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/bottomsheetfragments/CopyTextBottomSheetFragment.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/bottomsheetfragments/CopyTextBottomSheetFragment.java @@ -74,7 +74,7 @@ public class CopyTextBottomSheetFragment extends LandscapeExpandedRoundedBottomS if (markdownText != null) { //markdownText = markdownText.replaceAll("", "^").replaceAll("", ""); - markdownText = markdownText.replaceAll(">", ">"); + markdownText = markdownText.replaceAll(">!", ">!"); copyMarkdownTextView.setOnClickListener(view -> { showCopyDialog(markdownText); dismiss(); diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/SidebarFragment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/SidebarFragment.java index 3f22a64e..bea0c4ed 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/SidebarFragment.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/SidebarFragment.java @@ -58,7 +58,7 @@ import ml.docilealligator.infinityforreddit.customviews.LinearLayoutManagerBugFi import ml.docilealligator.infinityforreddit.subreddit.FetchSubredditData; import ml.docilealligator.infinityforreddit.subreddit.SubredditData; import ml.docilealligator.infinityforreddit.subreddit.SubredditViewModel; -import ml.docilealligator.infinityforreddit.utils.SuperscriptInlineProcessor; +import ml.docilealligator.infinityforreddit.markdown.SuperscriptInlineProcessor; import ml.docilealligator.infinityforreddit.utils.Utils; import retrofit2.Retrofit; diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java new file mode 100644 index 00000000..47c484c9 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java @@ -0,0 +1,65 @@ +package ml.docilealligator.infinityforreddit.markdown; + +import android.graphics.Color; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import io.noties.markwon.AbstractMarkwonPlugin; +import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper; + +public class SpoilerParserPlugin extends AbstractMarkwonPlugin { + private final int textColor; + private final int backgroundColor; + + SpoilerParserPlugin(int textColor, int backgroundColor) { + this.textColor = textColor; + this.backgroundColor = backgroundColor; + } + + public static SpoilerParserPlugin create(@NonNull int textColor, @NonNull int backgroundColor) { + return new SpoilerParserPlugin(textColor, backgroundColor); + } + + @Override + public void afterSetText(@NonNull TextView textView) { + textView.setHighlightColor(Color.TRANSPARENT); + SpannableStringBuilder markdownStringBuilder = new SpannableStringBuilder(textView.getText()); + Pattern spoilerPattern = Pattern.compile(">!(\\n?(?:.+?(?:\\n?.+?)?)\\n?)!<"); + Matcher matcher = spoilerPattern.matcher(markdownStringBuilder); + int start = 0; + boolean find = false; + while (matcher.find(start)) { + if (markdownStringBuilder.length() < 4 + || matcher.start() < 0 + || matcher.end() > markdownStringBuilder.length()) { + break; + } + find = true; + markdownStringBuilder.delete(matcher.end() - 2, matcher.end()); + markdownStringBuilder.delete(matcher.start(), matcher.start() + 2); + SpoilerSpan spoilerSpan = new SpoilerSpan(textColor, backgroundColor); + markdownStringBuilder.setSpan(spoilerSpan, matcher.start(), matcher.end() - 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + start = matcher.end() - 4; + } + /*Pattern escapedSpoilerOpenerPattern = Pattern.compile(">!"); + Matcher escapedSpoilerOpenerMatcher = escapedSpoilerOpenerPattern.matcher(markdownStringBuilder); + ArrayList matched = new ArrayList<>(); + while (escapedSpoilerOpenerMatcher.find()) { + matched.add(escapedSpoilerOpenerMatcher.start()); + find = true; + } + for (int i = matched.size() - 1; i >= 0; i--) { + markdownStringBuilder.replace(matched.get(i), matched.get(i) + 4, ">"); + }*/ + if (find) { + textView.setText(markdownStringBuilder); + } + } + +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerSpan.java b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerSpan.java new file mode 100644 index 00000000..5be3797c --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerSpan.java @@ -0,0 +1,63 @@ +package ml.docilealligator.infinityforreddit.markdown; + +import android.text.Layout; +import android.text.Spannable; +import android.text.TextPaint; +import android.text.style.ClickableSpan; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.transition.AutoTransition; +import androidx.transition.TransitionManager; + +import ml.docilealligator.infinityforreddit.customviews.SpoilerOnClickTextView; + +public class SpoilerSpan extends ClickableSpan { + final int textColor; + final int backgroundColor; + private boolean isShowing = false; + + public SpoilerSpan(@NonNull int textColor, @NonNull int backgroundColor) { + this.textColor = textColor; + this.backgroundColor = backgroundColor; + } + + @Override + public void onClick(@NonNull View widget) { + if (!(widget instanceof TextView)) { + return; + } + + final TextView textView = (TextView) widget; + final Spannable spannable = (Spannable) textView.getText(); + + final int end = spannable.getSpanEnd(this); + + if (end < 0) { + return; + } + + final Layout layout = textView.getLayout(); + if (layout == null) { + return; + } + + if (widget instanceof SpoilerOnClickTextView) { + ((SpoilerOnClickTextView) textView).setSpoilerOnClick(true); + } + isShowing = !isShowing; + widget.invalidate(); + } + + @Override + public void updateDrawState(@NonNull TextPaint ds) { + if (isShowing) { + super.updateDrawState(ds); + } else { + ds.bgColor = backgroundColor; + } + ds.setColor(textColor); + ds.setUnderlineText(false); + } +} \ No newline at end of file diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SuperscriptInlineProcessor.java b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SuperscriptInlineProcessor.java new file mode 100644 index 00000000..27f4d1e4 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SuperscriptInlineProcessor.java @@ -0,0 +1,32 @@ +package ml.docilealligator.infinityforreddit.markdown; + +import androidx.annotation.Nullable; + +import org.commonmark.node.HtmlInline; +import org.commonmark.node.Node; + +import java.util.regex.Pattern; + +import io.noties.markwon.inlineparser.InlineProcessor; + +public class SuperscriptInlineProcessor extends InlineProcessor { + private static final Pattern HTML_TAG = Pattern.compile("", Pattern.CASE_INSENSITIVE); + + @Override + public char specialCharacter() { + return '<'; + } + + @Nullable + @Override + protected Node parse() { + String m = match(HTML_TAG); + if (m != null) { + HtmlInline node = new HtmlInline(); + node.setLiteral(m); + return node; + } else { + return null; + } + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/utils/SuperscriptInlineProcessor.java b/app/src/main/java/ml/docilealligator/infinityforreddit/utils/SuperscriptInlineProcessor.java deleted file mode 100644 index 136a69d4..00000000 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/utils/SuperscriptInlineProcessor.java +++ /dev/null @@ -1,32 +0,0 @@ -package ml.docilealligator.infinityforreddit.utils; - -import androidx.annotation.Nullable; - -import org.commonmark.node.HtmlInline; -import org.commonmark.node.Node; - -import java.util.regex.Pattern; - -import io.noties.markwon.inlineparser.InlineProcessor; - -public class SuperscriptInlineProcessor extends InlineProcessor { - private static final Pattern HTML_TAG = Pattern.compile("", Pattern.CASE_INSENSITIVE); - - @Override - public char specialCharacter() { - return '<'; - } - - @Nullable - @Override - protected Node parse() { - String m = match(HTML_TAG); - if (m != null) { - HtmlInline node = new HtmlInline(); - node.setLiteral(m); - return node; - } else { - return null; - } - } -} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/utils/Utils.java b/app/src/main/java/ml/docilealligator/infinityforreddit/utils/Utils.java index 15bcefa0..2963a58f 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/utils/Utils.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/utils/Utils.java @@ -65,7 +65,7 @@ public class Utils { .replaceAll("((?<=[\\s])|^)/[rRuU]/[\\w-]+/{0,1}", "[$0](https://www.reddit.com$0)") .replaceAll("((?<=[\\s])|^)[rRuU]/[\\w-]+/{0,1}", "[$0](https://www.reddit.com/$0)") .replaceAll("\\^{2,}", "^") - .replaceAll(">!(\\n?(?:[\\S\\h]+?(?:\\n?[\\S\\h]+?)?)\\n?)!<", ">!$1!<") // html entity remains escaped inside an inline block + .replaceAll(">!(\\n?(?:.+?(?:\\n?.+?)?)\\n?)!<", ">!$1!<") // html entity remains escaped inside an inline block .replaceAll("(^|^ *|\\n *)#(?!($|\\s|#))", "$0 ") .replaceAll("(^|^ *|\\n *)##(?!($|\\s|#))", "$0 ") .replaceAll("(^|^ *|\\n *)###(?!($|\\s|#))", "$0 ") -- cgit v1.2.3 From b0134984ca078e0c7f3c7bebeaafe2146e76dde4 Mon Sep 17 00:00:00 2001 From: scria1000 <91804886+scria1000@users.noreply.github.com> Date: Thu, 25 Nov 2021 19:04:34 +0300 Subject: Add a non-regex solution for parsing spoilers Add a non-regex solution for parsing spoilers that can also handle code blocks Add a custom BlockQuoteParser to prevent spoilers from being consumed as block quotes without the need for html escaping Make revealed spoiler backgrounds slightly darker --- .../activities/EditCommentActivity.java | 2 +- .../activities/EditPostActivity.java | 2 +- .../adapters/CommentsRecyclerViewAdapter.java | 8 +- .../CopyTextBottomSheetFragment.java | 1 - .../markdown/BlockQuoteWithExceptionParser.java | 85 ++++++++++ .../markdown/SpoilerParserPlugin.java | 185 +++++++++++++++++---- .../infinityforreddit/markdown/SpoilerSpan.java | 1 + .../infinityforreddit/utils/Utils.java | 1 - 8 files changed, 246 insertions(+), 39 deletions(-) create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/markdown/BlockQuoteWithExceptionParser.java diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/EditCommentActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/EditCommentActivity.java index 6b358806..4f4b81ec 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/EditCommentActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/EditCommentActivity.java @@ -132,7 +132,7 @@ public class EditCommentActivity extends BaseActivity implements UploadImageEnab mFullName = getIntent().getStringExtra(EXTRA_FULLNAME); mAccessToken = mCurrentAccountSharedPreferences.getString(SharedPreferencesUtils.ACCESS_TOKEN, null); - mCommentContent = getIntent().getStringExtra(EXTRA_CONTENT).replaceAll(">!",">!"); + mCommentContent = getIntent().getStringExtra(EXTRA_CONTENT); contentEditText.setText(mCommentContent); if (savedInstanceState != null) { diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/EditPostActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/EditPostActivity.java index 2de2a47b..0d22d9b3 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/EditPostActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/EditPostActivity.java @@ -141,7 +141,7 @@ public class EditPostActivity extends BaseActivity implements UploadImageEnabled mFullName = getIntent().getStringExtra(EXTRA_FULLNAME); mAccessToken = mCurrentAccountSharedPreferences.getString(SharedPreferencesUtils.ACCESS_TOKEN, null); titleTextView.setText(getIntent().getStringExtra(EXTRA_TITLE)); - mPostContent = getIntent().getStringExtra(EXTRA_CONTENT).replaceAll(">!",">!");; + mPostContent = getIntent().getStringExtra(EXTRA_CONTENT); contentEditText.setText(mPostContent); if (savedInstanceState != null) { diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/CommentsRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/CommentsRecyclerViewAdapter.java index 8c17bc2d..6e6d18d5 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/CommentsRecyclerViewAdapter.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/CommentsRecyclerViewAdapter.java @@ -9,8 +9,6 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.Handler; -import android.text.SpannableStringBuilder; -import android.text.Spanned; import android.text.util.Linkify; import android.view.LayoutInflater; import android.view.View; @@ -36,8 +34,6 @@ import com.lsjwzh.widget.materialloadingprogressbar.CircleProgressBar; import java.util.ArrayList; import java.util.Locale; import java.util.concurrent.Executor; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import butterknife.BindView; import butterknife.ButterKnife; @@ -48,6 +44,7 @@ import io.noties.markwon.core.MarkwonTheme; import io.noties.markwon.ext.strikethrough.StrikethroughPlugin; import io.noties.markwon.html.HtmlPlugin; import io.noties.markwon.html.tag.SuperScriptHandler; +import io.noties.markwon.inlineparser.BackslashInlineProcessor; import io.noties.markwon.inlineparser.BangInlineProcessor; import io.noties.markwon.inlineparser.HtmlInlineProcessor; import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin; @@ -70,11 +67,10 @@ import ml.docilealligator.infinityforreddit.customviews.CommentIndentationView; import ml.docilealligator.infinityforreddit.customviews.SpoilerOnClickTextView; import ml.docilealligator.infinityforreddit.fragments.ViewPostDetailFragment; import ml.docilealligator.infinityforreddit.markdown.SpoilerParserPlugin; -import ml.docilealligator.infinityforreddit.markdown.SpoilerSpan; +import ml.docilealligator.infinityforreddit.markdown.SuperscriptInlineProcessor; import ml.docilealligator.infinityforreddit.post.Post; import ml.docilealligator.infinityforreddit.utils.APIUtils; import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; -import ml.docilealligator.infinityforreddit.markdown.SuperscriptInlineProcessor; import ml.docilealligator.infinityforreddit.utils.Utils; import retrofit2.Retrofit; diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/bottomsheetfragments/CopyTextBottomSheetFragment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/bottomsheetfragments/CopyTextBottomSheetFragment.java index 1e458fc7..e30156dc 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/bottomsheetfragments/CopyTextBottomSheetFragment.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/bottomsheetfragments/CopyTextBottomSheetFragment.java @@ -74,7 +74,6 @@ public class CopyTextBottomSheetFragment extends LandscapeExpandedRoundedBottomS if (markdownText != null) { //markdownText = markdownText.replaceAll("", "^").replaceAll("", ""); - markdownText = markdownText.replaceAll(">!", ">!"); copyMarkdownTextView.setOnClickListener(view -> { showCopyDialog(markdownText); dismiss(); diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/BlockQuoteWithExceptionParser.java b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/BlockQuoteWithExceptionParser.java new file mode 100644 index 00000000..f500bb93 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/BlockQuoteWithExceptionParser.java @@ -0,0 +1,85 @@ +package ml.docilealligator.infinityforreddit.markdown; + +import org.commonmark.internal.util.Parsing; +import org.commonmark.node.Block; +import org.commonmark.node.BlockQuote; +import org.commonmark.parser.block.AbstractBlockParser; +import org.commonmark.parser.block.AbstractBlockParserFactory; +import org.commonmark.parser.block.BlockContinue; +import org.commonmark.parser.block.BlockStart; +import org.commonmark.parser.block.MatchedBlockParser; +import org.commonmark.parser.block.ParserState; + +// Parse and consume a block quote except when it's a spoiler opening +public class BlockQuoteWithExceptionParser extends AbstractBlockParser { + private final BlockQuote block = new BlockQuote(); + + private static boolean isMarker(ParserState state, int index) { + CharSequence line = state.getLine(); + return state.getIndent() < Parsing.CODE_BLOCK_INDENT && index < line.length() && line.charAt(index) == '>'; + } + + private static boolean isMarkerSpoiler(ParserState state, int index) { + CharSequence line = state.getLine(); + int length = line.length(); + try { + return state.getIndent() < Parsing.CODE_BLOCK_INDENT && index < length && line.charAt(index) == '>' && (index + 1) < length && line.charAt(index + 1) == '!'; + } catch (IndexOutOfBoundsException e) { + e.printStackTrace(); + return false; + } + } + + @Override + public boolean isContainer() { + return true; + } + + @Override + public boolean canContain(Block block) { + return true; + } + + @Override + public BlockQuote getBlock() { + return block; + } + + @Override + public BlockContinue tryContinue(ParserState state) { + int nextNonSpace = state.getNextNonSpaceIndex(); + if (isMarker(state, nextNonSpace)) { + int newColumn = state.getColumn() + state.getIndent() + 1; + // optional following space or tab + if (Parsing.isSpaceOrTab(state.getLine(), nextNonSpace + 1)) { + newColumn++; + } + return BlockContinue.atColumn(newColumn); + } else { + return BlockContinue.none(); + } + } + + public static class Factory extends AbstractBlockParserFactory { + public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { + int nextNonSpace = state.getNextNonSpaceIndex(); + // Potential for a spoiler opening + // We don't check for spoiler closing, neither does Reddit + if (isMarkerSpoiler(state, nextNonSpace)) { + // It might be a spoiler, don't consume + return BlockStart.none(); + } + // Not a spoiler then + else if (isMarker(state, nextNonSpace)) { + int newColumn = state.getColumn() + state.getIndent() + 1; + // optional following space or tab + if (Parsing.isSpaceOrTab(state.getLine(), nextNonSpace + 1)) { + newColumn++; + } + return BlockStart.of(new BlockQuoteWithExceptionParser()).atColumn(newColumn); + } else { + return BlockStart.none(); + } + } + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java index 47c484c9..36068b9a 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java @@ -7,11 +7,20 @@ import android.widget.TextView; import androidx.annotation.NonNull; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import org.commonmark.node.Block; +import org.commonmark.node.BlockQuote; +import org.commonmark.node.HtmlBlock; +import org.commonmark.node.HtmlInline; +import org.commonmark.parser.Parser; + +import java.util.LinkedHashMap; +import java.util.Set; +import java.util.Stack; import io.noties.markwon.AbstractMarkwonPlugin; -import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper; +import io.noties.markwon.core.CorePlugin; +import io.noties.markwon.core.spans.CodeBlockSpan; +import io.noties.markwon.core.spans.CodeSpan; public class SpoilerParserPlugin extends AbstractMarkwonPlugin { private final int textColor; @@ -26,40 +35,158 @@ public class SpoilerParserPlugin extends AbstractMarkwonPlugin { return new SpoilerParserPlugin(textColor, backgroundColor); } + @Override + public void configureParser(@NonNull Parser.Builder builder) { + builder.customBlockParserFactory(new BlockQuoteWithExceptionParser.Factory()); + + Set> blocks = CorePlugin.enabledBlockTypes(); + blocks.remove(HtmlBlock.class); + blocks.remove(HtmlInline.class); + blocks.remove(BlockQuote.class); + + builder.enabledBlockTypes(blocks); + } + @Override public void afterSetText(@NonNull TextView textView) { textView.setHighlightColor(Color.TRANSPARENT); SpannableStringBuilder markdownStringBuilder = new SpannableStringBuilder(textView.getText()); - Pattern spoilerPattern = Pattern.compile(">!(\\n?(?:.+?(?:\\n?.+?)?)\\n?)!<"); - Matcher matcher = spoilerPattern.matcher(markdownStringBuilder); - int start = 0; - boolean find = false; - while (matcher.find(start)) { - if (markdownStringBuilder.length() < 4 - || matcher.start() < 0 - || matcher.end() > markdownStringBuilder.length()) { - break; + + LinkedHashMap spoilers = parse(markdownStringBuilder); + int offset = 2; + + for (var entry : spoilers.entrySet()) { + int spoilerStart = entry.getKey() - offset; + int spoilerEnd = entry.getValue() - offset; + + CodeSpan[] codeSpans = markdownStringBuilder.getSpans(spoilerStart, spoilerEnd, CodeSpan.class); + CodeBlockSpan[] codeBlockSpans = markdownStringBuilder.getSpans(spoilerStart, spoilerEnd, CodeBlockSpan.class); + + if (codeSpans.length == 0 && codeBlockSpans.length == 0) { + markdownStringBuilder.delete(spoilerStart, spoilerStart + 2); + markdownStringBuilder.delete(spoilerEnd, spoilerEnd + 2); + SpoilerSpan spoilerSpan = new SpoilerSpan(textColor, backgroundColor); + markdownStringBuilder.setSpan(spoilerSpan, spoilerStart, spoilerEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + offset += 4; + } + s + for (CodeSpan codeSpan : codeSpans) { + int spanBeginning = markdownStringBuilder.getSpanStart(codeSpan); + int spanEnd = markdownStringBuilder.getSpanEnd(codeSpan); + if (spoilerStart < spanBeginning && spanEnd < spoilerEnd) { + markdownStringBuilder.delete(spoilerStart, spoilerStart + 2); + markdownStringBuilder.delete(spoilerEnd, spoilerEnd + 2); + SpoilerSpan spoilerSpan = new SpoilerSpan(textColor, backgroundColor); + markdownStringBuilder.setSpan(spoilerSpan, spoilerStart, spoilerEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + offset += 4; + } else { + break; + } + } + + for (CodeBlockSpan codeBlockSpan : codeBlockSpans) { + int spanBeginning = markdownStringBuilder.getSpanStart(codeBlockSpan); + int spanEnd = markdownStringBuilder.getSpanEnd(codeBlockSpan); + if (spoilerStart < spanBeginning && spanEnd < spoilerEnd) { + markdownStringBuilder.delete(spoilerStart, spoilerStart + 2); + markdownStringBuilder.delete(spoilerEnd, spoilerEnd + 2); + SpoilerSpan spoilerSpan = new SpoilerSpan(textColor, backgroundColor); + markdownStringBuilder.setSpan(spoilerSpan, spoilerStart, spoilerEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + offset += 4; + } else { + break; + } } - find = true; - markdownStringBuilder.delete(matcher.end() - 2, matcher.end()); - markdownStringBuilder.delete(matcher.start(), matcher.start() + 2); - SpoilerSpan spoilerSpan = new SpoilerSpan(textColor, backgroundColor); - markdownStringBuilder.setSpan(spoilerSpan, matcher.start(), matcher.end() - 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - start = matcher.end() - 4; } - /*Pattern escapedSpoilerOpenerPattern = Pattern.compile(">!"); - Matcher escapedSpoilerOpenerMatcher = escapedSpoilerOpenerPattern.matcher(markdownStringBuilder); - ArrayList matched = new ArrayList<>(); - while (escapedSpoilerOpenerMatcher.find()) { - matched.add(escapedSpoilerOpenerMatcher.start()); - find = true; + + textView.setText(markdownStringBuilder); + } + + // Very naive implementation, needs to be improved for efficiency and edge cases + // Don't allow more than one new line after every non-blank line + // Try not to care about recursing spoilers, we just want the outermost spoiler because + // spoiler revealing-hiding breaks with recursing spoilers + // Try not to set a spoiler span if it's inside a CodeSpan + private LinkedHashMap parse(SpannableStringBuilder markdown) { + final int MAX_NEW_LINE = 1; + var openSpoilerStack = new Stack(); + var closedSpoilerMap = new LinkedHashMap(); + int variable_max_depth = calculateBalance(0, markdown) + 1; + int new_lines = 0; + int depth = 0; + for (int i = 0; i < markdown.length(); i++) { + if (markdown.charAt(i) == '\u2000' || markdown.charAt(i) == '\t') { + continue; + } else if (markdown.charAt(i) == '>' && (i + 1) < markdown.length() && markdown.charAt(i + 1) == '!') { + openSpoilerStack.push(i + 1); + depth++; + } else if (openSpoilerStack.size() > 0 + && markdown.charAt(i) == '!' && (i + 1) < markdown.length() + && markdown.charAt(i + 1) == '<') { + var pos = i + 1; + for (int j = 0; j < depth; j++) { + if (!openSpoilerStack.isEmpty()) pos = openSpoilerStack.peek(); + if (pos + 1 <= i) { + if (!openSpoilerStack.isEmpty()) pos = openSpoilerStack.peek(); + break; + } else { + if (!openSpoilerStack.isEmpty()) pos = openSpoilerStack.pop(); + } + } + if (depth <= variable_max_depth && pos + 1 <= i) //Spoiler content cannot be zero or less length + { + openSpoilerStack.clear(); + closedSpoilerMap.put(pos + 1, i); + } + depth--; + } else if (markdown.charAt(i) == '\n') { + new_lines++; + if (openSpoilerStack.size() >= 1 && new_lines > MAX_NEW_LINE) { + openSpoilerStack.clear(); + new_lines = 0; + depth = 0; + variable_max_depth = calculateBalance(i, markdown) + 1; + } + } else { + new_lines = 0; + } + + if (openSpoilerStack.size() >= 32) // No + { + openSpoilerStack.clear(); + closedSpoilerMap.clear(); + continue; + } } - for (int i = matched.size() - 1; i >= 0; i--) { - markdownStringBuilder.replace(matched.get(i), matched.get(i) + 4, ">"); - }*/ - if (find) { - textView.setText(markdownStringBuilder); + return closedSpoilerMap; + } + + private int calculateBalance(int index, SpannableStringBuilder line) { + final int MAX_NEW_LINE = 1; + int new_lines = 0; + int opening = 0; + int closing = 0; + for (int i = index; i < line.length(); i++) { + if (line.charAt(i) == '\u0020' || line.charAt(i) == '\t') { + continue; + } else if (line.charAt(i) == '>' + && (i + 1) < line.length() + && line.charAt(i + 1) == '!') { + opening++; + } else if (line.charAt(i) == '!' && (i + 1) < line.length() + && line.charAt(i + 1) == '<') { + closing++; + } else if (line.charAt(i) == '\n') { + new_lines++; + if (new_lines > MAX_NEW_LINE) { + break; + } + } else { + new_lines = 0; + continue; + } } + return Math.abs(opening - closing); } } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerSpan.java b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerSpan.java index 5be3797c..2264449c 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerSpan.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerSpan.java @@ -53,6 +53,7 @@ public class SpoilerSpan extends ClickableSpan { @Override public void updateDrawState(@NonNull TextPaint ds) { if (isShowing) { + ds.bgColor = backgroundColor & 0x0D000000; //Slightly darker background color for revealed spoiler super.updateDrawState(ds); } else { ds.bgColor = backgroundColor; diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/utils/Utils.java b/app/src/main/java/ml/docilealligator/infinityforreddit/utils/Utils.java index 2963a58f..f292088d 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/utils/Utils.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/utils/Utils.java @@ -65,7 +65,6 @@ public class Utils { .replaceAll("((?<=[\\s])|^)/[rRuU]/[\\w-]+/{0,1}", "[$0](https://www.reddit.com$0)") .replaceAll("((?<=[\\s])|^)[rRuU]/[\\w-]+/{0,1}", "[$0](https://www.reddit.com/$0)") .replaceAll("\\^{2,}", "^") - .replaceAll(">!(\\n?(?:.+?(?:\\n?.+?)?)\\n?)!<", ">!$1!<") // html entity remains escaped inside an inline block .replaceAll("(^|^ *|\\n *)#(?!($|\\s|#))", "$0 ") .replaceAll("(^|^ *|\\n *)##(?!($|\\s|#))", "$0 ") .replaceAll("(^|^ *|\\n *)###(?!($|\\s|#))", "$0 ") -- cgit v1.2.3 From d1096f1f7648b3e09d4bf3437121c37c96d966e4 Mon Sep 17 00:00:00 2001 From: scria1000 <91804886+scria1000@users.noreply.github.com> Date: Thu, 25 Nov 2021 19:07:15 +0300 Subject: Fix typo --- .../docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java index 36068b9a..2fd243ac 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java @@ -69,7 +69,7 @@ public class SpoilerParserPlugin extends AbstractMarkwonPlugin { markdownStringBuilder.setSpan(spoilerSpan, spoilerStart, spoilerEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); offset += 4; } - s + for (CodeSpan codeSpan : codeSpans) { int spanBeginning = markdownStringBuilder.getSpanStart(codeSpan); int spanEnd = markdownStringBuilder.getSpanEnd(codeSpan); -- cgit v1.2.3 From e481980480bc5666edd671a06a71078525824bac Mon Sep 17 00:00:00 2001 From: scria1000 <91804886+scria1000@users.noreply.github.com> Date: Thu, 25 Nov 2021 19:22:32 +0300 Subject: Check if spoiler was found before setting markdown --- .../infinityforreddit/markdown/SpoilerParserPlugin.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java index 2fd243ac..a83df76a 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java @@ -14,6 +14,7 @@ import org.commonmark.node.HtmlInline; import org.commonmark.parser.Parser; import java.util.LinkedHashMap; +import java.util.Map; import java.util.Set; import java.util.Stack; @@ -55,7 +56,7 @@ public class SpoilerParserPlugin extends AbstractMarkwonPlugin { LinkedHashMap spoilers = parse(markdownStringBuilder); int offset = 2; - for (var entry : spoilers.entrySet()) { + for (Map.Entry entry : spoilers.entrySet()) { int spoilerStart = entry.getKey() - offset; int spoilerEnd = entry.getValue() - offset; @@ -69,7 +70,7 @@ public class SpoilerParserPlugin extends AbstractMarkwonPlugin { markdownStringBuilder.setSpan(spoilerSpan, spoilerStart, spoilerEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); offset += 4; } - + for (CodeSpan codeSpan : codeSpans) { int spanBeginning = markdownStringBuilder.getSpanStart(codeSpan); int spanEnd = markdownStringBuilder.getSpanEnd(codeSpan); @@ -98,8 +99,9 @@ public class SpoilerParserPlugin extends AbstractMarkwonPlugin { } } } - - textView.setText(markdownStringBuilder); + if(offset > 2) { + textView.setText(markdownStringBuilder); + } } // Very naive implementation, needs to be improved for efficiency and edge cases -- cgit v1.2.3 From 8423a1807162268d3f4b47b9ca7f5d4280b255ce Mon Sep 17 00:00:00 2001 From: scria1000 <91804886+scria1000@users.noreply.github.com> Date: Thu, 25 Nov 2021 20:59:18 +0300 Subject: Return early if not spoiler --- .../infinityforreddit/markdown/SpoilerParserPlugin.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java index a83df76a..bd926f7c 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java @@ -51,15 +51,24 @@ public class SpoilerParserPlugin extends AbstractMarkwonPlugin { @Override public void afterSetText(@NonNull TextView textView) { textView.setHighlightColor(Color.TRANSPARENT); + + if(textView.getText().length() < 5) { + return; + } + SpannableStringBuilder markdownStringBuilder = new SpannableStringBuilder(textView.getText()); LinkedHashMap spoilers = parse(markdownStringBuilder); + if(spoilers.size() == 0) { + return; + } int offset = 2; for (Map.Entry entry : spoilers.entrySet()) { int spoilerStart = entry.getKey() - offset; int spoilerEnd = entry.getValue() - offset; + // Try not to set a spoiler span if it's inside a CodeSpan CodeSpan[] codeSpans = markdownStringBuilder.getSpans(spoilerStart, spoilerEnd, CodeSpan.class); CodeBlockSpan[] codeBlockSpans = markdownStringBuilder.getSpans(spoilerStart, spoilerEnd, CodeBlockSpan.class); @@ -108,7 +117,6 @@ public class SpoilerParserPlugin extends AbstractMarkwonPlugin { // Don't allow more than one new line after every non-blank line // Try not to care about recursing spoilers, we just want the outermost spoiler because // spoiler revealing-hiding breaks with recursing spoilers - // Try not to set a spoiler span if it's inside a CodeSpan private LinkedHashMap parse(SpannableStringBuilder markdown) { final int MAX_NEW_LINE = 1; var openSpoilerStack = new Stack(); -- cgit v1.2.3 From 209a874f57f37fd7b32daa5df2c8951df0c940f4 Mon Sep 17 00:00:00 2001 From: scria1000 <91804886+scria1000@users.noreply.github.com> Date: Thu, 25 Nov 2021 21:54:08 +0300 Subject: Minor tweaks --- .../infinityforreddit/markdown/SpoilerParserPlugin.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java index bd926f7c..1cca0cfe 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/markdown/SpoilerParserPlugin.java @@ -83,7 +83,7 @@ public class SpoilerParserPlugin extends AbstractMarkwonPlugin { for (CodeSpan codeSpan : codeSpans) { int spanBeginning = markdownStringBuilder.getSpanStart(codeSpan); int spanEnd = markdownStringBuilder.getSpanEnd(codeSpan); - if (spoilerStart < spanBeginning && spanEnd < spoilerEnd) { + if (spoilerStart + 2 <= spanBeginning && spanEnd <= spoilerEnd + 2) { markdownStringBuilder.delete(spoilerStart, spoilerStart + 2); markdownStringBuilder.delete(spoilerEnd, spoilerEnd + 2); SpoilerSpan spoilerSpan = new SpoilerSpan(textColor, backgroundColor); @@ -97,7 +97,7 @@ public class SpoilerParserPlugin extends AbstractMarkwonPlugin { for (CodeBlockSpan codeBlockSpan : codeBlockSpans) { int spanBeginning = markdownStringBuilder.getSpanStart(codeBlockSpan); int spanEnd = markdownStringBuilder.getSpanEnd(codeBlockSpan); - if (spoilerStart < spanBeginning && spanEnd < spoilerEnd) { + if (spoilerStart + 2 <= spanBeginning && spanEnd <= spoilerEnd + 2) { markdownStringBuilder.delete(spoilerStart, spoilerStart + 2); markdownStringBuilder.delete(spoilerEnd, spoilerEnd + 2); SpoilerSpan spoilerSpan = new SpoilerSpan(textColor, backgroundColor); -- cgit v1.2.3