diff --git a/CHANGES.md b/CHANGES.md index e29f338b7d..375fe90a78 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -35,6 +35,7 @@ - Upgrade to Bootstrap 5 - 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. ### Breaking changes: diff --git a/src/plugins/muc-views/sidebar.js b/src/plugins/muc-views/sidebar.js index a110f3195d..ff7661dc1c 100644 --- a/src/plugins/muc-views/sidebar.js +++ b/src/plugins/muc-views/sidebar.js @@ -55,7 +55,7 @@ export default class MUCSidebar extends CustomElement { const tpl = tplMUCSidebar(this, Object.assign( this.model.toJSON(), { 'occupants': [...this.model.occupants.models], - 'onOccupantClicked': ev => this.onOccupantClicked(ev), + 'onOccupantClicked': ev => this.onOccupantClicked(ev) } )); return tpl; diff --git a/src/plugins/muc-views/styles/muc-occupants.scss b/src/plugins/muc-views/styles/muc-occupants.scss index ab327b14a7..5be009a137 100644 --- a/src/plugins/muc-views/styles/muc-occupants.scss +++ b/src/plugins/muc-views/styles/muc-occupants.scss @@ -109,19 +109,32 @@ .occupant-nick-badge { display: flex; justify-content: space-between; - flex-wrap: wrap; + flex-wrap: nowrap; + align-items: center; + gap: 0.25rem; + + .occupant-nick { + flex-grow: 2; + } .occupant-badges { display: flex; justify-content: flex-end; flex-wrap: wrap; flex-direction: row; + flex-shrink: 1; + gap: 0.25rem; span { height: 1.6em; - margin-right: 0.25rem; } } + + .occupant-actions { + // We must specify the position, else there is a bug: + // clicking on an action would close the dropdown without triggering the action. + position: static; + } } div.row.g-0{ diff --git a/src/plugins/muc-views/templates/occupant.js b/src/plugins/muc-views/templates/occupant.js index 006593cee8..d8ff85c3fd 100644 --- a/src/plugins/muc-views/templates/occupant.js +++ b/src/plugins/muc-views/templates/occupant.js @@ -1,9 +1,11 @@ /** * @typedef {import('@converse/headless').MUCOccupant} MUCOccupant */ +import { api } from '@converse/headless'; import { PRETTY_CHAT_STATUS } from '../constants.js'; import { __ } from 'i18n'; import { html } from "lit"; +import { until } from 'lit/directives/until.js'; import { showOccupantModal } from '../utils.js'; import { getAuthorStyle } from 'utils/color.js'; @@ -29,6 +31,45 @@ const occupant_title = /** @param {MUCOccupant} o */(o) => { } } +/** + * @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` + ` + }); + + return html``; +} /** * @param {MUCOccupant} o @@ -87,6 +128,9 @@ export default (o, chat) => { ${ (role === "moderator") ? html`${i18n_moderator}` : '' } ${ (role === "visitor") ? html`${i18n_visitor}` : '' } + ${ + until(tplActionButtons(o)) + }