diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index d8f9a4daa..62bffa762 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -16,15 +16,16 @@ limitations under the License. 'use strict'; -const React = require('react'); +import React from 'react'; -const MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg'); -const dis = require('matrix-react-sdk/lib/dispatcher'); -const sdk = require('matrix-react-sdk'); +import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg'; +import dis from 'matrix-react-sdk/lib/dispatcher'; +import sdk from 'matrix-react-sdk'; import { _t } from 'matrix-react-sdk/lib/languageHandler'; -const Modal = require('matrix-react-sdk/lib/Modal'); -const Resend = require("matrix-react-sdk/lib/Resend"); +import Modal from 'matrix-react-sdk/lib/Modal'; +import Resend from "matrix-react-sdk/lib/Resend"; import SettingsStore from "matrix-react-sdk/lib/settings/SettingsStore"; +import {makeEventPermalink} from 'matrix-react-sdk/lib/matrix-to'; module.exports = React.createClass({ displayName: 'MessageContextMenu', @@ -107,15 +108,14 @@ module.exports = React.createClass({ onFinished: (proceed) => { if (!proceed) return; - MatrixClientPeg.get().redactEvent( - this.props.mxEvent.getRoomId(), this.props.mxEvent.getId() - ).catch(function(e) { + const cli = MatrixClientPeg.get(); + cli.redactEvent(this.props.mxEvent.getRoomId(), this.props.mxEvent.getId()).catch(function(e) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); // display error message stating you couldn't delete this. const code = e.errcode || e.statusCode; Modal.createTrackedDialog('You cannot delete this message', '', ErrorDialog, { title: _t('Error'), - description: _t('You cannot delete this message. (%(code)s)', {code: code}) + description: _t('You cannot delete this message. (%(code)s)', {code}), }); }).done(); }, @@ -138,12 +138,12 @@ module.exports = React.createClass({ onPinClick: function() { MatrixClientPeg.get().getStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', '') - .catch(e => { + .catch((e) => { // Intercept the Event Not Found error and fall through the promise chain with no event. if (e.errcode === "M_NOT_FOUND") return null; throw e; }) - .then(event => { + .then((event) => { const eventIds = (event ? event.pinned : []) || []; if (!eventIds.includes(this.props.mxEvent.getId())) { // Not pinned - add @@ -153,7 +153,8 @@ module.exports = React.createClass({ eventIds.splice(eventIds.indexOf(this.props.mxEvent.getId()), 1); } - MatrixClientPeg.get().sendStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', {pinned: eventIds}, ''); + const cli = MatrixClientPeg.get(); + cli.sendStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', {pinned: eventIds}, ''); }); this.closeMenu(); }, @@ -177,6 +178,14 @@ module.exports = React.createClass({ this.closeMenu(); }, + onReplyClick: function() { + dis.dispatch({ + action: 'quote_event', + event: this.props.mxEvent, + }); + this.closeMenu(); + }, + render: function() { const eventStatus = this.props.mxEvent.status; let resendButton; @@ -184,12 +193,11 @@ module.exports = React.createClass({ let cancelButton; let forwardButton; let pinButton; - let viewSourceButton; let viewClearSourceButton; let unhidePreviewButton; - let permalinkButton; let externalURLButton; let quoteButton; + let replyButton; if (eventStatus === 'not_sent') { resendButton = ( @@ -227,14 +235,14 @@ module.exports = React.createClass({ if (this.state.canPin) { pinButton = (
- {this._isPinned() ? _t('Unpin Message') : _t('Pin Message')} + { this._isPinned() ? _t('Unpin Message') : _t('Pin Message') }
); } } } - viewSourceButton = ( + const viewSourceButton = (
{ _t('View Source') }
@@ -259,10 +267,10 @@ module.exports = React.createClass({ } // XXX: if we use room ID, we should also include a server where the event can be found (other than in the domain of the event ID) - permalinkButton = ( + const permalinkButton = (
- { _t('Permalink') } + { _t('Permalink') }
); @@ -272,32 +280,41 @@ module.exports = React.createClass({ { _t('Quote') } ); + + if (SettingsStore.isFeatureEnabled("feature_rich_quoting")) { + replyButton = ( +
+ { _t('Reply') } +
+ ); + } } // Bridges can provide a 'external_url' to link back to the source. - if( typeof(this.props.mxEvent.event.content.external_url) === "string") { - externalURLButton = ( -
- { _t('Source URL') } -
- ); + if (typeof(this.props.mxEvent.event.content.external_url) === "string") { + externalURLButton = ( +
+ { _t('Source URL') } +
+ ); } return (
- {resendButton} - {redactButton} - {cancelButton} - {forwardButton} - {pinButton} - {viewSourceButton} - {viewClearSourceButton} - {unhidePreviewButton} - {permalinkButton} - {quoteButton} - {externalURLButton} + { resendButton } + { redactButton } + { cancelButton } + { forwardButton } + { pinButton } + { viewSourceButton } + { viewClearSourceButton } + { unhidePreviewButton } + { permalinkButton } + { quoteButton } + { replyButton } + { externalURLButton }
); }, diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js index bdb9d17f2..d1a34d02d 100644 --- a/src/components/views/elements/ImageView.js +++ b/src/components/views/elements/ImageView.js @@ -20,7 +20,7 @@ var React = require('react'); var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg'); -var DateUtils = require('matrix-react-sdk/lib/DateUtils'); +import {formatDate} from 'matrix-react-sdk/lib/DateUtils'; var filesize = require('filesize'); var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton'); const Modal = require('matrix-react-sdk/lib/Modal'); @@ -159,7 +159,7 @@ module.exports = React.createClass({ } eventMeta = (
- { _t('Uploaded on %(date)s by %(user)s', {date: DateUtils.formatDate(new Date(this.props.mxEvent.getTs())), user: sender}) } + { _t('Uploaded on %(date)s by %(user)s', {date: formatDate(new Date(this.props.mxEvent.getTs())), user: sender}) }
); } diff --git a/src/components/views/messages/DateSeparator.js b/src/components/views/messages/DateSeparator.js index 84d63dd5f..5d579e5d8 100644 --- a/src/components/views/messages/DateSeparator.js +++ b/src/components/views/messages/DateSeparator.js @@ -16,7 +16,7 @@ limitations under the License. import React from 'react'; import { _t } from 'matrix-react-sdk/lib/languageHandler'; -import DateUtils from 'matrix-react-sdk/lib/DateUtils'; +import {formatFullDate} from 'matrix-react-sdk/lib/DateUtils'; function getdaysArray() { return [ @@ -49,7 +49,7 @@ module.exports = React.createClass({ label = days[date.getDay()]; } else { - label = DateUtils.formatFullDate(date, this.props.showTwelveHour); + label = formatFullDate(date, this.props.showTwelveHour); } return ( diff --git a/src/components/views/messages/MessageTimestamp.js b/src/components/views/messages/MessageTimestamp.js index 3102b5591..3d972e4c1 100644 --- a/src/components/views/messages/MessageTimestamp.js +++ b/src/components/views/messages/MessageTimestamp.js @@ -17,7 +17,7 @@ limitations under the License. 'use strict'; import React from 'react'; -import DateUtils from 'matrix-react-sdk/lib/DateUtils'; +import {formatFullDate, formatTime} from 'matrix-react-sdk/lib/DateUtils'; module.exports = React.createClass({ displayName: 'MessageTimestamp', @@ -29,8 +29,8 @@ module.exports = React.createClass({ render: function() { const date = new Date(this.props.ts); return ( - - { DateUtils.formatTime(date, this.props.showTwelveHour) } + + { formatTime(date, this.props.showTwelveHour) } ); }, diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c1857e314..cdca2570c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -217,5 +217,6 @@ "Contributing code to Matrix and Riot": "Contributing code to Matrix and Riot", "Dev chat for the Riot/Web dev team": "Dev chat for the Riot/Web dev team", "Dev chat for the Dendrite dev team": "Dev chat for the Dendrite dev team", - "Co-ordination for Riot/Web translators": "Co-ordination for Riot/Web translators" + "Co-ordination for Riot/Web translators": "Co-ordination for Riot/Web translators", + "Reply": "Reply" } diff --git a/src/skins/vector/css/_components.scss b/src/skins/vector/css/_components.scss index 0dd6a1b1b..c2725aeb9 100644 --- a/src/skins/vector/css/_components.scss +++ b/src/skins/vector/css/_components.scss @@ -39,6 +39,7 @@ @import "./matrix-react-sdk/views/elements/_RichText.scss"; @import "./matrix-react-sdk/views/elements/_RoleButton.scss"; @import "./matrix-react-sdk/views/elements/_ToolTipButton.scss"; +@import "./matrix-react-sdk/views/elements/_Quote.scss"; @import "./matrix-react-sdk/views/groups/_GroupPublicityToggle.scss"; @import "./matrix-react-sdk/views/groups/_GroupRoomList.scss"; @import "./matrix-react-sdk/views/groups/_GroupUserSettings.scss"; @@ -70,6 +71,7 @@ @import "./matrix-react-sdk/views/rooms/_RoomTile.scss"; @import "./matrix-react-sdk/views/rooms/_SearchableEntityList.scss"; @import "./matrix-react-sdk/views/rooms/_TopUnreadMessagesBar.scss"; +@import "./matrix-react-sdk/views/rooms/_QuotePreview.scss"; @import "./matrix-react-sdk/views/settings/_DevicesPanel.scss"; @import "./matrix-react-sdk/views/settings/_IntegrationsManager.scss"; @import "./matrix-react-sdk/views/voip/_CallView.scss"; diff --git a/src/skins/vector/css/matrix-react-sdk/views/elements/_Quote.scss b/src/skins/vector/css/matrix-react-sdk/views/elements/_Quote.scss new file mode 100644 index 000000000..0af555b5a --- /dev/null +++ b/src/skins/vector/css/matrix-react-sdk/views/elements/_Quote.scss @@ -0,0 +1,26 @@ +/* +Copyright 2017 Vector Creations 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. +*/ + +.mx_Quote .mx_DateSeparator { + font-size: 1em !important; + margin-bottom: 0; + padding-bottom: 1px; + bottom: -5px; +} + +.mx_Quote_show { + cursor: pointer; +} diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/_Autocomplete.scss b/src/skins/vector/css/matrix-react-sdk/views/rooms/_Autocomplete.scss index 6bf3f3b60..732ada088 100644 --- a/src/skins/vector/css/matrix-react-sdk/views/rooms/_Autocomplete.scss +++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/_Autocomplete.scss @@ -1,7 +1,7 @@ .mx_Autocomplete { position: absolute; bottom: 0; - z-index: 1000; + z-index: 1001; width: 100%; border: 1px solid $primary-hairline-color; background: $primary-bg-color; @@ -90,3 +90,4 @@ .mx_Autocomplete_Completion_description { color: gray; } + diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/_EventTile.scss b/src/skins/vector/css/matrix-react-sdk/views/rooms/_EventTile.scss index 5bb8a5663..33edd045a 100644 --- a/src/skins/vector/css/matrix-react-sdk/views/rooms/_EventTile.scss +++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/_EventTile.scss @@ -109,7 +109,7 @@ limitations under the License. /* this is used for the tile for the event which is selected via the URL. * TODO: ultimately we probably want some transition on here. */ -.mx_EventTile_selected .mx_EventTile_line { +.mx_EventTile_selected > .mx_EventTile_line { border-left: $accent-color 5px solid; padding-left: 60px; background-color: $event-selected-color; @@ -209,7 +209,7 @@ limitations under the License. visibility: visible; } -.mx_EventTile_selected .mx_MessageTimestamp { +.mx_EventTile_selected > div > a > .mx_MessageTimestamp { left: 3px; width: auto; } diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/_QuotePreview.scss b/src/skins/vector/css/matrix-react-sdk/views/rooms/_QuotePreview.scss new file mode 100644 index 000000000..f0fb62b8e --- /dev/null +++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/_QuotePreview.scss @@ -0,0 +1,35 @@ +.mx_QuotePreview { + position: absolute; + bottom: 0; + z-index: 1000; + width: 100%; + border: 1px solid $primary-hairline-color; + background: $primary-bg-color; + border-bottom: none; + border-radius: 4px 4px 0 0; + max-height: 50vh; + overflow: auto +} + +.mx_QuotePreview_section { + border-bottom: 1px solid $primary-hairline-color; +} + +.mx_QuotePreview_header { + margin: 12px; + color: $primary-fg-color; + font-weight: 400; + opacity: 0.4; +} + +.mx_QuotePreview_title { + float: left; +} + +.mx_QuotePreview_cancel { + float: right; +} + +.mx_QuotePreview_clear { + clear: both; +}