commit 1533be77d8e8b8e8546ebc4b4def67c6dcbe6de0
parent c05223ca2b651ffa0b98b209751e832515c395d5
Author: William Casarin <jb55@jb55.com>
Date: Wed, 15 Mar 2023 08:44:03 -0600
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
Diffstat:
2 files changed, 72 insertions(+), 28 deletions(-)
diff --git a/damus/Views/Posting/UserSearch.swift b/damus/Views/Posting/UserSearch.swift
@@ -25,10 +25,10 @@ struct UserSearch: View {
var users: [SearchedUser] {
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) {
@@ -36,21 +36,35 @@ struct UserSearch: View {
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 != "@" {
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,
- attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18.0),
- NSAttributedString.Key.link: "@\(pk)"])
+ attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18.0),
+ NSAttributedString.Key.link: "@\(pk)"])
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))
+
+ return tagAttributedString
+ }
+
+ private func appendUserTag(_ tagAttributedString: NSMutableAttributedString) {
let mutableString = NSMutableAttributedString()
mutableString.append(post)
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>()
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 {
return
}
@@ -103,11 +117,29 @@ func search_users(profiles: Profiles, tags: [[String]], search _search: String)
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
}
let searched_user = SearchedUser(petname: petname, profile: profile, pubkey: pubkey)
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
}
diff --git a/damus/Views/SearchResultsView.swift b/damus/Views/SearchResultsView.swift
@@ -8,7 +8,7 @@
import SwiftUI
enum Search {
- case profiles([(String, Profile)])
+ case profiles([SearchedUser])
case hashtag(String)
case profile(String)
case note(String)
@@ -21,7 +21,7 @@ struct SearchResultsView: View {
@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)
}
@@ -31,8 +31,8 @@ struct SearchResultsView: View {
switch result {
case .profiles(let results):
LazyVStack {
- ForEach(results, id: \.0) { prof in
- ProfileSearchResult(pk: prof.0, res: prof.1)
+ ForEach(results) { prof in
+ ProfileSearchResult(pk: prof.pubkey)
}
}
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))
}
-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
let pk = els.element.key
let prof = els.element.value.profile
- 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 {
- acc.append((pk, prof))
+
+ if let searched = profile_search_matches(profiles: profiles, profile: prof, pubkey: pk, search: new) {
+ acc.append(searched)
}
}
}
+
+
+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
+}