damus

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

commit 73652513d91c0c780dc6bfb3d74f336dfa90711a
parent 4704431c7449080c949c0048c3d560c185ba092d
Author: William Casarin <jb55@jb55.com>
Date:   Wed,  4 May 2022 21:33:08 -0700

initial mention parsing

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

Diffstat:
Mdamus.xcodeproj/project.pbxproj | 16++++++++++++++++
Mdamus/Models/Mentions.swift | 16++++++++++++++--
Adamus/Views/BlocksView.swift | 16++++++++++++++++
Mdamus/Views/ChatView.swift | 3+--
Mdamus/Views/EventView.swift | 2+-
Adamus/Views/MentionView.swift | 33+++++++++++++++++++++++++++++++++
Adamus/Views/NoteContentView.swift | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdamus/Views/ProfileView.swift | 7++++++-
Adamus/Views/PubkeyView.swift | 33+++++++++++++++++++++++++++++++++
Mdamus/Views/ThreadView.swift | 1+
MdamusTests/damusTests.swift | 31+++++++++++++++++++++++++++++++
11 files changed, 209 insertions(+), 6 deletions(-)

diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj @@ -15,6 +15,10 @@ 4C0A3F97280F8E02000448DE /* ThreadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A3F96280F8E02000448DE /* ThreadView.swift */; }; 4C363A8428233689006E126D /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8328233689006E126D /* Parser.swift */; }; 4C363A8628234FDE006E126D /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8528234FDE006E126D /* ImageCache.swift */; }; + 4C363A8828236948006E126D /* BlocksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8728236948006E126D /* BlocksView.swift */; }; + 4C363A8A28236B57006E126D /* MentionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8928236B57006E126D /* MentionView.swift */; }; + 4C363A8C28236B92006E126D /* PubkeyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8B28236B92006E126D /* PubkeyView.swift */; }; + 4C363A8E28236FE4006E126D /* NoteContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8D28236FE4006E126D /* NoteContentView.swift */; }; 4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD12819DB9B00B3DE84 /* ProfileModel.swift */; }; 4C3BEFD42819DE8F00B3DE84 /* NostrKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */; }; 4C3BEFD6281D995700B3DE84 /* ActionBarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD5281D995700B3DE84 /* ActionBarModel.swift */; }; @@ -83,6 +87,10 @@ 4C0A3F96280F8E02000448DE /* ThreadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadView.swift; sourceTree = "<group>"; }; 4C363A8328233689006E126D /* Parser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = "<group>"; }; 4C363A8528234FDE006E126D /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; }; + 4C363A8728236948006E126D /* BlocksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlocksView.swift; sourceTree = "<group>"; }; + 4C363A8928236B57006E126D /* MentionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionView.swift; sourceTree = "<group>"; }; + 4C363A8B28236B92006E126D /* PubkeyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PubkeyView.swift; sourceTree = "<group>"; }; + 4C363A8D28236FE4006E126D /* NoteContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteContentView.swift; sourceTree = "<group>"; }; 4C3BEFD12819DB9B00B3DE84 /* ProfileModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileModel.swift; sourceTree = "<group>"; }; 4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrKind.swift; sourceTree = "<group>"; }; 4C3BEFD5281D995700B3DE84 /* ActionBarModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionBarModel.swift; sourceTree = "<group>"; }; @@ -189,6 +197,10 @@ 4C0A3F94280F6C78000448DE /* ReplyQuoteView.swift */, 4C0A3F96280F8E02000448DE /* ThreadView.swift */, 4C8682862814DE470026224F /* ProfileView.swift */, + 4C363A8728236948006E126D /* BlocksView.swift */, + 4C363A8928236B57006E126D /* MentionView.swift */, + 4C363A8B28236B92006E126D /* PubkeyView.swift */, + 4C363A8D28236FE4006E126D /* NoteContentView.swift */, ); path = Views; sourceTree = "<group>"; @@ -430,7 +442,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4C363A8A28236B57006E126D /* MentionView.swift in Sources */, 4CE4F8CD281352B30009DFBB /* Notifications.swift in Sources */, + 4C363A8C28236B92006E126D /* PubkeyView.swift in Sources */, 4C363A8628234FDE006E126D /* ImageCache.swift in Sources */, 4C75EFB728049D990006080F /* RelayPool.swift in Sources */, 4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */, @@ -454,6 +468,7 @@ 4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */, 4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */, 4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */, + 4C363A8828236948006E126D /* BlocksView.swift in Sources */, 4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */, 4C8682872814DE470026224F /* ProfileView.swift in Sources */, 4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */, @@ -465,6 +480,7 @@ 4CEE2AED2805B22500AB5EEF /* NostrRequest.swift in Sources */, 4C0A3F95280F6C78000448DE /* ReplyQuoteView.swift in Sources */, 4C3BEFDA281DCA1400B3DE84 /* LikeCounter.swift in Sources */, + 4C363A8E28236FE4006E126D /* NoteContentView.swift in Sources */, 4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */, 4C0A3F97280F8E02000448DE /* ThreadView.swift in Sources */, 4C75EFA427FA577B0006080F /* PostView.swift in Sources */, diff --git a/damus/Models/Mentions.swift b/damus/Models/Mentions.swift @@ -14,7 +14,13 @@ enum MentionType { struct Mention { let index: Int - let kind: MentionType + let type: MentionType + let ref: ReferencedId +} + +struct IdBlock: Identifiable { + let id: String = UUID().description + let block: Block } enum Block { @@ -90,6 +96,8 @@ 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 { + p.pos += 1 } } @@ -127,6 +135,10 @@ func parse_mention(_ p: Parser, tags: [[String]]) -> Mention? { default: return nil } - return Mention(index: digit, kind: kind) + guard let ref = tag_to_refid(tags[digit]) else { + return nil + } + + return Mention(index: digit, type: kind, ref: ref) } diff --git a/damus/Views/BlocksView.swift b/damus/Views/BlocksView.swift @@ -0,0 +1,16 @@ +// +// BlocksView.swift +// damus +// +// Created by William Casarin on 2022-05-04. +// + +import SwiftUI + +/* +struct BlocksView_Previews: PreviewProvider { + static var previews: some View { + BlocksView() + } +} + */ diff --git a/damus/Views/ChatView.swift b/damus/Views/ChatView.swift @@ -128,8 +128,7 @@ struct ChatView: View { } } - Text(event.content) - .textSelection(.enabled) + NoteContentView(event) if is_active || next_ev == nil || next_ev!.pubkey != event.pubkey { EventActionBar(event: event, diff --git a/damus/Views/EventView.swift b/damus/Views/EventView.swift @@ -74,7 +74,7 @@ struct EventView: View { .frame(maxWidth: .infinity, alignment: .leading) } - Text(event.content) + NoteContentView(event) .frame(maxWidth: .infinity, alignment: .leading) .textSelection(.enabled) diff --git a/damus/Views/MentionView.swift b/damus/Views/MentionView.swift @@ -0,0 +1,33 @@ +// +// MentionView.swift +// damus +// +// Created by William Casarin on 2022-05-04. +// + +import SwiftUI + +struct MentionView: View { + let mention: Mention + + @EnvironmentObject var profiles: Profiles + + var body: some View { + switch mention.type { + case .pubkey: + PubkeyView(pubkey: mention.ref.ref_id, relay: mention.ref.relay_id) + .environmentObject(profiles) + case .event: + Text("< e >") + //EventBlockView(pubkey: mention.ref.ref_id, relay: mention.ref.relay_id) + } + } +} + +/* +struct MentionView_Previews: PreviewProvider { + static var previews: some View { + MentionView() + } +} +*/ diff --git a/damus/Views/NoteContentView.swift b/damus/Views/NoteContentView.swift @@ -0,0 +1,57 @@ +// +// NoteContentView.swift +// damus +// +// Created by William Casarin on 2022-05-04. +// + +import SwiftUI + +func NoteContentView(_ ev: NostrEvent) -> some View { + let txt = parse_mentions(content: ev.content, tags: ev.tags) + .reduce("") { str, block in + switch block { + case .mention(let m): + return str + mention_str(m) + case .text(let txt): + return str + txt + } + } + + let md_opts: AttributedString.MarkdownParsingOptions = + .init(interpretedSyntax: .inlineOnlyPreservingWhitespace) + + guard let txt = try? AttributedString(markdown: txt, options: md_opts) else { + return Text(ev.content) + } + + return Text(txt) +} + +func mention_str(_ m: Mention) -> String { + switch m.type { + case .pubkey: + let pk = m.ref.ref_id + return "[@\(abbrev_pubkey(pk))](nostr:\(encode_pubkey(m.ref)))" + case .event: + let evid = m.ref.ref_id + return "[*\(abbrev_pubkey(evid))](nostr:\(encode_event_id(m.ref)))" + } +} + +// TODO: bech32 and relay hints +func encode_event_id(_ ref: ReferencedId) -> String { + return "e_" + ref.ref_id +} + +func encode_pubkey(_ ref: ReferencedId) -> String { + return "p_" + ref.ref_id +} + +/* +struct NoteContentView_Previews: PreviewProvider { + static var previews: some View { + NoteContentView() + } +} + */ diff --git a/damus/Views/ProfileView.swift b/damus/Views/ProfileView.swift @@ -24,7 +24,7 @@ struct ProfileView: View { var TopSection: some View { HStack(alignment: .top) { let data = profiles.lookup(id: profile.pubkey) - ProfilePicView(picture: data?.picture, size: 64, highlight: .custom(Color.black, 4), image_cache: damus.image_cache) + ProfilePicView(picture: data?.picture, size: PFP_SIZE!, highlight: .custom(Color.black, 4), image_cache: damus.image_cache) //.border(Color.blue) VStack(alignment: .leading) { if let pubkey = profile.pubkey { @@ -44,11 +44,14 @@ struct ProfileView: View { var body: some View { VStack(alignment: .leading) { TopSection + /* Picker("", selection: $selected_tab) { Text("Posts").tag(ProfileTab.posts) Text("Following").tag(ProfileTab.following) } .pickerStyle(SegmentedPickerStyle()) + */ + Divider() @@ -64,7 +67,9 @@ struct ProfileView: View { .frame(maxHeight: .infinity, alignment: .topLeading) } //.border(Color.white) + .padding([.leading, .trailing], 6) .frame(maxWidth: .infinity, alignment: .topLeading) + .navigationBarTitle("Profile") .onAppear() { profile.subscribe() diff --git a/damus/Views/PubkeyView.swift b/damus/Views/PubkeyView.swift @@ -0,0 +1,33 @@ +// +// PubkeyView.swift +// damus +// +// Created by William Casarin on 2022-05-04. +// + +import SwiftUI + +struct PubkeyView: View { + let pubkey: String + let relay: String? + + var body: some View { + let color: Color = id_to_color(pubkey) + ZStack { + Text("\(abbrev_pubkey(pubkey))") + .foregroundColor(color) + } + } +} + +func abbrev_pubkey(_ pubkey: String) -> String { + return pubkey.prefix(4) + ":" + pubkey.suffix(4) +} + +/* +struct PubkeyView_Previews: PreviewProvider { + static var previews: some View { + PubkeyView() + } +} + */ diff --git a/damus/Views/ThreadView.swift b/damus/Views/ThreadView.swift @@ -36,6 +36,7 @@ struct ThreadView: View { } */ } + .padding([.leading, .trailing], 6) .onReceive(NotificationCenter.default.publisher(for: .switched_timeline)) { n in dismiss() } diff --git a/damusTests/damusTests.swift b/damusTests/damusTests.swift @@ -58,6 +58,37 @@ class damusTests: XCTestCase { XCTAssertEqual(txt, "this is #[0] a mention") } + func testParseMentionWithMarkdown() { + let md = """ + Testing markdown in damus + + **bold** + + _italics_ + + `monospace` + + # h1 + + ## h2 + + ### h3 + + * list1 + * list2 + + > some awesome quote + + [my website](https://jb55.com) + """ + + let parsed = parse_mentions(content: md, tags: []) + + XCTAssertNotNil(parsed) + XCTAssertEqual(parsed.count, 1) + XCTAssertTrue(parsed[0].is_text) + } + func testParseMentionBlank() { let parsed = parse_mentions(content: "", tags: [["e", "event_id"]])