Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Next release

- #3382: Fix audio player accessibility for screen reader users (NVDA, JAWS)
- #2398: Add support for XEP-0444: Message Reactions (see [XEP-0444](https://xmpp.org/extensions/xep-0444.html))
- #3815: Removal of boomark leads to new bookmark named `Symbol(lit-nothing)`
- #3824: Dates and times are not translated
- #3829: Rich text from LibreOffice Calc is sent as screenshots
Expand Down
19 changes: 19 additions & 0 deletions docs/source/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1665,6 +1665,25 @@ You can set the URL where the sound files are hosted with the `sounds_path`_ opt

Requires the `src/converse-notification.js` plugin.

popular_reactions
-----------------

* Default: ``[':thumbsup:', ':heart:', ':joy:', ':open_mouth:']``

An array of emoji shortnames that are displayed as quick-access reaction buttons
in the message reaction picker (see `XEP-0444: Message Reactions <https://xmpp.org/extensions/xep-0444.html>`_).

These emojis are shown as the first row of buttons in the reaction picker,
allowing users to quickly react to messages without opening the full emoji selector.

Example:

.. code-block:: javascript

converse.initialize({
popular_reactions: [':thumbsup:', ':thumbsdown:', ':heart:', ':laughing:', ':tada:']
});

.. _`prebind_url`:

prebind_url
Expand Down
1 change: 1 addition & 0 deletions karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ module.exports = function(config) {
{ pattern: "src/plugins/roomslist/tests/*.js", type: 'module' },
{ pattern: "src/plugins/rootview/tests/*.js", type: 'module' },
{ pattern: "src/plugins/rosterview/tests/*.js", type: 'module' },
{ pattern: "src/plugins/reactions/tests/*.js", type: 'module' },
{ pattern: "src/plugins/rosterview/tests/requesting_contacts.js", type: 'module' },
{ pattern: "src/shared/modals/tests/*.js", type: 'module' },
{ pattern: "src/utils/tests/*.js", type: 'module' },
Expand Down
1 change: 1 addition & 0 deletions src/headless/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export { MUCMessage, MUCMessages, MUC, MUCOccupant, MUCOccupants } from './plugi

import './plugins/ping/index.js'; // XEP-0199 XMPP Ping
import './plugins/pubsub/index.js'; // XEP-0060 Pubsub
import './plugins/reactions/index.js'; // XEP-0444 Message Reactions

// RFC-6121 Contacts Roster
export { RosterContact, RosterContacts, RosterFilter, Presence, Presences } from './plugins/roster/index.js';
Expand Down
5 changes: 5 additions & 0 deletions src/headless/plugins/reactions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* XEP-0444: Message Reactions - Headless core
*/

import './plugin.js';
62 changes: 62 additions & 0 deletions src/headless/plugins/reactions/parsers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* @typedef {import('../../shared/types').MessageAttributes} MessageAttributes
* @typedef {import('../../plugins/muc/types').MUCMessageAttributes} MUCMessageAttributes
*/

import converse from '../../shared/api/public.js';

const { Strophe } = converse.env;

/**
* Parse reactions from a message stanza and return updated attributes
* @param {Element} stanza - The XMPP message stanza
* @param {MessageAttributes|MUCMessageAttributes} attrs - Current message attributes
* @returns {Promise<import('./types').MessageAttrsWithReactions|import('./types').MUCMessageAttrsWithReactions>}
*/
export async function parseReactionsMessage(stanza, attrs) {
const reactions_element = stanza.querySelector(
`reactions[xmlns="${Strophe.NS.REACTIONS}"]`
);

if (!reactions_element) {
return attrs;
}

const id = reactions_element.getAttribute('id');
if (!id) {
return attrs;
}

const reaction_elements = reactions_element.getElementsByTagName('reaction');
const emojis = Array.from(reaction_elements)
.map(el => el.textContent)
.filter(e => e);

if (emojis.length === 0) {
return attrs;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're exiting too quickly here and thereby preventing previously added emojis from being removed again.

}

const reactions = { ...(/** @type {any} */(attrs).reactions || {}) };
const reacting_jid = attrs.from;

emojis.forEach(emoji => {
if (!reactions[emoji]) {
reactions[emoji] = [];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

}
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];
}
}
}

