2020-12-28 18:41:38 +01:00
|
|
|
import log from "@converse/headless/log";
|
2020-12-28 21:04:44 +01:00
|
|
|
import tpl_pending_contact from "./templates/pending_contact.js";
|
|
|
|
import tpl_requesting_contact from "./templates/requesting_contact.js";
|
|
|
|
import tpl_roster_item from "./templates/roster_item.js";
|
2021-01-22 16:07:52 +01:00
|
|
|
import { ViewWithAvatar } from 'shared/avatar.js';
|
2020-12-28 18:41:38 +01:00
|
|
|
import { __ } from 'i18n';
|
|
|
|
import { _converse, api, converse } from "@converse/headless/core";
|
|
|
|
import { debounce, without } from "lodash-es";
|
2020-12-28 21:04:44 +01:00
|
|
|
import { render } from 'lit-html';
|
2020-12-28 18:41:38 +01:00
|
|
|
|
|
|
|
const u = converse.env.utils;
|
|
|
|
|
|
|
|
const STATUSES = {
|
|
|
|
'dnd': __('This contact is busy'),
|
|
|
|
'online': __('This contact is online'),
|
|
|
|
'offline': __('This contact is offline'),
|
|
|
|
'unavailable': __('This contact is unavailable'),
|
|
|
|
'xa': __('This contact is away for an extended period'),
|
|
|
|
'away': __('This contact is away')
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const RosterContactView = ViewWithAvatar.extend({
|
|
|
|
tagName: 'li',
|
|
|
|
className: 'list-item d-flex hidden controlbox-padded',
|
|
|
|
|
|
|
|
events: {
|
|
|
|
"click .accept-xmpp-request": "acceptRequest",
|
|
|
|
"click .decline-xmpp-request": "declineRequest",
|
|
|
|
"click .open-chat": "openChat",
|
|
|
|
"click .remove-xmpp-contact": "removeContact"
|
|
|
|
},
|
|
|
|
|
|
|
|
async initialize () {
|
|
|
|
await this.model.initialized;
|
|
|
|
this.debouncedRender = debounce(this.render, 50);
|
|
|
|
this.listenTo(this.model, "change", this.debouncedRender);
|
|
|
|
this.listenTo(this.model, "destroy", this.remove);
|
|
|
|
this.listenTo(this.model, "highlight", this.highlight);
|
|
|
|
this.listenTo(this.model, "remove", this.remove);
|
|
|
|
this.listenTo(this.model, 'vcard:change', this.debouncedRender);
|
|
|
|
this.listenTo(this.model.presence, "change:show", this.debouncedRender);
|
|
|
|
this.render();
|
|
|
|
},
|
|
|
|
|
|
|
|
render () {
|
|
|
|
if (!this.mayBeShown()) {
|
|
|
|
u.hideElement(this.el);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
const ask = this.model.get('ask'),
|
|
|
|
show = this.model.presence.get('show'),
|
|
|
|
requesting = this.model.get('requesting'),
|
|
|
|
subscription = this.model.get('subscription'),
|
|
|
|
jid = this.model.get('jid');
|
|
|
|
|
|
|
|
const classes_to_remove = [
|
|
|
|
'current-xmpp-contact',
|
|
|
|
'pending-xmpp-contact',
|
|
|
|
'requesting-xmpp-contact'
|
|
|
|
].concat(Object.keys(STATUSES));
|
|
|
|
classes_to_remove.forEach(c => u.removeClass(c, this.el));
|
|
|
|
|
|
|
|
this.el.classList.add(show);
|
|
|
|
this.el.setAttribute('data-status', show);
|
|
|
|
this.highlight();
|
|
|
|
|
|
|
|
if (_converse.isUniView()) {
|
|
|
|
const chatbox = _converse.chatboxes.get(this.model.get('jid'));
|
|
|
|
if (chatbox) {
|
|
|
|
if (chatbox.get('hidden')) {
|
|
|
|
this.el.classList.remove('open');
|
|
|
|
} else {
|
|
|
|
this.el.classList.add('open');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((ask === 'subscribe') || (subscription === 'from')) {
|
|
|
|
/* ask === 'subscribe'
|
|
|
|
* Means we have asked to subscribe to them.
|
|
|
|
*
|
|
|
|
* subscription === 'from'
|
|
|
|
* They are subscribed to use, but not vice versa.
|
|
|
|
* We assume that there is a pending subscription
|
|
|
|
* from us to them (otherwise we're in a state not
|
|
|
|
* supported by converse.js).
|
|
|
|
*
|
|
|
|
* So in both cases the user is a "pending" contact.
|
|
|
|
*/
|
|
|
|
const display_name = this.model.getDisplayName();
|
|
|
|
this.el.classList.add('pending-xmpp-contact');
|
2020-12-28 21:04:44 +01:00
|
|
|
render(tpl_pending_contact(Object.assign(this.model.toJSON(), { display_name })), this.el);
|
|
|
|
|
2020-12-28 18:41:38 +01:00
|
|
|
} else if (requesting === true) {
|
|
|
|
const display_name = this.model.getDisplayName();
|
|
|
|
this.el.classList.add('requesting-xmpp-contact');
|
2020-12-28 21:04:44 +01:00
|
|
|
render(tpl_requesting_contact(
|
2020-12-28 18:41:38 +01:00
|
|
|
Object.assign(this.model.toJSON(), {
|
|
|
|
display_name,
|
|
|
|
'desc_accept': __("Click to accept the contact request from %1$s", display_name),
|
|
|
|
'desc_decline': __("Click to decline the contact request from %1$s", display_name),
|
|
|
|
'allow_chat_pending_contacts': api.settings.get('allow_chat_pending_contacts')
|
|
|
|
})
|
2020-12-28 21:04:44 +01:00
|
|
|
), this.el);
|
2020-12-28 18:41:38 +01:00
|
|
|
} else if (subscription === 'both' || subscription === 'to' || _converse.rosterview.isSelf(jid)) {
|
|
|
|
this.el.classList.add('current-xmpp-contact');
|
|
|
|
this.el.classList.remove(without(['both', 'to'], subscription)[0]);
|
|
|
|
this.el.classList.add(subscription);
|
|
|
|
this.renderRosterItem(this.model);
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* If appropriate, highlight the contact (by adding the 'open' class).
|
|
|
|
* @private
|
|
|
|
* @method _converse.RosterContactView#highlight
|
|
|
|
*/
|
|
|
|
highlight () {
|
|
|
|
if (_converse.isUniView()) {
|
|
|
|
const chatbox = _converse.chatboxes.get(this.model.get('jid'));
|
|
|
|
if ((chatbox && chatbox.get('hidden')) || !chatbox) {
|
|
|
|
this.el.classList.remove('open');
|
|
|
|
} else {
|
|
|
|
this.el.classList.add('open');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
renderRosterItem (item) {
|
|
|
|
const show = item.presence.get('show') || 'offline';
|
|
|
|
let status_icon;
|
|
|
|
if (show === 'online') {
|
|
|
|
status_icon = 'fa fa-circle chat-status chat-status--online';
|
|
|
|
} else if (show === 'away') {
|
|
|
|
status_icon = 'fa fa-circle chat-status chat-status--away';
|
|
|
|
} else if (show === 'xa') {
|
|
|
|
status_icon = 'far fa-circle chat-status chat-status-xa';
|
|
|
|
} else if (show === 'dnd') {
|
|
|
|
status_icon = 'fa fa-minus-circle chat-status chat-status--busy';
|
|
|
|
} else {
|
|
|
|
status_icon = 'fa fa-times-circle chat-status chat-status--offline';
|
|
|
|
}
|
|
|
|
const display_name = item.getDisplayName();
|
2020-12-28 21:04:44 +01:00
|
|
|
render(tpl_roster_item(
|
2020-12-28 18:41:38 +01:00
|
|
|
Object.assign(item.toJSON(), {
|
|
|
|
show,
|
|
|
|
display_name,
|
|
|
|
status_icon,
|
|
|
|
'desc_status': STATUSES[show],
|
|
|
|
'num_unread': item.get('num_unread') || 0,
|
|
|
|
classes: ''
|
|
|
|
})
|
2020-12-28 21:04:44 +01:00
|
|
|
), this.el);
|
2020-12-28 18:41:38 +01:00
|
|
|
this.renderAvatar();
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a boolean indicating whether this contact should
|
|
|
|
* generally be visible in the roster.
|
|
|
|
* It doesn't check for the more specific case of whether
|
|
|
|
* the group it's in is collapsed.
|
|
|
|
* @private
|
|
|
|
* @method _converse.RosterContactView#mayBeShown
|
|
|
|
*/
|
|
|
|
mayBeShown () {
|
|
|
|
const chatStatus = this.model.presence.get('show');
|
|
|
|
if (api.settings.get('hide_offline_users') && chatStatus === 'offline') {
|
|
|
|
// If pending or requesting, show
|
|
|
|
if ((this.model.get('ask') === 'subscribe') ||
|
|
|
|
(this.model.get('subscription') === 'from') ||
|
|
|
|
(this.model.get('requesting') === true)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
|
|
|
openChat (ev) {
|
|
|
|
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
|
|
|
this.model.openChat();
|
|
|
|
},
|
|
|
|
|
|
|
|
async removeContact (ev) {
|
|
|
|
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
|
|
|
if (!api.settings.get('allow_contact_removal')) { return; }
|
|
|
|
if (!confirm(__("Are you sure you want to remove this contact?"))) { return; }
|
|
|
|
|
|
|
|
try {
|
|
|
|
await this.model.removeFromRoster();
|
|
|
|
this.remove();
|
|
|
|
if (this.model.collection) {
|
|
|
|
// The model might have already been removed as
|
|
|
|
// result of a roster push.
|
|
|
|
this.model.destroy();
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
log.error(e);
|
|
|
|
api.alert('error', __('Error'),
|
|
|
|
[__('Sorry, there was an error while trying to remove %1$s as a contact.', this.model.getDisplayName())]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
async acceptRequest (ev) {
|
|
|
|
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
|
|
|
|
|
|
|
await _converse.roster.sendContactAddIQ(
|
|
|
|
this.model.get('jid'),
|
|
|
|
this.model.getFullname(),
|
|
|
|
[]
|
|
|
|
);
|
|
|
|
this.model.authorize().subscribe();
|
|
|
|
},
|
|
|
|
|
|
|
|
declineRequest (ev) {
|
|
|
|
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
|
|
|
const result = confirm(__("Are you sure you want to decline this contact request?"));
|
|
|
|
if (result === true) {
|
|
|
|
this.model.unauthorize().destroy();
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
export default RosterContactView;
|