damus

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

commit 4db06b015c37ff0abe2b6d71b1757d739097d389
parent 0ad17c05fe1f5fb41ab4e9b0b1901041273d4ddb
Author: William Casarin <jb55@jb55.com>
Date:   Sat,  7 May 2022 17:48:00 -0700

event mentions working

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

Diffstat:
Mdamus/ContentView.swift | 20+++++++++++++++++++-
Mdamus/Models/Mentions.swift | 70+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mdamus/Models/ThreadModel.swift | 63++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mdamus/Nostr/NostrEvent.swift | 8++++++++
Mdamus/Views/ChatView.swift | 2+-
Mdamus/Views/ChatroomView.swift | 6+++---
Mdamus/Views/EventDetailView.swift | 2+-
Mdamus/Views/EventView.swift | 2+-
Mdamus/Views/TimelineView.swift | 2+-
MdamusTests/damusTests.swift | 25+++++++++++++++++++++++++
10 files changed, 174 insertions(+), 26 deletions(-)

diff --git a/damus/ContentView.swift b/damus/ContentView.swift @@ -59,7 +59,9 @@ struct ContentView: View { @State var friend_events: [NostrEvent] = [] @State var notifications: [NostrEvent] = [] @State var active_profile: String? = nil + @State var active_event_id: String? = nil @State var profile_open: Bool = false + @State var thread_open: Bool = false // connect retry timer let timer = Timer.publish(every: 60, on: .main, in: .common).autoconnect() @@ -151,6 +153,9 @@ struct ContentView: View { NavigationLink(destination: MaybeProfileView, isActive: $profile_open) { EmptyView() } + NavigationLink(destination: MaybeThreadView, isActive: $thread_open) { + EmptyView() + } switch selected_timeline { case .home: PostingTimelineView @@ -178,6 +183,18 @@ struct ContentView: View { .navigationViewStyle(.stack) } + var MaybeThreadView: some View { + Group { + if let evid = self.active_event_id { + let thread_model = ThreadModel(evid: evid, pool: damus!.pool) + ThreadView(thread: thread_model, damus: damus!) + .environmentObject(profiles) + } else { + EmptyView() + } + } + } + var MaybeProfileView: some View { Group { if let pk = self.active_profile { @@ -226,7 +243,8 @@ struct ContentView: View { active_profile = ref.ref_id profile_open = true } else if ref.key == "e" { - // TODO open event view + active_event_id = ref.ref_id + thread_open = true } case .filter: break diff --git a/damus/Models/Mentions.swift b/damus/Models/Mentions.swift @@ -129,15 +129,71 @@ func parse_mention(_ p: Parser, tags: [[String]]) -> Mention? { return Mention(index: digit, type: kind, ref: ref) } -func post_to_event(post: NostrPost, privkey: String, pubkey: String) -> NostrEvent { - let new_ev = NostrEvent(content: post.content, pubkey: pubkey) - for id in post.references { - var tag = [id.key, id.ref_id] - if let relay_id = id.relay_id { - tag.append(relay_id) +func find_tag_ref(type: String, id: String, tags: [[String]]) -> Int? { + var i: Int = 0 + for tag in tags { + if tag.count >= 2 { + if tag[0] == type && tag[1] == id { + return i + } } - new_ev.tags.append(tag) + i += i + } + + return nil +} + +struct PostTags { + let blocks: [Block] + let tags: [[String]] +} + +func parse_mention_type(_ c: String) -> MentionType? { + if c == "e" { + return .event + } else if c == "p" { + return .pubkey } + + return nil +} + +/// Convert +func make_post_tags(post_blocks: [PostBlock], tags: [[String]]) -> PostTags { + var new_tags = tags + var blocks: [Block] = [] + + for post_block in post_blocks { + switch post_block { + case .ref(let ref): + guard let mention_type = parse_mention_type(ref.key) else { + continue + } + if let ind = find_tag_ref(type: ref.key, id: ref.ref_id, tags: tags) { + let mention = Mention(index: ind, type: mention_type, ref: ref) + let block = Block.mention(mention) + blocks.append(block) + } else { + let ind = new_tags.count + new_tags.append(refid_to_tag(ref)) + let mention = Mention(index: ind, type: mention_type, ref: ref) + let block = Block.mention(mention) + blocks.append(block) + } + case .text(let txt): + blocks.append(Block.text(txt)) + } + } + + return PostTags(blocks: blocks, tags: new_tags) +} + +func post_to_event(post: NostrPost, privkey: String, pubkey: String) -> NostrEvent { + let tags = post.references.map(refid_to_tag) + let post_blocks = parse_post_blocks(content: post.content) + let post_tags = make_post_tags(post_blocks: post_blocks, tags: tags) + let content = render_blocks(blocks: post_tags.blocks) + let new_ev = NostrEvent(content: content, pubkey: pubkey, kind: 1, tags: post_tags.tags) new_ev.calculate_id() new_ev.sign(privkey: privkey) return new_ev diff --git a/damus/Models/ThreadModel.swift b/damus/Models/ThreadModel.swift @@ -7,24 +7,57 @@ import Foundation +enum InitialEvent { + case event(NostrEvent) + case event_id(String) + + var id: String { + switch self { + case .event(let ev): + return ev.id + case .event_id(let evid): + return evid + } + } +} + /// manages the lifetime of a thread class ThreadModel: ObservableObject { - @Published var event: NostrEvent + @Published var initial_event: InitialEvent @Published var events: [NostrEvent] = [] @Published var event_map: [String: Int] = [:] var replies: ReplyMap = ReplyMap() + var event: NostrEvent? { + switch initial_event { + case .event(let ev): + return ev + case .event_id(let evid): + for event in events { + if event.id == evid { + return event + } + } + return nil + } + } + let pool: RelayPool var sub_id = UUID().description + + init(evid: String, pool: RelayPool) { + self.pool = pool + self.initial_event = .event_id(evid) + } - init(ev: NostrEvent, pool: RelayPool) { - self.event = ev + init(event: NostrEvent, pool: RelayPool) { self.pool = pool + self.initial_event = .event(event) } func unsubscribe() { self.pool.unsubscribe(sub_id: sub_id) - print("unsubscribing from thread \(event.id) with sub_id \(sub_id)") + print("unsubscribing from thread \(initial_event.id) with sub_id \(sub_id)") } func reset_events() { @@ -50,11 +83,10 @@ class ThreadModel: ObservableObject { func set_active_event(_ ev: NostrEvent) { if should_resubscribe(ev) { unsubscribe() - self.event = ev - add_event(ev) + self.initial_event = .event(ev) subscribe() } else { - self.event = ev + self.initial_event = .event(ev) if events.count == 0 { add_event(ev) } @@ -68,13 +100,20 @@ class ThreadModel: ObservableObject { //var likes_filter = NostrFilter.filter_kinds(7]) // TODO: add referenced relays - ref_events.referenced_ids = event.referenced_ids.map { $0.ref_id } - ref_events.referenced_ids!.append(event.id) + switch self.initial_event { + case .event(let ev): + ref_events.referenced_ids = ev.referenced_ids.map { $0.ref_id } + ref_events.referenced_ids?.append(ev.id) + events_filter.ids = ref_events.referenced_ids! + events_filter.ids?.append(ev.id) + case .event_id(let evid): + events_filter.ids = [evid] + ref_events.referenced_ids = [evid] + } //likes_filter.ids = ref_events.referenced_ids! - events_filter.ids = ref_events.referenced_ids! - print("subscribing to thread \(event.id) with sub_id \(sub_id)") + print("subscribing to thread \(initial_event.id) with sub_id \(sub_id)") pool.register_handler(sub_id: sub_id, handler: handle_event) pool.send(.subscribe(.init(filters: [ref_events, events_filter], sub_id: sub_id))) } @@ -97,6 +136,8 @@ class ThreadModel: ObservableObject { self.events.append(ev) self.events = self.events.sorted { $0.created_at < $1.created_at } + objectWillChange.send() + var i: Int = 0 for ev in events { self.event_map[ev.id] = i diff --git a/damus/Nostr/NostrEvent.swift b/damus/Nostr/NostrEvent.swift @@ -359,6 +359,14 @@ func random_bytes(count: Int) -> Data { return data } +func refid_to_tag(_ ref: ReferencedId) -> [String] { + var tag = [ref.key, ref.ref_id] + if let relay_id = ref.relay_id { + tag.append(relay_id) + } + return tag +} + func tag_to_refid(_ tag: [String]) -> ReferencedId? { if tag.count == 0 { return nil diff --git a/damus/Views/ChatView.swift b/damus/Views/ChatView.swift @@ -42,7 +42,7 @@ struct ChatView: View { } var is_active: Bool { - return thread.event.id == event.id + return thread.initial_event.id == event.id } func prev_reply_is_same() -> String? { diff --git a/damus/Views/ChatroomView.swift b/damus/Views/ChatroomView.swift @@ -24,7 +24,7 @@ struct ChatroomView: View { damus: damus ) .onTapGesture { - if thread.event.id == ev.id { + if thread.initial_event.id == ev.id { //dismiss() toggle_thread_view() } else { @@ -37,13 +37,13 @@ struct ChatroomView: View { } .onReceive(NotificationCenter.default.publisher(for: .select_quote)) { notif in let ev = notif.object as! NostrEvent - if ev.id != thread.event.id { + if ev.id != thread.initial_event.id { thread.set_active_event(ev) } scroll_to_event(scroller: scroller, id: ev.id, delay: 0, animate: true, anchor: .top) } .onAppear() { - scroll_to_event(scroller: scroller, id: thread.event.id, delay: 0.3, animate: true, anchor: .bottom) + scroll_to_event(scroller: scroller, id: thread.initial_event.id, delay: 0.3, animate: true, anchor: .bottom) } } } diff --git a/damus/Views/EventDetailView.swift b/damus/Views/EventDetailView.swift @@ -72,7 +72,7 @@ struct EventDetailView: View { case .event(let ev, let highlight): EventView(event: ev, highlight: highlight, has_action_bar: true, damus: damus) .onTapGesture { - if thread.event.id == ev.id { + if thread.initial_event.id == ev.id { toggle_thread_view() } else { thread.set_active_event(ev) diff --git a/damus/Views/EventView.swift b/damus/Views/EventView.swift @@ -102,7 +102,7 @@ struct EventView: View { } Button { - UIPasteboard.general.string = event.id + UIPasteboard.general.string = "&" + event.id } label: { Label("Copy ID", systemImage: "tag") } diff --git a/damus/Views/TimelineView.swift b/damus/Views/TimelineView.swift @@ -37,7 +37,7 @@ struct TimelineView: View { .environmentObject(profiles) */ - let tv = ThreadView(thread: ThreadModel(ev: ev, pool: damus.pool), damus: damus) + let tv = ThreadView(thread: ThreadModel(event: ev, pool: damus.pool), damus: damus) .environmentObject(profiles) NavigationLink(destination: tv) { diff --git a/damusTests/damusTests.swift b/damusTests/damusTests.swift @@ -48,6 +48,31 @@ class damusTests: XCTestCase { XCTAssertEqual(parsed.count, 0) } + func testPostWithMentions() throws { + let evid = "0000000000000000000000000000000000000000000000000000000000000005" + let pk = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245" + let content = "this is a @\(pk) mention" + let reply_ref = ReferencedId(ref_id: evid, relay_id: nil, key: "e") + let post = NostrPost(content: content, references: [reply_ref]) + let ev = post_to_event(post: post, privkey: evid, pubkey: pk) + + XCTAssertEqual(ev.tags.count, 2) + XCTAssertEqual(ev.content, "this is a #[1] mention") + } + + func testPostTags() throws { + let pk = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245" + let content = "this is a @\(pk) mention" + let parsed = parse_post_blocks(content: content) + let post_tags = make_post_tags(post_blocks: parsed, tags: []) + + XCTAssertEqual(post_tags.blocks.count, 3) + XCTAssertEqual(post_tags.tags.count, 1) + XCTAssertEqual(post_tags.tags[0].count, 2) + XCTAssertEqual(post_tags.tags[0][0], "p") + XCTAssertEqual(post_tags.tags[0][1], pk) + } + func testInvalidPostReference() throws { let pk = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e24" let content = "this is a @\(pk) mention"