Add support for XEP-0317 MUC Hats
This commit is contained in:
parent
e2a7045e22
commit
df9612f937
|
@ -959,6 +959,17 @@ VCard is taken, and if that is not set but `muc_nickname_from_jid`_ is set to
|
|||
|
||||
If no nickame value is found, then an error will be raised.
|
||||
|
||||
muc_hats_from_vcard
|
||||
-------------------
|
||||
|
||||
* Default: ``false``
|
||||
|
||||
Since version 7 Converse now has rudimentary support for `XEP-0317 Hats <https://xmpp.org/extensions/xep-0317.html>`_.
|
||||
|
||||
Previously we used a non-standard hack of showing the VCard roles as if they
|
||||
were hats. Set this value to ``true`` for the old behaviour.
|
||||
|
||||
|
||||
muc_mention_autocomplete_min_chars
|
||||
-----------------------------------
|
||||
|
||||
|
|
|
@ -263,7 +263,6 @@
|
|||
margin-top: 0.5em;
|
||||
padding-right: 0.25rem;
|
||||
padding-bottom: 0.25rem;
|
||||
display: flex;
|
||||
|
||||
.chat-msg__author {
|
||||
overflow: hidden;
|
||||
|
|
85
spec/hats.js
Normal file
85
spec/hats.js
Normal file
|
@ -0,0 +1,85 @@
|
|||
(function (root, factory) {
|
||||
define([
|
||||
"jasmine",
|
||||
"mock",
|
||||
"test-utils"
|
||||
], factory);
|
||||
} (this, function (jasmine, mock, test_utils) {
|
||||
"use strict";
|
||||
const u = converse.env.utils;
|
||||
|
||||
describe("A XEP-0317 MUC Hat", function () {
|
||||
|
||||
it("can be included in a presence stanza",
|
||||
mock.initConverse(
|
||||
['rosterGroupsFetched', 'chatBoxesFetched'], {},
|
||||
async function (done, _converse) {
|
||||
|
||||
const muc_jid = 'lounge@montague.lit';
|
||||
await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
|
||||
const view = _converse.chatboxviews.get(muc_jid);
|
||||
const hat1_id = u.getUniqueId();
|
||||
const hat2_id = u.getUniqueId();
|
||||
_converse.connection._dataRecv(test_utils.createRequest(u.toStanza(`
|
||||
<presence from="${muc_jid}/Terry" id="${u.getUniqueId()}" to="${_converse.jid}">
|
||||
<x xmlns="http://jabber.org/protocol/muc#user">
|
||||
<item affiliation="member" role="participant"/>
|
||||
</x>
|
||||
<hats xmlns="xmpp:prosody.im/protocol/hats:1">
|
||||
<hat title="Teacher's Assistant" id="${hat1_id}"/>
|
||||
<hat title="Dark Mage" id="${hat2_id}"/>
|
||||
</hats>
|
||||
</presence>
|
||||
`)));
|
||||
await u.waitUntil(() => view.el.querySelector('.chat-content__notifications').textContent.trim() ===
|
||||
"romeo and Terry have entered the groupchat");
|
||||
|
||||
let hats = view.model.getOccupant("Terry").get('hats');
|
||||
expect(hats.length).toBe(2);
|
||||
expect(hats.map(h => h.title).join(' ')).toBe("Teacher's Assistant Dark Mage");
|
||||
|
||||
_converse.connection._dataRecv(test_utils.createRequest(u.toStanza(`
|
||||
<message type="groupchat" from="${muc_jid}/Terry" id="${u.getUniqueId()}" to="${_converse.jid}">
|
||||
<body>Hello world</body>
|
||||
</message>
|
||||
`)));
|
||||
|
||||
const msg_el = await u.waitUntil(() => view.el.querySelector('.chat-msg'));
|
||||
let badges = Array.from(msg_el.querySelectorAll('.badge'));
|
||||
expect(badges.length).toBe(2);
|
||||
expect(badges.map(b => b.textContent.trim()).join(' ' )).toBe("Teacher's Assistant Dark Mage");
|
||||
|
||||
const hat3_id = u.getUniqueId();
|
||||
_converse.connection._dataRecv(test_utils.createRequest(u.toStanza(`
|
||||
<presence from="${muc_jid}/Terry" id="${u.getUniqueId()}" to="${_converse.jid}">
|
||||
<x xmlns="http://jabber.org/protocol/muc#user">
|
||||
<item affiliation="member" role="participant"/>
|
||||
</x>
|
||||
<hats xmlns="xmpp:prosody.im/protocol/hats:1">
|
||||
<hat title="Teacher's Assistant" id="${hat1_id}"/>
|
||||
<hat title="Dark Mage" id="${hat2_id}"/>
|
||||
<hat title="Mad hatter" id="${hat3_id}"/>
|
||||
</hats>
|
||||
</presence>
|
||||
`)));
|
||||
|
||||
await u.waitUntil(() => view.model.getOccupant("Terry").get('hats').length === 3);
|
||||
hats = view.model.getOccupant("Terry").get('hats');
|
||||
expect(hats.map(h => h.title).join(' ')).toBe("Teacher's Assistant Dark Mage Mad hatter");
|
||||
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg .badge').length === 3);
|
||||
badges = Array.from(view.el.querySelectorAll('.chat-msg .badge'));
|
||||
expect(badges.map(b => b.textContent.trim()).join(' ' )).toBe("Teacher's Assistant Dark Mage Mad hatter");
|
||||
|
||||
_converse.connection._dataRecv(test_utils.createRequest(u.toStanza(`
|
||||
<presence from="${muc_jid}/Terry" id="${u.getUniqueId()}" to="${_converse.jid}">
|
||||
<x xmlns="http://jabber.org/protocol/muc#user">
|
||||
<item affiliation="member" role="participant"/>
|
||||
</x>
|
||||
</presence>
|
||||
`)));
|
||||
await u.waitUntil(() => view.model.getOccupant("Terry").get('hats').length === 0);
|
||||
await u.waitUntil(() => view.el.querySelectorAll('.chat-msg .badge').length === 0);
|
||||
done();
|
||||
}));
|
||||
})
|
||||
}));
|
|
@ -67,6 +67,7 @@ converse.plugins.add('converse-message-view', {
|
|||
|
||||
|
||||
api.settings.update({
|
||||
'muc_hats_from_vcard': false,
|
||||
'show_images_inline': true,
|
||||
'time_format': 'HH:mm',
|
||||
});
|
||||
|
@ -107,8 +108,9 @@ converse.plugins.add('converse-message-view', {
|
|||
}
|
||||
|
||||
if (this.model.occupant) {
|
||||
this.listenTo(this.model.occupant, 'change:role', this.debouncedRender);
|
||||
this.listenTo(this.model.occupant, 'change:affiliation', this.debouncedRender);
|
||||
this.listenTo(this.model.occupant, 'change:hats', this.debouncedRender);
|
||||
this.listenTo(this.model.occupant, 'change:role', this.debouncedRender);
|
||||
this.debouncedRender();
|
||||
}
|
||||
|
||||
|
@ -228,25 +230,36 @@ converse.plugins.add('converse-message-view', {
|
|||
async renderChatMessage () {
|
||||
await api.waitUntil('emojisInitialized');
|
||||
const time = dayjs(this.model.get('time'));
|
||||
const role = this.model.vcard ? this.model.vcard.get('role') : null;
|
||||
const roles = role ? role.split(',') : [];
|
||||
const is_retracted = this.model.get('retracted') || this.model.get('moderated') === 'retracted';
|
||||
const may_be_moderated = this.model.get('type') === 'groupchat' && await this.model.mayBeModerated();
|
||||
const retractable= !is_retracted && (this.model.mayBeRetracted() || may_be_moderated);
|
||||
const is_groupchat_message = this.model.get('type') === 'groupchat';
|
||||
|
||||
let hats = [];
|
||||
if (is_groupchat_message) {
|
||||
if (api.settings.get('muc_hats_from_vcard')) {
|
||||
const role = this.model.vcard ? this.model.vcard.get('role') : null;
|
||||
hats = role ? role.split(',') : [];
|
||||
} else {
|
||||
const o = this.model.occupant;
|
||||
hats = o && o.get('hats').map(h => h.title).filter(h => h) || [];
|
||||
}
|
||||
}
|
||||
|
||||
const msg = u.stringToElement(tpl_message(
|
||||
Object.assign(
|
||||
this.model.toJSON(), {
|
||||
__,
|
||||
hats,
|
||||
is_groupchat_message,
|
||||
is_retracted,
|
||||
retractable,
|
||||
'extra_classes': this.getExtraMessageClasses(),
|
||||
'is_groupchat_message': this.model.get('type') === 'groupchat',
|
||||
'is_me_message': this.model.isMeCommand(),
|
||||
'label_show': __('Show more'),
|
||||
'occupant': this.model.occupant,
|
||||
'pretty_time': time.format(api.settings.get('time_format')),
|
||||
'retraction_text': is_retracted ? this.getRetractionText() : null,
|
||||
'roles': roles,
|
||||
'time': time.toISOString(),
|
||||
'username': this.model.getDisplayName()
|
||||
})
|
||||
|
|
|
@ -33,6 +33,7 @@ Strophe.addNamespace('MUC_OWNER', Strophe.NS.MUC + "#owner");
|
|||
Strophe.addNamespace('MUC_REGISTER', "jabber:iq:register");
|
||||
Strophe.addNamespace('MUC_ROOMCONF', Strophe.NS.MUC + "#roomconfig");
|
||||
Strophe.addNamespace('MUC_USER', Strophe.NS.MUC + "#user");
|
||||
Strophe.addNamespace('MUC_HATS', "xmpp:prosody.im/protocol/hats:1");
|
||||
|
||||
converse.MUC_NICK_CHANGED_CODE = "303";
|
||||
|
||||
|
@ -2423,6 +2424,7 @@ converse.plugins.add('converse-muc', {
|
|||
_converse.ChatRoomOccupant = Model.extend({
|
||||
|
||||
defaults: {
|
||||
'hats': [],
|
||||
'show': 'offline',
|
||||
'states': []
|
||||
},
|
||||
|
|
|
@ -367,6 +367,7 @@ const stanza_utils = {
|
|||
'nick': Strophe.getResourceFromJid(from),
|
||||
'type': type,
|
||||
'states': [],
|
||||
'hats': [],
|
||||
'show': type !== 'unavailable' ? 'online' : 'offline'
|
||||
};
|
||||
Array.from(stanza.children).forEach(child => {
|
||||
|
@ -387,6 +388,11 @@ const stanza_utils = {
|
|||
});
|
||||
} else if (child.matches('x') && child.getAttribute('xmlns') === Strophe.NS.VCARDUPDATE) {
|
||||
data.image_hash = child.querySelector('photo')?.textContent;
|
||||
} else if (child.matches('hats') && child.getAttribute('xmlns') === Strophe.NS.MUC_HATS) {
|
||||
data['hats'] = Array.from(child.children).map(c => c.matches('hat') && {
|
||||
'title': c.getAttribute('title'),
|
||||
'id': c.getAttribute('id')
|
||||
});
|
||||
}
|
||||
});
|
||||
return data;
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
{[ if (o.is_me_message) { ]}<time timestamp="{{{o.isodate}}}" class="chat-msg__time">{{{o.pretty_time}}}</time>{[ } ]}
|
||||
<span class="chat-msg__author">{[ if (o.is_me_message) { ]}**{[ }; ]}{{{o.username}}}</span>
|
||||
{[ if (!o.is_me_message) { ]}
|
||||
{[o.roles.forEach(function (role) { ]} <span class="badge badge-secondary">{{{role}}}</span> {[ }); ]}
|
||||
{[o.hats.forEach(function (hat) { ]} <span class="badge badge-secondary">{{{hat}}}</span> {[ }); ]}
|
||||
<time timestamp="{{{o.isodate}}}" class="chat-msg__time">{{{o.pretty_time}}}</time>
|
||||
{[ } ]}
|
||||
{[ if (o.is_encrypted) { ]}<span class="fa fa-lock"></span>{[ } ]}
|
||||
|
|
|
@ -64,6 +64,7 @@ var specs = [
|
|||
"spec/notification",
|
||||
"spec/login",
|
||||
"spec/register",
|
||||
"spec/hats",
|
||||
"spec/http-file-upload",
|
||||
"spec/emojis",
|
||||
"spec/xss"
|
||||
|
|
Loading…
Reference in New Issue
Block a user