mirror of
https://codeberg.org/Bazsalanszky/Infinity-For-Lemmy.git
synced 2025-01-27 10:04:45 +01:00
Superscript parser overhaul (#1162)
* Superscript parser overhaul * Make superscript work with tables * Fix some issues with Table compatibility * Some bug fixes * Re-enable Autolink
This commit is contained in:
parent
e362f2ecf0
commit
20214ce323
@ -161,7 +161,6 @@ dependencies {
|
||||
implementation "io.noties.markwon:linkify:$markwonVersion"
|
||||
implementation "io.noties.markwon:recycler-table:$markwonVersion"
|
||||
implementation "io.noties.markwon:simple-ext:$markwonVersion"
|
||||
implementation "io.noties.markwon:html:$markwonVersion"
|
||||
implementation "io.noties.markwon:inline-parser:$markwonVersion"
|
||||
implementation 'com.atlassian.commonmark:commonmark-ext-gfm-tables:0.14.0'
|
||||
implementation 'me.saket:better-link-movement-method:2.2.0'
|
||||
|
@ -178,12 +178,6 @@ public class CommentActivity extends BaseActivity implements UploadImageEnabledA
|
||||
binding.commentContentMarkdownView.setNestedScrollingEnabled(false);
|
||||
int linkColor = mCustomThemeWrapper.getLinkColor();
|
||||
MarkwonPlugin miscPlugin = new AbstractMarkwonPlugin() {
|
||||
@NonNull
|
||||
@Override
|
||||
public String processMarkdown(@NonNull String markdown) {
|
||||
return Utils.fixSuperScript(markdown);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
|
||||
if (contentTypeface != null) {
|
||||
|
@ -117,12 +117,6 @@ public class FullMarkdownActivity extends BaseActivity {
|
||||
int spoilerBackgroundColor = markdownColor | 0xFF000000;
|
||||
int linkColor = mCustomThemeWrapper.getLinkColor();
|
||||
MarkwonPlugin miscPlugin = new AbstractMarkwonPlugin() {
|
||||
@NonNull
|
||||
@Override
|
||||
public String processMarkdown(@NonNull String markdown) {
|
||||
return Utils.fixSuperScript(markdown);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
|
||||
if (typeface != null) {
|
||||
|
@ -149,12 +149,6 @@ public class WikiActivity extends BaseActivity {
|
||||
int spoilerBackgroundColor = markdownColor | 0xFF000000;
|
||||
int linkColor = mCustomThemeWrapper.getLinkColor();
|
||||
MarkwonPlugin miscPlugin = new AbstractMarkwonPlugin() {
|
||||
@NonNull
|
||||
@Override
|
||||
public String processMarkdown(@NonNull String markdown) {
|
||||
return Utils.fixSuperScript(markdown);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
|
||||
textView.setTextColor(markdownColor);
|
||||
|
@ -138,12 +138,6 @@ public class CommentsListingRecyclerViewAdapter extends PagedListAdapter<Comment
|
||||
mCommentIconAndInfoColor = customThemeWrapper.getCommentIconAndInfoColor();
|
||||
int linkColor = customThemeWrapper.getLinkColor();
|
||||
MarkwonPlugin miscPlugin = new AbstractMarkwonPlugin() {
|
||||
@NonNull
|
||||
@Override
|
||||
public String processMarkdown(@NonNull String markdown) {
|
||||
return Utils.fixSuperScript(markdown);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
|
||||
if (mActivity.contentTypeface != null) {
|
||||
|
@ -169,12 +169,6 @@ public class CommentsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVi
|
||||
int commentSpoilerBackgroundColor = mCommentTextColor | 0xFF000000;
|
||||
int linkColor = customThemeWrapper.getLinkColor();
|
||||
MarkwonPlugin miscPlugin = new AbstractMarkwonPlugin() {
|
||||
@NonNull
|
||||
@Override
|
||||
public String processMarkdown(@NonNull String markdown) {
|
||||
return Utils.fixSuperScript(markdown);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
|
||||
if (mActivity.contentTypeface != null) {
|
||||
|
@ -28,8 +28,6 @@ import io.noties.markwon.Markwon;
|
||||
import io.noties.markwon.MarkwonConfiguration;
|
||||
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.AutolinkInlineProcessor;
|
||||
import io.noties.markwon.inlineparser.BangInlineProcessor;
|
||||
import io.noties.markwon.inlineparser.HtmlInlineProcessor;
|
||||
@ -47,11 +45,10 @@ import ml.docilealligator.infinityforreddit.events.ChangeInboxCountEvent;
|
||||
import ml.docilealligator.infinityforreddit.markdown.RedditHeadingPlugin;
|
||||
import ml.docilealligator.infinityforreddit.markdown.SpoilerAwareMovementMethod;
|
||||
import ml.docilealligator.infinityforreddit.markdown.SpoilerParserPlugin;
|
||||
import ml.docilealligator.infinityforreddit.markdown.SuperscriptInlineProcessor;
|
||||
import ml.docilealligator.infinityforreddit.markdown.SuperscriptPlugin;
|
||||
import ml.docilealligator.infinityforreddit.message.FetchMessage;
|
||||
import ml.docilealligator.infinityforreddit.message.Message;
|
||||
import ml.docilealligator.infinityforreddit.message.ReadMessage;
|
||||
import ml.docilealligator.infinityforreddit.utils.Utils;
|
||||
import retrofit2.Retrofit;
|
||||
|
||||
public class MessageRecyclerViewAdapter extends PagedListAdapter<Message, RecyclerView.ViewHolder> {
|
||||
@ -109,21 +106,10 @@ public class MessageRecyclerViewAdapter extends PagedListAdapter<Message, Recycl
|
||||
// add tables support and replace with MarkdownUtils#commonPostMarkwonBuilder
|
||||
mMarkwon = Markwon.builder(mActivity)
|
||||
.usePlugin(MarkwonInlineParserPlugin.create(plugin -> {
|
||||
plugin.excludeInlineProcessor(AutolinkInlineProcessor.class);
|
||||
plugin.excludeInlineProcessor(HtmlInlineProcessor.class);
|
||||
plugin.excludeInlineProcessor(BangInlineProcessor.class);
|
||||
plugin.addInlineProcessor(new SuperscriptInlineProcessor());
|
||||
}))
|
||||
.usePlugin(HtmlPlugin.create(plugin -> {
|
||||
plugin.excludeDefaults(true).addHandler(new SuperScriptHandler());
|
||||
}))
|
||||
.usePlugin(new AbstractMarkwonPlugin() {
|
||||
@NonNull
|
||||
@Override
|
||||
public String processMarkdown(@NonNull String markdown) {
|
||||
return Utils.fixSuperScript(markdown);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
|
||||
builder.linkResolver((view, link) -> {
|
||||
@ -139,6 +125,7 @@ public class MessageRecyclerViewAdapter extends PagedListAdapter<Message, Recycl
|
||||
builder.linkColor(customThemeWrapper.getLinkColor());
|
||||
}
|
||||
})
|
||||
.usePlugin(SuperscriptPlugin.create())
|
||||
.usePlugin(SpoilerParserPlugin.create(mSecondaryTextColor, spoilerBackgroundColor))
|
||||
.usePlugin(RedditHeadingPlugin.create())
|
||||
.usePlugin(StrikethroughPlugin.create())
|
||||
|
@ -232,12 +232,6 @@ public class PostDetailRecyclerViewAdapter extends RecyclerView.Adapter<Recycler
|
||||
int postSpoilerBackgroundColor = markdownColor | 0xFF000000;
|
||||
int linkColor = customThemeWrapper.getLinkColor();
|
||||
MarkwonPlugin miscPlugin = new AbstractMarkwonPlugin() {
|
||||
@NonNull
|
||||
@Override
|
||||
public String processMarkdown(@NonNull String markdown) {
|
||||
return Utils.fixSuperScript(markdown);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
|
||||
if (mActivity.contentTypeface != null) {
|
||||
|
@ -32,8 +32,6 @@ import io.noties.markwon.Markwon;
|
||||
import io.noties.markwon.MarkwonConfiguration;
|
||||
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.AutolinkInlineProcessor;
|
||||
import io.noties.markwon.inlineparser.BangInlineProcessor;
|
||||
import io.noties.markwon.inlineparser.HtmlInlineProcessor;
|
||||
@ -49,7 +47,7 @@ import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper;
|
||||
import ml.docilealligator.infinityforreddit.markdown.RedditHeadingPlugin;
|
||||
import ml.docilealligator.infinityforreddit.markdown.SpoilerAwareMovementMethod;
|
||||
import ml.docilealligator.infinityforreddit.markdown.SpoilerParserPlugin;
|
||||
import ml.docilealligator.infinityforreddit.markdown.SuperscriptInlineProcessor;
|
||||
import ml.docilealligator.infinityforreddit.markdown.SuperscriptPlugin;
|
||||
import ml.docilealligator.infinityforreddit.message.Message;
|
||||
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
|
||||
import ml.docilealligator.infinityforreddit.utils.Utils;
|
||||
@ -85,21 +83,10 @@ public class PrivateMessagesDetailRecyclerViewAdapter extends RecyclerView.Adapt
|
||||
// add tables support and replace with MarkdownUtils#commonPostMarkwonBuilder
|
||||
mMarkwon = Markwon.builder(viewPrivateMessagesActivity)
|
||||
.usePlugin(MarkwonInlineParserPlugin.create(plugin -> {
|
||||
plugin.excludeInlineProcessor(AutolinkInlineProcessor.class);
|
||||
plugin.excludeInlineProcessor(HtmlInlineProcessor.class);
|
||||
plugin.excludeInlineProcessor(BangInlineProcessor.class);
|
||||
plugin.addInlineProcessor(new SuperscriptInlineProcessor());
|
||||
}))
|
||||
.usePlugin(HtmlPlugin.create(plugin -> {
|
||||
plugin.excludeDefaults(true).addHandler(new SuperScriptHandler());
|
||||
}))
|
||||
.usePlugin(new AbstractMarkwonPlugin() {
|
||||
@NonNull
|
||||
@Override
|
||||
public String processMarkdown(@NonNull String markdown) {
|
||||
return Utils.fixSuperScript(markdown);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
|
||||
if (mViewPrivateMessagesActivity.contentTypeface != null) {
|
||||
@ -122,6 +109,7 @@ public class PrivateMessagesDetailRecyclerViewAdapter extends RecyclerView.Adapt
|
||||
builder.linkColor(customThemeWrapper.getLinkColor());
|
||||
}
|
||||
})
|
||||
.usePlugin(SuperscriptPlugin.create())
|
||||
.usePlugin(StrikethroughPlugin.create())
|
||||
.usePlugin(SpoilerParserPlugin.create(commentColor, commentColor | 0xFF000000))
|
||||
.usePlugin(RedditHeadingPlugin.create())
|
||||
|
@ -53,12 +53,6 @@ public class RulesRecyclerViewAdapter extends RecyclerView.Adapter<RulesRecycler
|
||||
mPrimaryTextColor = customThemeWrapper.getPrimaryTextColor();
|
||||
int spoilerBackgroundColor = mPrimaryTextColor | 0xFF000000;
|
||||
MarkwonPlugin miscPlugin = new AbstractMarkwonPlugin() {
|
||||
@NonNull
|
||||
@Override
|
||||
public String processMarkdown(@NonNull String markdown) {
|
||||
return Utils.fixSuperScript(markdown);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
|
||||
if (activity.typeface != null) {
|
||||
|
@ -104,12 +104,6 @@ public class SidebarFragment extends Fragment {
|
||||
int spoilerBackgroundColor = markdownColor | 0xFF000000;
|
||||
|
||||
MarkwonPlugin miscPlugin = new AbstractMarkwonPlugin() {
|
||||
@NonNull
|
||||
@Override
|
||||
public String processMarkdown(@NonNull String markdown) {
|
||||
return Utils.fixSuperScript(markdown);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
|
||||
if (activity.contentTypeface != null) {
|
||||
|
@ -11,8 +11,6 @@ import org.commonmark.ext.gfm.tables.TableBlock;
|
||||
import io.noties.markwon.Markwon;
|
||||
import io.noties.markwon.MarkwonPlugin;
|
||||
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.AutolinkInlineProcessor;
|
||||
import io.noties.markwon.inlineparser.BangInlineProcessor;
|
||||
import io.noties.markwon.inlineparser.HtmlInlineProcessor;
|
||||
@ -39,15 +37,11 @@ public class MarkdownUtils {
|
||||
@Nullable BetterLinkMovementMethod.OnLinkLongClickListener onLinkLongClickListener) {
|
||||
return Markwon.builder(context)
|
||||
.usePlugin(MarkwonInlineParserPlugin.create(plugin -> {
|
||||
plugin.excludeInlineProcessor(AutolinkInlineProcessor.class);
|
||||
plugin.excludeInlineProcessor(HtmlInlineProcessor.class);
|
||||
plugin.excludeInlineProcessor(BangInlineProcessor.class);
|
||||
plugin.addInlineProcessor(new SuperscriptInlineProcessor());
|
||||
}))
|
||||
.usePlugin(HtmlPlugin.create(plugin -> {
|
||||
plugin.excludeDefaults(true).addHandler(new SuperScriptHandler());
|
||||
}))
|
||||
.usePlugin(miscPlugin)
|
||||
.usePlugin(SuperscriptPlugin.create())
|
||||
.usePlugin(SpoilerParserPlugin.create(markdownColor, spoilerBackgroundColor))
|
||||
.usePlugin(RedditHeadingPlugin.create())
|
||||
.usePlugin(StrikethroughPlugin.create())
|
||||
@ -68,7 +62,6 @@ public class MarkdownUtils {
|
||||
@Nullable BetterLinkMovementMethod.OnLinkLongClickListener onLinkLongClickListener) {
|
||||
return Markwon.builder(context)
|
||||
.usePlugin(MarkwonInlineParserPlugin.create(plugin -> {
|
||||
plugin.excludeInlineProcessor(AutolinkInlineProcessor.class);
|
||||
plugin.excludeInlineProcessor(HtmlInlineProcessor.class);
|
||||
plugin.excludeInlineProcessor(BangInlineProcessor.class);
|
||||
}))
|
||||
|
@ -0,0 +1,21 @@
|
||||
package ml.docilealligator.infinityforreddit.markdown;
|
||||
|
||||
import org.commonmark.node.CustomNode;
|
||||
import org.commonmark.node.Visitor;
|
||||
|
||||
public class Superscript extends CustomNode {
|
||||
private boolean isBracketed;
|
||||
|
||||
@Override
|
||||
public void accept(Visitor visitor) {
|
||||
visitor.visit(this);
|
||||
}
|
||||
|
||||
public boolean isBracketed() {
|
||||
return isBracketed;
|
||||
}
|
||||
|
||||
public void setBracketed(boolean bracketed) {
|
||||
isBracketed = bracketed;
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package ml.docilealligator.infinityforreddit.markdown;
|
||||
|
||||
import static io.noties.markwon.inlineparser.InlineParserUtils.mergeChildTextNodes;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.commonmark.node.Node;
|
||||
|
||||
import io.noties.markwon.inlineparser.InlineProcessor;
|
||||
|
||||
public class SuperscriptClosingInlineProcessor extends InlineProcessor {
|
||||
@NonNull
|
||||
private final SuperscriptOpeningStorage superscriptOpeningStorage;
|
||||
|
||||
public SuperscriptClosingInlineProcessor(@NonNull SuperscriptOpeningStorage superscriptOpeningStorage) {
|
||||
this.superscriptOpeningStorage = superscriptOpeningStorage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char specialCharacter() {
|
||||
return ')';
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Node parse() {
|
||||
SuperscriptOpeningBracket superscriptOpening = superscriptOpeningStorage.pop(block);
|
||||
if (superscriptOpening == null) {
|
||||
return null;
|
||||
}
|
||||
index++;
|
||||
|
||||
Superscript superscript = new Superscript();
|
||||
superscript.setBracketed(true);
|
||||
Node node = superscriptOpening.node.getNext();
|
||||
while (node != null) {
|
||||
Node next = node.getNext();
|
||||
superscript.appendChild(node);
|
||||
node = next;
|
||||
}
|
||||
|
||||
// Process delimiters such as emphasis inside spoiler
|
||||
processDelimiters(superscriptOpening.previousDelimiter);
|
||||
mergeChildTextNodes(superscript);
|
||||
// We don't need the corresponding text node anymore, we turned it into a spoiler node
|
||||
superscriptOpening.node.unlink();
|
||||
|
||||
return superscript;
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
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("^</?sup>", 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package ml.docilealligator.infinityforreddit.markdown;
|
||||
|
||||
import org.commonmark.node.Node;
|
||||
|
||||
public class SuperscriptOpening {
|
||||
/**
|
||||
* Node that contains non-bracketed superscript opening markdown ({@code ^}).
|
||||
*/
|
||||
public final Node node;
|
||||
|
||||
public final Integer start;
|
||||
|
||||
public SuperscriptOpening(Node node, int start) {
|
||||
this.node = node;
|
||||
this.start = start;
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package ml.docilealligator.infinityforreddit.markdown;
|
||||
|
||||
import org.commonmark.internal.Delimiter;
|
||||
import org.commonmark.node.Node;
|
||||
|
||||
public class SuperscriptOpeningBracket {
|
||||
/**
|
||||
* Node that contains superscript opening bracket markdown ({@code ^(}).
|
||||
*/
|
||||
public final Node node;
|
||||
|
||||
/**
|
||||
* Previous superscript opening bracket.
|
||||
*/
|
||||
public final SuperscriptOpeningBracket previous;
|
||||
|
||||
/**
|
||||
* Previous delimiter (emphasis, etc) before this bracket.
|
||||
*/
|
||||
public final Delimiter previousDelimiter;
|
||||
|
||||
public final Integer start;
|
||||
|
||||
public SuperscriptOpeningBracket(Node node, SuperscriptOpeningBracket previous, Delimiter previousDelimiter) {
|
||||
this.node = node;
|
||||
this.previous = previous;
|
||||
this.previousDelimiter = previousDelimiter;
|
||||
this.start = null;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,50 @@
|
||||
package ml.docilealligator.infinityforreddit.markdown;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.node.Text;
|
||||
|
||||
import io.noties.markwon.inlineparser.InlineProcessor;
|
||||
|
||||
public class SuperscriptOpeningInlineProcessor extends InlineProcessor {
|
||||
@NonNull
|
||||
private final SuperscriptOpeningStorage superscriptOpeningStorage;
|
||||
|
||||
public SuperscriptOpeningInlineProcessor(@NonNull SuperscriptOpeningStorage superscriptOpeningStorage) {
|
||||
this.superscriptOpeningStorage = superscriptOpeningStorage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char specialCharacter() {
|
||||
return '^';
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Node parse() {
|
||||
index++;
|
||||
char c = peek();
|
||||
if (c != '\0' && !Character.isWhitespace(c)) {
|
||||
if (c == '(') {
|
||||
index++;
|
||||
Text node = text("^(");
|
||||
superscriptOpeningStorage.add(block, node, lastDelimiter());
|
||||
return node;
|
||||
}
|
||||
|
||||
if (lastDelimiter() != null && lastDelimiter().canOpen && block.getLastChild() != null) {
|
||||
if (lastDelimiter().node == this.block.getLastChild()) {
|
||||
if (lastDelimiter().delimiterChar == peek()) {
|
||||
index--;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Superscript();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package ml.docilealligator.infinityforreddit.markdown;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.commonmark.internal.Delimiter;
|
||||
import org.commonmark.node.Node;
|
||||
|
||||
public class SuperscriptOpeningStorage {
|
||||
@Nullable
|
||||
private SuperscriptOpeningBracket lastBracket;
|
||||
private Node currentBlock;
|
||||
|
||||
public void clear() {
|
||||
lastBracket = null;
|
||||
}
|
||||
|
||||
public void add(Node block, Node node, Delimiter lastDelimiter) {
|
||||
updateBlock(block);
|
||||
lastBracket = new SuperscriptOpeningBracket(node, lastBracket, lastDelimiter);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public SuperscriptOpeningBracket pop(Node block) {
|
||||
updateBlock(block);
|
||||
SuperscriptOpeningBracket opening = lastBracket;
|
||||
if (opening != null) {
|
||||
lastBracket = opening.previous;
|
||||
}
|
||||
return opening;
|
||||
}
|
||||
|
||||
private void updateBlock(Node block) {
|
||||
if (block != currentBlock) {
|
||||
clear();
|
||||
}
|
||||
currentBlock = block;
|
||||
}
|
||||
}
|
@ -0,0 +1,206 @@
|
||||
package ml.docilealligator.infinityforreddit.markdown;
|
||||
|
||||
import android.text.Spannable;
|
||||
import android.text.Spanned;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.commonmark.ext.gfm.tables.TableCell;
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.node.Paragraph;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import io.noties.markwon.AbstractMarkwonPlugin;
|
||||
import io.noties.markwon.MarkwonSpansFactory;
|
||||
import io.noties.markwon.MarkwonVisitor;
|
||||
import io.noties.markwon.core.spans.CodeSpan;
|
||||
import io.noties.markwon.core.spans.TextViewSpan;
|
||||
import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin;
|
||||
|
||||
public class SuperscriptPlugin extends AbstractMarkwonPlugin {
|
||||
private final SuperscriptOpeningStorage superscriptOpeningBracketStorage;
|
||||
private final List<SuperscriptOpening> superscriptOpeningList;
|
||||
|
||||
SuperscriptPlugin() {
|
||||
this.superscriptOpeningBracketStorage = new SuperscriptOpeningStorage();
|
||||
this.superscriptOpeningList = new ArrayList<>();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static SuperscriptPlugin create() {
|
||||
return new SuperscriptPlugin();
|
||||
}
|
||||
|
||||
private static char peek(int index, CharSequence input) {
|
||||
return index >= 0 && index < input.length() ? input.charAt(index) : '\0';
|
||||
}
|
||||
|
||||
private static List<SpanInfo> getSpans(Spannable spannable, int start, int end) {
|
||||
var spanArray = spannable.getSpans(start, end, Object.class);
|
||||
List<SpanInfo> spanList = new ArrayList<>();
|
||||
for (int i = spanArray.length - 1; i >= 0; i--) {
|
||||
Object span = spanArray[i];
|
||||
int spanStart = spannable.getSpanStart(span);
|
||||
int spanEnd = spannable.getSpanEnd(span);
|
||||
int spanFlags = spannable.getSpanFlags(span);
|
||||
spanList.add(new SpanInfo(span, spanStart, spanEnd, spanFlags));
|
||||
}
|
||||
return spanList;
|
||||
}
|
||||
|
||||
private static SpanInfo matchSuperscriptAtPosition(List<SpanInfo> spans, int value) {
|
||||
for (var span : spans)
|
||||
if (span.what.getClass() == SuperscriptSpan.class && !((SuperscriptSpan) span.what).isBracketed && span.start <= value && value <= span.end)
|
||||
return span;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static SpanInfo matchSpanAtPosition(List<SpanInfo> spans, int value, Object spanClass) {
|
||||
for (var span : spans)
|
||||
if (span.what.getClass() == spanClass && span.start <= value && value <= span.end)
|
||||
return span;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static SpanInfo matchNonTextSpanAtBoundary(List<SpanInfo> spans, int value) {
|
||||
for (var span : spans)
|
||||
if ((span.end == value || span.start == value) && span.what.getClass() != CodeSpan.class && span.what.getClass() != SuperscriptSpan.class && span.what.getClass() != TextViewSpan.class)
|
||||
return span;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(@NonNull Registry registry) {
|
||||
registry.require(MarkwonInlineParserPlugin.class, plugin -> {
|
||||
plugin.factoryBuilder().addInlineProcessor(new SuperscriptOpeningInlineProcessor(superscriptOpeningBracketStorage));
|
||||
plugin.factoryBuilder().addInlineProcessor(new SuperscriptClosingInlineProcessor(superscriptOpeningBracketStorage));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
|
||||
builder.setFactory(Superscript.class, (config, renderProps) -> new SuperscriptSpan(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
|
||||
builder.on(Superscript.class, new MarkwonVisitor.NodeVisitor<>() {
|
||||
int depth = 0;
|
||||
|
||||
@Override
|
||||
public void visit(@NonNull MarkwonVisitor visitor, @NonNull Superscript superscript) {
|
||||
int start = visitor.length();
|
||||
|
||||
if (!superscript.isBracketed()) {
|
||||
visitor.builder().setSpan(new SuperscriptSpan(false), start, start + 1); // Workaround for Table Plugin
|
||||
superscriptOpeningList.add(new SuperscriptOpening(superscript, start));
|
||||
return;
|
||||
}
|
||||
|
||||
depth++;
|
||||
visitor.visitChildren(superscript);
|
||||
depth--;
|
||||
if (depth == 0) {
|
||||
int end = visitor.builder().length();
|
||||
var spans = visitor.builder().getSpans(start, end);
|
||||
for (var span : spans) {
|
||||
if (span.what instanceof CodeSpan) {
|
||||
if (span.end <= end) {
|
||||
visitor.builder().setSpan(new SuperscriptSpan(true), start, span.start);
|
||||
}
|
||||
start = span.end;
|
||||
}
|
||||
}
|
||||
if (start < end) {
|
||||
visitor.setSpansForNode(superscript, start);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterRender(@NonNull Node node, @NonNull MarkwonVisitor visitor) {
|
||||
superscriptOpeningBracketStorage.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
|
||||
if (superscriptOpeningList.size() == 0 || !(markdown instanceof Spannable)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var spannable = (Spannable) markdown;
|
||||
var spans = getSpans(spannable, 0, spannable.length());
|
||||
final String text = spannable.toString();
|
||||
|
||||
outerLoop:
|
||||
for (int i = 0; i < superscriptOpeningList.size(); i++) {
|
||||
SuperscriptOpening opening = superscriptOpeningList.get(i);
|
||||
SuperscriptOpening nextOpening = i + 1 < superscriptOpeningList.size() ? superscriptOpeningList.get(i + 1) : null;
|
||||
|
||||
// Workaround for Table Plugin
|
||||
var superscriptMarker = matchSuperscriptAtPosition(spans, opening.start);
|
||||
if (superscriptMarker == null)
|
||||
return;
|
||||
spannable.removeSpan(superscriptMarker.what);
|
||||
spans.remove(superscriptMarker);
|
||||
|
||||
boolean isNextOpeningOfLocalNode = nextOpening != null && opening.node.getParent().equals(nextOpening.node.getParent());
|
||||
if (opening.start >= text.length() || (matchSpanAtPosition(spans, opening.start, CodeSpan.class) == null && Character.isWhitespace(text.charAt(opening.start))) || (isNextOpeningOfLocalNode && opening.start.equals(nextOpening.start))) {
|
||||
superscriptOpeningList.remove(i);
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean isChildOfDelimited = !(opening.node.getParent() == null || opening.node.getParent() instanceof Paragraph || opening.node.getParent() instanceof TableCell);
|
||||
int openingStart = opening.start;
|
||||
for (int j = opening.start; j <= text.length(); j++) {
|
||||
char currentChar = peek(j, text);
|
||||
SpanInfo codeSpanAtPosition = matchSpanAtPosition(spans, j, CodeSpan.class);
|
||||
SpanInfo nonTextSpanAtBoundary = matchNonTextSpanAtBoundary(spans, j);
|
||||
// When we reach the end position of, for example, an Emphasis
|
||||
// Check whether the superscript originated from inside this Emphasis
|
||||
// If so, stop further spanning of the current Superscript
|
||||
boolean isInsideDelimited = nonTextSpanAtBoundary != null && openingStart != j && j == nonTextSpanAtBoundary.end && (openingStart > nonTextSpanAtBoundary.start || isChildOfDelimited);
|
||||
if (codeSpanAtPosition != null) {
|
||||
if (openingStart < j) {
|
||||
spannable.setSpan(new SuperscriptSpan(false), openingStart, j, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
//Skip to end of CodeSpan
|
||||
j = codeSpanAtPosition.end;
|
||||
currentChar = peek(j, text);
|
||||
if (currentChar == '\0' || Character.isWhitespace(currentChar) || (isNextOpeningOfLocalNode && j == nextOpening.start) || isInsideDelimited) {
|
||||
superscriptOpeningList.remove(i);
|
||||
i--;
|
||||
continue outerLoop;
|
||||
}
|
||||
openingStart = j;
|
||||
} else if (currentChar == '\0' || Character.isWhitespace(currentChar) || isInsideDelimited) {
|
||||
spannable.setSpan(new SuperscriptSpan(false), openingStart, j, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
superscriptOpeningList.remove(i);
|
||||
i--;
|
||||
continue outerLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class SpanInfo {
|
||||
public final Object what;
|
||||
public final int start;
|
||||
public final int end;
|
||||
public final int flags;
|
||||
|
||||
private SpanInfo(Object what, int start, int end, int flags) {
|
||||
this.what = what;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.flags = flags;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package ml.docilealligator.infinityforreddit.markdown;
|
||||
|
||||
import android.text.TextPaint;
|
||||
import android.text.style.MetricAffectingSpan;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class SuperscriptSpan extends MetricAffectingSpan {
|
||||
private static final float SCRIPT_DEF_TEXT_SIZE_RATIO = .75F;
|
||||
public final boolean isBracketed;
|
||||
|
||||
public SuperscriptSpan() {
|
||||
this.isBracketed = false;
|
||||
}
|
||||
|
||||
public SuperscriptSpan(boolean isBracketed) {
|
||||
this.isBracketed = isBracketed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDrawState(TextPaint tp) {
|
||||
apply(tp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMeasureState(@NonNull TextPaint tp) {
|
||||
apply(tp);
|
||||
}
|
||||
|
||||
private void apply(TextPaint paint) {
|
||||
paint.setTextSize(paint.getTextSize() * SCRIPT_DEF_TEXT_SIZE_RATIO);
|
||||
paint.baselineShift += (int) (paint.ascent() / 2);
|
||||
}
|
||||
}
|
@ -83,69 +83,9 @@ public final class Utils {
|
||||
regexed = REGEX_PATTERNS[1].matcher(regexed).replaceAll("[$0](https://www.reddit.com/$0)");
|
||||
regexed = REGEX_PATTERNS[2].matcher(regexed).replaceAll("^");
|
||||
|
||||
//return fixSuperScript(regexed);
|
||||
// We don't want to fix super scripts here because we need the original markdown later for editing posts
|
||||
return regexed;
|
||||
}
|
||||
|
||||
public static String fixSuperScript(String regexedMarkdown) {
|
||||
StringBuilder regexed = new StringBuilder(regexedMarkdown);
|
||||
boolean hasBracket = false;
|
||||
int nCarets = 0;
|
||||
int newLines = 0;
|
||||
for (int i = 0; i < regexed.length(); i++) {
|
||||
char currentChar = regexed.charAt(i);
|
||||
if (hasBracket && currentChar == '\n') {
|
||||
newLines++;
|
||||
if (newLines > 1) {
|
||||
hasBracket = false;
|
||||
nCarets = 0;
|
||||
newLines = 0;
|
||||
}
|
||||
} else if (currentChar == '^') {
|
||||
if (!(i > 0 && regexed.charAt(i - 1) == '\\')) {
|
||||
if (nCarets == 0 && i < regexed.length() - 1 && regexed.charAt(i + 1) == '(') {
|
||||
regexed.replace(i, i + 2, "<sup>");
|
||||
hasBracket = true;
|
||||
} else {
|
||||
regexed.replace(i, i + 1, "<sup>");
|
||||
}
|
||||
nCarets++;
|
||||
}
|
||||
} else if (hasBracket && currentChar == ')') {
|
||||
if (i > 0 && regexed.charAt(i - 1) == '\\') {
|
||||
hasBracket = false;
|
||||
nCarets--;
|
||||
continue;
|
||||
}
|
||||
hasBracket = false;
|
||||
regexed.replace(i, i + 1, "</sup>");
|
||||
nCarets--;
|
||||
} else if (!hasBracket && currentChar == '\n') {
|
||||
for (int j = 0; j < nCarets; j++) {
|
||||
regexed.insert(i, "</sup>");
|
||||
i += 6;
|
||||
}
|
||||
nCarets = 0;
|
||||
} else if (!hasBracket && Character.isWhitespace(currentChar)) {
|
||||
for (int j = 0; j < nCarets; j++) {
|
||||
regexed.insert(i, "</sup>");
|
||||
i += 6;
|
||||
}
|
||||
nCarets = 0;
|
||||
} else {
|
||||
newLines = 0;
|
||||
}
|
||||
}
|
||||
if (!hasBracket) {
|
||||
for (int j = 0; j < nCarets; j++) {
|
||||
regexed.append("</sup>");
|
||||
}
|
||||
}
|
||||
|
||||
return regexed.toString();
|
||||
}
|
||||
|
||||
public static String parseInlineGifInComments(String markdown) {
|
||||
StringBuilder markdownStringBuilder = new StringBuilder(markdown);
|
||||
Pattern inlineGifPattern = REGEX_PATTERNS[3];
|
||||
|
Loading…
x
Reference in New Issue
Block a user