Skip to content

Commit

Permalink
Fix bare URLs are getting cleared on Markdown
Browse files Browse the repository at this point in the history
  • Loading branch information
MohamedRejeb committed Sep 21, 2024
1 parent d53040b commit 7fe40f8
Show file tree
Hide file tree
Showing 4 changed files with 255 additions and 168 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,32 +55,36 @@ internal object RichTextStateMarkdownParser : RichTextStateParser<String> {
currentRichSpan = null
}

fun onText(text: String) {
if (text.isEmpty()) return

if (richParagraphList.isEmpty())
richParagraphList.add(RichParagraph())

val currentRichParagraph = richParagraphList.last()
val safeCurrentRichSpan = currentRichSpan ?: RichSpan(paragraph = currentRichParagraph)

if (safeCurrentRichSpan.children.isEmpty()) {
safeCurrentRichSpan.text += text
} else {
val newRichSpan = RichSpan(
paragraph = currentRichParagraph,
parent = safeCurrentRichSpan,
)
newRichSpan.text = text
safeCurrentRichSpan.children.add(newRichSpan)
}

if (currentRichSpan == null) {
currentRichSpan = safeCurrentRichSpan
currentRichParagraph.children.add(safeCurrentRichSpan)
}
}

encodeMarkdownToRichText(
markdown = input,
onText = { text ->
if (text.isEmpty()) return@encodeMarkdownToRichText

if (richParagraphList.isEmpty())
richParagraphList.add(RichParagraph())

val currentRichParagraph = richParagraphList.last()
val safeCurrentRichSpan = currentRichSpan ?: RichSpan(paragraph = currentRichParagraph)

if (safeCurrentRichSpan.children.isEmpty()) {
safeCurrentRichSpan.text += text
} else {
val newRichSpan = RichSpan(
paragraph = currentRichParagraph,
parent = safeCurrentRichSpan,
)
newRichSpan.text = text
safeCurrentRichSpan.children.add(newRichSpan)
}

if (currentRichSpan == null) {
currentRichSpan = safeCurrentRichSpan
currentRichParagraph.children.add(safeCurrentRichSpan)
}
onText(text)
},
onOpenNode = { node ->
openedNodes.add(node)
Expand Down Expand Up @@ -121,7 +125,22 @@ internal object RichTextStateMarkdownParser : RichTextStateParser<String> {
newRichSpan.spanStyle = tagSpanStyle ?: SpanStyle()
newRichSpan.richSpanStyle = richSpanStyle

if (currentRichSpan != null) {
// Avoid nesting if the current rich span doesn't add a styling
if (currentRichSpan?.isEmpty() == true) {
currentRichSpan = null
currentRichParagraph.children.removeLast()
}

if (
currentRichSpan?.fullSpanStyle == SpanStyle() &&
currentRichSpan?.fullStyle is RichSpanStyle.Default
) {
currentRichSpan = null
}

if (
currentRichSpan != null
) {
newRichSpan.parent = currentRichSpan
currentRichSpan?.children?.add(newRichSpan)
currentRichSpan = newRichSpan
Expand All @@ -136,6 +155,10 @@ internal object RichTextStateMarkdownParser : RichTextStateParser<String> {
)
newRichSpan.text = "$".repeat(node.endOffset - node.startOffset)
}

if (node.type == GFMTokenTypes.GFM_AUTOLINK) {
onText(node.getTextInNode(input).toString())
}
},
onCloseNode = { node ->
openedNodes.removeLastOrNull()
Expand Down Expand Up @@ -375,9 +398,17 @@ internal object RichTextStateMarkdownParser : RichTextStateParser<String> {
markdown: String,
): RichSpanStyle {
return when (node.type) {
GFMTokenTypes.GFM_AUTOLINK -> {
val destination = node.getTextInNode(markdown).toString()
RichSpanStyle.Link(url = destination)
}
MarkdownElementTypes.INLINE_LINK -> {
val destination = node.findChildOfType(MarkdownElementTypes.LINK_DESTINATION)?.getTextInNode(markdown)?.toString()
RichSpanStyle.Link(url = destination ?: "")
val destination = node
.findChildOfType(MarkdownElementTypes.LINK_DESTINATION)
?.getTextInNode(markdown)
?.toString()
.orEmpty()
RichSpanStyle.Link(url = destination)
}
MarkdownElementTypes.CODE_SPAN -> RichSpanStyle.Code()
else -> RichSpanStyle.Default
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ class RichTextStateTest {
assertEquals(richTextState.currentSpanStyle, SpanStyle(fontWeight = FontWeight.Bold))
}

@OptIn(ExperimentalRichTextApi::class)
@Test
fun testClearSpanStyles() {
val richTextState = RichTextState(
Expand Down Expand Up @@ -421,6 +422,7 @@ class RichTextStateTest {
assertEquals(defaultSpan, richTextState.currentRichSpanStyle)
}

@OptIn(ExperimentalRichTextApi::class)
@Test
fun testGetSpanStyle() {
val richTextState = RichTextState(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package com.mohamedrejeb.richeditor.parser.markdown

import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextDecoration
import com.mohamedrejeb.richeditor.annotation.ExperimentalRichTextApi
import com.mohamedrejeb.richeditor.model.RichSpan
import com.mohamedrejeb.richeditor.model.RichTextState
import com.mohamedrejeb.richeditor.paragraph.RichParagraph
import kotlin.test.Test
import kotlin.test.assertEquals

class RichTextStateMarkdownParserDecodeTest {

/**
* Decode tests
*/

@Test
fun testDecodeBold() {
val expectedText = "Hello World!"
val state = RichTextState()

state.toggleSpanStyle(SpanStyle(fontWeight = FontWeight.Bold))
state.onTextFieldValueChange(
TextFieldValue(
text = expectedText,
selection = TextRange(expectedText.length)
)
)

val markdown = RichTextStateMarkdownParser.decode(state)
val actualText = state.annotatedString.text

assertEquals(
expected = expectedText,
actual = actualText,
)

assertEquals(
expected = "**$expectedText**",
actual = markdown
)
}

@Test
fun testDecodeItalic() {
val expectedText = "Hello World!"
val state = RichTextState()

state.toggleSpanStyle(SpanStyle(fontStyle = FontStyle.Italic))
state.onTextFieldValueChange(
TextFieldValue(
text = expectedText,
selection = TextRange(expectedText.length)
)
)

val markdown = RichTextStateMarkdownParser.decode(state)
val actualText = state.annotatedString.text

assertEquals(
expected = expectedText,
actual = actualText,
)

assertEquals(
expected = "*$expectedText*",
actual = markdown
)
}

@Test
fun testDecodeLineThrough() {
val expectedText = "Hello World!"
val state = RichTextState()

state.toggleSpanStyle(SpanStyle(textDecoration = TextDecoration.LineThrough))
state.onTextFieldValueChange(
TextFieldValue(
text = expectedText,
selection = TextRange(expectedText.length)
)
)

val markdown = RichTextStateMarkdownParser.decode(state)
val actualText = state.annotatedString.text

assertEquals(
expected = expectedText,
actual = actualText,
)

assertEquals(
expected = "~~$expectedText~~",
actual = markdown
)
}

@OptIn(ExperimentalRichTextApi::class)
@Test
fun testDecodeUnderline() {
val expectedText = "Hello World!"
val state = RichTextState(
initialRichParagraphList = listOf(
RichParagraph().also {
it.children.add(
RichSpan(
text = expectedText,
paragraph = it,
spanStyle = SpanStyle(textDecoration = TextDecoration.Underline)
)
)
}
)
)

val markdown = RichTextStateMarkdownParser.decode(state)
val actualText = state.annotatedString.text

assertEquals(
expected = expectedText,
actual = actualText,
)

assertEquals(
expected = "<u>$expectedText</u>",
actual = markdown
)
}

@OptIn(ExperimentalRichTextApi::class)
@Test
fun testDecodeLineBreak() {
val state = RichTextState(
initialRichParagraphList = listOf(
RichParagraph().also {
it.children.add(
RichSpan(
text = "Hello",
paragraph = it
)
)
},
RichParagraph(),
RichParagraph(),
RichParagraph(),
RichParagraph().also {
it.children.add(
RichSpan(
text = "World!",
paragraph = it
)
)
}
)
)

val markdown = RichTextStateMarkdownParser.decode(state)

assertEquals(
expected =
"""
Hello
<br>
<br>
World!
""".trimIndent(),
actual = markdown,
)
}

}
Loading

0 comments on commit 7fe40f8

Please sign in to comment.