return Object.assign(attrs, { reactions });
}
29 changes: 29 additions & 0 deletions src/headless/plugins/reactions/plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* @module converse-headless-reactions
* @copyright The Converse.js contributors
* @license Mozilla Public License (MPLv2)
* @description XEP-0444: Message Reactions - Headless core logic
*/

import converse from '../../shared/api/public.js';
import api from '../../shared/api/index.js';
import { parseReactionsMessage } from './parsers.js';

const { Strophe } = converse.env;

Strophe.addNamespace('REACTIONS', 'urn:xmpp:reactions:0');

converse.plugins.add('converse-headless-reactions', {
dependencies: ['converse-chat', 'converse-muc'],

/**
* Initializes the headless reactions plugin
* Hooks into message parsing to extract and store reactions
*/
initialize() {
// Hook into message parsing for 1:1 chats and MUCs
// This runs when messages are received/parsed
api.listen.on('parseMessage', parseReactionsMessage);
api.listen.on('parseMUCMessage', parseReactionsMessage);
}
});
9 changes: 9 additions & 0 deletions src/headless/plugins/reactions/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { MUCMessageAttributes } from '../../plugins/muc/types';
import { MessageAttributes } from '../../shared/types';

export type ReactionsAttributes = {
reactions?: Record<string, string[]>;
};

