Merge pull request #586 from matrix-org/kegan/read-receipt-show-time-on-hover

Add read receipt times to the hovertip of read markers
This commit is contained in:
Kegsay 2016-12-09 13:19:44 +00:00 committed by GitHub
commit 21e7b03e53
4 changed files with 60 additions and 22 deletions

View file

@ -471,27 +471,33 @@ module.exports = React.createClass({
!== new Date(nextEventTs).toDateString()); !== new Date(nextEventTs).toDateString());
}, },
// get a list of the userids whose read receipts should // get a list of read receipts that should be shown next to this event
// be shown next to this event // Receipts are objects which have a 'roomMember' and 'ts'.
_getReadReceiptsForEvent: function(event) { _getReadReceiptsForEvent: function(event) {
var myUserId = MatrixClientPeg.get().credentials.userId; const myUserId = MatrixClientPeg.get().credentials.userId;
// get list of read receipts, sorted most recent first // get list of read receipts, sorted most recent first
var room = MatrixClientPeg.get().getRoom(event.getRoomId()); const room = MatrixClientPeg.get().getRoom(event.getRoomId());
if (!room) { if (!room) {
// huh.
return null; return null;
} }
let receipts = [];
room.getReceiptsForEvent(event).forEach((r) => {
if (!r.userId || r.type !== "m.read" || r.userId === myUserId) {
return; // ignore non-read receipts and receipts from self.
}
let member = room.getMember(r.userId);
if (!member) {
return; // ignore unknown user IDs
}
receipts.push({
roomMember: member,
ts: r.data ? r.data.ts : 0,
});
});
return room.getReceiptsForEvent(event).filter(function(r) { return receipts.sort((r1, r2) => {
return r.type === "m.read" && r.userId != myUserId; return r2.ts - r1.ts;
}).sort(function(r1, r2) {
return r2.data.ts - r1.data.ts;
}).map(function(r) {
return room.getMember(r.userId);
}).filter(function(m) {
// check that the user is a known room member
return m;
}); });
}, },

View file

