Feature: Add support for XEP-0444 Message Reactions#3906
Feature: Add support for XEP-0444 Message Reactions#3906marcellintacite wants to merge 1 commit intoconversejs:masterfrom
Conversation
e2289f4 to
cac7f22
Compare
|
@marcellintacite , did you see that it is possible to add «custom emojis» in the ConverseJS emoji picker? (see bellow for an explanation). I think those emojis should not be selectable as a message reaction. With this feature, you can link a code name (for example |
|
I thinks there are 2 other missing things in your implementation: Discovering supportConverseJS must declare to other client that it handles message reactions, by adding the feature The XEP says it MUST be implemented. Also, if you are chatting with a user (in a 1 to 1 conversation) that does not support this feature, maybe we should not display the action in the chatbox. Restricted reactionsThe XEP allow some clients or MUC to limit the set of supported emojis. See https://xmpp.org/extensions/xep-0444.html#disco-restricted So, you should check if there is such limitation (for a user in 1 to 1 conversations, and for the MUC in rooms), and filter the emoji picker. |
|
@marcellintacite: Have you seen @JohnXLivingston comments? |
Yes, i am working on it |
|
@marcellintacite: Good :) |
Alright , thanks. I am sorry i didn't remember. I'll do |
I added a support for them |
can you check my implementation please |
What do you mean by "added a support for them"? I can't find the related code. |
Yeah, the XEP compliance seems OK now :) |
Thanks , |
|
Hi @JohnXLivingston , could you please take a look at the MR? I need to finalize it for review now that it's in draft form. |
I took a quick look. Seems good now. I think you can mark it as ready for a review. |
|
@marcellintacite there are 10 failing tests. Looks like a bunch of them are because the CAPS version string is different, since you're now advertising support for a new additional XMPP feature. You'll have to update the tests to check for the new version string. See here for example: The old version string is |
Thanks, i am working on it |
|
Hello @jcbrand , I've updated all the test files to use the new CAPS version string and rebased with the latest changes. I also added the |
|
Thanks @marcellintacite, can you please do a rebase onto the Currently there are a bunch of unrelated commits in this PR. |
c72d580 to
34ace89
Compare
Done |
jcbrand
left a comment
There was a problem hiding this comment.
Thanks @marcellintacite. I did a first review from my side and left some review comments.
| registerEvents() { | ||
| this.onKeyDown = (ev) => this.#onKeyDown(ev); | ||
| this.dropdown.addEventListener("hide.bs.dropdown", () => this.onDropdownHide()); | ||
| this.dropdown?.addEventListener("hide.bs.dropdown", () => this.onDropdownHide()); |
There was a problem hiding this comment.
Why would this.dropdown sometimes be undefined?
This seems like a code smell (red flag) that masks a potential underlying problem.
0cd7b3d to
9a1a566
Compare
|
Hello @jcbrand , I've completed the rebase, but the tests are still failing. It appears some of the failing tests aren't connected to this pull request. Could you please take a look? |
|
@marcellintacite The latest commit in the The failing Message Replies tests might be because you didn't compile a new dist/converse.js file before running the tests again. Looking at the CI run of your last commit in this PR, I still see a failing test due to a changed CAPS version. |
Thanks , i have fixed the issue |
|
Hi @jcbrand , can you check the PR please ? |
jcbrand
left a comment
There was a problem hiding this comment.
I had a quick look and left some comments.
I'll do a more thorough check a bit later when I have more time.
|
@marcellintacite Can you please rebase your branch onto It's also a good idea to squash all your commits into a single one. Doing so makes rebasing onto Thanks!
|
772a8ba to
d38b48b
Compare
|
Hi @jcbrand , I've completed the rebase to master and merged all commit changes. |
jcbrand
left a comment
There was a problem hiding this comment.
Thanks for your effort and patience so far @marcellintacite.
I think we're close to being able to merge this PR, but I still found some issues that should be fixed and improvements to be made.
Also, when clicking on an already made reaction, it should be removed:

src/plugins/reactions/utils.js
Outdated
| const current_reactions = message.get('reactions') || {}; | ||
| const reactions = JSON.parse(JSON.stringify(current_reactions)); |
There was a problem hiding this comment.
This should also work and doesn't require two function JSON function calls:
| const current_reactions = message.get('reactions') || {}; | |
| const reactions = JSON.parse(JSON.stringify(current_reactions)); | |
| const reactions = { ...(message.get('reactions') || {}) }; |
src/plugins/reactions/index.js
Outdated
| // Strategy 1: Try to find chatbox by sender's bare JID | ||
| const { Strophe } = converse.env; | ||
| const bare_jid = Strophe.getBareJidFromJid(from_jid); | ||
| const chatbox = api.chatboxes.get(bare_jid); |
There was a problem hiding this comment.
| const chatbox = api.chatboxes.get(bare_jid); | |
| const chatbox = await api.chatboxes.get(bare_jid); |
i am working on it |
d38b48b to
1075632
Compare
1075632 to
8cd9396
Compare
| * Popular emojis shown in the quick picker | ||
| * These are the most commonly used reactions across messaging platforms | ||
| */ | ||
| const POPULAR_EMOJIS = [ |
8cd9396 to
6c88500
Compare
6c88500 to
777c55c
Compare
|
Hello @jcbrand , I've completed the plugin's headless implementation as you requested. Would you mind reviewing it? |
| if (emojis.length === 0) { | ||
| return attrs; | ||
| } | ||
|
|
||
| const reactions = { ...(/** @type {any} */(attrs).reactions || {}) }; | ||
| const reacting_jid = attrs.from; | ||
|
|
||
| emojis.forEach(emoji => { | ||
| if (!reactions[emoji]) { | ||
| reactions[emoji] = []; | ||
| } | ||
| if (!reactions[emoji].includes(reacting_jid)) { | ||
| reactions[emoji].push(reacting_jid); | ||
| } | ||
| }); | ||
|
|
||
| // Remove user's reactions that aren't in the new emoji list | ||
| for (const emoji in reactions) { | ||
| if (!emojis.includes(emoji)) { | ||
| reactions[emoji] = reactions[emoji].filter(jid => jid !== reacting_jid); | ||
| if (reactions[emoji].length === 0) { | ||
| delete reactions[emoji]; | ||
| } | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
When parsing an incoming reaction message, you only have the reactions from a single JID. You don't have any of the other reactions from other JIDs that might have already been applied to the message.
For now the task is only to parse this message and then later you can properly update the reactions on the original message.
Also, given that when parsing you only know the emojis of a single JID, you need make the keys of your reactions object the JIDs of the reactors, not the emojis themselves.
Then, in getUpdatedMessageAttributes in you need to trigger a hook which you can listen to in your reactions headless plugin.
Then in inside the handler for the hook, you need to update the reactions object to also add all the other JIDs and their emojis.
Otherwise if you don't do that, you'll delete all the emojis from the other JIDs when the new attributes are saved.
| if (emojis.length === 0) { | |
| return attrs; | |
| } | |
| const reactions = { ...(/** @type {any} */(attrs).reactions || {}) }; | |
| const reacting_jid = attrs.from; | |
| emojis.forEach(emoji => { | |
| if (!reactions[emoji]) { | |
| reactions[emoji] = []; | |
| } | |
| if (!reactions[emoji].includes(reacting_jid)) { | |
| reactions[emoji].push(reacting_jid); | |
| } | |
| }); | |
| // Remove user's reactions that aren't in the new emoji list | |
| for (const emoji in reactions) { | |
| if (!emojis.includes(emoji)) { | |
| reactions[emoji] = reactions[emoji].filter(jid => jid !== reacting_jid); | |
| if (reactions[emoji].length === 0) { | |
| delete reactions[emoji]; | |
| } | |
| } | |
| } | |
| const reacting_jid = attrs.from; | |
| const reactions = { [reacting_jid]: emojis }; |
|
Hi @marcellintacite I noticed that some changes were requested on this PR. It has been about |
Hi, I am still working on it. I experienced a busy week last week, but I am now addressing the requested changes. Thanks for the follow up |
Implement support for XEP-0444 message reactions with the following features: - Add emoji reaction picker to message actions - Support for discovering reaction support via service disco - Restrict reactions based on server capabilities - Reactive UI updates using Lit components - Proper handling of multiple reactions per message - CAPS version string updates for test suite Fixing the reaction auto-open behavior
e9c4d93 to
362eb2d
Compare
|
Hello @jcbrand, I've resolved the test issue and cleaned up some comments. Please review the changes. All tests are now successfully passing. |
| msg_model.save({ 'reactions': { '👍': [contact_jid], '❤️': [contact_jid] } }); | ||
| expect(msg_model.get('reactions')['👍']).toContain(contact_jid); | ||
|
|
||
| msg_model.save({ 'reactions': {} }); |
There was a problem hiding this comment.
It would be better to test with incoming message stanzas, first with one that contains reactions and then one that doesn't contain reactions, instead of calling msg_model.save which is too low-level.
If you did that then this test would have caught this bug:
https://github.com/conversejs/converse.js/pull/3906/changes#r2851508030
| .filter(e => e); | ||
|
|
||
| if (emojis.length === 0) { | ||
| return attrs; |
There was a problem hiding this comment.
You're exiting too quickly here and thereby preventing previously added emojis from being removed again.
| await u.waitUntil(() => view.querySelectorAll('.chat-msg__text').length); | ||
| const msg_model = view.model.messages.at(0); | ||
| const reactions = { '👍': [contact1_jid, contact2_jid] }; | ||
| msg_model.save({ 'reactions': reactions }); |
There was a problem hiding this comment.
Same here, you need to test by mocking incoming message stanzas containing reactions, not by calling .save on the model.
|
|
||
| emojis.forEach(emoji => { | ||
| if (!reactions[emoji]) { | ||
| reactions[emoji] = []; |
There was a problem hiding this comment.
It doesn't look like you implemented the recommendations in this comment?
#3906 (comment)
The reactions object needs to be changed so that the JID is the key instead of the emoji.
Otherwise you'll lose all reaction emojis added by other users.
I even gave a code suggestion which you ignored. You can choose to not accept a code suggestion and do something else, but then please explain to me what you did and why.
Looks like you also didn't create a listener for getUpdatedMessageAttributes.
The fact that the tests don't highlight this issue also shows that they're not written properly. The tests should mock incoming message stanzas with reactions instead of manually calling .save() on the message model which is not representative of actual usage.



Feature: Add support for XEP-0444 Message Reactions
Description
This PR implements XEP-0444: Message Reactions, allowing users to react to messages with emojis. It introduces a new plugin (
reactions) that handles the UI for picking reactions, displaying them on messages, and managing the underlying XMPP stanza logic.Key Changes
1. New Plugin:
reactionssrc/plugins/reactions/.<reaction>elements from message stanzas.urn:xmpp:reactions:0).2. UI Components
converse-reaction-picker):3. Technical Details
src/shared/chat/styles/emoji.scss) to ensure consistency between the chat input and the reaction picker.Screenshots / Video
emoji_converse.mp4
This still a Draft
CHANGES.mddocument it in
docs/source/configuration.rstwith
make checkor you can run them in the browser by runningmake serve