From e16f5115276466256c223502a04eb2ee451ca868 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 24 Apr 2020 15:07:39 +0100 Subject: [PATCH 1/9] Redesign redactions Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/_components.scss | 2 +- .../{_UnknownBody.scss => _RedactedBody.scss} | 23 +++++++++- res/css/views/rooms/_EventTile.scss | 28 ------------ res/img/feather-customised/trash (custom).svg | 7 +++ .../views/messages/EditHistoryMessage.js | 4 +- src/components/views/messages/MessageEvent.js | 5 +-- .../views/messages/RedactedBody.tsx | 45 +++++++++++++++++++ src/components/views/messages/UnknownBody.js | 40 ----------------- src/i18n/strings/en_EN.json | 5 +-- 9 files changed, 80 insertions(+), 79 deletions(-) rename res/css/views/messages/{_UnknownBody.scss => _RedactedBody.scss} (52%) create mode 100644 res/img/feather-customised/trash (custom).svg create mode 100644 src/components/views/messages/RedactedBody.tsx delete mode 100644 src/components/views/messages/UnknownBody.js diff --git a/res/css/_components.scss b/res/css/_components.scss index 0ba2b609e8..0344074369 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -140,10 +140,10 @@ @import "./views/messages/_ReactionsRow.scss"; @import "./views/messages/_ReactionsRowButton.scss"; @import "./views/messages/_ReactionsRowButtonTooltip.scss"; +@import "./views/messages/_RedactedBody.scss"; @import "./views/messages/_RoomAvatarEvent.scss"; @import "./views/messages/_SenderProfile.scss"; @import "./views/messages/_TextualEvent.scss"; -@import "./views/messages/_UnknownBody.scss"; @import "./views/messages/_ViewSourceEvent.scss"; @import "./views/messages/_common_CryptoEvent.scss"; @import "./views/right_panel/_EncryptionInfo.scss"; diff --git a/res/css/views/messages/_UnknownBody.scss b/res/css/views/messages/_RedactedBody.scss similarity index 52% rename from res/css/views/messages/_UnknownBody.scss rename to res/css/views/messages/_RedactedBody.scss index 9036e12bf0..c0001db5d3 100644 --- a/res/css/views/messages/_UnknownBody.scss +++ b/res/css/views/messages/_RedactedBody.scss @@ -1,5 +1,6 @@ /* -Copyright 2015, 2016 OpenMarket Ltd +Copyright 2020 The Matrix.org Foundation C.I.C. + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -11,6 +12,24 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_UnknownBody { +.mx_RedactedBody { white-space: pre-wrap; + color: $muted-fg-color; + + padding-left: 16px; + position: relative; + + &::before { + height: 14px; + width: 14px; + background-color: $muted-fg-color; + mask-image: url('$(res)/img/feather-customised/trash (custom).svg'); + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + content: ''; + position: absolute; + top: 2px; + left: 0; + } } diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 0dc60226b8..3f5f039005 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -242,34 +242,6 @@ limitations under the License. color: $event-notsent-color; } -.mx_EventTile_redacted .mx_EventTile_line .mx_UnknownBody, -.mx_EventTile_redacted .mx_EventTile_reply .mx_UnknownBody { - --lozenge-color: $event-redacted-fg-color; - --lozenge-border-color: $event-redacted-border-color; - display: block; - height: 22px; - width: 250px; - border-radius: 11px; - background: - repeating-linear-gradient( - -45deg, - var(--lozenge-color), - var(--lozenge-color) 3px, - transparent 3px, - transparent 6px - ); - box-shadow: 0px 0px 3px var(--lozenge-border-color) inset; -} - -.mx_EventTile_sending.mx_EventTile_redacted .mx_UnknownBody { - opacity: 0.4; -} - -div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { - --lozenge-color: $event-notsent-color; - --lozenge-border-color: $event-notsent-color; -} - .mx_EventTile_contextual { opacity: 0.4; } diff --git a/res/img/feather-customised/trash (custom).svg b/res/img/feather-customised/trash (custom).svg new file mode 100644 index 0000000000..dc1985ddb2 --- /dev/null +++ b/res/img/feather-customised/trash (custom).svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/components/views/messages/EditHistoryMessage.js b/src/components/views/messages/EditHistoryMessage.js index 679a8f7471..e0ca23d244 100644 --- a/src/components/views/messages/EditHistoryMessage.js +++ b/src/components/views/messages/EditHistoryMessage.js @@ -26,6 +26,7 @@ import * as sdk from '../../../index'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import Modal from '../../../Modal'; import classNames from 'classnames'; +import RedactedBody from "./RedactedBody"; function getReplacedContent(event) { const originalContent = event.getOriginalContent(); @@ -132,8 +133,7 @@ export default class EditHistoryMessage extends React.PureComponent { const content = getReplacedContent(mxEvent); let contentContainer; if (mxEvent.isRedacted()) { - const UnknownBody = sdk.getComponent('messages.UnknownBody'); - contentContainer = ; + contentContainer = ; } else { let contentElements; if (this.props.previousEdit) { diff --git a/src/components/views/messages/MessageEvent.js b/src/components/views/messages/MessageEvent.js index beba986104..f8bd23cbe3 100644 --- a/src/components/views/messages/MessageEvent.js +++ b/src/components/views/messages/MessageEvent.js @@ -20,6 +20,7 @@ import createReactClass from 'create-react-class'; import * as sdk from '../../../index'; import SettingsStore from "../../../settings/SettingsStore"; import {Mjolnir} from "../../../mjolnir/Mjolnir"; +import RedactedBody from "./RedactedBody"; export default createReactClass({ displayName: 'MessageEvent', @@ -61,8 +62,6 @@ export default createReactClass({ }, render: function() { - const UnknownBody = sdk.getComponent('messages.UnknownBody'); - const bodyTypes = { 'm.text': sdk.getComponent('messages.TextualBody'), 'm.notice': sdk.getComponent('messages.TextualBody'), @@ -79,7 +78,7 @@ export default createReactClass({ const content = this.props.mxEvent.getContent(); const type = this.props.mxEvent.getType(); const msgtype = content.msgtype; - let BodyType = UnknownBody; + let BodyType = RedactedBody; if (!this.props.mxEvent.isRedacted()) { // only resolve BodyType if event is not redacted if (type && evTypes[type]) { diff --git a/src/components/views/messages/RedactedBody.tsx b/src/components/views/messages/RedactedBody.tsx new file mode 100644 index 0000000000..f219e3bd91 --- /dev/null +++ b/src/components/views/messages/RedactedBody.tsx @@ -0,0 +1,45 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +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"; + +interface IProps { + mxEvent: MatrixEvent; +} + +const RedactedBody = React.forwardRef(({mxEvent}, ref) => { + const cli: MatrixClient = useContext(MatrixClientContext); + + let text = _t("Message deleted"); + const redactedBecauseUserId = mxEvent.getUnsigned().redacted_because.sender; + if (redactedBecauseUserId !== cli.getUserId()) { + const room = cli.getRoom(mxEvent.getRoomId()); + const sender = room && room.getMember(redactedBecauseUserId); + text = _t("Message deleted by %(user)s", { user: sender.name || redactedBecauseUserId }); + } + + return ( + + { text } + + ); +}); + +export default RedactedBody; diff --git a/src/components/views/messages/UnknownBody.js b/src/components/views/messages/UnknownBody.js deleted file mode 100644 index 2a19f324e8..0000000000 --- a/src/components/views/messages/UnknownBody.js +++ /dev/null @@ -1,40 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import createReactClass from 'create-react-class'; -import { _t } from '../../../languageHandler'; - -export default createReactClass({ - displayName: 'UnknownBody', - - render: function() { - let tooltip = _t("Removed or unknown message type"); - if (this.props.mxEvent.isRedacted()) { - const redactedBecauseUserId = this.props.mxEvent.getUnsigned().redacted_because.sender; - tooltip = redactedBecauseUserId ? - _t("Message removed by %(userId)s", { userId: redactedBecauseUserId }) : - _t("Message removed"); - } - - const text = this.props.mxEvent.getContent().body; - return ( - - { text } - - ); - }, -}); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3eac055054..356e80afcf 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1328,6 +1328,8 @@ "Reactions": "Reactions", " reacted with %(content)s": " reacted with %(content)s", "reacted with %(shortName)s": "reacted with %(shortName)s", + "Message deleted": "Message deleted", + "Message deleted by %(user)s": "Message deleted by %(user)s", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s changed the avatar for %(roomName)s", "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removed the room avatar.", "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s changed the room avatar to ", @@ -1341,9 +1343,6 @@ "edited": "edited", "Can't load this message": "Can't load this message", "Submit logs": "Submit logs", - "Removed or unknown message type": "Removed or unknown message type", - "Message removed by %(userId)s": "Message removed by %(userId)s", - "Message removed": "Message removed", "Failed to load group members": "Failed to load group members", "Filter community members": "Filter community members", "Invite to this community": "Invite to this community", From d3b0e008c1b80acecd537c728c20cb9894d9f550 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 24 Apr 2020 15:39:23 +0100 Subject: [PATCH 2/9] first draft of Redaction ELS Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/messages/_RedactedBody.scss | 1 + src/components/structures/MessagePanel.js | 100 +++++++++++++++++- .../elements/RedactionEventListSummary.tsx | 81 ++++++++++++++ .../views/messages/RedactedBody.tsx | 2 +- src/i18n/strings/en_EN.json | 4 +- 5 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 src/components/views/elements/RedactionEventListSummary.tsx diff --git a/res/css/views/messages/_RedactedBody.scss b/res/css/views/messages/_RedactedBody.scss index c0001db5d3..c0e5be2c89 100644 --- a/res/css/views/messages/_RedactedBody.scss +++ b/res/css/views/messages/_RedactedBody.scss @@ -15,6 +15,7 @@ limitations under the License. .mx_RedactedBody { white-space: pre-wrap; color: $muted-fg-color; + vertical-align: middle; padding-left: 16px; position: relative; diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 6fbfdb504b..30c139d440 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -29,6 +29,7 @@ import SettingsStore from '../../settings/SettingsStore'; import {_t} from "../../languageHandler"; import {haveTileForEvent} from "../views/rooms/EventTile"; import {textForEvent} from "../../TextForEvent"; +import RedactionEventListSummary from "../views/elements/RedactionEventListSummary"; const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes const continuedTypes = ['m.sticker', 'm.room.message']; @@ -1062,5 +1063,102 @@ class MemberGrouper { } } +// Wrap consecutive redactions by the same user in a ListSummary, ignore if redacted +class RedactionGrouper { + static canStartGroup = function(panel, ev) { + return panel._shouldShowEvent(ev) && ev.isRedacted(); + } + + constructor(panel, ev, prevEvent, lastShownEvent) { + this.panel = panel; + this.readMarker = panel._readMarkerForEvent( + ev.getId(), + ev === lastShownEvent, + ); + this.events = [ev]; + this.prevEvent = prevEvent; + this.lastShownEvent = lastShownEvent; + } + + shouldGroup(ev) { + if (this.panel._wantsDateSeparator(this.events[0], ev.getDate())) return false; + if (ev.getType() === "m.room.redaction") return true; // for show-hidden-events users + return ev.isRedacted() && ev.sender === this.events[0].sender && + ev.getUnsigned().redacted_because.sender === this.events[0].getUnsigned().redacted_because.sender; + } + + add(ev) { + if (ev.getType() === "m.room.redaction") return; // for show-hidden-events users + this.readMarker = this.readMarker || this.panel._readMarkerForEvent( + ev.getId(), + ev === this.lastShownEvent, + ); + this.events.push(ev); + } + + getTiles() { + // If we don't have any events to group, don't even try to group them. The logic + // below assumes that we have a group of events to deal with, but we might not if + // the events we were supposed to group were redacted. + if (!this.events || !this.events.length) return []; + + const DateSeparator = sdk.getComponent('messages.DateSeparator'); + + const panel = this.panel; + const lastShownEvent = this.lastShownEvent; + const ret = []; + + if (panel._wantsDateSeparator(this.prevEvent, this.events[0].getDate())) { + const ts = this.events[0].getTs(); + ret.push( +
  • , + ); + } + + // Ensure that the key of the MemberEventListSummary does not change with new + // member events. This will prevent it from being re-created unnecessarily, and + // instead will allow new props to be provided. In turn, the shouldComponentUpdate + // method on ELS can be used to prevent unnecessary renderings. + const key = "redactioneventlistsummary-" + (this.prevEvent ? this.events[0].getId() : "initial"); + + let highlightInMels = false; + let eventTiles = this.events.map((e) => { + if (e.getId() === panel.props.highlightedEventId) { + highlightInMels = true; + } + // In order to prevent DateSeparators from appearing in the expanded form + // of MemberEventListSummary, render each member event as if the previous + // one was itself. This way, the timestamp of the previous event === the + // timestamp of the current event, and no DateSeparator is inserted. + return panel._getTilesForEvent(e, e, e === lastShownEvent); + }).reduce((a, b) => a.concat(b), []); + + if (eventTiles.length === 0) { + eventTiles = null; + } + + ret.push( + + { eventTiles } + , + ); + + if (this.readMarker) { + ret.push(this.readMarker); + } + + return ret; + } + + getNewPrevEvent() { + return this.events[0]; + } +} + // all the grouper classes that we use -const groupers = [CreationGrouper, MemberGrouper]; +const groupers = [CreationGrouper, MemberGrouper, RedactionGrouper]; diff --git a/src/components/views/elements/RedactionEventListSummary.tsx b/src/components/views/elements/RedactionEventListSummary.tsx new file mode 100644 index 0000000000..55538ca236 --- /dev/null +++ b/src/components/views/elements/RedactionEventListSummary.tsx @@ -0,0 +1,81 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from "react"; +import {MatrixClient} from "matrix-js-sdk/src/client"; +import {MatrixEvent} from "matrix-js-sdk/src/models/event"; + +import { _t } from "../../../languageHandler"; +import * as sdk from "../../../index"; +import MatrixClientContext from "../../../contexts/MatrixClientContext"; + +interface IProps { + // An array of member events to summarise + events: MatrixEvent[]; + // An array of EventTiles to render when expanded + children: React.ReactChildren; + // The minimum number of events needed to trigger summarisation + threshold?: number; + // Called when the ELS expansion is toggled + onToggle: () => void; + // Whether or not to begin with state.expanded=true + startExpanded?: boolean; +} + +export default class RedactionEventListSummary extends React.Component { + static displayName = "RedactionEventListSummary"; + + static defaultProps = { + threshold: 2, + }; + + static contextType = MatrixClientContext; + + shouldComponentUpdate(nextProps) { + // Update if + // - The number of summarised events has changed + // - or if the summary is about to toggle to become collapsed + // - or if there are fewEvents, meaning the child eventTiles are shown as-is + return ( + nextProps.events.length !== this.props.events.length || + nextProps.events.length < this.props.threshold + ); + } + + render() { + const count = this.props.events.length; + const redactionSender = this.props.events[0].getUnsigned().redacted_because.sender; + + let avatarMember = this.props.events[0].sender; + let summaryText = _t("%(count)s messages deleted", { count }); + if (redactionSender !== this.context.getUserId()) { + const room = (this.context as MatrixClient).getRoom(redactionSender || this.props.events[0].getSender()); + avatarMember = room && room.getMember(redactionSender); + const name = avatarMember ? avatarMember.name : redactionSender; + summaryText = _t("%(count)s messages deleted by %(name)s", { count, name }); + } + + const EventListSummary = sdk.getComponent("views.elements.EventListSummary"); + return ; + } +} diff --git a/src/components/views/messages/RedactedBody.tsx b/src/components/views/messages/RedactedBody.tsx index f219e3bd91..654f1622b1 100644 --- a/src/components/views/messages/RedactedBody.tsx +++ b/src/components/views/messages/RedactedBody.tsx @@ -32,7 +32,7 @@ const RedactedBody = React.forwardRef(({mxEvent}, ref) => { if (redactedBecauseUserId !== cli.getUserId()) { const room = cli.getRoom(mxEvent.getRoomId()); const sender = room && room.getMember(redactedBecauseUserId); - text = _t("Message deleted by %(user)s", { user: sender.name || redactedBecauseUserId }); + text = _t("Message deleted by %(name)s", { name: sender ? sender.name : redactedBecauseUserId }); } return ( diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 356e80afcf..53818d4747 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1329,7 +1329,7 @@ " reacted with %(content)s": " reacted with %(content)s", "reacted with %(shortName)s": "reacted with %(shortName)s", "Message deleted": "Message deleted", - "Message deleted by %(user)s": "Message deleted by %(user)s", + "Message deleted by %(name)s": "Message deleted by %(name)s", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s changed the avatar for %(roomName)s", "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removed the room avatar.", "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s changed the room avatar to ", @@ -1487,6 +1487,8 @@ "%(oneUser)smade no changes %(count)s times|one": "%(oneUser)smade no changes", "Power level": "Power level", "Custom level": "Custom level", + "%(count)s messages deleted|other": "%(count)s messages deleted", + "%(count)s messages deleted by %(name)s|other": "%(count)s messages deleted by %(name)s", "Unable to load event that was replied to, it either does not exist or you do not have permission to view it.": "Unable to load event that was replied to, it either does not exist or you do not have permission to view it.", "In reply to ": "In reply to ", "Room alias": "Room alias", From a399b1018ae1aa18e815dcbccdf35222ec1571ed Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 27 Apr 2020 23:16:08 +0100 Subject: [PATCH 3/9] fix flashing `by ""` Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/messages/RedactedBody.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/messages/RedactedBody.tsx b/src/components/views/messages/RedactedBody.tsx index 654f1622b1..e347e879a3 100644 --- a/src/components/views/messages/RedactedBody.tsx +++ b/src/components/views/messages/RedactedBody.tsx @@ -29,7 +29,7 @@ const RedactedBody = React.forwardRef(({mxEvent}, ref) => { let text = _t("Message deleted"); const redactedBecauseUserId = mxEvent.getUnsigned().redacted_because.sender; - if (redactedBecauseUserId !== cli.getUserId()) { + if (redactedBecauseUserId && redactedBecauseUserId !== cli.getUserId()) { const room = cli.getRoom(mxEvent.getRoomId()); const sender = room && room.getMember(redactedBecauseUserId); text = _t("Message deleted by %(name)s", { name: sender ? sender.name : redactedBecauseUserId }); From 83a4558d49021b58f30a47637ce69beb5d6b1fd3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 27 Apr 2020 23:27:46 +0100 Subject: [PATCH 4/9] remove RedactedGrouper for now Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/MessagePanel.js | 99 +------------------ .../views/elements/EventListSummary.js | 18 ++-- .../elements/RedactionEventListSummary.tsx | 81 --------------- 3 files changed, 13 insertions(+), 185 deletions(-) delete mode 100644 src/components/views/elements/RedactionEventListSummary.tsx diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 30c139d440..a83529e055 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -1063,102 +1063,5 @@ class MemberGrouper { } } -// Wrap consecutive redactions by the same user in a ListSummary, ignore if redacted -class RedactionGrouper { - static canStartGroup = function(panel, ev) { - return panel._shouldShowEvent(ev) && ev.isRedacted(); - } - - constructor(panel, ev, prevEvent, lastShownEvent) { - this.panel = panel; - this.readMarker = panel._readMarkerForEvent( - ev.getId(), - ev === lastShownEvent, - ); - this.events = [ev]; - this.prevEvent = prevEvent; - this.lastShownEvent = lastShownEvent; - } - - shouldGroup(ev) { - if (this.panel._wantsDateSeparator(this.events[0], ev.getDate())) return false; - if (ev.getType() === "m.room.redaction") return true; // for show-hidden-events users - return ev.isRedacted() && ev.sender === this.events[0].sender && - ev.getUnsigned().redacted_because.sender === this.events[0].getUnsigned().redacted_because.sender; - } - - add(ev) { - if (ev.getType() === "m.room.redaction") return; // for show-hidden-events users - this.readMarker = this.readMarker || this.panel._readMarkerForEvent( - ev.getId(), - ev === this.lastShownEvent, - ); - this.events.push(ev); - } - - getTiles() { - // If we don't have any events to group, don't even try to group them. The logic - // below assumes that we have a group of events to deal with, but we might not if - // the events we were supposed to group were redacted. - if (!this.events || !this.events.length) return []; - - const DateSeparator = sdk.getComponent('messages.DateSeparator'); - - const panel = this.panel; - const lastShownEvent = this.lastShownEvent; - const ret = []; - - if (panel._wantsDateSeparator(this.prevEvent, this.events[0].getDate())) { - const ts = this.events[0].getTs(); - ret.push( -
  • , - ); - } - - // Ensure that the key of the MemberEventListSummary does not change with new - // member events. This will prevent it from being re-created unnecessarily, and - // instead will allow new props to be provided. In turn, the shouldComponentUpdate - // method on ELS can be used to prevent unnecessary renderings. - const key = "redactioneventlistsummary-" + (this.prevEvent ? this.events[0].getId() : "initial"); - - let highlightInMels = false; - let eventTiles = this.events.map((e) => { - if (e.getId() === panel.props.highlightedEventId) { - highlightInMels = true; - } - // In order to prevent DateSeparators from appearing in the expanded form - // of MemberEventListSummary, render each member event as if the previous - // one was itself. This way, the timestamp of the previous event === the - // timestamp of the current event, and no DateSeparator is inserted. - return panel._getTilesForEvent(e, e, e === lastShownEvent); - }).reduce((a, b) => a.concat(b), []); - - if (eventTiles.length === 0) { - eventTiles = null; - } - - ret.push( - - { eventTiles } - , - ); - - if (this.readMarker) { - ret.push(this.readMarker); - } - - return ret; - } - - getNewPrevEvent() { - return this.events[0]; - } -} - // all the grouper classes that we use -const groupers = [CreationGrouper, MemberGrouper, RedactionGrouper]; +const groupers = [CreationGrouper, MemberGrouper]; diff --git a/src/components/views/elements/EventListSummary.js b/src/components/views/elements/EventListSummary.js index 79c84293c2..707b9e79b0 100644 --- a/src/components/views/elements/EventListSummary.js +++ b/src/components/views/elements/EventListSummary.js @@ -22,7 +22,8 @@ import {MatrixEvent, RoomMember} from "matrix-js-sdk"; import {useStateToggle} from "../../../hooks/useStateToggle"; import AccessibleButton from "./AccessibleButton"; -const EventListSummary = ({events, children, threshold=3, onToggle, startExpanded, summaryMembers=[], summaryText}) => { +const EventListSummary = (props) => { + const {events, children, threshold=3, onToggle, startExpanded, summaryMembers=[], summaryText, summary} = props; const [expanded, toggleExpanded] = useStateToggle(startExpanded); // Whenever expanded changes call onToggle @@ -49,6 +50,8 @@ const EventListSummary = ({events, children, threshold=3, onToggle, startExpande
     
    { children } ; + } else if (summary) { + body = summary; } else { const avatars = summaryMembers.map((m) => ); body = ( @@ -66,12 +69,12 @@ const EventListSummary = ({events, children, threshold=3, onToggle, startExpande } return ( -
    +
  • { expanded ? _t('collapse') : _t('expand') } { body } -
  • + ); }; @@ -87,10 +90,13 @@ EventListSummary.propTypes = { // Whether or not to begin with state.expanded=true startExpanded: PropTypes.bool, - // The list of room members for which to show avatars next to the summary + // The node to render as a summary when the summary is not collapsed, + // alternately summaryMembers and summaryText can be provided. + summary: PropTypes.node, + // The list of room members for which to show avatars next to the summary, ignored if summary is provided summaryMembers: PropTypes.arrayOf(PropTypes.instanceOf(RoomMember)), - // The text to show as the summary of this event list - summaryText: PropTypes.string.isRequired, + // The text to show as the summary of this event list, ignored if summary is provided + summaryText: PropTypes.string, }; export default EventListSummary; diff --git a/src/components/views/elements/RedactionEventListSummary.tsx b/src/components/views/elements/RedactionEventListSummary.tsx deleted file mode 100644 index 55538ca236..0000000000 --- a/src/components/views/elements/RedactionEventListSummary.tsx +++ /dev/null @@ -1,81 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from "react"; -import {MatrixClient} from "matrix-js-sdk/src/client"; -import {MatrixEvent} from "matrix-js-sdk/src/models/event"; - -import { _t } from "../../../languageHandler"; -import * as sdk from "../../../index"; -import MatrixClientContext from "../../../contexts/MatrixClientContext"; - -interface IProps { - // An array of member events to summarise - events: MatrixEvent[]; - // An array of EventTiles to render when expanded - children: React.ReactChildren; - // The minimum number of events needed to trigger summarisation - threshold?: number; - // Called when the ELS expansion is toggled - onToggle: () => void; - // Whether or not to begin with state.expanded=true - startExpanded?: boolean; -} - -export default class RedactionEventListSummary extends React.Component { - static displayName = "RedactionEventListSummary"; - - static defaultProps = { - threshold: 2, - }; - - static contextType = MatrixClientContext; - - shouldComponentUpdate(nextProps) { - // Update if - // - The number of summarised events has changed - // - or if the summary is about to toggle to become collapsed - // - or if there are fewEvents, meaning the child eventTiles are shown as-is - return ( - nextProps.events.length !== this.props.events.length || - nextProps.events.length < this.props.threshold - ); - } - - render() { - const count = this.props.events.length; - const redactionSender = this.props.events[0].getUnsigned().redacted_because.sender; - - let avatarMember = this.props.events[0].sender; - let summaryText = _t("%(count)s messages deleted", { count }); - if (redactionSender !== this.context.getUserId()) { - const room = (this.context as MatrixClient).getRoom(redactionSender || this.props.events[0].getSender()); - avatarMember = room && room.getMember(redactionSender); - const name = avatarMember ? avatarMember.name : redactionSender; - summaryText = _t("%(count)s messages deleted by %(name)s", { count, name }); - } - - const EventListSummary = sdk.getComponent("views.elements.EventListSummary"); - return ; - } -} From c7870090f8466bff4f40ea074c36c56b68469f28 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 27 Apr 2020 23:29:38 +0100 Subject: [PATCH 5/9] clean ups Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/MessagePanel.js | 1 - src/components/views/elements/EventListSummary.js | 8 +------- src/i18n/strings/en_EN.json | 2 -- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index a83529e055..6fbfdb504b 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -29,7 +29,6 @@ import SettingsStore from '../../settings/SettingsStore'; import {_t} from "../../languageHandler"; import {haveTileForEvent} from "../views/rooms/EventTile"; import {textForEvent} from "../../TextForEvent"; -import RedactionEventListSummary from "../views/elements/RedactionEventListSummary"; const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes const continuedTypes = ['m.sticker', 'm.room.message']; diff --git a/src/components/views/elements/EventListSummary.js b/src/components/views/elements/EventListSummary.js index 707b9e79b0..bf0f8ea133 100644 --- a/src/components/views/elements/EventListSummary.js +++ b/src/components/views/elements/EventListSummary.js @@ -22,8 +22,7 @@ import {MatrixEvent, RoomMember} from "matrix-js-sdk"; import {useStateToggle} from "../../../hooks/useStateToggle"; import AccessibleButton from "./AccessibleButton"; -const EventListSummary = (props) => { - const {events, children, threshold=3, onToggle, startExpanded, summaryMembers=[], summaryText, summary} = props; +const EventListSummary = ({events, children, threshold=3, onToggle, startExpanded, summaryMembers=[], summaryText}) => { const [expanded, toggleExpanded] = useStateToggle(startExpanded); // Whenever expanded changes call onToggle @@ -50,8 +49,6 @@ const EventListSummary = (props) => {
     
    { children } ; - } else if (summary) { - body = summary; } else { const avatars = summaryMembers.map((m) => ); body = ( @@ -90,9 +87,6 @@ EventListSummary.propTypes = { // Whether or not to begin with state.expanded=true startExpanded: PropTypes.bool, - // The node to render as a summary when the summary is not collapsed, - // alternately summaryMembers and summaryText can be provided. - summary: PropTypes.node, // The list of room members for which to show avatars next to the summary, ignored if summary is provided summaryMembers: PropTypes.arrayOf(PropTypes.instanceOf(RoomMember)), // The text to show as the summary of this event list, ignored if summary is provided diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index de3f88c244..8785b5e244 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1488,8 +1488,6 @@ "%(oneUser)smade no changes %(count)s times|one": "%(oneUser)smade no changes", "Power level": "Power level", "Custom level": "Custom level", - "%(count)s messages deleted|other": "%(count)s messages deleted", - "%(count)s messages deleted by %(name)s|other": "%(count)s messages deleted by %(name)s", "Unable to load event that was replied to, it either does not exist or you do not have permission to view it.": "Unable to load event that was replied to, it either does not exist or you do not have permission to view it.", "In reply to ": "In reply to ", "Room alias": "Room alias", From a5830c229ec62448cb014d3552955552be0f3ec6 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 27 Apr 2020 23:30:40 +0100 Subject: [PATCH 6/9] more tidying Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/elements/EventListSummary.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/elements/EventListSummary.js b/src/components/views/elements/EventListSummary.js index bf0f8ea133..5a4a6e4f5a 100644 --- a/src/components/views/elements/EventListSummary.js +++ b/src/components/views/elements/EventListSummary.js @@ -87,9 +87,9 @@ EventListSummary.propTypes = { // Whether or not to begin with state.expanded=true startExpanded: PropTypes.bool, - // The list of room members for which to show avatars next to the summary, ignored if summary is provided + // The list of room members for which to show avatars next to the summary summaryMembers: PropTypes.arrayOf(PropTypes.instanceOf(RoomMember)), - // The text to show as the summary of this event list, ignored if summary is provided + // The text to show as the summary of this event list summaryText: PropTypes.string, }; From 3e35cffae7ffb4ab6447f75464b84df82176a84d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 27 Apr 2020 23:53:32 +0100 Subject: [PATCH 7/9] null-guards Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/messages/RedactedBody.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/views/messages/RedactedBody.tsx b/src/components/views/messages/RedactedBody.tsx index e347e879a3..5dada64b52 100644 --- a/src/components/views/messages/RedactedBody.tsx +++ b/src/components/views/messages/RedactedBody.tsx @@ -28,8 +28,9 @@ const RedactedBody = React.forwardRef(({mxEvent}, ref) => { const cli: MatrixClient = useContext(MatrixClientContext); let text = _t("Message deleted"); - const redactedBecauseUserId = mxEvent.getUnsigned().redacted_because.sender; - if (redactedBecauseUserId && redactedBecauseUserId !== cli.getUserId()) { + const unsigned = mxEvent.getUnsigned(); + const redactedBecauseUserId = unsigned && unsigned.redacted_because && unsigned.redacted_because.sender; + if (redactedBecauseUserId && redactedBecauseUserId !== mxEvent.getSender()) { const room = cli.getRoom(mxEvent.getRoomId()); const sender = room && room.getMember(redactedBecauseUserId); text = _t("Message deleted by %(name)s", { name: sender ? sender.name : redactedBecauseUserId }); From 7c1ac7aaffe17a7a3941cb8bda4fb822876bf1ad Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 7 May 2020 10:02:40 +0100 Subject: [PATCH 8/9] fix unrelated tautology Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/MatrixChat.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 05354fa5f2..3929711406 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1339,7 +1339,7 @@ export default class MatrixChat extends React.PureComponent { // this if we are not scrolled up in the view. To find out, delegate to // the timeline panel. If the timeline panel doesn't exist, then we assume // it is safe to reset the timeline. - if (!this.loggedInView.current || !this.loggedInView.current) { + if (!this.loggedInView.current) { return true; } return this.loggedInView.current.canResetTimelineInRoom(roomId); From 4d6cd3c0504e7f4bc07e4cce95177affdc84bb66 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 7 May 2020 12:50:04 +0100 Subject: [PATCH 9/9] Update padding-left to 20px as per Nad's iteration Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/messages/_RedactedBody.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/messages/_RedactedBody.scss b/res/css/views/messages/_RedactedBody.scss index c0e5be2c89..9ba35d7297 100644 --- a/res/css/views/messages/_RedactedBody.scss +++ b/res/css/views/messages/_RedactedBody.scss @@ -17,7 +17,7 @@ limitations under the License. color: $muted-fg-color; vertical-align: middle; - padding-left: 16px; + padding-left: 20px; position: relative; &::before {