allowing duplicate text content and handling header line breaks
This commit is contained in:
parent
2fab30060f
commit
918f186560
|
@ -1,6 +1,6 @@
|
|||
package app.dapk.st.core
|
||||
|
||||
data class RichText(val parts: Set<Part>) {
|
||||
data class RichText(val parts: List<Part>) {
|
||||
sealed interface Part {
|
||||
data class Normal(val content: String) : Part
|
||||
data class Link(val url: String, val label: String) : Part
|
||||
|
|
|
@ -30,7 +30,7 @@ import app.dapk.st.core.RichText
|
|||
import coil.compose.rememberAsyncImagePainter
|
||||
import coil.request.ImageRequest
|
||||
|
||||
private val ENCRYPTED_MESSAGE = RichText(setOf(RichText.Part.Normal("Encrypted message")))
|
||||
private val ENCRYPTED_MESSAGE = RichText(listOf(RichText.Part.Normal("Encrypted message")))
|
||||
|
||||
sealed interface BubbleModel {
|
||||
val event: Event
|
||||
|
|
|
@ -238,7 +238,7 @@ private fun RichText.toApp(): app.dapk.st.core.RichText {
|
|||
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())
|
||||
})
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName
|
|||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class RichText(@SerialName("parts") val parts: Set<Part>) {
|
||||
data class RichText(@SerialName("parts") val parts: List<Part>) {
|
||||
@Serializable
|
||||
sealed interface Part {
|
||||
@Serializable
|
||||
|
@ -27,7 +27,7 @@ data class RichText(@SerialName("parts") val parts: Set<Part>) {
|
|||
}
|
||||
|
||||
companion object {
|
||||
fun of(text: String) = RichText(setOf(RichText.Part.Normal(text)))
|
||||
fun of(text: String) = RichText(listOf(RichText.Part.Normal(text)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,9 +9,7 @@ private val SKIPPED_TAGS = setOf("mx-reply")
|
|||
|
||||
internal class HtmlParser {
|
||||
|
||||
fun test(startingFrom: Int, input: String): Int {
|
||||
return input.indexOf(TAG_OPEN, startingFrom)
|
||||
}
|
||||
fun test(startingFrom: Int, input: String) = input.indexOf(TAG_OPEN, startingFrom)
|
||||
|
||||
fun parseHtmlTags(input: String, searchIndex: Int, builder: PartBuilder, nestingLevel: Int = 0): SearchIndex = input.findTag(
|
||||
fromIndex = searchIndex,
|
||||
|
@ -32,19 +30,19 @@ internal class HtmlParser {
|
|||
tagClose.next()
|
||||
}
|
||||
|
||||
else -> parseTagWithContent(tagName, input, tagClose, builder, searchIndex, tagOpen, wholeTag, nestingLevel)
|
||||
else -> parseTagWithContent(input, tagName, tagClose, searchIndex, tagOpen, wholeTag, builder, nestingLevel)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
private fun parseTagWithContent(
|
||||
tagName: String,
|
||||
input: String,
|
||||
tagName: String,
|
||||
tagClose: Int,
|
||||
builder: PartBuilder,
|
||||
searchIndex: Int,
|
||||
tagOpen: Int,
|
||||
wholeTag: String,
|
||||
builder: PartBuilder,
|
||||
nestingLevel: Int
|
||||
): Int {
|
||||
val exitTag = "</$tagName>"
|
||||
|
@ -124,7 +122,7 @@ internal class HtmlParser {
|
|||
}
|
||||
|
||||
"h1", "h2", "h3", "h4", "h5" -> {
|
||||
builder.appendBold(tagContent)
|
||||
builder.appendBold(tagContent.trim())
|
||||
builder.appendNewline()
|
||||
exitTagCloseIndex
|
||||
}
|
||||
|
|
|
@ -7,9 +7,11 @@ internal class PartBuilder {
|
|||
|
||||
private var normalBuffer = StringBuilder()
|
||||
|
||||
private val parts = mutableSetOf<RichText.Part>()
|
||||
private val parts = mutableListOf<RichText.Part>()
|
||||
|
||||
fun appendText(value: String) {
|
||||
println("append text")
|
||||
|
||||
normalBuffer.append(value.cleanFirstTextLine())
|
||||
}
|
||||
|
||||
|
@ -35,11 +37,11 @@ internal class PartBuilder {
|
|||
parts.add(RichText.Part.Link(url, label ?: url))
|
||||
}
|
||||
|
||||
fun build(): Set<RichText.Part> {
|
||||
fun build(): List<RichText.Part> {
|
||||
flushNormalBuffer()
|
||||
val last = parts.last()
|
||||
if (last is RichText.Part.Normal) {
|
||||
parts.remove(last)
|
||||
parts.removeLast()
|
||||
val newContent = last.content.trimEnd()
|
||||
if (newContent.isNotEmpty()) {
|
||||
parts.add(last.copy(content = newContent))
|
||||
|
|
|
@ -15,69 +15,73 @@ class RichMessageParserTest {
|
|||
@Test
|
||||
fun `parses plain text`() = runParserTest(
|
||||
input = "Hello world!",
|
||||
expected = RichText(setOf(Normal("Hello world!")))
|
||||
expected = RichText(listOf(Normal("Hello world!")))
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `parses p tags`() = runParserTest(
|
||||
input = "<p>Hello world!</p><p>foo bar</p>after paragraph",
|
||||
expected = RichText(setOf(Normal("Hello world!\nfoo bar\nafter paragraph")))
|
||||
expected = RichText(listOf(Normal("Hello world!\nfoo bar\nafter paragraph")))
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `parses nesting within p tags`() = runParserTest(
|
||||
input = "<p><b>Hello world!</b></p>",
|
||||
expected = RichText(setOf(Bold("Hello world!")))
|
||||
expected = RichText(listOf(Bold("Hello world!")))
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `replaces quote entity`() = runParserTest(
|
||||
input = "Hello world! "foo bar"",
|
||||
expected = RichText(setOf(Normal("Hello world! \"foo bar\"")))
|
||||
expected = RichText(listOf(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")))
|
||||
expected = RichText(listOf(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("!")))
|
||||
expected = RichText(listOf(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")))
|
||||
expected = RichText(listOf(Normal("Hello "), Person(aUserId("@a-name:foo.bar"), "@a-name"), Normal(" world")))
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `parses header tags`() = runParserTest(
|
||||
Case(
|
||||
input = "<h1>hello</h1>",
|
||||
expected = RichText(setOf(Bold("hello")))
|
||||
expected = RichText(listOf(Bold("hello")))
|
||||
),
|
||||
Case(
|
||||
input = "<h1>hello</h1>text after title",
|
||||
expected = RichText(setOf(Bold("hello"), Normal("\ntext after title")))
|
||||
expected = RichText(listOf(Bold("hello"), Normal("\ntext after title")))
|
||||
),
|
||||
Case(
|
||||
input = "<h2>hello</h2>",
|
||||
expected = RichText(setOf(Bold("hello")))
|
||||
expected = RichText(listOf(Bold("hello")))
|
||||
),
|
||||
Case(
|
||||
input = "<h3>hello</h3>",
|
||||
expected = RichText(setOf(Bold("hello")))
|
||||
expected = RichText(listOf(Bold("hello")))
|
||||
),
|
||||
Case(
|
||||
input = "<h1>1</h1>\n<h2>1</h2>\n<h3>1</h3>\n",
|
||||
expected = RichText(listOf(Bold("1"), Normal("\n\n"), Bold("1"), Normal("\n\n"), Bold("1")))
|
||||
),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `replaces br tags`() = runParserTest(
|
||||
input = "Hello world!<br />next line<br />another line",
|
||||
expected = RichText(setOf(Normal("Hello world!\nnext line\nanother line")))
|
||||
expected = RichText(listOf(Normal("Hello world!\nnext line\nanother line")))
|
||||
)
|
||||
|
||||
|
||||
|
@ -85,19 +89,19 @@ class RichMessageParserTest {
|
|||
fun `parses lists`() = runParserTest(
|
||||
Case(
|
||||
input = "<ul><li>content in list item</li><li>another item in list</li></ul>",
|
||||
expected = RichText(setOf(Normal("- content in list item\n- another item in list")))
|
||||
expected = RichText(listOf(Normal("- content in list item\n- another item in list")))
|
||||
),
|
||||
Case(
|
||||
input = "<ol><li>content in list item</li><li>another item in list</li></ol>",
|
||||
expected = RichText(setOf(Normal("1. content in list item\n2. another item in list")))
|
||||
expected = RichText(listOf(Normal("1. content in list item\n2. another item in list")))
|
||||
),
|
||||
Case(
|
||||
input = """<ol><li value="5">content in list item</li><li>another item in list</li></ol>""",
|
||||
expected = RichText(setOf(Normal("5. content in list item\n6. another item in list")))
|
||||
expected = RichText(listOf(Normal("5. content in list item\n6. another item in list")))
|
||||
),
|
||||
Case(
|
||||
input = """<ol><li value="3">content in list item</li><li>another item in list</li><li value="10">another change</li><li>without value</li></ol>""",
|
||||
expected = RichText(setOf(Normal("3. content in list item\n4. another item in list\n10. another change\n11. without value")))
|
||||
expected = RichText(listOf(Normal("3. content in list item\n4. another item in list\n10. another change\n11. without value")))
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -105,19 +109,19 @@ class RichMessageParserTest {
|
|||
fun `parses urls`() = runParserTest(
|
||||
Case(
|
||||
input = "https://google.com",
|
||||
expected = RichText(setOf(Link("https://google.com", "https://google.com")))
|
||||
expected = RichText(listOf(Link("https://google.com", "https://google.com")))
|
||||
),
|
||||
Case(
|
||||
input = "https://google.com. after link",
|
||||
expected = RichText(setOf(Link("https://google.com", "https://google.com"), Normal(". after link")))
|
||||
expected = RichText(listOf(Link("https://google.com", "https://google.com"), Normal(". after link")))
|
||||
),
|
||||
Case(
|
||||
input = "ending sentence with url https://google.com.",
|
||||
expected = RichText(setOf(Normal("ending sentence with url "), Link("https://google.com", "https://google.com"), Normal(".")))
|
||||
expected = RichText(listOf(Normal("ending sentence with url "), Link("https://google.com", "https://google.com"), Normal(".")))
|
||||
),
|
||||
Case(
|
||||
input = "https://google.com<br>html after url",
|
||||
expected = RichText(setOf(Link("https://google.com", "https://google.com"), Normal("\nhtml after url")))
|
||||
expected = RichText(listOf(Link("https://google.com", "https://google.com"), Normal("\nhtml after url")))
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -131,7 +135,7 @@ class RichMessageParserTest {
|
|||
</mx-reply>
|
||||
Reply to message
|
||||
""".trimIndent(),
|
||||
expected = RichText(setOf(Normal("Reply to message")))
|
||||
expected = RichText(listOf(Normal("Reply to message")))
|
||||
)
|
||||
|
||||
@Test
|
||||
|
@ -142,19 +146,19 @@ class RichMessageParserTest {
|
|||
|
||||
Reply to message
|
||||
""".trimIndent(),
|
||||
expected = RichText(setOf(Normal("Reply to message")))
|
||||
expected = RichText(listOf(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")))
|
||||
expected = RichText(listOf(Italic("hello"), Normal(" "), Bold("world")))
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `parses invalid tags text`() = runParserTest(
|
||||
input = ">><foo> ><>> << more content",
|
||||
expected = RichText(setOf(Normal(">><foo> ><>> << more content")))
|
||||
expected = RichText(listOf(Normal(">><foo> ><>> << more content")))
|
||||
)
|
||||
|
||||
@Test
|
||||
|
@ -162,7 +166,7 @@ class RichMessageParserTest {
|
|||
Case(
|
||||
input = """hello <strong>wor</strong>ld""",
|
||||
expected = RichText(
|
||||
setOf(
|
||||
listOf(
|
||||
Normal("hello "),
|
||||
Bold("wor"),
|
||||
Normal("ld"),
|
||||
|
@ -176,7 +180,7 @@ class RichMessageParserTest {
|
|||
Case(
|
||||
input = """hello <em>wor</em>ld""",
|
||||
expected = RichText(
|
||||
setOf(
|
||||
listOf(
|
||||
Normal("hello "),
|
||||
Italic("wor"),
|
||||
Normal("ld"),
|
||||
|
@ -191,7 +195,7 @@ class RichMessageParserTest {
|
|||
Case(
|
||||
input = """hello <b><i>wor<i/><b/>ld""",
|
||||
expected = RichText(
|
||||
setOf(
|
||||
listOf(
|
||||
Normal("hello "),
|
||||
BoldItalic("wor"),
|
||||
Normal("ld"),
|
||||
|
@ -201,7 +205,7 @@ class RichMessageParserTest {
|
|||
Case(
|
||||
input = """<a href="www.google.com"><a href="www.google.com">www.google.com<a/><a/>""",
|
||||
expected = RichText(
|
||||
setOf(
|
||||
listOf(
|
||||
Link(url = "www.google.com", label = "www.google.com"),
|
||||
Link(url = "www.bing.com", label = "www.bing.com"),
|
||||
)
|
||||
|
@ -214,7 +218,7 @@ class RichMessageParserTest {
|
|||
Case(
|
||||
input = """hello world <a href="www.google.com">a link!</a> more content.""",
|
||||
expected = RichText(
|
||||
setOf(
|
||||
listOf(
|
||||
Normal("hello world "),
|
||||
Link(url = "www.google.com", label = "a link!"),
|
||||
Normal(" more content."),
|
||||
|
@ -224,7 +228,7 @@ class RichMessageParserTest {
|
|||
Case(
|
||||
input = """<a href="www.google.com">www.google.com</a><a href="www.bing.com">www.bing.com</a>""",
|
||||
expected = RichText(
|
||||
setOf(
|
||||
listOf(
|
||||
Link(url = "www.google.com", label = "www.google.com"),
|
||||
Link(url = "www.bing.com", label = "www.bing.com"),
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue