commit 436d20dfbde7f90c6052db2f078d7b65d9ea3cc9
parent 810b3e1fa5fa88a46dafb85acee0d27f79c33e56
Author: Swift <scoder1747@gmail.com>
Date: Fri, 24 Feb 2023 13:28:47 -0500
Rich tagging
Changelog-Changed: No more inline npubs when tagging users
Closes: #691
Diffstat:
5 files changed, 91 insertions(+), 17 deletions(-)
diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj
@@ -219,6 +219,7 @@
7C95CAEE299DCEF1009DCB67 /* KFOptionSetter+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */; };
7CFF6317299FEFE5005D382A /* SelectableText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CFF6316299FEFE5005D382A /* SelectableText.swift */; };
9609F058296E220800069BF3 /* BannerImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9609F057296E220800069BF3 /* BannerImageView.swift */; };
+ 9C83F89329A937B900136C08 /* TextViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C83F89229A937B900136C08 /* TextViewWrapper.swift */; };
BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA693073295D649800ADDB87 /* UserSettingsStore.swift */; };
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; };
DD597CBD2963D85A00C64D32 /* MarkdownTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */; };
@@ -548,6 +549,7 @@
7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KFOptionSetter+.swift"; sourceTree = "<group>"; };
7CFF6316299FEFE5005D382A /* SelectableText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableText.swift; sourceTree = "<group>"; };
9609F057296E220800069BF3 /* BannerImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerImageView.swift; sourceTree = "<group>"; };
+ 9C83F89229A937B900136C08 /* TextViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewWrapper.swift; sourceTree = "<group>"; };
BA693073295D649800ADDB87 /* UserSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsStore.swift; sourceTree = "<group>"; };
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = "<group>"; };
DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkdownTests.swift; sourceTree = "<group>"; };
@@ -741,6 +743,7 @@
4C363A8D28236FE4006E126D /* NoteContentView.swift */,
4C75EFAC28049CFB0006080F /* PostButton.swift */,
4C75EFA327FA577B0006080F /* PostView.swift */,
+ 9C83F89229A937B900136C08 /* TextViewWrapper.swift */,
4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */,
4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */,
4C285C892838B985008A31F1 /* ProfilePictureSelector.swift */,
@@ -1301,6 +1304,7 @@
E9E4ED0B295867B900DD7078 /* ThreadV2View.swift in Sources */,
4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */,
4CF0ABE7298444FD00D66079 /* MutedEventView.swift in Sources */,
+ 9C83F89329A937B900136C08 /* TextViewWrapper.swift in Sources */,
4CF0ABE12981A83900D66079 /* MutelistView.swift in Sources */,
4CB883A82975FC1800DC99E7 /* Zaps.swift in Sources */,
4C75EFB128049D510006080F /* NostrResponse.swift in Sources */,
diff --git a/damus/Models/DraftsModel.swift b/damus/Models/DraftsModel.swift
@@ -8,6 +8,6 @@
import Foundation
class Drafts: ObservableObject {
- @Published var post: String = ""
- @Published var replies: [NostrEvent: String] = [:]
+ @Published var post: NSMutableAttributedString = NSMutableAttributedString(string: "")
+ @Published var replies: [NostrEvent: NSMutableAttributedString] = [:]
}
diff --git a/damus/Views/PostView.swift b/damus/Views/PostView.swift
@@ -15,7 +15,7 @@ enum NostrPostResult {
let POST_PLACEHOLDER = NSLocalizedString("Type your post here...", comment: "Text box prompt to ask user to type their post.")
struct PostView: View {
- @State var post: String = ""
+ @State var post: NSMutableAttributedString = NSMutableAttributedString()
@FocusState var focus: Bool
@State var showPrivateKeyWarning: Bool = false
@@ -44,7 +44,14 @@ struct PostView: View {
if replying_to?.known_kind == .chat {
kind = .chat
}
- let content = self.post.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
+
+ post.enumerateAttributes(in: NSRange(location: 0, length: post.length), options: []) { attributes, range, stop in
+ if let link = attributes[.link] as? String {
+ post.replaceCharacters(in: range, with: link)
+ }
+ }
+
+ let content = self.post.string.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
let new_post = NostrPost(content: content, references: references, kind: kind)
NotificationCenter.default.post(name: .post, object: NostrPostResult.post(new_post))
@@ -52,14 +59,14 @@ struct PostView: View {
if let replying_to {
damus_state.drafts.replies.removeValue(forKey: replying_to)
} else {
- damus_state.drafts.post = ""
+ damus_state.drafts.post = NSMutableAttributedString(string: "")
}
dismiss()
}
var is_post_empty: Bool {
- return post.allSatisfy { $0.isWhitespace }
+ return post.string.allSatisfy { $0.isWhitespace }
}
var body: some View {
@@ -74,7 +81,7 @@ struct PostView: View {
if !is_post_empty {
Button(NSLocalizedString("Post", comment: "Button to post a note.")) {
- showPrivateKeyWarning = contentContainsPrivateKey(self.post)
+ showPrivateKeyWarning = contentContainsPrivateKey(self.post.string)
if !showPrivateKeyWarning {
self.send_post()
@@ -97,7 +104,7 @@ struct PostView: View {
VStack(alignment: .leading) {
ZStack(alignment: .topLeading) {
- TextEditor(text: $post)
+ TextViewWrapper(attributedText: $post)
.focused($focus)
.textInputAutocapitalization(.sentences)
.onChange(of: post) { _ in
@@ -108,7 +115,7 @@ struct PostView: View {
}
}
- if post.isEmpty {
+ if post.string.isEmpty {
Text(POST_PLACEHOLDER)
.padding(.top, 8)
.padding(.leading, 4)
@@ -120,7 +127,7 @@ struct PostView: View {
}
// This if-block observes @ for tagging
- if let searching = get_searching_string(post) {
+ if let searching = get_searching_string(post.string) {
VStack {
Spacer()
UserSearch(damus_state: damus_state, search: searching, post: $post)
@@ -130,7 +137,7 @@ struct PostView: View {
.onAppear() {
if let replying_to {
if damus_state.drafts.replies[replying_to] == nil {
- damus_state.drafts.replies[replying_to] = ""
+ damus_state.drafts.post = NSMutableAttributedString(string: "")
}
if let p = damus_state.drafts.replies[replying_to] {
post = p
@@ -144,10 +151,10 @@ struct PostView: View {
}
}
.onDisappear {
- if let replying_to, let reply = damus_state.drafts.replies[replying_to], reply.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
+ if let replying_to, let reply = damus_state.drafts.replies[replying_to], reply.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
damus_state.drafts.replies.removeValue(forKey: replying_to)
- } else if replying_to == nil && damus_state.drafts.post.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
- damus_state.drafts.post = ""
+ } else if replying_to == nil && damus_state.drafts.post.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
+ damus_state.drafts.post = NSMutableAttributedString(string : "")
}
}
.padding()
diff --git a/damus/Views/Posting/UserSearch.swift b/damus/Views/Posting/UserSearch.swift
@@ -20,7 +20,8 @@ struct SearchedUser: Identifiable {
struct UserSearch: View {
let damus_state: DamusState
let search: String
- @Binding var post: String
+
+ @Binding var post: NSMutableAttributedString
var users: [SearchedUser] {
guard let contacts = damus_state.contacts.event else {
@@ -39,7 +40,25 @@ struct UserSearch: View {
guard let pk = bech32_pubkey(user.pubkey) else {
return
}
- post = post.replacingOccurrences(of: "@"+search, with: "@"+pk+" ")
+
+ while post.string.last != "@" {
+ post.deleteCharacters(in: NSRange(location: post.length - 1, length: 1))
+ }
+ post.deleteCharacters(in: NSRange(location: post.length - 1, length: 1))
+
+
+ var tagString = ""
+ if let name = user.profile?.name {
+ tagString = "@\(name)\u{200B} "
+ }
+ let tagAttributedString = NSMutableAttributedString(string: tagString,
+ attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18.0),
+ NSAttributedString.Key.link: "@\(pk)"])
+ tagAttributedString.removeAttribute(.link, range: NSRange(location: tagAttributedString.length - 2, length: 2))
+ let mutableString = NSMutableAttributedString()
+ mutableString.append(post)
+ mutableString.append(tagAttributedString)
+ post = mutableString
}
}
}
@@ -49,7 +68,7 @@ struct UserSearch: View {
struct UserSearch_Previews: PreviewProvider {
static let search: String = "jb55"
- @State static var post: String = "some @jb55"
+ @State static var post: NSMutableAttributedString = NSMutableAttributedString(string: "some @jb55")
static var previews: some View {
UserSearch(damus_state: test_damus_state(), search: search, post: $post)
diff --git a/damus/Views/TextViewWrapper.swift b/damus/Views/TextViewWrapper.swift
@@ -0,0 +1,44 @@
+//
+// TextViewWrapper.swift
+// damus
+//
+// Created by Swift on 2/24/23.
+//
+
+import SwiftUI
+
+struct TextViewWrapper: UIViewRepresentable {
+ @Binding var attributedText: NSMutableAttributedString
+
+ func makeUIView(context: Context) -> UITextView {
+ let textView = UITextView()
+ textView.delegate = context.coordinator
+ textView.font = UIFont.systemFont(ofSize: 18)
+ textView.textColor = UIColor.black
+ let linkAttributes: [NSAttributedString.Key : Any] = [
+ NSAttributedString.Key.foregroundColor: UIColor(Color.accentColor)]
+ textView.linkTextAttributes = linkAttributes
+ return textView
+ }
+
+ func updateUIView(_ uiView: UITextView, context: Context) {
+ uiView.attributedText = attributedText
+ }
+
+ func makeCoordinator() -> Coordinator {
+ Coordinator(attributedText: $attributedText)
+ }
+
+ class Coordinator: NSObject, UITextViewDelegate {
+ @Binding var attributedText: NSMutableAttributedString
+
+ init(attributedText: Binding<NSMutableAttributedString>) {
+ _attributedText = attributedText
+ }
+
+ func textViewDidChange(_ textView: UITextView) {
+ attributedText = NSMutableAttributedString(attributedString: textView.attributedText)
+ }
+ }
+}
+