User tagging and autocompletion
Co-authored-by: William Casarin <jb55@jb55.com> Changelog-Added: User tagging and autocompletion in posts Closes: #347 Fixes: #411, #63
This commit is contained in:
@@ -171,6 +171,7 @@
|
||||
4CF0ABEC29844B4700D66079 /* AnyDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABEB29844B4700D66079 /* AnyDecodable.swift */; };
|
||||
4CF0ABEE29844B5500D66079 /* AnyEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABED29844B5500D66079 /* AnyEncodable.swift */; };
|
||||
4CF0ABF029857E9200D66079 /* Bech32Object.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABEF29857E9200D66079 /* Bech32Object.swift */; };
|
||||
4CF0ABF62985CD5500D66079 /* UserSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABF52985CD5500D66079 /* UserSearch.swift */; };
|
||||
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; };
|
||||
5C513FCC2984ACA60072348F /* QRCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FCB2984ACA60072348F /* QRCodeView.swift */; };
|
||||
6439E014296790CF0020672B /* ProfileZoomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6439E013296790CF0020672B /* ProfileZoomView.swift */; };
|
||||
@@ -426,6 +427,7 @@
|
||||
4CF0ABEB29844B4700D66079 /* AnyDecodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyDecodable.swift; sourceTree = "<group>"; };
|
||||
4CF0ABED29844B5500D66079 /* AnyEncodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyEncodable.swift; sourceTree = "<group>"; };
|
||||
4CF0ABEF29857E9200D66079 /* Bech32Object.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32Object.swift; sourceTree = "<group>"; };
|
||||
4CF0ABF52985CD5500D66079 /* UserSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSearch.swift; sourceTree = "<group>"; };
|
||||
4FE60CDC295E1C5E00105A1F /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = "<group>"; };
|
||||
5C513FCB2984ACA60072348F /* QRCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeView.swift; sourceTree = "<group>"; };
|
||||
6439E013296790CF0020672B /* ProfileZoomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileZoomView.swift; sourceTree = "<group>"; };
|
||||
@@ -587,6 +589,7 @@
|
||||
4C75EFA227FA576C0006080F /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4CF0ABF42985CD4200D66079 /* Posting */,
|
||||
4CF0ABDF2981A83000D66079 /* Muting */,
|
||||
4CC7AAEE297F11B300430951 /* Events */,
|
||||
3AA24800297E3DAE0090C62D /* Reposts */,
|
||||
@@ -846,6 +849,14 @@
|
||||
path = AnyCodable;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4CF0ABF42985CD4200D66079 /* Posting */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4CF0ABF52985CD5500D66079 /* UserSearch.swift */,
|
||||
);
|
||||
path = Posting;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F7F0BA23297892AE009531F3 /* Modifiers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1014,6 +1025,7 @@
|
||||
4C216F34286F5ACD00040376 /* DMView.swift in Sources */,
|
||||
4C3EA64428FF558100C48A62 /* sha256.c in Sources */,
|
||||
4CE4F9E1285287B800C00DD9 /* TextFieldAlert.swift in Sources */,
|
||||
4CF0ABF62985CD5500D66079 /* UserSearch.swift in Sources */,
|
||||
4C363AA828297703006E126D /* InsertSort.swift in Sources */,
|
||||
4C285C86283892E7008A31F1 /* CreateAccountModel.swift in Sources */,
|
||||
4C64987C286D03E000EAE2B3 /* DirectMessagesView.swift in Sources */,
|
||||
|
||||
@@ -302,7 +302,7 @@ struct ContentView: View {
|
||||
case .report(let target):
|
||||
MaybeReportView(target: target)
|
||||
case .post:
|
||||
PostView(replying_to: nil, references: [])
|
||||
PostView(replying_to: nil, references: [], damus_state: damus_state!)
|
||||
case .reply(let event):
|
||||
ReplyView(replying_to: event, damus: damus_state!)
|
||||
}
|
||||
|
||||
@@ -16,10 +16,11 @@ let POST_PLACEHOLDER = NSLocalizedString("Type your post here...", comment: "Tex
|
||||
|
||||
struct PostView: View {
|
||||
@State var post: String = ""
|
||||
|
||||
let replying_to: NostrEvent?
|
||||
@FocusState var focus: Bool
|
||||
|
||||
let replying_to: NostrEvent?
|
||||
let references: [ReferencedId]
|
||||
let damus_state: DamusState
|
||||
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
|
||||
@@ -74,6 +75,7 @@ struct PostView: View {
|
||||
TextEditor(text: $post)
|
||||
.focused($focus)
|
||||
.textInputAutocapitalization(.sentences)
|
||||
|
||||
if post.isEmpty {
|
||||
Text(POST_PLACEHOLDER)
|
||||
.padding(.top, 8)
|
||||
@@ -82,6 +84,14 @@ struct PostView: View {
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
}
|
||||
|
||||
// This if-block observes @ for tagging
|
||||
if let searching = get_searching_string(post) {
|
||||
VStack {
|
||||
Spacer()
|
||||
UserSearch(damus_state: damus_state, search: searching, post: $post)
|
||||
}.zIndex(1)
|
||||
}
|
||||
}
|
||||
.onAppear() {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
@@ -92,3 +102,23 @@ struct PostView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func get_searching_string(_ post: String) -> String? {
|
||||
guard let last_word = post.components(separatedBy: .whitespacesAndNewlines).last else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard last_word.count >= 2 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard last_word.first! == "@" else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// don't include @npub... strings
|
||||
guard last_word.count != 64 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return String(last_word.dropFirst())
|
||||
}
|
||||
|
||||
87
damus/Views/Posting/UserSearch.swift
Normal file
87
damus/Views/Posting/UserSearch.swift
Normal file
@@ -0,0 +1,87 @@
|
||||
//
|
||||
// UserAutocompletion.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-01-28.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SearchedUser: Identifiable {
|
||||
let petname: String?
|
||||
let profile: Profile?
|
||||
let pubkey: String
|
||||
|
||||
var id: String {
|
||||
return pubkey
|
||||
}
|
||||
}
|
||||
|
||||
struct UserSearch: View {
|
||||
let damus_state: DamusState
|
||||
let search: String
|
||||
@Binding var post: String
|
||||
|
||||
var users: [SearchedUser] {
|
||||
guard let contacts = damus_state.contacts.event else {
|
||||
return []
|
||||
}
|
||||
|
||||
return search_users(profiles: damus_state.profiles, tags: contacts.tags, search: search)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
LazyVStack {
|
||||
ForEach(users) { user in
|
||||
UserView(damus_state: damus_state, pubkey: user.pubkey)
|
||||
.onTapGesture {
|
||||
guard let pk = bech32_pubkey(user.pubkey) else {
|
||||
return
|
||||
}
|
||||
post = post.replacingOccurrences(of: "@"+search, with: "@"+pk)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct UserSearch_Previews: PreviewProvider {
|
||||
static let search: String = "jb55"
|
||||
@State static var post: String = "some @jb55"
|
||||
|
||||
static var previews: some View {
|
||||
UserSearch(damus_state: test_damus_state(), search: search, post: $post)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func search_users(profiles: Profiles, tags: [[String]], search: String) -> [SearchedUser] {
|
||||
var seen_user = Set<String>()
|
||||
return tags.reduce(into: Array<SearchedUser>()) { arr, tag in
|
||||
guard tag.count >= 2 && tag[0] == "p" else {
|
||||
return
|
||||
}
|
||||
|
||||
let pubkey = tag[1]
|
||||
guard !seen_user.contains(pubkey) else {
|
||||
return
|
||||
}
|
||||
seen_user.insert(pubkey)
|
||||
|
||||
var petname: String? = nil
|
||||
if tag.count >= 4 {
|
||||
petname = tag[3]
|
||||
}
|
||||
|
||||
let profile = profiles.lookup(id: pubkey)
|
||||
|
||||
guard ((petname?.hasPrefix(search) ?? false) || (profile?.name?.hasPrefix(search) ?? false)) else {
|
||||
return
|
||||
}
|
||||
|
||||
let searched_user = SearchedUser(petname: petname, profile: profile, pubkey: pubkey)
|
||||
arr.append(searched_user)
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ struct ReplyView: View {
|
||||
ScrollView {
|
||||
EventView(damus: damus, event: replying_to, has_action_bar: false)
|
||||
}
|
||||
PostView(replying_to: replying_to, references: references)
|
||||
PostView(replying_to: replying_to, references: gather_reply_ids(our_pubkey: damus.pubkey, from: replying_to), damus_state: damus)
|
||||
}
|
||||
.onAppear {
|
||||
references = gather_reply_ids(our_pubkey: damus.pubkey, from: replying_to)
|
||||
|
||||
Reference in New Issue
Block a user