From 986c291767efbc812eb4bc07563ffe4633afb28b Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Tue, 3 Oct 2023 16:09:48 +0200 Subject: [PATCH] fixup! refactor: Contacts menu to Vue Signed-off-by: Christoph Wurst --- core/src/OC/contactsmenu.js | 473 ------------------- core/src/OC/contactsmenu/contact.handlebars | 70 --- core/src/components/ContactsMenu/Contact.vue | 5 + core/src/views/ContactsMenu.vue | 64 ++- 4 files changed, 44 insertions(+), 568 deletions(-) delete mode 100644 core/src/OC/contactsmenu.js delete mode 100644 core/src/OC/contactsmenu/contact.handlebars diff --git a/core/src/OC/contactsmenu.js b/core/src/OC/contactsmenu.js deleted file mode 100644 index 61fe3b495060c..0000000000000 --- a/core/src/OC/contactsmenu.js +++ /dev/null @@ -1,473 +0,0 @@ -/** - * @copyright 2017 Christoph Wurst - * - * @author Christoph Wurst - * @author John Molakvoæ - * @author Roeland Jago Douma - * - * @license AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -/* eslint-disable */ -import _ from 'underscore' -import $ from 'jquery' -import { Collection, Model, View } from 'backbone' - -import OC from './index.js' - -/** - * @class Contact - */ -const Contact = Model.extend({ - defaults: { - fullName: '', - lastMessage: '', - actions: [], - hasOneAction: false, - hasTwoActions: false, - hasManyActions: false - }, - - /** - * @returns {undefined} - */ - initialize: function() { - // Add needed property for easier template rendering - if (this.get('actions').length === 0) { - this.set('hasOneAction', true) - } else if (this.get('actions').length === 1) { - this.set('hasTwoActions', true) - this.set('secondAction', this.get('actions')[0]) - } else { - this.set('hasManyActions', true) - } - - const fullName = this.get('fullName') - if (this.get('avatar') && fullName) { - this.set('avatarLabel', t('core', 'Avatar of {fullName}', { fullName })) - } - } -}) - -/** - * @class ContactCollection - * @private - */ -const ContactCollection = Collection.extend({ - model: Contact -}) - -/** - * @class ContactsListView - * @private - */ -const ContactsListView = View.extend({ - - /** @type {ContactCollection} */ - _collection: undefined, - - /** @type {array} */ - _subViews: [], - - /** @type {string} */ - tagName: 'ul', - - /** - * @param {object} options - * @returns {undefined} - */ - initialize: function(options) { - this._collection = options.collection - }, - - /** - * @returns {self} - */ - render: function() { - var self = this - self.$el.html('') - self._subViews = [] - self._collection.forEach(function(contact) { - var item = new ContactsListItemView({ - model: contact - }) - item.render() - self.$el.append(item.$el) - item.on('toggle:actionmenu', self._onChildActionMenuToggle, self) - self._subViews.push(item) - }) - - return self - }, - - /** - * Event callback to propagate opening (another) entry's action menu - * - * @param {type} $src - * @returns {undefined} - */ - _onChildActionMenuToggle: function($src) { - this._subViews.forEach(function(view) { - view.trigger('parent:toggle:actionmenu', $src) - }) - } -}) - -/** - * @class ContactsListItemView - * @private - */ -const ContactsListItemView = View.extend({ - - /** @type {string} */ - className: 'contact', - - /** @type {string} */ - tagName: 'li', - - /** @type {undefined|function} */ - _template: undefined, - - /** @type {Contact} */ - _model: undefined, - - /** @type {boolean} */ - _actionMenuShown: false, - - events: { - 'click .icon-more': '_onToggleActionsMenu' - }, - - contactTemplate: require('./contactsmenu/contact.handlebars'), - - /** - * @param {object} data - * @returns {undefined} - */ - template: function(data) { - return this.contactTemplate(data) - }, - - /** - * @param {object} options - * @returns {undefined} - */ - initialize: function(options) { - this._model = options.model - this.on('parent:toggle:actionmenu', this._onOtherActionMenuOpened, this) - }, - - /** - * @returns {self} - */ - render: function() { - this.$el.html(this.template({ - contact: this._model.toJSON() - })) - this.delegateEvents() - - // Show placeholder if no avatar is available (avatar is rendered as img, not div) - this.$('div.avatar').imageplaceholder(this._model.get('fullName')) - - return this - }, - - /** - * Toggle the visibility of the action popover menu - * - * @private - * @returns {undefined} - */ - _onToggleActionsMenu: function() { - this._actionMenuShown = !this._actionMenuShown - if (this._actionMenuShown) { - this.$('.menu').show() - } else { - this.$('.menu').hide() - } - this.trigger('toggle:actionmenu', this.$el) - }, - - /** - * @private - * @argument {jQuery} $src - * @returns {undefined} - */ - _onOtherActionMenuOpened: function($src) { - if (this.$el.is($src)) { - // Ignore - return - } - this._actionMenuShown = false - this.$('.menu').hide() - } -}) - -/** - * @class ContactsMenuView - * @private - */ -const ContactsMenuView = View.extend({ - - /** @type {undefined|function} */ - _loadingTemplate: undefined, - - /** @type {undefined|function} */ - _errorTemplate: undefined, - - /** @type {undefined|function} */ - _contentTemplate: undefined, - - /** @type {undefined|function} */ - _contactsTemplate: undefined, - - /** @type {undefined|ContactCollection} */ - _contacts: undefined, - - /** @type {string} */ - _searchTerm: '', - - events: { - 'input #contactsmenu-search': '_onSearch' - }, - - templates: { - loading: require('./contactsmenu/loading.handlebars'), - error: require('./contactsmenu/error.handlebars'), - menu: require('./contactsmenu/menu.handlebars'), - list: require('./contactsmenu/list.handlebars') - }, - - /** - * @returns {undefined} - */ - _onSearch: _.debounce(function(e) { - var searchTerm = this.$('#contactsmenu-search').val() - // IE11 triggers an 'input' event after the view has been rendered - // resulting in an endless loading loop. To prevent this, we remember - // the last search term to savely ignore some events - // See https://github.com/nextcloud/server/issues/5281 - if (searchTerm !== this._searchTerm) { - this.trigger('search', this.$('#contactsmenu-search').val()) - this._searchTerm = searchTerm - } - }, 700), - - /** - * @param {object} data - * @returns {string} - */ - loadingTemplate: function(data) { - return this.templates.loading(data) - }, - - /** - * @param {object} data - * @returns {string} - */ - errorTemplate: function(data) { - return this.templates.error( - _.extend({ - couldNotLoadText: t('core', 'Could not load your contacts') - }, data) - ) - }, - - /** - * @param {object} data - * @returns {string} - */ - contentTemplate: function(data) { - return this.templates.menu( - _.extend({ - searchContactsLabel: t('core', 'Search contacts'), - }, data) - ) - }, - - /** - * @param {object} data - * @returns {string} - */ - contactsTemplate: function(data) { - return this.templates.list( - _.extend({ - noContactsFoundText: t('core', 'No contacts found'), - showAllContactsText: t('core', 'Show all contacts …'), - contactsAppMgmtText: t('core', 'Install the Contacts app') - }, data) - ) - }, - - /** - * @param {object} options - * @returns {undefined} - */ - initialize: function(options) { - this.options = options - }, - - /** - * @param {string} text - * @returns {undefined} - */ - showLoading: function(text) { - this.render() - this._contacts = undefined - this.$('.content').html(this.loadingTemplate({ - loadingText: text - })) - }, - - /** - * @returns {undefined} - */ - showError: function() { - this.render() - this._contacts = undefined - this.$('.content').html(this.errorTemplate()) - }, - - /** - * @param {object} viewData - * @param {string} searchTerm - * @returns {undefined} - */ - showContacts: function(viewData, searchTerm) { - this._contacts = viewData.contacts - this.render({ - contacts: viewData.contacts - }) - - var list = new ContactsListView({ - collection: viewData.contacts - }) - list.render() - this.$('.content').html(this.contactsTemplate({ - contacts: viewData.contacts, - searchTerm: searchTerm, - contactsAppEnabled: viewData.contactsAppEnabled, - contactsAppURL: OC.generateUrl('/apps/contacts'), - canInstallApp: OC.isUserAdmin(), - contactsAppMgmtURL: OC.generateUrl('/settings/apps/social/contacts') - })) - this.$('#contactsmenu-contacts').html(list.$el) - }, - - /** - * @param {object} data - * @returns {self} - */ - render: function(data) { - var searchVal = this.$('#contactsmenu-search').val() - this.$el.html(this.contentTemplate(data)) - - // Focus search - this.$('#contactsmenu-search').val(searchVal) - this.$('#contactsmenu-search').focus() - return this - } - -}) - -/** - * @param {Object} options - * @param {string} options.el - * @class ContactsMenu - * @memberOf OC - */ -const ContactsMenu = function(options) { - this.initialize(options) -} - -ContactsMenu.prototype = { - /** @type {string} */ - $el: undefined, - - /** @type {ContactsMenuView} */ - _view: undefined, - - /** @type {Promise} */ - _contactsPromise: undefined, - - /** - * @param {Object} options - * @param {string} options.el - the selector of the element to render the menu in - * @returns {undefined} - */ - initialize: function(options) { - this.$el = $(options.el) - - this._view = new ContactsMenuView({ - el: this.$el, - }) - - this._view.on('search', function(searchTerm) { - this.loadContacts(searchTerm) - }, this) - }, - - /** - * @private - * @param {string|undefined} searchTerm - * @returns {Promise} - */ - _getContacts: function(searchTerm) { - var url = OC.generateUrl('/contactsmenu/contacts') - return Promise.resolve($.ajax(url, { - method: 'POST', - data: { - filter: searchTerm - } - })) - }, - - /** - * @param {string|undefined} searchTerm - * @returns {undefined} - */ - loadContacts: function(searchTerm) { - var self = this - - if (!self._contactsPromise) { - self._contactsPromise = self._getContacts(searchTerm) - } - - if (_.isUndefined(searchTerm) || searchTerm === '') { - self._view.showLoading(t('core', 'Loading your contacts …')) - } else { - self._view.showLoading(t('core', 'Looking for {term} …', { - term: searchTerm - })) - } - return self._contactsPromise.then(function(data) { - // Convert contact entries to Backbone collection - data.contacts = new ContactCollection(data.contacts) - - self._view.showContacts(data, searchTerm) - }, function(e) { - self._view.showError() - console.error('There was an error loading your contacts', e) - }).then(function() { - // Delete promise, so that contacts are fetched again when the - // menu is opened the next time. - delete self._contactsPromise - }).catch(console.error.bind(this)) - } -} - -export default ContactsMenu diff --git a/core/src/OC/contactsmenu/contact.handlebars b/core/src/OC/contactsmenu/contact.handlebars deleted file mode 100644 index c020cb797da1c..0000000000000 --- a/core/src/OC/contactsmenu/contact.handlebars +++ /dev/null @@ -1,70 +0,0 @@ -{{#if contact.avatar}} - {{#if contact.profileUrl}} - {{#if contact.profileTitle}} - - {{contact.avatarLabel}} - - {{/if}} - {{else}} - {{contact.avatarLabel}} - {{/if}} -{{else}} - {{#if contact.profileUrl}} - {{#if contact.profileTitle}} - -
-
- {{/if}} - {{else}} -
- {{/if}} -{{/if}} -{{#if contact.profileUrl}} - {{#if contact.profileTitle}} - -
{{contact.fullName}}
-
{{contact.lastMessage}}
- -
- {{/if}} - {{#if contact.topAction}} - - {{contact.topAction.title}} - - {{/if}} -{{else if contact.topAction}} - -
{{contact.fullName}}
-
{{contact.lastMessage}}
- -
- - {{contact.topAction.title}} - -{{else}} -
-
{{contact.fullName}}
-
{{contact.lastMessage}}
- -
-{{/if}} -{{#if contact.hasTwoActions}} - - {{contact.secondAction.title}} - -{{/if}} -{{#if contact.hasManyActions}} - - -{{/if}} diff --git a/core/src/components/ContactsMenu/Contact.vue b/core/src/components/ContactsMenu/Contact.vue index 9f6370da9e7d8..9632c54273bd6 100644 --- a/core/src/components/ContactsMenu/Contact.vue +++ b/core/src/components/ContactsMenu/Contact.vue @@ -118,6 +118,11 @@ export default { width: 20px; height: 20px; padding: 12px; + opacity: 0.5; + + &:hover { + opacity: 1; + } } } diff --git a/core/src/views/ContactsMenu.vue b/core/src/views/ContactsMenu.vue index 937e52aebea51..835d59151b3f1 100644 --- a/core/src/views/ContactsMenu.vue +++ b/core/src/views/ContactsMenu.vue @@ -32,19 +32,23 @@ -
- -
-
-

{{ loadingText }}

-
-
- + :placeholder="t('core', 'Search contacts …')" + @input="onInputDebounced"> + + + + + + + + +
    @@ -65,9 +69,13 @@ @@ -129,16 +153,6 @@ export default { width: 350px; &:deep { - .emptycontent { - margin-top: 5vh !important; - margin-bottom: 1.5vh; - - .icon-loading, - .icon-search { - display: inline-block; - } - } - label[for="contactsmenu-search"] { font-weight: bold; font-size: 19px;