mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-11-01 07:53:26 +00:00
fix(composer): match @mentions across Unicode NFC/NFD forms
Stored nicknames are NFC after #1502, but typed mentions can be NFD; exact token contains() dropped them from the outbound mentions list.
This commit is contained in:
parent
1f59e814f9
commit
89b449a8a1
@ -125,16 +125,22 @@ final class ChatComposerCoordinator {
|
||||
)
|
||||
|
||||
let peerNicknames = context.meshPeerNicknames()
|
||||
var validTokens = Set(peerNicknames.values)
|
||||
validTokens.insert(context.nickname)
|
||||
validTokens.insert(context.nickname + "#" + String(context.myPeerID.id.prefix(4)))
|
||||
// Key by NFC so a typed combining-accent form still resolves to the
|
||||
// stored (precomposed) nickname token used on the wire.
|
||||
var tokensByCanonical = [String: String]()
|
||||
for token in peerNicknames.values {
|
||||
tokensByCanonical[token.normalizedNickname] = token
|
||||
}
|
||||
tokensByCanonical[context.nickname.normalizedNickname] = context.nickname
|
||||
let selfSuffix = context.nickname + "#" + String(context.myPeerID.id.prefix(4))
|
||||
tokensByCanonical[selfSuffix.normalizedNickname] = selfSuffix
|
||||
|
||||
var mentions: [String] = []
|
||||
for match in matches {
|
||||
guard let range = Range(match.range(at: 1), in: content) else { continue }
|
||||
let mentionedName = String(content[range])
|
||||
if validTokens.contains(mentionedName) {
|
||||
mentions.append(mentionedName)
|
||||
if let canonical = tokensByCanonical[mentionedName.normalizedNickname] {
|
||||
mentions.append(canonical)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -192,4 +192,16 @@ struct ChatComposerCoordinatorContextTests {
|
||||
)
|
||||
#expect(Set(mentions) == ["alice", "me", "me#0011"])
|
||||
}
|
||||
|
||||
@Test @MainActor
|
||||
func parseMentions_matchesUnicodeEquivalentNicknames() {
|
||||
let context = MockChatComposerContext()
|
||||
let coordinator = ChatComposerCoordinator(context: context)
|
||||
let precomposed = "caf\u{00E9}"
|
||||
let decomposed = "cafe\u{0301}"
|
||||
context.meshNicknamesByPeerID = [PeerID(str: "1111111111111111"): precomposed]
|
||||
|
||||
let mentions = coordinator.parseMentions(from: "hi @\(decomposed)")
|
||||
#expect(mentions == [precomposed])
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user