mirror of
https://github.com/element-hq/element-web
synced 2024-11-26 19:26:04 +03:00
Allow options to cascade kicks/bans throughout spaces
This commit is contained in:
parent
cf8100ad17
commit
1858c63c4a
12 changed files with 481 additions and 177 deletions
|
@ -1,8 +1,10 @@
|
||||||
// autogenerated by rethemendex.sh
|
// autogenerated by rethemendex.sh
|
||||||
|
@import "./_animations.scss";
|
||||||
@import "./_common.scss";
|
@import "./_common.scss";
|
||||||
@import "./_font-sizes.scss";
|
@import "./_font-sizes.scss";
|
||||||
@import "./_font-weights.scss";
|
@import "./_font-weights.scss";
|
||||||
@import "./structures/_AutoHideScrollbar.scss";
|
@import "./structures/_AutoHideScrollbar.scss";
|
||||||
|
@import "./structures/_BackdropPanel.scss";
|
||||||
@import "./structures/_CompatibilityPage.scss";
|
@import "./structures/_CompatibilityPage.scss";
|
||||||
@import "./structures/_ContextualMenu.scss";
|
@import "./structures/_ContextualMenu.scss";
|
||||||
@import "./structures/_CreateRoom.scss";
|
@import "./structures/_CreateRoom.scss";
|
||||||
|
@ -17,7 +19,6 @@
|
||||||
@import "./structures/_LeftPanelWidget.scss";
|
@import "./structures/_LeftPanelWidget.scss";
|
||||||
@import "./structures/_MainSplit.scss";
|
@import "./structures/_MainSplit.scss";
|
||||||
@import "./structures/_MatrixChat.scss";
|
@import "./structures/_MatrixChat.scss";
|
||||||
@import "./structures/_BackdropPanel.scss";
|
|
||||||
@import "./structures/_MyGroups.scss";
|
@import "./structures/_MyGroups.scss";
|
||||||
@import "./structures/_NonUrgentToastContainer.scss";
|
@import "./structures/_NonUrgentToastContainer.scss";
|
||||||
@import "./structures/_NotificationPanel.scss";
|
@import "./structures/_NotificationPanel.scss";
|
||||||
|
@ -72,6 +73,7 @@
|
||||||
@import "./views/dialogs/_ChangelogDialog.scss";
|
@import "./views/dialogs/_ChangelogDialog.scss";
|
||||||
@import "./views/dialogs/_ChatCreateOrReuseChatDialog.scss";
|
@import "./views/dialogs/_ChatCreateOrReuseChatDialog.scss";
|
||||||
@import "./views/dialogs/_CommunityPrototypeInviteDialog.scss";
|
@import "./views/dialogs/_CommunityPrototypeInviteDialog.scss";
|
||||||
|
@import "./views/dialogs/_ConfirmSpaceUserActionDialog.scss";
|
||||||
@import "./views/dialogs/_ConfirmUserActionDialog.scss";
|
@import "./views/dialogs/_ConfirmUserActionDialog.scss";
|
||||||
@import "./views/dialogs/_CreateCommunityPrototypeDialog.scss";
|
@import "./views/dialogs/_CreateCommunityPrototypeDialog.scss";
|
||||||
@import "./views/dialogs/_CreateGroupDialog.scss";
|
@import "./views/dialogs/_CreateGroupDialog.scss";
|
||||||
|
@ -266,6 +268,7 @@
|
||||||
@import "./views/settings/tabs/user/_SecurityUserSettingsTab.scss";
|
@import "./views/settings/tabs/user/_SecurityUserSettingsTab.scss";
|
||||||
@import "./views/settings/tabs/user/_VoiceUserSettingsTab.scss";
|
@import "./views/settings/tabs/user/_VoiceUserSettingsTab.scss";
|
||||||
@import "./views/spaces/_SpaceBasicSettings.scss";
|
@import "./views/spaces/_SpaceBasicSettings.scss";
|
||||||
|
@import "./views/spaces/_SpaceChildrenPicker.scss";
|
||||||
@import "./views/spaces/_SpaceCreateMenu.scss";
|
@import "./views/spaces/_SpaceCreateMenu.scss";
|
||||||
@import "./views/spaces/_SpacePublicShare.scss";
|
@import "./views/spaces/_SpacePublicShare.scss";
|
||||||
@import "./views/terms/_InlineTermsAgreement.scss";
|
@import "./views/terms/_InlineTermsAgreement.scss";
|
||||||
|
|
66
res/css/views/dialogs/_ConfirmSpaceUserActionDialog.scss
Normal file
66
res/css/views/dialogs/_ConfirmSpaceUserActionDialog.scss
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_ConfirmSpaceUserActionDialog_wrapper {
|
||||||
|
.mx_Dialog {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 24px 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ConfirmSpaceUserActionDialog {
|
||||||
|
width: 440px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
height: 520px;
|
||||||
|
|
||||||
|
.mx_Dialog_content {
|
||||||
|
margin: 12px 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ConfirmUserActionDialog_reasonField {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ConfirmSpaceUserActionDialog_warning {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px 8px 12px 42px;
|
||||||
|
background-color: $header-panel-bg-color;
|
||||||
|
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
color: $secondary-content;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 10px;
|
||||||
|
top: calc(50% - 8px); // vertical centering
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
background-color: $secondary-content;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-size: contain;
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/room-summary.svg');
|
||||||
|
mask-position: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_ConfirmUserActionDialog .mx_Dialog_content {
|
.mx_ConfirmUserActionDialog .mx_Dialog_content .mx_ConfirmUserActionDialog_user {
|
||||||
min-height: 48px;
|
min-height: 48px;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,33 +27,13 @@ limitations under the License.
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
max-height: 520px;
|
height: 520px;
|
||||||
|
|
||||||
.mx_Dialog_content {
|
.mx_Dialog_content {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
.mx_RadioButton + .mx_RadioButton {
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SearchBox {
|
|
||||||
// To match the space around the title
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
flex-grow: 0;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_LeaveSpaceDialog_noResults {
|
|
||||||
display: block;
|
|
||||||
margin-top: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_LeaveSpaceDialog_section {
|
|
||||||
margin: 16px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_LeaveSpaceDialog_section_warning {
|
.mx_LeaveSpaceDialog_section_warning {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|
35
res/css/views/spaces/_SpaceChildrenPicker.scss
Normal file
35
res/css/views/spaces/_SpaceChildrenPicker.scss
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_SpaceChildrenPicker {
|
||||||
|
margin: 16px 0;
|
||||||
|
|
||||||
|
.mx_RadioButton + .mx_RadioButton {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SearchBox {
|
||||||
|
// To match the space around the title
|
||||||
|
margin: 0 0 15px 0;
|
||||||
|
flex-grow: 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceChildrenPicker_noResults {
|
||||||
|
display: block;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { ComponentProps, useMemo, useState } from 'react';
|
||||||
|
import ConfirmUserActionDialog from "./ConfirmUserActionDialog";
|
||||||
|
import SpaceStore from "../../../stores/SpaceStore";
|
||||||
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
import SpaceChildrenPicker from "../spaces/SpaceChildrenPicker";
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
|
type BaseProps = ComponentProps<typeof ConfirmUserActionDialog>;
|
||||||
|
interface IProps extends Omit<BaseProps, "groupMember" | "matrixClient" | "children" | "onFinished"> {
|
||||||
|
space: Room;
|
||||||
|
allLabel: string;
|
||||||
|
specificLabel: string;
|
||||||
|
noneLabel?: string;
|
||||||
|
onFinished(success: boolean, reason?: string, rooms?: Room[]): void;
|
||||||
|
spaceChildFilter?(child: Room): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConfirmSpaceUserActionDialog: React.FC<IProps> = ({
|
||||||
|
space,
|
||||||
|
spaceChildFilter,
|
||||||
|
allLabel,
|
||||||
|
specificLabel,
|
||||||
|
noneLabel,
|
||||||
|
onFinished,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const spaceChildren = useMemo(() => {
|
||||||
|
const children = SpaceStore.instance.getChildren(space.roomId);
|
||||||
|
if (spaceChildFilter) {
|
||||||
|
return children.filter(spaceChildFilter);
|
||||||
|
}
|
||||||
|
return children;
|
||||||
|
}, [space.roomId, spaceChildFilter]);
|
||||||
|
|
||||||
|
const [roomsToLeave, setRoomsToLeave] = useState<Room[]>([]);
|
||||||
|
const selectedRooms = useMemo(() => new Set(roomsToLeave), [roomsToLeave]);
|
||||||
|
|
||||||
|
let warning: JSX.Element;
|
||||||
|
if (!spaceChildren.length) {
|
||||||
|
warning = <div className="mx_ConfirmSpaceUserActionDialog_warning">
|
||||||
|
{ _t("You’re not an admin of anything they’re a member of in <SpaceName/>, " +
|
||||||
|
"so banning won’t remove them from any rooms or spaces in <SpaceName/>.", {}, {
|
||||||
|
SpaceName: () => <b>{ space.name }</b>,
|
||||||
|
}) }
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfirmUserActionDialog
|
||||||
|
{...props}
|
||||||
|
onFinished={(success: boolean, reason?: string) => {
|
||||||
|
onFinished(success, reason, roomsToLeave);
|
||||||
|
}}
|
||||||
|
className="mx_ConfirmSpaceUserActionDialog"
|
||||||
|
>
|
||||||
|
{ warning }
|
||||||
|
<SpaceChildrenPicker
|
||||||
|
space={space}
|
||||||
|
spaceChildren={spaceChildren}
|
||||||
|
selected={selectedRooms}
|
||||||
|
allLabel={allLabel}
|
||||||
|
specificLabel={specificLabel}
|
||||||
|
noneLabel={noneLabel}
|
||||||
|
onChange={setRoomsToLeave}
|
||||||
|
/>
|
||||||
|
</ConfirmUserActionDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConfirmSpaceUserActionDialog;
|
|
@ -14,9 +14,11 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { ReactNode } from 'react';
|
||||||
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import { GroupMemberType } from '../../../groups';
|
import { GroupMemberType } from '../../../groups';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
@ -28,9 +30,9 @@ import DialogButtons from "../elements/DialogButtons";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
// matrix-js-sdk (room) member object. Supply either this or 'groupMember'
|
// matrix-js-sdk (room) member object. Supply either this or 'groupMember'
|
||||||
member: RoomMember;
|
member?: RoomMember;
|
||||||
// group member object. Supply either this or 'member'
|
// group member object. Supply either this or 'member'
|
||||||
groupMember: GroupMemberType;
|
groupMember?: GroupMemberType;
|
||||||
// needed if a group member is specified
|
// needed if a group member is specified
|
||||||
matrixClient?: MatrixClient;
|
matrixClient?: MatrixClient;
|
||||||
action: string; // eg. 'Ban'
|
action: string; // eg. 'Ban'
|
||||||
|
@ -41,6 +43,8 @@ interface IProps {
|
||||||
// be the string entered.
|
// be the string entered.
|
||||||
askReason?: boolean;
|
askReason?: boolean;
|
||||||
danger?: boolean;
|
danger?: boolean;
|
||||||
|
children?: ReactNode;
|
||||||
|
className?: string;
|
||||||
onFinished: (success: boolean, reason?: string) => void;
|
onFinished: (success: boolean, reason?: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,19 +109,23 @@ export default class ConfirmUserActionDialog extends React.Component<IProps> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
className="mx_ConfirmUserActionDialog"
|
className={classNames("mx_ConfirmUserActionDialog", this.props.className)}
|
||||||
onFinished={this.props.onFinished}
|
onFinished={this.props.onFinished}
|
||||||
title={this.props.title}
|
title={this.props.title}
|
||||||
contentId='mx_Dialog_content'
|
contentId='mx_Dialog_content'
|
||||||
>
|
>
|
||||||
<div id="mx_Dialog_content" className="mx_Dialog_content">
|
<div id="mx_Dialog_content" className="mx_Dialog_content">
|
||||||
<div className="mx_ConfirmUserActionDialog_avatar">
|
<div className="mx_ConfirmUserActionDialog_user">
|
||||||
{ avatar }
|
<div className="mx_ConfirmUserActionDialog_avatar">
|
||||||
|
{ avatar }
|
||||||
|
</div>
|
||||||
|
<div className="mx_ConfirmUserActionDialog_name">{ name }</div>
|
||||||
|
<div className="mx_ConfirmUserActionDialog_userId">{ userId }</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_ConfirmUserActionDialog_name">{ name }</div>
|
|
||||||
<div className="mx_ConfirmUserActionDialog_userId">{ userId }</div>
|
{ reasonBox }
|
||||||
|
{ this.props.children }
|
||||||
</div>
|
</div>
|
||||||
{ reasonBox }
|
|
||||||
<DialogButtons primaryButton={this.props.action}
|
<DialogButtons primaryButton={this.props.action}
|
||||||
onPrimaryButtonClick={this.onOk}
|
onPrimaryButtonClick={this.onOk}
|
||||||
primaryButtonClass={confirmButtonClass}
|
primaryButtonClass={confirmButtonClass}
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
|
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
|
||||||
|
|
||||||
|
@ -22,108 +22,7 @@ import { _t } from '../../../languageHandler';
|
||||||
import DialogButtons from "../elements/DialogButtons";
|
import DialogButtons from "../elements/DialogButtons";
|
||||||
import BaseDialog from "../dialogs/BaseDialog";
|
import BaseDialog from "../dialogs/BaseDialog";
|
||||||
import SpaceStore from "../../../stores/SpaceStore";
|
import SpaceStore from "../../../stores/SpaceStore";
|
||||||
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
import SpaceChildrenPicker from "../spaces/SpaceChildrenPicker";
|
||||||
import { Entry } from "./AddExistingToSpaceDialog";
|
|
||||||
import SearchBox from "../../structures/SearchBox";
|
|
||||||
import QueryMatcher from "../../../autocomplete/QueryMatcher";
|
|
||||||
import StyledRadioGroup from "../elements/StyledRadioGroup";
|
|
||||||
|
|
||||||
enum RoomsToLeave {
|
|
||||||
All = "All",
|
|
||||||
Specific = "Specific",
|
|
||||||
None = "None",
|
|
||||||
}
|
|
||||||
|
|
||||||
const SpaceChildPicker = ({ filterPlaceholder, rooms, selected, onChange }) => {
|
|
||||||
const [query, setQuery] = useState("");
|
|
||||||
const lcQuery = query.toLowerCase().trim();
|
|
||||||
|
|
||||||
const filteredRooms = useMemo(() => {
|
|
||||||
if (!lcQuery) {
|
|
||||||
return rooms;
|
|
||||||
}
|
|
||||||
|
|
||||||
const matcher = new QueryMatcher<Room>(rooms, {
|
|
||||||
keys: ["name"],
|
|
||||||
funcs: [r => [r.getCanonicalAlias(), ...r.getAltAliases()].filter(Boolean)],
|
|
||||||
shouldMatchWordsOnly: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
return matcher.match(lcQuery);
|
|
||||||
}, [rooms, lcQuery]);
|
|
||||||
|
|
||||||
return <div className="mx_LeaveSpaceDialog_section">
|
|
||||||
<SearchBox
|
|
||||||
className="mx_textinput_icon mx_textinput_search"
|
|
||||||
placeholder={filterPlaceholder}
|
|
||||||
onSearch={setQuery}
|
|
||||||
autoFocus={true}
|
|
||||||
/>
|
|
||||||
<AutoHideScrollbar className="mx_LeaveSpaceDialog_content">
|
|
||||||
{ filteredRooms.map(room => {
|
|
||||||
return <Entry
|
|
||||||
key={room.roomId}
|
|
||||||
room={room}
|
|
||||||
checked={selected.has(room)}
|
|
||||||
onChange={(checked) => {
|
|
||||||
onChange(checked, room);
|
|
||||||
}}
|
|
||||||
/>;
|
|
||||||
}) }
|
|
||||||
{ filteredRooms.length < 1 ? <span className="mx_LeaveSpaceDialog_noResults">
|
|
||||||
{ _t("No results") }
|
|
||||||
</span> : undefined }
|
|
||||||
</AutoHideScrollbar>
|
|
||||||
</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const LeaveRoomsPicker = ({ space, spaceChildren, roomsToLeave, setRoomsToLeave }) => {
|
|
||||||
const selected = useMemo(() => new Set(roomsToLeave), [roomsToLeave]);
|
|
||||||
const [state, setState] = useState<string>(RoomsToLeave.None);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (state === RoomsToLeave.All) {
|
|
||||||
setRoomsToLeave(spaceChildren);
|
|
||||||
} else {
|
|
||||||
setRoomsToLeave([]);
|
|
||||||
}
|
|
||||||
}, [setRoomsToLeave, state, spaceChildren]);
|
|
||||||
|
|
||||||
return <div className="mx_LeaveSpaceDialog_section">
|
|
||||||
<StyledRadioGroup
|
|
||||||
name="roomsToLeave"
|
|
||||||
value={state}
|
|
||||||
onChange={setState}
|
|
||||||
definitions={[
|
|
||||||
{
|
|
||||||
value: RoomsToLeave.None,
|
|
||||||
label: _t("Don't leave any"),
|
|
||||||
}, {
|
|
||||||
value: RoomsToLeave.All,
|
|
||||||
label: _t("Leave all rooms and spaces"),
|
|
||||||
}, {
|
|
||||||
value: RoomsToLeave.Specific,
|
|
||||||
label: _t("Leave specific rooms and spaces"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{ state === RoomsToLeave.Specific && (
|
|
||||||
<SpaceChildPicker
|
|
||||||
filterPlaceholder={_t("Search %(spaceName)s", { spaceName: space.name })}
|
|
||||||
rooms={spaceChildren}
|
|
||||||
selected={selected}
|
|
||||||
onChange={(selected: boolean, room: Room) => {
|
|
||||||
if (selected) {
|
|
||||||
setRoomsToLeave([room, ...roomsToLeave]);
|
|
||||||
} else {
|
|
||||||
setRoomsToLeave(roomsToLeave.filter(r => r !== room));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) }
|
|
||||||
</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
space: Room;
|
space: Room;
|
||||||
|
@ -139,6 +38,7 @@ const isOnlyAdmin = (room: Room): boolean => {
|
||||||
const LeaveSpaceDialog: React.FC<IProps> = ({ space, onFinished }) => {
|
const LeaveSpaceDialog: React.FC<IProps> = ({ space, onFinished }) => {
|
||||||
const spaceChildren = useMemo(() => SpaceStore.instance.getChildren(space.roomId), [space.roomId]);
|
const spaceChildren = useMemo(() => SpaceStore.instance.getChildren(space.roomId), [space.roomId]);
|
||||||
const [roomsToLeave, setRoomsToLeave] = useState<Room[]>([]);
|
const [roomsToLeave, setRoomsToLeave] = useState<Room[]>([]);
|
||||||
|
const selectedRooms = useMemo(() => new Set(roomsToLeave), [roomsToLeave]);
|
||||||
|
|
||||||
let rejoinWarning;
|
let rejoinWarning;
|
||||||
if (space.getJoinRule() !== JoinRule.Public) {
|
if (space.getJoinRule() !== JoinRule.Public) {
|
||||||
|
@ -173,12 +73,17 @@ const LeaveSpaceDialog: React.FC<IProps> = ({ space, onFinished }) => {
|
||||||
{ rejoinWarning }
|
{ rejoinWarning }
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{ spaceChildren.length > 0 && <LeaveRoomsPicker
|
{ spaceChildren.length > 0 && (
|
||||||
space={space}
|
<SpaceChildrenPicker
|
||||||
spaceChildren={spaceChildren}
|
space={space}
|
||||||
roomsToLeave={roomsToLeave}
|
spaceChildren={spaceChildren}
|
||||||
setRoomsToLeave={setRoomsToLeave}
|
selected={selectedRooms}
|
||||||
/> }
|
onChange={setRoomsToLeave}
|
||||||
|
noneLabel={_t("Don't leave any")}
|
||||||
|
allLabel={_t("Leave all rooms and spaces")}
|
||||||
|
specificLabel={_t("Leave specific rooms and spaces")}
|
||||||
|
/>
|
||||||
|
) }
|
||||||
|
|
||||||
{ onlyAdminWarning && <div className="mx_LeaveSpaceDialog_section_warning">
|
{ onlyAdminWarning && <div className="mx_LeaveSpaceDialog_section_warning">
|
||||||
{ onlyAdminWarning }
|
{ onlyAdminWarning }
|
||||||
|
|
|
@ -70,6 +70,8 @@ import { mediaFromMxc } from "../../../customisations/Media";
|
||||||
import UIStore from "../../../stores/UIStore";
|
import UIStore from "../../../stores/UIStore";
|
||||||
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
|
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
|
||||||
import SpaceStore from "../../../stores/SpaceStore";
|
import SpaceStore from "../../../stores/SpaceStore";
|
||||||
|
import ConfirmSpaceUserActionDialog from "../dialogs/ConfirmSpaceUserActionDialog";
|
||||||
|
import { bulkSpaceBehaviour } from "../../../utils/space";
|
||||||
|
|
||||||
export interface IDevice {
|
export interface IDevice {
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
|
@ -530,7 +532,7 @@ interface IBaseProps {
|
||||||
stopUpdating(): void;
|
stopUpdating(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RoomKickButton: React.FC<IBaseProps> = ({ member, startUpdating, stopUpdating }) => {
|
const RoomKickButton = ({ room, member, startUpdating, stopUpdating }: Omit<IBaseRoomProps, "powerLevels">) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
|
|
||||||
// check if user can be kicked/disinvited
|
// check if user can be kicked/disinvited
|
||||||
|
@ -540,21 +542,35 @@ const RoomKickButton: React.FC<IBaseProps> = ({ member, startUpdating, stopUpdat
|
||||||
const { finished } = Modal.createTrackedDialog(
|
const { finished } = Modal.createTrackedDialog(
|
||||||
'Confirm User Action Dialog',
|
'Confirm User Action Dialog',
|
||||||
'onKick',
|
'onKick',
|
||||||
ConfirmUserActionDialog,
|
room.isSpaceRoom() ? ConfirmSpaceUserActionDialog : ConfirmUserActionDialog,
|
||||||
{
|
{
|
||||||
member,
|
member,
|
||||||
action: member.membership === "invite" ? _t("Disinvite") : _t("Kick"),
|
action: member.membership === "invite" ? _t("Disinvite") : _t("Kick"),
|
||||||
title: member.membership === "invite" ? _t("Disinvite this user?") : _t("Kick this user?"),
|
title: member.membership === "invite" ? _t("Disinvite this user?") : _t("Kick this user?"),
|
||||||
askReason: member.membership === "join",
|
askReason: member.membership === "join",
|
||||||
danger: true,
|
danger: true,
|
||||||
|
// space-specific props
|
||||||
|
space: room,
|
||||||
|
spaceChildFilter: (child: Room) => {
|
||||||
|
// Return true if the target member is not banned and we have sufficient PL to ban them
|
||||||
|
const myMember = child.getMember(cli.credentials.userId);
|
||||||
|
const theirMember = child.getMember(member.userId);
|
||||||
|
return myMember && theirMember && theirMember.membership === member.membership &&
|
||||||
|
myMember.powerLevel > theirMember.powerLevel &&
|
||||||
|
child.currentState.hasSufficientPowerLevelFor("kick", myMember.powerLevel);
|
||||||
|
},
|
||||||
|
allLabel: _t("Kick them from everything I'm able to"),
|
||||||
|
specificLabel: _t("Kick them from specific things I'm able to"),
|
||||||
},
|
},
|
||||||
|
room.isSpaceRoom() ? "mx_ConfirmSpaceUserActionDialog_wrapper" : undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [proceed, reason] = await finished;
|
const [proceed, reason, rooms = []] = await finished;
|
||||||
if (!proceed) return;
|
if (!proceed) return;
|
||||||
|
|
||||||
startUpdating();
|
startUpdating();
|
||||||
cli.kick(member.roomId, member.userId, reason || undefined).then(() => {
|
|
||||||
|
bulkSpaceBehaviour(room, rooms, room => cli.kick(room.roomId, member.userId, reason || undefined)).then(() => {
|
||||||
// NO-OP; rely on the m.room.member event coming down else we could
|
// NO-OP; rely on the m.room.member event coming down else we could
|
||||||
// get out of sync if we force setState here!
|
// get out of sync if we force setState here!
|
||||||
console.log("Kick success");
|
console.log("Kick success");
|
||||||
|
@ -654,34 +670,64 @@ const RedactMessagesButton: React.FC<IBaseProps> = ({ member }) => {
|
||||||
</AccessibleButton>;
|
</AccessibleButton>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const BanToggleButton: React.FC<IBaseProps> = ({ member, startUpdating, stopUpdating }) => {
|
const BanToggleButton = ({ room, member, startUpdating, stopUpdating }: Omit<IBaseRoomProps, "powerLevels">) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
|
|
||||||
|
const isBanned = member.membership === "ban";
|
||||||
const onBanOrUnban = async () => {
|
const onBanOrUnban = async () => {
|
||||||
const { finished } = Modal.createTrackedDialog(
|
const { finished } = Modal.createTrackedDialog(
|
||||||
'Confirm User Action Dialog',
|
'Confirm User Action Dialog',
|
||||||
'onBanOrUnban',
|
'onBanOrUnban',
|
||||||
ConfirmUserActionDialog,
|
room.isSpaceRoom() ? ConfirmSpaceUserActionDialog : ConfirmUserActionDialog,
|
||||||
{
|
{
|
||||||
member,
|
member,
|
||||||
action: member.membership === 'ban' ? _t("Unban") : _t("Ban"),
|
action: isBanned ? _t("Unban") : _t("Ban"),
|
||||||
title: member.membership === 'ban' ? _t("Unban this user?") : _t("Ban this user?"),
|
title: isBanned ? _t("Unban this user?") : _t("Ban this user?"),
|
||||||
askReason: member.membership !== 'ban',
|
askReason: !isBanned,
|
||||||
danger: member.membership !== 'ban',
|
danger: !isBanned,
|
||||||
|
// space-specific props
|
||||||
|
space: room,
|
||||||
|
spaceChildFilter: isBanned
|
||||||
|
? (child: Room) => {
|
||||||
|
// Return true if the target member is banned and we have sufficient PL to unban
|
||||||
|
const myMember = child.getMember(cli.credentials.userId);
|
||||||
|
const theirMember = child.getMember(member.userId);
|
||||||
|
return myMember && theirMember && theirMember.membership === "ban" &&
|
||||||
|
myMember.powerLevel > theirMember.powerLevel &&
|
||||||
|
child.currentState.hasSufficientPowerLevelFor("ban", myMember.powerLevel);
|
||||||
|
}
|
||||||
|
: (child: Room) => {
|
||||||
|
// Return true if the target member isn't banned and we have sufficient PL to ban
|
||||||
|
const myMember = child.getMember(cli.credentials.userId);
|
||||||
|
const theirMember = child.getMember(member.userId);
|
||||||
|
return myMember && theirMember && theirMember.membership !== "ban" &&
|
||||||
|
myMember.powerLevel > theirMember.powerLevel &&
|
||||||
|
child.currentState.hasSufficientPowerLevelFor("ban", myMember.powerLevel);
|
||||||
|
},
|
||||||
|
allLabel: isBanned
|
||||||
|
? _t("Unban them from everything I'm able to")
|
||||||
|
: _t("Ban them from everything I'm able to"),
|
||||||
|
specificLabel: isBanned
|
||||||
|
? _t("Unban them from specific things I'm able to")
|
||||||
|
: _t("Ban them from specific things I'm able to"),
|
||||||
},
|
},
|
||||||
|
room.isSpaceRoom() ? "mx_ConfirmSpaceUserActionDialog_wrapper" : undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [proceed, reason] = await finished;
|
const [proceed, reason, rooms = []] = await finished;
|
||||||
if (!proceed) return;
|
if (!proceed) return;
|
||||||
|
|
||||||
startUpdating();
|
startUpdating();
|
||||||
let promise;
|
|
||||||
if (member.membership === 'ban') {
|
const fn = (roomId: string) => {
|
||||||
promise = cli.unban(member.roomId, member.userId);
|
if (isBanned) {
|
||||||
} else {
|
return cli.unban(roomId, member.userId);
|
||||||
promise = cli.ban(member.roomId, member.userId, reason || undefined);
|
} else {
|
||||||
}
|
return cli.ban(roomId, member.userId, reason || undefined);
|
||||||
promise.then(() => {
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bulkSpaceBehaviour(room, rooms, room => fn(room.roomId)).then(() => {
|
||||||
// NO-OP; rely on the m.room.member event coming down else we could
|
// NO-OP; rely on the m.room.member event coming down else we could
|
||||||
// get out of sync if we force setState here!
|
// get out of sync if we force setState here!
|
||||||
console.log("Ban success");
|
console.log("Ban success");
|
||||||
|
@ -697,12 +743,12 @@ const BanToggleButton: React.FC<IBaseProps> = ({ member, startUpdating, stopUpda
|
||||||
};
|
};
|
||||||
|
|
||||||
let label = _t("Ban");
|
let label = _t("Ban");
|
||||||
if (member.membership === 'ban') {
|
if (isBanned) {
|
||||||
label = _t("Unban");
|
label = _t("Unban");
|
||||||
}
|
}
|
||||||
|
|
||||||
const classes = classNames("mx_UserInfo_field", {
|
const classes = classNames("mx_UserInfo_field", {
|
||||||
mx_UserInfo_destructive: member.membership !== 'ban',
|
mx_UserInfo_destructive: !isBanned,
|
||||||
});
|
});
|
||||||
|
|
||||||
return <AccessibleButton className={classes} onClick={onBanOrUnban}>
|
return <AccessibleButton className={classes} onClick={onBanOrUnban}>
|
||||||
|
@ -816,7 +862,12 @@ const RoomAdminToolsContainer: React.FC<IBaseRoomProps> = ({
|
||||||
const canAffectUser = member.powerLevel < me.powerLevel || isMe;
|
const canAffectUser = member.powerLevel < me.powerLevel || isMe;
|
||||||
|
|
||||||
if (canAffectUser && me.powerLevel >= kickPowerLevel) {
|
if (canAffectUser && me.powerLevel >= kickPowerLevel) {
|
||||||
kickButton = <RoomKickButton member={member} startUpdating={startUpdating} stopUpdating={stopUpdating} />;
|
kickButton = <RoomKickButton
|
||||||
|
room={room}
|
||||||
|
member={member}
|
||||||
|
startUpdating={startUpdating}
|
||||||
|
stopUpdating={stopUpdating}
|
||||||
|
/>;
|
||||||
}
|
}
|
||||||
if (me.powerLevel >= redactPowerLevel && (!SpaceStore.spacesEnabled || !room.isSpaceRoom())) {
|
if (me.powerLevel >= redactPowerLevel && (!SpaceStore.spacesEnabled || !room.isSpaceRoom())) {
|
||||||
redactButton = (
|
redactButton = (
|
||||||
|
@ -824,7 +875,12 @@ const RoomAdminToolsContainer: React.FC<IBaseRoomProps> = ({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (canAffectUser && me.powerLevel >= banPowerLevel) {
|
if (canAffectUser && me.powerLevel >= banPowerLevel) {
|
||||||
banButton = <BanToggleButton member={member} startUpdating={startUpdating} stopUpdating={stopUpdating} />;
|
banButton = <BanToggleButton
|
||||||
|
room={room}
|
||||||
|
member={member}
|
||||||
|
startUpdating={startUpdating}
|
||||||
|
stopUpdating={stopUpdating}
|
||||||
|
/>;
|
||||||
}
|
}
|
||||||
if (canAffectUser && me.powerLevel >= editPowerLevel && !room.isSpaceRoom()) {
|
if (canAffectUser && me.powerLevel >= editPowerLevel && !room.isSpaceRoom()) {
|
||||||
muteButton = (
|
muteButton = (
|
||||||
|
|
150
src/components/views/spaces/SpaceChildrenPicker.tsx
Normal file
150
src/components/views/spaces/SpaceChildrenPicker.tsx
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
|
||||||
|
import { _t } from "../../../languageHandler";
|
||||||
|
import StyledRadioGroup from "../elements/StyledRadioGroup";
|
||||||
|
import QueryMatcher from "../../../autocomplete/QueryMatcher";
|
||||||
|
import SearchBox from "../../structures/SearchBox";
|
||||||
|
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
||||||
|
import { Entry } from "../dialogs/AddExistingToSpaceDialog";
|
||||||
|
|
||||||
|
enum Target {
|
||||||
|
All = "All",
|
||||||
|
Specific = "Specific",
|
||||||
|
None = "None",
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ISpecificChildrenPickerProps {
|
||||||
|
filterPlaceholder: string;
|
||||||
|
rooms: Room[];
|
||||||
|
selected: Set<Room>;
|
||||||
|
onChange(selected: boolean, room: Room): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SpecificChildrenPicker = ({ filterPlaceholder, rooms, selected, onChange }: ISpecificChildrenPickerProps) => {
|
||||||
|
const [query, setQuery] = useState("");
|
||||||
|
const lcQuery = query.toLowerCase().trim();
|
||||||
|
|
||||||
|
const filteredRooms = useMemo(() => {
|
||||||
|
if (!lcQuery) {
|
||||||
|
return rooms;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matcher = new QueryMatcher<Room>(rooms, {
|
||||||
|
keys: ["name"],
|
||||||
|
funcs: [r => [r.getCanonicalAlias(), ...r.getAltAliases()].filter(Boolean)],
|
||||||
|
shouldMatchWordsOnly: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return matcher.match(lcQuery);
|
||||||
|
}, [rooms, lcQuery]);
|
||||||
|
|
||||||
|
return <div className="mx_SpaceChildrenPicker">
|
||||||
|
<SearchBox
|
||||||
|
className="mx_textinput_icon mx_textinput_search"
|
||||||
|
placeholder={filterPlaceholder}
|
||||||
|
onSearch={setQuery}
|
||||||
|
autoFocus={true}
|
||||||
|
/>
|
||||||
|
<AutoHideScrollbar>
|
||||||
|
{ filteredRooms.map(room => {
|
||||||
|
return <Entry
|
||||||
|
key={room.roomId}
|
||||||
|
room={room}
|
||||||
|
checked={selected.has(room)}
|
||||||
|
onChange={(checked) => {
|
||||||
|
onChange(checked, room);
|
||||||
|
}}
|
||||||
|
/>;
|
||||||
|
}) }
|
||||||
|
{ filteredRooms.length < 1 ? <span className="mx_SpaceChildrenPicker_noResults">
|
||||||
|
{ _t("No results") }
|
||||||
|
</span> : undefined }
|
||||||
|
</AutoHideScrollbar>
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
space: Room;
|
||||||
|
spaceChildren: Room[];
|
||||||
|
selected: Set<Room>;
|
||||||
|
noneLabel?: string;
|
||||||
|
allLabel: string;
|
||||||
|
specificLabel: string;
|
||||||
|
onChange(rooms: Room[]): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SpaceChildrenPicker = ({
|
||||||
|
space,
|
||||||
|
spaceChildren,
|
||||||
|
selected,
|
||||||
|
onChange,
|
||||||
|
noneLabel,
|
||||||
|
allLabel,
|
||||||
|
specificLabel,
|
||||||
|
}: IProps) => {
|
||||||
|
const [state, setState] = useState<string>(noneLabel ? Target.None : Target.All);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (state === Target.All) {
|
||||||
|
onChange(spaceChildren);
|
||||||
|
} else {
|
||||||
|
onChange([]);
|
||||||
|
}
|
||||||
|
}, [onChange, state, spaceChildren]);
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<div className="mx_SpaceChildrenPicker">
|
||||||
|
<StyledRadioGroup
|
||||||
|
name="roomsToLeave"
|
||||||
|
value={state}
|
||||||
|
onChange={setState}
|
||||||
|
definitions={[
|
||||||
|
{
|
||||||
|
value: Target.None,
|
||||||
|
label: noneLabel,
|
||||||
|
}, {
|
||||||
|
value: Target.All,
|
||||||
|
label: allLabel,
|
||||||
|
}, {
|
||||||
|
value: Target.Specific,
|
||||||
|
label: specificLabel,
|
||||||
|
},
|
||||||
|
].filter(d => d.label)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{ state === Target.Specific && (
|
||||||
|
<SpecificChildrenPicker
|
||||||
|
filterPlaceholder={_t("Search %(spaceName)s", { spaceName: space.name })}
|
||||||
|
rooms={spaceChildren}
|
||||||
|
selected={selected}
|
||||||
|
onChange={(isSelected: boolean, room: Room) => {
|
||||||
|
if (isSelected) {
|
||||||
|
onChange([room, ...selected]);
|
||||||
|
} else {
|
||||||
|
onChange([...selected].filter(r => r !== room));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) }
|
||||||
|
</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SpaceChildrenPicker;
|
|
@ -1018,6 +1018,8 @@
|
||||||
"Upload": "Upload",
|
"Upload": "Upload",
|
||||||
"Name": "Name",
|
"Name": "Name",
|
||||||
"Description": "Description",
|
"Description": "Description",
|
||||||
|
"No results": "No results",
|
||||||
|
"Search %(spaceName)s": "Search %(spaceName)s",
|
||||||
"Please enter a name for the space": "Please enter a name for the space",
|
"Please enter a name for the space": "Please enter a name for the space",
|
||||||
"Spaces are a new feature.": "Spaces are a new feature.",
|
"Spaces are a new feature.": "Spaces are a new feature.",
|
||||||
"Spaces feedback": "Spaces feedback",
|
"Spaces feedback": "Spaces feedback",
|
||||||
|
@ -1847,6 +1849,8 @@
|
||||||
"Kick": "Kick",
|
"Kick": "Kick",
|
||||||
"Disinvite this user?": "Disinvite this user?",
|
"Disinvite this user?": "Disinvite this user?",
|
||||||
"Kick this user?": "Kick this user?",
|
"Kick this user?": "Kick this user?",
|
||||||
|
"Kick them from everything I'm able to": "Kick them from everything I'm able to",
|
||||||
|
"Kick them from specific things I'm able to": "Kick them from specific things I'm able to",
|
||||||
"Failed to kick": "Failed to kick",
|
"Failed to kick": "Failed to kick",
|
||||||
"No recent messages by %(user)s found": "No recent messages by %(user)s found",
|
"No recent messages by %(user)s found": "No recent messages by %(user)s found",
|
||||||
"Try scrolling up in the timeline to see if there are any earlier ones.": "Try scrolling up in the timeline to see if there are any earlier ones.",
|
"Try scrolling up in the timeline to see if there are any earlier ones.": "Try scrolling up in the timeline to see if there are any earlier ones.",
|
||||||
|
@ -1860,6 +1864,10 @@
|
||||||
"Ban": "Ban",
|
"Ban": "Ban",
|
||||||
"Unban this user?": "Unban this user?",
|
"Unban this user?": "Unban this user?",
|
||||||
"Ban this user?": "Ban this user?",
|
"Ban this user?": "Ban this user?",
|
||||||
|
"Unban them from everything I'm able to": "Unban them from everything I'm able to",
|
||||||
|
"Ban them from everything I'm able to": "Ban them from everything I'm able to",
|
||||||
|
"Unban them from specific things I'm able to": "Unban them from specific things I'm able to",
|
||||||
|
"Ban them from specific things I'm able to": "Ban them from specific things I'm able to",
|
||||||
"Failed to ban user": "Failed to ban user",
|
"Failed to ban user": "Failed to ban user",
|
||||||
"Failed to mute user": "Failed to mute user",
|
"Failed to mute user": "Failed to mute user",
|
||||||
"Unmute": "Unmute",
|
"Unmute": "Unmute",
|
||||||
|
@ -2050,7 +2058,6 @@
|
||||||
"Application window": "Application window",
|
"Application window": "Application window",
|
||||||
"Share content": "Share content",
|
"Share content": "Share content",
|
||||||
"Join": "Join",
|
"Join": "Join",
|
||||||
"No results": "No results",
|
|
||||||
"Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.": "Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.",
|
"Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.": "Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.",
|
||||||
"collapse": "collapse",
|
"collapse": "collapse",
|
||||||
"expand": "expand",
|
"expand": "expand",
|
||||||
|
@ -2217,6 +2224,7 @@
|
||||||
"Confirm Removal": "Confirm Removal",
|
"Confirm Removal": "Confirm Removal",
|
||||||
"Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.",
|
"Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.",
|
||||||
"Reason (optional)": "Reason (optional)",
|
"Reason (optional)": "Reason (optional)",
|
||||||
|
"You’re not an admin of anything they’re a member of in <SpaceName/>, so banning won’t remove them from any rooms or spaces in <SpaceName/>.": "You’re not an admin of anything they’re a member of in <SpaceName/>, so banning won’t remove them from any rooms or spaces in <SpaceName/>.",
|
||||||
"Clear all data in this session?": "Clear all data in this session?",
|
"Clear all data in this session?": "Clear all data in this session?",
|
||||||
"Clearing all data from this session is permanent. Encrypted messages will be lost unless their keys have been backed up.": "Clearing all data from this session is permanent. Encrypted messages will be lost unless their keys have been backed up.",
|
"Clearing all data from this session is permanent. Encrypted messages will be lost unless their keys have been backed up.": "Clearing all data from this session is permanent. Encrypted messages will be lost unless their keys have been backed up.",
|
||||||
"Clear all data": "Clear all data",
|
"Clear all data": "Clear all data",
|
||||||
|
@ -2430,15 +2438,14 @@
|
||||||
"Clear cache and resync": "Clear cache and resync",
|
"Clear cache and resync": "Clear cache and resync",
|
||||||
"%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!",
|
"%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!",
|
||||||
"Updating %(brand)s": "Updating %(brand)s",
|
"Updating %(brand)s": "Updating %(brand)s",
|
||||||
"Don't leave any": "Don't leave any",
|
|
||||||
"Leave all rooms and spaces": "Leave all rooms and spaces",
|
|
||||||
"Leave specific rooms and spaces": "Leave specific rooms and spaces",
|
|
||||||
"Search %(spaceName)s": "Search %(spaceName)s",
|
|
||||||
"You won't be able to rejoin unless you are re-invited.": "You won't be able to rejoin unless you are re-invited.",
|
"You won't be able to rejoin unless you are re-invited.": "You won't be able to rejoin unless you are re-invited.",
|
||||||
"You're the only admin of this space. Leaving it will mean no one has control over it.": "You're the only admin of this space. Leaving it will mean no one has control over it.",
|
"You're the only admin of this space. Leaving it will mean no one has control over it.": "You're the only admin of this space. Leaving it will mean no one has control over it.",
|
||||||
"You're the only admin of some of the rooms or spaces you wish to leave. Leaving them will leave them without any admins.": "You're the only admin of some of the rooms or spaces you wish to leave. Leaving them will leave them without any admins.",
|
"You're the only admin of some of the rooms or spaces you wish to leave. Leaving them will leave them without any admins.": "You're the only admin of some of the rooms or spaces you wish to leave. Leaving them will leave them without any admins.",
|
||||||
"Leave %(spaceName)s": "Leave %(spaceName)s",
|
"Leave %(spaceName)s": "Leave %(spaceName)s",
|
||||||
"Are you sure you want to leave <spaceName/>?": "Are you sure you want to leave <spaceName/>?",
|
"Are you sure you want to leave <spaceName/>?": "Are you sure you want to leave <spaceName/>?",
|
||||||
|
"Don't leave any": "Don't leave any",
|
||||||
|
"Leave all rooms and spaces": "Leave all rooms and spaces",
|
||||||
|
"Leave specific rooms and spaces": "Leave specific rooms and spaces",
|
||||||
"Leave space": "Leave space",
|
"Leave space": "Leave space",
|
||||||
"Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.",
|
"Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.",
|
||||||
"Start using Key Backup": "Start using Key Backup",
|
"Start using Key Backup": "Start using Key Backup",
|
||||||
|
|
|
@ -155,20 +155,28 @@ export const showCreateNewSubspace = (space: Room): void => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const bulkSpaceBehaviour = async (
|
||||||
|
space: Room,
|
||||||
|
children: Room[],
|
||||||
|
fn: (room: Room) => Promise<unknown>,
|
||||||
|
): Promise<void> => {
|
||||||
|
const modal = Modal.createDialog(Spinner, null, "mx_Dialog_spinner");
|
||||||
|
try {
|
||||||
|
for (const room of children) {
|
||||||
|
await fn(room);
|
||||||
|
}
|
||||||
|
await fn(space);
|
||||||
|
} finally {
|
||||||
|
modal.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const leaveSpace = (space: Room) => {
|
export const leaveSpace = (space: Room) => {
|
||||||
Modal.createTrackedDialog("Leave Space", "", LeaveSpaceDialog, {
|
Modal.createTrackedDialog("Leave Space", "", LeaveSpaceDialog, {
|
||||||
space,
|
space,
|
||||||
onFinished: async (leave: boolean, rooms: Room[]) => {
|
onFinished: async (leave: boolean, rooms: Room[]) => {
|
||||||
if (!leave) return;
|
if (!leave) return;
|
||||||
const modal = Modal.createDialog(Spinner, null, "mx_Dialog_spinner");
|
await bulkSpaceBehaviour(space, rooms, room => leaveRoomBehaviour(room.roomId));
|
||||||
try {
|
|
||||||
for (const room of rooms) {
|
|
||||||
await leaveRoomBehaviour(room.roomId);
|
|
||||||
}
|
|
||||||
await leaveRoomBehaviour(space.roomId);
|
|
||||||
} finally {
|
|
||||||
modal.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: "after_leave_room",
|
action: "after_leave_room",
|
||||||
|
|
Loading…
Reference in a new issue