فهرست منبع

Add new mixin ModelWithVCard

JC Brand 4 ماه پیش
والد
کامیت
8ca99042cc

+ 2 - 0
src/headless/plugins/muc/message.js

@@ -130,6 +130,8 @@ class MUCMessage extends Message {
 
         this.trigger('occupant:add');
         this.listenTo(this.occupant, 'change', (changed) => this.trigger('occupant:change', changed));
+        this.listenTo(this.occupant, 'vcard:add', (changed) => this.trigger('occupant:change', changed));
+        this.listenTo(this.occupant, 'vcard:change', (changed) => this.trigger('occupant:change', changed));
         this.listenTo(this.occupant, 'destroy', this.onOccupantRemoved);
         this.stopListening(this.occupants, 'add', this.onOccupantAdded);
 

+ 5 - 13
src/headless/plugins/vcard/plugin.js

@@ -11,11 +11,7 @@ import vcard_api from './api.js';
 import VCards from "./vcards";
 import {
     clearVCardsSession,
-    initVCardCollection,
     onOccupantAvatarChanged,
-    setVCardOnMUCMessage,
-    setVCardOnModel,
-    setVCardOnOccupant,
 } from './utils.js';
 
 const { Strophe } = converse.env;
@@ -25,6 +21,10 @@ converse.plugins.add('converse-vcard', {
 
     dependencies: ["converse-status", "converse-roster"],
 
+    enabled () {
+        return !api.settings.get('blacklisted_plugins')?.includes('converse-vcard');
+    },
+
     initialize () {
         api.promises.add('VCardsInitialized');
 
@@ -33,19 +33,11 @@ converse.plugins.add('converse-vcard', {
         Object.assign(_converse.exports, exports);
 
         api.listen.on('chatRoomInitialized', (m) => {
-            setVCardOnModel(m)
-            m.occupants.forEach(setVCardOnOccupant);
-            m.listenTo(m.occupants, 'add', setVCardOnOccupant);
-            m.listenTo(m.occupants, 'change:image_hash', o => onOccupantAvatarChanged(o));
+            m.listenTo(m.occupants, 'change:image_hash', (o) => onOccupantAvatarChanged(o));
         });
 
-        api.listen.on('chatBoxInitialized', m => setVCardOnModel(m));
-        api.listen.on('chatRoomMessageInitialized', m => setVCardOnMUCMessage(m));
         api.listen.on('addClientFeatures', () => api.disco.own.features.add(Strophe.NS.VCARD));
         api.listen.on('clearSession', () => clearVCardsSession());
-        api.listen.on('messageInitialized', m => setVCardOnModel(m));
-        api.listen.on('rosterContactInitialized', m => setVCardOnModel(m));
-        api.listen.on('statusInitialized', initVCardCollection);
 
         Object.assign(_converse.api, vcard_api);
     }

+ 47 - 49
src/headless/plugins/vcard/utils.js

@@ -2,8 +2,10 @@
  * @typedef {import('../../plugins/muc/message').default} MUCMessage
  * @typedef {import('../../plugins/status/status').default} XMPPStatus
  * @typedef {import('../../plugins/vcard/vcards').default} VCards
+ * @typedef {import('../../plugins/vcard/vcard').default} VCard
  * @typedef {import('../../shared/model-with-contact.js').default} ModelWithContact
  * @typedef {import('../muc/occupant.js').default} MUCOccupant
+ * @typedef {import('@converse/skeletor/src/types/helpers.js').Model} Model
  */
 import _converse from '../../shared/_converse.js';
 import api from '../../shared/api/index.js';
@@ -77,11 +79,20 @@ export function onOccupantAvatarChanged (occupant) {
 
 
 /**
- * @param {InstanceType<ReturnType<ModelWithContact>>} model
+ * @param {Model} model
+ * @param {boolean} [create=true]
+ * @returns {Promise<VCard>}
  */
-export async function setVCardOnModel (model) {
+export async function getVCardForModel (model, create=true) {
+    await initVCardCollection();
+
+    let vcard;
+    if (model instanceof _converse.exports.MUCOccupant) {
+        return getVCardForOccupant(/** @type {MUCOccupant} */(model), create);
+    }
+
     if (model instanceof _converse.exports.MUCMessage) {
-        return setVCardOnMUCMessage(/** @type {MUCMessage} */(model));
+        return getVCardForMUCMessage(/** @type {MUCMessage} */(model), create);
     }
 
     let jid;
@@ -99,87 +110,80 @@ export async function setVCardOnModel (model) {
         return;
     }
 
-    await api.waitUntil('VCardsInitialized');
     const { vcards } = _converse.state;
-    model.vcard = vcards.get(jid) || vcards.create({ jid });
-    model.vcard.on('change', () => model.trigger('vcard:change'));
-    model.trigger('vcard:add');
-}
+    vcard = vcards.get(jid) || create && vcards.create({ jid });
 
+    vcard.on('change', () => model.trigger('vcard:change'));
+    return vcard;
+}
 
 /**
  * @param {MUCOccupant} occupant
+ * @param {boolean} [create=true]
+ * @returns {Promise<VCard|null>}
  */
-function getVCardForOccupant (occupant) {
+export async function getVCardForOccupant(occupant, create=true) {
+    await api.waitUntil('VCardsInitialized');
+
     const { vcards, xmppstatus } = _converse.state;
     const muc = occupant?.collection?.chatroom;
     const nick = occupant.get('nick');
+    let vcard;
 
     if (nick && muc?.get('nick') === nick) {
-        return xmppstatus.vcard;
+        vcard = xmppstatus.vcard;
     } else {
         const jid = occupant.get('jid') || occupant.get('from');
         if (jid) {
-            return vcards.get(jid) || vcards.create({ jid });
+            vcard = vcards.get(jid) || create && vcards.create({ jid });
         } else {
             log.warn(`Could not get VCard for occupant because no JID found!`);
-            return;
+            return null;
         }
     }
-}
 
-/**
- * @param {MUCOccupant} occupant
- */
-export async function setVCardOnOccupant (occupant) {
-    await api.waitUntil('VCardsInitialized');
-    occupant.vcard = getVCardForOccupant(occupant);
-    if (occupant.vcard) {
-        occupant.vcard.on('change', () => occupant.trigger('vcard:change'));
-        occupant.trigger('vcard:add');
+    if (vcard) {
+        vcard.on('change', () => occupant.trigger('vcard:change'));
     }
+    return vcard;
 }
 
 
 /**
  * @param {MUCMessage} message
+ * @param {boolean} [create=true]
+ * @returns {Promise<VCard|null>}
  */
-function getVCardForMUCMessage (message) {
+async function getVCardForMUCMessage (message, create=true) {
+    if (['error', 'info'].includes(message.get('type'))) return;
+
+    await api.waitUntil('VCardsInitialized');
     const { vcards, xmppstatus } = _converse.state;
     const muc = message?.collection?.chatbox;
     const nick = Strophe.getResourceFromJid(message.get('from'));
+    let vcard;
 
     if (nick && muc?.get('nick') === nick) {
-        return xmppstatus.vcard;
+        vcard = xmppstatus.vcard;
     } else {
         const jid = message.occupant?.get('jid') || message.get('from');
         if (jid) {
-            return vcards.get(jid) || vcards.create({ jid });
+            vcard = vcards.get(jid) || create && vcards.create({ jid });
         } else {
             log.warn(`Could not get VCard for message because no JID found! msgid: ${message.get('msgid')}`);
-            return;
+            return null;
         }
     }
-}
 
-/**
- * @param {MUCMessage} message
- */
-export async function setVCardOnMUCMessage (message) {
-    if (['error', 'info'].includes(message.get('type'))) {
-        return;
-    } else {
-        await api.waitUntil('VCardsInitialized');
-        message.vcard = getVCardForMUCMessage(message);
-        if (message.vcard) {
-            message.vcard.on('change', () => message.trigger('vcard:change'));
-            message.trigger('vcard:add');
-        }
+    if (vcard) {
+        vcard.on('change', () => message.trigger('vcard:change'));
     }
+    return vcard;
 }
 
+async function initVCardCollection () {
+    if (_converse.state.vcards) return _converse.state.vcards;
 
-export async function initVCardCollection () {
     const vcards = new _converse.exports.VCards();
     _converse.state.vcards = vcards;
     Object.assign(_converse, { vcards }); // XXX DEPRECATED
@@ -189,16 +193,10 @@ export async function initVCardCollection () {
     initStorage(vcards, id);
     await new Promise(resolve => {
         vcards.fetch({
-            'success': resolve,
-            'error': resolve
+            success: resolve,
+            error: resolve
         }, {'silent': true});
     });
-    const { xmppstatus } = _converse.state;
-    xmppstatus.vcard = vcards.get(bare_jid) || vcards.create({'jid': bare_jid});
-    if (xmppstatus.vcard) {
-        xmppstatus.vcard.on('change', () => xmppstatus.trigger('vcard:change'));
-        xmppstatus.trigger('vcard:add');
-    }
     /**
      * Triggered as soon as the `_converse.vcards` collection has been initialized and populated from cache.
      * @event _converse#VCardsInitialized

+ 4 - 0
src/headless/shared/model-with-contact.js

@@ -53,6 +53,10 @@ export default function ModelWithContact(BaseModel) {
                 this.contact = contact;
                 this.set('nickname', contact.get('nickname'));
 
+                this.listenTo(this.contact, 'vcard:add', (changed) => {
+                    this.trigger('contact:change', changed);
+                });
+
                 this.listenTo(this.contact, 'change', (changed) => {
                     if (changed.nickname) {
                         this.set('nickname', changed.nickname);

+ 49 - 0
src/headless/shared/model-with-vcard.js

@@ -0,0 +1,49 @@
+import { getVCardForModel } from "../plugins/vcard/utils.js";
+import _converse from "./_converse.js";
+import VCard from "../plugins/vcard/vcard.js";
+
+/**
+ * @template {import('./types').ModelExtender} T
+ * @param {T} BaseModel
+ */
+export default function ModelWithVCard(BaseModel) {
+    return class ModelWithVCard extends BaseModel {
+        /**
+         * @param {any[]} args
+         */
+        constructor(...args) {
+            super(...args);
+            this._vcard = null;
+            this.lazy_load_vcard = false;
+        }
+
+        initialize() {
+            super.initialize();
+            if (this.lazy_load_vcard) {
+                this.getVCard(false);
+                this.once("visibilityChanged", () => this.getVCard());
+            } else {
+                this.getVCard();
+            }
+        }
+
+        get vcard() {
+            return this._vcard;
+        }
+
+        /**
+         * @returns {Promise<VCard|null>}
+         */
+        async getVCard(create=true) {
+            const { pluggable } = _converse;
+            if (!pluggable.plugins["converse-vcard"]?.enabled(_converse)) return null;
+
+            if (this._vcard) return this._vcard;
+
+            this._vcard = await getVCardForModel(this, create);
+            this.trigger("vcard:add", { vcard: this._vcard });
+
+            return this._vcard;
+        }
+    };
+}

+ 9 - 8
src/headless/types/plugins/vcard/utils.d.ts

@@ -9,18 +9,17 @@ export function createStanza(type: "get" | "set" | "result", jid: string, vcard_
  */
 export function onOccupantAvatarChanged(occupant: MUCOccupant): void;
 /**
- * @param {InstanceType<ReturnType<ModelWithContact>>} model
+ * @param {Model} model
+ * @param {boolean} [create=true]
+ * @returns {Promise<VCard>}
  */
-export function setVCardOnModel(model: InstanceType<ReturnType<ModelWithContact>>): Promise<void>;
+export function getVCardForModel(model: Model, create?: boolean): Promise<VCard>;
 /**
  * @param {MUCOccupant} occupant
+ * @param {boolean} [create=true]
+ * @returns {Promise<VCard|null>}
  */
-export function setVCardOnOccupant(occupant: MUCOccupant): Promise<void>;
-/**
- * @param {MUCMessage} message
- */
-export function setVCardOnMUCMessage(message: MUCMessage): Promise<void>;
-export function initVCardCollection(): Promise<void>;
+export function getVCardForOccupant(occupant: MUCOccupant, create?: boolean): Promise<VCard | null>;
 export function clearVCardsSession(): void;
 /**
  * @param {string} jid
@@ -36,6 +35,8 @@ export function getVCard(jid: string): Promise<{
 export type MUCMessage = import("../../plugins/muc/message").default;
 export type XMPPStatus = import("../../plugins/status/status").default;
 export type VCards = import("../../plugins/vcard/vcards").default;
+export type VCard = import("../../plugins/vcard/vcard").default;
 export type ModelWithContact = typeof import("../../shared/model-with-contact.js").default;
 export type MUCOccupant = import("../muc/occupant.js").default;
+export type Model = import("@converse/skeletor/src/types/helpers.js").Model;
 //# sourceMappingURL=utils.d.ts.map

+ 79 - 0
src/headless/types/shared/model-with-vcard.d.ts

@@ -0,0 +1,79 @@
+/**
+ * @template {import('./types').ModelExtender} T
+ * @param {T} BaseModel
+ */
+export default function ModelWithVCard<T extends import("./types").ModelExtender>(BaseModel: T): {
+    new (...args: any[]): {
+        _vcard: VCard;
+        lazy_load_vcard: boolean;
+        initialize(): void;
+        readonly vcard: VCard;
+        /**
+         * @returns {Promise<VCard|null>}
+         */
+        getVCard(create?: boolean): Promise<VCard | null>;
+        cid: any;
+        attributes: {};
+        validationError: string;
+        collection: any;
+        changed: {};
+        browserStorage: Storage;
+        _browserStorage: Storage;
+        readonly idAttribute: string;
+        readonly cidPrefix: string;
+        preinitialize(): void;
+        validate(attrs: object, options?: object): string;
+        toJSON(): any;
+        sync(method: "create" | "update" | "patch" | "delete" | "read", model: import("@converse/skeletor").Model, options: import("@converse/skeletor/src/types/model.js").Options): any;
+        get(attr: string): any;
+        keys(): string[];
+        values(): any[];
+        pairs(): [string, any][];
+        entries(): [string, any][];
+        invert(): any;
+        pick(...args: any[]): any;
+        omit(...args: any[]): any;
+        isEmpty(): any;
+        has(attr: string): boolean;
+        matches(attrs: import("@converse/skeletor/src/types/model.js").Attributes): boolean;
+        set(key: string | any, val?: string | any, options?: import("@converse/skeletor/src/types/model.js").Options): false | any;
+        _changing: boolean;
+        _previousAttributes: any;
+        id: any;
+        _pending: boolean | import("@converse/skeletor/src/types/model.js").Options;
+        unset(attr: string, options?: import("@converse/skeletor/src/types/model.js").Options): false | any;
+        clear(options: import("@converse/skeletor/src/types/model.js").Options): false | any;
+        hasChanged(attr?: string): any;
+        changedAttributes(diff: any): any;
+        previous(attr?: string): any;
+        previousAttributes(): any;
+        fetch(options?: import("@converse/skeletor/src/types/model.js").Options): any;
+        save(key?: string | import("@converse/skeletor/src/types/model.js").Attributes, val?: boolean | number | string | import("@converse/skeletor/src/types/model.js").Options, options?: import("@converse/skeletor/src/types/model.js").Options): any;
+        destroy(options?: import("@converse/skeletor/src/types/model.js").Options): boolean;
+        url(): any;
+        parse(resp: import("@converse/skeletor/src/types/model.js").Options, options?: import("@converse/skeletor/src/types/model.js").Options): import("@converse/skeletor/src/types/model.js").Options;
+        isNew(): boolean;
+        isValid(options?: import("@converse/skeletor/src/types/model.js").Options): boolean;
+        _validate(attrs: import("@converse/skeletor/src/types/model.js").Attributes, options?: import("@converse/skeletor/src/types/model.js").Options): boolean;
+        on(name: string, callback: (event: any, model: import("@converse/skeletor").Model, collection: import("@converse/skeletor").Collection, options: Record<string, any>) => any, context: any): any;
+        _events: any;
+        _listeners: {};
+        listenTo(obj: any, name: string, callback?: (event: any, model: import("@converse/skeletor").Model, collection: import("@converse/skeletor").Collection, options: Record<string, any>) => any): any;
+        _listeningTo: {};
+        _listenId: any;
+        off(name: string, callback: (event: any, model: import("@converse/skeletor").Model, collection: import("@converse/skeletor").Collection, options: Record<string, any>) => any, context?: any): any;
+        stopListening(obj?: any, name?: string, callback?: (event: any, model: import("@converse/skeletor").Model, collection: import("@converse/skeletor").Collection, options: Record<string, any>) => any): any;
+        once(name: string, callback: (event: any, model: import("@converse/skeletor").Model, collection: import("@converse/skeletor").Collection, options: Record<string, any>) => any, context: any): any;
+        listenToOnce(obj: any, name: string, callback?: (event: any, model: import("@converse/skeletor").Model, collection: import("@converse/skeletor").Collection, options: Record<string, any>) => any): any;
+        trigger(name: string, ...args: any[]): any;
+        constructor: Function;
+        toString(): string;
+        toLocaleString(): string;
+        valueOf(): Object;
+        hasOwnProperty(v: PropertyKey): boolean;
+        isPrototypeOf(v: Object): boolean;
+        propertyIsEnumerable(v: PropertyKey): boolean;
+    };
+} & T;
+import VCard from "../plugins/vcard/vcard.js";
+//# sourceMappingURL=model-with-vcard.d.ts.map