diff --git a/src/app/molecules/message/Message.jsx b/src/app/molecules/message/Message.jsx
index 9fbd8ff..b17cb33 100644
--- a/src/app/molecules/message/Message.jsx
+++ b/src/app/molecules/message/Message.jsx
@@ -223,16 +223,38 @@ MessageEdit.propTypes = {
onCancel: PropTypes.func.isRequired,
};
-function MessageReactionGroup({ children }) {
- return (
-
- { children }
-
- );
+function getMyEmojiEvent(emojiKey, eventId, roomTimeline) {
+ const mx = initMatrix.matrixClient;
+ const rEvents = roomTimeline.reactionTimeline.get(eventId);
+ let rEvent = null;
+ rEvents?.find((rE) => {
+ if (rE.getRelation() === null) return false;
+ if (rE.getRelation().key === emojiKey && rE.getSender() === mx.getUserId()) {
+ rEvent = rE;
+ return true;
+ }
+ return false;
+ });
+ return rEvent;
+}
+
+function toggleEmoji(roomId, eventId, emojiKey, roomTimeline) {
+ const myAlreadyReactEvent = getMyEmojiEvent(emojiKey, eventId, roomTimeline);
+ if (myAlreadyReactEvent) {
+ const rId = myAlreadyReactEvent.getId();
+ if (rId.startsWith('~')) return;
+ redactEvent(roomId, rId);
+ return;
+ }
+ sendReaction(roomId, eventId, emojiKey);
+}
+
+function pickEmoji(e, roomId, eventId, roomTimeline) {
+ openEmojiBoard(getEventCords(e), (emoji) => {
+ toggleEmoji(roomId, eventId, emoji.unicode, roomTimeline);
+ e.target.click();
+ });
}
-MessageReactionGroup.propTypes = {
- children: PropTypes.node.isRequired,
-};
function genReactionMsg(userIds, reaction) {
const genLessContText = (text) => {text};
@@ -254,12 +276,12 @@ function genReactionMsg(userIds, reaction) {
}
function MessageReaction({
- reaction, users, isActive, onClick,
+ reaction, count, users, isActive, onClick,
}) {
return (
{genReactionMsg(users, reaction)}}
+ content={{users.length > 0 ? genReactionMsg(users, reaction) : 'Unable to load who has reacted'}}
>
);
}
MessageReaction.propTypes = {
reaction: PropTypes.node.isRequired,
+ count: PropTypes.number.isRequired,
users: PropTypes.arrayOf(PropTypes.string).isRequired,
isActive: PropTypes.bool.isRequired,
onClick: PropTypes.func.isRequired,
};
+function MessageReactionGroup({ roomTimeline, mEvent }) {
+ const { roomId, reactionTimeline } = roomTimeline;
+ const eventId = mEvent.getId();
+ const mx = initMatrix.matrixClient;
+ const reactions = {};
+
+ const eventReactions = reactionTimeline.get(eventId);
+ const addReaction = (key, count, senderId, isActive) => {
+ let reaction = reactions[key];
+ if (reaction === undefined) {
+ reaction = {
+ count: 0,
+ users: [],
+ isActive: false,
+ };
+ }
+ if (count) {
+ reaction.count = count;
+ } else {
+ reaction.users.push(senderId);
+ reaction.count = reaction.users.length;
+ reaction.isActive = isActive;
+ }
+
+ reactions[key] = reaction;
+ };
+ if (eventReactions) {
+ eventReactions.forEach((rEvent) => {
+ if (rEvent.getRelation() === null) return;
+ const reaction = rEvent.getRelation();
+ const senderId = rEvent.getSender();
+ const isActive = senderId === mx.getUserId();
+
+ addReaction(reaction.key, undefined, senderId, isActive);
+ });
+ } else {
+ // Use aggregated reactions
+ const aggregatedReaction = mEvent.getServerAggregatedRelation('m.annotation')?.chunk;
+ if (!aggregatedReaction) return null;
+ aggregatedReaction.forEach((reaction) => {
+ if (reaction.type !== 'm.reaction') return;
+ addReaction(reaction.key, reaction.count, undefined, false);
+ });
+ }
+
+ return (
+
+ {
+ Object.keys(reactions).map((key) => (
+ {
+ toggleEmoji(roomId, eventId, key, roomTimeline);
+ }}
+ />
+ ))
+ }
+ {
+ pickEmoji(e, roomId, eventId, roomTimeline);
+ }}
+ src={EmojiAddIC}
+ size="extra-small"
+ tooltip="Add reaction"
+ />
+
+ );
+}
+MessageReactionGroup.propTypes = {
+ roomTimeline: PropTypes.shape({}).isRequired,
+ mEvent: PropTypes.shape({}).isRequired,
+};
+
function MessageOptions({ children }) {
return (
@@ -367,37 +467,6 @@ function genMediaContent(mE) {
return Malformed event;
}
}
-function getMyEmojiEventId(emojiKey, eventId, roomTimeline) {
- const mx = initMatrix.matrixClient;
- const rEvents = roomTimeline.reactionTimeline.get(eventId);
- let rEventId = null;
- rEvents?.find((rE) => {
- if (rE.getRelation() === null) return false;
- if (rE.getRelation().key === emojiKey && rE.getSender() === mx.getUserId()) {
- rEventId = rE.getId();
- return true;
- }
- return false;
- });
- return rEventId;
-}
-
-function toggleEmoji(roomId, eventId, emojiKey, roomTimeline) {
- const myAlreadyReactEventId = getMyEmojiEventId(emojiKey, eventId, roomTimeline);
- if (typeof myAlreadyReactEventId === 'string') {
- if (myAlreadyReactEventId.indexOf('~') === 0) return;
- redactEvent(roomId, myAlreadyReactEventId);
- return;
- }
- sendReaction(roomId, eventId, emojiKey);
-}
-
-function pickEmoji(e, roomId, eventId, roomTimeline) {
- openEmojiBoard(getEventCords(e), (emoji) => {
- toggleEmoji(roomId, eventId, emoji.unicode, roomTimeline);
- e.target.click();
- });
-}
function getEditedBody(editedMEvent) {
const newContent = editedMEvent.getContent()['m.new_content'];
@@ -438,8 +507,9 @@ function Message({
const myPowerlevel = room.getMember(mx.getUserId())?.powerLevel;
const canIRedact = room.currentState.hasSufficientPowerLevelFor('redact', myPowerlevel);
- let [reactions, isCustomHTML] = [null, content.format === 'org.matrix.custom.html'];
- const [isEdited, haveReactions] = [editedTimeline.has(eventId), reactionTimeline.has(eventId)];
+ let isCustomHTML = content.format === 'org.matrix.custom.html';
+ const isEdited = editedTimeline.has(eventId);
+ const haveReactions = reactionTimeline.has(eventId) || !!mEvent.getServerAggregatedRelation('m.annotation');
const isReply = !!mEvent.replyEventId;
let customHTML = isCustomHTML ? content.formatted_body : null;
@@ -450,39 +520,6 @@ function Message({
if (typeof body !== 'string') return null;
}
- if (haveReactions) {
- reactions = [];
- reactionTimeline.get(eventId).forEach((rEvent) => {
- if (rEvent.getRelation() === null) return;
- function alreadyHaveThisReaction(rE) {
- for (let i = 0; i < reactions.length; i += 1) {
- if (reactions[i].key === rE.getRelation().key) return true;
- }
- return false;
- }
- if (alreadyHaveThisReaction(rEvent)) {
- for (let i = 0; i < reactions.length; i += 1) {
- if (reactions[i].key === rEvent.getRelation().key) {
- reactions[i].users.push(rEvent.getSender());
- if (reactions[i].isActive !== true) {
- const myUserId = mx.getUserId();
- reactions[i].isActive = rEvent.getSender() === myUserId;
- if (reactions[i].isActive) reactions[i].id = rEvent.getId();
- }
- break;
- }
- }
- } else {
- reactions.push({
- id: rEvent.getId(),
- key: rEvent.getRelation().key,
- users: [rEvent.getSender()],
- isActive: (rEvent.getSender() === mx.getUserId()),
- });
- }
- });
- }
-
if (isReply) {
body = parseReply(body)?.body ?? body;
}
@@ -528,29 +565,7 @@ function Message({
/>
)}
{haveReactions && (
-
- {
- reactions.map((reaction) => (
- {
- toggleEmoji(roomId, eventId, reaction.key, roomTimeline);
- }}
- />
- ))
- }
- {
- pickEmoji(e, roomId, eventId, roomTimeline);
- }}
- src={EmojiAddIC}
- size="extra-small"
- tooltip="Add reaction"
- />
-
+
)}
{!isEditing && (
diff --git a/src/app/organisms/room/RoomViewContent.jsx b/src/app/organisms/room/RoomViewContent.jsx
index 1fa0ab2..0a5e9c6 100644
--- a/src/app/organisms/room/RoomViewContent.jsx
+++ b/src/app/organisms/room/RoomViewContent.jsx
@@ -446,19 +446,43 @@ function useEventArrive(roomTimeline, readEventStore) {
readEventStore.setItem(roomTimeline.findEventByIdInTimelineSet(readUpToId));
return;
}
- if (readUpToEvent?.getId() !== readUpToId) {
+ const isUnreadMsg = readUpToEvent?.getId() === readUpToId;
+ if (!isUnreadMsg) {
+ roomTimeline.markAllAsRead();
+ }
+ const { timeline } = roomTimeline;
+ const unreadMsgIsLast = timeline[timeline.length - 2].getId() === readUpToEvent?.getId();
+ if (unreadMsgIsLast) {
roomTimeline.markAllAsRead();
}
};
const handleEvent = (event) => {
const tLength = roomTimeline.timeline.length;
- if (roomTimeline.isServingLiveTimeline()
+ const isUserViewingLive = (
+ roomTimeline.isServingLiveTimeline()
&& limit.getEndIndex() >= tLength - 1
- && timelineScroll.bottom < SCROLL_TRIGGER_POS) {
+ && timelineScroll.bottom < SCROLL_TRIGGER_POS
+ );
+ if (isUserViewingLive) {
limit.setFrom(tLength - limit.getMaxEvents());
sendReadReceipt(event);
setEvent(event);
+ return;
+ }
+ const isRelates = (event.getType() === 'm.reaction' || event.getRelation()?.rel_type === 'm.replace');
+ if (isRelates) {
+ setEvent(event);
+ return;
+ }
+ const isUserDitchedLive = (
+ roomTimeline.isServingLiveTimeline()
+ && limit.getEndIndex() >= tLength - 1
+ );
+ if (isUserDitchedLive) {
+ // This stateUpdate will help to put the
+ // loading msg placeholder at bottom
+ setEvent(event);
}
};
diff --git a/src/client/state/RoomTimeline.js b/src/client/state/RoomTimeline.js
index 1b7eec6..ea7376a 100644
--- a/src/client/state/RoomTimeline.js
+++ b/src/client/state/RoomTimeline.js
@@ -18,9 +18,12 @@ function getRelateToId(mEvent) {
function addToMap(myMap, mEvent) {
const relateToId = getRelateToId(mEvent);
if (relateToId === null) return null;
+ const mEventId = mEvent.getId();
if (typeof myMap.get(relateToId) === 'undefined') myMap.set(relateToId, []);
- myMap.get(relateToId).push(mEvent);
+ const mEvents = myMap.get(relateToId);
+ if (mEvents.find((ev) => ev.getId() === mEventId)) return mEvent;
+ mEvents.push(mEvent);
return mEvent;
}
@@ -101,10 +104,6 @@ class RoomTimeline extends EventEmitter {
clearLocalTimelines() {
this.timeline = [];
-
- // TODO: don't clear these timeline cause there data can be used in other timeline
- this.reactionTimeline.clear();
- this.editedTimeline.clear();
}
addToTimeline(mEvent) {
@@ -295,8 +294,11 @@ class RoomTimeline extends EventEmitter {
if (this.isOngoingPagination) return;
// User is currently viewing the old events probably
- // no need to add this event and emit changes.
- if (this.isServingLiveTimeline() === false) return;
+ // no need to add new event and emit changes.
+ // only add reactions and edited messages
+ if (this.isServingLiveTimeline() === false) {
+ if (!isReaction(event) && !isEdited(event)) return;
+ }
// We only process live events here
if (!data.liveEvent) return;