Merge branch 'develop' into gsouquet/threaded-messaging-2349
commit
edd4d42e7f
res/css
structures
views/rooms
src
autocomplete
components
structures
views
elements
messages
settings
i18n/strings
utils
|
@ -62,6 +62,9 @@ $SpaceRoomViewInnerWidth: 428px;
|
|||
}
|
||||
|
||||
.mx_SpaceRoomView {
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
|
||||
.mx_MainSplit > div:first-child {
|
||||
padding: 80px 60px;
|
||||
flex-grow: 1;
|
||||
|
@ -248,6 +251,7 @@ $SpaceRoomViewInnerWidth: 428px;
|
|||
.mx_SpaceRoomView_landing {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
|
||||
> .mx_BaseAvatar_image,
|
||||
> .mx_BaseAvatar > .mx_BaseAvatar_image {
|
||||
|
|
|
@ -7,13 +7,15 @@
|
|||
background: $primary-bg-color;
|
||||
border-bottom: none;
|
||||
border-radius: 8px 8px 0 0;
|
||||
max-height: 50vh;
|
||||
overflow: auto;
|
||||
max-height: 35vh;
|
||||
overflow: clip;
|
||||
display: flex;
|
||||
box-shadow: 0px -16px 32px $composer-shadow-color;
|
||||
}
|
||||
|
||||
.mx_Autocomplete_ProviderSection {
|
||||
border-bottom: 1px solid $primary-hairline-color;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* a "block" completion takes up a whole line */
|
||||
|
@ -59,8 +61,8 @@
|
|||
|
||||
.mx_Autocomplete_Completion_container_pill {
|
||||
margin: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.mx_Autocomplete_Completion_container_truncate {
|
||||
|
|
|
@ -105,6 +105,8 @@ limitations under the License.
|
|||
|
||||
.mx_ReplyTile .mx_SenderProfile {
|
||||
display: block;
|
||||
top: unset;
|
||||
left: unset;
|
||||
}
|
||||
|
||||
.mx_ReactionsRow {
|
||||
|
@ -188,8 +190,6 @@ limitations under the License.
|
|||
}
|
||||
|
||||
.mx_ReplyThread {
|
||||
margin: 0 calc(-1 * var(--gutterSize));
|
||||
|
||||
.mx_EventTile_reply {
|
||||
max-width: 90%;
|
||||
padding: 0;
|
||||
|
@ -223,11 +223,6 @@ limitations under the License.
|
|||
margin-left: -9px;
|
||||
}
|
||||
|
||||
.mx_ReplyThread {
|
||||
border-left-width: 2px;
|
||||
border-left-color: $eventbubble-reply-color;
|
||||
}
|
||||
|
||||
/* Special layout scenario for "Unable To Decrypt (UTD)" events */
|
||||
&.mx_EventTile_bad > .mx_EventTile_line {
|
||||
display: grid;
|
||||
|
|
|
@ -67,7 +67,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
|||
constructor() {
|
||||
super(EMOJI_REGEX);
|
||||
this.matcher = new QueryMatcher<ISortedEmoji>(SORTED_EMOJI, {
|
||||
keys: ['emoji.emoticon'],
|
||||
keys: [],
|
||||
funcs: [o => o.emoji.shortcodes.map(s => `:${s}:`)],
|
||||
// For matching against ascii equivalents
|
||||
shouldMatchWordsOnly: false,
|
||||
|
@ -91,7 +91,8 @@ export default class EmojiProvider extends AutocompleteProvider {
|
|||
|
||||
let completions = [];
|
||||
const { command, range } = this.getCurrentCommand(query, selection);
|
||||
if (command) {
|
||||
|
||||
if (command && command[0].length > 2) {
|
||||
const matchedString = command[0];
|
||||
completions = this.matcher.match(matchedString, limit);
|
||||
|
||||
|
|
|
@ -628,9 +628,12 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
break;
|
||||
}
|
||||
|
||||
const wrapperClasses = classNames({
|
||||
'mx_MatrixChat_wrapper': true,
|
||||
'mx_MatrixChat_useCompactLayout': this.state.useCompactLayout,
|
||||
});
|
||||
const bodyClasses = classNames({
|
||||
'mx_MatrixChat': true,
|
||||
'mx_MatrixChat_useCompactLayout': this.state.useCompactLayout,
|
||||
'mx_MatrixChat--with-avatar': this.state.backgroundImage,
|
||||
});
|
||||
|
||||
|
@ -645,7 +648,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
<div
|
||||
onPaste={this.onPaste}
|
||||
onKeyDown={this.onReactKeyDown}
|
||||
className='mx_MatrixChat_wrapper'
|
||||
className={wrapperClasses}
|
||||
aria-hidden={this.props.hideToSRUsers}
|
||||
>
|
||||
<ToastContainer />
|
||||
|
|
|
@ -215,7 +215,7 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
|
|||
{
|
||||
a: (sub) => <a
|
||||
target="_blank"
|
||||
href="https://github.com/vector-im/element-web/issues/new"
|
||||
href="https://github.com/vector-im/element-web/issues/new/choose"
|
||||
>
|
||||
{ sub }
|
||||
</a>,
|
||||
|
|
|
@ -28,7 +28,7 @@ import StyledRadioGroup from "../elements/StyledRadioGroup";
|
|||
|
||||
const existingIssuesUrl = "https://github.com/vector-im/element-web/issues" +
|
||||
"?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) => {
|
||||
const [rating, setRating] = useState("");
|
||||
|
|
|
@ -77,7 +77,7 @@ export default class ErrorBoundary extends React.PureComponent<{}, IState> {
|
|||
|
||||
render() {
|
||||
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;
|
||||
if (SdkConfig.get().bug_report_endpoint_url) {
|
||||
|
|
|
@ -19,14 +19,12 @@ import MAudioBody from "./MAudioBody";
|
|||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import MVoiceMessageBody from "./MVoiceMessageBody";
|
||||
import { IBodyProps } from "./IBodyProps";
|
||||
import { isVoiceMessage } from "../../../utils/EventUtils";
|
||||
|
||||
@replaceableComponent("views.messages.MVoiceOrAudioBody")
|
||||
export default class MVoiceOrAudioBody extends React.PureComponent<IBodyProps> {
|
||||
public render() {
|
||||
// MSC2516 is a legacy identifier. See https://github.com/matrix-org/matrix-doc/pull/3245
|
||||
const isVoiceMessage = !!this.props.mxEvent.getContent()['org.matrix.msc2516.voice']
|
||||
|| !!this.props.mxEvent.getContent()['org.matrix.msc3245.voice'];
|
||||
if (isVoiceMessage) {
|
||||
if (isVoiceMessage(this.props.mxEvent)) {
|
||||
return <MVoiceMessageBody {...this.props} />;
|
||||
} else {
|
||||
return <MAudioBody {...this.props} />;
|
||||
|
|
|
@ -36,6 +36,7 @@ import { showSpaceInvite } from "../../../utils/space";
|
|||
import { privateShouldBeEncrypted } from "../../../createRoom";
|
||||
import EventTileBubble from "../messages/EventTileBubble";
|
||||
import { ROOM_SECURITY_TAB } from "../dialogs/RoomSettingsDialog";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
|
||||
function hasExpectedEncryptionSettings(matrixClient: MatrixClient, room: Room): boolean {
|
||||
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. "+
|
||||
"Usually this is due to an unsupported device or method being used, " +
|
||||
"like email invites. <a>Enable encryption in settings.</a>", {},
|
||||
{ a: sub => <a onClick={openRoomSettings} href="#">{ sub }</a> },
|
||||
"like email invites.",
|
||||
);
|
||||
|
||||
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">
|
||||
|
@ -204,7 +215,7 @@ const NewRoomIntro = () => {
|
|||
<EventTileBubble
|
||||
className="mx_cryptoEvent mx_cryptoEvent_icon_warning"
|
||||
title={_t("End-to-end encryption isn't enabled")}
|
||||
subtitle={sub2}
|
||||
subtitle={subtitle}
|
||||
/>
|
||||
) }
|
||||
|
||||
|
|
|
@ -25,8 +25,9 @@ import MImageReplyBody from "../messages/MImageReplyBody";
|
|||
import * as sdk from '../../../index';
|
||||
import { EventType, MsgType } from 'matrix-js-sdk/src/@types/event';
|
||||
import { replaceableComponent } from '../../../utils/replaceableComponent';
|
||||
import { getEventDisplayInfo } from '../../../utils/EventUtils';
|
||||
import { getEventDisplayInfo, isVoiceMessage } from '../../../utils/EventUtils';
|
||||
import MFileBody from "../messages/MFileBody";
|
||||
import MVoiceMessageBody from "../messages/MVoiceMessageBody";
|
||||
|
||||
interface IProps {
|
||||
mxEvent: MatrixEvent;
|
||||
|
@ -95,7 +96,7 @@ export default class ReplyTile extends React.PureComponent<IProps> {
|
|||
const msgType = mxEvent.getContent().msgtype;
|
||||
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
|
||||
// before trying to instantiate us
|
||||
if (!tileHandler) {
|
||||
|
@ -109,14 +110,14 @@ export default class ReplyTile extends React.PureComponent<IProps> {
|
|||
const EventTileType = sdk.getComponent(tileHandler);
|
||||
|
||||
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_video: msgType === MsgType.Video,
|
||||
});
|
||||
|
||||
let permalink = "#";
|
||||
if (this.props.permalinkCreator) {
|
||||
permalink = this.props.permalinkCreator.forEvent(this.props.mxEvent.getId());
|
||||
permalink = this.props.permalinkCreator.forEvent(mxEvent.getId());
|
||||
}
|
||||
|
||||
let sender;
|
||||
|
@ -129,7 +130,7 @@ export default class ReplyTile extends React.PureComponent<IProps> {
|
|||
|
||||
if (needsSenderProfile) {
|
||||
sender = <SenderProfile
|
||||
mxEvent={this.props.mxEvent}
|
||||
mxEvent={mxEvent}
|
||||
enableFlair={false}
|
||||
/>;
|
||||
}
|
||||
|
@ -137,7 +138,7 @@ export default class ReplyTile extends React.PureComponent<IProps> {
|
|||
const msgtypeOverrides = {
|
||||
[MsgType.Image]: MImageReplyBody,
|
||||
// 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,
|
||||
};
|
||||
const evOverrides = {
|
||||
|
@ -151,14 +152,14 @@ export default class ReplyTile extends React.PureComponent<IProps> {
|
|||
{ sender }
|
||||
<EventTileType
|
||||
ref="tile"
|
||||
mxEvent={this.props.mxEvent}
|
||||
mxEvent={mxEvent}
|
||||
highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
onHeightChanged={this.props.onHeightChanged}
|
||||
showUrlPreview={false}
|
||||
overrideBodyTypes={msgtypeOverrides}
|
||||
overrideEventTypes={evOverrides}
|
||||
replacingEventId={this.props.mxEvent.replacingEventId()}
|
||||
replacingEventId={mxEvent.replacingEventId()}
|
||||
maxImageHeight={96} />
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -25,6 +25,8 @@ import InteractiveAuthDialog from '../dialogs/InteractiveAuthDialog';
|
|||
import ConfirmDestroyCrossSigningDialog from '../dialogs/security/ConfirmDestroyCrossSigningDialog';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { MatrixEvent } from 'matrix-js-sdk/src';
|
||||
import SetupEncryptionDialog from '../dialogs/security/SetupEncryptionDialog';
|
||||
import { accessSecretStorage } from '../../../SecurityManager';
|
||||
|
||||
interface IState {
|
||||
error?: Error;
|
||||
|
@ -72,7 +74,16 @@ export default class CrossSigningPanel extends React.PureComponent<{}, IState> {
|
|||
};
|
||||
|
||||
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 = () => {
|
||||
|
@ -176,10 +187,14 @@ export default class CrossSigningPanel extends React.PureComponent<{}, IState> {
|
|||
summarisedStatus = <p>{ _t(
|
||||
"Your homeserver does not support cross-signing.",
|
||||
) }</p>;
|
||||
} else if (crossSigningReady) {
|
||||
} else if (crossSigningReady && crossSigningPrivateKeysInStorage) {
|
||||
summarisedStatus = <p>✅ { _t(
|
||||
"Cross-signing is ready for use.",
|
||||
) }</p>;
|
||||
} else if (crossSigningReady && !crossSigningPrivateKeysInStorage) {
|
||||
summarisedStatus = <p>⚠️ { _t(
|
||||
"Cross-signing is ready but keys are not backed up.",
|
||||
) }</p>;
|
||||
} else if (crossSigningPrivateKeysInStorage) {
|
||||
summarisedStatus = <p>{ _t(
|
||||
"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
|
||||
if (!keysExistEverywhere && homeserverSupportsCrossSigning) {
|
||||
let buttonCaption = _t("Set up Secure Backup");
|
||||
if (crossSigningPrivateKeysInStorage) {
|
||||
buttonCaption = _t("Verify this session");
|
||||
}
|
||||
actions.push(
|
||||
<AccessibleButton key="setup" kind="primary" onClick={this.onBootstrapClick}>
|
||||
{ _t("Set up") }
|
||||
{ buttonCaption }
|
||||
</AccessibleButton>,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1103,9 +1103,9 @@
|
|||
"Change Password": "Change Password",
|
||||
"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 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.",
|
||||
"Cross-signing is not set up.": "Cross-signing is not set up.",
|
||||
"Set up": "Set up",
|
||||
"Reset": "Reset",
|
||||
"Cross-signing public keys:": "Cross-signing public keys:",
|
||||
"in memory": "in memory",
|
||||
|
@ -1203,6 +1203,7 @@
|
|||
"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>.",
|
||||
"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",
|
||||
"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.",
|
||||
|
@ -1572,7 +1573,8 @@
|
|||
"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.",
|
||||
"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",
|
||||
"Unpin": "Unpin",
|
||||
"View message": "View message",
|
||||
|
|
|
@ -144,3 +144,12 @@ export function getEventDisplayInfo(mxEvent: MatrixEvent): {
|
|||
|
||||
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']
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue