mirror of
https://github.com/LiveFastEatTrashRaccoon/RaccoonForLemmy.git
synced 2025-02-10 04:00:43 +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) {
|
if (actionMode == null) {
|
||||||
status = TextToolbarStatus.Shown
|
status = TextToolbarStatus.Shown
|
||||||
view.startActionMode(
|
actionMode = view.startActionMode(
|
||||||
CustomTextActionModeCallback(
|
CustomTextActionModeCallback(
|
||||||
rect = rect,
|
rect = rect,
|
||||||
onCopy = {
|
onCopy = {
|
||||||
@ -50,6 +50,7 @@ class CustomTextToolbar(
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
actionMode?.invalidate()
|
actionMode?.invalidate()
|
||||||
|
actionMode = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -76,13 +77,21 @@ internal class CustomTextActionModeCallback(
|
|||||||
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean = false
|
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean = false
|
||||||
|
|
||||||
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
|
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
|
||||||
when (item?.itemId) {
|
val res = when (item?.itemId) {
|
||||||
ACTION_ID_COPY -> onCopy()
|
ACTION_ID_COPY -> {
|
||||||
ACTION_ID_SEARCH -> onSearch()
|
onCopy()
|
||||||
else -> return false
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
ACTION_ID_SEARCH -> {
|
||||||
|
onSearch()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> false
|
||||||
}
|
}
|
||||||
mode?.finish()
|
mode?.finish()
|
||||||
return true
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
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)
|
onTextFieldValueChanged(newValue)
|
||||||
},
|
},
|
||||||
@ -192,10 +193,19 @@ fun TextFormattingBar(
|
|||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.onClick {
|
modifier = Modifier.onClick {
|
||||||
val newValue = textFieldValue.let {
|
val newValue = textFieldValue.let {
|
||||||
it.copy(
|
val selection = it.selection
|
||||||
text = it.text + "\n> ",
|
val newText = buildString {
|
||||||
selection = TextRange(index = it.text.length + 3),
|
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)
|
onTextFieldValueChanged(newValue)
|
||||||
},
|
},
|
||||||
|
@ -646,18 +646,12 @@ private fun findNode(id: String, node: Node): Node? {
|
|||||||
|
|
||||||
|
|
||||||
private fun linearize(node: Node, list: MutableList<CommentModel>) {
|
private fun linearize(node: Node, list: MutableList<CommentModel>) {
|
||||||
if (node.children.isEmpty()) {
|
if (node.comment != null) {
|
||||||
if (node.comment != null) {
|
list.add(node.comment)
|
||||||
list.add(node.comment)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
for (c in node.children) {
|
for (c in node.children) {
|
||||||
linearize(c, list)
|
linearize(c, list)
|
||||||
}
|
}
|
||||||
if (node.comment != null) {
|
|
||||||
list.add(node.comment)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<CommentModel>.populateLoadMoreComments() = mapIndexed() { idx, comment ->
|
private fun List<CommentModel>.populateLoadMoreComments() = mapIndexed() { idx, comment ->
|
||||||
@ -688,5 +682,5 @@ private fun List<CommentModel>.processCommentsToGetNestedOrder(
|
|||||||
val result = mutableListOf<CommentModel>()
|
val result = mutableListOf<CommentModel>()
|
||||||
linearize(root, result)
|
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.Spanned
|
||||||
import android.text.TextPaint
|
import android.text.TextPaint
|
||||||
import android.text.style.ClickableSpan
|
import android.text.style.ClickableSpan
|
||||||
import android.util.Log
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import io.noties.markwon.AbstractMarkwonPlugin
|
import io.noties.markwon.AbstractMarkwonPlugin
|
||||||
@ -17,7 +16,7 @@ data class SpoilerTitleSpan(val title: CharSequence)
|
|||||||
class SpoilerCloseSpan
|
class SpoilerCloseSpan
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* CREDITS:
|
* Originally inspired by:
|
||||||
* https://github.com/dessalines/jerboa/blob/main/app/src/main/java/com/jerboa/util/markwon/MarkwonSpoilerPlugin.kt
|
* 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) :
|
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 afterSetText(textView: TextView) {
|
||||||
override fun onTextAdded(visitor: MarkwonVisitor, text: String, start: Int) {
|
runCatching {
|
||||||
val spoilerTitleRegex = Regex("(:::\\s+spoiler\\s+)(.*)")
|
val spanned = SpannableStringBuilder(textView.text)
|
||||||
// Find all spoiler "start" lines
|
val startSpans =
|
||||||
val spoilerTitles = spoilerTitleRegex.findAll(text)
|
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) {
|
startSpans
|
||||||
val spoilerTitle = match.groups[2]!!.value
|
.zip(closeSpans)
|
||||||
visitor.builder().setSpan(
|
.forEach { (startSpan, closeSpan) ->
|
||||||
SpoilerTitleSpan(spoilerTitle),
|
val spoilerStart = spanned.getSpanStart(startSpan)
|
||||||
start,
|
val spoilerEnd = spanned.getSpanEnd(closeSpan)
|
||||||
start + match.groups[2]!!.range.last,
|
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE,
|
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).*:::")
|
textView.text = spanned
|
||||||
// Find all spoiler "end" lines
|
AsyncDrawableScheduler.schedule(textView)
|
||||||
val spoilerCloses = spoilerCloseRegex.findAll(text)
|
|
||||||
for (match in spoilerCloses) {
|
|
||||||
visitor.builder()
|
|
||||||
.setSpan(SpoilerCloseSpan(), start, start + 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun afterSetText(textView: TextView) {
|
override fun updateDrawState(ds: TextPaint) {
|
||||||
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)
|
|
||||||
|
|
||||||
spoilerTitleSpans.sortBy { spanned.getSpanStart(it) }
|
private class SpoilerTextAddedListener : CorePlugin.OnTextAddedListener {
|
||||||
spoilerCloseSpans.sortBy { spanned.getSpanStart(it) }
|
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 ->
|
for (match in spoilerTitles) {
|
||||||
val spoilerStart = spanned.getSpanStart(spoilerTitleSpan)
|
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
|
val spoilerCloseRegex = Regex("^(?!.*spoiler).*:::")
|
||||||
if (index < spoilerCloseSpans.size) {
|
// Find all spoiler "end" lines
|
||||||
val spoilerCloseSpan = spoilerCloseSpans[index]
|
val spoilerCloses = spoilerCloseRegex.findAll(text)
|
||||||
spoilerEnd = spanned.getSpanEnd(spoilerCloseSpan)
|
for (match in spoilerCloses) {
|
||||||
}
|
visitor.builder()
|
||||||
|
.setSpan(SpoilerCloseSpan(), start, start + 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user