Merge pull request #7113 from thundernest/wrap_pasted_url
Wrap pasted URI in angle brackets
This commit is contained in:
commit
6ff3d4087f
4 changed files with 92 additions and 0 deletions
|
@ -31,4 +31,20 @@ object UriMatcher {
|
|||
parser.parseUri(text, startIndex)
|
||||
}.filterNotNull().toList()
|
||||
}
|
||||
|
||||
@Suppress("ReturnCount")
|
||||
fun isValidUri(text: CharSequence): Boolean {
|
||||
val matchResult = URI_SCHEME.matchAt(text, 0) ?: return false
|
||||
|
||||
val matchGroup = matchResult.groups[1]!!
|
||||
if (matchGroup.range.first != 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
val scheme = matchGroup.value.lowercase()
|
||||
val parser = SUPPORTED_URIS[scheme] ?: throw AssertionError("Scheme not found: $scheme")
|
||||
val uriMatch = parser.parseUri(text, startPos = 0) ?: return false
|
||||
|
||||
return uriMatch.startIndex == 0 && uriMatch.endIndex == text.length
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
package com.fsck.k9.message.html
|
||||
|
||||
import assertk.Assert
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.hasSize
|
||||
import assertk.assertions.isEmpty
|
||||
import assertk.assertions.isEqualTo
|
||||
import assertk.assertions.isFalse
|
||||
import assertk.assertions.isNotEqualTo
|
||||
import assertk.assertions.isTrue
|
||||
import org.junit.Test
|
||||
|
||||
class UriMatcherTest {
|
||||
|
@ -82,6 +85,27 @@ class UriMatcherTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `valid URIs`() {
|
||||
assertThat("http://domain.example").isValidUri()
|
||||
assertThat("https://domain.example/").isValidUri()
|
||||
assertThat("mailto:user@domain.example").isValidUri()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `not URIs`() {
|
||||
assertThat("some text here").isNotValidUri()
|
||||
assertThat("some text including the string http:").isNotValidUri()
|
||||
assertThat("some text including an email address without URI scheme: user@domain.example").isNotValidUri()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `valid URIs surrounded by other characters`() {
|
||||
assertThat("text https://domain.example/").isNotValidUri()
|
||||
assertThat("https://domain.example/ text").isNotValidUri()
|
||||
assertThat("<https://domain.example/>").isNotValidUri()
|
||||
}
|
||||
|
||||
private fun assertNoMatch(text: String) {
|
||||
val uriMatches = UriMatcher.findUris(text)
|
||||
assertThat(uriMatches).isEmpty()
|
||||
|
@ -104,3 +128,11 @@ class UriMatcherTest {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Assert<String>.isValidUri() = given { actual ->
|
||||
assertThat(UriMatcher.isValidUri(actual)).isTrue()
|
||||
}
|
||||
|
||||
private fun Assert<String>.isNotValidUri() = given { actual ->
|
||||
assertThat(UriMatcher.isValidUri(actual)).isFalse()
|
||||
}
|
||||
|
|
|
@ -109,6 +109,7 @@ import com.fsck.k9.search.LocalSearch;
|
|||
import com.fsck.k9.ui.R;
|
||||
import com.fsck.k9.ui.base.K9Activity;
|
||||
import com.fsck.k9.ui.base.ThemeManager;
|
||||
import com.fsck.k9.ui.compose.WrapUriTextWatcher;
|
||||
import com.fsck.k9.ui.compose.QuotedMessageMvpView;
|
||||
import com.fsck.k9.ui.compose.QuotedMessagePresenter;
|
||||
import com.fsck.k9.ui.helper.SizeFormatter;
|
||||
|
@ -360,10 +361,12 @@ public class MessageCompose extends K9Activity implements OnClickListener,
|
|||
replyToView.addTextChangedListener(draftNeedsChangingTextWatcher);
|
||||
recipientMvpView.addTextChangedListener(draftNeedsChangingTextWatcher);
|
||||
quotedMessageMvpView.addTextChangedListener(draftNeedsChangingTextWatcher);
|
||||
quotedMessageMvpView.addTextChangedListener(new WrapUriTextWatcher());
|
||||
|
||||
subjectView.addTextChangedListener(draftNeedsChangingTextWatcher);
|
||||
|
||||
messageContentView.addTextChangedListener(draftNeedsChangingTextWatcher);
|
||||
messageContentView.addTextChangedListener(new WrapUriTextWatcher());
|
||||
|
||||
/*
|
||||
* We set this to invisible by default. Other methods will turn it back on if it's
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
package com.fsck.k9.ui.compose
|
||||
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import com.fsck.k9.message.html.UriMatcher
|
||||
|
||||
private const val NO_INDEX = -1
|
||||
private const val MINIMUM_URI_LENGTH = 2 // scheme name + colon
|
||||
|
||||
/**
|
||||
* Wraps inserted URIs in angle brackets.
|
||||
*/
|
||||
class WrapUriTextWatcher : TextWatcher {
|
||||
private var insertedStartIndex = NO_INDEX
|
||||
private var insertedEndIndex = NO_INDEX
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
|
||||
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||
if (s == null || count < MINIMUM_URI_LENGTH) {
|
||||
insertedStartIndex = NO_INDEX
|
||||
} else {
|
||||
insertedStartIndex = start
|
||||
insertedEndIndex = start + count
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
// Changing s below will lead to this TextWatcher being invoked again. Keep necessary state local.
|
||||
val insertedStartIndex = insertedStartIndex
|
||||
val insertedEndIndex = insertedEndIndex
|
||||
|
||||
if (s != null && insertedStartIndex != NO_INDEX) {
|
||||
val insertedText = s.subSequence(insertedStartIndex, insertedEndIndex)
|
||||
if (UriMatcher.isValidUri(insertedText)) {
|
||||
s.insert(insertedEndIndex, ">")
|
||||
s.insert(insertedStartIndex, "<")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue