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:
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)