123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494 |
- /**
- * @copyright 2017 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * 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 <http://www.gnu.org/licenses/>.
- *
- */
-
- /* eslint-disable */
- import _ from 'underscore'
- import $ from 'jquery'
- import { Collection, Model, View } from 'backbone'
-
- import OC from './index'
-
- /**
- * @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)
- }
- }
- })
-
- /**
- * @class ContactCollection
- * @private
- */
- const ContactCollection = Collection.extend({
- model: Contact
- })
-
- /**
- * @class ContactsListView
- * @private
- */
- const ContactsListView = View.extend({
-
- /** @type {ContactCollection} */
- _collection: undefined,
-
- /** @type {array} */
- _subViews: [],
-
- /**
- * @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 {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'))
-
- // Show tooltip for top action
- this.$('.top-action').tooltip({ placement: 'left' })
- // Show tooltip for second action
- this.$('.second-action').tooltip({ placement: 'left' })
-
- 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({
- searchContactsText: 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 {jQuery} options.el
- * @param {jQuery} options.trigger
- * @class ContactsMenu
- * @memberOf OC
- */
- const ContactsMenu = function(options) {
- this.initialize(options)
- }
-
- ContactsMenu.prototype = {
- /** @type {jQuery} */
- $el: undefined,
-
- /** @type {jQuery} */
- _$trigger: undefined,
-
- /** @type {ContactsMenuView} */
- _view: undefined,
-
- /** @type {Promise} */
- _contactsPromise: undefined,
-
- /**
- * @param {Object} options
- * @param {jQuery} options.el - the element to render the menu in
- * @param {jQuery} options.trigger - the element to click on to open the menu
- * @returns {undefined}
- */
- initialize: function(options) {
- this.$el = options.el
- this._$trigger = options.trigger
-
- this._view = new ContactsMenuView({
- el: this.$el
- })
- this._view.on('search', function(searchTerm) {
- this._loadContacts(searchTerm)
- }, this)
-
- OC.registerMenu(this._$trigger, this.$el, function() {
- this._toggleVisibility(true)
- }.bind(this), true)
- this.$el.on('beforeHide', function() {
- this._toggleVisibility(false)
- }.bind(this))
- },
-
- /**
- * @private
- * @param {boolean} show
- * @returns {Promise}
- */
- _toggleVisibility: function(show) {
- if (show) {
- return this._loadContacts()
- } else {
- this.$el.html('')
- return Promise.resolve()
- }
- },
-
- /**
- * @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
|