Skip to content
Open

1.x #619

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
848d2e3
Save clipboard progress
MohamedRejeb Mar 7, 2025
d23e342
Update gitignore
MohamedRejeb Mar 7, 2025
8380493
Update gitignore
MohamedRejeb Mar 7, 2025
f6f76e8
Update gitignore
MohamedRejeb Mar 25, 2025
9c9b9bb
Update rich editor to 1.0.0-rc12
MohamedRejeb Mar 25, 2025
fba7a72
Merge remote-tracking branch 'refs/remotes/origin/main' into 1.x
MohamedRejeb Mar 25, 2025
8a9dd07
Merge remote-tracking branch 'origin/main' into 1.x
MohamedRejeb May 21, 2025
69ea336
Merge remote-tracking branch 'refs/remotes/origin/main' into 1.x
MohamedRejeb Jun 18, 2025
c3af294
Bump ktor to 3.1.3
MohamedRejeb Jun 18, 2025
5fb6fa2
Merge remote-tracking branch 'origin/main' into 1.x
MohamedRejeb Aug 13, 2025
612e29b
Fix encoding the Markdown sub lists type correctly
MohamedRejeb Aug 13, 2025
a0cc690
Bump Gradle wrapper to 8.14.1
MohamedRejeb Aug 13, 2025
6dafbc1
Fix issue with remove text from list edges
MohamedRejeb Sep 6, 2025
4b9ce56
Merge remote-tracking branch 'origin/main' into 1.x
MohamedRejeb Sep 6, 2025
bf51c4f
Update Compose to 1.9.0-rc01
MohamedRejeb Sep 6, 2025
4229e96
Fix issue with set ordered list item number on remove text
MohamedRejeb Sep 6, 2025
fafce3b
Fix issue with not setting rich span parent in html parser
MohamedRejeb Sep 7, 2025
7ea1412
Support rich text clipboard for desktop target
MohamedRejeb Sep 7, 2025
766947d
Fix issue with encode html inherit span style
MohamedRejeb Sep 7, 2025
e52ad7a
Remove kotlin js store from git
MohamedRejeb Sep 7, 2025
a76d1de
Fix issue with encode html inherit span style
MohamedRejeb Sep 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,8 @@ docs/api
site

### Python ###
venv/
venv/

.junie

kotlin-js-store
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#Gradle
org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx4096M"
org.gradle.daemon=true
org.gradle.caching=true
org.gradle.parallel=true

Expand Down
12 changes: 6 additions & 6 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[versions]
agp = "8.8.2"
agp = "8.11.1"
kotlin = "2.2.10"
compose = "1.8.2"
compose = "1.9.0-rc01"
dokka = "2.0.0"

ksoup = "0.6.0"
Expand All @@ -13,13 +13,13 @@ nexus-publish = "2.0.0"
# For sample
androidx-appcompat = "1.7.1"
activity-compose = "1.10.1"
richeditor = "1.0.0-rc10"
richeditor = "1.0.0-rc12"
coroutines = "1.10.2"
ktor = "3.2.3"
android-minSdk = "21"
android-compileSdk = "35"
lifecycle = "2.9.0"
navigation = "2.9.0-beta01"
android-compileSdk = "36"
lifecycle = "2.9.3"
navigation = "2.9.0-rc01"

[libraries]
ksoup-html = { module = "com.mohamedrejeb.ksoup:ksoup-html", version.ref = "ksoup" }
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Empty file removed kotlin-js-store/package-lock.json
Empty file.
2,850 changes: 0 additions & 2,850 deletions kotlin-js-store/yarn.lock

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.mohamedrejeb.richeditor.clipboard

import androidx.compose.ui.platform.Clipboard
import com.mohamedrejeb.richeditor.model.RichTextState

internal actual fun createRichTextClipboardManager(
richTextState: RichTextState,
clipboard: Clipboard
): RichTextClipboardManager =
AndroidRichTextClipboardManager(
richTextState = richTextState,
clipboard = clipboard
)

/**
* Android implementation of [RichTextClipboardManager].
* Handles rich text clipboard operations using Android's clipboard functionality.
*
* @property richTextState The [RichTextState] to be used for clipboard operations
* @property clipboard The Compose [Clipboard] for handling clipboard operations
*/
internal class AndroidRichTextClipboardManager(
private val richTextState: RichTextState,
private val clipboard: Clipboard,
) : RichTextClipboardManager, Clipboard by clipboard {

override fun getRichTextContent() {
// TODO: Implement Android-specific clipboard content retrieval
}

override fun setRichTextContent() {
// TODO: Implement Android-specific clipboard content setting
}

override fun hasRichTextContent(): Boolean {
// TODO: Implement Android-specific clipboard content check
return false
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.mohamedrejeb.richeditor.clipboard

import androidx.compose.ui.platform.Clipboard
import com.mohamedrejeb.richeditor.model.RichTextState

/**
* Creates a new instance of [RichTextClipboardManager]
* @param richTextState The [RichTextState] to be used for clipboard operations
* @param clipboard The Compose [Clipboard] for handling clipboard operations
* @return A new instance of [RichTextClipboardManager]
*/
internal expect fun createRichTextClipboardManager(
richTextState: RichTextState,
clipboard: Clipboard,
): RichTextClipboardManager

/**
* Interface for managing clipboard operations with rich text content.
* This interface provides methods for copying and pasting rich text while preserving formatting.
*/
internal interface RichTextClipboardManager: Clipboard {
/**
* Gets the current content from the clipboard.
* @return [RichTextContent] containing the clipboard content, or null if clipboard is empty
*/
fun getRichTextContent()

/**
* Sets the rich text content to the clipboard.
* @param content The [RichTextContent] to be set to the clipboard
*/
fun setRichTextContent()

/**
* Checks if the clipboard has content that can be converted to rich text.
* @return true if clipboard has compatible content, false otherwise
*/
fun hasRichTextContent(): Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import androidx.compose.ui.text.input.TransformedText
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.Density
import androidx.compose.ui.util.fastCoerceAtLeast
import androidx.compose.ui.util.fastFirstOrNull
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastForEachIndexed
Expand Down Expand Up @@ -142,7 +143,7 @@ public class RichTextState internal constructor(

@Deprecated(
message = "Use isRichSpan with T or KClass instead",
replaceWith = ReplaceWith("isRichSpan<>()"),
replaceWith = ReplaceWith("isRichSpan<RichSpanStyle>()"),
level = DeprecationLevel.WARNING,
)
public fun isRichSpan(spanStyle: RichSpanStyle): Boolean =
Expand Down Expand Up @@ -296,6 +297,9 @@ public class RichTextState internal constructor(
"but the end index is ${textRange.max}."
}

if (textRange.collapsed)
return

onTextFieldValueChange(
newTextFieldValue = textFieldValue.copy(
text = textFieldValue.text.removeRange(
Expand Down Expand Up @@ -1489,7 +1493,8 @@ public class RichTextState internal constructor(

val beforeText = textFieldValue.text.substring(
0,
paragraphFirstChildStartIndex - paragraphOldStartTextLength
(paragraphFirstChildStartIndex - paragraphOldStartTextLength)
.fastCoerceAtLeast(0)
)
val afterText = textFieldValue.text.substring(paragraphFirstChildStartIndex)

Expand All @@ -1505,8 +1510,8 @@ public class RichTextState internal constructor(
return textFieldValue.copy(
text = beforeText + newType.startText + afterText,
selection = TextRange(
newSelectionMin,
newSelectionMax,
newSelectionMin.coerceAtLeast(0),
newSelectionMax.coerceAtLeast(0),
),
)
}
Expand Down Expand Up @@ -1889,7 +1894,10 @@ public class RichTextState internal constructor(
}

// Handle Remove the max paragraph custom text
if (maxRemoveIndex < maxParagraphFirstChildMinIndex) {
if (
(minParagraphIndex == maxParagraphIndex || minRemoveIndex >= minParagraphFirstChildMinIndex) &&
maxRemoveIndex < maxParagraphFirstChildMinIndex
) {
handleRemoveMaxParagraphStartText(
minRemoveIndex = minRemoveIndex,
maxRemoveIndex = maxRemoveIndex,
Expand Down Expand Up @@ -1928,7 +1936,7 @@ public class RichTextState internal constructor(
if (isMinParagraphEmpty) {
// Set the min paragraph type to the max paragraph type
// Since the max paragraph is going to take the min paragraph's place
maxRichSpan.paragraph.type = minRichSpan.paragraph.type
// maxRichSpan.paragraph.type = minRichSpan.paragraph.type

// Remove the min paragraph if it's empty
richParagraphList.remove(minRichSpan.paragraph)
Expand Down Expand Up @@ -2048,7 +2056,11 @@ public class RichTextState internal constructor(
paragraphStartTextLength: Int,
paragraphFirstChildMinIndex: Int,
) {
if (maxRemoveIndex < paragraphFirstChildMinIndex && paragraphStartTextLength > 0) {
if (
maxRemoveIndex < paragraphFirstChildMinIndex &&
// maxRemoveIndex > paragraphFirstChildMinIndex - paragraphStartTextLength &&
paragraphStartTextLength > 0
) {
paragraphStartTextLength - (paragraphFirstChildMinIndex - maxRemoveIndex)

val beforeText =
Expand Down Expand Up @@ -2186,20 +2198,23 @@ public class RichTextState internal constructor(
if (startParagraphType is OrderedList)
levelNumberMap[startParagraphType.level] = startParagraphType.number

if (startParagraphIndex == -1)
levelNumberMap[1] = 0

// Update the paragraph type of the paragraphs after the new paragraph
for (i in (startParagraphIndex + 1)..richParagraphList.lastIndex) {
val currentParagraph = richParagraphList[i]
val currentParagraphType = currentParagraph.type

if (currentParagraphType is ConfigurableListLevel) {
// Clear the completed list levels
levelNumberMap.keys.toList().fastForEach { level ->
if (level > currentParagraphType.level)
levelNumberMap.remove(level)
levelNumberMap.filterKeys { level ->
level <= currentParagraphType.level
}
} else {
// Clear the map if the current paragraph is not a list
levelNumberMap.clear()
levelNumberMap[1] = 0
}

// Remove current list level from map if the current paragraph is an unordered list
Expand All @@ -2226,9 +2241,14 @@ public class RichTextState internal constructor(
)
}

// Break if we reach the end paragraph index
if (i >= endParagraphIndex)
break
if (
currentParagraphType !is ConfigurableListLevel ||
(currentParagraphType is UnorderedList && currentParagraphType.level == 1)
) {
// Break if we reach the end paragraph index
if (i >= endParagraphIndex)
break
}
}
}

Expand Down Expand Up @@ -3640,7 +3660,10 @@ public class RichTextState internal constructor(
richTextState.config.listIndent = config.listIndent
richTextState.config.orderedListIndent = config.orderedListIndent
richTextState.config.unorderedListIndent = config.unorderedListIndent
richTextState.config.unorderedListStyleType = config.unorderedListStyleType
richTextState.config.orderedListStyleType = config.orderedListStyleType
richTextState.config.preserveStyleOnEmptyLine = config.preserveStyleOnEmptyLine
richTextState.config.exitListOnEmptyItem = config.exitListOnEmptyItem

return richTextState
}
Expand Down Expand Up @@ -4042,6 +4065,37 @@ public class RichTextState internal constructor(
public fun toText(): String =
toText(richParagraphList = richParagraphList)

/**
* Returns a specific range of the [RichTextState] as a text string.
*
* @param range The [TextRange] to convert to text.
* @return The text string for the specified range.
*/
public fun toText(range: TextRange): String {
// Create a new RichTextState with only the content within the range
val state = copy()

if (range.max < state.textFieldValue.text.length)
state.removeTextRange(
textRange = TextRange(
start = range.max
.coerceAtLeast(0),
end = state.textFieldValue.text.length
)
)

if (range.min > 0)
state.removeTextRange(
textRange = TextRange(
start = 0,
end = range.min
.coerceAtMost(state.textFieldValue.text.length)
)
)

return state.toText()
}

/**
* Decodes the [RichTextState] to a html string.
*
Expand All @@ -4051,15 +4105,77 @@ public class RichTextState internal constructor(
return RichTextStateHtmlParser.decode(this)
}

/**
* Decodes a specific range of the [RichTextState] to a html string.
*
* @param range The [TextRange] to convert to HTML.
* @return The html string for the specified range.
*/
public fun toHtml(range: TextRange): String {
// Create a new RichTextState with only the content within the range
val state = copy()

if (range.max < state.textFieldValue.text.length)
state.removeTextRange(
textRange = TextRange(
start = range.max
.coerceAtLeast(0),
end = state.textFieldValue.text.length
)
)

if (range.min > 0)
state.removeTextRange(
textRange = TextRange(
start = 0,
end = range.min
.coerceAtMost(state.textFieldValue.text.length)
)
)

return RichTextStateHtmlParser.decode(state)
}

/**
* Decodes the [RichTextState] to a markdown string.
*
* @return The html string.
* @return The markdown string.
*/
public fun toMarkdown(): String {
return RichTextStateMarkdownParser.decode(this)
}

/**
* Decodes a specific range of the [RichTextState] to a markdown string.
*
* @param range The [TextRange] to convert to markdown.
* @return The markdown string for the specified range.
*/
public fun toMarkdown(range: TextRange): String {
// Create a new RichTextState with only the content within the range
val state = copy()

if (range.max < state.textFieldValue.text.length)
state.removeTextRange(
textRange = TextRange(
start = range.max
.coerceAtLeast(0),
end = state.textFieldValue.text.length
)
)

if (range.min > 0)
state.removeTextRange(
textRange = TextRange(
start = 0,
end = range.min
.coerceAtMost(state.textFieldValue.text.length)
)
)

return RichTextStateMarkdownParser.decode(state)
}

/**
* Clears the [RichTextState] and sets the [TextFieldValue] to an empty value.
*/
Expand Down
Loading
Loading