Skip to content

Commit

Permalink
Merge pull request #411 from MohamedRejeb/1.x
Browse files Browse the repository at this point in the history
Trim paragraphs on converting to and from Markdown
  • Loading branch information
MohamedRejeb authored Nov 10, 2024
2 parents 9ab31f3 + 6ea7704 commit a12f181
Show file tree
Hide file tree
Showing 8 changed files with 412 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.mohamedrejeb.richeditor.annotation.ExperimentalRichTextApi
import com.mohamedrejeb.richeditor.paragraph.RichParagraph
import com.mohamedrejeb.richeditor.utils.customMerge
import com.mohamedrejeb.richeditor.utils.isSpecifiedFieldsEquals
import kotlin.collections.indices

/**
* A rich span is a part of a rich paragraph.
Expand Down Expand Up @@ -232,6 +233,94 @@ internal class RichSpan(
return null
}

/**
* Trim the start of the rich span
*
* @return True if the rich span is empty after trimming, false otherwise
*/
internal fun trimStart(): Boolean {
if (richSpanStyle is RichSpanStyle.Image)
return false

if (isBlank()) {
text = ""
children.clear()
return true
}

text = text.trimStart()

if (text.isNotEmpty())
return false

var isEmpty = true
val toRemoveIndices = mutableListOf<Int>()

for (i in children.indices) {
val richSpan = children[i]

val isChildEmpty = richSpan.trimStart()

if (isChildEmpty) {
// Remove the child if it's empty
toRemoveIndices.add(i)
} else {
isEmpty = false
break
}
}

toRemoveIndices.fastForEachReversed {
children.removeAt(it)
}

return isEmpty
}

internal fun trimEnd(): Boolean {
val isImage = richSpanStyle is RichSpanStyle.Image

if (isImage)
return false

val isChildrenBlank = isChildrenBlank() && !isImage

if (text.isBlank() && isChildrenBlank) {
text = ""
children.clear()
return true
}

if (isChildrenBlank) {
children.clear()
text = text.trimEnd()
return false
}

var isEmpty = true
val toRemoveIndices = mutableListOf<Int>()

for (i in children.indices.reversed()) {
val richSpan = children[i]

val isChildEmpty = richSpan.trimEnd()

if (isChildEmpty) {
// Remove the child if it's empty
toRemoveIndices.add(i)
} else {
isEmpty = false
break
}
}

toRemoveIndices.fastForEach {
children.removeAt(it)
}

return isEmpty
}

/**
* Get the last non-empty child
*
Expand Down Expand Up @@ -497,6 +586,6 @@ internal class RichSpan(
)

override fun toString(): String {
return "richSpan(text='$text', textRange=$textRange, fullTextRange=$fullTextRange), richSpanStyle=$richSpanStyle)"
return "richSpan(text='$text', textRange=$textRange, fullTextRange=$fullTextRange, fontSize=${spanStyle.fontSize}, fontWeight=${spanStyle.fontWeight}, richSpanStyle=$richSpanStyle)"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,47 @@ internal class RichParagraph(
return firstChild
}

/**
* Trim the rich paragraph
*/
fun trim() {
val isEmpty = trimStart()
if (!isEmpty)
trimEnd()
}

/**
* Trim the start of the rich paragraph
*
* @return True if the rich paragraph is empty after trimming, false otherwise
*/
fun trimStart(): Boolean {
children.fastForEach { richSpan ->
val isEmpty = richSpan.trimStart()

if (!isEmpty)
return false
}

return true
}

/**
* Trim the end of the rich paragraph
*
* @return True if the rich paragraph is empty after trimming, false otherwise
*/
fun trimEnd(): Boolean {
children.fastForEachReversed { richSpan ->
val isEmpty = richSpan.trimEnd()

if (!isEmpty)
return false
}

return true
}

/**
* Update the paragraph of the children recursively
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,8 @@ internal object RichTextStateMarkdownParser : RichTextStateParser<String> {
var lastBrParagraphIndex = -1

richParagraphList.forEachIndexed { i, paragraph ->
paragraph.trim()

val isEmpty = paragraph.isEmpty()
val isBr = i in brParagraphIndices

Expand Down Expand Up @@ -341,16 +343,20 @@ internal object RichTextStateMarkdownParser : RichTextStateParser<String> {
// Append paragraph start text
builder.appendParagraphStartText(richParagraph)

var isHeading = false

richParagraph.getFirstNonEmptyChild()?.let { firstNonEmptyChild ->
if (firstNonEmptyChild.text.isNotEmpty()) {
// Append markdown line start text
builder.append(getMarkdownLineStartTextFromFirstRichSpan(firstNonEmptyChild))
val lineStartText = getMarkdownLineStartTextFromFirstRichSpan(firstNonEmptyChild)
builder.append(lineStartText)
isHeading = lineStartText.startsWith('#')
}
}

// Append paragraph children
richParagraph.children.fastForEach { richSpan ->
builder.append(decodeRichSpanToMarkdown(richSpan))
builder.append(decodeRichSpanToMarkdown(richSpan, isHeading))
}

// Append line break if needed
Expand All @@ -371,7 +377,10 @@ internal object RichTextStateMarkdownParser : RichTextStateParser<String> {
}

@OptIn(ExperimentalRichTextApi::class)
private fun decodeRichSpanToMarkdown(richSpan: RichSpan): String {
private fun decodeRichSpanToMarkdown(
richSpan: RichSpan,
isHeading: Boolean,
): String {
val stringBuilder = StringBuilder()

// Check if span is empty
Expand All @@ -384,7 +393,8 @@ internal object RichTextStateMarkdownParser : RichTextStateParser<String> {
val markdownOpen = mutableListOf<String>()
val markdownClose = mutableListOf<String>()

if ((richSpan.spanStyle.fontWeight?.weight ?: 400) > 400) {
// Ignore adding bold `**` for heading since it's already bold
if ((richSpan.spanStyle.fontWeight?.weight ?: 400) > 400 && !isHeading) {
markdownOpen += "**"
markdownClose += "**"
}
Expand All @@ -405,7 +415,7 @@ internal object RichTextStateMarkdownParser : RichTextStateParser<String> {
}

// Append markdown open
if (!isBlank)
if (!isBlank && markdownOpen.isNotEmpty())
stringBuilder.append(markdownOpen.joinToString(separator = ""))

// Apply rich span style to markdown
Expand All @@ -416,11 +426,11 @@ internal object RichTextStateMarkdownParser : RichTextStateParser<String> {

// Append children
richSpan.children.fastForEach { child ->
stringBuilder.append(decodeRichSpanToMarkdown(child))
stringBuilder.append(decodeRichSpanToMarkdown(child, isHeading))
}

// Append markdown close
if (!isBlank)
if (!isBlank && markdownClose.isNotEmpty())
stringBuilder.append(markdownClose.reversed().joinToString(separator = ""))

return stringBuilder.toString()
Expand Down

This file was deleted.

Loading

0 comments on commit a12f181

Please # to comment.