Merge branch 'develop' of https://github.com/matrix-org/matrix-react-sdk into t3chguy/dpsah/6785.2

This commit is contained in:
Michael Telatynski 2020-09-03 16:07:37 +01:00
commit 368571bcff
31 changed files with 747 additions and 119 deletions

View file

@ -25,8 +25,7 @@ import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import {Key} from "../../../Keyboard";
import {privateShouldBeEncrypted} from "../../../createRoom";
import TagOrderStore from "../../../stores/TagOrderStore";
import GroupStore from "../../../stores/GroupStore";
import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore";
export default createReactClass({
displayName: 'CreateRoomDialog',
@ -72,8 +71,8 @@ export default createReactClass({
opts.encryption = this.state.isEncrypted;
}
if (TagOrderStore.getSelectedPrototypeTag()) {
opts.associatedWithCommunity = TagOrderStore.getSelectedPrototypeTag();
if (CommunityPrototypeStore.instance.getSelectedCommunityId()) {
opts.associatedWithCommunity = CommunityPrototypeStore.instance.getSelectedCommunityId();
}
return opts;
@ -198,7 +197,7 @@ export default createReactClass({
"Private rooms can be found and joined by invitation only. Public rooms can be " +
"found and joined by anyone.",
)}</p>;
if (TagOrderStore.getSelectedPrototypeTag()) {
if (CommunityPrototypeStore.instance.getSelectedCommunityId()) {
publicPrivateLabel = <p>{_t(
"Private rooms can be found and joined by invitation only. Public rooms can be " +
"found and joined by anyone in this community.",
@ -239,9 +238,8 @@ export default createReactClass({
}
let title = this.state.isPublic ? _t('Create a public room') : _t('Create a private room');
if (TagOrderStore.getSelectedPrototypeTag()) {
const summary = GroupStore.getSummary(TagOrderStore.getSelectedPrototypeTag());
const name = summary?.profile?.name || TagOrderStore.getSelectedPrototypeTag();
if (CommunityPrototypeStore.instance.getSelectedCommunityId()) {
const name = CommunityPrototypeStore.instance.getSelectedCommunityName();
title = _t("Create a room in %(communityName)s", {communityName: name});
}
return (

View file

@ -0,0 +1,167 @@
/*
Copyright 2020 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, { ChangeEvent } from 'react';
import BaseDialog from "./BaseDialog";
import { _t } from "../../../languageHandler";
import { IDialogProps } from "./IDialogProps";
import Field from "../elements/Field";
import AccessibleButton from "../elements/AccessibleButton";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore";
import FlairStore from "../../../stores/FlairStore";
interface IProps extends IDialogProps {
communityId: string;
}
interface IState {
name: string;
error: string;
busy: boolean;
currentAvatarUrl: string;
avatarFile: File;
avatarPreview: string;
}
// XXX: This is a lot of duplication from the create dialog, just in a different shape
export default class EditCommunityPrototypeDialog extends React.PureComponent<IProps, IState> {
private avatarUploadRef: React.RefObject<HTMLInputElement> = React.createRef();
constructor(props: IProps) {
super(props);
const profile = CommunityPrototypeStore.instance.getCommunityProfile(props.communityId);
this.state = {
name: profile?.name || "",
error: null,
busy: false,
avatarFile: null,
avatarPreview: null,
currentAvatarUrl: profile?.avatarUrl,
};
}
private onNameChange = (ev: ChangeEvent<HTMLInputElement>) => {
this.setState({name: ev.target.value});
};
private onSubmit = async (ev) => {
ev.preventDefault();
ev.stopPropagation();
if (this.state.busy) return;
// We'll create the community now to see if it's taken, leaving it active in
// the background for the user to look at while they invite people.
this.setState({busy: true});
try {
let avatarUrl = this.state.currentAvatarUrl || ""; // must be a string for synapse to accept it
if (this.state.avatarFile) {
avatarUrl = await MatrixClientPeg.get().uploadContent(this.state.avatarFile);
}
await MatrixClientPeg.get().setGroupProfile(this.props.communityId, {
name: this.state.name,
avatar_url: avatarUrl,
});
// ask the flair store to update the profile too
await FlairStore.refreshGroupProfile(MatrixClientPeg.get(), this.props.communityId);
// we did it, so close the dialog
this.props.onFinished(true);
} catch (e) {
console.error(e);
this.setState({
busy: false,
error: _t("There was an error updating your community. The server is unable to process your request."),
});
}
};
private onAvatarChanged = (e: ChangeEvent<HTMLInputElement>) => {
if (!e.target.files || !e.target.files.length) {
this.setState({avatarFile: null});
} else {
this.setState({busy: true});
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = (ev: ProgressEvent<FileReader>) => {
this.setState({avatarFile: file, busy: false, avatarPreview: ev.target.result as string});
};
reader.readAsDataURL(file);
}
};
private onChangeAvatar = () => {
if (this.avatarUploadRef.current) this.avatarUploadRef.current.click();
};
public render() {
let preview = <img src={this.state.avatarPreview} className="mx_EditCommunityPrototypeDialog_avatar" />;
if (!this.state.avatarPreview) {
if (this.state.currentAvatarUrl) {
const url = MatrixClientPeg.get().mxcUrlToHttp(this.state.currentAvatarUrl);
preview = <img src={url} className="mx_EditCommunityPrototypeDialog_avatar" />;
} else {
preview = <div className="mx_EditCommunityPrototypeDialog_placeholderAvatar" />
}
}
return (
<BaseDialog
className="mx_EditCommunityPrototypeDialog"
onFinished={this.props.onFinished}
title={_t("Update community")}
>
<form onSubmit={this.onSubmit}>
<div className="mx_Dialog_content">
<div className="mx_EditCommunityPrototypeDialog_rowName">
<Field
value={this.state.name}
onChange={this.onNameChange}
placeholder={_t("Enter name")}
label={_t("Enter name")}
/>
</div>
<div className="mx_EditCommunityPrototypeDialog_rowAvatar">
<input
type="file" style={{display: "none"}}
ref={this.avatarUploadRef} accept="image/*"
onChange={this.onAvatarChanged}
/>
<AccessibleButton
onClick={this.onChangeAvatar}
className="mx_EditCommunityPrototypeDialog_avatarContainer"
>{preview}</AccessibleButton>
<div className="mx_EditCommunityPrototypeDialog_tip">
<b>{_t("Add image (optional)")}</b>
<span>
{_t("An image will help people identify your community.")}
</span>
</div>
</div>
<AccessibleButton kind="primary" onClick={this.onSubmit} disabled={this.state.busy}>
{_t("Save")}
</AccessibleButton>
</div>
</form>
</BaseDialog>
);
}
}

View file

@ -32,11 +32,12 @@ import IdentityAuthClient from "../../../IdentityAuthClient";
import Modal from "../../../Modal";
import {humanizeTime} from "../../../utils/humanize";
import createRoom, {canEncryptToAllUsers, privateShouldBeEncrypted} from "../../../createRoom";
import {inviteMultipleToRoom} from "../../../RoomInvite";
import {inviteMultipleToRoom, showCommunityInviteDialog} from "../../../RoomInvite";
import {Key} from "../../../Keyboard";
import {Action} from "../../../dispatcher/actions";
import {DefaultTagID} from "../../../stores/room-list/models";
import RoomListStore from "../../../stores/room-list/RoomListStore";
import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore";
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
/* eslint-disable camelcase */
@ -909,12 +910,23 @@ export default class InviteDialog extends React.PureComponent {
this.props.onFinished();
};
_onCommunityInviteClick = (e) => {
this.props.onFinished();
showCommunityInviteDialog(CommunityPrototypeStore.instance.getSelectedCommunityId());
};
_renderSection(kind: "recents"|"suggestions") {
let sourceMembers = kind === 'recents' ? this.state.recents : this.state.suggestions;
let showNum = kind === 'recents' ? this.state.numRecentsShown : this.state.numSuggestionsShown;
const showMoreFn = kind === 'recents' ? this._showMoreRecents.bind(this) : this._showMoreSuggestions.bind(this);
const lastActive = (m) => kind === 'recents' ? m.lastActive : null;
let sectionName = kind === 'recents' ? _t("Recent Conversations") : _t("Suggestions");
let sectionSubname = null;
if (kind === 'suggestions' && CommunityPrototypeStore.instance.getSelectedCommunityId()) {
const communityName = CommunityPrototypeStore.instance.getSelectedCommunityName();
sectionSubname = _t("May include members not in %(communityName)s", {communityName});
}
if (this.props.kind === KIND_INVITE) {
sectionName = kind === 'recents' ? _t("Recently Direct Messaged") : _t("Suggestions");
@ -993,6 +1005,7 @@ export default class InviteDialog extends React.PureComponent {
return (
<div className='mx_InviteDialog_section'>
<h3>{sectionName}</h3>
{sectionSubname ? <p className="mx_InviteDialog_subname">{sectionSubname}</p> : null}
{tiles}
{showMore}
</div>
@ -1083,6 +1096,33 @@ export default class InviteDialog extends React.PureComponent {
return <a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>;
}},
);
if (CommunityPrototypeStore.instance.getSelectedCommunityId()) {
const communityName = CommunityPrototypeStore.instance.getSelectedCommunityName();
helpText = _t(
"Start a conversation with someone using their name, username (like <userId/>) or email address. " +
"This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click " +
"<a>here</a>.",
{communityName}, {
userId: () => {
return (
<a
href={makeUserPermalink(userId)}
rel="noreferrer noopener"
target="_blank"
>{userId}</a>
);
},
a: (sub) => {
return (
<AccessibleButton
kind="link"
onClick={this._onCommunityInviteClick}
>{sub}</AccessibleButton>
);
},
},
);
}
buttonText = _t("Go");
goButtonFn = this._startDm;
} else { // KIND_INVITE

View file

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { debounce } from 'lodash';
import {debounce} from "lodash";
import classNames from 'classnames';
import React from 'react';
import PropTypes from "prop-types";

View file

@ -17,7 +17,7 @@ limitations under the License.
import React, {InputHTMLAttributes, SelectHTMLAttributes, TextareaHTMLAttributes} from 'react';
import classNames from 'classnames';
import * as sdk from '../../../index';
import { debounce } from 'lodash';
import {debounce} from "lodash";
import {IFieldState, IValidationResult} from "./Validation";
// Invoke validation from user input (when typing, etc.) at most once every N ms.

View file

@ -331,8 +331,14 @@ export default class ReplyThread extends React.Component {
{
_t('<a>In reply to</a> <pill>', {}, {
'a': (sub) => <a onClick={this.onQuoteClick} className="mx_ReplyThread_show">{ sub }</a>,
'pill': <Pill type={Pill.TYPE_USER_MENTION} room={room}
url={makeUserPermalink(ev.getSender())} shouldShowPillAvatar={true} />,
'pill': (
<Pill
type={Pill.TYPE_USER_MENTION}
room={room}
url={makeUserPermalink(ev.getSender())}
shouldShowPillAvatar={SettingsStore.getValue("Pill.shouldShowPillAvatar")}
/>
),
})
}
</blockquote>;

View file

@ -17,7 +17,7 @@ limitations under the License.
import React, {createRef, KeyboardEvent} from 'react';
import classNames from 'classnames';
import flatMap from 'lodash/flatMap';
import {flatMap} from "lodash";
import {ICompletion, ISelectionRange, IProviderCompletions} from '../../../autocomplete/Autocompleter';
import {Room} from 'matrix-js-sdk/src/models/room';

View file

@ -27,6 +27,7 @@ import rate_limited_func from "../../../ratelimitedfunc";
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import * as sdk from "../../../index";
import CallHandler from "../../../CallHandler";
import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore";
const INITIAL_LOAD_NUM_MEMBERS = 30;
const INITIAL_LOAD_NUM_INVITED = 5;
@ -464,10 +465,16 @@ export default createReactClass({
}
}
let inviteButtonText = _t("Invite to this room");
const chat = CommunityPrototypeStore.instance.getSelectedCommunityGeneralChat();
if (chat && chat.roomId === this.props.roomId) {
inviteButtonText = _t("Invite to this community");
}
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
inviteButton =
<AccessibleButton className="mx_MemberList_invite" onClick={this.onInviteButtonClick} disabled={!canInvite}>
<span>{ _t('Invite to this room') }</span>
<span>{ inviteButtonText }</span>
</AccessibleButton>;
}

View file

@ -45,7 +45,7 @@ import { arrayFastClone, arrayHasDiff } from "../../../utils/arrays";
import { objectShallowClone, objectWithOnly } from "../../../utils/objects";
import { IconizedContextMenuOption, IconizedContextMenuOptionList } from "../context_menus/IconizedContextMenu";
import AccessibleButton from "../elements/AccessibleButton";
import TagOrderStore from "../../../stores/TagOrderStore";
import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore";
interface IProps {
onKeyDown: (ev: React.KeyboardEvent) => void;
@ -130,7 +130,7 @@ const TAG_AESTHETICS: {
}}
/>
<IconizedContextMenuOption
label={TagOrderStore.getSelectedPrototypeTag()
label={CommunityPrototypeStore.instance.getSelectedCommunityId()
? _t("Explore community rooms")
: _t("Explore public rooms")}
iconClassName="mx_RoomList_iconExplore"

View file

@ -24,6 +24,7 @@ import {makeUserPermalink} from "../../../utils/permalinks/Permalinks";
import BaseAvatar from "../avatars/BaseAvatar";
import AccessibleButton from "../elements/AccessibleButton";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import SettingsStore from "../../../settings/SettingsStore";
@replaceableComponent("views.settings.BridgeTile")
export default class BridgeTile extends React.PureComponent {
@ -56,7 +57,7 @@ export default class BridgeTile extends React.PureComponent {
type={Pill.TYPE_USER_MENTION}
room={this.props.room}
url={makeUserPermalink(content.creator)}
shouldShowPillAvatar={true}
shouldShowPillAvatar={SettingsStore.getValue("Pill.shouldShowPillAvatar")}
/>,
});
}
@ -66,7 +67,7 @@ export default class BridgeTile extends React.PureComponent {
type={Pill.TYPE_USER_MENTION}
room={this.props.room}
url={makeUserPermalink(this.props.ev.getSender())}
shouldShowPillAvatar={true}
shouldShowPillAvatar={SettingsStore.getValue("Pill.shouldShowPillAvatar")}
/>,
});