mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-11 16:57:39 +00:00
Revert "fix(richtext): don't truncate single-atom content mid-token"
This reverts commit 3dbf247108.
This commit is contained in:
+5
-21
@@ -45,28 +45,12 @@ class ExpandableTextCutOffCalculator {
|
||||
// if it is still too big, finds the first space or new line BEFORE the cut off.
|
||||
val newString = content.take(SHORT_TEXT_LENGTH)
|
||||
|
||||
val firstSpaceBeforeCut = newString.lastIndexOf(' ')
|
||||
val firstNewLineBeforeCut = newString.lastIndexOf('\n')
|
||||
val firstSpaceBeforeCut =
|
||||
newString.lastIndexOf(' ').let { if (it < 0) newString.length else it }
|
||||
val firstNewLineBeforeCut =
|
||||
newString.lastIndexOf('\n').let { if (it < 0) newString.length else it }
|
||||
|
||||
if (firstSpaceBeforeCut < 0 && firstNewLineBeforeCut < 0) {
|
||||
// No whitespace boundary anywhere in the first
|
||||
// SHORT_TEXT_LENGTH characters either. The
|
||||
// entire content is one indivisible atom — a
|
||||
// long cashuA/cashuB token, a base64 data: URI,
|
||||
// a Lightning invoice, a single huge URL. Cutting
|
||||
// mid-atom corrupts the segment so the inline
|
||||
// renderer can't decode it (the cashuB prefix
|
||||
// survives so it gets matched, but the truncated
|
||||
// body fails to base64-decode and falls back to
|
||||
// rendering the raw text + a useless Show More
|
||||
// button). Render the whole atom.
|
||||
return content.length
|
||||
}
|
||||
|
||||
maxOf(
|
||||
firstSpaceBeforeCut.coerceAtLeast(0),
|
||||
firstNewLineBeforeCut.coerceAtLeast(0),
|
||||
)
|
||||
maxOf(firstSpaceBeforeCut, firstNewLineBeforeCut)
|
||||
} else {
|
||||
min
|
||||
}
|
||||
|
||||
-79
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.commons.richtext
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.EmptyTagList
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class CashuTokenParserTest {
|
||||
private val realCashuBToken =
|
||||
"cashuBv2FteCJodHRwczovL21pbnQubWluaWJpdHMuY2FzaC9CaXRjb2luYXVjc2F0YWRkVEVTVGF0n79haUgAEHk32wzI" +
|
||||
"ZWFwn79hYQJhc3hAZWM1YWI3Yjc1NjViYjBjZTZhNzg2NzBkMDA0OGExMjVlZGQzMjJhYmVjMTEzYWMwZTBmZGVkZmE3NTQ4Mzg3OWFj" +
|
||||
"WCED0-ops8Ta6NjKChNJPe_jgIbXLlyxg2KSy2WaSTADo5D_v2FhCGFzeEBkNDZlODU5MDExNjU0NmNjZjAwNTE3ZTQ1NmU0MTY0N2Fm" +
|
||||
"ZWUxOWNlMzY2N2IzYTcxODZkMzEwZDY1MjM3OTM4YWNYIQMTDGTY943O4ojhKopoYdemsUE2rSLfzwNBODL8WgOX0v______"
|
||||
|
||||
@Test
|
||||
fun detectsCashuBAlone() {
|
||||
val state = RichTextParser().parseText(realCashuBToken, EmptyTagList, null)
|
||||
val segment = state.paragraphs[0].words[0]
|
||||
assertTrue(
|
||||
segment is CashuSegment,
|
||||
"Expected CashuSegment for cashuB token alone, got ${segment::class.simpleName} (\"${segment.segmentText.take(20)}…\")",
|
||||
)
|
||||
assertEquals(realCashuBToken, segment.segmentText)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun detectsCashuBSurroundedByText() {
|
||||
val text = "Here is a token: $realCashuBToken — enjoy"
|
||||
val state = RichTextParser().parseText(text, EmptyTagList, null)
|
||||
val tokenSegment =
|
||||
state.paragraphs[0]
|
||||
.words
|
||||
.firstOrNull { it is CashuSegment }
|
||||
assertTrue(
|
||||
tokenSegment != null,
|
||||
"Expected a CashuSegment in the parsed words; got " +
|
||||
state.paragraphs[0]
|
||||
.words
|
||||
.joinToString { (it::class.simpleName ?: "?") + "(\"${it.segmentText.take(12)}…\")" },
|
||||
)
|
||||
assertEquals(realCashuBToken, tokenSegment.segmentText)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun detectsCashuBWithLeadingNewline() {
|
||||
val text = "Here is a token:\n$realCashuBToken"
|
||||
val state = RichTextParser().parseText(text, EmptyTagList, null)
|
||||
val tokenSegment =
|
||||
state.paragraphs
|
||||
.flatMap { it.words }
|
||||
.firstOrNull { it is CashuSegment }
|
||||
assertTrue(
|
||||
tokenSegment != null,
|
||||
"Expected a CashuSegment after a newline; got " +
|
||||
state.paragraphs.flatMap { it.words }.joinToString { it::class.simpleName ?: "?" },
|
||||
)
|
||||
assertEquals(realCashuBToken, tokenSegment.segmentText)
|
||||
}
|
||||
}
|
||||
+1
-32
@@ -56,37 +56,6 @@ class ExpandableTextCutOffCalculatorTest {
|
||||
|
||||
@Test
|
||||
fun testImage() {
|
||||
// The data: URI has no whitespace anywhere — cutting it mid-base64 would
|
||||
// give RichTextViewer a malformed token that fails the BASE64_IMAGE regex
|
||||
// and falls back to a wall of raw text. Render the whole atom instead.
|
||||
assertEquals(image.length, ExpandableTextCutOffCalculator.indexToCutOff(image))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStandaloneCashuBTokenIsNotTruncated() {
|
||||
// Real 480-char cashuB token reported by a user — no whitespace, no
|
||||
// newlines. The pre-fix calculator cut at 350, leaving the renderer
|
||||
// with a corrupt token that failed to decode (wall of base64 + a
|
||||
// useless "Show more" button). With the fix, the whole token is
|
||||
// rendered so CashuPreview can decode and show the redeem card.
|
||||
val token =
|
||||
"cashuBv2FteCJodHRwczovL21pbnQubWluaWJpdHMuY2FzaC9CaXRjb2luYXVjc2F0YWRkVEVTVGF0n79haUgAEHk32wzI" +
|
||||
"ZWFwn79hYQJhc3hAZWM1YWI3Yjc1NjViYjBjZTZhNzg2NzBkMDA0OGExMjVlZGQzMjJhYmVjMTEzYWMwZTBmZGVkZmE3NTQ4Mzg3OWFj" +
|
||||
"WCED0-ops8Ta6NjKChNJPe_jgIbXLlyxg2KSy2WaSTADo5D_v2FhCGFzeEBkNDZlODU5MDExNjU0NmNjZjAwNTE3ZTQ1NmU0MTY0N2Fm" +
|
||||
"ZWUxOWNlMzY2N2IzYTcxODZkMzEwZDY1MjM3OTM4YWNYIQMTDGTY943O4ojhKopoYdemsUE2rSLfzwNBODL8WgOX0v______"
|
||||
assertEquals(token.length, ExpandableTextCutOffCalculator.indexToCutOff(token))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTextBeforeLongTokenCutsAtTheBoundary() {
|
||||
// Preamble of breathable text followed by a single very long atomic
|
||||
// token. The cut should land at the boundary BEFORE the token starts,
|
||||
// not inside it.
|
||||
val preamble = "Here is a token for you: "
|
||||
val token = "cashuBv2F" + "x".repeat(800)
|
||||
val content = preamble + token
|
||||
|
||||
val cut = ExpandableTextCutOffCalculator.indexToCutOff(content)
|
||||
assertEquals(preamble.length - 1, cut) // index of the space right before the token
|
||||
assertEquals(350, ExpandableTextCutOffCalculator.indexToCutOff(image))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user