From f73dc05e259cd16145304db0c40415df63d70e3c Mon Sep 17 00:00:00 2001 From: Ajay Bura <32841439+ajbura@users.noreply.github.com> Date: Wed, 19 Feb 2025 11:23:32 +0530 Subject: [PATCH 1/4] add order algorithm in search result --- .../autocomplete/EmoticonAutocomplete.tsx | 4 +- src/app/components/emoji-board/EmojiBoard.tsx | 56 ++++++------ src/app/hooks/useAsyncSearch.ts | 89 ++++++++++++++++--- 3 files changed, 107 insertions(+), 42 deletions(-) diff --git a/src/app/components/editor/autocomplete/EmoticonAutocomplete.tsx b/src/app/components/editor/autocomplete/EmoticonAutocomplete.tsx index 591f1bff..9479a698 100644 --- a/src/app/components/editor/autocomplete/EmoticonAutocomplete.tsx +++ b/src/app/components/editor/autocomplete/EmoticonAutocomplete.tsx @@ -64,9 +64,7 @@ export function EmoticonAutocomplete({ }, [imagePacks]); const [result, search, resetSearch] = useAsyncSearch(searchList, getEmoticonStr, SEARCH_OPTIONS); - const autoCompleteEmoticon = (result ? result.items : recentEmoji).sort((a, b) => - a.shortcode.localeCompare(b.shortcode) - ); + const autoCompleteEmoticon = result ? result.items : recentEmoji; useEffect(() => { if (query.text) search(query.text); diff --git a/src/app/components/emoji-board/EmojiBoard.tsx b/src/app/components/emoji-board/EmojiBoard.tsx index f3bd551f..77e56a91 100644 --- a/src/app/components/emoji-board/EmojiBoard.tsx +++ b/src/app/components/emoji-board/EmojiBoard.tsx @@ -471,36 +471,34 @@ export function SearchEmojiGroup({ return ( {tab === EmojiBoardTab.Emoji - ? searchResult - .sort((a, b) => a.shortcode.localeCompare(b.shortcode)) - .map((emoji) => - 'unicode' in emoji ? ( - - {emoji.unicode} - - ) : ( - - {emoji.body - - ) + ? searchResult.map((emoji) => + 'unicode' in emoji ? ( + + {emoji.unicode} + + ) : ( + + {emoji.body + ) + ) : searchResult.map((emoji) => 'unicode' in emoji ? null : ( = export type SearchResetHandler = () => void; +const performMatch = ( + target: string | string[], + query: string, + options?: UseAsyncSearchOptions +): string | undefined => { + if (Array.isArray(target)) { + const matchTarget = target.find((i) => + matchQuery(normalize(i, options?.normalizeOptions), query, options?.matchOptions) + ); + return matchTarget ? normalize(matchTarget, options?.normalizeOptions) : undefined; + } + + const normalizedTargetStr = normalize(target, options?.normalizeOptions); + const matches = matchQuery(normalizedTargetStr, query, options?.matchOptions); + return matches ? normalizedTargetStr : undefined; +}; + +export const orderSearchItems = ( + query: string, + items: TSearchItem[], + getItemStr: SearchItemStrGetter, + options?: UseAsyncSearchOptions +): TSearchItem[] => { + const orderedItems: TSearchItem[] = Array.from(items); + + // we will consider "_" as word boundary char. + // because in more use-cases it is used. (like: emojishortcode) + const boundaryRegex = new RegExp(`(\\b|_)${query}`); + const perfectBoundaryRegex = new RegExp(`(\\b|_)${query}(\\b|_)`); + + orderedItems.sort((i1, i2) => { + const str1 = performMatch(getItemStr(i1, query), query, options); + const str2 = performMatch(getItemStr(i2, query), query, options); + + if (str1 === undefined && str2 === undefined) return 0; + if (str1 === undefined) return 1; + if (str2 === undefined) return -1; + + let points1 = 0; + let points2 = 0; + + // short string should score more + const pointsToSmallStr = (points: number) => { + if (str1.length < str2.length) points1 += points; + else if (str2.length < str1.length) points2 += points; + }; + pointsToSmallStr(1); + + // closes query match should score more + const indexIn1 = str1.indexOf(query); + const indexIn2 = str2.indexOf(query); + if (indexIn1 < indexIn2) points1 += 2; + else if (indexIn2 < indexIn1) points2 += 2; + else pointsToSmallStr(2); + + // query match word start on boundary should score more + const boundaryIn1 = str1.match(boundaryRegex); + const boundaryIn2 = str2.match(boundaryRegex); + if (boundaryIn1 && boundaryIn2) pointsToSmallStr(4); + else if (boundaryIn1) points1 += 4; + else if (boundaryIn2) points2 += 4; + + // query match word start and end on boundary should score more + const perfectBoundaryIn1 = str1.match(perfectBoundaryRegex); + const perfectBoundaryIn2 = str2.match(perfectBoundaryRegex); + if (perfectBoundaryIn1 && perfectBoundaryIn2) pointsToSmallStr(8); + else if (perfectBoundaryIn1) points1 += 8; + else if (perfectBoundaryIn2) points2 += 8; + + return points2 - points1; + }); + + return orderedItems; +}; + export const useAsyncSearch = ( list: TSearchItem[], getItemStr: SearchItemStrGetter, @@ -40,21 +115,15 @@ export const useAsyncSearch = ( const handleMatch: MatchHandler = (item, query) => { const itemStr = getItemStr(item, query); - if (Array.isArray(itemStr)) - return !!itemStr.find((i) => - matchQuery(normalize(i, options?.normalizeOptions), query, options?.matchOptions) - ); - return matchQuery( - normalize(itemStr, options?.normalizeOptions), - query, - options?.matchOptions - ); + + const strWithMatch = performMatch(itemStr, query, options); + return typeof strWithMatch === 'string'; }; const handleResult: ResultHandler = (results, query) => setResult({ query, - items: [...results], + items: orderSearchItems(query, results, getItemStr, options), }); return AsyncSearch(list, handleMatch, handleResult, options); From 2e0c7c4406d8cf6aab795c86bcf868415436f31e Mon Sep 17 00:00:00 2001 From: Ajay Bura <32841439+ajbura@users.noreply.github.com> Date: Wed, 19 Feb 2025 22:07:33 +1100 Subject: [PATCH 2/4] Fix link visible inside spoiler (#2215) * hide links in spoiler * prevent link click inside spoiler --- src/index.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/index.scss b/src/index.scss index 563f611f..14bf4749 100644 --- a/src/index.scss +++ b/src/index.scss @@ -427,6 +427,12 @@ a { text-decoration: underline; } } + +[data-mx-spoiler][aria-pressed='true'] a { + color: transparent; + pointer-events: none; +} + b { font-weight: var(--fw-medium); } From b3979b31c70266d115ae74d701d07929f935f2bf Mon Sep 17 00:00:00 2001 From: Ajay Bura <32841439+ajbura@users.noreply.github.com> Date: Wed, 19 Feb 2025 22:08:58 +1100 Subject: [PATCH 3/4] fix room activity indicator appearing on self typing (#2217) --- src/app/features/room-nav/RoomNavItem.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/app/features/room-nav/RoomNavItem.tsx b/src/app/features/room-nav/RoomNavItem.tsx index 19d04f35..ef59bf98 100644 --- a/src/app/features/room-nav/RoomNavItem.tsx +++ b/src/app/features/room-nav/RoomNavItem.tsx @@ -182,7 +182,9 @@ export function RoomNavItem({ const { focusWithinProps } = useFocusWithin({ onFocusWithinChange: setHover }); const [menuAnchor, setMenuAnchor] = useState(); const unread = useRoomUnread(room.roomId, roomToUnreadAtom); - const typingMember = useRoomTypingMember(room.roomId); + const typingMember = useRoomTypingMember(room.roomId).filter( + (receipt) => receipt.userId !== mx.getUserId() + ); const handleContextMenu: MouseEventHandler = (evt) => { evt.preventDefault(); @@ -219,7 +221,9 @@ export function RoomNavItem({ ( From d8d4bce287445e92ebab184aa5f9ae348bf34972 Mon Sep 17 00:00:00 2001 From: Ajay Bura <32841439+ajbura@users.noreply.github.com> Date: Wed, 19 Feb 2025 22:13:29 +1100 Subject: [PATCH 4/4] add button to select all room pack as global pack (#2218) --- .../settings/emojis-stickers/GlobalPacks.tsx | 58 ++++++++++++++++++- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/src/app/features/settings/emojis-stickers/GlobalPacks.tsx b/src/app/features/settings/emojis-stickers/GlobalPacks.tsx index 3413ec49..a9288728 100644 --- a/src/app/features/settings/emojis-stickers/GlobalPacks.tsx +++ b/src/app/features/settings/emojis-stickers/GlobalPacks.tsx @@ -79,6 +79,28 @@ function GlobalPackSelector({ }); }; + const addSelected = (adds: PackAddress[]) => { + setSelected((addresses) => { + const newAddresses = Array.from(addresses); + adds.forEach((address) => { + if (newAddresses.find((addr) => packAddressEqual(addr, address))) { + return; + } + newAddresses.push(address); + }); + return newAddresses; + }); + }; + + const removeSelected = (adds: PackAddress[]) => { + setSelected((addresses) => { + const newAddresses = addresses.filter( + (addr) => !adds.find((address) => packAddressEqual(addr, address)) + ); + return newAddresses; + }); + }; + const hasSelected = selected.length > 0; return ( @@ -115,9 +137,35 @@ function GlobalPackSelector({ {Array.from(roomToPacks.entries()).map(([roomId, roomPacks]) => { const room = mx.getRoom(roomId); if (!room) return null; + const roomPackAddresses = roomPacks + .map((pack) => pack.address) + .filter((addr) => addr !== undefined); + const allSelected = roomPackAddresses.every((addr) => + selected.find((address) => packAddressEqual(addr, address)) + ); + return ( - {room.name} + + + {room.name} + + + { + if (allSelected) { + removeSelected(roomPackAddresses); + return; + } + addSelected(roomPackAddresses); + }} + > + {allSelected ? 'Unselect All' : 'Select All'} + + + {roomPacks.map((pack) => { const avatarMxc = pack.getAvatarUrl(ImageUsage.Emoticon); const avatarUrl = avatarMxc @@ -126,7 +174,7 @@ function GlobalPackSelector({ const { address } = pack; if (!address) return null; - const added = selected.find((addr) => packAddressEqual(addr, address)); + const added = !!selected.find((addr) => packAddressEqual(addr, address)); return ( } after={ - toggleSelect(address)} /> + toggleSelect(address)} + /> } />