mirror of
https://github.com/nextcloud/notes-android.git
synced 2024-11-26 23:27:55 +03:00
parent
82fd1b02b4
commit
43432c9a84
16 changed files with 506 additions and 656 deletions
|
@ -21,12 +21,11 @@ import java.lang.reflect.Method;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil;
|
import it.niedermann.android.markdown.model.EListType;
|
||||||
import it.niedermann.android.markdown.markwon.model.EListType;
|
import it.niedermann.android.markdown.model.SearchSpan;
|
||||||
import it.niedermann.android.markdown.markwon.span.SearchSpan;
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class MarkwonMarkdownUtilTest extends TestCase {
|
public class MarkdownUtilTest extends TestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetStartOfLine() {
|
public void testGetStartOfLine() {
|
||||||
|
@ -42,7 +41,7 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
||||||
);
|
);
|
||||||
|
|
||||||
for (int i = 0; i < test.length(); i++) {
|
for (int i = 0; i < test.length(); i++) {
|
||||||
int startOfLine = MarkwonMarkdownUtil.getStartOfLine(test, i);
|
int startOfLine = MarkdownUtil.getStartOfLine(test, i);
|
||||||
if (i <= 11) {
|
if (i <= 11) {
|
||||||
assertEquals(0, startOfLine);
|
assertEquals(0, startOfLine);
|
||||||
} else if (i <= 12) {
|
} else if (i <= 12) {
|
||||||
|
@ -73,7 +72,7 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
||||||
"\n"; // line 78 - 79
|
"\n"; // line 78 - 79
|
||||||
|
|
||||||
for (int i = 0; i < test.length(); i++) {
|
for (int i = 0; i < test.length(); i++) {
|
||||||
int endOfLine = MarkwonMarkdownUtil.getEndOfLine(test, i);
|
int endOfLine = MarkdownUtil.getEndOfLine(test, i);
|
||||||
if (i <= 11) {
|
if (i <= 11) {
|
||||||
assertEquals(11, endOfLine);
|
assertEquals(11, endOfLine);
|
||||||
} else if (i <= 12) {
|
} else if (i <= 12) {
|
||||||
|
@ -159,7 +158,7 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
||||||
lines.put("*[]", false);
|
lines.put("*[]", false);
|
||||||
lines.put("+[]", false);
|
lines.put("+[]", false);
|
||||||
|
|
||||||
lines.forEach((key, value) -> assertEquals(value, (Boolean) MarkwonMarkdownUtil.lineStartsWithCheckbox(key)));
|
lines.forEach((key, value) -> assertEquals(value, (Boolean) MarkdownUtil.lineStartsWithCheckbox(key)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -168,121 +167,121 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
||||||
|
|
||||||
// Add italic
|
// Add italic
|
||||||
builder = new SpannableStringBuilder("Lorem ipsum dolor sit amet.");
|
builder = new SpannableStringBuilder("Lorem ipsum dolor sit amet.");
|
||||||
assertEquals(13, MarkwonMarkdownUtil.togglePunctuation(builder, 6, 11, "*"));
|
assertEquals(13, MarkdownUtil.togglePunctuation(builder, 6, 11, "*"));
|
||||||
assertEquals("Lorem *ipsum* dolor sit amet.", builder.toString());
|
assertEquals("Lorem *ipsum* dolor sit amet.", builder.toString());
|
||||||
|
|
||||||
// Remove italic
|
// Remove italic
|
||||||
builder = new SpannableStringBuilder("Lorem *ipsum* dolor sit amet.");
|
builder = new SpannableStringBuilder("Lorem *ipsum* dolor sit amet.");
|
||||||
assertEquals(11, MarkwonMarkdownUtil.togglePunctuation(builder, 7, 12, "*"));
|
assertEquals(11, MarkdownUtil.togglePunctuation(builder, 7, 12, "*"));
|
||||||
assertEquals("Lorem ipsum dolor sit amet.", builder.toString());
|
assertEquals("Lorem ipsum dolor sit amet.", builder.toString());
|
||||||
|
|
||||||
// Add bold
|
// Add bold
|
||||||
builder = new SpannableStringBuilder("Lorem ipsum dolor sit amet.");
|
builder = new SpannableStringBuilder("Lorem ipsum dolor sit amet.");
|
||||||
assertEquals(15, MarkwonMarkdownUtil.togglePunctuation(builder, 6, 11, "**"));
|
assertEquals(15, MarkdownUtil.togglePunctuation(builder, 6, 11, "**"));
|
||||||
assertEquals("Lorem **ipsum** dolor sit amet.", builder.toString());
|
assertEquals("Lorem **ipsum** dolor sit amet.", builder.toString());
|
||||||
|
|
||||||
// Remove bold
|
// Remove bold
|
||||||
builder = new SpannableStringBuilder("Lorem **ipsum** dolor sit amet.");
|
builder = new SpannableStringBuilder("Lorem **ipsum** dolor sit amet.");
|
||||||
assertEquals(11, MarkwonMarkdownUtil.togglePunctuation(builder, 8, 13, "**"));
|
assertEquals(11, MarkdownUtil.togglePunctuation(builder, 8, 13, "**"));
|
||||||
assertEquals("Lorem ipsum dolor sit amet.", builder.toString());
|
assertEquals("Lorem ipsum dolor sit amet.", builder.toString());
|
||||||
|
|
||||||
// Add strike
|
// Add strike
|
||||||
builder = new SpannableStringBuilder("Lorem ipsum dolor sit amet.");
|
builder = new SpannableStringBuilder("Lorem ipsum dolor sit amet.");
|
||||||
assertEquals(15, MarkwonMarkdownUtil.togglePunctuation(builder, 6, 11, "~~"));
|
assertEquals(15, MarkdownUtil.togglePunctuation(builder, 6, 11, "~~"));
|
||||||
assertEquals("Lorem ~~ipsum~~ dolor sit amet.", builder.toString());
|
assertEquals("Lorem ~~ipsum~~ dolor sit amet.", builder.toString());
|
||||||
|
|
||||||
// Remove strike
|
// Remove strike
|
||||||
builder = new SpannableStringBuilder("Lorem ~~ipsum~~ dolor sit amet.");
|
builder = new SpannableStringBuilder("Lorem ~~ipsum~~ dolor sit amet.");
|
||||||
assertEquals(11, MarkwonMarkdownUtil.togglePunctuation(builder, 8, 13, "~~"));
|
assertEquals(11, MarkdownUtil.togglePunctuation(builder, 8, 13, "~~"));
|
||||||
assertEquals("Lorem ipsum dolor sit amet.", builder.toString());
|
assertEquals("Lorem ipsum dolor sit amet.", builder.toString());
|
||||||
|
|
||||||
// Add italic at first position
|
// Add italic at first position
|
||||||
builder = new SpannableStringBuilder("Lorem ipsum dolor sit amet.");
|
builder = new SpannableStringBuilder("Lorem ipsum dolor sit amet.");
|
||||||
assertEquals(7, MarkwonMarkdownUtil.togglePunctuation(builder, 0, 5, "*"));
|
assertEquals(7, MarkdownUtil.togglePunctuation(builder, 0, 5, "*"));
|
||||||
assertEquals("*Lorem* ipsum dolor sit amet.", builder.toString());
|
assertEquals("*Lorem* ipsum dolor sit amet.", builder.toString());
|
||||||
|
|
||||||
// Remove italic from first position
|
// Remove italic from first position
|
||||||
builder = new SpannableStringBuilder("*Lorem* ipsum dolor sit amet.");
|
builder = new SpannableStringBuilder("*Lorem* ipsum dolor sit amet.");
|
||||||
assertEquals(5, MarkwonMarkdownUtil.togglePunctuation(builder, 1, 6, "*"));
|
assertEquals(5, MarkdownUtil.togglePunctuation(builder, 1, 6, "*"));
|
||||||
assertEquals("Lorem ipsum dolor sit amet.", builder.toString());
|
assertEquals("Lorem ipsum dolor sit amet.", builder.toString());
|
||||||
|
|
||||||
// Add italic at last position
|
// Add italic at last position
|
||||||
builder = new SpannableStringBuilder("Lorem ipsum dolor sit amet.");
|
builder = new SpannableStringBuilder("Lorem ipsum dolor sit amet.");
|
||||||
assertEquals(29, MarkwonMarkdownUtil.togglePunctuation(builder, 22, 27, "*"));
|
assertEquals(29, MarkdownUtil.togglePunctuation(builder, 22, 27, "*"));
|
||||||
assertEquals("Lorem ipsum dolor sit *amet.*", builder.toString());
|
assertEquals("Lorem ipsum dolor sit *amet.*", builder.toString());
|
||||||
|
|
||||||
// Remove italic from last position
|
// Remove italic from last position
|
||||||
builder = new SpannableStringBuilder("Lorem ipsum dolor sit *amet.*");
|
builder = new SpannableStringBuilder("Lorem ipsum dolor sit *amet.*");
|
||||||
assertEquals(27, MarkwonMarkdownUtil.togglePunctuation(builder, 23, 28, "*"));
|
assertEquals(27, MarkdownUtil.togglePunctuation(builder, 23, 28, "*"));
|
||||||
assertEquals("Lorem ipsum dolor sit amet.", builder.toString());
|
assertEquals("Lorem ipsum dolor sit amet.", builder.toString());
|
||||||
|
|
||||||
// Text is not directly surrounded by punctuation but contains it
|
// Text is not directly surrounded by punctuation but contains it
|
||||||
|
|
||||||
// Do nothing when the same punctuation is contained only one time
|
// Do nothing when the same punctuation is contained only one time
|
||||||
builder = new SpannableStringBuilder("Lorem *ipsum dolor sit amet.");
|
builder = new SpannableStringBuilder("Lorem *ipsum dolor sit amet.");
|
||||||
assertEquals(28, MarkwonMarkdownUtil.togglePunctuation(builder, 0, 28, "*"));
|
assertEquals(28, MarkdownUtil.togglePunctuation(builder, 0, 28, "*"));
|
||||||
assertEquals("Lorem *ipsum dolor sit amet.", builder.toString());
|
assertEquals("Lorem *ipsum dolor sit amet.", builder.toString());
|
||||||
|
|
||||||
// Do nothing when the same punctuation is contained only one time
|
// Do nothing when the same punctuation is contained only one time
|
||||||
builder = new SpannableStringBuilder("Lorem **ipsum dolor sit amet.");
|
builder = new SpannableStringBuilder("Lorem **ipsum dolor sit amet.");
|
||||||
assertEquals(29, MarkwonMarkdownUtil.togglePunctuation(builder, 0, 29, "**"));
|
assertEquals(29, MarkdownUtil.togglePunctuation(builder, 0, 29, "**"));
|
||||||
assertEquals("Lorem **ipsum dolor sit amet.", builder.toString());
|
assertEquals("Lorem **ipsum dolor sit amet.", builder.toString());
|
||||||
|
|
||||||
// Remove containing punctuation
|
// Remove containing punctuation
|
||||||
builder = new SpannableStringBuilder("Lorem *ipsum* dolor sit amet.");
|
builder = new SpannableStringBuilder("Lorem *ipsum* dolor sit amet.");
|
||||||
assertEquals(11, MarkwonMarkdownUtil.togglePunctuation(builder, 6, 13, "*"));
|
assertEquals(11, MarkdownUtil.togglePunctuation(builder, 6, 13, "*"));
|
||||||
assertEquals("Lorem ipsum dolor sit amet.", builder.toString());
|
assertEquals("Lorem ipsum dolor sit amet.", builder.toString());
|
||||||
|
|
||||||
// Remove containing punctuation
|
// Remove containing punctuation
|
||||||
builder = new SpannableStringBuilder("Lorem *ipsum* dolor sit amet.");
|
builder = new SpannableStringBuilder("Lorem *ipsum* dolor sit amet.");
|
||||||
assertEquals(27, MarkwonMarkdownUtil.togglePunctuation(builder, 0, 29, "*"));
|
assertEquals(27, MarkdownUtil.togglePunctuation(builder, 0, 29, "*"));
|
||||||
assertEquals("Lorem ipsum dolor sit amet.", builder.toString());
|
assertEquals("Lorem ipsum dolor sit amet.", builder.toString());
|
||||||
|
|
||||||
// Remove multiple containing punctuations
|
// Remove multiple containing punctuations
|
||||||
builder = new SpannableStringBuilder("Lorem *ipsum* dolor *sit* amet.");
|
builder = new SpannableStringBuilder("Lorem *ipsum* dolor *sit* amet.");
|
||||||
assertEquals(27, MarkwonMarkdownUtil.togglePunctuation(builder, 0, 31, "*"));
|
assertEquals(27, MarkdownUtil.togglePunctuation(builder, 0, 31, "*"));
|
||||||
assertEquals("Lorem ipsum dolor sit amet.", builder.toString());
|
assertEquals("Lorem ipsum dolor sit amet.", builder.toString());
|
||||||
|
|
||||||
// Special use-case: toggle from italic to bold and back
|
// Special use-case: toggle from italic to bold and back
|
||||||
|
|
||||||
// TODO Toggle italic on bold text
|
// TODO Toggle italic on bold text
|
||||||
// builder = new SpannableStringBuilder("Lorem **ipsum** dolor sit amet.");
|
// builder = new SpannableStringBuilder("Lorem **ipsum** dolor sit amet.");
|
||||||
// assertEquals(17, MarkwonMarkdownUtil.togglePunctuation(builder, 8, 13, "*"));
|
// assertEquals(17, MarkdownUtil.togglePunctuation(builder, 8, 13, "*"));
|
||||||
// assertEquals("Lorem ***ipsum*** dolor sit amet.", builder.toString());
|
// assertEquals("Lorem ***ipsum*** dolor sit amet.", builder.toString());
|
||||||
|
|
||||||
// TODO Toggle bold on italic text
|
// TODO Toggle bold on italic text
|
||||||
// builder = new SpannableStringBuilder("Lorem *ipsum* dolor sit amet.");
|
// builder = new SpannableStringBuilder("Lorem *ipsum* dolor sit amet.");
|
||||||
// assertEquals(17, MarkwonMarkdownUtil.togglePunctuation(builder, 7, 12, "**"));
|
// assertEquals(17, MarkdownUtil.togglePunctuation(builder, 7, 12, "**"));
|
||||||
// assertEquals("Lorem ***ipsum*** dolor sit amet.", builder.toString());
|
// assertEquals("Lorem ***ipsum*** dolor sit amet.", builder.toString());
|
||||||
|
|
||||||
// TODO Toggle bold to italic
|
// TODO Toggle bold to italic
|
||||||
// builder = new SpannableStringBuilder("Lorem **ipsum** dolor sit amet.");
|
// builder = new SpannableStringBuilder("Lorem **ipsum** dolor sit amet.");
|
||||||
// assertEquals(33, MarkwonMarkdownUtil.togglePunctuation(builder, 0, 31, "*"));
|
// assertEquals(33, MarkdownUtil.togglePunctuation(builder, 0, 31, "*"));
|
||||||
// assertEquals("Lorem ***ipsum*** dolor sit amet.", builder.toString());
|
// assertEquals("Lorem ***ipsum*** dolor sit amet.", builder.toString());
|
||||||
|
|
||||||
// TODO Toggle multiple bold parts to italic
|
// TODO Toggle multiple bold parts to italic
|
||||||
// builder = new SpannableStringBuilder("Lorem **ipsum** dolor **sit** amet.");
|
// builder = new SpannableStringBuilder("Lorem **ipsum** dolor **sit** amet.");
|
||||||
// assertEquals(38, MarkwonMarkdownUtil.togglePunctuation(builder, 0, 34, "*"));
|
// assertEquals(38, MarkdownUtil.togglePunctuation(builder, 0, 34, "*"));
|
||||||
// assertEquals("Lorem ***ipsum*** dolor ***sit*** amet.", builder.toString());
|
// assertEquals("Lorem ***ipsum*** dolor ***sit*** amet.", builder.toString());
|
||||||
|
|
||||||
// TODO Toggle italic and bold to bold
|
// TODO Toggle italic and bold to bold
|
||||||
// builder = new SpannableStringBuilder("Lorem ***ipsum*** dolor sit amet.");
|
// builder = new SpannableStringBuilder("Lorem ***ipsum*** dolor sit amet.");
|
||||||
// assertEquals(13, MarkwonMarkdownUtil.togglePunctuation(builder, 0, 14, "*"));
|
// assertEquals(13, MarkdownUtil.togglePunctuation(builder, 0, 14, "*"));
|
||||||
// assertEquals("Lorem **ipsum** dolor sit amet.", builder.toString());
|
// assertEquals("Lorem **ipsum** dolor sit amet.", builder.toString());
|
||||||
|
|
||||||
// TODO Toggle italic and bold to italic
|
// TODO Toggle italic and bold to italic
|
||||||
// builder = new SpannableStringBuilder("Lorem ***ipsum*** dolor sit amet.");
|
// builder = new SpannableStringBuilder("Lorem ***ipsum*** dolor sit amet.");
|
||||||
// assertEquals(12, MarkwonMarkdownUtil.togglePunctuation(builder, 9, 14, "**"));
|
// assertEquals(12, MarkdownUtil.togglePunctuation(builder, 9, 14, "**"));
|
||||||
// assertEquals("Lorem *ipsum* dolor sit amet.", builder.toString());
|
// assertEquals("Lorem *ipsum* dolor sit amet.", builder.toString());
|
||||||
|
|
||||||
// TODO Toggle multiple italic and bold to bold
|
// TODO Toggle multiple italic and bold to bold
|
||||||
// builder = new SpannableStringBuilder("Lorem ***ipsum*** dolor ***sit*** amet.");
|
// builder = new SpannableStringBuilder("Lorem ***ipsum*** dolor ***sit*** amet.");
|
||||||
// assertEquals(34, MarkwonMarkdownUtil.togglePunctuation(builder, 0, 38, "*"));
|
// assertEquals(34, MarkdownUtil.togglePunctuation(builder, 0, 38, "*"));
|
||||||
// assertEquals("Lorem **ipsum** dolor **sit** amet.", builder.toString());
|
// assertEquals("Lorem **ipsum** dolor **sit** amet.", builder.toString());
|
||||||
|
|
||||||
// TODO Toggle multiple italic and bold to italic
|
// TODO Toggle multiple italic and bold to italic
|
||||||
// builder = new SpannableStringBuilder("Lorem ***ipsum*** dolor ***sit*** amet.");
|
// builder = new SpannableStringBuilder("Lorem ***ipsum*** dolor ***sit*** amet.");
|
||||||
// assertEquals(30, MarkwonMarkdownUtil.togglePunctuation(builder, 0, 38, "**"));
|
// assertEquals(30, MarkdownUtil.togglePunctuation(builder, 0, 38, "**"));
|
||||||
// assertEquals("Lorem *ipsum* dolor *sit* amet.", builder.toString());
|
// assertEquals("Lorem *ipsum* dolor *sit* amet.", builder.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,59 +291,59 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
||||||
|
|
||||||
// Add link without clipboardUrl to normal text
|
// Add link without clipboardUrl to normal text
|
||||||
builder = new SpannableStringBuilder("Lorem ipsum dolor sit amet.");
|
builder = new SpannableStringBuilder("Lorem ipsum dolor sit amet.");
|
||||||
assertEquals(14, MarkwonMarkdownUtil.insertLink(builder, 6, 11, null));
|
assertEquals(14, MarkdownUtil.insertLink(builder, 6, 11, null));
|
||||||
assertEquals("Lorem [ipsum]() dolor sit amet.", builder.toString());
|
assertEquals("Lorem [ipsum]() dolor sit amet.", builder.toString());
|
||||||
|
|
||||||
// Add link without clipboardUrl to url
|
// Add link without clipboardUrl to url
|
||||||
builder = new SpannableStringBuilder("Lorem https://example.com dolor sit amet.");
|
builder = new SpannableStringBuilder("Lorem https://example.com dolor sit amet.");
|
||||||
assertEquals(7, MarkwonMarkdownUtil.insertLink(builder, 6, 25, null));
|
assertEquals(7, MarkdownUtil.insertLink(builder, 6, 25, null));
|
||||||
assertEquals("Lorem [](https://example.com) dolor sit amet.", builder.toString());
|
assertEquals("Lorem [](https://example.com) dolor sit amet.", builder.toString());
|
||||||
|
|
||||||
// TODO Add link without clipboardUrl to empty selection before space character
|
// TODO Add link without clipboardUrl to empty selection before space character
|
||||||
// builder = new SpannableStringBuilder("Lorem ipsum dolor sit amet.");
|
// builder = new SpannableStringBuilder("Lorem ipsum dolor sit amet.");
|
||||||
// assertEquals(13, MarkwonMarkdownUtil.insertLink(builder, 11, 11, null));
|
// assertEquals(13, MarkdownUtil.insertLink(builder, 11, 11, null));
|
||||||
// assertEquals("Lorem ipsum []() dolor sit amet.", builder.toString());
|
// assertEquals("Lorem ipsum []() dolor sit amet.", builder.toString());
|
||||||
|
|
||||||
// TODO Add link without clipboardUrl to empty selection after space character
|
// TODO Add link without clipboardUrl to empty selection after space character
|
||||||
// builder = new SpannableStringBuilder("Lorem ipsum dolor sit amet.");
|
// builder = new SpannableStringBuilder("Lorem ipsum dolor sit amet.");
|
||||||
// assertEquals(13, MarkwonMarkdownUtil.insertLink(builder, 12, 12, null));
|
// assertEquals(13, MarkdownUtil.insertLink(builder, 12, 12, null));
|
||||||
// assertEquals("Lorem ipsum []() dolor sit amet.", builder.toString());
|
// assertEquals("Lorem ipsum []() dolor sit amet.", builder.toString());
|
||||||
|
|
||||||
// TODO Add link without clipboardUrl to empty selection in word
|
// TODO Add link without clipboardUrl to empty selection in word
|
||||||
// builder = new SpannableStringBuilder("Lorem ipsum dolor sit amet.");
|
// builder = new SpannableStringBuilder("Lorem ipsum dolor sit amet.");
|
||||||
// assertEquals(20, MarkwonMarkdownUtil.insertLink(builder, 14, 14, null));
|
// assertEquals(20, MarkdownUtil.insertLink(builder, 14, 14, null));
|
||||||
// assertEquals("Lorem ipsum [dolor]() sit amet.", builder.toString());
|
// assertEquals("Lorem ipsum [dolor]() sit amet.", builder.toString());
|
||||||
|
|
||||||
// Add link with clipboardUrl to normal text
|
// Add link with clipboardUrl to normal text
|
||||||
builder = new SpannableStringBuilder("Lorem ipsum dolor sit amet.");
|
builder = new SpannableStringBuilder("Lorem ipsum dolor sit amet.");
|
||||||
assertEquals(33, MarkwonMarkdownUtil.insertLink(builder, 6, 11, "https://example.com"));
|
assertEquals(33, MarkdownUtil.insertLink(builder, 6, 11, "https://example.com"));
|
||||||
assertEquals("Lorem [ipsum](https://example.com) dolor sit amet.", builder.toString());
|
assertEquals("Lorem [ipsum](https://example.com) dolor sit amet.", builder.toString());
|
||||||
|
|
||||||
// Add link with clipboardUrl to url
|
// Add link with clipboardUrl to url
|
||||||
builder = new SpannableStringBuilder("Lorem https://example.com dolor sit amet.");
|
builder = new SpannableStringBuilder("Lorem https://example.com dolor sit amet.");
|
||||||
assertEquals(46, MarkwonMarkdownUtil.insertLink(builder, 6, 25, "https://example.de"));
|
assertEquals(46, MarkdownUtil.insertLink(builder, 6, 25, "https://example.de"));
|
||||||
assertEquals("Lorem [https://example.com](https://example.de) dolor sit amet.", builder.toString());
|
assertEquals("Lorem [https://example.com](https://example.de) dolor sit amet.", builder.toString());
|
||||||
|
|
||||||
// TODO Add link with clipboardUrl to empty selection before space character
|
// TODO Add link with clipboardUrl to empty selection before space character
|
||||||
// builder = new SpannableStringBuilder("Lorem ipsum dolor sit amet.");
|
// builder = new SpannableStringBuilder("Lorem ipsum dolor sit amet.");
|
||||||
// assertEquals(13, MarkwonMarkdownUtil.insertLink(builder, 11, 11, "https://example.de"));
|
// assertEquals(13, MarkdownUtil.insertLink(builder, 11, 11, "https://example.de"));
|
||||||
// assertEquals("Lorem ipsum []("https://example.de") dolor sit amet.", builder.toString());
|
// assertEquals("Lorem ipsum []("https://example.de") dolor sit amet.", builder.toString());
|
||||||
|
|
||||||
// TODO Add link with clipboardUrl to empty selection after space character
|
// TODO Add link with clipboardUrl to empty selection after space character
|
||||||
// builder = new SpannableStringBuilder("Lorem ipsum dolor sit amet.");
|
// builder = new SpannableStringBuilder("Lorem ipsum dolor sit amet.");
|
||||||
// assertEquals(13, MarkwonMarkdownUtil.insertLink(builder, 12, 12, "https://example.de"));
|
// assertEquals(13, MarkdownUtil.insertLink(builder, 12, 12, "https://example.de"));
|
||||||
// assertEquals("Lorem ipsum []("https://example.de") dolor sit amet.", builder.toString());
|
// assertEquals("Lorem ipsum []("https://example.de") dolor sit amet.", builder.toString());
|
||||||
|
|
||||||
// TODO Add link with clipboardUrl to empty selection in word
|
// TODO Add link with clipboardUrl to empty selection in word
|
||||||
// builder = new SpannableStringBuilder("Lorem ipsum dolor sit amet.");
|
// builder = new SpannableStringBuilder("Lorem ipsum dolor sit amet.");
|
||||||
// assertEquals(18, MarkwonMarkdownUtil.insertLink(builder, 14, 14, "https://example.de"));
|
// assertEquals(18, MarkdownUtil.insertLink(builder, 14, 14, "https://example.de"));
|
||||||
// assertEquals("Lorem ipsum [dolor]("https://example.de") sit amet.", builder.toString());
|
// assertEquals("Lorem ipsum [dolor]("https://example.de") sit amet.", builder.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRemoveContainingPunctuation() {
|
public void testRemoveContainingPunctuation() {
|
||||||
try {
|
try {
|
||||||
final Method m = MarkwonMarkdownUtil.class.getDeclaredMethod("removeContainingPunctuation", Editable.class, int.class, int.class, String.class);
|
final Method m = MarkdownUtil.class.getDeclaredMethod("removeContainingPunctuation", Editable.class, int.class, int.class, String.class);
|
||||||
m.setAccessible(true);
|
m.setAccessible(true);
|
||||||
Editable builder;
|
Editable builder;
|
||||||
|
|
||||||
|
@ -384,7 +383,7 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
public void testSelectionIsSurroundedByPunctuation() {
|
public void testSelectionIsSurroundedByPunctuation() {
|
||||||
try {
|
try {
|
||||||
final Method m = MarkwonMarkdownUtil.class.getDeclaredMethod("selectionIsSurroundedByPunctuation", CharSequence.class, int.class, int.class, String.class);
|
final Method m = MarkdownUtil.class.getDeclaredMethod("selectionIsSurroundedByPunctuation", CharSequence.class, int.class, int.class, String.class);
|
||||||
m.setAccessible(true);
|
m.setAccessible(true);
|
||||||
assertTrue((Boolean) m.invoke(null, "*Lorem ipsum*", 1, 12, "*"));
|
assertTrue((Boolean) m.invoke(null, "*Lorem ipsum*", 1, 12, "*"));
|
||||||
assertTrue((Boolean) m.invoke(null, "**Lorem ipsum**", 2, 13, "*"));
|
assertTrue((Boolean) m.invoke(null, "**Lorem ipsum**", 2, 13, "*"));
|
||||||
|
@ -402,7 +401,7 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
public void testGetContainedPunctuationCount() {
|
public void testGetContainedPunctuationCount() {
|
||||||
try {
|
try {
|
||||||
final Method m = MarkwonMarkdownUtil.class.getDeclaredMethod("getContainedPunctuationCount", CharSequence.class, int.class, int.class, String.class);
|
final Method m = MarkdownUtil.class.getDeclaredMethod("getContainedPunctuationCount", CharSequence.class, int.class, int.class, String.class);
|
||||||
m.setAccessible(true);
|
m.setAccessible(true);
|
||||||
assertEquals(0, (int) m.invoke(null, "*Lorem ipsum*", 1, 12, "*"));
|
assertEquals(0, (int) m.invoke(null, "*Lorem ipsum*", 1, 12, "*"));
|
||||||
assertEquals(1, (int) m.invoke(null, "*Lorem ipsum*", 1, 13, "*"));
|
assertEquals(1, (int) m.invoke(null, "*Lorem ipsum*", 1, 13, "*"));
|
||||||
|
@ -419,7 +418,7 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
public void testSelectionIsInLink() {
|
public void testSelectionIsInLink() {
|
||||||
try {
|
try {
|
||||||
final Method m = MarkwonMarkdownUtil.class.getDeclaredMethod("selectionIsInLink", CharSequence.class, int.class, int.class);
|
final Method m = MarkdownUtil.class.getDeclaredMethod("selectionIsInLink", CharSequence.class, int.class, int.class);
|
||||||
m.setAccessible(true);
|
m.setAccessible(true);
|
||||||
|
|
||||||
assertTrue((Boolean) m.invoke(null, "Lorem [ipsum](https://example.com) dolor sit amet.", 7, 12));
|
assertTrue((Boolean) m.invoke(null, "Lorem [ipsum](https://example.com) dolor sit amet.", 7, 12));
|
||||||
|
@ -457,35 +456,35 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetListItemIfIsEmpty() {
|
public void testGetListItemIfIsEmpty() {
|
||||||
assertEquals("- ", MarkwonMarkdownUtil.getListItemIfIsEmpty("- "));
|
assertEquals("- ", MarkdownUtil.getListItemIfIsEmpty("- "));
|
||||||
assertEquals("+ ", MarkwonMarkdownUtil.getListItemIfIsEmpty("+ "));
|
assertEquals("+ ", MarkdownUtil.getListItemIfIsEmpty("+ "));
|
||||||
assertEquals("* ", MarkwonMarkdownUtil.getListItemIfIsEmpty("* "));
|
assertEquals("* ", MarkdownUtil.getListItemIfIsEmpty("* "));
|
||||||
assertEquals("1. ", MarkwonMarkdownUtil.getListItemIfIsEmpty("1. "));
|
assertEquals("1. ", MarkdownUtil.getListItemIfIsEmpty("1. "));
|
||||||
|
|
||||||
assertNull(MarkwonMarkdownUtil.getListItemIfIsEmpty("- Test"));
|
assertNull(MarkdownUtil.getListItemIfIsEmpty("- Test"));
|
||||||
assertNull(MarkwonMarkdownUtil.getListItemIfIsEmpty("+ Test"));
|
assertNull(MarkdownUtil.getListItemIfIsEmpty("+ Test"));
|
||||||
assertNull(MarkwonMarkdownUtil.getListItemIfIsEmpty("* Test"));
|
assertNull(MarkdownUtil.getListItemIfIsEmpty("* Test"));
|
||||||
assertNull(MarkwonMarkdownUtil.getListItemIfIsEmpty("1. s"));
|
assertNull(MarkdownUtil.getListItemIfIsEmpty("1. s"));
|
||||||
assertNull(MarkwonMarkdownUtil.getListItemIfIsEmpty("1. "));
|
assertNull(MarkdownUtil.getListItemIfIsEmpty("1. "));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLineStartsWithOrderedList() {
|
public void testLineStartsWithOrderedList() {
|
||||||
assertEquals(1, MarkwonMarkdownUtil.getOrderedListNumber("1. Test"));
|
assertEquals(1, MarkdownUtil.getOrderedListNumber("1. Test"));
|
||||||
assertEquals(2, MarkwonMarkdownUtil.getOrderedListNumber("2. Test"));
|
assertEquals(2, MarkdownUtil.getOrderedListNumber("2. Test"));
|
||||||
assertEquals(3, MarkwonMarkdownUtil.getOrderedListNumber("3. Test"));
|
assertEquals(3, MarkdownUtil.getOrderedListNumber("3. Test"));
|
||||||
assertEquals(10, MarkwonMarkdownUtil.getOrderedListNumber("10. Test"));
|
assertEquals(10, MarkdownUtil.getOrderedListNumber("10. Test"));
|
||||||
assertEquals(11, MarkwonMarkdownUtil.getOrderedListNumber("11. Test"));
|
assertEquals(11, MarkdownUtil.getOrderedListNumber("11. Test"));
|
||||||
assertEquals(12, MarkwonMarkdownUtil.getOrderedListNumber("12. Test"));
|
assertEquals(12, MarkdownUtil.getOrderedListNumber("12. Test"));
|
||||||
assertEquals(1, MarkwonMarkdownUtil.getOrderedListNumber("1. 1"));
|
assertEquals(1, MarkdownUtil.getOrderedListNumber("1. 1"));
|
||||||
assertEquals(1, MarkwonMarkdownUtil.getOrderedListNumber("1. Test 1"));
|
assertEquals(1, MarkdownUtil.getOrderedListNumber("1. Test 1"));
|
||||||
|
|
||||||
assertEquals(-1, MarkwonMarkdownUtil.getOrderedListNumber(""));
|
assertEquals(-1, MarkdownUtil.getOrderedListNumber(""));
|
||||||
assertEquals(-1, MarkwonMarkdownUtil.getOrderedListNumber("1."));
|
assertEquals(-1, MarkdownUtil.getOrderedListNumber("1."));
|
||||||
assertEquals(-1, MarkwonMarkdownUtil.getOrderedListNumber("1. "));
|
assertEquals(-1, MarkdownUtil.getOrderedListNumber("1. "));
|
||||||
assertEquals(-1, MarkwonMarkdownUtil.getOrderedListNumber("11. "));
|
assertEquals(-1, MarkdownUtil.getOrderedListNumber("11. "));
|
||||||
assertEquals(-1, MarkwonMarkdownUtil.getOrderedListNumber("-1. Test"));
|
assertEquals(-1, MarkdownUtil.getOrderedListNumber("-1. Test"));
|
||||||
assertEquals(-1, MarkwonMarkdownUtil.getOrderedListNumber(" 1. Test"));
|
assertEquals(-1, MarkdownUtil.getOrderedListNumber(" 1. Test"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -493,19 +492,19 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
||||||
for (EListType listType : EListType.values()) {
|
for (EListType listType : EListType.values()) {
|
||||||
final String origin_1 = listType.checkboxUnchecked + " Item";
|
final String origin_1 = listType.checkboxUnchecked + " Item";
|
||||||
final String expected_1 = listType.checkboxChecked + " Item";
|
final String expected_1 = listType.checkboxChecked + " Item";
|
||||||
assertEquals(expected_1, MarkwonMarkdownUtil.setCheckboxStatus(origin_1, 0, true));
|
assertEquals(expected_1, MarkdownUtil.setCheckboxStatus(origin_1, 0, true));
|
||||||
|
|
||||||
final String origin_2 = listType.checkboxChecked + " Item";
|
final String origin_2 = listType.checkboxChecked + " Item";
|
||||||
final String expected_2 = listType.checkboxChecked + " Item";
|
final String expected_2 = listType.checkboxChecked + " Item";
|
||||||
assertEquals(expected_2, MarkwonMarkdownUtil.setCheckboxStatus(origin_2, 0, true));
|
assertEquals(expected_2, MarkdownUtil.setCheckboxStatus(origin_2, 0, true));
|
||||||
|
|
||||||
final String origin_3 = listType.checkboxChecked + " Item";
|
final String origin_3 = listType.checkboxChecked + " Item";
|
||||||
final String expected_3 = listType.checkboxChecked + " Item";
|
final String expected_3 = listType.checkboxChecked + " Item";
|
||||||
assertEquals(expected_3, MarkwonMarkdownUtil.setCheckboxStatus(origin_3, -1, true));
|
assertEquals(expected_3, MarkdownUtil.setCheckboxStatus(origin_3, -1, true));
|
||||||
|
|
||||||
final String origin_4 = listType.checkboxChecked + " Item";
|
final String origin_4 = listType.checkboxChecked + " Item";
|
||||||
final String expected_4 = listType.checkboxChecked + " Item";
|
final String expected_4 = listType.checkboxChecked + " Item";
|
||||||
assertEquals(expected_4, MarkwonMarkdownUtil.setCheckboxStatus(origin_4, 3, true));
|
assertEquals(expected_4, MarkdownUtil.setCheckboxStatus(origin_4, 3, true));
|
||||||
|
|
||||||
final String origin_5 = "" +
|
final String origin_5 = "" +
|
||||||
listType.checkboxChecked + " Item\n" +
|
listType.checkboxChecked + " Item\n" +
|
||||||
|
@ -513,7 +512,7 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
||||||
final String expected_5 = "" +
|
final String expected_5 = "" +
|
||||||
listType.checkboxChecked + " Item\n" +
|
listType.checkboxChecked + " Item\n" +
|
||||||
listType.checkboxUnchecked + " Item";
|
listType.checkboxUnchecked + " Item";
|
||||||
assertEquals(expected_5, MarkwonMarkdownUtil.setCheckboxStatus(origin_5, 1, false));
|
assertEquals(expected_5, MarkdownUtil.setCheckboxStatus(origin_5, 1, false));
|
||||||
|
|
||||||
// Checkboxes in fenced code block aren't rendered by Markwon and therefore don't count to the checkbox index
|
// Checkboxes in fenced code block aren't rendered by Markwon and therefore don't count to the checkbox index
|
||||||
final String origin_6 = "" +
|
final String origin_6 = "" +
|
||||||
|
@ -528,7 +527,7 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
||||||
listType.checkboxUnchecked + " Item\n" +
|
listType.checkboxUnchecked + " Item\n" +
|
||||||
"```\n" +
|
"```\n" +
|
||||||
listType.checkboxChecked + " Item";
|
listType.checkboxChecked + " Item";
|
||||||
assertEquals(expected_6, MarkwonMarkdownUtil.setCheckboxStatus(origin_6, 1, true));
|
assertEquals(expected_6, MarkdownUtil.setCheckboxStatus(origin_6, 1, true));
|
||||||
|
|
||||||
// Checkbox in partial nested fenced code block does not count as rendered checkbox
|
// Checkbox in partial nested fenced code block does not count as rendered checkbox
|
||||||
final String origin_7 = "" +
|
final String origin_7 = "" +
|
||||||
|
@ -545,7 +544,7 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
||||||
listType.checkboxUnchecked + " Item\n" +
|
listType.checkboxUnchecked + " Item\n" +
|
||||||
"````\n" +
|
"````\n" +
|
||||||
listType.checkboxChecked + " Item";
|
listType.checkboxChecked + " Item";
|
||||||
assertEquals(expected_7, MarkwonMarkdownUtil.setCheckboxStatus(origin_7, 1, true));
|
assertEquals(expected_7, MarkdownUtil.setCheckboxStatus(origin_7, 1, true));
|
||||||
|
|
||||||
// Checkbox in complete nested fenced code block does not count as rendered checkbox
|
// Checkbox in complete nested fenced code block does not count as rendered checkbox
|
||||||
final String origin_8 = "" +
|
final String origin_8 = "" +
|
||||||
|
@ -564,7 +563,7 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
||||||
"```\n" +
|
"```\n" +
|
||||||
"````\n" +
|
"````\n" +
|
||||||
listType.checkboxChecked + " Item";
|
listType.checkboxChecked + " Item";
|
||||||
assertEquals(expected_8, MarkwonMarkdownUtil.setCheckboxStatus(origin_8, 1, true));
|
assertEquals(expected_8, MarkdownUtil.setCheckboxStatus(origin_8, 1, true));
|
||||||
|
|
||||||
// If checkbox has no content, it doesn't get rendered by Markwon and therefore can not be checked
|
// If checkbox has no content, it doesn't get rendered by Markwon and therefore can not be checked
|
||||||
final String origin_9 = "" +
|
final String origin_9 = "" +
|
||||||
|
@ -585,30 +584,30 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
||||||
"````\n" +
|
"````\n" +
|
||||||
listType.checkboxUnchecked + " \n" +
|
listType.checkboxUnchecked + " \n" +
|
||||||
listType.checkboxChecked + " Item";
|
listType.checkboxChecked + " Item";
|
||||||
assertEquals(expected_9, MarkwonMarkdownUtil.setCheckboxStatus(origin_9, 1, true));
|
assertEquals(expected_9, MarkdownUtil.setCheckboxStatus(origin_9, 1, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRemoveSpans() {
|
public void testRemoveSpans() {
|
||||||
try {
|
try {
|
||||||
final Method removeSpans = MarkwonMarkdownUtil.class.getDeclaredMethod("removeSpans", Spannable.class, Class.class);
|
final Method removeSpans = MarkdownUtil.class.getDeclaredMethod("removeSpans", Spannable.class, Class.class);
|
||||||
removeSpans.setAccessible(true);
|
removeSpans.setAccessible(true);
|
||||||
|
|
||||||
final Context context = ApplicationProvider.getApplicationContext();
|
final Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
final Editable editable_1 = new SpannableStringBuilder("Lorem Ipsum dolor sit amet");
|
final Editable editable_1 = new SpannableStringBuilder("Lorem Ipsum dolor sit amet");
|
||||||
editable_1.setSpan(new SearchSpan(context, Color.RED, false), 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
editable_1.setSpan(new SearchSpan(Color.RED, Color.GRAY, false, false), 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
editable_1.setSpan(new ForegroundColorSpan(Color.BLUE), 6, 11, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
editable_1.setSpan(new ForegroundColorSpan(Color.BLUE), 6, 11, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
editable_1.setSpan(new SearchSpan(context, Color.GREEN, true), 12, 17, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
editable_1.setSpan(new SearchSpan(Color.BLUE, Color.GREEN, true, false), 12, 17, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
removeSpans.invoke(null, editable_1, SearchSpan.class);
|
removeSpans.invoke(null, editable_1, SearchSpan.class);
|
||||||
assertEquals(0, editable_1.getSpans(0, editable_1.length(), SearchSpan.class).length);
|
assertEquals(0, editable_1.getSpans(0, editable_1.length(), SearchSpan.class).length);
|
||||||
assertEquals(1, editable_1.getSpans(0, editable_1.length(), ForegroundColorSpan.class).length);
|
assertEquals(1, editable_1.getSpans(0, editable_1.length(), ForegroundColorSpan.class).length);
|
||||||
|
|
||||||
final Editable editable_2 = new SpannableStringBuilder("Lorem Ipsum dolor sit amet");
|
final Editable editable_2 = new SpannableStringBuilder("Lorem Ipsum dolor sit amet");
|
||||||
editable_2.setSpan(new SearchSpan(context, Color.RED, false), 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
editable_2.setSpan(new SearchSpan(Color.GRAY, Color.RED, false, true), 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
editable_2.setSpan(new ForegroundColorSpan(Color.BLUE), 2, 7, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
editable_2.setSpan(new ForegroundColorSpan(Color.BLUE), 2, 7, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
editable_2.setSpan(new SearchSpan(context, Color.GREEN, true), 3, 9, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
editable_2.setSpan(new SearchSpan(Color.BLUE, Color.GREEN, true, false), 3, 9, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
removeSpans.invoke(null, editable_2, SearchSpan.class);
|
removeSpans.invoke(null, editable_2, SearchSpan.class);
|
||||||
assertEquals(0, editable_2.getSpans(0, editable_2.length(), SearchSpan.class).length);
|
assertEquals(0, editable_2.getSpans(0, editable_2.length(), SearchSpan.class).length);
|
||||||
assertEquals(1, editable_2.getSpans(0, editable_2.length(), ForegroundColorSpan.class).length);
|
assertEquals(1, editable_2.getSpans(0, editable_2.length(), ForegroundColorSpan.class).length);
|
|
@ -1,25 +1,45 @@
|
||||||
package it.niedermann.android.markdown;
|
package it.niedermann.android.markdown;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.Spannable;
|
||||||
|
import android.text.SpannableString;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
import android.widget.RemoteViews.RemoteView;
|
import android.widget.RemoteViews.RemoteView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.ColorInt;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.text.HtmlCompat;
|
import androidx.core.text.HtmlCompat;
|
||||||
|
|
||||||
import com.yydcdut.markdown.MarkdownProcessor;
|
import com.yydcdut.markdown.MarkdownProcessor;
|
||||||
import com.yydcdut.markdown.syntax.text.TextFactory;
|
import com.yydcdut.markdown.syntax.text.TextFactory;
|
||||||
import com.yydcdut.rxmarkdown.RxMarkdown;
|
import com.yydcdut.rxmarkdown.RxMarkdown;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import io.noties.markwon.Markwon;
|
import io.noties.markwon.Markwon;
|
||||||
|
import it.niedermann.android.markdown.model.SearchSpan;
|
||||||
|
import it.niedermann.android.markdown.model.EListType;
|
||||||
|
|
||||||
public class MarkdownUtil {
|
public class MarkdownUtil {
|
||||||
|
|
||||||
|
private static final String TAG = MarkdownUtil.class.getSimpleName();
|
||||||
|
|
||||||
private static final String MD_IMAGE_WITH_EMPTY_DESCRIPTION = "![](";
|
private static final String MD_IMAGE_WITH_EMPTY_DESCRIPTION = "![](";
|
||||||
private static final String MD_IMAGE_WITH_SPACE_DESCRIPTION = "![ ](";
|
private static final String MD_IMAGE_WITH_SPACE_DESCRIPTION = "![ ](";
|
||||||
private static final String[] MD_IMAGE_WITH_EMPTY_DESCRIPTION_ARRAY = new String[]{MD_IMAGE_WITH_EMPTY_DESCRIPTION};
|
private static final String[] MD_IMAGE_WITH_EMPTY_DESCRIPTION_ARRAY = new String[]{MD_IMAGE_WITH_EMPTY_DESCRIPTION};
|
||||||
private static final String[] MD_IMAGE_WITH_SPACE_DESCRIPTION_ARRAY = new String[]{MD_IMAGE_WITH_SPACE_DESCRIPTION};
|
private static final String[] MD_IMAGE_WITH_SPACE_DESCRIPTION_ARRAY = new String[]{MD_IMAGE_WITH_SPACE_DESCRIPTION};
|
||||||
|
|
||||||
|
private static final Pattern PATTERN_CODE_FENCE = Pattern.compile("^(`{3,})");
|
||||||
|
private static final Pattern PATTERN_ORDERED_LIST_ITEM = Pattern.compile("^(\\d+).\\s.+$");
|
||||||
|
private static final Pattern PATTERN_ORDERED_LIST_ITEM_EMPTY = Pattern.compile("^(\\d+).\\s$");
|
||||||
|
private static final Pattern PATTERN_MARKDOWN_LINK = Pattern.compile("\\[(.+)?]\\(([^ ]+?)?( \"(.+)\")?\\)");
|
||||||
|
|
||||||
private MarkdownUtil() {
|
private MarkdownUtil() {
|
||||||
// Util class
|
// Util class
|
||||||
}
|
}
|
||||||
|
@ -58,4 +78,263 @@ public class MarkdownUtil {
|
||||||
|
|
||||||
return markdownProcessor.parse(text);
|
return markdownProcessor.parse(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int getStartOfLine(@NonNull CharSequence s, int cursorPosition) {
|
||||||
|
int startOfLine = cursorPosition;
|
||||||
|
while (startOfLine > 0 && s.charAt(startOfLine - 1) != '\n') {
|
||||||
|
startOfLine--;
|
||||||
|
}
|
||||||
|
return startOfLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getEndOfLine(@NonNull CharSequence s, int cursorPosition) {
|
||||||
|
int nextLinebreak = s.toString().indexOf('\n', cursorPosition);
|
||||||
|
if (nextLinebreak > -1) {
|
||||||
|
return nextLinebreak;
|
||||||
|
}
|
||||||
|
return cursorPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getListItemIfIsEmpty(@NonNull String line) {
|
||||||
|
for (EListType listType : EListType.values()) {
|
||||||
|
if (line.equals(listType.checkboxUncheckedWithTrailingSpace)) {
|
||||||
|
return listType.checkboxUncheckedWithTrailingSpace;
|
||||||
|
} else if (line.equals(listType.listSymbolWithTrailingSpace)) {
|
||||||
|
return listType.listSymbolWithTrailingSpace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final Matcher matcher = PATTERN_ORDERED_LIST_ITEM_EMPTY.matcher(line);
|
||||||
|
if (matcher.find()) {
|
||||||
|
return matcher.group();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CharSequence setCheckboxStatus(@NonNull String markdownString, int targetCheckboxIndex, boolean newCheckedState) {
|
||||||
|
final String[] lines = markdownString.split("\n");
|
||||||
|
int checkboxIndex = 0;
|
||||||
|
boolean isInFencedCodeBlock = false;
|
||||||
|
int fencedCodeBlockSigns = 0;
|
||||||
|
for (int i = 0; i < lines.length; i++) {
|
||||||
|
final Matcher matcher = PATTERN_CODE_FENCE.matcher(lines[i]);
|
||||||
|
if (matcher.find()) {
|
||||||
|
final String fence = matcher.group(1);
|
||||||
|
if (fence != null) {
|
||||||
|
int currentFencedCodeBlockSigns = fence.length();
|
||||||
|
if (isInFencedCodeBlock) {
|
||||||
|
if (currentFencedCodeBlockSigns == fencedCodeBlockSigns) {
|
||||||
|
isInFencedCodeBlock = false;
|
||||||
|
fencedCodeBlockSigns = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isInFencedCodeBlock = true;
|
||||||
|
fencedCodeBlockSigns = currentFencedCodeBlockSigns;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isInFencedCodeBlock) {
|
||||||
|
if (lineStartsWithCheckbox(lines[i]) && lines[i].trim().length() > EListType.DASH.checkboxChecked.length()) {
|
||||||
|
if (checkboxIndex == targetCheckboxIndex) {
|
||||||
|
final int indexOfStartingBracket = lines[i].indexOf("[");
|
||||||
|
final String toggledLine = lines[i].substring(0, indexOfStartingBracket + 1) +
|
||||||
|
(newCheckedState ? 'x' : ' ') +
|
||||||
|
lines[i].substring(indexOfStartingBracket + 2);
|
||||||
|
lines[i] = toggledLine;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
checkboxIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TextUtils.join("\n", lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean lineStartsWithCheckbox(@NonNull String line) {
|
||||||
|
for (EListType listType : EListType.values()) {
|
||||||
|
if (lineStartsWithCheckbox(line, listType)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean lineStartsWithCheckbox(@NonNull String line, @NonNull EListType listType) {
|
||||||
|
final String trimmedLine = line.trim();
|
||||||
|
return (trimmedLine.startsWith(listType.checkboxUnchecked) || trimmedLine.startsWith(listType.checkboxChecked));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the number of the ordered list item if the line is an ordered list, otherwise -1.
|
||||||
|
*/
|
||||||
|
public static int getOrderedListNumber(@NonNull String line) {
|
||||||
|
final Matcher matcher = PATTERN_ORDERED_LIST_ITEM.matcher(line);
|
||||||
|
if (matcher.find()) {
|
||||||
|
final String groupNumber = matcher.group(1);
|
||||||
|
if (groupNumber != null) {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(groupNumber);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modifies the {@param editable} and adds the given {@param punctuation} from
|
||||||
|
* {@param selectionStart} to {@param selectionEnd} or removes the {@param punctuation} in case
|
||||||
|
* it already is around the selected part.
|
||||||
|
*
|
||||||
|
* @return the new cursor position
|
||||||
|
*/
|
||||||
|
public static int togglePunctuation(@NonNull Editable editable, int selectionStart, int selectionEnd, @NonNull String punctuation) {
|
||||||
|
switch (punctuation) {
|
||||||
|
case "**":
|
||||||
|
case "__":
|
||||||
|
case "*":
|
||||||
|
case "_":
|
||||||
|
case "~~": {
|
||||||
|
final boolean selectionIsSurroundedByPunctuation = selectionIsSurroundedByPunctuation(editable, selectionStart, selectionEnd, punctuation);
|
||||||
|
if (selectionIsSurroundedByPunctuation) {
|
||||||
|
editable.delete(selectionEnd, selectionEnd + punctuation.length());
|
||||||
|
editable.delete(selectionStart - punctuation.length(), selectionStart);
|
||||||
|
return selectionEnd - punctuation.length();
|
||||||
|
} else {
|
||||||
|
final int containedPunctuationCount = getContainedPunctuationCount(editable, selectionStart, selectionEnd, punctuation);
|
||||||
|
if (containedPunctuationCount == 0) {
|
||||||
|
editable.insert(selectionEnd, punctuation);
|
||||||
|
editable.insert(selectionStart, punctuation);
|
||||||
|
return selectionEnd + punctuation.length() * 2;
|
||||||
|
} else if (containedPunctuationCount % 2 > 0) {
|
||||||
|
return selectionEnd;
|
||||||
|
} else {
|
||||||
|
removeContainingPunctuation(editable, selectionStart, selectionEnd, punctuation);
|
||||||
|
return selectionEnd - containedPunctuationCount * punctuation.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException("This kind of punctuation is not yet supported: " + punctuation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a link into the given {@param editable} from {@param selectionStart} to {@param selectionEnd} and uses the {@param clipboardUrl} if available.
|
||||||
|
*
|
||||||
|
* @return the new cursor position
|
||||||
|
*/
|
||||||
|
public static int insertLink(@NonNull Editable editable, int selectionStart, int selectionEnd, @Nullable String clipboardUrl) {
|
||||||
|
if (selectionStart == selectionEnd) {
|
||||||
|
editable.insert(selectionStart, "[](" + (clipboardUrl == null ? "" : clipboardUrl) + ")");
|
||||||
|
return selectionStart + 1;
|
||||||
|
} else {
|
||||||
|
final boolean textToFormatIsLink = TextUtils.indexOf(editable.subSequence(selectionStart, selectionEnd), "http") == 0;
|
||||||
|
if (textToFormatIsLink) {
|
||||||
|
if (clipboardUrl == null) {
|
||||||
|
editable.insert(selectionEnd, ")");
|
||||||
|
editable.insert(selectionStart, "[](");
|
||||||
|
} else {
|
||||||
|
editable.insert(selectionEnd, "](" + clipboardUrl + ")");
|
||||||
|
editable.insert(selectionStart, "[");
|
||||||
|
selectionEnd += clipboardUrl.length();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (clipboardUrl == null) {
|
||||||
|
editable.insert(selectionEnd, "]()");
|
||||||
|
} else {
|
||||||
|
editable.insert(selectionEnd, "](" + clipboardUrl + ")");
|
||||||
|
selectionEnd += clipboardUrl.length();
|
||||||
|
}
|
||||||
|
editable.insert(selectionStart, "[");
|
||||||
|
}
|
||||||
|
return textToFormatIsLink && clipboardUrl == null
|
||||||
|
? selectionStart + 1
|
||||||
|
: selectionEnd + 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether or not the selection of {@param text} from {@param start} to {@param end} is
|
||||||
|
* surrounded or not by the given {@param punctuation}.
|
||||||
|
*/
|
||||||
|
private static boolean selectionIsSurroundedByPunctuation(@NonNull CharSequence text, int start, int end, @NonNull String punctuation) {
|
||||||
|
if (text.length() < end + punctuation.length()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (start - punctuation.length() < 0 || end + punctuation.length() > text.length()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return punctuation.contentEquals(text.subSequence(start - punctuation.length(), start))
|
||||||
|
&& punctuation.contentEquals(text.subSequence(end, end + punctuation.length()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getContainedPunctuationCount(@NonNull CharSequence text, int start, int end, @NonNull String punctuation) {
|
||||||
|
final Matcher matcher = Pattern.compile(Pattern.quote(punctuation)).matcher(text.subSequence(start, end));
|
||||||
|
int counter = 0;
|
||||||
|
while (matcher.find()) {
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
return counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void removeContainingPunctuation(@NonNull Editable editable, int start, int end, @NonNull String punctuation) {
|
||||||
|
final Matcher matcher = Pattern.compile(Pattern.quote(punctuation)).matcher(editable.subSequence(start, end));
|
||||||
|
int countDeletedPunctuations = 0;
|
||||||
|
while (matcher.find()) {
|
||||||
|
editable.delete(start + matcher.start() - countDeletedPunctuations * punctuation.length(), start + matcher.end() - countDeletedPunctuations * punctuation.length());
|
||||||
|
countDeletedPunctuations++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean selectionIsInLink(@NonNull CharSequence text, int start, int end) {
|
||||||
|
final Matcher matcher = PATTERN_MARKDOWN_LINK.matcher(text);
|
||||||
|
while (matcher.find()) {
|
||||||
|
if ((start >= matcher.start() && start < matcher.end()) || (end > matcher.start() && end <= matcher.end())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void searchAndColor(@NonNull Spannable editable, @Nullable CharSequence searchText,@Nullable Integer current, @ColorInt int mainColor, @ColorInt int highlightColor, boolean darkTheme) {
|
||||||
|
if (searchText != null) {
|
||||||
|
final Matcher m = Pattern
|
||||||
|
.compile(searchText.toString(), Pattern.CASE_INSENSITIVE | Pattern.LITERAL)
|
||||||
|
.matcher(editable);
|
||||||
|
|
||||||
|
int i = 1;
|
||||||
|
while (m.find()) {
|
||||||
|
int start = m.start();
|
||||||
|
int end = m.end();
|
||||||
|
editable.setSpan(new SearchSpan(mainColor, highlightColor, (current != null && i == current), darkTheme), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all spans of {@param spanType} from {@param spannable}.
|
||||||
|
*/
|
||||||
|
public static <T> void removeSpans(@NonNull Spannable spannable, @SuppressWarnings("SameParameterValue") Class<T> spanType) {
|
||||||
|
for (T span : spannable.getSpans(0, spannable.length(), spanType)) {
|
||||||
|
spannable.removeSpan(span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return When the content of the {@param textView} is already of type {@link Spannable}, it will cast and return it directly.
|
||||||
|
* Otherwise it will create a new {@link SpannableString} from the content, set this as new content of the {@param textView} and return it.
|
||||||
|
*/
|
||||||
|
public static Spannable getContentAsSpannable(@NonNull TextView textView) {
|
||||||
|
final CharSequence content = textView.getText();
|
||||||
|
if (content.getClass() == SpannableString.class || content instanceof Spannable) {
|
||||||
|
return (Spannable) content;
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Expected " + TextView.class.getSimpleName() + " content to be of type " + Spannable.class.getSimpleName() + ", but was of type " + content.getClass() + ". Search highlighting will be not performant.");
|
||||||
|
final Spannable spannableContent = new SpannableString(content);
|
||||||
|
textView.setText(spannableContent, TextView.BufferType.SPANNABLE);
|
||||||
|
return spannableContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,10 @@ import io.noties.markwon.Markwon;
|
||||||
import io.noties.markwon.editor.MarkwonEditor;
|
import io.noties.markwon.editor.MarkwonEditor;
|
||||||
import io.noties.markwon.editor.handler.EmphasisEditHandler;
|
import io.noties.markwon.editor.handler.EmphasisEditHandler;
|
||||||
import io.noties.markwon.editor.handler.StrongEmphasisEditHandler;
|
import io.noties.markwon.editor.handler.StrongEmphasisEditHandler;
|
||||||
|
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin;
|
||||||
|
import io.noties.markwon.image.ImagesPlugin;
|
||||||
|
import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin;
|
||||||
|
import io.noties.markwon.simple.ext.SimpleExtPlugin;
|
||||||
import it.niedermann.android.markdown.MarkdownEditor;
|
import it.niedermann.android.markdown.MarkdownEditor;
|
||||||
import it.niedermann.android.markdown.markwon.format.ContextBasedFormattingCallback;
|
import it.niedermann.android.markdown.markwon.format.ContextBasedFormattingCallback;
|
||||||
import it.niedermann.android.markdown.markwon.format.ContextBasedRangeFormattingCallback;
|
import it.niedermann.android.markdown.markwon.format.ContextBasedRangeFormattingCallback;
|
||||||
|
@ -26,6 +30,8 @@ import it.niedermann.android.markdown.markwon.handler.CodeBlockEditHandler;
|
||||||
import it.niedermann.android.markdown.markwon.handler.CodeEditHandler;
|
import it.niedermann.android.markdown.markwon.handler.CodeEditHandler;
|
||||||
import it.niedermann.android.markdown.markwon.handler.HeadingEditHandler;
|
import it.niedermann.android.markdown.markwon.handler.HeadingEditHandler;
|
||||||
import it.niedermann.android.markdown.markwon.handler.StrikethroughEditHandler;
|
import it.niedermann.android.markdown.markwon.handler.StrikethroughEditHandler;
|
||||||
|
import it.niedermann.android.markdown.markwon.plugins.SearchHighlightPlugin;
|
||||||
|
import it.niedermann.android.markdown.markwon.plugins.ThemePlugin;
|
||||||
import it.niedermann.android.markdown.markwon.textwatcher.CombinedTextWatcher;
|
import it.niedermann.android.markdown.markwon.textwatcher.CombinedTextWatcher;
|
||||||
import it.niedermann.android.markdown.markwon.textwatcher.SearchHighlightTextWatcher;
|
import it.niedermann.android.markdown.markwon.textwatcher.SearchHighlightTextWatcher;
|
||||||
|
|
||||||
|
@ -46,16 +52,10 @@ public class MarkwonMarkdownEditor extends AppCompatEditText implements Markdown
|
||||||
|
|
||||||
public MarkwonMarkdownEditor(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
public MarkwonMarkdownEditor(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||||
super(context, attrs, defStyleAttr);
|
super(context, attrs, defStyleAttr);
|
||||||
final Markwon markwon = MarkwonMarkdownUtil.initMarkwonEditor(context).build();
|
|
||||||
final MarkwonEditor editor = MarkwonEditor.builder(markwon)
|
final Markwon markwon = createMarkwonBuilder(context).build();
|
||||||
.useEditHandler(new EmphasisEditHandler())
|
final MarkwonEditor editor = createMarkwonEditorBuilder(markwon).build();
|
||||||
.useEditHandler(new StrongEmphasisEditHandler())
|
|
||||||
.useEditHandler(new StrikethroughEditHandler())
|
|
||||||
.useEditHandler(new CodeEditHandler())
|
|
||||||
.useEditHandler(new CodeBlockEditHandler())
|
|
||||||
.useEditHandler(new BlockQuoteEditHandler())
|
|
||||||
.useEditHandler(new HeadingEditHandler())
|
|
||||||
.build();
|
|
||||||
combinedWatcher = new CombinedTextWatcher(editor, this);
|
combinedWatcher = new CombinedTextWatcher(editor, this);
|
||||||
addTextChangedListener(combinedWatcher);
|
addTextChangedListener(combinedWatcher);
|
||||||
setCustomSelectionActionModeCallback(new ContextBasedRangeFormattingCallback(this));
|
setCustomSelectionActionModeCallback(new ContextBasedRangeFormattingCallback(this));
|
||||||
|
@ -64,6 +64,27 @@ public class MarkwonMarkdownEditor extends AppCompatEditText implements Markdown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Markwon.Builder createMarkwonBuilder(@NonNull Context context) {
|
||||||
|
return Markwon.builder(context)
|
||||||
|
.usePlugin(ThemePlugin.create(context))
|
||||||
|
.usePlugin(StrikethroughPlugin.create())
|
||||||
|
.usePlugin(SimpleExtPlugin.create())
|
||||||
|
.usePlugin(ImagesPlugin.create())
|
||||||
|
.usePlugin(MarkwonInlineParserPlugin.create())
|
||||||
|
.usePlugin(SearchHighlightPlugin.create(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MarkwonEditor.Builder createMarkwonEditorBuilder(@NonNull Markwon markwon) {
|
||||||
|
return MarkwonEditor.builder(markwon)
|
||||||
|
.useEditHandler(new EmphasisEditHandler())
|
||||||
|
.useEditHandler(new StrongEmphasisEditHandler())
|
||||||
|
.useEditHandler(new StrikethroughEditHandler())
|
||||||
|
.useEditHandler(new CodeEditHandler())
|
||||||
|
.useEditHandler(new CodeBlockEditHandler())
|
||||||
|
.useEditHandler(new BlockQuoteEditHandler())
|
||||||
|
.useEditHandler(new HeadingEditHandler());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSearchColor(@ColorInt int color) {
|
public void setSearchColor(@ColorInt int color) {
|
||||||
final SearchHighlightTextWatcher searchHighlightTextWatcher = combinedWatcher.get(SearchHighlightTextWatcher.class);
|
final SearchHighlightTextWatcher searchHighlightTextWatcher = combinedWatcher.get(SearchHighlightTextWatcher.class);
|
||||||
|
|
|
@ -2,344 +2,17 @@ package it.niedermann.android.markdown.markwon;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.Spannable;
|
|
||||||
import android.text.SpannableString;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.annotation.ColorInt;
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.RestrictTo;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import io.noties.markwon.Markwon;
|
|
||||||
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin;
|
|
||||||
import io.noties.markwon.ext.tables.TablePlugin;
|
|
||||||
import io.noties.markwon.ext.tasklist.TaskListPlugin;
|
|
||||||
import io.noties.markwon.image.ImagesPlugin;
|
|
||||||
import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin;
|
|
||||||
import io.noties.markwon.linkify.LinkifyPlugin;
|
|
||||||
import io.noties.markwon.simple.ext.SimpleExtPlugin;
|
|
||||||
import io.noties.markwon.syntax.Prism4jTheme;
|
|
||||||
import io.noties.markwon.syntax.Prism4jThemeDarkula;
|
|
||||||
import io.noties.markwon.syntax.Prism4jThemeDefault;
|
|
||||||
import io.noties.markwon.syntax.SyntaxHighlightPlugin;
|
|
||||||
import io.noties.prism4j.Prism4j;
|
|
||||||
import io.noties.prism4j.annotations.PrismBundle;
|
|
||||||
import it.niedermann.android.markdown.markwon.model.EListType;
|
|
||||||
import it.niedermann.android.markdown.markwon.plugins.LinkClickInterceptorPlugin;
|
|
||||||
import it.niedermann.android.markdown.markwon.plugins.NextcloudMentionsPlugin;
|
|
||||||
import it.niedermann.android.markdown.markwon.plugins.SearchHighlightPlugin;
|
|
||||||
import it.niedermann.android.markdown.markwon.plugins.ThemePlugin;
|
|
||||||
import it.niedermann.android.markdown.markwon.span.SearchSpan;
|
|
||||||
|
|
||||||
@RestrictTo(value = RestrictTo.Scope.LIBRARY)
|
|
||||||
@PrismBundle(includeAll = true, grammarLocatorClassName = ".MarkwonGrammarLocator")
|
|
||||||
public class MarkwonMarkdownUtil {
|
public class MarkwonMarkdownUtil {
|
||||||
|
|
||||||
private static final String TAG = MarkwonMarkdownUtil.class.getSimpleName();
|
|
||||||
private static final Pattern PATTERN_CODE_FENCE = Pattern.compile("^(`{3,})");
|
|
||||||
private static final Pattern PATTERN_ORDERED_LIST_ITEM = Pattern.compile("^(\\d+).\\s.+$");
|
|
||||||
private static final Pattern PATTERN_ORDERED_LIST_ITEM_EMPTY = Pattern.compile("^(\\d+).\\s$");
|
|
||||||
private static final Pattern PATTERN_MARKDOWN_LINK = Pattern.compile("\\[(.+)?]\\(([^ ]+?)?( \"(.+)\")?\\)");
|
|
||||||
|
|
||||||
private MarkwonMarkdownUtil() {
|
private MarkwonMarkdownUtil() {
|
||||||
// Util class
|
// Util class
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Markwon.Builder initMarkwonEditor(@NonNull Context context) {
|
public static boolean isDarkThemeActive(@NonNull Context context) {
|
||||||
return Markwon.builder(context)
|
|
||||||
.usePlugin(ThemePlugin.create(context))
|
|
||||||
.usePlugin(StrikethroughPlugin.create())
|
|
||||||
.usePlugin(SimpleExtPlugin.create())
|
|
||||||
.usePlugin(ImagesPlugin.create())
|
|
||||||
.usePlugin(MarkwonInlineParserPlugin.create())
|
|
||||||
.usePlugin(SearchHighlightPlugin.create(context));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Markwon.Builder initMarkwonViewer(@NonNull Context context) {
|
|
||||||
final Prism4j prism4j = new Prism4j(new MarkwonGrammarLocator());
|
|
||||||
final Prism4jTheme prism4jTheme = isDarkThemeActive(context)
|
|
||||||
? Prism4jThemeDarkula.create()
|
|
||||||
: Prism4jThemeDefault.create();
|
|
||||||
return initMarkwonEditor(context)
|
|
||||||
.usePlugin(TablePlugin.create(context))
|
|
||||||
.usePlugin(TaskListPlugin.create(context))
|
|
||||||
.usePlugin(LinkifyPlugin.create(true))
|
|
||||||
.usePlugin(LinkClickInterceptorPlugin.create())
|
|
||||||
.usePlugin(ImagesPlugin.create())
|
|
||||||
.usePlugin(SyntaxHighlightPlugin.create(prism4j, prism4jTheme));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Markwon.Builder initMarkwonViewer(@NonNull Context context, @NonNull Map<String, String> mentions) {
|
|
||||||
return initMarkwonViewer(context)
|
|
||||||
.usePlugin(NextcloudMentionsPlugin.create(context, mentions));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isDarkThemeActive(@NonNull Context context) {
|
|
||||||
final int uiMode = context.getResources().getConfiguration().uiMode;
|
final int uiMode = context.getResources().getConfiguration().uiMode;
|
||||||
return (uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
|
return (uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getStartOfLine(@NonNull CharSequence s, int cursorPosition) {
|
|
||||||
int startOfLine = cursorPosition;
|
|
||||||
while (startOfLine > 0 && s.charAt(startOfLine - 1) != '\n') {
|
|
||||||
startOfLine--;
|
|
||||||
}
|
|
||||||
return startOfLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getEndOfLine(@NonNull CharSequence s, int cursorPosition) {
|
|
||||||
int nextLinebreak = s.toString().indexOf('\n', cursorPosition);
|
|
||||||
if (nextLinebreak > -1) {
|
|
||||||
return nextLinebreak;
|
|
||||||
}
|
|
||||||
return cursorPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getListItemIfIsEmpty(@NonNull String line) {
|
|
||||||
for (EListType listType : EListType.values()) {
|
|
||||||
if (line.equals(listType.checkboxUncheckedWithTrailingSpace)) {
|
|
||||||
return listType.checkboxUncheckedWithTrailingSpace;
|
|
||||||
} else if (line.equals(listType.listSymbolWithTrailingSpace)) {
|
|
||||||
return listType.listSymbolWithTrailingSpace;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final Matcher matcher = PATTERN_ORDERED_LIST_ITEM_EMPTY.matcher(line);
|
|
||||||
if (matcher.find()) {
|
|
||||||
return matcher.group();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CharSequence setCheckboxStatus(@NonNull String markdownString, int targetCheckboxIndex, boolean newCheckedState) {
|
|
||||||
final String[] lines = markdownString.split("\n");
|
|
||||||
int checkboxIndex = 0;
|
|
||||||
boolean isInFencedCodeBlock = false;
|
|
||||||
int fencedCodeBlockSigns = 0;
|
|
||||||
for (int i = 0; i < lines.length; i++) {
|
|
||||||
final Matcher matcher = PATTERN_CODE_FENCE.matcher(lines[i]);
|
|
||||||
if (matcher.find()) {
|
|
||||||
final String fence = matcher.group(1);
|
|
||||||
if (fence != null) {
|
|
||||||
int currentFencedCodeBlockSigns = fence.length();
|
|
||||||
if (isInFencedCodeBlock) {
|
|
||||||
if (currentFencedCodeBlockSigns == fencedCodeBlockSigns) {
|
|
||||||
isInFencedCodeBlock = false;
|
|
||||||
fencedCodeBlockSigns = 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
isInFencedCodeBlock = true;
|
|
||||||
fencedCodeBlockSigns = currentFencedCodeBlockSigns;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!isInFencedCodeBlock) {
|
|
||||||
if (lineStartsWithCheckbox(lines[i]) && lines[i].trim().length() > EListType.DASH.checkboxChecked.length()) {
|
|
||||||
if (checkboxIndex == targetCheckboxIndex) {
|
|
||||||
final int indexOfStartingBracket = lines[i].indexOf("[");
|
|
||||||
final String toggledLine = lines[i].substring(0, indexOfStartingBracket + 1) +
|
|
||||||
(newCheckedState ? 'x' : ' ') +
|
|
||||||
lines[i].substring(indexOfStartingBracket + 2);
|
|
||||||
lines[i] = toggledLine;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
checkboxIndex++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return TextUtils.join("\n", lines);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean lineStartsWithCheckbox(@NonNull String line) {
|
|
||||||
for (EListType listType : EListType.values()) {
|
|
||||||
if (lineStartsWithCheckbox(line, listType)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean lineStartsWithCheckbox(@NonNull String line, @NonNull EListType listType) {
|
|
||||||
final String trimmedLine = line.trim();
|
|
||||||
return (trimmedLine.startsWith(listType.checkboxUnchecked) || trimmedLine.startsWith(listType.checkboxChecked));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the number of the ordered list item if the line is an ordered list, otherwise -1.
|
|
||||||
*/
|
|
||||||
public static int getOrderedListNumber(@NonNull String line) {
|
|
||||||
final Matcher matcher = PATTERN_ORDERED_LIST_ITEM.matcher(line);
|
|
||||||
if (matcher.find()) {
|
|
||||||
final String groupNumber = matcher.group(1);
|
|
||||||
if (groupNumber != null) {
|
|
||||||
try {
|
|
||||||
return Integer.parseInt(groupNumber);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modifies the {@param editable} and adds the given {@param punctuation} from
|
|
||||||
* {@param selectionStart} to {@param selectionEnd} or removes the {@param punctuation} in case
|
|
||||||
* it already is around the selected part.
|
|
||||||
*
|
|
||||||
* @return the new cursor position
|
|
||||||
*/
|
|
||||||
public static int togglePunctuation(@NonNull Editable editable, int selectionStart, int selectionEnd, @NonNull String punctuation) {
|
|
||||||
switch (punctuation) {
|
|
||||||
case "**":
|
|
||||||
case "__":
|
|
||||||
case "*":
|
|
||||||
case "_":
|
|
||||||
case "~~": {
|
|
||||||
final boolean selectionIsSurroundedByPunctuation = selectionIsSurroundedByPunctuation(editable, selectionStart, selectionEnd, punctuation);
|
|
||||||
if (selectionIsSurroundedByPunctuation) {
|
|
||||||
editable.delete(selectionEnd, selectionEnd + punctuation.length());
|
|
||||||
editable.delete(selectionStart - punctuation.length(), selectionStart);
|
|
||||||
return selectionEnd - punctuation.length();
|
|
||||||
} else {
|
|
||||||
final int containedPunctuationCount = getContainedPunctuationCount(editable, selectionStart, selectionEnd, punctuation);
|
|
||||||
if (containedPunctuationCount == 0) {
|
|
||||||
editable.insert(selectionEnd, punctuation);
|
|
||||||
editable.insert(selectionStart, punctuation);
|
|
||||||
return selectionEnd + punctuation.length() * 2;
|
|
||||||
} else if (containedPunctuationCount % 2 > 0) {
|
|
||||||
return selectionEnd;
|
|
||||||
} else {
|
|
||||||
removeContainingPunctuation(editable, selectionStart, selectionEnd, punctuation);
|
|
||||||
return selectionEnd - containedPunctuationCount * punctuation.length();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new UnsupportedOperationException("This kind of punctuation is not yet supported: " + punctuation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts a link into the given {@param editable} from {@param selectionStart} to {@param selectionEnd} and uses the {@param clipboardUrl} if available.
|
|
||||||
*
|
|
||||||
* @return the new cursor position
|
|
||||||
*/
|
|
||||||
public static int insertLink(@NonNull Editable editable, int selectionStart, int selectionEnd, @Nullable String clipboardUrl) {
|
|
||||||
if (selectionStart == selectionEnd) {
|
|
||||||
editable.insert(selectionStart, "[](" + (clipboardUrl == null ? "" : clipboardUrl) + ")");
|
|
||||||
return selectionStart + 1;
|
|
||||||
} else {
|
|
||||||
final boolean textToFormatIsLink = TextUtils.indexOf(editable.subSequence(selectionStart, selectionEnd), "http") == 0;
|
|
||||||
if (textToFormatIsLink) {
|
|
||||||
if (clipboardUrl == null) {
|
|
||||||
editable.insert(selectionEnd, ")");
|
|
||||||
editable.insert(selectionStart, "[](");
|
|
||||||
} else {
|
|
||||||
editable.insert(selectionEnd, "](" + clipboardUrl + ")");
|
|
||||||
editable.insert(selectionStart, "[");
|
|
||||||
selectionEnd += clipboardUrl.length();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (clipboardUrl == null) {
|
|
||||||
editable.insert(selectionEnd, "]()");
|
|
||||||
} else {
|
|
||||||
editable.insert(selectionEnd, "](" + clipboardUrl + ")");
|
|
||||||
selectionEnd += clipboardUrl.length();
|
|
||||||
}
|
|
||||||
editable.insert(selectionStart, "[");
|
|
||||||
}
|
|
||||||
return textToFormatIsLink && clipboardUrl == null
|
|
||||||
? selectionStart + 1
|
|
||||||
: selectionEnd + 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return whether or not the selection of {@param text} from {@param start} to {@param end} is
|
|
||||||
* surrounded or not by the given {@param punctuation}.
|
|
||||||
*/
|
|
||||||
private static boolean selectionIsSurroundedByPunctuation(@NonNull CharSequence text, int start, int end, @NonNull String punctuation) {
|
|
||||||
if (text.length() < end + punctuation.length()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (start - punctuation.length() < 0 || end + punctuation.length() > text.length()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return punctuation.contentEquals(text.subSequence(start - punctuation.length(), start))
|
|
||||||
&& punctuation.contentEquals(text.subSequence(end, end + punctuation.length()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int getContainedPunctuationCount(@NonNull CharSequence text, int start, int end, @NonNull String punctuation) {
|
|
||||||
final Matcher matcher = Pattern.compile(Pattern.quote(punctuation)).matcher(text.subSequence(start, end));
|
|
||||||
int counter = 0;
|
|
||||||
while (matcher.find()) {
|
|
||||||
counter++;
|
|
||||||
}
|
|
||||||
return counter;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void removeContainingPunctuation(@NonNull Editable editable, int start, int end, @NonNull String punctuation) {
|
|
||||||
final Matcher matcher = Pattern.compile(Pattern.quote(punctuation)).matcher(editable.subSequence(start, end));
|
|
||||||
int countDeletedPunctuations = 0;
|
|
||||||
while (matcher.find()) {
|
|
||||||
editable.delete(start + matcher.start() - countDeletedPunctuations * punctuation.length(), start + matcher.end() - countDeletedPunctuations * punctuation.length());
|
|
||||||
countDeletedPunctuations++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean selectionIsInLink(@NonNull CharSequence text, int start, int end) {
|
|
||||||
final Matcher matcher = PATTERN_MARKDOWN_LINK.matcher(text);
|
|
||||||
while (matcher.find()) {
|
|
||||||
if ((start >= matcher.start() && start < matcher.end()) || (end > matcher.start() && end <= matcher.end())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void searchAndColor(@NonNull Spannable editable, @Nullable CharSequence searchText, @NonNull Context context, @Nullable Integer current, @ColorInt int mainColor) {
|
|
||||||
if (searchText != null) {
|
|
||||||
final Matcher m = Pattern
|
|
||||||
.compile(searchText.toString(), Pattern.CASE_INSENSITIVE | Pattern.LITERAL)
|
|
||||||
.matcher(editable);
|
|
||||||
|
|
||||||
int i = 1;
|
|
||||||
while (m.find()) {
|
|
||||||
int start = m.start();
|
|
||||||
int end = m.end();
|
|
||||||
editable.setSpan(new SearchSpan(context, mainColor, (current != null && i == current)), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> void removeSpans(@NonNull Spannable spannable, @SuppressWarnings("SameParameterValue") Class<T> spanType) {
|
|
||||||
for (T span : spannable.getSpans(0, spannable.length(), spanType)) {
|
|
||||||
spannable.removeSpan(span);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return When the content of the {@param textView} is already of type {@link Spannable}, it will cast and return it directly.
|
|
||||||
* Otherwise it will create a new {@link SpannableString} from the content, set this as new content of the {@param textView} and return it.
|
|
||||||
*/
|
|
||||||
public static Spannable getContentAsSpannable(@NonNull TextView textView) {
|
|
||||||
final CharSequence content = textView.getText();
|
|
||||||
if (content.getClass() == SpannableString.class || content instanceof Spannable) {
|
|
||||||
return (Spannable) content;
|
|
||||||
} else {
|
|
||||||
Log.w(TAG, "Expected " + TextView.class.getSimpleName() + " content to be of type " + Spannable.class.getSimpleName() + ", but was of type " + content.getClass() + ". Search highlighting will be not performant.");
|
|
||||||
final Spannable spannableContent = new SpannableString(content);
|
|
||||||
textView.setText(spannableContent, TextView.BufferType.SPANNABLE);
|
|
||||||
return spannableContent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package it.niedermann.android.markdown.markwon;
|
package it.niedermann.android.markdown.markwon;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.res.Configuration;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
@ -19,18 +20,36 @@ import java.util.function.Function;
|
||||||
|
|
||||||
import io.noties.markwon.Markwon;
|
import io.noties.markwon.Markwon;
|
||||||
import io.noties.markwon.MarkwonPlugin;
|
import io.noties.markwon.MarkwonPlugin;
|
||||||
|
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin;
|
||||||
|
import io.noties.markwon.ext.tables.TablePlugin;
|
||||||
|
import io.noties.markwon.ext.tasklist.TaskListPlugin;
|
||||||
|
import io.noties.markwon.image.ImagesPlugin;
|
||||||
|
import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin;
|
||||||
|
import io.noties.markwon.linkify.LinkifyPlugin;
|
||||||
|
import io.noties.markwon.simple.ext.SimpleExtPlugin;
|
||||||
|
import io.noties.markwon.syntax.Prism4jTheme;
|
||||||
|
import io.noties.markwon.syntax.Prism4jThemeDarkula;
|
||||||
|
import io.noties.markwon.syntax.Prism4jThemeDefault;
|
||||||
|
import io.noties.markwon.syntax.SyntaxHighlightPlugin;
|
||||||
|
import io.noties.prism4j.Prism4j;
|
||||||
|
import io.noties.prism4j.annotations.PrismBundle;
|
||||||
import it.niedermann.android.markdown.MarkdownEditor;
|
import it.niedermann.android.markdown.MarkdownEditor;
|
||||||
|
import it.niedermann.android.markdown.MarkdownUtil;
|
||||||
import it.niedermann.android.markdown.markwon.plugins.LinkClickInterceptorPlugin;
|
import it.niedermann.android.markdown.markwon.plugins.LinkClickInterceptorPlugin;
|
||||||
|
import it.niedermann.android.markdown.markwon.plugins.NextcloudMentionsPlugin;
|
||||||
import it.niedermann.android.markdown.markwon.plugins.SearchHighlightPlugin;
|
import it.niedermann.android.markdown.markwon.plugins.SearchHighlightPlugin;
|
||||||
|
import it.niedermann.android.markdown.markwon.plugins.ThemePlugin;
|
||||||
import it.niedermann.android.markdown.markwon.plugins.ToggleableTaskListPlugin;
|
import it.niedermann.android.markdown.markwon.plugins.ToggleableTaskListPlugin;
|
||||||
|
|
||||||
import static androidx.lifecycle.Transformations.distinctUntilChanged;
|
import static androidx.lifecycle.Transformations.distinctUntilChanged;
|
||||||
import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.initMarkwonViewer;
|
|
||||||
|
|
||||||
|
@PrismBundle(includeAll = true, grammarLocatorClassName = ".MarkwonGrammarLocator")
|
||||||
public class MarkwonMarkdownViewer extends AppCompatTextView implements MarkdownEditor {
|
public class MarkwonMarkdownViewer extends AppCompatTextView implements MarkdownEditor {
|
||||||
|
|
||||||
private static final String TAG = MarkwonMarkdownViewer.class.getSimpleName();
|
private static final String TAG = MarkwonMarkdownViewer.class.getSimpleName();
|
||||||
|
|
||||||
|
private static final Prism4j prism4j = new Prism4j(new MarkwonGrammarLocator());
|
||||||
|
|
||||||
private Markwon markwon;
|
private Markwon markwon;
|
||||||
private final MutableLiveData<CharSequence> unrenderedText$ = new MutableLiveData<>();
|
private final MutableLiveData<CharSequence> unrenderedText$ = new MutableLiveData<>();
|
||||||
|
|
||||||
|
@ -46,27 +65,60 @@ public class MarkwonMarkdownViewer extends AppCompatTextView implements Markdown
|
||||||
|
|
||||||
public MarkwonMarkdownViewer(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
public MarkwonMarkdownViewer(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||||
super(context, attrs, defStyleAttr);
|
super(context, attrs, defStyleAttr);
|
||||||
this.markwon = initMarkwonViewer(context)
|
this.markwon = createMarkwonBuilder(context).build();
|
||||||
|
this.renderService = Executors.newSingleThreadExecutor();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Markwon.Builder createMarkwonBuilder(@NonNull Context context) {
|
||||||
|
final Prism4jTheme prism4jTheme = MarkwonMarkdownUtil.isDarkThemeActive(context)
|
||||||
|
? Prism4jThemeDarkula.create()
|
||||||
|
: Prism4jThemeDefault.create();
|
||||||
|
return Markwon.builder(context)
|
||||||
|
.usePlugin(ThemePlugin.create(context))
|
||||||
|
.usePlugin(StrikethroughPlugin.create())
|
||||||
|
.usePlugin(SimpleExtPlugin.create())
|
||||||
|
.usePlugin(ImagesPlugin.create())
|
||||||
|
.usePlugin(MarkwonInlineParserPlugin.create())
|
||||||
|
.usePlugin(SearchHighlightPlugin.create(context))
|
||||||
|
.usePlugin(TablePlugin.create(context))
|
||||||
|
.usePlugin(TaskListPlugin.create(context))
|
||||||
|
.usePlugin(LinkifyPlugin.create(true))
|
||||||
|
.usePlugin(LinkClickInterceptorPlugin.create())
|
||||||
|
.usePlugin(ImagesPlugin.create())
|
||||||
|
.usePlugin(SyntaxHighlightPlugin.create(prism4j, prism4jTheme))
|
||||||
.usePlugin(new ToggleableTaskListPlugin((toggledCheckboxPosition, newCheckedState) -> {
|
.usePlugin(new ToggleableTaskListPlugin((toggledCheckboxPosition, newCheckedState) -> {
|
||||||
final CharSequence oldUnrenderedText = unrenderedText$.getValue();
|
final CharSequence oldUnrenderedText = unrenderedText$.getValue();
|
||||||
if (oldUnrenderedText == null) {
|
if (oldUnrenderedText == null) {
|
||||||
throw new IllegalStateException("Checkbox #" + toggledCheckboxPosition + ", but unrenderedText$ value is null.");
|
throw new IllegalStateException("Checkbox #" + toggledCheckboxPosition + ", but unrenderedText$ value is null.");
|
||||||
}
|
}
|
||||||
final CharSequence newUnrenderedText = MarkwonMarkdownUtil.setCheckboxStatus(oldUnrenderedText.toString(), toggledCheckboxPosition, newCheckedState);
|
final CharSequence newUnrenderedText = MarkdownUtil.setCheckboxStatus(oldUnrenderedText.toString(), toggledCheckboxPosition, newCheckedState);
|
||||||
this.setMarkdownString(newUnrenderedText);
|
this.setMarkdownString(newUnrenderedText);
|
||||||
}))
|
}));
|
||||||
.build();
|
}
|
||||||
this.renderService = Executors.newSingleThreadExecutor();
|
|
||||||
|
public Markwon.Builder createMarkwonBuilder(@NonNull Context context, @NonNull Map<String, String> mentions) {
|
||||||
|
return createMarkwonBuilder(context)
|
||||||
|
.usePlugin(NextcloudMentionsPlugin.create(context, mentions));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerOnLinkClickCallback(@NonNull Function<String, Boolean> callback) {
|
public void registerOnLinkClickCallback(@NonNull Function<String, Boolean> callback) {
|
||||||
this.markwon.getPlugin(LinkClickInterceptorPlugin.class).registerOnLinkClickCallback(callback);
|
final LinkClickInterceptorPlugin plugin = this.markwon.getPlugin(LinkClickInterceptorPlugin.class);
|
||||||
|
if (plugin == null) {
|
||||||
|
Log.w(TAG, "Tried to register callback, but " + LinkClickInterceptorPlugin.class.getSimpleName() + " is not a registered " + MarkwonPlugin.class.getSimpleName() + ".");
|
||||||
|
} else {
|
||||||
|
plugin.registerOnLinkClickCallback(callback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setEnabled(boolean enabled) {
|
public void setEnabled(boolean enabled) {
|
||||||
this.markwon.getPlugin(ToggleableTaskListPlugin.class).setEnabled(enabled);
|
final ToggleableTaskListPlugin plugin = this.markwon.getPlugin(ToggleableTaskListPlugin.class);
|
||||||
|
if (plugin == null) {
|
||||||
|
Log.w(TAG, "Tried to set enabled state for " + ToggleableTaskListPlugin.class.getSimpleName() + ", but " + ToggleableTaskListPlugin.class.getSimpleName() + " is not a registered " + MarkwonPlugin.class.getSimpleName() + ".");
|
||||||
|
} else {
|
||||||
|
plugin.setEnabled(enabled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -104,7 +156,7 @@ public class MarkwonMarkdownViewer extends AppCompatTextView implements Markdown
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setMarkdownString(CharSequence text, @NonNull Map<String, String> mentions) {
|
public void setMarkdownString(CharSequence text, @NonNull Map<String, String> mentions) {
|
||||||
this.markwon = initMarkwonViewer(getContext(), mentions).build();
|
this.markwon = createMarkwonBuilder(getContext(), mentions).build();
|
||||||
setMarkdownString(text);
|
setMarkdownString(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,13 +8,13 @@ import android.view.MenuItem;
|
||||||
|
|
||||||
import it.niedermann.android.markdown.R;
|
import it.niedermann.android.markdown.R;
|
||||||
import it.niedermann.android.markdown.markwon.MarkwonMarkdownEditor;
|
import it.niedermann.android.markdown.markwon.MarkwonMarkdownEditor;
|
||||||
import it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil;
|
import it.niedermann.android.markdown.MarkdownUtil;
|
||||||
import it.niedermann.android.markdown.markwon.model.EListType;
|
import it.niedermann.android.markdown.model.EListType;
|
||||||
import it.niedermann.android.util.ClipboardUtil;
|
import it.niedermann.android.util.ClipboardUtil;
|
||||||
|
|
||||||
import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.getEndOfLine;
|
import static it.niedermann.android.markdown.MarkdownUtil.getEndOfLine;
|
||||||
import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.getStartOfLine;
|
import static it.niedermann.android.markdown.MarkdownUtil.getStartOfLine;
|
||||||
import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.lineStartsWithCheckbox;
|
import static it.niedermann.android.markdown.MarkdownUtil.lineStartsWithCheckbox;
|
||||||
|
|
||||||
public class ContextBasedFormattingCallback implements ActionMode.Callback {
|
public class ContextBasedFormattingCallback implements ActionMode.Callback {
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ public class ContextBasedFormattingCallback implements ActionMode.Callback {
|
||||||
editText.setSelection(cursorPosition + EListType.DASH.checkboxUncheckedWithTrailingSpace.length());
|
editText.setSelection(cursorPosition + EListType.DASH.checkboxUncheckedWithTrailingSpace.length());
|
||||||
return true;
|
return true;
|
||||||
} else if (itemId == R.id.link) {
|
} else if (itemId == R.id.link) {
|
||||||
final int newSelection = MarkwonMarkdownUtil.insertLink(editable, cursorPosition, cursorPosition, ClipboardUtil.INSTANCE.getClipboardURLorNull(editText.getContext()));
|
final int newSelection = MarkdownUtil.insertLink(editable, cursorPosition, cursorPosition, ClipboardUtil.INSTANCE.getClipboardURLorNull(editText.getContext()));
|
||||||
editText.setMarkdownStringModel(editable);
|
editText.setMarkdownStringModel(editable);
|
||||||
editText.setSelection(newSelection);
|
editText.setSelection(newSelection);
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -12,7 +12,7 @@ import android.view.MenuItem;
|
||||||
|
|
||||||
import it.niedermann.android.markdown.R;
|
import it.niedermann.android.markdown.R;
|
||||||
import it.niedermann.android.markdown.markwon.MarkwonMarkdownEditor;
|
import it.niedermann.android.markdown.markwon.MarkwonMarkdownEditor;
|
||||||
import it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil;
|
import it.niedermann.android.markdown.MarkdownUtil;
|
||||||
import it.niedermann.android.util.ClipboardUtil;
|
import it.niedermann.android.util.ClipboardUtil;
|
||||||
|
|
||||||
public class ContextBasedRangeFormattingCallback implements ActionMode.Callback {
|
public class ContextBasedRangeFormattingCallback implements ActionMode.Callback {
|
||||||
|
@ -55,7 +55,7 @@ public class ContextBasedRangeFormattingCallback implements ActionMode.Callback
|
||||||
final int selectionStart = editText.getSelectionStart();
|
final int selectionStart = editText.getSelectionStart();
|
||||||
final int selectionEnd = editText.getSelectionEnd();
|
final int selectionEnd = editText.getSelectionEnd();
|
||||||
if (selectionStart >= 0 && selectionStart <= text.length()) {
|
if (selectionStart >= 0 && selectionStart <= text.length()) {
|
||||||
if (MarkwonMarkdownUtil.selectionIsInLink(text, selectionStart, selectionEnd)) {
|
if (MarkdownUtil.selectionIsInLink(text, selectionStart, selectionEnd)) {
|
||||||
menu.findItem(R.id.link).setVisible(false);
|
menu.findItem(R.id.link).setVisible(false);
|
||||||
Log.i(TAG, "Hide link menu item because the selection is already within a link.");
|
Log.i(TAG, "Hide link menu item because the selection is already within a link.");
|
||||||
}
|
}
|
||||||
|
@ -75,17 +75,17 @@ public class ContextBasedRangeFormattingCallback implements ActionMode.Callback
|
||||||
final int end = editText.getSelectionEnd();
|
final int end = editText.getSelectionEnd();
|
||||||
|
|
||||||
if (itemId == R.id.bold) {
|
if (itemId == R.id.bold) {
|
||||||
final int newSelection = MarkwonMarkdownUtil.togglePunctuation(editable, start, end, "**");
|
final int newSelection = MarkdownUtil.togglePunctuation(editable, start, end, "**");
|
||||||
editText.setMarkdownStringModel(editable);
|
editText.setMarkdownStringModel(editable);
|
||||||
editText.setSelection(newSelection);
|
editText.setSelection(newSelection);
|
||||||
return true;
|
return true;
|
||||||
} else if (itemId == R.id.italic) {
|
} else if (itemId == R.id.italic) {
|
||||||
final int newSelection = MarkwonMarkdownUtil.togglePunctuation(editable, start, end, "*");
|
final int newSelection = MarkdownUtil.togglePunctuation(editable, start, end, "*");
|
||||||
editText.setMarkdownStringModel(editable);
|
editText.setMarkdownStringModel(editable);
|
||||||
editText.setSelection(newSelection);
|
editText.setSelection(newSelection);
|
||||||
return true;
|
return true;
|
||||||
} else if (itemId == R.id.link) {
|
} else if (itemId == R.id.link) {
|
||||||
final int newSelection = MarkwonMarkdownUtil.insertLink(editable, start, end, ClipboardUtil.INSTANCE.getClipboardURLorNull(editText.getContext()));
|
final int newSelection = MarkdownUtil.insertLink(editable, start, end, ClipboardUtil.INSTANCE.getClipboardURLorNull(editText.getContext()));
|
||||||
editText.setMarkdownStringModel(editable);
|
editText.setMarkdownStringModel(editable);
|
||||||
editText.setSelection(newSelection);
|
editText.setSelection(newSelection);
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -15,7 +15,7 @@ import io.noties.markwon.MarkwonPlugin;
|
||||||
import it.niedermann.android.markdown.markwon.span.InterceptedURLSpan;
|
import it.niedermann.android.markdown.markwon.span.InterceptedURLSpan;
|
||||||
|
|
||||||
import static android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
|
import static android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
|
||||||
import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.getContentAsSpannable;
|
import static it.niedermann.android.markdown.MarkdownUtil.getContentAsSpannable;
|
||||||
|
|
||||||
public class LinkClickInterceptorPlugin extends AbstractMarkwonPlugin {
|
public class LinkClickInterceptorPlugin extends AbstractMarkwonPlugin {
|
||||||
|
|
||||||
|
|
|
@ -12,21 +12,28 @@ import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import io.noties.markwon.AbstractMarkwonPlugin;
|
import io.noties.markwon.AbstractMarkwonPlugin;
|
||||||
import io.noties.markwon.MarkwonPlugin;
|
import io.noties.markwon.MarkwonPlugin;
|
||||||
|
import it.niedermann.android.markdown.MarkdownUtil;
|
||||||
import it.niedermann.android.markdown.R;
|
import it.niedermann.android.markdown.R;
|
||||||
import it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil;
|
import it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil;
|
||||||
import it.niedermann.android.markdown.markwon.span.SearchSpan;
|
import it.niedermann.android.markdown.model.SearchSpan;
|
||||||
|
|
||||||
import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.getContentAsSpannable;
|
import static it.niedermann.android.markdown.MarkdownUtil.getContentAsSpannable;
|
||||||
|
|
||||||
public class SearchHighlightPlugin extends AbstractMarkwonPlugin {
|
public class SearchHighlightPlugin extends AbstractMarkwonPlugin {
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private CharSequence searchText = null;
|
private CharSequence searchText = null;
|
||||||
private Integer current;
|
private Integer current;
|
||||||
|
@ColorInt
|
||||||
private int color;
|
private int color;
|
||||||
|
@ColorInt
|
||||||
|
private final int highlightColor;
|
||||||
|
private final boolean darkTheme;
|
||||||
|
|
||||||
public SearchHighlightPlugin(@NonNull Context context) {
|
public SearchHighlightPlugin(@NonNull Context context) {
|
||||||
color = ContextCompat.getColor(context, R.color.search_color);
|
this.color = ContextCompat.getColor(context, R.color.search_color);
|
||||||
|
this.highlightColor = ContextCompat.getColor(context, R.color.bg_highlighted);
|
||||||
|
this.darkTheme = MarkwonMarkdownUtil.isDarkThemeActive(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MarkwonPlugin create(@NonNull Context context) {
|
public static MarkwonPlugin create(@NonNull Context context) {
|
||||||
|
@ -35,7 +42,7 @@ public class SearchHighlightPlugin extends AbstractMarkwonPlugin {
|
||||||
|
|
||||||
public void setSearchText(@Nullable CharSequence searchText, @Nullable Integer current, @NonNull TextView textView) {
|
public void setSearchText(@Nullable CharSequence searchText, @Nullable Integer current, @NonNull TextView textView) {
|
||||||
this.current = current;
|
this.current = current;
|
||||||
MarkwonMarkdownUtil.removeSpans(getContentAsSpannable(textView), SearchSpan.class);
|
MarkdownUtil.removeSpans(getContentAsSpannable(textView), SearchSpan.class);
|
||||||
if (TextUtils.isEmpty(searchText)) {
|
if (TextUtils.isEmpty(searchText)) {
|
||||||
this.searchText = null;
|
this.searchText = null;
|
||||||
} else {
|
} else {
|
||||||
|
@ -54,7 +61,7 @@ public class SearchHighlightPlugin extends AbstractMarkwonPlugin {
|
||||||
super.afterSetText(textView);
|
super.afterSetText(textView);
|
||||||
if (this.searchText != null) {
|
if (this.searchText != null) {
|
||||||
final Spannable spannable = getContentAsSpannable(textView);
|
final Spannable spannable = getContentAsSpannable(textView);
|
||||||
MarkwonMarkdownUtil.searchAndColor(spannable, searchText, textView.getContext(), current, color);
|
MarkdownUtil.searchAndColor(spannable, searchText, current, color, highlightColor, darkTheme);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,12 @@ import android.text.TextWatcher;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import it.niedermann.android.markdown.markwon.MarkwonMarkdownEditor;
|
import it.niedermann.android.markdown.markwon.MarkwonMarkdownEditor;
|
||||||
import it.niedermann.android.markdown.markwon.model.EListType;
|
import it.niedermann.android.markdown.model.EListType;
|
||||||
|
|
||||||
import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.getListItemIfIsEmpty;
|
import static it.niedermann.android.markdown.MarkdownUtil.getListItemIfIsEmpty;
|
||||||
import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.getOrderedListNumber;
|
import static it.niedermann.android.markdown.MarkdownUtil.getOrderedListNumber;
|
||||||
import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.getStartOfLine;
|
import static it.niedermann.android.markdown.MarkdownUtil.getStartOfLine;
|
||||||
import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.lineStartsWithCheckbox;
|
import static it.niedermann.android.markdown.MarkdownUtil.lineStartsWithCheckbox;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Automatically continues lists and checkbox lists when pressing enter
|
* Automatically continues lists and checkbox lists when pressing enter
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package it.niedermann.android.markdown.markwon.textwatcher;
|
package it.niedermann.android.markdown.markwon.textwatcher;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
|
@ -9,10 +10,11 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import it.niedermann.android.markdown.MarkdownUtil;
|
||||||
import it.niedermann.android.markdown.R;
|
import it.niedermann.android.markdown.R;
|
||||||
import it.niedermann.android.markdown.markwon.MarkwonMarkdownEditor;
|
import it.niedermann.android.markdown.markwon.MarkwonMarkdownEditor;
|
||||||
import it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil;
|
import it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil;
|
||||||
import it.niedermann.android.markdown.markwon.span.SearchSpan;
|
import it.niedermann.android.markdown.model.SearchSpan;
|
||||||
|
|
||||||
public class SearchHighlightTextWatcher extends InterceptorTextWatcher {
|
public class SearchHighlightTextWatcher extends InterceptorTextWatcher {
|
||||||
|
|
||||||
|
@ -20,19 +22,29 @@ public class SearchHighlightTextWatcher extends InterceptorTextWatcher {
|
||||||
@Nullable
|
@Nullable
|
||||||
private CharSequence searchText;
|
private CharSequence searchText;
|
||||||
private Integer current;
|
private Integer current;
|
||||||
|
@ColorInt
|
||||||
private int color;
|
private int color;
|
||||||
|
@ColorInt
|
||||||
|
private final int highlightColor;
|
||||||
|
private final boolean darkTheme;
|
||||||
|
|
||||||
public SearchHighlightTextWatcher(@NonNull TextWatcher originalWatcher, @NonNull MarkwonMarkdownEditor editText) {
|
public SearchHighlightTextWatcher(@NonNull TextWatcher originalWatcher, @NonNull MarkwonMarkdownEditor editText) {
|
||||||
super(originalWatcher);
|
super(originalWatcher);
|
||||||
this.editText = editText;
|
this.editText = editText;
|
||||||
this.color = ContextCompat.getColor(editText.getContext(), R.color.search_color);
|
final Context context = editText.getContext();
|
||||||
|
this.color = ContextCompat.getColor(context, R.color.search_color);
|
||||||
|
this.highlightColor = ContextCompat.getColor(context, R.color.bg_highlighted);
|
||||||
|
this.darkTheme = MarkwonMarkdownUtil.isDarkThemeActive(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSearchText(@Nullable CharSequence searchText, @Nullable Integer current) {
|
public void setSearchText(@Nullable CharSequence searchText, @Nullable Integer current) {
|
||||||
this.current = current;
|
this.current = current;
|
||||||
if (TextUtils.isEmpty(searchText)) {
|
if (TextUtils.isEmpty(searchText)) {
|
||||||
this.searchText = null;
|
this.searchText = null;
|
||||||
MarkwonMarkdownUtil.removeSpans(editText.getText(), SearchSpan.class);
|
final Editable text = editText.getText();
|
||||||
|
if (text != null) {
|
||||||
|
MarkdownUtil.removeSpans(text, SearchSpan.class);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.searchText = searchText;
|
this.searchText = searchText;
|
||||||
afterTextChanged(editText.getText());
|
afterTextChanged(editText.getText());
|
||||||
|
@ -48,8 +60,8 @@ public class SearchHighlightTextWatcher extends InterceptorTextWatcher {
|
||||||
public void afterTextChanged(Editable s) {
|
public void afterTextChanged(Editable s) {
|
||||||
originalWatcher.afterTextChanged(s);
|
originalWatcher.afterTextChanged(s);
|
||||||
if (searchText != null) {
|
if (searchText != null) {
|
||||||
MarkwonMarkdownUtil.removeSpans(s, SearchSpan.class);
|
MarkdownUtil.removeSpans(s, SearchSpan.class);
|
||||||
MarkwonMarkdownUtil.searchAndColor(s, searchText, editText.getContext(), current, color);
|
MarkdownUtil.searchAndColor(s, searchText, current, color, highlightColor, darkTheme);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package it.niedermann.android.markdown.markwon.model;
|
package it.niedermann.android.markdown.model;
|
||||||
|
|
||||||
public enum EListType {
|
public enum EListType {
|
||||||
STAR('*'),
|
STAR('*'),
|
|
@ -1,7 +1,5 @@
|
||||||
package it.niedermann.android.markdown.markwon.span;
|
package it.niedermann.android.markdown.model;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.text.TextPaint;
|
import android.text.TextPaint;
|
||||||
import android.text.style.MetricAffectingSpan;
|
import android.text.style.MetricAffectingSpan;
|
||||||
|
@ -9,30 +7,28 @@ import android.text.style.MetricAffectingSpan;
|
||||||
import androidx.annotation.ColorInt;
|
import androidx.annotation.ColorInt;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import it.niedermann.android.markdown.R;
|
|
||||||
import it.niedermann.android.util.ColorUtil;
|
import it.niedermann.android.util.ColorUtil;
|
||||||
|
|
||||||
public class SearchSpan extends MetricAffectingSpan {
|
public class SearchSpan extends MetricAffectingSpan {
|
||||||
|
|
||||||
private final boolean current;
|
private final boolean current;
|
||||||
@NonNull
|
|
||||||
Context context;
|
|
||||||
@ColorInt
|
@ColorInt
|
||||||
private final int mainColor;
|
private final int mainColor;
|
||||||
@ColorInt
|
@ColorInt
|
||||||
private final int highlightColor;
|
private final int highlightColor;
|
||||||
|
private final boolean darkTheme;
|
||||||
|
|
||||||
public SearchSpan(@NonNull Context context, @ColorInt int mainColor, boolean current) {
|
public SearchSpan(@ColorInt int mainColor, @ColorInt int highlightColor, boolean current, boolean darkTheme) {
|
||||||
this.context = context;
|
|
||||||
this.mainColor = mainColor;
|
this.mainColor = mainColor;
|
||||||
this.current = current;
|
this.current = current;
|
||||||
this.highlightColor = context.getResources().getColor(R.color.bg_highlighted);
|
this.highlightColor = highlightColor;
|
||||||
|
this.darkTheme = darkTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateDrawState(TextPaint tp) {
|
public void updateDrawState(TextPaint tp) {
|
||||||
if (current) {
|
if (current) {
|
||||||
if (isDarkThemeActive(context)) {
|
if (darkTheme) {
|
||||||
if (ColorUtil.INSTANCE.isColorDark(mainColor)) {
|
if (ColorUtil.INSTANCE.isColorDark(mainColor)) {
|
||||||
tp.bgColor = Color.WHITE;
|
tp.bgColor = Color.WHITE;
|
||||||
tp.setColor(mainColor);
|
tp.setColor(mainColor);
|
||||||
|
@ -58,7 +54,7 @@ public class SearchSpan extends MetricAffectingSpan {
|
||||||
if (ColorUtil.INSTANCE.getContrastRatio(mainColor, highlightColor) > 3d) {
|
if (ColorUtil.INSTANCE.getContrastRatio(mainColor, highlightColor) > 3d) {
|
||||||
tp.setColor(mainColor);
|
tp.setColor(mainColor);
|
||||||
} else {
|
} else {
|
||||||
if (isDarkThemeActive(context)) {
|
if (darkTheme) {
|
||||||
tp.setColor(Color.WHITE);
|
tp.setColor(Color.WHITE);
|
||||||
} else {
|
} else {
|
||||||
tp.setColor(Color.BLACK);
|
tp.setColor(Color.BLACK);
|
||||||
|
@ -72,9 +68,4 @@ public class SearchSpan extends MetricAffectingSpan {
|
||||||
public void updateMeasureState(@NonNull TextPaint tp) {
|
public void updateMeasureState(@NonNull TextPaint tp) {
|
||||||
tp.setFakeBoldText(true);
|
tp.setFakeBoldText(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isDarkThemeActive(Context context) {
|
|
||||||
int uiMode = context.getResources().getConfiguration().uiMode;
|
|
||||||
return (uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,72 +0,0 @@
|
||||||
package it.niedermann.android.markdown.rxmarkdown;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.MutableLiveData;
|
|
||||||
|
|
||||||
import com.yydcdut.markdown.MarkdownEditText;
|
|
||||||
import com.yydcdut.markdown.MarkdownProcessor;
|
|
||||||
import com.yydcdut.markdown.syntax.edit.EditFactory;
|
|
||||||
|
|
||||||
import it.niedermann.android.markdown.MarkdownEditor;
|
|
||||||
|
|
||||||
import static androidx.lifecycle.Transformations.distinctUntilChanged;
|
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
public class RxMarkdownEditor extends MarkdownEditText implements MarkdownEditor {
|
|
||||||
|
|
||||||
private final MutableLiveData<CharSequence> unrenderedText$ = new MutableLiveData<>();
|
|
||||||
private MarkdownProcessor markdownProcessor;
|
|
||||||
|
|
||||||
public RxMarkdownEditor(Context context) {
|
|
||||||
super(context);
|
|
||||||
init(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RxMarkdownEditor(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
init(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RxMarkdownEditor(Context context, AttributeSet attrs, int defStyleAttr) {
|
|
||||||
super(context, attrs, defStyleAttr);
|
|
||||||
init(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init(Context context) {
|
|
||||||
markdownProcessor = new MarkdownProcessor(context);
|
|
||||||
markdownProcessor.config(RxMarkdownUtil.getMarkDownConfiguration(context).build());
|
|
||||||
markdownProcessor.factory(EditFactory.create());
|
|
||||||
markdownProcessor.live(this);
|
|
||||||
addTextChangedListener(new TextWatcher() {
|
|
||||||
@Override
|
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
unrenderedText$.setValue(s.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterTextChanged(Editable s) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setMarkdownString(CharSequence text) {
|
|
||||||
setText(markdownProcessor.parse(text));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LiveData<CharSequence> getMarkdownString() {
|
|
||||||
return distinctUntilChanged(unrenderedText$);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
package it.niedermann.android.markdown.rxmarkdown;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import androidx.annotation.RestrictTo;
|
|
||||||
|
|
||||||
import com.yydcdut.rxmarkdown.RxMDConfiguration.Builder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by stefan on 07.12.16.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
@RestrictTo(value = RestrictTo.Scope.LIBRARY)
|
|
||||||
public class RxMarkdownUtil {
|
|
||||||
|
|
||||||
private RxMarkdownUtil() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures every instance of RxMD uses the same configuration
|
|
||||||
*
|
|
||||||
* @param context Context
|
|
||||||
* @return RxMDConfiguration
|
|
||||||
*/
|
|
||||||
public static Builder getMarkDownConfiguration(Context context) {
|
|
||||||
return new Builder(context)
|
|
||||||
.setHeader2RelativeSize(1.35f)
|
|
||||||
.setHeader3RelativeSize(1.25f)
|
|
||||||
.setHeader4RelativeSize(1.15f)
|
|
||||||
.setHeader5RelativeSize(1.1f)
|
|
||||||
.setHeader6RelativeSize(1.05f)
|
|
||||||
.setHorizontalRulesHeight(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Builder getMarkDownConfiguration(Context context, Boolean darkTheme) {
|
|
||||||
return new Builder(context)
|
|
||||||
.setHeader2RelativeSize(1.35f)
|
|
||||||
.setHeader3RelativeSize(1.25f)
|
|
||||||
.setHeader4RelativeSize(1.15f)
|
|
||||||
.setHeader5RelativeSize(1.1f)
|
|
||||||
.setHeader6RelativeSize(1.05f)
|
|
||||||
.setHorizontalRulesHeight(2);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
package it.niedermann.android.markdown.rxmarkdown;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.MutableLiveData;
|
|
||||||
|
|
||||||
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
|
|
||||||
import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException;
|
|
||||||
import com.nextcloud.android.sso.helper.SingleAccountHelper;
|
|
||||||
import com.yydcdut.markdown.MarkdownProcessor;
|
|
||||||
import com.yydcdut.markdown.MarkdownTextView;
|
|
||||||
import com.yydcdut.markdown.syntax.text.TextFactory;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import it.niedermann.android.markdown.MarkdownEditor;
|
|
||||||
|
|
||||||
import static it.niedermann.android.markdown.MentionUtil.setupMentions;
|
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
public class RxMarkdownViewer extends MarkdownTextView implements MarkdownEditor {
|
|
||||||
|
|
||||||
private MarkdownProcessor markdownProcessor;
|
|
||||||
|
|
||||||
public RxMarkdownViewer(Context context) {
|
|
||||||
super(context);
|
|
||||||
init(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RxMarkdownViewer(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
init(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RxMarkdownViewer(Context context, AttributeSet attrs, int defStyleAttr) {
|
|
||||||
super(context, attrs, defStyleAttr);
|
|
||||||
init(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init(Context context) {
|
|
||||||
markdownProcessor = new MarkdownProcessor(context);
|
|
||||||
markdownProcessor.config(RxMarkdownUtil.getMarkDownConfiguration(context).build());
|
|
||||||
markdownProcessor.factory(TextFactory.create());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setMarkdownString(CharSequence text) {
|
|
||||||
setText(markdownProcessor.parse(text));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LiveData<CharSequence> getMarkdownString() {
|
|
||||||
return new MutableLiveData<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setMarkdownString(CharSequence text, @NonNull Map<String, String> mentions) {
|
|
||||||
try {
|
|
||||||
setMarkdownString(text);
|
|
||||||
setupMentions(SingleAccountHelper.getCurrentSingleSignOnAccount(getContext()), mentions, this);
|
|
||||||
} catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue