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))
+ }