From 1aaffb380df30b137917e68d590e2c81ba25f325 Mon Sep 17 00:00:00 2001 From: Strycher Date: Thu, 18 Jun 2026 22:47:33 -0400 Subject: [PATCH] fix(#45): emoji autocomplete substring matching + check aliases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Match the typed text anywhere in the shortcode name (not just the prefix), ranked exact > prefix > substring, so `:check` surfaces the checkmarks (✅ is `white_check_mark`). Add check/checkmark/tick aliases for ✅ in a separate, regen-safe map the chat + channel composers consume. Closes #45 Co-Authored-By: Claude Opus 4.8 --- lib/helpers/emoji_shortcodes.dart | 14 ++++++++++++++ lib/screens/channel_chat_screen.dart | 2 +- lib/screens/chat_screen.dart | 2 +- lib/widgets/mention_autocomplete.dart | 6 +++++- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/helpers/emoji_shortcodes.dart b/lib/helpers/emoji_shortcodes.dart index 55e4625..ec399b9 100644 --- a/lib/helpers/emoji_shortcodes.dart +++ b/lib/helpers/emoji_shortcodes.dart @@ -1916,3 +1916,17 @@ const Map emojiShortcodes = { 'zombie_woman': '🧟‍♀️', 'zzz': '💤', }; + +// Hand-authored aliases (not gemoji), kept separate so regenerating the map +// above never clobbers them. ✅ is `white_check_mark`, hence `check`. +const Map _emojiAliases = { + 'check': '✅', + 'checkmark': '✅', + 'tick': '✅', +}; + +/// gemoji shortcodes plus [_emojiAliases] — what composers use. +final Map emojiShortcodesWithAliases = { + ...emojiShortcodes, + ..._emojiAliases, +}; diff --git a/lib/screens/channel_chat_screen.dart b/lib/screens/channel_chat_screen.dart index cabd631..256546b 100644 --- a/lib/screens/channel_chat_screen.dart +++ b/lib/screens/channel_chat_screen.dart @@ -1250,7 +1250,7 @@ class _ChannelChatScreenState extends State { controller: _textController, focusNode: _textFieldFocusNode, candidates: _buildMentionCandidates(connector), - emojiShortcodes: emojiShortcodes, + emojiShortcodes: emojiShortcodesWithAliases, hintText: context.l10n.chat_typeMessage, onSubmitted: (_) => _sendMessage(), encoder: diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index d9e9cd5..b763aa3 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -627,7 +627,7 @@ class _ChatScreenState extends State { controller: _textController, focusNode: _textFieldFocusNode, candidates: const [], - emojiShortcodes: emojiShortcodes, + emojiShortcodes: emojiShortcodesWithAliases, hintText: context.l10n.chat_typeMessage, onSubmitted: (_) => _sendMessage(connector), encoder: diff --git a/lib/widgets/mention_autocomplete.dart b/lib/widgets/mention_autocomplete.dart index 26dd575..e998f44 100644 --- a/lib/widgets/mention_autocomplete.dart +++ b/lib/widgets/mention_autocomplete.dart @@ -277,14 +277,18 @@ class _MentionAutocompleteFieldState extends State { return; } } - final names = shortcodes.keys.where((k) => k.startsWith(query)).toList(); + final names = shortcodes.keys.where((k) => k.contains(query)).toList(); if (names.isEmpty) { _close(); return; } names.sort((a, b) { + // Rank exact, then prefix, then mid-name matches; shorter names first. if (a == query && b != query) return -1; if (b == query && a != query) return 1; + final aPrefix = a.startsWith(query); + final bPrefix = b.startsWith(query); + if (aPrefix != bPrefix) return aPrefix ? -1 : 1; final byLen = a.length.compareTo(b.length); if (byLen != 0) return byLen; return a.compareTo(b);