From af5cfff51db48c4a96c5393a25c76675839e864d Mon Sep 17 00:00:00 2001 From: Panagiotis <27917356+panoschal@users.noreply.github.com> Date: Thu, 4 Mar 2021 23:17:29 +0200 Subject: [PATCH 1/7] feat: edit button on View Source dialog reuse component SendCustomEvent swap it in place in the View Source dialog the Back button takes you to the View Source dialog, not the DevTools dialog do not display the flip toggle box for changing between State Event and Normal Event --- src/components/structures/ViewSource.js | 180 ++++++++++++++---- .../views/context_menus/MessageContextMenu.js | 14 +- .../views/dialogs/DevtoolsDialog.js | 7 +- 3 files changed, 152 insertions(+), 49 deletions(-) diff --git a/src/components/structures/ViewSource.js b/src/components/structures/ViewSource.js index ca6c0d4226..7fe862cff5 100644 --- a/src/components/structures/ViewSource.js +++ b/src/components/structures/ViewSource.js @@ -16,12 +16,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; -import PropTypes from 'prop-types'; -import SyntaxHighlight from '../views/elements/SyntaxHighlight'; -import {_t} from "../../languageHandler"; +import React from "react"; +import PropTypes from "prop-types"; +import SyntaxHighlight from "../views/elements/SyntaxHighlight"; +import { _t } from "../../languageHandler"; import * as sdk from "../../index"; - +import MatrixClientContext from "../../contexts/MatrixClientContext"; +import { SendCustomEvent } from "../views/dialogs/DevtoolsDialog"; export default class ViewSource extends React.Component { static propTypes = { @@ -31,48 +32,157 @@ export default class ViewSource extends React.Component { eventId: PropTypes.string.isRequired, isEncrypted: PropTypes.bool.isRequired, decryptedContent: PropTypes.object, + event: PropTypes.object.isRequired, // the MatrixEvent associated with the context menu }; + constructor(props) { + super(props); + + this.state = { + editComponent: null, + }; + } + + onBack() { + this.setState({ editComponent: null }); + } + + editEvent() { + const isStateEvent = this.props.event.isState(); + console.log("isStateEvent", isStateEvent); + if (isStateEvent) { + this.setState({ + editComponent: ( + + {(cli) => ( + this.onBack()} + inputs={{ + eventType: this.props.event.getType(), + evContent: JSON.stringify( + this.props.event.getContent(), + null, + "\t" + ), + stateKey: this.props.event.getStateKey(), + }} + /> + )} + + ), + }); + } else { + // send an edit-message event + // prefill the "m.new_content" field + const originalContent = this.props.event.getContent(); + const originalEventId = this.props.eventId; + const content = { + ...originalContent, + "m.new_content": originalContent, + "m.relates_to": { + rel_type: "m.replace", + event_id: originalEventId, + }, + }; + this.setState({ + editComponent: ( + + {(cli) => ( + this.onBack()} + inputs={{ + eventType: this.props.event.getType(), + evContent: JSON.stringify( + content, + null, + "\t" + ), + }} + /> + )} + + ), + }); + } + } + render() { - const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); + const BaseDialog = sdk.getComponent("views.dialogs.BaseDialog"); let content; if (this.props.isEncrypted) { - content = <> -
- - {_t("Decrypted event source")} - - - { JSON.stringify(this.props.decryptedContent, null, 2) } - -
-
- - {_t("Original event source")} - - - { JSON.stringify(this.props.content, null, 2) } - -
- ; + content = ( + <> +
+ + + {_t("Decrypted event source")} + + + + {JSON.stringify( + this.props.decryptedContent, + null, + 2 + )} + +
+
+ + + {_t("Original event source")} + + + + {JSON.stringify(this.props.content, null, 2)} + +
+ + ); } else { - content = <> -
{_t("Original event source")}
- - { JSON.stringify(this.props.content, null, 2) } - - ; + content = ( + <> +
+ {_t("Original event source")} +
+ + {JSON.stringify(this.props.content, null, 2)} + + + ); } + const isEditing = this.state.editComponent !== null; + console.log(isEditing); + return ( - -
-
Room ID: { this.props.roomId }
-
Event ID: { this.props.eventId }
+ +
+
+ Room ID: {this.props.roomId} +
+
+ Event ID: {this.props.eventId} +
- { content } + {isEditing ? this.state.editComponent : content}
+ {!isEditing && ( +
+ +
+ )} ); } diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index b002d1ec62..a1c111b19c 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -130,20 +130,10 @@ export default class MessageContextMenu extends React.Component { roomId: ev.getRoomId(), eventId: ev.getId(), content: ev.event, + event: ev, isEncrypted: this.props.mxEvent.getType() !== this.props.mxEvent.getWireType(), - decryptedContent: ev._clearEvent, - }, 'mx_Dialog_viewsource'); - this.closeMenu(); - }; - - onViewClearSourceClick = () => { - const ev = this.props.mxEvent.replacingEvent() || this.props.mxEvent; - const ViewSource = sdk.getComponent('structures.ViewSource'); - Modal.createTrackedDialog('View Clear Event Source', '', ViewSource, { - roomId: ev.getRoomId(), - eventId: ev.getId(), // FIXME: _clearEvent is private - content: ev._clearEvent, + decryptedContent: ev._clearEvent, }, 'mx_Dialog_viewsource'); this.closeMenu(); }; diff --git a/src/components/views/dialogs/DevtoolsDialog.js b/src/components/views/dialogs/DevtoolsDialog.js index 814378bb51..5d571461fc 100644 --- a/src/components/views/dialogs/DevtoolsDialog.js +++ b/src/components/views/dialogs/DevtoolsDialog.js @@ -73,13 +73,14 @@ class GenericEditor extends React.PureComponent { } } -class SendCustomEvent extends GenericEditor { +export class SendCustomEvent extends GenericEditor { static getLabel() { return _t('Send Custom Event'); } static propTypes = { onBack: PropTypes.func.isRequired, room: PropTypes.instanceOf(Room).isRequired, forceStateEvent: PropTypes.bool, + forceGeneralEvent: PropTypes.bool, inputs: PropTypes.object, }; @@ -140,6 +141,8 @@ class SendCustomEvent extends GenericEditor {
; } + const showTglFlip = !this.state.message && !this.props.forceStateEvent && !this.props.forceGeneralEvent; + return
@@ -155,7 +158,7 @@ class SendCustomEvent extends GenericEditor {
{ !this.state.message && } - { !this.state.message && !this.props.forceStateEvent &&
+ { showTglFlip &&
} From 288d98daede9f1ea6b1045e6de930c61f5f14c0e Mon Sep 17 00:00:00 2001 From: Panagiotis <27917356+panoschal@users.noreply.github.com> Date: Fri, 5 Mar 2021 00:07:59 +0200 Subject: [PATCH 2/7] chore: format, lint --- src/components/structures/ViewSource.js | 58 ++++--------------- .../views/dialogs/DevtoolsDialog.js | 2 +- 2 files changed, 13 insertions(+), 47 deletions(-) diff --git a/src/components/structures/ViewSource.js b/src/components/structures/ViewSource.js index 7fe862cff5..a31876ea76 100644 --- a/src/components/structures/ViewSource.js +++ b/src/components/structures/ViewSource.js @@ -61,11 +61,7 @@ export default class ViewSource extends React.Component { onBack={() => this.onBack()} inputs={{ eventType: this.props.event.getType(), - evContent: JSON.stringify( - this.props.event.getContent(), - null, - "\t" - ), + evContent: JSON.stringify(this.props.event.getContent(), null, "\t"), stateKey: this.props.event.getStateKey(), }} /> @@ -97,11 +93,7 @@ export default class ViewSource extends React.Component { onBack={() => this.onBack()} inputs={{ eventType: this.props.event.getType(), - evContent: JSON.stringify( - content, - null, - "\t" - ), + evContent: JSON.stringify(content, null, "\t"), }} /> )} @@ -120,39 +112,23 @@ export default class ViewSource extends React.Component { <>
- - {_t("Decrypted event source")} - + {_t("Decrypted event source")} - - {JSON.stringify( - this.props.decryptedContent, - null, - 2 - )} - + {JSON.stringify(this.props.decryptedContent, null, 2)}
- - {_t("Original event source")} - + {_t("Original event source")} - - {JSON.stringify(this.props.content, null, 2)} - + {JSON.stringify(this.props.content, null, 2)}
); } else { content = ( <> -
- {_t("Original event source")} -
- - {JSON.stringify(this.props.content, null, 2)} - +
{_t("Original event source")}
+ {JSON.stringify(this.props.content, null, 2)} ); } @@ -161,26 +137,16 @@ export default class ViewSource extends React.Component { console.log(isEditing); return ( - +
-
- Room ID: {this.props.roomId} -
-
- Event ID: {this.props.eventId} -
+
Room ID: {this.props.roomId}
+
Event ID: {this.props.eventId}
{isEditing ? this.state.editComponent : content}
{!isEditing && (
- +
)} diff --git a/src/components/views/dialogs/DevtoolsDialog.js b/src/components/views/dialogs/DevtoolsDialog.js index 5d571461fc..82f2df6534 100644 --- a/src/components/views/dialogs/DevtoolsDialog.js +++ b/src/components/views/dialogs/DevtoolsDialog.js @@ -142,7 +142,7 @@ export class SendCustomEvent extends GenericEditor { } const showTglFlip = !this.state.message && !this.props.forceStateEvent && !this.props.forceGeneralEvent; - + return
From 51ac5421c9848be775c977009454e7f55c79d155 Mon Sep 17 00:00:00 2001 From: Panagiotis <27917356+panoschal@users.noreply.github.com> Date: Sat, 6 Mar 2021 11:30:31 +0200 Subject: [PATCH 3/7] chore: refactor code pass only the mxEvent object to ViewSource derive the necessary values inside the component --- src/components/structures/ViewSource.js | 161 +++++++++--------- .../views/context_menus/MessageContextMenu.js | 9 +- .../views/messages/EditHistoryMessage.js | 6 +- 3 files changed, 86 insertions(+), 90 deletions(-) diff --git a/src/components/structures/ViewSource.js b/src/components/structures/ViewSource.js index a31876ea76..369a0a1ddd 100644 --- a/src/components/structures/ViewSource.js +++ b/src/components/structures/ViewSource.js @@ -26,127 +26,134 @@ import { SendCustomEvent } from "../views/dialogs/DevtoolsDialog"; export default class ViewSource extends React.Component { static propTypes = { - content: PropTypes.object.isRequired, onFinished: PropTypes.func.isRequired, - roomId: PropTypes.string.isRequired, - eventId: PropTypes.string.isRequired, - isEncrypted: PropTypes.bool.isRequired, - decryptedContent: PropTypes.object, - event: PropTypes.object.isRequired, // the MatrixEvent associated with the context menu + mxEvent: PropTypes.object.isRequired, // the MatrixEvent associated with the context menu }; constructor(props) { super(props); this.state = { - editComponent: null, + isEditing: false, }; } onBack() { - this.setState({ editComponent: null }); + this.setState({ isEditing: false }); } - editEvent() { - const isStateEvent = this.props.event.isState(); - console.log("isStateEvent", isStateEvent); - if (isStateEvent) { - this.setState({ - editComponent: ( - - {(cli) => ( - this.onBack()} - inputs={{ - eventType: this.props.event.getType(), - evContent: JSON.stringify(this.props.event.getContent(), null, "\t"), - stateKey: this.props.event.getStateKey(), - }} - /> - )} - - ), - }); - } else { - // send an edit-message event - // prefill the "m.new_content" field - const originalContent = this.props.event.getContent(); - const originalEventId = this.props.eventId; - const content = { - ...originalContent, - "m.new_content": originalContent, - "m.relates_to": { - rel_type: "m.replace", - event_id: originalEventId, - }, - }; - this.setState({ - editComponent: ( - - {(cli) => ( - this.onBack()} - inputs={{ - eventType: this.props.event.getType(), - evContent: JSON.stringify(content, null, "\t"), - }} - /> - )} - - ), - }); - } + onEdit() { + this.setState({ isEditing: true }); } - render() { - const BaseDialog = sdk.getComponent("views.dialogs.BaseDialog"); + // returns the dialog body for viewing the event source + viewSourceContent() { + const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit + const isEncrypted = this.props.mxEvent.getType() !== this.props.mxEvent.getWireType(); + const decryptedEventSource = mxEvent._clearEvent; // FIXME: _clearEvent is private + const originalEventSource = mxEvent.event; - let content; - if (this.props.isEncrypted) { - content = ( + if (isEncrypted) { + return ( <>
{_t("Decrypted event source")} - {JSON.stringify(this.props.decryptedContent, null, 2)} + {JSON.stringify(decryptedEventSource, null, 2)}
{_t("Original event source")} - {JSON.stringify(this.props.content, null, 2)} + {JSON.stringify(originalEventSource, null, 2)}
); } else { - content = ( + return ( <>
{_t("Original event source")}
- {JSON.stringify(this.props.content, null, 2)} + {JSON.stringify(originalEventSource, null, 2)} ); } + } - const isEditing = this.state.editComponent !== null; - console.log(isEditing); + // returns the SendCustomEvent component prefilled with the correct details + editSourceContent() { + const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit + const isStateEvent = mxEvent.isState(); + console.log("isStateEvent", isStateEvent); + const roomId = mxEvent.getRoomId(); + const eventId = mxEvent.getId(); + const originalContent = mxEvent.getContent(); + if (isStateEvent) { + return ( + + {(cli) => ( + this.onBack()} + inputs={{ + eventType: mxEvent.getType(), + evContent: JSON.stringify(originalContent, null, "\t"), + stateKey: mxEvent.getStateKey(), + }} + /> + )} + + ); + } else { + // send an edit-message event + // prefill the "m.new_content" field + const newContent = { + ...originalContent, + "m.new_content": originalContent, + "m.relates_to": { + rel_type: "m.replace", + event_id: eventId, + }, + }; + return ( + + {(cli) => ( + this.onBack()} + inputs={{ + eventType: mxEvent.getType(), + evContent: JSON.stringify(newContent, null, "\t"), + }} + /> + )} + + ); + } + } + + render() { + const BaseDialog = sdk.getComponent("views.dialogs.BaseDialog"); + const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit + + const isEditing = this.state.isEditing; + const roomId = mxEvent.getRoomId(); + const eventId = mxEvent.getId(); return (
-
Room ID: {this.props.roomId}
-
Event ID: {this.props.eventId}
+
Room ID: {roomId}
+
Event ID: {eventId}
- {isEditing ? this.state.editComponent : content} + {isEditing ? this.editSourceContent() : this.viewSourceContent()}
{!isEditing && (
- +
)} diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index a1c111b19c..6809d28e36 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -124,16 +124,9 @@ export default class MessageContextMenu extends React.Component { }; onViewSourceClick = () => { - const ev = this.props.mxEvent.replacingEvent() || this.props.mxEvent; const ViewSource = sdk.getComponent('structures.ViewSource'); Modal.createTrackedDialog('View Event Source', '', ViewSource, { - roomId: ev.getRoomId(), - eventId: ev.getId(), - content: ev.event, - event: ev, - isEncrypted: this.props.mxEvent.getType() !== this.props.mxEvent.getWireType(), - // FIXME: _clearEvent is private - decryptedContent: ev._clearEvent, + mxEvent: this.props.mxEvent, }, 'mx_Dialog_viewsource'); this.closeMenu(); }; diff --git a/src/components/views/messages/EditHistoryMessage.js b/src/components/views/messages/EditHistoryMessage.js index 68a3c95745..3bd9dfbd21 100644 --- a/src/components/views/messages/EditHistoryMessage.js +++ b/src/components/views/messages/EditHistoryMessage.js @@ -74,11 +74,7 @@ export default class EditHistoryMessage extends React.PureComponent { _onViewSourceClick = () => { const ViewSource = sdk.getComponent('structures.ViewSource'); Modal.createTrackedDialog('View Event Source', 'Edit history', ViewSource, { - roomId: this.props.mxEvent.getRoomId(), - eventId: this.props.mxEvent.getId(), - content: this.props.mxEvent.event, - isEncrypted: this.props.mxEvent.getType() !== this.props.mxEvent.getWireType(), - decryptedContent: this.props.mxEvent._clearEvent, + mxEvent: this.props.mxEvent, }, 'mx_Dialog_viewsource'); }; From 29b95e60833fef1bed598897dad2deff4e875ff4 Mon Sep 17 00:00:00 2001 From: Panagiotis <27917356+panoschal@users.noreply.github.com> Date: Sat, 6 Mar 2021 16:47:29 +0200 Subject: [PATCH 4/7] fix: make edit prefill work correctly from EditHistory handle encrypted and unencrypted events get the correct event_id (the base message) when called from EditHistoryMessage keep only the `body` and `msgtype` fields when prefilling --- src/components/structures/ViewSource.js | 33 +++++++++++++++++++------ 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/src/components/structures/ViewSource.js b/src/components/structures/ViewSource.js index 369a0a1ddd..4ee70ee2a7 100644 --- a/src/components/structures/ViewSource.js +++ b/src/components/structures/ViewSource.js @@ -39,6 +39,7 @@ export default class ViewSource extends React.Component { } onBack() { + // TODO: refresh the "Event ID:" modal header this.setState({ isEditing: false }); } @@ -80,15 +81,28 @@ export default class ViewSource extends React.Component { } } + // returns the id of the initial message, not the id of the previous edit + getBaseEventId() { + const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit + const isEncrypted = this.props.mxEvent.getType() !== this.props.mxEvent.getWireType(); + const baseMxEvent = this.props.mxEvent; + + if (isEncrypted) { + // `relates_to` field is inside the encrypted event + return mxEvent.event.content["m.relates_to"]?.event_id ?? baseMxEvent.getId(); + } else { + return mxEvent.getContent()["m.relates_to"]?.event_id ?? baseMxEvent.getId(); + } + } + // returns the SendCustomEvent component prefilled with the correct details editSourceContent() { const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit const isStateEvent = mxEvent.isState(); - console.log("isStateEvent", isStateEvent); const roomId = mxEvent.getRoomId(); - const eventId = mxEvent.getId(); const originalContent = mxEvent.getContent(); + if (isStateEvent) { return ( @@ -107,14 +121,19 @@ export default class ViewSource extends React.Component { ); } else { - // send an edit-message event - // prefill the "m.new_content" field + // prefill an edit-message event + // keep only the `body` and `msgtype` fields of originalContent + const bodyToStartFrom = originalContent["m.new_content"]?.body ?? originalContent.body; // prefill the last edit body, to start editing from there const newContent = { - ...originalContent, - "m.new_content": originalContent, + "body": ` * ${bodyToStartFrom}`, + "msgtype": originalContent.msgtype, + "m.new_content": { + body: bodyToStartFrom, + msgtype: originalContent.msgtype, + }, "m.relates_to": { rel_type: "m.replace", - event_id: eventId, + event_id: this.getBaseEventId(), }, }; return ( From df52ec28d60ecdf9719f3b5339a805f6a643f753 Mon Sep 17 00:00:00 2001 From: Panagiotis <27917356+panoschal@users.noreply.github.com> Date: Sat, 6 Mar 2021 17:09:46 +0200 Subject: [PATCH 5/7] fix: show edit button only if you have permission --- src/components/structures/ViewSource.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/structures/ViewSource.js b/src/components/structures/ViewSource.js index 4ee70ee2a7..ddcffe4f7f 100644 --- a/src/components/structures/ViewSource.js +++ b/src/components/structures/ViewSource.js @@ -23,6 +23,7 @@ import { _t } from "../../languageHandler"; import * as sdk from "../../index"; import MatrixClientContext from "../../contexts/MatrixClientContext"; import { SendCustomEvent } from "../views/dialogs/DevtoolsDialog"; +import { canEditContent } from "../../utils/EventUtils"; export default class ViewSource extends React.Component { static propTypes = { @@ -162,6 +163,7 @@ export default class ViewSource extends React.Component { const isEditing = this.state.isEditing; const roomId = mxEvent.getRoomId(); const eventId = mxEvent.getId(); + const canEdit = canEditContent(this.props.mxEvent); return (
@@ -170,7 +172,7 @@ export default class ViewSource extends React.Component {
{isEditing ? this.editSourceContent() : this.viewSourceContent()}
- {!isEditing && ( + {!isEditing && canEdit && (
From 9287e8dfa4f55b368a3c108d3c15442cdfdc4c1c Mon Sep 17 00:00:00 2001 From: Panagiotis <27917356+panoschal@users.noreply.github.com> Date: Mon, 8 Mar 2021 22:15:34 +0200 Subject: [PATCH 6/7] use isEncrypted, edit state events --- src/components/structures/ViewSource.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/structures/ViewSource.js b/src/components/structures/ViewSource.js index ddcffe4f7f..cfe28e9f73 100644 --- a/src/components/structures/ViewSource.js +++ b/src/components/structures/ViewSource.js @@ -51,7 +51,7 @@ export default class ViewSource extends React.Component { // returns the dialog body for viewing the event source viewSourceContent() { const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit - const isEncrypted = this.props.mxEvent.getType() !== this.props.mxEvent.getWireType(); + const isEncrypted = mxEvent.isEncrypted(); const decryptedEventSource = mxEvent._clearEvent; // FIXME: _clearEvent is private const originalEventSource = mxEvent.event; @@ -85,7 +85,7 @@ export default class ViewSource extends React.Component { // returns the id of the initial message, not the id of the previous edit getBaseEventId() { const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit - const isEncrypted = this.props.mxEvent.getType() !== this.props.mxEvent.getWireType(); + const isEncrypted = mxEvent.isEncrypted(); const baseMxEvent = this.props.mxEvent; if (isEncrypted) { @@ -163,7 +163,7 @@ export default class ViewSource extends React.Component { const isEditing = this.state.isEditing; const roomId = mxEvent.getRoomId(); const eventId = mxEvent.getId(); - const canEdit = canEditContent(this.props.mxEvent); + const canEdit = canEditContent(this.props.mxEvent) || mxEvent.isState(); return (
From 0936ea7e640ac10449150eaed1615bc99c52e70c Mon Sep 17 00:00:00 2001 From: Panagiotis <27917356+panoschal@users.noreply.github.com> Date: Tue, 9 Mar 2021 14:46:37 +0200 Subject: [PATCH 7/7] feat: show edit button only when user has permissions call appropriate functions for state events and edit message events --- src/components/structures/ViewSource.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/structures/ViewSource.js b/src/components/structures/ViewSource.js index cfe28e9f73..39666edd65 100644 --- a/src/components/structures/ViewSource.js +++ b/src/components/structures/ViewSource.js @@ -24,6 +24,7 @@ import * as sdk from "../../index"; import MatrixClientContext from "../../contexts/MatrixClientContext"; import { SendCustomEvent } from "../views/dialogs/DevtoolsDialog"; import { canEditContent } from "../../utils/EventUtils"; +import { MatrixClientPeg } from '../../MatrixClientPeg'; export default class ViewSource extends React.Component { static propTypes = { @@ -156,6 +157,12 @@ export default class ViewSource extends React.Component { } } + canSendStateEvent(mxEvent) { + const cli = MatrixClientPeg.get(); + const room = cli.getRoom(mxEvent.getRoomId()); + return room.currentState.mayClientSendStateEvent(mxEvent.getType(), cli); + } + render() { const BaseDialog = sdk.getComponent("views.dialogs.BaseDialog"); const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit @@ -163,7 +170,7 @@ export default class ViewSource extends React.Component { const isEditing = this.state.isEditing; const roomId = mxEvent.getRoomId(); const eventId = mxEvent.getId(); - const canEdit = canEditContent(this.props.mxEvent) || mxEvent.isState(); + const canEdit = mxEvent.isState() ? this.canSendStateEvent(mxEvent) : canEditContent(this.props.mxEvent); return (