Merge branch 'develop' into matthew/notif-panel
This commit is contained in:
commit
83209197f4
13 changed files with 461 additions and 187 deletions
|
@ -256,6 +256,7 @@ function returnStateEvent(event, roomId, eventType, stateKey) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentRoomId = null;
|
var currentRoomId = null;
|
||||||
|
var currentRoomAlias = null;
|
||||||
|
|
||||||
// Listen for when a room is viewed
|
// Listen for when a room is viewed
|
||||||
dis.register(onAction);
|
dis.register(onAction);
|
||||||
|
@ -264,6 +265,7 @@ function onAction(payload) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
currentRoomId = payload.room_id;
|
currentRoomId = payload.room_id;
|
||||||
|
currentRoomAlias = payload.room_alias;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onMessage = function(event) {
|
const onMessage = function(event) {
|
||||||
|
@ -287,45 +289,59 @@ const onMessage = function(event) {
|
||||||
sendError(event, "Missing room_id in request");
|
sendError(event, "Missing room_id in request");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let promise = Promise.resolve(currentRoomId);
|
||||||
if (!currentRoomId) {
|
if (!currentRoomId) {
|
||||||
sendError(event, "Must be viewing a room");
|
if (!currentRoomAlias) {
|
||||||
return;
|
sendError(event, "Must be viewing a room");
|
||||||
}
|
return;
|
||||||
if (roomId !== currentRoomId) {
|
}
|
||||||
sendError(event, "Room " + roomId + " not visible");
|
// no room ID but there is an alias, look it up.
|
||||||
return;
|
console.log("Looking up alias " + currentRoomAlias);
|
||||||
|
promise = MatrixClientPeg.get().getRoomIdForAlias(currentRoomAlias).then((res) => {
|
||||||
|
return res.room_id;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getting join rules does not require userId
|
promise.then((viewingRoomId) => {
|
||||||
if (event.data.action === "join_rules_state") {
|
if (roomId !== viewingRoomId) {
|
||||||
getJoinRules(event, roomId);
|
sendError(event, "Room " + roomId + " not visible");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!userId) {
|
// Getting join rules does not require userId
|
||||||
sendError(event, "Missing user_id in request");
|
if (event.data.action === "join_rules_state") {
|
||||||
return;
|
getJoinRules(event, roomId);
|
||||||
}
|
return;
|
||||||
switch (event.data.action) {
|
}
|
||||||
case "membership_state":
|
|
||||||
getMembershipState(event, roomId, userId);
|
if (!userId) {
|
||||||
break;
|
sendError(event, "Missing user_id in request");
|
||||||
case "invite":
|
return;
|
||||||
inviteUser(event, roomId, userId);
|
}
|
||||||
break;
|
switch (event.data.action) {
|
||||||
case "bot_options":
|
case "membership_state":
|
||||||
botOptions(event, roomId, userId);
|
getMembershipState(event, roomId, userId);
|
||||||
break;
|
break;
|
||||||
case "set_bot_options":
|
case "invite":
|
||||||
setBotOptions(event, roomId, userId);
|
inviteUser(event, roomId, userId);
|
||||||
break;
|
break;
|
||||||
case "set_bot_power":
|
case "bot_options":
|
||||||
setBotPower(event, roomId, userId, event.data.level);
|
botOptions(event, roomId, userId);
|
||||||
break;
|
break;
|
||||||
default:
|
case "set_bot_options":
|
||||||
console.warn("Unhandled postMessage event with action '" + event.data.action +"'");
|
setBotOptions(event, roomId, userId);
|
||||||
break;
|
break;
|
||||||
}
|
case "set_bot_power":
|
||||||
|
setBotPower(event, roomId, userId, event.data.level);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn("Unhandled postMessage event with action '" + event.data.action +"'");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}, (err) => {
|
||||||
|
console.error(err);
|
||||||
|
sendError(event, "Failed to lookup current room.");
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
|
@ -6,6 +6,7 @@ var MatrixClientPeg = require("./MatrixClientPeg");
|
||||||
var SignupStages = require("./SignupStages");
|
var SignupStages = require("./SignupStages");
|
||||||
var dis = require("./dispatcher");
|
var dis = require("./dispatcher");
|
||||||
var q = require("q");
|
var q = require("q");
|
||||||
|
var url = require("url");
|
||||||
|
|
||||||
const EMAIL_STAGE_TYPE = "m.login.email.identity";
|
const EMAIL_STAGE_TYPE = "m.login.email.identity";
|
||||||
|
|
||||||
|
@ -413,6 +414,15 @@ class Login extends Signup {
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
redirectToCas() {
|
||||||
|
var client = this._createTemporaryClient();
|
||||||
|
var parsedUrl = url.parse(window.location.href, true);
|
||||||
|
parsedUrl.query["homeserver"] = client.getHomeserverUrl();
|
||||||
|
parsedUrl.query["identityServer"] = client.getIdentityServerUrl();
|
||||||
|
var casUrl = client.getCasLoginUrl(url.format(parsedUrl));
|
||||||
|
window.location.href = casUrl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.Register = Register;
|
module.exports.Register = Register;
|
||||||
|
|
|
@ -92,6 +92,10 @@ module.exports = React.createClass({
|
||||||
}).done();
|
}).done();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onCasLogin: function() {
|
||||||
|
this._loginLogic.redirectToCas();
|
||||||
|
},
|
||||||
|
|
||||||
_onLoginAsGuestClick: function() {
|
_onLoginAsGuestClick: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
self.setState({
|
self.setState({
|
||||||
|
@ -228,7 +232,7 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
case 'm.login.cas':
|
case 'm.login.cas':
|
||||||
return (
|
return (
|
||||||
<CasLogin />
|
<CasLogin onSubmit={this.onCasLogin} />
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
if (!step) {
|
if (!step) {
|
||||||
|
|
|
@ -16,26 +16,19 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
var url = require("url");
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'CasLogin',
|
displayName: 'CasLogin',
|
||||||
|
|
||||||
onCasClicked: function(ev) {
|
propTypes: {
|
||||||
var cli = MatrixClientPeg.get();
|
onSubmit: React.PropTypes.func, // fn()
|
||||||
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;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<button onClick={this.onCasClicked}>Sign in with CAS</button>
|
<button onClick={this.props.onSubmit}>Sign in with CAS</button>
|
||||||
</div>
|
</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 (
|
return (
|
||||||
<div className="mx_MemberDeviceInfo">
|
<div className="mx_MemberDeviceInfo" title={this.props.device.deviceId}>
|
||||||
<div className="mx_MemberDeviceInfo_deviceId">{deviceName}</div>
|
<div className="mx_MemberDeviceInfo_deviceId">{deviceName}</div>
|
||||||
{indicator}
|
{indicator}
|
||||||
|
<div className="mx_MemberDeviceInfo_deviceKey">
|
||||||
|
{this.props.device.getFingerprint()}
|
||||||
|
</div>
|
||||||
{verifyButton}
|
{verifyButton}
|
||||||
{blockButton}
|
{blockButton}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -26,12 +26,16 @@ limitations under the License.
|
||||||
* 'isTargetMod': boolean
|
* 'isTargetMod': boolean
|
||||||
*/
|
*/
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
|
var classNames = require('classnames');
|
||||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||||
var dis = require("../../../dispatcher");
|
var dis = require("../../../dispatcher");
|
||||||
var Modal = require("../../../Modal");
|
var Modal = require("../../../Modal");
|
||||||
var sdk = require('../../../index');
|
var sdk = require('../../../index');
|
||||||
var UserSettingsStore = require('../../../UserSettingsStore');
|
var UserSettingsStore = require('../../../UserSettingsStore');
|
||||||
var createRoom = require('../../../createRoom');
|
var createRoom = require('../../../createRoom');
|
||||||
|
var DMRoomMap = require('../../../utils/DMRoomMap');
|
||||||
|
var Unread = require('../../../Unread');
|
||||||
|
var Receipt = require('../../../utils/Receipt');
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'MemberInfo',
|
displayName: 'MemberInfo',
|
||||||
|
@ -60,7 +64,6 @@ module.exports = React.createClass({
|
||||||
updating: 0,
|
updating: 0,
|
||||||
devicesLoading: true,
|
devicesLoading: true,
|
||||||
devices: null,
|
devices: null,
|
||||||
existingOneToOneRoomId: null,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -72,14 +75,20 @@ module.exports = React.createClass({
|
||||||
this._enableDevices = MatrixClientPeg.get().isCryptoEnabled() &&
|
this._enableDevices = MatrixClientPeg.get().isCryptoEnabled() &&
|
||||||
UserSettingsStore.isFeatureEnabled("e2e_encryption");
|
UserSettingsStore.isFeatureEnabled("e2e_encryption");
|
||||||
|
|
||||||
this.setState({
|
const cli = MatrixClientPeg.get();
|
||||||
existingOneToOneRoomId: this.getExistingOneToOneRoomId()
|
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() {
|
componentDidMount: function() {
|
||||||
this._updateStateForNewMember(this.props.member);
|
this._updateStateForNewMember(this.props.member);
|
||||||
MatrixClientPeg.get().on("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillReceiveProps: function(newProps) {
|
componentWillReceiveProps: function(newProps) {
|
||||||
|
@ -92,65 +101,20 @@ module.exports = React.createClass({
|
||||||
var client = MatrixClientPeg.get();
|
var client = MatrixClientPeg.get();
|
||||||
if (client) {
|
if (client) {
|
||||||
client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
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) {
|
if (this._cancelDeviceList) {
|
||||||
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) {
|
onDeviceVerificationChanged: function(userId, device) {
|
||||||
if (!this._enableDevices) {
|
if (!this._enableDevices) {
|
||||||
return;
|
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) {
|
_updateStateForNewMember: function(member) {
|
||||||
var newState = this._calculateOpsPermissions(member);
|
var newState = this._calculateOpsPermissions(member);
|
||||||
newState.devicesLoading = true;
|
newState.devicesLoading = true;
|
||||||
|
@ -416,33 +419,16 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onChatClick: function() {
|
onNewDMClick: function() {
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
this.setState({ updating: this.state.updating + 1 });
|
||||||
|
createRoom({
|
||||||
// TODO: keep existingOneToOneRoomId updated if we see any room member changes anywhere
|
createOpts: {
|
||||||
|
invite: [this.props.member.userId],
|
||||||
const useExistingOneToOneRoom = this.state.existingOneToOneRoomId && (this.state.existingOneToOneRoomId !== this.props.member.roomId);
|
},
|
||||||
|
}).finally(() => {
|
||||||
// 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,
|
|
||||||
});
|
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
}
|
this.setState({ updating: this.state.updating - 1 });
|
||||||
else {
|
}).done();
|
||||||
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();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onLeaveClick: function() {
|
onLeaveClick: function() {
|
||||||
|
@ -583,24 +569,50 @@ module.exports = React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
var startChat, kickButton, banButton, muteButton, giveModButton, spinner;
|
var startChat, kickButton, banButton, muteButton, giveModButton, spinner;
|
||||||
if (this.props.member.userId !== MatrixClientPeg.get().credentials.userId) {
|
if (this.props.member.userId !== MatrixClientPeg.get().credentials.userId) {
|
||||||
// FIXME: we're referring to a vector component from react-sdk
|
const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
|
||||||
var BottomLeftMenuTile = sdk.getComponent('rooms.BottomLeftMenuTile');
|
const dmRooms = dmRoomMap.getDMRoomsForUserId(this.props.member.userId);
|
||||||
|
|
||||||
var label;
|
const RoomTile = sdk.getComponent("rooms.RoomTile");
|
||||||
if (this.state.existingOneToOneRoomId) {
|
|
||||||
if (this.state.existingOneToOneRoomId == this.props.member.roomId) {
|
const tiles = [];
|
||||||
label = "Start new direct chat";
|
for (const roomId of dmRooms) {
|
||||||
}
|
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||||
else {
|
if (room) {
|
||||||
label = "Go to direct chat";
|
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"
|
const labelClasses = classNames({
|
||||||
label={ label } onClick={ this.onChatClick }/>
|
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) {
|
if (this.state.updating) {
|
||||||
|
|
|
@ -31,7 +31,7 @@ import {Editor, EditorState, RichUtils, CompositeDecorator,
|
||||||
convertFromRaw, convertToRaw, Modifier, EditorChangeType,
|
convertFromRaw, convertToRaw, Modifier, EditorChangeType,
|
||||||
getDefaultKeyBinding, KeyBindingUtil, ContentState, ContentBlock, SelectionState} from 'draft-js';
|
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 classNames from 'classnames';
|
||||||
import escape from 'lodash/escape';
|
import escape from 'lodash/escape';
|
||||||
|
|
||||||
|
@ -51,6 +51,16 @@ const TYPING_USER_TIMEOUT = 10000, TYPING_SERVER_TIMEOUT = 30000;
|
||||||
|
|
||||||
const KEY_M = 77;
|
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>
|
// FIXME Breaks markdown with multiple paragraphs, since it only strips first and last <p>
|
||||||
function mdownToHtml(mdown: string): string {
|
function mdownToHtml(mdown: string): string {
|
||||||
let html = marked(mdown) || "";
|
let html = marked(mdown) || "";
|
||||||
|
@ -480,7 +490,7 @@ export default class MessageComposerInput extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (cmd.promise) {
|
if (cmd.promise) {
|
||||||
cmd.promise.done(function() {
|
cmd.promise.then(function() {
|
||||||
console.log("Command success.");
|
console.log("Command success.");
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
console.error("Command failure: %s", err);
|
console.error("Command failure: %s", err);
|
||||||
|
@ -520,7 +530,7 @@ export default class MessageComposerInput extends React.Component {
|
||||||
this.sentHistory.push(contentHTML);
|
this.sentHistory.push(contentHTML);
|
||||||
let sendMessagePromise = sendFn.call(this.client, this.props.room.roomId, contentText, contentHTML);
|
let sendMessagePromise = sendFn.call(this.client, this.props.room.roomId, contentText, contentHTML);
|
||||||
|
|
||||||
sendMessagePromise.done(() => {
|
sendMessagePromise.then(() => {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'message_sent'
|
action: 'message_sent'
|
||||||
});
|
});
|
||||||
|
|
|
@ -27,6 +27,7 @@ var sdk = require('../../../index');
|
||||||
var rate_limited_func = require('../../../ratelimitedfunc');
|
var rate_limited_func = require('../../../ratelimitedfunc');
|
||||||
var Rooms = require('../../../Rooms');
|
var Rooms = require('../../../Rooms');
|
||||||
var DMRoomMap = require('../../../utils/DMRoomMap');
|
var DMRoomMap = require('../../../utils/DMRoomMap');
|
||||||
|
var Receipt = require('../../../utils/Receipt');
|
||||||
|
|
||||||
var HIDE_CONFERENCE_CHANS = true;
|
var HIDE_CONFERENCE_CHANS = true;
|
||||||
|
|
||||||
|
@ -156,13 +157,8 @@ module.exports = React.createClass({
|
||||||
onRoomReceipt: function(receiptEvent, room) {
|
onRoomReceipt: function(receiptEvent, room) {
|
||||||
// because if we read a notification, it will affect notification count
|
// because if we read a notification, it will affect notification count
|
||||||
// only bother updating if there's a receipt from us
|
// only bother updating if there's a receipt from us
|
||||||
var receiptKeys = Object.keys(receiptEvent.getContent());
|
if (Receipt.findReadReceiptFromUserId(receiptEvent, MatrixClientPeg.get().credentials.userId)) {
|
||||||
for (var i = 0; i < receiptKeys.length; ++i) {
|
this._delayedRefreshRoomList();
|
||||||
var rcpt = receiptEvent.getContent()[receiptKeys[i]];
|
|
||||||
if (rcpt['m.read'] && rcpt['m.read'][MatrixClientPeg.get().credentials.userId]) {
|
|
||||||
this._delayedRefreshRoomList();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -235,10 +231,6 @@ module.exports = React.createClass({
|
||||||
else if (HIDE_CONFERENCE_CHANS && Rooms.isConfCallRoom(room, me, self.props.ConferenceHandler)) {
|
else if (HIDE_CONFERENCE_CHANS && Rooms.isConfCallRoom(room, me, self.props.ConferenceHandler)) {
|
||||||
// skip past this room & don't put it in any lists
|
// 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" ||
|
else if (me.membership == "join" || me.membership === "ban" ||
|
||||||
(me.membership === "leave" && me.events.member.getSender() !== me.events.member.getStateKey()))
|
(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);
|
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 {
|
else {
|
||||||
s.lists["im.vector.fake.recent"].push(room);
|
s.lists["im.vector.fake.recent"].push(room);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ var ReactDOM = require("react-dom");
|
||||||
var classNames = require('classnames');
|
var classNames = require('classnames');
|
||||||
var dis = require("../../../dispatcher");
|
var dis = require("../../../dispatcher");
|
||||||
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||||
|
var DMRoomMap = require('../../../utils/DMRoomMap');
|
||||||
var sdk = require('../../../index');
|
var sdk = require('../../../index');
|
||||||
var ContextualMenu = require('../../structures/ContextualMenu');
|
var ContextualMenu = require('../../structures/ContextualMenu');
|
||||||
var RoomNotifs = require('../../../RoomNotifs');
|
var RoomNotifs = require('../../../RoomNotifs');
|
||||||
|
@ -29,10 +30,9 @@ module.exports = React.createClass({
|
||||||
displayName: 'RoomTile',
|
displayName: 'RoomTile',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
// TODO: We should *optionally* support DND stuff and ideally be impl agnostic about it
|
connectDragSource: React.PropTypes.func,
|
||||||
connectDragSource: React.PropTypes.func.isRequired,
|
connectDropTarget: React.PropTypes.func,
|
||||||
connectDropTarget: React.PropTypes.func.isRequired,
|
isDragging: React.PropTypes.bool,
|
||||||
isDragging: React.PropTypes.bool.isRequired,
|
|
||||||
|
|
||||||
room: React.PropTypes.object.isRequired,
|
room: React.PropTypes.object.isRequired,
|
||||||
collapsed: React.PropTypes.bool.isRequired,
|
collapsed: React.PropTypes.bool.isRequired,
|
||||||
|
@ -40,11 +40,15 @@ module.exports = React.createClass({
|
||||||
unread: React.PropTypes.bool.isRequired,
|
unread: React.PropTypes.bool.isRequired,
|
||||||
highlight: React.PropTypes.bool.isRequired,
|
highlight: React.PropTypes.bool.isRequired,
|
||||||
isInvite: React.PropTypes.bool.isRequired,
|
isInvite: React.PropTypes.bool.isRequired,
|
||||||
roomSubList: React.PropTypes.object.isRequired,
|
|
||||||
refreshSubList: React.PropTypes.func.isRequired,
|
|
||||||
incomingCall: React.PropTypes.object,
|
incomingCall: React.PropTypes.object,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
isDragging: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return({
|
return({
|
||||||
hover : false,
|
hover : false,
|
||||||
|
@ -64,6 +68,16 @@ module.exports = React.createClass({
|
||||||
return this.state.notifState != RoomNotifs.MUTE;
|
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) {
|
onAccountData: function(accountDataEvent) {
|
||||||
if (accountDataEvent.getType() == 'm.push_rules') {
|
if (accountDataEvent.getType() == 'm.push_rules') {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -261,18 +275,24 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
var RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
|
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,
|
// These props are injected by React DnD,
|
||||||
// as defined by your `collect` function above:
|
// as defined by your `collect` function above:
|
||||||
var isDragging = this.props.isDragging;
|
var isDragging = this.props.isDragging;
|
||||||
var connectDragSource = this.props.connectDragSource;
|
var connectDragSource = this.props.connectDragSource;
|
||||||
var connectDropTarget = this.props.connectDropTarget;
|
var connectDropTarget = this.props.connectDropTarget;
|
||||||
|
|
||||||
return connectDragSource(connectDropTarget(
|
let ret = (
|
||||||
<div className={classes} onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
<div className={classes} onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||||
<div className={avatarClasses}>
|
<div className={avatarClasses}>
|
||||||
<div className="mx_RoomTile_avatar_menu" onClick={this.onAvatarClicked}>
|
<div className="mx_RoomTile_avatar_menu" onClick={this.onAvatarClicked}>
|
||||||
<div className={avatarContainerClasses}>
|
<div className={avatarContainerClasses}>
|
||||||
<RoomAvatar room={this.props.room} width={24} height={24} />
|
<RoomAvatar room={this.props.room} width={24} height={24} />
|
||||||
|
{directMessageIndicator}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -283,6 +303,11 @@ module.exports = React.createClass({
|
||||||
{ incomingCallBox }
|
{ incomingCallBox }
|
||||||
{ tooltip }
|
{ tooltip }
|
||||||
</div>
|
</div>
|
||||||
));
|
);
|
||||||
|
|
||||||
|
if (connectDropTarget) ret = connectDropTarget(ret);
|
||||||
|
if (connectDragSource) ret = connectDragSource(ret);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,26 +21,43 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
export default class DMRoomMap {
|
export default class DMRoomMap {
|
||||||
constructor(matrixClient) {
|
constructor(matrixClient) {
|
||||||
|
this.roomToUser = null;
|
||||||
|
|
||||||
const mDirectEvent = matrixClient.getAccountData('m.direct');
|
const mDirectEvent = matrixClient.getAccountData('m.direct');
|
||||||
if (!mDirectEvent) {
|
if (!mDirectEvent) {
|
||||||
this.userToRooms = {};
|
this.userToRooms = {};
|
||||||
this.roomToUser = {};
|
|
||||||
} else {
|
} else {
|
||||||
this.userToRooms = mDirectEvent.getContent();
|
this.userToRooms = mDirectEvent.getContent();
|
||||||
this.roomToUser = {};
|
|
||||||
for (const user of Object.keys(this.userToRooms)) {
|
|
||||||
for (const roomId of this.userToRooms[user]) {
|
|
||||||
this.roomToUser[roomId] = user;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getDMRoomsForUserId(userId) {
|
getDMRoomsForUserId(userId) {
|
||||||
return this.userToRooms[userId];
|
// Here, we return the empty list if there are no rooms,
|
||||||
|
// since the number of conversations you have with this user is zero.
|
||||||
|
return this.userToRooms[userId] || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserIdForRoomId(roomId) {
|
getUserIdForRoomId(roomId) {
|
||||||
|
if (this.roomToUser == null) {
|
||||||
|
// we lazily populate roomToUser so you can use
|
||||||
|
// this class just to call getDMRoomsForUserId
|
||||||
|
// which doesn't do very much, but is a fairly
|
||||||
|
// convenient wrapper and there's no point
|
||||||
|
// iterating through the map if getUserIdForRoomId()
|
||||||
|
// is never called.
|
||||||
|
this._populateRoomToUser();
|
||||||
|
}
|
||||||
|
// Here, we return undefined if the room is not in the map:
|
||||||
|
// the room ID you gave is not a DM room for any user.
|
||||||
return this.roomToUser[roomId];
|
return this.roomToUser[roomId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_populateRoomToUser() {
|
||||||
|
this.roomToUser = {};
|
||||||
|
for (const user of Object.keys(this.userToRooms)) {
|
||||||
|
for (const roomId of this.userToRooms[user]) {
|
||||||
|
this.roomToUser[roomId] = user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
32
src/utils/Receipt.js
Normal file
32
src/utils/Receipt.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 OpenMarket 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given MatrixEvent containing receipts, return the first
|
||||||
|
* read receipt from the given user ID, or null if no such
|
||||||
|
* receipt exists.
|
||||||
|
*/
|
||||||
|
export function findReadReceiptFromUserId(receiptEvent, userId) {
|
||||||
|
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'][userId]) {
|
||||||
|
return rcpt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
144
test/components/views/rooms/MessageComposerInput-test.js
Normal file
144
test/components/views/rooms/MessageComposerInput-test.js
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ReactTestUtils from 'react-addons-test-utils';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import expect, {createSpy} from 'expect';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
import Q from 'q';
|
||||||
|
import * as testUtils from '../../../test-utils';
|
||||||
|
import sdk from 'matrix-react-sdk';
|
||||||
|
import UserSettingsStore from '../../../../src/UserSettingsStore';
|
||||||
|
const MessageComposerInput = sdk.getComponent('views.rooms.MessageComposerInput');
|
||||||
|
import MatrixClientPeg from 'MatrixClientPeg';
|
||||||
|
|
||||||
|
function addTextToDraft(text) {
|
||||||
|
const components = document.getElementsByClassName('public-DraftEditor-content');
|
||||||
|
if (components && components.length) {
|
||||||
|
const textarea = components[0];
|
||||||
|
const textEvent = document.createEvent('TextEvent');
|
||||||
|
textEvent.initTextEvent('textInput', true, true, null, text);
|
||||||
|
textarea.dispatchEvent(textEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('MessageComposerInput', () => {
|
||||||
|
let parentDiv = null,
|
||||||
|
sandbox = null,
|
||||||
|
client = null,
|
||||||
|
mci = null,
|
||||||
|
room = testUtils.mkStubRoom('!DdJkzRliezrwpNebLk:matrix.org');
|
||||||
|
|
||||||
|
// TODO Remove when RTE is out of labs.
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
sandbox = testUtils.stubClient(sandbox);
|
||||||
|
client = MatrixClientPeg.get();
|
||||||
|
UserSettingsStore.isFeatureEnabled = sinon.stub()
|
||||||
|
.withArgs('rich_text_editor').returns(true);
|
||||||
|
|
||||||
|
parentDiv = document.createElement('div');
|
||||||
|
document.body.appendChild(parentDiv);
|
||||||
|
mci = ReactDOM.render(
|
||||||
|
<MessageComposerInput
|
||||||
|
room={room}
|
||||||
|
client={client}
|
||||||
|
/>,
|
||||||
|
parentDiv);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (parentDiv) {
|
||||||
|
ReactDOM.unmountComponentAtNode(parentDiv);
|
||||||
|
parentDiv.remove();
|
||||||
|
parentDiv = null;
|
||||||
|
}
|
||||||
|
sandbox.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should change mode if indicator is clicked', () => {
|
||||||
|
mci.enableRichtext(true);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const indicator = ReactTestUtils.findRenderedDOMComponentWithClass(
|
||||||
|
mci,
|
||||||
|
'mx_MessageComposer_input_markdownIndicator');
|
||||||
|
ReactTestUtils.Simulate.click(indicator);
|
||||||
|
|
||||||
|
expect(mci.state.isRichtextEnabled).toEqual(false, 'should have changed mode');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not send messages when composer is empty', () => {
|
||||||
|
const spy = sinon.spy(client, 'sendHtmlMessage');
|
||||||
|
mci.enableRichtext(true);
|
||||||
|
mci.handleReturn(sinon.stub());
|
||||||
|
|
||||||
|
expect(spy.calledOnce).toEqual(false, 'should not send message');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not change content unnecessarily on RTE -> Markdown conversion', () => {
|
||||||
|
const spy = sinon.spy(client, 'sendHtmlMessage');
|
||||||
|
mci.enableRichtext(true);
|
||||||
|
addTextToDraft('a');
|
||||||
|
mci.handleKeyCommand('toggle-mode');
|
||||||
|
mci.handleReturn(sinon.stub());
|
||||||
|
|
||||||
|
expect(spy.calledOnce).toEqual(true);
|
||||||
|
expect(spy.args[0][1]).toEqual('a');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not change content unnecessarily on Markdown -> RTE conversion', () => {
|
||||||
|
const spy = sinon.spy(client, 'sendHtmlMessage');
|
||||||
|
mci.enableRichtext(false);
|
||||||
|
addTextToDraft('a');
|
||||||
|
mci.handleKeyCommand('toggle-mode');
|
||||||
|
mci.handleReturn(sinon.stub());
|
||||||
|
expect(spy.calledOnce).toEqual(true);
|
||||||
|
expect(spy.args[0][1]).toEqual('a');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should send emoji messages in rich text', () => {
|
||||||
|
const spy = sinon.spy(client, 'sendHtmlMessage');
|
||||||
|
mci.enableRichtext(true);
|
||||||
|
addTextToDraft('☹');
|
||||||
|
mci.handleReturn(sinon.stub());
|
||||||
|
|
||||||
|
expect(spy.calledOnce).toEqual(true, 'should send message');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should send emoji messages in Markdown', () => {
|
||||||
|
const spy = sinon.spy(client, 'sendHtmlMessage');
|
||||||
|
mci.enableRichtext(false);
|
||||||
|
addTextToDraft('☹');
|
||||||
|
mci.handleReturn(sinon.stub());
|
||||||
|
|
||||||
|
expect(spy.calledOnce).toEqual(true, 'should send message');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert basic Markdown to rich text correctly', () => {
|
||||||
|
const spy = sinon.spy(client, 'sendHtmlMessage');
|
||||||
|
mci.enableRichtext(false);
|
||||||
|
addTextToDraft('*abc*');
|
||||||
|
mci.handleKeyCommand('toggle-mode');
|
||||||
|
mci.handleReturn(sinon.stub());
|
||||||
|
expect(spy.args[0][2]).toContain('<em>abc');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert basic rich text to Markdown correctly', () => {
|
||||||
|
const spy = sinon.spy(client, 'sendHtmlMessage');
|
||||||
|
mci.enableRichtext(true);
|
||||||
|
mci.handleKeyCommand('italic');
|
||||||
|
addTextToDraft('abc');
|
||||||
|
mci.handleKeyCommand('toggle-mode');
|
||||||
|
mci.handleReturn(sinon.stub());
|
||||||
|
expect(['_abc_', '*abc*']).toContain(spy.args[0][1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should insert formatting characters in Markdown mode', () => {
|
||||||
|
const spy = sinon.spy(client, 'sendHtmlMessage');
|
||||||
|
mci.enableRichtext(false);
|
||||||
|
mci.handleKeyCommand('italic');
|
||||||
|
mci.handleReturn(sinon.stub());
|
||||||
|
expect(['__', '**']).toContain(spy.args[0][1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -12,7 +12,7 @@ var MatrixEvent = jssdk.MatrixEvent;
|
||||||
* name to stdout.
|
* name to stdout.
|
||||||
* @param {Mocha.Context} context The test context
|
* @param {Mocha.Context} context The test context
|
||||||
*/
|
*/
|
||||||
module.exports.beforeEach = function(context) {
|
export function beforeEach(context) {
|
||||||
var desc = context.currentTest.fullTitle();
|
var desc = context.currentTest.fullTitle();
|
||||||
console.log();
|
console.log();
|
||||||
console.log(desc);
|
console.log(desc);
|
||||||
|
@ -26,7 +26,7 @@ module.exports.beforeEach = function(context) {
|
||||||
*
|
*
|
||||||
* @returns {sinon.Sandbox}; remember to call sandbox.restore afterwards.
|
* @returns {sinon.Sandbox}; remember to call sandbox.restore afterwards.
|
||||||
*/
|
*/
|
||||||
module.exports.stubClient = function() {
|
export function stubClient() {
|
||||||
var sandbox = sinon.sandbox.create();
|
var sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
var client = {
|
var client = {
|
||||||
|
@ -44,6 +44,16 @@ module.exports.stubClient = function() {
|
||||||
sendReadReceipt: sinon.stub().returns(q()),
|
sendReadReceipt: sinon.stub().returns(q()),
|
||||||
getRoomIdForAlias: sinon.stub().returns(q()),
|
getRoomIdForAlias: sinon.stub().returns(q()),
|
||||||
getProfileInfo: sinon.stub().returns(q({})),
|
getProfileInfo: sinon.stub().returns(q({})),
|
||||||
|
getAccountData: (type) => {
|
||||||
|
return mkEvent({
|
||||||
|
type,
|
||||||
|
event: true,
|
||||||
|
content: {},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setAccountData: sinon.stub(),
|
||||||
|
sendTyping: sinon.stub().returns(q({})),
|
||||||
|
sendHtmlMessage: () => q({}),
|
||||||
};
|
};
|
||||||
|
|
||||||
// stub out the methods in MatrixClientPeg
|
// stub out the methods in MatrixClientPeg
|
||||||
|
@ -73,7 +83,7 @@ module.exports.stubClient = function() {
|
||||||
* @param {boolean} opts.event True to make a MatrixEvent.
|
* @param {boolean} opts.event True to make a MatrixEvent.
|
||||||
* @return {Object} a JSON object representing this event.
|
* @return {Object} a JSON object representing this event.
|
||||||
*/
|
*/
|
||||||
module.exports.mkEvent = function(opts) {
|
export function mkEvent(opts) {
|
||||||
if (!opts.type || !opts.content) {
|
if (!opts.type || !opts.content) {
|
||||||
throw new Error("Missing .type or .content =>" + JSON.stringify(opts));
|
throw new Error("Missing .type or .content =>" + JSON.stringify(opts));
|
||||||
}
|
}
|
||||||
|
@ -101,7 +111,7 @@ module.exports.mkEvent = function(opts) {
|
||||||
* @param {Object} opts Values for the presence.
|
* @param {Object} opts Values for the presence.
|
||||||
* @return {Object|MatrixEvent} The event
|
* @return {Object|MatrixEvent} The event
|
||||||
*/
|
*/
|
||||||
module.exports.mkPresence = function(opts) {
|
export function mkPresence(opts) {
|
||||||
if (!opts.user) {
|
if (!opts.user) {
|
||||||
throw new Error("Missing user");
|
throw new Error("Missing user");
|
||||||
}
|
}
|
||||||
|
@ -132,7 +142,7 @@ module.exports.mkPresence = function(opts) {
|
||||||
* @param {boolean} opts.event True to make a MatrixEvent.
|
* @param {boolean} opts.event True to make a MatrixEvent.
|
||||||
* @return {Object|MatrixEvent} The event
|
* @return {Object|MatrixEvent} The event
|
||||||
*/
|
*/
|
||||||
module.exports.mkMembership = function(opts) {
|
export function mkMembership(opts) {
|
||||||
opts.type = "m.room.member";
|
opts.type = "m.room.member";
|
||||||
if (!opts.skey) {
|
if (!opts.skey) {
|
||||||
opts.skey = opts.user;
|
opts.skey = opts.user;
|
||||||
|
@ -145,7 +155,7 @@ module.exports.mkMembership = function(opts) {
|
||||||
};
|
};
|
||||||
if (opts.name) { opts.content.displayname = opts.name; }
|
if (opts.name) { opts.content.displayname = opts.name; }
|
||||||
if (opts.url) { opts.content.avatar_url = opts.url; }
|
if (opts.url) { opts.content.avatar_url = opts.url; }
|
||||||
return module.exports.mkEvent(opts);
|
return mkEvent(opts);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -157,7 +167,7 @@ module.exports.mkMembership = function(opts) {
|
||||||
* @param {boolean} opts.event True to make a MatrixEvent.
|
* @param {boolean} opts.event True to make a MatrixEvent.
|
||||||
* @return {Object|MatrixEvent} The event
|
* @return {Object|MatrixEvent} The event
|
||||||
*/
|
*/
|
||||||
module.exports.mkMessage = function(opts) {
|
export function mkMessage(opts) {
|
||||||
opts.type = "m.room.message";
|
opts.type = "m.room.message";
|
||||||
if (!opts.msg) {
|
if (!opts.msg) {
|
||||||
opts.msg = "Random->" + Math.random();
|
opts.msg = "Random->" + Math.random();
|
||||||
|
@ -169,11 +179,12 @@ module.exports.mkMessage = function(opts) {
|
||||||
msgtype: "m.text",
|
msgtype: "m.text",
|
||||||
body: opts.msg
|
body: opts.msg
|
||||||
};
|
};
|
||||||
return module.exports.mkEvent(opts);
|
return mkEvent(opts);
|
||||||
};
|
}
|
||||||
|
|
||||||
module.exports.mkStubRoom = function() {
|
export function mkStubRoom(roomId = null) {
|
||||||
return {
|
return {
|
||||||
|
roomId,
|
||||||
getReceiptsForEvent: sinon.stub().returns([]),
|
getReceiptsForEvent: sinon.stub().returns([]),
|
||||||
getMember: sinon.stub().returns({}),
|
getMember: sinon.stub().returns({}),
|
||||||
getJoinedMembers: sinon.stub().returns([]),
|
getJoinedMembers: sinon.stub().returns([]),
|
||||||
|
@ -182,4 +193,4 @@ module.exports.mkStubRoom = function() {
|
||||||
members: [],
|
members: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue