diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx
index aad770888b..a91c47b067 100644
--- a/src/components/structures/SpaceRoomView.tsx
+++ b/src/components/structures/SpaceRoomView.tsx
@@ -14,58 +14,60 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React, {RefObject, useContext, useRef, useState} from "react";
-import {EventType} from "matrix-js-sdk/src/@types/event";
-import {Room} from "matrix-js-sdk/src/models/room";
-import {EventSubscription} from "fbemitter";
+import React, { RefObject, useContext, useRef, useState } from "react";
+import { EventType } from "matrix-js-sdk/src/@types/event";
+import { Preset } from "matrix-js-sdk/src/@types/partials";
+import { Room } from "matrix-js-sdk/src/models/room";
+import { EventSubscription } from "fbemitter";
import MatrixClientContext from "../../contexts/MatrixClientContext";
import RoomAvatar from "../views/avatars/RoomAvatar";
-import {_t} from "../../languageHandler";
+import { _t } from "../../languageHandler";
import AccessibleButton from "../views/elements/AccessibleButton";
import RoomName from "../views/elements/RoomName";
import RoomTopic from "../views/elements/RoomTopic";
import InlineSpinner from "../views/elements/InlineSpinner";
-import {inviteMultipleToRoom, showRoomInviteDialog} from "../../RoomInvite";
-import {useRoomMembers} from "../../hooks/useRoomMembers";
-import createRoom, {IOpts} from "../../createRoom";
+import { inviteMultipleToRoom, showRoomInviteDialog } from "../../RoomInvite";
+import { useRoomMembers } from "../../hooks/useRoomMembers";
+import createRoom, { IOpts } from "../../createRoom";
import Field from "../views/elements/Field";
-import {useEventEmitter} from "../../hooks/useEventEmitter";
+import { useEventEmitter } from "../../hooks/useEventEmitter";
import withValidation from "../views/elements/Validation";
import * as Email from "../../email";
import defaultDispatcher from "../../dispatcher/dispatcher";
-import {Action} from "../../dispatcher/actions";
+import dis from "../../dispatcher/dispatcher";
+import { Action } from "../../dispatcher/actions";
import ResizeNotifier from "../../utils/ResizeNotifier"
import MainSplit from './MainSplit';
import ErrorBoundary from "../views/elements/ErrorBoundary";
-import {ActionPayload} from "../../dispatcher/payloads";
+import { ActionPayload } from "../../dispatcher/payloads";
import RightPanel from "./RightPanel";
import RightPanelStore from "../../stores/RightPanelStore";
-import {RightPanelPhases} from "../../stores/RightPanelStorePhases";
-import {SetRightPanelPhasePayload} from "../../dispatcher/payloads/SetRightPanelPhasePayload";
-import {useStateArray} from "../../hooks/useStateArray";
+import { RightPanelPhases } from "../../stores/RightPanelStorePhases";
+import { SetRightPanelPhasePayload } from "../../dispatcher/payloads/SetRightPanelPhasePayload";
+import { useStateArray } from "../../hooks/useStateArray";
import SpacePublicShare from "../views/spaces/SpacePublicShare";
-import {showAddExistingRooms, showCreateNewRoom, shouldShowSpaceSettings, showSpaceSettings} from "../../utils/space";
-import {showRoom, SpaceHierarchy} from "./SpaceRoomDirectory";
+import { shouldShowSpaceSettings, showAddExistingRooms, showCreateNewRoom, showSpaceSettings } from "../../utils/space";
+import { showRoom, SpaceHierarchy } from "./SpaceRoomDirectory";
import MemberAvatar from "../views/avatars/MemberAvatar";
-import {useStateToggle} from "../../hooks/useStateToggle";
+import { useStateToggle } from "../../hooks/useStateToggle";
import SpaceStore from "../../stores/SpaceStore";
import FacePile from "../views/elements/FacePile";
-import {AddExistingToSpace} from "../views/dialogs/AddExistingToSpaceDialog";
-import {ChevronFace, ContextMenuButton, useContextMenu} from "./ContextMenu";
+import { AddExistingToSpace } from "../views/dialogs/AddExistingToSpaceDialog";
+import { ChevronFace, ContextMenuButton, useContextMenu } from "./ContextMenu";
import IconizedContextMenu, {
IconizedContextMenuOption,
IconizedContextMenuOptionList,
} from "../views/context_menus/IconizedContextMenu";
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
-import {BetaPill} from "../views/beta/BetaCard";
+import { BetaPill } from "../views/beta/BetaCard";
import { UserTab } from "../views/dialogs/UserSettingsDialog";
import SettingsStore from "../../settings/SettingsStore";
-import dis from "../../dispatcher/dispatcher";
import Modal from "../../Modal";
import BetaFeedbackDialog from "../views/dialogs/BetaFeedbackDialog";
import SdkConfig from "../../SdkConfig";
-import { Preset } from "matrix-js-sdk/src/@types/partials";
+import { EffectiveMembership, getEffectiveMembership } from "../../utils/membership";
+import { JoinRule } from "../views/settings/tabs/room/SecurityRoomSettingsTab";
interface IProps {
space: Room;
@@ -178,6 +180,9 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
const spacesEnabled = SettingsStore.getValue("feature_spaces");
+ const cannotJoin = getEffectiveMembership(myMembership) === EffectiveMembership.Leave
+ && space.getJoinRule() !== JoinRule.Public;
+
let inviterSection;
let joinButtons;
if (myMembership === "join") {
@@ -244,7 +249,7 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
setBusy(true);
onJoinButtonClicked();
}}
- disabled={!spacesEnabled}
+ disabled={!spacesEnabled || cannotJoin}
>
{ _t("Join") }
@@ -255,6 +260,30 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
joinButtons =
;
}
+ let footer;
+ if (!spacesEnabled) {
+ footer =
+ { myMembership === "join"
+ ? _t("To view %(spaceName)s, turn on the
Spaces beta", {
+ spaceName: space.name,
+ }, {
+ a: sub =>
{ sub },
+ })
+ : _t("To join %(spaceName)s, turn on the
Spaces beta", {
+ spaceName: space.name,
+ }, {
+ a: sub =>
{ sub },
+ })
+ }
+
;
+ } else if (cannotJoin) {
+ footer =
+ { _t("To view %(spaceName)s, you need an invite", {
+ spaceName: space.name,
+ }) }
+
;
+ }
+
return
{ inviterSection }
@@ -274,20 +303,7 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
{ joinButtons }
- { !spacesEnabled &&
- { myMembership === "join"
- ? _t("To view %(spaceName)s, turn on the
Spaces beta", {
- spaceName: space.name,
- }, {
- a: sub =>
{ sub },
- })
- : _t("To join %(spaceName)s, turn on the
Spaces beta", {
- spaceName: space.name,
- }, {
- a: sub =>
{ sub },
- })
- }
-
}
+ { footer }
;
};
diff --git a/src/components/views/messages/TextualEvent.js b/src/components/views/messages/TextualEvent.js
index a020cc6c52..25da18da0d 100644
--- a/src/components/views/messages/TextualEvent.js
+++ b/src/components/views/messages/TextualEvent.js
@@ -28,7 +28,7 @@ export default class TextualEvent extends React.Component {
};
render() {
- const text = TextForEvent.textForEvent(this.props.mxEvent);
+ const text = TextForEvent.textForEvent(this.props.mxEvent, true);
if (text == null || text.length === 0) return null;
return (
{ text }
diff --git a/src/components/views/rooms/PresenceLabel.tsx b/src/components/views/rooms/PresenceLabel.tsx
index abedb394b5..be324a8437 100644
--- a/src/components/views/rooms/PresenceLabel.tsx
+++ b/src/components/views/rooms/PresenceLabel.tsx
@@ -41,11 +41,11 @@ export default class PresenceLabel extends React.Component
{
// XXX: This would be better handled using a culture-aware library, but we don't use one yet.
private getDuration(time: number): string {
if (!time) return;
- const t = time / 1000;
+ const t = Math.round(time / 1000);
const s = t % 60;
- const m = t / 60 % 60;
- const h = t / (60 * 60) % 24;
- const d = t / (60 * 60 * 24);
+ const m = Math.round(t / 60) % 60;
+ const h = Math.round(t / (60 * 60)) % 24;
+ const d = Math.round(t / (60 * 60 * 24));
if (t < 60) {
if (t < 0) {
return _t("%(duration)ss", { duration: 0 });
diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx
index 0b79f7b52e..eb50224a60 100644
--- a/src/components/views/rooms/RoomList.tsx
+++ b/src/components/views/rooms/RoomList.tsx
@@ -45,7 +45,6 @@ import { objectShallowClone, objectWithOnly } from "../../../utils/objects";
import { IconizedContextMenuOption, IconizedContextMenuOptionList } from "../context_menus/IconizedContextMenu";
import AccessibleButton from "../elements/AccessibleButton";
import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore";
-import CallHandler from "../../../CallHandler";
import SpaceStore, {ISuggestedRoom, SUGGESTED_ROOMS} from "../../../stores/SpaceStore";
import {showAddExistingRooms, showCreateNewRoom, showSpaceInvite} from "../../../utils/space";
import {replaceableComponent} from "../../../utils/replaceableComponent";
@@ -103,38 +102,6 @@ interface ITagAestheticsMap {
[tagId: TagID]: ITagAesthetics;
}
-// If we have no dialer support, we just show the create chat dialog
-const dmOnAddRoom = (dispatcher?: Dispatcher) => {
- (dispatcher || defaultDispatcher).dispatch({action: 'view_create_chat'});
-};
-
-// If we have dialer support, show a context menu so the user can pick between
-// the dialer and the create chat dialog
-const dmAddRoomContextMenu = (onFinished: () => void) => {
- return
- {
- e.preventDefault();
- e.stopPropagation();
- onFinished();
- defaultDispatcher.dispatch({action: "view_create_chat"});
- }}
- />
- {
- e.preventDefault();
- e.stopPropagation();
- onFinished();
- defaultDispatcher.fire(Action.OpenDialPad);
- }}
- />
- ;
-};
-
const TAG_AESTHETICS: ITagAestheticsMap = {
[DefaultTagID.Invite]: {
sectionLabel: _td("Invites"),
@@ -151,8 +118,9 @@ const TAG_AESTHETICS: ITagAestheticsMap = {
isInvite: false,
defaultHidden: false,
addRoomLabel: _td("Start chat"),
- // Either onAddRoom or addRoomContextMenu are set depending on whether we
- // have dialer support.
+ onAddRoom: (dispatcher?: Dispatcher) => {
+ (dispatcher || defaultDispatcher).dispatch({action: 'view_create_chat'});
+ },
},
[DefaultTagID.Untagged]: {
sectionLabel: _td("Rooms"),
@@ -271,7 +239,6 @@ function customTagAesthetics(tagId: TagID): ITagAesthetics {
export default class RoomList extends React.PureComponent {
private dispatcherRef;
private customTagStoreRef;
- private tagAesthetics: ITagAestheticsMap;
private roomStoreToken: fbEmitter.EventSubscription;
constructor(props: IProps) {
@@ -282,10 +249,6 @@ export default class RoomList extends React.PureComponent {
isNameFiltering: !!RoomListStore.instance.getFirstNameFilterCondition(),
suggestedRooms: SpaceStore.instance.suggestedRooms,
};
-
- // shallow-copy from the template as we need to make modifications to it
- this.tagAesthetics = objectShallowClone(TAG_AESTHETICS);
- this.updateDmAddRoomAction();
}
public componentDidMount(): void {
@@ -311,17 +274,6 @@ export default class RoomList extends React.PureComponent {
});
};
- private updateDmAddRoomAction() {
- const dmTagAesthetics = objectShallowClone(TAG_AESTHETICS[DefaultTagID.DM]);
- if (CallHandler.sharedInstance().getSupportsPstnProtocol()) {
- dmTagAesthetics.addRoomContextMenu = dmAddRoomContextMenu;
- } else {
- dmTagAesthetics.onAddRoom = dmOnAddRoom;
- }
-
- this.tagAesthetics[DefaultTagID.DM] = dmTagAesthetics;
- }
-
private onAction = (payload: ActionPayload) => {
if (payload.action === Action.ViewRoomDelta) {
const viewRoomDeltaPayload = payload as ViewRoomDeltaPayload;
@@ -335,7 +287,6 @@ export default class RoomList extends React.PureComponent {
});
}
} else if (payload.action === Action.PstnSupportUpdated) {
- this.updateDmAddRoomAction();
this.updateLists();
}
};
@@ -524,7 +475,7 @@ export default class RoomList extends React.PureComponent {
const aesthetics: ITagAesthetics = isCustomTag(orderedTagId)
? customTagAesthetics(orderedTagId)
- : this.tagAesthetics[orderedTagId];
+ : TAG_AESTHETICS[orderedTagId];
if (!aesthetics) throw new Error(`Tag ${orderedTagId} does not have aesthetics`);
// The cost of mounting/unmounting this component offsets the cost
diff --git a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
index bb7e194253..16c620ad84 100644
--- a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
+++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
@@ -33,6 +33,9 @@ export enum JoinRule {
Public = "public",
Knock = "knock",
Invite = "invite",
+ /**
+ * @deprecated Reserved. Should not be used.
+ */
Private = "private",
}
diff --git a/src/components/views/spaces/SpaceCreateMenu.tsx b/src/components/views/spaces/SpaceCreateMenu.tsx
index 2d096e1b9f..0a92bd2dbf 100644
--- a/src/components/views/spaces/SpaceCreateMenu.tsx
+++ b/src/components/views/spaces/SpaceCreateMenu.tsx
@@ -129,7 +129,9 @@ const SpaceCreateMenu = ({ onFinished }) => {
events_default: 100,
...Visibility.Public ? { invite: 0 } : {},
},
- room_alias_name: alias ? alias.substr(1, alias.indexOf(":") - 1) : undefined,
+ room_alias_name: visibility === Visibility.Public && alias
+ ? alias.substr(1, alias.indexOf(":") - 1)
+ : undefined,
topic,
},
spinner: false,
diff --git a/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx b/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
index 263823603b..f27b73a511 100644
--- a/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
+++ b/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
@@ -62,9 +62,9 @@ const SpaceSettingsVisibilityTab = ({ matrixClient: cli, space }: IProps) => {
const userId = cli.getUserId();
const [visibility, setVisibility] = useLocalEcho(
- () => space.getJoinRule() === JoinRule.Private ? SpaceVisibility.Private : SpaceVisibility.Unlisted,
+ () => space.getJoinRule() === JoinRule.Invite ? SpaceVisibility.Private : SpaceVisibility.Unlisted,
visibility => cli.sendStateEvent(space.roomId, EventType.RoomJoinRules, {
- join_rule: visibility === SpaceVisibility.Unlisted ? JoinRule.Public : JoinRule.Private,
+ join_rule: visibility === SpaceVisibility.Unlisted ? JoinRule.Public : JoinRule.Invite,
}, ""),
() => setError(_t("Failed to update the visibility of this space")),
);
diff --git a/src/dispatcher/payloads/SetRightPanelPhasePayload.ts b/src/dispatcher/payloads/SetRightPanelPhasePayload.ts
index a4dcb8cfe1..ea997e4ed1 100644
--- a/src/dispatcher/payloads/SetRightPanelPhasePayload.ts
+++ b/src/dispatcher/payloads/SetRightPanelPhasePayload.ts
@@ -27,6 +27,11 @@ export interface SetRightPanelPhasePayload extends ActionPayload {
phase: RightPanelPhases;
refireParams?: SetRightPanelPhaseRefireParams;
+
+ /**
+ * By default SetRightPanelPhase can close the panel, this allows overriding that behaviour
+ */
+ allowClose?: boolean;
}
export interface SetRightPanelPhaseRefireParams {
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 8af51cb580..5e443b1474 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -567,6 +567,7 @@
"%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s made future room history visible to unknown (%(visibility)s).",
"%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s changed the power level of %(powerLevelDiffText)s.",
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s",
+ "%(senderName)s changed the pinned messages for the room.": "%(senderName)s changed the pinned messages for the room.",
"%(senderName)s changed the pinned messages for the room.": "%(senderName)s changed the pinned messages for the room.",
"%(widgetName)s widget modified by %(senderName)s": "%(widgetName)s widget modified by %(senderName)s",
"%(widgetName)s widget added by %(senderName)s": "%(widgetName)s widget added by %(senderName)s",
@@ -1599,8 +1600,6 @@
"Search": "Search",
"Voice call": "Voice call",
"Video call": "Video call",
- "Start a Conversation": "Start a Conversation",
- "Open dial pad": "Open dial pad",
"Invites": "Invites",
"Favourites": "Favourites",
"People": "People",
@@ -2691,6 +2690,7 @@
"Explore Public Rooms": "Explore Public Rooms",
"Create a Group Chat": "Create a Group Chat",
"Upgrade to %(hostSignupBrand)s": "Upgrade to %(hostSignupBrand)s",
+ "Open dial pad": "Open dial pad",
"Failed to reject invitation": "Failed to reject invitation",
"Cannot create rooms in this community": "Cannot create rooms in this community",
"You do not have permission to create rooms in this community.": "You do not have permission to create rooms in this community.",
@@ -2797,6 +2797,7 @@
" invites you": " invites you",
"To view %(spaceName)s, turn on the Spaces beta": "To view %(spaceName)s, turn on the Spaces beta",
"To join %(spaceName)s, turn on the Spaces beta": "To join %(spaceName)s, turn on the Spaces beta",
+ "To view %(spaceName)s, you need an invite": "To view %(spaceName)s, you need an invite",
"Welcome to ": "Welcome to ",
"Random": "Random",
"Support": "Support",
diff --git a/src/stores/RightPanelStore.ts b/src/stores/RightPanelStore.ts
index c539fcdb40..2bad0572b1 100644
--- a/src/stores/RightPanelStore.ts
+++ b/src/stores/RightPanelStore.ts
@@ -161,6 +161,7 @@ export default class RightPanelStore extends Store {
case Action.SetRightPanelPhase: {
let targetPhase = payload.phase;
let refireParams = payload.refireParams;
+ const allowClose = payload.allowClose ?? true;
// redirect to EncryptionPanel if there is an ongoing verification request
if (targetPhase === RightPanelPhases.RoomMemberInfo && payload.refireParams) {
const {member} = payload.refireParams;
@@ -192,7 +193,7 @@ export default class RightPanelStore extends Store {
});
}
} else {
- if (targetPhase === this.state.lastRoomPhase && !refireParams) {
+ if (targetPhase === this.state.lastRoomPhase && !refireParams && allowClose) {
this.setState({
showRoomPanel: !this.state.showRoomPanel,
previousPhase: null,