Merge branch 'develop' into matthew/notif-panel
This commit is contained in:
commit
83209197f4
13 changed files with 461 additions and 187 deletions
|
@ -16,26 +16,19 @@ limitations under the License.
|
|||
|
||||
'use strict';
|
||||
|
||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||
var React = require('react');
|
||||
var url = require("url");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'CasLogin',
|
||||
|
||||
onCasClicked: function(ev) {
|
||||
var cli = MatrixClientPeg.get();
|
||||
var parsedUrl = url.parse(window.location.href, true);
|
||||
parsedUrl.query["homeserver"] = cli.getHomeserverUrl();
|
||||
parsedUrl.query["identityServer"] = cli.getIdentityServerUrl();
|
||||
var casUrl = MatrixClientPeg.get().getCasLoginUrl(url.format(parsedUrl));
|
||||
window.location.href = casUrl;
|
||||
propTypes: {
|
||||
onSubmit: React.PropTypes.func, // fn()
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={this.onCasClicked}>Sign in with CAS</button>
|
||||
<button onClick={this.props.onSubmit}>Sign in with CAS</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -99,12 +99,16 @@ export default class MemberDeviceInfo extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
var deviceName = this.props.device.display_name || this.props.device.deviceId;
|
||||
var deviceName = this.props.device.getDisplayName() || this.props.device.deviceId;
|
||||
|
||||
// add the deviceId as a titletext to help with debugging
|
||||
return (
|
||||
<div className="mx_MemberDeviceInfo">
|
||||
<div className="mx_MemberDeviceInfo" title={this.props.device.deviceId}>
|
||||
<div className="mx_MemberDeviceInfo_deviceId">{deviceName}</div>
|
||||
{indicator}
|
||||
<div className="mx_MemberDeviceInfo_deviceKey">
|
||||
{this.props.device.getFingerprint()}
|
||||
</div>
|
||||
{verifyButton}
|
||||
{blockButton}
|
||||
</div>
|
||||
|
|
|
@ -26,12 +26,16 @@ limitations under the License.
|
|||
* 'isTargetMod': boolean
|
||||
*/
|
||||
var React = require('react');
|
||||
var classNames = require('classnames');
|
||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||
var dis = require("../../../dispatcher");
|
||||
var Modal = require("../../../Modal");
|
||||
var sdk = require('../../../index');
|
||||
var UserSettingsStore = require('../../../UserSettingsStore');
|
||||
var createRoom = require('../../../createRoom');
|
||||
var DMRoomMap = require('../../../utils/DMRoomMap');
|
||||
var Unread = require('../../../Unread');
|
||||
var Receipt = require('../../../utils/Receipt');
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MemberInfo',
|
||||
|
@ -60,7 +64,6 @@ module.exports = React.createClass({
|
|||
updating: 0,
|
||||
devicesLoading: true,
|
||||
devices: null,
|
||||
existingOneToOneRoomId: null,
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -72,14 +75,20 @@ module.exports = React.createClass({
|
|||
this._enableDevices = MatrixClientPeg.get().isCryptoEnabled() &&
|
||||
UserSettingsStore.isFeatureEnabled("e2e_encryption");
|
||||
|
||||
this.setState({
|
||||
existingOneToOneRoomId: this.getExistingOneToOneRoomId()
|
||||
});
|
||||
const cli = MatrixClientPeg.get();
|
||||
cli.on("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
||||
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.receipt", this.onRoomReceipt);
|
||||
cli.on("RoomState.events", this.onRoomStateEvents);
|
||||
cli.on("RoomMember.name", this.onRoomMemberName);
|
||||
cli.on("accountData", this.onAccountData);
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this._updateStateForNewMember(this.props.member);
|
||||
MatrixClientPeg.get().on("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(newProps) {
|
||||
|
@ -92,65 +101,20 @@ module.exports = React.createClass({
|
|||
var client = MatrixClientPeg.get();
|
||||
if (client) {
|
||||
client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
||||
client.removeListener("Room", this.onRoom);
|
||||
client.removeListener("deleteRoom", this.onDeleteRoom);
|
||||
client.removeListener("Room.timeline", this.onRoomTimeline);
|
||||
client.removeListener("Room.name", this.onRoomName);
|
||||
client.removeListener("Room.receipt", this.onRoomReceipt);
|
||||
client.removeListener("RoomState.events", this.onRoomStateEvents);
|
||||
client.removeListener("RoomMember.name", this.onRoomMemberName);
|
||||
client.removeListener("accountData", this.onAccountData);
|
||||
}
|
||||
if (this._cancelDeviceList) {
|
||||
this._cancelDeviceList();
|
||||
}
|
||||
},
|
||||
|
||||
getExistingOneToOneRoomId: function() {
|
||||
const rooms = MatrixClientPeg.get().getRooms();
|
||||
const userIds = [
|
||||
this.props.member.userId,
|
||||
MatrixClientPeg.get().credentials.userId
|
||||
];
|
||||
let existingRoomId = null;
|
||||
let invitedRoomId = null;
|
||||
|
||||
// roomId can be null here because of a hack in MatrixChat.onUserClick where we
|
||||
// abuse this to view users rather than room members.
|
||||
let currentMembers;
|
||||
if (this.props.member.roomId) {
|
||||
const currentRoom = MatrixClientPeg.get().getRoom(this.props.member.roomId);
|
||||
currentMembers = currentRoom.getJoinedMembers();
|
||||
}
|
||||
|
||||
// reuse the first private 1:1 we find
|
||||
existingRoomId = null;
|
||||
|
||||
for (let i = 0; i < rooms.length; i++) {
|
||||
// don't try to reuse public 1:1 rooms
|
||||
const join_rules = rooms[i].currentState.getStateEvents("m.room.join_rules", '');
|
||||
if (join_rules && join_rules.getContent().join_rule === 'public') continue;
|
||||
|
||||
const members = rooms[i].getJoinedMembers();
|
||||
if (members.length === 2 &&
|
||||
userIds.indexOf(members[0].userId) !== -1 &&
|
||||
userIds.indexOf(members[1].userId) !== -1)
|
||||
{
|
||||
existingRoomId = rooms[i].roomId;
|
||||
break;
|
||||
}
|
||||
|
||||
const invited = rooms[i].getMembersWithMembership('invite');
|
||||
if (members.length === 1 &&
|
||||
invited.length === 1 &&
|
||||
userIds.indexOf(members[0].userId) !== -1 &&
|
||||
userIds.indexOf(invited[0].userId) !== -1 &&
|
||||
invitedRoomId === null)
|
||||
{
|
||||
invitedRoomId = rooms[i].roomId;
|
||||
// keep looking: we'll use this one if there's nothing better
|
||||
}
|
||||
}
|
||||
|
||||
if (existingRoomId === null) {
|
||||
existingRoomId = invitedRoomId;
|
||||
}
|
||||
|
||||
return existingRoomId;
|
||||
},
|
||||
|
||||
onDeviceVerificationChanged: function(userId, device) {
|
||||
if (!this._enableDevices) {
|
||||
return;
|
||||
|
@ -164,6 +128,45 @@ module.exports = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
onRoom: function(room) {
|
||||
this.forceUpdate();
|
||||
},
|
||||
|
||||
onDeleteRoom: function(roomId) {
|
||||
this.forceUpdate();
|
||||
},
|
||||
|
||||
onRoomTimeline: function(ev, room, toStartOfTimeline) {
|
||||
if (toStartOfTimeline) return;
|
||||
this.forceUpdate();
|
||||
},
|
||||
|
||||
onRoomName: function(room) {
|
||||
this.forceUpdate();
|
||||
},
|
||||
|
||||
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
|
||||
if (Receipt.findReadReceiptFromUserId(receiptEvent, MatrixClientPeg.get().credentials.userId)) {
|
||||
this.forceUpdate();
|
||||
}
|
||||
},
|
||||
|
||||
onRoomStateEvents: function(ev, state) {
|
||||
this.forceUpdate();
|
||||
},
|
||||
|
||||
onRoomMemberName: function(ev, member) {
|
||||
this.forceUpdate();
|
||||
},
|
||||
|
||||
onAccountData: function(ev) {
|
||||
if (ev.getType() == 'm.direct') {
|
||||
this.forceUpdate();
|
||||
}
|
||||
},
|
||||
|
||||
_updateStateForNewMember: function(member) {
|
||||
var newState = this._calculateOpsPermissions(member);
|
||||
newState.devicesLoading = true;
|
||||
|
@ -416,33 +419,16 @@ module.exports = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
onChatClick: function() {
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
|
||||
// TODO: keep existingOneToOneRoomId updated if we see any room member changes anywhere
|
||||
|
||||
const useExistingOneToOneRoom = this.state.existingOneToOneRoomId && (this.state.existingOneToOneRoomId !== this.props.member.roomId);
|
||||
|
||||
// check if there are any existing rooms with just us and them (1:1)
|
||||
// If so, just view that room. If not, create a private room with them.
|
||||
if (useExistingOneToOneRoom) {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: this.state.existingOneToOneRoomId,
|
||||
});
|
||||
onNewDMClick: function() {
|
||||
this.setState({ updating: this.state.updating + 1 });
|
||||
createRoom({
|
||||
createOpts: {
|
||||
invite: [this.props.member.userId],
|
||||
},
|
||||
}).finally(() => {
|
||||
this.props.onFinished();
|
||||
}
|
||||
else {
|
||||
this.setState({ updating: this.state.updating + 1 });
|
||||
createRoom({
|
||||
createOpts: {
|
||||
invite: [this.props.member.userId],
|
||||
},
|
||||
}).finally(() => {
|
||||
this.props.onFinished();
|
||||
this.setState({ updating: this.state.updating - 1 });
|
||||
}).done();
|
||||
}
|
||||
this.setState({ updating: this.state.updating - 1 });
|
||||
}).done();
|
||||
},
|
||||
|
||||
onLeaveClick: function() {
|
||||
|
@ -583,24 +569,50 @@ module.exports = React.createClass({
|
|||
render: function() {
|
||||
var startChat, kickButton, banButton, muteButton, giveModButton, spinner;
|
||||
if (this.props.member.userId !== MatrixClientPeg.get().credentials.userId) {
|
||||
// FIXME: we're referring to a vector component from react-sdk
|
||||
var BottomLeftMenuTile = sdk.getComponent('rooms.BottomLeftMenuTile');
|
||||
const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
|
||||
const dmRooms = dmRoomMap.getDMRoomsForUserId(this.props.member.userId);
|
||||
|
||||
var label;
|
||||
if (this.state.existingOneToOneRoomId) {
|
||||
if (this.state.existingOneToOneRoomId == this.props.member.roomId) {
|
||||
label = "Start new direct chat";
|
||||
}
|
||||
else {
|
||||
label = "Go to direct chat";
|
||||
const RoomTile = sdk.getComponent("rooms.RoomTile");
|
||||
|
||||
const tiles = [];
|
||||
for (const roomId of dmRooms) {
|
||||
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||
if (room) {
|
||||
const me = room.getMember(MatrixClientPeg.get().credentials.userId);
|
||||
const highlight = (
|
||||
room.getUnreadNotificationCount('highlight') > 0 ||
|
||||
me.membership == "invite"
|
||||
);
|
||||
tiles.push(
|
||||
<RoomTile key={room.roomId} room={room}
|
||||
collapsed={false}
|
||||
selected={false}
|
||||
unread={Unread.doesRoomHaveUnreadMessages(room)}
|
||||
highlight={highlight}
|
||||
isInvite={me.membership == "invite"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
label = "Start direct chat";
|
||||
}
|
||||
|
||||
startChat = <BottomLeftMenuTile collapsed={ false } img="img/create-big.svg"
|
||||
label={ label } onClick={ this.onChatClick }/>
|
||||
const labelClasses = classNames({
|
||||
mx_MemberInfo_createRoom_label: true,
|
||||
mx_RoomTile_name: true,
|
||||
});
|
||||
const startNewChat = <div
|
||||
className="mx_MemberInfo_createRoom"
|
||||
onClick={this.onNewDMClick}
|
||||
>
|
||||
<div className="mx_RoomTile_avatar">
|
||||
<img src="img/create-big.svg" width="26" height="26" />
|
||||
</div>
|
||||
<div className={labelClasses}><i>Start new direct chat</i></div>
|
||||
</div>
|
||||
|
||||
startChat = <div>
|
||||
{tiles}
|
||||
{startNewChat}
|
||||
</div>;
|
||||
}
|
||||
|
||||
if (this.state.updating) {
|
||||
|
|
|
@ -31,7 +31,7 @@ import {Editor, EditorState, RichUtils, CompositeDecorator,
|
|||
convertFromRaw, convertToRaw, Modifier, EditorChangeType,
|
||||
getDefaultKeyBinding, KeyBindingUtil, ContentState, ContentBlock, SelectionState} from 'draft-js';
|
||||
|
||||
import {stateToMarkdown} from 'draft-js-export-markdown';
|
||||
import {stateToMarkdown as __stateToMarkdown} from 'draft-js-export-markdown';
|
||||
import classNames from 'classnames';
|
||||
import escape from 'lodash/escape';
|
||||
|
||||
|
@ -51,6 +51,16 @@ const TYPING_USER_TIMEOUT = 10000, TYPING_SERVER_TIMEOUT = 30000;
|
|||
|
||||
const KEY_M = 77;
|
||||
|
||||
const ZWS_CODE = 8203;
|
||||
const ZWS = String.fromCharCode(ZWS_CODE); // zero width space
|
||||
function stateToMarkdown(state) {
|
||||
return __stateToMarkdown(state)
|
||||
.replace(
|
||||
ZWS, // draft-js-export-markdown adds these
|
||||
''); // this is *not* a zero width space, trust me :)
|
||||
}
|
||||
|
||||
|
||||
// FIXME Breaks markdown with multiple paragraphs, since it only strips first and last <p>
|
||||
function mdownToHtml(mdown: string): string {
|
||||
let html = marked(mdown) || "";
|
||||
|
@ -480,7 +490,7 @@ export default class MessageComposerInput extends React.Component {
|
|||
});
|
||||
}
|
||||
if (cmd.promise) {
|
||||
cmd.promise.done(function() {
|
||||
cmd.promise.then(function() {
|
||||
console.log("Command success.");
|
||||
}, function(err) {
|
||||
console.error("Command failure: %s", err);
|
||||
|
@ -520,7 +530,7 @@ export default class MessageComposerInput extends React.Component {
|
|||
this.sentHistory.push(contentHTML);
|
||||
let sendMessagePromise = sendFn.call(this.client, this.props.room.roomId, contentText, contentHTML);
|
||||
|
||||
sendMessagePromise.done(() => {
|
||||
sendMessagePromise.then(() => {
|
||||
dis.dispatch({
|
||||
action: 'message_sent'
|
||||
});
|
||||
|
|
|
@ -27,6 +27,7 @@ var sdk = require('../../../index');
|
|||
var rate_limited_func = require('../../../ratelimitedfunc');
|
||||
var Rooms = require('../../../Rooms');
|
||||
var DMRoomMap = require('../../../utils/DMRoomMap');
|
||||
var Receipt = require('../../../utils/Receipt');
|
||||
|
||||
var HIDE_CONFERENCE_CHANS = true;
|
||||
|
||||
|
@ -156,13 +157,8 @@ module.exports = React.createClass({
|
|||
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
|
||||
var receiptKeys = Object.keys(receiptEvent.getContent());
|
||||
for (var i = 0; i < receiptKeys.length; ++i) {
|
||||
var rcpt = receiptEvent.getContent()[receiptKeys[i]];
|
||||
if (rcpt['m.read'] && rcpt['m.read'][MatrixClientPeg.get().credentials.userId]) {
|
||||
this._delayedRefreshRoomList();
|
||||
break;
|
||||
}
|
||||
if (Receipt.findReadReceiptFromUserId(receiptEvent, MatrixClientPeg.get().credentials.userId)) {
|
||||
this._delayedRefreshRoomList();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -235,10 +231,6 @@ module.exports = React.createClass({
|
|||
else if (HIDE_CONFERENCE_CHANS && Rooms.isConfCallRoom(room, me, self.props.ConferenceHandler)) {
|
||||
// skip past this room & don't put it in any lists
|
||||
}
|
||||
else if (dmRoomMap.getUserIdForRoomId(room.roomId)) {
|
||||
// "Direct Message" rooms
|
||||
s.lists["im.vector.fake.direct"].push(room);
|
||||
}
|
||||
else if (me.membership == "join" || me.membership === "ban" ||
|
||||
(me.membership === "leave" && me.events.member.getSender() !== me.events.member.getStateKey()))
|
||||
{
|
||||
|
@ -252,6 +244,10 @@ module.exports = React.createClass({
|
|||
s.lists[tagNames[i]].push(room);
|
||||
}
|
||||
}
|
||||
else if (dmRoomMap.getUserIdForRoomId(room.roomId)) {
|
||||
// "Direct Message" rooms (that we're still in and that aren't otherwise tagged)
|
||||
s.lists["im.vector.fake.direct"].push(room);
|
||||
}
|
||||
else {
|
||||
s.lists["im.vector.fake.recent"].push(room);
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ var ReactDOM = require("react-dom");
|
|||
var classNames = require('classnames');
|
||||
var dis = require("../../../dispatcher");
|
||||
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||
var DMRoomMap = require('../../../utils/DMRoomMap');
|
||||
var sdk = require('../../../index');
|
||||
var ContextualMenu = require('../../structures/ContextualMenu');
|
||||
var RoomNotifs = require('../../../RoomNotifs');
|
||||
|
@ -29,10 +30,9 @@ module.exports = React.createClass({
|
|||
displayName: 'RoomTile',
|
||||
|
||||
propTypes: {
|
||||
// TODO: We should *optionally* support DND stuff and ideally be impl agnostic about it
|
||||
connectDragSource: React.PropTypes.func.isRequired,
|
||||
connectDropTarget: React.PropTypes.func.isRequired,
|
||||
isDragging: React.PropTypes.bool.isRequired,
|
||||
connectDragSource: React.PropTypes.func,
|
||||
connectDropTarget: React.PropTypes.func,
|
||||
isDragging: React.PropTypes.bool,
|
||||
|
||||
room: React.PropTypes.object.isRequired,
|
||||
collapsed: React.PropTypes.bool.isRequired,
|
||||
|
@ -40,11 +40,15 @@ module.exports = React.createClass({
|
|||
unread: React.PropTypes.bool.isRequired,
|
||||
highlight: React.PropTypes.bool.isRequired,
|
||||
isInvite: React.PropTypes.bool.isRequired,
|
||||
roomSubList: React.PropTypes.object.isRequired,
|
||||
refreshSubList: React.PropTypes.func.isRequired,
|
||||
incomingCall: React.PropTypes.object,
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
isDragging: false,
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return({
|
||||
hover : false,
|
||||
|
@ -64,6 +68,16 @@ module.exports = React.createClass({
|
|||
return this.state.notifState != RoomNotifs.MUTE;
|
||||
},
|
||||
|
||||
_isDirectMessageRoom: function(roomId) {
|
||||
const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
|
||||
var dmRooms = dmRoomMap.getUserIdForRoomId(roomId);
|
||||
if (dmRooms) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
onAccountData: function(accountDataEvent) {
|
||||
if (accountDataEvent.getType() == 'm.push_rules') {
|
||||
this.setState({
|
||||
|
@ -261,18 +275,24 @@ module.exports = React.createClass({
|
|||
|
||||
var RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
|
||||
|
||||
var directMessageIndicator;
|
||||
if (this._isDirectMessageRoom(this.props.room.roomId)) {
|
||||
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:
|
||||
var isDragging = this.props.isDragging;
|
||||
var connectDragSource = this.props.connectDragSource;
|
||||
var connectDropTarget = this.props.connectDropTarget;
|
||||
|
||||
return connectDragSource(connectDropTarget(
|
||||
let ret = (
|
||||
<div className={classes} onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||
<div className={avatarClasses}>
|
||||
<div className="mx_RoomTile_avatar_menu" onClick={this.onAvatarClicked}>
|
||||
<div className={avatarContainerClasses}>
|
||||
<RoomAvatar room={this.props.room} width={24} height={24} />
|
||||
{directMessageIndicator}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -283,6 +303,11 @@ module.exports = React.createClass({
|
|||
{ incomingCallBox }
|
||||
{ tooltip }
|
||||
</div>
|
||||
));
|
||||
);
|
||||
|
||||
if (connectDropTarget) ret = connectDropTarget(ret);
|
||||
if (connectDragSource) ret = connectDragSource(ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue