From 4517fea496cdf65a472ded5dff4ebf83a2fab221 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 31 Jul 2019 00:46:21 +0100 Subject: [PATCH 1/4] Introduce RoomContext for sharing state between RoomView and children Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/RoomView.js | 52 ++++++++++++++++++- .../views/messages/MessageActionBar.js | 40 +++----------- 2 files changed, 56 insertions(+), 36 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 03b371ef7e..535a1f3df3 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -27,8 +27,8 @@ import React from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; import Promise from 'bluebird'; -import filesize from 'filesize'; import classNames from 'classnames'; +import {Room} from "matrix-js-sdk"; import { _t } from '../../languageHandler'; import {RoomPermalinkCreator} from '../../matrix-to'; @@ -64,6 +64,12 @@ if (DEBUG) { debuglog = console.log.bind(console); } +const RoomContext = PropTypes.shape({ + canReact: PropTypes.bool.isRequired, + canReply: PropTypes.bool.isRequired, + room: PropTypes.instanceOf(Room), +}); + module.exports = React.createClass({ displayName: 'RoomView', propTypes: { @@ -88,7 +94,7 @@ module.exports = React.createClass({ // * name (string) The room's name // * avatarUrl (string) The mxc:// avatar URL for the room // * inviterName (string) The display name of the person who - // * invited us tovthe room + // * invited us to the room oobData: PropTypes.object, // is the RightPanel collapsed? @@ -156,6 +162,24 @@ module.exports = React.createClass({ // We load this later by asking the js-sdk to suggest a version for us. // This object is the result of Room#getRecommendedVersion() upgradeRecommendation: null, + + canReact: false, + canReply: false, + }; + }, + + childContextTypes: { + room: RoomContext, + }, + + getChildContext: function() { + const {canReact, canReply, room} = this.state; + return { + room: { + canReact, + canReply, + room, + }, }; }, @@ -165,6 +189,7 @@ module.exports = React.createClass({ MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline); MatrixClientPeg.get().on("Room.name", this.onRoomName); MatrixClientPeg.get().on("Room.accountData", this.onRoomAccountData); + MatrixClientPeg.get().on("RoomState.events", this.onRoomStateEvents); MatrixClientPeg.get().on("RoomState.members", this.onRoomStateMember); MatrixClientPeg.get().on("Room.myMembership", this.onMyMembership); MatrixClientPeg.get().on("accountData", this.onAccountData); @@ -672,6 +697,7 @@ module.exports = React.createClass({ this._loadMembersIfJoined(room); this._calculateRecommendedVersion(room); this._updateE2EStatus(room); + this._updatePermissions(room); }, _calculateRecommendedVersion: async function(room) { @@ -795,6 +821,15 @@ module.exports = React.createClass({ } }, + onRoomStateEvents: function(ev, state) { + // ignore if we don't have a room yet + if (!this.state.room || this.state.room.roomId !== state.roomId) { + return; + } + + this._updatePermissions(this.state.room); + }, + onRoomStateMember: function(ev, state, member) { // ignore if we don't have a room yet if (!this.state.room) { @@ -813,6 +848,17 @@ module.exports = React.createClass({ if (room.roomId === this.state.roomId) { this.forceUpdate(); this._loadMembersIfJoined(room); + this._updatePermissions(room); + } + }, + + _updatePermissions: function(room) { + if (room) { + const me = MatrixClientPeg.get().getUserId(); + const canReact = room.getMyMembership() === "join" && room.currentState.maySendEvent("m.reaction", me); + const canReply = room.maySendMessage(); + + this.setState({canReact, canReply}); } }, @@ -1916,3 +1962,5 @@ module.exports = React.createClass({ ); }, }); + +module.exports.RoomContext = RoomContext; diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index 387a15d292..8db268076c 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -24,7 +24,7 @@ import dis from '../../../dispatcher'; import Modal from '../../../Modal'; import { createMenu } from '../../structures/ContextualMenu'; import { isContentActionable, canEditContent } from '../../../utils/EventUtils'; -import MatrixClientPeg from "../../../MatrixClientPeg"; +import {RoomContext} from "../../structures/RoomView"; export default class MessageActionBar extends React.PureComponent { static propTypes = { @@ -37,44 +37,16 @@ export default class MessageActionBar extends React.PureComponent { onFocusChange: PropTypes.func, }; - constructor(props, context) { - super(props, context); - - this.state = { - canReact: true, - canReply: true, - }; - } + static contextTypes = { + room: RoomContext, + }; componentDidMount() { this.props.mxEvent.on("Event.decrypted", this.onDecrypted); - const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); - if (room) { - room.on("RoomMember.powerLevel", this.onPermissionsChange); - room.on("RoomMember.membership", this.onPermissionsChange); - this.onPermissionsChange(); - } } componentWillUnmount() { this.props.mxEvent.removeListener("Event.decrypted", this.onDecrypted); - const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); - if (room) { - room.removeListener("RoomMember.powerLevel", this.onPermissionsChange); - room.removeListener("RoomMember.membership", this.onPermissionsChange); - } - } - - onPermissionsChange() { - const cli = MatrixClientPeg.get(); - const room = cli.getRoom(this.props.mxEvent.getRoomId()); - if (room) { - const me = cli.getUserId(); - const canReact = room.getMyMembership() === "join" && room.currentState.maySendEvent("m.reaction", me); - const canReply = room.maySendMessage(); - - this.setState({canReact, canReply}); - } } onDecrypted = () => { @@ -173,10 +145,10 @@ export default class MessageActionBar extends React.PureComponent { let editButton; if (isContentActionable(this.props.mxEvent)) { - if (this.state.canReact) { + if (this.context.room.canReact) { reactButton = this.renderReactButton(); } - if (this.state.canReply) { + if (this.context.room.canReply) { replyButton = Date: Wed, 31 Jul 2019 11:10:49 +0100 Subject: [PATCH 2/4] Fix context when using subtree of components --- src/components/views/messages/MessageActionBar.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index 8db268076c..fb3c53c9f0 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -145,10 +145,12 @@ export default class MessageActionBar extends React.PureComponent { let editButton; if (isContentActionable(this.props.mxEvent)) { - if (this.context.room.canReact) { + // `context` can be null in tests that use a subtree of components + // that don't create the context. + if (!this.context || !this.context.room || this.context.room.canReact) { reactButton = this.renderReactButton(); } - if (this.context.room.canReply) { + if (!this.context || !this.context.room || this.context.room.canReply) { replyButton = Date: Wed, 31 Jul 2019 11:17:24 +0100 Subject: [PATCH 3/4] Add `room` context to test --- src/components/views/messages/MessageActionBar.js | 6 ++---- test/components/structures/MessagePanel-test.js | 5 +++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index fb3c53c9f0..8db268076c 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -145,12 +145,10 @@ export default class MessageActionBar extends React.PureComponent { let editButton; if (isContentActionable(this.props.mxEvent)) { - // `context` can be null in tests that use a subtree of components - // that don't create the context. - if (!this.context || !this.context.room || this.context.room.canReact) { + if (this.context.room.canReact) { reactButton = this.renderReactButton(); } - if (!this.context || !this.context.room || this.context.room.canReply) { + if (this.context.room.canReply) { replyButton = Date: Wed, 31 Jul 2019 12:25:04 +0200 Subject: [PATCH 4/4] workaround diff-dom returning redundant diff actions --- src/utils/MessageDiffUtils.js | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/utils/MessageDiffUtils.js b/src/utils/MessageDiffUtils.js index 8c1035132b..78f3faa0c5 100644 --- a/src/utils/MessageDiffUtils.js +++ b/src/utils/MessageDiffUtils.js @@ -219,6 +219,32 @@ function renderDifferenceInDOM(originalRootNode, diff, diffMathPatch) { } } +function routeIsEqual(r1, r2) { + return r1.length === r2.length && !r1.some((e, i) => e !== r2[i]); +} + +// workaround for https://github.com/fiduswriter/diffDOM/issues/90 +function filterCancelingOutDiffs(originalDiffActions) { + const diffActions = originalDiffActions.slice(); + + for (let i = 0; i < diffActions.length; ++i) { + const diff = diffActions[i]; + if (diff.action === "removeTextElement") { + const nextDiff = diffActions[i + 1]; + const cancelsOut = nextDiff && + nextDiff.action === "addTextElement" && + nextDiff.text === diff.text && + routeIsEqual(nextDiff.route, diff.route); + + if (cancelsOut) { + diffActions.splice(i, 2); + } + } + } + + return diffActions; +} + /** * Renders a message with the changes made in an edit shown visually. * @param {object} originalContent the content for the base message @@ -233,7 +259,9 @@ export function editBodyDiffToHtml(originalContent, editContent) { // diffActions is an array of objects with at least a `action` and `route` // property. `action` tells us what the diff object changes, and `route` where. // `route` is a path on the DOM tree expressed as an array of indices. - const diffActions = dd.diff(originalBody, editBody); + const originaldiffActions = dd.diff(originalBody, editBody); + // work around https://github.com/fiduswriter/diffDOM/issues/90 + const diffActions = filterCancelingOutDiffs(originaldiffActions); // for diffing text fragments const diffMathPatch = new DiffMatchPatch(); // parse the base html message as a DOM tree, to which we'll apply the differences found.