mirror of
https://github.com/nextcloud/notes-android.git
synced 2024-11-23 05:16:15 +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.Map;
|
||||
|
||||
import it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil;
|
||||
import it.niedermann.android.markdown.markwon.model.EListType;
|
||||
import it.niedermann.android.markdown.markwon.span.SearchSpan;
|
||||
import it.niedermann.android.markdown.model.EListType;
|
||||
import it.niedermann.android.markdown.model.SearchSpan;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class MarkwonMarkdownUtilTest extends TestCase {
|
||||
public class MarkdownUtilTest extends TestCase {
|
||||
|
||||
@Test
|
||||
public void testGetStartOfLine() {
|
||||
|
@ -42,7 +41,7 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
|||
);
|
||||
|
||||
for (int i = 0; i < test.length(); i++) {
|
||||
int startOfLine = MarkwonMarkdownUtil.getStartOfLine(test, i);
|
||||
int startOfLine = MarkdownUtil.getStartOfLine(test, i);
|
||||
if (i <= 11) {
|
||||
assertEquals(0, startOfLine);
|
||||
} else if (i <= 12) {
|
||||
|
@ -73,7 +72,7 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
|||
"\n"; // line 78 - 79
|
||||
|
||||
for (int i = 0; i < test.length(); i++) {
|
||||
int endOfLine = MarkwonMarkdownUtil.getEndOfLine(test, i);
|
||||
int endOfLine = MarkdownUtil.getEndOfLine(test, i);
|
||||
if (i <= 11) {
|
||||
assertEquals(11, endOfLine);
|
||||
} else if (i <= 12) {
|
||||
|
@ -159,7 +158,7 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
|||
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
|
||||
|
@ -168,121 +167,121 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
|||
|
||||
// Add italic
|
||||
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());
|
||||
|
||||
// Remove italic
|
||||
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());
|
||||
|
||||
// Add bold
|
||||
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());
|
||||
|
||||
// Remove bold
|
||||
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());
|
||||
|
||||
// Add strike
|
||||
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());
|
||||
|
||||
// Remove strike
|
||||
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());
|
||||
|
||||
// Add italic at first position
|
||||
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());
|
||||
|
||||
// Remove italic from first position
|
||||
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());
|
||||
|
||||
// Add italic at last position
|
||||
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());
|
||||
|
||||
// Remove italic from last position
|
||||
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());
|
||||
|
||||
// Text is not directly surrounded by punctuation but contains it
|
||||
|
||||
// Do nothing when the same punctuation is contained only one time
|
||||
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());
|
||||
|
||||
// Do nothing when the same punctuation is contained only one time
|
||||
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());
|
||||
|
||||
// Remove containing punctuation
|
||||
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());
|
||||
|
||||
// Remove containing punctuation
|
||||
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());
|
||||
|
||||
// Remove multiple containing punctuations
|
||||
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());
|
||||
|
||||
// Special use-case: toggle from italic to bold and back
|
||||
|
||||
// TODO Toggle italic on bold text
|
||||
// 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());
|
||||
|
||||
// TODO Toggle bold on italic text
|
||||
// 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());
|
||||
|
||||
// TODO Toggle bold to italic
|
||||
// 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());
|
||||
|
||||
// TODO Toggle multiple bold parts to italic
|
||||
// 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());
|
||||
|
||||
// TODO Toggle italic and bold to bold
|
||||
// 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());
|
||||
|
||||
// TODO Toggle italic and bold to italic
|
||||
// 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());
|
||||
|
||||
// TODO Toggle multiple italic and bold to bold
|
||||
// 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());
|
||||
|
||||
// TODO Toggle multiple italic and bold to italic
|
||||
// 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());
|
||||
}
|
||||
|
||||
|
@ -292,59 +291,59 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
|||
|
||||
// Add link without clipboardUrl to normal text
|
||||
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());
|
||||
|
||||
// Add link without clipboardUrl to url
|
||||
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());
|
||||
|
||||
// TODO Add link without clipboardUrl to empty selection before space character
|
||||
// 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());
|
||||
|
||||
// TODO Add link without clipboardUrl to empty selection after space character
|
||||
// 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());
|
||||
|
||||
// TODO Add link without clipboardUrl to empty selection in word
|
||||
// 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());
|
||||
|
||||
// Add link with clipboardUrl to normal text
|
||||
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());
|
||||
|
||||
// Add link with clipboardUrl to url
|
||||
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());
|
||||
|
||||
// TODO Add link with clipboardUrl to empty selection before space character
|
||||
// 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());
|
||||
|
||||
// TODO Add link with clipboardUrl to empty selection after space character
|
||||
// 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());
|
||||
|
||||
// TODO Add link with clipboardUrl to empty selection in word
|
||||
// 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());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveContainingPunctuation() {
|
||||
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);
|
||||
Editable builder;
|
||||
|
||||
|
@ -384,7 +383,7 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
|||
@SuppressWarnings("ConstantConditions")
|
||||
public void testSelectionIsSurroundedByPunctuation() {
|
||||
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);
|
||||
assertTrue((Boolean) m.invoke(null, "*Lorem ipsum*", 1, 12, "*"));
|
||||
assertTrue((Boolean) m.invoke(null, "**Lorem ipsum**", 2, 13, "*"));
|
||||
|
@ -402,7 +401,7 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
|||
@SuppressWarnings("ConstantConditions")
|
||||
public void testGetContainedPunctuationCount() {
|
||||
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);
|
||||
assertEquals(0, (int) m.invoke(null, "*Lorem ipsum*", 1, 12, "*"));
|
||||
assertEquals(1, (int) m.invoke(null, "*Lorem ipsum*", 1, 13, "*"));
|
||||
|
@ -419,7 +418,7 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
|||
@SuppressWarnings("ConstantConditions")
|
||||
public void testSelectionIsInLink() {
|
||||
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);
|
||||
|
||||
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
|
||||
public void testGetListItemIfIsEmpty() {
|
||||
assertEquals("- ", MarkwonMarkdownUtil.getListItemIfIsEmpty("- "));
|
||||
assertEquals("+ ", MarkwonMarkdownUtil.getListItemIfIsEmpty("+ "));
|
||||
assertEquals("* ", MarkwonMarkdownUtil.getListItemIfIsEmpty("* "));
|
||||
assertEquals("1. ", MarkwonMarkdownUtil.getListItemIfIsEmpty("1. "));
|
||||
assertEquals("- ", MarkdownUtil.getListItemIfIsEmpty("- "));
|
||||
assertEquals("+ ", MarkdownUtil.getListItemIfIsEmpty("+ "));
|
||||
assertEquals("* ", MarkdownUtil.getListItemIfIsEmpty("* "));
|
||||
assertEquals("1. ", MarkdownUtil.getListItemIfIsEmpty("1. "));
|
||||
|
||||
assertNull(MarkwonMarkdownUtil.getListItemIfIsEmpty("- Test"));
|
||||
assertNull(MarkwonMarkdownUtil.getListItemIfIsEmpty("+ Test"));
|
||||
assertNull(MarkwonMarkdownUtil.getListItemIfIsEmpty("* Test"));
|
||||
assertNull(MarkwonMarkdownUtil.getListItemIfIsEmpty("1. s"));
|
||||
assertNull(MarkwonMarkdownUtil.getListItemIfIsEmpty("1. "));
|
||||
assertNull(MarkdownUtil.getListItemIfIsEmpty("- Test"));
|
||||
assertNull(MarkdownUtil.getListItemIfIsEmpty("+ Test"));
|
||||
assertNull(MarkdownUtil.getListItemIfIsEmpty("* Test"));
|
||||
assertNull(MarkdownUtil.getListItemIfIsEmpty("1. s"));
|
||||
assertNull(MarkdownUtil.getListItemIfIsEmpty("1. "));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLineStartsWithOrderedList() {
|
||||
assertEquals(1, MarkwonMarkdownUtil.getOrderedListNumber("1. Test"));
|
||||
assertEquals(2, MarkwonMarkdownUtil.getOrderedListNumber("2. Test"));
|
||||
assertEquals(3, MarkwonMarkdownUtil.getOrderedListNumber("3. Test"));
|
||||
assertEquals(10, MarkwonMarkdownUtil.getOrderedListNumber("10. Test"));
|
||||
assertEquals(11, MarkwonMarkdownUtil.getOrderedListNumber("11. Test"));
|
||||
assertEquals(12, MarkwonMarkdownUtil.getOrderedListNumber("12. Test"));
|
||||
assertEquals(1, MarkwonMarkdownUtil.getOrderedListNumber("1. 1"));
|
||||
assertEquals(1, MarkwonMarkdownUtil.getOrderedListNumber("1. Test 1"));
|
||||
assertEquals(1, MarkdownUtil.getOrderedListNumber("1. Test"));
|
||||
assertEquals(2, MarkdownUtil.getOrderedListNumber("2. Test"));
|
||||
assertEquals(3, MarkdownUtil.getOrderedListNumber("3. Test"));
|
||||
assertEquals(10, MarkdownUtil.getOrderedListNumber("10. Test"));
|
||||
assertEquals(11, MarkdownUtil.getOrderedListNumber("11. Test"));
|
||||
assertEquals(12, MarkdownUtil.getOrderedListNumber("12. Test"));
|
||||
assertEquals(1, MarkdownUtil.getOrderedListNumber("1. 1"));
|
||||
assertEquals(1, MarkdownUtil.getOrderedListNumber("1. Test 1"));
|
||||
|
||||
assertEquals(-1, MarkwonMarkdownUtil.getOrderedListNumber(""));
|
||||
assertEquals(-1, MarkwonMarkdownUtil.getOrderedListNumber("1."));
|
||||
assertEquals(-1, MarkwonMarkdownUtil.getOrderedListNumber("1. "));
|
||||
assertEquals(-1, MarkwonMarkdownUtil.getOrderedListNumber("11. "));
|
||||
assertEquals(-1, MarkwonMarkdownUtil.getOrderedListNumber("-1. Test"));
|
||||
assertEquals(-1, MarkwonMarkdownUtil.getOrderedListNumber(" 1. Test"));
|
||||
assertEquals(-1, MarkdownUtil.getOrderedListNumber(""));
|
||||
assertEquals(-1, MarkdownUtil.getOrderedListNumber("1."));
|
||||
assertEquals(-1, MarkdownUtil.getOrderedListNumber("1. "));
|
||||
assertEquals(-1, MarkdownUtil.getOrderedListNumber("11. "));
|
||||
assertEquals(-1, MarkdownUtil.getOrderedListNumber("-1. Test"));
|
||||
assertEquals(-1, MarkdownUtil.getOrderedListNumber(" 1. Test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -493,19 +492,19 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
|||
for (EListType listType : EListType.values()) {
|
||||
final String origin_1 = listType.checkboxUnchecked + " 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 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 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 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 = "" +
|
||||
listType.checkboxChecked + " Item\n" +
|
||||
|
@ -513,7 +512,7 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
|||
final String expected_5 = "" +
|
||||
listType.checkboxChecked + " Item\n" +
|
||||
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
|
||||
final String origin_6 = "" +
|
||||
|
@ -528,7 +527,7 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
|||
listType.checkboxUnchecked + " Item\n" +
|
||||
"```\n" +
|
||||
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
|
||||
final String origin_7 = "" +
|
||||
|
@ -545,7 +544,7 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
|||
listType.checkboxUnchecked + " Item\n" +
|
||||
"````\n" +
|
||||
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
|
||||
final String origin_8 = "" +
|
||||
|
@ -564,7 +563,7 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
|||
"```\n" +
|
||||
"````\n" +
|
||||
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
|
||||
final String origin_9 = "" +
|
||||
|
@ -585,30 +584,30 @@ public class MarkwonMarkdownUtilTest extends TestCase {
|
|||
"````\n" +
|
||||
listType.checkboxUnchecked + " \n" +
|
||||
listType.checkboxChecked + " Item";
|
||||
assertEquals(expected_9, MarkwonMarkdownUtil.setCheckboxStatus(origin_9, 1, true));
|
||||
assertEquals(expected_9, MarkdownUtil.setCheckboxStatus(origin_9, 1, true));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveSpans() {
|
||||
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);
|
||||
|
||||
final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
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 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);
|
||||
assertEquals(0, editable_1.getSpans(0, editable_1.length(), SearchSpan.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");
|
||||
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 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);
|
||||
assertEquals(0, editable_2.getSpans(0, editable_2.length(), SearchSpan.class).length);
|
||||
assertEquals(1, editable_2.getSpans(0, editable_2.length(), ForegroundColorSpan.class).length);
|
|
@ -1,25 +1,45 @@
|
|||
package it.niedermann.android.markdown;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.Editable;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.widget.RemoteViews.RemoteView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.text.HtmlCompat;
|
||||
|
||||
import com.yydcdut.markdown.MarkdownProcessor;
|
||||
import com.yydcdut.markdown.syntax.text.TextFactory;
|
||||
import com.yydcdut.rxmarkdown.RxMarkdown;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import io.noties.markwon.Markwon;
|
||||
import it.niedermann.android.markdown.model.SearchSpan;
|
||||
import it.niedermann.android.markdown.model.EListType;
|
||||
|
||||
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_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_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() {
|
||||
// Util class
|
||||
}
|
||||
|
@ -58,4 +78,263 @@ public class MarkdownUtil {
|
|||
|
||||
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.handler.EmphasisEditHandler;
|
||||
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.markwon.format.ContextBasedFormattingCallback;
|
||||
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.HeadingEditHandler;
|
||||
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.SearchHighlightTextWatcher;
|
||||
|
||||
|
@ -46,16 +52,10 @@ public class MarkwonMarkdownEditor extends AppCompatEditText implements Markdown
|
|||
|
||||
public MarkwonMarkdownEditor(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
final Markwon markwon = MarkwonMarkdownUtil.initMarkwonEditor(context).build();
|
||||
final MarkwonEditor editor = MarkwonEditor.builder(markwon)
|
||||
.useEditHandler(new EmphasisEditHandler())
|
||||
.useEditHandler(new StrongEmphasisEditHandler())
|
||||
.useEditHandler(new StrikethroughEditHandler())
|
||||
.useEditHandler(new CodeEditHandler())
|
||||
.useEditHandler(new CodeBlockEditHandler())
|
||||
.useEditHandler(new BlockQuoteEditHandler())
|
||||
.useEditHandler(new HeadingEditHandler())
|
||||
.build();
|
||||
|
||||
final Markwon markwon = createMarkwonBuilder(context).build();
|
||||
final MarkwonEditor editor = createMarkwonEditorBuilder(markwon).build();
|
||||
|
||||
combinedWatcher = new CombinedTextWatcher(editor, this);
|
||||
addTextChangedListener(combinedWatcher);
|
||||
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
|
||||
public void setSearchColor(@ColorInt int color) {
|
||||
final SearchHighlightTextWatcher searchHighlightTextWatcher = combinedWatcher.get(SearchHighlightTextWatcher.class);
|
||||
|
|
|
@ -2,344 +2,17 @@ package it.niedermann.android.markdown.markwon;
|
|||
|
||||
import android.content.Context;
|
||||
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.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 {
|
||||
|
||||
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() {
|
||||
// Util class
|
||||
}
|
||||
|
||||
public static Markwon.Builder initMarkwonEditor(@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) {
|
||||
public static boolean isDarkThemeActive(@NonNull Context context) {
|
||||
final int uiMode = context.getResources().getConfiguration().uiMode;
|
||||
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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
|
@ -19,18 +20,36 @@ import java.util.function.Function;
|
|||
|
||||
import io.noties.markwon.Markwon;
|
||||
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.MarkdownUtil;
|
||||
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.plugins.ToggleableTaskListPlugin;
|
||||
|
||||
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 {
|
||||
|
||||
private static final String TAG = MarkwonMarkdownViewer.class.getSimpleName();
|
||||
|
||||
private static final Prism4j prism4j = new Prism4j(new MarkwonGrammarLocator());
|
||||
|
||||
private Markwon markwon;
|
||||
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) {
|
||||
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) -> {
|
||||
final CharSequence oldUnrenderedText = unrenderedText$.getValue();
|
||||
if (oldUnrenderedText == 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);
|
||||
}))
|
||||
.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
|
||||
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
|
||||
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
|
||||
|
@ -104,7 +156,7 @@ public class MarkwonMarkdownViewer extends AppCompatTextView implements Markdown
|
|||
|
||||
@Override
|
||||
public void setMarkdownString(CharSequence text, @NonNull Map<String, String> mentions) {
|
||||
this.markwon = initMarkwonViewer(getContext(), mentions).build();
|
||||
this.markwon = createMarkwonBuilder(getContext(), mentions).build();
|
||||
setMarkdownString(text);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,13 +8,13 @@ import android.view.MenuItem;
|
|||
|
||||
import it.niedermann.android.markdown.R;
|
||||
import it.niedermann.android.markdown.markwon.MarkwonMarkdownEditor;
|
||||
import it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil;
|
||||
import it.niedermann.android.markdown.markwon.model.EListType;
|
||||
import it.niedermann.android.markdown.MarkdownUtil;
|
||||
import it.niedermann.android.markdown.model.EListType;
|
||||
import it.niedermann.android.util.ClipboardUtil;
|
||||
|
||||
import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.getEndOfLine;
|
||||
import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.getStartOfLine;
|
||||
import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.lineStartsWithCheckbox;
|
||||
import static it.niedermann.android.markdown.MarkdownUtil.getEndOfLine;
|
||||
import static it.niedermann.android.markdown.MarkdownUtil.getStartOfLine;
|
||||
import static it.niedermann.android.markdown.MarkdownUtil.lineStartsWithCheckbox;
|
||||
|
||||
public class ContextBasedFormattingCallback implements ActionMode.Callback {
|
||||
|
||||
|
@ -65,7 +65,7 @@ public class ContextBasedFormattingCallback implements ActionMode.Callback {
|
|||
editText.setSelection(cursorPosition + EListType.DASH.checkboxUncheckedWithTrailingSpace.length());
|
||||
return true;
|
||||
} 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.setSelection(newSelection);
|
||||
return true;
|
||||
|
|
|
@ -12,7 +12,7 @@ import android.view.MenuItem;
|
|||
|
||||
import it.niedermann.android.markdown.R;
|
||||
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;
|
||||
|
||||
public class ContextBasedRangeFormattingCallback implements ActionMode.Callback {
|
||||
|
@ -55,7 +55,7 @@ public class ContextBasedRangeFormattingCallback implements ActionMode.Callback
|
|||
final int selectionStart = editText.getSelectionStart();
|
||||
final int selectionEnd = editText.getSelectionEnd();
|
||||
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);
|
||||
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();
|
||||
|
||||
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.setSelection(newSelection);
|
||||
return true;
|
||||
} 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.setSelection(newSelection);
|
||||
return true;
|
||||
} 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.setSelection(newSelection);
|
||||
return true;
|
||||
|
|
|
@ -15,7 +15,7 @@ import io.noties.markwon.MarkwonPlugin;
|
|||
import it.niedermann.android.markdown.markwon.span.InterceptedURLSpan;
|
||||
|
||||
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 {
|
||||
|
||||
|
|
|
@ -12,21 +12,28 @@ import androidx.core.content.ContextCompat;
|
|||
|
||||
import io.noties.markwon.AbstractMarkwonPlugin;
|
||||
import io.noties.markwon.MarkwonPlugin;
|
||||
import it.niedermann.android.markdown.MarkdownUtil;
|
||||
import it.niedermann.android.markdown.R;
|
||||
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 {
|
||||
|
||||
@Nullable
|
||||
private CharSequence searchText = null;
|
||||
private Integer current;
|
||||
@ColorInt
|
||||
private int color;
|
||||
@ColorInt
|
||||
private final int highlightColor;
|
||||
private final boolean darkTheme;
|
||||
|
||||
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) {
|
||||
|
@ -35,7 +42,7 @@ public class SearchHighlightPlugin extends AbstractMarkwonPlugin {
|
|||
|
||||
public void setSearchText(@Nullable CharSequence searchText, @Nullable Integer current, @NonNull TextView textView) {
|
||||
this.current = current;
|
||||
MarkwonMarkdownUtil.removeSpans(getContentAsSpannable(textView), SearchSpan.class);
|
||||
MarkdownUtil.removeSpans(getContentAsSpannable(textView), SearchSpan.class);
|
||||
if (TextUtils.isEmpty(searchText)) {
|
||||
this.searchText = null;
|
||||
} else {
|
||||
|
@ -54,7 +61,7 @@ public class SearchHighlightPlugin extends AbstractMarkwonPlugin {
|
|||
super.afterSetText(textView);
|
||||
if (this.searchText != null) {
|
||||
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 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.markwon.MarkwonMarkdownUtil.getOrderedListNumber;
|
||||
import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.getStartOfLine;
|
||||
import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.lineStartsWithCheckbox;
|
||||
import static it.niedermann.android.markdown.MarkdownUtil.getListItemIfIsEmpty;
|
||||
import static it.niedermann.android.markdown.MarkdownUtil.getOrderedListNumber;
|
||||
import static it.niedermann.android.markdown.MarkdownUtil.getStartOfLine;
|
||||
import static it.niedermann.android.markdown.MarkdownUtil.lineStartsWithCheckbox;
|
||||
|
||||
/**
|
||||
* Automatically continues lists and checkbox lists when pressing enter
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package it.niedermann.android.markdown.markwon.textwatcher;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
|
@ -9,10 +10,11 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import it.niedermann.android.markdown.MarkdownUtil;
|
||||
import it.niedermann.android.markdown.R;
|
||||
import it.niedermann.android.markdown.markwon.MarkwonMarkdownEditor;
|
||||
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 {
|
||||
|
||||
|
@ -20,19 +22,29 @@ public class SearchHighlightTextWatcher extends InterceptorTextWatcher {
|
|||
@Nullable
|
||||
private CharSequence searchText;
|
||||
private Integer current;
|
||||
@ColorInt
|
||||
private int color;
|
||||
@ColorInt
|
||||
private final int highlightColor;
|
||||
private final boolean darkTheme;
|
||||
|
||||
public SearchHighlightTextWatcher(@NonNull TextWatcher originalWatcher, @NonNull MarkwonMarkdownEditor editText) {
|
||||
super(originalWatcher);
|
||||
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) {
|
||||
this.current = current;
|
||||
if (TextUtils.isEmpty(searchText)) {
|
||||
this.searchText = null;
|
||||
MarkwonMarkdownUtil.removeSpans(editText.getText(), SearchSpan.class);
|
||||
final Editable text = editText.getText();
|
||||
if (text != null) {
|
||||
MarkdownUtil.removeSpans(text, SearchSpan.class);
|
||||
}
|
||||
} else {
|
||||
this.searchText = searchText;
|
||||
afterTextChanged(editText.getText());
|
||||
|
@ -48,8 +60,8 @@ public class SearchHighlightTextWatcher extends InterceptorTextWatcher {
|
|||
public void afterTextChanged(Editable s) {
|
||||
originalWatcher.afterTextChanged(s);
|
||||
if (searchText != null) {
|
||||
MarkwonMarkdownUtil.removeSpans(s, SearchSpan.class);
|
||||
MarkwonMarkdownUtil.searchAndColor(s, searchText, editText.getContext(), current, color);
|
||||
MarkdownUtil.removeSpans(s, SearchSpan.class);
|
||||
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 {
|
||||
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.text.TextPaint;
|
||||
import android.text.style.MetricAffectingSpan;
|
||||
|
@ -9,30 +7,28 @@ import android.text.style.MetricAffectingSpan;
|
|||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import it.niedermann.android.markdown.R;
|
||||
import it.niedermann.android.util.ColorUtil;
|
||||
|
||||
public class SearchSpan extends MetricAffectingSpan {
|
||||
|
||||
private final boolean current;
|
||||
@NonNull
|
||||
Context context;
|
||||
@ColorInt
|
||||
private final int mainColor;
|
||||
@ColorInt
|
||||
private final int highlightColor;
|
||||
private final boolean darkTheme;
|
||||
|
||||
public SearchSpan(@NonNull Context context, @ColorInt int mainColor, boolean current) {
|
||||
this.context = context;
|
||||
public SearchSpan(@ColorInt int mainColor, @ColorInt int highlightColor, boolean current, boolean darkTheme) {
|
||||
this.mainColor = mainColor;
|
||||
this.current = current;
|
||||
this.highlightColor = context.getResources().getColor(R.color.bg_highlighted);
|
||||
this.highlightColor = highlightColor;
|
||||
this.darkTheme = darkTheme;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDrawState(TextPaint tp) {
|
||||
if (current) {
|
||||
if (isDarkThemeActive(context)) {
|
||||
if (darkTheme) {
|
||||
if (ColorUtil.INSTANCE.isColorDark(mainColor)) {
|
||||
tp.bgColor = Color.WHITE;
|
||||
tp.setColor(mainColor);
|
||||
|
@ -58,7 +54,7 @@ public class SearchSpan extends MetricAffectingSpan {
|
|||
if (ColorUtil.INSTANCE.getContrastRatio(mainColor, highlightColor) > 3d) {
|
||||
tp.setColor(mainColor);
|
||||
} else {
|
||||
if (isDarkThemeActive(context)) {
|
||||
if (darkTheme) {
|
||||
tp.setColor(Color.WHITE);
|
||||
} else {
|
||||
tp.setColor(Color.BLACK);
|
||||
|
@ -72,9 +68,4 @@ public class SearchSpan extends MetricAffectingSpan {
|
|||
public void updateMeasureState(@NonNull TextPaint tp) {
|
||||
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