mirror of https://github.com/vector-im/riot-web
Merge pull request #2276 from matrix-org/bwindels/timelinetypingnotif
Redesign: typing notifications in timelinepull/21833/head
commit
04bb9aad3a
|
@ -108,6 +108,7 @@
|
||||||
@import "./views/rooms/_SearchableEntityList.scss";
|
@import "./views/rooms/_SearchableEntityList.scss";
|
||||||
@import "./views/rooms/_Stickers.scss";
|
@import "./views/rooms/_Stickers.scss";
|
||||||
@import "./views/rooms/_TopUnreadMessagesBar.scss";
|
@import "./views/rooms/_TopUnreadMessagesBar.scss";
|
||||||
|
@import "./views/rooms/_WhoIsTypingTile.scss";
|
||||||
@import "./views/settings/_DevicesPanel.scss";
|
@import "./views/settings/_DevicesPanel.scss";
|
||||||
@import "./views/settings/_IntegrationsManager.scss";
|
@import "./views/settings/_IntegrationsManager.scss";
|
||||||
@import "./views/settings/_Notifications.scss";
|
@import "./views/settings/_Notifications.scss";
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_WhoIsTypingTile {
|
||||||
|
margin-left: -18px; //offset padding from mx_RoomView_MessageList to center avatars
|
||||||
|
padding-top: 18px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* position the indicator in the same place horizontally as .mx_EventTile_avatar. */
|
||||||
|
.mx_WhoIsTypingTile_avatars {
|
||||||
|
flex: 0 0 83px; // 18 + 65
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_WhoIsTypingTile_avatars > :not(:first-child) {
|
||||||
|
margin-left: -12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_WhoIsTypingTile_avatars .mx_BaseAvatar_image {
|
||||||
|
border: 1px solid $primary-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_WhoIsTypingTile_avatars .mx_BaseAvatar_initial {
|
||||||
|
padding-top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_WhoIsTypingTile_remainingAvatarPlaceholder {
|
||||||
|
display: inline-block;
|
||||||
|
color: #acacac;
|
||||||
|
background-color: #ddd;
|
||||||
|
border: 1px solid $primary-bg-color;
|
||||||
|
border-radius: 40px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-size: 0.8em;
|
||||||
|
vertical-align: top;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_WhoIsTypingTile_label {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $eventtile-meta-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_WhoIsTypingTile_label > span {
|
||||||
|
background-image: url('../../img/typing-indicator-2x.gif');
|
||||||
|
background-size: 25px;
|
||||||
|
background-position: left bottom;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_MatrixChat_useCompactLayout {
|
||||||
|
|
||||||
|
.mx_WhoIsTypingTile {
|
||||||
|
padding-top: 4px;
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 4.6 KiB |
|
@ -119,6 +119,7 @@ $topleftmenu-color: #212121;
|
||||||
$roomheader-color: #45474a;
|
$roomheader-color: #45474a;
|
||||||
$roomheader-addroom-color: #929eb4;
|
$roomheader-addroom-color: #929eb4;
|
||||||
$roomtopic-color: #9fa9ba;
|
$roomtopic-color: #9fa9ba;
|
||||||
|
$eventtile-meta-color: $roomtopic-color;
|
||||||
|
|
||||||
// ********************
|
// ********************
|
||||||
|
|
||||||
|
|
|
@ -115,7 +115,7 @@ $topleftmenu-color: $primary-fg-color;
|
||||||
$roomheader-color: $primary-fg-color;
|
$roomheader-color: $primary-fg-color;
|
||||||
$roomheader-addroom-color: $primary-bg-color;
|
$roomheader-addroom-color: $primary-bg-color;
|
||||||
$roomtopic-color: $settings-grey-fg-color;
|
$roomtopic-color: $settings-grey-fg-color;
|
||||||
|
$eventtile-meta-color: $roomtopic-color;
|
||||||
// ********************
|
// ********************
|
||||||
|
|
||||||
$roomtile-name-color: rgba(69, 69, 69, 0.8);
|
$roomtile-name-color: rgba(69, 69, 69, 0.8);
|
||||||
|
|
|
@ -63,16 +63,16 @@ module.exports = {
|
||||||
if (whoIsTyping.length == 0) {
|
if (whoIsTyping.length == 0) {
|
||||||
return '';
|
return '';
|
||||||
} else if (whoIsTyping.length == 1) {
|
} else if (whoIsTyping.length == 1) {
|
||||||
return _t('%(displayName)s is typing', {displayName: whoIsTyping[0].name});
|
return _t('%(displayName)s is typing …', {displayName: whoIsTyping[0].name});
|
||||||
}
|
}
|
||||||
const names = whoIsTyping.map(function(m) {
|
const names = whoIsTyping.map(function(m) {
|
||||||
return m.name;
|
return m.name;
|
||||||
});
|
});
|
||||||
if (othersCount>=1) {
|
if (othersCount>=1) {
|
||||||
return _t('%(names)s and %(count)s others are typing', {names: names.slice(0, limit - 1).join(', '), count: othersCount});
|
return _t('%(names)s and %(count)s others are typing …', {names: names.slice(0, limit - 1).join(', '), count: othersCount});
|
||||||
} else {
|
} else {
|
||||||
const lastPerson = names.pop();
|
const lastPerson = names.pop();
|
||||||
return _t('%(names)s and %(lastPerson)s are typing', {names: names.join(', '), lastPerson: lastPerson});
|
return _t('%(names)s and %(lastPerson)s are typing …', {names: names.join(', '), lastPerson: lastPerson});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -631,12 +631,20 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_scrollDownIfAtBottom: function() {
|
||||||
|
const scrollPanel = this.refs.scrollPanel;
|
||||||
|
if (scrollPanel) {
|
||||||
|
scrollPanel.checkScroll();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
onResize: function() {
|
onResize: function() {
|
||||||
dis.dispatch({ action: 'timeline_resize' }, true);
|
dis.dispatch({ action: 'timeline_resize' }, true);
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
|
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
|
||||||
|
const WhoIsTypingTile = sdk.getComponent("rooms.WhoIsTypingTile");
|
||||||
const Spinner = sdk.getComponent("elements.Spinner");
|
const Spinner = sdk.getComponent("elements.Spinner");
|
||||||
let topSpinner;
|
let topSpinner;
|
||||||
let bottomSpinner;
|
let bottomSpinner;
|
||||||
|
@ -666,6 +674,7 @@ module.exports = React.createClass({
|
||||||
stickyBottom={this.props.stickyBottom}>
|
stickyBottom={this.props.stickyBottom}>
|
||||||
{ topSpinner }
|
{ topSpinner }
|
||||||
{ this._getEventTiles() }
|
{ this._getEventTiles() }
|
||||||
|
<WhoIsTypingTile room={this.props.room} onVisible={this._scrollDownIfAtBottom} />
|
||||||
{ bottomSpinner }
|
{ bottomSpinner }
|
||||||
</ScrollPanel>
|
</ScrollPanel>
|
||||||
);
|
);
|
||||||
|
|
|
@ -62,10 +62,6 @@ module.exports = React.createClass({
|
||||||
// more interesting)
|
// more interesting)
|
||||||
hasActiveCall: PropTypes.bool,
|
hasActiveCall: PropTypes.bool,
|
||||||
|
|
||||||
// Number of names to display in typing indication. E.g. set to 3, will
|
|
||||||
// result in "X, Y, Z and 100 others are typing."
|
|
||||||
whoIsTypingLimit: PropTypes.number,
|
|
||||||
|
|
||||||
// true if the room is being peeked at. This affects components that shouldn't
|
// true if the room is being peeked at. This affects components that shouldn't
|
||||||
// logically be shown when peeking, such as a prompt to invite people to a room.
|
// logically be shown when peeking, such as a prompt to invite people to a room.
|
||||||
isPeeking: PropTypes.bool,
|
isPeeking: PropTypes.bool,
|
||||||
|
@ -103,24 +99,16 @@ module.exports = React.createClass({
|
||||||
onVisible: PropTypes.func,
|
onVisible: PropTypes.func,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
whoIsTypingLimit: 3,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
syncState: MatrixClientPeg.get().getSyncState(),
|
syncState: MatrixClientPeg.get().getSyncState(),
|
||||||
syncStateData: MatrixClientPeg.get().getSyncStateData(),
|
syncStateData: MatrixClientPeg.get().getSyncStateData(),
|
||||||
usersTyping: WhoIsTyping.usersTypingApartFromMe(this.props.room),
|
|
||||||
unsentMessages: getUnsentMessages(this.props.room),
|
unsentMessages: getUnsentMessages(this.props.room),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
MatrixClientPeg.get().on("sync", this.onSyncStateChange);
|
MatrixClientPeg.get().on("sync", this.onSyncStateChange);
|
||||||
MatrixClientPeg.get().on("RoomMember.typing", this.onRoomMemberTyping);
|
|
||||||
MatrixClientPeg.get().on("Room.localEchoUpdated", this._onRoomLocalEchoUpdated);
|
MatrixClientPeg.get().on("Room.localEchoUpdated", this._onRoomLocalEchoUpdated);
|
||||||
|
|
||||||
this._checkSize();
|
this._checkSize();
|
||||||
|
@ -135,7 +123,6 @@ module.exports = React.createClass({
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
if (client) {
|
if (client) {
|
||||||
client.removeListener("sync", this.onSyncStateChange);
|
client.removeListener("sync", this.onSyncStateChange);
|
||||||
client.removeListener("RoomMember.typing", this.onRoomMemberTyping);
|
|
||||||
client.removeListener("Room.localEchoUpdated", this._onRoomLocalEchoUpdated);
|
client.removeListener("Room.localEchoUpdated", this._onRoomLocalEchoUpdated);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -150,12 +137,6 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomMemberTyping: function(ev, member) {
|
|
||||||
this.setState({
|
|
||||||
usersTyping: WhoIsTyping.usersTypingApartFromMeAndIgnored(this.props.room),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_onSendWithoutVerifyingClick: function() {
|
_onSendWithoutVerifyingClick: function() {
|
||||||
cryptodevices.getUnknownDevicesForRoom(MatrixClientPeg.get(), this.props.room).then((devices) => {
|
cryptodevices.getUnknownDevicesForRoom(MatrixClientPeg.get(), this.props.room).then((devices) => {
|
||||||
cryptodevices.markAllDevicesKnown(MatrixClientPeg.get(), devices);
|
cryptodevices.markAllDevicesKnown(MatrixClientPeg.get(), devices);
|
||||||
|
@ -199,7 +180,6 @@ module.exports = React.createClass({
|
||||||
// indicate other sizes.
|
// indicate other sizes.
|
||||||
_getSize: function() {
|
_getSize: function() {
|
||||||
if (this._shouldShowConnectionError() ||
|
if (this._shouldShowConnectionError() ||
|
||||||
(this.state.usersTyping.length > 0) ||
|
|
||||||
this.props.numUnreadMessages ||
|
this.props.numUnreadMessages ||
|
||||||
!this.props.atEndOfLiveTimeline ||
|
!this.props.atEndOfLiveTimeline ||
|
||||||
this.props.hasActiveCall ||
|
this.props.hasActiveCall ||
|
||||||
|
@ -213,10 +193,7 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
// return suitable content for the image on the left of the status bar.
|
// return suitable content for the image on the left of the status bar.
|
||||||
//
|
_getIndicator: function() {
|
||||||
// if wantPlaceholder is true, we include a "..." placeholder if
|
|
||||||
// there is nothing better to put in.
|
|
||||||
_getIndicator: function(wantPlaceholder) {
|
|
||||||
if (this.props.numUnreadMessages) {
|
if (this.props.numUnreadMessages) {
|
||||||
return (
|
return (
|
||||||
<div className="mx_RoomStatusBar_scrollDownIndicator"
|
<div className="mx_RoomStatusBar_scrollDownIndicator"
|
||||||
|
@ -250,49 +227,9 @@ module.exports = React.createClass({
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wantPlaceholder) {
|
|
||||||
return (
|
|
||||||
<div className="mx_RoomStatusBar_typingIndicatorAvatars">
|
|
||||||
{ this._renderTypingIndicatorAvatars(this.props.whoIsTypingLimit) }
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
|
||||||
_renderTypingIndicatorAvatars: function(limit) {
|
|
||||||
let users = this.state.usersTyping;
|
|
||||||
|
|
||||||
let othersCount = 0;
|
|
||||||
if (users.length > limit) {
|
|
||||||
othersCount = users.length - limit + 1;
|
|
||||||
users = users.slice(0, limit - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const avatars = users.map((u) => {
|
|
||||||
return (
|
|
||||||
<MemberAvatar
|
|
||||||
key={u.userId}
|
|
||||||
member={u}
|
|
||||||
width={24}
|
|
||||||
height={24}
|
|
||||||
resizeMethod="crop"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (othersCount > 0) {
|
|
||||||
avatars.push(
|
|
||||||
<span className="mx_RoomStatusBar_typingIndicatorRemaining" key="others">
|
|
||||||
+{ othersCount }
|
|
||||||
</span>,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return avatars;
|
|
||||||
},
|
|
||||||
|
|
||||||
_shouldShowConnectionError: function() {
|
_shouldShowConnectionError: function() {
|
||||||
// no conn bar trumps unread count since you can't get unread messages
|
// no conn bar trumps unread count since you can't get unread messages
|
||||||
// without a connection! (technically may already have some but meh)
|
// without a connection! (technically may already have some but meh)
|
||||||
|
@ -440,18 +377,6 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const typingString = WhoIsTyping.whoIsTypingString(
|
|
||||||
this.state.usersTyping,
|
|
||||||
this.props.whoIsTypingLimit,
|
|
||||||
);
|
|
||||||
if (typingString) {
|
|
||||||
return (
|
|
||||||
<div className="mx_RoomStatusBar_typingBar">
|
|
||||||
<EmojiText>{ typingString }</EmojiText>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.hasActiveCall) {
|
if (this.props.hasActiveCall) {
|
||||||
return (
|
return (
|
||||||
<div className="mx_RoomStatusBar_callBar">
|
<div className="mx_RoomStatusBar_callBar">
|
||||||
|
@ -483,7 +408,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const content = this._getContent();
|
const content = this._getContent();
|
||||||
const indicator = this._getIndicator(this.state.usersTyping.length > 0);
|
const indicator = this._getIndicator();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_RoomStatusBar">
|
<div className="mx_RoomStatusBar">
|
||||||
|
|
|
@ -1613,7 +1613,6 @@ module.exports = React.createClass({
|
||||||
onResize={this.onChildResize}
|
onResize={this.onChildResize}
|
||||||
onVisible={this.onStatusBarVisible}
|
onVisible={this.onStatusBarVisible}
|
||||||
onHidden={this.onStatusBarHidden}
|
onHidden={this.onStatusBarHidden}
|
||||||
whoIsTypingLimit={3}
|
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1154,6 +1154,7 @@ var TimelinePanel = React.createClass({
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<MessagePanel ref="messagePanel"
|
<MessagePanel ref="messagePanel"
|
||||||
|
room={this.props.timelineSet.room}
|
||||||
hidden={this.props.hidden}
|
hidden={this.props.hidden}
|
||||||
backPaginating={this.state.backPaginating}
|
backPaginating={this.state.backPaginating}
|
||||||
forwardPaginating={forwardPaginating}
|
forwardPaginating={forwardPaginating}
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2017, 2018 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 PropTypes from 'prop-types';
|
||||||
|
import sdk from '../../../index';
|
||||||
|
import WhoIsTyping from '../../../WhoIsTyping';
|
||||||
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
|
import MemberAvatar from '../avatars/MemberAvatar';
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
displayName: 'WhoIsTypingTile',
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
// the room this statusbar is representing.
|
||||||
|
room: PropTypes.object.isRequired,
|
||||||
|
onVisible: PropTypes.func,
|
||||||
|
// Number of names to display in typing indication. E.g. set to 3, will
|
||||||
|
// result in "X, Y, Z and 100 others are typing."
|
||||||
|
whoIsTypingLimit: PropTypes.number,
|
||||||
|
},
|
||||||
|
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
whoIsTypingLimit: 3,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
usersTyping: WhoIsTyping.usersTypingApartFromMe(this.props.room),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillMount: function() {
|
||||||
|
MatrixClientPeg.get().on("RoomMember.typing", this.onRoomMemberTyping);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidUpdate: function(_, prevState) {
|
||||||
|
if (this.props.onVisible &&
|
||||||
|
!prevState.usersTyping.length &&
|
||||||
|
this.state.usersTyping.length
|
||||||
|
) {
|
||||||
|
this.props.onVisible();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
// we may have entirely lost our client as we're logging out before clicking login on the guest bar...
|
||||||
|
const client = MatrixClientPeg.get();
|
||||||
|
if (client) {
|
||||||
|
client.removeListener("RoomMember.typing", this.onRoomMemberTyping);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onRoomMemberTyping: function(ev, member) {
|
||||||
|
this.setState({
|
||||||
|
usersTyping: WhoIsTyping.usersTypingApartFromMeAndIgnored(this.props.room),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_renderTypingIndicatorAvatars: function(limit) {
|
||||||
|
let users = this.state.usersTyping;
|
||||||
|
|
||||||
|
let othersCount = 0;
|
||||||
|
if (users.length > limit) {
|
||||||
|
othersCount = users.length - limit + 1;
|
||||||
|
users = users.slice(0, limit - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const avatars = users.map((u) => {
|
||||||
|
return (
|
||||||
|
<MemberAvatar
|
||||||
|
key={u.userId}
|
||||||
|
member={u}
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
resizeMethod="crop"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (othersCount > 0) {
|
||||||
|
avatars.push(
|
||||||
|
<span className="mx_WhoIsTypingTile_remainingAvatarPlaceholder" key="others">
|
||||||
|
+{ othersCount }
|
||||||
|
</span>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return avatars;
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
const typingString = WhoIsTyping.whoIsTypingString(
|
||||||
|
this.state.usersTyping,
|
||||||
|
this.props.whoIsTypingLimit,
|
||||||
|
);
|
||||||
|
if (!typingString) {
|
||||||
|
return (<div className="mx_WhoIsTypingTile_empty" />);
|
||||||
|
}
|
||||||
|
|
||||||
|
const EmojiText = sdk.getComponent('elements.EmojiText');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li className="mx_WhoIsTypingTile">
|
||||||
|
<div className="mx_WhoIsTypingTile_avatars">
|
||||||
|
{ this._renderTypingIndicatorAvatars(this.props.whoIsTypingLimit) }
|
||||||
|
</div>
|
||||||
|
<div className="mx_WhoIsTypingTile_label">
|
||||||
|
<EmojiText>{ typingString }</EmojiText>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
|
@ -202,10 +202,10 @@
|
||||||
"%(widgetName)s widget modified by %(senderName)s": "%(widgetName)s widget modified by %(senderName)s",
|
"%(widgetName)s widget modified by %(senderName)s": "%(widgetName)s widget modified by %(senderName)s",
|
||||||
"%(widgetName)s widget added by %(senderName)s": "%(widgetName)s widget added by %(senderName)s",
|
"%(widgetName)s widget added by %(senderName)s": "%(widgetName)s widget added by %(senderName)s",
|
||||||
"%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s widget removed by %(senderName)s",
|
"%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s widget removed by %(senderName)s",
|
||||||
"%(displayName)s is typing": "%(displayName)s is typing",
|
"%(displayName)s is typing …": "%(displayName)s is typing …",
|
||||||
"%(names)s and %(count)s others are typing|other": "%(names)s and %(count)s others are typing",
|
"%(names)s and %(count)s others are typing …|other": "%(names)s and %(count)s others are typing …",
|
||||||
"%(names)s and %(count)s others are typing|one": "%(names)s and one other is typing",
|
"%(names)s and %(count)s others are typing …|one": "%(names)s and one other is typing …",
|
||||||
"%(names)s and %(lastPerson)s are typing": "%(names)s and %(lastPerson)s are typing",
|
"%(names)s and %(lastPerson)s are typing …": "%(names)s and %(lastPerson)s are typing …",
|
||||||
"Failure to create room": "Failure to create room",
|
"Failure to create room": "Failure to create room",
|
||||||
"Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.",
|
"Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.",
|
||||||
"Send anyway": "Send anyway",
|
"Send anyway": "Send anyway",
|
||||||
|
|
|
@ -26,11 +26,13 @@ const sdk = require('matrix-react-sdk');
|
||||||
|
|
||||||
const MessagePanel = sdk.getComponent('structures.MessagePanel');
|
const MessagePanel = sdk.getComponent('structures.MessagePanel');
|
||||||
import MatrixClientPeg from '../../../src/MatrixClientPeg';
|
import MatrixClientPeg from '../../../src/MatrixClientPeg';
|
||||||
|
import Matrix from 'matrix-js-sdk';
|
||||||
|
|
||||||
const test_utils = require('test-utils');
|
const test_utils = require('test-utils');
|
||||||
const mockclock = require('mock-clock');
|
const mockclock = require('mock-clock');
|
||||||
|
|
||||||
let client;
|
let client;
|
||||||
|
const room = new Matrix.Room();
|
||||||
|
|
||||||
// wrap MessagePanel with a component which provides the MatrixClient in the context.
|
// wrap MessagePanel with a component which provides the MatrixClient in the context.
|
||||||
const WrappedMessagePanel = React.createClass({
|
const WrappedMessagePanel = React.createClass({
|
||||||
|
@ -45,7 +47,7 @@ const WrappedMessagePanel = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
return <MessagePanel {...this.props} />;
|
return <MessagePanel room={room} {...this.props} />;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,8 @@ describe('TimelinePanel', function() {
|
||||||
sandbox = test_utils.stubClient(sandbox);
|
sandbox = test_utils.stubClient(sandbox);
|
||||||
|
|
||||||
room = sinon.createStubInstance(jssdk.Room);
|
room = sinon.createStubInstance(jssdk.Room);
|
||||||
|
room.currentState = sinon.createStubInstance(jssdk.RoomState);
|
||||||
|
room.currentState.members = {};
|
||||||
room.roomId = ROOM_ID;
|
room.roomId = ROOM_ID;
|
||||||
|
|
||||||
timelineSet = sinon.createStubInstance(jssdk.EventTimelineSet);
|
timelineSet = sinon.createStubInstance(jssdk.EventTimelineSet);
|
||||||
|
|
Loading…
Reference in New Issue