Compare commits
No commits in common. "9715d958ae2a439b55a08115411397f545aa7c4d" and "3749b7ff0764de1b8be92bacb98bc9954e29d048" have entirely different histories.
9715d958ae
...
3749b7ff07
@ -264,9 +264,6 @@ fun EditableFullScreenNoteView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GANTI SELURUH bagian DraggableMiniMarkdownToolbar di EditableFullScreenNoteView.kt
|
|
||||||
// Letakkan kode ini di dalam Box setelah Scaffold, sebelum closing bracket
|
|
||||||
|
|
||||||
if (isContentFocused) {
|
if (isContentFocused) {
|
||||||
DraggableMiniMarkdownToolbar(
|
DraggableMiniMarkdownToolbar(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -295,8 +292,6 @@ fun EditableFullScreenNoteView(
|
|||||||
isBoldActive = editorState.isBoldActive(),
|
isBoldActive = editorState.isBoldActive(),
|
||||||
isItalicActive = editorState.isItalicActive(),
|
isItalicActive = editorState.isItalicActive(),
|
||||||
isUnderlineActive = editorState.isUnderlineActive(),
|
isUnderlineActive = editorState.isUnderlineActive(),
|
||||||
canUndo = editorState.canUndo(),
|
|
||||||
canRedo = editorState.canRedo(),
|
|
||||||
|
|
||||||
onDrag = ::moveToolbar,
|
onDrag = ::moveToolbar,
|
||||||
onBold = {
|
onBold = {
|
||||||
@ -307,16 +302,9 @@ fun EditableFullScreenNoteView(
|
|||||||
ensureFocus()
|
ensureFocus()
|
||||||
editorState.toggleItalic()
|
editorState.toggleItalic()
|
||||||
},
|
},
|
||||||
onUnderline = {
|
onUnderline = { ensureFocus(); editorState.toggleUnderline() },
|
||||||
ensureFocus()
|
onHeading = { ensureFocus(); editorState.setHeading(1) },
|
||||||
editorState.toggleUnderline()
|
onBullet = { ensureFocus(); editorState.toggleBulletList() }
|
||||||
},
|
|
||||||
onUndo = {
|
|
||||||
editorState.undo()
|
|
||||||
},
|
|
||||||
onRedo = {
|
|
||||||
editorState.redo()
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,15 +25,13 @@ fun DraggableMiniMarkdownToolbar(
|
|||||||
isBoldActive: Boolean,
|
isBoldActive: Boolean,
|
||||||
isItalicActive: Boolean,
|
isItalicActive: Boolean,
|
||||||
isUnderlineActive: Boolean,
|
isUnderlineActive: Boolean,
|
||||||
canUndo: Boolean = false,
|
|
||||||
canRedo: Boolean = false,
|
|
||||||
|
|
||||||
// ACTIONS
|
// ACTIONS
|
||||||
onBold: () -> Unit,
|
onBold: () -> Unit,
|
||||||
onItalic: () -> Unit,
|
onItalic: () -> Unit,
|
||||||
|
onHeading: () -> Unit,
|
||||||
onUnderline: () -> Unit,
|
onUnderline: () -> Unit,
|
||||||
onUndo: () -> Unit = {},
|
onBullet: () -> Unit
|
||||||
onRedo: () -> Unit = {}
|
|
||||||
) {
|
) {
|
||||||
Surface(
|
Surface(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
@ -85,27 +83,17 @@ fun DraggableMiniMarkdownToolbar(
|
|||||||
onClick = onUnderline
|
onClick = onUnderline
|
||||||
)
|
)
|
||||||
|
|
||||||
// Divider
|
ToolbarIcon(
|
||||||
Box(
|
icon = Icons.Default.Title,
|
||||||
modifier = Modifier
|
onClick = onHeading
|
||||||
.width(1.dp)
|
|
||||||
.height(24.dp)
|
|
||||||
.background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Undo
|
|
||||||
ToolbarIcon(
|
ToolbarIcon(
|
||||||
icon = Icons.Default.Undo,
|
icon = Icons.Default.FormatListBulleted,
|
||||||
onClick = onUndo,
|
onClick = onBullet
|
||||||
enabled = canUndo
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Redo
|
|
||||||
ToolbarIcon(
|
|
||||||
icon = Icons.Default.Redo,
|
|
||||||
onClick = onRedo,
|
|
||||||
enabled = canRedo
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -114,12 +102,10 @@ fun DraggableMiniMarkdownToolbar(
|
|||||||
private fun ToolbarIcon(
|
private fun ToolbarIcon(
|
||||||
icon: androidx.compose.ui.graphics.vector.ImageVector,
|
icon: androidx.compose.ui.graphics.vector.ImageVector,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
isActive: Boolean = false,
|
isActive: Boolean = false
|
||||||
enabled: Boolean = true
|
|
||||||
) {
|
) {
|
||||||
val activeBg = MaterialTheme.colorScheme.primary.copy(alpha = 0.15f)
|
val activeBg = MaterialTheme.colorScheme.primary.copy(alpha = 0.15f)
|
||||||
val activeColor = MaterialTheme.colorScheme.primary
|
val activeColor = MaterialTheme.colorScheme.primary
|
||||||
val disabledColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.3f)
|
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -132,17 +118,12 @@ private fun ToolbarIcon(
|
|||||||
) {
|
) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
modifier = Modifier.size(36.dp),
|
modifier = Modifier.size(36.dp)
|
||||||
enabled = enabled
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
icon,
|
icon,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = when {
|
tint = if (isActive) activeColor else MaterialTheme.colorScheme.onSurface,
|
||||||
!enabled -> disabledColor
|
|
||||||
isActive -> activeColor
|
|
||||||
else -> MaterialTheme.colorScheme.onSurface
|
|
||||||
},
|
|
||||||
modifier = Modifier.size(18.dp)
|
modifier = Modifier.size(18.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import androidx.compose.ui.text.font.FontStyle
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextDecoration
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class RichEditorState(initial: AnnotatedString) {
|
class RichEditorState(initial: AnnotatedString) {
|
||||||
@ -34,13 +35,13 @@ class RichEditorState(initial: AnnotatedString) {
|
|||||||
fun undo() {
|
fun undo() {
|
||||||
if (!canUndo()) return
|
if (!canUndo()) return
|
||||||
redoStack.add(value)
|
redoStack.add(value)
|
||||||
value = undoStack.removeAt(undoStack.lastIndex)
|
value = undoStack.removeLast()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun redo() {
|
fun redo() {
|
||||||
if (!canRedo()) return
|
if (!canRedo()) return
|
||||||
undoStack.add(value)
|
undoStack.add(value)
|
||||||
value = redoStack.removeAt(redoStack.lastIndex)
|
value = redoStack.removeLast()
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =====================
|
/* =====================
|
||||||
@ -49,7 +50,7 @@ class RichEditorState(initial: AnnotatedString) {
|
|||||||
private val activeStyles = mutableStateListOf<SpanStyle>()
|
private val activeStyles = mutableStateListOf<SpanStyle>()
|
||||||
|
|
||||||
/* =====================
|
/* =====================
|
||||||
VALUE CHANGE
|
VALUE CHANGE (KEY)
|
||||||
===================== */
|
===================== */
|
||||||
fun onValueChange(newValue: TextFieldValue) {
|
fun onValueChange(newValue: TextFieldValue) {
|
||||||
val old = value
|
val old = value
|
||||||
@ -65,10 +66,10 @@ class RichEditorState(initial: AnnotatedString) {
|
|||||||
|
|
||||||
snapshot()
|
snapshot()
|
||||||
|
|
||||||
// Build new annotated string by preserving old spans
|
// 1) build new annotated string by preserving old spans
|
||||||
val built = buildPreservingSpans(old, newValue)
|
val built = buildPreservingSpans(old, newValue)
|
||||||
|
|
||||||
// Auto-convert markdown patterns
|
// 2) auto-convert markdown patterns around cursor
|
||||||
val converted = autoConvertMarkdown(built)
|
val converted = autoConvertMarkdown(built)
|
||||||
|
|
||||||
value = converted
|
value = converted
|
||||||
@ -77,73 +78,18 @@ class RichEditorState(initial: AnnotatedString) {
|
|||||||
private fun buildPreservingSpans(old: TextFieldValue, newValue: TextFieldValue): TextFieldValue {
|
private fun buildPreservingSpans(old: TextFieldValue, newValue: TextFieldValue): TextFieldValue {
|
||||||
val builder = AnnotatedString.Builder(newValue.text)
|
val builder = AnnotatedString.Builder(newValue.text)
|
||||||
|
|
||||||
val oldText = old.text
|
// copy old spans (clamped)
|
||||||
val newText = newValue.text
|
|
||||||
|
|
||||||
// Detect where text was inserted/deleted
|
|
||||||
val oldLen = oldText.length
|
|
||||||
val newLen = newText.length
|
|
||||||
val delta = newLen - oldLen
|
|
||||||
|
|
||||||
// Find insertion/deletion point
|
|
||||||
var changeStart = 0
|
|
||||||
while (changeStart < minOf(oldLen, newLen) &&
|
|
||||||
oldText.getOrNull(changeStart) == newText.getOrNull(changeStart)) {
|
|
||||||
changeStart++
|
|
||||||
}
|
|
||||||
|
|
||||||
val changeEnd = changeStart + maxOf(0, -delta)
|
|
||||||
|
|
||||||
// Copy and adjust old spans
|
|
||||||
old.annotatedString.spanStyles.forEach { r ->
|
old.annotatedString.spanStyles.forEach { r ->
|
||||||
var start = r.start
|
val s = r.start.coerceIn(0, newValue.text.length)
|
||||||
var end = r.end
|
val e = r.end.coerceIn(0, newValue.text.length)
|
||||||
|
if (s < e) builder.addStyle(r.item, s, e)
|
||||||
// Adjust span positions based on where text changed
|
|
||||||
when {
|
|
||||||
// Span is completely before change
|
|
||||||
end <= changeStart -> {
|
|
||||||
// No adjustment needed
|
|
||||||
}
|
|
||||||
// Span is completely after change
|
|
||||||
start >= changeEnd -> {
|
|
||||||
start += delta
|
|
||||||
end += delta
|
|
||||||
}
|
|
||||||
// Span contains the change point
|
|
||||||
start < changeStart && end > changeEnd -> {
|
|
||||||
end += delta
|
|
||||||
}
|
|
||||||
// Span starts before change, ends in change area
|
|
||||||
start < changeStart && end in (changeStart + 1)..changeEnd -> {
|
|
||||||
end = changeStart
|
|
||||||
}
|
|
||||||
// Span starts in change area, ends after
|
|
||||||
start in changeStart until changeEnd && end >= changeEnd -> {
|
|
||||||
start = changeStart + delta
|
|
||||||
end += delta
|
|
||||||
}
|
|
||||||
// Span is completely inside change area - skip it
|
|
||||||
else -> return@forEach
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clamp to valid range
|
// apply sticky styles to newly inserted char (simple heuristic)
|
||||||
start = start.coerceIn(0, newLen)
|
val insertPos = newValue.selection.start - 1
|
||||||
end = end.coerceIn(0, newLen)
|
if (insertPos >= 0 && insertPos < newValue.text.length) {
|
||||||
|
|
||||||
if (start < end) {
|
|
||||||
builder.addStyle(r.item, start, end)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply sticky styles to newly inserted character(s)
|
|
||||||
if (delta > 0 && activeStyles.isNotEmpty()) {
|
|
||||||
val insertStart = changeStart
|
|
||||||
val insertEnd = (changeStart + delta).coerceIn(0, newLen)
|
|
||||||
if (insertStart < insertEnd) {
|
|
||||||
activeStyles.forEach { st ->
|
activeStyles.forEach { st ->
|
||||||
builder.addStyle(st, insertStart, insertEnd)
|
builder.addStyle(st, insertPos, insertPos + 1)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,14 +143,68 @@ class RichEditorState(initial: AnnotatedString) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* =====================
|
/* =====================
|
||||||
AUTO-CONVERT MARKDOWN
|
HEADING / BULLET (for toolbar)
|
||||||
|
===================== */
|
||||||
|
fun setHeading(level: Int) {
|
||||||
|
snapshot()
|
||||||
|
|
||||||
|
val sel = value.selection
|
||||||
|
val text = value.text
|
||||||
|
val lineStart = text.lastIndexOf('\n', (sel.start - 1).coerceAtLeast(0)).let { if (it == -1) 0 else it + 1 }
|
||||||
|
val lineEnd = text.indexOf('\n', sel.start).let { if (it == -1) text.length else it }
|
||||||
|
|
||||||
|
val size = when (level) {
|
||||||
|
1 -> 28.sp
|
||||||
|
2 -> 22.sp
|
||||||
|
3 -> 18.sp
|
||||||
|
else -> return
|
||||||
|
}
|
||||||
|
|
||||||
|
val builder = AnnotatedString.Builder(text)
|
||||||
|
value.annotatedString.spanStyles.forEach { r -> builder.addStyle(r.item, r.start, r.end) }
|
||||||
|
builder.addStyle(SpanStyle(fontSize = size, fontWeight = FontWeight.Bold), lineStart, lineEnd)
|
||||||
|
|
||||||
|
value = value.copy(annotatedString = builder.toAnnotatedString())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleBulletList() {
|
||||||
|
snapshot()
|
||||||
|
|
||||||
|
val sel = value.selection
|
||||||
|
val text = value.text
|
||||||
|
val prefix = "• "
|
||||||
|
|
||||||
|
val lineStart = text.lastIndexOf('\n', (sel.start - 1).coerceAtLeast(0)).let { if (it == -1) 0 else it + 1 }
|
||||||
|
val isBullet = text.startsWith(prefix, startIndex = lineStart)
|
||||||
|
|
||||||
|
if (isBullet) {
|
||||||
|
replaceTextPreserveSpans(
|
||||||
|
start = lineStart,
|
||||||
|
end = lineStart + prefix.length,
|
||||||
|
replacement = "",
|
||||||
|
newCursor = (sel.start - prefix.length).coerceAtLeast(lineStart)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
replaceTextPreserveSpans(
|
||||||
|
start = lineStart,
|
||||||
|
end = lineStart,
|
||||||
|
replacement = prefix,
|
||||||
|
newCursor = sel.start + prefix.length
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =====================
|
||||||
|
AUTO-CONVERT MARKDOWN (LEVEL 3)
|
||||||
===================== */
|
===================== */
|
||||||
private fun autoConvertMarkdown(v: TextFieldValue): TextFieldValue {
|
private fun autoConvertMarkdown(v: TextFieldValue): TextFieldValue {
|
||||||
var cur = v
|
var cur = v
|
||||||
|
|
||||||
// Bold before italic to avoid conflicts
|
// order matters: bold before italic
|
||||||
cur = convertBold(cur)
|
cur = convertBold(cur)
|
||||||
cur = convertItalic(cur)
|
cur = convertItalic(cur)
|
||||||
|
cur = convertHeading(cur)
|
||||||
|
cur = convertDashBullet(cur)
|
||||||
|
|
||||||
return cur
|
return cur
|
||||||
}
|
}
|
||||||
@ -223,17 +223,17 @@ class RichEditorState(initial: AnnotatedString) {
|
|||||||
if (contentEnd <= contentStart) return v
|
if (contentEnd <= contentStart) return v
|
||||||
if (text.substring(contentStart, contentEnd).contains('\n')) return v
|
if (text.substring(contentStart, contentEnd).contains('\n')) return v
|
||||||
|
|
||||||
// Remove end marker then start marker
|
// remove end marker then start marker (preserve spans)
|
||||||
var out = v
|
var out = v
|
||||||
out = replaceTextPreserveSpansLocal(out, cursor - 2, cursor, "")
|
out = replaceTextPreserveSpansLocal(out, cursor - 2, cursor, "")
|
||||||
out = replaceTextPreserveSpansLocal(out, startMarker, startMarker + 2, "")
|
out = replaceTextPreserveSpansLocal(out, startMarker, startMarker + 2, "")
|
||||||
|
|
||||||
// Apply bold style
|
// after removing start marker, content shifts -2
|
||||||
val newStart = startMarker
|
val newStart = startMarker
|
||||||
val newEnd = contentEnd - 2
|
val newEnd = contentEnd - 2
|
||||||
out = addStylePreserve(out, SpanStyle(fontWeight = FontWeight.Bold), newStart, newEnd)
|
out = addStylePreserve(out, SpanStyle(fontWeight = FontWeight.Bold), newStart, newEnd)
|
||||||
|
|
||||||
// Adjust cursor
|
// cursor shifts back 4 chars total
|
||||||
out = out.copy(selection = TextRange((cursor - 4).coerceAtLeast(newEnd)))
|
out = out.copy(selection = TextRange((cursor - 4).coerceAtLeast(newEnd)))
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
@ -243,13 +243,13 @@ class RichEditorState(initial: AnnotatedString) {
|
|||||||
val text = v.text
|
val text = v.text
|
||||||
val cursor = v.selection.start
|
val cursor = v.selection.start
|
||||||
if (cursor < 1) return v
|
if (cursor < 1) return v
|
||||||
// Avoid triggering on bold closing (**)
|
// avoid triggering on bold closing (**)
|
||||||
if (text.getOrNull(cursor - 1) != '*') return v
|
if (text.getOrNull(cursor - 1) != '*') return v
|
||||||
if (text.getOrNull(cursor - 2) == '*') return v
|
if (text.getOrNull(cursor - 2) == '*') return v
|
||||||
|
|
||||||
val startMarker = text.lastIndexOf('*', startIndex = cursor - 2)
|
val startMarker = text.lastIndexOf('*', startIndex = cursor - 2)
|
||||||
if (startMarker == -1) return v
|
if (startMarker == -1) return v
|
||||||
// Avoid ** as start
|
// avoid ** as start
|
||||||
if (text.getOrNull(startMarker - 1) == '*') return v
|
if (text.getOrNull(startMarker - 1) == '*') return v
|
||||||
|
|
||||||
val contentStart = startMarker + 1
|
val contentStart = startMarker + 1
|
||||||
@ -268,6 +268,74 @@ class RichEditorState(initial: AnnotatedString) {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// "# " / "## " / "### " at line start -> heading + remove markers
|
||||||
|
private fun convertHeading(v: TextFieldValue): TextFieldValue {
|
||||||
|
val text = v.text
|
||||||
|
val cursor = v.selection.start
|
||||||
|
|
||||||
|
val lineStart = text.lastIndexOf('\n', (cursor - 1).coerceAtLeast(0)).let { if (it == -1) 0 else it + 1 }
|
||||||
|
val lineEnd = text.indexOf('\n', cursor).let { if (it == -1) text.length else it }
|
||||||
|
|
||||||
|
val linePrefix = text.substring(lineStart, minOf(lineStart + 4, text.length))
|
||||||
|
|
||||||
|
val level = when {
|
||||||
|
linePrefix.startsWith("### ") -> 3
|
||||||
|
linePrefix.startsWith("## ") -> 2
|
||||||
|
linePrefix.startsWith("# ") -> 1
|
||||||
|
else -> return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// only trigger when user just typed the space after #'s OR when cursor is still on same line early
|
||||||
|
val removeLen = when (level) {
|
||||||
|
1 -> 2
|
||||||
|
2 -> 3
|
||||||
|
else -> 4
|
||||||
|
}
|
||||||
|
val triggerPos = lineStart + removeLen
|
||||||
|
if (cursor < triggerPos) return v
|
||||||
|
|
||||||
|
var out = v
|
||||||
|
out = replaceTextPreserveSpansLocal(out, lineStart, lineStart + removeLen, "")
|
||||||
|
// apply heading style to the line content
|
||||||
|
val newLineStart = lineStart
|
||||||
|
val newLineEnd = (lineEnd - removeLen).coerceAtLeast(newLineStart)
|
||||||
|
|
||||||
|
val size = when (level) {
|
||||||
|
1 -> 28.sp
|
||||||
|
2 -> 22.sp
|
||||||
|
else -> 18.sp
|
||||||
|
}
|
||||||
|
|
||||||
|
out = addStylePreserve(
|
||||||
|
out,
|
||||||
|
SpanStyle(fontSize = size, fontWeight = FontWeight.Bold),
|
||||||
|
newLineStart,
|
||||||
|
newLineEnd
|
||||||
|
)
|
||||||
|
|
||||||
|
// shift cursor back by removedLen
|
||||||
|
out = out.copy(selection = TextRange((cursor - removeLen).coerceAtLeast(newLineStart)))
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// "- " at line start -> "• "
|
||||||
|
private fun convertDashBullet(v: TextFieldValue): TextFieldValue {
|
||||||
|
val text = v.text
|
||||||
|
val cursor = v.selection.start
|
||||||
|
|
||||||
|
val lineStart = text.lastIndexOf('\n', (cursor - 1).coerceAtLeast(0)).let { if (it == -1) 0 else it + 1 }
|
||||||
|
val prefix = "- "
|
||||||
|
|
||||||
|
if (!text.startsWith(prefix, startIndex = lineStart)) return v
|
||||||
|
// only trigger when user already typed "- "
|
||||||
|
if (cursor < lineStart + 2) return v
|
||||||
|
|
||||||
|
var out = v
|
||||||
|
out = replaceTextPreserveSpansLocal(out, lineStart, lineStart + 2, "• ")
|
||||||
|
// cursor stays same length (2)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
/* =====================
|
/* =====================
|
||||||
TOOLBAR STATE
|
TOOLBAR STATE
|
||||||
===================== */
|
===================== */
|
||||||
@ -300,6 +368,11 @@ class RichEditorState(initial: AnnotatedString) {
|
|||||||
/* =====================
|
/* =====================
|
||||||
INTERNAL: text replace while preserving spans
|
INTERNAL: text replace while preserving spans
|
||||||
===================== */
|
===================== */
|
||||||
|
private fun replaceTextPreserveSpans(start: Int, end: Int, replacement: String, newCursor: Int) {
|
||||||
|
value = replaceTextPreserveSpansLocal(value, start, end, replacement)
|
||||||
|
.copy(selection = TextRange(newCursor))
|
||||||
|
}
|
||||||
|
|
||||||
private fun replaceTextPreserveSpansLocal(
|
private fun replaceTextPreserveSpansLocal(
|
||||||
v: TextFieldValue,
|
v: TextFieldValue,
|
||||||
start: Int,
|
start: Int,
|
||||||
@ -319,7 +392,7 @@ class RichEditorState(initial: AnnotatedString) {
|
|||||||
var rs = r.start
|
var rs = r.start
|
||||||
var re = r.end
|
var re = r.end
|
||||||
|
|
||||||
// Adjust spans
|
// adjust spans
|
||||||
when {
|
when {
|
||||||
re <= s -> Unit
|
re <= s -> Unit
|
||||||
rs >= e -> { rs += delta; re += delta }
|
rs >= e -> { rs += delta; re += delta }
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user