diff --git a/damus-c/damus.c b/damus-c/damus.c index 47eac06ef..ab6ca412c 100644 --- a/damus-c/damus.c +++ b/damus-c/damus.c @@ -328,7 +328,12 @@ int damus_parse_content(struct note_blocks *blocks, const char *content) { blocks->words = 0; blocks->num_blocks = 0; make_cursor((u8*)content, (u8*)content + strlen(content), &cur); - + + // **Skip leading whitespace** + while (cur.p < cur.end && is_whitespace(*cur.p)) { + cur.p++; + } + start = cur.p; while (cur.p < cur.end && blocks->num_blocks < MAX_BLOCKS) { cp = peek_char(&cur, -1); @@ -362,7 +367,12 @@ int damus_parse_content(struct note_blocks *blocks, const char *content) { cur.p++; } - + + // **Trim trailing whitespace** + while (cur.p > start && is_whitespace(*(cur.p - 1))) { + cur.p--; + } + if (cur.p - start > 0) { if (!add_text_block(blocks, start, cur.p)) return 0; diff --git a/damus/Components/TranslateView.swift b/damus/Components/TranslateView.swift index d7d530ed8..5ec6e745e 100644 --- a/damus/Components/TranslateView.swift +++ b/damus/Components/TranslateView.swift @@ -160,7 +160,7 @@ func translate_note(profiles: Profiles, keypair: Keypair, event: NostrEvent, set // Render translated note let translated_blocks = parse_note_content(content: .content(translated_note, event.tags)) - let artifacts = render_blocks(blocks: translated_blocks, profiles: profiles) + let artifacts = render_blocks(blocks: translated_blocks, profiles: profiles, hide_last_only_previewable_ref: true) // and cache it return .translated(Translated(artifacts: artifacts, language: note_lang)) diff --git a/damus/Models/NoteContent.swift b/damus/Models/NoteContent.swift index 81adde7a6..63d783b4f 100644 --- a/damus/Models/NoteContent.swift +++ b/damus/Models/NoteContent.swift @@ -73,83 +73,74 @@ func render_note_content(ev: NostrEvent, profiles: Profiles, keypair: Keypair) - return .longform(LongformContent(ev.content)) } - return .separated(render_blocks(blocks: blocks, profiles: profiles)) + return .separated(render_blocks(blocks: blocks, profiles: profiles, hide_last_only_previewable_ref: true)) } -func render_blocks(blocks bs: Blocks, profiles: Profiles) -> NoteArtifactsSeparated { +func render_blocks(blocks bs: Blocks, profiles: Profiles, hide_last_only_previewable_ref: Bool = false) -> NoteArtifactsSeparated { var invoices: [Invoice] = [] var urls: [UrlType] = [] let blocks = bs.blocks - - let one_note_ref = blocks - .filter({ - if case .mention(let mention) = $0, - case .note = mention.ref { - return true - } - else { - return false - } - }) - .count == 1 - + + // Use lazy with prefix to avoid iterating over all blocks if at least two previewable blocks have been found. + let one_preview_ref = hide_last_only_previewable_ref && blocks.lazy.filter({ $0.is_previewable }).prefix(2).count == 1 + var ind: Int = -1 let txt: CompatibleText = blocks.reduce(CompatibleText()) { str, block in ind = ind + 1 - + let lastBlock = ind == blocks.count - 1 + + // Add the rendered previewable blocks to their type-specific lists. + switch block { + case .invoice(let invoice): + invoices.append(invoice) + case .url(let url): + let url_type = classify_url(url) + urls.append(url_type) + default: + break + } + + // No need to show the string if the block is previewable, there is only one previewable block and it is the + // last block. This is to save unnecessary use of screen space. + if one_preview_ref && lastBlock && block.is_previewable { + return str + } + switch block { case .mention(let m): - if case .note = m.ref, one_note_ref { - return str - } return str + mention_str(m, profiles: profiles) case .text(let txt): - return str + CompatibleText(stringLiteral: reduce_text_block(blocks: blocks, ind: ind, txt: txt, one_note_ref: one_note_ref)) - + return str + CompatibleText(stringLiteral: reduce_text_block(blocks: blocks, ind: ind, txt: txt, one_preview_ref: one_preview_ref)) case .relay(let relay): return str + CompatibleText(stringLiteral: relay) - case .hashtag(let htag): return str + hashtag_str(htag) case .invoice(let invoice): - invoices.append(invoice) - return str + return str + invoice_str(invoice) case .url(let url): - let url_type = classify_url(url) - switch url_type { - case .media: - urls.append(url_type) - return str - case .link(let url): - urls.append(url_type) - return str + url_str(url) - } + return str + url_str(url) } } return NoteArtifactsSeparated(content: txt, words: bs.words, urls: urls, invoices: invoices) } -func reduce_text_block(blocks: [Block], ind: Int, txt: String, one_note_ref: Bool) -> String { - var trimmed = txt - - if let prev = blocks[safe: ind-1], - case .url(let u) = prev, - classify_url(u).is_media != nil { - trimmed = " " + trim_prefix(trimmed) - } - +func reduce_text_block(blocks: [Block], ind: Int, txt: String, one_preview_ref: Bool) -> String { if let next = blocks[safe: ind+1] { - if case .url(let u) = next, classify_url(u).is_media != nil { - trimmed = trim_suffix(trimmed) - } else if case .mention(let m) = next, - case .note = m.ref, - one_note_ref { - trimmed = trim_suffix(trimmed) + if one_preview_ref && ind+1 == blocks.count-1 && next.is_previewable { + return trim_suffix(txt) } } - - return trimmed + + return txt +} + +func invoice_str(_ invoice: Invoice) -> CompatibleText { + // Add foreground color to Lightning invoice but no need to link to anything as the attached preview is sufficient. + var attributedString = AttributedString(stringLiteral: abbrev_identifier(invoice.string)) + attributedString.foregroundColor = DamusColors.purple + + return CompatibleText(attributed: attributedString) } func url_str(_ url: URL) -> CompatibleText { @@ -194,11 +185,11 @@ func mention_str(_ m: Mention, profiles: Profiles) -> CompatibleText let display_str: String = { switch m.ref { case .pubkey(let pk): return getDisplayName(pk: pk, profiles: profiles) - case .note: return abbrev_pubkey(bech32String) - case .nevent: return abbrev_pubkey(bech32String) + case .note: return abbrev_identifier(bech32String) + case .nevent: return abbrev_identifier(bech32String) case .nprofile(let nprofile): return getDisplayName(pk: nprofile.author, profiles: profiles) case .nrelay(let url): return url - case .naddr: return abbrev_pubkey(bech32String) + case .naddr: return abbrev_identifier(bech32String) } }() @@ -216,11 +207,6 @@ func trim_suffix(_ str: String) -> String { return str.replacingOccurrences(of: "\\s+$", with: "", options: .regularExpression) } -// trim prefix whitespace and newlines -func trim_prefix(_ str: String) -> String { - return str.replacingOccurrences(of: "^\\s+", with: "", options: .regularExpression) -} - struct LongformContent { let markdown: MarkdownContent let words: Int diff --git a/damus/Types/Block.swift b/damus/Types/Block.swift index a5cb99369..7ee9cdeeb 100644 --- a/damus/Types/Block.swift +++ b/damus/Types/Block.swift @@ -37,7 +37,23 @@ enum Block: Equatable { return false } } - + + var is_previewable: Bool { + switch self { + case .mention(let m): + switch m.ref { + case .note, .nevent: return true + default: return false + } + case .invoice: + return true + case .url: + return true + default: + return false + } + } + case text(String) case mention(Mention) case hashtag(String) diff --git a/damus/Util/DisplayName.swift b/damus/Util/DisplayName.swift index 60249b3bd..71ba2ed9c 100644 --- a/damus/Util/DisplayName.swift +++ b/damus/Util/DisplayName.swift @@ -55,9 +55,9 @@ func parse_display_name(profile: Profile?, pubkey: Pubkey) -> DisplayName { } func abbrev_bech32_pubkey(pubkey: Pubkey) -> String { - return abbrev_pubkey(String(pubkey.npub.dropFirst(4))) + return abbrev_identifier(String(pubkey.npub.dropFirst(4))) } -func abbrev_pubkey(_ pubkey: String, amount: Int = 8) -> String { +func abbrev_identifier(_ pubkey: String, amount: Int = 8) -> String { return pubkey.prefix(amount) + ":" + pubkey.suffix(amount) } diff --git a/damus/Views/PubkeyView.swift b/damus/Views/PubkeyView.swift index 496bcc213..79ed753cc 100644 --- a/damus/Views/PubkeyView.swift +++ b/damus/Views/PubkeyView.swift @@ -46,7 +46,7 @@ struct PubkeyView: View { let bech32 = pubkey.npub HStack { - Text(verbatim: "\(abbrev_pubkey(bech32, amount: sidemenu ? 12 : 16))") + Text(verbatim: "\(abbrev_identifier(bech32, amount: sidemenu ? 12 : 16))") .font(sidemenu ? .system(size: 10) : .footnote) .foregroundColor(keyColor()) .padding(5) diff --git a/damusTests/damusTests.swift b/damusTests/damusTests.swift index 71fd1df47..93aaa23cf 100644 --- a/damusTests/damusTests.swift +++ b/damusTests/damusTests.swift @@ -36,10 +36,9 @@ class damusTests: XCTestCase { XCTAssertEqual(bytes.count, 32) } - func testTrimmingFunctions() { + func testTrimSuffix() { let txt = " bobs " - XCTAssertEqual(trim_prefix(txt), "bobs ") XCTAssertEqual(trim_suffix(txt), " bobs") }