diff --git a/CHANGES.md b/CHANGES.md index 2482d9fbb0..3bb760cd05 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -24,6 +24,7 @@ - #3939: Don't show invitations to groupchats in which the user is already present - #3941: add adhoc completed command result and text-multi as merged lines of text - Don't render unfurls for retracted messages. +- #3949: Allow pinning bookmarked conversations to the top (XEP-0469) ## 12.0.0 (2025-08-28) diff --git a/src/headless/plugins/bookmarks/collection.js b/src/headless/plugins/bookmarks/collection.js index 3c5f4e86f9..0e90b66078 100644 --- a/src/headless/plugins/bookmarks/collection.js +++ b/src/headless/plugins/bookmarks/collection.js @@ -82,6 +82,7 @@ class Bookmarks extends Collection { const groupchat = await api.rooms.create(bookmark.get('jid'), { nick: bookmark.get('nick'), password: bookmark.get('password'), + pinned: bookmark.get('pinned'), }); groupchat.maybeShow(); } @@ -251,6 +252,7 @@ class Bookmarks extends Collection { const { chatboxes } = _converse.state; const groupchat = chatboxes.get(bookmark.get('jid')); groupchat?.save('bookmarked', true); + groupchat?.save('pinned', bookmark.pinned); } /** @@ -334,6 +336,52 @@ class Bookmarks extends Collection { const { chatboxes } = _converse.state; return this.filter((b) => !chatboxes.get(b.get('jid'))); } + + /** + * + * @param {Bookmark} bookmark + */ + pinBookmark(bookmark) { + const extensions = [...bookmark.get('extensions'), '']; + + const { chatboxes } = _converse.state; + const groupchat = chatboxes.get(bookmark.get('jid')); + groupchat?.save('pinned', true); + + try { + api.bookmarks.set({ + jid: bookmark.get('jid'), + extensions, + }); + } catch (error) { + groupchat?.save('pinned', false); + log.error('Error while trying to pin bookmark'); + log.error(error); + } + } + + /** + * + * @param {Bookmark} bookmark + */ + unpinBookmark(bookmark) { + const extensions = bookmark.get('extensions').filter(/** @param {String} e */ e => !(e.includes(' e.includes('`); }), ); + + it("can be pinned and sends out a stanza", mock.initConverse( + ['connected', 'chatBoxesFetched'], {}, async function (_converse) { + await mock.waitForRoster(_converse, 'current', 0); + await mock.waitUntilBookmarksReturned(_converse); + + const bare_jid = _converse.session.get('bare_jid'); + const muc_jid = 'theplay@conference.shakespeare.lit'; + const { api, state } = _converse; + + // First create a bookmark + state.bookmarks.create({ + jid: muc_jid, + autojoin: true, + name: 'The Play', + nick: 'romeo', + extensions: [], + }); + + await mock.waitForMUCDiscoInfo(_converse, muc_jid); + await u.waitUntil(() => state.chatboxes.length === 1); + + const IQ_stanzas = api.connection.get().IQ_stanzas; + + // Now pin the bookmark + const bookmark = state.bookmarks.findWhere({ jid: muc_jid }); + expect(bookmark).toBeTruthy(); + await state.bookmarks.pinBookmark(bookmark); + + + const sent_stanza = await u.waitUntil(() => + IQ_stanzas.filter((s) => sizzle('publish[node="urn:xmpp:bookmarks:1"] conference[name="The Play"] extensions pinned', s).length).pop() + ); + + expect(bookmark.pinned).toBe(true); + + const chatbox = state.chatboxes.get(muc_jid); + expect(chatbox.get('pinned')).toBe(true); + + expect(sent_stanza).toEqualStanza(stx` + + + + + + romeo + + + + + + + + + + http://jabber.org/protocol/pubsub#publish-options + + + true + + + max + + + never + + + whitelist + + + + + `); + }) + ); + + it("can be unpinned and sends out a stanza", mock.initConverse( + ['connected', 'chatBoxesFetched'], {}, async function (_converse) { + await mock.waitForRoster(_converse, 'current', 0); + await mock.waitUntilBookmarksReturned(_converse); + + const bare_jid = _converse.session.get('bare_jid'); + const muc_jid = 'theplay@conference.shakespeare.lit'; + const { api, state } = _converse; + + // First create a pinned bookmark + const bookmark = state.bookmarks.create({ + jid: muc_jid, + autojoin: true, + name: 'The Play', + nick: 'romeo', + extensions: [''], + }); + + await mock.waitForMUCDiscoInfo(_converse, muc_jid); + await u.waitUntil(() => state.chatboxes.length === 1); + + const IQ_stanzas = api.connection.get().IQ_stanzas; + + expect(bookmark.pinned).toBe(true); + expect(state.chatboxes.get(muc_jid).get('pinned')).toBe(true); + + // Now unpin the bookmark + await state.bookmarks.unpinBookmark(bookmark); + + const sent_stanza = await u.waitUntil(() => + IQ_stanzas.filter((s) => sizzle('publish[node="urn:xmpp:bookmarks:1"] conference[name="The Play"]', s).length).pop() + ); + + expect(bookmark.pinned).toBe(false); + expect(state.chatboxes.get(muc_jid).get('pinned')).toBe(false); + + expect(sent_stanza).toEqualStanza(stx` + + + + + + romeo + + + + + + + http://jabber.org/protocol/pubsub#publish-options + + + true + + + max + + + never + + + whitelist + + + + + `); + }) + ); }); diff --git a/src/headless/types/plugins/bookmarks/collection.d.ts b/src/headless/types/plugins/bookmarks/collection.d.ts index f4ec17cfeb..4ffdca2a06 100644 --- a/src/headless/types/plugins/bookmarks/collection.d.ts +++ b/src/headless/types/plugins/bookmarks/collection.d.ts @@ -78,6 +78,16 @@ declare class Bookmarks extends Collection { */ onBookmarksReceivedError(deferred: any, iq: Element): Promise; getUnopenedBookmarks(): Promise; + /** + * + * @param {Bookmark} bookmark + */ + pinBookmark(bookmark: Bookmark): void; + /** + * + * @param {Bookmark} bookmark + */ + unpinBookmark(bookmark: Bookmark): void; } import Bookmark from './model.js'; import { Collection } from '@converse/skeletor'; diff --git a/src/headless/types/plugins/bookmarks/model.d.ts b/src/headless/types/plugins/bookmarks/model.d.ts index 0ba37472ea..6fc20c5de0 100644 --- a/src/headless/types/plugins/bookmarks/model.d.ts +++ b/src/headless/types/plugins/bookmarks/model.d.ts @@ -1,6 +1,10 @@ export default Bookmark; declare class Bookmark extends Model { constructor(attributes?: Partial, options?: import("@converse/skeletor").ModelOptions); + /** + * @returns {boolean} + */ + get pinned(): boolean; getDisplayName(): any; } import { Model } from '@converse/skeletor'; diff --git a/src/plugins/bookmark-views/components/bookmarks-pin-list.js b/src/plugins/bookmark-views/components/bookmarks-pin-list.js new file mode 100644 index 0000000000..b01bee0b48 --- /dev/null +++ b/src/plugins/bookmark-views/components/bookmarks-pin-list.js @@ -0,0 +1,38 @@ +import { _converse, api, u } from '@converse/headless'; +import tplBookmarksPinList from './templates/pin-list'; +import BookmarksPinListModel from './model'; +import { RoomsList } from 'plugins/roomslist/view'; + +const { initStorage } = u; + +export class BookmarksPinView extends RoomsList { + model = null; + + initialize() { + const bare_jid = _converse.session.get('bare_jid'); + const id = `converse.bookmarks-pin-list-model-${bare_jid}`; + this.model = new BookmarksPinListModel({ id }); + _converse.state.bookmarks_pin_list = this.model; + + initStorage(this.model, id); + this.model.fetch(); + + this.handleEvents(); + + this.requestUpdate(); + } + + /** @returns {import('@converse/headless').MUC[]} */ + getRoomsToShow() { + const { chatboxes } = _converse.state; + const rooms = chatboxes.filter((m) => m.get('pinned')); + rooms.sort((a, b) => (a.getDisplayName().toLowerCase() <= b.getDisplayName().toLowerCase() ? -1 : 1)); + return rooms; + } + + render() { + return tplBookmarksPinList(this); + } +} + +api.elements.define('converse-bookmarks-pin', BookmarksPinView); diff --git a/src/plugins/bookmark-views/components/model.js b/src/plugins/bookmark-views/components/model.js new file mode 100644 index 0000000000..e55ffc8c04 --- /dev/null +++ b/src/plugins/bookmark-views/components/model.js @@ -0,0 +1,9 @@ +import { constants, Model } from "@converse/headless"; + +export default class BookmarksPinListModel extends Model { + defaults () { + return { + toggle_state: constants.OPENED, + } + } +} diff --git a/src/plugins/bookmark-views/components/styles/pin-list.scss b/src/plugins/bookmark-views/components/styles/pin-list.scss new file mode 100644 index 0000000000..691c42f1e7 --- /dev/null +++ b/src/plugins/bookmark-views/components/styles/pin-list.scss @@ -0,0 +1,22 @@ +.conversejs { + #converse-bookmarks-pin { + padding-bottom: 1rem; + + converse-bookmarks-pin { + .list-item { + .open-room { + display: flex; + flex-direction: row; + line-height: 1.5em; + height: 2.5em; + padding: 0.2em 0; + span { + overflow-x: hidden; + text-overflow: ellipsis; + padding-top: 0.25em; + } + } + } + } + } +} diff --git a/src/plugins/bookmark-views/components/templates/pin-list.js b/src/plugins/bookmark-views/components/templates/pin-list.js new file mode 100644 index 0000000000..c1baa1c1c9 --- /dev/null +++ b/src/plugins/bookmark-views/components/templates/pin-list.js @@ -0,0 +1,43 @@ +/** + * @typedef {import('@converse/headless').MUC} MUC + * @typedef {import('plugins/bookmark-views/components/bookmarks-pin-list').BookmarksPinView} BookmarksPinView + */ + +import { constants } from "@converse/headless"; +import { __ } from "i18n"; +import { html } from "lit"; +import { tplRoomItem } from "shared/roomslist/templates/room-item"; +import '../styles/pin-list.scss'; + +/** + * @param {BookmarksPinView} el + */ +export default (el) => { + const rooms = el.getRoomsToShow(); + const is_closed = el.model.get('toggle_state') === constants.CLOSED; + + return html` + + +
+
    + ${ + rooms.map(/** @param {MUC} room */(room) => tplRoomItem(el, room)) + } +
+
`; +} diff --git a/src/plugins/bookmark-views/index.js b/src/plugins/bookmark-views/index.js index e927fff4ea..2bd617cc95 100644 --- a/src/plugins/bookmark-views/index.js +++ b/src/plugins/bookmark-views/index.js @@ -9,6 +9,7 @@ import BookmarkForm from './components/bookmark-form.js'; import BookmarksView from './components/bookmarks-list.js'; import { BookmarkableChatRoomView } from './mixins.js'; import { removeBookmarkViaEvent } from './utils.js'; +import { BookmarksPinView } from './components/bookmarks-pin-list.js'; import './styles/bookmarks.scss'; @@ -36,6 +37,7 @@ converse.plugins.add('converse-bookmark-views', { removeBookmarkViaEvent, MUCBookmarkForm: BookmarkForm, BookmarksView, + BookmarksPinView, }; Object.assign(_converse, exports); // DEPRECATED diff --git a/src/plugins/bookmark-views/tests/bookmarks-pin-list.js b/src/plugins/bookmark-views/tests/bookmarks-pin-list.js new file mode 100644 index 0000000000..0d2900d419 --- /dev/null +++ b/src/plugins/bookmark-views/tests/bookmarks-pin-list.js @@ -0,0 +1,56 @@ +/* global mock, converse */ +const { u } = converse.env; + +describe("The bookmarks pin list", function () { + it("shows a list of pinned bookmarks", mock.initConverse(['connected', 'chatboxesFetched'], {}, async function (_converse) { + const { api } = _converse; + await mock.waitForRoster(_converse, 'current', 0); + await mock.waitUntilBookmarksReturned(_converse); + await mock.openControlBox(_converse); + + const bookmarks_pin_list = document.querySelector('converse-bookmarks-pin'); + const main_list = document.querySelector('converse-rooms-list'); + + let muc_jid = 'room@conference.shakespeare.lit'; + await api.bookmarks.set({ + jid: muc_jid, + name: 'Romeo\'s room', + autojoin: true, + nick: 'romeo', + extensions: [''], + }) + await mock.waitForMUCDiscoInfo(_converse, muc_jid); + + await u.waitUntil(() => bookmarks_pin_list.querySelectorAll(".open-room").length); + let room_els = bookmarks_pin_list.querySelectorAll(".open-room"); + expect(room_els.length).toBe(1); + expect(main_list.querySelectorAll(".open-room").length).toBe(0); + + muc_jid = 'lounge@montague.lit'; + await api.bookmarks.set({ + jid: muc_jid, + name: 'Lounge', + autojoin: true, + nick: 'romeo', + extensions: [''], + }); + await mock.waitForMUCDiscoInfo(_converse, muc_jid); + + await u.waitUntil(() => bookmarks_pin_list.querySelectorAll(".open-room").length > 1); + room_els = bookmarks_pin_list.querySelectorAll(".open-room"); + expect(room_els.length).toBe(2); + expect(main_list.querySelectorAll(".open-room").length).toBe(0); + + // Unpin a room + bookmarks_pin_list.querySelector('.unpin-room').click(); + await u.waitUntil(() => bookmarks_pin_list.querySelectorAll(".open-room").length === 1); + expect(bookmarks_pin_list.querySelectorAll(".open-room").length).toBe(1); + expect(main_list.querySelectorAll(".open-room").length).toBe(1); + + // pin it again + main_list.querySelector('.pin-room').click(); + await u.waitUntil(() => bookmarks_pin_list.querySelectorAll(".open-room").length === 2); + expect(bookmarks_pin_list.querySelectorAll(".open-room").length).toBe(2); + expect(main_list.querySelectorAll(".open-room").length).toBe(0); + })); +}); diff --git a/src/plugins/controlbox/templates/controlbox.js b/src/plugins/controlbox/templates/controlbox.js index de632966d9..db5e996310 100644 --- a/src/plugins/controlbox/templates/controlbox.js +++ b/src/plugins/controlbox/templates/controlbox.js @@ -47,6 +47,9 @@ export default (el) => { ? html`
+
+ +
${api.settings.get('authentication') === ANONYMOUS ? '' diff --git a/src/plugins/roomslist/templates/roomslist.js b/src/plugins/roomslist/templates/roomslist.js index b39fbb9054..b25f6827fb 100644 --- a/src/plugins/roomslist/templates/roomslist.js +++ b/src/plugins/roomslist/templates/roomslist.js @@ -3,72 +3,14 @@ * @typedef {import('@converse/headless').MUC} MUC */ import { html } from "lit"; -import { _converse, api, u, constants } from "@converse/headless"; +import { api, constants } from "@converse/headless"; import 'plugins/muc-views/modals/add-muc.js'; import 'plugins/muc-views/modals/muc-list.js'; import { __ } from 'i18n'; -import { getUnreadMsgsDisplay } from "shared/chat/utils"; - import '../styles/roomsgroups.scss'; +import { tplRoomItem } from "shared/roomslist/templates/room-item"; const { CLOSED } = constants; -const { isUniView } = u; - -/** @param {MUC} room */ -function isCurrentlyOpen (room) { - return isUniView() && !room.get('hidden'); -} - -/** @param {MUC} room */ -function tplUnreadIndicator (room) { - return html`${ getUnreadMsgsDisplay(room) }`; -} - -function tplActivityIndicator () { - return html``; -} - -/** - * @param {RoomsList} el - * @param {MUC} room - */ -function tplRoomItem (el, room) { - const i18n_leave_room = __('Leave this groupchat'); - const has_unread_msgs = room.get('num_unread_general') || room.get('has_activity'); - return html` -
  • - - el.openRoom(ev)}> - - ${ room.get('num_unread') ? - tplUnreadIndicator(room) : - (room.get('has_activity') ? tplActivityIndicator() : '') } - ${room.getDisplayName()} - - - el.closeRoom(ev)}> - - -
  • `; -} /** * @param {RoomsList} el diff --git a/src/plugins/roomslist/view.js b/src/plugins/roomslist/view.js index 1b14e515c6..8a335ee9fb 100644 --- a/src/plugins/roomslist/view.js +++ b/src/plugins/roomslist/view.js @@ -19,6 +19,12 @@ export class RoomsList extends CustomElement { initStorage(this.model, id); this.model.fetch(); + this.handleEvents(); + + this.requestUpdate(); + } + + handleEvents() { const { chatboxes } = _converse.state; this.listenTo(chatboxes, 'add', this.renderIfChatRoom); this.listenTo(chatboxes, 'remove', this.renderIfChatRoom); @@ -27,8 +33,6 @@ export class RoomsList extends CustomElement { this.listenTo(chatboxes, 'vcard:add', () => this.requestUpdate()); this.listenTo(chatboxes, 'vcard:change', () => this.requestUpdate()); this.listenTo(this.model, 'change', () => this.requestUpdate()); - - this.requestUpdate(); } render() { @@ -42,7 +46,7 @@ export class RoomsList extends CustomElement { /** @param {import('@converse/headless').Model} model */ renderIfRelevantChange(model) { - const attrs = ['bookmarked', 'hidden', 'name', 'num_unread', 'num_unread_general', 'has_activity']; + const attrs = ['bookmarked', 'hidden', 'name', 'num_unread', 'num_unread_general', 'has_activity', 'pinned']; const changed = model.changed || {}; if (u.muc.isChatRoom(model) && Object.keys(changed).filter((m) => attrs.includes(m)).length) { this.requestUpdate(); @@ -52,7 +56,7 @@ export class RoomsList extends CustomElement { /** @returns {import('@converse/headless').MUC[]} */ getRoomsToShow() { const { chatboxes } = _converse.state; - const rooms = chatboxes.filter((m) => m.get('type') === CHATROOMS_TYPE && !m.get('closed')); + const rooms = chatboxes.filter((m) => m.get('type') === CHATROOMS_TYPE && !m.get('closed') && !m.get('pinned')); rooms.sort((a, b) => (a.getDisplayName().toLowerCase() <= b.getDisplayName().toLowerCase() ? -1 : 1)); return rooms; } @@ -82,6 +86,29 @@ export class RoomsList extends CustomElement { } } + /** @param {Event} ev */ + pinRoom(ev) { + ev.preventDefault(); + const target = /** @type {HTMLElement} */ (ev.currentTarget); + const jid = target.getAttribute('data-room-jid'); + const { bookmarks } = _converse.state; + bookmarks + .where({ jid }) + .forEach(/** @param {import('@converse/headless').Bookmark} b */ (b) => + bookmarks.pinBookmark(b)); + } + + /** @param {Event} ev */ + unpinRoom(ev) { + ev.preventDefault(); + const target = /** @type {HTMLElement} */ (ev.currentTarget); + const jid = target.getAttribute('data-room-jid'); + const { bookmarks } = _converse.state; + bookmarks + .where({ jid }) + .forEach((b) => bookmarks.unpinBookmark(b)); + } + /** @param {Event} [ev] */ toggleRoomsList(ev) { ev?.preventDefault?.(); diff --git a/src/shared/roomslist/templates/room-item.js b/src/shared/roomslist/templates/room-item.js new file mode 100644 index 0000000000..b0d76c6cbf --- /dev/null +++ b/src/shared/roomslist/templates/room-item.js @@ -0,0 +1,116 @@ +/** + * @typedef {import('plugins/roomslist/view').RoomsList} RoomsList + * @typedef {import('plugins/bookmark-views/components/bookmarks-pin-list').BookmarksPinView} BookmarksPinView + * @typedef {import('@converse/headless').MUC} MUC + */ +import { html } from "lit"; +import { api, u } from "@converse/headless"; +import 'plugins/muc-views/modals/add-muc.js'; +import 'plugins/muc-views/modals/muc-list.js'; +import { __ } from 'i18n'; +import { getUnreadMsgsDisplay } from "shared/chat/utils"; + +const { isUniView } = u; + +/** @param {MUC} room */ +function isCurrentlyOpen (room) { + return isUniView() && !room.get('hidden'); +} + +/** @param {MUC} room */ +function tplUnreadIndicator (room) { + return html`${ getUnreadMsgsDisplay(room) }`; +} + +function tplActivityIndicator () { + return html``; +} + +/** + * @param {RoomsList|BookmarksPinView} el + * @param {MUC} room + */ +export function tplRoomItem (el, room) { + const i18n_leave_room = __('Leave this groupchat'); + const has_unread_msgs = room.get('num_unread_general') || room.get('has_activity'); + + const buttons = [ + tplRoomMenuItem({ + room, + alt_text: i18n_leave_room, + text: __('Leave'), + icon_class: 'fa-sign-out-alt', + btn_class: 'close-room', + handler: (ev) => el.closeRoom(ev) + }), + ]; + + if (api.settings.get('allow_bookmarks')) { + if (!room.get('pinned')) { + buttons.push(tplRoomMenuItem({ + room, + alt_text: __('Pin this groupchat to the top of the list'), + text: __('Pin'), + icon_class: 'fa-bookmark', + btn_class: 'pin-room', + handler: (ev) => el.pinRoom(ev) + })) + } else { + buttons.push(tplRoomMenuItem({ + room, + alt_text: __('Unpin this groupchat from the top of the list'), + text: __('Unpin'), + icon_class: 'fa-bookmark-empty', + btn_class: 'unpin-room', + handler: (ev) => el.unpinRoom(ev) + })) + } + } + + return html` +
  • + + el.openRoom(ev)}> + + ${ room.get('num_unread') ? + tplUnreadIndicator(room) : + (room.get('has_activity') ? tplActivityIndicator() : '') } + ${room.getDisplayName()} + + + +
  • `; +} + +/** + * @param {Object} config + * @param {MUC} config.room + * @param {string} config.alt_text + * @param {string} config.text + * @param {function} config.handler + * @param {string} config.icon_class + * @param {string} config.btn_class + * @returns + */ +function tplRoomMenuItem (config) { + const { room, alt_text, text, handler, icon_class, btn_class } = config; + return html` + + ${text} + `; +} diff --git a/src/types/plugins/bookmark-views/components/bookmarks-pin-list.d.ts b/src/types/plugins/bookmark-views/components/bookmarks-pin-list.d.ts new file mode 100644 index 0000000000..a1a54f973a --- /dev/null +++ b/src/types/plugins/bookmark-views/components/bookmarks-pin-list.d.ts @@ -0,0 +1,5 @@ +export class BookmarksPinView extends RoomsList { + model: any; +} +import { RoomsList } from 'plugins/roomslist/view'; +//# sourceMappingURL=bookmarks-pin-list.d.ts.map \ No newline at end of file diff --git a/src/types/plugins/bookmark-views/components/model.d.ts b/src/types/plugins/bookmark-views/components/model.d.ts new file mode 100644 index 0000000000..b406ee8223 --- /dev/null +++ b/src/types/plugins/bookmark-views/components/model.d.ts @@ -0,0 +1,8 @@ +export default class BookmarksPinListModel extends Model { + constructor(attributes?: Partial, options?: import("@converse/skeletor").ModelOptions); + defaults(): { + toggle_state: "opened"; + }; +} +import { Model } from "@converse/headless"; +//# sourceMappingURL=model.d.ts.map \ No newline at end of file diff --git a/src/types/plugins/bookmark-views/components/templates/pin-list.d.ts b/src/types/plugins/bookmark-views/components/templates/pin-list.d.ts new file mode 100644 index 0000000000..942587b0a3 --- /dev/null +++ b/src/types/plugins/bookmark-views/components/templates/pin-list.d.ts @@ -0,0 +1,5 @@ +declare function _default(el: BookmarksPinView): import("lit-html").TemplateResult<1>; +export default _default; +export type MUC = import("@converse/headless").MUC; +export type BookmarksPinView = import("plugins/bookmark-views/components/bookmarks-pin-list").BookmarksPinView; +//# sourceMappingURL=pin-list.d.ts.map \ No newline at end of file diff --git a/src/types/plugins/roomslist/view.d.ts b/src/types/plugins/roomslist/view.d.ts index 0139f4dadd..7492164a5b 100644 --- a/src/types/plugins/roomslist/view.d.ts +++ b/src/types/plugins/roomslist/view.d.ts @@ -1,6 +1,7 @@ export class RoomsList extends CustomElement { initialize(): void; model: RoomsListModel; + handleEvents(): void; render(): import("lit-html").TemplateResult<1>; /** @param {import('@converse/headless').Model} model */ renderIfChatRoom(model: import("@converse/headless").Model): void; @@ -12,6 +13,10 @@ export class RoomsList extends CustomElement { openRoom(ev: Event): Promise; /** @param {Event} ev */ closeRoom(ev: Event): Promise; + /** @param {Event} ev */ + pinRoom(ev: Event): void; + /** @param {Event} ev */ + unpinRoom(ev: Event): void; /** @param {Event} [ev] */ toggleRoomsList(ev?: Event): void; /** diff --git a/src/types/shared/roomslist/templates/room-item.d.ts b/src/types/shared/roomslist/templates/room-item.d.ts new file mode 100644 index 0000000000..703abab1d8 --- /dev/null +++ b/src/types/shared/roomslist/templates/room-item.d.ts @@ -0,0 +1,9 @@ +/** + * @param {RoomsList|BookmarksPinView} el + * @param {MUC} room + */ +export function tplRoomItem(el: RoomsList | BookmarksPinView, room: MUC): import("lit-html").TemplateResult<1>; +export type RoomsList = import("plugins/roomslist/view").RoomsList; +export type BookmarksPinView = import("plugins/bookmark-views/components/bookmarks-pin-list").BookmarksPinView; +export type MUC = import("@converse/headless").MUC; +//# sourceMappingURL=room-item.d.ts.map \ No newline at end of file