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 = ( +
+ { _t('Resend') } +
+ ); + } - if (eventStatus === EventStatus.NOT_SENT) { - resendButton = ( -
- { _t('Resend') } + if (editStatus === EventStatus.NOT_SENT) { + resendEditButton = ( +
+ { _t('Resend edit') } +
+ ); + } + + if (unsentReactionsCount !== 0) { + resendReactionsButton = ( +
+ { _t('Resend %(unsentCount)s reaction(s)', {unsentCount: unsentReactionsCount}) } +
+ ); + } + } + + if (redactStatus === EventStatus.NOT_SENT) { + resendRedactionButton = ( +
+ { _t('Resend removal') }
); } @@ -250,7 +346,7 @@ module.exports = React.createClass({ ); } - if (eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT) { + if (allowCancel) { cancelButton = (
{ _t('Cancel Sending') } @@ -352,6 +448,9 @@ module.exports = React.createClass({ return (
{ resendButton } + { resendEditButton } + { resendReactionsButton } + { resendRedactionButton } { redactButton } { cancelButton } { forwardButton } diff --git a/src/components/views/elements/MessageEditor.js b/src/components/views/elements/MessageEditor.js index 0830708701..593e5bc616 100644 --- a/src/components/views/elements/MessageEditor.js +++ b/src/components/views/elements/MessageEditor.js @@ -31,6 +31,7 @@ import {renderModel} from '../../../editor/render'; import EditorStateTransfer from '../../../utils/EditorStateTransfer'; import {MatrixClient} from 'matrix-js-sdk'; import classNames from 'classnames'; +import {EventStatus} from 'matrix-js-sdk'; export default class MessageEditor extends React.Component { static propTypes = { @@ -195,12 +196,24 @@ export default class MessageEditor extends React.Component { }, contentBody); const roomId = this.props.editState.getEvent().getRoomId(); + this._cancelPreviousPendingEdit(); this.context.matrixClient.sendMessage(roomId, content); dis.dispatch({action: "edit_event", event: null}); dis.dispatch({action: 'focus_composer'}); } + _cancelPreviousPendingEdit() { + const originalEvent = this.props.editState.getEvent(); + const previousEdit = originalEvent.replacingEvent(); + if (previousEdit && ( + previousEdit.status === EventStatus.QUEUED || + previousEdit.status === EventStatus.NOT_SENT + )) { + this.context.matrixClient.cancelPendingEvent(previousEdit); + } + } + _onAutoCompleteConfirm = (completion) => { this.model.autoComplete.onComponentConfirm(completion); } diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 988bf7eb3c..192e203f35 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -681,7 +681,7 @@ module.exports = withMatrixClient(React.createClass({
: null; let reactionsRow; - if (SettingsStore.isFeatureEnabled("feature_reactions")) { + if (SettingsStore.isFeatureEnabled("feature_reactions") && !isRedacted) { const ReactionsRow = sdk.getComponent('messages.ReactionsRow'); reactionsRow =