From 60f9ed6241c3f7441f41bbd87d36c6f9e04c974b Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Mon, 24 Apr 2017 11:39:03 +0200 Subject: [PATCH 01/11] 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 b0e0e0c6a7..bbb990f1a4 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 552e20c80c..2ea80b146b 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 aadd66a055..15e406bf2d 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 0000000000..3c782cc5bf --- /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 12b7ca8faa..0dd6bed532 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 3a481e53dd..982004bf52 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 37db2642c1..c167dad2f9 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 1cdb5d6fc5..40a0bf8703 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 16d77c2df0..766b462325 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 bf6188e909..92a185cf2a 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 80c26a9078..8ded33bce2 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 9c92ec54b9..783e5590a2 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); + } + } From c844b2931e72ae341b466a02509ef94c37568af6 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Mon, 24 Apr 2017 13:03:52 +0200 Subject: [PATCH 02/11] close menu again when clicking on avatar Signed-off-by: Georg Ehrke --- core/js/jquery.contactsmenu.js | 55 ++++++++++++++++------------------ 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/core/js/jquery.contactsmenu.js b/core/js/jquery.contactsmenu.js index 3c782cc5bf..6e5f3228e3 100644 --- a/core/js/jquery.contactsmenu.js +++ b/core/js/jquery.contactsmenu.js @@ -14,25 +14,18 @@ + ' ' + '
  • '; - $.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; - } + var LIST = '' + + ''; + $.fn.contactsMenu = function(shareWith, shareType, appendTo) { // 0 - user, 4 - email, 6 - remote var allowedTypes = [0, 4, 6]; if (allowedTypes.indexOf(shareType) === -1) { @@ -40,11 +33,17 @@ } var $div = this; - appendTo.append(''); + appendTo.append(LIST); var $list = appendTo.find('div.contactsmenu-popover'); - var url = OC.generateUrl('/contactsmenu/findOne'); $div.click(function() { + if (!$list.hasClass('hidden')) { + $list.addClass('hidden'); + $list.hide(); + return; + } + + $list.removeClass('hidden'); $list.show(); if ($list.hasClass('loaded')) { @@ -52,7 +51,7 @@ } $list.addClass('loaded'); - $.ajax(url, { + $.ajax(OC.generateUrl('/contactsmenu/findOne'), { method: 'POST', data: { shareType: shareType, @@ -80,28 +79,26 @@ } }); + }).catch(function(reason) { + // TODO }); $(document).click(function(event) { var clickedList = $.contains($list, event.target); - var clickedLi = $.contains($div, event.target); + var clickedTarget = $.contains($div, event.target); $div.each(function() { if ($(this).is(event.target)) { - clickedLi = true; + clickedTarget = true; } }); - if (clickedList) { - return; - } - - if (clickedLi) { + if (clickedList || clickedTarget) { return; } + $list.addClass('hidden'); $list.hide(); - }); }; }(jQuery)); From 8f404c1f5640693c3fddd30fd36a3b97e725f2e1 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Mon, 24 Apr 2017 13:04:49 +0200 Subject: [PATCH 03/11] don't toggle sharing popover together with contactsmenu popover Signed-off-by: Georg Ehrke --- core/js/sharedialogshareelistview.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/js/sharedialogshareelistview.js b/core/js/sharedialogshareelistview.js index 982004bf52..f513eb7584 100644 --- a/core/js/sharedialogshareelistview.js +++ b/core/js/sharedialogshareelistview.js @@ -408,7 +408,7 @@ var shareId = parseInt(this._menuOpen, 10); if(!_.isNaN(shareId)) { var liSelector = 'li[data-share-id=' + shareId + ']'; - OC.showMenu(null, this.$(liSelector + ' .popovermenu')); + OC.showMenu(null, this.$(liSelector + '.sharingOptionsGroup .popovermenu')); } } @@ -485,7 +485,7 @@ event.stopPropagation(); var $element = $(event.target); var $li = $element.closest('li[data-share-id]'); - var $menu = $li.find('.popovermenu'); + var $menu = $li.find('.sharingOptionsGroup .popovermenu'); OC.showMenu(null, $menu); this._menuOpen = $li.data('share-id'); From 4d60aff6ecfcd1d77962d4aa65eb3812fdd6eb72 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Mon, 24 Apr 2017 13:09:24 +0200 Subject: [PATCH 04/11] Contactsmenu popover: show proper message when server throws error Signed-off-by: Georg Ehrke --- core/js/jquery.contactsmenu.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/core/js/jquery.contactsmenu.js b/core/js/jquery.contactsmenu.js index 6e5f3228e3..2c950fb35f 100644 --- a/core/js/jquery.contactsmenu.js +++ b/core/js/jquery.contactsmenu.js @@ -78,9 +78,15 @@ if (actions.length === 0) { } + }, function() { + $list.find('ul').find('li').addClass('hidden'); + + var template = Handlebars.compile(ENTRY); + $list.find('ul').append(template({ + hyperlink: '#', + title: t('core', 'Error fetching contact actions') + })); }); - }).catch(function(reason) { - // TODO }); $(document).click(function(event) { From 399f08bd330070d53aa36441632bed0578845a53 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Mon, 24 Apr 2017 15:31:44 +0200 Subject: [PATCH 05/11] add contactsmenu popover to resharer infobox Signed-off-by: Georg Ehrke --- core/css/share.scss | 4 ++++ core/js/sharedialogresharerinfoview.js | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/core/css/share.scss b/core/css/share.scss index 2ea80b146b..2e1c99b6f4 100644 --- a/core/css/share.scss +++ b/core/css/share.scss @@ -161,6 +161,10 @@ a { padding: 6px 4px; } +.resharerInfoView.subView { + position: relative; +} + #defaultExpireMessage, .reshare { /* fix shared by text going out of box */ white-space: normal; diff --git a/core/js/sharedialogresharerinfoview.js b/core/js/sharedialogresharerinfoview.js index a82b495bdc..201484c52a 100644 --- a/core/js/sharedialogresharerinfoview.js +++ b/core/js/sharedialogresharerinfoview.js @@ -100,6 +100,11 @@ $this.avatar($this.data('username'), 32); }); + this.$el.find('.reshare').contactsMenu( + this.model.getReshareOwner(), + OC.Share.SHARE_TYPE_USER, + this.$el); + return this; }, From e61cf83faf5cda15ba9f27193b7c5a89c9129da9 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Mon, 24 Apr 2017 15:32:51 +0200 Subject: [PATCH 06/11] better detection whether or not contactsmenu target was clicked Signed-off-by: Georg Ehrke --- core/js/jquery.contactsmenu.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/js/jquery.contactsmenu.js b/core/js/jquery.contactsmenu.js index 2c950fb35f..84f5a3f9ea 100644 --- a/core/js/jquery.contactsmenu.js +++ b/core/js/jquery.contactsmenu.js @@ -90,8 +90,8 @@ }); $(document).click(function(event) { - var clickedList = $.contains($list, event.target); - var clickedTarget = $.contains($div, event.target); + var clickedList = ($list.has(event.target).length > 0); + var clickedTarget = ($div.has(event.target).length > 0); $div.each(function() { if ($(this).is(event.target)) { From 897bd5cfefde22b4bdfc6d9dd67aa3bd865af306 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Mon, 24 Apr 2017 19:52:45 +0200 Subject: [PATCH 07/11] add contactsmenu popover to comments Signed-off-by: Georg Ehrke --- apps/comments/css/comments.css | 6 +++++- apps/comments/js/commentstabview.js | 20 +++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/apps/comments/css/comments.css b/apps/comments/css/comments.css index 796a550227..2d794d5270 100644 --- a/apps/comments/css/comments.css +++ b/apps/comments/css/comments.css @@ -54,7 +54,6 @@ #commentsTabView .comment { position: relative; - z-index: 1; margin-bottom: 30px; } @@ -108,6 +107,11 @@ vertical-align: middle; } +#commentsTabView .authorRow>div.hidden { + display: none !important; +} + +#commentsTabView .comments li .message .avatar-name-wrapper, #commentsTabView .comment .authorRow { position: relative; } diff --git a/apps/comments/js/commentstabview.js b/apps/comments/js/commentstabview.js index 2256bea943..ace0862ad2 100644 --- a/apps/comments/js/commentstabview.js +++ b/apps/comments/js/commentstabview.js @@ -232,6 +232,21 @@ var $this = $(this); $this.avatar($this.attr('data-username'), 32); }); + + var username = $el.find('.avatar').data('username'); + if (username !== oc_current_user) { + $el.find('.authorRow .avatar, .authorRow .author').contactsMenu( + username, 0, $el.find('.authorRow')); + } + + var message = $el.find('.message'); + message.find('.avatar').each(function() { + var avatar = $(this); + var strong = $(this).next(); + var appendTo = $(this).parent(); + + $.merge(avatar, strong).contactsMenu(avatar.data('user'), 0, appendTo); + }); }, /** @@ -251,7 +266,10 @@ // escape possible regex characters in the name mention = mention.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - var displayName = avatar + ' '+ _.escape(mentions[i].mentionDisplayName)+''; + var displayName = '' + + '' + + avatar + ' '+ _.escape(mentions[i].mentionDisplayName)+'' + + ''; // replace every mention either at the start of the input or after a whitespace // followed by a non-word character. From f32fc97533a0dd1a43b132cc1ff457693434083c Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Mon, 24 Apr 2017 20:54:33 +0200 Subject: [PATCH 08/11] fix ContactsStoreTest Signed-off-by: Georg Ehrke --- .../Contacts/ContactsMenu/ContactsStoreTest.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php b/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php index 8ded33bce2..08da360388 100644 --- a/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php +++ b/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php @@ -161,10 +161,11 @@ class ContactsStoreTest extends TestCase { $user = $this->createMock(IUser::class); $this->contactsManager->expects($this->once()) ->method('search') - ->with($this->equalTo(''), $this->equalTo(['FN'])) + ->with($this->equalTo('a567'), $this->equalTo(['UID'])) ->willReturn([ [ 'UID' => 123, + 'isLocalSystemBook' => false ], [ 'UID' => 'a567', @@ -190,17 +191,19 @@ class ContactsStoreTest extends TestCase { $user = $this->createMock(IUser::class); $this->contactsManager->expects($this->once()) ->method('search') - ->with($this->equalTo(''), $this->equalTo(['FN'])) + ->with($this->equalTo('darren@roner.au'), $this->equalTo(['EMAIL'])) ->willReturn([ [ 'UID' => 123, + 'isLocalSystemBook' => false ], [ 'UID' => 'a567', 'FN' => 'Darren Roner', 'EMAIL' => [ 'darren@roner.au' - ] + ], + 'isLocalSystemBook' => false ], ]); $user->expects($this->once()) @@ -226,17 +229,19 @@ class ContactsStoreTest extends TestCase { $user = $this->createMock(IUser::class); $this->contactsManager->expects($this->once()) ->method('search') - ->with($this->equalTo(''), $this->equalTo(['FN'])) + ->with($this->equalTo('a567'), $this->equalTo(['UID'])) ->willReturn([ [ 'UID' => 123, + 'isLocalSystemBook' => false ], [ 'UID' => 'a567', 'FN' => 'Darren Roner', 'EMAIL' => [ 'darren@roner.au123' - ] + ], + 'isLocalSystemBook' => false ], ]); $user->expects($this->once()) From 6d7714f1826f443ec9395b4d1c630c3ad44dc7ef Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Mon, 24 Apr 2017 21:04:05 +0200 Subject: [PATCH 09/11] fix CommentsTabsView js test Signed-off-by: Georg Ehrke --- apps/comments/tests/js/commentstabviewSpec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/comments/tests/js/commentstabviewSpec.js b/apps/comments/tests/js/commentstabviewSpec.js index 0bbfaa1f29..c961548d80 100644 --- a/apps/comments/tests/js/commentstabviewSpec.js +++ b/apps/comments/tests/js/commentstabviewSpec.js @@ -153,7 +153,7 @@ describe('OCA.Comments.CommentsTabView tests', function() { expect($comment.find('strong:first').text()).toEqual('Thane of Cawdor'); expect($comment.find('.avatar[data-user=banquo]').length).toEqual(1); - expect($comment.find('strong:last-child').text()).toEqual('Lord Banquo'); + expect($comment.find('.avatar-name-wrapper:last-child strong').text()).toEqual('Lord Banquo'); }); }); From 99b201a188a346c6af832e0d083cc4a958bbb32e Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Mon, 24 Apr 2017 22:34:33 +0200 Subject: [PATCH 10/11] unit test jquery.contactsmenu Signed-off-by: Georg Ehrke --- .../js/tests/specs/jquery.contactsmenuSpec.js | 198 ++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 core/js/tests/specs/jquery.contactsmenuSpec.js diff --git a/core/js/tests/specs/jquery.contactsmenuSpec.js b/core/js/tests/specs/jquery.contactsmenuSpec.js new file mode 100644 index 0000000000..b979be8883 --- /dev/null +++ b/core/js/tests/specs/jquery.contactsmenuSpec.js @@ -0,0 +1,198 @@ +/** + * Copyright (c) 2017 Georg Ehrke + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + +describe('jquery.contactsMenu tests', function() { + + var $selector1, $selector2, $appendTo; + + beforeEach(function() { + $('#testArea').append($('
    ')); + $('#testArea').append($('
    ')); + $('#testArea').append($('
    ')); + $selector1 = $('#selector1'); + $selector2 = $('#selector2'); + $appendTo = $('#appendTo'); + }); + + afterEach(function() { + $selector1.remove(); + $selector2.remove(); + $appendTo.remove(); + }); + + describe('shareType', function() { + it('stops if type not supported', function() { + $selector1.contactsMenu('user', 1, $appendTo); + expect($appendTo.children().length).toEqual(0); + + $selector1.contactsMenu('user', 2, $appendTo); + expect($appendTo.children().length).toEqual(0); + + $selector1.contactsMenu('user', 3, $appendTo); + expect($appendTo.children().length).toEqual(0); + + $selector1.contactsMenu('user', 5, $appendTo); + expect($appendTo.children().length).toEqual(0); + }); + + it('append list if shareType supported', function() { + $selector1.contactsMenu('user', 0, $appendTo); + expect($appendTo.children().length).toEqual(1); + expect($appendTo.html()).toEqual(''); + }); + }); + + describe('open on click', function() { + it('with one selector', function() { + $selector1.contactsMenu('user', 0, $appendTo); + expect($appendTo.children().length).toEqual(1); + expect($appendTo.find('div.contactsmenu-popover').hasClass('hidden')).toEqual(true); + $selector1.click(); + expect($appendTo.find('div.contactsmenu-popover').hasClass('hidden')).toEqual(false); + }); + + it('with multiple selectors - 1', function() { + $('#selector1, #selector2').contactsMenu('user', 0, $appendTo); + + expect($appendTo.children().length).toEqual(1); + expect($appendTo.find('div.contactsmenu-popover').hasClass('hidden')).toEqual(true); + $selector1.click(); + expect($appendTo.find('div.contactsmenu-popover').hasClass('hidden')).toEqual(false); + }); + + it('with multiple selectors - 2', function() { + $('#selector1, #selector2').contactsMenu('user', 0, $appendTo); + + expect($appendTo.children().length).toEqual(1); + expect($appendTo.find('div.contactsmenu-popover').hasClass('hidden')).toEqual(true); + $selector2.click(); + expect($appendTo.find('div.contactsmenu-popover').hasClass('hidden')).toEqual(false); + }); + + it ('should close when clicking the selector again - 1', function() { + $('#selector1, #selector2').contactsMenu('user', 0, $appendTo); + + expect($appendTo.children().length).toEqual(1); + expect($appendTo.find('div').hasClass('hidden')).toEqual(true); + $selector1.click(); + expect($appendTo.find('div').hasClass('hidden')).toEqual(false); + $selector1.click(); + expect($appendTo.find('div').hasClass('hidden')).toEqual(true); + }); + + it ('should close when clicking the selector again - 1', function() { + $('#selector1, #selector2').contactsMenu('user', 0, $appendTo); + + expect($appendTo.children().length).toEqual(1); + expect($appendTo.find('div').hasClass('hidden')).toEqual(true); + $selector1.click(); + expect($appendTo.find('div').hasClass('hidden')).toEqual(false); + $selector2.click(); + expect($appendTo.find('div').hasClass('hidden')).toEqual(true); + }); + }); + + describe('send requests to the server and render', function() { + it('load a topaction only', function() { + $('#selector1, #selector2').contactsMenu('user', 0, $appendTo); + $selector1.click(); + + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json; charset=utf-8' }, + JSON.stringify({ + "id": null, + "fullName": "Name 123", + "topAction": { + "title": "bar@baz.wtf", + "icon": "foo.svg", + "hyperlink": "mailto:bar%40baz.wtf"}, + "actions": [] + }) + ); + expect(fakeServer.requests[0].method).toEqual('POST'); + expect(fakeServer.requests[0].url).toEqual('http://localhost/index.php/contactsmenu/findOne'); + + expect($appendTo.html()).toEqual(''); + }); + + it('load topaction and more actions', function() { + $('#selector1, #selector2').contactsMenu('user', 0, $appendTo); + $selector1.click(); + + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json; charset=utf-8' }, + JSON.stringify({ + "id": null, + "fullName": "Name 123", + "topAction": { + "title": "bar@baz.wtf", + "icon": "foo.svg", + "hyperlink": "mailto:bar%40baz.wtf"}, + "actions": [{ + "title": "Details", + "icon": "details.svg", + "hyperlink": "http:\/\/localhost\/index.php\/apps\/contacts" + }] + }) + ); + expect(fakeServer.requests[0].method).toEqual('POST'); + expect(fakeServer.requests[0].url).toEqual('http://localhost/index.php/contactsmenu/findOne'); + + expect($appendTo.html()).toEqual(''); + }); + + it('load no actions', function() { + $('#selector1, #selector2').contactsMenu('user', 0, $appendTo); + $selector1.click(); + + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json; charset=utf-8' }, + JSON.stringify({ + "id": null, + "fullName": "Name 123", + "topAction": null, + "actions": [] + }) + ); + expect(fakeServer.requests[0].method).toEqual('POST'); + expect(fakeServer.requests[0].url).toEqual('http://localhost/index.php/contactsmenu/findOne'); + + expect($appendTo.html()).toEqual(''); + }); + + it('should throw an error', function() { + $('#selector1, #selector2').contactsMenu('user', 0, $appendTo); + $selector1.click(); + + fakeServer.requests[0].respond( + 400, + { 'Content-Type': 'application/json; charset=utf-8' }, + JSON.stringify([]) + ); + expect(fakeServer.requests[0].method).toEqual('POST'); + expect(fakeServer.requests[0].url).toEqual('http://localhost/index.php/contactsmenu/findOne'); + + expect($appendTo.html()).toEqual(''); + }); + }); + + it('click anywhere else to close the menu', function() { + $('#selector1, #selector2').contactsMenu('user', 0, $appendTo); + + expect($appendTo.find('div').hasClass('hidden')).toEqual(true); + $selector1.click(); + expect($appendTo.find('div').hasClass('hidden')).toEqual(false); + $(document).click(); + expect($appendTo.find('div').hasClass('hidden')).toEqual(true); + }); +}); From 6bbc682c4b24212d36ef595d3692653dca1c67b1 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Tue, 25 Apr 2017 22:01:56 +0200 Subject: [PATCH 11/11] handle 404 separately Signed-off-by: Georg Ehrke --- core/js/jquery.contactsmenu.js | 11 +++++++++-- core/js/tests/specs/jquery.contactsmenuSpec.js | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/core/js/jquery.contactsmenu.js b/core/js/jquery.contactsmenu.js index 84f5a3f9ea..1ea9f732f7 100644 --- a/core/js/jquery.contactsmenu.js +++ b/core/js/jquery.contactsmenu.js @@ -78,13 +78,20 @@ if (actions.length === 0) { } - }, function() { + }, function(jqXHR) { $list.find('ul').find('li').addClass('hidden'); + var title; + if (jqXHR.status === 404) { + title = t('core', 'No action available'); + } else { + title = t('core', 'Error fetching contact actions'); + } + var template = Handlebars.compile(ENTRY); $list.find('ul').append(template({ hyperlink: '#', - title: t('core', 'Error fetching contact actions') + title: title })); }); }); diff --git a/core/js/tests/specs/jquery.contactsmenuSpec.js b/core/js/tests/specs/jquery.contactsmenuSpec.js index b979be8883..7287648f5a 100644 --- a/core/js/tests/specs/jquery.contactsmenuSpec.js +++ b/core/js/tests/specs/jquery.contactsmenuSpec.js @@ -184,6 +184,21 @@ describe('jquery.contactsMenu tests', function() { expect($appendTo.html()).toEqual(''); }); + + it('should handle 404', function() { + $('#selector1, #selector2').contactsMenu('user', 0, $appendTo); + $selector1.click(); + + fakeServer.requests[0].respond( + 404, + { 'Content-Type': 'application/json; charset=utf-8' }, + JSON.stringify([]) + ); + expect(fakeServer.requests[0].method).toEqual('POST'); + expect(fakeServer.requests[0].url).toEqual('http://localhost/index.php/contactsmenu/findOne'); + + expect($appendTo.html()).toEqual(''); + }); }); it('click anywhere else to close the menu', function() {