Markdown support

This commit is contained in:
Thomas 2023-08-07 17:03:14 +02:00
parent 6790158ed9
commit e954fd860a
6 changed files with 92 additions and 31 deletions

View file

@ -85,6 +85,8 @@ android {
} }
} }
configurations { configurations {
cleanedAnnotations
implementation.exclude group: 'org.jetbrains', module: 'annotations'
all { all {
exclude group: 'androidx.lifecycle', module: 'lifecycle-viewmodel-ktx' exclude group: 'androidx.lifecycle', module: 'lifecycle-viewmodel-ktx'
} }
@ -186,6 +188,9 @@ dependencies {
implementation 'io.noties.markwon:core:4.6.2' implementation 'io.noties.markwon:core:4.6.2'
implementation 'io.noties.markwon:ext-tables:4.6.2'
implementation 'io.noties.markwon:syntax-highlight:4.6.2'
annotationProcessor 'io.noties:prism4j-bundler:2.0.0'
//************ CAST **************/// //************ CAST **************///

View file

@ -42,7 +42,9 @@ import java.util.Objects;
import app.fedilab.android.mastodon.helper.ThemeHelper; import app.fedilab.android.mastodon.helper.ThemeHelper;
import app.fedilab.android.peertube.services.GlobalUploadObserver; import app.fedilab.android.peertube.services.GlobalUploadObserver;
import es.dmoral.toasty.Toasty; import es.dmoral.toasty.Toasty;
import io.noties.prism4j.annotations.PrismBundle;
@PrismBundle(includeAll = true, grammarLocatorClassName = ".MySuperGrammerLocator")
public class MainApplication extends MultiDexApplication { public class MainApplication extends MultiDexApplication {

View file

@ -37,5 +37,15 @@ public class MarkdownConverter {
public String code; public String code;
public int position; public int position;
public URLSpan urlSpan; public URLSpan urlSpan;
public int regexPosition(List<MarkdownItem> markdownItems) {
int position = 0;
for (MarkdownItem markdownItem : markdownItems) {
if (markdownItem.code.equals(code) && position <= this.position) {
position++;
}
}
return position;
}
} }
} }

View file

@ -43,6 +43,7 @@ import android.view.View;
import android.webkit.URLUtil; import android.webkit.URLUtil;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
@ -71,6 +72,7 @@ import java.util.regex.Pattern;
import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.HttpsURLConnection;
import app.fedilab.android.BaseMainActivity; import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.MySuperGrammerLocator;
import app.fedilab.android.R; import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity; import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.databinding.PopupLinksBinding; import app.fedilab.android.databinding.PopupLinksBinding;
@ -89,6 +91,10 @@ import app.fedilab.android.mastodon.ui.drawer.StatusAdapter;
import app.fedilab.android.mastodon.viewmodel.mastodon.FiltersVM; import app.fedilab.android.mastodon.viewmodel.mastodon.FiltersVM;
import es.dmoral.toasty.Toasty; import es.dmoral.toasty.Toasty;
import io.noties.markwon.Markwon; import io.noties.markwon.Markwon;
import io.noties.markwon.ext.tables.TablePlugin;
import io.noties.markwon.syntax.Prism4jThemeDefault;
import io.noties.markwon.syntax.SyntaxHighlightPlugin;
import io.noties.prism4j.Prism4j;
public class SpannableHelper { public class SpannableHelper {
@ -149,44 +155,56 @@ public class SpannableHelper {
} else { } else {
initialContent = new SpannableString(text); initialContent = new SpannableString(text);
} }
boolean markdownSupport = sharedpreferences.getBoolean(context.getString(R.string.SET_MARKDOWN_SUPPORT), true);
//Get all links //Get all links
MarkdownConverter markdownConverter = new MarkdownConverter(); SpannableStringBuilder content;
markdownConverter.markdownItems = new ArrayList<>(); if (markdownSupport) {
int next; MarkdownConverter markdownConverter = new MarkdownConverter();
int position = 0; markdownConverter.markdownItems = new ArrayList<>();
for (int i = 0; i < initialContent.length(); i = next) { int next;
// find the next span transition int position = 0;
next = initialContent.nextSpanTransition(i, initialContent.length(), URLSpan.class); for (int i = 0; i < initialContent.length(); i = next) {
MarkdownConverter.MarkdownItem markdownItem = new MarkdownConverter.MarkdownItem(); // find the next span transition
markdownItem.code = initialContent.subSequence(i, next).toString(); next = initialContent.nextSpanTransition(i, initialContent.length(), URLSpan.class);
MarkdownConverter.MarkdownItem markdownItem = new MarkdownConverter.MarkdownItem();
markdownItem.code = initialContent.subSequence(i, next).toString();
markdownItem.position = position; markdownItem.position = position;
// get all spans in this range // get all spans in this range
URLSpan[] spans = initialContent.getSpans(i, next, URLSpan.class); URLSpan[] spans = initialContent.getSpans(i, next, URLSpan.class);
if (spans != null && spans.length > 0) { if (spans != null && spans.length > 0) {
markdownItem.urlSpan = spans[0]; markdownItem.urlSpan = spans[0];
}
if (markdownItem.code.trim().length() > 0) {
markdownConverter.markdownItems.add(markdownItem);
position++;
}
} }
if (markdownItem.code.trim().length() > 0) { final Markwon markwon = Markwon.builder(context)
markdownConverter.markdownItems.add(markdownItem); .usePlugin(TablePlugin.create(context))
.usePlugin(SyntaxHighlightPlugin.create(new Prism4j(new MySuperGrammerLocator()), Prism4jThemeDefault.create())).build();
final Spanned markdown = markwon.toMarkdown(initialContent.toString());
content = new SpannableStringBuilder(markdown);
position = 0;
for (MarkdownConverter.MarkdownItem markdownItem : markdownConverter.markdownItems) {
Pattern p = Pattern.compile("(" + Pattern.quote(markdownItem.code) + ")", Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(content);
int fetchPosition = 1;
while (m.find()) {
int regexPosition = markdownItem.regexPosition(markdownConverter.markdownItems);
if (regexPosition == fetchPosition) {
content.setSpan(markdownItem.urlSpan, m.start(), m.end(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
fetchPosition++;
}
position++; position++;
} }
} else {
content = new SpannableStringBuilder(initialContent);
} }
final Markwon markwon = Markwon.create(context);
final Spanned markdown = markwon.toMarkdown(initialContent.toString());
SpannableStringBuilder content = new SpannableStringBuilder(markdown);
position = 0;
for (MarkdownConverter.MarkdownItem markdownItem : markdownConverter.markdownItems) {
Pattern p = Pattern.compile("(" + Pattern.quote(markdownItem.code) + ")", Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(content);
while (m.find()) {
content.setSpan(markdownItem.urlSpan, m.start(), m.end(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
position++;
}
URLSpan[] urls = content.getSpans(0, (content.length() - 1), URLSpan.class); URLSpan[] urls = content.getSpans(0, (content.length() - 1), URLSpan.class);
//Loop through links //Loop through links
@ -336,6 +354,23 @@ public class SpannableHelper {
return trimSpannable(new SpannableStringBuilder(content)); return trimSpannable(new SpannableStringBuilder(content));
} }
public interface Prism4jTheme {
@ColorInt
int background();
@ColorInt
int textColor();
void apply(
@NonNull String language,
@NonNull Prism4j.Syntax syntax,
@NonNull SpannableStringBuilder builder,
int start,
int end
);
}
private static void makeLinks(Context context, SpannableStringBuilder content, String url, int start, int end) { private static void makeLinks(Context context, SpannableStringBuilder content, String url, int start, int end) {
String newUrl = url; String newUrl = url;

View file

@ -1156,6 +1156,7 @@
<string name="SET_GROUP_REBLOGS" translatable="false">SET_GROUP_REBLOGS</string> <string name="SET_GROUP_REBLOGS" translatable="false">SET_GROUP_REBLOGS</string>
<string name="SET_BOOST_ORIGINAL_DATE" translatable="false">SET_BOOST_ORIGINAL_DATE</string> <string name="SET_BOOST_ORIGINAL_DATE" translatable="false">SET_BOOST_ORIGINAL_DATE</string>
<string name="SET_MARKDOWN_SUPPORT" translatable="false">SET_MARKDOWN_SUPPORT</string>
<string name="SET_TRUNCATE_LINKS" translatable="false">SET_TRUNCATE_LINKS</string> <string name="SET_TRUNCATE_LINKS" translatable="false">SET_TRUNCATE_LINKS</string>
<string name="SET_TRUNCATE_LINKS_MAX" translatable="false">SET_TRUNCATE_LINKS_MAX</string> <string name="SET_TRUNCATE_LINKS_MAX" translatable="false">SET_TRUNCATE_LINKS_MAX</string>
@ -1907,6 +1908,7 @@
<string name="also_followed_by">Followed by:</string> <string name="also_followed_by">Followed by:</string>
<string name="Directory">Directory</string> <string name="Directory">Directory</string>
<string name="boost_original_date">Display original date for boosts</string> <string name="boost_original_date">Display original date for boosts</string>
<string name="markdown_support">Markdown support</string>
<string name="set_disable_release_notes">Disable release notes</string> <string name="set_disable_release_notes">Disable release notes</string>
<string name="set_disable_release_notes_indication">When a new version is published, you will not be alerted inside the app.</string> <string name="set_disable_release_notes_indication">When a new version is published, you will not be alerted inside the app.</string>
<string name="formula">Formula</string> <string name="formula">Formula</string>

View file

@ -50,6 +50,13 @@
app:singleLineTitle="false" app:singleLineTitle="false"
app:title="@string/boost_original_date" /> app:title="@string/boost_original_date" />
<SwitchPreferenceCompat
android:defaultValue="true"
app:iconSpaceReserved="false"
app:key="@string/SET_MARKDOWN_SUPPORT"
app:singleLineTitle="false"
app:title="@string/markdown_support" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:defaultValue="true" android:defaultValue="true"
app:iconSpaceReserved="false" app:iconSpaceReserved="false"