mirror of
https://github.com/SchildiChat/element-web.git
synced 2024-10-01 01:26:12 -04:00
Merge pull request #5804 from vector-im/t3chguy/nvl/rich_quoting
Implement Rich Quoting/Replies
This commit is contained in:
commit
56300f9578
@ -16,15 +16,16 @@ limitations under the License.
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const React = require('react');
|
import React from 'react';
|
||||||
|
|
||||||
const MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
|
import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
|
||||||
const dis = require('matrix-react-sdk/lib/dispatcher');
|
import dis from 'matrix-react-sdk/lib/dispatcher';
|
||||||
const sdk = require('matrix-react-sdk');
|
import sdk from 'matrix-react-sdk';
|
||||||
import { _t } from 'matrix-react-sdk/lib/languageHandler';
|
import { _t } from 'matrix-react-sdk/lib/languageHandler';
|
||||||
const Modal = require('matrix-react-sdk/lib/Modal');
|
import Modal from 'matrix-react-sdk/lib/Modal';
|
||||||
const Resend = require("matrix-react-sdk/lib/Resend");
|
import Resend from "matrix-react-sdk/lib/Resend";
|
||||||
import SettingsStore from "matrix-react-sdk/lib/settings/SettingsStore";
|
import SettingsStore from "matrix-react-sdk/lib/settings/SettingsStore";
|
||||||
|
import {makeEventPermalink} from 'matrix-react-sdk/lib/matrix-to';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'MessageContextMenu',
|
displayName: 'MessageContextMenu',
|
||||||
@ -107,15 +108,14 @@ module.exports = React.createClass({
|
|||||||
onFinished: (proceed) => {
|
onFinished: (proceed) => {
|
||||||
if (!proceed) return;
|
if (!proceed) return;
|
||||||
|
|
||||||
MatrixClientPeg.get().redactEvent(
|
const cli = MatrixClientPeg.get();
|
||||||
this.props.mxEvent.getRoomId(), this.props.mxEvent.getId()
|
cli.redactEvent(this.props.mxEvent.getRoomId(), this.props.mxEvent.getId()).catch(function(e) {
|
||||||
).catch(function(e) {
|
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
// display error message stating you couldn't delete this.
|
// display error message stating you couldn't delete this.
|
||||||
const code = e.errcode || e.statusCode;
|
const code = e.errcode || e.statusCode;
|
||||||
Modal.createTrackedDialog('You cannot delete this message', '', ErrorDialog, {
|
Modal.createTrackedDialog('You cannot delete this message', '', ErrorDialog, {
|
||||||
title: _t('Error'),
|
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();
|
}).done();
|
||||||
},
|
},
|
||||||
@ -138,12 +138,12 @@ module.exports = React.createClass({
|
|||||||
|
|
||||||
onPinClick: function() {
|
onPinClick: function() {
|
||||||
MatrixClientPeg.get().getStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', '')
|
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.
|
// Intercept the Event Not Found error and fall through the promise chain with no event.
|
||||||
if (e.errcode === "M_NOT_FOUND") return null;
|
if (e.errcode === "M_NOT_FOUND") return null;
|
||||||
throw e;
|
throw e;
|
||||||
})
|
})
|
||||||
.then(event => {
|
.then((event) => {
|
||||||
const eventIds = (event ? event.pinned : []) || [];
|
const eventIds = (event ? event.pinned : []) || [];
|
||||||
if (!eventIds.includes(this.props.mxEvent.getId())) {
|
if (!eventIds.includes(this.props.mxEvent.getId())) {
|
||||||
// Not pinned - add
|
// Not pinned - add
|
||||||
@ -153,7 +153,8 @@ module.exports = React.createClass({
|
|||||||
eventIds.splice(eventIds.indexOf(this.props.mxEvent.getId()), 1);
|
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();
|
this.closeMenu();
|
||||||
},
|
},
|
||||||
@ -177,6 +178,14 @@ module.exports = React.createClass({
|
|||||||
this.closeMenu();
|
this.closeMenu();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onReplyClick: function() {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'quote_event',
|
||||||
|
event: this.props.mxEvent,
|
||||||
|
});
|
||||||
|
this.closeMenu();
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const eventStatus = this.props.mxEvent.status;
|
const eventStatus = this.props.mxEvent.status;
|
||||||
let resendButton;
|
let resendButton;
|
||||||
@ -184,12 +193,11 @@ module.exports = React.createClass({
|
|||||||
let cancelButton;
|
let cancelButton;
|
||||||
let forwardButton;
|
let forwardButton;
|
||||||
let pinButton;
|
let pinButton;
|
||||||
let viewSourceButton;
|
|
||||||
let viewClearSourceButton;
|
let viewClearSourceButton;
|
||||||
let unhidePreviewButton;
|
let unhidePreviewButton;
|
||||||
let permalinkButton;
|
|
||||||
let externalURLButton;
|
let externalURLButton;
|
||||||
let quoteButton;
|
let quoteButton;
|
||||||
|
let replyButton;
|
||||||
|
|
||||||
if (eventStatus === 'not_sent') {
|
if (eventStatus === 'not_sent') {
|
||||||
resendButton = (
|
resendButton = (
|
||||||
@ -227,14 +235,14 @@ module.exports = React.createClass({
|
|||||||
if (this.state.canPin) {
|
if (this.state.canPin) {
|
||||||
pinButton = (
|
pinButton = (
|
||||||
<div className="mx_MessageContextMenu_field" onClick={this.onPinClick}>
|
<div className="mx_MessageContextMenu_field" onClick={this.onPinClick}>
|
||||||
{this._isPinned() ? _t('Unpin Message') : _t('Pin Message')}
|
{ this._isPinned() ? _t('Unpin Message') : _t('Pin Message') }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
viewSourceButton = (
|
const viewSourceButton = (
|
||||||
<div className="mx_MessageContextMenu_field" onClick={this.onViewSourceClick}>
|
<div className="mx_MessageContextMenu_field" onClick={this.onViewSourceClick}>
|
||||||
{ _t('View Source') }
|
{ _t('View Source') }
|
||||||
</div>
|
</div>
|
||||||
@ -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)
|
// 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 = (
|
||||||
<div className="mx_MessageContextMenu_field">
|
<div className="mx_MessageContextMenu_field">
|
||||||
<a href={ "https://matrix.to/#/" + this.props.mxEvent.getRoomId() +"/"+ this.props.mxEvent.getId() }
|
<a href={makeEventPermalink(this.props.mxEvent.getRoomId(), this.props.mxEvent.getId())}
|
||||||
target="_blank" rel="noopener" onClick={ this.closeMenu }>{ _t('Permalink') }</a>
|
target="_blank" rel="noopener" onClick={this.closeMenu}>{ _t('Permalink') }</a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -272,32 +280,41 @@ module.exports = React.createClass({
|
|||||||
{ _t('Quote') }
|
{ _t('Quote') }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (SettingsStore.isFeatureEnabled("feature_rich_quoting")) {
|
||||||
|
replyButton = (
|
||||||
|
<div className="mx_MessageContextMenu_field" onClick={this.onReplyClick}>
|
||||||
|
{ _t('Reply') }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bridges can provide a 'external_url' to link back to the source.
|
// Bridges can provide a 'external_url' to link back to the source.
|
||||||
if( typeof(this.props.mxEvent.event.content.external_url) === "string") {
|
if (typeof(this.props.mxEvent.event.content.external_url) === "string") {
|
||||||
externalURLButton = (
|
externalURLButton = (
|
||||||
<div className="mx_MessageContextMenu_field">
|
<div className="mx_MessageContextMenu_field">
|
||||||
<a href={ this.props.mxEvent.event.content.external_url }
|
<a href={this.props.mxEvent.event.content.external_url}
|
||||||
rel="noopener" target="_blank" onClick={ this.closeMenu }>{ _t('Source URL') }</a>
|
rel="noopener" target="_blank" onClick={this.closeMenu}>{ _t('Source URL') }</a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{resendButton}
|
{ resendButton }
|
||||||
{redactButton}
|
{ redactButton }
|
||||||
{cancelButton}
|
{ cancelButton }
|
||||||
{forwardButton}
|
{ forwardButton }
|
||||||
{pinButton}
|
{ pinButton }
|
||||||
{viewSourceButton}
|
{ viewSourceButton }
|
||||||
{viewClearSourceButton}
|
{ viewClearSourceButton }
|
||||||
{unhidePreviewButton}
|
{ unhidePreviewButton }
|
||||||
{permalinkButton}
|
{ permalinkButton }
|
||||||
{quoteButton}
|
{ quoteButton }
|
||||||
{externalURLButton}
|
{ replyButton }
|
||||||
|
{ externalURLButton }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -20,7 +20,7 @@ var React = require('react');
|
|||||||
|
|
||||||
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
|
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 filesize = require('filesize');
|
||||||
var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
|
var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
|
||||||
const Modal = require('matrix-react-sdk/lib/Modal');
|
const Modal = require('matrix-react-sdk/lib/Modal');
|
||||||
@ -159,7 +159,7 @@ module.exports = React.createClass({
|
|||||||
}
|
}
|
||||||
|
|
||||||
eventMeta = (<div className="mx_ImageView_metadata">
|
eventMeta = (<div className="mx_ImageView_metadata">
|
||||||
{ _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}) }
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { _t } from 'matrix-react-sdk/lib/languageHandler';
|
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() {
|
function getdaysArray() {
|
||||||
return [
|
return [
|
||||||
@ -49,7 +49,7 @@ module.exports = React.createClass({
|
|||||||
label = days[date.getDay()];
|
label = days[date.getDay()];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
label = DateUtils.formatFullDate(date, this.props.showTwelveHour);
|
label = formatFullDate(date, this.props.showTwelveHour);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from 'react';
|
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({
|
module.exports = React.createClass({
|
||||||
displayName: 'MessageTimestamp',
|
displayName: 'MessageTimestamp',
|
||||||
@ -29,8 +29,8 @@ module.exports = React.createClass({
|
|||||||
render: function() {
|
render: function() {
|
||||||
const date = new Date(this.props.ts);
|
const date = new Date(this.props.ts);
|
||||||
return (
|
return (
|
||||||
<span className="mx_MessageTimestamp" title={ DateUtils.formatFullDate(date, this.props.showTwelveHour) }>
|
<span className="mx_MessageTimestamp" title={ formatFullDate(date, this.props.showTwelveHour) }>
|
||||||
{ DateUtils.formatTime(date, this.props.showTwelveHour) }
|
{ formatTime(date, this.props.showTwelveHour) }
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -217,5 +217,6 @@
|
|||||||
"Contributing code to Matrix and Riot": "Contributing code to Matrix and Riot",
|
"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 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",
|
"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"
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
@import "./matrix-react-sdk/views/elements/_RichText.scss";
|
@import "./matrix-react-sdk/views/elements/_RichText.scss";
|
||||||
@import "./matrix-react-sdk/views/elements/_RoleButton.scss";
|
@import "./matrix-react-sdk/views/elements/_RoleButton.scss";
|
||||||
@import "./matrix-react-sdk/views/elements/_ToolTipButton.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/_GroupPublicityToggle.scss";
|
||||||
@import "./matrix-react-sdk/views/groups/_GroupRoomList.scss";
|
@import "./matrix-react-sdk/views/groups/_GroupRoomList.scss";
|
||||||
@import "./matrix-react-sdk/views/groups/_GroupUserSettings.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/_RoomTile.scss";
|
||||||
@import "./matrix-react-sdk/views/rooms/_SearchableEntityList.scss";
|
@import "./matrix-react-sdk/views/rooms/_SearchableEntityList.scss";
|
||||||
@import "./matrix-react-sdk/views/rooms/_TopUnreadMessagesBar.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/_DevicesPanel.scss";
|
||||||
@import "./matrix-react-sdk/views/settings/_IntegrationsManager.scss";
|
@import "./matrix-react-sdk/views/settings/_IntegrationsManager.scss";
|
||||||
@import "./matrix-react-sdk/views/voip/_CallView.scss";
|
@import "./matrix-react-sdk/views/voip/_CallView.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;
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
.mx_Autocomplete {
|
.mx_Autocomplete {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
z-index: 1000;
|
z-index: 1001;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: 1px solid $primary-hairline-color;
|
border: 1px solid $primary-hairline-color;
|
||||||
background: $primary-bg-color;
|
background: $primary-bg-color;
|
||||||
@ -90,3 +90,4 @@
|
|||||||
.mx_Autocomplete_Completion_description {
|
.mx_Autocomplete_Completion_description {
|
||||||
color: gray;
|
color: gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ limitations under the License.
|
|||||||
/* this is used for the tile for the event which is selected via the URL.
|
/* this is used for the tile for the event which is selected via the URL.
|
||||||
* TODO: ultimately we probably want some transition on here.
|
* 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;
|
border-left: $accent-color 5px solid;
|
||||||
padding-left: 60px;
|
padding-left: 60px;
|
||||||
background-color: $event-selected-color;
|
background-color: $event-selected-color;
|
||||||
@ -209,7 +209,7 @@ limitations under the License.
|
|||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_selected .mx_MessageTimestamp {
|
.mx_EventTile_selected > div > a > .mx_MessageTimestamp {
|
||||||
left: 3px;
|
left: 3px;
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user