mirror of
https://github.com/LiveFastEatTrashRaccoon/RaccoonForLemmy.git
synced 2025-02-09 09:28:42 +01:00
fix: bug fixes (#88)
* enhancement: position of quotes * fix: comment ordering * fix: custom toolbar visibility * fix: markdown spoiler expansion
This commit is contained in:
parent
f8250b31d2
commit
df0336ab42
@ -35,7 +35,7 @@ class CustomTextToolbar(
|
||||
) {
|
||||
if (actionMode == null) {
|
||||
status = TextToolbarStatus.Shown
|
||||
view.startActionMode(
|
||||
actionMode = view.startActionMode(
|
||||
CustomTextActionModeCallback(
|
||||
rect = rect,
|
||||
onCopy = {
|
||||
@ -50,6 +50,7 @@ class CustomTextToolbar(
|
||||
)
|
||||
} else {
|
||||
actionMode?.invalidate()
|
||||
actionMode = null
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -76,13 +77,21 @@ internal class CustomTextActionModeCallback(
|
||||
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean = false
|
||||
|
||||
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
|
||||
when (item?.itemId) {
|
||||
ACTION_ID_COPY -> onCopy()
|
||||
ACTION_ID_SEARCH -> onSearch()
|
||||
else -> return false
|
||||
val res = when (item?.itemId) {
|
||||
ACTION_ID_COPY -> {
|
||||
onCopy()
|
||||
true
|
||||
}
|
||||
|
||||
ACTION_ID_SEARCH -> {
|
||||
onSearch()
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
mode?.finish()
|
||||
return true
|
||||
return res
|
||||
}
|
||||
|
||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||
|
@ -149,7 +149,8 @@ fun TextFormattingBar(
|
||||
)
|
||||
)
|
||||
}
|
||||
it.copy(text = newText, selection = TextRange(index = selection.start + 2))
|
||||
val newSelection = TextRange(index = selection.start + 2)
|
||||
it.copy(text = newText, selection = newSelection)
|
||||
}
|
||||
onTextFieldValueChanged(newValue)
|
||||
},
|
||||
@ -192,10 +193,19 @@ fun TextFormattingBar(
|
||||
Icon(
|
||||
modifier = Modifier.onClick {
|
||||
val newValue = textFieldValue.let {
|
||||
it.copy(
|
||||
text = it.text + "\n> ",
|
||||
selection = TextRange(index = it.text.length + 3),
|
||||
)
|
||||
val selection = it.selection
|
||||
val newText = buildString {
|
||||
append(it.text.substring(0, selection.start))
|
||||
append("\n> ")
|
||||
append(
|
||||
it.text.substring(
|
||||
selection.end,
|
||||
it.text.length
|
||||
)
|
||||
)
|
||||
}
|
||||
val newSelection = TextRange(index = selection.start + 3)
|
||||
it.copy(text = newText, selection = newSelection)
|
||||
}
|
||||
onTextFieldValueChanged(newValue)
|
||||
},
|
||||
|
@ -646,18 +646,12 @@ private fun findNode(id: String, node: Node): Node? {
|
||||
|
||||
|
||||
private fun linearize(node: Node, list: MutableList<CommentModel>) {
|
||||
if (node.children.isEmpty()) {
|
||||
if (node.comment != null) {
|
||||
list.add(node.comment)
|
||||
}
|
||||
return
|
||||
if (node.comment != null) {
|
||||
list.add(node.comment)
|
||||
}
|
||||
for (c in node.children) {
|
||||
linearize(c, list)
|
||||
}
|
||||
if (node.comment != null) {
|
||||
list.add(node.comment)
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<CommentModel>.populateLoadMoreComments() = mapIndexed() { idx, comment ->
|
||||
@ -688,5 +682,5 @@ private fun List<CommentModel>.processCommentsToGetNestedOrder(
|
||||
val result = mutableListOf<CommentModel>()
|
||||
linearize(root, result)
|
||||
|
||||
return result.reversed().toList()
|
||||
return result.toList()
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import android.text.SpannableStringBuilder
|
||||
import android.text.Spanned
|
||||
import android.text.TextPaint
|
||||
import android.text.style.ClickableSpan
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import io.noties.markwon.AbstractMarkwonPlugin
|
||||
@ -17,7 +16,7 @@ data class SpoilerTitleSpan(val title: CharSequence)
|
||||
class SpoilerCloseSpan
|
||||
|
||||
/*
|
||||
* CREDITS:
|
||||
* Originally inspired by:
|
||||
* https://github.com/dessalines/jerboa/blob/main/app/src/main/java/com/jerboa/util/markwon/MarkwonSpoilerPlugin.kt
|
||||
*/
|
||||
class MarkwonSpoilerPlugin private constructor(private val enableInteraction: Boolean) :
|
||||
@ -35,118 +34,132 @@ class MarkwonSpoilerPlugin private constructor(private val enableInteraction: Bo
|
||||
}
|
||||
}
|
||||
|
||||
private class SpoilerTextAddedListener : CorePlugin.OnTextAddedListener {
|
||||
override fun onTextAdded(visitor: MarkwonVisitor, text: String, start: Int) {
|
||||
val spoilerTitleRegex = Regex("(:::\\s+spoiler\\s+)(.*)")
|
||||
// Find all spoiler "start" lines
|
||||
val spoilerTitles = spoilerTitleRegex.findAll(text)
|
||||
override fun afterSetText(textView: TextView) {
|
||||
runCatching {
|
||||
val spanned = SpannableStringBuilder(textView.text)
|
||||
val startSpans =
|
||||
spanned.getSpans(0, spanned.length, SpoilerTitleSpan::class.java)
|
||||
.sortedBy { spanned.getSpanStart(it) }
|
||||
val closeSpans =
|
||||
spanned.getSpans(0, spanned.length, SpoilerCloseSpan::class.java)
|
||||
.sortedBy { spanned.getSpanStart(it) }
|
||||
|
||||
for (match in spoilerTitles) {
|
||||
val spoilerTitle = match.groups[2]!!.value
|
||||
visitor.builder().setSpan(
|
||||
SpoilerTitleSpan(spoilerTitle),
|
||||
start,
|
||||
start + match.groups[2]!!.range.last,
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE,
|
||||
startSpans
|
||||
.zip(closeSpans)
|
||||
.forEach { (startSpan, closeSpan) ->
|
||||
val spoilerStart = spanned.getSpanStart(startSpan)
|
||||
val spoilerEnd = spanned.getSpanEnd(closeSpan)
|
||||
|
||||
val spoilerTitle = getSpoilerTitle(false, startSpan.title.toString())
|
||||
|
||||
val spoilerContent = spanned.subSequence(
|
||||
spanned.getSpanEnd(startSpan) + 1,
|
||||
spoilerEnd - 3,
|
||||
)
|
||||
|
||||
// Remove spoiler content from span
|
||||
spanned.replace(spoilerStart, spoilerEnd, spoilerTitle)
|
||||
|
||||
// Set span block title
|
||||
spanned.setSpan(
|
||||
spoilerTitle,
|
||||
spoilerStart,
|
||||
spoilerStart + spoilerTitle.length,
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE,
|
||||
)
|
||||
val wrapper = SpoilerClickableSpan(
|
||||
enableInteraction = enableInteraction,
|
||||
textView = textView,
|
||||
spoilerTitle = startSpan.title.toString(),
|
||||
spoilerContent = spoilerContent.toString(),
|
||||
)
|
||||
|
||||
// Set spoiler block type as ClickableSpan
|
||||
spanned.setSpan(
|
||||
wrapper,
|
||||
spoilerStart,
|
||||
spoilerStart + spoilerTitle.length,
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE,
|
||||
)
|
||||
|
||||
textView.text = spanned
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSpoilerTitle(openParam: Boolean, title: String): String = if (openParam) {
|
||||
"▼ ${title}\n"
|
||||
} else {
|
||||
// The space at the end is necessary for the lengths to be the same
|
||||
// This reduces complexity as else it would need complex logic to determine the replacement length
|
||||
"▶ ${title}\u200B"
|
||||
}
|
||||
|
||||
private class SpoilerClickableSpan(
|
||||
private val enableInteraction: Boolean,
|
||||
private val textView: TextView,
|
||||
private val spoilerTitle: String,
|
||||
private val spoilerContent: String,
|
||||
) : ClickableSpan() {
|
||||
|
||||
private var open = false
|
||||
|
||||
override fun onClick(view: View) {
|
||||
if (enableInteraction) {
|
||||
textView.cancelPendingInputEvents()
|
||||
val spanned = SpannableStringBuilder(textView.text)
|
||||
val title = getSpoilerTitle(open, spoilerTitle)
|
||||
val start = spanned.indexOf(title).coerceAtLeast(0)
|
||||
|
||||
open = !open
|
||||
|
||||
spanned.replace(
|
||||
start,
|
||||
start + title.length,
|
||||
getSpoilerTitle(open, spoilerTitle),
|
||||
)
|
||||
if (open) {
|
||||
spanned.insert(start + title.length, spoilerContent)
|
||||
} else {
|
||||
spanned.replace(
|
||||
start + title.length,
|
||||
start + title.length + spoilerContent.length,
|
||||
"",
|
||||
)
|
||||
}
|
||||
|
||||
val spoilerCloseRegex = Regex("^(?!.*spoiler).*:::")
|
||||
// Find all spoiler "end" lines
|
||||
val spoilerCloses = spoilerCloseRegex.findAll(text)
|
||||
for (match in spoilerCloses) {
|
||||
visitor.builder()
|
||||
.setSpan(SpoilerCloseSpan(), start, start + 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
textView.text = spanned
|
||||
AsyncDrawableScheduler.schedule(textView)
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterSetText(textView: TextView) {
|
||||
try {
|
||||
val spanned = SpannableStringBuilder(textView.text)
|
||||
val spoilerTitleSpans =
|
||||
spanned.getSpans(0, spanned.length, SpoilerTitleSpan::class.java)
|
||||
val spoilerCloseSpans =
|
||||
spanned.getSpans(0, spanned.length, SpoilerCloseSpan::class.java)
|
||||
override fun updateDrawState(ds: TextPaint) {
|
||||
}
|
||||
}
|
||||
|
||||
spoilerTitleSpans.sortBy { spanned.getSpanStart(it) }
|
||||
spoilerCloseSpans.sortBy { spanned.getSpanStart(it) }
|
||||
private class SpoilerTextAddedListener : CorePlugin.OnTextAddedListener {
|
||||
override fun onTextAdded(visitor: MarkwonVisitor, text: String, start: Int) {
|
||||
val spoilerTitleRegex = Regex("(:::\\s+spoiler\\s+)(.*)")
|
||||
// Find all spoiler "start" lines
|
||||
val spoilerTitles = spoilerTitleRegex.findAll(text)
|
||||
|
||||
spoilerTitleSpans.forEachIndexed { index, spoilerTitleSpan ->
|
||||
val spoilerStart = spanned.getSpanStart(spoilerTitleSpan)
|
||||
for (match in spoilerTitles) {
|
||||
val spoilerTitle = match.groups[2]!!.value
|
||||
visitor.builder().setSpan(
|
||||
SpoilerTitleSpan(spoilerTitle),
|
||||
start,
|
||||
start + match.groups[2]!!.range.last,
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE,
|
||||
)
|
||||
}
|
||||
|
||||
var spoilerEnd = spanned.length
|
||||
if (index < spoilerCloseSpans.size) {
|
||||
val spoilerCloseSpan = spoilerCloseSpans[index]
|
||||
spoilerEnd = spanned.getSpanEnd(spoilerCloseSpan)
|
||||
}
|
||||
|
||||
var open = false
|
||||
// The space at the end is necessary for the lengths to be the same
|
||||
// This reduces complexity as else it would need complex logic to determine the replacement length
|
||||
val getSpoilerTitle = { openParam: Boolean ->
|
||||
if (openParam) "▼ ${spoilerTitleSpan.title}\n" else "▶ ${spoilerTitleSpan.title}\u200B"
|
||||
}
|
||||
|
||||
val spoilerTitle = getSpoilerTitle(false)
|
||||
|
||||
val spoilerContent = spanned.subSequence(
|
||||
spanned.getSpanEnd(spoilerTitleSpan) + 1,
|
||||
spoilerEnd - 3,
|
||||
) as SpannableStringBuilder
|
||||
|
||||
// Remove spoiler content from span
|
||||
spanned.replace(spoilerStart, spoilerEnd, spoilerTitle)
|
||||
// Set span block title
|
||||
spanned.setSpan(
|
||||
spoilerTitle,
|
||||
spoilerStart,
|
||||
spoilerStart + spoilerTitle.length,
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE,
|
||||
)
|
||||
|
||||
val wrapper = object : ClickableSpan() {
|
||||
override fun onClick(p0: View) {
|
||||
if (enableInteraction) {
|
||||
textView.cancelPendingInputEvents()
|
||||
open = !open
|
||||
|
||||
spanned.replace(
|
||||
spoilerStart,
|
||||
spoilerStart + spoilerTitle.length,
|
||||
getSpoilerTitle(open),
|
||||
)
|
||||
if (open) {
|
||||
spanned.insert(spoilerStart + spoilerTitle.length, spoilerContent)
|
||||
} else {
|
||||
spanned.replace(
|
||||
spoilerStart + spoilerTitle.length,
|
||||
spoilerStart + spoilerTitle.length + spoilerContent.length,
|
||||
"",
|
||||
)
|
||||
}
|
||||
|
||||
textView.text = spanned
|
||||
AsyncDrawableScheduler.schedule(textView)
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateDrawState(ds: TextPaint) {
|
||||
}
|
||||
}
|
||||
|
||||
// Set spoiler block type as ClickableSpan
|
||||
spanned.setSpan(
|
||||
wrapper,
|
||||
spoilerStart,
|
||||
spoilerStart + spoilerTitle.length,
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE,
|
||||
)
|
||||
|
||||
textView.text = spanned
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w("MarkdownSpoilerPlugin", "Failed to parse spoiler tag. Format incorrect")
|
||||
val spoilerCloseRegex = Regex("^(?!.*spoiler).*:::")
|
||||
// Find all spoiler "end" lines
|
||||
val spoilerCloses = spoilerCloseRegex.findAll(text)
|
||||
for (match in spoilerCloses) {
|
||||
visitor.builder()
|
||||
.setSpan(SpoilerCloseSpan(), start, start + 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user