From 60f9ed6241c3f7441f41bbd87d36c6f9e04c974b Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Mon, 24 Apr 2017 11:39:03 +0200 Subject: [PATCH] add contactsmenu popover Signed-off-by: Georg Ehrke --- core/Controller/ContactsMenuController.php | 17 +++ core/css/share.scss | 15 +++ core/js/core.json | 1 + core/js/jquery.contactsmenu.js | 107 ++++++++++++++++++ core/js/merged-template-prepend.json | 3 +- core/js/sharedialogshareelistview.js | 11 +- core/routes.php | 1 + .../Contacts/ContactsMenu/ContactsStore.php | 44 +++++++ lib/private/Contacts/ContactsMenu/Manager.php | 17 ++- .../Controller/ContactsMenuControllerTest.php | 31 +++++ .../ContactsMenu/ContactsStoreTest.php | 90 +++++++++++++++ .../lib/Contacts/ContactsMenu/ManagerTest.php | 45 ++++++++ 12 files changed, 379 insertions(+), 3 deletions(-) create mode 100644 core/js/jquery.contactsmenu.js diff --git a/core/Controller/ContactsMenuController.php b/core/Controller/ContactsMenuController.php index b0e0e0c6a77..bbb990f1a4f 100644 --- a/core/Controller/ContactsMenuController.php +++ b/core/Controller/ContactsMenuController.php @@ -26,6 +26,7 @@ namespace OC\Core\Controller; use OC\Contacts\ContactsMenu\Manager; use OCP\AppFramework\Controller; +use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; use OCP\IRequest; use OCP\IUserSession; @@ -59,4 +60,20 @@ class ContactsMenuController extends Controller { return $this->manager->getEntries($this->userSession->getUser(), $filter); } + /** + * @NoAdminRequired + * + * @param integer $shareType + * @param string $shareWith + * @return JSONResponse + */ + public function findOne($shareType, $shareWith) { + $contact = $this->manager->findOne($this->userSession->getUser(), $shareType, $shareWith); + + if ($contact) { + return $contact; + } else { + return new JSONResponse([], Http::STATUS_NOT_FOUND); + } + } } diff --git a/core/css/share.scss b/core/css/share.scss index 552e20c80cc..2ea80b146b5 100644 --- a/core/css/share.scss +++ b/core/css/share.scss @@ -87,6 +87,7 @@ list-style-type: none; padding: 8px; > li { + position: relative; padding-top: 10px; padding-bottom: 10px; font-weight: bold; @@ -103,6 +104,7 @@ padding: 3px 6px; } } + .shareOption { white-space: nowrap; display: inline-block; @@ -185,6 +187,19 @@ a { color: rgba($color-main-text, .4); } +.contactsmenu-popover { + left: -8px; + right: auto; + padding: 3px 6px; + li.hidden { + display: none !important; + } + &:after { + left: 8px; + right: auto; + } +} + .popovermenu .datepicker { margin-left: 35px; } diff --git a/core/js/core.json b/core/js/core.json index aadd66a0558..15e406bf2d2 100644 --- a/core/js/core.json +++ b/core/js/core.json @@ -20,6 +20,7 @@ "libraries": [ "jquery-showpassword.js", "jquery.avatar.js", + "jquery.contactsmenu.js", "placeholder.js" ], "modules": [ diff --git a/core/js/jquery.contactsmenu.js b/core/js/jquery.contactsmenu.js new file mode 100644 index 00000000000..3c782cc5bfd --- /dev/null +++ b/core/js/jquery.contactsmenu.js @@ -0,0 +1,107 @@ +/** + * Copyright (c) 2017 Georg Ehrke + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +(function ($) { + var ENTRY = '' + + '
  • ' + + ' ' + + ' {{#if icon}}{{/if}}' + + ' {{title}}' + + ' ' + + '
  • '; + + $.fn.contactsMenu = function(shareWith, shareType, appendTo) { + if (typeof(shareWith) !== 'undefined') { + shareWith = String(shareWith); + } else { + if (typeof(this.data('share-with')) !== 'undefined') { + shareWith = this.data('share-with'); + } + } + if (typeof(shareType) !== 'undefined') { + shareType = Number(shareType); + } else { + if (typeof(this.data('share-type')) !== 'undefined') { + shareType = this.data('share-type'); + } + } + if (typeof(appendTo) === 'undefined') { + appendTo = this; + } + + // 0 - user, 4 - email, 6 - remote + var allowedTypes = [0, 4, 6]; + if (allowedTypes.indexOf(shareType) === -1) { + return; + } + + var $div = this; + appendTo.append(''); + var $list = appendTo.find('div.contactsmenu-popover'); + var url = OC.generateUrl('/contactsmenu/findOne'); + + $div.click(function() { + $list.show(); + + if ($list.hasClass('loaded')) { + return; + } + + $list.addClass('loaded'); + $.ajax(url, { + method: 'POST', + data: { + shareType: shareType, + shareWith: shareWith + } + }).then(function(data) { + $list.find('ul').find('li').addClass('hidden'); + + var actions; + if (!data.topAction) { + actions = [{ + hyperlink: '#', + title: t('core', 'No action available') + }]; + } else { + actions = [data.topAction].concat(data.actions); + } + + actions.forEach(function(action) { + var template = Handlebars.compile(ENTRY); + $list.find('ul').append(template(action)); + }); + + if (actions.length === 0) { + + } + }); + }); + + $(document).click(function(event) { + var clickedList = $.contains($list, event.target); + var clickedLi = $.contains($div, event.target); + + $div.each(function() { + if ($(this).is(event.target)) { + clickedLi = true; + } + }); + + if (clickedList) { + return; + } + + if (clickedLi) { + return; + } + + $list.hide(); + + }); + }; +}(jQuery)); diff --git a/core/js/merged-template-prepend.json b/core/js/merged-template-prepend.json index 12b7ca8faa3..0dd6bed5329 100644 --- a/core/js/merged-template-prepend.json +++ b/core/js/merged-template-prepend.json @@ -13,5 +13,6 @@ "mimetypelist.js", "oc-backbone.js", "placeholder.js", - "jquery.avatar.js" + "jquery.avatar.js", + "jquery.contactsmenu.js" ] diff --git a/core/js/sharedialogshareelistview.js b/core/js/sharedialogshareelistview.js index 3a481e53dde..982004bf52d 100644 --- a/core/js/sharedialogshareelistview.js +++ b/core/js/sharedialogshareelistview.js @@ -26,7 +26,7 @@ '{{#each sharees}}' + '
  • ' + '
    ' + - '{{shareWithDisplayName}}' + + '{{shareWithDisplayName}}' + '' + '{{#if editPermissionPossible}}' + '' + @@ -361,6 +361,15 @@ this.$('.has-tooltip').tooltip({ placement: 'bottom' }); + + this.$('ul.shareWithList > li').each(function() { + var $this = $(this); + + var shareWith = $this.data('share-with'); + var shareType = $this.data('share-type'); + + $this.find('div.avatar, span.username').contactsMenu(shareWith, shareType, $this); + }) } else { var permissionChangeShareId = parseInt(this._renderPermissionChange, 10); var shareWithIndex = this.model.findShareWithIndex(permissionChangeShareId); diff --git a/core/routes.php b/core/routes.php index 37db2642c1b..c167dad2f9f 100644 --- a/core/routes.php +++ b/core/routes.php @@ -61,6 +61,7 @@ $application->registerRoutes($this, [ ['name' => 'Css#getCss', 'url' => '/css/{appName}/{fileName}', 'verb' => 'GET'], ['name' => 'Js#getJs', 'url' => '/js/{appName}/{fileName}', 'verb' => 'GET'], ['name' => 'contactsMenu#index', 'url' => '/contactsmenu/contacts', 'verb' => 'POST'], + ['name' => 'contactsMenu#findOne', 'url' => '/contactsmenu/findOne', 'verb' => 'POST'], ], 'ocs' => [ ['root' => '/cloud', 'name' => 'OCS#getCapabilities', 'url' => '/capabilities', 'verb' => 'GET'], diff --git a/lib/private/Contacts/ContactsMenu/ContactsStore.php b/lib/private/Contacts/ContactsMenu/ContactsStore.php index 1cdb5d6fc5f..40a0bf87031 100644 --- a/lib/private/Contacts/ContactsMenu/ContactsStore.php +++ b/lib/private/Contacts/ContactsMenu/ContactsStore.php @@ -59,6 +59,50 @@ class ContactsStore { }); } + /** + * @param IUser $user + * @param integer $shareType + * @param string $shareWith + * @return IEntry|null + */ + public function findOne(IUser $user, $shareType, $shareWith) { + switch($shareType) { + case 0: + case 6: + $filter = ['UID']; + break; + case 4: + $filter = ['EMAIL']; + break; + default: + return null; + } + + $userId = $user->getUID(); + $allContacts = $this->contactsManager->search($shareWith, $filter); + $contacts = array_filter($allContacts, function($contact) use ($userId) { + return $contact['UID'] !== $userId; + }); + $match = null; + + foreach ($contacts as $contact) { + if ($shareType === 4 && isset($contact['EMAIL'])) { + if (in_array($shareWith, $contact['EMAIL'])) { + $match = $contact; + break; + } + } + if ($shareType === 0 || $shareType === 6) { + if ($contact['UID'] === $shareWith && $contact['isLocalSystemBook'] === true) { + $match = $contact; + break; + } + } + } + + return $match ? $this->contactArrayToEntry($match) : null; + } + /** * @param array $contact * @return Entry diff --git a/lib/private/Contacts/ContactsMenu/Manager.php b/lib/private/Contacts/ContactsMenu/Manager.php index 16d77c2df08..766b4623253 100644 --- a/lib/private/Contacts/ContactsMenu/Manager.php +++ b/lib/private/Contacts/ContactsMenu/Manager.php @@ -51,7 +51,7 @@ class Manager { } /** - * @param string $user + * @param IUser $user * @param string $filter * @return array */ @@ -69,6 +69,21 @@ class Manager { ]; } + /** + * @param IUser $user + * @param integer $shareType + * @param string $shareWith + * @return IEntry + */ + public function findOne(IUser $user, $shareType, $shareWith) { + $entry = $this->store->findOne($user, $shareType, $shareWith); + if ($entry) { + $this->processEntries([$entry], $user); + } + + return $entry; + } + /** * @param IEntry[] $entries * @return IEntry[] diff --git a/tests/Core/Controller/ContactsMenuControllerTest.php b/tests/Core/Controller/ContactsMenuControllerTest.php index bf6188e9097..92a185cf2ad 100644 --- a/tests/Core/Controller/ContactsMenuControllerTest.php +++ b/tests/Core/Controller/ContactsMenuControllerTest.php @@ -76,4 +76,35 @@ class ContactsMenuControllerTest extends TestCase { $this->assertEquals($entries, $response); } + public function testFindOne() { + $user = $this->createMock(IUser::class); + $entry = $this->createMock(IEntry::class); + $this->userSession->expects($this->once()) + ->method('getUser') + ->willReturn($user); + $this->contactsManager->expects($this->once()) + ->method('findOne') + ->with($this->equalTo($user), $this->equalTo(42), $this->equalTo('test-search-phrase')) + ->willReturn($entry); + + $response = $this->controller->findOne(42, 'test-search-phrase'); + + $this->assertEquals($entry, $response); + } + + public function testFindOne404() { + $user = $this->createMock(IUser::class); + $this->userSession->expects($this->once()) + ->method('getUser') + ->willReturn($user); + $this->contactsManager->expects($this->once()) + ->method('findOne') + ->with($this->equalTo($user), $this->equalTo(42), $this->equalTo('test-search-phrase')) + ->willReturn(null); + + $response = $this->controller->findOne(42, 'test-search-phrase'); + + $this->assertEquals([], $response->getData()); + $this->assertEquals(404, $response->getStatus()); + } } diff --git a/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php b/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php index 80c26a9078e..8ded33bce27 100644 --- a/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php +++ b/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php @@ -157,4 +157,94 @@ class ContactsStoreTest extends TestCase { $this->assertEquals('https://photo', $entries[1]->getAvatar()); } + public function testFindOneUser() { + $user = $this->createMock(IUser::class); + $this->contactsManager->expects($this->once()) + ->method('search') + ->with($this->equalTo(''), $this->equalTo(['FN'])) + ->willReturn([ + [ + 'UID' => 123, + ], + [ + 'UID' => 'a567', + 'FN' => 'Darren Roner', + 'EMAIL' => [ + 'darren@roner.au' + ], + 'isLocalSystemBook' => true + ], + ]); + $user->expects($this->once()) + ->method('getUID') + ->willReturn('user123'); + + $entry = $this->contactsStore->findOne($user, 0, 'a567'); + + $this->assertEquals([ + 'darren@roner.au' + ], $entry->getEMailAddresses()); + } + + public function testFindOneEMail() { + $user = $this->createMock(IUser::class); + $this->contactsManager->expects($this->once()) + ->method('search') + ->with($this->equalTo(''), $this->equalTo(['FN'])) + ->willReturn([ + [ + 'UID' => 123, + ], + [ + 'UID' => 'a567', + 'FN' => 'Darren Roner', + 'EMAIL' => [ + 'darren@roner.au' + ] + ], + ]); + $user->expects($this->once()) + ->method('getUID') + ->willReturn('user123'); + + $entry = $this->contactsStore->findOne($user, 4, 'darren@roner.au'); + + $this->assertEquals([ + 'darren@roner.au' + ], $entry->getEMailAddresses()); + } + + public function testFindOneNotSupportedType() { + $user = $this->createMock(IUser::class); + + $entry = $this->contactsStore->findOne($user, 42, 'darren@roner.au'); + + $this->assertEquals(null, $entry); + } + + public function testFindOneNoMatches() { + $user = $this->createMock(IUser::class); + $this->contactsManager->expects($this->once()) + ->method('search') + ->with($this->equalTo(''), $this->equalTo(['FN'])) + ->willReturn([ + [ + 'UID' => 123, + ], + [ + 'UID' => 'a567', + 'FN' => 'Darren Roner', + 'EMAIL' => [ + 'darren@roner.au123' + ] + ], + ]); + $user->expects($this->once()) + ->method('getUID') + ->willReturn('user123'); + + $entry = $this->contactsStore->findOne($user, 0, 'a567'); + + $this->assertEquals(null, $entry); + } } diff --git a/tests/lib/Contacts/ContactsMenu/ManagerTest.php b/tests/lib/Contacts/ContactsMenu/ManagerTest.php index 9c92ec54b9f..783e5590a29 100644 --- a/tests/lib/Contacts/ContactsMenu/ManagerTest.php +++ b/tests/lib/Contacts/ContactsMenu/ManagerTest.php @@ -99,4 +99,49 @@ class ManagerTest extends TestCase { $this->assertEquals($expected, $data); } + public function testFindOne() { + $shareTypeFilter = 42; + $shareWithFilter = 'foobar'; + + $user = $this->createMock(IUser::class); + $entry = current($this->generateTestEntries()); + $provider = $this->createMock(IProvider::class); + $this->contactsStore->expects($this->once()) + ->method('findOne') + ->with($user, $shareTypeFilter, $shareWithFilter) + ->willReturn($entry); + $this->actionProviderStore->expects($this->once()) + ->method('getProviders') + ->with($user) + ->willReturn([$provider]); + $provider->expects($this->once()) + ->method('process'); + + $data = $this->manager->findOne($user, $shareTypeFilter, $shareWithFilter); + + $this->assertEquals($entry, $data); + } + + public function testFindOne404() { + $shareTypeFilter = 42; + $shareWithFilter = 'foobar'; + + $user = $this->createMock(IUser::class); + $provider = $this->createMock(IProvider::class); + $this->contactsStore->expects($this->once()) + ->method('findOne') + ->with($user, $shareTypeFilter, $shareWithFilter) + ->willReturn(null); + $this->actionProviderStore->expects($this->never()) + ->method('getProviders') + ->with($user) + ->willReturn([$provider]); + $provider->expects($this->never()) + ->method('process'); + + $data = $this->manager->findOne($user, $shareTypeFilter, $shareWithFilter); + + $this->assertEquals(null, $data); + } + }