From 57a6de592229938adaa1f6e02afca7ae9925c010 Mon Sep 17 00:00:00 2001 From: yaya-usman Date: Tue, 16 Aug 2022 13:45:35 +0300 Subject: [PATCH 01/15] removed dead code related favorite messages panel --- src/PageTypes.ts | 1 + .../structures/FavouriteMessagesView.tsx | 88 ++++++++++++++ src/components/structures/LeftPanel.tsx | 1 + src/components/structures/LoggedInView.tsx | 5 + src/components/structures/MatrixChat.tsx | 12 ++ .../views/messages/MessageActionBar.tsx | 11 +- .../views/rooms/FavouriteMessageTile.tsx | 113 ++++++++++++++++++ src/components/views/rooms/RoomList.tsx | 11 +- src/dispatcher/actions.ts | 5 + src/hooks/useFavouriteMessages.ts | 22 ++-- src/i18n/strings/en_EN.json | 1 + src/stores/RoomViewStore.tsx | 7 ++ 12 files changed, 262 insertions(+), 15 deletions(-) create mode 100644 src/components/structures/FavouriteMessagesView.tsx create mode 100644 src/components/views/rooms/FavouriteMessageTile.tsx diff --git a/src/PageTypes.ts b/src/PageTypes.ts index fb0424f6e05..28e9178f6e2 100644 --- a/src/PageTypes.ts +++ b/src/PageTypes.ts @@ -21,6 +21,7 @@ enum PageType { RoomView = "room_view", UserView = "user_view", LegacyGroupView = "legacy_group_view", + FavouriteMessagesView = "favourite_messages_view" } export default PageType; diff --git a/src/components/structures/FavouriteMessagesView.tsx b/src/components/structures/FavouriteMessagesView.tsx new file mode 100644 index 00000000000..62edd4ed3ff --- /dev/null +++ b/src/components/structures/FavouriteMessagesView.tsx @@ -0,0 +1,88 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd +Copyright 2017 Vector Creations Ltd +Copyright 2018, 2019 New Vector Ltd +Copyright 2019 - 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { MatrixClient, MatrixEvent } from 'matrix-js-sdk/src/matrix'; +import React, { useContext, useRef } from 'react'; + +import MatrixClientContext from '../../contexts/MatrixClientContext'; +import { _t } from '../../languageHandler'; +import FavouriteMessageTile from '../views/rooms/FavouriteMessageTile'; +import ScrollPanel from './ScrollPanel'; + +function getFavouriteMessagesTiles(cli: MatrixClient, favouriteMessageEvents) { + const ret = []; + let lastRoomId: string; + + for (let i = (favouriteMessageEvents?.length || 0) - 1; i >= 0; i--) { + const timeline = [] as MatrixEvent[]; + const resultObj = favouriteMessageEvents[i]; + const roomId = resultObj.roomId; + const room = cli.getRoom(roomId); + const mxEvent = room.findEventById(resultObj.eventId); + timeline.push(mxEvent); + + if (roomId !== lastRoomId) { + ret.push(
  • +

    { _t("Room") }: { room.name }

    +
  • ); + lastRoomId = roomId; + } + + const resultLink = "#/room/"+roomId+"/"+mxEvent.getId(); + + ret.push( + , + ); + } + return ret; +} + +let favouriteMessagesPanel; + +const FavouriteMessagesView = () => { + const favouriteMessageEvents= JSON.parse( + localStorage?.getItem("io_element_favouriteMessages") ?? "[]") as any[]; + + const favouriteMessagesPanelRef = useRef(); + const cli = useContext(MatrixClientContext); + + if (favouriteMessageEvents?.length === 0) { + favouriteMessagesPanel = (

    { _t("No Saved Messages") }

    ); + } else { + favouriteMessagesPanel = ( + + { getFavouriteMessagesTiles(cli, favouriteMessageEvents) } + + ); + } + + return ( + <> { favouriteMessagesPanel } + ); +}; + +export default FavouriteMessagesView; diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index bd120529a90..d039ca69c59 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -371,6 +371,7 @@ export default class LeftPanel extends React.Component { onResize={this.refreshStickyHeaders} onListCollapse={this.refreshStickyHeaders} ref={this.roomListRef} + pageType={this.props.pageType} />; const containerClasses = classNames({ diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index d4737c2fca2..97c67e43123 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -71,6 +71,7 @@ import LegacyGroupView from "./LegacyGroupView"; import { IConfigOptions } from "../../IConfigOptions"; import LeftPanelLiveShareWarning from '../views/beacon/LeftPanelLiveShareWarning'; import { UserOnboardingPage } from '../views/user-onboarding/UserOnboardingPage'; +import FavouriteMessagesView from './FavouriteMessagesView'; // We need to fetch each pinned message individually (if we don't already have it) // so each pinned message may trigger a request. Limit the number per room for sanity. @@ -645,6 +646,10 @@ class LoggedInView extends React.Component { case PageTypes.LegacyGroupView: pageElement = ; break; + + case PageTypes.FavouriteMessagesView: + pageElement = ; + break; } const wrapperClasses = classNames({ diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 8c173a36301..841e2b739e9 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -803,6 +803,9 @@ export default class MatrixChat extends React.PureComponent { hideAnalyticsToast(); SettingsStore.setValue("pseudonymousAnalyticsOptIn", null, SettingLevel.ACCOUNT, true); break; + case Action.ViewFavouriteMessages: + this.viewFavouriteMessages(); + break; case Action.PseudonymousAnalyticsReject: hideAnalyticsToast(); SettingsStore.setValue("pseudonymousAnalyticsOptIn", null, SettingLevel.ACCOUNT, false); @@ -1007,6 +1010,11 @@ export default class MatrixChat extends React.PureComponent { this.themeWatcher.recheck(); } + private viewFavouriteMessages() { + this.setPage(PageType.FavouriteMessagesView); + this.notifyNewScreen('favourite_messages'); + } + private viewUser(userId: string, subAction: string) { // Wait for the first sync so that `getRoom` gives us a room object if it's // in the sync response @@ -1704,6 +1712,10 @@ export default class MatrixChat extends React.PureComponent { dis.dispatch({ action: Action.ViewHomePage, }); + } else if (screen === 'favourite_messages') { + dis.dispatch({ + action: Action.ViewFavouriteMessages, + }); } else if (screen === 'start') { this.showScreen('home'); dis.dispatch({ diff --git a/src/components/views/messages/MessageActionBar.tsx b/src/components/views/messages/MessageActionBar.tsx index 9d51c61074f..75a9ddf1fa7 100644 --- a/src/components/views/messages/MessageActionBar.tsx +++ b/src/components/views/messages/MessageActionBar.tsx @@ -258,18 +258,17 @@ interface IFavouriteButtonProp { } const FavouriteButton = ({ mxEvent }: IFavouriteButtonProp) => { - const { isFavourite, toggleFavourite } = useFavouriteMessages(); + const { isFavourite, toggleFavourite } = useFavouriteMessages({ mxEvent }); - const eventId = mxEvent.getId(); - const classes = classNames("mx_MessageActionBar_iconButton mx_MessageActionBar_favouriteButton", { - 'mx_MessageActionBar_favouriteButton_fillstar': isFavourite(eventId), + const classes = classNames("mx_MessageActionBar_iconButton", { + 'mx_MessageActionBar_favouriteButton_fillstar': isFavourite(), }); return toggleFavourite(eventId)} - data-testid={eventId} + onClick={() => toggleFavourite()} + data-testid={mxEvent.getId()} > ; diff --git a/src/components/views/rooms/FavouriteMessageTile.tsx b/src/components/views/rooms/FavouriteMessageTile.tsx new file mode 100644 index 00000000000..272117be35a --- /dev/null +++ b/src/components/views/rooms/FavouriteMessageTile.tsx @@ -0,0 +1,113 @@ +/* +Copyright 2015 OpenMarket Ltd +Copyright 2019 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from "react"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; + +import RoomContext from "../../../contexts/RoomContext"; +import SettingsStore from "../../../settings/SettingsStore"; +import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks'; +import DateSeparator from "../messages/DateSeparator"; +import EventTile from "./EventTile"; +import { shouldFormContinuation } from "../../structures/MessagePanel"; +import { wantsDateSeparator } from "../../../DateUtils"; +import { haveRendererForEvent } from "../../../events/EventTileFactory"; + +interface IProps { + // an event result object + result: MatrixEvent; + // href for the highlights in this result + resultLink?: string; + onHeightChanged?: () => void; + permalinkCreator?: RoomPermalinkCreator; + //a list containing the saved items events + timeline?: MatrixEvent[]; +} + +export default class FavouriteMessageTile extends React.Component { + static contextType = RoomContext; + public context!: React.ContextType; + + constructor(props, context) { + super(props, context); + } + + public render() { + const result = this.props.result; + const eventId = result.getId(); + + const ts1 = result?.getTs(); + const ret = []; + const layout = SettingsStore.getValue("layout"); + const isTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps"); + const alwaysShowTimestamps = SettingsStore.getValue("alwaysShowTimestamps"); + const threadsEnabled = SettingsStore.getValue("feature_thread"); + + for (let j = 0; j < this.props.timeline.length; j++) { + const mxEv = this.props.timeline[j]; + + if (haveRendererForEvent(mxEv, this.context?.showHiddenEvents)) { + // do we need a date separator since the last event? + const prevEv = this.props.timeline[j - 1]; + // is this a continuation of the previous message? + const continuation = prevEv && + !wantsDateSeparator(prevEv.getDate(), mxEv.getDate()) && + shouldFormContinuation( + prevEv, + mxEv, + this.context?.showHiddenEvents, + threadsEnabled, + ); + + let lastInSection = true; + const nextEv = this.props.timeline[j + 1]; + if (nextEv) { + const willWantDateSeparator = wantsDateSeparator(mxEv.getDate(), nextEv.getDate()); + lastInSection = ( + willWantDateSeparator || + mxEv.getSender() !== nextEv.getSender() || + !shouldFormContinuation( + mxEv, + nextEv, + this.context?.showHiddenEvents, + threadsEnabled, + ) + ); + } + + ret.push( + , + ); + } + } + + return
  • +
      { ret }
    +
  • ; + } +} diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index b2493db25cc..9ae3779246c 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -30,6 +30,7 @@ import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { useEventEmitterState } from "../../../hooks/useEventEmitter"; import { _t, _td } from "../../../languageHandler"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; +import PageType from "../../../PageTypes"; import PosthogTrackers from "../../../PosthogTrackers"; import SettingsStore from "../../../settings/SettingsStore"; import { UIComponent } from "../../../settings/UIFeature"; @@ -71,6 +72,7 @@ interface IProps { resizeNotifier: ResizeNotifier; isMinimized: boolean; activeSpace: SpaceKey; + pageType: PageType; } interface IState { @@ -570,15 +572,20 @@ export default class RoomList extends React.PureComponent { resizeMethod="crop" />); + const onFavouriteClicked = () => { + defaultDispatcher.dispatch({ action: Action.ViewFavouriteMessages }); + }; + return [ ""} + onClick={onFavouriteClicked} key="favMessagesTile_key" />, + ]; } diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index 4e161a70051..826d1401188 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -331,4 +331,9 @@ export enum Action { * Fired when we want to view a thread, either a new one or an existing one */ ShowThread = "show_thread", + + /** + * Fired when we want to view favourited messages panel + */ + ViewFavouriteMessages = "view_favourite_messages", } diff --git a/src/hooks/useFavouriteMessages.ts b/src/hooks/useFavouriteMessages.ts index a6d5c315ed2..7d0621bfcfe 100644 --- a/src/hooks/useFavouriteMessages.ts +++ b/src/hooks/useFavouriteMessages.ts @@ -1,4 +1,3 @@ - /* Copyright 2022 The Matrix.org Foundation C.I.C. @@ -15,20 +14,29 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { useState } from "react"; +interface IButtonProp { + mxEvent: MatrixEvent; +} + const favouriteMessageIds = JSON.parse( - localStorage?.getItem("io_element_favouriteMessages")?? "[]") as string[]; + localStorage?.getItem("io_element_favouriteMessages")?? "[]") as any[]; -export default function useFavouriteMessages() { +export default function useFavouriteMessages({ mxEvent }: IButtonProp) { const [, setX] = useState(); + const eventId = mxEvent.getId(); + const roomId = mxEvent.getRoomId(); //checks if an id already exist - const isFavourite = (eventId: string): boolean => favouriteMessageIds.includes(eventId); + const isFavourite = (): boolean => { + return favouriteMessageIds.some(val => val.eventId === eventId); + }; - const toggleFavourite = (eventId: string) => { - isFavourite(eventId) ? favouriteMessageIds.splice(favouriteMessageIds.indexOf(eventId), 1) - : favouriteMessageIds.push(eventId); + const toggleFavourite = () => { + isFavourite() ? favouriteMessageIds.splice(favouriteMessageIds.findIndex(val => val.eventId === eventId), 1) + : favouriteMessageIds.push({ eventId, roomId }); //update the local storage localStorage.setItem('io_element_favouriteMessages', JSON.stringify(favouriteMessageIds)); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 7c3483e09e7..34f6b700e1d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -3109,6 +3109,7 @@ "Pause": "Pause", "Play": "Play", "Couldn't load page": "Couldn't load page", + "No Saved Messages": "No Saved Messages", "Drop file here to upload": "Drop file here to upload", "You must register to use this functionality": "You must register to use this functionality", "You must join the room to see its files": "You must join the room to see its files", diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx index ea2b7e93ad2..f5bc361fcf7 100644 --- a/src/stores/RoomViewStore.tsx +++ b/src/stores/RoomViewStore.tsx @@ -81,6 +81,7 @@ const INITIAL_STATE = { viaServers: [] as string[], wasContextSwitch: false, + }; type Listener = (isActive: boolean) => void; @@ -183,6 +184,12 @@ export class RoomViewStore extends Store { wasContextSwitch: false, }); break; + case Action.ViewFavouriteMessages: + this.setState({ + roomId: null, + roomAlias: null, + }); + break; case Action.ViewRoomError: this.viewRoomError(payload); break; From f92d917848b749e5afa22bececcd88c2a9f1938c Mon Sep 17 00:00:00 2001 From: yaya-usman Date: Tue, 16 Aug 2022 14:24:37 +0300 Subject: [PATCH 02/15] correct code nits --- src/components/views/rooms/RoomList.tsx | 1 - src/stores/RoomViewStore.tsx | 8 +------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index 9ae3779246c..2e7b7ffce7e 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -585,7 +585,6 @@ export default class RoomList extends React.PureComponent { onClick={onFavouriteClicked} key="favMessagesTile_key" />, - ]; } diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx index f5bc361fcf7..083e154cdf1 100644 --- a/src/stores/RoomViewStore.tsx +++ b/src/stores/RoomViewStore.tsx @@ -81,7 +81,6 @@ const INITIAL_STATE = { viaServers: [] as string[], wasContextSwitch: false, - }; type Listener = (isActive: boolean) => void; @@ -177,6 +176,7 @@ export class RoomViewStore extends Store { // for these events blank out the roomId as we are no longer in the RoomView case 'view_welcome_page': case Action.ViewHomePage: + case Action.ViewFavouriteMessages: this.setState({ roomId: null, roomAlias: null, @@ -184,12 +184,6 @@ export class RoomViewStore extends Store { wasContextSwitch: false, }); break; - case Action.ViewFavouriteMessages: - this.setState({ - roomId: null, - roomAlias: null, - }); - break; case Action.ViewRoomError: this.viewRoomError(payload); break; From 60e62a0d42460d088d0f2a52c336e55f215a862a Mon Sep 17 00:00:00 2001 From: yaya-usman Date: Wed, 17 Aug 2022 21:48:41 +0300 Subject: [PATCH 03/15] updated code: fetching of messages asynchronously --- .../structures/FavouriteMessagesTilesList.tsx | 80 +++++++++++++++++++ .../structures/FavouriteMessagesView.tsx | 72 +++++++---------- 2 files changed, 111 insertions(+), 41 deletions(-) create mode 100644 src/components/structures/FavouriteMessagesTilesList.tsx diff --git a/src/components/structures/FavouriteMessagesTilesList.tsx b/src/components/structures/FavouriteMessagesTilesList.tsx new file mode 100644 index 00000000000..628b42b2eea --- /dev/null +++ b/src/components/structures/FavouriteMessagesTilesList.tsx @@ -0,0 +1,80 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd +Copyright 2017 Vector Creations Ltd +Copyright 2018, 2019 New Vector Ltd +Copyright 2019 - 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { MatrixClient, MatrixEvent } from 'matrix-js-sdk/src/matrix'; +import React, { useContext } from 'react'; + +import MatrixClientContext from '../../contexts/MatrixClientContext'; +import { _t } from '../../languageHandler'; +import Spinner from '../views/elements/Spinner'; +import FavouriteMessageTile from '../views/rooms/FavouriteMessageTile'; + +interface IProps{ + favouriteMessageEvents: MatrixEvent[]; + favouriteMessagesPanelRef: any; +} + +const FavouriteMessagesTilesList = ({ favouriteMessageEvents, favouriteMessagesPanelRef }: IProps) => { + const ret = []; + let lastRoomId: string; + const cli = useContext(MatrixClientContext); + + if (!favouriteMessageEvents) { + ret.push(); + } else { + favouriteMessageEvents.reverse().forEach(mxEvent => { + const timeline = [] as MatrixEvent[]; + const roomId = mxEvent.getRoomId(); + const room = cli.getRoom(roomId); + + timeline.push(mxEvent); + + if (roomId !== lastRoomId) { + ret.push(
  • +

    { _t("Room") }: { room.name }

    +
  • ); + lastRoomId = roomId; + } + // once dynamic content in the favourite messages panel loads, make the scrollPanel check + // the scroll offsets. + const onHeightChanged = () => { + const scrollPanel = favouriteMessagesPanelRef.current; + if (scrollPanel) { + scrollPanel.checkScroll(); + } + }; + + const resultLink = "#/room/"+roomId+"/"+mxEvent.getId(); + + ret.push( + , + ); + }); + } + + return <>{ ret }; +}; + +export default FavouriteMessagesTilesList; diff --git a/src/components/structures/FavouriteMessagesView.tsx b/src/components/structures/FavouriteMessagesView.tsx index 62edd4ed3ff..8edcda17ea3 100644 --- a/src/components/structures/FavouriteMessagesView.tsx +++ b/src/components/structures/FavouriteMessagesView.tsx @@ -17,57 +17,47 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { MatrixClient, MatrixEvent } from 'matrix-js-sdk/src/matrix'; import React, { useContext, useRef } from 'react'; +import { MatrixClient, MatrixEvent } from 'matrix-js-sdk/src/matrix'; +import { logger } from 'matrix-js-sdk/src/logger'; -import MatrixClientContext from '../../contexts/MatrixClientContext'; import { _t } from '../../languageHandler'; -import FavouriteMessageTile from '../views/rooms/FavouriteMessageTile'; import ScrollPanel from './ScrollPanel'; - -function getFavouriteMessagesTiles(cli: MatrixClient, favouriteMessageEvents) { - const ret = []; - let lastRoomId: string; - - for (let i = (favouriteMessageEvents?.length || 0) - 1; i >= 0; i--) { - const timeline = [] as MatrixEvent[]; - const resultObj = favouriteMessageEvents[i]; - const roomId = resultObj.roomId; - const room = cli.getRoom(roomId); - const mxEvent = room.findEventById(resultObj.eventId); - timeline.push(mxEvent); - - if (roomId !== lastRoomId) { - ret.push(
  • -

    { _t("Room") }: { room.name }

    -
  • ); - lastRoomId = roomId; - } - - const resultLink = "#/room/"+roomId+"/"+mxEvent.getId(); - - ret.push( - , - ); - } - return ret; -} - -let favouriteMessagesPanel; +import FavouriteMessagesTilesList from './FavouriteMessagesTilesList'; +import MatrixClientContext from '../../contexts/MatrixClientContext'; +import { useAsyncMemo } from '../../hooks/useAsyncMemo'; const FavouriteMessagesView = () => { - const favouriteMessageEvents= JSON.parse( + const favouriteMessagesIds= JSON.parse( localStorage?.getItem("io_element_favouriteMessages") ?? "[]") as any[]; const favouriteMessagesPanelRef = useRef(); const cli = useContext(MatrixClientContext); - if (favouriteMessageEvents?.length === 0) { + const favouriteMessageEvents = useAsyncMemo(() => { + const promises = favouriteMessagesIds.map(async (resultObj) => { + try { + const [evJson] = await Promise.all([ + cli.fetchRoomEvent(resultObj.roomId, resultObj.eventId), + ]); + const event = new MatrixEvent(evJson); + + if (event.isEncrypted()) { + await cli.decryptEventIfNeeded(event); + } + return event; + } catch (err) { + logger.error(err); + } + return null; + }); + + return Promise.all(promises); + }, [cli, favouriteMessagesIds], null); + + let favouriteMessagesPanel; + + if (favouriteMessagesIds?.length === 0) { favouriteMessagesPanel = (

    { _t("No Saved Messages") }

    ); } else { favouriteMessagesPanel = ( @@ -75,7 +65,7 @@ const FavouriteMessagesView = () => { ref={favouriteMessagesPanelRef} className="mx_RoomView_searchResultsPanel " > - { getFavouriteMessagesTiles(cli, favouriteMessageEvents) } +
    ); } From d7f8a7eb33e5613d484d48ec022b72299d38e024 Mon Sep 17 00:00:00 2001 From: yaya-usman Date: Thu, 18 Aug 2022 18:28:12 +0300 Subject: [PATCH 04/15] display sender details correctly and removed edit button from messageActionBar --- .../structures/FavouriteMessagesView.tsx | 41 +++++++++++++++---- .../views/messages/MessageActionBar.tsx | 4 +- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/components/structures/FavouriteMessagesView.tsx b/src/components/structures/FavouriteMessagesView.tsx index 8edcda17ea3..75a8be1d725 100644 --- a/src/components/structures/FavouriteMessagesView.tsx +++ b/src/components/structures/FavouriteMessagesView.tsx @@ -18,7 +18,7 @@ limitations under the License. */ import React, { useContext, useRef } from 'react'; -import { MatrixClient, MatrixEvent } from 'matrix-js-sdk/src/matrix'; +import { MatrixClient, MatrixEvent, RelationType } from 'matrix-js-sdk/src/matrix'; import { logger } from 'matrix-js-sdk/src/logger'; import { _t } from '../../languageHandler'; @@ -37,15 +37,28 @@ const FavouriteMessagesView = () => { const favouriteMessageEvents = useAsyncMemo(() => { const promises = favouriteMessagesIds.map(async (resultObj) => { try { - const [evJson] = await Promise.all([ + // Fetch the events and latest edit in parallel + const [evJson, { events: [edit] }] = await Promise.all([ cli.fetchRoomEvent(resultObj.roomId, resultObj.eventId), + cli.relations(resultObj.roomId, resultObj.eventId, RelationType.Replace, null, { limit: 1 }), ]); const event = new MatrixEvent(evJson); + const roomId = event.getRoomId(); + const room = cli.getRoom(roomId); if (event.isEncrypted()) { await cli.decryptEventIfNeeded(event); } - return event; + + if (event) { + // Inject sender information + event.sender = room.getMember(event.getSender()); + + // Also inject any edits found + if (edit) event.makeReplaced(edit); + + return event; + } } catch (err) { logger.error(err); } @@ -61,12 +74,22 @@ const FavouriteMessagesView = () => { favouriteMessagesPanel = (

    { _t("No Saved Messages") }

    ); } else { favouriteMessagesPanel = ( - - - + <> + { /* */ } + + + + ); } diff --git a/src/components/views/messages/MessageActionBar.tsx b/src/components/views/messages/MessageActionBar.tsx index 75a9ddf1fa7..e28c5ca3ca2 100644 --- a/src/components/views/messages/MessageActionBar.tsx +++ b/src/components/views/messages/MessageActionBar.tsx @@ -417,7 +417,9 @@ export default class MessageActionBar extends React.PureComponent Date: Fri, 19 Aug 2022 01:55:31 +0300 Subject: [PATCH 05/15] moved FavMessages related codes to one folder and added FavMessagesView Header --- res/css/_components.pcss | 1 + .../structures/_FavouriteMessagesHeader.pcss | 43 +++++++++++++++++++ .../FavouriteMessageTile.tsx | 6 +-- .../FavouriteMessagesHeader.tsx | 40 +++++++++++++++++ .../FavouriteMessagesTilesList.tsx | 8 ++-- .../FavouriteMessagesView.tsx | 22 ++++------ src/components/structures/LoggedInView.tsx | 2 +- 7 files changed, 100 insertions(+), 22 deletions(-) create mode 100644 res/css/structures/_FavouriteMessagesHeader.pcss rename src/components/{views/rooms => structures/FavouriteMessagesView}/FavouriteMessageTile.tsx (96%) create mode 100644 src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx rename src/components/structures/{ => FavouriteMessagesView}/FavouriteMessagesTilesList.tsx (91%) rename src/components/structures/{ => FavouriteMessagesView}/FavouriteMessagesView.tsx (86%) diff --git a/res/css/_components.pcss b/res/css/_components.pcss index 0e5c5fd3a59..695d9ac3a5a 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -35,6 +35,7 @@ @import "./components/views/settings/shared/_SettingsSubsection.pcss"; @import "./components/views/spaces/_QuickThemeSwitcher.pcss"; @import "./structures/_AutoHideScrollbar.pcss"; +@import "./structures/_FavouriteMessagesHeader.pcss"; @import "./structures/_BackdropPanel.pcss"; @import "./structures/_CompatibilityPage.pcss"; @import "./structures/_ContextualMenu.pcss"; diff --git a/res/css/structures/_FavouriteMessagesHeader.pcss b/res/css/structures/_FavouriteMessagesHeader.pcss new file mode 100644 index 00000000000..9f68e6e3167 --- /dev/null +++ b/res/css/structures/_FavouriteMessagesHeader.pcss @@ -0,0 +1,43 @@ +/* +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_FavMessagesHeader { + position: fixed; + top: 0; + left: 0; + width: 100%; + flex: 0 0 50px; + border-bottom: 1px solid $primary-hairline-color; + background-color: $background; + z-index: 999; +} + +.mx_FavMessagesHeader_Wrapper{ + height: 44px; + display: flex; + align-items: center; + min-width: 0; + margin: 0 20px 0 16px; + padding-top: 8px; + border-bottom: 1px solid $system; +} + +.mx_FavMessagesHeader_Wrapper > span { + color: $primary-content; + font-weight: $font-semi-bold; + font-size: $font-18px; + margin: 0 8px; +} diff --git a/src/components/views/rooms/FavouriteMessageTile.tsx b/src/components/structures/FavouriteMessagesView/FavouriteMessageTile.tsx similarity index 96% rename from src/components/views/rooms/FavouriteMessageTile.tsx rename to src/components/structures/FavouriteMessagesView/FavouriteMessageTile.tsx index 272117be35a..7e79645b3c9 100644 --- a/src/components/views/rooms/FavouriteMessageTile.tsx +++ b/src/components/structures/FavouriteMessagesView/FavouriteMessageTile.tsx @@ -21,9 +21,9 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import RoomContext from "../../../contexts/RoomContext"; import SettingsStore from "../../../settings/SettingsStore"; import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks'; -import DateSeparator from "../messages/DateSeparator"; -import EventTile from "./EventTile"; -import { shouldFormContinuation } from "../../structures/MessagePanel"; +import DateSeparator from "../../views/messages/DateSeparator"; +import EventTile from "../../views/rooms/EventTile"; +import { shouldFormContinuation } from "../MessagePanel"; import { wantsDateSeparator } from "../../../DateUtils"; import { haveRendererForEvent } from "../../../events/EventTileFactory"; diff --git a/src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx b/src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx new file mode 100644 index 00000000000..0ab0b60bcfd --- /dev/null +++ b/src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx @@ -0,0 +1,40 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; + +import RoomAvatar from '../../views/avatars/RoomAvatar'; + +const FavouriteMessagesHeader = () => { + return ( +
    +
    + + Favourite Messages +
    +
    + ); +}; + +export default FavouriteMessagesHeader; diff --git a/src/components/structures/FavouriteMessagesTilesList.tsx b/src/components/structures/FavouriteMessagesView/FavouriteMessagesTilesList.tsx similarity index 91% rename from src/components/structures/FavouriteMessagesTilesList.tsx rename to src/components/structures/FavouriteMessagesView/FavouriteMessagesTilesList.tsx index 628b42b2eea..48c00869e34 100644 --- a/src/components/structures/FavouriteMessagesTilesList.tsx +++ b/src/components/structures/FavouriteMessagesView/FavouriteMessagesTilesList.tsx @@ -20,10 +20,10 @@ limitations under the License. import { MatrixClient, MatrixEvent } from 'matrix-js-sdk/src/matrix'; import React, { useContext } from 'react'; -import MatrixClientContext from '../../contexts/MatrixClientContext'; -import { _t } from '../../languageHandler'; -import Spinner from '../views/elements/Spinner'; -import FavouriteMessageTile from '../views/rooms/FavouriteMessageTile'; +import MatrixClientContext from '../../../contexts/MatrixClientContext'; +import { _t } from '../../../languageHandler'; +import Spinner from '../../views/elements/Spinner'; +import FavouriteMessageTile from './FavouriteMessageTile'; interface IProps{ favouriteMessageEvents: MatrixEvent[]; diff --git a/src/components/structures/FavouriteMessagesView.tsx b/src/components/structures/FavouriteMessagesView/FavouriteMessagesView.tsx similarity index 86% rename from src/components/structures/FavouriteMessagesView.tsx rename to src/components/structures/FavouriteMessagesView/FavouriteMessagesView.tsx index 75a8be1d725..71ca4287d35 100644 --- a/src/components/structures/FavouriteMessagesView.tsx +++ b/src/components/structures/FavouriteMessagesView/FavouriteMessagesView.tsx @@ -21,11 +21,12 @@ import React, { useContext, useRef } from 'react'; import { MatrixClient, MatrixEvent, RelationType } from 'matrix-js-sdk/src/matrix'; import { logger } from 'matrix-js-sdk/src/logger'; -import { _t } from '../../languageHandler'; -import ScrollPanel from './ScrollPanel'; +import { _t } from '../../../languageHandler'; +import ScrollPanel from '../ScrollPanel'; import FavouriteMessagesTilesList from './FavouriteMessagesTilesList'; -import MatrixClientContext from '../../contexts/MatrixClientContext'; -import { useAsyncMemo } from '../../hooks/useAsyncMemo'; +import MatrixClientContext from '../../../contexts/MatrixClientContext'; +import { useAsyncMemo } from '../../../hooks/useAsyncMemo'; +import FavouriteMessagesHeader from './FavouriteMessagesHeader'; const FavouriteMessagesView = () => { const favouriteMessagesIds= JSON.parse( @@ -74,22 +75,15 @@ const FavouriteMessagesView = () => { favouriteMessagesPanel = (

    { _t("No Saved Messages") }

    ); } else { favouriteMessagesPanel = ( - <> - { /* */ } + + - + ); } diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 97c67e43123..8afe9b0e26f 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -71,7 +71,7 @@ import LegacyGroupView from "./LegacyGroupView"; import { IConfigOptions } from "../../IConfigOptions"; import LeftPanelLiveShareWarning from '../views/beacon/LeftPanelLiveShareWarning'; import { UserOnboardingPage } from '../views/user-onboarding/UserOnboardingPage'; -import FavouriteMessagesView from './FavouriteMessagesView'; +import FavouriteMessagesView from './FavouriteMessagesView/FavouriteMessagesView'; // We need to fetch each pinned message individually (if we don't already have it) // so each pinned message may trigger a request. Limit the number per room for sanity. From 4b4bf4bd3d7e14818ba9b4fe231e6e055be7c674 Mon Sep 17 00:00:00 2001 From: yaya-usman Date: Sun, 21 Aug 2022 10:17:04 +0300 Subject: [PATCH 06/15] added the searchFilter, sort and clear all functionality --- res/css/_components.pcss | 2 +- ...eader.pcss => _FavouriteMessagesView.pcss} | 45 ++++++++++-- res/img/element-icons/room/clear-all.svg | 1 + res/img/element-icons/room/sort-twoway.svg | 11 +++ .../FavouriteMessageTile.tsx | 4 ++ .../FavouriteMessagesHeader.tsx | 68 ++++++++++++++++--- .../FavouriteMessagesTilesList.tsx | 8 ++- .../FavouriteMessagesView.tsx | 40 +++++++++-- src/hooks/useFavouriteMessages.ts | 45 +++++++++--- src/i18n/strings/en_EN.json | 4 +- 10 files changed, 190 insertions(+), 38 deletions(-) rename res/css/structures/{_FavouriteMessagesHeader.pcss => _FavouriteMessagesView.pcss} (52%) create mode 100644 res/img/element-icons/room/clear-all.svg create mode 100644 res/img/element-icons/room/sort-twoway.svg diff --git a/res/css/_components.pcss b/res/css/_components.pcss index b8170185ff8..e10b60e0e05 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -37,7 +37,7 @@ @import "./components/views/settings/shared/_SettingsSubsection.pcss"; @import "./components/views/spaces/_QuickThemeSwitcher.pcss"; @import "./structures/_AutoHideScrollbar.pcss"; -@import "./structures/_FavouriteMessagesHeader.pcss"; +@import "./structures/_FavouriteMessagesView.pcss"; @import "./structures/_BackdropPanel.pcss"; @import "./structures/_CompatibilityPage.pcss"; @import "./structures/_ContextualMenu.pcss"; diff --git a/res/css/structures/_FavouriteMessagesHeader.pcss b/res/css/structures/_FavouriteMessagesView.pcss similarity index 52% rename from res/css/structures/_FavouriteMessagesHeader.pcss rename to res/css/structures/_FavouriteMessagesView.pcss index 9f68e6e3167..55538a7919e 100644 --- a/res/css/structures/_FavouriteMessagesHeader.pcss +++ b/res/css/structures/_FavouriteMessagesView.pcss @@ -33,11 +33,46 @@ limitations under the License. margin: 0 20px 0 16px; padding-top: 8px; border-bottom: 1px solid $system; + justify-content: space-between; + + &--left{ + display: flex; + align-items: center; + flex: 0.4; + + & > span { + color: $primary-content; + font-weight: $font-semi-bold; + font-size: $font-18px; + margin: 0 8px; + } + } + + &--right{ + display: flex; + align-items: center; + flex: 0.6; + justify-content: flex-end; + } +} + + +.mx_FavMessagesHeader_sortButton::before { + mask-image: url('$(res)/img/element-icons/room/sort-twoway.svg'); } -.mx_FavMessagesHeader_Wrapper > span { - color: $primary-content; - font-weight: $font-semi-bold; - font-size: $font-18px; - margin: 0 8px; +.mx_FavMessagesHeader_clearAllButton::before { + mask-image: url('$(res)/img/element-icons/room/clear-all.svg'); +} + +.mx_FavMessagesHeader_Search{ + width: 70%; +} + +.mx_FavouriteMessages_emptyMarker{ + display: flex; + align-items: center; + justify-content: center; + font-size: 25px; + font-weight: 600; } diff --git a/res/img/element-icons/room/clear-all.svg b/res/img/element-icons/room/clear-all.svg new file mode 100644 index 00000000000..dde0bb131b1 --- /dev/null +++ b/res/img/element-icons/room/clear-all.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/img/element-icons/room/sort-twoway.svg b/res/img/element-icons/room/sort-twoway.svg new file mode 100644 index 00000000000..c1c68e3e87e --- /dev/null +++ b/res/img/element-icons/room/sort-twoway.svg @@ -0,0 +1,11 @@ + + + + + + diff --git a/src/components/structures/FavouriteMessagesView/FavouriteMessageTile.tsx b/src/components/structures/FavouriteMessagesView/FavouriteMessageTile.tsx index 7e79645b3c9..3e097b631a3 100644 --- a/src/components/structures/FavouriteMessagesView/FavouriteMessageTile.tsx +++ b/src/components/structures/FavouriteMessagesView/FavouriteMessageTile.tsx @@ -32,6 +32,8 @@ interface IProps { result: MatrixEvent; // href for the highlights in this result resultLink?: string; + // a list of strings to be highlighted in the results + searchHighlights?: string[]; onHeightChanged?: () => void; permalinkCreator?: RoomPermalinkCreator; //a list containing the saved items events @@ -59,6 +61,7 @@ export default class FavouriteMessageTile extends React.Component { for (let j = 0; j < this.props.timeline.length; j++) { const mxEv = this.props.timeline[j]; + const highlights = this.props.searchHighlights; if (haveRendererForEvent(mxEv, this.context?.showHiddenEvents)) { // do we need a date separator since the last event? @@ -94,6 +97,7 @@ export default class FavouriteMessageTile extends React.Component { key={`${eventId}+${j}`} mxEvent={mxEv} layout={layout} + highlights={highlights} permalinkCreator={this.props.permalinkCreator} highlightLink={this.props.resultLink} onHeightChanged={this.props.onHeightChanged} diff --git a/src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx b/src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx index 0ab0b60bcfd..40e7cebbc8b 100644 --- a/src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx +++ b/src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx @@ -15,23 +15,69 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; +import React, { useEffect, useState } from 'react'; +import useFavouriteMessages from '../../../hooks/useFavouriteMessages'; +import { _t } from '../../../languageHandler'; import RoomAvatar from '../../views/avatars/RoomAvatar'; +import AccessibleTooltipButton from '../../views/elements/AccessibleTooltipButton'; +import { Alignment } from '../../views/elements/Tooltip'; + +const FavouriteMessagesHeader = ({ handleSearchQuery }) => { + const { sortFavouriteMessages, clearFavouriteMessages } = useFavouriteMessages(); + + const [isSearchClicked, setSearchClicked] = useState(false); + const [query, setQuery] = useState(); + + useEffect(() => { + handleSearchQuery(query); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [query]); -const FavouriteMessagesHeader = () => { return (
    - - Favourite Messages +
    + + Favourite Messages +
    +
    + { isSearchClicked && + ( setQuery(e.target.value)} + />) + } + setSearchClicked(!isSearchClicked)} + title={_t("Search")} + key="search" + alignment={Alignment.Bottom} + /> + sortFavouriteMessages()} + title={_t("Sort")} + key="sort" + alignment={Alignment.Bottom} + /> + clearFavouriteMessages()} + title={_t("Clear")} + key="clear" + /> +
    ); diff --git a/src/components/structures/FavouriteMessagesView/FavouriteMessagesTilesList.tsx b/src/components/structures/FavouriteMessagesView/FavouriteMessagesTilesList.tsx index 48c00869e34..a9f3f23b217 100644 --- a/src/components/structures/FavouriteMessagesView/FavouriteMessagesTilesList.tsx +++ b/src/components/structures/FavouriteMessagesView/FavouriteMessagesTilesList.tsx @@ -28,15 +28,17 @@ import FavouriteMessageTile from './FavouriteMessageTile'; interface IProps{ favouriteMessageEvents: MatrixEvent[]; favouriteMessagesPanelRef: any; + searchQuery: string; } -const FavouriteMessagesTilesList = ({ favouriteMessageEvents, favouriteMessagesPanelRef }: IProps) => { +const FavouriteMessagesTilesList = ({ favouriteMessageEvents, favouriteMessagesPanelRef, searchQuery }: IProps) => { const ret = []; let lastRoomId: string; const cli = useContext(MatrixClientContext); + const highlights = []; if (!favouriteMessageEvents) { - ret.push(); + ret.push(); } else { favouriteMessageEvents.reverse().forEach(mxEvent => { const timeline = [] as MatrixEvent[]; @@ -44,6 +46,7 @@ const FavouriteMessagesTilesList = ({ favouriteMessageEvents, favouriteMessagesP const room = cli.getRoom(roomId); timeline.push(mxEvent); + if (searchQuery) highlights.push(searchQuery); if (roomId !== lastRoomId) { ret.push(
  • @@ -65,6 +68,7 @@ const FavouriteMessagesTilesList = ({ favouriteMessageEvents, favouriteMessagesP ret.push( { - const favouriteMessagesIds= JSON.parse( - localStorage?.getItem("io_element_favouriteMessages") ?? "[]") as any[]; + const { getFavouriteMessagesIds } = useFavouriteMessages(); + const favouriteMessagesIds = getFavouriteMessagesIds(); const favouriteMessagesPanelRef = useRef(); const cli = useContext(MatrixClientContext); + const [, setX] = useState(); + + //temporary implementation till a better approach comes up + const handleSearchQuery = (query: string) => { + if (query?.length === 0 || query?.length > 2) { + temp = favouriteMessagesIds.filter((evtObj) => ( + evtObj.content.body.trim().toLowerCase().includes(query))); + searchQuery = query; + //force rerender + setX([]); + } + }; const favouriteMessageEvents = useAsyncMemo(() => { - const promises = favouriteMessagesIds.map(async (resultObj) => { + const currentFavMessageIds = temp.length < favouriteMessagesIds.length ? temp : favouriteMessagesIds; + + const promises = currentFavMessageIds.map(async (resultObj) => { try { // Fetch the events and latest edit in parallel const [evJson, { events: [edit] }] = await Promise.all([ @@ -72,16 +93,21 @@ const FavouriteMessagesView = () => { let favouriteMessagesPanel; if (favouriteMessagesIds?.length === 0) { - favouriteMessagesPanel = (

    { _t("No Saved Messages") }

    ); + favouriteMessagesPanel = ( + <> + +

    { _t("No Saved Messages") }

    + + ); } else { favouriteMessagesPanel = ( - + - + ); diff --git a/src/hooks/useFavouriteMessages.ts b/src/hooks/useFavouriteMessages.ts index 7d0621bfcfe..b363d1ddee1 100644 --- a/src/hooks/useFavouriteMessages.ts +++ b/src/hooks/useFavouriteMessages.ts @@ -18,32 +18,55 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { useState } from "react"; interface IButtonProp { - mxEvent: MatrixEvent; + mxEvent?: MatrixEvent; } -const favouriteMessageIds = JSON.parse( - localStorage?.getItem("io_element_favouriteMessages")?? "[]") as any[]; +let sortState = false; + +export default function useFavouriteMessages(props?: IButtonProp) { + let favouriteMessagesIds = JSON.parse( + localStorage?.getItem("io_element_favouriteMessages")?? "[]") as any[]; -export default function useFavouriteMessages({ mxEvent }: IButtonProp) { const [, setX] = useState(); - const eventId = mxEvent.getId(); - const roomId = mxEvent.getRoomId(); + const eventId = props?.mxEvent.getId(); + const roomId = props?.mxEvent.getRoomId(); + const content = props?.mxEvent.getContent(); //checks if an id already exist const isFavourite = (): boolean => { - return favouriteMessageIds.some(val => val.eventId === eventId); + return favouriteMessagesIds.some(val => val.eventId === eventId); }; const toggleFavourite = () => { - isFavourite() ? favouriteMessageIds.splice(favouriteMessageIds.findIndex(val => val.eventId === eventId), 1) - : favouriteMessageIds.push({ eventId, roomId }); + isFavourite() ? favouriteMessagesIds.splice(favouriteMessagesIds.findIndex(val => val.eventId === eventId), 1) + : favouriteMessagesIds.push({ eventId, roomId, content }); //update the local storage - localStorage.setItem('io_element_favouriteMessages', JSON.stringify(favouriteMessageIds)); + localStorage.setItem('io_element_favouriteMessages', JSON.stringify(favouriteMessagesIds)); // This forces a re-render to account for changes in appearance in real-time when the favourite button is toggled setX([]); }; - return { isFavourite, toggleFavourite }; + const sortFavouriteMessages = () => { + sortState = !sortState; + }; + + const clearFavouriteMessages = () => { + favouriteMessagesIds = []; + //update the local storage + localStorage.setItem('io_element_favouriteMessages', JSON.stringify(favouriteMessagesIds)); + }; + + const getFavouriteMessagesIds = () => { + return sortState ? favouriteMessagesIds.reverse(): favouriteMessagesIds; + }; + + return { + isFavourite, + toggleFavourite, + getFavouriteMessagesIds, + sortFavouriteMessages, + clearFavouriteMessages, + }; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 31fddea03da..5cbf9cad4fa 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -3128,7 +3128,6 @@ "Pause": "Pause", "Play": "Play", "Couldn't load page": "Couldn't load page", - "No Saved Messages": "No Saved Messages", "Drop file here to upload": "Drop file here to upload", "You must register to use this functionality": "You must register to use this functionality", "You must join the room to see its files": "You must join the room to see its files", @@ -3279,6 +3278,9 @@ "Decrypted event source": "Decrypted event source", "Original event source": "Original event source", "Event ID: %(eventId)s": "Event ID: %(eventId)s", + "Sort": "Sort", + "Clear all": "Clear all", + "No Saved Messages": "No Saved Messages", "Unable to verify this device": "Unable to verify this device", "Verify this device": "Verify this device", "Device verified": "Device verified", From 7d404b1dafb238481b0fc993b4ee58cd1b58b9aa Mon Sep 17 00:00:00 2001 From: yaya-usman Date: Sun, 21 Aug 2022 19:28:15 +0300 Subject: [PATCH 07/15] filter search terms and highlight --- .../structures/_FavouriteMessagesView.pcss | 15 ++++++++++ .../FavouriteMessagesHeader.tsx | 30 ++++++++++++++----- .../FavouriteMessagesTilesList.tsx | 9 ++++-- .../FavouriteMessagesView.tsx | 17 ++++++++--- src/hooks/useFavouriteMessages.ts | 8 +++++ 5 files changed, 65 insertions(+), 14 deletions(-) diff --git a/res/css/structures/_FavouriteMessagesView.pcss b/res/css/structures/_FavouriteMessagesView.pcss index 55538a7919e..6ef137af462 100644 --- a/res/css/structures/_FavouriteMessagesView.pcss +++ b/res/css/structures/_FavouriteMessagesView.pcss @@ -65,6 +65,17 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/room/clear-all.svg'); } +.mx_FavMessagesHeader_cancelButton { + background-color: $alert; + mask: url('$(res)/img/cancel.svg'); + mask-repeat: no-repeat; + mask-position: center; + mask-size: 17px; + padding: 9px; + margin: 0 12px 0 3px; + cursor: pointer; +} + .mx_FavMessagesHeader_Search{ width: 70%; } @@ -76,3 +87,7 @@ limitations under the License. font-size: 25px; font-weight: 600; } + +.mx_FavouriteMessages_scrollPanel { + margin-top: 40px; +} \ No newline at end of file diff --git a/src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx b/src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx index 40e7cebbc8b..21a06017c41 100644 --- a/src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx +++ b/src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx @@ -24,7 +24,7 @@ import AccessibleTooltipButton from '../../views/elements/AccessibleTooltipButto import { Alignment } from '../../views/elements/Tooltip'; const FavouriteMessagesHeader = ({ handleSearchQuery }) => { - const { sortFavouriteMessages, clearFavouriteMessages } = useFavouriteMessages(); + const { sortFavouriteMessages, clearFavouriteMessages, setSearchState } = useFavouriteMessages(); const [isSearchClicked, setSearchClicked] = useState(false); const [query, setQuery] = useState(); @@ -34,6 +34,11 @@ const FavouriteMessagesHeader = ({ handleSearchQuery }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [query]); + useEffect(() => { + setSearchState(isSearchClicked); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isSearchClicked]); + return (
    @@ -57,13 +62,22 @@ const FavouriteMessagesHeader = ({ handleSearchQuery }) => { onChange={(e) => setQuery(e.target.value)} />) } - setSearchClicked(!isSearchClicked)} - title={_t("Search")} - key="search" - alignment={Alignment.Bottom} - /> + { !isSearchClicked ? + setSearchClicked(!isSearchClicked)} + title={_t("Search")} + key="search" + alignment={Alignment.Bottom} + /> : + setSearchClicked(!isSearchClicked)} + title={_t("Cancel")} + key="cancel" + alignment={Alignment.Bottom} + /> + } sortFavouriteMessages()} diff --git a/src/components/structures/FavouriteMessagesView/FavouriteMessagesTilesList.tsx b/src/components/structures/FavouriteMessagesView/FavouriteMessagesTilesList.tsx index a9f3f23b217..89424fe494d 100644 --- a/src/components/structures/FavouriteMessagesView/FavouriteMessagesTilesList.tsx +++ b/src/components/structures/FavouriteMessagesView/FavouriteMessagesTilesList.tsx @@ -21,6 +21,7 @@ import { MatrixClient, MatrixEvent } from 'matrix-js-sdk/src/matrix'; import React, { useContext } from 'react'; import MatrixClientContext from '../../../contexts/MatrixClientContext'; +import useFavouriteMessages from '../../../hooks/useFavouriteMessages'; import { _t } from '../../../languageHandler'; import Spinner from '../../views/elements/Spinner'; import FavouriteMessageTile from './FavouriteMessageTile'; @@ -32,10 +33,12 @@ interface IProps{ } const FavouriteMessagesTilesList = ({ favouriteMessageEvents, favouriteMessagesPanelRef, searchQuery }: IProps) => { + const { isSearchClicked } = useFavouriteMessages(); + const ret = []; let lastRoomId: string; - const cli = useContext(MatrixClientContext); const highlights = []; + const cli = useContext(MatrixClientContext); if (!favouriteMessageEvents) { ret.push(); @@ -46,7 +49,9 @@ const FavouriteMessagesTilesList = ({ favouriteMessageEvents, favouriteMessagesP const room = cli.getRoom(roomId); timeline.push(mxEvent); - if (searchQuery) highlights.push(searchQuery); + if (searchQuery && isSearchClicked) { + highlights.push(searchQuery); + } if (roomId !== lastRoomId) { ret.push(
  • diff --git a/src/components/structures/FavouriteMessagesView/FavouriteMessagesView.tsx b/src/components/structures/FavouriteMessagesView/FavouriteMessagesView.tsx index 89b6b912c84..dd5ab57753f 100644 --- a/src/components/structures/FavouriteMessagesView/FavouriteMessagesView.tsx +++ b/src/components/structures/FavouriteMessagesView/FavouriteMessagesView.tsx @@ -17,7 +17,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useContext, useRef, useState } from 'react'; +import React, { useContext, useEffect, useRef, useState } from 'react'; import { MatrixClient, MatrixEvent, RelationType } from 'matrix-js-sdk/src/matrix'; import { logger } from 'matrix-js-sdk/src/logger'; @@ -36,24 +36,33 @@ let temp = JSON.parse( let searchQuery: string; const FavouriteMessagesView = () => { - const { getFavouriteMessagesIds } = useFavouriteMessages(); + const { getFavouriteMessagesIds, isSearchClicked } = useFavouriteMessages(); const favouriteMessagesIds = getFavouriteMessagesIds(); const favouriteMessagesPanelRef = useRef(); const cli = useContext(MatrixClientContext); const [, setX] = useState(); - //temporary implementation till a better approach comes up + //not the best solution, temporary implementation till a better approach comes up const handleSearchQuery = (query: string) => { if (query?.length === 0 || query?.length > 2) { temp = favouriteMessagesIds.filter((evtObj) => ( evtObj.content.body.trim().toLowerCase().includes(query))); searchQuery = query; + //force rerender setX([]); } }; + useEffect(() => { + if (!isSearchClicked) { + searchQuery = ''; + temp = favouriteMessagesIds; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isSearchClicked]); + const favouriteMessageEvents = useAsyncMemo(() => { const currentFavMessageIds = temp.length < favouriteMessagesIds.length ? temp : favouriteMessagesIds; @@ -105,7 +114,7 @@ const FavouriteMessagesView = () => { diff --git a/src/hooks/useFavouriteMessages.ts b/src/hooks/useFavouriteMessages.ts index b363d1ddee1..9e5ce40d304 100644 --- a/src/hooks/useFavouriteMessages.ts +++ b/src/hooks/useFavouriteMessages.ts @@ -22,6 +22,7 @@ interface IButtonProp { } let sortState = false; +let isSearchClicked = false; export default function useFavouriteMessages(props?: IButtonProp) { let favouriteMessagesIds = JSON.parse( @@ -52,6 +53,10 @@ export default function useFavouriteMessages(props?: IButtonProp) { sortState = !sortState; }; + const setSearchState = (val: boolean) => { + isSearchClicked = val; + }; + const clearFavouriteMessages = () => { favouriteMessagesIds = []; //update the local storage @@ -68,5 +73,8 @@ export default function useFavouriteMessages(props?: IButtonProp) { getFavouriteMessagesIds, sortFavouriteMessages, clearFavouriteMessages, + setSearchState, + isSearchClicked, + }; } From 5ffa8b3ea8c7ef8b9dd4edc59a4d3cdb538ad949 Mon Sep 17 00:00:00 2001 From: yaya-usman Date: Sun, 21 Aug 2022 19:53:04 +0300 Subject: [PATCH 08/15] typescript check error fix --- src/PosthogTrackers.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PosthogTrackers.ts b/src/PosthogTrackers.ts index 0422f0bf9b6..d47747fcf6f 100644 --- a/src/PosthogTrackers.ts +++ b/src/PosthogTrackers.ts @@ -42,6 +42,7 @@ const loggedInPageTypeMap: Record = { [PageType.RoomView]: "Room", [PageType.UserView]: "User", [PageType.LegacyGroupView]: "Group", + [PageType.FavouriteMessagesView]: "favourite_messages", }; export default class PosthogTrackers { From 9450fbfb1a0152483524c34be79b5ba58a181202 Mon Sep 17 00:00:00 2001 From: yaya-usman Date: Mon, 22 Aug 2022 14:15:40 +0300 Subject: [PATCH 09/15] Added clear all confirmation modal --- .../structures/_FavouriteMessagesView.pcss | 6 +- .../ConfirmClearDialog.tsx | 63 +++++++++++++++++++ .../FavouriteMessagesHeader.tsx | 23 ++++--- .../FavouriteMessagesView.tsx | 8 ++- src/components/structures/LoggedInView.tsx | 2 +- src/components/structures/MatrixChat.tsx | 10 +++ src/dispatcher/actions.ts | 5 ++ src/hooks/useFavouriteMessages.ts | 10 ++- src/i18n/strings/en_EN.json | 4 +- 9 files changed, 111 insertions(+), 20 deletions(-) create mode 100644 src/components/structures/FavouriteMessagesView/ConfirmClearDialog.tsx diff --git a/res/css/structures/_FavouriteMessagesView.pcss b/res/css/structures/_FavouriteMessagesView.pcss index 6ef137af462..f592f7ec661 100644 --- a/res/css/structures/_FavouriteMessagesView.pcss +++ b/res/css/structures/_FavouriteMessagesView.pcss @@ -89,5 +89,9 @@ limitations under the License. } .mx_FavouriteMessages_scrollPanel { - margin-top: 40px; + margin-top: 25px; +} + +.mx_ClearDialog{ + width: 100%; } \ No newline at end of file diff --git a/src/components/structures/FavouriteMessagesView/ConfirmClearDialog.tsx b/src/components/structures/FavouriteMessagesView/ConfirmClearDialog.tsx new file mode 100644 index 00000000000..36822a43d6c --- /dev/null +++ b/src/components/structures/FavouriteMessagesView/ConfirmClearDialog.tsx @@ -0,0 +1,63 @@ +/* +Copyright 2017 Vector Creations Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React, { FC } from 'react'; + +import useFavouriteMessages from '../../../hooks/useFavouriteMessages'; +import BaseDialog from "../../views/dialogs/BaseDialog"; +import { IDialogProps } from '../../views/dialogs/IDialogProps'; +import DialogButtons from "../../views/elements/DialogButtons"; + +interface IProps extends IDialogProps { + title?: string; + description?: string; + button?: string; + hasCancel?: boolean; +} + +/* + * A dialog for confirming a clearing of starred messages. + */ +const ConfirmClearDialog: FC = (props: IProps) => { + const { clearFavouriteMessages } = useFavouriteMessages(); + + const onConfirmClick = () => { + clearFavouriteMessages(); + props.onFinished(); + }; + + return ( + +
    +
    + { props.description } +
    +
    + +
    + ); +}; + +export default ConfirmClearDialog; diff --git a/src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx b/src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx index 21a06017c41..1fc66c3fd93 100644 --- a/src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx +++ b/src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx @@ -17,14 +17,16 @@ limitations under the License. import React, { useEffect, useState } from 'react'; +import { Action } from '../../../dispatcher/actions'; +import defaultDispatcher from '../../../dispatcher/dispatcher'; import useFavouriteMessages from '../../../hooks/useFavouriteMessages'; import { _t } from '../../../languageHandler'; import RoomAvatar from '../../views/avatars/RoomAvatar'; import AccessibleTooltipButton from '../../views/elements/AccessibleTooltipButton'; -import { Alignment } from '../../views/elements/Tooltip'; const FavouriteMessagesHeader = ({ handleSearchQuery }) => { - const { sortFavouriteMessages, clearFavouriteMessages, setSearchState } = useFavouriteMessages(); + const { reorderFavouriteMessages, setSearchState, getFavouriteMessagesIds } = useFavouriteMessages(); + const favouriteMessagesIds = getFavouriteMessagesIds(); const [isSearchClicked, setSearchClicked] = useState(false); const [query, setQuery] = useState(); @@ -39,6 +41,12 @@ const FavouriteMessagesHeader = ({ handleSearchQuery }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [isSearchClicked]); + const onClearClick = () => { + if (favouriteMessagesIds.length > 0) { + defaultDispatcher.dispatch({ action: Action.OpenClearModal }); + } + }; + return (
    @@ -68,26 +76,23 @@ const FavouriteMessagesHeader = ({ handleSearchQuery }) => { onClick={() => setSearchClicked(!isSearchClicked)} title={_t("Search")} key="search" - alignment={Alignment.Bottom} /> : setSearchClicked(!isSearchClicked)} title={_t("Cancel")} key="cancel" - alignment={Alignment.Bottom} /> } sortFavouriteMessages()} - title={_t("Sort")} - key="sort" - alignment={Alignment.Bottom} + onClick={() => reorderFavouriteMessages()} + title={_t("Reorder")} + key="reorder" /> clearFavouriteMessages()} + onClick={onClearClick} title={_t("Clear")} key="clear" /> diff --git a/src/components/structures/FavouriteMessagesView/FavouriteMessagesView.tsx b/src/components/structures/FavouriteMessagesView/FavouriteMessagesView.tsx index dd5ab57753f..6bf1c079010 100644 --- a/src/components/structures/FavouriteMessagesView/FavouriteMessagesView.tsx +++ b/src/components/structures/FavouriteMessagesView/FavouriteMessagesView.tsx @@ -28,6 +28,11 @@ import MatrixClientContext from '../../../contexts/MatrixClientContext'; import { useAsyncMemo } from '../../../hooks/useAsyncMemo'; import FavouriteMessagesHeader from './FavouriteMessagesHeader'; import useFavouriteMessages from '../../../hooks/useFavouriteMessages'; +import ResizeNotifier from '../../../utils/ResizeNotifier'; + +interface IProps { + resizeNotifier: ResizeNotifier; +} //temporary container for current messageids after filtering let temp = JSON.parse( @@ -35,7 +40,7 @@ let temp = JSON.parse( let searchQuery: string; -const FavouriteMessagesView = () => { +const FavouriteMessagesView = ({ resizeNotifier }: IProps) => { const { getFavouriteMessagesIds, isSearchClicked } = useFavouriteMessages(); const favouriteMessagesIds = getFavouriteMessagesIds(); @@ -115,6 +120,7 @@ const FavouriteMessagesView = () => { diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 8afe9b0e26f..e2a668ea49e 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -648,7 +648,7 @@ class LoggedInView extends React.Component { break; case PageTypes.FavouriteMessagesView: - pageElement = ; + pageElement = ; break; } diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 841e2b739e9..7ad915505c0 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -137,6 +137,7 @@ import { TimelineRenderingType } from "../../contexts/RoomContext"; import { UseCaseSelection } from '../views/elements/UseCaseSelection'; import { ValidatedServerConfig } from '../../utils/ValidatedServerConfig'; import { isLocalRoom } from '../../utils/localRoom/isLocalRoom'; +import ConfirmClearDialog from './FavouriteMessagesView/ConfirmClearDialog'; // legacy export export { default as Views } from "../../Views"; @@ -707,6 +708,15 @@ export default class MatrixChat extends React.PureComponent { this.viewSomethingBehindModal(); break; } + case Action.OpenClearModal: { + Modal.createDialog(ConfirmClearDialog, { + title: _t("Confirm Removal"), + description: _t("Are you sure you wish to clear all your starred messages? "), + button: _t("Confirm"), + hasCancel: true, + }); + break; + } case 'view_welcome_page': this.viewWelcome(); break; diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index 826d1401188..306821ef883 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -336,4 +336,9 @@ export enum Action { * Fired when we want to view favourited messages panel */ ViewFavouriteMessages = "view_favourite_messages", + + /** + * Fired when we want to clear all favourited messages + */ + OpenClearModal = "open_clear_modal", } diff --git a/src/hooks/useFavouriteMessages.ts b/src/hooks/useFavouriteMessages.ts index 9e5ce40d304..8a7b628c661 100644 --- a/src/hooks/useFavouriteMessages.ts +++ b/src/hooks/useFavouriteMessages.ts @@ -25,7 +25,7 @@ let sortState = false; let isSearchClicked = false; export default function useFavouriteMessages(props?: IButtonProp) { - let favouriteMessagesIds = JSON.parse( + const favouriteMessagesIds = JSON.parse( localStorage?.getItem("io_element_favouriteMessages")?? "[]") as any[]; const [, setX] = useState(); @@ -49,7 +49,7 @@ export default function useFavouriteMessages(props?: IButtonProp) { setX([]); }; - const sortFavouriteMessages = () => { + const reorderFavouriteMessages = () => { sortState = !sortState; }; @@ -58,9 +58,7 @@ export default function useFavouriteMessages(props?: IButtonProp) { }; const clearFavouriteMessages = () => { - favouriteMessagesIds = []; - //update the local storage - localStorage.setItem('io_element_favouriteMessages', JSON.stringify(favouriteMessagesIds)); + localStorage.removeItem('io_element_favouriteMessages'); }; const getFavouriteMessagesIds = () => { @@ -71,7 +69,7 @@ export default function useFavouriteMessages(props?: IButtonProp) { isFavourite, toggleFavourite, getFavouriteMessagesIds, - sortFavouriteMessages, + reorderFavouriteMessages, clearFavouriteMessages, setSearchState, isSearchClicked, diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 5cbf9cad4fa..ea0c29f9ab9 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -3149,6 +3149,7 @@ "Reject invitation": "Reject invitation", "Are you sure you want to reject the invitation?": "Are you sure you want to reject the invitation?", "Failed to reject invitation": "Failed to reject invitation", + "Are you sure you wish to clear all your starred messages? ": "Are you sure you wish to clear all your starred messages? ", "You are the only person here. If you leave, no one will be able to join in the future, including you.": "You are the only person here. If you leave, no one will be able to join in the future, including you.", "This space is not public. You will not be able to rejoin without an invite.": "This space is not public. You will not be able to rejoin without an invite.", "This room is not public. You will not be able to rejoin without an invite.": "This room is not public. You will not be able to rejoin without an invite.", @@ -3278,8 +3279,7 @@ "Decrypted event source": "Decrypted event source", "Original event source": "Original event source", "Event ID: %(eventId)s": "Event ID: %(eventId)s", - "Sort": "Sort", - "Clear all": "Clear all", + "Reorder": "Reorder", "No Saved Messages": "No Saved Messages", "Unable to verify this device": "Unable to verify this device", "Verify this device": "Verify this device", From bbca082fd8df094d6c6220a37384c0cbfa2ad10f Mon Sep 17 00:00:00 2001 From: yaya-usman Date: Wed, 24 Aug 2022 15:37:18 +0300 Subject: [PATCH 10/15] written tests for Favourite Message View --- .../ConfirmClearDialog.tsx | 2 +- .../FavouriteMessageTile.tsx | 3 +- .../FavouriteMessagesHeader.tsx | 3 +- .../FavouriteMessagesPanel.tsx | 63 ++++++++++ .../FavouriteMessagesTilesList.tsx | 19 ++- .../FavouriteMessagesView.tsx | 51 ++------ .../structures/FavouriteMessagesView-test.tsx | 118 ++++++++++++++++++ .../FavouriteMessagesView-test.tsx.snap | 3 + 8 files changed, 208 insertions(+), 54 deletions(-) create mode 100644 src/components/structures/FavouriteMessagesView/FavouriteMessagesPanel.tsx create mode 100644 test/components/structures/FavouriteMessagesView-test.tsx create mode 100644 test/components/structures/__snapshots__/FavouriteMessagesView-test.tsx.snap diff --git a/src/components/structures/FavouriteMessagesView/ConfirmClearDialog.tsx b/src/components/structures/FavouriteMessagesView/ConfirmClearDialog.tsx index 36822a43d6c..7dc553ce023 100644 --- a/src/components/structures/FavouriteMessagesView/ConfirmClearDialog.tsx +++ b/src/components/structures/FavouriteMessagesView/ConfirmClearDialog.tsx @@ -1,5 +1,5 @@ /* -Copyright 2017 Vector Creations Ltd +Copyright 2022 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/components/structures/FavouriteMessagesView/FavouriteMessageTile.tsx b/src/components/structures/FavouriteMessagesView/FavouriteMessageTile.tsx index 3e097b631a3..2e81c8078ea 100644 --- a/src/components/structures/FavouriteMessagesView/FavouriteMessageTile.tsx +++ b/src/components/structures/FavouriteMessagesView/FavouriteMessageTile.tsx @@ -1,6 +1,5 @@ /* -Copyright 2015 OpenMarket Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2022 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx b/src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx index 1fc66c3fd93..0286a14aed1 100644 --- a/src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx +++ b/src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx @@ -1,6 +1,5 @@ /* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2019 New Vector Ltd +Copyright 2022 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/components/structures/FavouriteMessagesView/FavouriteMessagesPanel.tsx b/src/components/structures/FavouriteMessagesView/FavouriteMessagesPanel.tsx new file mode 100644 index 00000000000..e75b1c57e7e --- /dev/null +++ b/src/components/structures/FavouriteMessagesView/FavouriteMessagesPanel.tsx @@ -0,0 +1,63 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { MatrixClient, MatrixEvent } from 'matrix-js-sdk/src/matrix'; +import React, { useRef } from 'react'; + +import { _t } from '../../../languageHandler'; +import ResizeNotifier from '../../../utils/ResizeNotifier'; +import ScrollPanel from '../ScrollPanel'; +import FavouriteMessagesHeader from './FavouriteMessagesHeader'; +import FavouriteMessagesTilesList from './FavouriteMessagesTilesList'; + +interface IProps{ + favouriteMessageEvents?: MatrixEvent[]; + resizeNotifier?: ResizeNotifier; + searchQuery?: string; + handleSearchQuery?: (query: string) => void; + cli?: MatrixClient; +} + +const FavouriteMessagesPanel = (props: IProps) => { + const favouriteMessagesPanelRef = useRef(); + let favouriteMessagesPanel; + + if (props.favouriteMessageEvents?.length === 0) { + favouriteMessagesPanel = ( + <> + +

    { _t("No Saved Messages") }

    + + ); + } else { + favouriteMessagesPanel = ( + <> + + + + + + ); + } + + return <>{ favouriteMessagesPanel }; +}; +export default FavouriteMessagesPanel; + diff --git a/src/components/structures/FavouriteMessagesView/FavouriteMessagesTilesList.tsx b/src/components/structures/FavouriteMessagesView/FavouriteMessagesTilesList.tsx index 89424fe494d..81a81a6ea82 100644 --- a/src/components/structures/FavouriteMessagesView/FavouriteMessagesTilesList.tsx +++ b/src/components/structures/FavouriteMessagesView/FavouriteMessagesTilesList.tsx @@ -1,8 +1,5 @@ /* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2017 Vector Creations Ltd -Copyright 2018, 2019 New Vector Ltd -Copyright 2019 - 2022 The Matrix.org Foundation C.I.C. +Copyright 2022 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,27 +15,27 @@ limitations under the License. */ import { MatrixClient, MatrixEvent } from 'matrix-js-sdk/src/matrix'; -import React, { useContext } from 'react'; +import React from 'react'; -import MatrixClientContext from '../../../contexts/MatrixClientContext'; import useFavouriteMessages from '../../../hooks/useFavouriteMessages'; import { _t } from '../../../languageHandler'; import Spinner from '../../views/elements/Spinner'; import FavouriteMessageTile from './FavouriteMessageTile'; interface IProps{ - favouriteMessageEvents: MatrixEvent[]; - favouriteMessagesPanelRef: any; - searchQuery: string; + favouriteMessageEvents?: MatrixEvent[]; + favouriteMessagesPanelRef?: any; + searchQuery?: string; + cli?: MatrixClient; } -const FavouriteMessagesTilesList = ({ favouriteMessageEvents, favouriteMessagesPanelRef, searchQuery }: IProps) => { +// eslint-disable-next-line max-len +const FavouriteMessagesTilesList = ({ cli, favouriteMessageEvents, favouriteMessagesPanelRef, searchQuery }: IProps) => { const { isSearchClicked } = useFavouriteMessages(); const ret = []; let lastRoomId: string; const highlights = []; - const cli = useContext(MatrixClientContext); if (!favouriteMessageEvents) { ret.push(); diff --git a/src/components/structures/FavouriteMessagesView/FavouriteMessagesView.tsx b/src/components/structures/FavouriteMessagesView/FavouriteMessagesView.tsx index 6bf1c079010..cb177b988d1 100644 --- a/src/components/structures/FavouriteMessagesView/FavouriteMessagesView.tsx +++ b/src/components/structures/FavouriteMessagesView/FavouriteMessagesView.tsx @@ -1,8 +1,5 @@ /* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2017 Vector Creations Ltd -Copyright 2018, 2019 New Vector Ltd -Copyright 2019 - 2022 The Matrix.org Foundation C.I.C. +Copyright 2022 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,21 +14,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useContext, useEffect, useRef, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { MatrixClient, MatrixEvent, RelationType } from 'matrix-js-sdk/src/matrix'; import { logger } from 'matrix-js-sdk/src/logger'; -import { _t } from '../../../languageHandler'; -import ScrollPanel from '../ScrollPanel'; -import FavouriteMessagesTilesList from './FavouriteMessagesTilesList'; import MatrixClientContext from '../../../contexts/MatrixClientContext'; import { useAsyncMemo } from '../../../hooks/useAsyncMemo'; -import FavouriteMessagesHeader from './FavouriteMessagesHeader'; import useFavouriteMessages from '../../../hooks/useFavouriteMessages'; import ResizeNotifier from '../../../utils/ResizeNotifier'; +import FavouriteMessagesPanel from './FavouriteMessagesPanel'; interface IProps { - resizeNotifier: ResizeNotifier; + resizeNotifier?: ResizeNotifier; } //temporary container for current messageids after filtering @@ -44,7 +38,6 @@ const FavouriteMessagesView = ({ resizeNotifier }: IProps) => { const { getFavouriteMessagesIds, isSearchClicked } = useFavouriteMessages(); const favouriteMessagesIds = getFavouriteMessagesIds(); - const favouriteMessagesPanelRef = useRef(); const cli = useContext(MatrixClientContext); const [, setX] = useState(); @@ -104,33 +97,15 @@ const FavouriteMessagesView = ({ resizeNotifier }: IProps) => { return Promise.all(promises); }, [cli, favouriteMessagesIds], null); - let favouriteMessagesPanel; - - if (favouriteMessagesIds?.length === 0) { - favouriteMessagesPanel = ( - <> - -

    { _t("No Saved Messages") }

    - - ); - } else { - favouriteMessagesPanel = ( - - - - - - - ); - } - - return ( - <> { favouriteMessagesPanel } - ); + const props = { + favouriteMessageEvents, + resizeNotifier, + searchQuery, + handleSearchQuery, + cli, + }; + + return (); }; export default FavouriteMessagesView; diff --git a/test/components/structures/FavouriteMessagesView-test.tsx b/test/components/structures/FavouriteMessagesView-test.tsx new file mode 100644 index 00000000000..1c86dab9b02 --- /dev/null +++ b/test/components/structures/FavouriteMessagesView-test.tsx @@ -0,0 +1,118 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from "react"; +// eslint-disable-next-line deprecate/import +import { mount } from "enzyme"; +import { mocked, MockedObject } from "jest-mock"; +import { EventType, MatrixClient, MatrixEvent, MsgType } from "matrix-js-sdk/src/matrix"; + +import _FavouriteMessagesView from "../../../src/components/structures/FavouriteMessagesView/FavouriteMessagesView"; +import { stubClient, mockPlatformPeg, unmockPlatformPeg, wrapInMatrixClientContext } from "../../test-utils"; +import { MatrixClientPeg } from "../../../src/MatrixClientPeg"; +import FavouriteMessagesPanel from "../../../src/components/structures/FavouriteMessagesView/FavouriteMessagesPanel"; +import SettingsStore from "../../../src/settings/SettingsStore"; + +const FavouriteMessagesView = wrapInMatrixClientContext(_FavouriteMessagesView); + +describe("FavouriteMessagesView", () => { + let cli: MockedObject; + // let room: Room; + const userId = '@alice:server.org'; + const roomId = '!room:server.org'; + const alicesFavouriteMessageEvent = new MatrixEvent({ + type: EventType.RoomMessage, + sender: userId, + room_id: roomId, + content: { + msgtype: MsgType.Text, + body: 'i am alice', + }, + event_id: "$alices_message", + }); + + const bobsFavouriteMessageEvent = new MatrixEvent({ + type: EventType.RoomMessage, + sender: '@bob:server.org', + room_id: roomId, + content: { + msgtype: MsgType.Text, + body: 'i am bob', + }, + event_id: "$bobs_message", + }); + + beforeEach(async () => { + mockPlatformPeg({ reload: () => {} }); + stubClient(); + cli = mocked(MatrixClientPeg.get()); + }); + + afterEach(async () => { + unmockPlatformPeg(); + jest.restoreAllMocks(); + }); + + describe('favourite_messages feature when enabled', () => { + beforeEach(() => { + jest.spyOn(SettingsStore, 'getValue') + .mockImplementation(setting => setting === 'feature_favourite_messages'); + }); + + it('renders correctly', () => { + const view = mount(); + expect(view.html()).toMatchSnapshot(); + }); + + it('renders component with empty or default props correctly', () => { + const props = { + favouriteMessageEvents: [], + handleSearchQuery: jest.fn(), + cli, + }; + const view = mount(); + expect(view.prop('favouriteMessageEvents')).toHaveLength(0); + expect(view.contains("No Saved Messages")).toBeTruthy(); + }); + + it('renders starred messages correctly for a single event', () => { + const props = { + favouriteMessageEvents: [bobsFavouriteMessageEvent], + handleSearchQuery: jest.fn(), + cli, + }; + const view = mount(); + + expect(view.find('.mx_EventTile_body').text()).toEqual("i am bob"); + }); + + it('renders starred messages correctly for multiple single event', () => { + const props = { + favouriteMessageEvents: [alicesFavouriteMessageEvent, bobsFavouriteMessageEvent], + handleSearchQuery: jest.fn(), + cli, + }; + const view = mount(); + //for alice + expect(view.find("li[data-event-id='$alices_message']")).toBeDefined(); + expect(view.find("li[data-event-id='$alices_message']").contains("i am alice")).toBeTruthy(); + + //for bob + expect(view.find("li[data-event-id='$bobs_message']")).toBeDefined(); + expect(view.find("li[data-event-id='$bobs_message']").contains("i am bob")).toBeTruthy(); + }); + }); +}); diff --git a/test/components/structures/__snapshots__/FavouriteMessagesView-test.tsx.snap b/test/components/structures/__snapshots__/FavouriteMessagesView-test.tsx.snap new file mode 100644 index 00000000000..a939a162ff2 --- /dev/null +++ b/test/components/structures/__snapshots__/FavouriteMessagesView-test.tsx.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`FavouriteMessagesView when favourite_messages feature is enabled renders correctly 1`] = `"
    F\\"\\"Favourite Messages
    "`; From 7cc7a996eed9894cfaef319c5cf3c684ae6bcc3a Mon Sep 17 00:00:00 2001 From: yaya-usman Date: Wed, 24 Aug 2022 16:34:20 +0300 Subject: [PATCH 11/15] resolve code nits --- .../structures/_FavouriteMessagesView.pcss | 33 +++-- .../ConfirmClearDialog.tsx | 18 +-- .../FavouriteMessageTile.tsx | 125 +++++++++--------- .../FavouriteMessagesHeader.tsx | 4 +- src/components/structures/MatrixChat.tsx | 7 +- 5 files changed, 85 insertions(+), 102 deletions(-) diff --git a/res/css/structures/_FavouriteMessagesView.pcss b/res/css/structures/_FavouriteMessagesView.pcss index f592f7ec661..ed15c9d8d34 100644 --- a/res/css/structures/_FavouriteMessagesView.pcss +++ b/res/css/structures/_FavouriteMessagesView.pcss @@ -1,5 +1,5 @@ /* -Copyright 2019 New Vector Ltd +Copyright 2022 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,14 +18,14 @@ limitations under the License. position: fixed; top: 0; left: 0; - width: 100%; - flex: 0 0 50px; + width: 100%; + flex: 0 0 50px; border-bottom: 1px solid $primary-hairline-color; background-color: $background; z-index: 999; } -.mx_FavMessagesHeader_Wrapper{ +.mx_FavMessagesHeader_Wrapper { height: 44px; display: flex; align-items: center; @@ -35,20 +35,20 @@ limitations under the License. border-bottom: 1px solid $system; justify-content: space-between; - &--left{ + .mx_FavMessagesHeader_Wrapper_left { display: flex; align-items: center; flex: 0.4; - + & > span { - color: $primary-content; - font-weight: $font-semi-bold; - font-size: $font-18px; - margin: 0 8px; - } + color: $primary-content; + font-weight: $font-semi-bold; + font-size: $font-18px; + margin: 0 8px; + } } - &--right{ + .mx_FavMessagesHeader_Wrapper_right { display: flex; align-items: center; flex: 0.6; @@ -56,7 +56,6 @@ limitations under the License. } } - .mx_FavMessagesHeader_sortButton::before { mask-image: url('$(res)/img/element-icons/room/sort-twoway.svg'); } @@ -76,11 +75,11 @@ limitations under the License. cursor: pointer; } -.mx_FavMessagesHeader_Search{ +.mx_FavMessagesHeader_Search { width: 70%; } -.mx_FavouriteMessages_emptyMarker{ +.mx_FavouriteMessages_emptyMarker { display: flex; align-items: center; justify-content: center; @@ -92,6 +91,6 @@ limitations under the License. margin-top: 25px; } -.mx_ClearDialog{ +.mx_ClearDialog { width: 100%; -} \ No newline at end of file +} diff --git a/src/components/structures/FavouriteMessagesView/ConfirmClearDialog.tsx b/src/components/structures/FavouriteMessagesView/ConfirmClearDialog.tsx index 7dc553ce023..4b9e6db9b9f 100644 --- a/src/components/structures/FavouriteMessagesView/ConfirmClearDialog.tsx +++ b/src/components/structures/FavouriteMessagesView/ConfirmClearDialog.tsx @@ -17,21 +17,15 @@ limitations under the License. import React, { FC } from 'react'; import useFavouriteMessages from '../../../hooks/useFavouriteMessages'; +import { _t } from '../../../languageHandler'; import BaseDialog from "../../views/dialogs/BaseDialog"; import { IDialogProps } from '../../views/dialogs/IDialogProps'; import DialogButtons from "../../views/elements/DialogButtons"; -interface IProps extends IDialogProps { - title?: string; - description?: string; - button?: string; - hasCancel?: boolean; -} - /* * A dialog for confirming a clearing of starred messages. */ -const ConfirmClearDialog: FC = (props: IProps) => { +const ConfirmClearDialog: FC = (props: IDialogProps) => { const { clearFavouriteMessages } = useFavouriteMessages(); const onConfirmClick = () => { @@ -43,18 +37,18 @@ const ConfirmClearDialog: FC = (props: IProps) => {
    - { props.description } + { _t("Are you sure you wish to clear all your starred messages? ") }
    ); diff --git a/src/components/structures/FavouriteMessagesView/FavouriteMessageTile.tsx b/src/components/structures/FavouriteMessagesView/FavouriteMessageTile.tsx index 2e81c8078ea..1b99841daa1 100644 --- a/src/components/structures/FavouriteMessagesView/FavouriteMessageTile.tsx +++ b/src/components/structures/FavouriteMessagesView/FavouriteMessageTile.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { FC } from "react"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import RoomContext from "../../../contexts/RoomContext"; @@ -39,78 +39,73 @@ interface IProps { timeline?: MatrixEvent[]; } -export default class FavouriteMessageTile extends React.Component { - static contextType = RoomContext; - public context!: React.ContextType; +const FavouriteMessageTile: FC = (props: IProps) => { + let context!: React.ContextType; - constructor(props, context) { - super(props, context); - } + const result = props.result; + const eventId = result.getId(); - public render() { - const result = this.props.result; - const eventId = result.getId(); + const ts1 = result?.getTs(); + const ret = []; + const layout = SettingsStore.getValue("layout"); + const isTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps"); + const alwaysShowTimestamps = SettingsStore.getValue("alwaysShowTimestamps"); + const threadsEnabled = SettingsStore.getValue("feature_thread"); - const ts1 = result?.getTs(); - const ret = []; - const layout = SettingsStore.getValue("layout"); - const isTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps"); - const alwaysShowTimestamps = SettingsStore.getValue("alwaysShowTimestamps"); - const threadsEnabled = SettingsStore.getValue("feature_thread"); + for (let j = 0; j < props.timeline.length; j++) { + const mxEv = props.timeline[j]; + const highlights = props.searchHighlights; - for (let j = 0; j < this.props.timeline.length; j++) { - const mxEv = this.props.timeline[j]; - const highlights = this.props.searchHighlights; + if (haveRendererForEvent(mxEv, context?.showHiddenEvents)) { + // do we need a date separator since the last event? + const prevEv = props.timeline[j - 1]; + // is this a continuation of the previous message? + const continuation = prevEv && + !wantsDateSeparator(prevEv.getDate(), mxEv.getDate()) && + shouldFormContinuation( + prevEv, + mxEv, + context?.showHiddenEvents, + threadsEnabled, + ); - if (haveRendererForEvent(mxEv, this.context?.showHiddenEvents)) { - // do we need a date separator since the last event? - const prevEv = this.props.timeline[j - 1]; - // is this a continuation of the previous message? - const continuation = prevEv && - !wantsDateSeparator(prevEv.getDate(), mxEv.getDate()) && - shouldFormContinuation( - prevEv, + let lastInSection = true; + const nextEv = props.timeline[j + 1]; + if (nextEv) { + const willWantDateSeparator = wantsDateSeparator(mxEv.getDate(), nextEv.getDate()); + lastInSection = ( + willWantDateSeparator || + mxEv.getSender() !== nextEv.getSender() || + !shouldFormContinuation( mxEv, - this.context?.showHiddenEvents, + nextEv, + context?.showHiddenEvents, threadsEnabled, - ); - - let lastInSection = true; - const nextEv = this.props.timeline[j + 1]; - if (nextEv) { - const willWantDateSeparator = wantsDateSeparator(mxEv.getDate(), nextEv.getDate()); - lastInSection = ( - willWantDateSeparator || - mxEv.getSender() !== nextEv.getSender() || - !shouldFormContinuation( - mxEv, - nextEv, - this.context?.showHiddenEvents, - threadsEnabled, - ) - ); - } - - ret.push( - , + ) ); } - } - return
  • -
      { ret }
    -
  • ; + ret.push( + , + ); + } } -} + + return
  • +
      { ret }
    +
  • ; +}; + +export default FavouriteMessageTile; diff --git a/src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx b/src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx index 0286a14aed1..73461e6c27b 100644 --- a/src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx +++ b/src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx @@ -49,7 +49,7 @@ const FavouriteMessagesHeader = ({ handleSearchQuery }) => { return (
    -
    +
    { /> Favourite Messages
    -
    +
    { isSearchClicked && ( { break; } case Action.OpenClearModal: { - Modal.createDialog(ConfirmClearDialog, { - title: _t("Confirm Removal"), - description: _t("Are you sure you wish to clear all your starred messages? "), - button: _t("Confirm"), - hasCancel: true, - }); + Modal.createDialog(ConfirmClearDialog); break; } case 'view_welcome_page': From 97e39ed418b576ac03ed33f4b2a980564038955c Mon Sep 17 00:00:00 2001 From: yaya-usman Date: Wed, 24 Aug 2022 16:49:09 +0300 Subject: [PATCH 12/15] update favourite messages related test in MessageActionBar.tsx --- .../views/messages/MessageActionBar-test.tsx | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/test/components/views/messages/MessageActionBar-test.tsx b/test/components/views/messages/MessageActionBar-test.tsx index d4dcb1a2dff..5bd53847701 100644 --- a/test/components/views/messages/MessageActionBar-test.tsx +++ b/test/components/views/messages/MessageActionBar-test.tsx @@ -16,7 +16,7 @@ limitations under the License. import React from 'react'; import { render, fireEvent } from '@testing-library/react'; -import { act } from 'react-test-renderer'; +import { act } from "react-test-renderer"; import { EventType, EventStatus, @@ -530,8 +530,7 @@ describe('', () => { expect(alicesAction.classList).toContain('mx_MessageActionBar_favouriteButton_fillstar'); expect(bobsAction.classList).not.toContain('mx_MessageActionBar_favouriteButton_fillstar'); - expect(localStorageMock.setItem) - .toHaveBeenCalledWith('io_element_favouriteMessages', '["$alices_message"]'); + expect(JSON.parse(localStorageMock.getItem('io_element_favouriteMessages'))).toHaveLength(1); //when bob's event is fired,both should be styled and stored in localStorage act(() => { @@ -540,20 +539,19 @@ describe('', () => { expect(alicesAction.classList).toContain('mx_MessageActionBar_favouriteButton_fillstar'); expect(bobsAction.classList).toContain('mx_MessageActionBar_favouriteButton_fillstar'); - expect(localStorageMock.setItem) - .toHaveBeenCalledWith('io_element_favouriteMessages', '["$alices_message","$bobs_message"]'); - //finally, at this point the localStorage should contain the two eventids - expect(localStorageMock.getItem('io_element_favouriteMessages')) - .toEqual('["$alices_message","$bobs_message"]'); + //if we decided to unfavourite alice's and bob's event by clicking again + act(() => { + fireEvent.click(alicesAction); + }); - //if decided to unfavourite bob's event by clicking again act(() => { fireEvent.click(bobsAction); }); + expect(bobsAction.classList).not.toContain('mx_MessageActionBar_favouriteButton_fillstar'); - expect(alicesAction.classList).toContain('mx_MessageActionBar_favouriteButton_fillstar'); - expect(localStorageMock.getItem('io_element_favouriteMessages')).toEqual('["$alices_message"]'); + expect(alicesAction.classList).not.toContain('mx_MessageActionBar_favouriteButton_fillstar'); + expect(JSON.parse(localStorageMock.getItem('io_element_favouriteMessages'))).toHaveLength(0); }); }); From 64b7c12aed484ada641036c52ac31689cfe05c46 Mon Sep 17 00:00:00 2001 From: yaya-usman Date: Wed, 24 Aug 2022 21:28:42 +0300 Subject: [PATCH 13/15] resolve typescript check problem --- src/PosthogTrackers.ts | 2 +- .../FavouriteMessageTile.tsx | 18 +++++++++--------- .../FavouriteMessagesPanel.tsx | 2 +- .../FavouriteMessagesTilesList.tsx | 8 ++++---- .../FavouriteMessagesView.tsx | 4 ++-- src/components/views/rooms/RoomList.tsx | 2 +- src/i18n/strings/en_EN.json | 2 +- .../FavouriteMessagesView-test.tsx.snap | 2 +- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/PosthogTrackers.ts b/src/PosthogTrackers.ts index d47747fcf6f..453a4fcdb5d 100644 --- a/src/PosthogTrackers.ts +++ b/src/PosthogTrackers.ts @@ -42,7 +42,7 @@ const loggedInPageTypeMap: Record = { [PageType.RoomView]: "Room", [PageType.UserView]: "User", [PageType.LegacyGroupView]: "Group", - [PageType.FavouriteMessagesView]: "favourite_messages", + [PageType.FavouriteMessagesView]: "FavouriteMessages", }; export default class PosthogTrackers { diff --git a/src/components/structures/FavouriteMessagesView/FavouriteMessageTile.tsx b/src/components/structures/FavouriteMessagesView/FavouriteMessageTile.tsx index 1b99841daa1..d3a02e75a97 100644 --- a/src/components/structures/FavouriteMessagesView/FavouriteMessageTile.tsx +++ b/src/components/structures/FavouriteMessagesView/FavouriteMessageTile.tsx @@ -30,13 +30,13 @@ interface IProps { // an event result object result: MatrixEvent; // href for the highlights in this result - resultLink?: string; + resultLink: string; // a list of strings to be highlighted in the results searchHighlights?: string[]; onHeightChanged?: () => void; permalinkCreator?: RoomPermalinkCreator; //a list containing the saved items events - timeline?: MatrixEvent[]; + timeline: MatrixEvent[]; } const FavouriteMessageTile: FC = (props: IProps) => { @@ -46,22 +46,22 @@ const FavouriteMessageTile: FC = (props: IProps) => { const eventId = result.getId(); const ts1 = result?.getTs(); - const ret = []; + const ret = []; const layout = SettingsStore.getValue("layout"); const isTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps"); const alwaysShowTimestamps = SettingsStore.getValue("alwaysShowTimestamps"); const threadsEnabled = SettingsStore.getValue("feature_thread"); - for (let j = 0; j < props.timeline.length; j++) { - const mxEv = props.timeline[j]; - const highlights = props.searchHighlights; + for (let j = 0; j < props?.timeline.length; j++) { + const mxEv = props?.timeline[j]; + const highlights = props?.searchHighlights; if (haveRendererForEvent(mxEv, context?.showHiddenEvents)) { // do we need a date separator since the last event? const prevEv = props.timeline[j - 1]; // is this a continuation of the previous message? const continuation = prevEv && - !wantsDateSeparator(prevEv.getDate(), mxEv.getDate()) && + !wantsDateSeparator((prevEv.getDate())!, mxEv.getDate()) && shouldFormContinuation( prevEv, mxEv, @@ -70,9 +70,9 @@ const FavouriteMessageTile: FC = (props: IProps) => { ); let lastInSection = true; - const nextEv = props.timeline[j + 1]; + const nextEv = props?.timeline[j + 1]; if (nextEv) { - const willWantDateSeparator = wantsDateSeparator(mxEv.getDate(), nextEv.getDate()); + const willWantDateSeparator = wantsDateSeparator((mxEv.getDate())!, nextEv.getDate()); lastInSection = ( willWantDateSeparator || mxEv.getSender() !== nextEv.getSender() || diff --git a/src/components/structures/FavouriteMessagesView/FavouriteMessagesPanel.tsx b/src/components/structures/FavouriteMessagesView/FavouriteMessagesPanel.tsx index e75b1c57e7e..8082ebd3292 100644 --- a/src/components/structures/FavouriteMessagesView/FavouriteMessagesPanel.tsx +++ b/src/components/structures/FavouriteMessagesView/FavouriteMessagesPanel.tsx @@ -47,7 +47,7 @@ const FavouriteMessagesPanel = (props: IProps) => { <> diff --git a/src/components/structures/FavouriteMessagesView/FavouriteMessagesTilesList.tsx b/src/components/structures/FavouriteMessagesView/FavouriteMessagesTilesList.tsx index 81a81a6ea82..ecce971b6ca 100644 --- a/src/components/structures/FavouriteMessagesView/FavouriteMessagesTilesList.tsx +++ b/src/components/structures/FavouriteMessagesView/FavouriteMessagesTilesList.tsx @@ -33,9 +33,9 @@ interface IProps{ const FavouriteMessagesTilesList = ({ cli, favouriteMessageEvents, favouriteMessagesPanelRef, searchQuery }: IProps) => { const { isSearchClicked } = useFavouriteMessages(); - const ret = []; + const ret: JSX.Element[] = []; let lastRoomId: string; - const highlights = []; + const highlights: string[] = []; if (!favouriteMessageEvents) { ret.push(); @@ -43,7 +43,7 @@ const FavouriteMessagesTilesList = ({ cli, favouriteMessageEvents, favouriteMess favouriteMessageEvents.reverse().forEach(mxEvent => { const timeline = [] as MatrixEvent[]; const roomId = mxEvent.getRoomId(); - const room = cli.getRoom(roomId); + const room = cli?.getRoom(roomId); timeline.push(mxEvent); if (searchQuery && isSearchClicked) { @@ -54,7 +54,7 @@ const FavouriteMessagesTilesList = ({ cli, favouriteMessageEvents, favouriteMess ret.push(
  • { _t("Room") }: { room.name }

  • ); - lastRoomId = roomId; + lastRoomId = roomId!; } // once dynamic content in the favourite messages panel loads, make the scrollPanel check // the scroll offsets. diff --git a/src/components/structures/FavouriteMessagesView/FavouriteMessagesView.tsx b/src/components/structures/FavouriteMessagesView/FavouriteMessagesView.tsx index cb177b988d1..d7b6dc61802 100644 --- a/src/components/structures/FavouriteMessagesView/FavouriteMessagesView.tsx +++ b/src/components/structures/FavouriteMessagesView/FavouriteMessagesView.tsx @@ -72,7 +72,7 @@ const FavouriteMessagesView = ({ resizeNotifier }: IProps) => { cli.relations(resultObj.roomId, resultObj.eventId, RelationType.Replace, null, { limit: 1 }), ]); const event = new MatrixEvent(evJson); - const roomId = event.getRoomId(); + const roomId = event.getRoomId()!; const room = cli.getRoom(roomId); if (event.isEncrypted()) { @@ -81,7 +81,7 @@ const FavouriteMessagesView = ({ resizeNotifier }: IProps) => { if (event) { // Inject sender information - event.sender = room.getMember(event.getSender()); + event.sender = room.getMember(event.getSender())!; // Also inject any edits found if (edit) event.makeReplaced(edit); diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index 2e7b7ffce7e..500c85417a7 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -72,7 +72,7 @@ interface IProps { resizeNotifier: ResizeNotifier; isMinimized: boolean; activeSpace: SpaceKey; - pageType: PageType; + pageType?: PageType; } interface IState { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b51a65ecc37..d6062d0c6a4 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -3148,7 +3148,6 @@ "Reject invitation": "Reject invitation", "Are you sure you want to reject the invitation?": "Are you sure you want to reject the invitation?", "Failed to reject invitation": "Failed to reject invitation", - "Are you sure you wish to clear all your starred messages? ": "Are you sure you wish to clear all your starred messages? ", "You are the only person here. If you leave, no one will be able to join in the future, including you.": "You are the only person here. If you leave, no one will be able to join in the future, including you.", "This space is not public. You will not be able to rejoin without an invite.": "This space is not public. You will not be able to rejoin without an invite.", "This room is not public. You will not be able to rejoin without an invite.": "This room is not public. You will not be able to rejoin without an invite.", @@ -3278,6 +3277,7 @@ "Decrypted event source": "Decrypted event source", "Original event source": "Original event source", "Event ID: %(eventId)s": "Event ID: %(eventId)s", + "Are you sure you wish to clear all your starred messages? ": "Are you sure you wish to clear all your starred messages? ", "Reorder": "Reorder", "No Saved Messages": "No Saved Messages", "Unable to verify this device": "Unable to verify this device", diff --git a/test/components/structures/__snapshots__/FavouriteMessagesView-test.tsx.snap b/test/components/structures/__snapshots__/FavouriteMessagesView-test.tsx.snap index a939a162ff2..9d60baba05a 100644 --- a/test/components/structures/__snapshots__/FavouriteMessagesView-test.tsx.snap +++ b/test/components/structures/__snapshots__/FavouriteMessagesView-test.tsx.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FavouriteMessagesView when favourite_messages feature is enabled renders correctly 1`] = `"
    F\\"\\"Favourite Messages
    "`; +exports[`FavouriteMessagesView favourite_messages feature when enabled renders correctly 1`] = `"
    F\\"\\"Favourite Messages
    "`; From 7d04db32b26a1266071a01289619dee26fa9ebdd Mon Sep 17 00:00:00 2001 From: Yaya Usman <38439166+yaya-usman@users.noreply.github.com> Date: Tue, 6 Sep 2022 23:02:38 +0300 Subject: [PATCH 14/15] Update src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Šimon Brandner --- .../FavouriteMessagesView/FavouriteMessagesHeader.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx b/src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx index 73461e6c27b..a052c5c7393 100644 --- a/src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx +++ b/src/components/structures/FavouriteMessagesView/FavouriteMessagesHeader.tsx @@ -69,14 +69,14 @@ const FavouriteMessagesHeader = ({ handleSearchQuery }) => { onChange={(e) => setQuery(e.target.value)} />) } - { !isSearchClicked ? - setSearchClicked(!isSearchClicked)} title={_t("Search")} key="search" - /> : - + : setSearchClicked(!isSearchClicked)} title={_t("Cancel")} From b6ba7b9b028e52de017f1bf75dde2bcb7d3af0dd Mon Sep 17 00:00:00 2001 From: Yaya Usman <38439166+yaya-usman@users.noreply.github.com> Date: Tue, 6 Sep 2022 23:02:58 +0300 Subject: [PATCH 15/15] Update src/hooks/useFavouriteMessages.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Šimon Brandner --- src/hooks/useFavouriteMessages.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useFavouriteMessages.ts b/src/hooks/useFavouriteMessages.ts index 8a7b628c661..9420b05dced 100644 --- a/src/hooks/useFavouriteMessages.ts +++ b/src/hooks/useFavouriteMessages.ts @@ -26,7 +26,7 @@ let isSearchClicked = false; export default function useFavouriteMessages(props?: IButtonProp) { const favouriteMessagesIds = JSON.parse( - localStorage?.getItem("io_element_favouriteMessages")?? "[]") as any[]; + localStorage?.getItem("io_element_favouriteMessages") ?? "[]") as any[]; const [, setX] = useState(); const eventId = props?.mxEvent.getId();