Store message read receips

pull/9717/head
Diego Sampaio 8 years ago
parent 85940503a2
commit 1bda97d87b
No known key found for this signature in database
GPG Key ID: E060152B30502562
  1. 5
      imports/message-read-receipt/server/dbIndexes.js
  2. 2
      imports/message-read-receipt/server/index.js
  3. 55
      imports/message-read-receipt/server/lib/ReadReceipt.js
  4. 15
      imports/message-read-receipt/server/models/ReadReceipts.js
  5. 12
      imports/message-read-receipt/server/settings.js
  6. 1
      imports/startup/server/index.js
  7. 4
      packages/rocketchat-lib/server/functions/sendMessage.js
  8. 26
      packages/rocketchat-lib/server/models/Messages.js
  9. 13
      packages/rocketchat-lib/server/models/Subscriptions.js
  10. 8
      packages/rocketchat-theme/client/imports/components/messages.css
  11. 1
      packages/rocketchat-theme/client/imports/general/base.css
  12. 1
      packages/rocketchat-ui-master/public/icons.svg
  13. 5
      packages/rocketchat-ui-message/client/message.html
  14. 9
      packages/rocketchat-ui-message/client/message.js
  15. 1
      server/main.js
  16. 17
      server/methods/readMessages.js

@ -0,0 +1,5 @@
RocketChat.models.Messages.tryEnsureIndex({
unread: 1
}, {
sparse: true
});

@ -0,0 +1,2 @@
import './dbIndexes';
import './settings';

@ -0,0 +1,55 @@
import { Random } from 'meteor/random';
import ModelReadReceipts from '../models/ReadReceipts';
const rawReadReceipts = ModelReadReceipts.model.rawCollection();
// @TODO create a debounced function by roomId, so multiple calls to same roomId runs only once
export const ReadReceipt = {
markMessagesAsRead(roomId, userId, userLastSeen) {
if (!RocketChat.settings.get('Message_Read_Receipt_Enabled')) {
return;
}
const room = RocketChat.models.Rooms.findOneById(roomId, { fields: { lm: 1 } });
// if users last seen is greater than room's last message, it means the user already have this room marked as read
if (userLastSeen > room.lm) {
return;
}
const firstSubscription = RocketChat.models.Subscriptions.getMinimumLastSeenByRoomId(roomId);
// console.log('userLastSeen ->', userLastSeen);
// console.log('firstSubscription ->', firstSubscription);
// console.log('room ->', room);
// last time room was read is already past room's last message, so does nothing everybody have this room already
// if (firstSubscription.ls > room.lm) {
// console.log('already read by everyone');
// return;
// }
// @TODO maybe store firstSubscription in room object so we don't need to call the above update method
// if firstSubscription on room didn't change
if (RocketChat.settings.get('Message_Read_Receipt_Store_Users')) {
const receipts = RocketChat.models.Messages.findUnreadMessagesByRoomAndDate(roomId, userLastSeen).map(message => {
return {
_id: Random.id(),
roomId,
userId,
messageId: message._id
};
});
try {
rawReadReceipts.insertMany(receipts);
} catch (e) {
console.error('Error inserting read receipts per user');
}
}
RocketChat.models.Messages.setAsRead(roomId, firstSubscription.ls);
}
};

@ -0,0 +1,15 @@
class ModelReadReceipts extends RocketChat.models._Base {
constructor() {
super(...arguments);
this.tryEnsureIndex({
roomId: 1,
userId: 1,
messageId: 1
}, {
unique: 1
});
}
}
export default new ModelReadReceipts('message_read_receipt', true);

@ -0,0 +1,12 @@
RocketChat.settings.add('Message_Read_Receipt_Enabled', false, {
group: 'Message',
type: 'boolean',
public: true
});
RocketChat.settings.add('Message_Read_Receipt_Store_Users', false, {
group: 'Message',
type: 'boolean',
public: false,
enableQuery: { _id: 'Message_Read_Receipt_Enabled', value: true }
});

@ -0,0 +1 @@
import '../../message-read-receipt/server';

