|
@@ -4,32 +4,84 @@
|
|
|
* @typedef {import('@converse/headless').RosterContacts} RosterContacts
|
|
|
*/
|
|
|
import { __ } from 'i18n';
|
|
|
-import { _converse, api, converse, log, constants, u, XMPPStatus } from "@converse/headless";
|
|
|
+import { _converse, api, converse, log, constants, u, XMPPStatus } from '@converse/headless';
|
|
|
|
|
|
const { Strophe } = converse.env;
|
|
|
const { STATUS_WEIGHTS } = constants;
|
|
|
|
|
|
/**
|
|
|
* @param {RosterContact} contact
|
|
|
+ * @param {boolean} [unsubscribe]
|
|
|
+ * @returns {Promise<boolean>}
|
|
|
*/
|
|
|
-export async function removeContact (contact) {
|
|
|
+export async function removeContact(contact, unsubscribe = false) {
|
|
|
+ if (!api.settings.get('allow_contact_removal')) return;
|
|
|
+
|
|
|
+ const result = await api.confirm(__('Are you sure you want to remove this contact?'));
|
|
|
+ if (!result) return false;
|
|
|
+
|
|
|
+ const chat = await api.chats.get(contact.get('jid'));
|
|
|
+ chat?.close();
|
|
|
+ try {
|
|
|
+ contact.remove(unsubscribe);
|
|
|
+ } catch (e) {
|
|
|
+ log.error(e);
|
|
|
+ api.alert('error', __('Error'), [
|
|
|
+ __('Sorry, an error occurred while trying to remove %1$s as a contact', contact.getDisplayName()),
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @param {RosterContact} contact
|
|
|
+ * @returns {Promise<boolean>}
|
|
|
+ */
|
|
|
+export async function blockContact(contact) {
|
|
|
+ const domain = _converse.session.get('domain');
|
|
|
+ if (!(await api.disco.supports(Strophe.NS.BLOCKING, domain))) return false;
|
|
|
+
|
|
|
+ const i18n_confirm = __('Do you want to block this contact, so they cannot send you messages?');
|
|
|
+ if (!(await api.confirm(i18n_confirm))) return false;
|
|
|
+
|
|
|
+ (await api.chats.get(contact.get('jid')))?.close();
|
|
|
+
|
|
|
+ try {
|
|
|
+ contact.remove(true);
|
|
|
+ await api.blocklist.add(contact.get('jid'));
|
|
|
+ } catch (e) {
|
|
|
+ log.error(e);
|
|
|
+ api.alert('error', __('Error'), [
|
|
|
+ __('Sorry, an error occurred while trying to block %1$s', contact.getDisplayName()),
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @param {RosterContact} contact
|
|
|
+ * @returns {Promise<boolean>}
|
|
|
+ */
|
|
|
+export async function unblockContact(contact) {
|
|
|
+ const domain = _converse.session.get('domain');
|
|
|
+ if (!(await api.disco.supports(Strophe.NS.BLOCKING, domain))) return false;
|
|
|
+
|
|
|
+ const i18n_confirm = __('Do you want to unblock this contact, so they can send you messages?');
|
|
|
+ if (!(await api.confirm(i18n_confirm))) return false;
|
|
|
+
|
|
|
try {
|
|
|
- await contact.sendRosterRemoveStanza();
|
|
|
+ await api.blocklist.remove(contact.get('jid'));
|
|
|
} catch (e) {
|
|
|
log.error(e);
|
|
|
api.alert('error', __('Error'), [
|
|
|
- __('Sorry, there was an error while trying to remove %1$s as a contact.',
|
|
|
- contact.getDisplayName())
|
|
|
+ __('Sorry, an error occurred while trying to unblock %1$s', contact.getDisplayName()),
|
|
|
]);
|
|
|
- } finally {
|
|
|
- contact.destroy();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @param {string} jid
|
|
|
*/
|
|
|
-export function highlightRosterItem (jid) {
|
|
|
+export function highlightRosterItem(jid) {
|
|
|
_converse.state.roster?.get(jid)?.trigger('highlight');
|
|
|
}
|
|
|
|
|
@@ -37,12 +89,15 @@ export function highlightRosterItem (jid) {
|
|
|
* @param {Event} ev
|
|
|
* @param {string} name
|
|
|
*/
|
|
|
-export function toggleGroup (ev, name) {
|
|
|
+export function toggleGroup(ev, name) {
|
|
|
ev?.preventDefault?.();
|
|
|
const { roster } = _converse.state;
|
|
|
const collapsed = roster.state.get('collapsed_groups');
|
|
|
if (collapsed.includes(name)) {
|
|
|
- roster.state.save('collapsed_groups', collapsed.filter(n => n !== name));
|
|
|
+ roster.state.save(
|
|
|
+ 'collapsed_groups',
|
|
|
+ collapsed.filter((n) => n !== name)
|
|
|
+ );
|
|
|
} else {
|
|
|
roster.state.save('collapsed_groups', [...collapsed, name]);
|
|
|
}
|
|
@@ -71,12 +126,10 @@ function getFilterCriteria(contact) {
|
|
|
* @param {string} groupname
|
|
|
* @returns {boolean}
|
|
|
*/
|
|
|
-export function isContactFiltered (contact, groupname) {
|
|
|
+export function isContactFiltered(contact, groupname) {
|
|
|
const filter = _converse.state.roster_filter;
|
|
|
const type = filter.get('type');
|
|
|
- const q = (type === 'state') ?
|
|
|
- filter.get('state').toLowerCase() :
|
|
|
- filter.get('text').toLowerCase();
|
|
|
+ const q = type === 'state' ? filter.get('state').toLowerCase() : filter.get('text').toLowerCase();
|
|
|
|
|
|
if (!q) return false;
|
|
|
|
|
@@ -90,11 +143,11 @@ export function isContactFiltered (contact, groupname) {
|
|
|
} else if (q === 'unread_messages') {
|
|
|
return contact.get('num_unread') === 0;
|
|
|
} else if (q === 'online') {
|
|
|
- return ["offline", "unavailable", "dnd", "away", "xa"].includes(contact.getStatus());
|
|
|
+ return ['offline', 'unavailable', 'dnd', 'away', 'xa'].includes(contact.getStatus());
|
|
|
} else {
|
|
|
return !contact.getStatus().includes(q);
|
|
|
}
|
|
|
- } else if (type === 'items') {
|
|
|
+ } else if (type === 'items') {
|
|
|
return !getFilterCriteria(contact).includes(q);
|
|
|
}
|
|
|
}
|
|
@@ -105,15 +158,17 @@ export function isContactFiltered (contact, groupname) {
|
|
|
* @param {Model} model
|
|
|
* @returns {boolean}
|
|
|
*/
|
|
|
-export function shouldShowContact (contact, groupname, model) {
|
|
|
+export function shouldShowContact(contact, groupname, model) {
|
|
|
if (!model.get('filter_visible')) return true;
|
|
|
|
|
|
const chat_status = contact.getStatus();
|
|
|
if (api.settings.get('hide_offline_users') && chat_status === 'offline') {
|
|
|
// If pending or requesting, show
|
|
|
- if ((contact.get('ask') === 'subscribe') ||
|
|
|
- (contact.get('subscription') === 'from') ||
|
|
|
- (contact.get('requesting') === true)) {
|
|
|
+ if (
|
|
|
+ contact.get('ask') === 'subscribe' ||
|
|
|
+ contact.get('subscription') === 'from' ||
|
|
|
+ contact.get('requesting') === true
|
|
|
+ ) {
|
|
|
return !isContactFiltered(contact, groupname);
|
|
|
}
|
|
|
return false;
|
|
@@ -125,7 +180,7 @@ export function shouldShowContact (contact, groupname, model) {
|
|
|
* @param {string} group
|
|
|
* @param {Model} model
|
|
|
*/
|
|
|
-export function shouldShowGroup (group, model) {
|
|
|
+export function shouldShowGroup(group, model) {
|
|
|
if (!model.get('filter_visible')) return true;
|
|
|
|
|
|
const filter = _converse.state.roster_filter;
|
|
@@ -148,21 +203,21 @@ export function shouldShowGroup (group, model) {
|
|
|
* @param {RosterContact} contact
|
|
|
* @returns {import('./types').ContactsMap}
|
|
|
*/
|
|
|
-export function populateContactsMap (contacts_map, contact) {
|
|
|
+export function populateContactsMap(contacts_map, contact) {
|
|
|
const { labels } = _converse;
|
|
|
|
|
|
- const contact_groups = /** @type {string[]} */(u.unique(contact.get('groups') ?? []));
|
|
|
+ const contact_groups = /** @type {string[]} */ (u.unique(contact.get('groups') ?? []));
|
|
|
|
|
|
if (contact.get('requesting')) {
|
|
|
- contact_groups.push(/** @type {string} */(labels.HEADER_REQUESTING_CONTACTS));
|
|
|
+ contact_groups.push(/** @type {string} */ (labels.HEADER_REQUESTING_CONTACTS));
|
|
|
} else if (contact.get('ask') === 'subscribe') {
|
|
|
- contact_groups.push(/** @type {string} */(labels.HEADER_PENDING_CONTACTS));
|
|
|
+ contact_groups.push(/** @type {string} */ (labels.HEADER_PENDING_CONTACTS));
|
|
|
} else if (contact.get('subscription') === 'none') {
|
|
|
- contact_groups.push(/** @type {string} */(labels.HEADER_UNSAVED_CONTACTS));
|
|
|
+ contact_groups.push(/** @type {string} */ (labels.HEADER_UNSAVED_CONTACTS));
|
|
|
} else if (!api.settings.get('roster_groups')) {
|
|
|
- contact_groups.push(/** @type {string} */(labels.HEADER_CURRENT_CONTACTS));
|
|
|
+ contact_groups.push(/** @type {string} */ (labels.HEADER_CURRENT_CONTACTS));
|
|
|
} else if (!contact_groups.length) {
|
|
|
- contact_groups.push(/** @type {string} */(labels.HEADER_UNGROUPED));
|
|
|
+ contact_groups.push(/** @type {string} */ (labels.HEADER_UNGROUPED));
|
|
|
}
|
|
|
|
|
|
for (const name of contact_groups) {
|
|
@@ -173,7 +228,7 @@ export function populateContactsMap (contacts_map, contact) {
|
|
|
}
|
|
|
|
|
|
if (contact.get('num_unread')) {
|
|
|
- const name = /** @type {string} */(labels.HEADER_UNREAD);
|
|
|
+ const name = /** @type {string} */ (labels.HEADER_UNREAD);
|
|
|
contacts_map[name] ? contacts_map[name].push(contact) : (contacts_map[name] = [contact]);
|
|
|
}
|
|
|
return contacts_map;
|
|
@@ -184,14 +239,14 @@ export function populateContactsMap (contacts_map, contact) {
|
|
|
* @param {RosterContact|XMPPStatus} contact2
|
|
|
* @returns {(-1|0|1)}
|
|
|
*/
|
|
|
-export function contactsComparator (contact1, contact2) {
|
|
|
+export function contactsComparator(contact1, contact2) {
|
|
|
const status1 = contact1.getStatus();
|
|
|
const status2 = contact2.getStatus();
|
|
|
if (STATUS_WEIGHTS[status1] === STATUS_WEIGHTS[status2]) {
|
|
|
- const name1 = (contact1.getDisplayName()).toLowerCase();
|
|
|
- const name2 = (contact2.getDisplayName()).toLowerCase();
|
|
|
- return name1 < name2 ? -1 : (name1 > name2? 1 : 0);
|
|
|
- } else {
|
|
|
+ const name1 = contact1.getDisplayName().toLowerCase();
|
|
|
+ const name2 = contact2.getDisplayName().toLowerCase();
|
|
|
+ return name1 < name2 ? -1 : name1 > name2 ? 1 : 0;
|
|
|
+ } else {
|
|
|
return STATUS_WEIGHTS[status1] < STATUS_WEIGHTS[status2] ? -1 : 1;
|
|
|
}
|
|
|
}
|
|
@@ -200,7 +255,7 @@ export function contactsComparator (contact1, contact2) {
|
|
|
* @param {string} a
|
|
|
* @param {string} b
|
|
|
*/
|
|
|
-export function groupsComparator (a, b) {
|
|
|
+export function groupsComparator(a, b) {
|
|
|
const HEADER_WEIGHTS = {};
|
|
|
const {
|
|
|
HEADER_UNREAD,
|
|
@@ -212,47 +267,47 @@ export function groupsComparator (a, b) {
|
|
|
|
|
|
HEADER_WEIGHTS[HEADER_UNREAD] = 0;
|
|
|
HEADER_WEIGHTS[HEADER_REQUESTING_CONTACTS] = 1;
|
|
|
- HEADER_WEIGHTS[HEADER_CURRENT_CONTACTS] = 2;
|
|
|
- HEADER_WEIGHTS[HEADER_UNGROUPED] = 3;
|
|
|
- HEADER_WEIGHTS[HEADER_PENDING_CONTACTS] = 4;
|
|
|
+ HEADER_WEIGHTS[HEADER_CURRENT_CONTACTS] = 2;
|
|
|
+ HEADER_WEIGHTS[HEADER_UNGROUPED] = 3;
|
|
|
+ HEADER_WEIGHTS[HEADER_PENDING_CONTACTS] = 4;
|
|
|
|
|
|
- const WEIGHTS = HEADER_WEIGHTS;
|
|
|
+ const WEIGHTS = HEADER_WEIGHTS;
|
|
|
const special_groups = Object.keys(HEADER_WEIGHTS);
|
|
|
const a_is_special = special_groups.includes(a);
|
|
|
const b_is_special = special_groups.includes(b);
|
|
|
- if (!a_is_special && !b_is_special ) {
|
|
|
- return a.toLowerCase() < b.toLowerCase() ? -1 : (a.toLowerCase() > b.toLowerCase() ? 1 : 0);
|
|
|
+ if (!a_is_special && !b_is_special) {
|
|
|
+ return a.toLowerCase() < b.toLowerCase() ? -1 : a.toLowerCase() > b.toLowerCase() ? 1 : 0;
|
|
|
} else if (a_is_special && b_is_special) {
|
|
|
- return WEIGHTS[a] < WEIGHTS[b] ? -1 : (WEIGHTS[a] > WEIGHTS[b] ? 1 : 0);
|
|
|
+ return WEIGHTS[a] < WEIGHTS[b] ? -1 : WEIGHTS[a] > WEIGHTS[b] ? 1 : 0;
|
|
|
} else if (!a_is_special && b_is_special) {
|
|
|
const a_header = HEADER_CURRENT_CONTACTS;
|
|
|
- return WEIGHTS[a_header] < WEIGHTS[b] ? -1 : (WEIGHTS[a_header] > WEIGHTS[b] ? 1 : 0);
|
|
|
+ return WEIGHTS[a_header] < WEIGHTS[b] ? -1 : WEIGHTS[a_header] > WEIGHTS[b] ? 1 : 0;
|
|
|
} else if (a_is_special && !b_is_special) {
|
|
|
const b_header = HEADER_CURRENT_CONTACTS;
|
|
|
- return WEIGHTS[a] < WEIGHTS[b_header] ? -1 : (WEIGHTS[a] > WEIGHTS[b_header] ? 1 : 0);
|
|
|
+ return WEIGHTS[a] < WEIGHTS[b_header] ? -1 : WEIGHTS[a] > WEIGHTS[b_header] ? 1 : 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-export function getGroupsAutoCompleteList () {
|
|
|
- const roster = /** @type {RosterContacts} */(_converse.state.roster);
|
|
|
+export function getGroupsAutoCompleteList() {
|
|
|
+ const roster = /** @type {RosterContacts} */ (_converse.state.roster);
|
|
|
const groups = roster.reduce((groups, contact) => groups.concat(contact.get('groups')), []);
|
|
|
- return [...new Set(groups.filter(i => i))];
|
|
|
+ return [...new Set(groups.filter((i) => i))];
|
|
|
}
|
|
|
|
|
|
-export function getJIDsAutoCompleteList () {
|
|
|
- const roster = /** @type {RosterContacts} */(_converse.state.roster);
|
|
|
- return [...new Set(roster.map(item => Strophe.getDomainFromJid(item.get('jid'))))];
|
|
|
+export function getJIDsAutoCompleteList() {
|
|
|
+ const roster = /** @type {RosterContacts} */ (_converse.state.roster);
|
|
|
+ return [...new Set(roster.map((item) => Strophe.getDomainFromJid(item.get('jid'))))];
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @param {string} query
|
|
|
*/
|
|
|
-export async function getNamesAutoCompleteList (query) {
|
|
|
+export async function getNamesAutoCompleteList(query) {
|
|
|
const options = {
|
|
|
- 'mode': /** @type {RequestMode} */('cors'),
|
|
|
+ 'mode': /** @type {RequestMode} */ ('cors'),
|
|
|
'headers': {
|
|
|
- 'Accept': 'text/json'
|
|
|
- }
|
|
|
+ 'Accept': 'text/json',
|
|
|
+ },
|
|
|
};
|
|
|
const url = `${api.settings.get('xhr_user_search_url')}q=${encodeURIComponent(query)}`;
|
|
|
let response;
|
|
@@ -269,5 +324,5 @@ export async function getNamesAutoCompleteList (query) {
|
|
|
log.error(`Invalid JSON returned"`);
|
|
|
return [];
|
|
|
}
|
|
|
- return json.map(i => ({'label': i.fullname || i.jid, 'value': i.jid}));
|
|
|
+ return json.map((i) => ({ 'label': i.fullname || i.jid, 'value': i.jid }));
|
|
|
}
|