diff --git a/src/components/views/messages/EditHistoryMessage.js b/src/components/views/messages/EditHistoryMessage.tsx similarity index 73% rename from src/components/views/messages/EditHistoryMessage.js rename to src/components/views/messages/EditHistoryMessage.tsx index 2c6a567f6b..1abed87b76 100644 --- a/src/components/views/messages/EditHistoryMessage.js +++ b/src/components/views/messages/EditHistoryMessage.tsx @@ -15,107 +15,112 @@ limitations under the License. */ import React, { createRef } from 'react'; -import PropTypes from 'prop-types'; import * as HtmlUtils from '../../../HtmlUtils'; import { editBodyDiffToHtml } from '../../../utils/MessageDiffUtils'; import { formatTime } from '../../../DateUtils'; -import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; +import { EventStatus, MatrixEvent } from 'matrix-js-sdk/src/models/event'; import { pillifyLinks, unmountPills } from '../../../utils/pillify'; import { _t } from '../../../languageHandler'; -import * as sdk from '../../../index'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import Modal from '../../../Modal'; import classNames from 'classnames'; import RedactedBody from "./RedactedBody"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import AccessibleButton from "../elements/AccessibleButton"; +import ConfirmAndWaitRedactDialog from "../dialogs/ConfirmAndWaitRedactDialog"; +import ViewSource from "../../structures/ViewSource"; function getReplacedContent(event) { const originalContent = event.getOriginalContent(); return originalContent["m.new_content"] || originalContent; } -@replaceableComponent("views.messages.EditHistoryMessage") -export default class EditHistoryMessage extends React.PureComponent { - static propTypes = { - // the message event being edited - mxEvent: PropTypes.instanceOf(MatrixEvent).isRequired, - previousEdit: PropTypes.instanceOf(MatrixEvent), - isBaseEvent: PropTypes.bool, - }; +interface IProps { + // the message event being edited + mxEvent: MatrixEvent; + previousEdit?: MatrixEvent; + isBaseEvent?: boolean; + isTwelveHour?: boolean; +} - constructor(props) { +interface IState { + canRedact: boolean; + sendStatus: EventStatus; +} + +@replaceableComponent("views.messages.EditHistoryMessage") +export default class EditHistoryMessage extends React.PureComponent { + private content = createRef(); + private pills: Element[] = []; + + constructor(props: IProps) { super(props); + const cli = MatrixClientPeg.get(); const { userId } = cli.credentials; const event = this.props.mxEvent; const room = cli.getRoom(event.getRoomId()); if (event.localRedactionEvent()) { - event.localRedactionEvent().on("status", this._onAssociatedStatusChanged); + event.localRedactionEvent().on("status", this.onAssociatedStatusChanged); } const canRedact = room.currentState.maySendRedactionForEvent(event, userId); this.state = { canRedact, sendStatus: event.getAssociatedStatus() }; - - this._content = createRef(); - this._pills = []; } - _onAssociatedStatusChanged = () => { + private onAssociatedStatusChanged = (): void => { this.setState({ sendStatus: this.props.mxEvent.getAssociatedStatus() }); }; - _onRedactClick = async () => { + private onRedactClick = async (): Promise => { const event = this.props.mxEvent; const cli = MatrixClientPeg.get(); - const ConfirmAndWaitRedactDialog = sdk.getComponent("dialogs.ConfirmAndWaitRedactDialog"); Modal.createTrackedDialog('Confirm Redact Dialog', 'Edit history', ConfirmAndWaitRedactDialog, { redact: () => cli.redactEvent(event.getRoomId(), event.getId()), }, 'mx_Dialog_confirmredact'); }; - _onViewSourceClick = () => { - const ViewSource = sdk.getComponent('structures.ViewSource'); + private onViewSourceClick = (): void => { Modal.createTrackedDialog('View Event Source', 'Edit history', ViewSource, { mxEvent: this.props.mxEvent, }, 'mx_Dialog_viewsource'); }; - pillifyLinks() { + private pillifyLinks(): void { // not present for redacted events - if (this._content.current) { - pillifyLinks(this._content.current.children, this.props.mxEvent, this._pills); + if (this.content.current) { + pillifyLinks(this.content.current.children, this.props.mxEvent, this.pills); } } - componentDidMount() { + public componentDidMount(): void { this.pillifyLinks(); } - componentWillUnmount() { - unmountPills(this._pills); + public componentWillUnmount(): void { + unmountPills(this.pills); const event = this.props.mxEvent; if (event.localRedactionEvent()) { - event.localRedactionEvent().off("status", this._onAssociatedStatusChanged); + event.localRedactionEvent().off("status", this.onAssociatedStatusChanged); } } - componentDidUpdate() { + public componentDidUpdate(): void { this.pillifyLinks(); } - _renderActionBar() { - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + private renderActionBar(): JSX.Element { // hide the button when already redacted let redactButton; if (!this.props.mxEvent.isRedacted() && !this.props.isBaseEvent && this.state.canRedact) { redactButton = ( - + { _t("Remove") } ); } const viewSourceButton = ( - + { _t("View Source") } ); @@ -128,7 +133,7 @@ export default class EditHistoryMessage extends React.PureComponent { ); } - render() { + public render(): JSX.Element { const { mxEvent } = this.props; const content = getReplacedContent(mxEvent); let contentContainer; @@ -139,18 +144,22 @@ export default class EditHistoryMessage extends React.PureComponent { if (this.props.previousEdit) { contentElements = editBodyDiffToHtml(getReplacedContent(this.props.previousEdit), content); } else { - contentElements = HtmlUtils.bodyToHtml(content, null, { stripReplyFallback: true }); + contentElements = HtmlUtils.bodyToHtml( + content, + null, + { stripReplyFallback: true, returnString: false }, + ); } if (mxEvent.getContent().msgtype === "m.emote") { const name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender(); contentContainer = ( -
*  +
{ name }  { contentElements }
); } else { - contentContainer =
{ contentElements }
; + contentContainer =
{ contentElements }
; } } @@ -167,7 +176,7 @@ export default class EditHistoryMessage extends React.PureComponent {
{ timestamp } { contentContainer } - { this._renderActionBar() } + { this.renderActionBar() }
diff --git a/src/components/views/messages/MKeyVerificationConclusion.js b/src/components/views/messages/MKeyVerificationConclusion.tsx similarity index 69% rename from src/components/views/messages/MKeyVerificationConclusion.js rename to src/components/views/messages/MKeyVerificationConclusion.tsx index a5f12df47d..1ce39e1157 100644 --- a/src/components/views/messages/MKeyVerificationConclusion.js +++ b/src/components/views/messages/MKeyVerificationConclusion.tsx @@ -16,44 +16,50 @@ limitations under the License. import React from 'react'; import classNames from 'classnames'; -import PropTypes from 'prop-types'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { _t } from '../../../languageHandler'; -import { getNameForEventRoom, userLabelForEventRoom } - from '../../../utils/KeyVerificationStateObserver'; +import { getNameForEventRoom, userLabelForEventRoom } from '../../../utils/KeyVerificationStateObserver'; import EventTileBubble from "./EventTileBubble"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; +import { EventType } from "matrix-js-sdk/src/@types/event"; + +interface IProps { + /* the MatrixEvent to show */ + mxEvent: MatrixEvent; +} @replaceableComponent("views.messages.MKeyVerificationConclusion") -export default class MKeyVerificationConclusion extends React.Component { - constructor(props) { +export default class MKeyVerificationConclusion extends React.Component { + constructor(props: IProps) { super(props); } - componentDidMount() { + public componentDidMount(): void { const request = this.props.mxEvent.verificationRequest; if (request) { - request.on("change", this._onRequestChanged); + request.on("change", this.onRequestChanged); } - MatrixClientPeg.get().on("userTrustStatusChanged", this._onTrustChanged); + MatrixClientPeg.get().on("userTrustStatusChanged", this.onTrustChanged); } - componentWillUnmount() { + public componentWillUnmount(): void { const request = this.props.mxEvent.verificationRequest; if (request) { - request.off("change", this._onRequestChanged); + request.off("change", this.onRequestChanged); } const cli = MatrixClientPeg.get(); if (cli) { - cli.removeListener("userTrustStatusChanged", this._onTrustChanged); + cli.removeListener("userTrustStatusChanged", this.onTrustChanged); } } - _onRequestChanged = () => { + private onRequestChanged = (): void => { this.forceUpdate(); }; - _onTrustChanged = (userId, status) => { + private onTrustChanged = (userId: string): void => { const { mxEvent } = this.props; const request = mxEvent.verificationRequest; if (!request || request.otherUserId !== userId) { @@ -62,17 +68,17 @@ export default class MKeyVerificationConclusion extends React.Component { this.forceUpdate(); }; - _shouldRender(mxEvent, request) { + public static shouldRender(mxEvent: MatrixEvent, request: VerificationRequest): boolean { // normally should not happen if (!request) { return false; } // .cancel event that was sent after the verification finished, ignore - if (mxEvent.getType() === "m.key.verification.cancel" && !request.cancelled) { + if (mxEvent.getType() === EventType.KeyVerificationCancel && !request.cancelled) { return false; } // .done event that was sent after the verification cancelled, ignore - if (mxEvent.getType() === "m.key.verification.done" && !request.done) { + if (mxEvent.getType() === EventType.KeyVerificationDone && !request.done) { return false; } @@ -89,11 +95,11 @@ export default class MKeyVerificationConclusion extends React.Component { return true; } - render() { + public render(): JSX.Element { const { mxEvent } = this.props; const request = mxEvent.verificationRequest; - if (!this._shouldRender(mxEvent, request)) { + if (!MKeyVerificationConclusion.shouldRender(mxEvent, request)) { return null; } @@ -103,15 +109,18 @@ export default class MKeyVerificationConclusion extends React.Component { let title; if (request.done) { - title = _t("You verified %(name)s", { name: getNameForEventRoom(request.otherUserId, mxEvent) }); + title = _t( + "You verified %(name)s", + { name: getNameForEventRoom(request.otherUserId, mxEvent.getRoomId()) }, + ); } else if (request.cancelled) { const userId = request.cancellingUserId; if (userId === myUserId) { title = _t("You cancelled verifying %(name)s", - { name: getNameForEventRoom(request.otherUserId, mxEvent) }); + { name: getNameForEventRoom(request.otherUserId, mxEvent.getRoomId()) }); } else { title = _t("%(name)s cancelled verifying", - { name: getNameForEventRoom(userId, mxEvent) }); + { name: getNameForEventRoom(userId, mxEvent.getRoomId()) }); } } @@ -129,8 +138,3 @@ export default class MKeyVerificationConclusion extends React.Component { return null; } } - -MKeyVerificationConclusion.propTypes = { - /* the MatrixEvent to show */ - mxEvent: PropTypes.object.isRequired, -}; diff --git a/src/components/views/messages/MjolnirBody.js b/src/components/views/messages/MjolnirBody.tsx similarity index 75% rename from src/components/views/messages/MjolnirBody.js rename to src/components/views/messages/MjolnirBody.tsx index 23f255b569..7e922a5715 100644 --- a/src/components/views/messages/MjolnirBody.js +++ b/src/components/views/messages/MjolnirBody.tsx @@ -15,22 +15,18 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import { _t } from '../../../languageHandler'; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; + +interface IProps { + mxEvent: MatrixEvent; + onMessageAllowed: () => void; +} @replaceableComponent("views.messages.MjolnirBody") -export default class MjolnirBody extends React.Component { - static propTypes = { - mxEvent: PropTypes.object.isRequired, - onMessageAllowed: PropTypes.func.isRequired, - }; - - constructor() { - super(); - } - - _onAllowClick = (e) => { +export default class MjolnirBody extends React.Component { + private onAllowClick = (e: React.MouseEvent): void => { e.preventDefault(); e.stopPropagation(); @@ -39,11 +35,11 @@ export default class MjolnirBody extends React.Component { this.props.onMessageAllowed(); }; - render() { + public render(): JSX.Element { return (
{ _t( "You have ignored this user, so their message is hidden. Show anyways.", - {}, { a: (sub) => { sub } }, + {}, { a: (sub) => { sub } }, ) }
); } diff --git a/src/components/views/messages/RedactedBody.tsx b/src/components/views/messages/RedactedBody.tsx index c2e137c97b..66200036cd 100644 --- a/src/components/views/messages/RedactedBody.tsx +++ b/src/components/views/messages/RedactedBody.tsx @@ -16,13 +16,18 @@ limitations under the License. import React, { useContext } from "react"; import { MatrixClient } from "matrix-js-sdk/src/client"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { _t } from "../../../languageHandler"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { formatFullDate } from "../../../DateUtils"; import SettingsStore from "../../../settings/SettingsStore"; import { IBodyProps } from "./IBodyProps"; -const RedactedBody = React.forwardRef(({ mxEvent }, ref) => { +interface IProps { + mxEvent: MatrixEvent; +} + +const RedactedBody = React.forwardRef(({ mxEvent }, ref) => { const cli: MatrixClient = useContext(MatrixClientContext); let text = _t("Message deleted"); diff --git a/src/components/views/messages/RoomAvatarEvent.js b/src/components/views/messages/RoomAvatarEvent.tsx similarity index 88% rename from src/components/views/messages/RoomAvatarEvent.js rename to src/components/views/messages/RoomAvatarEvent.tsx index 9832332311..12a8c88913 100644 --- a/src/components/views/messages/RoomAvatarEvent.js +++ b/src/components/views/messages/RoomAvatarEvent.tsx @@ -17,23 +17,24 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { _t } from '../../../languageHandler'; -import * as sdk from '../../../index'; import Modal from '../../../Modal'; import AccessibleButton from '../elements/AccessibleButton'; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { mediaFromMxc } from "../../../customisations/Media"; +import RoomAvatar from "../avatars/RoomAvatar"; +import ImageView from "../elements/ImageView"; + +interface IProps { + /* the MatrixEvent to show */ + mxEvent: MatrixEvent; +} @replaceableComponent("views.messages.RoomAvatarEvent") -export default class RoomAvatarEvent extends React.Component { - static propTypes = { - /* the MatrixEvent to show */ - mxEvent: PropTypes.object.isRequired, - }; - - onAvatarClick = () => { +export default class RoomAvatarEvent extends React.Component { + private onAvatarClick = (): void => { const cli = MatrixClientPeg.get(); const ev = this.props.mxEvent; const httpUrl = mediaFromMxc(ev.getContent().url).srcHttp; @@ -44,7 +45,6 @@ export default class RoomAvatarEvent extends React.Component { roomName: room ? room.name : '', }); - const ImageView = sdk.getComponent("elements.ImageView"); const params = { src: httpUrl, name: text, @@ -52,10 +52,9 @@ export default class RoomAvatarEvent extends React.Component { Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", null, true); }; - render() { + public render(): JSX.Element { const ev = this.props.mxEvent; const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); - const RoomAvatar = sdk.getComponent("avatars.RoomAvatar"); if (!ev.getContent().url || ev.getContent().url.trim().length === 0) { return ( diff --git a/src/components/views/messages/RoomCreate.js b/src/components/views/messages/RoomCreate.tsx similarity index 85% rename from src/components/views/messages/RoomCreate.js rename to src/components/views/messages/RoomCreate.tsx index a0bc8daa64..c846ba5632 100644 --- a/src/components/views/messages/RoomCreate.js +++ b/src/components/views/messages/RoomCreate.tsx @@ -16,7 +16,6 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import dis from '../../../dispatcher/dispatcher'; import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks'; @@ -24,15 +23,16 @@ import { _t } from '../../../languageHandler'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import EventTileBubble from "./EventTileBubble"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; + +interface IProps { + /* the MatrixEvent to show */ + mxEvent: MatrixEvent; +} @replaceableComponent("views.messages.RoomCreate") -export default class RoomCreate extends React.Component { - static propTypes = { - /* the MatrixEvent to show */ - mxEvent: PropTypes.object.isRequired, - }; - - _onLinkClicked = e => { +export default class RoomCreate extends React.Component { + private onLinkClicked = (e: React.MouseEvent): void => { e.preventDefault(); const predecessor = this.props.mxEvent.getContent()['predecessor']; @@ -45,7 +45,7 @@ export default class RoomCreate extends React.Component { }); }; - render() { + public render(): JSX.Element { const predecessor = this.props.mxEvent.getContent()['predecessor']; if (predecessor === undefined) { return
; // We should never have been instantiated in this case @@ -55,7 +55,7 @@ export default class RoomCreate extends React.Component { permalinkCreator.load(); const predecessorPermalink = permalinkCreator.forEvent(predecessor['event_id']); const link = ( - + { _t("Click here to see older messages.") } ); diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index e1f0eb5368..d1ac06b199 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -58,6 +58,7 @@ import ReactionsRow from '../messages/ReactionsRow'; import { getEventDisplayInfo } from '../../../utils/EventUtils'; import { RightPanelPhases } from "../../../stores/RightPanelStorePhases"; import SettingsStore from "../../../settings/SettingsStore"; +import MKeyVerificationConclusion from "../messages/MKeyVerificationConclusion"; const eventTileTypes = { [EventType.RoomMessage]: 'messages.MessageEvent', @@ -144,8 +145,7 @@ export function getHandlerTile(ev) { // XXX: This is extremely a hack. Possibly these components should have an interface for // declining to render? if (type === "m.key.verification.cancel" || type === "m.key.verification.done") { - const MKeyVerificationConclusion = sdk.getComponent("messages.MKeyVerificationConclusion"); - if (!MKeyVerificationConclusion.prototype._shouldRender.call(null, ev, ev.request)) { + if (!MKeyVerificationConclusion.shouldRender(ev, ev.request)) { return; } }