// Converse.js (A browser based XMPP chat client) // https://conversejs.org // // Copyright (c) 2012-2017, Jan-Carel Brand // Licensed under the Mozilla Public License (MPLv2) // /*global define, window, document */ import "converse-chatview"; import "converse-controlbox"; import converse from "@converse/headless/converse-core"; import tpl_dragresize from "templates/dragresize.html"; const { _ } = converse.env; function renderDragResizeHandles (_converse, view) { const flyout = view.el.querySelector('.box-flyout'); const div = document.createElement('div'); div.innerHTML = tpl_dragresize(); flyout.insertBefore( div, flyout.firstChild ); } converse.plugins.add('converse-dragresize', { /* Plugin dependencies are other plugins which might be * overridden or relied upon, and therefore need to be loaded before * this plugin. * * If the setting "strict_plugin_dependencies" is set to true, * an error will be raised if the plugin is not found. By default it's * false, which means these plugins are only loaded opportunistically. * * NB: These plugins need to have already been loaded via require.js. */ dependencies: ["converse-chatview", "converse-headline", "converse-muc-views"], 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 that = this; document.addEventListener('mousemove', function (ev) { if (!that.resizing || !that.allow_dragresize) { return true; } ev.preventDefault(); that.resizing.chatbox.resizeChatBox(ev); }); document.addEventListener('mouseup', function (ev) { if (!that.resizing || !that.allow_dragresize) { return true; } ev.preventDefault(); const height = that.applyDragResistance( that.resizing.chatbox.height, that.resizing.chatbox.model.get('default_height') ); const width = that.applyDragResistance( that.resizing.chatbox.width, that.resizing.chatbox.model.get('default_width') ); if (that.connection.connected) { that.resizing.chatbox.model.save({'height': height}); that.resizing.chatbox.model.save({'width': width}); } else { that.resizing.chatbox.model.set({'height': height}); that.resizing.chatbox.model.set({'width': width}); } that.resizing = null; }); return this.__super__.registerGlobalEventHandlers.apply(this, arguments); }, ChatBox: { initialize () { const { _converse } = this.__super__; const result = this.__super__.initialize.apply(this, arguments), height = this.get('height'), width = this.get('width'), save = this.get('id') === 'controlbox' ? this.set.bind(this) : this.save.bind(this); save({ 'height': _converse.applyDragResistance(height, this.get('default_height')), 'width': _converse.applyDragResistance(width, this.get('default_width')), }); return result; } }, ChatBoxView: { events: { 'mousedown .dragresize-top': 'onStartVerticalResize', 'mousedown .dragresize-left': 'onStartHorizontalResize', 'mousedown .dragresize-topleft': 'onStartDiagonalResize' }, initialize () { window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100)); this.__super__.initialize.apply(this, arguments); }, render () { const result = this.__super__.render.apply(this, arguments); renderDragResizeHandles(this.__super__._converse, this); this.setWidth(); return result; }, setWidth () { // If a custom width is applied (due to drag-resizing), // then we need to set the width of the .chatbox element as well. if (this.model.get('width')) { this.el.style.width = this.model.get('width'); } }, _show () { this.initDragResize().setDimensions(); this.__super__._show.apply(this, arguments); }, initDragResize () { /* Determine and store the default box size. * We need this information for the drag-resizing feature. */ const { _converse } = this.__super__, flyout = this.el.querySelector('.box-flyout'), style = window.getComputedStyle(flyout); if (_.isUndefined(this.model.get('height'))) { const height = parseInt(style.height.replace(/px$/, ''), 10), width = parseInt(style.width.replace(/px$/, ''), 10); this.model.set('height', height); this.model.set('default_height', height); this.model.set('width', width); this.model.set('default_width', width); } const min_width = style['min-width']; const min_height = style['min-height']; this.model.set('min_width', min_width.endsWith('px') ? Number(min_width.replace(/px$/, '')) :0); this.model.set('min_height', min_height.endsWith('px') ? Number(min_height.replace(/px$/, '')) :0); // Initialize last known mouse position this.prev_pageY = 0; this.prev_pageX = 0; if (_converse.connection.connected) { this.height = this.model.get('height'); this.width = this.model.get('width'); } return this; }, setDimensions () { // Make sure the chat box has the right height and width. this.adjustToViewport(); this.setChatBoxHeight(this.model.get('height')); this.setChatBoxWidth(this.model.get('width')); }, setChatBoxHeight (height) { const { _converse } = this.__super__; if (height) { height = _converse.applyDragResistance(height, this.model.get('default_height'))+'px'; } else { height = ""; } const flyout_el = this.el.querySelector('.box-flyout'); if (!_.isNull(flyout_el)) { flyout_el.style.height = height; } }, setChatBoxWidth (width) { const { _converse } = this.__super__; if (width) { width = _converse.applyDragResistance(width, this.model.get('default_width'))+'px'; } else { width = ""; } this.el.style.width = width; const flyout_el = this.el.querySelector('.box-flyout'); if (!_.isNull(flyout_el)) { flyout_el.style.width = width; } }, adjustToViewport () { /* Event handler called when viewport gets resized. We remove * custom width/height from chat boxes. */ const viewport_width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); const viewport_height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); if (viewport_width <= 480) { this.model.set('height', undefined); this.model.set('width', undefined); } else if (viewport_width <= this.model.get('width')) { this.model.set('width', undefined); } else if (viewport_height <= this.model.get('height')) { this.model.set('height', undefined); } }, onStartVerticalResize (ev) { const { _converse } = this.__super__; if (!_converse.allow_dragresize) { return true; } // Record element attributes for mouseMove(). const flyout = this.el.querySelector('.box-flyout'), style = window.getComputedStyle(flyout); this.height = parseInt(style.height.replace(/px$/, ''), 10); _converse.resizing = { 'chatbox': this, 'direction': 'top' }; this.prev_pageY = ev.pageY; }, onStartHorizontalResize (ev) { const { _converse } = this.__super__; if (!_converse.allow_dragresize) { return true; } const flyout = this.el.querySelector('.box-flyout'), style = window.getComputedStyle(flyout); this.width = parseInt(style.width.replace(/px$/, ''), 10); _converse.resizing = { 'chatbox': this, 'direction': 'left' }; this.prev_pageX = ev.pageX; }, onStartDiagonalResize (ev) { const { _converse } = this.__super__; this.onStartHorizontalResize(ev); this.onStartVerticalResize(ev); _converse.resizing.direction = 'topleft'; }, resizeChatBox (ev) { let diff; const { _converse } = this.__super__; if (_converse.resizing.direction.indexOf('top') === 0) { diff = ev.pageY - this.prev_pageY; if (diff) { this.height = ((this.height-diff) > (this.model.get('min_height') || 0)) ? (this.height-diff) : this.model.get('min_height'); this.prev_pageY = ev.pageY; this.setChatBoxHeight(this.height); } } if (_.includes(_converse.resizing.direction, 'left')) { diff = this.prev_pageX - ev.pageX; if (diff) { this.width = ((this.width+diff) > (this.model.get('min_width') || 0)) ? (this.width+diff) : this.model.get('min_width'); this.prev_pageX = ev.pageX; this.setChatBoxWidth(this.width); } } } }, HeadlinesBoxView: { events: { 'mousedown .dragresize-top': 'onStartVerticalResize', 'mousedown .dragresize-left': 'onStartHorizontalResize', 'mousedown .dragresize-topleft': 'onStartDiagonalResize' }, initialize () { window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100)); return this.__super__.initialize.apply(this, arguments); }, render () { const result = this.__super__.render.apply(this, arguments); renderDragResizeHandles(this.__super__._converse, this); this.setWidth(); return result; } }, ControlBoxView: { events: { 'mousedown .dragresize-top': 'onStartVerticalResize', 'mousedown .dragresize-left': 'onStartHorizontalResize', 'mousedown .dragresize-topleft': 'onStartDiagonalResize' }, initialize () { window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100)); this.__super__.initialize.apply(this, arguments); }, render () { const result = this.__super__.render.apply(this, arguments); renderDragResizeHandles(this.__super__._converse, this); this.setWidth(); return result; }, renderLoginPanel () { const result = this.__super__.renderLoginPanel.apply(this, arguments); this.initDragResize().setDimensions(); return result; }, renderControlBoxPane () { const result = this.__super__.renderControlBoxPane.apply(this, arguments); this.initDragResize().setDimensions(); return result; } }, ChatRoomView: { events: { 'mousedown .dragresize-top': 'onStartVerticalResize', 'mousedown .dragresize-left': 'onStartHorizontalResize', 'mousedown .dragresize-topleft': 'onStartDiagonalResize' }, initialize () { window.addEventListener('resize', _.debounce(this.setDimensions.bind(this), 100)); this.__super__.initialize.apply(this, arguments); }, render () { const result = this.__super__.render.apply(this, arguments); renderDragResizeHandles(this.__super__._converse, this); this.setWidth(); return result; } } }, initialize () { /* The initialize function gets called as soon as the plugin is * loaded by converse.js's plugin machinery. */ const { _converse } = this; _converse.api.settings.update({ allow_dragresize: true, }); _converse.applyDragResistance = function (value, default_value) { /* This method applies some resistance around the * default_value. If value is close enough to * default_value, then default_value is returned instead. */ if (_.isUndefined(value)) { return undefined; } else if (_.isUndefined(default_value)) { return value; } const resistance = 10; if ((value !== default_value) && (Math.abs(value- default_value) < resistance)) { return default_value; } return value; }; } });