Merge branch 'develop' into t3chguy/fix/6606
commit
93010d34fd
|
@ -222,7 +222,6 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_RoomView_MessageList li {
|
.mx_RoomView_MessageList li {
|
||||||
clear: both;
|
clear: both;
|
||||||
contain: content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
li.mx_RoomView_myReadMarker_container {
|
li.mx_RoomView_myReadMarker_container {
|
||||||
|
@ -239,6 +238,7 @@ hr.mx_RoomView_myReadMarker {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -1px;
|
top: -1px;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
will-change: width;
|
||||||
transition: width 400ms easeinsine 1s, opacity 400ms easeinsine 1s;
|
transition: width 400ms easeinsine 1s, opacity 400ms easeinsine 1s;
|
||||||
width: 99%;
|
width: 99%;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|
|
@ -115,8 +115,7 @@ $irc-line-height: $font-18px;
|
||||||
.mx_EventTile_line {
|
.mx_EventTile_line {
|
||||||
.mx_EventTile_e2eIcon,
|
.mx_EventTile_e2eIcon,
|
||||||
.mx_TextualEvent,
|
.mx_TextualEvent,
|
||||||
.mx_MTextBody,
|
.mx_MTextBody {
|
||||||
.mx_ReplyThread_wrapper_empty {
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,8 +176,6 @@ $irc-line-height: $font-18px;
|
||||||
.mx_SenderProfile_hover {
|
.mx_SenderProfile_hover {
|
||||||
background-color: $primary-bg-color;
|
background-color: $primary-bg-color;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
> span {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
> .mx_SenderProfile_name {
|
> .mx_SenderProfile_name {
|
||||||
|
@ -188,7 +185,6 @@ $irc-line-height: $font-18px;
|
||||||
text-align: end;
|
text-align: end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SenderProfile:hover {
|
.mx_SenderProfile:hover {
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
|
|
@ -32,14 +32,14 @@ limitations under the License.
|
||||||
// first triggering the enter state with the newest breadcrumb off screen (-40px) then
|
// first triggering the enter state with the newest breadcrumb off screen (-40px) then
|
||||||
// sliding it into view.
|
// sliding it into view.
|
||||||
&.mx_RoomBreadcrumbs-enter {
|
&.mx_RoomBreadcrumbs-enter {
|
||||||
margin-left: -40px; // 32px for the avatar, 8px for the margin
|
transform: translateX(-40px); // 32px for the avatar, 8px for the margin
|
||||||
}
|
}
|
||||||
&.mx_RoomBreadcrumbs-enter-active {
|
&.mx_RoomBreadcrumbs-enter-active {
|
||||||
margin-left: 0;
|
transform: translateX(0);
|
||||||
|
|
||||||
// Timing function is as-requested by design.
|
// Timing function is as-requested by design.
|
||||||
// NOTE: The transition time MUST match the value passed to CSSTransition!
|
// NOTE: The transition time MUST match the value passed to CSSTransition!
|
||||||
transition: margin-left 640ms cubic-bezier(0.66, 0.02, 0.36, 1);
|
transition: transform 640ms cubic-bezier(0.66, 0.02, 0.36, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomBreadcrumbs_placeholder {
|
.mx_RoomBreadcrumbs_placeholder {
|
||||||
|
|
|
@ -19,7 +19,7 @@ limitations under the License.
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
|
|
||||||
contain: strict;
|
contain: content; // Not strict as it will break when resizing a sublist vertically
|
||||||
height: 40px;
|
height: 40px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
|
|
@ -264,7 +264,7 @@ export default class CallHandler extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSupportsVirtualRooms() {
|
public getSupportsVirtualRooms() {
|
||||||
return this.supportsPstnProtocol;
|
return this.supportsSipNativeVirtual;
|
||||||
}
|
}
|
||||||
|
|
||||||
public pstnLookup(phoneNumber: string): Promise<ThirdpartyLookupResponse[]> {
|
public pstnLookup(phoneNumber: string): Promise<ThirdpartyLookupResponse[]> {
|
||||||
|
@ -521,7 +521,9 @@ export default class CallHandler extends EventEmitter {
|
||||||
let newNativeAssertedIdentity = newAssertedIdentity;
|
let newNativeAssertedIdentity = newAssertedIdentity;
|
||||||
if (newAssertedIdentity) {
|
if (newAssertedIdentity) {
|
||||||
const response = await this.sipNativeLookup(newAssertedIdentity);
|
const response = await this.sipNativeLookup(newAssertedIdentity);
|
||||||
if (response.length) newNativeAssertedIdentity = response[0].userid;
|
if (response.length && response[0].fields.lookup_success) {
|
||||||
|
newNativeAssertedIdentity = response[0].userid;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
console.log(`Asserted identity ${newAssertedIdentity} mapped to ${newNativeAssertedIdentity}`);
|
console.log(`Asserted identity ${newAssertedIdentity} mapped to ${newNativeAssertedIdentity}`);
|
||||||
|
|
||||||
|
@ -862,9 +864,43 @@ export default class CallHandler extends EventEmitter {
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case Action.DialNumber:
|
||||||
|
this.dialNumber(payload.number);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async dialNumber(number: string) {
|
||||||
|
const results = await this.pstnLookup(number);
|
||||||
|
if (!results || results.length === 0 || !results[0].userid) {
|
||||||
|
Modal.createTrackedDialog('', '', ErrorDialog, {
|
||||||
|
title: _t("Unable to look up phone number"),
|
||||||
|
description: _t("There was an error looking up the phone number"),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const userId = results[0].userid;
|
||||||
|
|
||||||
|
// Now check to see if this is a virtual user, in which case we should find the
|
||||||
|
// native user
|
||||||
|
let nativeUserId;
|
||||||
|
if (this.getSupportsVirtualRooms()) {
|
||||||
|
const nativeLookupResults = await this.sipNativeLookup(userId);
|
||||||
|
const lookupSuccess = nativeLookupResults.length > 0 && nativeLookupResults[0].fields.lookup_success;
|
||||||
|
nativeUserId = lookupSuccess ? nativeLookupResults[0].userid : userId;
|
||||||
|
console.log("Looked up " + number + " to " + userId + " and mapped to native user " + nativeUserId);
|
||||||
|
} else {
|
||||||
|
nativeUserId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const roomId = await ensureDMExists(MatrixClientPeg.get(), nativeUserId);
|
||||||
|
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'view_room',
|
||||||
|
room_id: roomId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setActiveCallRoomId(activeCallRoomId: string) {
|
setActiveCallRoomId(activeCallRoomId: string) {
|
||||||
logger.info("Setting call in room " + activeCallRoomId + " active");
|
logger.info("Setting call in room " + activeCallRoomId + " active");
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ export default class VoipUserMapper {
|
||||||
|
|
||||||
private async userToVirtualUser(userId: string): Promise<string> {
|
private async userToVirtualUser(userId: string): Promise<string> {
|
||||||
const results = await CallHandler.sharedInstance().sipVirtualLookup(userId);
|
const results = await CallHandler.sharedInstance().sipVirtualLookup(userId);
|
||||||
if (results.length === 0) return null;
|
if (results.length === 0 || !results[0].fields.lookup_success) return null;
|
||||||
return results[0].userid;
|
return results[0].userid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,14 +82,14 @@ export default class VoipUserMapper {
|
||||||
return Boolean(claimedNativeRoomId);
|
return Boolean(claimedNativeRoomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onNewInvitedRoom(invitedRoom: Room) {
|
public async onNewInvitedRoom(invitedRoom: Room): Promise<void> {
|
||||||
if (!CallHandler.sharedInstance().getSupportsVirtualRooms()) return;
|
if (!CallHandler.sharedInstance().getSupportsVirtualRooms()) return;
|
||||||
|
|
||||||
const inviterId = invitedRoom.getDMInviter();
|
const inviterId = invitedRoom.getDMInviter();
|
||||||
console.log(`Checking virtual-ness of room ID ${invitedRoom.roomId}, invited by ${inviterId}`);
|
console.log(`Checking virtual-ness of room ID ${invitedRoom.roomId}, invited by ${inviterId}`);
|
||||||
const result = await CallHandler.sharedInstance().sipNativeLookup(inviterId);
|
const result = await CallHandler.sharedInstance().sipNativeLookup(inviterId);
|
||||||
if (result.length === 0) {
|
if (result.length === 0) {
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result[0].fields.is_virtual) {
|
if (result[0].fields.is_virtual) {
|
||||||
|
|
|
@ -648,13 +648,12 @@ export default class MessagePanel extends React.Component {
|
||||||
|
|
||||||
// use txnId as key if available so that we don't remount during sending
|
// use txnId as key if available so that we don't remount during sending
|
||||||
ret.push(
|
ret.push(
|
||||||
<li
|
<TileErrorBoundary key={mxEv.getTxnId() || eventId} mxEvent={mxEv}>
|
||||||
key={mxEv.getTxnId() || eventId}
|
|
||||||
ref={this._collectEventNode.bind(this, eventId)}
|
|
||||||
data-scroll-tokens={scrollToken}
|
|
||||||
>
|
|
||||||
<TileErrorBoundary mxEvent={mxEv}>
|
|
||||||
<EventTile
|
<EventTile
|
||||||
|
as="li"
|
||||||
|
data-scroll-tokens={scrollToken}
|
||||||
|
ref={this._collectEventNode.bind(this, eventId)}
|
||||||
|
alwaysShowTimestamps={this.props.alwaysShowTimestamps}
|
||||||
mxEvent={mxEv}
|
mxEvent={mxEv}
|
||||||
continuation={continuation}
|
continuation={continuation}
|
||||||
isRedacted={mxEv.isRedacted()}
|
isRedacted={mxEv.isRedacted()}
|
||||||
|
@ -679,8 +678,7 @@ export default class MessagePanel extends React.Component {
|
||||||
enableFlair={this.props.enableFlair}
|
enableFlair={this.props.enableFlair}
|
||||||
showReadReceipts={this.props.showReadReceipts}
|
showReadReceipts={this.props.showReadReceipts}
|
||||||
/>
|
/>
|
||||||
</TileErrorBoundary>
|
</TileErrorBoundary>,
|
||||||
</li>,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -782,7 +780,7 @@ export default class MessagePanel extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_collectEventNode = (eventId, node) => {
|
_collectEventNode = (eventId, node) => {
|
||||||
this.eventNodes[eventId] = node;
|
this.eventNodes[eventId] = node?.ref?.current;
|
||||||
}
|
}
|
||||||
|
|
||||||
// once dynamic content in the events load, make the scrollPanel check the
|
// once dynamic content in the events load, make the scrollPanel check the
|
||||||
|
|
|
@ -79,8 +79,9 @@ import { Container, WidgetLayoutStore } from "../../stores/widgets/WidgetLayoutS
|
||||||
import { getKeyBindingsManager, RoomAction } from '../../KeyBindingsManager';
|
import { getKeyBindingsManager, RoomAction } from '../../KeyBindingsManager';
|
||||||
import { objectHasDiff } from "../../utils/objects";
|
import { objectHasDiff } from "../../utils/objects";
|
||||||
import SpaceRoomView from "./SpaceRoomView";
|
import SpaceRoomView from "./SpaceRoomView";
|
||||||
import { IOpts } from "../../createRoom";
|
import { IOpts } from "../../createRoom
|
||||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../utils/replaceableComponent
|
||||||
|
import { omit } from 'lodash';
|
||||||
import UIStore from "../../stores/UIStore";
|
import UIStore from "../../stores/UIStore";
|
||||||
|
|
||||||
const DEBUG = false;
|
const DEBUG = false;
|
||||||
|
@ -173,6 +174,7 @@ export interface IState {
|
||||||
statusBarVisible: boolean;
|
statusBarVisible: boolean;
|
||||||
// We load this later by asking the js-sdk to suggest a version for us.
|
// We load this later by asking the js-sdk to suggest a version for us.
|
||||||
// This object is the result of Room#getRecommendedVersion()
|
// This object is the result of Room#getRecommendedVersion()
|
||||||
|
|
||||||
upgradeRecommendation?: {
|
upgradeRecommendation?: {
|
||||||
version: string;
|
version: string;
|
||||||
needsUpgrade: boolean;
|
needsUpgrade: boolean;
|
||||||
|
@ -524,7 +526,20 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
return (objectHasDiff(this.props, nextProps) || objectHasDiff(this.state, nextState));
|
const hasPropsDiff = objectHasDiff(this.props, nextProps);
|
||||||
|
|
||||||
|
// React only shallow comparison and we only want to trigger
|
||||||
|
// a component re-render if a room requires an upgrade
|
||||||
|
const newUpgradeRecommendation = nextState.upgradeRecommendation || {}
|
||||||
|
|
||||||
|
const state = omit(this.state, ['upgradeRecommendation']);
|
||||||
|
const newState = omit(nextState, ['upgradeRecommendation'])
|
||||||
|
|
||||||
|
const hasStateDiff =
|
||||||
|
objectHasDiff(state, newState) ||
|
||||||
|
(newUpgradeRecommendation.needsUpgrade === true)
|
||||||
|
|
||||||
|
return hasPropsDiff || hasStateDiff;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
|
@ -818,7 +833,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private onEvent = (ev) => {
|
private onEvent = (ev) => {
|
||||||
if (ev.isBeingDecrypted() || ev.isDecryptionFailure() || ev.shouldAttemptDecryption()) return;
|
if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return;
|
||||||
this.handleEffects(ev);
|
this.handleEffects(ev);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,6 @@ import shouldHideEvent from '../../shouldHideEvent';
|
||||||
import EditorStateTransfer from '../../utils/EditorStateTransfer';
|
import EditorStateTransfer from '../../utils/EditorStateTransfer';
|
||||||
import {haveTileForEvent} from "../views/rooms/EventTile";
|
import {haveTileForEvent} from "../views/rooms/EventTile";
|
||||||
import {UIFeature} from "../../settings/UIFeature";
|
import {UIFeature} from "../../settings/UIFeature";
|
||||||
import {objectHasDiff} from "../../utils/objects";
|
|
||||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
import { arrayFastClone } from "../../utils/arrays";
|
import { arrayFastClone } from "../../utils/arrays";
|
||||||
|
|
||||||
|
@ -270,30 +269,6 @@ class TimelinePanel extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
|
||||||
if (objectHasDiff(this.props, nextProps)) {
|
|
||||||
if (DEBUG) {
|
|
||||||
console.group("Timeline.shouldComponentUpdate: props change");
|
|
||||||
console.log("props before:", this.props);
|
|
||||||
console.log("props after:", nextProps);
|
|
||||||
console.groupEnd();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (objectHasDiff(this.state, nextState)) {
|
|
||||||
if (DEBUG) {
|
|
||||||
console.group("Timeline.shouldComponentUpdate: state change");
|
|
||||||
console.log("state before:", this.state);
|
|
||||||
console.log("state after:", nextState);
|
|
||||||
console.groupEnd();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
// set a boolean to say we've been unmounted, which any pending
|
// set a boolean to say we've been unmounted, which any pending
|
||||||
// promises can use to throw away their results.
|
// promises can use to throw away their results.
|
||||||
|
|
|
@ -55,6 +55,7 @@ export default class ToastContainer extends React.Component<{}, IState> {
|
||||||
const totalCount = this.state.toasts.length;
|
const totalCount = this.state.toasts.length;
|
||||||
const isStacked = totalCount > 1;
|
const isStacked = totalCount > 1;
|
||||||
let toast;
|
let toast;
|
||||||
|
let containerClasses;
|
||||||
if (totalCount !== 0) {
|
if (totalCount !== 0) {
|
||||||
const topToast = this.state.toasts[0];
|
const topToast = this.state.toasts[0];
|
||||||
const {title, icon, key, component, className, props} = topToast;
|
const {title, icon, key, component, className, props} = topToast;
|
||||||
|
@ -79,16 +80,17 @@ export default class ToastContainer extends React.Component<{}, IState> {
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Toast_body">{React.createElement(component, toastProps)}</div>
|
<div className="mx_Toast_body">{React.createElement(component, toastProps)}</div>
|
||||||
</div>);
|
</div>);
|
||||||
}
|
|
||||||
|
|
||||||
const containerClasses = classNames("mx_ToastContainer", {
|
containerClasses = classNames("mx_ToastContainer", {
|
||||||
"mx_ToastContainer_stacked": isStacked,
|
"mx_ToastContainer_stacked": isStacked,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
return (
|
return toast
|
||||||
|
? (
|
||||||
<div className={containerClasses} role="alert">
|
<div className={containerClasses} role="alert">
|
||||||
{toast}
|
{toast}
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,7 @@ export default class AccessibleTooltipButton extends React.PureComponent<IToolti
|
||||||
tooltipClassName={classNames("mx_AccessibleTooltipButton_tooltip", tooltipClassName)}
|
tooltipClassName={classNames("mx_AccessibleTooltipButton_tooltip", tooltipClassName)}
|
||||||
label={tooltip || title}
|
label={tooltip || title}
|
||||||
yOffset={yOffset}
|
yOffset={yOffset}
|
||||||
/> : <div />;
|
/> : null;
|
||||||
return (
|
return (
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
@ -116,7 +116,7 @@ export default class Flair extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.profiles.length === 0) {
|
if (this.state.profiles.length === 0) {
|
||||||
return <span className="mx_Flair" />;
|
return null;
|
||||||
}
|
}
|
||||||
const avatars = this.state.profiles.map((profile, index) => {
|
const avatars = this.state.profiles.map((profile, index) => {
|
||||||
return <FlairAvatar key={index} groupProfile={profile} />;
|
return <FlairAvatar key={index} groupProfile={profile} />;
|
||||||
|
|
|
@ -214,7 +214,7 @@ export default class ReplyThread extends React.Component {
|
||||||
|
|
||||||
static makeThread(parentEv, onHeightChanged, permalinkCreator, ref, layout) {
|
static makeThread(parentEv, onHeightChanged, permalinkCreator, ref, layout) {
|
||||||
if (!ReplyThread.getParentEventId(parentEv)) {
|
if (!ReplyThread.getParentEventId(parentEv)) {
|
||||||
return <div className="mx_ReplyThread_wrapper_empty" />;
|
return null;
|
||||||
}
|
}
|
||||||
return <ReplyThread
|
return <ReplyThread
|
||||||
parentEv={parentEv}
|
parentEv={parentEv}
|
||||||
|
@ -269,39 +269,30 @@ export default class ReplyThread extends React.Component {
|
||||||
const {parentEv} = this.props;
|
const {parentEv} = this.props;
|
||||||
// at time of making this component we checked that props.parentEv has a parentEventId
|
// at time of making this component we checked that props.parentEv has a parentEventId
|
||||||
const ev = await this.getEvent(ReplyThread.getParentEventId(parentEv));
|
const ev = await this.getEvent(ReplyThread.getParentEventId(parentEv));
|
||||||
|
|
||||||
if (this.unmounted) return;
|
if (this.unmounted) return;
|
||||||
|
|
||||||
if (ev) {
|
if (ev) {
|
||||||
|
const loadedEv = await this.getNextEvent(ev);
|
||||||
this.setState({
|
this.setState({
|
||||||
events: [ev],
|
events: [ev],
|
||||||
}, this.loadNextEvent);
|
loadedEv,
|
||||||
} else {
|
|
||||||
this.setState({err: true});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadNextEvent() {
|
|
||||||
if (this.unmounted) return;
|
|
||||||
const ev = this.state.events[0];
|
|
||||||
const inReplyToEventId = ReplyThread.getParentEventId(ev);
|
|
||||||
|
|
||||||
if (!inReplyToEventId) {
|
|
||||||
this.setState({
|
|
||||||
loading: false,
|
loading: false,
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadedEv = await this.getEvent(inReplyToEventId);
|
|
||||||
if (this.unmounted) return;
|
|
||||||
|
|
||||||
if (loadedEv) {
|
|
||||||
this.setState({loadedEv});
|
|
||||||
} else {
|
} else {
|
||||||
this.setState({err: true});
|
this.setState({err: true});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getNextEvent(ev) {
|
||||||
|
try {
|
||||||
|
const inReplyToEventId = ReplyThread.getParentEventId(ev);
|
||||||
|
return await this.getEvent(inReplyToEventId);
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async getEvent(eventId) {
|
async getEvent(eventId) {
|
||||||
const event = this.room.findEventById(eventId);
|
const event = this.room.findEventById(eventId);
|
||||||
if (event) return event;
|
if (event) return event;
|
||||||
|
@ -326,13 +317,18 @@ export default class ReplyThread extends React.Component {
|
||||||
this.initialize();
|
this.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
onQuoteClick() {
|
async onQuoteClick() {
|
||||||
const events = [this.state.loadedEv, ...this.state.events];
|
const events = [this.state.loadedEv, ...this.state.events];
|
||||||
|
|
||||||
|
let loadedEv = null;
|
||||||
|
if (events.length > 0) {
|
||||||
|
loadedEv = await this.getNextEvent(events[0]);
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
loadedEv: null,
|
loadedEv,
|
||||||
events,
|
events,
|
||||||
}, this.loadNextEvent);
|
});
|
||||||
|
|
||||||
dis.fire(Action.FocusComposer);
|
dis.fire(Action.FocusComposer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,21 +31,23 @@ export default class SenderProfile extends React.Component {
|
||||||
|
|
||||||
static contextType = MatrixClientContext;
|
static contextType = MatrixClientContext;
|
||||||
|
|
||||||
state = {
|
constructor(props) {
|
||||||
userGroups: null,
|
super(props);
|
||||||
|
const senderId = this.props.mxEvent.getSender();
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
userGroups: FlairStore.cachedPublicisedGroups(senderId) || [],
|
||||||
relatedGroups: [],
|
relatedGroups: [],
|
||||||
};
|
};
|
||||||
|
}
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.unmounted = false;
|
this.unmounted = false;
|
||||||
this._updateRelatedGroups();
|
this._updateRelatedGroups();
|
||||||
|
|
||||||
FlairStore.getPublicisedGroupsCached(
|
if (this.state.userGroups.length === 0) {
|
||||||
this.context, this.props.mxEvent.getSender(),
|
this.getPublicisedGroups();
|
||||||
).then((userGroups) => {
|
}
|
||||||
if (this.unmounted) return;
|
|
||||||
this.setState({userGroups});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.context.on('RoomState.events', this.onRoomStateEvents);
|
this.context.on('RoomState.events', this.onRoomStateEvents);
|
||||||
}
|
}
|
||||||
|
@ -55,6 +57,15 @@ export default class SenderProfile extends React.Component {
|
||||||
this.context.removeListener('RoomState.events', this.onRoomStateEvents);
|
this.context.removeListener('RoomState.events', this.onRoomStateEvents);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getPublicisedGroups() {
|
||||||
|
if (!this.unmounted) {
|
||||||
|
const userGroups = await FlairStore.getPublicisedGroupsCached(
|
||||||
|
this.context, this.props.mxEvent.getSender(),
|
||||||
|
);
|
||||||
|
this.setState({userGroups});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onRoomStateEvents = event => {
|
onRoomStateEvents = event => {
|
||||||
if (event.getType() === 'm.room.related_groups' &&
|
if (event.getType() === 'm.room.related_groups' &&
|
||||||
event.getRoomId() === this.props.mxEvent.getRoomId()
|
event.getRoomId() === this.props.mxEvent.getRoomId()
|
||||||
|
@ -93,10 +104,10 @@ export default class SenderProfile extends React.Component {
|
||||||
const {msgtype} = mxEvent.getContent();
|
const {msgtype} = mxEvent.getContent();
|
||||||
|
|
||||||
if (msgtype === 'm.emote') {
|
if (msgtype === 'm.emote') {
|
||||||
return <span />; // emote message must include the name so don't duplicate it
|
return null; // emote message must include the name so don't duplicate it
|
||||||
}
|
}
|
||||||
|
|
||||||
let flair = <div />;
|
let flair = null;
|
||||||
if (this.props.enableFlair) {
|
if (this.props.enableFlair) {
|
||||||
const displayedGroups = this._getDisplayedGroups(
|
const displayedGroups = this._getDisplayedGroups(
|
||||||
this.state.userGroups, this.state.relatedGroups,
|
this.state.userGroups, this.state.relatedGroups,
|
||||||
|
@ -110,19 +121,12 @@ export default class SenderProfile extends React.Component {
|
||||||
|
|
||||||
const nameElem = name || '';
|
const nameElem = name || '';
|
||||||
|
|
||||||
// Name + flair
|
return (
|
||||||
const nameFlair = <span>
|
<div className="mx_SenderProfile mx_SenderProfile_hover" dir="auto" onClick={this.props.onClick}>
|
||||||
<span className={`mx_SenderProfile_name ${colorClass}`}>
|
<span className={`mx_SenderProfile_name ${colorClass}`}>
|
||||||
{ nameElem }
|
{ nameElem }
|
||||||
</span>
|
</span>
|
||||||
{ flair }
|
{ flair }
|
||||||
</span>;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="mx_SenderProfile" dir="auto" onClick={this.props.onClick}>
|
|
||||||
<div className="mx_SenderProfile_hover">
|
|
||||||
{ nameFlair }
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -277,6 +277,12 @@ interface IProps {
|
||||||
|
|
||||||
// Helper to build permalinks for the room
|
// Helper to build permalinks for the room
|
||||||
permalinkCreator?: RoomPermalinkCreator;
|
permalinkCreator?: RoomPermalinkCreator;
|
||||||
|
|
||||||
|
// Symbol of the root node
|
||||||
|
as?: string
|
||||||
|
|
||||||
|
// whether or not to always show timestamps
|
||||||
|
alwaysShowTimestamps?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -291,12 +297,15 @@ interface IState {
|
||||||
previouslyRequestedKeys: boolean;
|
previouslyRequestedKeys: boolean;
|
||||||
// The Relations model from the JS SDK for reactions to `mxEvent`
|
// The Relations model from the JS SDK for reactions to `mxEvent`
|
||||||
reactions: Relations;
|
reactions: Relations;
|
||||||
|
|
||||||
|
hover: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@replaceableComponent("views.rooms.EventTile")
|
@replaceableComponent("views.rooms.EventTile")
|
||||||
export default class EventTile extends React.Component<IProps, IState> {
|
export default class EventTile extends React.Component<IProps, IState> {
|
||||||
private suppressReadReceiptAnimation: boolean;
|
private suppressReadReceiptAnimation: boolean;
|
||||||
private isListeningForReceipts: boolean;
|
private isListeningForReceipts: boolean;
|
||||||
|
private ref: React.RefObject<unknown>;
|
||||||
private tile = React.createRef();
|
private tile = React.createRef();
|
||||||
private replyThread = React.createRef();
|
private replyThread = React.createRef();
|
||||||
|
|
||||||
|
@ -322,6 +331,8 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
previouslyRequestedKeys: false,
|
previouslyRequestedKeys: false,
|
||||||
// The Relations model from the JS SDK for reactions to `mxEvent`
|
// The Relations model from the JS SDK for reactions to `mxEvent`
|
||||||
reactions: this.getReactions(),
|
reactions: this.getReactions(),
|
||||||
|
|
||||||
|
hover: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// don't do RR animations until we are mounted
|
// don't do RR animations until we are mounted
|
||||||
|
@ -333,6 +344,8 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
// to determine if we've already subscribed and use a combination of other flags to find
|
// to determine if we've already subscribed and use a combination of other flags to find
|
||||||
// out if we should even be subscribed at all.
|
// out if we should even be subscribed at all.
|
||||||
this.isListeningForReceipts = false;
|
this.isListeningForReceipts = false;
|
||||||
|
|
||||||
|
this.ref = React.createRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -631,7 +644,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
// return early if there are no read receipts
|
// return early if there are no read receipts
|
||||||
if (!this.props.readReceipts || this.props.readReceipts.length === 0) {
|
if (!this.props.readReceipts || this.props.readReceipts.length === 0) {
|
||||||
return (<span className="mx_EventTile_readAvatars" />);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReadReceiptMarker = sdk.getComponent('rooms.ReadReceiptMarker');
|
const ReadReceiptMarker = sdk.getComponent('rooms.ReadReceiptMarker');
|
||||||
|
@ -640,6 +653,11 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
let left = 0;
|
let left = 0;
|
||||||
|
|
||||||
const receipts = this.props.readReceipts || [];
|
const receipts = this.props.readReceipts || [];
|
||||||
|
|
||||||
|
if (receipts.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < receipts.length; ++i) {
|
for (let i = 0; i < receipts.length; ++i) {
|
||||||
const receipt = receipts[i];
|
const receipt = receipts[i];
|
||||||
|
|
||||||
|
@ -690,10 +708,14 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return <span className="mx_EventTile_readAvatars">
|
return (
|
||||||
|
<div className="mx_EventTile_msgOption">
|
||||||
|
<span className="mx_EventTile_readAvatars">
|
||||||
{ remText }
|
{ remText }
|
||||||
{ avatars }
|
{ avatars }
|
||||||
</span>;
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
onSenderProfileClick = event => {
|
onSenderProfileClick = event => {
|
||||||
|
@ -953,7 +975,8 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
onFocusChange={this.onActionBarFocusChange}
|
onFocusChange={this.onActionBarFocusChange}
|
||||||
/> : undefined;
|
/> : undefined;
|
||||||
|
|
||||||
const timestamp = this.props.mxEvent.getTs() ?
|
const showTimestamp = this.props.mxEvent.getTs() && (this.props.alwaysShowTimestamps || this.state.hover);
|
||||||
|
const timestamp = showTimestamp ?
|
||||||
<MessageTimestamp showTwelveHour={this.props.isTwelveHour} ts={this.props.mxEvent.getTs()} /> : null;
|
<MessageTimestamp showTwelveHour={this.props.isTwelveHour} ts={this.props.mxEvent.getTs()} /> : null;
|
||||||
|
|
||||||
const keyRequestHelpText =
|
const keyRequestHelpText =
|
||||||
|
@ -1016,11 +1039,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
let msgOption;
|
let msgOption;
|
||||||
if (this.props.showReadReceipts) {
|
if (this.props.showReadReceipts) {
|
||||||
const readAvatars = this.getReadAvatars();
|
const readAvatars = this.getReadAvatars();
|
||||||
msgOption = (
|
msgOption = readAvatars;
|
||||||
<div className="mx_EventTile_msgOption">
|
|
||||||
{ readAvatars }
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (this.props.tileShape) {
|
switch (this.props.tileShape) {
|
||||||
|
@ -1124,11 +1143,20 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
// tab-index=-1 to allow it to be focusable but do not add tab stop for it, primarily for screen readers
|
// tab-index=-1 to allow it to be focusable but do not add tab stop for it, primarily for screen readers
|
||||||
return (
|
return (
|
||||||
<div className={classes} tabIndex={-1} aria-live={ariaLive} aria-atomic="true">
|
React.createElement(this.props.as || "div", {
|
||||||
{ ircTimestamp }
|
"ref": this.ref,
|
||||||
{ sender }
|
"className": classes,
|
||||||
{ ircPadlock }
|
"tabIndex": -1,
|
||||||
<div className="mx_EventTile_line">
|
"aria-live": ariaLive,
|
||||||
|
"aria-atomic": "true",
|
||||||
|
"data-scroll-tokens": this.props["data-scroll-tokens"],
|
||||||
|
"onMouseEnter": () => this.setState({ hover: true }),
|
||||||
|
"onMouseLeave": () => this.setState({ hover: false }),
|
||||||
|
}, [
|
||||||
|
ircTimestamp,
|
||||||
|
sender,
|
||||||
|
ircPadlock,
|
||||||
|
<div className="mx_EventTile_line" key="mx_EventTile_line">
|
||||||
{ groupTimestamp }
|
{ groupTimestamp }
|
||||||
{ groupPadlock }
|
{ groupPadlock }
|
||||||
{ thread }
|
{ thread }
|
||||||
|
@ -1145,16 +1173,12 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
{ keyRequestInfo }
|
{ keyRequestInfo }
|
||||||
{ reactionsRow }
|
{ reactionsRow }
|
||||||
{ actionBar }
|
{ actionBar }
|
||||||
</div>
|
</div>,
|
||||||
{msgOption}
|
msgOption,
|
||||||
{
|
avatar,
|
||||||
// The avatar goes after the event tile as it's absolutely positioned to be over the
|
|
||||||
// event tile line, so needs to be later in the DOM so it appears on top (this avoids
|
])
|
||||||
// the need for further z-indexing chaos)
|
)
|
||||||
}
|
|
||||||
{ avatar }
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,15 +62,13 @@ export default class SimpleRoomHeader extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_RoomHeader" >
|
<div className="mx_RoomHeader mx_RoomHeader_wrapper" >
|
||||||
<div className="mx_RoomHeader_wrapper">
|
|
||||||
<div className="mx_RoomHeader_simpleHeader">
|
<div className="mx_RoomHeader_simpleHeader">
|
||||||
{ icon }
|
{ icon }
|
||||||
{ this.props.title }
|
{ this.props.title }
|
||||||
{ cancelButton }
|
{ cancelButton }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -215,7 +215,7 @@ export default class WhoIsTypingTile extends React.Component<IProps, IState> {
|
||||||
this.props.whoIsTypingLimit,
|
this.props.whoIsTypingLimit,
|
||||||
);
|
);
|
||||||
if (!typingString) {
|
if (!typingString) {
|
||||||
return (<div className="mx_WhoIsTypingTile_empty" />);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -15,17 +15,14 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { ensureDMExists } from "../../../createRoom";
|
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import Field from "../elements/Field";
|
import Field from "../elements/Field";
|
||||||
import DialPad from './DialPad';
|
import DialPad from './DialPad';
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import Modal from "../../../Modal";
|
|
||||||
import ErrorDialog from "../../views/dialogs/ErrorDialog";
|
|
||||||
import CallHandler from "../../../CallHandler";
|
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
import { DialNumberPayload } from "../../../dispatcher/payloads/DialNumberPayload";
|
||||||
|
import { Action } from "../../../dispatcher/actions";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
onFinished: (boolean) => void;
|
onFinished: (boolean) => void;
|
||||||
|
@ -67,21 +64,11 @@ export default class DialpadModal extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
onDialPress = async () => {
|
onDialPress = async () => {
|
||||||
const results = await CallHandler.sharedInstance().pstnLookup(this.state.value);
|
const payload: DialNumberPayload = {
|
||||||
if (!results || results.length === 0 || !results[0].userid) {
|
action: Action.DialNumber,
|
||||||
Modal.createTrackedDialog('', '', ErrorDialog, {
|
number: this.state.value,
|
||||||
title: _t("Unable to look up phone number"),
|
};
|
||||||
description: _t("There was an error looking up the phone number"),
|
dis.dispatch(payload);
|
||||||
});
|
|
||||||
}
|
|
||||||
const userId = results[0].userid;
|
|
||||||
|
|
||||||
const roomId = await ensureDMExists(MatrixClientPeg.get(), userId);
|
|
||||||
|
|
||||||
dis.dispatch({
|
|
||||||
action: 'view_room',
|
|
||||||
room_id: roomId,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.props.onFinished(true);
|
this.props.onFinished(true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,6 +100,12 @@ export enum Action {
|
||||||
*/
|
*/
|
||||||
OpenDialPad = "open_dial_pad",
|
OpenDialPad = "open_dial_pad",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dial the phone number in the payload
|
||||||
|
* payload: DialNumberPayload
|
||||||
|
*/
|
||||||
|
DialNumber = "dial_number",
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fired when CallHandler has checked for PSTN protocol support
|
* Fired when CallHandler has checked for PSTN protocol support
|
||||||
* payload: none
|
* payload: none
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 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 { ActionPayload } from "../payloads";
|
||||||
|
import { Action } from "../actions";
|
||||||
|
|
||||||
|
export interface DialNumberPayload extends ActionPayload {
|
||||||
|
action: Action.DialNumber;
|
||||||
|
number: string;
|
||||||
|
}
|
|
@ -63,6 +63,8 @@
|
||||||
"Already in call": "Already in call",
|
"Already in call": "Already in call",
|
||||||
"You're already in a call with this person.": "You're already in a call with this person.",
|
"You're already in a call with this person.": "You're already in a call with this person.",
|
||||||
"You cannot place a call with yourself.": "You cannot place a call with yourself.",
|
"You cannot place a call with yourself.": "You cannot place a call with yourself.",
|
||||||
|
"Unable to look up phone number": "Unable to look up phone number",
|
||||||
|
"There was an error looking up the phone number": "There was an error looking up the phone number",
|
||||||
"Call in Progress": "Call in Progress",
|
"Call in Progress": "Call in Progress",
|
||||||
"A call is currently being placed!": "A call is currently being placed!",
|
"A call is currently being placed!": "A call is currently being placed!",
|
||||||
"Permission Required": "Permission Required",
|
"Permission Required": "Permission Required",
|
||||||
|
@ -898,8 +900,6 @@
|
||||||
"Fill Screen": "Fill Screen",
|
"Fill Screen": "Fill Screen",
|
||||||
"Return to call": "Return to call",
|
"Return to call": "Return to call",
|
||||||
"%(name)s on hold": "%(name)s on hold",
|
"%(name)s on hold": "%(name)s on hold",
|
||||||
"Unable to look up phone number": "Unable to look up phone number",
|
|
||||||
"There was an error looking up the phone number": "There was an error looking up the phone number",
|
|
||||||
"Dial pad": "Dial pad",
|
"Dial pad": "Dial pad",
|
||||||
"Unknown caller": "Unknown caller",
|
"Unknown caller": "Unknown caller",
|
||||||
"Incoming voice call": "Incoming voice call",
|
"Incoming voice call": "Incoming voice call",
|
||||||
|
|
|
@ -65,6 +65,10 @@ class FlairStore extends EventEmitter {
|
||||||
delete this._userGroups[userId];
|
delete this._userGroups[userId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cachedPublicisedGroups(userId) {
|
||||||
|
return this._userGroups[userId];
|
||||||
|
}
|
||||||
|
|
||||||
getPublicisedGroupsCached(matrixClient, userId) {
|
getPublicisedGroupsCached(matrixClient, userId) {
|
||||||
if (this._userGroups[userId]) {
|
if (this._userGroups[userId]) {
|
||||||
return Promise.resolve(this._userGroups[userId]);
|
return Promise.resolve(this._userGroups[userId]);
|
||||||
|
|
|
@ -23,8 +23,10 @@ import dis from '../src/dispatcher/dispatcher';
|
||||||
import { CallEvent, CallState } from 'matrix-js-sdk/src/webrtc/call';
|
import { CallEvent, CallState } from 'matrix-js-sdk/src/webrtc/call';
|
||||||
import DMRoomMap from '../src/utils/DMRoomMap';
|
import DMRoomMap from '../src/utils/DMRoomMap';
|
||||||
import EventEmitter from 'events';
|
import EventEmitter from 'events';
|
||||||
import { Action } from '../src/dispatcher/actions';
|
|
||||||
import SdkConfig from '../src/SdkConfig';
|
import SdkConfig from '../src/SdkConfig';
|
||||||
|
import { ActionPayload } from '../src/dispatcher/payloads';
|
||||||
|
import { Actions } from '../src/notifications/types';
|
||||||
|
import { Action } from '../src/dispatcher/actions';
|
||||||
|
|
||||||
const REAL_ROOM_ID = '$room1:example.org';
|
const REAL_ROOM_ID = '$room1:example.org';
|
||||||
const MAPPED_ROOM_ID = '$room2:example.org';
|
const MAPPED_ROOM_ID = '$room2:example.org';
|
||||||
|
@ -75,6 +77,18 @@ class FakeCall extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function untilDispatch(waitForAction: string): Promise<ActionPayload> {
|
||||||
|
let dispatchHandle;
|
||||||
|
return new Promise<ActionPayload>(resolve => {
|
||||||
|
dispatchHandle = dis.register(payload => {
|
||||||
|
if (payload.action === waitForAction) {
|
||||||
|
dis.unregister(dispatchHandle);
|
||||||
|
resolve(payload);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
describe('CallHandler', () => {
|
describe('CallHandler', () => {
|
||||||
let dmRoomMap;
|
let dmRoomMap;
|
||||||
let callHandler;
|
let callHandler;
|
||||||
|
@ -94,6 +108,21 @@ describe('CallHandler', () => {
|
||||||
callHandler = new CallHandler();
|
callHandler = new CallHandler();
|
||||||
callHandler.start();
|
callHandler.start();
|
||||||
|
|
||||||
|
const realRoom = mkStubDM(REAL_ROOM_ID, '@user1:example.org');
|
||||||
|
const mappedRoom = mkStubDM(MAPPED_ROOM_ID, '@user2:example.org');
|
||||||
|
const mappedRoom2 = mkStubDM(MAPPED_ROOM_ID_2, '@user3:example.org');
|
||||||
|
|
||||||
|
MatrixClientPeg.get().getRoom = roomId => {
|
||||||
|
switch (roomId) {
|
||||||
|
case REAL_ROOM_ID:
|
||||||
|
return realRoom;
|
||||||
|
case MAPPED_ROOM_ID:
|
||||||
|
return mappedRoom;
|
||||||
|
case MAPPED_ROOM_ID_2:
|
||||||
|
return mappedRoom2;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
dmRoomMap = {
|
dmRoomMap = {
|
||||||
getUserIdForRoomId: roomId => {
|
getUserIdForRoomId: roomId => {
|
||||||
if (roomId === REAL_ROOM_ID) {
|
if (roomId === REAL_ROOM_ID) {
|
||||||
|
@ -134,38 +163,34 @@ describe('CallHandler', () => {
|
||||||
SdkConfig.unset();
|
SdkConfig.unset();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should look up the correct user and open the room when a phone number is dialled', async () => {
|
||||||
|
MatrixClientPeg.get().getThirdpartyUser = jest.fn().mockResolvedValue([{
|
||||||
|
userid: '@user2:example.org',
|
||||||
|
protocol: "im.vector.protocol.sip_native",
|
||||||
|
fields: {
|
||||||
|
is_native: true,
|
||||||
|
lookup_success: true,
|
||||||
|
},
|
||||||
|
}]);
|
||||||
|
|
||||||
|
dis.dispatch({
|
||||||
|
action: Action.DialNumber,
|
||||||
|
number: '01818118181',
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
const viewRoomPayload = await untilDispatch('view_room');
|
||||||
|
expect(viewRoomPayload.room_id).toEqual(MAPPED_ROOM_ID);
|
||||||
|
});
|
||||||
|
|
||||||
it('should move calls between rooms when remote asserted identity changes', async () => {
|
it('should move calls between rooms when remote asserted identity changes', async () => {
|
||||||
const realRoom = mkStubDM(REAL_ROOM_ID, '@user1:example.org');
|
|
||||||
const mappedRoom = mkStubDM(MAPPED_ROOM_ID, '@user2:example.org');
|
|
||||||
const mappedRoom2 = mkStubDM(MAPPED_ROOM_ID_2, '@user3:example.org');
|
|
||||||
|
|
||||||
MatrixClientPeg.get().getRoom = roomId => {
|
|
||||||
switch (roomId) {
|
|
||||||
case REAL_ROOM_ID:
|
|
||||||
return realRoom;
|
|
||||||
case MAPPED_ROOM_ID:
|
|
||||||
return mappedRoom;
|
|
||||||
case MAPPED_ROOM_ID_2:
|
|
||||||
return mappedRoom2;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'place_call',
|
action: 'place_call',
|
||||||
type: PlaceCallType.Voice,
|
type: PlaceCallType.Voice,
|
||||||
room_id: REAL_ROOM_ID,
|
room_id: REAL_ROOM_ID,
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
let dispatchHandle;
|
|
||||||
// wait for the call to be set up
|
// wait for the call to be set up
|
||||||
await new Promise<void>(resolve => {
|
await untilDispatch('call_state');
|
||||||
dispatchHandle = dis.register(payload => {
|
|
||||||
if (payload.action === 'call_state') {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
dis.unregister(dispatchHandle);
|
|
||||||
|
|
||||||
// should start off in the actual room ID it's in at the protocol level
|
// should start off in the actual room ID it's in at the protocol level
|
||||||
expect(callHandler.getCallForRoom(REAL_ROOM_ID)).toBe(fakeCall);
|
expect(callHandler.getCallForRoom(REAL_ROOM_ID)).toBe(fakeCall);
|
||||||
|
|
|
@ -309,7 +309,7 @@ describe('MessagePanel', function() {
|
||||||
const rm = TestUtils.findRenderedDOMComponentWithClass(res, 'mx_RoomView_myReadMarker_container');
|
const rm = TestUtils.findRenderedDOMComponentWithClass(res, 'mx_RoomView_myReadMarker_container');
|
||||||
|
|
||||||
// it should follow the <li> which wraps the event tile for event 4
|
// it should follow the <li> which wraps the event tile for event 4
|
||||||
const eventContainer = ReactDOM.findDOMNode(tiles[4]).parentNode;
|
const eventContainer = ReactDOM.findDOMNode(tiles[4]);
|
||||||
expect(rm.previousSibling).toEqual(eventContainer);
|
expect(rm.previousSibling).toEqual(eventContainer);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -365,7 +365,7 @@ describe('MessagePanel', function() {
|
||||||
const tiles = TestUtils.scryRenderedComponentsWithType(
|
const tiles = TestUtils.scryRenderedComponentsWithType(
|
||||||
mp, sdk.getComponent('rooms.EventTile'));
|
mp, sdk.getComponent('rooms.EventTile'));
|
||||||
const tileContainers = tiles.map(function(t) {
|
const tileContainers = tiles.map(function(t) {
|
||||||
return ReactDOM.findDOMNode(t).parentNode;
|
return ReactDOM.findDOMNode(t);
|
||||||
});
|
});
|
||||||
|
|
||||||
// find the <li> which wraps the read marker
|
// find the <li> which wraps the read marker
|
||||||
|
|
Loading…
Reference in New Issue