diff --git a/res/css/_common.scss b/res/css/_common.scss index b128a82442..6b4e109b3a 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -104,8 +104,8 @@ a:visited { input[type=text], input[type=search], input[type=password] { + font-family: inherit; padding: 9px; - font-family: $font-family; font-size: $font-14px; font-weight: 600; min-width: 0; @@ -146,7 +146,6 @@ input[type=text], input[type=password], textarea { /* Required by Firefox */ textarea { - font-family: $font-family; color: $primary-fg-color; } diff --git a/res/css/views/dialogs/_AddressPickerDialog.scss b/res/css/views/dialogs/_AddressPickerDialog.scss index 136e497994..a1147e6fbc 100644 --- a/res/css/views/dialogs/_AddressPickerDialog.scss +++ b/res/css/views/dialogs/_AddressPickerDialog.scss @@ -29,7 +29,6 @@ limitations under the License. .mx_AddressPickerDialog_input:focus { height: 26px; font-size: $font-14px; - font-family: $font-family; padding-left: 12px; padding-right: 12px; margin: 0 !important; diff --git a/res/css/views/dialogs/_ConfirmUserActionDialog.scss b/res/css/views/dialogs/_ConfirmUserActionDialog.scss index 823f4d1e28..284c171f4e 100644 --- a/res/css/views/dialogs/_ConfirmUserActionDialog.scss +++ b/res/css/views/dialogs/_ConfirmUserActionDialog.scss @@ -34,7 +34,6 @@ limitations under the License. } .mx_ConfirmUserActionDialog_reasonField { - font-family: $font-family; font-size: $font-14px; color: $primary-fg-color; background-color: $primary-bg-color; diff --git a/res/css/views/dialogs/_DevtoolsDialog.scss b/res/css/views/dialogs/_DevtoolsDialog.scss index 8fee740016..4d35e8d569 100644 --- a/res/css/views/dialogs/_DevtoolsDialog.scss +++ b/res/css/views/dialogs/_DevtoolsDialog.scss @@ -55,22 +55,6 @@ limitations under the License. padding-right: 24px; } -.mx_DevTools_inputCell { - display: table-cell; - width: 240px; -} - -.mx_DevTools_inputCell input { - display: inline-block; - border: 0; - border-bottom: 1px solid $input-underline-color; - padding: 0; - width: 240px; - color: $input-fg-color; - font-family: $font-family; - font-size: $font-16px; -} - .mx_DevTools_textarea { font-size: $font-12px; max-width: 684px; @@ -139,7 +123,6 @@ limitations under the License. + .mx_DevTools_tgl-btn { padding: 2px; transition: all .2s ease; - font-family: sans-serif; perspective: 100px; &::after, &::before { diff --git a/res/css/views/elements/_Field.scss b/res/css/views/elements/_Field.scss index f67da6477b..cae81dcc97 100644 --- a/res/css/views/elements/_Field.scss +++ b/res/css/views/elements/_Field.scss @@ -39,7 +39,6 @@ limitations under the License. .mx_Field select, .mx_Field textarea { font-weight: normal; - font-family: $font-family; font-size: $font-14px; border: none; // Even without a border here, we still need this avoid overlapping the rounded diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 4a419244ff..206fe843de 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -132,15 +132,6 @@ $hover-select-border: 4px; } } - &.mx_EventTile_info .mx_EventTile_line, - & ~ .mx_EventListSummary > :not(.mx_EventTile) .mx_EventTile_avatar ~ .mx_EventTile_line { - padding-left: calc($left-gutter + 18px); - } - - & ~ .mx_EventListSummary .mx_EventTile_line { - padding-left: calc($left-gutter); - } - &.mx_EventTile_selected.mx_EventTile_info .mx_EventTile_line { padding-left: calc($left-gutter + 18px - $hover-select-border); } @@ -276,10 +267,19 @@ $hover-select-border: 4px; .mx_ReactionsRow { margin: 0; - padding: 6px 60px; + padding: 4px 64px; } } +.mx_EventTile:not([data-layout=bubble]).mx_EventTile_info .mx_EventTile_line, +.mx_EventListSummary:not([data-layout=bubble]) > :not(.mx_EventTile) .mx_EventTile_avatar ~ .mx_EventTile_line { + padding-left: calc($left-gutter + 18px); +} + +.mx_EventListSummary:not([data-layout=bubble]) .mx_EventTile_line { + padding-left: calc($left-gutter); +} + /* all the overflow-y: hidden; are to trap Zalgos - but they introduce an implicit overflow-x: auto. so make that explicitly hidden too to avoid random diff --git a/res/css/views/rooms/_MessageComposer.scss b/res/css/views/rooms/_MessageComposer.scss index e6c0cc3f46..5e2eff4047 100644 --- a/res/css/views/rooms/_MessageComposer.scss +++ b/res/css/views/rooms/_MessageComposer.scss @@ -165,8 +165,6 @@ limitations under the License. font-size: $font-14px; max-height: 120px; overflow: auto; - /* needed for FF */ - font-family: $font-family; } /* hack for FF as vertical alignment of custom placeholder text is broken */ diff --git a/res/css/views/settings/tabs/_SettingsTab.scss b/res/css/views/settings/tabs/_SettingsTab.scss index 0d679af4e5..8e6b99871c 100644 --- a/res/css/views/settings/tabs/_SettingsTab.scss +++ b/res/css/views/settings/tabs/_SettingsTab.scss @@ -36,7 +36,6 @@ limitations under the License. .mx_SettingsTab_subheading { font-size: $font-16px; display: block; - font-family: $font-family; font-weight: 600; color: $primary-fg-color; margin-bottom: 10px; diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 59298ef8e6..104e2993d8 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -279,7 +279,7 @@ limitations under the License. max-width: 240px; } -.mx_CallView_header_phoneIcon { +.mx_CallView_header_callTypeIcon { display: inline-block; margin-right: 6px; height: 16px; @@ -293,12 +293,19 @@ limitations under the License. height: 16px; width: 16px; - background-color: $warning-color; + background-color: $secondary-fg-color; mask-repeat: no-repeat; mask-size: contain; mask-position: center; + } + + &.mx_CallView_header_callTypeIcon_voice::before { mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); } + + &.mx_CallView_header_callTypeIcon_video::before { + mask-image: url('$(res)/img/element-icons/call/video-call.svg'); + } } .mx_CallView_callControls { @@ -306,7 +313,6 @@ limitations under the License. display: flex; justify-content: center; bottom: 5px; - width: 100%; opacity: 1; transition: opacity 0.5s; z-index: 200; // To be above _all_ feeds diff --git a/src/components/views/dialogs/CreateRoomDialog.tsx b/src/components/views/dialogs/CreateRoomDialog.tsx index a06f508908..572212a96c 100644 --- a/src/components/views/dialogs/CreateRoomDialog.tsx +++ b/src/components/views/dialogs/CreateRoomDialog.tsx @@ -250,7 +250,7 @@ export default class CreateRoomDialog extends React.Component {   { _t("You can change this at any time from room settings.") }

; - } else if (this.state.joinRule === JoinRule.Public) { + } else if (this.state.joinRule === JoinRule.Public && this.props.parentSpace) { publicPrivateLabel =

{ _t( "Anyone will be able to find and join this room, not just members of .", {}, { @@ -260,6 +260,12 @@ export default class CreateRoomDialog extends React.Component {   { _t("You can change this at any time from room settings.") }

; + } else if (this.state.joinRule === JoinRule.Public) { + publicPrivateLabel =

+ { _t("Anyone will be able to find and join this room.") } +   + { _t("You can change this at any time from room settings.") } +

; } else if (this.state.joinRule === JoinRule.Invite) { publicPrivateLabel =

{ _t( diff --git a/src/components/views/rooms/ReplyTile.tsx b/src/components/views/rooms/ReplyTile.tsx index 18b30d33d5..16a7141bd7 100644 --- a/src/components/views/rooms/ReplyTile.tsx +++ b/src/components/views/rooms/ReplyTile.tsx @@ -67,15 +67,21 @@ export default class ReplyTile extends React.PureComponent { }; private onClick = (e: React.MouseEvent): void => { - // This allows the permalink to be opened in a new tab/window or copied as - // matrix.to, but also for it to enable routing within Riot when clicked. - e.preventDefault(); - dis.dispatch({ - action: 'view_room', - event_id: this.props.mxEvent.getId(), - highlighted: true, - room_id: this.props.mxEvent.getRoomId(), - }); + const clickTarget = e.target as HTMLElement; + // Following a link within a reply should not dispatch the `view_room` action + // so that the browser can direct the user to the correct location + // The exception being the link wrapping the reply + if (clickTarget.tagName.toLowerCase() !== "a" || clickTarget.closest("a") === null) { + // This allows the permalink to be opened in a new tab/window or copied as + // matrix.to, but also for it to enable routing within Riot when clicked. + e.preventDefault(); + dis.dispatch({ + action: 'view_room', + event_id: this.props.mxEvent.getId(), + highlighted: true, + room_id: this.props.mxEvent.getRoomId(), + }); + } }; render() { diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index a98c42526e..481d7b303c 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -67,6 +67,7 @@ interface IState { screensharing: boolean; callState: CallState; controlsVisible: boolean; + hoveringControls: boolean; showMoreMenu: boolean; showDialpad: boolean; primaryFeed: CallFeed; @@ -102,7 +103,7 @@ function exitFullscreen() { if (exitMethod) exitMethod.call(document); } -const CONTROLS_HIDE_DELAY = 1000; +const CONTROLS_HIDE_DELAY = 2000; // Height of the header duplicated from CSS because we need to subtract it from our max // height to get the max height of the video const CONTEXT_MENU_VPADDING = 8; // How far the context menu sits above the button (px) @@ -128,6 +129,7 @@ export default class CallView extends React.Component { screensharing: this.props.call.isScreensharing(), callState: this.props.call.state, controlsVisible: true, + hoveringControls: false, showMoreMenu: false, showDialpad: false, primaryFeed: primary, @@ -244,6 +246,7 @@ export default class CallView extends React.Component { }; private onControlsHideTimer = () => { + if (this.state.hoveringControls || this.state.showDialpad || this.state.showMoreMenu) return; this.controlsHideTimer = null; this.setState({ controlsVisible: false, @@ -293,24 +296,10 @@ export default class CallView extends React.Component { private onDialpadClick = (): void => { if (!this.state.showDialpad) { - if (this.controlsHideTimer) { - clearTimeout(this.controlsHideTimer); - this.controlsHideTimer = null; - } - - this.setState({ - showDialpad: true, - controlsVisible: true, - }); + this.setState({ showDialpad: true }); + this.showControls(); } else { - if (this.controlsHideTimer !== null) { - clearTimeout(this.controlsHideTimer); - } - this.controlsHideTimer = window.setTimeout(this.onControlsHideTimer, CONTROLS_HIDE_DELAY); - - this.setState({ - showDialpad: false, - }); + this.setState({ showDialpad: false }); } }; @@ -345,29 +334,16 @@ export default class CallView extends React.Component { }; private onMoreClick = (): void => { - if (this.controlsHideTimer) { - clearTimeout(this.controlsHideTimer); - this.controlsHideTimer = null; - } - - this.setState({ - showMoreMenu: true, - controlsVisible: true, - }); + this.setState({ showMoreMenu: true }); + this.showControls(); }; private closeDialpad = (): void => { - this.setState({ - showDialpad: false, - }); - this.controlsHideTimer = window.setTimeout(this.onControlsHideTimer, CONTROLS_HIDE_DELAY); + this.setState({ showDialpad: false }); }; private closeContextMenu = (): void => { - this.setState({ - showMoreMenu: false, - }); - this.controlsHideTimer = window.setTimeout(this.onControlsHideTimer, CONTROLS_HIDE_DELAY); + this.setState({ showMoreMenu: false }); }; // we register global shortcuts here, they *must not conflict* with local shortcuts elsewhere or both will fire @@ -403,6 +379,15 @@ export default class CallView extends React.Component { } }; + private onCallControlsMouseEnter = (): void => { + this.setState({ hoveringControls: true }); + this.showControls(); + }; + + private onCallControlsMouseLeave = (): void => { + this.setState({ hoveringControls: false }); + }; + private onRoomAvatarClick = (): void => { const userFacingRoomId = CallHandler.sharedInstance().roomIdForCall(this.props.call); dis.dispatch({ @@ -537,8 +522,6 @@ export default class CallView extends React.Component { } // The dial pad & 'more' button actions are only relevant in a connected call - // When not connected, we have to put something there to make the flexbox alignment correct - let dialpadButton; let contextMenuButton; if (this.state.callState === CallState.Connected) { contextMenuButton = ( @@ -549,6 +532,9 @@ export default class CallView extends React.Component { isExpanded={this.state.showMoreMenu} /> ); + } + let dialpadButton; + if (this.state.callState === CallState.Connected && this.props.call.opponentSupportsDTMF()) { dialpadButton = ( { } return ( -

+
{ dialpadButton } { { expandButton }
; + const callTypeIconClassName = classNames("mx_CallView_header_callTypeIcon", { + "mx_CallView_header_callTypeIcon_voice": !isVideoCall, + "mx_CallView_header_callTypeIcon_video": isVideoCall, + }); + let header: React.ReactNode; if (!this.props.pipMode) { header =
-
+
{ callTypeText } { headerControls }
; diff --git a/src/customisations/WidgetVariables.ts b/src/customisations/WidgetVariables.ts new file mode 100644 index 0000000000..db3a56436d --- /dev/null +++ b/src/customisations/WidgetVariables.ts @@ -0,0 +1,53 @@ +/* + * Copyright 2021 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. + */ + +// Populate this class with the details of your customisations when copying it. +import { ITemplateParams } from "matrix-widget-api"; + +/** + * Provides a partial set of the variables needed to render any widget. If + * variables are missing or not provided then they will be filled with the + * application-determined defaults. + * + * This will not be called until after isReady() resolves. + * @returns {Partial>} The variables. + */ +function provideVariables(): Partial> { + return {}; +} + +/** + * Resolves to whether or not the customisation point is ready for variables + * to be provided. This will block widgets being rendered. + * @returns {Promise} Resolves when ready. + */ +async function isReady(): Promise { + return; // default no waiting +} + +// This interface summarises all available customisation points and also marks +// them all as optional. This allows customisers to only define and export the +// customisations they need while still maintaining type safety. +export interface IWidgetVariablesCustomisations { + provideVariables?: typeof provideVariables; + + // If not provided, the app will assume that the customisation is always ready. + isReady?: typeof isReady; +} + +// A real customisation module will define and export one or more of the +// customisation points that make up the interface above. +export const WidgetVariableCustomisations: IWidgetVariablesCustomisations = {}; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 0ca7b9da9e..f1c033dbea 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -797,9 +797,6 @@ "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.": "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.", "Your feedback will help make spaces better. The more detail you can go into, the better.": "Your feedback will help make spaces better. The more detail you can go into, the better.", "Show all rooms in Home": "Show all rooms in Home", - "Show people in spaces": "Show people in spaces", - "If disabled, you can still add Direct Messages to Personal Spaces. If enabled, you'll automatically see everyone who is a member of the Space.": "If disabled, you can still add Direct Messages to Personal Spaces. If enabled, you'll automatically see everyone who is a member of the Space.", - "Show notification badges for People in Spaces": "Show notification badges for People in Spaces", "Show options to enable 'Do not disturb' mode": "Show options to enable 'Do not disturb' mode", "Send and receive voice messages": "Send and receive voice messages", "Render LaTeX maths in messages": "Render LaTeX maths in messages", @@ -2200,6 +2197,7 @@ "Everyone in will be able to find and join this room.": "Everyone in will be able to find and join this room.", "You can change this at any time from room settings.": "You can change this at any time from room settings.", "Anyone will be able to find and join this room, not just members of .": "Anyone will be able to find and join this room, not just members of .", + "Anyone will be able to find and join this room.": "Anyone will be able to find and join this room.", "Only people invited will be able to find and join this room.": "Only people invited will be able to find and join this room.", "You can’t disable this later. Bridges & most bots won’t work yet.": "You can’t disable this later. Bridges & most bots won’t work yet.", "Your server requires encryption to be enabled in private rooms.": "Your server requires encryption to be enabled in private rooms.", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index f0bdb2e0e5..5aa49df8a1 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -181,8 +181,6 @@ export const SETTINGS: {[setting: string]: ISetting} = { feedbackLabel: "spaces-feedback", extraSettings: [ "feature_spaces.all_rooms", - "feature_spaces.space_member_dms", - "feature_spaces.space_dm_badges", ], }, }, @@ -192,20 +190,6 @@ export const SETTINGS: {[setting: string]: ISetting} = { default: true, controller: new ReloadOnChangeController(), }, - "feature_spaces.space_member_dms": { - displayName: _td("Show people in spaces"), - description: _td("If disabled, you can still add Direct Messages to Personal Spaces. " + - "If enabled, you'll automatically see everyone who is a member of the Space."), - supportedLevels: LEVELS_FEATURE, - default: true, - controller: new ReloadOnChangeController(), - }, - "feature_spaces.space_dm_badges": { - displayName: _td("Show notification badges for People in Spaces"), - supportedLevels: LEVELS_FEATURE, - default: false, - controller: new ReloadOnChangeController(), - }, "feature_dnd": { isFeature: true, displayName: _td("Show options to enable 'Do not disturb' mode"), diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx index a338e71838..d064b01257 100644 --- a/src/stores/SpaceStore.tsx +++ b/src/stores/SpaceStore.tsx @@ -72,8 +72,6 @@ const MAX_SUGGESTED_ROOMS = 20; // All of these settings cause the page to reload and can be costly if read frequently, so read them here only const spacesEnabled = SettingsStore.getValue("feature_spaces"); const spacesTweakAllRoomsEnabled = SettingsStore.getValue("feature_spaces.all_rooms"); -const spacesTweakSpaceMemberDMsEnabled = SettingsStore.getValue("feature_spaces.space_member_dms"); -const spacesTweakSpaceDMBadgesEnabled = SettingsStore.getValue("feature_spaces.space_dm_badges"); const homeSpaceKey = spacesTweakAllRoomsEnabled ? "ALL_ROOMS" : "HOME_SPACE"; const getSpaceContextKey = (space?: Room) => `mx_space_context_${space?.roomId || homeSpaceKey}`; @@ -535,15 +533,13 @@ export class SpaceStoreClass extends AsyncStoreWithClient { const roomIds = new Set(childRooms.map(r => r.roomId)); const space = this.matrixClient?.getRoom(spaceId); - if (spacesTweakSpaceMemberDMsEnabled) { - // Add relevant DMs - space?.getMembers().forEach(member => { - if (member.membership !== "join" && member.membership !== "invite") return; - DMRoomMap.shared().getDMRoomsForUserId(member.userId).forEach(roomId => { - roomIds.add(roomId); - }); + // Add relevant DMs + space?.getMembers().forEach(member => { + if (member.membership !== "join" && member.membership !== "invite") return; + DMRoomMap.shared().getDMRoomsForUserId(member.userId).forEach(roomId => { + roomIds.add(roomId); }); - } + }); const newPath = new Set(parentPath).add(spaceId); childSpaces.forEach(childSpace => { @@ -568,14 +564,13 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this.spaceFilteredRooms.forEach((roomIds, s) => { // Update NotificationStates this.getNotificationState(s)?.setRooms(visibleRooms.filter(room => { - if (roomIds.has(room.roomId)) { - if (s !== HOME_SPACE && spacesTweakSpaceDMBadgesEnabled) return true; + if (!roomIds.has(room.roomId)) return false; - return !DMRoomMap.shared().getUserIdForRoomId(room.roomId) - || RoomListStore.instance.getTagsForRoom(room).includes(DefaultTagID.Favourite); + if (DMRoomMap.shared().getUserIdForRoomId(room.roomId)) { + return s === HOME_SPACE; } - return false; + return true; })); }); }, 100, { trailing: true, leading: true }); @@ -878,8 +873,6 @@ export class SpaceStoreClass extends AsyncStoreWithClient { export default class SpaceStore { public static spacesEnabled = spacesEnabled; public static spacesTweakAllRoomsEnabled = spacesTweakAllRoomsEnabled; - public static spacesTweakSpaceMemberDMsEnabled = spacesTweakSpaceMemberDMsEnabled; - public static spacesTweakSpaceDMBadgesEnabled = spacesTweakSpaceDMBadgesEnabled; private static internalInstance = new SpaceStoreClass(); diff --git a/src/stores/notifications/SpaceNotificationState.ts b/src/stores/notifications/SpaceNotificationState.ts index 4c0a582f3f..f8eb07251b 100644 --- a/src/stores/notifications/SpaceNotificationState.ts +++ b/src/stores/notifications/SpaceNotificationState.ts @@ -23,7 +23,7 @@ import { NOTIFICATION_STATE_UPDATE, NotificationState } from "./NotificationStat import { FetchRoomFn } from "./ListNotificationState"; export class SpaceNotificationState extends NotificationState { - private rooms: Room[] = []; + public rooms: Room[] = []; // exposed only for tests private states: { [spaceId: string]: RoomNotificationState } = {}; constructor(private spaceId: string | symbol, private getRoomFn: FetchRoomFn) { diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts index 24869b5edc..daa1e0e787 100644 --- a/src/stores/widgets/StopGapWidget.ts +++ b/src/stores/widgets/StopGapWidget.ts @@ -54,6 +54,7 @@ import { ElementWidgetCapabilities } from "./ElementWidgetCapabilities"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { ELEMENT_CLIENT_ID } from "../../identifiers"; import { getUserLanguage } from "../../languageHandler"; +import { WidgetVariableCustomisations } from "../../customisations/WidgetVariables"; // TODO: Destroy all of this code @@ -191,7 +192,8 @@ export class StopGapWidget extends EventEmitter { } private runUrlTemplate(opts = { asPopout: false }): string { - const templated = this.mockWidget.getCompleteUrl({ + const fromCustomisation = WidgetVariableCustomisations?.provideVariables?.() ?? {}; + const defaults: ITemplateParams = { widgetRoomId: this.roomId, currentUserId: MatrixClientPeg.get().getUserId(), userDisplayName: OwnProfileStore.instance.displayName, @@ -199,7 +201,8 @@ export class StopGapWidget extends EventEmitter { clientId: ELEMENT_CLIENT_ID, clientTheme: SettingsStore.getValue("theme"), clientLanguage: getUserLanguage(), - }, opts?.asPopout); + }; + const templated = this.mockWidget.getCompleteUrl(Object.assign(defaults, fromCustomisation), opts?.asPopout); const parsed = new URL(templated); @@ -363,6 +366,9 @@ export class StopGapWidget extends EventEmitter { } public async prepare(): Promise { + // Ensure the variables are ready for us to be rendered before continuing + await (WidgetVariableCustomisations?.isReady?.() ?? Promise.resolve()); + if (this.scalarToken) return; const existingMessaging = WidgetMessagingStore.instance.getMessaging(this.mockWidget); if (existingMessaging) this.messaging = existingMessaging; diff --git a/test/stores/SpaceStore-setup.ts b/test/stores/SpaceStore-setup.ts index 67d492255f..b9b865e89a 100644 --- a/test/stores/SpaceStore-setup.ts +++ b/test/stores/SpaceStore-setup.ts @@ -19,5 +19,3 @@ limitations under the License. localStorage.setItem("mx_labs_feature_feature_spaces", "true"); localStorage.setItem("mx_labs_feature_feature_spaces.all_rooms", "true"); -localStorage.setItem("mx_labs_feature_feature_spaces.space_member_dms", "true"); -localStorage.setItem("mx_labs_feature_feature_spaces.space_dm_badges", "false"); diff --git a/test/stores/SpaceStore-test.ts b/test/stores/SpaceStore-test.ts index 8855f4e470..d772a7a658 100644 --- a/test/stores/SpaceStore-test.ts +++ b/test/stores/SpaceStore-test.ts @@ -17,6 +17,7 @@ limitations under the License. import { EventEmitter } from "events"; import { EventType } from "matrix-js-sdk/src/@types/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import "./SpaceStore-setup"; // enable space lab import "../skinned-sdk"; // Must be first for skinning to work @@ -53,18 +54,22 @@ const emitPromise = (e: EventEmitter, k: string | symbol) => new Promise(r => e. const testUserId = "@test:user"; const getUserIdForRoomId = jest.fn(); +const getDMRoomsForUserId = jest.fn(); // @ts-ignore -DMRoomMap.sharedInstance = { getUserIdForRoomId }; +DMRoomMap.sharedInstance = { getUserIdForRoomId, getDMRoomsForUserId }; const fav1 = "!fav1:server"; const fav2 = "!fav2:server"; const fav3 = "!fav3:server"; const dm1 = "!dm1:server"; -const dm1Partner = "@dm1Partner:server"; +const dm1Partner = new RoomMember(dm1, "@dm1Partner:server"); +dm1Partner.membership = "join"; const dm2 = "!dm2:server"; -const dm2Partner = "@dm2Partner:server"; +const dm2Partner = new RoomMember(dm2, "@dm2Partner:server"); +dm2Partner.membership = "join"; const dm3 = "!dm3:server"; -const dm3Partner = "@dm3Partner:server"; +const dm3Partner = new RoomMember(dm3, "@dm3Partner:server"); +dm3Partner.membership = "join"; const orphan1 = "!orphan1:server"; const orphan2 = "!orphan2:server"; const invite1 = "!invite1:server"; @@ -320,11 +325,40 @@ describe("SpaceStore", () => { getUserIdForRoomId.mockImplementation(roomId => { return { - [dm1]: dm1Partner, - [dm2]: dm2Partner, - [dm3]: dm3Partner, + [dm1]: dm1Partner.userId, + [dm2]: dm2Partner.userId, + [dm3]: dm3Partner.userId, }[roomId]; }); + getDMRoomsForUserId.mockImplementation(userId => { + switch (userId) { + case dm1Partner.userId: + return [dm1]; + case dm2Partner.userId: + return [dm2]; + case dm3Partner.userId: + return [dm3]; + default: + return []; + } + }); + + // have dmPartner1 be in space1 with you + const mySpace1Member = new RoomMember(space1, testUserId); + mySpace1Member.membership = "join"; + (rooms.find(r => r.roomId === space1).getMembers as jest.Mock).mockReturnValue([ + mySpace1Member, + dm1Partner, + ]); + // have dmPartner2 be in space2 with you + const mySpace2Member = new RoomMember(space2, testUserId); + mySpace2Member.membership = "join"; + (rooms.find(r => r.roomId === space2).getMembers as jest.Mock).mockReturnValue([ + mySpace2Member, + dm2Partner, + ]); + // dmPartner3 is not in any common spaces with you + await run(); }); @@ -375,6 +409,66 @@ describe("SpaceStore", () => { const space = client.getRoom(space3); expect(store.getSpaceFilteredRoomIds(space).has(invite2)).toBeTruthy(); }); + + it("spaces contain dms which you have with members of that space", () => { + expect(store.getSpaceFilteredRoomIds(client.getRoom(space1)).has(dm1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(client.getRoom(space2)).has(dm1)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(client.getRoom(space3)).has(dm1)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(client.getRoom(space1)).has(dm2)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(client.getRoom(space2)).has(dm2)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(client.getRoom(space3)).has(dm2)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(client.getRoom(space1)).has(dm3)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(client.getRoom(space2)).has(dm3)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(client.getRoom(space3)).has(dm3)).toBeFalsy(); + }); + + it("dms are only added to Notification States for only the Home Space", () => { + // XXX: All rooms space is forcibly enabled, as part of a future PR test Home space better + // [dm1, dm2, dm3].forEach(d => { + // expect(store.getNotificationState(HOME_SPACE).rooms.map(r => r.roomId).includes(d)).toBeTruthy(); + // }); + [space1, space2, space3].forEach(s => { + [dm1, dm2, dm3].forEach(d => { + expect(store.getNotificationState(s).rooms.map(r => r.roomId).includes(d)).toBeFalsy(); + }); + }); + }); + + it("orphan rooms are added to Notification States for only the Home Space", () => { + // XXX: All rooms space is forcibly enabled, as part of a future PR test Home space better + // [orphan1, orphan2].forEach(d => { + // expect(store.getNotificationState(HOME_SPACE).rooms.map(r => r.roomId).includes(d)).toBeTruthy(); + // }); + [space1, space2, space3].forEach(s => { + [orphan1, orphan2].forEach(d => { + expect(store.getNotificationState(s).rooms.map(r => r.roomId).includes(d)).toBeFalsy(); + }); + }); + }); + + it("favourites are added to Notification States for all spaces containing the room inc Home", () => { + // XXX: All rooms space is forcibly enabled, as part of a future PR test Home space better + // [fav1, fav2, fav3].forEach(d => { + // expect(store.getNotificationState(HOME_SPACE).rooms.map(r => r.roomId).includes(d)).toBeTruthy(); + // }); + expect(store.getNotificationState(space1).rooms.map(r => r.roomId).includes(fav1)).toBeTruthy(); + expect(store.getNotificationState(space1).rooms.map(r => r.roomId).includes(fav2)).toBeFalsy(); + expect(store.getNotificationState(space1).rooms.map(r => r.roomId).includes(fav3)).toBeFalsy(); + expect(store.getNotificationState(space2).rooms.map(r => r.roomId).includes(fav1)).toBeTruthy(); + expect(store.getNotificationState(space2).rooms.map(r => r.roomId).includes(fav2)).toBeTruthy(); + expect(store.getNotificationState(space2).rooms.map(r => r.roomId).includes(fav3)).toBeTruthy(); + expect(store.getNotificationState(space3).rooms.map(r => r.roomId).includes(fav1)).toBeFalsy(); + expect(store.getNotificationState(space3).rooms.map(r => r.roomId).includes(fav2)).toBeFalsy(); + expect(store.getNotificationState(space3).rooms.map(r => r.roomId).includes(fav3)).toBeFalsy(); + }); + + it("other rooms are added to Notification States for all spaces containing the room exc Home", () => { + // XXX: All rooms space is forcibly enabled, as part of a future PR test Home space better + // expect(store.getNotificationState(HOME_SPACE).rooms.map(r => r.roomId).includes(room1)).toBeFalsy(); + expect(store.getNotificationState(space1).rooms.map(r => r.roomId).includes(room1)).toBeTruthy(); + expect(store.getNotificationState(space2).rooms.map(r => r.roomId).includes(room1)).toBeTruthy(); + expect(store.getNotificationState(space3).rooms.map(r => r.roomId).includes(room1)).toBeFalsy(); + }); }); });