export type MUCMessageAttrsWithReactions = MUCMessageAttributes & ReactionsAttributes;
export type MessageAttrsWithReactions = MessageAttributes & ReactionsAttributes;
1 change: 1 addition & 0 deletions src/headless/types/shared/_converse.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ declare const ConversePrivateGlobal_base: (new (...args: any[]) => {
* @namespace _converse
*/
export class ConversePrivateGlobal extends ConversePrivateGlobal_base {
disco_entities: any;
constructor();
initialize(): void;
VERSION_NAME: string;
Expand Down
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import "./plugins/rosterview/index.js";
import "./plugins/singleton/index.js";
import "./plugins/dragresize/index.js"; // Allows chat boxes to be resized by dragging them
import "./plugins/fullscreen/index.js";
import "./plugins/reactions/index.js"; // XEP-0444 Reactions
/* END: Removable components */

_converse.exports.CustomElement = CustomElement;
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/bookmark-views/tests/bookmarks.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ describe("Bookmarks", function () {
<c xmlns="http://jabber.org/protocol/caps"
hash="sha-1"
node="https://conversejs.org"
ver="H63l6q0hnLTdpFJt2JA5AOAGl/o="/>
ver="Hbd4V8rlZualGDSkxW/4bVlnudc="/>
</presence>`);
}));
});
6 changes: 3 additions & 3 deletions src/plugins/muc-views/tests/nickname.js
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ describe("A MUC", function () {
xmlns="jabber:client">
<x xmlns="http://jabber.org/protocol/muc"><history maxstanzas="0"/></x>
<c xmlns="http://jabber.org/protocol/caps" hash="sha-1" node="https://conversejs.org"
ver="H63l6q0hnLTdpFJt2JA5AOAGl/o="/>
ver="Hbd4V8rlZualGDSkxW/4bVlnudc="/>>
</presence>`);

while (IQ_stanzas.length) IQ_stanzas.pop();
Expand Down Expand Up @@ -419,7 +419,7 @@ describe("A MUC", function () {
xmlns="jabber:client">
<x xmlns="http://jabber.org/protocol/muc"><history maxstanzas="0"/></x>
<c xmlns="http://jabber.org/protocol/caps" hash="sha-1" node="https://conversejs.org"
ver="H63l6q0hnLTdpFJt2JA5AOAGl/o="/>
ver="Hbd4V8rlZualGDSkxW/4bVlnudc="/>>
</presence>`);

while (IQ_stanzas.length) IQ_stanzas.pop();
Expand Down Expand Up @@ -449,7 +449,7 @@ describe("A MUC", function () {
xmlns="jabber:client">
<x xmlns="http://jabber.org/protocol/muc"><history maxstanzas="0"/></x>
<c xmlns="http://jabber.org/protocol/caps" hash="sha-1" node="https://conversejs.org"
ver="H63l6q0hnLTdpFJt2JA5AOAGl/o="/>
ver="Hbd4V8rlZualGDSkxW/4bVlnudc="/>>
</presence>`);
}));

Expand Down
4 changes: 2 additions & 2 deletions src/plugins/muc-views/tests/probes.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('Groupchats', function () {
stx`<presence to="${muc_jid}/ralphm" type="probe" xmlns="jabber:client">
<priority>0</priority>
<x xmlns="vcard-temp:x:update"/>
<c hash="sha-1" node="https://conversejs.org" ver="H63l6q0hnLTdpFJt2JA5AOAGl/o=" xmlns="http://jabber.org/protocol/caps"/>
<c hash="sha-1" node="https://conversejs.org" ver="Hbd4V8rlZualGDSkxW/4bVlnudc=" xmlns="http://jabber.org/protocol/caps"/>
</presence>`
);

Expand Down Expand Up @@ -67,7 +67,7 @@ describe('Groupchats', function () {
stx`<presence to="${muc_jid}/gonePhising" type="probe" xmlns="jabber:client">
<priority>0</priority>
<x xmlns="vcard-temp:x:update"/>
<c hash="sha-1" node="https://conversejs.org" ver="H63l6q0hnLTdpFJt2JA5AOAGl/o=" xmlns="http://jabber.org/protocol/caps"/>
<c hash="sha-1" node="https://conversejs.org" ver="Hbd4V8rlZualGDSkxW/4bVlnudc=" xmlns="http://jabber.org/protocol/caps"/>
</presence>`
);

Expand Down
12 changes: 6 additions & 6 deletions src/plugins/muc-views/tests/rai.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,14 @@ describe("XEP-0437 Room Activity Indicators", function () {
<presence to="${muc_jid}/romeo" type="unavailable" xmlns="jabber:client">
<priority>0</priority>
<x xmlns="${Strophe.NS.VCARD_UPDATE}"></x>
<c hash="sha-1" node="https://conversejs.org" ver="xLNm7xDIHf5niRYX69ViCnDXhb4=" xmlns="http://jabber.org/protocol/caps"/>
<c hash="sha-1" node="https://conversejs.org" ver="gdck24wSF+suQUiLraUQ715DZm0=" xmlns="http://jabber.org/protocol/caps"/>
</presence>`
);
expect(sent_stanzas[3]).toEqualStanza(stx`
<presence to="montague.lit" xmlns="jabber:client">
<priority>0</priority>
<x xmlns="${Strophe.NS.VCARD_UPDATE}"></x>
<c hash="sha-1" node="https://conversejs.org" ver="xLNm7xDIHf5niRYX69ViCnDXhb4=" xmlns="http://jabber.org/protocol/caps"/>
<c hash="sha-1" node="https://conversejs.org" ver="gdck24wSF+suQUiLraUQ715DZm0=" xmlns="http://jabber.org/protocol/caps"/>
<rai xmlns="urn:xmpp:rai:0"/>
</presence>`
);
Expand Down Expand Up @@ -157,14 +157,14 @@ describe("XEP-0437 Room Activity Indicators", function () {
<presence to="${muc_jid}/romeo" type="unavailable" xmlns="jabber:client">
<priority>0</priority>
<x xmlns="${Strophe.NS.VCARD_UPDATE}"></x>
<c hash="sha-1" node="https://conversejs.org" ver="xLNm7xDIHf5niRYX69ViCnDXhb4=" xmlns="http://jabber.org/protocol/caps"/>
<c hash="sha-1" node="https://conversejs.org" ver="gdck24wSF+suQUiLraUQ715DZm0=" xmlns="http://jabber.org/protocol/caps"/>
</presence>`
);
expect(sent_presences[2]).toEqualStanza(stx`
<presence to="montague.lit" xmlns="jabber:client">
<priority>0</priority>
<x xmlns="${Strophe.NS.VCARD_UPDATE}"></x>
<c hash="sha-1" node="https://conversejs.org" ver="xLNm7xDIHf5niRYX69ViCnDXhb4=" xmlns="http://jabber.org/protocol/caps"/>
<c hash="sha-1" node="https://conversejs.org" ver="gdck24wSF+suQUiLraUQ715DZm0=" xmlns="http://jabber.org/protocol/caps"/>
<rai xmlns="urn:xmpp:rai:0"/>
</presence>`
);
Expand Down Expand Up @@ -213,14 +213,14 @@ describe("XEP-0437 Room Activity Indicators", function () {
<presence to="${muc_jid}/romeo" type="unavailable" xmlns="jabber:client">
<priority>0</priority>
<x xmlns="${Strophe.NS.VCARD_UPDATE}"></x>
<c hash="sha-1" node="https://conversejs.org" ver="xLNm7xDIHf5niRYX69ViCnDXhb4=" xmlns="http://jabber.org/protocol/caps"/>
<c hash="sha-1" node="https://conversejs.org" ver="gdck24wSF+suQUiLraUQ715DZm0=" xmlns="http://jabber.org/protocol/caps"/>
</presence>`
);
expect(sent_presences[1]).toEqualStanza(stx`
<presence to="montague.lit" xmlns="jabber:client">
<priority>0</priority>
<x xmlns="${Strophe.NS.VCARD_UPDATE}"></x>
<c hash="sha-1" node="https://conversejs.org" ver="xLNm7xDIHf5niRYX69ViCnDXhb4=" xmlns="http://jabber.org/protocol/caps"/>
<c hash="sha-1" node="https://conversejs.org" ver="gdck24wSF+suQUiLraUQ715DZm0=" xmlns="http://jabber.org/protocol/caps"/>
<rai xmlns="urn:xmpp:rai:0"/>
</presence>`
);
Expand Down
4 changes: 2 additions & 2 deletions src/plugins/profile/tests/status.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe("The Controlbox", function () {
<show>dnd</show>
<priority>0</priority>
<x xmlns="${Strophe.NS.VCARD_UPDATE}"></x>
<c hash="sha-1" node="https://conversejs.org" ver="H63l6q0hnLTdpFJt2JA5AOAGl/o=" xmlns="http://jabber.org/protocol/caps"/>
<c hash="sha-1" node="https://conversejs.org" ver="Hbd4V8rlZualGDSkxW/4bVlnudc=" xmlns="http://jabber.org/protocol/caps"/>
</presence>`);
const view = await u.waitUntil(() => document.querySelector('converse-user-profile'));
const first_child = view.querySelector('.xmpp-status span:first-child');
Expand Down Expand Up @@ -62,7 +62,7 @@ describe("The Controlbox", function () {
<status>I am happy</status>
<priority>0</priority>
<x xmlns="${Strophe.NS.VCARD_UPDATE}"></x>
<c hash="sha-1" node="https://conversejs.org" ver="H63l6q0hnLTdpFJt2JA5AOAGl/o=" xmlns="http://jabber.org/protocol/caps"/>
<c hash="sha-1" node="https://conversejs.org" ver="Hbd4V8rlZualGDSkxW/4bVlnudc=" xmlns="http://jabber.org/protocol/caps"/>
</presence>`);

const view = await u.waitUntil(() => document.querySelector('converse-user-profile'));
Expand Down
Loading
Loading