Merge branch 'develop' into gsouquet/threaded-messaging-2349
						commit
						edd4d42e7f
					
				|  | @ -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
	
	 Germain Souquet
						Germain Souquet