Extend user tagging search to all local profiles
Changelog-Added: Extend user tagging search to all local profiles Changelog-Fixed: Show @ mentions for users with display_names and no username Changelog-Fixed: Make user search case insensitive
This commit is contained in:
@@ -25,10 +25,10 @@ struct UserSearch: View {
|
|||||||
|
|
||||||
var users: [SearchedUser] {
|
var users: [SearchedUser] {
|
||||||
guard let contacts = damus_state.contacts.event else {
|
guard let contacts = damus_state.contacts.event else {
|
||||||
return []
|
return search_profiles(profiles: damus_state.profiles, search: search)
|
||||||
}
|
}
|
||||||
|
|
||||||
return search_users(profiles: damus_state.profiles, tags: contacts.tags, search: search)
|
return search_users_for_autocomplete(profiles: damus_state.profiles, tags: contacts.tags, search: search)
|
||||||
}
|
}
|
||||||
|
|
||||||
func on_user_tapped(user: SearchedUser) {
|
func on_user_tapped(user: SearchedUser) {
|
||||||
@@ -36,21 +36,35 @@ struct UserSearch: View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove all characters after the last '@'
|
||||||
|
removeCharactersAfterLastAtSymbol()
|
||||||
|
|
||||||
|
// Create and append the user tag
|
||||||
|
let tagAttributedString = createUserTag(for: user, with: pk)
|
||||||
|
appendUserTag(tagAttributedString)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func removeCharactersAfterLastAtSymbol() {
|
||||||
while post.string.last != "@" {
|
while post.string.last != "@" {
|
||||||
post.deleteCharacters(in: NSRange(location: post.length - 1, length: 1))
|
post.deleteCharacters(in: NSRange(location: post.length - 1, length: 1))
|
||||||
}
|
}
|
||||||
post.deleteCharacters(in: NSRange(location: post.length - 1, length: 1))
|
post.deleteCharacters(in: NSRange(location: post.length - 1, length: 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createUserTag(for user: SearchedUser, with pk: String) -> NSMutableAttributedString {
|
||||||
|
let name = Profile.displayName(profile: user.profile, pubkey: pk).username
|
||||||
|
let tagString = "@\(name)\u{200B} "
|
||||||
|
|
||||||
var tagString = ""
|
|
||||||
if let name = user.profile?.name {
|
|
||||||
tagString = "@\(name)\u{200B} "
|
|
||||||
}
|
|
||||||
let tagAttributedString = NSMutableAttributedString(string: tagString,
|
let tagAttributedString = NSMutableAttributedString(string: tagString,
|
||||||
attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18.0),
|
attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18.0),
|
||||||
NSAttributedString.Key.link: "@\(pk)"])
|
NSAttributedString.Key.link: "@\(pk)"])
|
||||||
tagAttributedString.removeAttribute(.link, range: NSRange(location: tagAttributedString.length - 2, length: 2))
|
tagAttributedString.removeAttribute(.link, range: NSRange(location: tagAttributedString.length - 2, length: 2))
|
||||||
tagAttributedString.addAttributes([NSAttributedString.Key.foregroundColor: UIColor.label], range: NSRange(location: tagAttributedString.length - 2, length: 2))
|
tagAttributedString.addAttributes([NSAttributedString.Key.foregroundColor: UIColor.label], range: NSRange(location: tagAttributedString.length - 2, length: 2))
|
||||||
|
|
||||||
|
return tagAttributedString
|
||||||
|
}
|
||||||
|
|
||||||
|
private func appendUserTag(_ tagAttributedString: NSMutableAttributedString) {
|
||||||
let mutableString = NSMutableAttributedString()
|
let mutableString = NSMutableAttributedString()
|
||||||
mutableString.append(post)
|
mutableString.append(post)
|
||||||
mutableString.append(tagAttributedString)
|
mutableString.append(tagAttributedString)
|
||||||
@@ -81,11 +95,11 @@ struct UserSearch_Previews: PreviewProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func search_users(profiles: Profiles, tags: [[String]], search _search: String) -> [SearchedUser] {
|
func search_users_for_autocomplete(profiles: Profiles, tags: [[String]], search _search: String) -> [SearchedUser] {
|
||||||
var seen_user = Set<String>()
|
var seen_user = Set<String>()
|
||||||
let search = _search.lowercased()
|
let search = _search.lowercased()
|
||||||
|
|
||||||
return tags.reduce(into: Array<SearchedUser>()) { arr, tag in
|
var matches = tags.reduce(into: Array<SearchedUser>()) { arr, tag in
|
||||||
guard tag.count >= 2 && tag[0] == "p" else {
|
guard tag.count >= 2 && tag[0] == "p" else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -103,11 +117,29 @@ func search_users(profiles: Profiles, tags: [[String]], search _search: String)
|
|||||||
|
|
||||||
let profile = profiles.lookup(id: pubkey)
|
let profile = profiles.lookup(id: pubkey)
|
||||||
|
|
||||||
guard ((petname?.lowercased().hasPrefix(search) ?? false) || (profile?.name?.lowercased().hasPrefix(search) ?? false)) else {
|
guard ((petname?.lowercased().hasPrefix(search) ?? false) ||
|
||||||
|
(profile?.name?.lowercased().hasPrefix(search) ?? false) ||
|
||||||
|
(profile?.display_name?.lowercased().hasPrefix(search) ?? false)) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let searched_user = SearchedUser(petname: petname, profile: profile, pubkey: pubkey)
|
let searched_user = SearchedUser(petname: petname, profile: profile, pubkey: pubkey)
|
||||||
arr.append(searched_user)
|
arr.append(searched_user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// search profile cache as well
|
||||||
|
for tup in profiles.profiles.enumerated() {
|
||||||
|
let pk = tup.element.key
|
||||||
|
let prof = tup.element.value.profile
|
||||||
|
|
||||||
|
guard !seen_user.contains(pk) else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if let match = profile_search_matches(profiles: profiles, profile: prof, pubkey: pk, search: search) {
|
||||||
|
matches.append(match)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
enum Search {
|
enum Search {
|
||||||
case profiles([(String, Profile)])
|
case profiles([SearchedUser])
|
||||||
case hashtag(String)
|
case hashtag(String)
|
||||||
case profile(String)
|
case profile(String)
|
||||||
case note(String)
|
case note(String)
|
||||||
@@ -21,7 +21,7 @@ struct SearchResultsView: View {
|
|||||||
|
|
||||||
@State var result: Search? = nil
|
@State var result: Search? = nil
|
||||||
|
|
||||||
func ProfileSearchResult(pk: String, res: Profile) -> some View {
|
func ProfileSearchResult(pk: String) -> some View {
|
||||||
FollowUserView(target: .pubkey(pk), damus_state: damus_state)
|
FollowUserView(target: .pubkey(pk), damus_state: damus_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,8 +31,8 @@ struct SearchResultsView: View {
|
|||||||
switch result {
|
switch result {
|
||||||
case .profiles(let results):
|
case .profiles(let results):
|
||||||
LazyVStack {
|
LazyVStack {
|
||||||
ForEach(results, id: \.0) { prof in
|
ForEach(results) { prof in
|
||||||
ProfileSearchResult(pk: prof.0, res: prof.1)
|
ProfileSearchResult(pk: prof.pubkey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .hashtag(let ht):
|
case .hashtag(let ht):
|
||||||
@@ -119,22 +119,34 @@ func search_for_string(profiles: Profiles, _ new: String) -> Search? {
|
|||||||
return .profiles(search_profiles(profiles: profiles, search: new))
|
return .profiles(search_profiles(profiles: profiles, search: new))
|
||||||
}
|
}
|
||||||
|
|
||||||
func search_profiles(profiles: Profiles, search new: String) -> [(String, Profile)] {
|
func search_profiles(profiles: Profiles, search: String) -> [SearchedUser] {
|
||||||
|
let new = search.lowercased()
|
||||||
return profiles.profiles.enumerated().reduce(into: []) { acc, els in
|
return profiles.profiles.enumerated().reduce(into: []) { acc, els in
|
||||||
let pk = els.element.key
|
let pk = els.element.key
|
||||||
let prof = els.element.value.profile
|
let prof = els.element.value.profile
|
||||||
let lowname = prof.name.map { $0.lowercased() }
|
|
||||||
let lownip05 = profiles.is_validated(pk).map { $0.host.lowercased() }
|
if let searched = profile_search_matches(profiles: profiles, profile: prof, pubkey: pk, search: new) {
|
||||||
let lowdisp = prof.display_name.map { $0.lowercased() }
|
acc.append(searched)
|
||||||
let ok = new.count == 1 ?
|
|
||||||
((lowname?.starts(with: new) ?? false) ||
|
|
||||||
(lownip05?.starts(with: new) ?? false) ||
|
|
||||||
(lowdisp?.starts(with: new) ?? false)) : (pk.starts(with: new) || String(new.dropFirst()) == pk
|
|
||||||
|| lowname?.contains(new) ?? false
|
|
||||||
|| lownip05?.contains(new) ?? false
|
|
||||||
|| lowdisp?.contains(new) ?? false)
|
|
||||||
if ok {
|
|
||||||
acc.append((pk, prof))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func profile_search_matches(profiles: Profiles, profile prof: Profile, pubkey pk: String, search new: String) -> SearchedUser? {
|
||||||
|
let lowname = prof.name.map { $0.lowercased() }
|
||||||
|
let lownip05 = profiles.is_validated(pk).map { $0.host.lowercased() }
|
||||||
|
let lowdisp = prof.display_name.map { $0.lowercased() }
|
||||||
|
let ok = new.count == 1 ?
|
||||||
|
((lowname?.starts(with: new) ?? false) ||
|
||||||
|
(lownip05?.starts(with: new) ?? false) ||
|
||||||
|
(lowdisp?.starts(with: new) ?? false)) : (pk.starts(with: new) || String(new.dropFirst()) == pk
|
||||||
|
|| lowname?.contains(new) ?? false
|
||||||
|
|| lownip05?.contains(new) ?? false
|
||||||
|
|| lowdisp?.contains(new) ?? false)
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
return SearchedUser(petname: nil, profile: prof, pubkey: pk)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user