@ -31,6 +31,10 @@ RocketChat.sendMessage = function(user, message, room, upsert = false) {
});
}
}
// @TODO test if setting is enabled?
message.unread = true;
message = RocketChat.callbacks.run('beforeSaveMessage', message);
if (message) {
// Avoid saving sandstormSessionId to the database

@ -622,4 +622,30 @@ RocketChat.models.Messages = new class extends RocketChat.models._Base {
getMessageByFileId(fileID) {
return this.findOne({ 'file._id': fileID });
}
setAsRead(rid, until) {
return this.update({
rid,
unread: true,
ts: { $lt: until }
}, {
$unset: {
unread: 1
}
}, {
multi: true
});
}
findUnreadMessagesByRoomAndDate(rid, after) {
return this.find({
unread: true,
rid,
ts: { $gt: after }
}, {
fields: {
_id: 1
}
});
}
};

@ -182,6 +182,19 @@ class ModelSubscriptions extends RocketChat.models._Base {
return this.find(query, { fields: { unread: 1 } });
}
getMinimumLastSeenByRoomId(rid) {
return this.db.findOne({
rid
}, {
sort: {
ls: 1
},
fields: {
ls: 1
}
});
}
// UPDATE
archiveByRoomId(roomId) {
const query =

@ -77,6 +77,14 @@
left: 0;
}
}
&.temp .read-receipt {
color: #999;
}
& .read-receipt.read {
color: blue;
}
}
.messages-box .rc-popover__list {

@ -123,6 +123,7 @@ button {
vertical-align: -0.15em;
fill: currentColor;
}
.ps-scrollbar-y-rail {

@ -80,6 +80,7 @@
<symbol viewBox="0 0 512 512" id="icon-shield"><path d="M466.5 83.7l-192-80a48.15 48.15 0 0 0-36.9 0l-192 80C27.7 91.1 16 108.6 16 128c0 198.5 114.5 335.7 221.5 380.3 11.8 4.9 25.1 4.9 36.9 0C360.1 472.6 496 349.3 496 128c0-19.4-11.7-36.9-29.5-44.3zM262.2 478.8c-3.9 1.6-8.3 1.6-12.3 0C152 440 48 304 48 128c0-6.5 3.9-12.3 9.8-14.8l192-80c3.8-1.6 8.3-1.7 12.3 0l192 80c6 2.5 9.8 8.3 9.8 14.8.1 176-103.9 312-201.7 350.8z"/></symbol>
<symbol viewBox="0 0 512 512" id="icon-shield-alt"><path d="M256 410.955V99.999l-142.684 59.452C123.437 279.598 190.389 374.493 256 410.955zm-32-66.764c-36.413-39.896-65.832-97.846-76.073-164.495L224 147.999v196.192zM466.461 83.692l-192-80a47.996 47.996 0 0 0-36.923 0l-192 80A48 48 0 0 0 16 128c0 198.487 114.495 335.713 221.539 380.308a48 48 0 0 0 36.923 0C360.066 472.645 496 349.282 496 128a48 48 0 0 0-29.539-44.308zM262.154 478.768a16.64 16.64 0 0 1-12.31-.001C152 440 48 304 48 128c0-6.48 3.865-12.277 9.846-14.769l192-80a15.99 15.99 0 0 1 12.308 0l192 80A15.957 15.957 0 0 1 464 128c0 176-104 312-201.846 350.768z"/></symbol>
<symbol viewBox="0 0 512 512" id="icon-shield-check"><path d="M466.461 83.692l-192-80a47.996 47.996 0 0 0-36.923 0l-192 80A48 48 0 0 0 16 128c0 198.487 114.495 335.713 221.539 380.308a48 48 0 0 0 36.923 0C360.066 472.645 496 349.282 496 128a48 48 0 0 0-29.539-44.308zM262.154 478.768a16.64 16.64 0 0 1-12.31-.001C152 440 48 304 48 128c0-6.48 3.865-12.277 9.846-14.769l192-80a15.99 15.99 0 0 1 12.308 0l192 80A15.957 15.957 0 0 1 464 128c0 176-104 312-201.846 350.768zm144.655-299.505l-180.48 179.032c-4.705 4.667-12.303 4.637-16.97-.068l-85.878-86.572c-4.667-4.705-4.637-12.303.068-16.97l8.52-8.451c4.705-4.667 12.303-4.637 16.97.068l68.976 69.533 163.441-162.13c4.705-4.667 12.303-4.637 16.97.068l8.451 8.52c4.668 4.705 4.637 12.303-.068 16.97z"/></symbol>
<symbol viewBox="0 0 448 512" id="icon-check"><path d="M413.505 91.951L133.49 371.966l-98.995-98.995c-4.686-4.686-12.284-4.686-16.971 0L6.211 284.284c-4.686 4.686-4.686 12.284 0 16.971l118.794 118.794c4.686 4.686 12.284 4.686 16.971 0l299.813-299.813c4.686-4.686 4.686-12.284 0-16.971l-11.314-11.314c-4.686-4.686-12.284-4.686-16.97 0z"/></symbol>
<symbol viewBox="0 0 640 512" id="icon-team"><path d="M573.127 249.095C584.979 233.127 592 213.369 592 192c0-52.935-43.065-96-96-96-26.331 0-50.217 10.658-67.578 27.885a128.993 128.993 0 0 0-17.913-22.394C386.334 77.314 354.19 64 320 64s-66.334 13.314-90.51 37.49a129.115 129.115 0 0 0-17.913 22.394C194.217 106.658 170.331 96 144 96c-52.935 0-96 43.065-96 96 0 21.369 7.021 41.127 18.873 57.095C28.987 255.378 0 288.36 0 328v44c0 24.262 19.738 44 44 44h117.677c5.238 18.445 22.222 32 42.323 32h232c20.102 0 37.085-13.555 42.323-32H596c24.262 0 44-19.738 44-44v-44c0-39.64-28.986-72.622-66.873-78.905zM496 128c35.346 0 64 28.654 64 64s-28.654 64-64 64c-22.083 0-41.554-11.185-53.057-28.199C446.27 216.314 448 204.291 448 192s-1.73-24.314-5.057-35.801C454.446 139.185 473.917 128 496 128zM320 96c53.02 0 96 42.981 96 96s-42.98 96-96 96-96-42.981-96-96 42.98-96 96-96zm-176 32c22.083 0 41.554 11.185 53.057 28.199C193.73 167.686 192 179.709 192 192s1.73 24.314 5.057 35.801C185.554 244.815 166.083 256 144 256c-35.346 0-64-28.654-64-64s28.654-64 64-64zm16 224v32H44c-6.627 0-12-5.373-12-12v-44c0-26.51 21.49-48 48-48h25.655c24.374 10.662 52.272 10.681 76.689 0h22.81C178.452 292.976 160 320.372 160 352zm288 52c0 6.627-5.373 12-12 12H204c-6.627 0-12-5.373-12-12v-52c0-26.51 21.49-48 48-48h17.929c37.818 21.031 85.208 21.651 124.142 0H400c26.51 0 48 21.49 48 48v52zm160-32c0 6.627-5.373 12-12 12H480v-32c0-31.628-18.452-59.024-45.154-72h22.81c24.374 10.662 52.272 10.681 76.689 0H560c26.51 0 48 21.49 48 48v44z"/></symbol>
<symbol viewBox="0 0 640 512" id="icon-user-plus"><path d="M624 332v8c0 6.627-5.373 12-12 12h-68v68c0 6.627-5.373 12-12 12h-8c-6.627 0-12-5.373-12-12v-68h-68c-6.627 0-12-5.373-12-12v-8c0-6.627 5.373-12 12-12h68v-68c0-6.627 5.373-12 12-12h8c6.627 0 12 5.373 12 12v68h68c6.627 0 12 5.373 12 12zm-209.796 60.045A72.186 72.186 0 0 1 416 408v16c0 13.255-10.745 24-24 24H56c-13.255 0-24-10.745-24-24v-16c0-39.765 32.235-72 72-72h50.196c44.019 21.336 95.521 21.369 139.609 0H344c15.072 0 29.057 4.639 40.62 12.555-1.281-8.932-1.108-24.647 2.376-35.244C373.884 307.334 359.325 304 344 304h-5.753C367.477 274.198 384 234.32 384 192c0-88.353-71.613-160-160-160-88.353 0-160 71.613-160 160 0 42.261 16.481 82.155 45.753 112H104C46.654 304 0 350.654 0 408v16c0 30.879 25.122 56 56 56h336c30.879 0 56-25.121 56-56v-16c0-2.691-.103-5.359-.305-8-11.7 0-21.636-1.141-33.491-7.955zM96 192c0-70.693 57.308-128 128-128s128 57.307 128 128-57.308 128-128 128S96 262.693 96 192z"/></symbol>
<symbol viewBox="0 0 448 512" id="icon-podcast"><path d="M326.011 313.366a81.658 81.658 0 0 0-11.127-16.147c-1.855-2.1-1.913-5.215-.264-7.481C328.06 271.264 336 248.543 336 224c0-63.221-52.653-114.375-116.41-111.915-57.732 2.228-104.69 48.724-107.458 106.433-1.278 26.636 6.812 51.377 21.248 71.22 1.648 2.266 1.592 5.381-.263 7.481a81.609 81.609 0 0 0-11.126 16.145c-2.003 3.816-7.25 4.422-9.961 1.072C92.009 289.7 80 258.228 80 224c0-79.795 65.238-144.638 145.178-143.995 77.583.624 141.19 63.4 142.79 140.969.73 35.358-11.362 67.926-31.928 93.377-2.738 3.388-8.004 2.873-10.029-.985zM224 0C100.206 0 0 100.185 0 224c0 82.003 43.765 152.553 107.599 191.485 4.324 2.637 9.775-.93 9.078-5.945-1.244-8.944-2.312-17.741-3.111-26.038a6.025 6.025 0 0 0-2.461-4.291c-48.212-35.164-79.495-92.212-79.101-156.409.636-103.637 84.348-188.625 187.964-190.76C327.674 29.822 416 116.79 416 224c0 63.708-31.192 120.265-79.104 155.21a6.027 6.027 0 0 0-2.462 4.292c-.799 8.297-1.866 17.092-3.11 26.035-.698 5.015 4.753 8.584 9.075 5.947C403.607 376.922 448 306.75 448 224 448 100.204 347.814 0 224 0zm64 355.75c0 32.949-12.871 104.179-20.571 132.813C262.286 507.573 242.858 512 224 512c-18.857 0-38.286-4.427-43.428-23.438C172.927 460.134 160 388.898 160 355.75c0-35.156 31.142-43.75 64-43.75 32.858 0 64 8.594 64 43.75zm-32 0c0-16.317-64-16.3-64 0 0 27.677 11.48 93.805 19.01 122.747 6.038 2.017 19.948 2.016 25.981 0C244.513 449.601 256 383.437 256 355.75zM288 224c0 35.346-28.654 64-64 64s-64-28.654-64-64 28.654-64 64-64 64 28.654 64 64zm-32 0c0-17.645-14.355-32-32-32s-32 14.355-32 32 14.355 32 32 32 32-14.355 32-32z"/></symbol>

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

@ -114,5 +114,10 @@
<span class="icon-people-plus"></span>
</li>
</ul>
{{#with readReceipt}}
<div class="read-receipt {{readByEveryone}}">
{{> icon icon="check" }}
</div>
{{/with}}
</li>
</template>

@ -284,6 +284,15 @@ Template.message.helpers({
},
isSnippet() {
return this.actionContext === 'snippeted';
},
readReceipt() {
if (!RocketChat.settings.get('Message_Read_Receipt_Enabled')) {
return;
}
return {
readByEveryone: !this.unread && 'read'
};
}
});

@ -0,0 +1 @@
import '/imports/startup/server';

@ -1,13 +1,26 @@
import { ReadReceipt } from '../../imports/message-read-receipt/server/lib/ReadReceipt';
Meteor.methods({
readMessages(rid) {
check(rid, String);
if (!Meteor.userId()) {
const userId = Meteor.userId();
if (!userId) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
method: 'readMessages'
});
}
return RocketChat.models.Subscriptions.setAsReadByRoomIdAndUserId(rid, Meteor.userId());
const userSubscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(rid, userId);
// this prevents cache from updating object reference/pointer
const { ls: lastSeen } = userSubscription;
RocketChat.models.Subscriptions.setAsReadByRoomIdAndUserId(rid, userId);
Meteor.defer(() => {
ReadReceipt.markMessagesAsRead(rid, userId, lastSeen);
});
}
});

Loading…
Cancel
Save