Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/stefan-niedermann/nextcloud-deck.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Niedermann <info@niedermann.it>2020-12-15 17:43:44 +0300
committerNiedermann IT-Dienstleistungen <stefan-niedermann@users.noreply.github.com>2020-12-17 18:14:29 +0300
commit6b4e57da9cc62c06511fc7a01b1c914b2a3144a0 (patch)
tree1d91323ababce89cdfb77cc31ecb67320df219df
parentc3a3ca561987f3eefbc8de8165192c2715c59714 (diff)
Enhance context based formatting
Signed-off-by: Stefan Niedermann <info@niedermann.it>
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/util/CommentsUtil.java4
-rw-r--r--app/src/test/java/it/niedermann/nextcloud/deck/CommentsUtilTest.java1
-rw-r--r--markdown/build.gradle1
-rw-r--r--markdown/src/androidTest/java/it/niedermann/android/markdown/ExampleInstrumentedTest.java26
-rw-r--r--markdown/src/androidTest/java/it/niedermann/android/markdown/MarkwonMarkdownUtilTest.java112
-rw-r--r--markdown/src/main/java/it/niedermann/android/markdown/markwon/MarkwonMarkdownEditor.java7
-rw-r--r--markdown/src/main/java/it/niedermann/android/markdown/markwon/MarkwonMarkdownUtil.java45
-rw-r--r--markdown/src/main/java/it/niedermann/android/markdown/markwon/format/ContextBasedFormattingCallback.java127
-rw-r--r--markdown/src/main/java/it/niedermann/android/markdown/markwon/format/ContextBasedRangeFormattingCallback.java130
-rw-r--r--markdown/src/main/java/it/niedermann/android/markdown/markwon/model/EListType.java21
-rw-r--r--markdown/src/main/java/it/niedermann/android/markdown/markwon/textwatcher/AutoContinuationTextWatcher.java53
-rw-r--r--markdown/src/main/res/drawable/ic_format_bold_black_24dp.xml11
-rw-r--r--markdown/src/main/res/drawable/ic_format_italic_black_24dp.xml11
-rw-r--r--markdown/src/main/res/drawable/ic_insert_link_black_24dp.xml11
-rw-r--r--markdown/src/main/res/menu/context_based_formatting.xml13
-rw-r--r--markdown/src/main/res/menu/context_based_range_formatting.xml19
-rw-r--r--markdown/src/main/res/values/strings.xml7
17 files changed, 525 insertions, 74 deletions
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/util/CommentsUtil.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/util/CommentsUtil.java
index 5251291c8..8fa24988d 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/util/CommentsUtil.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/util/CommentsUtil.java
@@ -1,12 +1,14 @@
package it.niedermann.nextcloud.deck.ui.card.comments.util;
+import androidx.annotation.Nullable;
import androidx.core.util.Pair;
public class CommentsUtil {
+ @Nullable
public static Pair<String, Integer> getUserNameForMentionProposal(String text, int cursorPosition) {
- Pair result = null;
+ Pair<String, Integer> result = null;
if (text != null) {
// find start of relevant substring
diff --git a/app/src/test/java/it/niedermann/nextcloud/deck/CommentsUtilTest.java b/app/src/test/java/it/niedermann/nextcloud/deck/CommentsUtilTest.java
index 47a23392e..bc76207e0 100644
--- a/app/src/test/java/it/niedermann/nextcloud/deck/CommentsUtilTest.java
+++ b/app/src/test/java/it/niedermann/nextcloud/deck/CommentsUtilTest.java
@@ -7,6 +7,7 @@ import it.niedermann.nextcloud.deck.ui.card.comments.util.CommentsUtil;
public class CommentsUtilTest {
+ @SuppressWarnings("ConstantConditions")
@Test
public void testMentionDiscovery() {
Assert.assertNull(CommentsUtil.getUserNameForMentionProposal("", 0));
diff --git a/markdown/build.gradle b/markdown/build.gradle
index c30c18a5a..d4ba32ef6 100644
--- a/markdown/build.gradle
+++ b/markdown/build.gradle
@@ -32,6 +32,7 @@ android {
dependencies {
implementation 'com.github.nextcloud:Android-SingleSignOn:0.5.4'
+ implementation 'com.github.stefan-niedermann:android-commons:0.1.1'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "androidx.lifecycle:lifecycle-livedata:2.2.0"
diff --git a/markdown/src/androidTest/java/it/niedermann/android/markdown/ExampleInstrumentedTest.java b/markdown/src/androidTest/java/it/niedermann/android/markdown/ExampleInstrumentedTest.java
deleted file mode 100644
index e765e0663..000000000
--- a/markdown/src/androidTest/java/it/niedermann/android/markdown/ExampleInstrumentedTest.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package it.niedermann.android.markdown;
-
-import android.content.Context;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.assertEquals;
-
-/**
- * Instrumented test, which will execute on an Android device.
- *
- * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
- */
-@RunWith(AndroidJUnit4.class)
-public class ExampleInstrumentedTest {
- @Test
- public void useAppContext() {
- // Context of the app under test.
- Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
- assertEquals("it.niedermann.android.markdown.test", appContext.getPackageName());
- }
-} \ No newline at end of file
diff --git a/markdown/src/androidTest/java/it/niedermann/android/markdown/MarkwonMarkdownUtilTest.java b/markdown/src/androidTest/java/it/niedermann/android/markdown/MarkwonMarkdownUtilTest.java
new file mode 100644
index 000000000..b20f7fd36
--- /dev/null
+++ b/markdown/src/androidTest/java/it/niedermann/android/markdown/MarkwonMarkdownUtilTest.java
@@ -0,0 +1,112 @@
+package it.niedermann.android.markdown;
+
+import junit.framework.TestCase;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil;
+
+/**
+ * Tests the NoteUtil
+ * Created by stefan on 06.10.15.
+ */
+public class MarkwonMarkdownUtilTest extends TestCase {
+
+ public void testGetStartOfLine() {
+ //language=md
+ StringBuilder test = new StringBuilder(
+ "# Test-Note\n" + // line start 0
+ "\n" + // line start 12
+ "- [ ] this is a test note\n" + // line start 13
+ "- [x] test\n" + // line start 39
+ "[test](https://example.com)\n" + // line start 50
+ "\n" + // line start 77
+ "\n" // line start 78
+ );
+
+ for (int i = 0; i < test.length(); i++) {
+ int startOfLine = MarkwonMarkdownUtil.getStartOfLine(test, i);
+ if (i <= 11) {
+ assertEquals(0, startOfLine);
+ } else if (i <= 12) {
+ assertEquals(12, startOfLine);
+ } else if (i <= 38) {
+ assertEquals(13, startOfLine);
+ } else if (i <= 49) {
+ assertEquals(39, startOfLine);
+ } else if (i <= 77) {
+ assertEquals(50, startOfLine);
+ } else if (i <= 78) {
+ assertEquals(78, startOfLine);
+ } else if (i <= 79) {
+ assertEquals(79, startOfLine);
+ }
+ }
+ }
+
+ public void testGetEndOfLine() {
+ //language=md
+ StringBuilder test = new StringBuilder(
+ "# Test-Note\n" + // line 0 - 11
+ "\n" + // line 12 - 12
+ "- [ ] this is a test note\n" + // line 13 - 38
+ "- [x] test\n" + // line start 39 - 49
+ "[test](https://example.com)\n" + // line 50 - 77
+ "\n" + // line 77 - 78
+ "\n" // line 78 - 79
+ );
+
+ for (int i = 0; i < test.length(); i++) {
+ int endOfLine = MarkwonMarkdownUtil.getEndOfLine(test, i);
+ if (i <= 11) {
+ assertEquals(11, endOfLine);
+ } else if (i <= 12) {
+ assertEquals(12, endOfLine);
+ } else if (i <= 38) {
+ assertEquals(38, endOfLine);
+ } else if (i <= 49) {
+ assertEquals(49, endOfLine);
+ } else if (i <= 77) {
+ assertEquals(77, endOfLine);
+ } else if (i <= 78) {
+ assertEquals(78, endOfLine);
+ } else if (i <= 79) {
+ assertEquals(79, endOfLine);
+ }
+ }
+ }
+
+ public void testLineStartsWithCheckbox() {
+ Map<String, Boolean> lines = new HashMap<>();
+ lines.put("- [ ] ", true);
+ lines.put("- [x] ", true);
+ lines.put("* [ ] ", true);
+ lines.put("* [x] ", true);
+ lines.put("- [ ]", true);
+ lines.put("- [x]", true);
+ lines.put("* [ ]", true);
+ lines.put("* [x]", true);
+
+ lines.put("-[ ] ", false);
+ lines.put("-[x] ", false);
+ lines.put("*[ ] ", false);
+ lines.put("*[x] ", false);
+ lines.put("-[ ]", false);
+ lines.put("-[x]", false);
+ lines.put("*[ ]", false);
+ lines.put("*[x]", false);
+
+ lines.put("- [] ", false);
+ lines.put("* [] ", false);
+ lines.put("- []", false);
+ lines.put("* []", false);
+
+ lines.put("-[] ", false);
+ lines.put("*[] ", false);
+ lines.put("-[]", false);
+ lines.put("*[]", false);
+
+ lines.forEach((key, value) -> assertEquals(value, (Boolean) MarkwonMarkdownUtil.lineStartsWithCheckbox(key)));
+ }
+} \ No newline at end of file
diff --git a/markdown/src/main/java/it/niedermann/android/markdown/markwon/MarkwonMarkdownEditor.java b/markdown/src/main/java/it/niedermann/android/markdown/markwon/MarkwonMarkdownEditor.java
index 7dd73a94b..345a75347 100644
--- a/markdown/src/main/java/it/niedermann/android/markdown/markwon/MarkwonMarkdownEditor.java
+++ b/markdown/src/main/java/it/niedermann/android/markdown/markwon/MarkwonMarkdownEditor.java
@@ -1,6 +1,7 @@
package it.niedermann.android.markdown.markwon;
import android.content.Context;
+import android.os.Build;
import android.util.AttributeSet;
import android.widget.EditText;
@@ -15,6 +16,8 @@ import io.noties.markwon.editor.MarkwonEditor;
import io.noties.markwon.editor.handler.EmphasisEditHandler;
import io.noties.markwon.editor.handler.StrongEmphasisEditHandler;
import it.niedermann.android.markdown.MarkdownEditor;
+import it.niedermann.android.markdown.markwon.format.ContextBasedFormattingCallback;
+import it.niedermann.android.markdown.markwon.format.ContextBasedRangeFormattingCallback;
import it.niedermann.android.markdown.markwon.handler.BlockQuoteEditHandler;
import it.niedermann.android.markdown.markwon.handler.CodeBlockEditHandler;
import it.niedermann.android.markdown.markwon.handler.CodeEditHandler;
@@ -47,6 +50,10 @@ public class MarkwonMarkdownEditor extends AppCompatEditText implements Markdown
.useEditHandler(new HeadingEditHandler())
.build();
addTextChangedListener(new AutoContinuationTextWatcher(editor, this));
+ setCustomSelectionActionModeCallback(new ContextBasedRangeFormattingCallback(this));
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ setCustomInsertionActionModeCallback(new ContextBasedFormattingCallback(this));
+ }
}
@Override
diff --git a/markdown/src/main/java/it/niedermann/android/markdown/markwon/MarkwonMarkdownUtil.java b/markdown/src/main/java/it/niedermann/android/markdown/markwon/MarkwonMarkdownUtil.java
index d33401b47..fdaf405da 100644
--- a/markdown/src/main/java/it/niedermann/android/markdown/markwon/MarkwonMarkdownUtil.java
+++ b/markdown/src/main/java/it/niedermann/android/markdown/markwon/MarkwonMarkdownUtil.java
@@ -21,6 +21,7 @@ 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.NextcloudMentionsPlugin;
import it.niedermann.android.markdown.markwon.plugins.ThemePlugin;
@@ -62,4 +63,48 @@ public class MarkwonMarkdownUtil {
return initMarkwonViewer(context)
.usePlugin(NextcloudMentionsPlugin.create(context, mentions));
}
+
+ 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;
+ }
+ }
+ return null;
+ }
+
+ 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) {
+ return line.startsWith(listType.checkboxUnchecked) || line.startsWith(listType.checkboxChecked);
+ }
+
+ public static boolean lineStartsWithList(@NonNull String line, @NonNull EListType listType) {
+ return line.startsWith(listType.listSymbol);
+ }
}
diff --git a/markdown/src/main/java/it/niedermann/android/markdown/markwon/format/ContextBasedFormattingCallback.java b/markdown/src/main/java/it/niedermann/android/markdown/markwon/format/ContextBasedFormattingCallback.java
new file mode 100644
index 000000000..6bed4b1db
--- /dev/null
+++ b/markdown/src/main/java/it/niedermann/android/markdown/markwon/format/ContextBasedFormattingCallback.java
@@ -0,0 +1,127 @@
+package it.niedermann.android.markdown.markwon.format;
+
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import it.niedermann.android.markdown.R;
+import it.niedermann.android.markdown.markwon.MarkwonMarkdownEditor;
+import it.niedermann.android.markdown.markwon.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;
+
+public class ContextBasedFormattingCallback implements ActionMode.Callback {
+
+ private static final String TAG = ContextBasedFormattingCallback.class.getSimpleName();
+
+ private final MarkwonMarkdownEditor editText;
+
+ public ContextBasedFormattingCallback(MarkwonMarkdownEditor editText) {
+ this.editText = editText;
+ }
+
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ mode.getMenuInflater().inflate(R.menu.context_based_formatting, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ final CharSequence text = editText.getText();
+ if (text != null) {
+ final int cursorPosition = editText.getSelectionStart();
+ if (cursorPosition >= 0 && cursorPosition <= text.length()) {
+ final int startOfLine = getStartOfLine(text, cursorPosition);
+ final int endOfLine = getEndOfLine(text, startOfLine);
+ final String line = text.subSequence(startOfLine, endOfLine).toString();
+ if (lineStartsWithCheckbox(line)) {
+ menu.findItem(R.id.checkbox).setVisible(false);
+ Log.i(TAG, "Hide checkbox menu item because line starts already with checkbox");
+ }
+ } else {
+ Log.e(TAG, "SelectionStart is " + cursorPosition + ". Expected to be between 0 and " + text.length());
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ int itemId = item.getItemId();
+ if (itemId == R.id.checkbox) {
+ insertCheckbox();
+ return true;
+ } else if (itemId == R.id.link) {
+ insertLink();
+ return true;
+ }
+ return false;
+ }
+
+ private void insertCheckbox() {
+ final CharSequence text = editText.getText();
+ if (text == null) {
+ editText.setMarkdownString(EListType.DASH.checkboxUncheckedWithTrailingSpace);
+ editText.setSelection(EListType.DASH.checkboxUncheckedWithTrailingSpace.length());
+ } else {
+ final int originalCursorPosition = editText.getSelectionStart();
+ final int startOfLine = getStartOfLine(text, originalCursorPosition);
+ Log.i(TAG, "Inserting checkbox at position " + startOfLine);
+ final CharSequence part1 = text.subSequence(0, startOfLine);
+ final CharSequence part2 = text.subSequence(startOfLine, text.length());
+ editText.setMarkdownString(TextUtils.concat(part1, EListType.DASH.checkboxUncheckedWithTrailingSpace, part2));
+ editText.setSelection(originalCursorPosition + EListType.DASH.checkboxUncheckedWithTrailingSpace.length());
+ }
+ }
+
+ private void insertLink() {
+ final CharSequence text = editText.getText();
+ final SpannableStringBuilder ssb = new SpannableStringBuilder(text);
+ final boolean textToFormatIsLink;
+ final int start;
+ int end;
+ if (text == null) {
+ start = end = 0;
+ textToFormatIsLink = false;
+ } else {
+ start = text.length();
+ end = start;
+ textToFormatIsLink = TextUtils.indexOf(text.subSequence(start, end), "http") == 0;
+ }
+
+ if (textToFormatIsLink) {
+ Log.i(TAG, "Inserting link description for position " + start + " to " + end);
+ ssb.insert(end, ")");
+ ssb.insert(start, "[](");
+ } else {
+ String clipboardURL = ClipboardUtil.INSTANCE.getClipboardURLorNull(editText.getContext());
+ if (clipboardURL != null) {
+ Log.i(TAG, "Inserting link from clipboard at position " + start + " to " + end + ": " + clipboardURL);
+ ssb.insert(end, "](" + clipboardURL + ")");
+ end += clipboardURL.length();
+ } else {
+ Log.i(TAG, "Inserting empty link for position " + start + " to " + end);
+ ssb.insert(end, "]()");
+ }
+ ssb.insert(start, "[");
+ }
+ editText.setMarkdownString(ssb);
+ if (textToFormatIsLink) {
+ editText.setSelection(start + 1);
+ } else {
+ editText.setSelection(end + 3); // after <end>](
+ }
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ // Nothing to do here...
+ }
+}
diff --git a/markdown/src/main/java/it/niedermann/android/markdown/markwon/format/ContextBasedRangeFormattingCallback.java b/markdown/src/main/java/it/niedermann/android/markdown/markwon/format/ContextBasedRangeFormattingCallback.java
new file mode 100644
index 000000000..20b26674d
--- /dev/null
+++ b/markdown/src/main/java/it/niedermann/android/markdown/markwon/format/ContextBasedRangeFormattingCallback.java
@@ -0,0 +1,130 @@
+package it.niedermann.android.markdown.markwon.format;
+
+import android.graphics.Typeface;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.text.style.StyleSpan;
+import android.util.SparseIntArray;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import androidx.annotation.Nullable;
+
+import it.niedermann.android.markdown.R;
+import it.niedermann.android.markdown.markwon.MarkwonMarkdownEditor;
+import it.niedermann.android.util.ClipboardUtil;
+
+public class ContextBasedRangeFormattingCallback implements ActionMode.Callback {
+
+ private static final String TAG = ContextBasedRangeFormattingCallback.class.getSimpleName();
+
+ private final MarkwonMarkdownEditor editText;
+
+ public ContextBasedRangeFormattingCallback(MarkwonMarkdownEditor editText) {
+ this.editText = editText;
+ }
+
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ mode.getMenuInflater().inflate(R.menu.context_based_range_formatting, menu);
+
+ final SparseIntArray styleFormatMap = new SparseIntArray();
+ styleFormatMap.append(R.id.bold, Typeface.BOLD);
+ styleFormatMap.append(R.id.italic, Typeface.ITALIC);
+
+ MenuItem item;
+ CharSequence title;
+ SpannableStringBuilder ssb;
+
+ for (int i = 0; i < styleFormatMap.size(); i++) {
+ item = menu.findItem(styleFormatMap.keyAt(i));
+ title = item.getTitle();
+ ssb = new SpannableStringBuilder(title);
+ ssb.setSpan(new StyleSpan(styleFormatMap.valueAt(i)), 0, title.length(), 0);
+ item.setTitle(ssb);
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ // TODO hide actions if not available?
+ return false;
+ }
+
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ final SpannableStringBuilder ssb = new SpannableStringBuilder(editText.getText());
+ final int itemId = item.getItemId();
+ final int start = editText.getSelectionStart();
+ int end = editText.getSelectionEnd();
+
+ if (itemId == R.id.bold) {
+ final String punctuation = "**";
+ final boolean hasAlreadyMarkdown = hasAlreadyMarkdown(editText.getText(), start, end, punctuation);
+ if (hasAlreadyMarkdown) {
+ removeMarkdown(ssb, start, end, punctuation);
+ } else {
+ ssb.insert(end, punctuation);
+ ssb.insert(start, punctuation);
+ }
+ editText.setMarkdownString(ssb);
+ editText.setSelection(hasAlreadyMarkdown ? end - punctuation.length() : end + punctuation.length() * 2);
+ return true;
+ } else if (itemId == R.id.italic) {
+ final String punctuation = "*";
+ final boolean hasAlreadyMarkdown = hasAlreadyMarkdown(editText.getText(), start, end, punctuation);
+ if (hasAlreadyMarkdown) {
+ removeMarkdown(ssb, start, end, punctuation);
+ } else {
+ ssb.insert(end, punctuation);
+ ssb.insert(start, punctuation);
+ }
+ editText.setMarkdownString(ssb);
+ editText.setSelection(hasAlreadyMarkdown ? end - punctuation.length() : end + punctuation.length() * 2);
+ return true;
+ } else if (itemId == R.id.link) {
+ final CharSequence text = editText.getText();
+ final boolean textToFormatIsLink = text != null && TextUtils.indexOf(text.subSequence(start, end), "http") == 0;
+ if (textToFormatIsLink) {
+ ssb.insert(end, ")");
+ ssb.insert(start, "[](");
+ } else {
+ String clipboardURL = ClipboardUtil.INSTANCE.getClipboardURLorNull(editText.getContext());
+ if (clipboardURL != null) {
+ ssb.insert(end, "](" + clipboardURL + ")");
+ end += clipboardURL.length();
+ } else {
+ ssb.insert(end, "]()");
+ }
+ ssb.insert(start, "[");
+ }
+ editText.setMarkdownString(ssb);
+ if (textToFormatIsLink) {
+ editText.setSelection(start + 1);
+ } else {
+ editText.setSelection(end + 3); // after <end>](
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ // Nothing to do here...
+ }
+
+ private static boolean hasAlreadyMarkdown(@Nullable CharSequence text, int start, int end, String punctuation) {
+ return text != null && (start > punctuation.length() && punctuation.contentEquals(text.subSequence(start - punctuation.length(), start)) &&
+ text.length() > end + punctuation.length() && punctuation.contentEquals(text.subSequence(end, end + punctuation.length())));
+ }
+
+ private static void removeMarkdown(SpannableStringBuilder ssb, int start, int end, String punctuation) {
+ // FIXME disabled, because it does not work properly and might cause data loss
+ ssb.delete(start - punctuation.length(), start);
+ ssb.delete(end - punctuation.length(), end);
+ }
+}
diff --git a/markdown/src/main/java/it/niedermann/android/markdown/markwon/model/EListType.java b/markdown/src/main/java/it/niedermann/android/markdown/markwon/model/EListType.java
new file mode 100644
index 000000000..a03987fda
--- /dev/null
+++ b/markdown/src/main/java/it/niedermann/android/markdown/markwon/model/EListType.java
@@ -0,0 +1,21 @@
+package it.niedermann.android.markdown.markwon.model;
+
+public enum EListType {
+ STAR('*'),
+ DASH('-'),
+ PLUS('+');
+
+ public final String listSymbol;
+ public final String listSymbolWithTrailingSpace;
+ public final String checkboxChecked;
+ public final String checkboxUnchecked;
+ public final String checkboxUncheckedWithTrailingSpace;
+
+ EListType(char listSymbol) {
+ this.listSymbol = String.valueOf(listSymbol);
+ this.listSymbolWithTrailingSpace = listSymbol + " ";
+ this.checkboxChecked = listSymbolWithTrailingSpace + "[x]";
+ this.checkboxUnchecked = listSymbolWithTrailingSpace + "[ ]";
+ this.checkboxUncheckedWithTrailingSpace = checkboxUnchecked + " ";
+ }
+} \ No newline at end of file
diff --git a/markdown/src/main/java/it/niedermann/android/markdown/markwon/textwatcher/AutoContinuationTextWatcher.java b/markdown/src/main/java/it/niedermann/android/markdown/markwon/textwatcher/AutoContinuationTextWatcher.java
index 384dc78c5..795812f4c 100644
--- a/markdown/src/main/java/it/niedermann/android/markdown/markwon/textwatcher/AutoContinuationTextWatcher.java
+++ b/markdown/src/main/java/it/niedermann/android/markdown/markwon/textwatcher/AutoContinuationTextWatcher.java
@@ -10,6 +10,12 @@ import java.util.concurrent.Executors;
import io.noties.markwon.editor.MarkwonEditor;
import io.noties.markwon.editor.MarkwonEditorTextWatcher;
import it.niedermann.android.markdown.markwon.MarkwonMarkdownEditor;
+import it.niedermann.android.markdown.markwon.model.EListType;
+
+import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.getListItemIfIsEmpty;
+import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.getStartOfLine;
+import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.lineStartsWithCheckbox;
+import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.lineStartsWithList;
/**
* Automatically continues lists and checkbox lists when pressing enter
@@ -88,51 +94,4 @@ public class AutoContinuationTextWatcher implements TextWatcher {
}
}
}
-
- private static int getStartOfLine(@NonNull CharSequence s, int cursorPosition) {
- int startOfLine = cursorPosition;
- while (startOfLine > 0 && s.charAt(startOfLine - 1) != '\n') {
- startOfLine--;
- }
- return startOfLine;
- }
-
- private 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;
- }
- }
- return null;
- }
-
- private static boolean lineStartsWithCheckbox(@NonNull String line, @NonNull EListType listType) {
- return line.startsWith(listType.checkboxUnchecked) || line.startsWith(listType.checkboxChecked);
- }
-
- private static boolean lineStartsWithList(@NonNull String line, @NonNull EListType listType) {
- return line.startsWith(listType.listSymbol);
- }
-
- enum EListType {
- STAR('*'),
- DASH('-'),
- PLUS('+');
-
- final String listSymbol;
- final String listSymbolWithTrailingSpace;
- final String checkboxChecked;
- final String checkboxUnchecked;
- final String checkboxUncheckedWithTrailingSpace;
-
- EListType(char listSymbol) {
- this.listSymbol = String.valueOf(listSymbol);
- this.listSymbolWithTrailingSpace = listSymbol + " ";
- this.checkboxChecked = listSymbolWithTrailingSpace + "[x]";
- this.checkboxUnchecked = listSymbolWithTrailingSpace + "[ ]";
- this.checkboxUncheckedWithTrailingSpace = checkboxUnchecked + " ";
- }
- }
}
diff --git a/markdown/src/main/res/drawable/ic_format_bold_black_24dp.xml b/markdown/src/main/res/drawable/ic_format_bold_black_24dp.xml
new file mode 100644
index 000000000..0b06db879
--- /dev/null
+++ b/markdown/src/main/res/drawable/ic_format_bold_black_24dp.xml
@@ -0,0 +1,11 @@
+<vector android:autoMirrored="true"
+ android:height="24dp"
+ android:tint="#757575"
+ android:viewportHeight="24.0"
+ android:viewportWidth="24.0"
+ android:width="24dp"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.6,10.79c0.97,-0.67 1.65,-1.77 1.65,-2.79 0,-2.26 -1.75,-4 -4,-4L7,4v14h7.04c2.09,0 3.71,-1.7 3.71,-3.79 0,-1.52 -0.86,-2.82 -2.15,-3.42zM10,6.5h3c0.83,0 1.5,0.67 1.5,1.5s-0.67,1.5 -1.5,1.5h-3v-3zM13.5,15.5L10,15.5v-3h3.5c0.83,0 1.5,0.67 1.5,1.5s-0.67,1.5 -1.5,1.5z" />
+</vector>
diff --git a/markdown/src/main/res/drawable/ic_format_italic_black_24dp.xml b/markdown/src/main/res/drawable/ic_format_italic_black_24dp.xml
new file mode 100644
index 000000000..20680318b
--- /dev/null
+++ b/markdown/src/main/res/drawable/ic_format_italic_black_24dp.xml
@@ -0,0 +1,11 @@
+<vector android:autoMirrored="true"
+ android:height="24dp"
+ android:tint="#757575"
+ android:viewportHeight="24.0"
+ android:viewportWidth="24.0"
+ android:width="24dp"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,4v3h2.21l-3.42,8H6v3h8v-3h-2.21l3.42,-8H18V4z" />
+</vector>
diff --git a/markdown/src/main/res/drawable/ic_insert_link_black_24dp.xml b/markdown/src/main/res/drawable/ic_insert_link_black_24dp.xml
new file mode 100644
index 000000000..91b42f7fb
--- /dev/null
+++ b/markdown/src/main/res/drawable/ic_insert_link_black_24dp.xml
@@ -0,0 +1,11 @@
+<vector android:autoMirrored="true"
+ android:height="24dp"
+ android:tint="#757575"
+ android:viewportHeight="24.0"
+ android:viewportWidth="24.0"
+ android:width="24dp"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3.9,12c0,-1.71 1.39,-3.1 3.1,-3.1h4L11,7L7,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h4v-1.9L7,15.1c-1.71,0 -3.1,-1.39 -3.1,-3.1zM8,13h8v-2L8,11v2zM17,7h-4v1.9h4c1.71,0 3.1,1.39 3.1,3.1s-1.39,3.1 -3.1,3.1h-4L13,17h4c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5z" />
+</vector>
diff --git a/markdown/src/main/res/menu/context_based_formatting.xml b/markdown/src/main/res/menu/context_based_formatting.xml
new file mode 100644
index 000000000..9bf2d779f
--- /dev/null
+++ b/markdown/src/main/res/menu/context_based_formatting.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:id="@+id/checkbox"
+ android:title="@string/simple_checkbox"
+ app:showAsAction="ifRoom" />
+ <item
+ android:id="@+id/link"
+ android:icon="@drawable/ic_insert_link_black_24dp"
+ android:title="@string/simple_link"
+ app:showAsAction="ifRoom" />
+</menu> \ No newline at end of file
diff --git a/markdown/src/main/res/menu/context_based_range_formatting.xml b/markdown/src/main/res/menu/context_based_range_formatting.xml
new file mode 100644
index 000000000..68d701f83
--- /dev/null
+++ b/markdown/src/main/res/menu/context_based_range_formatting.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:id="@+id/italic"
+ android:icon="@drawable/ic_format_italic_black_24dp"
+ android:title="@string/simple_italic"
+ app:showAsAction="ifRoom" />
+ <item
+ android:id="@+id/bold"
+ android:icon="@drawable/ic_format_bold_black_24dp"
+ android:title="@string/simple_bold"
+ app:showAsAction="ifRoom" />
+ <item
+ android:id="@+id/link"
+ android:icon="@drawable/ic_insert_link_black_24dp"
+ android:title="@string/simple_link"
+ app:showAsAction="ifRoom" />
+</menu> \ No newline at end of file
diff --git a/markdown/src/main/res/values/strings.xml b/markdown/src/main/res/values/strings.xml
new file mode 100644
index 000000000..f78123e3b
--- /dev/null
+++ b/markdown/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="simple_checkbox">Checkbox</string>
+ <string name="simple_bold">Bold</string>
+ <string name="simple_link">Link</string>
+ <string name="simple_italic">Italic</string>
+</resources> \ No newline at end of file