From dcef08009dddacb1c44cd096feff1f33bd0a61d2 Mon Sep 17 00:00:00 2001 From: Ajay Bura Date: Sun, 16 Jan 2022 14:17:50 +0530 Subject: [PATCH] Add ability to search room messages Signed-off-by: Ajay Bura --- src/app/molecules/message/Message.jsx | 47 +++-- src/app/molecules/room-search/RoomSearch.jsx | 193 ++++++++++++++++++ src/app/molecules/room-search/RoomSearch.scss | 62 ++++++ src/app/organisms/room/RoomSettings.jsx | 2 + src/app/organisms/room/RoomSettings.scss | 5 +- src/client/state/navigation.js | 2 - 6 files changed, 289 insertions(+), 22 deletions(-) create mode 100644 src/app/molecules/room-search/RoomSearch.jsx create mode 100644 src/app/molecules/room-search/RoomSearch.scss diff --git a/src/app/molecules/message/Message.jsx b/src/app/molecules/message/Message.jsx index 054f60b..6fe001b 100644 --- a/src/app/molecules/message/Message.jsx +++ b/src/app/molecules/message/Message.jsx @@ -52,17 +52,14 @@ function PlaceholderMessage() { } const MessageAvatar = React.memo(({ - roomId, mEvent, userId, username, -}) => { - const avatarSrc = mEvent.sender.getAvatarUrl(initMatrix.matrixClient.baseUrl, 36, 36, 'crop'); - return ( -
- -
- ); -}); + roomId, avatarSrc, userId, username, +}) => ( +
+ +
+)); const MessageHeader = React.memo(({ userId, username, time, @@ -597,7 +594,8 @@ function Message({ mEvent, isBodyOnly, roomTimeline, focus, time, }) { const [isEditing, setIsEditing] = useState(false); - const { roomId, editedTimeline, reactionTimeline } = roomTimeline; + const roomId = mEvent.getRoomId(); + const { editedTimeline, reactionTimeline } = roomTimeline ?? {}; const className = ['message', (isBodyOnly ? 'message--body-only' : 'message--full')]; if (focus) className.push('message--focus'); @@ -606,7 +604,8 @@ function Message({ const msgType = content?.msgtype; const senderId = mEvent.getSender(); let { body } = content; - const username = getUsernameOfRoomMember(mEvent.sender); + const username = mEvent.sender ? getUsernameOfRoomMember(mEvent.sender) : getUsername(senderId); + const avatarSrc = mEvent.sender?.getAvatarUrl(initMatrix.matrixClient.baseUrl, 36, 36, 'crop') ?? null; const edit = useCallback(() => { setIsEditing(true); @@ -619,8 +618,10 @@ function Message({ if (msgType === 'm.emote') className.push('message--type-emote'); let isCustomHTML = content.format === 'org.matrix.custom.html'; - const isEdited = editedTimeline.has(eventId); - const haveReactions = reactionTimeline.has(eventId) || !!mEvent.getServerAggregatedRelation('m.annotation'); + const isEdited = roomTimeline ? editedTimeline.has(eventId) : false; + const haveReactions = roomTimeline + ? reactionTimeline.has(eventId) || !!mEvent.getServerAggregatedRelation('m.annotation') + : false; const isReply = !!mEvent.replyEventId; let customHTML = isCustomHTML ? content.formatted_body : null; @@ -640,13 +641,20 @@ function Message({ { isBodyOnly ?
- : + : ( + + ) }
{!isBodyOnly && ( )} - {isReply && ( + {roomTimeline && isReply && ( )} - {!isEditing && ( + {roomTimeline && !isEditing && ( mountStore.setItem(true), [roomId]); + + useEffect(() => { + if (searchData?.results?.length > 0) { + roomIdToBackup.set(roomId, searchData); + } else { + roomIdToBackup.delete(roomId); + } + }, [searchData]); + + const search = async (term) => { + setSearchData(null); + if (term === '') { + setStatus({ type: cons.status.PRE_FLIGHT, term: null }); + return; + } + setStatus({ type: cons.status.IN_FLIGHT, term }); + const body = { + search_categories: { + room_events: { + search_term: term, + filter: { + limit: 10, + rooms: [roomId], + }, + order_by: 'recent', + event_context: { + before_limit: 0, + after_limit: 0, + include_profile: true, + }, + }, + }, + }; + try { + const res = await mx.search({ body }); + const data = mx.processRoomEventsSearch({ + _query: body, + results: [], + highlights: [], + }, res); + if (!mountStore.getItem()) return; + setStatus({ type: cons.status.SUCCESS, term }); + setSearchData(data); + if (!mountStore.getItem()) return; + } catch (error) { + setSearchData(null); + setStatus({ type: cons.status.ERROR, term }); + } + }; + + const paginate = async () => { + if (searchData === null) return; + const term = searchData._query.search_categories.room_events.search_term; + + setStatus({ type: cons.status.IN_FLIGHT, term }); + try { + const data = await mx.backPaginateRoomEventsSearch(searchData); + if (!mountStore.getItem()) return; + setStatus({ type: cons.status.SUCCESS, term }); + setSearchData(data); + } catch (error) { + if (!mountStore.getItem()) return; + setSearchData(null); + setStatus({ type: cons.status.ERROR, term }); + } + }; + + return [searchData, search, paginate, status]; +} + +function RoomSearch({ roomId }) { + const [searchData, search, paginate, status] = useRoomSearch(roomId); + + const searchTerm = searchData?._query.search_categories.room_events.search_term ?? ''; + + const handleSearch = (e) => { + e.preventDefault(); + const searchTermInput = e.target.elements['room-search-input']; + const term = searchTermInput.value.trim(); + + search(term); + }; + + const renderTimeline = (timeline) => ( +
+ { timeline.map((mEvent) => { + const time = dateFormat(mEvent.getDate(), 'dd/mm/yyyy - hh:MM TT'); + const id = mEvent.getId(); + return ( + + + + + ); + })} +
+ ); + + return ( +
+
+ Room search +
+ + +
+ {searchData?.results.length > 0 && ( + {`${searchData.count} results for "${searchTerm}"`} + )} +
+ {searchData === null && ( +
+ {status.type === cons.status.IN_FLIGHT && } + {status.type === cons.status.IN_FLIGHT && Searching room messages...} + {status.type === cons.status.PRE_FLIGHT && } + {status.type === cons.status.PRE_FLIGHT && Search room messages} + {status.type === cons.status.ERROR && Failed to search messages} +
+ )} + + {searchData?.results.length === 0 && ( +
+ No result found +
+ )} + {searchData?.results.length > 0 && ( + <> +
+ {searchData.results.map((searchResult) => { + const { timeline } = searchResult.context; + return renderTimeline(timeline); + })} +
+ {searchData?.next_batch && ( +
+ {status.type !== cons.status.IN_FLIGHT && ( + + )} + {status.type === cons.status.IN_FLIGHT && } +
+ )} + + )} +
+ ); +} + +RoomSearch.propTypes = { + roomId: PropTypes.string.isRequired, +}; + +export default RoomSearch; diff --git a/src/app/molecules/room-search/RoomSearch.scss b/src/app/molecules/room-search/RoomSearch.scss new file mode 100644 index 0000000..a40945e --- /dev/null +++ b/src/app/molecules/room-search/RoomSearch.scss @@ -0,0 +1,62 @@ +@use '../../partials/flex'; +@use '../../partials/dir'; + +.room-search { + &__form { + & div:nth-child(2) { + display: flex; + align-items: flex-end; + padding: var(--sp-normal);; + + & .input-container { + @extend .cp-fx__item-one; + @include dir.side(margin, 0, var(--sp-normal)); + } + & button { + height: 46px; + } + } + & .context-menu__header { + margin-bottom: 0; + } + & > .text { + padding: 0 var(--sp-normal) var(--sp-tight); + } + } + + &__help { + height: 248px; + @extend .cp-fx__column--c-c; + + & .ic-raw { + opacity: .5; + } + .text { + margin-top: var(--sp-normal); + } + } + &__more { + margin-bottom: var(--sp-normal); + @extend .cp-fx__row--c-c; + button { + width: 100%; + } + } + &__result-item { + padding: var(--sp-tight) var(--sp-normal); + display: flex; + align-items: flex-start; + + .message { + @include dir.side(margin, 0, var(--sp-normal)); + @extend .cp-fx__item-one; + padding: 0; + &:hover { + background-color: transparent; + } + & .message__time { + flex: 0; + } + } + } +} \ No newline at end of file diff --git a/src/app/organisms/room/RoomSettings.jsx b/src/app/organisms/room/RoomSettings.jsx index 98f49bc..057d45a 100644 --- a/src/app/organisms/room/RoomSettings.jsx +++ b/src/app/organisms/room/RoomSettings.jsx @@ -14,6 +14,7 @@ import ScrollView from '../../atoms/scroll/ScrollView'; import Tabs from '../../atoms/tabs/Tabs'; import { MenuHeader, MenuItem } from '../../atoms/context-menu/ContextMenu'; import RoomProfile from '../../molecules/room-profile/RoomProfile'; +import RoomSearch from '../../molecules/room-search/RoomSearch'; import RoomNotification from '../../molecules/room-notification/RoomNotification'; import RoomVisibility from '../../molecules/room-visibility/RoomVisibility'; import RoomAliases from '../../molecules/room-aliases/RoomAliases'; @@ -151,6 +152,7 @@ function RoomSettings({ roomId }) { />
{selectedTab.text === tabText.GENERAL && } + {selectedTab.text === tabText.SEARCH && } {selectedTab.text === tabText.PERMISSIONS && } {selectedTab.text === tabText.SECURITY && }
diff --git a/src/app/organisms/room/RoomSettings.scss b/src/app/organisms/room/RoomSettings.scss index 617024a..909dbb3 100644 --- a/src/app/organisms/room/RoomSettings.scss +++ b/src/app/organisms/room/RoomSettings.scss @@ -46,6 +46,9 @@ } } -.room-settings .room-permissions__card { +.room-settings .room-permissions__card, +.room-settings .room-search__form, +.room-settings .room-search__help, +.room-settings .room-search__result-item { @extend .room-settings__card; } \ No newline at end of file diff --git a/src/client/state/navigation.js b/src/client/state/navigation.js index 977cf7e..26ffca3 100644 --- a/src/client/state/navigation.js +++ b/src/client/state/navigation.js @@ -73,8 +73,6 @@ class Navigation extends EventEmitter { this.emit(cons.events.navigation.SPACE_SELECTED, this.selectedSpaceId); }, [cons.actions.navigation.SELECT_ROOM]: () => { - if (this.selectedRoomId === action.roomId) return; - const prevSelectedRoomId = this.selectedRoomId; this.selectedRoomId = action.roomId; this.removeRecentRoom(prevSelectedRoomId);