mirror of
https://github.com/element-hq/element-web
synced 2024-11-26 19:26:04 +03:00
Offer a way to create a space based on existing community
This commit is contained in:
parent
152c7dc865
commit
a6e5112be0
19 changed files with 895 additions and 68 deletions
|
@ -75,6 +75,7 @@
|
||||||
@import "./views/dialogs/_CreateCommunityPrototypeDialog.scss";
|
@import "./views/dialogs/_CreateCommunityPrototypeDialog.scss";
|
||||||
@import "./views/dialogs/_CreateGroupDialog.scss";
|
@import "./views/dialogs/_CreateGroupDialog.scss";
|
||||||
@import "./views/dialogs/_CreateRoomDialog.scss";
|
@import "./views/dialogs/_CreateRoomDialog.scss";
|
||||||
|
@import "./views/dialogs/_CreateSpaceFromCommunityDialog.scss";
|
||||||
@import "./views/dialogs/_CreateSubspaceDialog.scss";
|
@import "./views/dialogs/_CreateSubspaceDialog.scss";
|
||||||
@import "./views/dialogs/_DeactivateAccountDialog.scss";
|
@import "./views/dialogs/_DeactivateAccountDialog.scss";
|
||||||
@import "./views/dialogs/_DevtoolsDialog.scss";
|
@import "./views/dialogs/_DevtoolsDialog.scss";
|
||||||
|
|
|
@ -368,6 +368,61 @@ limitations under the License.
|
||||||
padding: 40px 20px;
|
padding: 40px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_GroupView_spaceUpgradePrompt {
|
||||||
|
padding: 16px 50px;
|
||||||
|
background-color: $header-panel-bg-color;
|
||||||
|
border-radius: 8px;
|
||||||
|
max-width: 632px;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
margin-top: 24px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
> h2 {
|
||||||
|
font-size: inherit;
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
> p, h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
height: $font-24px;
|
||||||
|
width: 20px;
|
||||||
|
left: 18px;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
mask-size: contain;
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/room-summary.svg');
|
||||||
|
background-color: $secondary-fg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: $input-darker-bg-color;
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
right: 16px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: inherit;
|
||||||
|
height: inherit;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
mask-size: 8px;
|
||||||
|
mask-image: url('$(res)/img/image-view/close.svg');
|
||||||
|
background-color: $secondary-fg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mx_GroupView .mx_MemberInfo .mx_AutoHideScrollbar > :not(.mx_MemberInfo_avatar) {
|
.mx_GroupView .mx_MemberInfo .mx_AutoHideScrollbar > :not(.mx_MemberInfo_avatar) {
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
padding-right: 16px;
|
padding-right: 16px;
|
||||||
|
|
|
@ -180,6 +180,18 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_preview_migratedCommunity {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid $input-border-color;
|
||||||
|
width: max-content;
|
||||||
|
|
||||||
|
.mx_BaseAvatar {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomView_preview_inviter {
|
.mx_SpaceRoomView_preview_inviter {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -342,7 +354,7 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
|
|
||||||
.mx_SpaceFeedbackPrompt {
|
.mx_SpaceFeedbackPrompt {
|
||||||
padding: 7px; // 8px - 1px border
|
padding: 7px; // 8px - 1px border
|
||||||
border: 1px solid $menu-border-color;
|
border: 1px solid rgba($primary-fg-color, .1);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
margin: 0 0 -40px auto; // collapse its own height to not push other components down
|
margin: 0 0 -40px auto; // collapse its own height to not push other components down
|
||||||
|
|
|
@ -51,6 +51,10 @@ limitations under the License.
|
||||||
mask-image: url('$(res)/img/element-icons/hide.svg');
|
mask-image: url('$(res)/img/element-icons/hide.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_TagTileContextMenu_createSpace::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/message/fwd.svg');
|
||||||
|
}
|
||||||
|
|
||||||
.mx_TagTileContextMenu_separator {
|
.mx_TagTileContextMenu_separator {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
|
176
res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss
Normal file
176
res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
/*
|
||||||
|
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_CreateSpaceFromCommunityDialog_wrapper {
|
||||||
|
.mx_Dialog {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CreateSpaceFromCommunityDialog {
|
||||||
|
width: 480px;
|
||||||
|
color: $primary-fg-color;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
.mx_CreateSpaceFromCommunityDialog_content {
|
||||||
|
> p {
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
|
||||||
|
&.mx_CreateSpaceFromCommunityDialog_flairNotice {
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceBasicSettings {
|
||||||
|
> p {
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
margin: 8px 0 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_JoinRuleDropdown .mx_Dropdown_menu {
|
||||||
|
width: auto !important; // override fixed width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CreateSpaceFromCommunityDialog_footer {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
flex-grow: 1;
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
|
||||||
|
.mx_ProgressBar {
|
||||||
|
height: 8px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
@mixin ProgressBarBorderRadius 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CreateSpaceFromCommunityDialog_progressText {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
color: $primary-fg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
> * {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CreateSpaceFromCommunityDialog_error {
|
||||||
|
padding-left: 12px;
|
||||||
|
|
||||||
|
> img {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CreateSpaceFromCommunityDialog_errorHeading {
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-18px;
|
||||||
|
color: $notice-primary-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CreateSpaceFromCommunityDialog_errorCaption {
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
color: $primary-fg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton {
|
||||||
|
display: inline-block;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_primary {
|
||||||
|
padding: 8px 36px;
|
||||||
|
margin-left: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_primary_outline {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CreateSpaceFromCommunityDialog_retryButton {
|
||||||
|
margin-left: 12px;
|
||||||
|
padding-left: 24px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
background-color: $primary-fg-color;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
mask-size: contain;
|
||||||
|
mask-image: url('$(res)/img/element-icons/retry.svg');
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_link {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CreateSpaceFromCommunityDialog_SuccessInfoDialog {
|
||||||
|
.mx_InfoDialog {
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_link {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CreateSpaceFromCommunityDialog_SuccessInfoDialog_checkmark {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 3px solid $accent-color;
|
||||||
|
width: 68px;
|
||||||
|
height: 68px;
|
||||||
|
margin: 12px auto 32px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
width: inherit;
|
||||||
|
height: inherit;
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
background-color: $accent-color;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
mask-image: url('$(res)/img/element-icons/roomlist/checkmark.svg');
|
||||||
|
mask-size: 48px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,4 +22,24 @@ limitations under the License.
|
||||||
.mx_SettingsTab_section {
|
.mx_SettingsTab_section {
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_PreferencesUserSettingsTab_CommunityMigrator {
|
||||||
|
margin-right: 200px;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-18px;
|
||||||
|
color: $primary-fg-color;
|
||||||
|
|
||||||
|
.mx_BaseAvatar {
|
||||||
|
margin-right: 12px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -399,6 +399,8 @@ class FeaturedUser extends React.Component {
|
||||||
const GROUP_JOINPOLICY_OPEN = "open";
|
const GROUP_JOINPOLICY_OPEN = "open";
|
||||||
const GROUP_JOINPOLICY_INVITE = "invite";
|
const GROUP_JOINPOLICY_INVITE = "invite";
|
||||||
|
|
||||||
|
const UPGRADE_NOTICE_LS_KEY = "mx_hide_community_upgrade_notice";
|
||||||
|
|
||||||
@replaceableComponent("structures.GroupView")
|
@replaceableComponent("structures.GroupView")
|
||||||
export default class GroupView extends React.Component {
|
export default class GroupView extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -422,6 +424,7 @@ export default class GroupView extends React.Component {
|
||||||
publicityBusy: false,
|
publicityBusy: false,
|
||||||
inviterProfile: null,
|
inviterProfile: null,
|
||||||
showRightPanel: RightPanelStore.getSharedInstance().isOpenForGroup,
|
showRightPanel: RightPanelStore.getSharedInstance().isOpenForGroup,
|
||||||
|
showUpgradeNotice: !localStorage.getItem(UPGRADE_NOTICE_LS_KEY),
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -807,6 +810,11 @@ export default class GroupView extends React.Component {
|
||||||
showGroupAddRoomDialog(this.props.groupId);
|
showGroupAddRoomDialog(this.props.groupId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_dismissUpgradeNotice = () => {
|
||||||
|
localStorage.setItem(UPGRADE_NOTICE_LS_KEY, "true");
|
||||||
|
this.setState({ showUpgradeNotice: false });
|
||||||
|
}
|
||||||
|
|
||||||
_getGroupSection() {
|
_getGroupSection() {
|
||||||
const groupSettingsSectionClasses = classnames({
|
const groupSettingsSectionClasses = classnames({
|
||||||
"mx_GroupView_group": this.state.editing,
|
"mx_GroupView_group": this.state.editing,
|
||||||
|
@ -843,10 +851,28 @@ export default class GroupView extends React.Component {
|
||||||
},
|
},
|
||||||
) }
|
) }
|
||||||
</div> : <div />;
|
</div> : <div />;
|
||||||
|
|
||||||
|
let communitiesUpgradeNotice;
|
||||||
|
if (this.state.showUpgradeNotice) {
|
||||||
|
communitiesUpgradeNotice = <div className="mx_GroupView_spaceUpgradePrompt">
|
||||||
|
<h2>{ _t("Communities can now be made into Spaces") }</h2>
|
||||||
|
<p>
|
||||||
|
{ _t("Spaces are a new way to make a community, with new features coming.") }
|
||||||
|
|
||||||
|
{ _t("Ask the admins of this community to make it into a Space " +
|
||||||
|
"and keep a look out for the invite.") }
|
||||||
|
|
||||||
|
{ _t("Communities won't receive further updates.") }
|
||||||
|
</p>
|
||||||
|
<AccessibleButton onClick={this._dismissUpgradeNotice} />
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
return <div className={groupSettingsSectionClasses}>
|
return <div className={groupSettingsSectionClasses}>
|
||||||
{ header }
|
{ header }
|
||||||
{ hostingSignup }
|
{ hostingSignup }
|
||||||
{ changeDelayWarning }
|
{ changeDelayWarning }
|
||||||
|
{ communitiesUpgradeNotice }
|
||||||
{ this._getJoinableNode() }
|
{ this._getJoinableNode() }
|
||||||
{ this._getLongDescriptionNode() }
|
{ this._getLongDescriptionNode() }
|
||||||
{ this._getRoomsNode() }
|
{ this._getRoomsNode() }
|
||||||
|
|
|
@ -74,6 +74,10 @@ import { BetaPill } from "../views/beta/BetaCard";
|
||||||
import { UserTab } from "../views/dialogs/UserSettingsDialog";
|
import { UserTab } from "../views/dialogs/UserSettingsDialog";
|
||||||
import { EffectiveMembership, getEffectiveMembership } from "../../utils/membership";
|
import { EffectiveMembership, getEffectiveMembership } from "../../utils/membership";
|
||||||
import { SpaceFeedbackPrompt } from "../views/spaces/SpaceCreateMenu";
|
import { SpaceFeedbackPrompt } from "../views/spaces/SpaceCreateMenu";
|
||||||
|
import { CreateEventField, IGroupSummary } from "../views/dialogs/CreateSpaceFromCommunityDialog";
|
||||||
|
import { useAsyncMemo } from "../../hooks/useAsyncMemo";
|
||||||
|
import Spinner from "../views/elements/Spinner";
|
||||||
|
import GroupAvatar from "../views/avatars/GroupAvatar";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
space: Room;
|
space: Room;
|
||||||
|
@ -158,7 +162,33 @@ const onBetaClick = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => {
|
// XXX: temporary community migration component
|
||||||
|
const GroupTile = ({ groupId }: { groupId: string }) => {
|
||||||
|
const cli = useContext(MatrixClientContext);
|
||||||
|
const groupSummary = useAsyncMemo<IGroupSummary>(() => cli.getGroupSummary(groupId), [cli, groupId]);
|
||||||
|
|
||||||
|
if (!groupSummary) return <Spinner />;
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<GroupAvatar
|
||||||
|
groupId={groupId}
|
||||||
|
groupName={groupSummary.profile.name}
|
||||||
|
groupAvatarUrl={groupSummary.profile.avatar_url}
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
resizeMethod='crop'
|
||||||
|
/>
|
||||||
|
{ groupSummary.profile.name }
|
||||||
|
</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ISpacePreviewProps {
|
||||||
|
space: Room;
|
||||||
|
onJoinButtonClicked(): void;
|
||||||
|
onRejectButtonClicked(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }: ISpacePreviewProps) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
const myMembership = useMyRoomMembership(space);
|
const myMembership = useMyRoomMembership(space);
|
||||||
|
|
||||||
|
@ -270,8 +300,18 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let migratedCommunitySection: JSX.Element;
|
||||||
|
const createContent = space.currentState.getStateEvents(EventType.RoomCreate, "")?.getContent();
|
||||||
|
if (createContent[CreateEventField]) {
|
||||||
|
migratedCommunitySection = <div className="mx_SpaceRoomView_preview_migratedCommunity">
|
||||||
|
{ _t("Created from <Community />", {}, {
|
||||||
|
Community: () => <GroupTile groupId={createContent[CreateEventField]} />,
|
||||||
|
}) }
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
return <div className="mx_SpaceRoomView_preview">
|
return <div className="mx_SpaceRoomView_preview">
|
||||||
<BetaPill onClick={onBetaClick} />
|
{ migratedCommunitySection }
|
||||||
{ inviterSection }
|
{ inviterSection }
|
||||||
<RoomAvatar room={space} height={80} width={80} viewAvatarOnClick={true} />
|
<RoomAvatar room={space} height={80} width={80} viewAvatarOnClick={true} />
|
||||||
<h1 className="mx_SpaceRoomView_preview_name">
|
<h1 className="mx_SpaceRoomView_preview_name">
|
||||||
|
|
|
@ -24,6 +24,8 @@ import { MenuItem } from "../../structures/ContextMenu";
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import GroupFilterOrderStore from "../../../stores/GroupFilterOrderStore";
|
import GroupFilterOrderStore from "../../../stores/GroupFilterOrderStore";
|
||||||
|
import { createSpaceFromCommunity } from "../../../utils/space";
|
||||||
|
import GroupStore from "../../../stores/GroupStore";
|
||||||
|
|
||||||
@replaceableComponent("views.context_menus.TagTileContextMenu")
|
@replaceableComponent("views.context_menus.TagTileContextMenu")
|
||||||
export default class TagTileContextMenu extends React.Component {
|
export default class TagTileContextMenu extends React.Component {
|
||||||
|
@ -49,6 +51,11 @@ export default class TagTileContextMenu extends React.Component {
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_onCreateSpaceClick = () => {
|
||||||
|
createSpaceFromCommunity(this.context, this.props.tag);
|
||||||
|
this.props.onFinished();
|
||||||
|
};
|
||||||
|
|
||||||
_onMoveUp = () => {
|
_onMoveUp = () => {
|
||||||
dis.dispatch(TagOrderActions.moveTag(this.context, this.props.tag, this.props.index - 1));
|
dis.dispatch(TagOrderActions.moveTag(this.context, this.props.tag, this.props.index - 1));
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
|
@ -77,6 +84,16 @@ export default class TagTileContextMenu extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let createSpaceOption;
|
||||||
|
if (GroupStore.isUserPrivileged(this.props.tag)) {
|
||||||
|
createSpaceOption = <>
|
||||||
|
<hr className="mx_TagTileContextMenu_separator" role="separator" />
|
||||||
|
<MenuItem className="mx_TagTileContextMenu_item mx_TagTileContextMenu_createSpace" onClick={this._onCreateSpaceClick}>
|
||||||
|
{ _t("Create Space") }
|
||||||
|
</MenuItem>
|
||||||
|
</>;
|
||||||
|
}
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
<MenuItem className="mx_TagTileContextMenu_item mx_TagTileContextMenu_viewCommunity" onClick={this._onViewCommunityClick}>
|
<MenuItem className="mx_TagTileContextMenu_item mx_TagTileContextMenu_viewCommunity" onClick={this._onViewCommunityClick}>
|
||||||
{ _t('View Community') }
|
{ _t('View Community') }
|
||||||
|
@ -88,6 +105,7 @@ export default class TagTileContextMenu extends React.Component {
|
||||||
<MenuItem className="mx_TagTileContextMenu_item mx_TagTileContextMenu_hideCommunity" onClick={this._onRemoveClick}>
|
<MenuItem className="mx_TagTileContextMenu_item mx_TagTileContextMenu_hideCommunity" onClick={this._onRemoveClick}>
|
||||||
{ _t("Unpin") }
|
{ _t("Unpin") }
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
{ createSpaceOption }
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
338
src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx
Normal file
338
src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx
Normal file
|
@ -0,0 +1,338 @@
|
||||||
|
/*
|
||||||
|
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, useRef, useState } from "react";
|
||||||
|
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
|
||||||
|
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||||
|
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
import BaseDialog from "./BaseDialog";
|
||||||
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
|
import { createSpace, SpaceCreateForm } from "../spaces/SpaceCreateMenu";
|
||||||
|
import JoinRuleDropdown from "../elements/JoinRuleDropdown";
|
||||||
|
import Field from "../elements/Field";
|
||||||
|
import RoomAliasField from "../elements/RoomAliasField";
|
||||||
|
import { GroupMember } from "../right_panel/UserInfo";
|
||||||
|
import { parseMembersResponse, parseRoomsResponse } from "../../../stores/GroupStore";
|
||||||
|
import { calculateRoomVia, makeRoomPermalink } from "../../../utils/permalinks/Permalinks";
|
||||||
|
import { useAsyncMemo } from "../../../hooks/useAsyncMemo";
|
||||||
|
import Spinner from "../elements/Spinner";
|
||||||
|
import { mediaFromMxc } from "../../../customisations/Media";
|
||||||
|
import SpaceStore from "../../../stores/SpaceStore";
|
||||||
|
import Modal from "../../../Modal";
|
||||||
|
import InfoDialog from "./InfoDialog";
|
||||||
|
import dis from "../../../dispatcher/dispatcher";
|
||||||
|
import { Action } from "../../../dispatcher/actions";
|
||||||
|
import { UserTab } from "./UserSettingsDialog";
|
||||||
|
import TagOrderActions from "../../../actions/TagOrderActions";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
matrixClient: MatrixClient;
|
||||||
|
groupId: string;
|
||||||
|
onFinished(spaceId?: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CreateEventField = "io.element.migrated_from_community";
|
||||||
|
|
||||||
|
interface IGroupRoom {
|
||||||
|
displayname: string;
|
||||||
|
name?: string;
|
||||||
|
roomId: string;
|
||||||
|
canonicalAlias?: string;
|
||||||
|
avatarUrl?: string;
|
||||||
|
topic?: string;
|
||||||
|
numJoinedMembers?: number;
|
||||||
|
worldReadable?: boolean;
|
||||||
|
guestCanJoin?: boolean;
|
||||||
|
isPublic?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* eslint-disable camelcase */
|
||||||
|
export interface IGroupSummary {
|
||||||
|
profile: {
|
||||||
|
avatar_url?: string;
|
||||||
|
is_openly_joinable?: boolean;
|
||||||
|
is_public?: boolean;
|
||||||
|
long_description: string;
|
||||||
|
name: string;
|
||||||
|
short_description: string;
|
||||||
|
};
|
||||||
|
rooms_section: {
|
||||||
|
rooms: unknown[];
|
||||||
|
categories: Record<string, unknown>;
|
||||||
|
total_room_count_estimate: number;
|
||||||
|
};
|
||||||
|
user: {
|
||||||
|
is_privileged: boolean;
|
||||||
|
is_public: boolean;
|
||||||
|
is_publicised: boolean;
|
||||||
|
membership: string;
|
||||||
|
};
|
||||||
|
users_section: {
|
||||||
|
users: unknown[];
|
||||||
|
roles: Record<string, unknown>;
|
||||||
|
total_user_count_estimate: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/* eslint-enable camelcase */
|
||||||
|
|
||||||
|
const CreateSpaceFromCommunityDialog: React.FC<IProps> = ({ matrixClient: cli, groupId, onFinished }) => {
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string>(null);
|
||||||
|
const [busy, setBusy] = useState(false);
|
||||||
|
|
||||||
|
const [avatar, setAvatar] = useState<File>(null); // undefined means to remove avatar
|
||||||
|
const [name, setName] = useState("");
|
||||||
|
const spaceNameField = useRef<Field>();
|
||||||
|
const [alias, setAlias] = useState("#" + groupId.substring(1, groupId.indexOf(":")) + ":" + cli.getDomain());
|
||||||
|
const spaceAliasField = useRef<RoomAliasField>();
|
||||||
|
const [topic, setTopic] = useState("");
|
||||||
|
const [joinRule, setJoinRule] = useState<JoinRule>(JoinRule.Public);
|
||||||
|
|
||||||
|
const groupSummary = useAsyncMemo<IGroupSummary>(() => cli.getGroupSummary(groupId), [groupId]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (groupSummary) {
|
||||||
|
setName(groupSummary.profile.name || "");
|
||||||
|
setTopic(groupSummary.profile.short_description || "");
|
||||||
|
setJoinRule(groupSummary.profile.is_openly_joinable ? JoinRule.Public : JoinRule.Invite);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [groupSummary]);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <Spinner />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCreateSpaceClick = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (busy) return;
|
||||||
|
|
||||||
|
setError(null);
|
||||||
|
setBusy(true);
|
||||||
|
|
||||||
|
// require & validate the space name field
|
||||||
|
if (!await spaceNameField.current.validate({ allowEmpty: false })) {
|
||||||
|
setBusy(false);
|
||||||
|
spaceNameField.current.focus();
|
||||||
|
spaceNameField.current.validate({ allowEmpty: false, focused: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// validate the space name alias field but do not require it
|
||||||
|
if (joinRule === JoinRule.Public && !await spaceAliasField.current.validate({ allowEmpty: true })) {
|
||||||
|
setBusy(false);
|
||||||
|
spaceAliasField.current.focus();
|
||||||
|
spaceAliasField.current.validate({ allowEmpty: true, focused: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [rooms, members, invitedMembers] = await Promise.all([
|
||||||
|
cli.getGroupRooms(groupId).then(parseRoomsResponse) as Promise<IGroupRoom[]>,
|
||||||
|
cli.getGroupUsers(groupId).then(parseMembersResponse) as Promise<GroupMember[]>,
|
||||||
|
cli.getGroupInvitedUsers(groupId).then(parseMembersResponse) as Promise<GroupMember[]>,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const viaMap = new Map<string, string[]>();
|
||||||
|
for (const { roomId, canonicalAlias } of rooms) {
|
||||||
|
const room = cli.getRoom(roomId);
|
||||||
|
if (room) {
|
||||||
|
viaMap.set(roomId, calculateRoomVia(room));
|
||||||
|
} else if (canonicalAlias) {
|
||||||
|
try {
|
||||||
|
const { servers } = await cli.getRoomIdForAlias(canonicalAlias);
|
||||||
|
viaMap.set(roomId, servers);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Failed to resolve alias during community migration", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!viaMap.get(roomId)?.length) {
|
||||||
|
// XXX: lets guess the via, this might end up being incorrect.
|
||||||
|
const str = canonicalAlias || roomId;
|
||||||
|
viaMap.set(roomId, [str.substring(1, str.indexOf(":"))]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const spaceAvatar = avatar !== undefined ? avatar : groupSummary.profile.avatar_url;
|
||||||
|
const roomId = await createSpace(name, joinRule === JoinRule.Public, alias, topic, spaceAvatar, {
|
||||||
|
creation_content: {
|
||||||
|
[CreateEventField]: groupId,
|
||||||
|
},
|
||||||
|
initial_state: rooms.map(({ roomId }) => ({
|
||||||
|
type: EventType.SpaceChild,
|
||||||
|
state_key: roomId,
|
||||||
|
content: {
|
||||||
|
via: viaMap.get(roomId) || [],
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
invite: [...members, ...invitedMembers].map(m => m.userId).filter(m => m !== cli.getUserId()),
|
||||||
|
}, {
|
||||||
|
andView: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// eagerly remove it from the community panel
|
||||||
|
dis.dispatch(TagOrderActions.removeTag(cli, groupId));
|
||||||
|
|
||||||
|
// don't bother awaiting this, as we don't hugely care if it fails
|
||||||
|
cli.setGroupProfile(groupId, {
|
||||||
|
...groupSummary.profile,
|
||||||
|
long_description: `<a href="${makeRoomPermalink(roomId)}"><h1>` +
|
||||||
|
_t("This community has been upgraded into a Space") + `</h1></a><br />`
|
||||||
|
+ groupSummary.profile.long_description,
|
||||||
|
} as IGroupSummary["profile"]).catch(e => {
|
||||||
|
console.warn("Failed to update community profile during migration", e);
|
||||||
|
});
|
||||||
|
|
||||||
|
onFinished(roomId);
|
||||||
|
|
||||||
|
const onSpaceClick = () => {
|
||||||
|
dis.dispatch({
|
||||||
|
action: "view_room",
|
||||||
|
room_id: roomId,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPreferencesClick = () => {
|
||||||
|
dis.dispatch({
|
||||||
|
action: Action.ViewUserSettings,
|
||||||
|
initialTabId: UserTab.Preferences,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let spacesDisabledCopy;
|
||||||
|
if (!SpaceStore.spacesEnabled) {
|
||||||
|
spacesDisabledCopy = _t("To view Spaces, hide communities in <a>Preferences</a>", {}, {
|
||||||
|
a: sub => <AccessibleButton onClick={onPreferencesClick} kind="link">{ sub }</AccessibleButton>,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Modal.createDialog(InfoDialog, {
|
||||||
|
title: _t("Space created"),
|
||||||
|
description: <>
|
||||||
|
<div className="mx_CreateSpaceFromCommunityDialog_SuccessInfoDialog_checkmark" />
|
||||||
|
<p>
|
||||||
|
{ _t("<SpaceName/> has been made and everyone who was a part of the community has " +
|
||||||
|
"been invited to it.", {}, {
|
||||||
|
SpaceName: () => <AccessibleButton onClick={onSpaceClick} kind="link">
|
||||||
|
{ name }
|
||||||
|
</AccessibleButton>,
|
||||||
|
}) }
|
||||||
|
|
||||||
|
{ spacesDisabledCopy }
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{ _t("To create a Space from another community, just pick the community in Preferences.") }
|
||||||
|
</p>
|
||||||
|
</>,
|
||||||
|
button: _t("Preferences"),
|
||||||
|
onFinished: (openPreferences: boolean) => {
|
||||||
|
if (openPreferences) {
|
||||||
|
onPreferencesClick();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}, "mx_CreateSpaceFromCommunityDialog_SuccessInfoDialog");
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
setError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
setBusy(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
let footer;
|
||||||
|
if (error) {
|
||||||
|
footer = <>
|
||||||
|
<img src={require("../../../../res/img/element-icons/warning-badge.svg")} height="24" width="24" alt="" />
|
||||||
|
|
||||||
|
<span className="mx_CreateSpaceFromCommunityDialog_error">
|
||||||
|
<div className="mx_CreateSpaceFromCommunityDialog_errorHeading">{ _t("Failed to migrate community") }</div>
|
||||||
|
<div className="mx_CreateSpaceFromCommunityDialog_errorCaption">{ _t("Try again") }</div>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<AccessibleButton className="mx_CreateSpaceFromCommunityDialog_retryButton" onClick={onCreateSpaceClick}>
|
||||||
|
{ _t("Retry") }
|
||||||
|
</AccessibleButton>
|
||||||
|
</>;
|
||||||
|
} else {
|
||||||
|
footer = <>
|
||||||
|
<AccessibleButton kind="primary_outline" disabled={busy} onClick={() => onFinished()}>
|
||||||
|
{ _t("Cancel") }
|
||||||
|
</AccessibleButton>
|
||||||
|
<AccessibleButton kind="primary" disabled={busy} onClick={onCreateSpaceClick}>
|
||||||
|
{ busy ? _t("Creating...") : _t("Create Space") }
|
||||||
|
</AccessibleButton>
|
||||||
|
</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <BaseDialog
|
||||||
|
title={_t("Create Space from community")}
|
||||||
|
className="mx_CreateSpaceFromCommunityDialog"
|
||||||
|
onFinished={onFinished}
|
||||||
|
fixedWidth={false}
|
||||||
|
>
|
||||||
|
<div className="mx_CreateSpaceFromCommunityDialog_content">
|
||||||
|
<p>
|
||||||
|
{ _t("Spaces are the new version of communities - with new features coming.") }
|
||||||
|
|
||||||
|
{ _t("All rooms will automatically be automatically added, a link to the Space will be " +
|
||||||
|
"added to your old community description and all community members will be invited.") }
|
||||||
|
</p>
|
||||||
|
<p className="mx_CreateSpaceFromCommunityDialog_flairNotice">
|
||||||
|
{ _t("Flair won't be available in Spaces for the foreseeable future.") }
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<SpaceCreateForm
|
||||||
|
busy={busy}
|
||||||
|
onSubmit={onCreateSpaceClick}
|
||||||
|
avatarUrl={groupSummary.profile.avatar_url
|
||||||
|
? mediaFromMxc(groupSummary.profile.avatar_url).getThumbnailOfSourceHttp(80, 80, "crop")
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
setAvatar={setAvatar}
|
||||||
|
name={name}
|
||||||
|
setName={setName}
|
||||||
|
nameFieldRef={spaceNameField}
|
||||||
|
topic={topic}
|
||||||
|
setTopic={setTopic}
|
||||||
|
alias={alias}
|
||||||
|
setAlias={setAlias}
|
||||||
|
showAliasField={joinRule === JoinRule.Public}
|
||||||
|
aliasFieldRef={spaceAliasField}
|
||||||
|
>
|
||||||
|
<p>{ _t("This description will be shown to people when they view your space") }</p>
|
||||||
|
<JoinRuleDropdown
|
||||||
|
label={_t("Space visibility")}
|
||||||
|
labelInvite={_t("Private space (invite only)")}
|
||||||
|
labelPublic={_t("Public space")}
|
||||||
|
value={joinRule}
|
||||||
|
onChange={setJoinRule}
|
||||||
|
/>
|
||||||
|
<p>{ joinRule === JoinRule.Public
|
||||||
|
? _t("Open space for anyone, best for communities")
|
||||||
|
: _t("Invite only, best for yourself or teams")
|
||||||
|
}</p>
|
||||||
|
</SpaceCreateForm>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mx_CreateSpaceFromCommunityDialog_footer">
|
||||||
|
{ footer }
|
||||||
|
</div>
|
||||||
|
</BaseDialog>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateSpaceFromCommunityDialog;
|
||||||
|
|
|
@ -16,8 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React, { useRef, useState } from "react";
|
import React, { useRef, useState } from "react";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { JoinRule, Preset } from "matrix-js-sdk/src/@types/partials";
|
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
|
||||||
import { RoomType } from "matrix-js-sdk/src/@types/event";
|
|
||||||
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import BaseDialog from "./BaseDialog";
|
import BaseDialog from "./BaseDialog";
|
||||||
|
@ -27,8 +26,7 @@ import { BetaPill } from "../beta/BetaCard";
|
||||||
import Field from "../elements/Field";
|
import Field from "../elements/Field";
|
||||||
import RoomAliasField from "../elements/RoomAliasField";
|
import RoomAliasField from "../elements/RoomAliasField";
|
||||||
import SpaceStore from "../../../stores/SpaceStore";
|
import SpaceStore from "../../../stores/SpaceStore";
|
||||||
import { SpaceCreateForm } from "../spaces/SpaceCreateMenu";
|
import { createSpace, SpaceCreateForm } from "../spaces/SpaceCreateMenu";
|
||||||
import createRoom from "../../../createRoom";
|
|
||||||
import { SubspaceSelector } from "./AddExistingToSpaceDialog";
|
import { SubspaceSelector } from "./AddExistingToSpaceDialog";
|
||||||
import JoinRuleDropdown from "../elements/JoinRuleDropdown";
|
import JoinRuleDropdown from "../elements/JoinRuleDropdown";
|
||||||
|
|
||||||
|
@ -81,28 +79,7 @@ const CreateSubspaceDialog: React.FC<IProps> = ({ space, onAddExistingSpaceClick
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await createRoom({
|
await createSpace(name, joinRule === JoinRule.Public, alias, topic, avatar, {}, { parentSpace });
|
||||||
createOpts: {
|
|
||||||
preset: joinRule === JoinRule.Public ? Preset.PublicChat : Preset.PrivateChat,
|
|
||||||
name,
|
|
||||||
power_level_content_override: {
|
|
||||||
// Only allow Admins to write to the timeline to prevent hidden sync spam
|
|
||||||
events_default: 100,
|
|
||||||
...joinRule === JoinRule.Public ? { invite: 0 } : {},
|
|
||||||
},
|
|
||||||
room_alias_name: joinRule === JoinRule.Public && alias
|
|
||||||
? alias.substr(1, alias.indexOf(":") - 1)
|
|
||||||
: undefined,
|
|
||||||
topic,
|
|
||||||
},
|
|
||||||
avatar,
|
|
||||||
roomType: RoomType.Space,
|
|
||||||
parentSpace,
|
|
||||||
spinner: false,
|
|
||||||
encryption: false,
|
|
||||||
andView: true,
|
|
||||||
inlineErrors: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
onFinished(true);
|
onFinished(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -114,7 +114,7 @@ export default class UserSettingsDialog extends React.Component<IProps, IState>
|
||||||
UserTab.Preferences,
|
UserTab.Preferences,
|
||||||
_td("Preferences"),
|
_td("Preferences"),
|
||||||
"mx_UserSettingsDialog_preferencesIcon",
|
"mx_UserSettingsDialog_preferencesIcon",
|
||||||
<PreferencesUserSettingsTab />,
|
<PreferencesUserSettingsTab closeSettingsFn={this.props.onFinished} />,
|
||||||
));
|
));
|
||||||
|
|
||||||
if (SettingsStore.getValue(UIFeature.Voip)) {
|
if (SettingsStore.getValue(UIFeature.Voip)) {
|
||||||
|
|
|
@ -851,7 +851,7 @@ const RoomAdminToolsContainer: React.FC<IBaseRoomProps> = ({
|
||||||
return <div />;
|
return <div />;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface GroupMember {
|
export interface GroupMember {
|
||||||
userId: string;
|
userId: string;
|
||||||
displayname?: string; // XXX: GroupMember objects are inconsistent :((
|
displayname?: string; // XXX: GroupMember objects are inconsistent :((
|
||||||
avatarUrl?: string;
|
avatarUrl?: string;
|
||||||
|
|
|
@ -15,7 +15,9 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
|
import { EventType } from 'matrix-js-sdk/src/@types/event';
|
||||||
|
|
||||||
import { _t } from "../../../../../languageHandler";
|
import { _t } from "../../../../../languageHandler";
|
||||||
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
|
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
|
||||||
import SettingsStore from "../../../../../settings/SettingsStore";
|
import SettingsStore from "../../../../../settings/SettingsStore";
|
||||||
|
@ -27,6 +29,18 @@ import SettingsFlag from '../../../elements/SettingsFlag';
|
||||||
import * as KeyboardShortcuts from "../../../../../accessibility/KeyboardShortcuts";
|
import * as KeyboardShortcuts from "../../../../../accessibility/KeyboardShortcuts";
|
||||||
import AccessibleButton from "../../../elements/AccessibleButton";
|
import AccessibleButton from "../../../elements/AccessibleButton";
|
||||||
import SpaceStore from "../../../../../stores/SpaceStore";
|
import SpaceStore from "../../../../../stores/SpaceStore";
|
||||||
|
import GroupAvatar from "../../../avatars/GroupAvatar";
|
||||||
|
import dis from "../../../../../dispatcher/dispatcher";
|
||||||
|
import GroupActions from "../../../../../actions/GroupActions";
|
||||||
|
import MatrixClientContext from "../../../../../contexts/MatrixClientContext";
|
||||||
|
import { useDispatcher } from "../../../../../hooks/useDispatcher";
|
||||||
|
import { CreateEventField, IGroupSummary } from "../../../dialogs/CreateSpaceFromCommunityDialog";
|
||||||
|
import { createSpaceFromCommunity } from "../../../../../utils/space";
|
||||||
|
import Spinner from "../../../elements/Spinner";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
closeSettingsFn(success: boolean): void;
|
||||||
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
autoLaunch: boolean;
|
autoLaunch: boolean;
|
||||||
|
@ -42,8 +56,86 @@ interface IState {
|
||||||
readMarkerOutOfViewThresholdMs: string;
|
readMarkerOutOfViewThresholdMs: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Community = IGroupSummary & {
|
||||||
|
groupId: string;
|
||||||
|
spaceId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CommunityMigrator = ({ onFinished }) => {
|
||||||
|
const cli = useContext(MatrixClientContext);
|
||||||
|
const [communities, setCommunities] = useState<Community[]>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
dis.dispatch(GroupActions.fetchJoinedGroups(cli));
|
||||||
|
}, [cli]);
|
||||||
|
useDispatcher(dis, async payload => {
|
||||||
|
if (payload.action === "GroupActions.fetchJoinedGroups.success") {
|
||||||
|
const communities: Community[] = [];
|
||||||
|
|
||||||
|
const migratedSpaceMap = new Map(cli.getRooms().map(room => {
|
||||||
|
const createContent = room.currentState.getStateEvents(EventType.RoomCreate, "")?.getContent();
|
||||||
|
if (createContent?.[CreateEventField]) {
|
||||||
|
return [createContent[CreateEventField], room.roomId] as [string, string];
|
||||||
|
}
|
||||||
|
}).filter(Boolean));
|
||||||
|
|
||||||
|
for (const groupId of payload.result.groups) {
|
||||||
|
const summary = await cli.getGroupSummary(groupId) as IGroupSummary;
|
||||||
|
if (summary.user.is_privileged) {
|
||||||
|
communities.push({
|
||||||
|
...summary,
|
||||||
|
groupId,
|
||||||
|
spaceId: migratedSpaceMap.get(groupId),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setCommunities(communities);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!communities) {
|
||||||
|
return <Spinner />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="mx_PreferencesUserSettingsTab_CommunityMigrator">
|
||||||
|
{ communities.map(community => (
|
||||||
|
<div key={community.groupId}>
|
||||||
|
<GroupAvatar
|
||||||
|
groupId={community.groupId}
|
||||||
|
groupAvatarUrl={community.profile.avatar_url}
|
||||||
|
groupName={community.profile.name}
|
||||||
|
width={32}
|
||||||
|
height={32}
|
||||||
|
/>
|
||||||
|
{ community.profile.name }
|
||||||
|
<AccessibleButton
|
||||||
|
kind="primary_outline"
|
||||||
|
onClick={() => {
|
||||||
|
if (community.spaceId) {
|
||||||
|
dis.dispatch({
|
||||||
|
action: "view_room",
|
||||||
|
room_id: community.spaceId,
|
||||||
|
});
|
||||||
|
onFinished();
|
||||||
|
} else {
|
||||||
|
createSpaceFromCommunity(cli, community.groupId).then(([spaceId]) => {
|
||||||
|
if (spaceId) {
|
||||||
|
community.spaceId = spaceId;
|
||||||
|
setCommunities([...communities]); // force component re-render
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ community.spaceId ? _t("Open Space") : _t("Create Space") }
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
)) }
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
@replaceableComponent("views.settings.tabs.user.PreferencesUserSettingsTab")
|
@replaceableComponent("views.settings.tabs.user.PreferencesUserSettingsTab")
|
||||||
export default class PreferencesUserSettingsTab extends React.Component<{}, IState> {
|
export default class PreferencesUserSettingsTab extends React.Component<IProps, IState> {
|
||||||
static ROOM_LIST_SETTINGS = [
|
static ROOM_LIST_SETTINGS = [
|
||||||
'breadcrumbs',
|
'breadcrumbs',
|
||||||
];
|
];
|
||||||
|
@ -52,6 +144,10 @@ export default class PreferencesUserSettingsTab extends React.Component<{}, ISta
|
||||||
"Spaces.allRoomsInHome",
|
"Spaces.allRoomsInHome",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
static COMMUNITIES_SETTINGS = [
|
||||||
|
// TODO: part of delabsing move the toggle here - https://github.com/vector-im/element-web/issues/18088
|
||||||
|
];
|
||||||
|
|
||||||
static KEYBINDINGS_SETTINGS = [
|
static KEYBINDINGS_SETTINGS = [
|
||||||
'ctrlFForSearch',
|
'ctrlFForSearch',
|
||||||
];
|
];
|
||||||
|
@ -241,6 +337,20 @@ export default class PreferencesUserSettingsTab extends React.Component<{}, ISta
|
||||||
{ this.renderGroup(PreferencesUserSettingsTab.SPACES_SETTINGS) }
|
{ this.renderGroup(PreferencesUserSettingsTab.SPACES_SETTINGS) }
|
||||||
</div> }
|
</div> }
|
||||||
|
|
||||||
|
<div className="mx_SettingsTab_section">
|
||||||
|
<span className="mx_SettingsTab_subheading">{ _t("Communities") }</span>
|
||||||
|
<p>{ _t("Communities have been archived to make way for Spaces but you can convert your " +
|
||||||
|
"communities into Spaces below.") }</p>
|
||||||
|
<span>{ _t("Convert your Communities to Spaces") }</span>
|
||||||
|
<p>{ _t("Converting will ensure your conversations get the latest features.") }</p>
|
||||||
|
<details>
|
||||||
|
<summary>{ _t("Show my Communities") }</summary>
|
||||||
|
<p>{ _t("If a community isn't shown you may not have permission to convert it.") }</p>
|
||||||
|
<CommunityMigrator onFinished={this.props.closeSettingsFn} />
|
||||||
|
</details>
|
||||||
|
{ this.renderGroup(PreferencesUserSettingsTab.COMMUNITIES_SETTINGS) }
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="mx_SettingsTab_section">
|
<div className="mx_SettingsTab_section">
|
||||||
<span className="mx_SettingsTab_subheading">{ _t("Keyboard shortcuts") }</span>
|
<span className="mx_SettingsTab_subheading">{ _t("Keyboard shortcuts") }</span>
|
||||||
<AccessibleButton className="mx_SettingsFlag" onClick={KeyboardShortcuts.toggleDialog}>
|
<AccessibleButton className="mx_SettingsFlag" onClick={KeyboardShortcuts.toggleDialog}>
|
||||||
|
|
|
@ -18,23 +18,57 @@ import React, { ComponentProps, RefObject, SyntheticEvent, useContext, useRef, u
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { RoomType } from "matrix-js-sdk/src/@types/event";
|
import { RoomType } from "matrix-js-sdk/src/@types/event";
|
||||||
import FocusLock from "react-focus-lock";
|
import FocusLock from "react-focus-lock";
|
||||||
|
import { HistoryVisibility, Preset } from "matrix-js-sdk/src/@types/partials";
|
||||||
|
import { ICreateRoomOpts } from "matrix-js-sdk/src/@types/requests";
|
||||||
|
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||||
import { ChevronFace, ContextMenu } from "../../structures/ContextMenu";
|
import { ChevronFace, ContextMenu } from "../../structures/ContextMenu";
|
||||||
import createRoom from "../../../createRoom";
|
import createRoom, { IOpts as ICreateOpts } from "../../../createRoom";
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import SpaceBasicSettings, { SpaceAvatar } from "./SpaceBasicSettings";
|
import SpaceBasicSettings, { SpaceAvatar } from "./SpaceBasicSettings";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import Field from "../elements/Field";
|
import Field from "../elements/Field";
|
||||||
import withValidation from "../elements/Validation";
|
import withValidation from "../elements/Validation";
|
||||||
import { HistoryVisibility, Preset } from "matrix-js-sdk/src/@types/partials";
|
|
||||||
import RoomAliasField from "../elements/RoomAliasField";
|
import RoomAliasField from "../elements/RoomAliasField";
|
||||||
import SdkConfig from "../../../SdkConfig";
|
import SdkConfig from "../../../SdkConfig";
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
import GenericFeatureFeedbackDialog from "../dialogs/GenericFeatureFeedbackDialog";
|
import GenericFeatureFeedbackDialog from "../dialogs/GenericFeatureFeedbackDialog";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
|
||||||
|
export const createSpace = async (
|
||||||
|
name: string,
|
||||||
|
isPublic: boolean,
|
||||||
|
alias?: string,
|
||||||
|
topic?: string,
|
||||||
|
avatar?: string | File,
|
||||||
|
createOpts: Partial<ICreateRoomOpts> = {},
|
||||||
|
otherOpts: Partial<Omit<ICreateOpts, "createOpts">> = {},
|
||||||
|
) => {
|
||||||
|
return createRoom({
|
||||||
|
createOpts: {
|
||||||
|
name,
|
||||||
|
preset: isPublic ? Preset.PublicChat : Preset.PrivateChat,
|
||||||
|
power_level_content_override: {
|
||||||
|
// Only allow Admins to write to the timeline to prevent hidden sync spam
|
||||||
|
events_default: 100,
|
||||||
|
...isPublic ? { invite: 0 } : {},
|
||||||
|
},
|
||||||
|
room_alias_name: isPublic && alias ? alias.substr(1, alias.indexOf(":") - 1) : undefined,
|
||||||
|
topic,
|
||||||
|
...createOpts,
|
||||||
|
},
|
||||||
|
avatar,
|
||||||
|
roomType: RoomType.Space,
|
||||||
|
historyVisibility: isPublic ? HistoryVisibility.WorldReadable : HistoryVisibility.Invited,
|
||||||
|
spinner: false,
|
||||||
|
encryption: false,
|
||||||
|
andView: true,
|
||||||
|
inlineErrors: true,
|
||||||
|
...otherOpts,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const SpaceCreateMenuType = ({ title, description, className, onClick }) => {
|
const SpaceCreateMenuType = ({ title, description, className, onClick }) => {
|
||||||
return (
|
return (
|
||||||
<AccessibleButton className={classNames("mx_SpaceCreateMenuType", className)} onClick={onClick}>
|
<AccessibleButton className={classNames("mx_SpaceCreateMenuType", className)} onClick={onClick}>
|
||||||
|
@ -92,7 +126,7 @@ export const SpaceFeedbackPrompt = ({ onClick }: { onClick?: () => void }) => {
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type BProps = Pick<ComponentProps<typeof SpaceBasicSettings>, "setAvatar" | "name" | "setName" | "topic" | "setTopic">;
|
type BProps = Omit<ComponentProps<typeof SpaceBasicSettings>, "nameDisabled" | "topicDisabled" | "avatarDisabled">;
|
||||||
interface ISpaceCreateFormProps extends BProps {
|
interface ISpaceCreateFormProps extends BProps {
|
||||||
busy: boolean;
|
busy: boolean;
|
||||||
alias: string;
|
alias: string;
|
||||||
|
@ -106,6 +140,7 @@ interface ISpaceCreateFormProps extends BProps {
|
||||||
export const SpaceCreateForm: React.FC<ISpaceCreateFormProps> = ({
|
export const SpaceCreateForm: React.FC<ISpaceCreateFormProps> = ({
|
||||||
busy,
|
busy,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
|
avatarUrl,
|
||||||
setAvatar,
|
setAvatar,
|
||||||
name,
|
name,
|
||||||
setName,
|
setName,
|
||||||
|
@ -122,7 +157,7 @@ export const SpaceCreateForm: React.FC<ISpaceCreateFormProps> = ({
|
||||||
const domain = cli.getDomain();
|
const domain = cli.getDomain();
|
||||||
|
|
||||||
return <form className="mx_SpaceBasicSettings" onSubmit={onSubmit}>
|
return <form className="mx_SpaceBasicSettings" onSubmit={onSubmit}>
|
||||||
<SpaceAvatar setAvatar={setAvatar} avatarDisabled={busy} />
|
<SpaceAvatar avatarUrl={avatarUrl} setAvatar={setAvatar} avatarDisabled={busy} />
|
||||||
|
|
||||||
<Field
|
<Field
|
||||||
name="spaceName"
|
name="spaceName"
|
||||||
|
@ -200,30 +235,7 @@ const SpaceCreateMenu = ({ onFinished }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await createRoom({
|
await createSpace(name, visibility === Visibility.Public, alias, topic, avatar);
|
||||||
createOpts: {
|
|
||||||
preset: visibility === Visibility.Public ? Preset.PublicChat : Preset.PrivateChat,
|
|
||||||
name,
|
|
||||||
power_level_content_override: {
|
|
||||||
// Only allow Admins to write to the timeline to prevent hidden sync spam
|
|
||||||
events_default: 100,
|
|
||||||
...visibility === Visibility.Public ? { invite: 0 } : {},
|
|
||||||
},
|
|
||||||
room_alias_name: visibility === Visibility.Public && alias
|
|
||||||
? alias.substr(1, alias.indexOf(":") - 1)
|
|
||||||
: undefined,
|
|
||||||
topic,
|
|
||||||
},
|
|
||||||
avatar,
|
|
||||||
roomType: RoomType.Space,
|
|
||||||
historyVisibility: visibility === Visibility.Public
|
|
||||||
? HistoryVisibility.WorldReadable
|
|
||||||
: HistoryVisibility.Invited,
|
|
||||||
spinner: false,
|
|
||||||
encryption: false,
|
|
||||||
andView: true,
|
|
||||||
inlineErrors: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
onFinished();
|
onFinished();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -1322,12 +1322,20 @@
|
||||||
"If this isn't what you want, please use a different tool to ignore users.": "If this isn't what you want, please use a different tool to ignore users.",
|
"If this isn't what you want, please use a different tool to ignore users.": "If this isn't what you want, please use a different tool to ignore users.",
|
||||||
"Room ID or address of ban list": "Room ID or address of ban list",
|
"Room ID or address of ban list": "Room ID or address of ban list",
|
||||||
"Subscribe": "Subscribe",
|
"Subscribe": "Subscribe",
|
||||||
|
"Open Space": "Open Space",
|
||||||
|
"Create Space": "Create Space",
|
||||||
"Start automatically after system login": "Start automatically after system login",
|
"Start automatically after system login": "Start automatically after system login",
|
||||||
"Warn before quitting": "Warn before quitting",
|
"Warn before quitting": "Warn before quitting",
|
||||||
"Always show the window menu bar": "Always show the window menu bar",
|
"Always show the window menu bar": "Always show the window menu bar",
|
||||||
"Show tray icon and minimize window to it on close": "Show tray icon and minimize window to it on close",
|
"Show tray icon and minimize window to it on close": "Show tray icon and minimize window to it on close",
|
||||||
"Preferences": "Preferences",
|
"Preferences": "Preferences",
|
||||||
"Room list": "Room list",
|
"Room list": "Room list",
|
||||||
|
"Communities": "Communities",
|
||||||
|
"Communities have been archived to make way for Spaces but you can convert your communities into Spaces below.": "Communities have been archived to make way for Spaces but you can convert your communities into Spaces below.",
|
||||||
|
"Convert your Communities to Spaces": "Convert your Communities to Spaces",
|
||||||
|
"Converting will ensure your conversations get the latest features.": "Converting will ensure your conversations get the latest features.",
|
||||||
|
"Show my Communities": "Show my Communities",
|
||||||
|
"If a community isn't shown you may not have permission to convert it.": "If a community isn't shown you may not have permission to convert it.",
|
||||||
"Keyboard shortcuts": "Keyboard shortcuts",
|
"Keyboard shortcuts": "Keyboard shortcuts",
|
||||||
"To view all keyboard shortcuts, click here.": "To view all keyboard shortcuts, click here.",
|
"To view all keyboard shortcuts, click here.": "To view all keyboard shortcuts, click here.",
|
||||||
"Displaying time": "Displaying time",
|
"Displaying time": "Displaying time",
|
||||||
|
@ -2218,13 +2226,24 @@
|
||||||
"Visible to space members": "Visible to space members",
|
"Visible to space members": "Visible to space members",
|
||||||
"Block anyone not part of %(serverName)s from ever joining this room.": "Block anyone not part of %(serverName)s from ever joining this room.",
|
"Block anyone not part of %(serverName)s from ever joining this room.": "Block anyone not part of %(serverName)s from ever joining this room.",
|
||||||
"Create Room": "Create Room",
|
"Create Room": "Create Room",
|
||||||
|
"This community has been upgraded into a Space": "This community has been upgraded into a Space",
|
||||||
|
"To view Spaces, hide communities in <a>Preferences</a>": "To view Spaces, hide communities in <a>Preferences</a>",
|
||||||
|
"Space created": "Space created",
|
||||||
|
"<SpaceName/> has been made and everyone who was a part of the community has been invited to it.": "<SpaceName/> has been made and everyone who was a part of the community has been invited to it.",
|
||||||
|
"To create a Space from another community, just pick the community in Preferences.": "To create a Space from another community, just pick the community in Preferences.",
|
||||||
|
"Failed to migrate community": "Failed to migrate community",
|
||||||
|
"Create Space from community": "Create Space from community",
|
||||||
|
"Spaces are the new version of communities - with new features coming.": "Spaces are the new version of communities - with new features coming.",
|
||||||
|
"All rooms will automatically be automatically added, a link to the Space will be added to your old community description and all community members will be invited.": "All rooms will automatically be automatically added, a link to the Space will be added to your old community description and all community members will be invited.",
|
||||||
|
"Flair won't be available in Spaces for the foreseeable future.": "Flair won't be available in Spaces for the foreseeable future.",
|
||||||
|
"This description will be shown to people when they view your space": "This description will be shown to people when they view your space",
|
||||||
|
"Space visibility": "Space visibility",
|
||||||
|
"Private space (invite only)": "Private space (invite only)",
|
||||||
|
"Public space": "Public space",
|
||||||
"Anyone in <SpaceName/> will be able to find and join.": "Anyone in <SpaceName/> will be able to find and join.",
|
"Anyone in <SpaceName/> will be able to find and join.": "Anyone in <SpaceName/> will be able to find and join.",
|
||||||
"Anyone will be able to find and join this space, not just members of <SpaceName/>.": "Anyone will be able to find and join this space, not just members of <SpaceName/>.",
|
"Anyone will be able to find and join this space, not just members of <SpaceName/>.": "Anyone will be able to find and join this space, not just members of <SpaceName/>.",
|
||||||
"Only people invited will be able to find and join this space.": "Only people invited will be able to find and join this space.",
|
"Only people invited will be able to find and join this space.": "Only people invited will be able to find and join this space.",
|
||||||
"Add a space to a space you manage.": "Add a space to a space you manage.",
|
"Add a space to a space you manage.": "Add a space to a space you manage.",
|
||||||
"Space visibility": "Space visibility",
|
|
||||||
"Private space (invite only)": "Private space (invite only)",
|
|
||||||
"Public space": "Public space",
|
|
||||||
"Want to add an existing space instead?": "Want to add an existing space instead?",
|
"Want to add an existing space instead?": "Want to add an existing space instead?",
|
||||||
"Adding...": "Adding...",
|
"Adding...": "Adding...",
|
||||||
"Sign out": "Sign out",
|
"Sign out": "Sign out",
|
||||||
|
@ -2676,7 +2695,6 @@
|
||||||
"You must join the room to see its files": "You must join the room to see its files",
|
"You must join the room to see its files": "You must join the room to see its files",
|
||||||
"No files visible in this room": "No files visible in this room",
|
"No files visible in this room": "No files visible in this room",
|
||||||
"Attach files from chat or just drag and drop them anywhere in a room.": "Attach files from chat or just drag and drop them anywhere in a room.",
|
"Attach files from chat or just drag and drop them anywhere in a room.": "Attach files from chat or just drag and drop them anywhere in a room.",
|
||||||
"Communities": "Communities",
|
|
||||||
"Create community": "Create community",
|
"Create community": "Create community",
|
||||||
"<h1>HTML for your community's page</h1>\n<p>\n Use the long description to introduce new members to the community, or distribute\n some important <a href=\"foo\">links</a>\n</p>\n<p>\n You can even add images with Matrix URLs <img src=\"mxc://url\" />\n</p>\n": "<h1>HTML for your community's page</h1>\n<p>\n Use the long description to introduce new members to the community, or distribute\n some important <a href=\"foo\">links</a>\n</p>\n<p>\n You can even add images with Matrix URLs <img src=\"mxc://url\" />\n</p>\n",
|
"<h1>HTML for your community's page</h1>\n<p>\n Use the long description to introduce new members to the community, or distribute\n some important <a href=\"foo\">links</a>\n</p>\n<p>\n You can even add images with Matrix URLs <img src=\"mxc://url\" />\n</p>\n": "<h1>HTML for your community's page</h1>\n<p>\n Use the long description to introduce new members to the community, or distribute\n some important <a href=\"foo\">links</a>\n</p>\n<p>\n You can even add images with Matrix URLs <img src=\"mxc://url\" />\n</p>\n",
|
||||||
"Add rooms to the community summary": "Add rooms to the community summary",
|
"Add rooms to the community summary": "Add rooms to the community summary",
|
||||||
|
@ -2704,6 +2722,10 @@
|
||||||
"Community Settings": "Community Settings",
|
"Community Settings": "Community Settings",
|
||||||
"Want more than a community? <a>Get your own server</a>": "Want more than a community? <a>Get your own server</a>",
|
"Want more than a community? <a>Get your own server</a>": "Want more than a community? <a>Get your own server</a>",
|
||||||
"Changes made to your community <bold1>name</bold1> and <bold2>avatar</bold2> might not be seen by other users for up to 30 minutes.": "Changes made to your community <bold1>name</bold1> and <bold2>avatar</bold2> might not be seen by other users for up to 30 minutes.",
|
"Changes made to your community <bold1>name</bold1> and <bold2>avatar</bold2> might not be seen by other users for up to 30 minutes.": "Changes made to your community <bold1>name</bold1> and <bold2>avatar</bold2> might not be seen by other users for up to 30 minutes.",
|
||||||
|
"Communities can now be made into Spaces": "Communities can now be made into Spaces",
|
||||||
|
"Spaces are a new way to make a community, with new features coming.": "Spaces are a new way to make a community, with new features coming.",
|
||||||
|
"Ask the admins of this community to make it into a Space and keep a look out for the invite.": "Ask the admins of this community to make it into a Space and keep a look out for the invite.",
|
||||||
|
"Communities won't receive further updates.": "Communities won't receive further updates.",
|
||||||
"These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.",
|
"These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.",
|
||||||
"Featured Rooms:": "Featured Rooms:",
|
"Featured Rooms:": "Featured Rooms:",
|
||||||
"Featured Users:": "Featured Users:",
|
"Featured Users:": "Featured Users:",
|
||||||
|
@ -2835,6 +2857,7 @@
|
||||||
"To view %(spaceName)s, turn on the <a>Spaces beta</a>": "To view %(spaceName)s, turn on the <a>Spaces beta</a>",
|
"To view %(spaceName)s, turn on the <a>Spaces beta</a>": "To view %(spaceName)s, turn on the <a>Spaces beta</a>",
|
||||||
"To join %(spaceName)s, turn on the <a>Spaces beta</a>": "To join %(spaceName)s, turn on the <a>Spaces beta</a>",
|
"To join %(spaceName)s, turn on the <a>Spaces beta</a>": "To join %(spaceName)s, turn on the <a>Spaces beta</a>",
|
||||||
"To view %(spaceName)s, you need an invite": "To view %(spaceName)s, you need an invite",
|
"To view %(spaceName)s, you need an invite": "To view %(spaceName)s, you need an invite",
|
||||||
|
"Created from <Community />": "Created from <Community />",
|
||||||
"Welcome to <name/>": "Welcome to <name/>",
|
"Welcome to <name/>": "Welcome to <name/>",
|
||||||
"Random": "Random",
|
"Random": "Random",
|
||||||
"Support": "Support",
|
"Support": "Support",
|
||||||
|
|
|
@ -14,12 +14,14 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
import { Store } from 'flux/utils';
|
import { Store } from 'flux/utils';
|
||||||
|
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||||
import dis from '../dispatcher/dispatcher';
|
import dis from '../dispatcher/dispatcher';
|
||||||
import GroupStore from './GroupStore';
|
import GroupStore from './GroupStore';
|
||||||
import Analytics from '../Analytics';
|
import Analytics from '../Analytics';
|
||||||
import * as RoomNotifs from "../RoomNotifs";
|
import * as RoomNotifs from "../RoomNotifs";
|
||||||
import { MatrixClientPeg } from '../MatrixClientPeg';
|
import { MatrixClientPeg } from '../MatrixClientPeg';
|
||||||
import SettingsStore from "../settings/SettingsStore";
|
import SettingsStore from "../settings/SettingsStore";
|
||||||
|
import { CreateEventField } from "../components/views/dialogs/CreateSpaceFromCommunityDialog";
|
||||||
|
|
||||||
const INITIAL_STATE = {
|
const INITIAL_STATE = {
|
||||||
orderedTags: null,
|
orderedTags: null,
|
||||||
|
@ -235,8 +237,12 @@ class GroupFilterOrderStore extends Store {
|
||||||
(t) => (t[0] !== '+' || groupIds.includes(t)) && !removedTags.has(t),
|
(t) => (t[0] !== '+' || groupIds.includes(t)) && !removedTags.has(t),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const cli = MatrixClientPeg.get();
|
||||||
|
const migratedCommunities = new Set(cli.getRooms().map(r => {
|
||||||
|
return r.currentState.getStateEvents(EventType.RoomCreate, "")?.getContent()[CreateEventField];
|
||||||
|
}).filter(Boolean));
|
||||||
const groupIdsToAdd = groupIds.filter(
|
const groupIdsToAdd = groupIds.filter(
|
||||||
(groupId) => !tags.includes(groupId) && !removedTags.has(groupId),
|
(groupId) => !tags.includes(groupId) && !removedTags.has(groupId) && !migratedCommunities.has(groupId),
|
||||||
);
|
);
|
||||||
|
|
||||||
return tagsToKeep.concat(groupIdsToAdd);
|
return tagsToKeep.concat(groupIdsToAdd);
|
||||||
|
|
|
@ -20,11 +20,11 @@ import FlairStore from './FlairStore';
|
||||||
import { MatrixClientPeg } from '../MatrixClientPeg';
|
import { MatrixClientPeg } from '../MatrixClientPeg';
|
||||||
import dis from '../dispatcher/dispatcher';
|
import dis from '../dispatcher/dispatcher';
|
||||||
|
|
||||||
function parseMembersResponse(response) {
|
export function parseMembersResponse(response) {
|
||||||
return response.chunk.map((apiMember) => groupMemberFromApiObject(apiMember));
|
return response.chunk.map((apiMember) => groupMemberFromApiObject(apiMember));
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseRoomsResponse(response) {
|
export function parseRoomsResponse(response) {
|
||||||
return response.chunk.map((apiRoom) => groupRoomFromApiObject(apiRoom));
|
return response.chunk.map((apiRoom) => groupRoomFromApiObject(apiRoom));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||||
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
|
|
||||||
import { calculateRoomVia } from "./permalinks/Permalinks";
|
import { calculateRoomVia } from "./permalinks/Permalinks";
|
||||||
import Modal from "../Modal";
|
import Modal from "../Modal";
|
||||||
|
@ -37,6 +38,7 @@ import { leaveRoomBehaviour } from "./membership";
|
||||||
import Spinner from "../components/views/elements/Spinner";
|
import Spinner from "../components/views/elements/Spinner";
|
||||||
import dis from "../dispatcher/dispatcher";
|
import dis from "../dispatcher/dispatcher";
|
||||||
import LeaveSpaceDialog from "../components/views/dialogs/LeaveSpaceDialog";
|
import LeaveSpaceDialog from "../components/views/dialogs/LeaveSpaceDialog";
|
||||||
|
import CreateSpaceFromCommunityDialog from "../components/views/dialogs/CreateSpaceFromCommunityDialog";
|
||||||
|
|
||||||
export const shouldShowSpaceSettings = (space: Room) => {
|
export const shouldShowSpaceSettings = (space: Room) => {
|
||||||
const userId = space.client.getUserId();
|
const userId = space.client.getUserId();
|
||||||
|
@ -173,3 +175,10 @@ export const leaveSpace = (space: Room) => {
|
||||||
},
|
},
|
||||||
}, "mx_LeaveSpaceDialog_wrapper");
|
}, "mx_LeaveSpaceDialog_wrapper");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createSpaceFromCommunity = (cli: MatrixClient, groupId: string): Promise<[string?]> => {
|
||||||
|
return Modal.createTrackedDialog('Create Space', 'from community', CreateSpaceFromCommunityDialog, {
|
||||||
|
matrixClient: cli,
|
||||||
|
groupId,
|
||||||
|
}, "mx_CreateSpaceFromCommunityDialog_wrapper").finished as Promise<[string?]>;
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue