Turn bookmarks list into a Lit component

This commit is contained in:
JC Brand 2022-04-19 20:53:01 +02:00
parent 230b72139a
commit ce22508344
8 changed files with 76 additions and 109 deletions

View File

@ -13,7 +13,7 @@ const Bookmarks = {
model: Bookmark,
comparator: (item) => item.get('name').toLowerCase(),
initialize () {
async initialize () {
this.on('add', bm => this.openBookmarkedRoom(bm)
.then(bm => this.markRoomAsBookmarked(bm))
.catch(e => log.fatal(e))
@ -25,6 +25,17 @@ const Bookmarks = {
const cache_key = `converse.room-bookmarks${_converse.bare_jid}`;
this.fetched_flag = cache_key+'fetched';
initStorage(this, cache_key);
await this.fetchBookmarks();
/**
* Triggered once the _converse.Bookmarks collection
* has been created and cached bookmarks have been fetched.
* @event _converse#bookmarksInitialized
* @type { _converse.Bookmarks }
* @example _converse.api.listen.on('bookmarksInitialized', (bookmarks) => { ... });
*/
api.trigger('bookmarksInitialized', this);
},
async openBookmarkedRoom (bookmark) {
@ -108,16 +119,12 @@ const Bookmarks = {
markRoomAsBookmarked (bookmark) {
const groupchat = _converse.chatboxes.get(bookmark.get('jid'));
if (groupchat !== undefined) {
groupchat.save('bookmarked', true);
}
groupchat?.save('bookmarked', true);
},
markRoomAsUnbookmarked (bookmark) {
const groupchat = _converse.chatboxes.get(bookmark.get('jid'));
if (groupchat !== undefined) {
groupchat.save('bookmarked', false);
}
groupchat?.save('bookmarked', false);
},
createBookmarksFromStanza (stanza) {

View File

@ -7,58 +7,31 @@
import "@converse/headless/plugins/muc/index.js";
import Bookmark from './model.js';
import Bookmarks from './collection.js';
import log from "@converse/headless/log.js";
import { Collection } from "@converse/skeletor/src/collection";
import { Collection } from "@converse/skeletor/src/collection.js";
import { Model } from '@converse/skeletor/src/model.js';
import { _converse, api, converse } from "@converse/headless/core";
import { initBookmarks, getNicknameFromBookmark } from './utils.js';
import { _converse, api, converse } from "@converse/headless/core.js";
import { initBookmarks, getNicknameFromBookmark, handleBookmarksPush } from './utils.js';
const { Strophe, sizzle } = converse.env;
const { Strophe } = converse.env;
Strophe.addNamespace('BOOKMARKS', 'storage:bookmarks');
function handleBookmarksPush (message) {
if (sizzle(`event[xmlns="${Strophe.NS.PUBSUB}#event"] items[node="${Strophe.NS.BOOKMARKS}"]`, message).length) {
api.waitUntil('bookmarksInitialized')
.then(() => _converse.bookmarks.createBookmarksFromStanza(message))
.catch(e => log.fatal(e));
}
return true;
}
converse.plugins.add('converse-bookmarks', {
/* Plugin dependencies are other plugins which might be
* overridden or relied upon, and therefore need to be loaded before
* this plugin.
*
* If the setting "strict_plugin_dependencies" is set to true,
* an error will be raised if the plugin is not found. By default it's
* false, which means these plugins are only loaded opportunistically.
*
* NB: These plugins need to have already been loaded via require.js.
*/
dependencies: ["converse-chatboxes", "converse-muc"],
overrides: {
// Overrides mentioned here will be picked up by converse.js's
// plugin architecture they will replace existing methods on the
// relevant objects or classes.
//
// New functions which don't exist yet can also be added.
ChatRoom: {
getDisplayName () {
const { _converse } = this.__super__;
if (this.get('bookmarked') && _converse.bookmarks) {
const bookmark = _converse.bookmarks.get(this.get('jid'));
if (bookmark) {
return bookmark.get('name');
}
}
return this.__super__.getDisplayName.apply(this, arguments);
const { _converse, getDisplayName } = this.__super__;
const bookmark = this.get('bookmarked') ? _converse.bookmarks?.get(this.get('jid')) : null;
return bookmark?.get('name') || getDisplayName.apply(this, arguments);
},
getAndPersistNickname (nick) {
@ -69,10 +42,6 @@ converse.plugins.add('converse-bookmarks', {
},
initialize () {
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
// Configuration values for this plugin
// ====================================
// Refer to docs/source/configuration.rst for explanations of these
@ -101,7 +70,7 @@ converse.plugins.add('converse-bookmarks', {
})
api.listen.on('clearSession', () => {
if (_converse.bookmarks !== undefined) {
if (_converse.bookmarks) {
_converse.bookmarks.clearStore({'silent': true});
window.sessionStorage.removeItem(_converse.bookmarks.fetched_flag);
delete _converse.bookmarks;

View File

@ -1,5 +1,7 @@
import { _converse, api, converse } from '@converse/headless/core';
const { Strophe } = converse.env;
import log from "@converse/headless/log.js";
import { _converse, api, converse } from '@converse/headless/core.js';
const { Strophe, sizzle } = converse.env;
export async function checkBookmarksSupport () {
const identity = await api.disco.getIdentity('pubsub', 'pep', _converse.bare_jid);
@ -16,27 +18,21 @@ export async function initBookmarks () {
}
if (await checkBookmarksSupport()) {
_converse.bookmarks = new _converse.Bookmarks();
await _converse.bookmarks.fetchBookmarks();
/**
* Triggered once the _converse.Bookmarks collection
* has been created and cached bookmarks have been fetched.
* @event _converse#bookmarksInitialized
* @example _converse.api.listen.on('bookmarksInitialized', () => { ... });
*/
api.trigger('bookmarksInitialized');
}
}
/**
* Check if the user has a bookmark with a saved nickanme
* for this groupchat and return it.
*/
export function getNicknameFromBookmark (jid) {
if (!_converse.bookmarks || !api.settings.get('allow_bookmarks')) {
if (!api.settings.get('allow_bookmarks')) {
return null;
}
const bookmark = _converse.bookmarks.get(jid);
if (bookmark) {
return bookmark.get('nick');
}
return _converse.bookmarks?.get(jid)?.get('nick');
}
export function handleBookmarksPush (message) {
if (sizzle(`event[xmlns="${Strophe.NS.PUBSUB}#event"] items[node="${Strophe.NS.BOOKMARKS}"]`, message).length) {
api.waitUntil('bookmarksInitialized')
.then(() => _converse.bookmarks.createBookmarksFromStanza(message))
.catch(e => log.fatal(e));
}
return true;
}

View File

@ -38,7 +38,7 @@ const UserDetailsModal = BootstrapModal.extend({
* Triggered once the UserDetailsModal has been initialized
* @event _converse#userDetailsModalInitialized
* @type { _converse.ChatBox }
* @example _converse.api.listen.on('userDetailsModalInitialized', chatbox => { ... });
* @example _converse.api.listen.on('userDetailsModalInitialized', (chatbox) => { ... });
*/
api.trigger('userDetailsModalInitialized', this.model);
},

View File

@ -1,57 +1,47 @@
import log from '@converse/headless/log';
import log from '@converse/headless/log.js';
import tpl_bookmarks_list from './templates/list.js';
import { ElementView } from '@converse/skeletor/src/element.js';
import { _converse, api, converse } from '@converse/headless/core';
import { CustomElement } from 'shared/components/element.js';
import { _converse, api } from '@converse/headless/core.js';
import { initStorage } from '@converse/headless/utils/storage.js';
import { render } from 'lit';
const u = converse.env.utils;
export default class BookmarksView extends ElementView {
export default class BookmarksView extends CustomElement {
async initialize () {
await api.waitUntil('bookmarksInitialized');
const { bookmarks, chatboxes } = _converse;
this.listenTo(_converse.bookmarks, 'add', this.render);
this.listenTo(_converse.bookmarks, 'remove', this.render);
this.listenTo(bookmarks, 'add', () => this.requestUpdate());
this.listenTo(bookmarks, 'remove', () => this.requestUpdate());
this.listenTo(_converse.chatboxes, 'add', this.render);
this.listenTo(_converse.chatboxes, 'remove', this.render);
this.listenTo(chatboxes, 'add', () => this.requestUpdate());
this.listenTo(chatboxes, 'remove', () => this.requestUpdate());
const id = `converse.bookmarks-list-model-${_converse.bare_jid}`;
this.model = new _converse.BookmarksList({ id });
initStorage(this.model, id);
this.listenTo(this.model, 'change', () => this.requestUpdate());
this.model.fetch({
'success': () => this.render(),
'error': (model, err) => {
'success': () => this.requestUpdate(),
'error': (_m, err) => {
log.error(err);
this.render();
this.requestUpdate();
}
});
}
render () {
render(tpl_bookmarks_list({
'toggleBookmarksList': ev => this.toggleBookmarksList(ev),
'toggle_state': this.model.get('toggle-state')
}), this);
return _converse.bookmarks ? tpl_bookmarks_list(this) : '';
}
toggleBookmarksList (ev) {
ev?.preventDefault?.();
const icon_el = ev.target.matches('.fa') ? ev.target : ev.target.querySelector('.fa');
if (u.hasClass('fa-caret-down', icon_el)) {
u.slideIn(this.querySelector('.bookmarks'));
this.model.save({ 'toggle-state': _converse.CLOSED });
icon_el.classList.remove('fa-caret-down');
icon_el.classList.add('fa-caret-right');
} else {
icon_el.classList.remove('fa-caret-right');
icon_el.classList.add('fa-caret-down');
u.slideOut(this.querySelector('.bookmarks'));
this.model.save({ 'toggle-state': _converse.OPENED });
}
const { CLOSED, OPENED } = _converse;
this.model.save({
'toggle-state': this.model.get('toggle-state') === CLOSED ? OPENED : CLOSED
});
}
}

View File

@ -3,8 +3,8 @@ import { _converse, api } from '@converse/headless/core.js';
import { html } from "lit";
import { openRoomViaEvent, removeBookmarkViaEvent } from '../utils.js';
export default (o) => {
const jid = o.bm.get('jid');
export default (bm) => {
const jid = bm.get('jid');
const is_hidden = !!(api.settings.get('hide_open_bookmarks') && _converse.chatboxes.get(jid));
const info_remove_bookmark = __('Unbookmark this groupchat');
const open_title = __('Click to open this groupchat');
@ -12,11 +12,11 @@ export default (o) => {
<div class="list-item controlbox-padded room-item available-chatroom d-flex flex-row ${ is_hidden ? 'hidden' : ''}" data-room-jid="${jid}">
<a class="list-item-link open-room w-100" data-room-jid="${jid}"
title="${open_title}"
@click=${openRoomViaEvent}>${o.bm.getDisplayName()}</a>
@click=${openRoomViaEvent}>${bm.getDisplayName()}</a>
<a class="list-item-action remove-bookmark fa fa-bookmark align-self-center ${ o.bm.get('bookmarked') ? 'button-on' : '' }"
<a class="list-item-action remove-bookmark fa fa-bookmark align-self-center ${ bm.get('bookmarked') ? 'button-on' : '' }"
data-room-jid="${jid}"
data-bookmark-name="${o.bm.getDisplayName()}"
data-bookmark-name="${bm.getDisplayName()}"
title="${info_remove_bookmark}"
@click=${removeBookmarkViaEvent}></a>
</div>

View File

@ -3,20 +3,21 @@ import { __ } from 'i18n';
import { _converse } from '@converse/headless/core.js';
import { html } from "lit";
export default (o) => {
const is_collapsed = _converse.bookmarks.getUnopenedBookmarks().length ? true : false;
export default (el) => {
const should_show = !!_converse.bookmarks.getUnopenedBookmarks().length;
const desc_bookmarks = __('Click to toggle the bookmarks list');
const label_bookmarks = __('Bookmarks');
const toggle_state = el.model.get('toggle-state');
return html`
<div class="list-container list-container--bookmarks ${ !is_collapsed && 'hidden' || '' }">
<div class="list-container list-container--bookmarks ${ should_show ? 'fade-in' : 'hidden' }">
<a class="list-toggle bookmarks-toggle controlbox-padded"
title="${desc_bookmarks}"
@click=${o.toggleBookmarksList}>
@click=${() => el.toggleBookmarksList()}>
<span class="fa ${(o.toggle_state === _converse.OPENED) ? 'fa-caret-down' : 'fa-caret-right' }">
<span class="fa ${(toggle_state === _converse.OPENED) ? 'fa-caret-down' : 'fa-caret-right' }">
</span> ${label_bookmarks}</a>
<div class="items-list bookmarks rooms-list ${ (o.toggle_state !== _converse.OPENED) ? 'hidden' : '' }">
${ _converse.bookmarks.map(bm => bookmark_item(Object.assign({bm}, o))) }
<div class="items-list bookmarks rooms-list ${ (toggle_state === _converse.OPENED) ? 'fade-in' : 'hidden fade-out' }">
${ _converse.bookmarks.map(bm => bookmark_item(bm)) }
</div>
</div>
`;

View File

@ -662,11 +662,15 @@ describe("Bookmarks", function () {
const bookmarks_el = chats_el.querySelector('converse-bookmarks');
expect(bookmarks_el.model.get('toggle-state')).toBe(_converse.OPENED);
sizzle('#chatrooms .bookmarks-toggle', chats_el).pop().click();
expect(u.hasClass('collapsed', sizzle('#chatrooms .bookmarks.rooms-list', chats_el).pop())).toBeTruthy();
await u.waitUntil(() => u.hasClass('hidden', sizzle('#chatrooms .bookmarks.rooms-list', chats_el).pop()));
expect(bookmarks_el.model.get('toggle-state')).toBe(_converse.CLOSED);
sizzle('#chatrooms .bookmarks-toggle', chats_el).pop().click();
expect(u.hasClass('collapsed', sizzle('#chatrooms .bookmarks.rooms-list', chats_el).pop())).toBeFalsy();
await u.waitUntil(() => !u.hasClass('hidden', sizzle('#chatrooms .bookmarks.rooms-list', chats_el).pop()));
expect(sizzle(selector, chats_el).filter(u.isVisible).length).toBe(1);
expect(bookmarks_el.model.get('toggle-state')).toBe(_converse.OPENED);
}));