Merge branch 'develop' into travis/widget-api
This commit is contained in:
commit
14766e24b8
20 changed files with 351 additions and 81 deletions
|
@ -117,7 +117,9 @@ export default class SetPasswordDialog extends React.Component {
|
|||
autoFocusNewPasswordInput={true}
|
||||
shouldAskForEmail={true}
|
||||
onError={this._onPasswordChangeError}
|
||||
onFinished={this._onPasswordChanged} />
|
||||
onFinished={this._onPasswordChanged}
|
||||
buttonLabel={_t("Set Password")}
|
||||
/>
|
||||
<div className="error">
|
||||
{ this.state.error }
|
||||
</div>
|
||||
|
|
|
@ -82,6 +82,7 @@ export default class PersistentApp extends React.Component {
|
|||
showDelete={false}
|
||||
showMinimise={false}
|
||||
miniMode={true}
|
||||
showMenubar={false}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
|
76
src/components/views/messages/MJitsiWidgetEvent.tsx
Normal file
76
src/components/views/messages/MJitsiWidgetEvent.tsx
Normal file
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
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 from 'react';
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import WidgetStore from "../../../stores/WidgetStore";
|
||||
|
||||
interface IProps {
|
||||
mxEvent: MatrixEvent;
|
||||
}
|
||||
|
||||
export default class MJitsiWidgetEvent extends React.PureComponent<IProps> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const url = this.props.mxEvent.getContent()['url'];
|
||||
const prevUrl = this.props.mxEvent.getPrevContent()['url'];
|
||||
const senderName = this.props.mxEvent.sender?.name || this.props.mxEvent.getSender();
|
||||
|
||||
let joinCopy = _t('Join the conference at the top of this room');
|
||||
if (!WidgetStore.instance.isPinned(this.props.mxEvent.getStateKey())) {
|
||||
joinCopy = _t('Join the conference from the room information card on the right');
|
||||
}
|
||||
|
||||
if (!url) {
|
||||
// removed
|
||||
return (
|
||||
<div className='mx_EventTile_bubble mx_MJitsiWidgetEvent'>
|
||||
<div className='mx_MJitsiWidgetEvent_title'>
|
||||
{_t('Video conference ended by %(senderName)s', {senderName})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (prevUrl) {
|
||||
// modified
|
||||
return (
|
||||
<div className='mx_EventTile_bubble mx_MJitsiWidgetEvent'>
|
||||
<div className='mx_MJitsiWidgetEvent_title'>
|
||||
{_t('Video conference updated by %(senderName)s', {senderName})}
|
||||
</div>
|
||||
<div className='mx_MJitsiWidgetEvent_subtitle'>
|
||||
{joinCopy}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
// assume added
|
||||
return (
|
||||
<div className='mx_EventTile_bubble mx_MJitsiWidgetEvent'>
|
||||
<div className='mx_MJitsiWidgetEvent_title'>
|
||||
{_t("Video conference started by %(senderName)s", {senderName})}
|
||||
</div>
|
||||
<div className='mx_MJitsiWidgetEvent_subtitle'>
|
||||
{joinCopy}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -619,13 +619,14 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
|
|||
}
|
||||
|
||||
private onFormatAction = (action: Formatting) => {
|
||||
const range = getRangeForSelection(
|
||||
this.editorRef.current,
|
||||
this.props.model,
|
||||
document.getSelection());
|
||||
const range = getRangeForSelection(this.editorRef.current, this.props.model, document.getSelection());
|
||||
// trim the range as we want it to exclude leading/trailing spaces
|
||||
range.trim();
|
||||
|
||||
if (range.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.historyManager.ensureLastChangesPushed(this.props.model);
|
||||
this.modifiedFlag = true;
|
||||
switch (action) {
|
||||
|
|
|
@ -34,6 +34,7 @@ import * as ObjectUtils from "../../../ObjectUtils";
|
|||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import {E2E_STATE} from "./E2EIcon";
|
||||
import {toRem} from "../../../utils/units";
|
||||
import {WidgetType} from "../../../widgets/WidgetType";
|
||||
import RoomAvatar from "../avatars/RoomAvatar";
|
||||
|
||||
const eventTileTypes = {
|
||||
|
@ -111,6 +112,19 @@ export function getHandlerTile(ev) {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
|
||||
if (type === "im.vector.modular.widgets") {
|
||||
let type = ev.getContent()['type'];
|
||||
if (!type) {
|
||||
// deleted/invalid widget - try the past widget type
|
||||
type = ev.getPrevContent()['type'];
|
||||
}
|
||||
|
||||
if (WidgetType.JITSI.matches(type)) {
|
||||
return "messages.MJitsiWidgetEvent";
|
||||
}
|
||||
}
|
||||
|
||||
return ev.isState() ? stateEventTileTypes[type] : eventTileTypes[type];
|
||||
}
|
||||
|
||||
|
@ -627,16 +641,18 @@ export default class EventTile extends React.Component {
|
|||
const msgtype = content.msgtype;
|
||||
const eventType = this.props.mxEvent.getType();
|
||||
|
||||
let tileHandler = getHandlerTile(this.props.mxEvent);
|
||||
|
||||
// Info messages are basically information about commands processed on a room
|
||||
const isBubbleMessage = eventType.startsWith("m.key.verification") ||
|
||||
(eventType === "m.room.message" && msgtype && msgtype.startsWith("m.key.verification")) ||
|
||||
(eventType === "m.room.encryption");
|
||||
(eventType === "m.room.encryption") ||
|
||||
(tileHandler === "messages.MJitsiWidgetEvent");
|
||||
let isInfoMessage = (
|
||||
!isBubbleMessage && eventType !== 'm.room.message' &&
|
||||
eventType !== 'm.sticker' && eventType !== 'm.room.create'
|
||||
);
|
||||
|
||||
let tileHandler = getHandlerTile(this.props.mxEvent);
|
||||
// If we're showing hidden events in the timeline, we should use the
|
||||
// source tile when there's no regular tile for an event and also for
|
||||
// replace relations (which otherwise would display as a confusing
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017, 2018 New Vector Ltd
|
||||
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.
|
||||
|
@ -32,6 +33,10 @@ import {aboveLeftOf, ContextMenu, ContextMenuTooltipButton, useContextMenu} from
|
|||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||
import ReplyPreview from "./ReplyPreview";
|
||||
import {UIFeature} from "../../../settings/UIFeature";
|
||||
import WidgetStore from "../../../stores/WidgetStore";
|
||||
import WidgetUtils from "../../../utils/WidgetUtils";
|
||||
import {UPDATE_EVENT} from "../../../stores/AsyncStore";
|
||||
import ActiveWidgetStore from "../../../stores/ActiveWidgetStore";
|
||||
|
||||
function ComposerAvatar(props) {
|
||||
const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar');
|
||||
|
@ -85,8 +90,15 @@ VideoCallButton.propTypes = {
|
|||
};
|
||||
|
||||
function HangupButton(props) {
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
const onHangupClick = () => {
|
||||
if (props.isConference) {
|
||||
dis.dispatch({
|
||||
action: props.canEndConference ? 'end_conference' : 'hangup_conference',
|
||||
room_id: props.roomId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const call = CallHandler.sharedInstance().getCallForRoom(props.roomId);
|
||||
if (!call) {
|
||||
return;
|
||||
|
@ -98,14 +110,28 @@ function HangupButton(props) {
|
|||
room_id: call.roomId,
|
||||
});
|
||||
};
|
||||
return (<AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_hangup"
|
||||
|
||||
let tooltip = _t("Hangup");
|
||||
if (props.isConference && props.canEndConference) {
|
||||
tooltip = _t("End conference");
|
||||
}
|
||||
|
||||
const canLeaveConference = !props.isConference ? true : props.isInConference;
|
||||
return (
|
||||
<AccessibleTooltipButton
|
||||
className="mx_MessageComposer_button mx_MessageComposer_hangup"
|
||||
onClick={onHangupClick}
|
||||
title={_t('Hangup')}
|
||||
/>);
|
||||
title={tooltip}
|
||||
disabled={!canLeaveConference}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
HangupButton.propTypes = {
|
||||
roomId: PropTypes.string.isRequired,
|
||||
isConference: PropTypes.bool.isRequired,
|
||||
canEndConference: PropTypes.bool,
|
||||
isInConference: PropTypes.bool,
|
||||
};
|
||||
|
||||
const EmojiButton = ({addEmoji}) => {
|
||||
|
@ -226,12 +252,17 @@ export default class MessageComposer extends React.Component {
|
|||
this._onRoomViewStoreUpdate = this._onRoomViewStoreUpdate.bind(this);
|
||||
this._onTombstoneClick = this._onTombstoneClick.bind(this);
|
||||
this.renderPlaceholderText = this.renderPlaceholderText.bind(this);
|
||||
WidgetStore.instance.on(UPDATE_EVENT, this._onWidgetUpdate);
|
||||
ActiveWidgetStore.on('update', this._onActiveWidgetUpdate);
|
||||
this._dispatcherRef = null;
|
||||
|
||||
this.state = {
|
||||
isQuoting: Boolean(RoomViewStore.getQuotingEvent()),
|
||||
tombstone: this._getRoomTombstone(),
|
||||
canSendMessages: this.props.room.maySendMessage(),
|
||||
showCallButtons: SettingsStore.getValue("showCallButtonsInComposer"),
|
||||
hasConference: WidgetStore.instance.doesRoomHaveConference(this.props.room),
|
||||
joinedConference: WidgetStore.instance.isJoinedToConferenceIn(this.props.room),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -247,6 +278,14 @@ export default class MessageComposer extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
_onWidgetUpdate = () => {
|
||||
this.setState({hasConference: WidgetStore.instance.doesRoomHaveConference(this.props.room)});
|
||||
};
|
||||
|
||||
_onActiveWidgetUpdate = () => {
|
||||
this.setState({joinedConference: WidgetStore.instance.isJoinedToConferenceIn(this.props.room)});
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
MatrixClientPeg.get().on("RoomState.events", this._onRoomStateEvents);
|
||||
|
@ -277,6 +316,8 @@ export default class MessageComposer extends React.Component {
|
|||
if (this._roomStoreToken) {
|
||||
this._roomStoreToken.remove();
|
||||
}
|
||||
WidgetStore.instance.removeListener(UPDATE_EVENT, this._onWidgetUpdate);
|
||||
ActiveWidgetStore.removeListener('update', this._onActiveWidgetUpdate);
|
||||
dis.unregister(this.dispatcherRef);
|
||||
}
|
||||
|
||||
|
@ -392,9 +433,19 @@ export default class MessageComposer extends React.Component {
|
|||
}
|
||||
|
||||
if (this.state.showCallButtons) {
|
||||
if (callInProgress) {
|
||||
if (this.state.hasConference) {
|
||||
const canEndConf = WidgetUtils.canUserModifyWidgets(this.props.room.roomId);
|
||||
controls.push(
|
||||
<HangupButton key="controls_hangup" roomId={this.props.room.roomId} />,
|
||||
<HangupButton
|
||||
roomId={this.props.room.roomId}
|
||||
isConference={true}
|
||||
canEndConference={canEndConf}
|
||||
isInConference={this.state.joinedConference}
|
||||
/>,
|
||||
);
|
||||
} else if (callInProgress) {
|
||||
controls.push(
|
||||
<HangupButton key="controls_hangup" roomId={this.props.room.roomId} isConference={false} />,
|
||||
);
|
||||
} else {
|
||||
controls.push(
|
||||
|
|
|
@ -35,6 +35,7 @@ export default class ChangePassword extends React.Component {
|
|||
rowClassName: PropTypes.string,
|
||||
buttonClassName: PropTypes.string,
|
||||
buttonKind: PropTypes.string,
|
||||
buttonLabel: PropTypes.string,
|
||||
confirm: PropTypes.bool,
|
||||
// Whether to autoFocus the new password input
|
||||
autoFocusNewPasswordInput: PropTypes.bool,
|
||||
|
@ -271,7 +272,7 @@ export default class ChangePassword extends React.Component {
|
|||
/>
|
||||
</div>
|
||||
<AccessibleButton className={buttonClassName} kind={this.props.buttonKind} onClick={this.onClickChange}>
|
||||
{ _t('Change Password') }
|
||||
{ this.props.buttonLabel || _t('Change Password') }
|
||||
</AccessibleButton>
|
||||
</form>
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue