Skip to content

Commit

Permalink
MUC occupants badges: displays short labels, with full label as title.
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnXLivingston committed Sep 4, 2024
1 parent 1943c91 commit 051c9f4
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 15 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
- Add a new theme 'Cyberpunk' and remove the old 'Concord' theme.
- Improved accessibility.
- New "getOccupantActionButtons" hook, so that plugins can add actions on MUC occupants.
- MUC occupants badges: displays short labels, with full label as title.

- New config option [stanza_timeout](https://conversejs.org/docs/html/configuration.html#show-background)

Expand Down
81 changes: 71 additions & 10 deletions src/plugins/muc-views/templates/occupant.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,72 @@ const i18n_occupant_hint = /** @param {MUCOccupant} o */(o) => {
return __('Click to mention %1$s in your message.', o.get('nick'));
}

let badges_definitions; // will be initialized at first call (to be sure that the __ function is correctly loaded).

/**
* Inits badges definitions.
* For short labels, it will use the label first letter. If there is ambigous short labels, it will try to add up to 4 letters.
* Letters will be uppercase.
*/
function init_badges_definitions () {
badges_definitions = {}
badges_definitions['owner'] = {
label: __('Owner'),
classname: 'badge-groupchat'
};
badges_definitions['admin'] = {
label: __('Admin'),
classname: 'badge-info'
};
badges_definitions['member'] = {
label: __('Member'),
classname: 'badge-info'
};
badges_definitions['moderator'] = {
label: __('Moderator'),
classname: 'badge-info'
};
badges_definitions['visitor'] = {
label: __('Visitor'),
classname: 'badge-secondary'
};

// And now we must compute unique short labels.
let seen;
for (
let current_length = 1;
current_length < 5 && (!seen || Object.values(seen).find(count => count > 1));
current_length++
) {
const currentlySeen = {}
for (const definition of Object.values(badges_definitions)) {
if (!seen || (seen[definition.shortlabel] ?? 0) >= 2) {
// (first loop, or count >= 2 in the previous loop)
definition.shortlabel = definition.label.substr(0, current_length).toLocaleUpperCase();
currentlySeen[definition.shortlabel]??= 0;
currentlySeen[definition.shortlabel]++;
}
}
seen = currentlySeen;
}
}

/**
* Badge template.
* @param {string} badge_code The badge to use ('owner', 'admin', ...)
*/
function tplBadge (badge_code) {
if (!badges_definitions) {
init_badges_definitions();
}
const definition = badges_definitions[badge_code];
if (!definition) { return ''; }

return html`<span title="${definition.label}" aria-label=${definition.label}
class="badge ${definition.classname ?? 'badge-info'}">${definition.shortlabel}</span>`;
}


const occupant_title = /** @param {MUCOccupant} o */(o) => {
const role = o.get('role');
const hint_occupant = i18n_occupant_hint(o);
Expand Down Expand Up @@ -78,11 +144,6 @@ async function tplActionButtons (o) {
export default (o, chat) => {
const affiliation = o.get('affiliation');
const hint_show = PRETTY_CHAT_STATUS[o.get('show')];
const i18n_admin = __('Admin');
const i18n_member = __('Member');
const i18n_moderator = __('Moderator');
const i18n_owner = __('Owner');
const i18n_visitor = __('Visitor');
const role = o.get('role');

const show = o.get('show');
Expand Down Expand Up @@ -122,11 +183,11 @@ export default (o, chat) => {
@click=${chat.onOccupantClicked}
style="${getAuthorStyle(o)}">${o.getDisplayName()}</span>
<span class="occupant-badges">
${ (affiliation === "owner") ? html`<span class="badge badge-groupchat">${i18n_owner}</span>` : '' }
${ (affiliation === "admin") ? html`<span class="badge badge-info">${i18n_admin}</span>` : '' }
${ (affiliation === "member") ? html`<span class="badge badge-info">${i18n_member}</span>` : '' }
${ (role === "moderator") ? html`<span class="badge badge-info">${i18n_moderator}</span>` : '' }
${ (role === "visitor") ? html`<span class="badge badge-secondary">${i18n_visitor}</span>` : '' }
${ (affiliation === "owner") ? tplBadge('owner') : '' }
${ (affiliation === "admin") ? tplBadge('admin') : '' }
${ (affiliation === "member") ? tplBadge('member') : '' }
${ (role === "moderator") ? tplBadge('moderator') : '' }
${ (role === "visitor") ? tplBadge('visitor') : '' }
</span>
${
until(tplActionButtons(o))
Expand Down
18 changes: 13 additions & 5 deletions src/plugins/muc-views/tests/occupants.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,11 @@ describe("The occupants sidebar", function () {
expect(occupants.length).toBe(1);
expect(occupants[0].querySelector('.occupant-nick').textContent.trim()).toBe("romeo");
expect(occupants[0].querySelectorAll('.badge').length).toBe(2);
expect(occupants[0].querySelectorAll('.badge')[0].textContent.trim()).toBe('Owner');
expect(sizzle('.badge:last', occupants[0]).pop().textContent.trim()).toBe('Moderator');
expect(occupants[0].querySelectorAll('.badge')[0].textContent.trim()).toBe('O');
expect(occupants[0].querySelectorAll('.badge')[0].getAttribute('title').trim()).toBe('Owner');
expect(occupants[0].querySelectorAll('.badge')[0].getAttribute('aria-label').trim()).toBe('Owner');
expect(sizzle('.badge:last', occupants[0]).pop().textContent.trim()).toBe('MO');
expect(sizzle('.badge:last', occupants[0]).pop().getAttribute('title').trim()).toBe('Moderator');

var presence = $pres({
to:'[email protected]/pda',
Expand All @@ -199,8 +202,12 @@ describe("The occupants sidebar", function () {
);
expect(occupants[1].querySelector('.occupant-nick').textContent.trim()).toBe("romeo");
expect(occupants[0].querySelectorAll('.badge').length).toBe(2);
expect(occupants[0].querySelectorAll('.badge')[0].textContent.trim()).toBe('Admin');
expect(occupants[0].querySelectorAll('.badge')[1].textContent.trim()).toBe('Moderator');
expect(occupants[0].querySelectorAll('.badge')[0].textContent.trim()).toBe('A');
expect(occupants[0].querySelectorAll('.badge')[0].getAttribute('title').trim()).toBe('Admin');
expect(occupants[0].querySelectorAll('.badge')[0].getAttribute('aria-label').trim()).toBe('Admin');
expect(occupants[0].querySelectorAll('.badge')[1].textContent.trim()).toBe('MO');
expect(occupants[0].querySelectorAll('.badge')[1].getAttribute('title').trim()).toBe('Moderator');
expect(occupants[0].querySelectorAll('.badge')[1].getAttribute('aria-label').trim()).toBe('Moderator');

contact_jid = mock.cur_names[3].replace(/ /g,'.').toLowerCase() + '@montague.lit';
presence = $pres({
Expand All @@ -222,6 +229,7 @@ describe("The occupants sidebar", function () {
contact_jid + ' This user can NOT send messages in this groupchat. Click to mention visitorwoman in your message.'
);
expect(occupants[2].querySelectorAll('.badge').length).toBe(1);
expect(sizzle('.badge', occupants[2]).pop().textContent.trim()).toBe('Visitor');
expect(sizzle('.badge', occupants[2]).pop().textContent.trim()).toBe('V');
expect(sizzle('.badge', occupants[2]).pop().getAttribute('title').trim()).toBe('Visitor');
}));
});

0 comments on commit 051c9f4

Please sign in to comment.