@ -33,6 +33,7 @@ module.exports = React.createClass({
onClick: React.PropTypes.func, onClick: React.PropTypes.func,
// Whether the onClick of the avatar should be overriden to dispatch 'view_user' // Whether the onClick of the avatar should be overriden to dispatch 'view_user'
viewUserOnClick: React.PropTypes.bool, viewUserOnClick: React.PropTypes.bool,
title: React.PropTypes.string,
}, },
getDefaultProps: function() { getDefaultProps: function() {
@ -58,7 +59,7 @@ module.exports = React.createClass({
} }
return { return {
name: props.member.name, name: props.member.name,
title: props.member.userId, title: props.title || props.member.userId,
imageUrl: Avatar.avatarUrlForMember(props.member, imageUrl: Avatar.avatarUrlForMember(props.member,
props.width, props.width,
props.height, props.height,

View file

@ -103,7 +103,7 @@ module.exports = WithMatrixClient(React.createClass({
/* callback called when dynamic content in events are loaded */ /* callback called when dynamic content in events are loaded */
onWidgetLoad: React.PropTypes.func, onWidgetLoad: React.PropTypes.func,
/* a list of Room Members whose read-receipts we should show */ /* a list of read-receipts we should show. Each object has a 'roomMember' and 'ts'. */
readReceipts: React.PropTypes.arrayOf(React.PropTypes.object), readReceipts: React.PropTypes.arrayOf(React.PropTypes.object),
/* opaque readreceipt info for each userId; used by ReadReceiptMarker /* opaque readreceipt info for each userId; used by ReadReceiptMarker
@ -231,7 +231,7 @@ module.exports = WithMatrixClient(React.createClass({
return false; return false;
} }
for (var j = 0; j < rA.length; j++) { for (var j = 0; j < rA.length; j++) {
if (rA[j].userId !== rB[j].userId) { if (rA[j].roomMember.userId !== rB[j].roomMember.userId) {
return false; return false;
} }
} }
@ -287,19 +287,28 @@ module.exports = WithMatrixClient(React.createClass({
getReadAvatars: function() { getReadAvatars: function() {
var ReadReceiptMarker = sdk.getComponent('rooms.ReadReceiptMarker'); var ReadReceiptMarker = sdk.getComponent('rooms.ReadReceiptMarker');
var avatars = []; var avatars = [];
var left = 0; var left = 0;
// It's possible that the receipt was sent several days AFTER the event.
// If it is, we want to display the complete date along with the HH:MM:SS,
// rather than just HH:MM:SS.
let dayAfterEvent = new Date(this.props.mxEvent.getTs());
dayAfterEvent.setDate(dayAfterEvent.getDate() + 1)
dayAfterEvent.setHours(0);
dayAfterEvent.setMinutes(0);
dayAfterEvent.setSeconds(0);
let dayAfterEventTime = dayAfterEvent.getTime();
var receipts = this.props.readReceipts || []; var receipts = this.props.readReceipts || [];
for (var i = 0; i < receipts.length; ++i) { for (var i = 0; i < receipts.length; ++i) {
var member = receipts[i]; var receipt = receipts[i];
var hidden = true; var hidden = true;
if ((i < MAX_READ_AVATARS) || this.state.allReadAvatars) { if ((i < MAX_READ_AVATARS) || this.state.allReadAvatars) {
hidden = false; hidden = false;
} }
var userId = member.userId; var userId = receipt.roomMember.userId;
var readReceiptInfo; var readReceiptInfo;
if (this.props.readReceiptMap) { if (this.props.readReceiptMap) {
@ -311,15 +320,16 @@ module.exports = WithMatrixClient(React.createClass({
} }
//console.log("i = " + i + ", MAX_READ_AVATARS = " + MAX_READ_AVATARS + ", allReadAvatars = " + this.state.allReadAvatars + " visibility = " + style.visibility); //console.log("i = " + i + ", MAX_READ_AVATARS = " + MAX_READ_AVATARS + ", allReadAvatars = " + this.state.allReadAvatars + " visibility = " + style.visibility);
// add to the start so the most recent is on the end (ie. ends up rightmost) // add to the start so the most recent is on the end (ie. ends up rightmost)
avatars.unshift( avatars.unshift(
<ReadReceiptMarker key={userId} member={member} <ReadReceiptMarker key={userId} member={receipt.roomMember}
leftOffset={left} hidden={hidden} leftOffset={left} hidden={hidden}
readReceiptInfo={readReceiptInfo} readReceiptInfo={readReceiptInfo}
checkUnmounting={this.props.checkUnmounting} checkUnmounting={this.props.checkUnmounting}
suppressAnimation={this._suppressReadReceiptAnimation} suppressAnimation={this._suppressReadReceiptAnimation}
onClick={this.toggleAllReadAvatars} onClick={this.toggleAllReadAvatars}
timestamp={receipt.ts}
showFullTimestamp={receipt.ts >= dayAfterEventTime}
/> />
); );

View file

@ -60,6 +60,12 @@ module.exports = React.createClass({
// callback for clicks on this RR // callback for clicks on this RR
onClick: React.PropTypes.func, onClick: React.PropTypes.func,
// Timestamp when the receipt was read
timestamp: React.PropTypes.number,
// True to show the full date/time rather than just the time
showFullTimestamp: React.PropTypes.bool,
}, },
getDefaultProps: function() { getDefaultProps: function() {
@ -162,6 +168,20 @@ module.exports = React.createClass({
visibility: this.props.hidden ? 'hidden' : 'visible', visibility: this.props.hidden ? 'hidden' : 'visible',
}; };
let title;
if (this.props.timestamp) {
let suffix = " (" + this.props.member.userId + ")";
let ts = new Date(this.props.timestamp);
if (this.props.showFullTimestamp) {
// "15/12/2016, 7:05:45 PM (@alice:matrix.org)"
title = ts.toLocaleString() + suffix;
}
else {
// "7:05:45 PM (@alice:matrix.org)"
title = ts.toLocaleTimeString() + suffix;
}
}
return ( return (
<Velociraptor <Velociraptor
startStyles={this.state.startStyles} startStyles={this.state.startStyles}
@ -170,6 +190,7 @@ module.exports = React.createClass({
member={this.props.member} member={this.props.member}
width={14} height={14} resizeMethod="crop" width={14} height={14} resizeMethod="crop"
style={style} style={style}
title={title}
onClick={this.props.onClick} onClick={this.props.onClick}
/> />
</Velociraptor> </Velociraptor>