From ece7d1f3b4b2fba1f2c0ff2f04028b5ceb3e8534 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Sat, 8 Jul 2023 11:17:55 +0300 Subject: [PATCH] ref --- client/src/views/email/list.js | 1154 ++++++++++++------------- client/src/views/email/record/list.js | 702 +++++++-------- 2 files changed, 923 insertions(+), 933 deletions(-) diff --git a/client/src/views/email/list.js b/client/src/views/email/list.js index 301bc8c33a..bfc7bdabd5 100644 --- a/client/src/views/email/list.js +++ b/client/src/views/email/list.js @@ -26,653 +26,635 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -define('views/email/list', ['views/list'], function (Dep) { +import ListView from 'views/list'; + +class EmailListView extends ListView { + + createButton = false + template = 'email/list' + folderId = null + folderScope = 'EmailFolder' + selectedFolderId = null + defaultFolderId = 'inbox' + keepCurrentRootUrl = true + stickableTop = null + + /** @const */ + FOLDER_ALL = 'all' + /** @const */ + FOLDER_INBOX = 'inbox' + /** @const */ + FOLDER_IMPORTANT = 'important' + /** @const */ + FOLDER_SENT = 'sent' + /** @const */ + FOLDER_DRAFTS = 'drafts' + /** @const */ + FOLDER_TRASH = 'trash' + + noDropFolderIdList = [ + 'sent', + 'drafts', + ] + + /** @inheritDoc */ + createListRecordView(fetch) { + return super.createListRecordView(fetch) + .then(view => { + this.listenTo(view, 'after:render', () => this.initDraggable(null)); + this.listenTo(view, 'after:show-more', fromIndex => this.initDraggable(fromIndex)); + }); + } /** - * @class - * @name Class - * @memberOf module:views/email/list - * @extends module:views/list + * @private */ - return Dep.extend(/** @lends module:views/email/list.Class# */{ + initDroppable() { + this.$el.find('.folders-container .folder-list > .droppable') + .droppable({ + accept: '.list-row', + tolerance: 'pointer', + over: (e) => { + if (!this.isDroppable(e)) { + return; + } - createButton: false, + let $target = $(e.target); - template: 'email/list', + $target.removeClass('success'); + $target.addClass('active'); + $target.find('a').css('pointer-events', 'none'); + }, + out: (e) => { + if (!this.isDroppable(e)) { + return; + } - folderId: null, + let $target = $(e.target); - folderScope: 'EmailFolder', + $target.removeClass('active'); + $target.find('a').css('pointer-events', ''); + }, + drop: (e, ui) => { + if (!this.isDroppable(e)) { + return; + } - selectedFolderId: null, + let $target = $(e.target); + let $helper = $(ui.helper); - defaultFolderId: 'inbox', + $target.find('a').css('pointer-events', ''); - keepCurrentRootUrl: true, + let folderId = $target.attr('data-id'); - /** @const */ - FOLDER_ALL: 'all', - /** @const */ - FOLDER_INBOX: 'inbox', - /** @const */ - FOLDER_IMPORTANT: 'important', - /** @const */ - FOLDER_SENT: 'sent', - /** @const */ - FOLDER_DRAFTS: 'drafts', - /** @const */ - FOLDER_TRASH: 'trash', + let id = $helper.attr('data-id'); + id = id === '' ? true : id; - noDropFolderIdList: [ - 'sent', - 'drafts', - ], + this.onDrop(folderId, id); - stickableTop: null, - - /** - * @inheritDoc - */ - createListRecordView: function (fetch) { - return Dep.prototype.createListRecordView.call(this, fetch) - .then(view => { - this.listenTo(view, 'after:render', () => this.initDraggable(null)); - this.listenTo(view, 'after:show-more', fromIndex => this.initDraggable(fromIndex)); - }); - }, - - /** - * @private - */ - initDroppable: function () { - this.$el.find('.folders-container .folder-list > .droppable') - .droppable({ - accept: '.list-row', - tolerance: 'pointer', - over: (e) => { - if (!this.isDroppable(e)) { - return; - } - - let $target = $(e.target); + $target.removeClass('active'); + $target.addClass('success'); + setTimeout(() => { $target.removeClass('success'); - $target.addClass('active'); - $target.find('a').css('pointer-events', 'none'); - }, - out: (e) => { - if (!this.isDroppable(e)) { - return; - } - - let $target = $(e.target); - - $target.removeClass('active'); - $target.find('a').css('pointer-events', ''); - }, - drop: (e, ui) => { - if (!this.isDroppable(e)) { - return; - } - - let $target = $(e.target); - let $helper = $(ui.helper); - - $target.find('a').css('pointer-events', ''); - - let folderId = $target.attr('data-id'); - - let id = $helper.attr('data-id'); - id = id === '' ? true : id; - - this.onDrop(folderId, id); - - $target.removeClass('active'); - $target.addClass('success'); - - setTimeout(() => { - $target.removeClass('success'); - }, 1000); - }, - }); - }, - - /** - * @private - * @param {?Number} fromIndex - */ - initDraggable: function (fromIndex) { - fromIndex = fromIndex || 0; - - let isTouchDevice = ('ontouchstart' in window) || navigator.maxTouchPoints > 0; - - if (isTouchDevice) { - return; - } - - let $container = this.$el.find('.list-container > .list'); - - const recordView = this.getRecordView(); - - this.collection.models.slice(fromIndex).forEach(m => { - let $row = $container.find(`.list-row[data-id="${m.id}"]`).first(); - - $row.draggable({ - cancel: 'input,textarea,button,select,option,.dropdown-menu', - helper: () => { - let text = this.translate('Moving to Folder', 'labels', 'Email'); - - if ( - recordView.isIdChecked(m.id) && - !recordView.allResultIsChecked && - recordView.checkedList.length > 1 - ) { - text += ' · ' + recordView.checkedList.length; - } - - let draggedId = m.id; - - if ( - recordView.isIdChecked(m.id) && - !recordView.allResultIsChecked - ) { - draggedId = ''; - } - - return $('
') - .attr('data-id', draggedId) - .css('cursor', 'grabbing') - .addClass('draggable-helper') - .text(text); - }, - distance: 8, - containment: this.$el, - appendTo: 'body', - cursor: 'grabbing', - cursorAt: { - top: 0, - left: 0, - }, - start: (e) => { - let $target = $(e.target); - - $target.closest('tr').addClass('active'); - }, - stop: () => { - if (!recordView.isIdChecked(m.id)) { - $container.find(`.list-row[data-id="${m.id}"]`).first().removeClass('active'); - } - }, - }); + }, 1000); + }, }); - }, + } - isDroppable: function (e) { - let $target = $(e.target); - let folderId = $target.attr('data-id'); + /** + * @private + * @param {?Number} fromIndex + */ + initDraggable(fromIndex) { + fromIndex = fromIndex || 0; - if (this.selectedFolderId === this.FOLDER_DRAFTS) { - return false; - } + let isTouchDevice = ('ontouchstart' in window) || navigator.maxTouchPoints > 0; - if (this.selectedFolderId === this.FOLDER_SENT && folderId === this.FOLDER_INBOX) { - return false; - } + if (isTouchDevice) { + return; + } - if (this.selectedFolderId === this.FOLDER_ALL) { - if (folderId.indexOf('group:') === 0) { - return true; - } + let $container = this.$el.find('.list-container > .list'); - return false; - } + const recordView = this.getRecordView(); - if (folderId === this.FOLDER_ALL) { - if (this.selectedFolderId.indexOf('group:') === 0) { - return true; - } + this.collection.models.slice(fromIndex).forEach(m => { + let $row = $container.find(`.list-row[data-id="${m.id}"]`).first(); - return false; - } + $row.draggable({ + cancel: 'input,textarea,button,select,option,.dropdown-menu', + helper: () => { + let text = this.translate('Moving to Folder', 'labels', 'Email'); - if (this.selectedFolderId === this.FOLDER_DRAFTS) { - if (folderId.indexOf('group:') === 0) { - return true; - } + if ( + recordView.isIdChecked(m.id) && + !recordView.allResultIsChecked && + recordView.checkedList.length > 1 + ) { + text += ' · ' + recordView.checkedList.length; + } - if (folderId === this.FOLDER_TRASH) { - return false; - } + let draggedId = m.id; + if ( + recordView.isIdChecked(m.id) && + !recordView.allResultIsChecked + ) { + draggedId = ''; + } + + return $('
') + .attr('data-id', draggedId) + .css('cursor', 'grabbing') + .addClass('draggable-helper') + .text(text); + }, + distance: 8, + containment: this.$el, + appendTo: 'body', + cursor: 'grabbing', + cursorAt: { + top: 0, + left: 0, + }, + start: (e) => { + let $target = $(e.target); + + $target.closest('tr').addClass('active'); + }, + stop: () => { + if (!recordView.isIdChecked(m.id)) { + $container.find(`.list-row[data-id="${m.id}"]`).first().removeClass('active'); + } + }, + }); + }); + } + + isDroppable(e) { + let $target = $(e.target); + let folderId = $target.attr('data-id'); + + if (this.selectedFolderId === this.FOLDER_DRAFTS) { + return false; + } + + if (this.selectedFolderId === this.FOLDER_SENT && folderId === this.FOLDER_INBOX) { + return false; + } + + if (this.selectedFolderId === this.FOLDER_ALL) { + if (folderId.indexOf('group:') === 0) { return true; } - return true; - }, + return false; + } - setup: function () { - Dep.prototype.setup.call(this); - - this.addMenuItem('dropdown', false); - - if (this.getAcl().checkScope('EmailAccountScope')) { - this.addMenuItem('dropdown', { - name: 'reply', - label: 'Email Accounts', - link: '#EmailAccount/list/userId=' + this.getUser().id + '&userName=' + - encodeURIComponent(this.getUser().get('name')) - }); + if (folderId === this.FOLDER_ALL) { + if (this.selectedFolderId.indexOf('group:') === 0) { + return true; } - if (this.getUser().isAdmin()) { - this.addMenuItem('dropdown', { - link: '#InboundEmail', - label: 'Inbound Emails' - }); - } + return false; + } - this.foldersDisabled = this.foldersDisabled || - this.getConfig().get('emailFoldersDisabled') || - this.getMetadata().get(['scopes', this.folderScope, 'disabled']) || - !this.getAcl().checkScope(this.folderScope); - - var params = this.options.params || {}; - - this.selectedFolderId = params.folder || this.defaultFolderId; - - if (this.foldersDisabled) { - this.selectedFolderId = null; - } - - this.applyFolder(); - - this.initEmailShortcuts(); - - this.on('remove', () => { - $(window).off('resize.email-folders'); - $(window).off('scroll.email-folders'); - }); - }, - - data: function () { - var data = {}; - data.foldersDisabled = this.foldersDisabled; - - return data; - }, - - /** - * @inheritDoc - */ - createSearchView: function () { - /** @type {Promise}*/ - let promise = Dep.prototype.createSearchView.call(this); - - promise.then(view => { - this.listenTo(view, 'update-ui', () => { - this.stickableTop = null; - - setTimeout(() => { - $(window).trigger('scroll') - - // If search fields are not yet rendered, the value may be wrong. - this.stickableTop = null; - }, 100); - }); - }); - - return promise; - }, - - initEmailShortcuts: function () { - this.shortcutKeys['Control+Delete'] = e => { - if (!this.hasSelectedRecords()) { - return; - } - - e.preventDefault(); - e.stopPropagation(); - - this.getRecordView().massActionMoveToTrash(); - }; - - this.shortcutKeys['Control+KeyI'] = e => { - if (!this.hasSelectedRecords()) { - return; - } - - e.preventDefault(); - e.stopPropagation(); - - this.getRecordView().toggleMassMarkAsImportant(); - }; - - this.shortcutKeys['Control+KeyM'] = e => { - if (!this.hasSelectedRecords()) { - return; - } - - e.preventDefault(); - e.stopPropagation(); - - this.getRecordView().massActionMoveToFolder(); - }; - }, - - hasSelectedRecords: function () { - let recordView = this.getRecordView(); - - return recordView.checkedList && - recordView.checkedList.length && - !recordView.allResultIsChecked; - }, - - /** - * @inheritDoc - */ - setupReuse: function (params) { - this.applyRoutingParams(params); - this.initDroppable(); - this.initStickableFolders(); - }, - - /** - * @param {Object.} [data] - */ - actionComposeEmail: function (data) { - data = data || {}; - - Espo.Ui.notify(' ... '); - - let viewName = this.getMetadata().get('clientDefs.Email.modalViews.compose') || - 'views/modals/compose-email'; - - let options = { - attributes: { - status: 'Draft', - }, - focusForCreate: data.focusForCreate, - }; - - this.createView('quickCreate', viewName, options, (view) => { - view.render(); - view.notify(false); - - this.listenToOnce(view, 'after:save', () => { - this.collection.fetch(); - }); - }); - }, - - afterRender: function () { - Dep.prototype.afterRender.call(this); - - if (!this.foldersDisabled && !this.hasView('folders')) { - this.loadFolders(); - } - }, - - getFolderCollection: function (callback) { - this.getCollectionFactory().create(this.folderScope, (collection) => { - collection.url = 'EmailFolder/action/listAll'; - collection.maxSize = 200; - - this.listenToOnce(collection, 'sync', () =>{ - callback.call(this, collection); - }); - - collection.fetch(); - }); - }, - - loadFolders: function () { - var xhr = null; - - let auxFolderList = [ - this.FOLDER_TRASH, - this.FOLDER_DRAFTS, - this.FOLDER_ALL, - this.FOLDER_INBOX, - this.FOLDER_IMPORTANT, - this.FOLDER_SENT, - ]; - - this.getFolderCollection(collection => { - collection.forEach(model => { - if (this.noDropFolderIdList.indexOf(model.id) === -1) { - model.droppable = true; - } - - if (model.id.indexOf('group:') === 0) { - model.title = this.translate('groupFolder', 'fields', 'Email'); - } - else if (auxFolderList.indexOf(model.id) === -1) { - model.title = this.translate('folder', 'fields', 'Email'); - } - }); - - this.createView('folders', 'views/email-folder/list-side', { - collection: collection, - emailCollection: this.collection, - selector: '.folders-container', - showEditLink: this.getAcl().check(this.folderScope, 'edit'), - selectedFolderId: this.selectedFolderId, - }, view => { - view.render() - .then(() => this.initDroppable()) - .then(() => this.initStickableFolders()); - - this.listenTo(view, 'select', (id) => { - this.selectedFolderId = id; - this.applyFolder(); - - if (xhr && xhr.readyState < 4) { - xhr.abort(); - } - - Espo.Ui.notify(' ... '); - - xhr = this.collection - .fetch() - .then(() => Espo.Ui.notify(false)); - - if (id !== this.defaultFolderId) { - this.getRouter().navigate('#Email/list/folder=' + id); - } else { - this.getRouter().navigate('#Email'); - } - - this.updateLastUrl(); - }); - }); - }); - }, - - applyFolder: function () { - this.collection.selectedFolderId = this.selectedFolderId; - - if (!this.selectedFolderId) { - this.collection.whereFunction = null; - - return; - } - - this.collection.whereFunction = () => { - return [ - { - type: 'inFolder', - attribute: 'folderId', - value: this.selectedFolderId, - } - ]; - }; - }, - - applyRoutingParams: function (params) { - var id; - - if ('folder' in params) { - id = params.folder || 'inbox'; - } else { - return; - } - - if (!params.isReturnThroughLink && id !== this.selectedFolderId) { - var foldersView = this.getView('folders'); - - if (foldersView) { - foldersView.actionSelectFolder(id); - foldersView.reRender(); - $(window).scrollTop(0); - } - } - }, - - onDrop: function (folderId, id) { - let recordView = this.getRecordView(); - - if (folderId === this.FOLDER_IMPORTANT) { - setTimeout(() => { - id === true ? - recordView.massActionMarkAsImportant() : - recordView.actionMarkAsImportant({id: id}); - }, 10); - - return; - } - - if (this.selectedFolderId === this.FOLDER_TRASH) { - if (folderId === this.FOLDER_TRASH) { - return; - } - - id === true ? - recordView.massRetrieveFromTrashMoveToFolder(folderId) : - recordView.actionRetrieveFromTrashMoveToFolder({id: id, folderId: folderId}); - - return; + if (this.selectedFolderId === this.FOLDER_DRAFTS) { + if (folderId.indexOf('group:') === 0) { + return true; } if (folderId === this.FOLDER_TRASH) { - id === true ? - recordView.massActionMoveToTrash() : - recordView.actionMoveToTrash({id: id}); - - return; + return false; } - if (this.selectedFolderId.indexOf('group:') === 0 && folderId === this.FOLDER_ALL) { - folderId = this.FOLDER_INBOX; - } + return true; + } - id === true ? - recordView.massMoveToFolder(folderId) : - recordView.actionMoveToFolder({id: id, folderId: folderId}); - }, + return true; + } - /** - * @protected - * @return {module:views/email/record/list.Class} - */ - getRecordView: function () { - return this.getView('list'); - }, + setup() { + super.setup(); - /** - * @private - */ - initStickableFolders: function () { - let $window = $(window); - let $list = this.$el.find('.list-container'); - let $container = this.$el.find('.folders-container'); - let $left = this.$el.find('.left-container').first(); + this.addMenuItem('dropdown', false); - let screenWidthXs = this.getThemeManager().getParam('screenWidthXs'); - let isSmallScreen = $(window.document).width() < screenWidthXs; - let offset = this.getThemeManager().getParam('navbarHeight') + - (this.getThemeManager().getParam('buttonsContainerHeight') || 47); + if (this.getAcl().checkScope('EmailAccountScope')) { + this.addMenuItem('dropdown', { + name: 'reply', + label: 'Email Accounts', + link: '#EmailAccount/list/userId=' + this.getUser().id + '&userName=' + + encodeURIComponent(this.getUser().get('name')) + }); + } - let bottomSpaceHeight = parseInt(window.getComputedStyle($('#content').get(0)).paddingBottom, 10); + if (this.getUser().isAdmin()) { + this.addMenuItem('dropdown', { + link: '#InboundEmail', + label: 'Inbound Emails' + }); + } - let getOffsetTop = (/** JQuery */$element) => { - let element = $element.get(0); + this.foldersDisabled = this.foldersDisabled || + this.getConfig().get('emailFoldersDisabled') || + this.getMetadata().get(['scopes', this.folderScope, 'disabled']) || + !this.getAcl().checkScope(this.folderScope); - let value = 0; + var params = this.options.params || {}; - while (element) { - value += !isNaN(element.offsetTop) ? element.offsetTop : 0; + this.selectedFolderId = params.folder || this.defaultFolderId; - element = element.offsetParent; - } + if (this.foldersDisabled) { + this.selectedFolderId = null; + } - if (isSmallScreen) { - return value; - } + this.applyFolder(); - return value - offset; - }; + this.initEmailShortcuts(); - this.stickableTop = getOffsetTop($list); + this.on('remove', () => { + $(window).off('resize.email-folders'); + $(window).off('scroll.email-folders'); + }); + } - let control = () => { - let start = this.stickableTop; + data() { + var data = {}; + data.foldersDisabled = this.foldersDisabled; - if (start === null) { - start = this.stickableTop = getOffsetTop($list); - } + return data; + } - let scrollTop = $window.scrollTop(); + /** @inheritDoc */ + createSearchView() { + /** @type {Promise} */ + let promise = super.createSearchView(); - if (scrollTop <= start || isSmallScreen) { - $container - .removeClass('sticked') - .width('') - .scrollTop(0); + promise.then(view => { + this.listenTo(view, 'update-ui', () => { + this.stickableTop = null; - $container.css({ - maxHeight: '', - }); + setTimeout(() => { + $(window).trigger('scroll') - return; - } + // If search fields are not yet rendered, the value may be wrong. + this.stickableTop = null; + }, 100); + }); + }); - if (scrollTop > start) { - let scroll = $window.scrollTop() - start; + return promise; + } - $container - .addClass('sticked') - .width($left.outerWidth(true)) - .scrollTop(scroll); - - let topStickPosition = parseInt(window.getComputedStyle($container.get(0)).top); - - let maxHeight = $window.height() - topStickPosition - bottomSpaceHeight; - - $container.css({maxHeight: maxHeight}); - } - }; - - $window.on('resize.email-folders', () => control()); - $window.on('scroll.email-folders', () => control()); - }, - - /** - * @protected - * @param {JQueryKeyEventObject} e - */ - handleShortcutKeyCtrlSpace: function (e) { - if (e.target.tagName === 'TEXTAREA' || e.target.tagName === 'INPUT') { - return; - } - - if (!this.getAcl().checkScope(this.scope, 'create')) { + initEmailShortcuts() { + this.shortcutKeys['Control+Delete'] = e => { + if (!this.hasSelectedRecords()) { return; } e.preventDefault(); e.stopPropagation(); - this.actionComposeEmail({focusForCreate: true}); - }, - }); -}); + this.getRecordView().massActionMoveToTrash(); + }; + + this.shortcutKeys['Control+KeyI'] = e => { + if (!this.hasSelectedRecords()) { + return; + } + + e.preventDefault(); + e.stopPropagation(); + + this.getRecordView().toggleMassMarkAsImportant(); + }; + + this.shortcutKeys['Control+KeyM'] = e => { + if (!this.hasSelectedRecords()) { + return; + } + + e.preventDefault(); + e.stopPropagation(); + + this.getRecordView().massActionMoveToFolder(); + }; + } + + hasSelectedRecords() { + let recordView = this.getRecordView(); + + return recordView.checkedList && + recordView.checkedList.length && + !recordView.allResultIsChecked; + } + + /** @inheritDoc */ + setupReuse(params) { + this.applyRoutingParams(params); + this.initDroppable(); + this.initStickableFolders(); + } + + /** + * @param {Object.} [data] + */ + actionComposeEmail(data) { + data = data || {}; + + Espo.Ui.notify(' ... '); + + let viewName = this.getMetadata().get('clientDefs.Email.modalViews.compose') || + 'views/modals/compose-email'; + + let options = { + attributes: { + status: 'Draft', + }, + focusForCreate: data.focusForCreate, + }; + + this.createView('quickCreate', viewName, options, (view) => { + view.render(); + view.notify(false); + + this.listenToOnce(view, 'after:save', () => { + this.collection.fetch(); + }); + }); + } + + afterRender() { + super.afterRender(); + + if (!this.foldersDisabled && !this.hasView('folders')) { + this.loadFolders(); + } + } + + getFolderCollection(callback) { + this.getCollectionFactory().create(this.folderScope, (collection) => { + collection.url = 'EmailFolder/action/listAll'; + collection.maxSize = 200; + + this.listenToOnce(collection, 'sync', () =>{ + callback.call(this, collection); + }); + + collection.fetch(); + }); + } + + loadFolders() { + var xhr = null; + + let auxFolderList = [ + this.FOLDER_TRASH, + this.FOLDER_DRAFTS, + this.FOLDER_ALL, + this.FOLDER_INBOX, + this.FOLDER_IMPORTANT, + this.FOLDER_SENT, + ]; + + this.getFolderCollection(collection => { + collection.forEach(model => { + if (this.noDropFolderIdList.indexOf(model.id) === -1) { + model.droppable = true; + } + + if (model.id.indexOf('group:') === 0) { + model.title = this.translate('groupFolder', 'fields', 'Email'); + } + else if (auxFolderList.indexOf(model.id) === -1) { + model.title = this.translate('folder', 'fields', 'Email'); + } + }); + + this.createView('folders', 'views/email-folder/list-side', { + collection: collection, + emailCollection: this.collection, + selector: '.folders-container', + showEditLink: this.getAcl().check(this.folderScope, 'edit'), + selectedFolderId: this.selectedFolderId, + }, view => { + view.render() + .then(() => this.initDroppable()) + .then(() => this.initStickableFolders()); + + this.listenTo(view, 'select', (id) => { + this.selectedFolderId = id; + this.applyFolder(); + + if (xhr && xhr.readyState < 4) { + xhr.abort(); + } + + Espo.Ui.notify(' ... '); + + xhr = this.collection + .fetch() + .then(() => Espo.Ui.notify(false)); + + if (id !== this.defaultFolderId) { + this.getRouter().navigate('#Email/list/folder=' + id); + } else { + this.getRouter().navigate('#Email'); + } + + this.updateLastUrl(); + }); + }); + }); + } + + applyFolder() { + this.collection.selectedFolderId = this.selectedFolderId; + + if (!this.selectedFolderId) { + this.collection.whereFunction = null; + + return; + } + + this.collection.whereFunction = () => { + return [ + { + type: 'inFolder', + attribute: 'folderId', + value: this.selectedFolderId, + } + ]; + }; + } + + applyRoutingParams(params) { + var id; + + if ('folder' in params) { + id = params.folder || 'inbox'; + } else { + return; + } + + if (!params.isReturnThroughLink && id !== this.selectedFolderId) { + var foldersView = this.getView('folders'); + + if (foldersView) { + foldersView.actionSelectFolder(id); + foldersView.reRender(); + $(window).scrollTop(0); + } + } + } + + onDrop(folderId, id) { + let recordView = /** @type {module:views/email/record/list} */this.getRecordView(); + + if (folderId === this.FOLDER_IMPORTANT) { + setTimeout(() => { + id === true ? + recordView.massActionMarkAsImportant() : + recordView.actionMarkAsImportant({id: id}); + }, 10); + + return; + } + + if (this.selectedFolderId === this.FOLDER_TRASH) { + if (folderId === this.FOLDER_TRASH) { + return; + } + + id === true ? + recordView.massRetrieveFromTrashMoveToFolder(folderId) : + recordView.actionRetrieveFromTrashMoveToFolder({id: id, folderId: folderId}); + + return; + } + + if (folderId === this.FOLDER_TRASH) { + id === true ? + recordView.massActionMoveToTrash() : + recordView.actionMoveToTrash({id: id}); + + return; + } + + if (this.selectedFolderId.indexOf('group:') === 0 && folderId === this.FOLDER_ALL) { + folderId = this.FOLDER_INBOX; + } + + id === true ? + recordView.massMoveToFolder(folderId) : + recordView.actionMoveToFolder({id: id, folderId: folderId}); + } + + /** + * @protected + * @return {module:views/email/record/list} + */ + getRecordView() { + return this.getView('list'); + } + + /** + * @private + */ + initStickableFolders() { + let $window = $(window); + let $list = this.$el.find('.list-container'); + let $container = this.$el.find('.folders-container'); + let $left = this.$el.find('.left-container').first(); + + let screenWidthXs = this.getThemeManager().getParam('screenWidthXs'); + let isSmallScreen = $(window.document).width() < screenWidthXs; + let offset = this.getThemeManager().getParam('navbarHeight') + + (this.getThemeManager().getParam('buttonsContainerHeight') || 47); + + let bottomSpaceHeight = parseInt(window.getComputedStyle($('#content').get(0)).paddingBottom, 10); + + let getOffsetTop = (/** JQuery */$element) => { + let element = $element.get(0); + + let value = 0; + + while (element) { + value += !isNaN(element.offsetTop) ? element.offsetTop : 0; + + element = element.offsetParent; + } + + if (isSmallScreen) { + return value; + } + + return value - offset; + }; + + this.stickableTop = getOffsetTop($list); + + let control = () => { + let start = this.stickableTop; + + if (start === null) { + start = this.stickableTop = getOffsetTop($list); + } + + let scrollTop = $window.scrollTop(); + + if (scrollTop <= start || isSmallScreen) { + $container + .removeClass('sticked') + .width('') + .scrollTop(0); + + $container.css({ + maxHeight: '', + }); + + return; + } + + if (scrollTop > start) { + let scroll = $window.scrollTop() - start; + + $container + .addClass('sticked') + .width($left.outerWidth(true)) + .scrollTop(scroll); + + let topStickPosition = parseInt(window.getComputedStyle($container.get(0)).top); + + let maxHeight = $window.height() - topStickPosition - bottomSpaceHeight; + + $container.css({maxHeight: maxHeight}); + } + }; + + $window.on('resize.email-folders', () => control()); + $window.on('scroll.email-folders', () => control()); + } + + /** + * @protected + * @param {JQueryKeyEventObject} e + */ + handleShortcutKeyCtrlSpace(e) { + if (e.target.tagName === 'TEXTAREA' || e.target.tagName === 'INPUT') { + return; + } + + if (!this.getAcl().checkScope(this.scope, 'create')) { + return; + } + + e.preventDefault(); + e.stopPropagation(); + + this.actionComposeEmail({focusForCreate: true}); + } +} + +export default EmailListView; diff --git a/client/src/views/email/record/list.js b/client/src/views/email/record/list.js index 2e83b0b7a9..83d7bff976 100644 --- a/client/src/views/email/record/list.js +++ b/client/src/views/email/record/list.js @@ -26,366 +26,391 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -define('views/email/record/list', ['views/record/list', 'helpers/mass-action'], function (Dep, MassActionHelper) { +/** @module views/email/record/list */ - /** - * @class - * @name Class - * @memberOf module:views/email/record/list - * @extends module:views/record/list - */ - return Dep.extend(/** @lends module:views/email/record/list.Class# */{ +import ListRecordView from 'views/record/list'; +import MassActionHelper from 'helpers/mass-action'; - rowActionsView: 'views/email/record/row-actions/default', +class EmailListRecordView extends ListRecordView { - massActionList: ['remove', 'massUpdate'], + rowActionsView = 'views/email/record/row-actions/default' - setup: function () { - Dep.prototype.setup.call(this); + massActionList = ['remove', 'massUpdate'] - if (this.collection.url === this.entityType) { - this.addMassAction('retrieveFromTrash', false, true); - this.addMassAction('moveToFolder', true, true); - this.addMassAction('markAsNotImportant', false, true); - this.addMassAction('markAsImportant', false, true); - this.addMassAction('markAsNotRead', false, true); - this.addMassAction('markAsRead', false, true); - this.addMassAction('moveToTrash', false, true); + setup() { + super.setup(); - this.dropdownItemList.push({ - name: 'markAllAsRead', - label: 'Mark all as read', - }); + if (this.collection.url === this.entityType) { + this.addMassAction('retrieveFromTrash', false, true); + this.addMassAction('moveToFolder', true, true); + this.addMassAction('markAsNotImportant', false, true); + this.addMassAction('markAsImportant', false, true); + this.addMassAction('markAsNotRead', false, true); + this.addMassAction('markAsRead', false, true); + this.addMassAction('moveToTrash', false, true); + + this.dropdownItemList.push({ + name: 'markAllAsRead', + label: 'Mark all as read', + }); + } + + this.listenTo(this.collection, 'moving-to-trash', (id) => { + let model = this.collection.get(id); + + if (model) { + model.set('inTrash', true); } - this.listenTo(this.collection, 'moving-to-trash', (id) => { - let model = this.collection.get(id); - - if (model) { - model.set('inTrash', true); - } - - if (this.collection.selectedFolderId !== 'trash' && this.collection.selectedFolderId !== 'all') { - this.removeRecordFromList(id); - } - }); - - this.listenTo(this.collection, 'retrieving-from-trash', (id) => { - let model = this.collection.get(id); - - if (model) { - model.set('inTrash', false); - } - - if (this.collection.selectedFolderId === 'trash') { - this.removeRecordFromList(id); - } - }); - }, - - massActionMarkAsRead: function () { - let ids = []; - - for (let i in this.checkedList) { - ids.push(this.checkedList[i]); + if (this.collection.selectedFolderId !== 'trash' && this.collection.selectedFolderId !== 'all') { + this.removeRecordFromList(id); } + }); - Espo.Ajax.postRequest('Email/inbox/read', {ids: ids}); + this.listenTo(this.collection, 'retrieving-from-trash', (id) => { + let model = this.collection.get(id); - ids.forEach(id => { - let model = this.collection.get(id); - - if (model) { - model.set('isRead', true); - } - }); - }, - - massActionMarkAsNotRead: function () { - let ids = []; - - for (let i in this.checkedList) { - ids.push(this.checkedList[i]); + if (model) { + model.set('inTrash', false); } - Espo.Ajax.deleteRequest('Email/inbox/read', {ids: ids}); - - ids.forEach(id => { - let model = this.collection.get(id); - - if (model) { - model.set('isRead', false); - } - }); - }, - - massActionMarkAsImportant: function () { - let ids = []; - - for (let i in this.checkedList) { - ids.push(this.checkedList[i]); - } - - Espo.Ajax.postRequest('Email/inbox/important', {ids: ids}); - - ids.forEach(id => { - let model = this.collection.get(id); - - if (model) { - model.set('isImportant', true); - } - }); - }, - - massActionMarkAsNotImportant: function () { - let ids = []; - - for (let i in this.checkedList) { - ids.push(this.checkedList[i]); - } - - Espo.Ajax.deleteRequest('Email/inbox/important', {ids: ids}); - - ids.forEach(id => { - let model = this.collection.get(id); - - if (model) { - model.set('isImportant', false); - } - }); - }, - - massActionMoveToTrash: function () { - let ids = []; - - for (let i in this.checkedList) { - ids.push(this.checkedList[i]); - } - - Espo.Ajax - .postRequest('Email/inbox/inTrash', {ids: ids}) - .then(() => { - Espo.Ui.warning(this.translate('Moved to Trash', 'labels', 'Email')); - }); - if (this.collection.selectedFolderId === 'trash') { - return; + this.removeRecordFromList(id); } + }); + } - ids.forEach(id => { - this.collection.trigger('moving-to-trash', id, this.collection.get(id)); + // noinspection JSUnusedGlobalSymbols + massActionMarkAsRead() { + let ids = []; - this.uncheckRecord(id, null, true); - }); - }, + for (let i in this.checkedList) { + ids.push(this.checkedList[i]); + } - massActionRetrieveFromTrash: function () { - let ids = []; + Espo.Ajax.postRequest('Email/inbox/read', {ids: ids}); - for (let i in this.checkedList) { - ids.push(this.checkedList[i]); + ids.forEach(id => { + let model = this.collection.get(id); + + if (model) { + model.set('isRead', true); } + }); + } - Espo.Ajax - .deleteRequest('Email/inbox/inTrash', {ids: ids}) - .then(() => { - Espo.Ui.success(this.translate('Done')); - }); + // noinspection JSUnusedGlobalSymbols + massActionMarkAsNotRead() { + let ids = []; - if (this.collection.selectedFolderId !== 'trash') { - return; + for (let i in this.checkedList) { + ids.push(this.checkedList[i]); + } + + Espo.Ajax.deleteRequest('Email/inbox/read', {ids: ids}); + + ids.forEach(id => { + let model = this.collection.get(id); + + if (model) { + model.set('isRead', false); } + }); + } - ids.forEach(id => { - this.collection.trigger('retrieving-from-trash', id, this.collection.get(id)); + massActionMarkAsImportant() { + let ids = []; - this.uncheckRecord(id, null, true); - }); - }, + for (let i in this.checkedList) { + ids.push(this.checkedList[i]); + } - massMoveToFolder: function (folderId) { - let params = this.getMassActionSelectionPostData(); - let helper = new MassActionHelper(this); - let idle = !!params.searchParams && helper.checkIsIdle(); - - Espo.Ui.notify(this.translate('pleaseWait', 'messages')); - - Espo.Ajax - .postRequest('MassAction', { - entityType: this.entityType, - action: 'moveToFolder', - params: params, - idle: idle, - data: { - folderId: folderId, - }, - }) - .then(result => { - Espo.Ui.notify(false); - - if (result.id) { - helper - .process(result.id, 'moveToFolder') - .then(view => { - this.listenToOnce(view, 'close:success', () => { - this.collection.fetch().then(() => { - Espo.Ui.success(this.translate('Done')); - }); - }); - }); - - return; - } - - this.collection.fetch().then(() => { - Espo.Ui.success(this.translate('Done')); - }); - }); - }, - - massActionMoveToFolder: function () { - this.createView('dialog', 'views/email-folder/modals/select-folder', { - headerText: this.translate('Move to Folder', 'labels', 'Email'), - }, view => { - view.render(); - - this.listenToOnce(view, 'select', folderId => { - this.clearView('dialog'); - - this.massMoveToFolder(folderId); - }); - }); - }, - - actionMarkAsImportant: function (data) { - data = data || {}; - - let id = data.id; - - Espo.Ajax.postRequest('Email/inbox/important', {id: id}); + Espo.Ajax.postRequest('Email/inbox/important', {ids: ids}); + ids.forEach(id => { let model = this.collection.get(id); if (model) { model.set('isImportant', true); } - }, + }); + } - actionMarkAsNotImportant: function (data) { - data = data || {}; + massActionMarkAsNotImportant() { + let ids = []; - let id = data.id; + for (let i in this.checkedList) { + ids.push(this.checkedList[i]); + } - Espo.Ajax.deleteRequest('Email/inbox/important', {id: id}); + Espo.Ajax.deleteRequest('Email/inbox/important', {ids: ids}); + ids.forEach(id => { let model = this.collection.get(id); if (model) { model.set('isImportant', false); } - }, + }); + } - actionMarkAllAsRead: function () { - Espo.Ajax.postRequest('Email/inbox/read', {all: true}); + // noinspection JSUnusedGlobalSymbols + massActionMoveToTrash() { + let ids = []; - this.collection.forEach(model => { - model.set('isRead', true); + for (let i in this.checkedList) { + ids.push(this.checkedList[i]); + } + + Espo.Ajax + .postRequest('Email/inbox/inTrash', {ids: ids}) + .then(() => { + Espo.Ui.warning(this.translate('Moved to Trash', 'labels', 'Email')); }); - this.collection.trigger('all-marked-read'); - }, + if (this.collection.selectedFolderId === 'trash') { + return; + } - actionMoveToTrash: function (data) { - let id = data.id; + ids.forEach(id => { + this.collection.trigger('moving-to-trash', id, this.collection.get(id)); - Espo.Ui.notify(' ... '); + this.uncheckRecord(id, null, true); + }); + } - Espo.Ajax - .postRequest('Email/inbox/inTrash', {id: id}) - .then(() => { - Espo.Ui.warning(this.translate('Moved to Trash', 'labels', 'Email')); + // noinspection JSUnusedGlobalSymbols + massActionRetrieveFromTrash() { + let ids = []; - this.collection.trigger('moving-to-trash', id, this.collection.get(id)); + for (let i in this.checkedList) { + ids.push(this.checkedList[i]); + } + + Espo.Ajax + .deleteRequest('Email/inbox/inTrash', {ids: ids}) + .then(() => { + Espo.Ui.success(this.translate('Done')); + }); + + if (this.collection.selectedFolderId !== 'trash') { + return; + } + + ids.forEach(id => { + this.collection.trigger('retrieving-from-trash', id, this.collection.get(id)); + + this.uncheckRecord(id, null, true); + }); + } + + massMoveToFolder(folderId) { + let params = this.getMassActionSelectionPostData(); + let helper = new MassActionHelper(this); + let idle = !!params.searchParams && helper.checkIsIdle(); + + Espo.Ui.notify(this.translate('pleaseWait', 'messages')); + + Espo.Ajax + .postRequest('MassAction', { + entityType: this.entityType, + action: 'moveToFolder', + params: params, + idle: idle, + data: { + folderId: folderId, + }, + }) + .then(result => { + Espo.Ui.notify(false); + + if (result.id) { + helper + .process(result.id, 'moveToFolder') + .then(view => { + this.listenToOnce(view, 'close:success', () => { + this.collection.fetch().then(() => { + Espo.Ui.success(this.translate('Done')); + }); + }); + }); + + return; + } + + this.collection.fetch().then(() => { + Espo.Ui.success(this.translate('Done')); }); - }, + }); + } - actionRetrieveFromTrash: function (data) { - let id = data.id; + // noinspection JSUnusedGlobalSymbols + massActionMoveToFolder() { + this.createView('dialog', 'views/email-folder/modals/select-folder', { + headerText: this.translate('Move to Folder', 'labels', 'Email'), + }, view => { + view.render(); - Espo.Ui.notify(' ... '); + this.listenToOnce(view, 'select', folderId => { + this.clearView('dialog'); - this.retrieveFromTrash(id) - .then(() => { - Espo.Ui.warning(this.translate('Retrieved from Trash', 'labels', 'Email')); + this.massMoveToFolder(folderId); + }); + }); + } + actionMarkAsImportant(data) { + data = data || {}; + + let id = data.id; + + Espo.Ajax.postRequest('Email/inbox/important', {id: id}); + + let model = this.collection.get(id); + + if (model) { + model.set('isImportant', true); + } + } + + actionMarkAsNotImportant(data) { + data = data || {}; + + let id = data.id; + + Espo.Ajax.deleteRequest('Email/inbox/important', {id: id}); + + let model = this.collection.get(id); + + if (model) { + model.set('isImportant', false); + } + } + + // noinspection JSUnusedGlobalSymbols + actionMarkAllAsRead() { + Espo.Ajax.postRequest('Email/inbox/read', {all: true}); + + this.collection.forEach(model => { + model.set('isRead', true); + }); + + this.collection.trigger('all-marked-read'); + } + + actionMoveToTrash(data) { + let id = data.id; + + Espo.Ui.notify(' ... '); + + Espo.Ajax + .postRequest('Email/inbox/inTrash', {id: id}) + .then(() => { + Espo.Ui.warning(this.translate('Moved to Trash', 'labels', 'Email')); + + this.collection.trigger('moving-to-trash', id, this.collection.get(id)); + }); + } + + // noinspection JSUnusedGlobalSymbols + actionRetrieveFromTrash(data) { + let id = data.id; + + Espo.Ui.notify(' ... '); + + this.retrieveFromTrash(id) + .then(() => { + Espo.Ui.warning(this.translate('Retrieved from Trash', 'labels', 'Email')); + + this.collection.trigger('retrieving-from-trash', id, this.collection.get(id)); + }); + } + + /** + * @param {string} id + * @return {Promise} + */ + retrieveFromTrash(id) { + return Espo.Ajax.deleteRequest('Email/inbox/inTrash', {id: id}); + } + + massRetrieveFromTrashMoveToFolder(folderId) { + let ids = []; + + for (let i in this.checkedList) { + ids.push(this.checkedList[i]); + } + + Espo.Ajax + .deleteRequest('Email/inbox/inTrash', {ids: ids}) + .then(() => { + ids.forEach(id => { this.collection.trigger('retrieving-from-trash', id, this.collection.get(id)); }); - }, - /** - * @param {string} id - * @return {Promise} - */ - retrieveFromTrash: function (id) { - return Espo.Ajax.deleteRequest('Email/inbox/inTrash', {id: id}); - }, + return Espo.Ajax + .postRequest(`Email/inbox/folders/${folderId}`, {ids: ids}) + .then(() => { + Espo.Ui.success(this.translate('Done')); + }) + }); + } - massRetrieveFromTrashMoveToFolder: function (folderId) { - let ids = []; + // noinspection JSUnusedGlobalSymbols + /** + * @todo Use one API request. + */ + actionRetrieveFromTrashMoveToFolder(data) { + let id = data.id; + let folderId = data.folderId; - for (let i in this.checkedList) { - ids.push(this.checkedList[i]); - } + Espo.Ui.notify(' ... '); - Espo.Ajax - .deleteRequest('Email/inbox/inTrash', {ids: ids}) - .then(() => { - ids.forEach(id => { - this.collection.trigger('retrieving-from-trash', id, this.collection.get(id)); - }); - - return Espo.Ajax - .postRequest(`Email/inbox/folders/${folderId}`, {ids: ids}) - .then(() => { - Espo.Ui.success(this.translate('Done')); - }) + this.retrieveFromTrash(id) + .then(() => { + return this.moveToFolder(id, folderId) + }) + .then(() => { + this.collection.fetch().then(() => { + Espo.Ui.success(this.translate('Done')); }); - }, + }); + } - /** - * @todo Use one API request. - */ - actionRetrieveFromTrashMoveToFolder: function (data) { - let id = data.id; - let folderId = data.folderId; + /** + * @param {string} id + * @param {string} folderId + * @return {Promise} + */ + moveToFolder(id, folderId) { + return Espo.Ajax.postRequest(`Email/inbox/folders/${folderId}`, {id: id}); + } + actionMoveToFolder(data) { + let id = data.id; + let folderId = data.folderId; + + if (folderId) { Espo.Ui.notify(' ... '); - this.retrieveFromTrash(id) - .then(() => { - return this.moveToFolder(id, folderId) - }) + this.moveToFolder(id, folderId) .then(() => { this.collection.fetch().then(() => { Espo.Ui.success(this.translate('Done')); }); }); - }, - /** - * @param {string} id - * @param {string} folderId - * @return {Promise} - */ - moveToFolder: function (id, folderId) { - return Espo.Ajax.postRequest(`Email/inbox/folders/${folderId}`, {id: id}); - }, + return; + } - actionMoveToFolder: function (data) { - let id = data.id; - let folderId = data.folderId; + this.createView('dialog', 'views/email-folder/modals/select-folder', { + headerText: this.translate('Move to Folder', 'labels', 'Email'), + }, view => { + view.render(); + + this.listenToOnce(view, 'select', folderId => { + this.clearView('dialog'); - if (folderId) { Espo.Ui.notify(' ... '); this.moveToFolder(id, folderId) @@ -394,74 +419,57 @@ define('views/email/record/list', ['views/record/list', 'helpers/mass-action'], Espo.Ui.success(this.translate('Done')); }); }); + }); + }); + } + // noinspection JSUnusedGlobalSymbols + actionSend(data) { + let id = data.id; + + this.confirm({ + message: this.translate('sendConfirm', 'messages', 'Email'), + confirmText: this.translate('Send', 'labels', 'Email'), + }).then(() => { + let model = this.collection.get(id); + + if (!model) { return; } - this.createView('dialog', 'views/email-folder/modals/select-folder', { - headerText: this.translate('Move to Folder', 'labels', 'Email'), - }, view => { - view.render(); + Espo.Ui.notify(this.translate('Sending...', 'labels', 'Email')); - this.listenToOnce(view, 'select', folderId => { - this.clearView('dialog'); + model + .save({ + status: 'Sending', + }) + .then(() => { + Espo.Ui.success(this.translate('emailSent', 'messages', 'Email')); - Espo.Ui.notify(' ... '); - - this.moveToFolder(id, folderId) - .then(() => { - this.collection.fetch().then(() => { - Espo.Ui.success(this.translate('Done')); - }); - }); - }); - }); - }, - - actionSend: function (data) { - let id = data.id; - - this.confirm({ - message: this.translate('sendConfirm', 'messages', 'Email'), - confirmText: this.translate('Send', 'labels', 'Email'), - }).then(() => { - let model = this.collection.get(id); - - if (!model) { - return; - } - - Espo.Ui.notify(this.translate('Sending...', 'labels', 'Email')); - - model - .save({ - status: 'Sending', - }) - .then(() => { - Espo.Ui.success(this.translate('emailSent', 'messages', 'Email')); - - if (this.collection.selectedFolderId === 'drafts') { - this.removeRecordFromList(id); - this.uncheckRecord(id, null, true); - this.collection.trigger('draft-sent'); - } + if (this.collection.selectedFolderId === 'drafts') { + this.removeRecordFromList(id); + this.uncheckRecord(id, null, true); + this.collection.trigger('draft-sent'); } - ); - }); - }, + } + ); + }); + } - toggleMassMarkAsImportant: function () { - let allImportant = !this.checkedList - .map(id => this.collection.get(id)) - .find(m => !m.get('isImportant')); + // noinspection JSUnusedGlobalSymbols + toggleMassMarkAsImportant() { + let allImportant = !this.checkedList + .map(id => this.collection.get(id)) + .find(m => !m.get('isImportant')); - if (allImportant) { - this.massActionMarkAsNotImportant(); + if (allImportant) { + this.massActionMarkAsNotImportant(); - return; - } + return; + } - this.massActionMarkAsImportant(); - }, - }); -}); + this.massActionMarkAsImportant(); + } +} + +export default EmailListRecordView;