Merge branch 'develop' of https://github.com/matrix-org/matrix-react-sdk into rxl881/snapshot
This commit is contained in:
commit
b2bf4d4709
219 changed files with 9309 additions and 3694 deletions
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import AppTile from '../elements/AppTile';
|
||||
import Modal from '../../../Modal';
|
||||
|
@ -27,6 +28,7 @@ import ScalarAuthClient from '../../../ScalarAuthClient';
|
|||
import ScalarMessaging from '../../../ScalarMessaging';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import WidgetUtils from '../../../WidgetUtils';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
|
||||
// The maximum number of widgets that can be added in a room
|
||||
const MAX_WIDGETS = 2;
|
||||
|
@ -35,10 +37,10 @@ module.exports = React.createClass({
|
|||
displayName: 'AppsDrawer',
|
||||
|
||||
propTypes: {
|
||||
userId: React.PropTypes.string.isRequired,
|
||||
room: React.PropTypes.object.isRequired,
|
||||
showApps: React.PropTypes.bool, // Should apps be rendered
|
||||
hide: React.PropTypes.bool, // If rendered, should apps drawer be visible
|
||||
userId: PropTypes.string.isRequired,
|
||||
room: PropTypes.object.isRequired,
|
||||
showApps: PropTypes.bool, // Should apps be rendered
|
||||
hide: PropTypes.bool, // If rendered, should apps drawer be visible
|
||||
},
|
||||
|
||||
defaultProps: {
|
||||
|
@ -139,6 +141,9 @@ module.exports = React.createClass({
|
|||
'$matrix_room_id': this.props.room.roomId,
|
||||
'$matrix_display_name': user ? user.displayName : this.props.userId,
|
||||
'$matrix_avatar_url': user ? MatrixClientPeg.get().mxcUrlToHttp(user.avatarUrl) : '',
|
||||
|
||||
// TODO: Namespace themes through some standard
|
||||
'$theme': SettingsStore.getValue("theme"),
|
||||
};
|
||||
|
||||
app.id = appId;
|
||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
import sdk from '../../../index';
|
||||
import dis from "../../../dispatcher";
|
||||
|
@ -29,27 +30,27 @@ module.exports = React.createClass({
|
|||
|
||||
propTypes: {
|
||||
// js-sdk room object
|
||||
room: React.PropTypes.object.isRequired,
|
||||
userId: React.PropTypes.string.isRequired,
|
||||
showApps: React.PropTypes.bool, // Render apps
|
||||
hideAppsDrawer: React.PropTypes.bool, // Do not display apps drawer and content (may still be rendered)
|
||||
room: PropTypes.object.isRequired,
|
||||
userId: PropTypes.string.isRequired,
|
||||
showApps: PropTypes.bool, // Render apps
|
||||
hideAppsDrawer: PropTypes.bool, // Do not display apps drawer and content (may still be rendered)
|
||||
|
||||
// Conference Handler implementation
|
||||
conferenceHandler: React.PropTypes.object,
|
||||
conferenceHandler: PropTypes.object,
|
||||
|
||||
// set to true to show the file drop target
|
||||
draggingFile: React.PropTypes.bool,
|
||||
draggingFile: PropTypes.bool,
|
||||
|
||||
// set to true to show the 'active conf call' banner
|
||||
displayConfCallNotification: React.PropTypes.bool,
|
||||
displayConfCallNotification: PropTypes.bool,
|
||||
|
||||
// maxHeight attribute for the aux panel and the video
|
||||
// therein
|
||||
maxHeight: React.PropTypes.number,
|
||||
maxHeight: PropTypes.number,
|
||||
|
||||
// a callback which is called when the content of the aux panel changes
|
||||
// content in a way that is likely to make it change size.
|
||||
onResize: React.PropTypes.func,
|
||||
onResize: PropTypes.func,
|
||||
},
|
||||
|
||||
defaultProps: {
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
'use strict';
|
||||
|
||||
const React = require('react');
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||
const sdk = require('../../../index');
|
||||
|
@ -51,18 +52,18 @@ const EntityTile = React.createClass({
|
|||
displayName: 'EntityTile',
|
||||
|
||||
propTypes: {
|
||||
name: React.PropTypes.string,
|
||||
title: React.PropTypes.string,
|
||||
avatarJsx: React.PropTypes.any, // <BaseAvatar />
|
||||
className: React.PropTypes.string,
|
||||
presenceState: React.PropTypes.string,
|
||||
presenceLastActiveAgo: React.PropTypes.number,
|
||||
presenceLastTs: React.PropTypes.number,
|
||||
presenceCurrentlyActive: React.PropTypes.bool,
|
||||
showInviteButton: React.PropTypes.bool,
|
||||
shouldComponentUpdate: React.PropTypes.func,
|
||||
onClick: React.PropTypes.func,
|
||||
suppressOnHover: React.PropTypes.bool,
|
||||
name: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
avatarJsx: PropTypes.any, // <BaseAvatar />
|
||||
className: PropTypes.string,
|
||||
presenceState: PropTypes.string,
|
||||
presenceLastActiveAgo: PropTypes.number,
|
||||
presenceLastTs: PropTypes.number,
|
||||
presenceCurrentlyActive: PropTypes.bool,
|
||||
showInviteButton: PropTypes.bool,
|
||||
shouldComponentUpdate: PropTypes.func,
|
||||
onClick: PropTypes.func,
|
||||
suppressOnHover: PropTypes.bool,
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -18,6 +19,7 @@ limitations under the License.
|
|||
|
||||
|
||||
const React = require('react');
|
||||
import PropTypes from 'prop-types';
|
||||
const classNames = require("classnames");
|
||||
import { _t, _td } from '../../../languageHandler';
|
||||
const Modal = require('../../../Modal');
|
||||
|
@ -28,6 +30,7 @@ import withMatrixClient from '../../../wrappers/withMatrixClient';
|
|||
|
||||
const ContextualMenu = require('../../structures/ContextualMenu');
|
||||
import dis from '../../../dispatcher';
|
||||
import {makeEventPermalink} from "../../../matrix-to";
|
||||
|
||||
const ObjectUtils = require('../../../ObjectUtils');
|
||||
|
||||
|
@ -76,65 +79,65 @@ module.exports = withMatrixClient(React.createClass({
|
|||
|
||||
propTypes: {
|
||||
/* MatrixClient instance for sender verification etc */
|
||||
matrixClient: React.PropTypes.object.isRequired,
|
||||
matrixClient: PropTypes.object.isRequired,
|
||||
|
||||
/* the MatrixEvent to show */
|
||||
mxEvent: React.PropTypes.object.isRequired,
|
||||
mxEvent: PropTypes.object.isRequired,
|
||||
|
||||
/* true if mxEvent is redacted. This is a prop because using mxEvent.isRedacted()
|
||||
* might not be enough when deciding shouldComponentUpdate - prevProps.mxEvent
|
||||
* references the same this.props.mxEvent.
|
||||
*/
|
||||
isRedacted: React.PropTypes.bool,
|
||||
isRedacted: PropTypes.bool,
|
||||
|
||||
/* true if this is a continuation of the previous event (which has the
|
||||
* effect of not showing another avatar/displayname
|
||||
*/
|
||||
continuation: React.PropTypes.bool,
|
||||
continuation: PropTypes.bool,
|
||||
|
||||
/* true if this is the last event in the timeline (which has the effect
|
||||
* of always showing the timestamp)
|
||||
*/
|
||||
last: React.PropTypes.bool,
|
||||
last: PropTypes.bool,
|
||||
|
||||
/* true if this is search context (which has the effect of greying out
|
||||
* the text
|
||||
*/
|
||||
contextual: React.PropTypes.bool,
|
||||
contextual: PropTypes.bool,
|
||||
|
||||
/* a list of words to highlight, ordered by longest first */
|
||||
highlights: React.PropTypes.array,
|
||||
highlights: PropTypes.array,
|
||||
|
||||
/* link URL for the highlights */
|
||||
highlightLink: React.PropTypes.string,
|
||||
highlightLink: PropTypes.string,
|
||||
|
||||
/* should show URL previews for this event */
|
||||
showUrlPreview: React.PropTypes.bool,
|
||||
showUrlPreview: PropTypes.bool,
|
||||
|
||||
/* is this the focused event */
|
||||
isSelectedEvent: React.PropTypes.bool,
|
||||
isSelectedEvent: PropTypes.bool,
|
||||
|
||||
/* callback called when dynamic content in events are loaded */
|
||||
onWidgetLoad: React.PropTypes.func,
|
||||
onWidgetLoad: PropTypes.func,
|
||||
|
||||
/* a list of read-receipts we should show. Each object has a 'roomMember' and 'ts'. */
|
||||
readReceipts: React.PropTypes.arrayOf(React.PropTypes.object),
|
||||
readReceipts: PropTypes.arrayOf(React.PropTypes.object),
|
||||
|
||||
/* opaque readreceipt info for each userId; used by ReadReceiptMarker
|
||||
* to manage its animations. Should be an empty object when the room
|
||||
* first loads
|
||||
*/
|
||||
readReceiptMap: React.PropTypes.object,
|
||||
readReceiptMap: PropTypes.object,
|
||||
|
||||
/* A function which is used to check if the parent panel is being
|
||||
* unmounted, to avoid unnecessary work. Should return true if we
|
||||
* are being unmounted.
|
||||
*/
|
||||
checkUnmounting: React.PropTypes.func,
|
||||
checkUnmounting: PropTypes.func,
|
||||
|
||||
/* the status of this event - ie, mxEvent.status. Denormalised to here so
|
||||
* that we can tell when it changes. */
|
||||
eventSendStatus: React.PropTypes.string,
|
||||
eventSendStatus: PropTypes.string,
|
||||
|
||||
/* the shape of the tile. by default, the layout is intended for the
|
||||
* normal room timeline. alternative values are: "file_list", "file_grid"
|
||||
|
@ -143,10 +146,10 @@ module.exports = withMatrixClient(React.createClass({
|
|||
* boiilerplatey. So just make the necessary render decisions conditional
|
||||
* for now.
|
||||
*/
|
||||
tileShape: React.PropTypes.string,
|
||||
tileShape: PropTypes.string,
|
||||
|
||||
// show twelve hour timestamps
|
||||
isTwelveHour: React.PropTypes.bool,
|
||||
isTwelveHour: PropTypes.bool,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -197,6 +200,8 @@ module.exports = withMatrixClient(React.createClass({
|
|||
*/
|
||||
_onDecrypted: function() {
|
||||
// we need to re-verify the sending device.
|
||||
// (we call onWidgetLoad in _verifyEvent to handle the case where decryption
|
||||
// has caused a change in size of the event tile)
|
||||
this._verifyEvent(this.props.mxEvent);
|
||||
this.forceUpdate();
|
||||
},
|
||||
|
@ -215,6 +220,9 @@ module.exports = withMatrixClient(React.createClass({
|
|||
const verified = await this.props.matrixClient.isEventSenderVerified(mxEvent);
|
||||
this.setState({
|
||||
verified: verified,
|
||||
}, () => {
|
||||
// Decryption may have caused a change in size
|
||||
this.props.onWidgetLoad();
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -473,9 +481,7 @@ module.exports = withMatrixClient(React.createClass({
|
|||
mx_EventTile_redacted: isRedacted,
|
||||
});
|
||||
|
||||
const permalink = "https://matrix.to/#/" +
|
||||
this.props.mxEvent.getRoomId() + "/" +
|
||||
this.props.mxEvent.getId();
|
||||
const permalink = makeEventPermalink(this.props.mxEvent.getRoomId(), this.props.mxEvent.getId());
|
||||
|
||||
const readAvatars = this.getReadAvatars();
|
||||
|
||||
|
@ -513,7 +519,7 @@ module.exports = withMatrixClient(React.createClass({
|
|||
|
||||
if (needsSenderProfile) {
|
||||
let text = null;
|
||||
if (!this.props.tileShape) {
|
||||
if (!this.props.tileShape || this.props.tileShape === 'quote') {
|
||||
if (msgtype === 'm.image') text = _td('%(senderName)s sent an image');
|
||||
else if (msgtype === 'm.video') text = _td('%(senderName)s sent a video');
|
||||
else if (msgtype === 'm.file') text = _td('%(senderName)s uploaded a file');
|
||||
|
@ -530,79 +536,104 @@ module.exports = withMatrixClient(React.createClass({
|
|||
const timestamp = this.props.mxEvent.getTs() ?
|
||||
<MessageTimestamp showTwelveHour={this.props.isTwelveHour} ts={this.props.mxEvent.getTs()} /> : null;
|
||||
|
||||
if (this.props.tileShape === "notif") {
|
||||
const room = this.props.matrixClient.getRoom(this.props.mxEvent.getRoomId());
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div className="mx_EventTile_roomName">
|
||||
<a href={permalink} onClick={this.onPermalinkClicked}>
|
||||
{ room ? room.name : '' }
|
||||
</a>
|
||||
</div>
|
||||
<div className="mx_EventTile_senderDetails">
|
||||
{ avatar }
|
||||
<a href={permalink} onClick={this.onPermalinkClicked}>
|
||||
{ sender }
|
||||
{ timestamp }
|
||||
</a>
|
||||
</div>
|
||||
<div className="mx_EventTile_line" >
|
||||
<EventTileType ref="tile"
|
||||
mxEvent={this.props.mxEvent}
|
||||
highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
showUrlPreview={this.props.showUrlPreview}
|
||||
onWidgetLoad={this.props.onWidgetLoad} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (this.props.tileShape === "file_grid") {
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div className="mx_EventTile_line" >
|
||||
<EventTileType ref="tile"
|
||||
mxEvent={this.props.mxEvent}
|
||||
highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
showUrlPreview={this.props.showUrlPreview}
|
||||
tileShape={this.props.tileShape}
|
||||
onWidgetLoad={this.props.onWidgetLoad} />
|
||||
</div>
|
||||
<a
|
||||
className="mx_EventTile_senderDetailsLink"
|
||||
href={permalink}
|
||||
onClick={this.onPermalinkClicked}
|
||||
>
|
||||
switch (this.props.tileShape) {
|
||||
case 'notif': {
|
||||
const room = this.props.matrixClient.getRoom(this.props.mxEvent.getRoomId());
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div className="mx_EventTile_roomName">
|
||||
<a href={permalink} onClick={this.onPermalinkClicked}>
|
||||
{ room ? room.name : '' }
|
||||
</a>
|
||||
</div>
|
||||
<div className="mx_EventTile_senderDetails">
|
||||
{ avatar }
|
||||
<a href={permalink} onClick={this.onPermalinkClicked}>
|
||||
{ sender }
|
||||
{ timestamp }
|
||||
</a>
|
||||
</div>
|
||||
<div className="mx_EventTile_line" >
|
||||
<EventTileType ref="tile"
|
||||
mxEvent={this.props.mxEvent}
|
||||
highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
showUrlPreview={this.props.showUrlPreview}
|
||||
onWidgetLoad={this.props.onWidgetLoad} />
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div className="mx_EventTile_msgOption">
|
||||
{ readAvatars }
|
||||
</div>
|
||||
{ avatar }
|
||||
{ sender }
|
||||
<div className="mx_EventTile_line">
|
||||
<a href={permalink} onClick={this.onPermalinkClicked}>
|
||||
{ timestamp }
|
||||
);
|
||||
}
|
||||
case 'file_grid': {
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div className="mx_EventTile_line" >
|
||||
<EventTileType ref="tile"
|
||||
mxEvent={this.props.mxEvent}
|
||||
highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
showUrlPreview={this.props.showUrlPreview}
|
||||
tileShape={this.props.tileShape}
|
||||
onWidgetLoad={this.props.onWidgetLoad} />
|
||||
</div>
|
||||
<a
|
||||
className="mx_EventTile_senderDetailsLink"
|
||||
href={permalink}
|
||||
onClick={this.onPermalinkClicked}
|
||||
>
|
||||
<div className="mx_EventTile_senderDetails">
|
||||
{ sender }
|
||||
{ timestamp }
|
||||
</div>
|
||||
</a>
|
||||
{ this._renderE2EPadlock() }
|
||||
<EventTileType ref="tile"
|
||||
mxEvent={this.props.mxEvent}
|
||||
highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
showUrlPreview={this.props.showUrlPreview}
|
||||
onWidgetLoad={this.props.onWidgetLoad} />
|
||||
{ editButton }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
case 'quote': {
|
||||
return (
|
||||
<div className={classes}>
|
||||
{ avatar }
|
||||
{ sender }
|
||||
<div className="mx_EventTile_line mx_EventTile_quote">
|
||||
<a href={permalink} onClick={this.onPermalinkClicked}>
|
||||
{ timestamp }
|
||||
</a>
|
||||
{ this._renderE2EPadlock() }
|
||||
<EventTileType ref="tile"
|
||||
tileShape="quote"
|
||||
mxEvent={this.props.mxEvent}
|
||||
highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
onWidgetLoad={this.props.onWidgetLoad}
|
||||
showUrlPreview={false} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
default: {
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div className="mx_EventTile_msgOption">
|
||||
{ readAvatars }
|
||||
</div>
|
||||
{ avatar }
|
||||
{ sender }
|
||||
<div className="mx_EventTile_line">
|
||||
<a href={permalink} onClick={this.onPermalinkClicked}>
|
||||
{ timestamp }
|
||||
</a>
|
||||
{ this._renderE2EPadlock() }
|
||||
<EventTileType ref="tile"
|
||||
mxEvent={this.props.mxEvent}
|
||||
highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
showUrlPreview={this.props.showUrlPreview}
|
||||
onWidgetLoad={this.props.onWidgetLoad} />
|
||||
{ editButton }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
@ -655,3 +686,5 @@ function E2ePadlockUnencrypted(props) {
|
|||
function E2ePadlock(props) {
|
||||
return <img className="mx_EventTile_e2eIcon" {...props} />;
|
||||
}
|
||||
|
||||
module.exports.getHandlerTile = getHandlerTile;
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import dis from '../../../dispatcher';
|
||||
import { KeyCode } from '../../../Keyboard';
|
||||
|
@ -25,7 +26,7 @@ module.exports = React.createClass({
|
|||
displayName: 'ForwardMessage',
|
||||
|
||||
propTypes: {
|
||||
onCancelClick: React.PropTypes.func.isRequired,
|
||||
onCancelClick: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
'use strict';
|
||||
|
||||
const React = require('react');
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const sdk = require('../../../index');
|
||||
const MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||
|
@ -32,10 +33,10 @@ module.exports = React.createClass({
|
|||
displayName: 'LinkPreviewWidget',
|
||||
|
||||
propTypes: {
|
||||
link: React.PropTypes.string.isRequired, // the URL being previewed
|
||||
mxEvent: React.PropTypes.object.isRequired, // the Event associated with the preview
|
||||
onCancelClick: React.PropTypes.func, // called when the preview's cancel ('hide') button is clicked
|
||||
onWidgetLoad: React.PropTypes.func, // called when the preview's contents has loaded
|
||||
link: PropTypes.string.isRequired, // the URL being previewed
|
||||
mxEvent: PropTypes.object.isRequired, // the Event associated with the preview
|
||||
onCancelClick: PropTypes.func, // called when the preview's cancel ('hide') button is clicked
|
||||
onWidgetLoad: PropTypes.func, // called when the preview's contents has loaded
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
|
@ -65,6 +66,6 @@ export default class MemberDeviceInfo extends React.Component {
|
|||
|
||||
MemberDeviceInfo.displayName = 'MemberDeviceInfo';
|
||||
MemberDeviceInfo.propTypes = {
|
||||
userId: React.PropTypes.string.isRequired,
|
||||
device: React.PropTypes.object.isRequired,
|
||||
userId: PropTypes.string.isRequired,
|
||||
device: PropTypes.object.isRequired,
|
||||
};
|
||||
|
|
|
@ -27,6 +27,7 @@ limitations under the License.
|
|||
* 'isTargetMod': boolean
|
||||
*/
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import dis from '../../../dispatcher';
|
||||
import Modal from '../../../Modal';
|
||||
|
@ -41,13 +42,12 @@ import AccessibleButton from '../elements/AccessibleButton';
|
|||
import GeminiScrollbar from 'react-gemini-scrollbar';
|
||||
import RoomViewStore from '../../../stores/RoomViewStore';
|
||||
|
||||
|
||||
module.exports = withMatrixClient(React.createClass({
|
||||
displayName: 'MemberInfo',
|
||||
|
||||
propTypes: {
|
||||
matrixClient: React.PropTypes.object.isRequired,
|
||||
member: React.PropTypes.object.isRequired,
|
||||
matrixClient: PropTypes.object.isRequired,
|
||||
member: PropTypes.object.isRequired,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -440,40 +440,57 @@ module.exports = withMatrixClient(React.createClass({
|
|||
const roomId = this.props.member.roomId;
|
||||
const target = this.props.member.userId;
|
||||
const room = this.props.matrixClient.getRoom(roomId);
|
||||
const self = this;
|
||||
if (!room) {
|
||||
return;
|
||||
}
|
||||
const powerLevelEvent = room.currentState.getStateEvents(
|
||||
"m.room.power_levels", "",
|
||||
);
|
||||
if (!powerLevelEvent) {
|
||||
return;
|
||||
}
|
||||
if (powerLevelEvent.getContent().users) {
|
||||
const myPower = powerLevelEvent.getContent().users[this.props.matrixClient.credentials.userId];
|
||||
if (parseInt(myPower) === parseInt(powerLevel)) {
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
Modal.createTrackedDialog('Promote to PL100 Warning', '', QuestionDialog, {
|
||||
title: _t("Warning!"),
|
||||
description:
|
||||
<div>
|
||||
{ _t("You will not be able to undo this change as you are promoting the user to have the same power level as yourself.") }<br />
|
||||
{ _t("Are you sure?") }
|
||||
</div>,
|
||||
button: _t("Continue"),
|
||||
onFinished: function(confirmed) {
|
||||
if (confirmed) {
|
||||
self._applyPowerChange(roomId, target, powerLevel, powerLevelEvent);
|
||||
}
|
||||
},
|
||||
});
|
||||
} else {
|
||||
this._applyPowerChange(roomId, target, powerLevel, powerLevelEvent);
|
||||
}
|
||||
} else {
|
||||
if (!room) return;
|
||||
|
||||
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
|
||||
if (!powerLevelEvent) return;
|
||||
|
||||
if (!powerLevelEvent.getContent().users) {
|
||||
this._applyPowerChange(roomId, target, powerLevel, powerLevelEvent);
|
||||
return;
|
||||
}
|
||||
|
||||
const myUserId = this.props.matrixClient.getUserId();
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
|
||||
// If we are changing our own PL it can only ever be decreasing, which we cannot reverse.
|
||||
if (myUserId === target) {
|
||||
Modal.createTrackedDialog('Demoting Self', '', QuestionDialog, {
|
||||
title: _t("Warning!"),
|
||||
description:
|
||||
<div>
|
||||
{ _t("You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.") }<br />
|
||||
{ _t("Are you sure?") }
|
||||
</div>,
|
||||
button: _t("Continue"),
|
||||
onFinished: (confirmed) => {
|
||||
if (confirmed) {
|
||||
this._applyPowerChange(roomId, target, powerLevel, powerLevelEvent);
|
||||
}
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const myPower = powerLevelEvent.getContent().users[myUserId];
|
||||
if (parseInt(myPower) === parseInt(powerLevel)) {
|
||||
Modal.createTrackedDialog('Promote to PL100 Warning', '', QuestionDialog, {
|
||||
title: _t("Warning!"),
|
||||
description:
|
||||
<div>
|
||||
{ _t("You will not be able to undo this change as you are promoting the user to have the same power level as yourself.") }<br />
|
||||
{ _t("Are you sure?") }
|
||||
</div>,
|
||||
button: _t("Continue"),
|
||||
onFinished: (confirmed) => {
|
||||
if (confirmed) {
|
||||
this._applyPowerChange(roomId, target, powerLevel, powerLevelEvent);
|
||||
}
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
this._applyPowerChange(roomId, target, powerLevel, powerLevelEvent);
|
||||
},
|
||||
|
||||
onNewDMClick: function() {
|
||||
|
@ -713,6 +730,10 @@ module.exports = withMatrixClient(React.createClass({
|
|||
|
||||
if (this.props.member.userId !== this.props.matrixClient.credentials.userId) {
|
||||
const dmRoomMap = new DMRoomMap(this.props.matrixClient);
|
||||
// dmRooms will not include dmRooms that we have been invited into but did not join.
|
||||
// Because DMRoomMap runs off account_data[m.direct] which is only set on join of dm room.
|
||||
// XXX: we potentially want DMs we have been invited to, to also show up here :L
|
||||
// especially as logic below concerns specially if we haven't joined but have been invited
|
||||
const dmRooms = dmRoomMap.getDMRoomsForUserId(this.props.member.userId);
|
||||
|
||||
const RoomTile = sdk.getComponent("rooms.RoomTile");
|
||||
|
@ -722,10 +743,15 @@ module.exports = withMatrixClient(React.createClass({
|
|||
const room = this.props.matrixClient.getRoom(roomId);
|
||||
if (room) {
|
||||
const me = room.getMember(this.props.matrixClient.credentials.userId);
|
||||
const highlight = (
|
||||
room.getUnreadNotificationCount('highlight') > 0 ||
|
||||
me.membership === "invite"
|
||||
);
|
||||
|
||||
// not a DM room if we have are not joined
|
||||
if (!me.membership || me.membership !== 'join') continue;
|
||||
// not a DM room if they are not joined
|
||||
const them = this.props.member;
|
||||
if (!them.membership || them.membership !== 'join') continue;
|
||||
|
||||
const highlight = room.getUnreadNotificationCount('highlight') > 0 || me.membership === 'invite';
|
||||
|
||||
tiles.push(
|
||||
<RoomTile key={room.roomId} room={room}
|
||||
collapsed={false}
|
||||
|
@ -831,8 +857,8 @@ module.exports = withMatrixClient(React.createClass({
|
|||
}
|
||||
|
||||
const room = this.props.matrixClient.getRoom(this.props.member.roomId);
|
||||
const poweLevelEvent = room ? room.currentState.getStateEvents("m.room.power_levels", "") : null;
|
||||
const powerLevelUsersDefault = poweLevelEvent.getContent().users_default;
|
||||
const powerLevelEvent = room ? room.currentState.getStateEvents("m.room.power_levels", "") : null;
|
||||
const powerLevelUsersDefault = powerLevelEvent ? powerLevelEvent.getContent().users_default : 0;
|
||||
|
||||
let roomMemberDetails = null;
|
||||
if (this.props.member.roomId) { // is in room
|
||||
|
|
|
@ -315,6 +315,30 @@ module.exports = React.createClass({
|
|||
});
|
||||
},
|
||||
|
||||
_getPending3PidInvites: function() {
|
||||
// include 3pid invites (m.room.third_party_invite) state events.
|
||||
// The HS may have already converted these into m.room.member invites so
|
||||
// we shouldn't add them if the 3pid invite state key (token) is in the
|
||||
// member invite (content.third_party_invite.signed.token)
|
||||
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
||||
|
||||
if (room) {
|
||||
return room.currentState.getStateEvents("m.room.third_party_invite").filter(function(e) {
|
||||
// any events without these keys are not valid 3pid invites, so we ignore them
|
||||
const requiredKeys = ['key_validity_url', 'public_key', 'display_name'];
|
||||
for (let i = 0; i < requiredKeys.length; ++i) {
|
||||
if (e.getContent()[requiredKeys[i]] === undefined) return false;
|
||||
}
|
||||
|
||||
// discard all invites which have a m.room.member event since we've
|
||||
// already added them.
|
||||
const memberEvent = room.currentState.getInviteForThreePidToken(e.getStateKey());
|
||||
if (memberEvent) return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_makeMemberTiles: function(members, membership) {
|
||||
const MemberTile = sdk.getComponent("rooms.MemberTile");
|
||||
|
||||
|
@ -329,33 +353,13 @@ module.exports = React.createClass({
|
|||
// Double XXX: Now it's really, really not the right home for this logic:
|
||||
// we shouldn't even be passing in the 'membership' param to this function.
|
||||
// Ew, ew, and ew.
|
||||
// Triple XXX: This violates the size constraint, the output is expected/desired
|
||||
// to be the same length as the members input array.
|
||||
if (membership === "invite") {
|
||||
// include 3pid invites (m.room.third_party_invite) state events.
|
||||
// The HS may have already converted these into m.room.member invites so
|
||||
// we shouldn't add them if the 3pid invite state key (token) is in the
|
||||
// member invite (content.third_party_invite.signed.token)
|
||||
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
||||
const EntityTile = sdk.getComponent("rooms.EntityTile");
|
||||
if (room) {
|
||||
room.currentState.getStateEvents("m.room.third_party_invite").forEach(
|
||||
function(e) {
|
||||
// any events without these keys are not valid 3pid invites, so we ignore them
|
||||
const required_keys = ['key_validity_url', 'public_key', 'display_name'];
|
||||
for (let i = 0; i < required_keys.length; ++i) {
|
||||
if (e.getContent()[required_keys[i]] === undefined) return;
|
||||
}
|
||||
|
||||
// discard all invites which have a m.room.member event since we've
|
||||
// already added them.
|
||||
const memberEvent = room.currentState.getInviteForThreePidToken(e.getStateKey());
|
||||
if (memberEvent) {
|
||||
return;
|
||||
}
|
||||
memberList.push(
|
||||
<EntityTile key={e.getStateKey()} name={e.getContent().display_name} suppressOnHover={true} />,
|
||||
);
|
||||
});
|
||||
}
|
||||
memberList.push(...this._getPending3PidInvites().map((e) => {
|
||||
return <EntityTile key={e.getStateKey()} name={e.getContent().display_name} suppressOnHover={true} />;
|
||||
}));
|
||||
}
|
||||
|
||||
return memberList;
|
||||
|
@ -374,7 +378,7 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
_getChildCountInvited: function() {
|
||||
return this.state.filteredInvitedMembers.length;
|
||||
return this.state.filteredInvitedMembers.length + (this._getPending3PidInvites() || []).length;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
'use strict';
|
||||
|
||||
const React = require('react');
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||
const sdk = require('../../../index');
|
||||
|
@ -28,7 +29,7 @@ module.exports = React.createClass({
|
|||
displayName: 'MemberTile',
|
||||
|
||||
propTypes: {
|
||||
member: React.PropTypes.any.isRequired, // RoomMember
|
||||
member: PropTypes.any.isRequired, // RoomMember
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
|
|
@ -15,13 +15,14 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import CallHandler from '../../../CallHandler';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import Modal from '../../../Modal';
|
||||
import sdk from '../../../index';
|
||||
import dis from '../../../dispatcher';
|
||||
import Autocomplete from './Autocomplete';
|
||||
import RoomViewStore from '../../../stores/RoomViewStore';
|
||||
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
||||
import Stickerpack from './Stickerpack';
|
||||
|
||||
|
@ -40,9 +41,7 @@ export default class MessageComposer extends React.Component {
|
|||
this.onToggleMarkdownClicked = this.onToggleMarkdownClicked.bind(this);
|
||||
this.onInputStateChanged = this.onInputStateChanged.bind(this);
|
||||
this.onEvent = this.onEvent.bind(this);
|
||||
this.render = this.render.bind(this);
|
||||
|
||||
//
|
||||
this._onRoomViewStoreUpdate = this._onRoomViewStoreUpdate.bind(this);
|
||||
|
||||
this.state = {
|
||||
autocompleteQuery: '',
|
||||
|
@ -54,6 +53,7 @@ export default class MessageComposer extends React.Component {
|
|||
wordCount: 0,
|
||||
},
|
||||
showFormatting: SettingsStore.getValue('MessageComposer.showFormatting'),
|
||||
isQuoting: Boolean(RoomViewStore.getQuotingEvent()),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -63,12 +63,16 @@ export default class MessageComposer extends React.Component {
|
|||
// marked as encrypted.
|
||||
// XXX: fragile as all hell - fixme somehow, perhaps with a dedicated Room.encryption event or something.
|
||||
MatrixClientPeg.get().on("event", this.onEvent);
|
||||
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (MatrixClientPeg.get()) {
|
||||
MatrixClientPeg.get().removeListener("event", this.onEvent);
|
||||
}
|
||||
if (this._roomStoreToken) {
|
||||
this._roomStoreToken.remove();
|
||||
}
|
||||
}
|
||||
|
||||
onEvent(event) {
|
||||
|
@ -77,6 +81,12 @@ export default class MessageComposer extends React.Component {
|
|||
this.forceUpdate();
|
||||
}
|
||||
|
||||
_onRoomViewStoreUpdate() {
|
||||
const isQuoting = Boolean(RoomViewStore.getQuotingEvent());
|
||||
if (this.state.isQuoting === isQuoting) return;
|
||||
this.setState({ isQuoting });
|
||||
}
|
||||
|
||||
onUploadClick(ev) {
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
dis.dispatch({action: 'view_set_mxid'});
|
||||
|
@ -304,8 +314,20 @@ export default class MessageComposer extends React.Component {
|
|||
key="controls_formatting" />
|
||||
);
|
||||
|
||||
const placeholderText = roomIsEncrypted ?
|
||||
_t('Send an encrypted message') + '…' : _t('Send a message (unencrypted)') + '…';
|
||||
let placeholderText;
|
||||
if (this.state.isQuoting) {
|
||||
if (roomIsEncrypted) {
|
||||
placeholderText = _t('Send an encrypted reply…');
|
||||
} else {
|
||||
placeholderText = _t('Send a reply (unencrypted)…');
|
||||
}
|
||||
} else {
|
||||
if (roomIsEncrypted) {
|
||||
placeholderText = _t('Send an encrypted message…');
|
||||
} else {
|
||||
placeholderText = _t('Send a message (unencrypted)…');
|
||||
}
|
||||
}
|
||||
|
||||
controls.push(
|
||||
<MessageComposerInput
|
||||
|
@ -377,14 +399,17 @@ export default class MessageComposer extends React.Component {
|
|||
MessageComposer.propTypes = {
|
||||
// a callback which is called when the height of the composer is
|
||||
// changed due to a change in content.
|
||||
onResize: React.PropTypes.func,
|
||||
onResize: PropTypes.func,
|
||||
|
||||
// js-sdk Room object
|
||||
room: React.PropTypes.object.isRequired,
|
||||
room: PropTypes.object.isRequired,
|
||||
|
||||
// string representing the current voip call state
|
||||
callState: React.PropTypes.string,
|
||||
callState: PropTypes.string,
|
||||
|
||||
// callback when a file to upload is chosen
|
||||
uploadFile: React.PropTypes.func.isRequired,
|
||||
uploadFile: PropTypes.func.isRequired,
|
||||
|
||||
// string representing the current room app drawer state
|
||||
showApps: PropTypes.bool,
|
||||
};
|
||||
|
|
|
@ -15,6 +15,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import type SyntheticKeyboardEvent from 'react/lib/SyntheticKeyboardEvent';
|
||||
|
||||
import {Editor, EditorState, RichUtils, CompositeDecorator, Modifier,
|
||||
|
@ -50,6 +51,10 @@ const REGEX_MATRIXTO_MARKDOWN_GLOBAL = new RegExp(MATRIXTO_MD_LINK_PATTERN, 'g')
|
|||
|
||||
import {asciiRegexp, shortnameToUnicode, emojioneList, asciiList, mapUnicodeToShort} from 'emojione';
|
||||
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
||||
import {makeEventPermalink, makeUserPermalink} from "../../../matrix-to";
|
||||
import QuotePreview from "./QuotePreview";
|
||||
import RoomViewStore from '../../../stores/RoomViewStore';
|
||||
|
||||
const EMOJI_SHORTNAMES = Object.keys(emojioneList);
|
||||
const EMOJI_UNICODE_TO_SHORTNAME = mapUnicodeToShort();
|
||||
const REGEX_EMOJI_WHITESPACE = new RegExp('(?:^|\\s)(' + asciiRegexp + ')\\s$');
|
||||
|
@ -86,15 +91,15 @@ export default class MessageComposerInput extends React.Component {
|
|||
static propTypes = {
|
||||
// a callback which is called when the height of the composer is
|
||||
// changed due to a change in content.
|
||||
onResize: React.PropTypes.func,
|
||||
onResize: PropTypes.func,
|
||||
|
||||
// js-sdk Room object
|
||||
room: React.PropTypes.object.isRequired,
|
||||
room: PropTypes.object.isRequired,
|
||||
|
||||
// called with current plaintext content (as a string) whenever it changes
|
||||
onContentChanged: React.PropTypes.func,
|
||||
onContentChanged: PropTypes.func,
|
||||
|
||||
onInputStateChanged: React.PropTypes.func,
|
||||
onInputStateChanged: PropTypes.func,
|
||||
};
|
||||
|
||||
static getKeyBinding(ev: SyntheticKeyboardEvent): string {
|
||||
|
@ -268,6 +273,7 @@ export default class MessageComposerInput extends React.Component {
|
|||
let contentState = this.state.editorState.getCurrentContent();
|
||||
|
||||
switch (payload.action) {
|
||||
case 'quote_event':
|
||||
case 'focus_composer':
|
||||
editor.focus();
|
||||
break;
|
||||
|
@ -281,12 +287,13 @@ export default class MessageComposerInput extends React.Component {
|
|||
this.setDisplayedCompletion({
|
||||
completion,
|
||||
selection,
|
||||
href: `https://matrix.to/#/${payload.user_id}`,
|
||||
href: makeUserPermalink(payload.user_id),
|
||||
suffix: selection.getStartOffset() === 0 ? ': ' : ' ',
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'quote': {
|
||||
|
||||
case 'quote': { // old quoting, whilst rich quoting is in labs
|
||||
/// XXX: Not doing rich-text quoting from formatted-body because draft-js
|
||||
/// has regressed such that when links are quoted, errors are thrown. See
|
||||
/// https://github.com/vector-im/riot-web/issues/4756.
|
||||
|
@ -512,7 +519,8 @@ export default class MessageComposerInput extends React.Component {
|
|||
// composer. For some reason the editor won't scroll automatically if we paste
|
||||
// blocks of text in or insert newlines.
|
||||
if (textContent.slice(selection.start).indexOf("\n") === -1) {
|
||||
this.refs.editor.refs.editor.scrollTop = this.refs.editor.refs.editor.scrollHeight;
|
||||
let editorRoot = this.refs.editor.refs.editor.parentNode.parentNode;
|
||||
editorRoot.scrollTop = editorRoot.scrollHeight;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -652,7 +660,7 @@ export default class MessageComposerInput extends React.Component {
|
|||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
onTextPasted(text: string, html?: string) {
|
||||
const currentSelection = this.state.editorState.getSelection();
|
||||
|
@ -715,6 +723,7 @@ export default class MessageComposerInput extends React.Component {
|
|||
const cmd = SlashCommands.processInput(this.props.room.roomId, commandText);
|
||||
if (cmd) {
|
||||
if (!cmd.error) {
|
||||
this.historyManager.save(contentState, this.state.isRichtextEnabled ? 'html' : 'markdown');
|
||||
this.setState({
|
||||
editorState: this.createEditorState(),
|
||||
});
|
||||
|
@ -742,9 +751,17 @@ export default class MessageComposerInput extends React.Component {
|
|||
return true;
|
||||
}
|
||||
|
||||
const quotingEv = RoomViewStore.getQuotingEvent();
|
||||
|
||||
if (this.state.isRichtextEnabled) {
|
||||
// We should only send HTML if any block is styled or contains inline style
|
||||
let shouldSendHTML = false;
|
||||
|
||||
// If we are quoting we need HTML Content
|
||||
if (quotingEv) {
|
||||
shouldSendHTML = true;
|
||||
}
|
||||
|
||||
const blocks = contentState.getBlocksAsArray();
|
||||
if (blocks.some((block) => block.getType() !== 'unstyled')) {
|
||||
shouldSendHTML = true;
|
||||
|
@ -802,7 +819,8 @@ export default class MessageComposerInput extends React.Component {
|
|||
}).join('\n');
|
||||
|
||||
const md = new Markdown(pt);
|
||||
if (md.isPlainText()) {
|
||||
// if contains no HTML and we're not quoting (needing HTML)
|
||||
if (md.isPlainText() && !quotingEv) {
|
||||
contentText = md.toPlaintext();
|
||||
} else {
|
||||
contentHTML = md.toHTML();
|
||||
|
@ -825,6 +843,24 @@ export default class MessageComposerInput extends React.Component {
|
|||
sendTextFn = this.client.sendEmoteMessage;
|
||||
}
|
||||
|
||||
if (quotingEv) {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const room = cli.getRoom(quotingEv.getRoomId());
|
||||
const sender = room.currentState.getMember(quotingEv.getSender());
|
||||
|
||||
const {body/*, formatted_body*/} = quotingEv.getContent();
|
||||
|
||||
const perma = makeEventPermalink(quotingEv.getRoomId(), quotingEv.getId());
|
||||
contentText = `${sender.name}:\n> ${body}\n\n${contentText}`;
|
||||
contentHTML = `<a href="${perma}">Quote<br></a>${contentHTML}`;
|
||||
|
||||
// we have finished quoting, clear the quotingEvent
|
||||
dis.dispatch({
|
||||
action: 'quote_event',
|
||||
event: null,
|
||||
});
|
||||
}
|
||||
|
||||
let sendMessagePromise;
|
||||
if (contentHTML) {
|
||||
sendMessagePromise = sendHtmlFn.call(
|
||||
|
@ -1137,6 +1173,7 @@ export default class MessageComposerInput extends React.Component {
|
|||
return (
|
||||
<div className="mx_MessageComposer_input_wrapper">
|
||||
<div className="mx_MessageComposer_autocomplete_wrapper">
|
||||
{ SettingsStore.isFeatureEnabled("feature_rich_quoting") && <QuotePreview /> }
|
||||
<Autocomplete
|
||||
ref={(e) => this.autocomplete = e}
|
||||
room={this.props.room}
|
||||
|
@ -1177,15 +1214,15 @@ export default class MessageComposerInput extends React.Component {
|
|||
MessageComposerInput.propTypes = {
|
||||
// a callback which is called when the height of the composer is
|
||||
// changed due to a change in content.
|
||||
onResize: React.PropTypes.func,
|
||||
onResize: PropTypes.func,
|
||||
|
||||
// js-sdk Room object
|
||||
room: React.PropTypes.object.isRequired,
|
||||
room: PropTypes.object.isRequired,
|
||||
|
||||
// called with current plaintext content (as a string) whenever it changes
|
||||
onContentChanged: React.PropTypes.func,
|
||||
onContentChanged: PropTypes.func,
|
||||
|
||||
onFilesPasted: React.PropTypes.func,
|
||||
onFilesPasted: PropTypes.func,
|
||||
|
||||
onInputStateChanged: React.PropTypes.func,
|
||||
onInputStateChanged: PropTypes.func,
|
||||
};
|
||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import PropTypes from 'prop-types';
|
||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
import dis from "../../../dispatcher";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
|
@ -25,9 +26,9 @@ import { _t } from '../../../languageHandler';
|
|||
module.exports = React.createClass({
|
||||
displayName: 'PinnedEventTile',
|
||||
propTypes: {
|
||||
mxRoom: React.PropTypes.object.isRequired,
|
||||
mxEvent: React.PropTypes.object.isRequired,
|
||||
onUnpinned: React.PropTypes.func,
|
||||
mxRoom: PropTypes.object.isRequired,
|
||||
mxEvent: PropTypes.object.isRequired,
|
||||
onUnpinned: PropTypes.func,
|
||||
},
|
||||
onTileClicked: function() {
|
||||
dis.dispatch({
|
||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import PropTypes from 'prop-types';
|
||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import PinnedEventTile from "./PinnedEventTile";
|
||||
|
@ -25,9 +26,9 @@ module.exports = React.createClass({
|
|||
displayName: 'PinnedEventsPanel',
|
||||
propTypes: {
|
||||
// The Room from the js-sdk we're going to show pinned events for
|
||||
room: React.PropTypes.object.isRequired,
|
||||
room: PropTypes.object.isRequired,
|
||||
|
||||
onCancelClick: React.PropTypes.func,
|
||||
onCancelClick: PropTypes.func,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
|
@ -27,14 +28,14 @@ module.exports = React.createClass({
|
|||
propTypes: {
|
||||
// number of milliseconds ago this user was last active.
|
||||
// zero = unknown
|
||||
activeAgo: React.PropTypes.number,
|
||||
activeAgo: PropTypes.number,
|
||||
|
||||
// if true, activeAgo is an approximation and "Now" should
|
||||
// be shown instead
|
||||
currentlyActive: React.PropTypes.bool,
|
||||
currentlyActive: PropTypes.bool,
|
||||
|
||||
// offline, online, etc
|
||||
presenceState: React.PropTypes.string,
|
||||
presenceState: PropTypes.string,
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
|
|
78
src/components/views/rooms/QuotePreview.js
Normal file
78
src/components/views/rooms/QuotePreview.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
Copyright 2017 New Vector 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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import dis from '../../../dispatcher';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import RoomViewStore from '../../../stores/RoomViewStore';
|
||||
|
||||
function cancelQuoting() {
|
||||
dis.dispatch({
|
||||
action: 'quote_event',
|
||||
event: null,
|
||||
});
|
||||
}
|
||||
|
||||
export default class QuotePreview extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
event: null,
|
||||
};
|
||||
|
||||
this._onRoomViewStoreUpdate = this._onRoomViewStoreUpdate.bind(this);
|
||||
|
||||
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
|
||||
this._onRoomViewStoreUpdate();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// Remove RoomStore listener
|
||||
if (this._roomStoreToken) {
|
||||
this._roomStoreToken.remove();
|
||||
}
|
||||
}
|
||||
|
||||
_onRoomViewStoreUpdate() {
|
||||
const event = RoomViewStore.getQuotingEvent();
|
||||
if (this.state.event !== event) {
|
||||
this.setState({ event });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.state.event) return null;
|
||||
|
||||
const EventTile = sdk.getComponent('rooms.EventTile');
|
||||
const EmojiText = sdk.getComponent('views.elements.EmojiText');
|
||||
|
||||
return <div className="mx_QuotePreview">
|
||||
<div className="mx_QuotePreview_section">
|
||||
<EmojiText element="div" className="mx_QuotePreview_header mx_QuotePreview_title">
|
||||
{ '💬 ' + _t('Replying') }
|
||||
</EmojiText>
|
||||
<div className="mx_QuotePreview_header mx_QuotePreview_cancel">
|
||||
<img className="mx_filterFlipColor" src="img/cancel.svg" width="18" height="18"
|
||||
onClick={cancelQuoting} />
|
||||
</div>
|
||||
<div className="mx_QuotePreview_clear" />
|
||||
<EventTile mxEvent={this.state.event} last={true} tileShape="quote" />
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
|||
|
||||
const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const sdk = require('../../../index');
|
||||
|
||||
|
@ -25,7 +26,7 @@ const Velociraptor = require('../../../Velociraptor');
|
|||
require('../../../VelocityBounce');
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
import DateUtils from '../../../DateUtils';
|
||||
import {formatDate} from '../../../DateUtils';
|
||||
|
||||
let bounce = false;
|
||||
try {
|
||||
|
@ -40,35 +41,35 @@ module.exports = React.createClass({
|
|||
|
||||
propTypes: {
|
||||
// the RoomMember to show the RR for
|
||||
member: React.PropTypes.object.isRequired,
|
||||
member: PropTypes.object.isRequired,
|
||||
|
||||
// number of pixels to offset the avatar from the right of its parent;
|
||||
// typically a negative value.
|
||||
leftOffset: React.PropTypes.number,
|
||||
leftOffset: PropTypes.number,
|
||||
|
||||
// true to hide the avatar (it will still be animated)
|
||||
hidden: React.PropTypes.bool,
|
||||
hidden: PropTypes.bool,
|
||||
|
||||
// don't animate this RR into position
|
||||
suppressAnimation: React.PropTypes.bool,
|
||||
suppressAnimation: PropTypes.bool,
|
||||
|
||||
// an opaque object for storing information about this user's RR in
|
||||
// this room
|
||||
readReceiptInfo: React.PropTypes.object,
|
||||
readReceiptInfo: PropTypes.object,
|
||||
|
||||
// A function which is used to check if the parent panel is being
|
||||
// unmounted, to avoid unnecessary work. Should return true if we
|
||||
// are being unmounted.
|
||||
checkUnmounting: React.PropTypes.func,
|
||||
checkUnmounting: PropTypes.func,
|
||||
|
||||
// callback for clicks on this RR
|
||||
onClick: React.PropTypes.func,
|
||||
onClick: PropTypes.func,
|
||||
|
||||
// Timestamp when the receipt was read
|
||||
timestamp: React.PropTypes.number,
|
||||
timestamp: PropTypes.number,
|
||||
|
||||
// True to show twelve hour format, false otherwise
|
||||
showTwelveHour: React.PropTypes.bool,
|
||||
showTwelveHour: PropTypes.bool,
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
|
@ -186,7 +187,7 @@ module.exports = React.createClass({
|
|||
if (this.props.timestamp) {
|
||||
title = _t(
|
||||
"Seen by %(userName)s at %(dateTime)s",
|
||||
{userName: this.props.member.userId, dateTime: DateUtils.formatDate(new Date(this.props.timestamp), this.props.showTwelveHour)},
|
||||
{userName: this.props.member.userId, dateTime: formatDate(new Date(this.props.timestamp), this.props.showTwelveHour)},
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,121 +18,40 @@ import sdk from '../../../index';
|
|||
import dis from '../../../dispatcher';
|
||||
import React from 'react';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import linkifyString from 'linkifyjs/string';
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
import { ContentRepo } from 'matrix-js-sdk';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
function getDisplayAliasForRoom(room) {
|
||||
return room.canonicalAlias || (room.aliases ? room.aliases[0] : "");
|
||||
}
|
||||
|
||||
const RoomDetailRow = React.createClass({
|
||||
propTypes: {
|
||||
room: PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
topic: PropTypes.string,
|
||||
roomId: PropTypes.string,
|
||||
avatarUrl: PropTypes.string,
|
||||
numJoinedMembers: PropTypes.number,
|
||||
canonicalAlias: PropTypes.string,
|
||||
aliases: PropTypes.arrayOf(PropTypes.string),
|
||||
|
||||
worldReadable: PropTypes.bool,
|
||||
guestCanJoin: PropTypes.bool,
|
||||
}),
|
||||
},
|
||||
|
||||
onClick: function(ev) {
|
||||
ev.preventDefault();
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: this.props.room.roomId,
|
||||
room_alias: this.props.room.canonicalAlias || (this.props.room.aliases || [])[0],
|
||||
});
|
||||
},
|
||||
|
||||
onTopicClick: function(ev) {
|
||||
// When clicking a link in the topic, prevent the event being propagated
|
||||
// to `onClick`.
|
||||
ev.stopPropagation();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||
|
||||
const room = this.props.room;
|
||||
const name = room.name || getDisplayAliasForRoom(room) || _t('Unnamed room');
|
||||
const topic = linkifyString(sanitizeHtml(room.topic || ''));
|
||||
|
||||
const guestRead = room.worldReadable ? (
|
||||
<div className="mx_RoomDirectory_perm">{ _t('World readable') }</div>
|
||||
) : <div />;
|
||||
const guestJoin = room.guestCanJoin ? (
|
||||
<div className="mx_RoomDirectory_perm">{ _t('Guests can join') }</div>
|
||||
) : <div />;
|
||||
|
||||
const perms = (guestRead || guestJoin) ? (<div className="mx_RoomDirectory_perms">
|
||||
{ guestRead }
|
||||
{ guestJoin }
|
||||
</div>) : <div />;
|
||||
|
||||
return <tr key={room.roomId} onClick={this.onClick}>
|
||||
<td className="mx_RoomDirectory_roomAvatar">
|
||||
<BaseAvatar width={24} height={24} resizeMethod='crop'
|
||||
name={name} idName={name}
|
||||
url={ContentRepo.getHttpUriForMxc(
|
||||
MatrixClientPeg.get().getHomeserverUrl(),
|
||||
room.avatarUrl, 24, 24, "crop")} />
|
||||
</td>
|
||||
<td className="mx_RoomDirectory_roomDescription">
|
||||
<div className="mx_RoomDirectory_name">{ name }</div>
|
||||
{ perms }
|
||||
<div className="mx_RoomDirectory_topic"
|
||||
onClick={this.onTopicClick}
|
||||
dangerouslySetInnerHTML={{ __html: topic }} />
|
||||
<div className="mx_RoomDirectory_alias">{ getDisplayAliasForRoom(room) }</div>
|
||||
</td>
|
||||
<td className="mx_RoomDirectory_roomMemberCount">
|
||||
{ room.numJoinedMembers }
|
||||
</td>
|
||||
</tr>;
|
||||
},
|
||||
});
|
||||
import {roomShape} from './RoomDetailRow';
|
||||
|
||||
export default React.createClass({
|
||||
displayName: 'RoomDetailList',
|
||||
|
||||
propTypes: {
|
||||
rooms: PropTypes.arrayOf(PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
topic: PropTypes.string,
|
||||
roomId: PropTypes.string,
|
||||
avatarUrl: PropTypes.string,
|
||||
numJoinedMembers: PropTypes.number,
|
||||
canonicalAlias: PropTypes.string,
|
||||
aliases: PropTypes.arrayOf(PropTypes.string),
|
||||
|
||||
worldReadable: PropTypes.bool,
|
||||
guestCanJoin: PropTypes.bool,
|
||||
})),
|
||||
|
||||
rooms: PropTypes.arrayOf(roomShape),
|
||||
className: PropTypes.string,
|
||||
},
|
||||
|
||||
getRows: function() {
|
||||
if (!this.props.rooms) return [];
|
||||
|
||||
const RoomDetailRow = sdk.getComponent('rooms.RoomDetailRow');
|
||||
return this.props.rooms.map((room, index) => {
|
||||
return <RoomDetailRow key={index} room={room} />;
|
||||
return <RoomDetailRow key={index} room={room} onClick={this.onDetailsClick} />;
|
||||
});
|
||||
},
|
||||
|
||||
onDetailsClick: function(ev, room) {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: room.roomId,
|
||||
room_alias: room.canonicalAlias || (room.aliases || [])[0],
|
||||
});
|
||||
},
|
||||
|
||||
render() {
|
||||
const rows = this.getRows();
|
||||
let rooms;
|
||||
if (rows.length == 0) {
|
||||
if (rows.length === 0) {
|
||||
rooms = <i>{ _t('No rooms to show') }</i>;
|
||||
} else {
|
||||
rooms = <table ref="directory_table" className="mx_RoomDirectory_table">
|
||||
|
|
120
src/components/views/rooms/RoomDetailRow.js
Normal file
120
src/components/views/rooms/RoomDetailRow.js
Normal file
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
Copyright 2017 New Vector 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.
|
||||
*/
|
||||
|
||||
import sdk from '../../../index';
|
||||
import React from 'react';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import * as linkify from 'linkifyjs';
|
||||
import linkifyElement from 'linkifyjs/element';
|
||||
import linkifyMatrix from '../../../linkify-matrix';
|
||||
import { ContentRepo } from 'matrix-js-sdk';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
linkifyMatrix(linkify);
|
||||
|
||||
export function getDisplayAliasForRoom(room) {
|
||||
return room.canonicalAlias || (room.aliases ? room.aliases[0] : "");
|
||||
}
|
||||
|
||||
export const roomShape = PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
topic: PropTypes.string,
|
||||
roomId: PropTypes.string,
|
||||
avatarUrl: PropTypes.string,
|
||||
numJoinedMembers: PropTypes.number,
|
||||
canonicalAlias: PropTypes.string,
|
||||
aliases: PropTypes.arrayOf(PropTypes.string),
|
||||
|
||||
worldReadable: PropTypes.bool,
|
||||
guestCanJoin: PropTypes.bool,
|
||||
});
|
||||
|
||||
export default React.createClass({
|
||||
propTypes: {
|
||||
room: roomShape,
|
||||
// passes ev, room as args
|
||||
onClick: PropTypes.func,
|
||||
onMouseDown: PropTypes.func,
|
||||
},
|
||||
|
||||
_linkifyTopic: function() {
|
||||
if (this.refs.topic) {
|
||||
linkifyElement(this.refs.topic, linkifyMatrix.options);
|
||||
}
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this._linkifyTopic();
|
||||
},
|
||||
|
||||
componentDidUpdate: function() {
|
||||
this._linkifyTopic();
|
||||
},
|
||||
|
||||
onClick: function(ev) {
|
||||
ev.preventDefault();
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick(ev, this.props.room);
|
||||
}
|
||||
},
|
||||
|
||||
onTopicClick: function(ev) {
|
||||
// When clicking a link in the topic, prevent the event being propagated
|
||||
// to `onClick`.
|
||||
ev.stopPropagation();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||
|
||||
const room = this.props.room;
|
||||
const name = room.name || getDisplayAliasForRoom(room) || _t('Unnamed room');
|
||||
|
||||
const guestRead = room.worldReadable ? (
|
||||
<div className="mx_RoomDirectory_perm">{ _t('World readable') }</div>
|
||||
) : <div />;
|
||||
const guestJoin = room.guestCanJoin ? (
|
||||
<div className="mx_RoomDirectory_perm">{ _t('Guests can join') }</div>
|
||||
) : <div />;
|
||||
|
||||
const perms = (guestRead || guestJoin) ? (<div className="mx_RoomDirectory_perms">
|
||||
{ guestRead }
|
||||
{ guestJoin }
|
||||
</div>) : <div />;
|
||||
|
||||
return <tr key={room.roomId} onClick={this.onClick} onMouseDown={this.props.onMouseDown}>
|
||||
<td className="mx_RoomDirectory_roomAvatar">
|
||||
<BaseAvatar width={24} height={24} resizeMethod='crop'
|
||||
name={name} idName={name}
|
||||
url={ContentRepo.getHttpUriForMxc(
|
||||
MatrixClientPeg.get().getHomeserverUrl(),
|
||||
room.avatarUrl, 24, 24, "crop")} />
|
||||
</td>
|
||||
<td className="mx_RoomDirectory_roomDescription">
|
||||
<div className="mx_RoomDirectory_name">{ name }</div>
|
||||
{ perms }
|
||||
<div className="mx_RoomDirectory_topic" ref="topic" onClick={this.onTopicClick}>
|
||||
{ room.topic }
|
||||
</div>
|
||||
<div className="mx_RoomDirectory_alias">{ getDisplayAliasForRoom(room) }</div>
|
||||
</td>
|
||||
<td className="mx_RoomDirectory_roomMemberCount">
|
||||
{ room.numJoinedMembers }
|
||||
</td>
|
||||
</tr>;
|
||||
},
|
||||
});
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
@ -39,18 +40,18 @@ module.exports = React.createClass({
|
|||
displayName: 'RoomHeader',
|
||||
|
||||
propTypes: {
|
||||
room: React.PropTypes.object,
|
||||
oobData: React.PropTypes.object,
|
||||
editing: React.PropTypes.bool,
|
||||
saving: React.PropTypes.bool,
|
||||
inRoom: React.PropTypes.bool,
|
||||
collapsedRhs: React.PropTypes.bool,
|
||||
onSettingsClick: React.PropTypes.func,
|
||||
onPinnedClick: React.PropTypes.func,
|
||||
onSaveClick: React.PropTypes.func,
|
||||
onSearchClick: React.PropTypes.func,
|
||||
onLeaveClick: React.PropTypes.func,
|
||||
onCancelClick: React.PropTypes.func,
|
||||
room: PropTypes.object,
|
||||
oobData: PropTypes.object,
|
||||
editing: PropTypes.bool,
|
||||
saving: PropTypes.bool,
|
||||
inRoom: PropTypes.bool,
|
||||
collapsedRhs: PropTypes.bool,
|
||||
onSettingsClick: PropTypes.func,
|
||||
onPinnedClick: PropTypes.func,
|
||||
onSaveClick: PropTypes.func,
|
||||
onSearchClick: PropTypes.func,
|
||||
onLeaveClick: PropTypes.func,
|
||||
onCancelClick: PropTypes.func,
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
|||
'use strict';
|
||||
const React = require("react");
|
||||
const ReactDOM = require("react-dom");
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../../languageHandler';
|
||||
const GeminiScrollbar = require('react-gemini-scrollbar');
|
||||
const MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||
|
@ -25,13 +26,15 @@ const CallHandler = require('../../../CallHandler');
|
|||
const dis = require("../../../dispatcher");
|
||||
const sdk = require('../../../index');
|
||||
const rate_limited_func = require('../../../ratelimitedfunc');
|
||||
const Rooms = require('../../../Rooms');
|
||||
import * as Rooms from '../../../Rooms';
|
||||
import DMRoomMap from '../../../utils/DMRoomMap';
|
||||
const Receipt = require('../../../utils/Receipt');
|
||||
import FilterStore from '../../../stores/FilterStore';
|
||||
import TagOrderStore from '../../../stores/TagOrderStore';
|
||||
import RoomListStore from '../../../stores/RoomListStore';
|
||||
import GroupStoreCache from '../../../stores/GroupStoreCache';
|
||||
|
||||
const HIDE_CONFERENCE_CHANS = true;
|
||||
const STANDARD_TAGS_REGEX = /^(m\.(favourite|lowpriority)|im\.vector\.fake\.(invite|recent|direct|archived))$/;
|
||||
|
||||
function phraseForSection(section) {
|
||||
switch (section) {
|
||||
|
@ -52,9 +55,9 @@ module.exports = React.createClass({
|
|||
displayName: 'RoomList',
|
||||
|
||||
propTypes: {
|
||||
ConferenceHandler: React.PropTypes.any,
|
||||
collapsed: React.PropTypes.bool.isRequired,
|
||||
searchFilter: React.PropTypes.string,
|
||||
ConferenceHandler: PropTypes.any,
|
||||
collapsed: PropTypes.bool.isRequired,
|
||||
searchFilter: PropTypes.string,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -71,11 +74,10 @@ module.exports = React.createClass({
|
|||
this.mounted = false;
|
||||
|
||||
const cli = MatrixClientPeg.get();
|
||||
|
||||
cli.on("Room", this.onRoom);
|
||||
cli.on("deleteRoom", this.onDeleteRoom);
|
||||
cli.on("Room.timeline", this.onRoomTimeline);
|
||||
cli.on("Room.name", this.onRoomName);
|
||||
cli.on("Room.tags", this.onRoomTags);
|
||||
cli.on("Room.receipt", this.onRoomReceipt);
|
||||
cli.on("RoomState.events", this.onRoomStateEvents);
|
||||
cli.on("RoomMember.name", this.onRoomMemberName);
|
||||
|
@ -83,21 +85,38 @@ module.exports = React.createClass({
|
|||
cli.on("accountData", this.onAccountData);
|
||||
cli.on("Group.myMembership", this._onGroupMyMembership);
|
||||
|
||||
const dmRoomMap = DMRoomMap.shared();
|
||||
this._groupStores = {};
|
||||
this._selectedTagsRoomIds = [];
|
||||
this._selectedTagsUserIds = [];
|
||||
this._groupStoreTokens = [];
|
||||
// A map between tags which are group IDs and the room IDs of rooms that should be kept
|
||||
// in the room list when filtering by that tag.
|
||||
this._visibleRoomsForGroup = {
|
||||
// $groupId: [$roomId1, $roomId2, ...],
|
||||
};
|
||||
// All rooms that should be kept in the room list when filtering.
|
||||
// By default, show all rooms.
|
||||
this._visibleRooms = MatrixClientPeg.get().getRooms();
|
||||
// When the selected tags are changed, initialise a group store if necessary
|
||||
this._filterStoreToken = FilterStore.addListener(() => {
|
||||
FilterStore.getSelectedTags().forEach((tag) => {
|
||||
this._tagStoreToken = TagOrderStore.addListener(() => {
|
||||
(TagOrderStore.getOrderedTags() || []).forEach((tag) => {
|
||||
if (tag[0] !== '+' || this._groupStores[tag]) {
|
||||
return;
|
||||
}
|
||||
this._groupStores[tag] = GroupStoreCache.getGroupStore(tag);
|
||||
this._groupStores[tag].registerListener(() => {
|
||||
this.updateSelectedTagsEntities();
|
||||
});
|
||||
this._groupStoreTokens.push(
|
||||
this._groupStores[tag].registerListener(() => {
|
||||
// This group's rooms or members may have updated, update rooms for its tag
|
||||
this.updateVisibleRoomsForTag(dmRoomMap, tag);
|
||||
this.updateVisibleRooms();
|
||||
}),
|
||||
);
|
||||
});
|
||||
this.updateSelectedTagsEntities();
|
||||
// Filters themselves have changed, refresh the selected tags
|
||||
this.updateVisibleRooms();
|
||||
});
|
||||
|
||||
this._roomListStoreToken = RoomListStore.addListener(() => {
|
||||
this._delayedRefreshRoomList();
|
||||
});
|
||||
|
||||
this.refreshRoomList();
|
||||
|
@ -158,9 +177,7 @@ module.exports = React.createClass({
|
|||
if (MatrixClientPeg.get()) {
|
||||
MatrixClientPeg.get().removeListener("Room", this.onRoom);
|
||||
MatrixClientPeg.get().removeListener("deleteRoom", this.onDeleteRoom);
|
||||
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
|
||||
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
|
||||
MatrixClientPeg.get().removeListener("Room.tags", this.onRoomTags);
|
||||
MatrixClientPeg.get().removeListener("Room.receipt", this.onRoomReceipt);
|
||||
MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents);
|
||||
MatrixClientPeg.get().removeListener("RoomMember.name", this.onRoomMemberName);
|
||||
|
@ -169,8 +186,17 @@ module.exports = React.createClass({
|
|||
MatrixClientPeg.get().removeListener("Group.myMembership", this._onGroupMyMembership);
|
||||
}
|
||||
|
||||
if (this._filterStoreToken) {
|
||||
this._filterStoreToken.remove();
|
||||
if (this._tagStoreToken) {
|
||||
this._tagStoreToken.remove();
|
||||
}
|
||||
|
||||
if (this._roomListStoreToken) {
|
||||
this._roomListStoreToken.remove();
|
||||
}
|
||||
|
||||
if (this._groupStoreTokens.length > 0) {
|
||||
// NB: GroupStore is not a Flux.Store
|
||||
this._groupStoreTokens.forEach((token) => token.unregister());
|
||||
}
|
||||
|
||||
// cancel any pending calls to the rate_limited_funcs
|
||||
|
@ -178,11 +204,11 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
onRoom: function(room) {
|
||||
this._delayedRefreshRoomList();
|
||||
this.updateVisibleRooms();
|
||||
},
|
||||
|
||||
onDeleteRoom: function(roomId) {
|
||||
this._delayedRefreshRoomList();
|
||||
this.updateVisibleRooms();
|
||||
},
|
||||
|
||||
onArchivedHeaderClick: function(isHidden, scrollToPosition) {
|
||||
|
@ -209,13 +235,6 @@ module.exports = React.createClass({
|
|||
this._updateStickyHeaders(true, scrollToPosition);
|
||||
},
|
||||
|
||||
onRoomTimeline: function(ev, room, toStartOfTimeline, removed, data) {
|
||||
if (toStartOfTimeline) return;
|
||||
if (!room) return;
|
||||
if (data.timeline.getTimelineSet() !== room.getUnfilteredTimelineSet()) return;
|
||||
this._delayedRefreshRoomList();
|
||||
},
|
||||
|
||||
onRoomReceipt: function(receiptEvent, room) {
|
||||
// because if we read a notification, it will affect notification count
|
||||
// only bother updating if there's a receipt from us
|
||||
|
@ -228,10 +247,6 @@ module.exports = React.createClass({
|
|||
this._delayedRefreshRoomList();
|
||||
},
|
||||
|
||||
onRoomTags: function(event, room) {
|
||||
this._delayedRefreshRoomList();
|
||||
},
|
||||
|
||||
onRoomStateEvents: function(ev, state) {
|
||||
this._delayedRefreshRoomList();
|
||||
},
|
||||
|
@ -259,39 +274,52 @@ module.exports = React.createClass({
|
|||
this.refreshRoomList();
|
||||
}, 500),
|
||||
|
||||
// Update which rooms and users should appear in RoomList as dictated by selected tags
|
||||
updateSelectedTagsEntities: function() {
|
||||
// Update which rooms and users should appear in RoomList for a given group tag
|
||||
updateVisibleRoomsForTag: function(dmRoomMap, tag) {
|
||||
if (!this.mounted) return;
|
||||
this._selectedTagsRoomIds = [];
|
||||
this._selectedTagsUserIds = [];
|
||||
FilterStore.getSelectedTags().forEach((tag) => {
|
||||
this._selectedTagsRoomIds = this._selectedTagsRoomIds.concat(
|
||||
this._groupStores[tag].getGroupRooms().map((room) => room.roomId),
|
||||
);
|
||||
// TODO: Check if room has been tagged to the group by the user
|
||||
// For now, only handle group tags
|
||||
const store = this._groupStores[tag];
|
||||
if (!store) return;
|
||||
|
||||
this._selectedTagsUserIds = this._selectedTagsUserIds.concat(
|
||||
this._groupStores[tag].getGroupMembers().map((member) => member.userId),
|
||||
this._visibleRoomsForGroup[tag] = [];
|
||||
store.getGroupRooms().forEach((room) => this._visibleRoomsForGroup[tag].push(room.roomId));
|
||||
store.getGroupMembers().forEach((member) => {
|
||||
if (member.userId === MatrixClientPeg.get().credentials.userId) return;
|
||||
dmRoomMap.getDMRoomsForUserId(member.userId).forEach(
|
||||
(roomId) => this._visibleRoomsForGroup[tag].push(roomId),
|
||||
);
|
||||
});
|
||||
this.setState({
|
||||
selectedTags: FilterStore.getSelectedTags(),
|
||||
}, () => {
|
||||
this.refreshRoomList();
|
||||
});
|
||||
// TODO: Check if room has been tagged to the group by the user
|
||||
},
|
||||
|
||||
isRoomInSelectedTags: function(room, me, dmRoomMap) {
|
||||
// No selected tags = every room is visible in the list
|
||||
if (this.state.selectedTags.length === 0) {
|
||||
return true;
|
||||
// Update which rooms and users should appear according to which tags are selected
|
||||
updateVisibleRooms: function() {
|
||||
const selectedTags = TagOrderStore.getSelectedTags();
|
||||
const visibleGroupRooms = [];
|
||||
selectedTags.forEach((tag) => {
|
||||
(this._visibleRoomsForGroup[tag] || []).forEach(
|
||||
(roomId) => visibleGroupRooms.push(roomId),
|
||||
);
|
||||
});
|
||||
|
||||
// If there are any tags selected, constrain the rooms listed to the
|
||||
// visible rooms as determined by visibleGroupRooms. Here, we
|
||||
// de-duplicate and filter out rooms that the client doesn't know
|
||||
// about (hence the Set and the null-guard on `room`).
|
||||
if (selectedTags.length > 0) {
|
||||
const roomSet = new Set();
|
||||
visibleGroupRooms.forEach((roomId) => {
|
||||
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||
if (room) {
|
||||
roomSet.add(room);
|
||||
}
|
||||
});
|
||||
this._visibleRooms = Array.from(roomSet);
|
||||
} else {
|
||||
// Show all rooms
|
||||
this._visibleRooms = MatrixClientPeg.get().getRooms();
|
||||
}
|
||||
if (this._selectedTagsRoomIds.includes(room.roomId)) {
|
||||
return true;
|
||||
}
|
||||
const dmUserId = dmRoomMap.getUserIdForRoomId(room.roomId);
|
||||
return dmUserId && dmUserId !== me.userId &&
|
||||
this._selectedTagsUserIds.includes(dmUserId);
|
||||
this._delayedRefreshRoomList();
|
||||
},
|
||||
|
||||
refreshRoomList: function() {
|
||||
|
@ -305,83 +333,49 @@ module.exports = React.createClass({
|
|||
totalRooms += l.length;
|
||||
}
|
||||
this.setState({
|
||||
lists: this.getRoomLists(),
|
||||
lists,
|
||||
totalRoomCount: totalRooms,
|
||||
// Do this here so as to not render every time the selected tags
|
||||
// themselves change.
|
||||
selectedTags: TagOrderStore.getSelectedTags(),
|
||||
});
|
||||
|
||||
// this._lastRefreshRoomListTs = Date.now();
|
||||
},
|
||||
|
||||
getRoomLists: function() {
|
||||
const lists = {};
|
||||
lists["im.vector.fake.invite"] = [];
|
||||
lists["m.favourite"] = [];
|
||||
lists["im.vector.fake.recent"] = [];
|
||||
lists["im.vector.fake.direct"] = [];
|
||||
lists["m.lowpriority"] = [];
|
||||
lists["im.vector.fake.archived"] = [];
|
||||
const lists = RoomListStore.getRoomLists();
|
||||
|
||||
const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
|
||||
MatrixClientPeg.get().getRooms().forEach((room) => {
|
||||
const me = room.getMember(MatrixClientPeg.get().credentials.userId);
|
||||
if (!me) return;
|
||||
const filteredLists = {};
|
||||
|
||||
// console.log("room = " + room.name + ", me.membership = " + me.membership +
|
||||
// ", sender = " + me.events.member.getSender() +
|
||||
// ", target = " + me.events.member.getStateKey() +
|
||||
// ", prevMembership = " + me.events.member.getPrevContent().membership);
|
||||
const isRoomVisible = {
|
||||
// $roomId: true,
|
||||
};
|
||||
|
||||
if (me.membership == "invite") {
|
||||
lists["im.vector.fake.invite"].push(room);
|
||||
} else if (HIDE_CONFERENCE_CHANS && Rooms.isConfCallRoom(room, me, this.props.ConferenceHandler)) {
|
||||
// skip past this room & don't put it in any lists
|
||||
} else if (me.membership == "join" || me.membership === "ban" ||
|
||||
(me.membership === "leave" && me.events.member.getSender() !== me.events.member.getStateKey())) {
|
||||
// Used to split rooms via tags
|
||||
const tagNames = Object.keys(room.tags);
|
||||
this._visibleRooms.forEach((r) => {
|
||||
isRoomVisible[r.roomId] = true;
|
||||
});
|
||||
|
||||
// Apply TagPanel filtering, derived from FilterStore
|
||||
if (!this.isRoomInSelectedTags(room, me, dmRoomMap)) {
|
||||
Object.keys(lists).forEach((tagName) => {
|
||||
const filteredRooms = lists[tagName].filter((taggedRoom) => {
|
||||
// Somewhat impossible, but guard against it anyway
|
||||
if (!taggedRoom) {
|
||||
return;
|
||||
}
|
||||
const me = taggedRoom.getMember(MatrixClientPeg.get().credentials.userId);
|
||||
if (HIDE_CONFERENCE_CHANS && Rooms.isConfCallRoom(taggedRoom, me, this.props.ConferenceHandler)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tagNames.length) {
|
||||
for (let i = 0; i < tagNames.length; i++) {
|
||||
const tagName = tagNames[i];
|
||||
lists[tagName] = lists[tagName] || [];
|
||||
lists[tagName].push(room);
|
||||
}
|
||||
} else if (dmRoomMap.getUserIdForRoomId(room.roomId)) {
|
||||
// "Direct Message" rooms (that we're still in and that aren't otherwise tagged)
|
||||
lists["im.vector.fake.direct"].push(room);
|
||||
} else {
|
||||
lists["im.vector.fake.recent"].push(room);
|
||||
}
|
||||
} else if (me.membership === "leave") {
|
||||
lists["im.vector.fake.archived"].push(room);
|
||||
} else {
|
||||
console.error("unrecognised membership: " + me.membership + " - this should never happen");
|
||||
return Boolean(isRoomVisible[taggedRoom.roomId]);
|
||||
});
|
||||
|
||||
if (filteredRooms.length > 0 || tagName.match(STANDARD_TAGS_REGEX)) {
|
||||
filteredLists[tagName] = filteredRooms;
|
||||
}
|
||||
});
|
||||
|
||||
// we actually apply the sorting to this when receiving the prop in RoomSubLists.
|
||||
|
||||
// we'll need this when we get to iterating through lists programatically - e.g. ctrl-shift-up/down
|
||||
/*
|
||||
this.listOrder = [
|
||||
"im.vector.fake.invite",
|
||||
"m.favourite",
|
||||
"im.vector.fake.recent",
|
||||
"im.vector.fake.direct",
|
||||
Object.keys(otherTagNames).filter(tagName=>{
|
||||
return (!tagName.match(/^m\.(favourite|lowpriority)$/));
|
||||
}).sort(),
|
||||
"m.lowpriority",
|
||||
"im.vector.fake.archived"
|
||||
];
|
||||
*/
|
||||
|
||||
return lists;
|
||||
return filteredLists;
|
||||
},
|
||||
|
||||
_getScrollNode: function() {
|
||||
|
@ -638,7 +632,6 @@ module.exports = React.createClass({
|
|||
editable={false}
|
||||
order="recent"
|
||||
isInvite={true}
|
||||
selectedRoom={self.props.selectedRoom}
|
||||
incomingCall={self.state.incomingCall}
|
||||
collapsed={self.props.collapsed}
|
||||
searchFilter={self.props.searchFilter}
|
||||
|
@ -652,7 +645,6 @@ module.exports = React.createClass({
|
|||
emptyContent={this._getEmptyContent('m.favourite')}
|
||||
editable={true}
|
||||
order="manual"
|
||||
selectedRoom={self.props.selectedRoom}
|
||||
incomingCall={self.state.incomingCall}
|
||||
collapsed={self.props.collapsed}
|
||||
searchFilter={self.props.searchFilter}
|
||||
|
@ -666,7 +658,6 @@ module.exports = React.createClass({
|
|||
headerItems={this._getHeaderItems('im.vector.fake.direct')}
|
||||
editable={true}
|
||||
order="recent"
|
||||
selectedRoom={self.props.selectedRoom}
|
||||
incomingCall={self.state.incomingCall}
|
||||
collapsed={self.props.collapsed}
|
||||
alwaysShowHeader={true}
|
||||
|
@ -680,7 +671,6 @@ module.exports = React.createClass({
|
|||
emptyContent={this._getEmptyContent('im.vector.fake.recent')}
|
||||
headerItems={this._getHeaderItems('im.vector.fake.recent')}
|
||||
order="recent"
|
||||
selectedRoom={self.props.selectedRoom}
|
||||
incomingCall={self.state.incomingCall}
|
||||
collapsed={self.props.collapsed}
|
||||
searchFilter={self.props.searchFilter}
|
||||
|
@ -688,7 +678,7 @@ module.exports = React.createClass({
|
|||
onShowMoreRooms={self.onShowMoreRooms} />
|
||||
|
||||
{ Object.keys(self.state.lists).map((tagName) => {
|
||||
if (!tagName.match(/^(m\.(favourite|lowpriority)|im\.vector\.fake\.(invite|recent|direct|archived))$/)) {
|
||||
if (!tagName.match(STANDARD_TAGS_REGEX)) {
|
||||
return <RoomSubList list={self.state.lists[tagName]}
|
||||
key={tagName}
|
||||
label={tagName}
|
||||
|
@ -696,7 +686,6 @@ module.exports = React.createClass({
|
|||
emptyContent={this._getEmptyContent(tagName)}
|
||||
editable={true}
|
||||
order="manual"
|
||||
selectedRoom={self.props.selectedRoom}
|
||||
incomingCall={self.state.incomingCall}
|
||||
collapsed={self.props.collapsed}
|
||||
searchFilter={self.props.searchFilter}
|
||||
|
@ -711,7 +700,6 @@ module.exports = React.createClass({
|
|||
emptyContent={this._getEmptyContent('m.lowpriority')}
|
||||
editable={true}
|
||||
order="recent"
|
||||
selectedRoom={self.props.selectedRoom}
|
||||
incomingCall={self.state.incomingCall}
|
||||
collapsed={self.props.collapsed}
|
||||
searchFilter={self.props.searchFilter}
|
||||
|
@ -722,7 +710,6 @@ module.exports = React.createClass({
|
|||
label={_t('Historical')}
|
||||
editable={false}
|
||||
order="recent"
|
||||
selectedRoom={self.props.selectedRoom}
|
||||
collapsed={self.props.collapsed}
|
||||
alwaysShowHeader={true}
|
||||
startAsHidden={true}
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
'use strict';
|
||||
|
||||
const React = require('react');
|
||||
import PropTypes from 'prop-types';
|
||||
const sdk = require('../../../index');
|
||||
const MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
@ -25,7 +26,13 @@ module.exports = React.createClass({
|
|||
displayName: 'RoomNameEditor',
|
||||
|
||||
propTypes: {
|
||||
room: React.PropTypes.object.isRequired,
|
||||
room: PropTypes.object.isRequired,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
name: null,
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
|
@ -34,7 +41,9 @@ module.exports = React.createClass({
|
|||
const myId = MatrixClientPeg.get().credentials.userId;
|
||||
const defaultName = room.getDefaultRoomName(myId);
|
||||
|
||||
this._initialName = name ? name.getContent().name : '';
|
||||
this.setState({
|
||||
name: name ? name.getContent().name : '',
|
||||
});
|
||||
|
||||
this._placeholderName = _t("Unnamed Room");
|
||||
if (defaultName && defaultName !== 'Empty room') { // default name from JS SDK, needs no translation as we don't ever show it.
|
||||
|
@ -43,7 +52,13 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
getRoomName: function() {
|
||||
return this.refs.editor.getValue();
|
||||
return this.state.name;
|
||||
},
|
||||
|
||||
_onValueChanged: function(value, shouldSubmit) {
|
||||
this.setState({
|
||||
name: value,
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
@ -56,7 +71,8 @@ module.exports = React.createClass({
|
|||
placeholderClassName="mx_RoomHeader_placeholder"
|
||||
placeholder={this._placeholderName}
|
||||
blurToCancel={false}
|
||||
initialValue={this._initialName}
|
||||
initialValue={this.state.name}
|
||||
onValueChanged={this._onValueChanged}
|
||||
dir="auto" />
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
|||
'use strict';
|
||||
|
||||
const React = require('react');
|
||||
import PropTypes from 'prop-types';
|
||||
const sdk = require('../../../index');
|
||||
const MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||
|
||||
|
@ -27,29 +28,29 @@ module.exports = React.createClass({
|
|||
displayName: 'RoomPreviewBar',
|
||||
|
||||
propTypes: {
|
||||
onJoinClick: React.PropTypes.func,
|
||||
onRejectClick: React.PropTypes.func,
|
||||
onForgetClick: React.PropTypes.func,
|
||||
onJoinClick: PropTypes.func,
|
||||
onRejectClick: PropTypes.func,
|
||||
onForgetClick: PropTypes.func,
|
||||
|
||||
// if inviterName is specified, the preview bar will shown an invite to the room.
|
||||
// You should also specify onRejectClick if specifiying inviterName
|
||||
inviterName: React.PropTypes.string,
|
||||
inviterName: PropTypes.string,
|
||||
|
||||
// If invited by 3rd party invite, the email address the invite was sent to
|
||||
invitedEmail: React.PropTypes.string,
|
||||
invitedEmail: PropTypes.string,
|
||||
|
||||
// A standard client/server API error object. If supplied, indicates that the
|
||||
// caller was unable to fetch details about the room for the given reason.
|
||||
error: React.PropTypes.object,
|
||||
error: PropTypes.object,
|
||||
|
||||
canPreview: React.PropTypes.bool,
|
||||
spinner: React.PropTypes.bool,
|
||||
room: React.PropTypes.object,
|
||||
canPreview: PropTypes.bool,
|
||||
spinner: PropTypes.bool,
|
||||
room: PropTypes.object,
|
||||
|
||||
// The alias that was used to access this room, if appropriate
|
||||
// If given, this will be how the room is referred to (eg.
|
||||
// in error messages).
|
||||
roomAlias: React.PropTypes.string,
|
||||
roomAlias: PropTypes.string,
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
|
||||
import Promise from 'bluebird';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t, _td } from '../../../languageHandler';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import sdk from '../../../index';
|
||||
|
@ -60,10 +61,10 @@ const plEventsToShow = {
|
|||
|
||||
const BannedUser = React.createClass({
|
||||
propTypes: {
|
||||
canUnban: React.PropTypes.bool,
|
||||
member: React.PropTypes.object.isRequired, // js-sdk RoomMember
|
||||
by: React.PropTypes.string.isRequired,
|
||||
reason: React.PropTypes.string,
|
||||
canUnban: PropTypes.bool,
|
||||
member: PropTypes.object.isRequired, // js-sdk RoomMember
|
||||
by: PropTypes.string.isRequired,
|
||||
reason: PropTypes.string,
|
||||
},
|
||||
|
||||
_onUnbanClick: function() {
|
||||
|
@ -115,8 +116,8 @@ module.exports = React.createClass({
|
|||
displayName: 'RoomSettings',
|
||||
|
||||
propTypes: {
|
||||
room: React.PropTypes.object.isRequired,
|
||||
onSaveClick: React.PropTypes.func,
|
||||
room: PropTypes.object.isRequired,
|
||||
onSaveClick: PropTypes.func,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -773,7 +774,8 @@ module.exports = React.createClass({
|
|||
const aliasEvents = this.props.room.currentState.getStateEvents('m.room.aliases') || [];
|
||||
let aliasCount = 0;
|
||||
aliasEvents.forEach((event) => {
|
||||
aliasCount += event.getContent().aliases.length;
|
||||
const aliases = event.getContent().aliases || [];
|
||||
aliasCount += aliases.length;
|
||||
});
|
||||
|
||||
if (this.state.join_rule === "public" && aliasCount == 0) {
|
||||
|
|
|
@ -19,6 +19,7 @@ limitations under the License.
|
|||
|
||||
const React = require('react');
|
||||
const ReactDOM = require("react-dom");
|
||||
import PropTypes from 'prop-types';
|
||||
const classNames = require('classnames');
|
||||
const MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||
import DMRoomMap from '../../../utils/DMRoomMap';
|
||||
|
@ -34,17 +35,14 @@ module.exports = React.createClass({
|
|||
displayName: 'RoomTile',
|
||||
|
||||
propTypes: {
|
||||
connectDragSource: React.PropTypes.func,
|
||||
connectDropTarget: React.PropTypes.func,
|
||||
onClick: React.PropTypes.func,
|
||||
isDragging: React.PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
|
||||
room: React.PropTypes.object.isRequired,
|
||||
collapsed: React.PropTypes.bool.isRequired,
|
||||
unread: React.PropTypes.bool.isRequired,
|
||||
highlight: React.PropTypes.bool.isRequired,
|
||||
isInvite: React.PropTypes.bool.isRequired,
|
||||
incomingCall: React.PropTypes.object,
|
||||
room: PropTypes.object.isRequired,
|
||||
collapsed: PropTypes.bool.isRequired,
|
||||
unread: PropTypes.bool.isRequired,
|
||||
highlight: PropTypes.bool.isRequired,
|
||||
isInvite: PropTypes.bool.isRequired,
|
||||
incomingCall: PropTypes.object,
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
|
@ -255,35 +253,19 @@ module.exports = React.createClass({
|
|||
directMessageIndicator = <img src="img/icon_person.svg" className="mx_RoomTile_dm" width="11" height="13" alt="dm" />;
|
||||
}
|
||||
|
||||
// These props are injected by React DnD,
|
||||
// as defined by your `collect` function above:
|
||||
const isDragging = this.props.isDragging;
|
||||
const connectDragSource = this.props.connectDragSource;
|
||||
const connectDropTarget = this.props.connectDropTarget;
|
||||
|
||||
|
||||
let ret = (
|
||||
<div> { /* Only native elements can be wrapped in a DnD object. */ }
|
||||
<AccessibleButton className={classes} tabIndex="0" onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||
<div className={avatarClasses}>
|
||||
<div className="mx_RoomTile_avatar_container">
|
||||
<RoomAvatar room={this.props.room} width={24} height={24} />
|
||||
{ directMessageIndicator }
|
||||
</div>
|
||||
return <AccessibleButton className={classes} tabIndex="0" onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||
<div className={avatarClasses}>
|
||||
<div className="mx_RoomTile_avatar_container">
|
||||
<RoomAvatar room={this.props.room} width={24} height={24} />
|
||||
{ directMessageIndicator }
|
||||
</div>
|
||||
<div className="mx_RoomTile_nameContainer">
|
||||
{ label }
|
||||
{ badge }
|
||||
</div>
|
||||
{ /* { incomingCallBox } */ }
|
||||
{ tooltip }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (connectDropTarget) ret = connectDropTarget(ret);
|
||||
if (connectDragSource) ret = connectDragSource(ret);
|
||||
|
||||
return ret;
|
||||
<div className="mx_RoomTile_nameContainer">
|
||||
{ label }
|
||||
{ badge }
|
||||
</div>
|
||||
{ /* { incomingCallBox } */ }
|
||||
{ tooltip }
|
||||
</AccessibleButton>;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
'use strict';
|
||||
|
||||
const React = require('react');
|
||||
import PropTypes from 'prop-types';
|
||||
const sdk = require('../../../index');
|
||||
import { _t } from "../../../languageHandler";
|
||||
|
||||
|
@ -24,29 +25,44 @@ module.exports = React.createClass({
|
|||
displayName: 'RoomTopicEditor',
|
||||
|
||||
propTypes: {
|
||||
room: React.PropTypes.object.isRequired,
|
||||
room: PropTypes.object.isRequired,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
topic: null,
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
const room = this.props.room;
|
||||
const topic = room.currentState.getStateEvents('m.room.topic', '');
|
||||
this._initialTopic = topic ? topic.getContent().topic : '';
|
||||
this.setState({
|
||||
topic: topic ? topic.getContent().topic : '',
|
||||
});
|
||||
},
|
||||
|
||||
getTopic: function() {
|
||||
return this.refs.editor.getValue();
|
||||
return this.state.topic;
|
||||
},
|
||||
|
||||
_onValueChanged: function(value) {
|
||||
this.setState({
|
||||
topic: value,
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const EditableText = sdk.getComponent("elements.EditableText");
|
||||
|
||||
return (
|
||||
<EditableText ref="editor"
|
||||
<EditableText
|
||||
className="mx_RoomHeader_topic mx_RoomHeader_editable"
|
||||
placeholderClassName="mx_RoomHeader_placeholder"
|
||||
placeholder={_t("Add a topic")}
|
||||
blurToCancel={false}
|
||||
initialValue={this._initialTopic}
|
||||
initialValue={this.state.topic}
|
||||
onValueChanged={this._onValueChanged}
|
||||
dir="auto" />
|
||||
);
|
||||
},
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
'use strict';
|
||||
|
||||
const React = require('react');
|
||||
import PropTypes from 'prop-types';
|
||||
const sdk = require('../../../index');
|
||||
|
||||
module.exports = React.createClass({
|
||||
|
@ -24,15 +25,15 @@ module.exports = React.createClass({
|
|||
|
||||
propTypes: {
|
||||
// a matrix-js-sdk SearchResult containing the details of this result
|
||||
searchResult: React.PropTypes.object.isRequired,
|
||||
searchResult: PropTypes.object.isRequired,
|
||||
|
||||
// a list of strings to be highlighted in the results
|
||||
searchHighlights: React.PropTypes.array,
|
||||
searchHighlights: PropTypes.array,
|
||||
|
||||
// href for the highlights in this result
|
||||
resultLink: React.PropTypes.string,
|
||||
resultLink: PropTypes.string,
|
||||
|
||||
onWidgetLoad: React.PropTypes.func,
|
||||
onWidgetLoad: PropTypes.func,
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
|
|
@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
const React = require('react');
|
||||
import PropTypes from 'prop-types';
|
||||
const MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||
const Modal = require("../../../Modal");
|
||||
const sdk = require("../../../index");
|
||||
|
@ -26,12 +27,12 @@ const SearchableEntityList = React.createClass({
|
|||
displayName: 'SearchableEntityList',
|
||||
|
||||
propTypes: {
|
||||
emptyQueryShowsAll: React.PropTypes.bool,
|
||||
showInputBox: React.PropTypes.bool,
|
||||
onQueryChanged: React.PropTypes.func, // fn(inputText)
|
||||
onSubmit: React.PropTypes.func, // fn(inputText)
|
||||
entities: React.PropTypes.array,
|
||||
truncateAt: React.PropTypes.number,
|
||||
emptyQueryShowsAll: PropTypes.bool,
|
||||
showInputBox: PropTypes.bool,
|
||||
onQueryChanged: PropTypes.func, // fn(inputText)
|
||||
onSubmit: PropTypes.func, // fn(inputText)
|
||||
entities: PropTypes.array,
|
||||
truncateAt: PropTypes.number,
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
@ -39,11 +40,11 @@ export default React.createClass({
|
|||
displayName: 'SimpleRoomHeader',
|
||||
|
||||
propTypes: {
|
||||
title: React.PropTypes.string,
|
||||
onCancelClick: React.PropTypes.func,
|
||||
title: PropTypes.string,
|
||||
onCancelClick: PropTypes.func,
|
||||
|
||||
// `src` to a TintableSvg. Optional.
|
||||
icon: React.PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
|||
'use strict';
|
||||
|
||||
const React = require('react');
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../../languageHandler';
|
||||
const sdk = require('../../../index');
|
||||
|
||||
|
@ -25,8 +26,8 @@ module.exports = React.createClass({
|
|||
displayName: 'TopUnreadMessagesBar',
|
||||
|
||||
propTypes: {
|
||||
onScrollUpClick: React.PropTypes.func,
|
||||
onCloseClick: React.PropTypes.func,
|
||||
onScrollUpClick: PropTypes.func,
|
||||
onCloseClick: PropTypes.func,
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
'use strict';
|
||||
|
||||
const React = require('react');
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const Avatar = require("../../../Avatar");
|
||||
const MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||
|
@ -28,7 +29,7 @@ module.exports = React.createClass({
|
|||
displayName: 'UserTile',
|
||||
|
||||
propTypes: {
|
||||
user: React.PropTypes.any.isRequired, // User
|
||||
user: PropTypes.any.isRequired, // User
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue