Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MUC occupants badges: displays short labels, with full label as title. #3498

Merged
merged 1 commit into from
Sep 4, 2024
Merged
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 @@ -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 initBadgesDefinitions () {
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 currently_seen = {}
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();
currently_seen[definition.shortlabel]??= 0;
currently_seen[definition.shortlabel]++;
}
}
seen = currently_seen;
}
}

/**
* Badge template.
* @param {string} badge_code The badge to use ('owner', 'admin', ...)
*/
function tplBadge (badge_code) {
if (!badges_definitions) {
initBadgesDefinitions();
}
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');
}));
});
Loading