diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 1f75373be8..9f14cf1b00 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -171,12 +171,19 @@ limitations under the License. opacity: 0.4; } +div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { + --lozenge-color: $event-notsent-color !important; + --lozenge-border-color: $event-notsent-color !important; +} + .mx_EventTile_notSent { 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; width: 100%; height: 22px; @@ -184,18 +191,22 @@ limitations under the License. border-radius: 11px; background: repeating-linear-gradient( -45deg, - $event-redacted-fg-color, - $event-redacted-fg-color 3px, + var(--lozenge-color), + var(--lozenge-color) 3px, transparent 3px, transparent 6px ); - box-shadow: 0px 0px 3px $event-redacted-border-color inset; + box-shadow: 0px 0px 3px var(--lozenge-border-color) inset; } .mx_EventTile_highlight, .mx_EventTile_highlight .markdown-body - { - color: $warning-color; +{ + color: $event-highlight-fg-color; + + .mx_EventTile_line { + background-color: $event-highlight-bg-color; + } } .mx_EventTile_contextual { diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index ed1cc162a0..f2bfe5bc8a 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -121,6 +121,9 @@ $event-sending-color: $text-secondary-color; $event-redacted-fg-color: #606060; $event-redacted-border-color: #000000; +$event-highlight-fg-color: $warning-color; +$event-highlight-bg-color: $event-selected-color; + // event timestamp $event-timestamp-color: $text-secondary-color; diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 2dd193b8c5..447516a26b 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -196,11 +196,19 @@ $widget-menu-bar-bg-color: $secondary-accent-color; // ******************** +// both $event-highlight-bg-color and $room-warning-bg-color share this value, +// so to not make their order dependent on who depends on who, have a shared value +// defined before both +$yellow-background: #fff8e3; + // event tile lifecycle $event-encrypting-color: #abddbc; $event-sending-color: #ddd; $event-notsent-color: #f44; +$event-highlight-fg-color: $warning-color; +$event-highlight-bg-color: $yellow-background; + // event redaction $event-redacted-fg-color: #e2e2e2; $event-redacted-border-color: #cccccc; @@ -244,7 +252,7 @@ $togglesw-ball-color: #fff; $progressbar-color: #000; -$room-warning-bg-color: #fff8e3; +$room-warning-bg-color: $yellow-background; $memberstatus-placeholder-color: $roomtile-name-color; diff --git a/src/components/structures/ScrollPanel.js b/src/components/structures/ScrollPanel.js index 7e1f0ff469..40caa627af 100644 --- a/src/components/structures/ScrollPanel.js +++ b/src/components/structures/ScrollPanel.js @@ -214,6 +214,9 @@ module.exports = React.createClass({ // after an update to the contents of the panel, check that the scroll is // where it ought to be, and set off pagination requests if necessary. checkScroll: function() { + if (this.unmounted) { + return; + } this._restoreSavedScrollState(); this.checkFillState(); }, diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index 95bf59b470..04bc7c75ef 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -29,6 +29,10 @@ import SettingsStore from '../../../settings/SettingsStore'; import { isUrlPermitted } from '../../../HtmlUtils'; import { isContentActionable } from '../../../utils/EventUtils'; +function canCancel(eventStatus) { + return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT; +} + module.exports = React.createClass({ displayName: 'MessageContextMenu', @@ -90,6 +94,23 @@ module.exports = React.createClass({ this.closeMenu(); }, + onResendEditClick: function() { + Resend.resend(this.props.mxEvent.replacingEvent()); + this.closeMenu(); + }, + + onResendRedactionClick: function() { + Resend.resend(this.props.mxEvent.localRedactionEvent()); + this.closeMenu(); + }, + + onResendReactionsClick: function() { + for (const reaction of this._getUnsentReactions()) { + Resend.resend(reaction); + } + this.closeMenu(); + }, + e2eInfoClicked: function() { this.props.e2eInfoCallback(); this.closeMenu(); @@ -148,7 +169,25 @@ module.exports = React.createClass({ }, onCancelSendClick: function() { - Resend.removeFromQueue(this.props.mxEvent); + const mxEvent = this.props.mxEvent; + const editEvent = mxEvent.replacingEvent(); + const redactEvent = mxEvent.localRedactionEvent(); + const pendingReactions = this._getPendingReactions(); + + if (editEvent && canCancel(editEvent.status)) { + Resend.removeFromQueue(editEvent); + } + if (redactEvent && canCancel(redactEvent.status)) { + Resend.removeFromQueue(redactEvent); + } + if (pendingReactions.length) { + for (const reaction of pendingReactions) { + Resend.removeFromQueue(reaction); + } + } + if (canCancel(mxEvent.status)) { + Resend.removeFromQueue(this.props.mxEvent); + } this.closeMenu(); }, @@ -217,10 +256,42 @@ module.exports = React.createClass({ this.closeMenu(); }, + _getReactions(filter) { + const cli = MatrixClientPeg.get(); + const room = cli.getRoom(this.props.mxEvent.getRoomId()); + const eventId = this.props.mxEvent.getId(); + return room.getPendingEvents().filter(e => { + const relation = e.getRelation(); + return relation && + relation.rel_type === "m.annotation" && + relation.event_id === eventId && + filter(e); + }); + }, + + _getPendingReactions() { + return this._getReactions(e => canCancel(e.status)); + }, + + _getUnsentReactions() { + return this._getReactions(e => e.status === EventStatus.NOT_SENT); + }, + render: function() { const mxEvent = this.props.mxEvent; const eventStatus = mxEvent.status; + const editStatus = mxEvent.replacingEvent() && mxEvent.replacingEvent().status; + const redactStatus = mxEvent.localRedactionEvent() && mxEvent.localRedactionEvent().status; + const unsentReactionsCount = this._getUnsentReactions().length; + const pendingReactionsCount = this._getPendingReactions().length; + const allowCancel = canCancel(mxEvent.status) || + canCancel(editStatus) || + canCancel(redactStatus) || + pendingReactionsCount !== 0; let resendButton; + let resendEditButton; + let resendReactionsButton; + let resendRedactionButton; let redactButton; let cancelButton; let forwardButton; @@ -233,11 +304,36 @@ module.exports = React.createClass({ // status is SENT before remote-echo, null after const isSent = !eventStatus || eventStatus === EventStatus.SENT; + if (!mxEvent.isRedacted()) { + if (eventStatus === EventStatus.NOT_SENT) { + resendButton = ( +