UNPKG

@jsxc/jsxc

Version:

Real-time XMPP chat application with video calls, file transfer and encrypted communication

564 lines (435 loc) 17.8 kB
import showContactDialog from './dialogs/contact'; import showContactSearchDialog from './dialogs/contactsearch'; import showAboutDialog from './dialogs/about'; import showMultiUserJoinDialog from './dialogs/multiUserJoin'; import showSettingsDialog from './dialogs/settings'; import * as CONST from '../CONST'; import RosterItem from './RosterItem'; import Menu from './util/Menu'; import { IContact } from '../Contact.interface'; import WindowList from './ChatWindowList'; import Client from '../Client'; import Translation from '../util/Translation'; import showSetExStatusDialog from './dialogs/exstatus'; import { Notice, TYPE } from '../Notice'; import { Presence } from '../connection/AbstractConnection'; import { NoticeManager } from '../NoticeManager'; import ClientAvatar from '../ClientAvatar'; import confirmDialog from './dialogs/confirm'; import Utils from '@util/Utils'; import Emoticons from '@src/Emoticons'; import showAddAvatarDialog from './dialogs/avatarupload'; let rosterTemplate = require('../../template/roster.hbs'); const APPEND_KEY = 'rosterAppend'; const VISIBILITY_KEY = 'rosterVisibility'; const HELP_KEY = 'onlineHelp'; const HIDE_OFFLINE_KEY = 'hideOfflineContacts'; export default class Roster { private element: JQuery; private contactList: JQuery; private groupList: JQuery; private rosterItems: { [uid: string]: RosterItem } = {}; private static instance: Roster; public static init(): void { Roster.get(); } public static get(): Roster { if (!Roster.instance) { Roster.instance = new Roster(); Client.getNoticeManager(); } return Roster.instance; } private constructor() { let template = rosterTemplate(); this.element = $(template); this.element.appendTo(Client.getOption(APPEND_KEY) + ':first'); //make sure css empty selector works $('.jsxc-js-notice-menu .jsxc-menu__button').text(''); this.contactList = this.element.find('.jsxc-contact-list'); this.groupList = this.element.find('.jsxc-group-list'); this.addMainMenuEntries(); this.registerPresenceHandler(); this.registerToggleHandler(); Menu.init(this.element.find('.jsxc-menu')); ClientAvatar.get().addElement(this.element.find('.jsxc-bottom .jsxc-avatar')); this.initOptions(); this.initHandler(); } public startProcessing(msg?: string) { this.element.addClass('jsxc-processing'); if (msg) { let spanElement = this.element.find('.jsxc-js-presence-menu > span'); spanElement.addClass('jsxc-waiting'); if (!spanElement.data('previousText')) { spanElement.data('previousText', spanElement.text()); } spanElement.text(msg); } } public endProcessing() { this.element.removeClass('jsxc-processing'); let spanElement = this.element.find('.jsxc-js-presence-menu > span'); spanElement.removeClass('jsxc-waiting'); let previousText = spanElement.data('previousText'); spanElement.data('previousText', undefined); if (previousText) { spanElement.text(previousText); } this.refreshOwnPresenceIndicator(); } public add(contact: IContact) { this.clearStatus(); if (this.rosterItems[contact.getUid()]) { return; } let rosterItem = new RosterItem(contact); this.rosterItems[contact.getUid()] = rosterItem; this.insert(rosterItem); const reinsert = () => { let globalRosterItem = this.rosterItems[contact.getUid()]; if (!globalRosterItem || globalRosterItem !== rosterItem) { return; } rosterItem.detach(); this.insert(rosterItem); }; contact.registerHook('name', reinsert); contact.registerHook('lastMessage', reinsert); } public remove(contact: IContact) { let rosterItem = this.rosterItems[contact.getUid()]; if (!rosterItem) { return; } delete this.rosterItems[contact.getUid()]; rosterItem.remove(); } public clearStatus() { this.element.find('.jsxc-roster-status').empty(); this.element.removeClass('jsxc-status-show'); } public setStatus(statusElement: JQuery) { this.element.find('.jsxc-roster-status').empty().append(statusElement); this.element.addClass('jsxc-status-show'); } public setEmptyContactList() { let statusElement = $('<p>'); statusElement.text(Translation.t('Your_roster_is_empty_add_')); statusElement.find('a').click(<any>showContactDialog); statusElement.append('.'); this.setStatus(statusElement); } public refreshOwnPresenceIndicator() { let status = Client.getPresenceController().getStatus(); let confirmedPresence = Client.getPresenceController().getCurrentPresence(); let requestedPresence = Client.getPresenceController().getTargetPresence(); let presence = typeof requestedPresence === 'number' ? requestedPresence : confirmedPresence; let htmlStatus = Emoticons.toImage(Utils.escapeHTML(status)); this.element.find('.jsxc-js-presence-menu .jsxc-bar__caption__secondary').html(htmlStatus); let label = $('.jsxc-js-presence-menu .jsxc-' + Presence[presence]).text(); let labelElement = this.element.find('.jsxc-js-presence-menu .jsxc-bar__caption__primary'); labelElement.text(label); this.element.attr('data-presence', Presence[confirmedPresence]); if (requestedPresence === confirmedPresence) { labelElement.removeClass('jsxc-waiting'); } else { labelElement.addClass('jsxc-waiting'); } } public registerHook(property: string, func: (newValue: any, oldValue: any) => void) { Client.getOptions().registerHook(property, func); } public addNotice(manager: NoticeManager, notice: Notice) { let noticeListElement = $('.jsxc-js-notice-menu ul'); let noticeElement = $('<li/>'); noticeElement.click(function (ev) { ev.stopPropagation(); ev.preventDefault(); notice.callFunction(); manager.removeNotice(notice); }); noticeElement.addClass('jsxc-icon--' + TYPE[notice.getType()].toLowerCase()); noticeElement.text(notice.getTitle()); noticeElement.attr('title', notice.getDescription()); noticeElement.attr('data-notice-id', notice.getId()); noticeElement.attr('data-manager-id', manager.getId()); noticeListElement.prepend(noticeElement); let numberOfNotices = noticeListElement.find('li').not('.jsxc-js-delete-all').length; $('.jsxc-js-notice-menu .jsxc-menu__button').text(numberOfNotices); if (numberOfNotices > 2 && noticeListElement.find('.jsxc-js-delete-all').length === 0) { let deleteAllElement = $('<li>'); deleteAllElement.addClass('jsxc-js-delete-all jsxc-menu__item--danger jsxc-icon--delete'); deleteAllElement.text(Translation.t('Close_all')); deleteAllElement.appendTo(noticeListElement); deleteAllElement.click(ev => { ev.stopPropagation(); ev.preventDefault(); let dialog = confirmDialog(Translation.t('Do_you_really_want_to_dismiss_all_notices')); dialog .getPromise() .then(() => { NoticeManager.removeAll(); }) .catch(() => {}); }); } } public removeNotice(manager: NoticeManager, noticeId: string) { let managerId = manager.getId() || ''; let noticeElement = $('.jsxc-js-notice-menu li').filter(function () { return $(this).attr('data-notice-id') === noticeId && ($(this).attr('data-manager-id') || '') === managerId; }); noticeElement.remove(); let numberOfNotices = $('.jsxc-js-notice-menu li').not('.jsxc-js-delete-all').length; $('.jsxc-js-notice-menu .jsxc-menu__button').text(numberOfNotices > 0 ? numberOfNotices : ''); if (numberOfNotices < 3) { $('.jsxc-js-notice-menu .jsxc-js-delete-all').remove(); } } public addMenuEntry(options: { id: string; handler: (ev: JQuery.ClickEvent<HTMLElement>) => void; label: string | JQuery<HTMLElement>; icon?: string; offlineAvailable?: boolean; }) { const { id, handler, label, icon, offlineAvailable } = options; let li = $('<li>'); if (!id || !handler || !label) { throw new Error('id, handler and label required for menu entry'); } if (typeof label === 'string') { li.text(label); } else { li.append(label); } li.addClass('jsxc-' + id.toLowerCase().replace(' ', '-')); if (offlineAvailable) { li.addClass('jsxc-offline-available'); } if (icon) { li.addClass('jsxc-icon-' + icon); } ((li, handler) => li.click(ev => { let presence = Client.getPresenceController().getCurrentPresence(); if (presence === Presence.offline && !li.hasClass('jsxc-offline-available')) { return; } return handler(ev); }))(li, handler); let mainMenu = this.element.find('.jsxc-js-main-menu .jsxc-menu__content ul'); mainMenu.prepend(li); } public setFilter(filter: string = '') { this.element.find('.jsxc-filter-input').val(filter).trigger('keyup'); } private insert(rosterItem: RosterItem) { let contact = rosterItem.getContact(); let list = contact.isChat() ? this.contactList : this.groupList; let contactName = contact.getName(); let lastMessageDate = contact.getLastMessageDate(); let pointer = lastMessageDate ? list.find('[data-date]') : list.children().first(); pointer = pointer.length > 0 ? pointer.first() : list.children().first(); while (pointer.length > 0) { let pointerDate = pointer.data('date') ? new Date(pointer.data('date')) : undefined; let pointerName = pointer.find('.jsxc-bar__caption__primary').text(); if ( (lastMessageDate && pointerDate && lastMessageDate > pointerDate) || (lastMessageDate && !pointerDate) || (!lastMessageDate && !pointerDate && contactName.localeCompare(pointerName) === -1) ) { pointer.before(rosterItem.getDom().detach()); return; } pointer = pointer.next(); } rosterItem.getDom().appendTo(list); } private addMainMenuEntries() { this.addMenuEntry({ id: 'about', handler: showAboutDialog, label: Translation.t('About'), offlineAvailable: true, }); let onlineHelpUrl = Client.getOption(HELP_KEY); if (onlineHelpUrl) { this.addMenuEntry({ id: 'online-help', handler(ev) { ev.stopPropagation(); if (ev.currentTarget === ev.target) { $(ev.target).find('a').get(0).click(); } }, label: $( `<a href="${onlineHelpUrl}" target="_blank" rel="noopener noreferrer">${Translation.t( 'Online_help' )}</a>` ), offlineAvailable: true, icon: 'help', }); } this.addMenuEntry({ id: 'search-contact', handler: showContactSearchDialog, label: Translation.t('contact_search'), icon: 'search', }); this.addMenuEntry({ id: 'add-contact', handler: () => showContactDialog(), label: Translation.t('Add_buddy'), icon: 'contact', }); this.addMenuEntry({ id: 'hide-offline', handler: this.toggleOffline, label: $(`<span class="jsxc-hide-offline">${Translation.t('Hide_offline')}</span> <span class="jsxc-show-offline">${Translation.t('Show_offline')}</span>`), }); this.addMenuEntry({ id: 'mute-notification', handler: this.toggleMuteNotification, label: Translation.t('Mute'), }); this.addMenuEntry({ id: 'join-muc', handler: () => showMultiUserJoinDialog(), label: Translation.t('Join_chat'), icon: 'groupcontact', }); this.addMenuEntry({ id: 'settings', handler: showSettingsDialog, label: Translation.t('Settings'), offlineAvailable: true, icon: 'gear', }); this.addMenuEntry({ id: 'add-avatar', handler: showAddAvatarDialog, label: Translation.t('Edit_avatar'), }); } private registerPresenceHandler() { this.element.find('.jsxc-js-presence-menu li').click(function () { let presenceString = <string>$(this).data('presence'); if (presenceString === 'extended-status') { showSetExStatusDialog(); return; } let oldPresence = Client.getPresenceController().getTargetPresence(); let requestedPresence = Presence[presenceString]; if (Client.getAccountManager().getAccount()) { Client.getPresenceController().setTargetPresence(requestedPresence); } if (oldPresence === Presence.offline && requestedPresence === Presence.online) { let onUserRequestsToGoOnline = Client.getOption('onUserRequestsToGoOnline'); if (typeof onUserRequestsToGoOnline === 'function') { onUserRequestsToGoOnline(); } } }); } private registerToggleHandler() { this.element.find('.jsxc-roster-toggle').click(this.toggle); } private toggleOffline = ev => { let hideOffline = !Client.getOption(HIDE_OFFLINE_KEY); Client.setOption(HIDE_OFFLINE_KEY, hideOffline); }; private hideOffline(yes: boolean) { if (yes) { this.element.addClass('jsxc-hide-offline'); } else { this.element.removeClass('jsxc-hide-offline'); } } private toggleMuteNotification = () => { let muteNotification = !Client.getOption('notification.mute'); Client.setOption('notification.mute', muteNotification); }; private muteNotification(yes: boolean) { let element = this.element.find('.jsxc-mute-notification'); element.text(yes ? Translation.t('Unmute') : Translation.t('Mute')); this.element.attr('data-mute', yes ? 'yes' : 'no'); } public toggle = () => { let state = Client.getOption(VISIBILITY_KEY); state = state === CONST.HIDDEN ? CONST.SHOWN : CONST.HIDDEN; Client.setOption(VISIBILITY_KEY, state); }; public hide = () => { Client.setOption(VISIBILITY_KEY, CONST.HIDDEN); }; public show = () => { Client.setOption(VISIBILITY_KEY, CONST.SHOWN); }; private setVisibility(state: string) { if (state === CONST.SHOWN && Client.isExtraSmallDevice()) { WindowList.get().minimizeAll(); } $('body').removeClass('jsxc-roster-hidden jsxc-roster-shown'); $('body').addClass('jsxc-roster-' + state); } private initOptions() { let muteNotification = Client.getOption('notification.mute'); this.muteNotification(muteNotification); Client.getOptions().registerHook('notification', (newValue = {}, oldValue = {}) => { if (newValue.mute !== oldValue.mute) { this.muteNotification(newValue.mute); } }); let hideOffline = Client.getOption(HIDE_OFFLINE_KEY); this.hideOffline(hideOffline); Client.getOptions().registerHook(HIDE_OFFLINE_KEY, hideOffline => { this.hideOffline(hideOffline); }); let visibility = Client.getOption(VISIBILITY_KEY); this.setVisibility([CONST.HIDDEN, CONST.SHOWN].indexOf(visibility) > -1 ? visibility : CONST.SHOWN); Client.getOptions().registerHook(VISIBILITY_KEY, visibility => { this.setVisibility(visibility); }); this.refreshOwnPresenceIndicator(); Client.getPresenceController().registerTargetPresenceHook(() => { this.refreshOwnPresenceIndicator(); }); Client.getPresenceController().registerCurrentPresenceHook(() => { this.refreshOwnPresenceIndicator(); }); $('.jsxc-bottom > .jsxc-avatar').on('click', () => { if (this.element.attr('data-presence') !== Presence[Presence.offline]) { showAddAvatarDialog(); } }); } private initHandler() { this.element.find('.jsxc-filter-input').on('keyup', ev => { let filterValue = $(ev.target).val().toString().trim().toLowerCase(); let listElements = this.element.find('.jsxc-contact-list-wrapper .jsxc-roster-item'); if (!filterValue) { listElements.removeClass('jsxc-roster-item--filtered'); return; } listElements .not(`[data-jid*="${filterValue}"]`) .not(`[data-name*="${filterValue}"]`) .not(`[data-groups*="${filterValue}"]`) .addClass('jsxc-roster-item--filtered'); listElements.filter(`[data-jid*="${filterValue}"]`).removeClass('jsxc-roster-item--filtered'); listElements.filter(`[data-name*="${filterValue}"]`).removeClass('jsxc-roster-item--filtered'); listElements.filter(`[data-groups*="${filterValue}"]`).removeClass('jsxc-roster-item--filtered'); }); this.element.find('.jsxc-filter-wrapper .jsxc-clear').on('mousedown', ev => { ev.preventDefault(); this.setFilter(''); }); this.element.find('.jsxc-collapsible').on('click', function () { $(this).toggleClass('jsxc-active'); }); } }