UNPKG

converse.js

Version:
208 lines (194 loc) 7.6 kB
/** * @typedef {import('@converse/headless').MUCOccupant} MUCOccupant */ import { api } from '@converse/headless'; import { PRETTY_CHAT_STATUS } from 'shared/constants.js'; import { __ } from 'i18n'; import { html } from 'lit'; import { until } from 'lit/directives/until.js'; import { getAuthorStyle } from 'utils/color.js'; import { getUnreadMsgsDisplay } from 'shared/chat/utils.js'; 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 >`; } /** * @param {MUCOccupant} o * @returns {string} */ function getOccupantTitle(o) { const role = o.get('role'); const hint_occupant = i18n_occupant_hint(o); const i18n_moderator_hint = __('This user is a moderator.'); const i18n_participant_hint = __('This user can send messages in this groupchat.'); const i18n_visitor_hint = __('This user can NOT send messages in this groupchat.'); const spaced_jid = o.get('jid') ? `${o.get('jid')} ` : ''; if (role === 'moderator') { return `${spaced_jid}${i18n_moderator_hint} ${hint_occupant}`; } else if (role === 'participant') { return `${spaced_jid}${i18n_participant_hint} ${hint_occupant}`; } else if (role === 'visitor') { return `${spaced_jid}${i18n_visitor_hint} ${hint_occupant}`; } else if (!['visitor', 'participant', 'moderator'].includes(role)) { return `${spaced_jid}${hint_occupant}`; } } /** * @param {MUCOccupant} o */ async function tplActionButtons(o) { /** * *Hook* which allows plugins to add action buttons on occupants * @event _converse#getOccupantActionButtons * @example * api.listen.on('getOccupantActionButtons', (el, buttons) => { * buttons.push({ * 'i18n_text': 'Foo', * 'handler': ev => alert('Foo!'), * 'button_class': 'chat-occupant__action-foo', * 'icon_class': 'fa fa-check', * 'name': 'foo' * }); * return buttons; * }); */ const buttons = await api.hook('getOccupantActionButtons', o, []); if (!buttons?.length) { return ''; } const items = buttons.map((b) => { return html` <button class="dropdown-item ${b.button_class}" @click=${b.handler} type="button"> <converse-icon class="${b.icon_class}" color="var(--foreground-color)" size="1em" aria-hidden="true" ></converse-icon >&nbsp;${b.i18n_text} </button>`; }); return html`<converse-dropdown class="occupant-actions chatbox-btn" .items=${items}></converse-dropdown>`; } /** * @param {import('../sidebar-occupant').default} el */ export default (el) => { const o = el.model; const { show, presence, affiliation } = el.model.attributes; const hint_show = PRETTY_CHAT_STATUS[show || presence]; const role = o.get('role'); let classes, color; if (show === 'chat' || (!show && presence === 'online')) { [classes, color] = ['fa fa-circle', 'chat-status-online']; } else if (show === 'dnd') { [classes, color] = ['fa fa-minus-circle', 'chat-status-busy']; } else if (show === 'away' || show === 'xa') { [classes, color] = ['fa fa-circle', 'chat-status-away']; } else { [classes, color] = ['fa fa-circle', 'chat-status-offline']; } const num_unread = getUnreadMsgsDisplay(o); return html` <li class="list-item occupant" id="${o.id}"> <a @click="${(ev) => el.onOccupantClicked(ev, o)}"> <div class="row g-0"> <div class="col-auto"> <converse-avatar .model=${o} class="avatar chat-msg__avatar" name="${o.getDisplayName()}" nonce=${o.vcard?.get('vcard_updated')} height="30" width="30" ></converse-avatar> <converse-icon title="${hint_show}" color="var(--${color})" size="0.82em" class="${classes} chat-status chat-status--avatar" ></converse-icon> ${num_unread ? html`<span class="msgs-indicator badge">${num_unread}</span>` : ''} </div> <div class="col occupant-nick-badge"> <span class="occupant-nick" title="${getOccupantTitle(o)}" style="${getAuthorStyle(o)}" >${o.getDisplayName()}</span > <span class="occupant-badges"> ${affiliation === 'owner' ? tplBadge('owner') : ''} ${affiliation === 'admin' ? tplBadge('admin') : ''} ${affiliation === 'member' ? tplBadge('member') : ''} ${role === 'moderator' ? tplBadge('moderator') : ''} ${role === 'visitor' ? tplBadge('visitor') : ''} </span> ${until(tplActionButtons(o))} </div> </div> </a> </li> `; };