Rich tagging
Changelog-Changed: No more inline npubs when tagging users Closes: #691
This commit is contained in:
@@ -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] = [:]
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
44
damus/Views/TextViewWrapper.swift
Normal file
44
damus/Views/TextViewWrapper.swift
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user