mirror of
https://github.com/ouchadam/small-talk.git
synced 2025-02-17 12:40:44 +01:00
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
|
package app.dapk.st.core
|
||||||
|
|
||||||
data class RichText(val parts: Set<Part>) {
|
data class RichText(val parts: List<Part>) {
|
||||||
sealed interface Part {
|
sealed interface Part {
|
||||||
data class Normal(val content: String) : Part
|
data class Normal(val content: String) : Part
|
||||||
data class Link(val url: String, val label: 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.compose.rememberAsyncImagePainter
|
||||||
import coil.request.ImageRequest
|
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 {
|
sealed interface BubbleModel {
|
||||||
val event: Event
|
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.Normal -> app.dapk.st.core.RichText.Part.Normal(it.content)
|
||||||
is RichText.Part.Person -> app.dapk.st.core.RichText.Part.Person(it.userId.value)
|
is RichText.Part.Person -> app.dapk.st.core.RichText.Part.Person(it.userId.value)
|
||||||
}
|
}
|
||||||
}.toSet())
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName
|
|||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class RichText(@SerialName("parts") val parts: Set<Part>) {
|
data class RichText(@SerialName("parts") val parts: List<Part>) {
|
||||||
@Serializable
|
@Serializable
|
||||||
sealed interface Part {
|
sealed interface Part {
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -27,7 +27,7 @@ data class RichText(@SerialName("parts") val parts: Set<Part>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
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 {
|
internal class HtmlParser {
|
||||||
|
|
||||||
fun test(startingFrom: Int, input: String): Int {
|
fun test(startingFrom: Int, input: String) = input.indexOf(TAG_OPEN, startingFrom)
|
||||||
return input.indexOf(TAG_OPEN, startingFrom)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun parseHtmlTags(input: String, searchIndex: Int, builder: PartBuilder, nestingLevel: Int = 0): SearchIndex = input.findTag(
|
fun parseHtmlTags(input: String, searchIndex: Int, builder: PartBuilder, nestingLevel: Int = 0): SearchIndex = input.findTag(
|
||||||
fromIndex = searchIndex,
|
fromIndex = searchIndex,
|
||||||
@ -32,19 +30,19 @@ internal class HtmlParser {
|
|||||||
tagClose.next()
|
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(
|
private fun parseTagWithContent(
|
||||||
tagName: String,
|
|
||||||
input: String,
|
input: String,
|
||||||
|
tagName: String,
|
||||||
tagClose: Int,
|
tagClose: Int,
|
||||||
builder: PartBuilder,
|
|
||||||
searchIndex: Int,
|
searchIndex: Int,
|
||||||
tagOpen: Int,
|
tagOpen: Int,
|
||||||
wholeTag: String,
|
wholeTag: String,
|
||||||
|
builder: PartBuilder,
|
||||||
nestingLevel: Int
|
nestingLevel: Int
|
||||||
): Int {
|
): Int {
|
||||||
val exitTag = "</$tagName>"
|
val exitTag = "</$tagName>"
|
||||||
@ -124,7 +122,7 @@ internal class HtmlParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
"h1", "h2", "h3", "h4", "h5" -> {
|
"h1", "h2", "h3", "h4", "h5" -> {
|
||||||
builder.appendBold(tagContent)
|
builder.appendBold(tagContent.trim())
|
||||||
builder.appendNewline()
|
builder.appendNewline()
|
||||||
exitTagCloseIndex
|
exitTagCloseIndex
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,11 @@ internal class PartBuilder {
|
|||||||
|
|
||||||
private var normalBuffer = StringBuilder()
|
private var normalBuffer = StringBuilder()
|
||||||
|
|
||||||
private val parts = mutableSetOf<RichText.Part>()
|
private val parts = mutableListOf<RichText.Part>()
|
||||||
|
|
||||||
fun appendText(value: String) {
|
fun appendText(value: String) {
|
||||||
|
println("append text")
|
||||||
|
|
||||||
normalBuffer.append(value.cleanFirstTextLine())
|
normalBuffer.append(value.cleanFirstTextLine())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,11 +37,11 @@ internal class PartBuilder {
|
|||||||
parts.add(RichText.Part.Link(url, label ?: url))
|
parts.add(RichText.Part.Link(url, label ?: url))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun build(): Set<RichText.Part> {
|
fun build(): List<RichText.Part> {
|
||||||
flushNormalBuffer()
|
flushNormalBuffer()
|
||||||
val last = parts.last()
|
val last = parts.last()
|
||||||
if (last is RichText.Part.Normal) {
|
if (last is RichText.Part.Normal) {
|
||||||
parts.remove(last)
|
parts.removeLast()
|
||||||
val newContent = last.content.trimEnd()
|
val newContent = last.content.trimEnd()
|
||||||
if (newContent.isNotEmpty()) {
|
if (newContent.isNotEmpty()) {
|
||||||
parts.add(last.copy(content = newContent))
|
parts.add(last.copy(content = newContent))
|
||||||
|
@ -15,69 +15,73 @@ class RichMessageParserTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `parses plain text`() = runParserTest(
|
fun `parses plain text`() = runParserTest(
|
||||||
input = "Hello world!",
|
input = "Hello world!",
|
||||||
expected = RichText(setOf(Normal("Hello world!")))
|
expected = RichText(listOf(Normal("Hello world!")))
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `parses p tags`() = runParserTest(
|
fun `parses p tags`() = runParserTest(
|
||||||
input = "<p>Hello world!</p><p>foo bar</p>after paragraph",
|
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
|
@Test
|
||||||
fun `parses nesting within p tags`() = runParserTest(
|
fun `parses nesting within p tags`() = runParserTest(
|
||||||
input = "<p><b>Hello world!</b></p>",
|
input = "<p><b>Hello world!</b></p>",
|
||||||
expected = RichText(setOf(Bold("Hello world!")))
|
expected = RichText(listOf(Bold("Hello world!")))
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `replaces quote entity`() = runParserTest(
|
fun `replaces quote entity`() = runParserTest(
|
||||||
input = "Hello world! "foo bar"",
|
input = "Hello world! "foo bar"",
|
||||||
expected = RichText(setOf(Normal("Hello world! \"foo bar\"")))
|
expected = RichText(listOf(Normal("Hello world! \"foo bar\"")))
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `replaces apostrophe entity`() = runParserTest(
|
fun `replaces apostrophe entity`() = runParserTest(
|
||||||
input = "Hello world! foo's bar",
|
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
|
@Test
|
||||||
fun `replaces people`() = runParserTest(
|
fun `replaces people`() = runParserTest(
|
||||||
input = "Hello <@my-name:a-domain.foo>!",
|
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
|
@Test
|
||||||
fun `replaces matrixdotto with person`() = runParserTest(
|
fun `replaces matrixdotto with person`() = runParserTest(
|
||||||
input = """Hello <a href="https://matrix.to/#/@a-name:foo.bar">a-name</a>: world""",
|
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
|
@Test
|
||||||
fun `parses header tags`() = runParserTest(
|
fun `parses header tags`() = runParserTest(
|
||||||
Case(
|
Case(
|
||||||
input = "<h1>hello</h1>",
|
input = "<h1>hello</h1>",
|
||||||
expected = RichText(setOf(Bold("hello")))
|
expected = RichText(listOf(Bold("hello")))
|
||||||
),
|
),
|
||||||
Case(
|
Case(
|
||||||
input = "<h1>hello</h1>text after title",
|
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(
|
Case(
|
||||||
input = "<h2>hello</h2>",
|
input = "<h2>hello</h2>",
|
||||||
expected = RichText(setOf(Bold("hello")))
|
expected = RichText(listOf(Bold("hello")))
|
||||||
),
|
),
|
||||||
Case(
|
Case(
|
||||||
input = "<h3>hello</h3>",
|
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
|
@Test
|
||||||
fun `replaces br tags`() = runParserTest(
|
fun `replaces br tags`() = runParserTest(
|
||||||
input = "Hello world!<br />next line<br />another line",
|
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(
|
fun `parses lists`() = runParserTest(
|
||||||
Case(
|
Case(
|
||||||
input = "<ul><li>content in list item</li><li>another item in list</li></ul>",
|
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(
|
Case(
|
||||||
input = "<ol><li>content in list item</li><li>another item in list</li></ol>",
|
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(
|
Case(
|
||||||
input = """<ol><li value="5">content in list item</li><li>another item in list</li></ol>""",
|
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(
|
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>""",
|
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(
|
fun `parses urls`() = runParserTest(
|
||||||
Case(
|
Case(
|
||||||
input = "https://google.com",
|
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(
|
Case(
|
||||||
input = "https://google.com. after link",
|
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(
|
Case(
|
||||||
input = "ending sentence with url https://google.com.",
|
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(
|
Case(
|
||||||
input = "https://google.com<br>html after url",
|
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>
|
</mx-reply>
|
||||||
Reply to message
|
Reply to message
|
||||||
""".trimIndent(),
|
""".trimIndent(),
|
||||||
expected = RichText(setOf(Normal("Reply to message")))
|
expected = RichText(listOf(Normal("Reply to message")))
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -142,19 +146,19 @@ class RichMessageParserTest {
|
|||||||
|
|
||||||
Reply to message
|
Reply to message
|
||||||
""".trimIndent(),
|
""".trimIndent(),
|
||||||
expected = RichText(setOf(Normal("Reply to message")))
|
expected = RichText(listOf(Normal("Reply to message")))
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `parses styling text`() = runParserTest(
|
fun `parses styling text`() = runParserTest(
|
||||||
input = "<em>hello</em> <strong>world</strong>",
|
input = "<em>hello</em> <strong>world</strong>",
|
||||||
expected = RichText(setOf(Italic("hello"), Normal(" "), Bold("world")))
|
expected = RichText(listOf(Italic("hello"), Normal(" "), Bold("world")))
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `parses invalid tags text`() = runParserTest(
|
fun `parses invalid tags text`() = runParserTest(
|
||||||
input = ">><foo> ><>> << more content",
|
input = ">><foo> ><>> << more content",
|
||||||
expected = RichText(setOf(Normal(">><foo> ><>> << more content")))
|
expected = RichText(listOf(Normal(">><foo> ><>> << more content")))
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -162,7 +166,7 @@ class RichMessageParserTest {
|
|||||||
Case(
|
Case(
|
||||||
input = """hello <strong>wor</strong>ld""",
|
input = """hello <strong>wor</strong>ld""",
|
||||||
expected = RichText(
|
expected = RichText(
|
||||||
setOf(
|
listOf(
|
||||||
Normal("hello "),
|
Normal("hello "),
|
||||||
Bold("wor"),
|
Bold("wor"),
|
||||||
Normal("ld"),
|
Normal("ld"),
|
||||||
@ -176,7 +180,7 @@ class RichMessageParserTest {
|
|||||||
Case(
|
Case(
|
||||||
input = """hello <em>wor</em>ld""",
|
input = """hello <em>wor</em>ld""",
|
||||||
expected = RichText(
|
expected = RichText(
|
||||||
setOf(
|
listOf(
|
||||||
Normal("hello "),
|
Normal("hello "),
|
||||||
Italic("wor"),
|
Italic("wor"),
|
||||||
Normal("ld"),
|
Normal("ld"),
|
||||||
@ -191,7 +195,7 @@ class RichMessageParserTest {
|
|||||||
Case(
|
Case(
|
||||||
input = """hello <b><i>wor<i/><b/>ld""",
|
input = """hello <b><i>wor<i/><b/>ld""",
|
||||||
expected = RichText(
|
expected = RichText(
|
||||||
setOf(
|
listOf(
|
||||||
Normal("hello "),
|
Normal("hello "),
|
||||||
BoldItalic("wor"),
|
BoldItalic("wor"),
|
||||||
Normal("ld"),
|
Normal("ld"),
|
||||||
@ -201,7 +205,7 @@ class RichMessageParserTest {
|
|||||||
Case(
|
Case(
|
||||||
input = """<a href="www.google.com"><a href="www.google.com">www.google.com<a/><a/>""",
|
input = """<a href="www.google.com"><a href="www.google.com">www.google.com<a/><a/>""",
|
||||||
expected = RichText(
|
expected = RichText(
|
||||||
setOf(
|
listOf(
|
||||||
Link(url = "www.google.com", label = "www.google.com"),
|
Link(url = "www.google.com", label = "www.google.com"),
|
||||||
Link(url = "www.bing.com", label = "www.bing.com"),
|
Link(url = "www.bing.com", label = "www.bing.com"),
|
||||||
)
|
)
|
||||||
@ -214,7 +218,7 @@ class RichMessageParserTest {
|
|||||||
Case(
|
Case(
|
||||||
input = """hello world <a href="www.google.com">a link!</a> more content.""",
|
input = """hello world <a href="www.google.com">a link!</a> more content.""",
|
||||||
expected = RichText(
|
expected = RichText(
|
||||||
setOf(
|
listOf(
|
||||||
Normal("hello world "),
|
Normal("hello world "),
|
||||||
Link(url = "www.google.com", label = "a link!"),
|
Link(url = "www.google.com", label = "a link!"),
|
||||||
Normal(" more content."),
|
Normal(" more content."),
|
||||||
@ -224,7 +228,7 @@ class RichMessageParserTest {
|
|||||||
Case(
|
Case(
|
||||||
input = """<a href="www.google.com">www.google.com</a><a href="www.bing.com">www.bing.com</a>""",
|
input = """<a href="www.google.com">www.google.com</a><a href="www.bing.com">www.bing.com</a>""",
|
||||||
expected = RichText(
|
expected = RichText(
|
||||||
setOf(
|
listOf(
|
||||||
Link(url = "www.google.com", label = "www.google.com"),
|
Link(url = "www.google.com", label = "www.google.com"),
|
||||||
Link(url = "www.bing.com", label = "www.bing.com"),
|
Link(url = "www.bing.com", label = "www.bing.com"),
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user