handling more text parsing cases for fallbacks and urls
This commit is contained in:
parent
fddcdaa50c
commit
9476fc5814
|
@ -7,6 +7,7 @@ data class RichText(val parts: Set<Part>) {
|
|||
data class Bold(val content: String) : Part
|
||||
data class Italic(val content: String) : Part
|
||||
data class BoldItalic(val content: String) : Part
|
||||
data class Person(val userId: String) : Part
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,14 +17,11 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.*
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
@ -265,6 +262,10 @@ val hyperLinkStyle = SpanStyle(
|
|||
textDecoration = TextDecoration.Underline
|
||||
)
|
||||
|
||||
val nameStyle = SpanStyle(
|
||||
color = Color(0xff64B5F6),
|
||||
)
|
||||
|
||||
fun RichText.toAnnotatedText() = buildAnnotatedString {
|
||||
parts.forEach {
|
||||
when (it) {
|
||||
|
@ -278,6 +279,9 @@ fun RichText.toAnnotatedText() = buildAnnotatedString {
|
|||
}
|
||||
|
||||
is RichText.Part.Normal -> append(it.content)
|
||||
is RichText.Part.Person -> withStyle(nameStyle) {
|
||||
append("@${it.userId.substringBefore(':').removePrefix("@")}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -295,4 +299,4 @@ private fun AuthorName(event: BubbleModel.Event, bubble: BubbleMeta) {
|
|||
@Composable
|
||||
private fun BubbleMeta.textColor(): Color {
|
||||
return if (this.isSelf) SmallTalkTheme.extendedColors.onSelfBubble else SmallTalkTheme.extendedColors.onOthersBubble
|
||||
}
|
||||
}
|
||||
|
|
|
@ -234,6 +234,7 @@ private fun RichText.toApp(): app.dapk.st.core.RichText {
|
|||
is RichText.Part.Italic -> app.dapk.st.core.RichText.Part.Italic(it.content)
|
||||
is RichText.Part.Link -> app.dapk.st.core.RichText.Part.Link(it.url, it.label)
|
||||
is RichText.Part.Normal -> app.dapk.st.core.RichText.Part.Normal(it.content)
|
||||
is RichText.Part.Person -> app.dapk.st.core.RichText.Part.Person(it.userId.value)
|
||||
}
|
||||
}.toSet())
|
||||
}
|
||||
|
|
|
@ -21,6 +21,9 @@ data class RichText(@SerialName("parts") val parts: Set<Part>) {
|
|||
|
||||
@Serializable
|
||||
data class BoldItalic(@SerialName("content") val content: String) : Part
|
||||
|
||||
@Serializable
|
||||
data class Person(@SerialName("user_id") val userId: UserId, @SerialName("display_name") val displayName: String) : Part
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -35,5 +38,6 @@ fun RichText.asString() = parts.joinToString(separator = "") {
|
|||
is RichText.Part.Italic -> it.content
|
||||
is RichText.Part.Link -> it.label
|
||||
is RichText.Part.Normal -> it.content
|
||||
is RichText.Part.Person -> it.userId.value
|
||||
}
|
||||
}
|
|
@ -2,10 +2,14 @@ package app.dapk.st.matrix.sync.internal.sync
|
|||
|
||||
import app.dapk.st.matrix.common.RichText
|
||||
import app.dapk.st.matrix.common.RichText.Part.*
|
||||
import app.dapk.st.matrix.common.UserId
|
||||
|
||||
class RichMessageParser {
|
||||
|
||||
fun parse(input: String): RichText {
|
||||
fun parse(source: String): RichText {
|
||||
val input = source
|
||||
.removeHtmlEntities()
|
||||
.dropTextFallback()
|
||||
return kotlin.runCatching {
|
||||
val buffer = mutableSetOf<RichText.Part>()
|
||||
var openIndex = 0
|
||||
|
@ -21,12 +25,23 @@ class RichMessageParser {
|
|||
val wholeTag = input.substring(foundIndex, closeIndex + 1)
|
||||
val tagName = wholeTag.substring(1, wholeTag.indexOfFirst { it == '>' || it == ' ' })
|
||||
|
||||
if (tagName.startsWith('@')) {
|
||||
if (openIndex != foundIndex) {
|
||||
buffer.add(Normal(input.substring(openIndex, foundIndex)))
|
||||
}
|
||||
buffer.add(Person(UserId(tagName), tagName))
|
||||
println(tagName)
|
||||
openIndex = foundIndex + wholeTag.length
|
||||
lastStartIndex = openIndex
|
||||
continue
|
||||
}
|
||||
|
||||
if (tagName == "br") {
|
||||
if (openIndex != foundIndex) {
|
||||
buffer.add(Normal(input.substring(openIndex, foundIndex)))
|
||||
}
|
||||
buffer.add(Normal("\n"))
|
||||
openIndex = foundIndex + "<br />".length
|
||||
openIndex = foundIndex + wholeTag.length
|
||||
lastStartIndex = openIndex
|
||||
continue
|
||||
}
|
||||
|
@ -39,6 +54,14 @@ class RichMessageParser {
|
|||
if (exitIndex == -1) {
|
||||
openIndex++
|
||||
} else {
|
||||
when (tagName) {
|
||||
"mx-reply" -> {
|
||||
openIndex = exitIndex + exitTag.length
|
||||
lastStartIndex = openIndex
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (openIndex != foundIndex) {
|
||||
buffer.add(Normal(input.substring(openIndex, foundIndex)))
|
||||
}
|
||||
|
@ -49,7 +72,16 @@ class RichMessageParser {
|
|||
when (tagName) {
|
||||
"a" -> {
|
||||
val findHrefUrl = wholeTag.substringAfter("href=").replace("\"", "").removeSuffix(">")
|
||||
buffer.add(Link(url = findHrefUrl, label = tagContent))
|
||||
if (findHrefUrl.startsWith("https://matrix.to/#/@")) {
|
||||
val userId = UserId(findHrefUrl.substringAfter("https://matrix.to/#/").substringBeforeLast("\""))
|
||||
buffer.add(Person(userId, "@${tagContent.removePrefix("@")}"))
|
||||
if (input.getOrNull(openIndex) == ':') {
|
||||
openIndex++
|
||||
lastStartIndex = openIndex
|
||||
}
|
||||
} else {
|
||||
buffer.add(Link(url = findHrefUrl, label = tagContent))
|
||||
}
|
||||
}
|
||||
|
||||
"b" -> buffer.add(Bold(tagContent))
|
||||
|
@ -84,12 +116,6 @@ class RichMessageParser {
|
|||
|
||||
else -> {
|
||||
val substring = input.substring(urlIndex, urlEndIndex)
|
||||
|
||||
val last = substring.last()
|
||||
if (last == '.' || last == ',') {
|
||||
substring.dropLast(1)
|
||||
}
|
||||
|
||||
val url = substring.removeSuffix(".").removeSuffix(",")
|
||||
buffer.add(Link(url = url, label = url))
|
||||
openIndex = if (substring.endsWith('.') || substring.endsWith(',')) urlEndIndex - 1 else urlEndIndex
|
||||
|
@ -114,3 +140,7 @@ class RichMessageParser {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
private fun String.removeHtmlEntities() = this.replace(""", "\"").replace("'", "'")
|
||||
|
||||
private fun String.dropTextFallback() = this.lines().dropWhile { it.startsWith("> ") || it.isEmpty() }.joinToString("")
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package app.dapk.st.matrix.sync.internal.sync
|
||||
|
||||
import app.dapk.st.matrix.common.RichText
|
||||
import app.dapk.st.matrix.common.RichText.Part.Link
|
||||
import app.dapk.st.matrix.common.RichText.Part.Normal
|
||||
import app.dapk.st.matrix.common.RichText.Part.*
|
||||
import fixture.aUserId
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
|
@ -23,6 +23,30 @@ class RichMessageParserTest {
|
|||
expected = RichText(setOf(Normal("Hello world! "), Normal("foo bar"), Normal(" after paragraph")))
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `replaces quote entity`() = runParserTest(
|
||||
input = "Hello world! "foo bar"",
|
||||
expected = RichText(setOf(Normal("Hello world! \"foo bar\"")))
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `replaces apostrophe entity`() = runParserTest(
|
||||
input = "Hello world! foo's bar",
|
||||
expected = RichText(setOf(Normal("Hello world! foo's bar")))
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `replaces people`() = runParserTest(
|
||||
input = "Hello <@my-name:a-domain.foo>!",
|
||||
expected = RichText(setOf(Normal("Hello "), Person(aUserId("@my-name:a-domain.foo"), "@my-name:a-domain.foo"), Normal("!")))
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `replaces matrixdotto with person`() = runParserTest(
|
||||
input = """Hello <a href="https://matrix.to/#/@a-name:foo.bar>a-name</a>: world""",
|
||||
expected = RichText(setOf(Normal("Hello "), Person(aUserId("@a-name:foo.bar"), "@a-name"), Normal(" world")))
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `skips header tags`() = runParserTest(
|
||||
Case(
|
||||
|
@ -62,15 +86,39 @@ class RichMessageParserTest {
|
|||
)
|
||||
|
||||
@Test
|
||||
fun `parses styling text`() = runParserTest(
|
||||
input = "<em>hello</em> <strong>world</strong>",
|
||||
expected = RichText(setOf(RichText.Part.Italic("hello"), Normal(" "), RichText.Part.Bold("world")))
|
||||
fun `removes reply fallback`() = runParserTest(
|
||||
input = """
|
||||
<mx-reply>
|
||||
<blockquote>
|
||||
Original message
|
||||
</blockquote>
|
||||
</mx-reply>
|
||||
Reply to message
|
||||
""".trimIndent(),
|
||||
expected = RichText(setOf(Normal("Reply to message")))
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `parses raw reply text`() = runParserTest(
|
||||
input = "> <@a-matrix-id:domain.foo> This is a reply",
|
||||
expected = RichText(setOf(Normal("> <@a-matrix-id:domain.foo> This is a reply")))
|
||||
fun `removes text fallback`() = runParserTest(
|
||||
input = """
|
||||
> <@user:domain.foo> Original message
|
||||
> Some more content
|
||||
|
||||
Reply to message
|
||||
""".trimIndent(),
|
||||
expected = RichText(setOf(Normal("Reply to message")))
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `parses styling text`() = runParserTest(
|
||||
input = "<em>hello</em> <strong>world</strong>",
|
||||
expected = RichText(setOf(Italic("hello"), Normal(" "), Bold("world")))
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `parses invalid tags text`() = runParserTest(
|
||||
input = ">><foo> ><>> << more content",
|
||||
expected = RichText(setOf(Normal(">><foo> ><>> << more content")))
|
||||
)
|
||||
|
||||
@Test
|
||||
|
@ -80,7 +128,7 @@ class RichMessageParserTest {
|
|||
expected = RichText(
|
||||
setOf(
|
||||
Normal("hello "),
|
||||
RichText.Part.Bold("wor"),
|
||||
Bold("wor"),
|
||||
Normal("ld"),
|
||||
)
|
||||
)
|
||||
|
@ -94,7 +142,7 @@ class RichMessageParserTest {
|
|||
expected = RichText(
|
||||
setOf(
|
||||
Normal("hello "),
|
||||
RichText.Part.Italic("wor"),
|
||||
Italic("wor"),
|
||||
Normal("ld"),
|
||||
)
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue