浏览代码

Turn bookmarks list into a Lit component

JC Brand 3 年之前
父节点
当前提交
ce22508344

+ 14 - 7
src/headless/plugins/bookmarks/collection.js

@@ -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) {

+ 8 - 39
src/headless/plugins/bookmarks/index.js

@@ -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;

+ 14 - 18
src/headless/plugins/bookmarks/utils.js

@@ -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;
 }

+ 1 - 1
src/modals/user-details.js

@@ -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);
     },

+ 19 - 29
src/plugins/bookmark-views/bookmarks-list.js

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

+ 5 - 5
src/plugins/bookmark-views/templates/item.js

@@ -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>

+ 8 - 7
src/plugins/bookmark-views/templates/list.js

@@ -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>
     `;

+ 6 - 2
src/plugins/bookmark-views/tests/bookmarks.js

@@ -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);
         }));