damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | LICENSE

commit cf7cba09bd8c5092b06d664d4fe2fb44e7751667
parent 812abba8d46647c8d4e65a5b9837c53b5447837b
Author: William Casarin <jb55@jb55.com>
Date:   Sun,  8 May 2022 20:34:57 -0700

parse hashtags

Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
Mdamus/Models/EventRef.swift | 2++
Mdamus/Models/Mentions.swift | 70+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mdamus/Views/NoteContentView.swift | 10++++++++--
Mdamus/Views/ProfileView.swift | 4++--
MdamusTests/ReplyTests.swift | 15++++-----------
MdamusTests/damusTests.swift | 23+++++++++++++++++++++--
6 files changed, 100 insertions(+), 24 deletions(-)

diff --git a/damus/Models/EventRef.swift b/damus/Models/EventRef.swift @@ -78,6 +78,8 @@ func build_mention_indices(_ blocks: [Block], type: MentionType) -> Set<Int> { } case .text: return + case .hashtag: + return } } } diff --git a/damus/Models/Mentions.swift b/damus/Models/Mentions.swift @@ -36,12 +36,20 @@ struct IdBlock: Identifiable { enum Block { case text(String) case mention(Mention) + case hashtag(String) - var is_text: Bool { - if case .text = self { - return true + var is_hashtag: String? { + if case .hashtag(let htag) = self { + return htag } - return false + return nil + } + + var is_text: String? { + if case .text(let txt) = self { + return txt + } + return nil } var is_mention: Bool { @@ -59,6 +67,8 @@ func render_blocks(blocks: [Block]) -> String { return str + "#[\(m.index)]" case .text(let txt): return str + txt + case .hashtag(let htag): + return "#" + htag } } } @@ -73,9 +83,8 @@ func parse_mentions(content: String, tags: [[String]]) -> [Block] { var starting_from: Int = 0 while p.pos < content.count { - if (!consume_until(p, match: { $0 == "#" })) { - blocks.append(parse_textblock(str: p.str, from: starting_from, to: p.str.count)) - return blocks + if !consume_until(p, match: { $0 == "#" }) { + break } let pre_mention = p.pos @@ -83,14 +92,61 @@ func parse_mentions(content: String, tags: [[String]]) -> [Block] { blocks.append(parse_textblock(str: p.str, from: starting_from, to: pre_mention)) blocks.append(.mention(mention)) starting_from = p.pos + } else if let hashtag = parse_hashtag(p) { + blocks.append(parse_textblock(str: p.str, from: starting_from, to: pre_mention)) + blocks.append(.hashtag(hashtag)) + starting_from = p.pos } else { p.pos += 1 } } + if p.str.count - starting_from > 0 { + blocks.append(parse_textblock(str: p.str, from: starting_from, to: p.str.count)) + } + return blocks } +func parse_while(_ p: Parser, match: (Character) -> Bool) -> String? { + var i: Int = 0 + let sub = substring(p.str, start: p.pos, end: p.str.count) + let start = p.pos + for c in sub { + if match(c) { + p.pos += 1 + } else { + break + } + i += 1 + } + + let end = start + i + if start == end { + return nil + } + return String(substring(p.str, start: start, end: end)) +} + +func is_hashtag_char(_ c: Character) -> Bool { + return c.isLetter || c.isNumber +} + +func parse_hashtag(_ p: Parser) -> String? { + let start = p.pos + + if !parse_char(p, "#") { + return nil + } + + guard let str = parse_while(p, match: is_hashtag_char) else { + p.pos = start + return nil + } + + return str +} + func parse_mention(_ p: Parser, tags: [[String]]) -> Mention? { let start = p.pos diff --git a/damus/Views/NoteContentView.swift b/damus/Views/NoteContentView.swift @@ -15,6 +15,8 @@ func render_note_content(ev: NostrEvent, profiles: Profiles) -> String { return str + mention_str(m, profiles: profiles) case .text(let txt): return str + txt + case .hashtag(let htag): + return str + hashtag_str(htag) } } } @@ -49,14 +51,18 @@ struct NoteContentView: View { if m.type == .pubkey && m.ref.ref_id == profile.pubkey { content = render_note_content(ev: event, profiles: profiles) } - case .text: - return + case .text: return + case .hashtag: return } } } } } +func hashtag_str(_ htag: String) -> String { + return "[#\(htag)](nostr:hashtag:\(htag))" +} + func mention_str(_ m: Mention, profiles: Profiles) -> String { switch m.type { case .pubkey: diff --git a/damus/Views/ProfileView.swift b/damus/Views/ProfileView.swift @@ -22,9 +22,9 @@ struct ProfileView: View { @EnvironmentObject var profiles: Profiles var TopSection: some View { - VStack{ + VStack(alignment: .leading) { let data = profiles.lookup(id: profile.pubkey) - HStack { + HStack(alignment: .top) { ProfilePicView(pubkey: profile.pubkey, size: PFP_SIZE!, highlight: .custom(Color.black, 2), image_cache: damus.image_cache) .environmentObject(profiles) diff --git a/damusTests/ReplyTests.swift b/damusTests/ReplyTests.swift @@ -97,9 +97,9 @@ class ReplyTests: XCTestCase { XCTAssertNotNil(parsed) XCTAssertEqual(parsed.count, 3) - XCTAssertTrue(parsed[0].is_text) - XCTAssertTrue(parsed[1].is_mention) - XCTAssertTrue(parsed[2].is_text) + XCTAssertEqual(parsed[0].is_text!, "this is ") + XCTAssertNotNil(parsed[1].is_mention) + XCTAssertEqual(parsed[2].is_text!, " a mention") } func testEmptyPostReference() throws { @@ -349,14 +349,7 @@ class ReplyTests: XCTestCase { XCTAssertNotNil(parsed) XCTAssertEqual(parsed.count, 1) - XCTAssertTrue(parsed[0].is_text) - - guard case .text(let txt) = parsed[0] else { - XCTAssertTrue(false) - return - } - - XCTAssertEqual(txt, "this is #[0] a mention") + XCTAssertEqual(parsed[0].is_text!, "this is #[0] a mention") } diff --git a/damusTests/damusTests.swift b/damusTests/damusTests.swift @@ -61,7 +61,7 @@ class damusTests: XCTestCase { XCTAssertNotNil(parsed) XCTAssertEqual(parsed.count, 1) - XCTAssertTrue(parsed[0].is_text) + XCTAssertNotNil(parsed[0].is_text) } func testParseMentionBlank() { @@ -71,12 +71,31 @@ class damusTests: XCTestCase { XCTAssertEqual(parsed.count, 0) } + func testParseHashtag() { + let parsed = parse_mentions(content: "some hashtag #bitcoin derp", tags: []) + + XCTAssertNotNil(parsed) + XCTAssertEqual(parsed.count, 3) + XCTAssertEqual(parsed[0].is_text!, "some hashtag ") + XCTAssertEqual(parsed[1].is_hashtag!, "bitcoin") + XCTAssertEqual(parsed[2].is_text!, " derp") + } + + func testParseHashtagEnd() { + let parsed = parse_mentions(content: "some hashtag #bitcoin", tags: []) + + XCTAssertNotNil(parsed) + XCTAssertEqual(parsed.count, 2) + XCTAssertEqual(parsed[0].is_text!, "some hashtag ") + XCTAssertEqual(parsed[1].is_hashtag!, "bitcoin") + } + func testParseMentionOnlyText() { let parsed = parse_mentions(content: "there is no mention here", tags: [["e", "event_id"]]) XCTAssertNotNil(parsed) XCTAssertEqual(parsed.count, 1) - XCTAssertTrue(parsed[0].is_text) + XCTAssertEqual(parsed[0].is_text!, "there is no mention here") guard case .text(let txt) = parsed[0] else { XCTAssertTrue(false)