Merge branch 'develop' into gsouquet/threaded-messaging-2349

This commit is contained in:
Germain Souquet 2021-08-23 17:31:37 +01:00
commit edd4d42e7f
14 changed files with 84 additions and 39 deletions

View file

@ -62,6 +62,9 @@ $SpaceRoomViewInnerWidth: 428px;
} }
.mx_SpaceRoomView { .mx_SpaceRoomView {
overflow-y: auto;
flex: 1;
.mx_MainSplit > div:first-child { .mx_MainSplit > div:first-child {
padding: 80px 60px; padding: 80px 60px;
flex-grow: 1; flex-grow: 1;
@ -248,6 +251,7 @@ $SpaceRoomViewInnerWidth: 428px;
.mx_SpaceRoomView_landing { .mx_SpaceRoomView_landing {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-width: 0;
> .mx_BaseAvatar_image, > .mx_BaseAvatar_image,
> .mx_BaseAvatar > .mx_BaseAvatar_image { > .mx_BaseAvatar > .mx_BaseAvatar_image {

View file

@ -7,13 +7,15 @@
background: $primary-bg-color; background: $primary-bg-color;
border-bottom: none; border-bottom: none;
border-radius: 8px 8px 0 0; border-radius: 8px 8px 0 0;
max-height: 50vh; max-height: 35vh;
overflow: auto; overflow: clip;
display: flex;
box-shadow: 0px -16px 32px $composer-shadow-color; box-shadow: 0px -16px 32px $composer-shadow-color;
} }
.mx_Autocomplete_ProviderSection { .mx_Autocomplete_ProviderSection {
border-bottom: 1px solid $primary-hairline-color; border-bottom: 1px solid $primary-hairline-color;
width: 100%;
} }
/* a "block" completion takes up a whole line */ /* a "block" completion takes up a whole line */
@ -59,8 +61,8 @@
.mx_Autocomplete_Completion_container_pill { .mx_Autocomplete_Completion_container_pill {
margin: 12px; margin: 12px;
display: flex; height: 100%;
flex-direction: column; overflow-y: scroll;
} }
.mx_Autocomplete_Completion_container_truncate { .mx_Autocomplete_Completion_container_truncate {

View file

@ -105,6 +105,8 @@ limitations under the License.
.mx_ReplyTile .mx_SenderProfile { .mx_ReplyTile .mx_SenderProfile {
display: block; display: block;
top: unset;
left: unset;
} }
.mx_ReactionsRow { .mx_ReactionsRow {
@ -188,8 +190,6 @@ limitations under the License.
} }
.mx_ReplyThread { .mx_ReplyThread {
margin: 0 calc(-1 * var(--gutterSize));
.mx_EventTile_reply { .mx_EventTile_reply {
max-width: 90%; max-width: 90%;
padding: 0; padding: 0;
@ -223,11 +223,6 @@ limitations under the License.
margin-left: -9px; margin-left: -9px;
} }
.mx_ReplyThread {
border-left-width: 2px;
border-left-color: $eventbubble-reply-color;
}
/* Special layout scenario for "Unable To Decrypt (UTD)" events */ /* Special layout scenario for "Unable To Decrypt (UTD)" events */
&.mx_EventTile_bad > .mx_EventTile_line { &.mx_EventTile_bad > .mx_EventTile_line {
display: grid; display: grid;

View file

@ -67,7 +67,7 @@ export default class EmojiProvider extends AutocompleteProvider {
constructor() { constructor() {
super(EMOJI_REGEX); super(EMOJI_REGEX);
this.matcher = new QueryMatcher<ISortedEmoji>(SORTED_EMOJI, { this.matcher = new QueryMatcher<ISortedEmoji>(SORTED_EMOJI, {
keys: ['emoji.emoticon'], keys: [],
funcs: [o => o.emoji.shortcodes.map(s => `:${s}:`)], funcs: [o => o.emoji.shortcodes.map(s => `:${s}:`)],
// For matching against ascii equivalents // For matching against ascii equivalents
shouldMatchWordsOnly: false, shouldMatchWordsOnly: false,
@ -91,7 +91,8 @@ export default class EmojiProvider extends AutocompleteProvider {
let completions = []; let completions = [];
const { command, range } = this.getCurrentCommand(query, selection); const { command, range } = this.getCurrentCommand(query, selection);
if (command) {
if (command && command[0].length > 2) {
const matchedString = command[0]; const matchedString = command[0];
completions = this.matcher.match(matchedString, limit); completions = this.matcher.match(matchedString, limit);

View file

@ -628,9 +628,12 @@ class LoggedInView extends React.Component<IProps, IState> {
break; break;
} }
const wrapperClasses = classNames({
'mx_MatrixChat_wrapper': true,
'mx_MatrixChat_useCompactLayout': this.state.useCompactLayout,
});
const bodyClasses = classNames({ const bodyClasses = classNames({
'mx_MatrixChat': true, 'mx_MatrixChat': true,
'mx_MatrixChat_useCompactLayout': this.state.useCompactLayout,
'mx_MatrixChat--with-avatar': this.state.backgroundImage, 'mx_MatrixChat--with-avatar': this.state.backgroundImage,
}); });
@ -645,7 +648,7 @@ class LoggedInView extends React.Component<IProps, IState> {
<div <div
onPaste={this.onPaste} onPaste={this.onPaste}
onKeyDown={this.onReactKeyDown} onKeyDown={this.onReactKeyDown}
className='mx_MatrixChat_wrapper' className={wrapperClasses}
aria-hidden={this.props.hideToSRUsers} aria-hidden={this.props.hideToSRUsers}
> >
<ToastContainer /> <ToastContainer />

View file

@ -215,7 +215,7 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
{ {
a: (sub) => <a a: (sub) => <a
target="_blank" target="_blank"
href="https://github.com/vector-im/element-web/issues/new" href="https://github.com/vector-im/element-web/issues/new/choose"
> >
{ sub } { sub }
</a>, </a>,

View file

@ -28,7 +28,7 @@ import StyledRadioGroup from "../elements/StyledRadioGroup";
const existingIssuesUrl = "https://github.com/vector-im/element-web/issues" + const existingIssuesUrl = "https://github.com/vector-im/element-web/issues" +
"?q=is%3Aopen+is%3Aissue+sort%3Areactions-%2B1-desc"; "?q=is%3Aopen+is%3Aissue+sort%3Areactions-%2B1-desc";
const newIssueUrl = "https://github.com/vector-im/element-web/issues/new"; const newIssueUrl = "https://github.com/vector-im/element-web/issues/new/choose";
export default (props) => { export default (props) => {
const [rating, setRating] = useState(""); const [rating, setRating] = useState("");

View file

@ -77,7 +77,7 @@ export default class ErrorBoundary extends React.PureComponent<{}, IState> {
render() { render() {
if (this.state.error) { if (this.state.error) {
const newIssueUrl = "https://github.com/vector-im/element-web/issues/new"; const newIssueUrl = "https://github.com/vector-im/element-web/issues/new/choose";
let bugReportSection; let bugReportSection;
if (SdkConfig.get().bug_report_endpoint_url) { if (SdkConfig.get().bug_report_endpoint_url) {

View file

@ -19,14 +19,12 @@ import MAudioBody from "./MAudioBody";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import MVoiceMessageBody from "./MVoiceMessageBody"; import MVoiceMessageBody from "./MVoiceMessageBody";
import { IBodyProps } from "./IBodyProps"; import { IBodyProps } from "./IBodyProps";
import { isVoiceMessage } from "../../../utils/EventUtils";
@replaceableComponent("views.messages.MVoiceOrAudioBody") @replaceableComponent("views.messages.MVoiceOrAudioBody")
export default class MVoiceOrAudioBody extends React.PureComponent<IBodyProps> { export default class MVoiceOrAudioBody extends React.PureComponent<IBodyProps> {
public render() { public render() {
// MSC2516 is a legacy identifier. See https://github.com/matrix-org/matrix-doc/pull/3245 if (isVoiceMessage(this.props.mxEvent)) {
const isVoiceMessage = !!this.props.mxEvent.getContent()['org.matrix.msc2516.voice']
|| !!this.props.mxEvent.getContent()['org.matrix.msc3245.voice'];
if (isVoiceMessage) {
return <MVoiceMessageBody {...this.props} />; return <MVoiceMessageBody {...this.props} />;
} else { } else {
return <MAudioBody {...this.props} />; return <MAudioBody {...this.props} />;

View file

@ -36,6 +36,7 @@ import { showSpaceInvite } from "../../../utils/space";
import { privateShouldBeEncrypted } from "../../../createRoom"; import { privateShouldBeEncrypted } from "../../../createRoom";
import EventTileBubble from "../messages/EventTileBubble"; import EventTileBubble from "../messages/EventTileBubble";
import { ROOM_SECURITY_TAB } from "../dialogs/RoomSettingsDialog"; import { ROOM_SECURITY_TAB } from "../dialogs/RoomSettingsDialog";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
function hasExpectedEncryptionSettings(matrixClient: MatrixClient, room: Room): boolean { function hasExpectedEncryptionSettings(matrixClient: MatrixClient, room: Room): boolean {
const isEncrypted: boolean = matrixClient.isRoomEncrypted(room.roomId); const isEncrypted: boolean = matrixClient.isRoomEncrypted(room.roomId);
@ -191,11 +192,21 @@ const NewRoomIntro = () => {
}); });
} }
const sub2 = _t( const subText = _t(
"Your private messages are normally encrypted, but this room isn't. "+ "Your private messages are normally encrypted, but this room isn't. "+
"Usually this is due to an unsupported device or method being used, " + "Usually this is due to an unsupported device or method being used, " +
"like email invites. <a>Enable encryption in settings.</a>", {}, "like email invites.",
{ a: sub => <a onClick={openRoomSettings} href="#">{ sub }</a> }, );
let subButton;
if (room.currentState.mayClientSendStateEvent(EventType.RoomEncryption, MatrixClientPeg.get())) {
subButton = (
<a onClick={openRoomSettings} href="#"> { _t("Enable encryption in settings.") }</a>
);
}
const subtitle = (
<span> { subText } { subButton } </span>
); );
return <div className="mx_NewRoomIntro"> return <div className="mx_NewRoomIntro">
@ -204,7 +215,7 @@ const NewRoomIntro = () => {
<EventTileBubble <EventTileBubble
className="mx_cryptoEvent mx_cryptoEvent_icon_warning" className="mx_cryptoEvent mx_cryptoEvent_icon_warning"
title={_t("End-to-end encryption isn't enabled")} title={_t("End-to-end encryption isn't enabled")}
subtitle={sub2} subtitle={subtitle}
/> />
) } ) }

View file

@ -25,8 +25,9 @@ import MImageReplyBody from "../messages/MImageReplyBody";
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import { EventType, MsgType } from 'matrix-js-sdk/src/@types/event'; import { EventType, MsgType } from 'matrix-js-sdk/src/@types/event';
import { replaceableComponent } from '../../../utils/replaceableComponent'; import { replaceableComponent } from '../../../utils/replaceableComponent';
import { getEventDisplayInfo } from '../../../utils/EventUtils'; import { getEventDisplayInfo, isVoiceMessage } from '../../../utils/EventUtils';
import MFileBody from "../messages/MFileBody"; import MFileBody from "../messages/MFileBody";
import MVoiceMessageBody from "../messages/MVoiceMessageBody";
interface IProps { interface IProps {
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
@ -95,7 +96,7 @@ export default class ReplyTile extends React.PureComponent<IProps> {
const msgType = mxEvent.getContent().msgtype; const msgType = mxEvent.getContent().msgtype;
const evType = mxEvent.getType() as EventType; const evType = mxEvent.getType() as EventType;
const { tileHandler, isInfoMessage } = getEventDisplayInfo(this.props.mxEvent); const { tileHandler, isInfoMessage } = getEventDisplayInfo(mxEvent);
// This shouldn't happen: the caller should check we support this type // This shouldn't happen: the caller should check we support this type
// before trying to instantiate us // before trying to instantiate us
if (!tileHandler) { if (!tileHandler) {
@ -109,14 +110,14 @@ export default class ReplyTile extends React.PureComponent<IProps> {
const EventTileType = sdk.getComponent(tileHandler); const EventTileType = sdk.getComponent(tileHandler);
const classes = classNames("mx_ReplyTile", { const classes = classNames("mx_ReplyTile", {
mx_ReplyTile_info: isInfoMessage && !this.props.mxEvent.isRedacted(), mx_ReplyTile_info: isInfoMessage && !mxEvent.isRedacted(),
mx_ReplyTile_audio: msgType === MsgType.Audio, mx_ReplyTile_audio: msgType === MsgType.Audio,
mx_ReplyTile_video: msgType === MsgType.Video, mx_ReplyTile_video: msgType === MsgType.Video,
}); });
let permalink = "#"; let permalink = "#";
if (this.props.permalinkCreator) { if (this.props.permalinkCreator) {
permalink = this.props.permalinkCreator.forEvent(this.props.mxEvent.getId()); permalink = this.props.permalinkCreator.forEvent(mxEvent.getId());
} }
let sender; let sender;
@ -129,7 +130,7 @@ export default class ReplyTile extends React.PureComponent<IProps> {
if (needsSenderProfile) { if (needsSenderProfile) {
sender = <SenderProfile sender = <SenderProfile
mxEvent={this.props.mxEvent} mxEvent={mxEvent}
enableFlair={false} enableFlair={false}
/>; />;
} }
@ -137,7 +138,7 @@ export default class ReplyTile extends React.PureComponent<IProps> {
const msgtypeOverrides = { const msgtypeOverrides = {
[MsgType.Image]: MImageReplyBody, [MsgType.Image]: MImageReplyBody,
// Override audio and video body with file body. We also hide the download/decrypt button using CSS // Override audio and video body with file body. We also hide the download/decrypt button using CSS
[MsgType.Audio]: MFileBody, [MsgType.Audio]: isVoiceMessage(mxEvent) ? MVoiceMessageBody : MFileBody,
[MsgType.Video]: MFileBody, [MsgType.Video]: MFileBody,
}; };
const evOverrides = { const evOverrides = {
@ -151,14 +152,14 @@ export default class ReplyTile extends React.PureComponent<IProps> {
{ sender } { sender }
<EventTileType <EventTileType
ref="tile" ref="tile"
mxEvent={this.props.mxEvent} mxEvent={mxEvent}
highlights={this.props.highlights} highlights={this.props.highlights}
highlightLink={this.props.highlightLink} highlightLink={this.props.highlightLink}
onHeightChanged={this.props.onHeightChanged} onHeightChanged={this.props.onHeightChanged}
showUrlPreview={false} showUrlPreview={false}
overrideBodyTypes={msgtypeOverrides} overrideBodyTypes={msgtypeOverrides}
overrideEventTypes={evOverrides} overrideEventTypes={evOverrides}
replacingEventId={this.props.mxEvent.replacingEventId()} replacingEventId={mxEvent.replacingEventId()}
maxImageHeight={96} /> maxImageHeight={96} />
</a> </a>
</div> </div>

View file

@ -25,6 +25,8 @@ import InteractiveAuthDialog from '../dialogs/InteractiveAuthDialog';
import ConfirmDestroyCrossSigningDialog from '../dialogs/security/ConfirmDestroyCrossSigningDialog'; import ConfirmDestroyCrossSigningDialog from '../dialogs/security/ConfirmDestroyCrossSigningDialog';
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import { MatrixEvent } from 'matrix-js-sdk/src'; import { MatrixEvent } from 'matrix-js-sdk/src';
import SetupEncryptionDialog from '../dialogs/security/SetupEncryptionDialog';
import { accessSecretStorage } from '../../../SecurityManager';
interface IState { interface IState {
error?: Error; error?: Error;
@ -72,7 +74,16 @@ export default class CrossSigningPanel extends React.PureComponent<{}, IState> {
}; };
private onBootstrapClick = () => { private onBootstrapClick = () => {
this.bootstrapCrossSigning({ forceReset: false }); if (this.state.crossSigningPrivateKeysInStorage) {
Modal.createTrackedDialog(
"Verify session", "Verify session", SetupEncryptionDialog,
{}, null, /* priority = */ false, /* static = */ true,
);
} else {
// Trigger the flow to set up secure backup, which is what this will do when in
// the appropriate state.
accessSecretStorage();
}
}; };
private onStatusChanged = () => { private onStatusChanged = () => {
@ -176,10 +187,14 @@ export default class CrossSigningPanel extends React.PureComponent<{}, IState> {
summarisedStatus = <p>{ _t( summarisedStatus = <p>{ _t(
"Your homeserver does not support cross-signing.", "Your homeserver does not support cross-signing.",
) }</p>; ) }</p>;
} else if (crossSigningReady) { } else if (crossSigningReady && crossSigningPrivateKeysInStorage) {
summarisedStatus = <p> { _t( summarisedStatus = <p> { _t(
"Cross-signing is ready for use.", "Cross-signing is ready for use.",
) }</p>; ) }</p>;
} else if (crossSigningReady && !crossSigningPrivateKeysInStorage) {
summarisedStatus = <p> { _t(
"Cross-signing is ready but keys are not backed up.",
) }</p>;
} else if (crossSigningPrivateKeysInStorage) { } else if (crossSigningPrivateKeysInStorage) {
summarisedStatus = <p>{ _t( summarisedStatus = <p>{ _t(
"Your account has a cross-signing identity in secret storage, " + "Your account has a cross-signing identity in secret storage, " +
@ -210,9 +225,13 @@ export default class CrossSigningPanel extends React.PureComponent<{}, IState> {
// TODO: determine how better to expose this to users in addition to prompts at login/toast // TODO: determine how better to expose this to users in addition to prompts at login/toast
if (!keysExistEverywhere && homeserverSupportsCrossSigning) { if (!keysExistEverywhere && homeserverSupportsCrossSigning) {
let buttonCaption = _t("Set up Secure Backup");
if (crossSigningPrivateKeysInStorage) {
buttonCaption = _t("Verify this session");
}
actions.push( actions.push(
<AccessibleButton key="setup" kind="primary" onClick={this.onBootstrapClick}> <AccessibleButton key="setup" kind="primary" onClick={this.onBootstrapClick}>
{ _t("Set up") } { buttonCaption }
</AccessibleButton>, </AccessibleButton>,
); );
} }

View file

@ -1103,9 +1103,9 @@
"Change Password": "Change Password", "Change Password": "Change Password",
"Your homeserver does not support cross-signing.": "Your homeserver does not support cross-signing.", "Your homeserver does not support cross-signing.": "Your homeserver does not support cross-signing.",
"Cross-signing is ready for use.": "Cross-signing is ready for use.", "Cross-signing is ready for use.": "Cross-signing is ready for use.",
"Cross-signing is ready but keys are not backed up.": "Cross-signing is ready but keys are not backed up.",
"Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.", "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.",
"Cross-signing is not set up.": "Cross-signing is not set up.", "Cross-signing is not set up.": "Cross-signing is not set up.",
"Set up": "Set up",
"Reset": "Reset", "Reset": "Reset",
"Cross-signing public keys:": "Cross-signing public keys:", "Cross-signing public keys:": "Cross-signing public keys:",
"in memory": "in memory", "in memory": "in memory",
@ -1203,6 +1203,7 @@
"Algorithm:": "Algorithm:", "Algorithm:": "Algorithm:",
"Your keys are <b>not being backed up from this session</b>.": "Your keys are <b>not being backed up from this session</b>.", "Your keys are <b>not being backed up from this session</b>.": "Your keys are <b>not being backed up from this session</b>.",
"Back up your keys before signing out to avoid losing them.": "Back up your keys before signing out to avoid losing them.", "Back up your keys before signing out to avoid losing them.": "Back up your keys before signing out to avoid losing them.",
"Set up": "Set up",
"well formed": "well formed", "well formed": "well formed",
"unexpected type": "unexpected type", "unexpected type": "unexpected type",
"Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Security Key.": "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Security Key.", "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Security Key.": "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Security Key.",
@ -1572,7 +1573,8 @@
"Invite to just this room": "Invite to just this room", "Invite to just this room": "Invite to just this room",
"Add a photo, so people can easily spot your room.": "Add a photo, so people can easily spot your room.", "Add a photo, so people can easily spot your room.": "Add a photo, so people can easily spot your room.",
"This is the start of <roomName/>.": "This is the start of <roomName/>.", "This is the start of <roomName/>.": "This is the start of <roomName/>.",
"Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. <a>Enable encryption in settings.</a>": "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. <a>Enable encryption in settings.</a>", "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.": "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.",
"Enable encryption in settings.": "Enable encryption in settings.",
"End-to-end encryption isn't enabled": "End-to-end encryption isn't enabled", "End-to-end encryption isn't enabled": "End-to-end encryption isn't enabled",
"Unpin": "Unpin", "Unpin": "Unpin",
"View message": "View message", "View message": "View message",

View file

@ -144,3 +144,12 @@ export function getEventDisplayInfo(mxEvent: MatrixEvent): {
return { tileHandler, isInfoMessage, isBubbleMessage, isLeftAlignedBubbleMessage }; return { tileHandler, isInfoMessage, isBubbleMessage, isLeftAlignedBubbleMessage };
} }
export function isVoiceMessage(mxEvent: MatrixEvent): boolean {
const content = mxEvent.getContent();
// MSC2516 is a legacy identifier. See https://github.com/matrix-org/matrix-doc/pull/3245
return (
!!content['org.matrix.msc2516.voice'] ||
!!content['org.matrix.msc3245.voice']
);
}