Merge branch 'develop' into gsouquet/dialogs-ts-migration
This commit is contained in:
commit
3f1ca970d6
80 changed files with 1061 additions and 449 deletions
|
@ -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();
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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>;
|
||||
});
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
);
|
|
@ -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;
|
||||
|
|
|
@ -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(),
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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(),
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue