Merge branch 'develop' of github.com:vector-im/riot-web into t3chguy/devtools-118247

This commit is contained in:
Michael Telatynski 2018-01-25 20:59:31 +00:00
commit 04bca93e0d
No known key found for this signature in database
GPG key ID: 3F879DA5AD802A5E
84 changed files with 670 additions and 465 deletions

View file

@ -17,10 +17,8 @@ limitations under the License.
'use strict';
import React from 'react';
import { DragDropContext } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import classNames from 'classnames';
import KeyCode from 'matrix-react-sdk/lib/KeyCode';
import { KeyCode } from 'matrix-react-sdk/lib/Keyboard';
import sdk from 'matrix-react-sdk';
import dis from 'matrix-react-sdk/lib/dispatcher';
import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
@ -199,4 +197,4 @@ var LeftPanel = React.createClass({
}
});
module.exports = DragDropContext(HTML5Backend)(LeftPanel);
module.exports = LeftPanel;

View file

@ -24,7 +24,7 @@ import sdk from 'matrix-react-sdk';
import dis from 'matrix-react-sdk/lib/dispatcher';
import { MatrixClient } from 'matrix-js-sdk';
import Analytics from 'matrix-react-sdk/lib/Analytics';
import rate_limited_func from 'matrix-react-sdk/lib/ratelimitedfunc';
import RateLimitedFunc from 'matrix-react-sdk/lib/ratelimitedfunc';
import AccessibleButton from 'matrix-react-sdk/lib/components/views/elements/AccessibleButton';
import { showGroupInviteDialog, showGroupAddRoomDialog } from 'matrix-react-sdk/lib/GroupAddressPicker';
import GroupStoreCache from 'matrix-react-sdk/lib/stores/GroupStoreCache';
@ -58,8 +58,8 @@ class HeaderButton extends React.Component {
<div className="mx_RightPanel_headerButton_badge">
{ this.props.badge ? this.props.badge : <span>&nbsp;</span> }
</div>
<TintableSvg src={this.props.iconSrc} width="25" height="25"/>
{ this.props.isHighlighted ? <div className="mx_RightPanel_headerButton_highlight"></div> : <div/> }
<TintableSvg src={this.props.iconSrc} width="25" height="25" />
{ this.props.isHighlighted ? <div className="mx_RightPanel_headerButton_highlight" /> : <div /> }
</AccessibleButton>;
}
@ -184,18 +184,17 @@ module.exports = React.createClass({
onRoomStateMember: function(ev, state, member) {
// redraw the badge on the membership list
if (this.state.phase == this.Phase.RoomMemberList && member.roomId === this.props.roomId) {
if (this.state.phase === this.Phase.RoomMemberList && member.roomId === this.props.roomId) {
this._delayedUpdate();
}
else if (this.state.phase === this.Phase.RoomMemberInfo && member.roomId === this.props.roomId &&
} else if (this.state.phase === this.Phase.RoomMemberInfo && member.roomId === this.props.roomId &&
member.userId === this.state.member.userId) {
// refresh the member info (e.g. new power level)
this._delayedUpdate();
}
},
_delayedUpdate: new rate_limited_func(function() {
this.forceUpdate();
_delayedUpdate: new RateLimitedFunc(function() {
this.forceUpdate(); // eslint-disable-line babel/no-invalid-this
}, 500),
onAction: function(payload) {
@ -266,22 +265,23 @@ module.exports = React.createClass({
let inviteGroup;
let membersBadge;
if ((this.state.phase == this.Phase.RoomMemberList || this.state.phase === this.Phase.RoomMemberInfo)
let membersTitle = _t('Members');
if ((this.state.phase === this.Phase.RoomMemberList || this.state.phase === this.Phase.RoomMemberInfo)
&& this.props.roomId
) {
const cli = this.context.matrixClient;
const room = cli.getRoom(this.props.roomId);
let userIsInRoom;
let isUserInRoom;
if (room) {
membersBadge = formatCount(room.getJoinedMembers().length);
userIsInRoom = room.hasMembershipState(
this.context.matrixClient.credentials.userId, 'join',
);
const numMembers = room.getJoinedMembers().length;
membersTitle = _t('%(count)s Members', { count: numMembers });
membersBadge = <div title={membersTitle}>{ formatCount(numMembers) }</div>;
isUserInRoom = room.hasMembershipState(this.context.matrixClient.credentials.userId, 'join');
}
if (userIsInRoom) {
if (isUserInRoom) {
inviteGroup =
<AccessibleButton className="mx_RightPanel_invite" onClick={ this.onInviteButtonClick } >
<AccessibleButton className="mx_RightPanel_invite" onClick={this.onInviteButtonClick}>
<div className="mx_RightPanel_icon" >
<TintableSvg src="img/icon-invite-people.svg" width="35" height="35" />
</div>
@ -292,13 +292,13 @@ module.exports = React.createClass({
const isPhaseGroup = [
this.Phase.GroupMemberInfo,
this.Phase.GroupMemberList
this.Phase.GroupMemberList,
].includes(this.state.phase);
let headerButtons = [];
if (this.props.roomId) {
headerButtons = [
<HeaderButton key="_membersButton" title={_t('Members')} iconSrc="img/icons-people.svg"
<HeaderButton key="_membersButton" title={membersTitle} iconSrc="img/icons-people.svg"
isHighlighted={[this.Phase.RoomMemberList, this.Phase.RoomMemberInfo].includes(this.state.phase)}
clickPhase={this.Phase.RoomMemberList}
badge={membersBadge}
@ -336,54 +336,54 @@ module.exports = React.createClass({
// button on these 2 screens or you won't be able to re-expand the panel.
headerButtons.push(
<div className="mx_RightPanel_headerButton mx_RightPanel_collapsebutton" key="_minimizeButton"
title={ _t("Hide panel") } aria-label={ _t("Hide panel") } onClick={ this.onCollapseClick }
title={_t("Hide panel")} aria-label={_t("Hide panel")} onClick={this.onCollapseClick}
>
<TintableSvg src="img/minimise.svg" width="10" height="16"/>
<TintableSvg src="img/minimise.svg" width="10" height="16" />
</div>,
);
}
let panel = <div />;
if (!this.props.collapsed) {
if (this.props.roomId && this.state.phase == this.Phase.RoomMemberList) {
if (this.props.roomId && this.state.phase === this.Phase.RoomMemberList) {
panel = <MemberList roomId={this.props.roomId} key={this.props.roomId} />;
} else if (this.props.groupId && this.state.phase == this.Phase.GroupMemberList) {
} else if (this.props.groupId && this.state.phase === this.Phase.GroupMemberList) {
panel = <GroupMemberList groupId={this.props.groupId} key={this.props.groupId} />;
} else if (this.state.phase === this.Phase.GroupRoomList) {
panel = <GroupRoomList groupId={this.props.groupId} key={this.props.groupId} />;
} else if (this.state.phase == this.Phase.RoomMemberInfo) {
} else if (this.state.phase === this.Phase.RoomMemberInfo) {
panel = <MemberInfo member={this.state.member} key={this.props.roomId || this.state.member.userId} />;
} else if (this.state.phase == this.Phase.GroupMemberInfo) {
} else if (this.state.phase === this.Phase.GroupMemberInfo) {
panel = <GroupMemberInfo
groupMember={this.state.member}
groupId={this.props.groupId}
key={this.state.member.user_id} />;
} else if (this.state.phase == this.Phase.GroupRoomInfo) {
} else if (this.state.phase === this.Phase.GroupRoomInfo) {
panel = <GroupRoomInfo
groupRoomId={this.state.groupRoomId}
groupId={this.props.groupId}
key={this.state.groupRoomId} />;
} else if (this.state.phase == this.Phase.NotificationPanel) {
} else if (this.state.phase === this.Phase.NotificationPanel) {
panel = <NotificationPanel />;
} else if (this.state.phase == this.Phase.FilePanel) {
} else if (this.state.phase === this.Phase.FilePanel) {
panel = <FilePanel roomId={this.props.roomId} />;
}
}
if (!panel) {
panel = <div className="mx_RightPanel_blank"></div>;
panel = <div className="mx_RightPanel_blank" />;
}
if (this.props.groupId && this.state.isUserPrivilegedInGroup) {
inviteGroup = isPhaseGroup ? (
<AccessibleButton className="mx_RightPanel_invite" onClick={ this.onInviteButtonClick } >
<AccessibleButton className="mx_RightPanel_invite" onClick={this.onInviteButtonClick}>
<div className="mx_RightPanel_icon" >
<TintableSvg src="img/icon-invite-people.svg" width="35" height="35" />
</div>
<div className="mx_RightPanel_message">{ _t('Invite to this community') }</div>
</AccessibleButton>
) : (
<AccessibleButton className="mx_RightPanel_invite" onClick={ this.onInviteButtonClick } >
<AccessibleButton className="mx_RightPanel_invite" onClick={this.onInviteButtonClick}>
<div className="mx_RightPanel_icon" >
<TintableSvg src="img/icons-room-add.svg" width="35" height="35" />
</div>
@ -392,19 +392,16 @@ module.exports = React.createClass({
);
}
let classes = classNames(
"mx_RightPanel", "mx_fadable",
{
"collapsed": this.props.collapsed,
"mx_fadable_faded": this.props.disabled,
}
);
const classes = classNames("mx_RightPanel", "mx_fadable", {
"collapsed": this.props.collapsed,
"mx_fadable_faded": this.props.disabled,
});
return (
<aside className={classes}>
<div className="mx_RightPanel_header">
<div className="mx_RightPanel_headerButtonGroup">
{headerButtons}
{ headerButtons }
</div>
</div>
{ panel }

View file

@ -20,8 +20,8 @@ limitations under the License.
var React = require('react');
var ReactDOM = require('react-dom');
var classNames = require('classnames');
var DropTarget = require('react-dnd').DropTarget;
var sdk = require('matrix-react-sdk');
import { Droppable } from 'react-beautiful-dnd';
import { _t } from 'matrix-react-sdk/lib/languageHandler';
var dis = require('matrix-react-sdk/lib/dispatcher');
var Unread = require('matrix-react-sdk/lib/Unread');
@ -30,7 +30,8 @@ var RoomNotifs = require('matrix-react-sdk/lib/RoomNotifs');
var FormattingUtils = require('matrix-react-sdk/lib/utils/FormattingUtils');
var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
import Modal from 'matrix-react-sdk/lib/Modal';
import KeyCode from 'matrix-react-sdk/lib/KeyCode';
import { KeyCode } from 'matrix-react-sdk/lib/Keyboard';
// turn this on for drop & drag console debugging galore
var debug = false;
@ -326,44 +327,45 @@ var RoomSubList = React.createClass({
});
},
calcManualOrderTagData: function(room) {
var index = this.state.sortedList.indexOf(room);
calcManualOrderTagData: function(index) {
// we sort rooms by the lexicographic ordering of the 'order' metadata on their tags.
// for convenience, we calculate this for now a floating point number between 0.0 and 1.0.
var orderA = 0.0; // by default we're next to the beginning of the list
let orderA = 0.0; // by default we're next to the beginning of the list
if (index > 0) {
var prevTag = this.state.sortedList[index - 1].tags[this.props.tagName];
const prevTag = this.state.sortedList[index - 1].tags[this.props.tagName];
if (!prevTag) {
console.error("Previous room in sublist is not tagged to be in this list. This should never happen.")
}
else if (prevTag.order === undefined) {
console.error("Previous room in sublist is not tagged to be in this list. This should never happen.");
} else if (prevTag.order === undefined) {
console.error("Previous room in sublist has no ordering metadata. This should never happen.");
}
else {
} else {
orderA = prevTag.order;
}
}
var orderB = 1.0; // by default we're next to the end of the list too
let orderB = 1.0; // by default we're next to the end of the list too
if (index < this.state.sortedList.length - 1) {
var nextTag = this.state.sortedList[index + 1].tags[this.props.tagName];
const nextTag = this.state.sortedList[index + 1].tags[this.props.tagName];
if (!nextTag) {
console.error("Next room in sublist is not tagged to be in this list. This should never happen.")
}
else if (nextTag.order === undefined) {
console.error("Next room in sublist is not tagged to be in this list. This should never happen.");
} else if (nextTag.order === undefined) {
console.error("Next room in sublist has no ordering metadata. This should never happen.");
}
else {
} else {
orderB = nextTag.order;
}
}
var order = (orderA + orderB) / 2.0;
const order = (orderA + orderB) / 2.0;
if (order === orderA || order === orderB) {
console.error("Cannot describe new list position. This should be incredibly unlikely.");
// TODO: renumber the list
this.state.sortedList.forEach((room, index) => {
MatrixClientPeg.get().setRoomTag(
room.roomId, this.props.tagName,
{order: index / this.state.sortedList.length},
);
});
return index / this.state.sortedList.length;
}
return order;
@ -372,12 +374,14 @@ var RoomSubList = React.createClass({
makeRoomTiles: function() {
var self = this;
var DNDRoomTile = sdk.getComponent("rooms.DNDRoomTile");
return this.state.sortedList.map(function(room) {
return this.state.sortedList.map(function(room, index) {
// XXX: is it evil to pass in self as a prop to RoomTile?
return (
<DNDRoomTile
index={index} // For DND
room={ room }
roomSubList={ self }
tagName={self.props.tagName}
key={ room.roomId }
collapsed={ self.props.collapsed || false}
unread={ Unread.doesRoomHaveUnreadMessages(room) }
@ -563,12 +567,18 @@ var RoomSubList = React.createClass({
</TruncatedList>;
}
return connectDropTarget(
<div>
{ this._getHeaderJsx() }
{ subList }
</div>
);
const subListContent = <div>
{ this._getHeaderJsx() }
{ subList }
</div>;
return this.props.editable ? <Droppable droppableId={"room-sub-list-droppable_" + this.props.tagName}>
{ (provided, snapshot) => (
<div ref={provided.innerRef}>
{ subListContent }
</div>
) }
</Droppable> : subListContent;
}
else {
var Loader = sdk.getComponent("elements.Spinner");
@ -582,11 +592,4 @@ var RoomSubList = React.createClass({
}
});
// Export the wrapped version, inlining the 'collect' functions
// to more closely resemble the ES7
module.exports =
DropTarget('RoomTile', roomListTarget, function(connect) {
return {
connectDropTarget: connect.dropTarget(),
}
})(RoomSubList);
module.exports = RoomSubList;

View file

@ -18,7 +18,7 @@ limitations under the License.
import React from 'react';
import { _t } from 'matrix-react-sdk/lib/languageHandler';
import KeyCode from 'matrix-react-sdk/lib/KeyCode';
import { KeyCode } from 'matrix-react-sdk/lib/Keyboard';
import sdk from 'matrix-react-sdk';
import dis from 'matrix-react-sdk/lib/dispatcher';
import rate_limited_func from 'matrix-react-sdk/lib/ratelimitedfunc';

View file

@ -16,14 +16,17 @@ limitations under the License.
'use strict';
var React = require('react');
import React from 'react';
import PropTypes from 'prop-types';
import SyntaxHighlight from '../views/elements/SyntaxHighlight';
module.exports = React.createClass({
displayName: 'ViewSource',
propTypes: {
content: React.PropTypes.object.isRequired,
onFinished: React.PropTypes.func.isRequired,
content: PropTypes.object.isRequired,
onFinished: PropTypes.func.isRequired,
},
componentDidMount: function() {
@ -45,9 +48,9 @@ module.exports = React.createClass({
render: function() {
return (
<div className="mx_ViewSource">
<pre>
{JSON.stringify(this.props.content, null, 2)}
</pre>
<SyntaxHighlight className="json">
{ JSON.stringify(this.props.content, null, 2) }
</SyntaxHighlight>
</div>
);
}

View file

@ -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 = (
@ -224,17 +232,25 @@ module.exports = React.createClass({
</div>
);
if (SettingsStore.isFeatureEnabled("feature_rich_quoting")) {
replyButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onReplyClick}>
{ _t('Reply') }
</div>
);
}
if (this.state.canPin) {
pinButton = (
<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>
);
}
}
}
viewSourceButton = (
const viewSourceButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onViewSourceClick}>
{ _t('View Source') }
</div>
@ -259,10 +275,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 = (
<div className="mx_MessageContextMenu_field">
<a href={ "https://matrix.to/#/" + this.props.mxEvent.getRoomId() +"/"+ this.props.mxEvent.getId() }
target="_blank" rel="noopener" onClick={ this.closeMenu }>{ _t('Permalink') }</a>
<a href={makeEventPermalink(this.props.mxEvent.getRoomId(), this.props.mxEvent.getId())}
target="_blank" rel="noopener" onClick={this.closeMenu}>{ _t('Permalink') }</a>
</div>
);
@ -275,29 +291,30 @@ module.exports = React.createClass({
}
// Bridges can provide a 'external_url' to link back to the source.
if( typeof(this.props.mxEvent.event.content.external_url) === "string") {
externalURLButton = (
<div className="mx_MessageContextMenu_field">
<a href={ this.props.mxEvent.event.content.external_url }
rel="noopener" target="_blank" onClick={ this.closeMenu }>{ _t('Source URL') }</a>
</div>
);
if (typeof(this.props.mxEvent.event.content.external_url) === "string") {
externalURLButton = (
<div className="mx_MessageContextMenu_field">
<a href={this.props.mxEvent.event.content.external_url}
rel="noopener" target="_blank" onClick={this.closeMenu}>{ _t('Source URL') }</a>
</div>
);
}
return (
<div>
{resendButton}
{redactButton}
{cancelButton}
{forwardButton}
{pinButton}
{viewSourceButton}
{viewClearSourceButton}
{unhidePreviewButton}
{permalinkButton}
{quoteButton}
{externalURLButton}
{ resendButton }
{ redactButton }
{ cancelButton }
{ forwardButton }
{ pinButton }
{ viewSourceButton }
{ viewClearSourceButton }
{ unhidePreviewButton }
{ permalinkButton }
{ quoteButton }
{ replyButton }
{ externalURLButton }
</div>
);
},

View file

@ -275,7 +275,7 @@ module.exports = React.createClass({
<div className={ alertMeClasses } onClick={this._onClickAlertMe} >
<img className="mx_RoomTileContextMenu_notif_activeIcon" src="img/notif-active.svg" width="12" height="12" />
<img className="mx_RoomTileContextMenu_notif_icon mx_filterFlipColor" src="img/icon-context-mute-off-copy.svg" width="16" height="12" />
{ _t('All messages (loud)') }
{ _t('All messages (noisy)') }
</div>
<div className={ allNotifsClasses } onClick={this._onClickAllNotifs} >
<img className="mx_RoomTileContextMenu_notif_activeIcon" src="img/notif-active.svg" width="12" height="12" />

View file

@ -17,6 +17,7 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import sdk from 'matrix-react-sdk';
import SyntaxHighlight from '../elements/SyntaxHighlight';
import { _t } from 'matrix-react-sdk/lib/languageHandler';
import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
@ -60,7 +61,7 @@ class GenericEditor extends DevtoolsComponent {
<label htmlFor={id}>{ label }</label>
</div>
<div className="mx_DevTools_inputCell">
<input id={id} onChange={this._onChange} value={this.state[id]} size="32" />
<input id={id} className="mx_TextInputDialog_input" onChange={this._onChange} value={this.state[id]} size="32" />
</div>
</div>;
}
@ -363,7 +364,9 @@ class RoomStateExplorer extends DevtoolsComponent {
return <div className="mx_ViewSource">
<div className="mx_Dialog_content">
<pre>{ JSON.stringify(this.state.event.event, null, 2) }</pre>
<SyntaxHighlight className="json">
{ JSON.stringify(this.state.event.event, null, 2) }
</SyntaxHighlight>
</div>
<div className="mx_Dialog_buttons">
<button onClick={this.onBack}>{ _t('Back') }</button>
@ -492,7 +495,9 @@ class AccountDataExplorer extends DevtoolsComponent {
return <div className="mx_ViewSource">
<div className="mx_Dialog_content">
<pre>{ JSON.stringify(this.state.event.event, null, 2) }</pre>
<SyntaxHighlight className="json">
{ JSON.stringify(this.state.event.event, null, 2) }
</SyntaxHighlight>
</div>
<div className="mx_Dialog_buttons">
<button onClick={this.onBack}>{ _t('Back') }</button>

View file

@ -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 = (<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>);
}

View file

@ -0,0 +1,53 @@
/*
Copyright 2017 Michael Telatynski <7t3chguy@gmail.com>
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 PropTypes from 'prop-types';
import {highlightBlock} from 'highlight.js';
export default class SyntaxHighlight extends React.Component {
static propTypes = {
className: PropTypes.string,
children: PropTypes.node,
};
constructor(props, context) {
super(props, context);
this._ref = this._ref.bind(this);
}
// componentDidUpdate used here for reusability
// componentWillReceiveProps fires too early to call highlightBlock on.
componentDidUpdate() {
if (this._el) highlightBlock(this._el);
}
// call componentDidUpdate because _ref is fired on initial render
// which does not fire componentDidUpdate
_ref(el) {
this._el = el;
this.componentDidUpdate();
}
render() {
const { className, children } = this.props;
return <pre className={`${className} mx_SyntaxHighlight`} ref={this._ref}>
<code>{ children }</code>
</pre>;
}
}

View file

@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2018 Michael Telatynski <7t3chguy@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -15,8 +16,9 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { _t } from 'matrix-react-sdk/lib/languageHandler';
import DateUtils from 'matrix-react-sdk/lib/DateUtils';
import {formatFullDateNoTime} from 'matrix-react-sdk/lib/DateUtils';
function getdaysArray() {
return [
@ -30,30 +32,30 @@ function getdaysArray() {
];
}
module.exports = React.createClass({
displayName: 'DateSeparator',
render: function() {
var date = new Date(this.props.ts);
var today = new Date();
var yesterday = new Date();
var days = getdaysArray();
yesterday.setDate(today.getDate() - 1);
var label;
if (date.toDateString() === today.toDateString()) {
label = _t('Today');
}
else if (date.toDateString() === yesterday.toDateString()) {
label = _t('Yesterday');
}
else if (today.getTime() - date.getTime() < 6 * 24 * 60 * 60 * 1000) {
label = days[date.getDay()];
}
else {
label = DateUtils.formatFullDate(date, this.props.showTwelveHour);
}
export default class DateSeparator extends React.Component {
static propTypes = {
ts: PropTypes.number.isRequired,
};
return (
<h2 className="mx_DateSeparator">{ label }</h2>
);
getLabel() {
const date = new Date(this.props.ts);
const today = new Date();
const yesterday = new Date();
const days = getdaysArray();
yesterday.setDate(today.getDate() - 1);
if (date.toDateString() === today.toDateString()) {
return _t('Today');
} else if (date.toDateString() === yesterday.toDateString()) {
return _t('Yesterday');
} else if (today.getTime() - date.getTime() < 6 * 24 * 60 * 60 * 1000) {
return days[date.getDay()];
} else {
return formatFullDateNoTime(date);
}
}
});
render() {
return <h2 className="mx_DateSeparator">{ this.getLabel() }</h2>;
}
}

View file

@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2018 Michael Telatynski <7t3chguy@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,24 +15,22 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
import React from 'react';
import DateUtils from 'matrix-react-sdk/lib/DateUtils';
import PropTypes from 'prop-types';
import {formatFullDate, formatTime} from 'matrix-react-sdk/lib/DateUtils';
module.exports = React.createClass({
displayName: 'MessageTimestamp',
export default class MessageTimestamp extends React.Component {
static propTypes = {
ts: PropTypes.number.isRequired,
showTwelveHour: PropTypes.bool,
};
propTypes: {
showTwelveHour: React.PropTypes.bool,
},
render: function() {
render() {
const date = new Date(this.props.ts);
return (
<span className="mx_MessageTimestamp" title={ DateUtils.formatFullDate(date, this.props.showTwelveHour) }>
{ DateUtils.formatTime(date, this.props.showTwelveHour) }
<span className="mx_MessageTimestamp" title={formatFullDate(date, this.props.showTwelveHour)}>
{ formatTime(date, this.props.showTwelveHour) }
</span>
);
},
});
}
}

View file

@ -14,227 +14,51 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
import React from 'react';
import {DragSource} from 'react-dnd';
import {DropTarget} from 'react-dnd';
import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
import sdk from 'matrix-react-sdk';
import { _t } from 'matrix-react-sdk/lib/languageHandler';
import { Draggable } from 'react-beautiful-dnd';
import RoomTile from 'matrix-react-sdk/lib/components/views/rooms/RoomTile';
import * as Rooms from 'matrix-react-sdk/lib/Rooms';
import Modal from 'matrix-react-sdk/lib/Modal';
/**
* Defines a new Component, DNDRoomTile that wraps RoomTile, making it draggable.
* Requires extra props:
* roomSubList: React.PropTypes.object.isRequired,
* refreshSubList: React.PropTypes.func.isRequired,
*/
import classNames from 'classnames';
/**
* Specifies the drag source contract.
* Only `beginDrag` function is required.
*/
var roomTileSource = {
canDrag: function(props, monitor) {
return props.roomSubList.props.editable;
},
beginDrag: function (props) {
// Return the data describing the dragged item
var item = {
room: props.room,
originalList: props.roomSubList,
originalIndex: props.roomSubList.findRoomTile(props.room).index,
targetList: props.roomSubList, // at first target is same as original
// lastTargetRoom: null,
// lastYOffset: null,
// lastYDelta: null,
};
if (props.roomSubList.debug) console.log("roomTile beginDrag for " + item.room.roomId);
// doing this 'correctly' with state causes react-dnd to break seemingly due to the state transitions
props.room._dragging = true;
return item;
},
endDrag: function (props, monitor, component) {
var item = monitor.getItem();
if (props.roomSubList.debug) console.log("roomTile endDrag for " + item.room.roomId + " with didDrop=" + monitor.didDrop());
props.room._dragging = false;
if (monitor.didDrop()) {
if (props.roomSubList.debug) console.log("force updating component " + item.targetList.props.label);
item.targetList.forceUpdate(); // as we're not using state
}
const prevTag = item.originalList.props.tagName;
const newTag = item.targetList.props.tagName;
if (monitor.didDrop() && item.targetList.props.editable) {
// Evil hack to get DMs behaving
if ((prevTag === undefined && newTag === 'im.vector.fake.direct') ||
(prevTag === 'im.vector.fake.direct' && newTag === undefined)
) {
Rooms.guessAndSetDMRoom(
item.room, newTag === 'im.vector.fake.direct',
).done(() => {
item.originalList.removeRoomTile(item.room);
}, (err) => {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to set direct chat tag " + err);
Modal.createTrackedDialog('Failed to set direct chat tag', '', ErrorDialog, {
title: _t('Failed to set direct chat tag'),
description: ((err && err.message) ? err.message : _t('Operation failed')),
});
});
return;
}
// More evilness: We will still be dealing with moving to favourites/low prio,
// but we avoid ever doing a request with 'im.vector.fake.direct`.
// if we moved lists, remove the old tag
if (prevTag && prevTag !== 'im.vector.fake.direct' &&
item.targetList !== item.originalList
) {
// commented out attempts to set a spinner on our target component as component is actually
// the original source component being dragged, not our target. To fix we just need to
// move all of this to endDrop in the target instead. FIXME later.
//component.state.set({ spinner: component.state.spinner ? component.state.spinner++ : 1 });
MatrixClientPeg.get().deleteRoomTag(item.room.roomId, prevTag).finally(function() {
//component.state.set({ spinner: component.state.spinner-- });
}).catch(function(err) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to remove tag " + prevTag + " from room: " + err);
Modal.createTrackedDialog('Failed to remove tag from room', '', ErrorDialog, {
title: _t('Failed to remove tag %(tagName)s from room', {tagName: prevTag}),
description: ((err && err.message) ? err.message : _t('Operation failed')),
});
});
}
var newOrder= {};
if (item.targetList.props.order === 'manual') {
newOrder['order'] = item.targetList.calcManualOrderTagData(item.room);
}
// if we moved lists or the ordering changed, add the new tag
if (newTag && newTag !== 'im.vector.fake.direct' &&
(item.targetList !== item.originalList || newOrder)
) {
MatrixClientPeg.get().setRoomTag(item.room.roomId, newTag, newOrder).catch(function(err) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to add tag " + newTag + " to room: " + err);
Modal.createTrackedDialog('Failed to add tag to room', '', ErrorDialog, {
title: _t('Failed to add tag %(tagName)s to room', {tagName: newTag}),
description: ((err && err.message) ? err.message : _t('Operation failed')),
});
});
}
}
else {
// cancel the drop and reset our original position
if (props.roomSubList.debug) console.log("cancelling drop & drag");
props.roomSubList.moveRoomTile(item.room, item.originalIndex);
if (item.targetList && item.targetList !== item.originalList) {
item.targetList.removeRoomTile(item.room);
}
}
export default class DNDRoomTile extends React.Component {
constructor() {
super();
this.getClassName = this.getClassName.bind(this);
}
};
var roomTileTarget = {
canDrop: function() {
return false;
},
hover: function(props, monitor) {
var item = monitor.getItem();
//var off = monitor.getClientOffset();
// console.log("hovering on room " + props.room.roomId + ", isOver=" + monitor.isOver());
//console.log("item.targetList=" + item.targetList + ", roomSubList=" + props.roomSubList);
var switchedTarget = false;
if (item.targetList !== props.roomSubList) {
// we've switched target, so remove the tile from the previous target.
// n.b. the previous target might actually be the source list.
if (props.roomSubList.debug) console.log("switched target sublist");
switchedTarget = true;
item.targetList.removeRoomTile(item.room);
item.targetList = props.roomSubList;
}
if (!item.targetList.props.editable) return;
if (item.targetList.props.order === 'manual') {
if (item.room.roomId !== props.room.roomId && props.room !== item.lastTargetRoom) {
// find the offset of the target tile in the list.
var roomTile = props.roomSubList.findRoomTile(props.room);
// shuffle the list to add our tile to that position.
props.roomSubList.moveRoomTile(item.room, roomTile.index);
}
// stop us from flickering between our droptarget and the previous room.
// whenever the cursor changes direction we have to reset the flicker-damping.
/*
var yDelta = off.y - item.lastYOffset;
if ((yDelta > 0 && item.lastYDelta < 0) ||
(yDelta < 0 && item.lastYDelta > 0))
{
// the cursor changed direction - forget our previous room
item.lastTargetRoom = null;
}
else {
// track the last room we were hovering over so we can stop
// bouncing back and forth if the droptarget is narrower than
// the other list items. The other way to do this would be
// to reduce the size of the hittarget on the list items, but
// can't see an easy way to do that.
item.lastTargetRoom = props.room;
}
if (yDelta) item.lastYDelta = yDelta;
item.lastYOffset = off.y;
*/
}
else if (switchedTarget) {
if (!props.roomSubList.findRoomTile(item.room).room) {
// add to the list in the right place
props.roomSubList.moveRoomTile(item.room, 0);
}
// we have to sort the list whatever to recalculate it
props.roomSubList.sortList();
}
},
};
// Export the wrapped version, inlining the 'collect' functions
// to more closely resemble the ES7
module.exports =
DropTarget('RoomTile', roomTileTarget, function(connect, monitor) {
return {
// Call this function inside render()
// to let React DnD handle the drag events:
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
getClassName(isDragging) {
return classNames({
"mx_DNDRoomTile": true,
"mx_DNDRoomTile_dragging": isDragging,
});
}
})(
DragSource('RoomTile', roomTileSource, function(connect, monitor) {
return {
// Call this function inside render()
// to let React DnD handle the drag events:
connectDragSource: connect.dragSource(),
// You can ask the monitor about the current drag state:
isDragging: monitor.isDragging()
};
})(RoomTile));
render() {
const props = this.props;
return <div>
<Draggable
key={props.room.roomId}
draggableId={props.tagName + '_' + props.room.roomId}
index={props.index}
>
{ (provided, snapshot) => {
return (
<div>
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<div className={this.getClassName(snapshot.isDragging)}>
<RoomTile {...props} />
</div>
</div>
{ provided.placeholder }
</div>
);
} }
</Draggable>
</div>;
}
}