123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553 |
- // Converse.js (A browser based XMPP chat client)
- // http://conversejs.org
- //
- // Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com>
- // Licensed under the Mozilla Public License (MPLv2)
- //
- /*global define, window, document */
- (function (root, factory) {
- define(["converse-core",
- "tpl!chatbox_minimize",
- "tpl!toggle_chats",
- "tpl!trimmed_chat",
- "tpl!chats_panel",
- "converse-chatview"
- ], factory);
- }(this, function (
- converse,
- tpl_chatbox_minimize,
- tpl_toggle_chats,
- tpl_trimmed_chat,
- tpl_chats_panel
- ) {
- "use strict";
- const { _ , Backbone, Promise, Strophe, b64_sha1, moment } = converse.env;
- const u = converse.env.utils;
- converse.plugins.add('converse-minimize', {
- /* Optional dependencies are other plugins which might be
- * overridden or relied upon, and therefore need to be loaded before
- * this plugin. They are called "optional" because they might not be
- * available, in which case any overrides applicable to them will be
- * ignored.
- *
- * It's possible however to make optional dependencies non-optional.
- * If the setting "strict_plugin_dependencies" is set to true,
- * an error will be raised if the plugin is not found.
- *
- * NB: These plugins need to have already been loaded via require.js.
- */
- dependencies: ["converse-chatview", "converse-controlbox", "converse-muc", "converse-muc-views", "converse-headline"],
- enabled (_converse) {
- return _converse.view_mode == 'overlayed';
- },
- 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.
- registerGlobalEventHandlers () {
- const { _converse } = this.__super__;
- window.addEventListener("resize", _.debounce(function (ev) {
- if (_converse.connection.connected) {
- _converse.chatboxviews.trimChats();
- }
- }, 200));
- return this.__super__.registerGlobalEventHandlers.apply(this, arguments);
- },
- ChatBox: {
- initialize () {
- this.__super__.initialize.apply(this, arguments);
- this.on('show', this.maximize, this);
- if (this.get('id') === 'controlbox') {
- return;
- }
- this.save({
- 'minimized': this.get('minimized') || false,
- 'time_minimized': this.get('time_minimized') || moment(),
- });
- },
- maximize () {
- u.safeSave(this, {
- 'minimized': false,
- 'time_opened': moment().valueOf()
- });
- },
- minimize () {
- u.safeSave(this, {
- 'minimized': true,
- 'time_minimized': moment().format()
- });
- },
- },
- ChatBoxView: {
- events: {
- 'click .toggle-chatbox-button': 'minimize',
- },
- initialize () {
- this.model.on('change:minimized', this.onMinimizedChanged, this);
- return this.__super__.initialize.apply(this, arguments);
- },
- _show () {
- const { _converse } = this.__super__;
- if (!this.model.get('minimized')) {
- this.__super__._show.apply(this, arguments);
- _converse.chatboxviews.trimChats(this);
- } else {
- this.minimize();
- }
- },
- isNewMessageHidden () {
- return this.model.get('minimized') ||
- this.__super__.isNewMessageHidden.apply(this, arguments);
- },
- shouldShowOnTextMessage () {
- return !this.model.get('minimized') &&
- this.__super__.shouldShowOnTextMessage.apply(this, arguments);
- },
- setChatBoxHeight (height) {
- if (!this.model.get('minimized')) {
- return this.__super__.setChatBoxHeight.apply(this, arguments);
- }
- },
- setChatBoxWidth (width) {
- if (!this.model.get('minimized')) {
- return this.__super__.setChatBoxWidth.apply(this, arguments);
- }
- },
- onMinimizedChanged (item) {
- if (item.get('minimized')) {
- this.minimize();
- } else {
- this.maximize();
- }
- },
- maximize () {
- // Restores a minimized chat box
- const { _converse } = this.__super__;
- this.insertIntoDOM();
- if (!this.model.isScrolledUp()) {
- this.model.clearUnreadMsgCounter();
- }
- this.show();
- this.__super__._converse.emit('chatBoxMaximized', this);
- return this;
- },
- minimize (ev) {
- const { _converse } = this.__super__;
- if (ev && ev.preventDefault) { ev.preventDefault(); }
- // save the scroll position to restore it on maximize
- if (this.model.collection && this.model.collection.browserStorage) {
- this.model.save({'scroll': this.content.scrollTop});
- } else {
- this.model.set({'scroll': this.content.scrollTop});
- }
- this.setChatState(_converse.INACTIVE).model.minimize();
- this.hide();
- _converse.emit('chatBoxMinimized', this);
- },
- },
- ChatBoxHeading: {
- render () {
- const { _converse } = this.__super__,
- { __ } = _converse;
- const result = this.__super__.render.apply(this, arguments);
- const new_html = tpl_chatbox_minimize(
- {info_minimize: __('Minimize this chat box')}
- );
- const el = this.el.querySelector('.toggle-chatbox-button');
- if (el) {
- el.outerHTML = new_html;
- } else {
- const button = this.el.querySelector('.close-chatbox-button');
- button.insertAdjacentHTML('afterEnd', new_html);
- }
- }
- },
- ChatRoomView: {
- events: {
- 'click .toggle-chatbox-button': 'minimize',
- },
- initialize () {
- this.model.on('change:minimized', function (item) {
- if (item.get('minimized')) {
- this.hide();
- } else {
- this.maximize();
- }
- }, this);
- const result = this.__super__.initialize.apply(this, arguments);
- if (this.model.get('minimized')) {
- this.hide();
- }
- return result;
- },
- generateHeadingHTML () {
- const { _converse } = this.__super__,
- { __ } = _converse;
- const html = this.__super__.generateHeadingHTML.apply(this, arguments);
- const div = document.createElement('div');
- div.innerHTML = html;
- const button = div.querySelector('.close-chatbox-button');
- button.insertAdjacentHTML('afterend',
- tpl_chatbox_minimize({
- 'info_minimize': __('Minimize this chat box')
- })
- );
- return div.innerHTML;
- }
- },
- ChatBoxes: {
- chatBoxMayBeShown (chatbox) {
- return this.__super__.chatBoxMayBeShown.apply(this, arguments) &&
- !chatbox.get('minimized');
- },
- },
- ChatBoxViews: {
- getChatBoxWidth (view) {
- if (!view.model.get('minimized') && u.isVisible(view.el)) {
- return u.getOuterWidth(view.el, true);
- }
- return 0;
- },
- getShownChats () {
- return this.filter((view) =>
- // The controlbox can take a while to close,
- // so we need to check its state. That's why we checked
- // the 'closed' state.
- !view.model.get('minimized') &&
- !view.model.get('closed') &&
- u.isVisible(view.el)
- );
- },
- trimChats (newchat) {
- /* This method is called when a newly created chat box will
- * be shown.
- *
- * It checks whether there is enough space on the page to show
- * another chat box. Otherwise it minimizes the oldest chat box
- * to create space.
- */
- const { _converse } = this.__super__,
- shown_chats = this.getShownChats(),
- body_width = u.getOuterWidth(document.querySelector('body'), true);
- if (_converse.no_trimming || shown_chats.length <= 1) {
- return;
- }
- if (this.getChatBoxWidth(shown_chats[0]) === body_width) {
- // If the chats shown are the same width as the body,
- // then we're in responsive mode and the chats are
- // fullscreen. In this case we don't trim.
- return;
- }
- _converse.api.waitUntil('minimizedChatsInitialized').then(() => {
- const minimized_el = _.get(_converse.minimized_chats, 'el'),
- new_id = newchat ? newchat.model.get('id') : null;
- if (minimized_el) {
- const minimized_width = _.includes(this.model.pluck('minimized'), true) ?
- u.getOuterWidth(minimized_el, true) : 0;
- const boxes_width = _.reduce(
- this.xget(new_id),
- (memo, view) => memo + this.getChatBoxWidth(view),
- newchat ? u.getOuterWidth(newchat.el, true) : 0
- );
- if ((minimized_width + boxes_width) > body_width) {
- const oldest_chat = this.getOldestMaximizedChat([new_id]);
- if (oldest_chat) {
- // We hide the chat immediately, because waiting
- // for the event to fire (and letting the
- // ChatBoxView hide it then) causes race
- // conditions.
- const view = this.get(oldest_chat.get('id'));
- if (view) {
- view.hide();
- }
- oldest_chat.minimize();
- }
- }
- }
- }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
- },
- getOldestMaximizedChat (exclude_ids) {
- // Get oldest view (if its id is not excluded)
- exclude_ids.push('controlbox');
- let i = 0;
- let model = this.model.sort().at(i);
- while (_.includes(exclude_ids, model.get('id')) ||
- model.get('minimized') === true) {
- i++;
- model = this.model.at(i);
- if (!model) {
- return null;
- }
- }
- return model;
- }
- }
- },
- initialize () {
- /* The initialize function gets called as soon as the plugin is
- * loaded by Converse.js's plugin machinery.
- */
- const { _converse } = this,
- { __ } = _converse;
- // Add new HTML templates.
- _converse.templates.chatbox_minimize = tpl_chatbox_minimize;
- _converse.templates.toggle_chats = tpl_toggle_chats;
- _converse.templates.trimmed_chat = tpl_trimmed_chat;
- _converse.templates.chats_panel = tpl_chats_panel;
- _converse.api.settings.update({
- no_trimming: false, // Set to true for phantomjs tests (where browser apparently has no width)
- });
- _converse.api.promises.add('minimizedChatsInitialized');
- _converse.MinimizedChatBoxView = Backbone.NativeView.extend({
- tagName: 'div',
- className: 'chat-head row no-gutters',
- events: {
- 'click .close-chatbox-button': 'close',
- 'click .restore-chat': 'restore'
- },
- initialize () {
- this.model.on('change:num_unread', this.render, this);
- },
- render () {
- const data = _.extend(
- this.model.toJSON(),
- { 'tooltip': __('Click to restore this chat') }
- );
- if (this.model.get('type') === 'chatroom') {
- data.title = this.model.get('name');
- u.addClass('chat-head-chatroom', this.el);
- } else {
- data.title = this.model.get('fullname');
- u.addClass('chat-head-chatbox', this.el);
- }
- this.el.innerHTML = tpl_trimmed_chat(data);
- return this.el;
- },
- close (ev) {
- if (ev && ev.preventDefault) { ev.preventDefault(); }
- this.remove();
- const view = _converse.chatboxviews.get(this.model.get('id'));
- if (view) {
- // This will call model.destroy(), removing it from the
- // collection and will also emit 'chatBoxClosed'
- view.close();
- } else {
- this.model.destroy();
- _converse.emit('chatBoxClosed', this);
- }
- return this;
- },
- restore: _.debounce(function (ev) {
- if (ev && ev.preventDefault) { ev.preventDefault(); }
- this.model.off('change:num_unread', null, this);
- this.remove();
- this.model.maximize();
- }, 200, {'leading': true})
- });
- _converse.MinimizedChats = Backbone.Overview.extend({
- tagName: 'div',
- id: "minimized-chats",
- className: 'hidden',
- events: {
- "click #toggle-minimized-chats": "toggle"
- },
- initialize () {
- this.render();
- this.initToggle();
- this.addMultipleChats(this.model.where({'minimized': true}));
- this.model.on("add", this.onChanged, this);
- this.model.on("destroy", this.removeChat, this);
- this.model.on("change:minimized", this.onChanged, this);
- this.model.on('change:num_unread', this.updateUnreadMessagesCounter, this);
- },
- render () {
- if (!this.el.parentElement) {
- this.el.innerHTML = tpl_chats_panel();
- _converse.chatboxviews.insertRowColumn(this.el);
- }
- if (this.keys().length === 0) {
- this.el.classList.add('hidden');
- } else if (this.keys().length > 0 && !u.isVisible(this.el)) {
- this.el.classList.remove('hidden');
- _converse.chatboxviews.trimChats();
- }
- return this.el;
- },
- tearDown () {
- this.model.off("add", this.onChanged);
- this.model.off("destroy", this.removeChat);
- this.model.off("change:minimized", this.onChanged);
- this.model.off('change:num_unread', this.updateUnreadMessagesCounter);
- return this;
- },
- initToggle () {
- this.toggleview = new _converse.MinimizedChatsToggleView({
- model: new _converse.MinimizedChatsToggle()
- });
- const id = b64_sha1(`converse.minchatstoggle${_converse.bare_jid}`);
- this.toggleview.model.id = id; // Appears to be necessary for backbone.browserStorage
- this.toggleview.model.browserStorage = new Backbone.BrowserStorage[_converse.storage](id);
- this.toggleview.model.fetch();
- },
- toggle (ev) {
- if (ev && ev.preventDefault) { ev.preventDefault(); }
- this.toggleview.model.save({'collapsed': !this.toggleview.model.get('collapsed')});
- u.slideToggleElement(this.el.querySelector('.minimized-chats-flyout'), 200);
- },
- onChanged (item) {
- if (item.get('id') === 'controlbox') {
- // The ControlBox has it's own minimize toggle
- return;
- }
- if (item.get('minimized')) {
- this.addChat(item);
- } else if (this.get(item.get('id'))) {
- this.removeChat(item);
- }
- },
- addChatView (item) {
- const existing = this.get(item.get('id'));
- if (existing && existing.el.parentNode) {
- return;
- }
- const view = new _converse.MinimizedChatBoxView({model: item});
- this.el.querySelector('.minimized-chats-flyout').insertAdjacentElement('beforeEnd', view.render());
- this.add(item.get('id'), view);
- },
- addMultipleChats (items) {
- _.each(items, this.addChatView.bind(this));
- this.toggleview.model.set({'num_minimized': this.keys().length});
- this.render();
- },
- addChat (item) {
- this.addChatView(item);
- this.toggleview.model.set({'num_minimized': this.keys().length});
- this.render();
- },
- removeChat (item) {
- this.remove(item.get('id'));
- this.toggleview.model.set({'num_minimized': this.keys().length});
- this.render();
- },
- updateUnreadMessagesCounter () {
- const ls = this.model.pluck('num_unread');
- let count = 0, i;
- for (i=0; i<ls.length; i++) { count += ls[i]; }
- this.toggleview.model.save({'num_unread': count});
- this.render();
- }
- });
- _converse.MinimizedChatsToggle = Backbone.Model.extend({
- defaults: {
- 'collapsed': false,
- 'num_minimized': 0,
- 'num_unread': 0
- }
- });
- _converse.MinimizedChatsToggleView = Backbone.NativeView.extend({
- el: '#toggle-minimized-chats',
- initialize () {
- this.model.on('change:num_minimized', this.render, this);
- this.model.on('change:num_unread', this.render, this);
- this.flyout = this.el.parentElement.querySelector('.minimized-chats-flyout');
- },
- render () {
- this.el.innerHTML = tpl_toggle_chats(
- _.extend(this.model.toJSON(), {
- 'Minimized': __('Minimized')
- })
- );
- if (this.model.get('collapsed')) {
- u.hideElement(this.flyout);
- } else {
- u.showElement(this.flyout);
- }
- return this.el;
- }
- });
- Promise.all([
- _converse.api.waitUntil('connectionInitialized'),
- _converse.api.waitUntil('chatBoxesInitialized')
- ]).then(() => {
- _converse.minimized_chats = new _converse.MinimizedChats({
- model: _converse.chatboxes
- });
- _converse.emit('minimizedChatsInitialized');
- }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
- _converse.on('controlBoxOpened', function (chatbox) {
- // Wrapped in anon method because at scan time, chatboxviews
- // attr not set yet.
- if (_converse.connection.connected) {
- _converse.chatboxviews.trimChats(chatbox);
- }
- });
- }
- });
- }));
|