1
0
mirror of https://github.com/matrix-org/matrix-react-sdk.git synced 2025-11-08 21:42:24 +03:00

Merge branch 'develop' into gsouquet/dialogs-ts-migration

This commit is contained in:
Germain Souquet
2021-06-18 12:27:00 +01:00
80 changed files with 1061 additions and 449 deletions

View File

@@ -1953,6 +1953,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
// Create and start the client
await Lifecycle.setLoggedIn(credentials);
await this.postLoginSetup();
PerformanceMonitor.instance.stop(PerformanceEntryNames.LOGIN);
PerformanceMonitor.instance.stop(PerformanceEntryNames.REGISTER);
};

View File

@@ -41,7 +41,7 @@ export function getUnsentMessages(room) {
}
@replaceableComponent("structures.RoomStatusBar")
export default class RoomStatusBar extends React.Component {
export default class RoomStatusBar extends React.PureComponent {
static propTypes = {
// the room this statusbar is representing.
room: PropTypes.object.isRequired,

View File

@@ -25,6 +25,7 @@ import React, { createRef } from 'react';
import classNames from 'classnames';
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { SearchResult } from "matrix-js-sdk/src/models/search-result";
import { EventSubscription } from "fbemitter";
import shouldHideEvent from '../../shouldHideEvent';
@@ -80,7 +81,6 @@ import { objectHasDiff } from "../../utils/objects";
import SpaceRoomView from "./SpaceRoomView";
import { IOpts } from "../../createRoom";
import { replaceableComponent } from "../../utils/replaceableComponent";
import { omit } from 'lodash';
import UIStore from "../../stores/UIStore";
const DEBUG = false;
@@ -143,7 +143,7 @@ export interface IState {
searchResults?: XOR<{}, {
count: number;
highlights: string[];
results: MatrixEvent[];
results: SearchResult[];
next_batch: string; // eslint-disable-line camelcase
}>;
searchHighlights?: string[];
@@ -572,16 +572,12 @@ export default class RoomView extends React.Component<IProps, IState> {
shouldComponentUpdate(nextProps, 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 { upgradeRecommendation, ...state } = this.state;
const { upgradeRecommendation: newUpgradeRecommendation, ...newState } = nextState;
const hasStateDiff =
objectHasDiff(state, newState) ||
(newUpgradeRecommendation.needsUpgrade === true)
newUpgradeRecommendation?.needsUpgrade !== upgradeRecommendation?.needsUpgrade ||
objectHasDiff(state, newState);
return hasPropsDiff || hasStateDiff;
}
@@ -701,16 +697,11 @@ export default class RoomView extends React.Component<IProps, IState> {
room_id: this.state.room.roomId,
event_id: this.state.initialEventId,
highlighted: false,
replyingToEvent: this.state.replyToEvent,
});
}
}
private onLayoutChange = () => {
this.setState({
layout: SettingsStore.getValue("layout"),
});
};
private onRightPanelStoreUpdate = () => {
this.setState({
showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom,
@@ -1644,29 +1635,27 @@ export default class RoomView extends React.Component<IProps, IState> {
let auxPanelMaxHeight = UIStore.instance.windowHeight -
(54 + // height of RoomHeader
36 + // height of the status area
51 + // minimum height of the message compmoser
51 + // minimum height of the message composer
120); // amount of desired scrollback
// XXX: this is a bit of a hack and might possibly cause the video to push out the page anyway
// but it's better than the video going missing entirely
if (auxPanelMaxHeight < 50) auxPanelMaxHeight = 50;
this.setState({auxPanelMaxHeight: auxPanelMaxHeight});
if (this.state.auxPanelMaxHeight !== auxPanelMaxHeight) {
this.setState({ auxPanelMaxHeight });
}
};
private onStatusBarVisible = () => {
if (this.unmounted) return;
this.setState({
statusBarVisible: true,
});
if (this.unmounted || this.state.statusBarVisible) return;
this.setState({ statusBarVisible: true });
};
private onStatusBarHidden = () => {
// This is currently not desired as it is annoying if it keeps expanding and collapsing
if (this.unmounted) return;
this.setState({
statusBarVisible: false,
});
if (this.unmounted || !this.state.statusBarVisible) return;
this.setState({ statusBarVisible: false });
};
/**

View File

@@ -18,14 +18,14 @@ limitations under the License.
*/
import SettingsStore from "../../settings/SettingsStore";
import {LayoutPropType} from "../../settings/Layout";
import React, {createRef} from 'react';
import { LayoutPropType } from "../../settings/Layout";
import React, { createRef } from 'react';
import ReactDOM from "react-dom";
import PropTypes from 'prop-types';
import {EventTimeline} from "matrix-js-sdk/src/models/event-timeline";
import {TimelineWindow} from "matrix-js-sdk/src/timeline-window";
import { EventTimeline } from "matrix-js-sdk/src/models/event-timeline";
import { TimelineWindow } from "matrix-js-sdk/src/timeline-window";
import { _t } from '../../languageHandler';
import {MatrixClientPeg} from "../../MatrixClientPeg";
import { MatrixClientPeg } from "../../MatrixClientPeg";
import RoomContext from "../../contexts/RoomContext";
import UserActivity from "../../UserActivity";
import Modal from "../../Modal";
@@ -35,10 +35,11 @@ import { Key } from '../../Keyboard';
import Timer from '../../utils/Timer';
import shouldHideEvent from '../../shouldHideEvent';
import EditorStateTransfer from '../../utils/EditorStateTransfer';
import {haveTileForEvent} from "../views/rooms/EventTile";
import {UIFeature} from "../../settings/UIFeature";
import {replaceableComponent} from "../../utils/replaceableComponent";
import { haveTileForEvent } from "../views/rooms/EventTile";
import { UIFeature } from "../../settings/UIFeature";
import { replaceableComponent } from "../../utils/replaceableComponent";
import { arrayFastClone } from "../../utils/arrays";
import { Action } from "../../dispatcher/actions";
const PAGINATE_SIZE = 20;
const INITIAL_SIZE = 20;
@@ -439,21 +440,42 @@ class TimelinePanel extends React.Component {
};
onAction = payload => {
if (payload.action === 'ignore_state_changed') {
this.forceUpdate();
}
if (payload.action === "edit_event") {
const editState = payload.event ? new EditorStateTransfer(payload.event) : null;
this.setState({editState}, () => {
if (payload.event && this._messagePanel.current) {
this._messagePanel.current.scrollToEventIfNeeded(
payload.event.getId(),
);
switch (payload.action) {
case "ignore_state_changed":
this.forceUpdate();
break;
case "edit_event": {
const editState = payload.event ? new EditorStateTransfer(payload.event) : null;
this.setState({editState}, () => {
if (payload.event && this._messagePanel.current) {
this._messagePanel.current.scrollToEventIfNeeded(
payload.event.getId(),
);
}
});
break;
}
case Action.ComposerInsert: {
// re-dispatch to the correct composer
if (this.state.editState) {
dis.dispatch({
...payload,
action: "edit_composer_insert",
});
} else {
dis.dispatch({
...payload,
action: "send_composer_insert",
});
}
});
}
if (payload.action === "scroll_to_bottom") {
this.jumpToLiveTimeline();
break;
}
case "scroll_to_bottom":
this.jumpToLiveTimeline();
break;
}
};

View File

@@ -33,6 +33,8 @@ import { EventType } from "matrix-js-sdk/src/@types/event";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { ReadPinsEventId } from "../right_panel/PinnedMessagesCard";
import ForwardDialog from "../dialogs/ForwardDialog";
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
import { Action } from "../../../dispatcher/actions";
export function canCancel(eventStatus) {
return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
@@ -199,8 +201,8 @@ export default class MessageContextMenu extends React.Component {
};
onQuoteClick = () => {
dis.dispatch({
action: 'quote',
dis.dispatch<ComposerInsertPayload>({
action: Action.ComposerInsert,
event: this.props.mxEvent,
});
this.closeMenu();

View File

@@ -525,11 +525,11 @@ class RoomStateExplorer extends React.PureComponent<IExplorerProps, IRoomStateEx
}
interface IAccountDataExplorerState {
[inputId: string]: boolean | string | any;
isRoomAccountData: boolean;
event?: MatrixEvent;
editing: boolean;
queryEventType: string;
[inputId: string]: boolean | string;
}
class AccountDataExplorer extends React.PureComponent<IExplorerProps, IAccountDataExplorerState> {

View File

@@ -162,6 +162,7 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
});
mockEvent.sender = {
name: profileInfo.displayname || userId,
rawDisplayName: profileInfo.displayname,
userId,
getAvatarUrl: (..._) => {
return avatarUrlForUser(

View File

@@ -153,8 +153,8 @@ class ThreepidMember extends Member {
}
interface IDMUserTileProps {
member: RoomMember;
onRemove(member: RoomMember): void;
member: Member;
onRemove(member: Member): void;
}
class DMUserTile extends React.PureComponent<IDMUserTileProps> {
@@ -168,7 +168,7 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
render() {
const avatarSize = 20;
const avatar = this.props.member.isEmail
const avatar = (this.props.member as ThreepidMember).isEmail
? <img
className='mx_InviteDialog_userTile_avatar mx_InviteDialog_userTile_threepidAvatar'
src={require("../../../../res/img/icon-email-pill-avatar.svg")}
@@ -210,9 +210,9 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
}
interface IDMRoomTileProps {
member: RoomMember;
member: Member;
lastActiveTs: number;
onToggle(member: RoomMember): void;
onToggle(member: Member): void;
highlightWord: string;
isSelected: boolean;
}
@@ -270,7 +270,7 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
}
const avatarSize = 36;
const avatar = this.props.member.isEmail
const avatar = (this.props.member as ThreepidMember).isEmail
? <img
src={require("../../../../res/img/icon-email-pill-avatar.svg")}
width={avatarSize} height={avatarSize} />
@@ -298,7 +298,7 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
</span>
);
const caption = this.props.member.isEmail
const caption = (this.props.member as ThreepidMember).isEmail
? _t("Invite by email")
: this.highlightName(this.props.member.userId);
@@ -334,7 +334,7 @@ interface IInviteDialogProps {
}
interface IInviteDialogState {
targets: RoomMember[]; // array of Member objects (see interface above)
targets: Member[]; // array of Member objects (see interface above)
filterText: string;
recents: { user: Member, userId: string }[];
numRecentsShown: number;

View File

@@ -24,7 +24,7 @@ import { _t } from '../../../languageHandler';
import { formatCommaSeparatedList } from '../../../utils/FormattingUtils';
import { isValid3pidInvite } from "../../../RoomInvite";
import EventListSummary from "./EventListSummary";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps {
// An array of member events to summarise
@@ -303,7 +303,7 @@ export default class MemberEventListSummary extends React.Component<IProps> {
return res;
}
private static getTransitionSequence(events: MatrixEvent[]) {
private static getTransitionSequence(events: IUserEvents[]) {
return events.map(MemberEventListSummary.getTransition);
}
@@ -315,7 +315,7 @@ export default class MemberEventListSummary extends React.Component<IProps> {
* @returns {string?} the transition type given to this event. This defaults to `null`
* if a transition is not recognised.
*/
private static getTransition(e: MatrixEvent): TransitionType {
private static getTransition(e: IUserEvents): TransitionType {
if (e.mxEvent.getType() === 'm.room.third_party_invite') {
// Handle 3pid invites the same as invites so they get bundled together
if (!isValid3pidInvite(e.mxEvent)) {

View File

@@ -297,6 +297,7 @@ export default class ReplyThread extends React.Component {
}
async getEvent(eventId) {
if (!eventId) return null;
const event = this.room.findEventById(eventId);
if (event) return event;
@@ -392,6 +393,7 @@ export default class ReplyThread extends React.Component {
alwaysShowTimestamps={this.props.alwaysShowTimestamps}
enableFlair={SettingsStore.getValue(UIFeature.Flair)}
replacingEventId={ev.replacingEventId()}
as="div"
/>
</blockquote>;
});

View File

@@ -24,6 +24,7 @@ import {_t} from "../../../languageHandler";
import {mediaFromContent} from "../../../customisations/Media";
import {decryptFile} from "../../../utils/DecryptFile";
import RecordingPlayback from "../voice_messages/RecordingPlayback";
import {IMediaEventContent} from "../../../customisations/models/IMediaEventContent";
interface IProps {
mxEvent: MatrixEvent;
@@ -45,7 +46,7 @@ export default class MVoiceMessageBody extends React.PureComponent<IProps, IStat
public async componentDidMount() {
let buffer: ArrayBuffer;
const content = this.props.mxEvent.getContent();
const content: IMediaEventContent = this.props.mxEvent.getContent();
const media = mediaFromContent(content);
if (media.isEncrypted) {
try {

View File

@@ -16,20 +16,19 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import {formatFullDate, formatTime, formatFullTime} from '../../../DateUtils';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { formatFullDate, formatTime, formatFullTime } from '../../../DateUtils';
import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps {
ts: number;
showTwelveHour?: boolean;
showFullDate?: boolean;
showSeconds?: boolean;
}
@replaceableComponent("views.messages.MessageTimestamp")
export default class MessageTimestamp extends React.Component {
static propTypes = {
ts: PropTypes.number.isRequired,
showTwelveHour: PropTypes.bool,
showFullDate: PropTypes.bool,
showSeconds: PropTypes.bool,
};
render() {
export default class MessageTimestamp extends React.Component<IProps> {
public render() {
const date = new Date(this.props.ts);
let timestamp;
if (this.props.showFullDate) {
@@ -41,7 +40,11 @@ export default class MessageTimestamp extends React.Component {
}
return (
<span className="mx_MessageTimestamp" title={formatFullDate(date, this.props.showTwelveHour)} aria-hidden={true}>
<span
className="mx_MessageTimestamp"
title={formatFullDate(date, this.props.showTwelveHour)}
aria-hidden={true}
>
{timestamp}
</span>
);

View File

@@ -17,10 +17,10 @@
import React from 'react';
import Flair from '../elements/Flair.js';
import FlairStore from '../../../stores/FlairStore';
import {getUserNameColorClass} from '../../../utils/FormattingUtils';
import { getUserNameColorClass } from '../../../utils/FormattingUtils';
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import MatrixEvent from "matrix-js-sdk/src/models/event";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
interface IProps {
mxEvent: MatrixEvent;

View File

@@ -16,12 +16,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, {createRef} from 'react';
import React, { createRef } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import highlight from 'highlight.js';
import * as HtmlUtils from '../../../HtmlUtils';
import {formatDate} from '../../../DateUtils';
import { formatDate } from '../../../DateUtils';
import * as sdk from '../../../index';
import Modal from '../../../Modal';
import dis from '../../../dispatcher/dispatcher';
@@ -29,14 +29,16 @@ import { _t } from '../../../languageHandler';
import * as ContextMenu from '../../structures/ContextMenu';
import SettingsStore from "../../../settings/SettingsStore";
import ReplyThread from "../elements/ReplyThread";
import {pillifyLinks, unmountPills} from '../../../utils/pillify';
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
import {isPermalinkHost} from "../../../utils/permalinks/Permalinks";
import {toRightOf} from "../../structures/ContextMenu";
import {copyPlaintext} from "../../../utils/strings";
import { pillifyLinks, unmountPills } from '../../../utils/pillify';
import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
import { isPermalinkHost } from "../../../utils/permalinks/Permalinks";
import { toRightOf } from "../../structures/ContextMenu";
import { copyPlaintext } from "../../../utils/strings";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import UIStore from "../../../stores/UIStore";
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
import { Action } from "../../../dispatcher/actions";
@replaceableComponent("views.messages.TextualBody")
export default class TextualBody extends React.Component {
@@ -390,9 +392,9 @@ export default class TextualBody extends React.Component {
onEmoteSenderClick = event => {
const mxEvent = this.props.mxEvent;
dis.dispatch({
action: 'insert_mention',
user_id: mxEvent.getSender(),
dis.dispatch<ComposerInsertPayload>({
action: Action.ComposerInsert,
userId: mxEvent.getSender(),
});
};

View File

@@ -17,8 +17,9 @@ limitations under the License.
import React from "react";
import * as sdk from "../../../index";
import {_t} from "../../../languageHandler";
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
import { _t } from "../../../languageHandler";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { User } from "matrix-js-sdk/src/models/user";
export const PendingActionSpinner = ({text}) => {
const Spinner = sdk.getComponent('elements.Spinner');
@@ -31,7 +32,7 @@ export const PendingActionSpinner = ({text}) => {
interface IProps {
waitingForOtherParty: boolean;
waitingForNetwork: boolean;
member: RoomMember;
member: RoomMember | User;
onStartVerification: () => Promise<void>;
isRoomEncrypted: boolean;
inDialog: boolean;
@@ -55,7 +56,7 @@ const EncryptionInfo: React.FC<IProps> = ({
text = _t("Accept on your other login…");
} else {
text = _t("Waiting for %(displayName)s to accept…", {
displayName: member.displayName || member.name || member.userId,
displayName: (member as User).displayName || (member as RoomMember).name || member.userId,
});
}
} else {

View File

@@ -38,7 +38,7 @@ import SettingsStore from "../../../settings/SettingsStore";
import RoomViewStore from "../../../stores/RoomViewStore";
import MultiInviter from "../../../utils/MultiInviter";
import GroupStore from "../../../stores/GroupStore";
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import E2EIcon from "../rooms/E2EIcon";
import { useEventEmitter } from "../../../hooks/useEventEmitter";
import { textualPowerLevel } from '../../../Roles';
@@ -68,6 +68,7 @@ import RoomAvatar from "../avatars/RoomAvatar";
import RoomName from "../elements/RoomName";
import { mediaFromMxc } from "../../../customisations/Media";
import UIStore from "../../../stores/UIStore";
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
export interface IDevice {
deviceId: string;
@@ -146,7 +147,7 @@ async function openDMForUser(matrixClient: MatrixClient, userId: string) {
type SetUpdating = (updating: boolean) => void;
function useHasCrossSigningKeys(cli: MatrixClient, member: RoomMember, canVerify: boolean, setUpdating: SetUpdating) {
function useHasCrossSigningKeys(cli: MatrixClient, member: User, canVerify: boolean, setUpdating: SetUpdating) {
return useAsyncMemo(async () => {
if (!canVerify) {
return undefined;
@@ -368,9 +369,9 @@ const UserOptionsSection: React.FC<{
};
const onInsertPillButton = function() {
dis.dispatch({
action: 'insert_mention',
user_id: member.userId,
dis.dispatch<ComposerInsertPayload>({
action: Action.ComposerInsert,
userId: member.userId,
});
};
@@ -971,7 +972,7 @@ interface IRoomPermissions {
canInvite: boolean;
}
function useRoomPermissions(cli: MatrixClient, room: Room, user: User): IRoomPermissions {
function useRoomPermissions(cli: MatrixClient, room: Room, user: RoomMember): IRoomPermissions {
const [roomPermissions, setRoomPermissions] = useState<IRoomPermissions>({
// modifyLevelMax is the max PL we can set this user to, typically min(their PL, our PL) && canSetPL
modifyLevelMax: -1,
@@ -1028,7 +1029,7 @@ function useRoomPermissions(cli: MatrixClient, room: Room, user: User): IRoomPer
}
const PowerLevelSection: React.FC<{
user: User;
user: RoomMember;
room: Room;
roomPermissions: IRoomPermissions;
powerLevels: IPowerLevelsContent;
@@ -1037,7 +1038,7 @@ const PowerLevelSection: React.FC<{
return (<PowerLevelEditor user={user} room={room} roomPermissions={roomPermissions} />);
} else {
const powerLevelUsersDefault = powerLevels.users_default || 0;
const powerLevel = parseInt(user.powerLevel, 10);
const powerLevel = user.powerLevel;
const role = textualPowerLevel(powerLevel, powerLevelUsersDefault);
return (
<div className="mx_UserInfo_profileField">
@@ -1048,13 +1049,13 @@ const PowerLevelSection: React.FC<{
};
const PowerLevelEditor: React.FC<{
user: User;
user: RoomMember;
room: Room;
roomPermissions: IRoomPermissions;
}> = ({user, room, roomPermissions}) => {
const cli = useContext(MatrixClientContext);
const [selectedPowerLevel, setSelectedPowerLevel] = useState(parseInt(user.powerLevel, 10));
const [selectedPowerLevel, setSelectedPowerLevel] = useState(user.powerLevel);
const onPowerChange = useCallback(async (powerLevelStr: string) => {
const powerLevel = parseInt(powerLevelStr, 10);
setSelectedPowerLevel(powerLevel);
@@ -1231,7 +1232,7 @@ const BasicUserInfo: React.FC<{
setPendingUpdateCount(pendingUpdateCount - 1);
}, [pendingUpdateCount]);
const roomPermissions = useRoomPermissions(cli, room, member);
const roomPermissions = useRoomPermissions(cli, room, member as RoomMember);
const onSynapseDeactivate = useCallback(async () => {
const {finished} = Modal.createTrackedDialog('Synapse User Deactivation', '', QuestionDialog, {
@@ -1275,12 +1276,26 @@ const BasicUserInfo: React.FC<{
);
}
let memberDetails;
let adminToolsContainer;
if (room && member.roomId) {
if (room && (member as RoomMember).roomId) {
// hide the Roles section for DMs as it doesn't make sense there
if (!DMRoomMap.shared().getUserIdForRoomId((member as RoomMember).roomId)) {
memberDetails = <div className="mx_UserInfo_container">
<h3>{ _t("Role") }</h3>
<PowerLevelSection
powerLevels={powerLevels}
user={member as RoomMember}
room={room}
roomPermissions={roomPermissions}
/>
</div>;
}
adminToolsContainer = (
<RoomAdminToolsContainer
powerLevels={powerLevels}
member={member}
member={member as RoomMember}
room={room}
startUpdating={startUpdating}
stopUpdating={stopUpdating}>
@@ -1309,20 +1324,6 @@ const BasicUserInfo: React.FC<{
spinner = <Spinner />;
}
let memberDetails;
// hide the Roles section for DMs as it doesn't make sense there
if (room && member.roomId && !DMRoomMap.shared().getUserIdForRoomId(member.roomId)) {
memberDetails = <div className="mx_UserInfo_container">
<h3>{ _t("Role") }</h3>
<PowerLevelSection
powerLevels={powerLevels}
user={member}
room={room}
roomPermissions={roomPermissions}
/>
</div>;
}
// only display the devices list if our client supports E2E
const cryptoEnabled = cli.isCryptoEnabled();
@@ -1349,8 +1350,7 @@ const BasicUserInfo: React.FC<{
const setUpdating = (updating) => {
setPendingUpdateCount(count => count + (updating ? 1 : -1));
};
const hasCrossSigningKeys =
useHasCrossSigningKeys(cli, member, canVerify, setUpdating );
const hasCrossSigningKeys = useHasCrossSigningKeys(cli, member as User, canVerify, setUpdating);
const showDeviceListSpinner = devices === undefined;
if (canVerify) {
@@ -1359,9 +1359,9 @@ const BasicUserInfo: React.FC<{
verifyButton = (
<AccessibleButton className="mx_UserInfo_field mx_UserInfo_verifyButton" onClick={() => {
if (hasCrossSigningKeys) {
verifyUser(member);
verifyUser(member as User);
} else {
legacyVerifyUser(member);
legacyVerifyUser(member as User);
}
}}>
{_t("Verify")}
@@ -1409,7 +1409,7 @@ const BasicUserInfo: React.FC<{
<UserOptionsSection
canInvite={roomPermissions.canInvite}
isIgnored={isIgnored}
member={member}
member={member as RoomMember}
isSpace={SettingsStore.getValue("feature_spaces") && room?.isSpaceRoom()}
/>
@@ -1428,13 +1428,15 @@ const UserInfoHeader: React.FC<{
const cli = useContext(MatrixClientContext);
const onMemberAvatarClick = useCallback(() => {
const avatarUrl = member.getMxcAvatarUrl ? member.getMxcAvatarUrl() : member.avatarUrl;
const avatarUrl = (member as RoomMember).getMxcAvatarUrl
? (member as RoomMember).getMxcAvatarUrl()
: (member as User).avatarUrl;
if (!avatarUrl) return;
const httpUrl = mediaFromMxc(avatarUrl).srcHttp;
const params = {
src: httpUrl,
name: member.name,
name: (member as RoomMember).name || (member as User).displayName,
};
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", null, true);
@@ -1446,13 +1448,13 @@ const UserInfoHeader: React.FC<{
<div>
<MemberAvatar
key={member.userId} // to instantly blank the avatar when UserInfo changes members
member={member}
member={member as RoomMember}
width={2 * 0.3 * UIStore.instance.windowHeight} // 2x@30vh
height={2 * 0.3 * UIStore.instance.windowHeight} // 2x@30vh
resizeMethod="scale"
fallbackUserId={member.userId}
onClick={onMemberAvatarClick}
urls={member.avatarUrl ? [member.avatarUrl] : undefined} />
urls={(member as User).avatarUrl ? [(member as User).avatarUrl] : undefined} />
</div>
</div>
</div>
@@ -1469,7 +1471,11 @@ const UserInfoHeader: React.FC<{
presenceCurrentlyActive = member.user.currentlyActive;
if (SettingsStore.getValue("feature_custom_status")) {
statusMessage = member.user._unstable_statusMessage;
if ((member as RoomMember).user) {
statusMessage = member.user.unstable_statusMessage;
} else {
statusMessage = (member as unknown as User).unstable_statusMessage;
}
}
}
@@ -1500,7 +1506,7 @@ const UserInfoHeader: React.FC<{
e2eIcon = <E2EIcon size={18} status={e2eStatus} isUser={true} />;
}
const displayName = member.rawDisplayName || member.displayname;
const displayName = (member as RoomMember).rawDisplayName || (member as GroupMember).displayname;
return <React.Fragment>
{ avatarElement }

View File

@@ -22,6 +22,7 @@ import {verificationMethods} from 'matrix-js-sdk/src/crypto';
import {SCAN_QR_CODE_METHOD} from "matrix-js-sdk/src/crypto/verification/QRCode";
import {VerificationRequest} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
import { User } from "matrix-js-sdk/src/models/user";
import {ReciprocateQRCode} from "matrix-js-sdk/src/crypto/verification/QRCode";
import {SAS} from "matrix-js-sdk/src/crypto/verification/SAS";
@@ -51,7 +52,7 @@ enum VerificationPhase {
interface IProps {
layout: string;
request: VerificationRequest;
member: RoomMember;
member: RoomMember | User;
phase: VerificationPhase;
onClose: () => void;
isRoomEncrypted: boolean;
@@ -134,7 +135,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
qrBlock = <div className="mx_UserInfo_container">
<h3>{_t("Verify by scanning")}</h3>
<p>{_t("Ask %(displayName)s to scan your code:", {
displayName: member.displayName || member.name || member.userId,
displayName: (member as User).displayName || (member as RoomMember).name || member.userId,
})}</p>
<div className="mx_VerificationPanel_qrCode">
@@ -205,7 +206,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
const description = request.isSelfVerification ?
_t("Almost there! Is your other session showing the same shield?") :
_t("Almost there! Is %(displayName)s showing the same shield?", {
displayName: member.displayName || member.name || member.userId,
displayName: (member as User).displayName || (member as RoomMember).name || member.userId,
});
let body: JSX.Element;
if (this.state.reciprocateQREvent) {
@@ -264,7 +265,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
}
} else {
description = _t("You've successfully verified %(displayName)s!", {
displayName: member.displayName || member.name || member.userId,
displayName: (member as User).displayName || (member as RoomMember).name || member.userId,
});
}
@@ -302,7 +303,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
text = _t("You cancelled verification on your other session.");
} else {
text = _t("%(displayName)s cancelled verification.", {
displayName: member.displayName || member.name || member.userId,
displayName: (member as User).displayName || (member as RoomMember).name || member.userId,
});
}
text = `${text} ${startAgainInstruction}`;
@@ -325,7 +326,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
public render() {
const {member, phase, request} = this.props;
const displayName = member.displayName || member.name || member.userId;
const displayName = (member as User).displayName || (member as RoomMember).name || member.userId;
switch (phase) {
case PHASE_READY:

View File

@@ -82,13 +82,6 @@ export default class AppsDrawer extends React.Component {
this.props.resizeNotifier.off("isResizing", this.onIsResizing);
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
// eslint-disable-next-line camelcase
UNSAFE_componentWillReceiveProps(newProps) {
// Room has changed probably, update apps
this._updateApps();
}
onIsResizing = (resizing) => {
// This one is the vertical, ie. change height of apps drawer
this.setState({ resizingVertical: resizing });
@@ -141,7 +134,10 @@ export default class AppsDrawer extends React.Component {
_getAppsHash = (apps) => apps.map(app => app.id).join("~");
componentDidUpdate(prevProps, prevState) {
if (this._getAppsHash(this.state.apps) !== this._getAppsHash(prevState.apps)) {
if (prevProps.userId !== this.props.userId || prevProps.room !== this.props.room) {
// Room has changed, update apps
this._updateApps();
} else if (this._getAppsHash(this.state.apps) !== this._getAppsHash(prevState.apps)) {
this._loadResizerPreferences();
}
}

View File

@@ -15,19 +15,18 @@ limitations under the License.
*/
import React from 'react';
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { Room } from 'matrix-js-sdk/src/models/room'
import dis from "../../../dispatcher/dispatcher";
import AppsDrawer from './AppsDrawer';
import classNames from 'classnames';
import RateLimitedFunc from '../../../ratelimitedfunc';
import SettingsStore from "../../../settings/SettingsStore";
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
import {UIFeature} from "../../../settings/UIFeature";
import { UIFeature } from "../../../settings/UIFeature";
import { ResizeNotifier } from "../../../utils/ResizeNotifier";
import CallViewForRoom from '../voip/CallViewForRoom';
import {objectHasDiff} from "../../../utils/objects";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { objectHasDiff } from "../../../utils/objects";
import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps {
// js-sdk room object
@@ -69,19 +68,21 @@ export default class AuxPanel extends React.Component<IProps, IState> {
super(props);
this.state = {
counters: this._computeCounters(),
counters: this.computeCounters(),
};
}
componentDidMount() {
const cli = MatrixClientPeg.get();
cli.on("RoomState.events", this._rateLimitedUpdate);
if (SettingsStore.getValue("feature_state_counters")) {
cli.on("RoomState.events", this.rateLimitedUpdate);
}
}
componentWillUnmount() {
const cli = MatrixClientPeg.get();
if (cli) {
cli.removeListener("RoomState.events", this._rateLimitedUpdate);
if (cli && SettingsStore.getValue("feature_state_counters")) {
cli.removeListener("RoomState.events", this.rateLimitedUpdate);
}
}
@@ -96,23 +97,11 @@ export default class AuxPanel extends React.Component<IProps, IState> {
}
}
onConferenceNotificationClick = (ev, type) => {
dis.dispatch({
action: 'place_call',
type: type,
room_id: this.props.room.roomId,
});
ev.stopPropagation();
ev.preventDefault();
};
_rateLimitedUpdate = new RateLimitedFunc(() => {
if (SettingsStore.getValue("feature_state_counters")) {
this.setState({counters: this._computeCounters()});
}
private rateLimitedUpdate = new RateLimitedFunc(() => {
this.setState({ counters: this.computeCounters() });
}, 500);
_computeCounters() {
private computeCounters() {
const counters = [];
if (this.props.room && SettingsStore.getValue("feature_state_counters")) {
@@ -225,7 +214,7 @@ export default class AuxPanel extends React.Component<IProps, IState> {
}
return (
<AutoHideScrollbar className={classes} style={style} >
<AutoHideScrollbar className={classes} style={style}>
{ stateViews }
{ appsDrawer }
{ callView }

View File

@@ -16,38 +16,39 @@ limitations under the License.
*/
import classNames from 'classnames';
import React, {createRef, ClipboardEvent} from 'react';
import {Room} from 'matrix-js-sdk/src/models/room';
import React, { createRef, ClipboardEvent } from 'react';
import { Room } from 'matrix-js-sdk/src/models/room';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import EMOTICON_REGEX from 'emojibase-regex/emoticon';
import EditorModel from '../../../editor/model';
import HistoryManager from '../../../editor/history';
import {Caret, setSelection} from '../../../editor/caret';
import { Caret, setSelection } from '../../../editor/caret';
import {
formatRangeAsQuote,
formatRangeAsCode,
toggleInlineFormat,
replaceRangeAndMoveCaret,
} from '../../../editor/operations';
import {getCaretOffsetAndText, getRangeForSelection} from '../../../editor/dom';
import Autocomplete, {generateCompletionDomId} from '../rooms/Autocomplete';
import {getAutoCompleteCreator} from '../../../editor/parts';
import {parsePlainTextMessage} from '../../../editor/deserialize';
import {renderModel} from '../../../editor/render';
import { getCaretOffsetAndText, getRangeForSelection } from '../../../editor/dom';
import Autocomplete, { generateCompletionDomId } from '../rooms/Autocomplete';
import { getAutoCompleteCreator } from '../../../editor/parts';
import { parseEvent, parsePlainTextMessage } from '../../../editor/deserialize';
import { renderModel } from '../../../editor/render';
import TypingStore from "../../../stores/TypingStore";
import SettingsStore from "../../../settings/SettingsStore";
import {Key} from "../../../Keyboard";
import {EMOTICON_TO_EMOJI} from "../../../emoji";
import {CommandCategories, CommandMap, parseCommandString} from "../../../SlashCommands";
import { Key } from "../../../Keyboard";
import { EMOTICON_TO_EMOJI } from "../../../emoji";
import { CommandCategories, CommandMap, parseCommandString } from "../../../SlashCommands";
import Range from "../../../editor/range";
import MessageComposerFormatBar from "./MessageComposerFormatBar";
import DocumentOffset from "../../../editor/offset";
import {IDiff} from "../../../editor/diff";
import { IDiff } from "../../../editor/diff";
import AutocompleteWrapperModel from "../../../editor/autocomplete";
import DocumentPosition from "../../../editor/position";
import {ICompletion} from "../../../autocomplete/Autocompleter";
import { ICompletion } from "../../../autocomplete/Autocompleter";
import { AutocompleteAction, getKeyBindingsManager, MessageComposerAction } from '../../../KeyBindingsManager';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
// matches emoticons which follow the start of a line or whitespace
const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')\\s$');
@@ -716,4 +717,48 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
focus() {
this.editorRef.current.focus();
}
public insertMention(userId: string) {
const {model} = this.props;
const {partCreator} = model;
const member = this.props.room.getMember(userId);
const displayName = member ?
member.rawDisplayName : userId;
const caret = this.getCaret();
const position = model.positionForOffset(caret.offset, caret.atNodeEnd);
// Insert suffix only if the caret is at the start of the composer
const parts = partCreator.createMentionParts(caret.offset === 0, displayName, userId);
model.transform(() => {
const addedLen = model.insert(parts, position);
return model.positionForOffset(caret.offset + addedLen, true);
});
// refocus on composer, as we just clicked "Mention"
this.focus();
}
public insertQuotedMessage(event: MatrixEvent) {
const {model} = this.props;
const {partCreator} = model;
const quoteParts = parseEvent(event, partCreator, {isQuotedMessage: true});
// add two newlines
quoteParts.push(partCreator.newline());
quoteParts.push(partCreator.newline());
model.transform(() => {
const addedLen = model.insert(quoteParts, model.positionForOffset(0));
return model.positionForOffset(addedLen, true);
});
// refocus on composer, as we just clicked "Quote"
this.focus();
}
public insertPlaintext(text: string) {
const {model} = this.props;
const {partCreator} = model;
const caret = this.getCaret();
const position = model.positionForOffset(caret.offset, caret.atNodeEnd);
model.transform(() => {
const addedLen = model.insert([partCreator.plain(text)], position);
return model.positionForOffset(caret.offset + addedLen, true);
});
}
}

View File

@@ -16,25 +16,25 @@ limitations under the License.
*/
import React from 'react';
import * as sdk from '../../../index';
import {_t, _td} from '../../../languageHandler';
import { _t, _td } from '../../../languageHandler';
import PropTypes from 'prop-types';
import dis from '../../../dispatcher/dispatcher';
import EditorModel from '../../../editor/model';
import {getCaretOffsetAndText} from '../../../editor/dom';
import {htmlSerializeIfNeeded, textSerialize, containsEmote, stripEmoteCommand} from '../../../editor/serialize';
import {findEditableEvent} from '../../../utils/EventUtils';
import {parseEvent} from '../../../editor/deserialize';
import {CommandPartCreator} from '../../../editor/parts';
import { getCaretOffsetAndText } from '../../../editor/dom';
import { htmlSerializeIfNeeded, textSerialize, containsEmote, stripEmoteCommand } from '../../../editor/serialize';
import { findEditableEvent } from '../../../utils/EventUtils';
import { parseEvent } from '../../../editor/deserialize';
import { CommandPartCreator } from '../../../editor/parts';
import EditorStateTransfer from '../../../utils/EditorStateTransfer';
import classNames from 'classnames';
import {EventStatus} from 'matrix-js-sdk/src/models/event';
import { EventStatus } from 'matrix-js-sdk/src/models/event';
import BasicMessageComposer from "./BasicMessageComposer";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {CommandCategories, getCommand} from '../../../SlashCommands';
import {Action} from "../../../dispatcher/actions";
import { CommandCategories, getCommand } from '../../../SlashCommands';
import { Action } from "../../../dispatcher/actions";
import CountlyAnalytics from "../../../CountlyAnalytics";
import {getKeyBindingsManager, MessageComposerAction} from '../../../KeyBindingsManager';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { getKeyBindingsManager, MessageComposerAction } from '../../../KeyBindingsManager';
import { replaceableComponent } from "../../../utils/replaceableComponent";
import SendHistoryManager from '../../../SendHistoryManager';
import Modal from '../../../Modal';
@@ -124,6 +124,7 @@ export default class EditMessageComposer extends React.Component {
};
this._createEditorModel();
window.addEventListener("beforeunload", this._saveStoredEditorState);
this.dispatcherRef = dis.register(this.onAction);
}
_setEditorRef = ref => {
@@ -399,6 +400,7 @@ export default class EditMessageComposer extends React.Component {
if (this._shouldSaveStoredEditorState) {
this._saveStoredEditorState();
}
dis.unregister(this.dispatcherRef);
}
_createEditorModel() {
@@ -443,6 +445,18 @@ export default class EditMessageComposer extends React.Component {
});
};
onAction = payload => {
if (payload.action === "edit_composer_insert" && this._editorRef) {
if (payload.userId) {
this._editorRef.insertMention(payload.userId);
} else if (payload.event) {
this._editorRef.insertQuotedMessage(payload.event);
} else if (payload.text) {
this._editorRef.insertPlaintext(payload.text);
}
}
};
render() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
return (<div className={classNames("mx_EditMessageComposer", this.props.className)} onKeyDown={this._onKeyDown}>

View File

@@ -46,6 +46,8 @@ import { EditorStateTransfer } from "../../../utils/EditorStateTransfer";
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
import {StaticNotificationState} from "../../../stores/notifications/StaticNotificationState";
import NotificationBadge from "./NotificationBadge";
import {ComposerInsertPayload} from "../../../dispatcher/payloads/ComposerInsertPayload";
import { Action } from '../../../dispatcher/actions';
const eventTileTypes = {
[EventType.RoomMessage]: 'messages.MessageEvent',
@@ -376,7 +378,7 @@ export default class EventTile extends React.Component<IProps, IState> {
EventType.RoomMessage,
EventType.RoomMessageEncrypted,
];
if (!simpleSendableEvents.includes(this.props.mxEvent.getType())) return false;
if (!simpleSendableEvents.includes(this.props.mxEvent.getType() as EventType)) return false;
// Default case
return true;
@@ -727,9 +729,9 @@ export default class EventTile extends React.Component<IProps, IState> {
onSenderProfileClick = event => {
const mxEvent = this.props.mxEvent;
dis.dispatch({
action: 'insert_mention',
user_id: mxEvent.getSender(),
dis.dispatch<ComposerInsertPayload>({
action: Action.ComposerInsert,
userId: mxEvent.getSender(),
});
};

View File

@@ -16,11 +16,11 @@ limitations under the License.
import React from 'react';
import classNames from 'classnames';
import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import * as sdk from '../../../index';
import {MatrixEvent} from "matrix-js-sdk/src/models/event";
import {Room} from "matrix-js-sdk/src/models/room";
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { Room } from "matrix-js-sdk/src/models/room";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import dis from '../../../dispatcher/dispatcher';
import { ActionPayload } from "../../../dispatcher/payloads";
import Stickerpicker from './Stickerpicker';
@@ -28,19 +28,21 @@ import { makeRoomPermalink, RoomPermalinkCreator } from '../../../utils/permalin
import ContentMessages from '../../../ContentMessages';
import E2EIcon from './E2EIcon';
import SettingsStore from "../../../settings/SettingsStore";
import {aboveLeftOf, ContextMenu, ContextMenuTooltipButton, useContextMenu} from "../../structures/ContextMenu";
import { aboveLeftOf, ContextMenu, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import ReplyPreview from "./ReplyPreview";
import {UIFeature} from "../../../settings/UIFeature";
import {UPDATE_EVENT} from "../../../stores/AsyncStore";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { UIFeature } from "../../../settings/UIFeature";
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import VoiceRecordComposerTile from "./VoiceRecordComposerTile";
import {VoiceRecordingStore} from "../../../stores/VoiceRecordingStore";
import {RecordingState} from "../../../voice/VoiceRecording";
import Tooltip, {Alignment} from "../elements/Tooltip";
import { VoiceRecordingStore } from "../../../stores/VoiceRecordingStore";
import { RecordingState } from "../../../voice/VoiceRecording";
import Tooltip, { Alignment } from "../elements/Tooltip";
import ResizeNotifier from "../../../utils/ResizeNotifier";
import { E2EStatus } from '../../../utils/ShieldUtils';
import SendMessageComposer from "./SendMessageComposer";
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
import { Action } from "../../../dispatcher/actions";
interface IComposerAvatarProps {
me: object;
@@ -316,10 +318,10 @@ export default class MessageComposer extends React.Component<IProps, IState> {
}
}
addEmoji(emoji) {
dis.dispatch({
action: "insert_emoji",
emoji,
addEmoji(emoji: string) {
dis.dispatch<ComposerInsertPayload>({
action: Action.ComposerInsert,
text: emoji,
});
}

View File

@@ -24,7 +24,7 @@ import {MatrixClientPeg} from "../../../MatrixClientPeg";
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.rooms.RoomUpgradeWarningBar")
export default class RoomUpgradeWarningBar extends React.Component {
export default class RoomUpgradeWarningBar extends React.PureComponent {
static propTypes = {
room: PropTypes.object.isRequired,
recommendation: PropTypes.object.isRequired,

View File

@@ -27,27 +27,26 @@ import {
startsWith,
stripPrefix,
} from '../../../editor/serialize';
import {CommandPartCreator} from '../../../editor/parts';
import { CommandPartCreator } from '../../../editor/parts';
import BasicMessageComposer from "./BasicMessageComposer";
import ReplyThread from "../elements/ReplyThread";
import {parseEvent} from '../../../editor/deserialize';
import {findEditableEvent} from '../../../utils/EventUtils';
import { findEditableEvent } from '../../../utils/EventUtils';
import SendHistoryManager from "../../../SendHistoryManager";
import {CommandCategories, getCommand} from '../../../SlashCommands';
import { CommandCategories, getCommand } from '../../../SlashCommands';
import * as sdk from '../../../index';
import Modal from '../../../Modal';
import {_t, _td} from '../../../languageHandler';
import { _t, _td } from '../../../languageHandler';
import ContentMessages from '../../../ContentMessages';
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import RateLimitedFunc from '../../../ratelimitedfunc';
import {Action} from "../../../dispatcher/actions";
import {containsEmoji} from "../../../effects/utils";
import {CHAT_EFFECTS} from '../../../effects';
import { Action } from "../../../dispatcher/actions";
import { containsEmoji } from "../../../effects/utils";
import { CHAT_EFFECTS } from '../../../effects';
import CountlyAnalytics from "../../../CountlyAnalytics";
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import EMOJI_REGEX from 'emojibase-regex';
import {getKeyBindingsManager, MessageComposerAction} from '../../../KeyBindingsManager';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { getKeyBindingsManager, MessageComposerAction } from '../../../KeyBindingsManager';
import { replaceableComponent } from "../../../utils/replaceableComponent";
import SettingsStore from '../../../settings/SettingsStore';
function addReplyToMessageContent(content, repliedToEvent, permalinkCreator) {
@@ -486,62 +485,18 @@ export default class SendMessageComposer extends React.Component {
case Action.FocusComposer:
this._editorRef && this._editorRef.focus();
break;
case 'insert_mention':
this._insertMention(payload.user_id);
break;
case 'quote':
this._insertQuotedMessage(payload.event);
break;
case 'insert_emoji':
this._insertEmoji(payload.emoji);
case "send_composer_insert":
if (payload.userId) {
this._editorRef && this._editorRef.insertMention(payload.userId);
} else if (payload.event) {
this._editorRef && this._editorRef.insertQuotedMessage(payload.event);
} else if (payload.text) {
this._editorRef && this._editorRef.insertPlaintext(payload.text);
}
break;
}
};
_insertMention(userId) {
const {model} = this;
const {partCreator} = model;
const member = this.props.room.getMember(userId);
const displayName = member ?
member.rawDisplayName : userId;
const caret = this._editorRef.getCaret();
const position = model.positionForOffset(caret.offset, caret.atNodeEnd);
// Insert suffix only if the caret is at the start of the composer
const parts = partCreator.createMentionParts(caret.offset === 0, displayName, userId);
model.transform(() => {
const addedLen = model.insert(parts, position);
return model.positionForOffset(caret.offset + addedLen, true);
});
// refocus on composer, as we just clicked "Mention"
this._editorRef && this._editorRef.focus();
}
_insertQuotedMessage(event) {
const {model} = this;
const {partCreator} = model;
const quoteParts = parseEvent(event, partCreator, {isQuotedMessage: true});
// add two newlines
quoteParts.push(partCreator.newline());
quoteParts.push(partCreator.newline());
model.transform(() => {
const addedLen = model.insert(quoteParts, model.positionForOffset(0));
return model.positionForOffset(addedLen, true);
});
// refocus on composer, as we just clicked "Quote"
this._editorRef && this._editorRef.focus();
}
_insertEmoji = (emoji) => {
const {model} = this;
const {partCreator} = model;
const caret = this._editorRef.getCaret();
const position = model.positionForOffset(caret.offset, caret.atNodeEnd);
model.transform(() => {
const addedLen = model.insert([partCreator.plain(emoji)], position);
return model.positionForOffset(caret.offset + addedLen, true);
});
};
_onPaste = (event) => {
const {clipboardData} = event;
// Prioritize text on the clipboard over files as Office on macOS puts a bitmap

View File

@@ -44,14 +44,11 @@ export default class BridgeSettingsTab extends React.Component<IProps> {
return <BridgeTile key={event.getId()} room={room} ev={event} />;
}
static getBridgeStateEvents(roomId: string) {
static getBridgeStateEvents(roomId: string): MatrixEvent[] {
const client = MatrixClientPeg.get();
const roomState = client.getRoom(roomId).currentState;
return BRIDGE_EVENT_TYPES.map(typeName => {
const events = roomState.events.get(typeName);
return events ? Array.from(events.values()) : [];
}).flat(1);
return BRIDGE_EVENT_TYPES.map(typeName => roomState.getStateEvents(typeName)).flat(1);
}
render() {

View File

@@ -21,17 +21,20 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg';
import dis from '../../../dispatcher/dispatcher';
import { _t } from '../../../languageHandler';
import { ActionPayload } from '../../../dispatcher/payloads';
import CallHandler from '../../../CallHandler';
import CallHandler, { AudioID } from '../../../CallHandler';
import RoomAvatar from '../avatars/RoomAvatar';
import FormButton from '../elements/FormButton';
import { CallState } from 'matrix-js-sdk/src/webrtc/call';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import AccessibleTooltipButton from '../elements/AccessibleTooltipButton';
import classNames from 'classnames';
interface IProps {
}
interface IState {
incomingCall: any;
silenced: boolean;
}
@replaceableComponent("views.voip.IncomingCallBox")
@@ -44,6 +47,7 @@ export default class IncomingCallBox extends React.Component<IProps, IState> {
this.dispatcherRef = dis.register(this.onAction);
this.state = {
incomingCall: null,
silenced: false,
};
}
@@ -58,6 +62,7 @@ export default class IncomingCallBox extends React.Component<IProps, IState> {
if (call && call.state === CallState.Ringing) {
this.setState({
incomingCall: call,
silenced: false, // Reset silenced state for new call
});
} else {
this.setState({
@@ -84,6 +89,13 @@ export default class IncomingCallBox extends React.Component<IProps, IState> {
});
};
private onSilenceClick: React.MouseEventHandler = (e) => {
e.stopPropagation();
const newState = !this.state.silenced
this.setState({silenced: newState});
newState ? CallHandler.sharedInstance().pause(AudioID.Ring) : CallHandler.sharedInstance().play(AudioID.Ring);
}
public render() {
if (!this.state.incomingCall) {
return null;
@@ -107,6 +119,12 @@ export default class IncomingCallBox extends React.Component<IProps, IState> {
}
}
const silenceClass = classNames({
"mx_IncomingCallBox_iconButton": true,
"mx_IncomingCallBox_unSilence": this.state.silenced,
"mx_IncomingCallBox_silence": !this.state.silenced,
});
return <div className="mx_IncomingCallBox">
<div className="mx_IncomingCallBox_CallerInfo">
<RoomAvatar
@@ -118,6 +136,11 @@ export default class IncomingCallBox extends React.Component<IProps, IState> {
<h1>{caller}</h1>
<p>{incomingCallText}</p>
</div>
<AccessibleTooltipButton
className={silenceClass}
onClick={this.onSilenceClick}
title={this.state.silenced ? _t("Sound on"): _t("Silence call")}
/>
</div>
<div className="mx_IncomingCallBox_buttons">
<FormButton