Browse Source

Show mime icon, bump bundles, make the SearchResultEntry class non-abstract, Fix header search icon, various fixes

Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
John Molakvoæ (skjnldsv) 3 years ago
parent
commit
71b62c4203
62 changed files with 404 additions and 308 deletions
  1. 1 2
      apps/comments/composer/composer/autoload_classmap.php
  2. 1 2
      apps/comments/composer/composer/autoload_static.php
  3. 0 0
      apps/comments/js/comments.js
  4. 0 0
      apps/comments/js/comments.js.map
  5. 2 2
      apps/comments/lib/AppInfo/Application.php
  6. 19 2
      apps/comments/lib/Search/CommentsSearchProvider.php
  7. 0 31
      apps/comments/lib/Search/CommentsSearchResultEntry.php
  8. 0 3
      apps/dav/composer/composer/autoload_classmap.php
  9. 0 3
      apps/dav/composer/composer/autoload_static.php
  10. 10 2
      apps/dav/lib/Search/ContactsSearchProvider.php
  11. 0 30
      apps/dav/lib/Search/ContactsSearchResultEntry.php
  12. 10 2
      apps/dav/lib/Search/EventsSearchProvider.php
  13. 0 30
      apps/dav/lib/Search/EventsSearchResultEntry.php
  14. 10 2
      apps/dav/lib/Search/TasksSearchProvider.php
  15. 0 30
      apps/dav/lib/Search/TasksSearchResultEntry.php
  16. 5 5
      apps/dav/tests/unit/Search/ContactsSearchProviderTest.php
  17. 7 7
      apps/dav/tests/unit/Search/EventsSearchProviderTest.php
  18. 7 7
      apps/dav/tests/unit/Search/TasksSearchProviderTest.php
  19. 0 1
      apps/files/composer/composer/autoload_classmap.php
  20. 0 1
      apps/files/composer/composer/autoload_static.php
  21. 29 6
      apps/files/lib/Search/FilesSearchProvider.php
  22. 0 38
      apps/files/lib/Search/FilesSearchResultEntry.php
  23. 0 1
      apps/settings/composer/composer/autoload_classmap.php
  24. 0 1
      apps/settings/composer/composer/autoload_static.php
  25. 0 29
      apps/settings/lib/Search/SectionResult.php
  26. 22 8
      apps/settings/lib/Search/SectionSearch.php
  27. 0 0
      core/js/dist/files_client.js
  28. 0 0
      core/js/dist/files_client.js.map
  29. 0 0
      core/js/dist/files_fileinfo.js
  30. 0 0
      core/js/dist/files_fileinfo.js.map
  31. 0 0
      core/js/dist/files_iedavclient.js
  32. 0 0
      core/js/dist/files_iedavclient.js.map
  33. 0 0
      core/js/dist/install.js
  34. 0 0
      core/js/dist/install.js.map
  35. 0 0
      core/js/dist/login.js
  36. 0 0
      core/js/dist/login.js.map
  37. 0 0
      core/js/dist/main.js
  38. 0 0
      core/js/dist/main.js.map
  39. 0 0
      core/js/dist/maintenance.js
  40. 0 0
      core/js/dist/maintenance.js.map
  41. 0 0
      core/js/dist/recommendedapps.js
  42. 0 0
      core/js/dist/recommendedapps.js.map
  43. 0 0
      core/js/dist/unified-search.js
  44. 0 0
      core/js/dist/unified-search.js.map
  45. 30 5
      core/src/components/UnifiedSearch/SearchResult.vue
  46. 68 0
      core/src/components/UnifiedSearch/SearchResultPlaceholder.vue
  47. 27 1
      core/src/services/UnifiedSearchService.js
  48. 59 14
      core/src/views/UnifiedSearch.vue
  49. 1 1
      lib/composer/composer/autoload_classmap.php
  50. 1 1
      lib/composer/composer/autoload_static.php
  51. 4 17
      lib/private/NavigationManager.php
  52. 1 1
      lib/private/Search/Provider/File.php
  53. 25 2
      lib/private/Search/Result/File.php
  54. 15 6
      lib/private/Search/SearchComposer.php
  55. 13 3
      lib/private/TemplateLayout.php
  56. 7 0
      lib/public/INavigationManager.php
  57. 10 0
      lib/public/Search/IProvider.php
  58. 4 4
      lib/public/Search/SearchResult.php
  59. 7 7
      lib/public/Search/SearchResultEntry.php
  60. 5 0
      package-lock.json
  61. 1 0
      package.json
  62. 3 1
      webpack.common.js

+ 1 - 2
apps/comments/composer/composer/autoload_classmap.php

@@ -21,8 +21,7 @@ return array(
     'OCA\\Comments\\Listener\\LoadSidebarScripts' => $baseDir . '/../lib/Listener/LoadSidebarScripts.php',
     'OCA\\Comments\\Notification\\Listener' => $baseDir . '/../lib/Notification/Listener.php',
     'OCA\\Comments\\Notification\\Notifier' => $baseDir . '/../lib/Notification/Notifier.php',
-    'OCA\\Comments\\Search\\CommentsSearchResultEntry' => $baseDir . '/../lib/Search/CommentsSearchResultEntry.php',
+    'OCA\\Comments\\Search\\CommentsSearchProvider' => $baseDir . '/../lib/Search/CommentsSearchProvider.php',
     'OCA\\Comments\\Search\\LegacyProvider' => $baseDir . '/../lib/Search/LegacyProvider.php',
-    'OCA\\Comments\\Search\\Provider' => $baseDir . '/../lib/Search/Provider.php',
     'OCA\\Comments\\Search\\Result' => $baseDir . '/../lib/Search/Result.php',
 );

+ 1 - 2
apps/comments/composer/composer/autoload_static.php

@@ -36,9 +36,8 @@ class ComposerStaticInitComments
         'OCA\\Comments\\Listener\\LoadSidebarScripts' => __DIR__ . '/..' . '/../lib/Listener/LoadSidebarScripts.php',
         'OCA\\Comments\\Notification\\Listener' => __DIR__ . '/..' . '/../lib/Notification/Listener.php',
         'OCA\\Comments\\Notification\\Notifier' => __DIR__ . '/..' . '/../lib/Notification/Notifier.php',
-        'OCA\\Comments\\Search\\CommentsSearchResultEntry' => __DIR__ . '/..' . '/../lib/Search/CommentsSearchResultEntry.php',
+        'OCA\\Comments\\Search\\CommentsSearchProvider' => __DIR__ . '/..' . '/../lib/Search/CommentsSearchProvider.php',
         'OCA\\Comments\\Search\\LegacyProvider' => __DIR__ . '/..' . '/../lib/Search/LegacyProvider.php',
-        'OCA\\Comments\\Search\\Provider' => __DIR__ . '/..' . '/../lib/Search/Provider.php',
         'OCA\\Comments\\Search\\Result' => __DIR__ . '/..' . '/../lib/Search/Result.php',
     );
 

File diff suppressed because it is too large
+ 0 - 0
apps/comments/js/comments.js


File diff suppressed because it is too large
+ 0 - 0
apps/comments/js/comments.js.map


+ 2 - 2
apps/comments/lib/AppInfo/Application.php

@@ -37,7 +37,7 @@ use OCA\Comments\Listener\LoadAdditionalScripts;
 use OCA\Comments\Listener\LoadSidebarScripts;
 use OCA\Comments\Notification\Notifier;
 use OCA\Comments\Search\LegacyProvider;
-use OCA\Comments\Search\Provider;
+use OCA\Comments\Search\CommentsSearchProvider;
 use OCA\Files\Event\LoadAdditionalScriptsEvent;
 use OCA\Files\Event\LoadSidebar;
 use OCP\AppFramework\App;
@@ -74,7 +74,7 @@ class Application extends App implements IBootstrap {
 			CommentsEntityEvent::EVENT_ENTITY,
 			CommentsEntityEventListener::class
 		);
-		$context->registerSearchProvider(Provider::class);
+		$context->registerSearchProvider(CommentsSearchProvider::class);
 	}
 
 	public function boot(IBootContext $context): void {

+ 19 - 2
apps/comments/lib/Search/Provider.php → apps/comments/lib/Search/CommentsSearchProvider.php

@@ -32,10 +32,11 @@ use OCP\IUserManager;
 use OCP\Search\IProvider;
 use OCP\Search\ISearchQuery;
 use OCP\Search\SearchResult;
+use OCP\Search\SearchResultEntry;
 use function array_map;
 use function pathinfo;
 
-class Provider implements IProvider {
+class CommentsSearchProvider implements IProvider {
 
 	/** @var IUserManager */
 	private $userManager;
@@ -59,14 +60,30 @@ class Provider implements IProvider {
 		$this->legacyProvider = $legacyProvider;
 	}
 
+	/**
+	 * @inheritDoc
+	 */
 	public function getId(): string {
 		return 'comments';
 	}
 
+	/**
+	 * @inheritDoc
+	 */
 	public function getName(): string {
 		return $this->l10n->t('Comments');
 	}
 
+	/**
+	 * @inheritDoc
+	 */
+	public function getOrder(): int {
+		return 10;
+	}
+
+	/**
+	 * @inheritDoc
+	 */
 	public function search(IUser $user, ISearchQuery $query): SearchResult {
 		return SearchResult::complete(
 			$this->l10n->t('Comments'),
@@ -77,7 +94,7 @@ class Provider implements IProvider {
 				$avatarUrl = $isUser
 					? $this->urlGenerator->linkToRoute('core.avatar.getAvatar', ['userId' => $result->authorId, 'size' => 42])
 					: $this->urlGenerator->linkToRoute('core.GuestAvatar.getAvatar', ['guestName' => $result->authorId, 'size' => 42]);
-				return new CommentsSearchResultEntry(
+				return new SearchResultEntry(
 					$avatarUrl,
 					$result->name,
 					$path,

+ 0 - 31
apps/comments/lib/Search/CommentsSearchResultEntry.php

@@ -1,31 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/**
- * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @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/>.
- */
-
-namespace OCA\Comments\Search;
-
-use OCP\Search\ASearchResultEntry;
-
-class CommentsSearchResultEntry extends ASearchResultEntry {
-}

+ 0 - 3
apps/dav/composer/composer/autoload_classmap.php

@@ -212,11 +212,8 @@ return array(
     'OCA\\DAV\\RootCollection' => $baseDir . '/../lib/RootCollection.php',
     'OCA\\DAV\\Search\\ACalendarSearchProvider' => $baseDir . '/../lib/Search/ACalendarSearchProvider.php',
     'OCA\\DAV\\Search\\ContactsSearchProvider' => $baseDir . '/../lib/Search/ContactsSearchProvider.php',
-    'OCA\\DAV\\Search\\ContactsSearchResultEntry' => $baseDir . '/../lib/Search/ContactsSearchResultEntry.php',
     'OCA\\DAV\\Search\\EventsSearchProvider' => $baseDir . '/../lib/Search/EventsSearchProvider.php',
-    'OCA\\DAV\\Search\\EventsSearchResultEntry' => $baseDir . '/../lib/Search/EventsSearchResultEntry.php',
     'OCA\\DAV\\Search\\TasksSearchProvider' => $baseDir . '/../lib/Search/TasksSearchProvider.php',
-    'OCA\\DAV\\Search\\TasksSearchResultEntry' => $baseDir . '/../lib/Search/TasksSearchResultEntry.php',
     'OCA\\DAV\\Server' => $baseDir . '/../lib/Server.php',
     'OCA\\DAV\\Settings\\CalDAVSettings' => $baseDir . '/../lib/Settings/CalDAVSettings.php',
     'OCA\\DAV\\Storage\\PublicOwnerWrapper' => $baseDir . '/../lib/Storage/PublicOwnerWrapper.php',

+ 0 - 3
apps/dav/composer/composer/autoload_static.php

@@ -227,11 +227,8 @@ class ComposerStaticInitDAV
         'OCA\\DAV\\RootCollection' => __DIR__ . '/..' . '/../lib/RootCollection.php',
         'OCA\\DAV\\Search\\ACalendarSearchProvider' => __DIR__ . '/..' . '/../lib/Search/ACalendarSearchProvider.php',
         'OCA\\DAV\\Search\\ContactsSearchProvider' => __DIR__ . '/..' . '/../lib/Search/ContactsSearchProvider.php',
-        'OCA\\DAV\\Search\\ContactsSearchResultEntry' => __DIR__ . '/..' . '/../lib/Search/ContactsSearchResultEntry.php',
         'OCA\\DAV\\Search\\EventsSearchProvider' => __DIR__ . '/..' . '/../lib/Search/EventsSearchProvider.php',
-        'OCA\\DAV\\Search\\EventsSearchResultEntry' => __DIR__ . '/..' . '/../lib/Search/EventsSearchResultEntry.php',
         'OCA\\DAV\\Search\\TasksSearchProvider' => __DIR__ . '/..' . '/../lib/Search/TasksSearchProvider.php',
-        'OCA\\DAV\\Search\\TasksSearchResultEntry' => __DIR__ . '/..' . '/../lib/Search/TasksSearchResultEntry.php',
         'OCA\\DAV\\Server' => __DIR__ . '/..' . '/../lib/Server.php',
         'OCA\\DAV\\Settings\\CalDAVSettings' => __DIR__ . '/..' . '/../lib/Settings/CalDAVSettings.php',
         'OCA\\DAV\\Storage\\PublicOwnerWrapper' => __DIR__ . '/..' . '/../lib/Storage/PublicOwnerWrapper.php',

+ 10 - 2
apps/dav/lib/Search/ContactsSearchProvider.php

@@ -32,6 +32,7 @@ use OCP\IUser;
 use OCP\Search\IProvider;
 use OCP\Search\ISearchQuery;
 use OCP\Search\SearchResult;
+use OCP\Search\SearchResultEntry;
 use Sabre\VObject\Component\VCard;
 use Sabre\VObject\Reader;
 
@@ -92,6 +93,13 @@ class ContactsSearchProvider implements IProvider {
 		return $this->l10n->t('Contacts');
 	}
 
+	/**
+	 * @inheritDoc
+	 */
+	public function getOrder(): int {
+		return 7;
+	}
+
 	/**
 	 * @inheritDoc
 	 */
@@ -116,7 +124,7 @@ class ContactsSearchProvider implements IProvider {
 				'offset' => $query->getCursor(),
 			]
 		);
-		$formattedResults = \array_map(function (array $contactRow) use ($addressBooksById):ContactsSearchResultEntry {
+		$formattedResults = \array_map(function (array $contactRow) use ($addressBooksById):SearchResultEntry {
 			$addressBook = $addressBooksById[$contactRow['addressbookid']];
 
 			/** @var VCard $vCard */
@@ -130,7 +138,7 @@ class ContactsSearchProvider implements IProvider {
 			$subline = $this->generateSubline($vCard);
 			$resourceUrl = $this->getDeepLinkToContactsApp($addressBook['uri'], (string) $vCard->UID);
 
-			return new ContactsSearchResultEntry($thumbnailUrl, $title, $subline, $resourceUrl, 'icon-contacts-dark', true);
+			return new SearchResultEntry($thumbnailUrl, $title, $subline, $resourceUrl, 'icon-contacts-dark', true);
 		}, $searchResults);
 
 		return SearchResult::paginated(

+ 0 - 30
apps/dav/lib/Search/ContactsSearchResultEntry.php

@@ -1,30 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/**
- * @copyright Copyright (c) 2020, Georg Ehrke
- *
- * @author Georg Ehrke <oc.list@georgehrke.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * 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, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
- */
-namespace OCA\DAV\Search;
-
-use OCP\Search\ASearchResultEntry;
-
-class ContactsSearchResultEntry extends ASearchResultEntry {
-}

+ 10 - 2
apps/dav/lib/Search/EventsSearchProvider.php

@@ -28,6 +28,7 @@ use OCA\DAV\CalDAV\CalDavBackend;
 use OCP\IUser;
 use OCP\Search\ISearchQuery;
 use OCP\Search\SearchResult;
+use OCP\Search\SearchResultEntry;
 use Sabre\VObject\Component;
 use Sabre\VObject\DateTimeParser;
 use Sabre\VObject\Property;
@@ -78,6 +79,13 @@ class EventsSearchProvider extends ACalendarSearchProvider {
 		return $this->l10n->t('Events');
 	}
 
+	/**
+	 * @inheritDoc
+	 */
+	public function getOrder(): int {
+		return 10;
+	}
+
 	/**
 	 * @inheritDoc
 	 */
@@ -102,7 +110,7 @@ class EventsSearchProvider extends ACalendarSearchProvider {
 				'offset' => $query->getCursor(),
 			]
 		);
-		$formattedResults = \array_map(function (array $eventRow) use ($calendarsById, $subscriptionsById):EventsSearchResultEntry {
+		$formattedResults = \array_map(function (array $eventRow) use ($calendarsById, $subscriptionsById):SearchResultEntry {
 			$component = $this->getPrimaryComponent($eventRow['calendardata'], self::$componentType);
 			$title = (string)($component->SUMMARY ?? $this->l10n->t('Untitled event'));
 			$subline = $this->generateSubline($component);
@@ -114,7 +122,7 @@ class EventsSearchProvider extends ACalendarSearchProvider {
 			}
 			$resourceUrl = $this->getDeepLinkToCalendarApp($calendar['principaluri'], $calendar['uri'], $eventRow['uri']);
 
-			return new EventsSearchResultEntry('', $title, $subline, $resourceUrl, 'icon-calendar-dark', false);
+			return new SearchResultEntry('', $title, $subline, $resourceUrl, 'icon-calendar-dark', false);
 		}, $searchResults);
 
 		return SearchResult::paginated(

+ 0 - 30
apps/dav/lib/Search/EventsSearchResultEntry.php

@@ -1,30 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/**
- * @copyright Copyright (c) 2020, Georg Ehrke
- *
- * @author Georg Ehrke <oc.list@georgehrke.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * 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, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
- */
-namespace OCA\DAV\Search;
-
-use OCP\Search\ASearchResultEntry;
-
-class EventsSearchResultEntry extends ASearchResultEntry {
-}

+ 10 - 2
apps/dav/lib/Search/TasksSearchProvider.php

@@ -28,6 +28,7 @@ use OCA\DAV\CalDAV\CalDavBackend;
 use OCP\IUser;
 use OCP\Search\ISearchQuery;
 use OCP\Search\SearchResult;
+use OCP\Search\SearchResultEntry;
 use Sabre\VObject\Component;
 
 /**
@@ -70,6 +71,13 @@ class TasksSearchProvider extends ACalendarSearchProvider {
 		return $this->l10n->t('Tasks');
 	}
 
+	/**
+	 * @inheritDoc
+	 */
+	public function getOrder(): int {
+		return 10;
+	}
+
 	/**
 	 * @inheritDoc
 	 */
@@ -94,7 +102,7 @@ class TasksSearchProvider extends ACalendarSearchProvider {
 				'offset' => $query->getCursor(),
 			]
 		);
-		$formattedResults = \array_map(function (array $taskRow) use ($calendarsById, $subscriptionsById):TasksSearchResultEntry {
+		$formattedResults = \array_map(function (array $taskRow) use ($calendarsById, $subscriptionsById):SearchResultEntry {
 			$component = $this->getPrimaryComponent($taskRow['calendardata'], self::$componentType);
 			$title = (string)($component->SUMMARY ?? $this->l10n->t('Untitled task'));
 			$subline = $this->generateSubline($component);
@@ -106,7 +114,7 @@ class TasksSearchProvider extends ACalendarSearchProvider {
 			}
 			$resourceUrl = $this->getDeepLinkToTasksApp($calendar['uri'], $taskRow['uri']);
 
-			return new TasksSearchResultEntry('', $title, $subline, $resourceUrl, 'icon-checkmark', false);
+			return new SearchResultEntry('', $title, $subline, $resourceUrl, 'icon-checkmark', false);
 		}, $searchResults);
 
 		return SearchResult::paginated(

+ 0 - 30
apps/dav/lib/Search/TasksSearchResultEntry.php

@@ -1,30 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/**
- * @copyright Copyright (c) 2020, Georg Ehrke
- *
- * @author Georg Ehrke <oc.list@georgehrke.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * 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, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
- */
-namespace OCA\DAV\Search;
-
-use OCP\Search\ASearchResultEntry;
-
-class TasksSearchResultEntry extends ASearchResultEntry {
-}

+ 5 - 5
apps/dav/tests/unit/Search/ContactsSearchProviderTest.php

@@ -26,13 +26,13 @@ namespace OCA\DAV\Tests\unit;
 
 use OCA\DAV\CardDAV\CardDavBackend;
 use OCA\DAV\Search\ContactsSearchProvider;
-use OCA\DAV\Search\ContactsSearchResultEntry;
 use OCP\App\IAppManager;
 use OCP\IL10N;
 use OCP\IURLGenerator;
 use OCP\IUser;
 use OCP\Search\ISearchQuery;
 use OCP\Search\SearchResult;
+use OCP\Search\SearchResultEntry;
 use Sabre\VObject\Reader;
 use Test\TestCase;
 
@@ -216,20 +216,20 @@ class ContactsSearchProviderTest extends TestCase {
 		$result1 = $data['entries'][1];
 		$result1Data = $result1->jsonSerialize();
 
-		$this->assertInstanceOf(ContactsSearchResultEntry::class, $result0);
+		$this->assertInstanceOf(SearchResultEntry::class, $result0);
 		$this->assertEquals('', $result0Data['thumbnailUrl']);
 		$this->assertEquals('FN of Test', $result0Data['title']);
 		$this->assertEquals('subline', $result0Data['subline']);
 		$this->assertEquals('deep-link-to-contacts', $result0Data['resourceUrl']);
-		$this->assertEquals('icon-contacts-dark', $result0Data['iconClass']);
+		$this->assertEquals('icon-contacts-dark', $result0Data['icon']);
 		$this->assertTrue($result0Data['rounded']);
 
-		$this->assertInstanceOf(ContactsSearchResultEntry::class, $result1);
+		$this->assertInstanceOf(SearchResultEntry::class, $result1);
 		$this->assertEquals('absolute-thumbnail-url?photo', $result1Data['thumbnailUrl']);
 		$this->assertEquals('FN of Test2', $result1Data['title']);
 		$this->assertEquals('subline', $result1Data['subline']);
 		$this->assertEquals('deep-link-to-contacts', $result1Data['resourceUrl']);
-		$this->assertEquals('icon-contacts-dark', $result1Data['iconClass']);
+		$this->assertEquals('icon-contacts-dark', $result1Data['icon']);
 		$this->assertTrue($result1Data['rounded']);
 	}
 

+ 7 - 7
apps/dav/tests/unit/Search/EventsSearchProviderTest.php

@@ -26,13 +26,13 @@ namespace OCA\DAV\Tests\unit\Search;
 
 use OCA\DAV\CalDAV\CalDavBackend;
 use OCA\DAV\Search\EventsSearchProvider;
-use OCA\DAV\Search\EventsSearchResultEntry;
 use OCP\App\IAppManager;
 use OCP\IL10N;
 use OCP\IURLGenerator;
 use OCP\IUser;
 use OCP\Search\ISearchQuery;
 use OCP\Search\SearchResult;
+use OCP\Search\SearchResultEntry;
 use Sabre\VObject\Reader;
 use Test\TestCase;
 
@@ -392,28 +392,28 @@ class EventsSearchProviderTest extends TestCase {
 		$result2 = $data['entries'][2];
 		$result2Data = $result2->jsonSerialize();
 
-		$this->assertInstanceOf(EventsSearchResultEntry::class, $result0);
+		$this->assertInstanceOf(SearchResultEntry::class, $result0);
 		$this->assertEmpty($result0Data['thumbnailUrl']);
 		$this->assertEquals('Untitled event', $result0Data['title']);
 		$this->assertEquals('subline', $result0Data['subline']);
 		$this->assertEquals('deep-link-to-calendar', $result0Data['resourceUrl']);
-		$this->assertEquals('icon-calendar-dark', $result0Data['iconClass']);
+		$this->assertEquals('icon-calendar-dark', $result0Data['icon']);
 		$this->assertFalse($result0Data['rounded']);
 
-		$this->assertInstanceOf(EventsSearchResultEntry::class, $result1);
+		$this->assertInstanceOf(SearchResultEntry::class, $result1);
 		$this->assertEmpty($result1Data['thumbnailUrl']);
 		$this->assertEquals('Test Europe Berlin', $result1Data['title']);
 		$this->assertEquals('subline', $result1Data['subline']);
 		$this->assertEquals('deep-link-to-calendar', $result1Data['resourceUrl']);
-		$this->assertEquals('icon-calendar-dark', $result1Data['iconClass']);
+		$this->assertEquals('icon-calendar-dark', $result1Data['icon']);
 		$this->assertFalse($result1Data['rounded']);
 
-		$this->assertInstanceOf(EventsSearchResultEntry::class, $result2);
+		$this->assertInstanceOf(SearchResultEntry::class, $result2);
 		$this->assertEmpty($result2Data['thumbnailUrl']);
 		$this->assertEquals('Test Europe Berlin', $result2Data['title']);
 		$this->assertEquals('subline', $result2Data['subline']);
 		$this->assertEquals('deep-link-to-calendar', $result2Data['resourceUrl']);
-		$this->assertEquals('icon-calendar-dark', $result2Data['iconClass']);
+		$this->assertEquals('icon-calendar-dark', $result2Data['icon']);
 		$this->assertFalse($result2Data['rounded']);
 	}
 

+ 7 - 7
apps/dav/tests/unit/Search/TasksSearchProviderTest.php

@@ -26,13 +26,13 @@ namespace OCA\DAV\Tests\unit\Search;
 
 use OCA\DAV\CalDAV\CalDavBackend;
 use OCA\DAV\Search\TasksSearchProvider;
-use OCA\DAV\Search\TasksSearchResultEntry;
 use OCP\App\IAppManager;
 use OCP\IL10N;
 use OCP\IURLGenerator;
 use OCP\IUser;
 use OCP\Search\ISearchQuery;
 use OCP\Search\SearchResult;
+use OCP\Search\SearchResultEntry;
 use Sabre\VObject\Reader;
 use Test\TestCase;
 
@@ -276,28 +276,28 @@ class TasksSearchProviderTest extends TestCase {
 		$result2 = $data['entries'][2];
 		$result2Data = $result2->jsonSerialize();
 
-		$this->assertInstanceOf(TasksSearchResultEntry::class, $result0);
+		$this->assertInstanceOf(SearchResultEntry::class, $result0);
 		$this->assertEmpty($result0Data['thumbnailUrl']);
 		$this->assertEquals('Untitled task', $result0Data['title']);
 		$this->assertEquals('subline', $result0Data['subline']);
 		$this->assertEquals('deep-link-to-tasks', $result0Data['resourceUrl']);
-		$this->assertEquals('icon-checkmark', $result0Data['iconClass']);
+		$this->assertEquals('icon-checkmark', $result0Data['icon']);
 		$this->assertFalse($result0Data['rounded']);
 
-		$this->assertInstanceOf(TasksSearchResultEntry::class, $result1);
+		$this->assertInstanceOf(SearchResultEntry::class, $result1);
 		$this->assertEmpty($result1Data['thumbnailUrl']);
 		$this->assertEquals('Task title', $result1Data['title']);
 		$this->assertEquals('subline', $result1Data['subline']);
 		$this->assertEquals('deep-link-to-tasks', $result1Data['resourceUrl']);
-		$this->assertEquals('icon-checkmark', $result1Data['iconClass']);
+		$this->assertEquals('icon-checkmark', $result1Data['icon']);
 		$this->assertFalse($result1Data['rounded']);
 
-		$this->assertInstanceOf(TasksSearchResultEntry::class, $result2);
+		$this->assertInstanceOf(SearchResultEntry::class, $result2);
 		$this->assertEmpty($result2Data['thumbnailUrl']);
 		$this->assertEquals('Task title', $result2Data['title']);
 		$this->assertEquals('subline', $result2Data['subline']);
 		$this->assertEquals('deep-link-to-tasks', $result2Data['resourceUrl']);
-		$this->assertEquals('icon-checkmark', $result2Data['iconClass']);
+		$this->assertEquals('icon-checkmark', $result2Data['icon']);
 		$this->assertFalse($result2Data['rounded']);
 	}
 

+ 0 - 1
apps/files/composer/composer/autoload_classmap.php

@@ -48,7 +48,6 @@ return array(
     'OCA\\Files\\Migration\\Version11301Date20191205150729' => $baseDir . '/../lib/Migration/Version11301Date20191205150729.php',
     'OCA\\Files\\Notification\\Notifier' => $baseDir . '/../lib/Notification/Notifier.php',
     'OCA\\Files\\Search\\FilesSearchProvider' => $baseDir . '/../lib/Search/FilesSearchProvider.php',
-    'OCA\\Files\\Search\\FilesSearchResultEntry' => $baseDir . '/../lib/Search/FilesSearchResultEntry.php',
     'OCA\\Files\\Service\\DirectEditingService' => $baseDir . '/../lib/Service/DirectEditingService.php',
     'OCA\\Files\\Service\\OwnershipTransferService' => $baseDir . '/../lib/Service/OwnershipTransferService.php',
     'OCA\\Files\\Service\\TagService' => $baseDir . '/../lib/Service/TagService.php',

+ 0 - 1
apps/files/composer/composer/autoload_static.php

@@ -63,7 +63,6 @@ class ComposerStaticInitFiles
         'OCA\\Files\\Migration\\Version11301Date20191205150729' => __DIR__ . '/..' . '/../lib/Migration/Version11301Date20191205150729.php',
         'OCA\\Files\\Notification\\Notifier' => __DIR__ . '/..' . '/../lib/Notification/Notifier.php',
         'OCA\\Files\\Search\\FilesSearchProvider' => __DIR__ . '/..' . '/../lib/Search/FilesSearchProvider.php',
-        'OCA\\Files\\Search\\FilesSearchResultEntry' => __DIR__ . '/..' . '/../lib/Search/FilesSearchResultEntry.php',
         'OCA\\Files\\Service\\DirectEditingService' => __DIR__ . '/..' . '/../lib/Service/DirectEditingService.php',
         'OCA\\Files\\Service\\OwnershipTransferService' => __DIR__ . '/..' . '/../lib/Service/OwnershipTransferService.php',
         'OCA\\Files\\Service\\TagService' => __DIR__ . '/..' . '/../lib/Service/TagService.php',

+ 29 - 6
apps/files/lib/Search/FilesSearchProvider.php

@@ -27,12 +27,14 @@ namespace OCA\Files\Search;
 
 use OC\Search\Provider\File;
 use OC\Search\Result\File as FileResult;
+use OCP\Files\IMimeTypeDetector;
 use OCP\IL10N;
 use OCP\IURLGenerator;
 use OCP\IUser;
 use OCP\Search\IProvider;
 use OCP\Search\ISearchQuery;
 use OCP\Search\SearchResult;
+use OCP\Search\SearchResultEntry;
 
 class FilesSearchProvider implements IProvider {
 
@@ -45,37 +47,58 @@ class FilesSearchProvider implements IProvider {
 	/** @var IURLGenerator */
 	private $urlGenerator;
 
+	/** @var IMimeTypeDetector */
+	private $mimeTypeDetector;
+
 	public function __construct(File $fileSearch,
 								IL10N $l10n,
-								IURLGenerator $urlGenerator) {
+								IURLGenerator $urlGenerator,
+								IMimeTypeDetector $mimeTypeDetector) {
 		$this->l10n = $l10n;
 		$this->fileSearch = $fileSearch;
 		$this->urlGenerator = $urlGenerator;
+		$this->mimeTypeDetector = $mimeTypeDetector;
 	}
 
+	/**
+	 * @inheritDoc
+	 */
 	public function getId(): string {
 		return 'files';
 	}
 
+	/**
+	 * @inheritDoc
+	 */
 	public function getName(): string {
 		return $this->l10n->t('Files');
 	}
 
+	/**
+	 * @inheritDoc
+	 */
+	public function getOrder(): int {
+		return 5;
+	}
+
+	/**
+	 * @inheritDoc
+	 */
 	public function search(IUser $user, ISearchQuery $query): SearchResult {
 		return SearchResult::complete(
 			$this->l10n->t('Files'),
 			array_map(function (FileResult $result) {
 				// Generate thumbnail url
-				$thumbnailUrl = $result->type === 'folder'
-					? ''
-					: $this->urlGenerator->linkToRoute('core.Preview.getPreviewByFileId', ['x' => 32, 'y' => 32, 'fileId' => $result->id]);
+				$thumbnailUrl = $result->has_preview
+					? $this->urlGenerator->linkToRoute('core.Preview.getPreviewByFileId', ['x' => 32, 'y' => 32, 'fileId' => $result->id])
+					: '';
 
-				return new FilesSearchResultEntry(
+				return new SearchResultEntry(
 					$thumbnailUrl,
 					$result->name,
 					$this->formatSubline($result),
 					$result->link,
-					$result->type === 'folder' ? 'icon-folder' : 'icon-filetype-file'
+					$result->type === 'folder' ? 'icon-folder' : $this->mimeTypeDetector->mimeTypeIcon($result->mime_type)
 				);
 			}, $this->fileSearch->search($query->getTerm()))
 		);

+ 0 - 38
apps/files/lib/Search/FilesSearchResultEntry.php

@@ -1,38 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/**
- * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @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/>.
- */
-
-namespace OCA\Files\Search;
-
-use OCP\Search\ASearchResultEntry;
-
-class FilesSearchResultEntry extends ASearchResultEntry {
-	public function __construct(string $thumbnailUrl,
-								string $filename,
-								string $path,
-								string $url,
-								string $icon) {
-		parent::__construct($thumbnailUrl, $filename, $path, $url, $icon, false);
-	}
-}

+ 0 - 1
apps/settings/composer/composer/autoload_classmap.php

@@ -31,7 +31,6 @@ return array(
     'OCA\\Settings\\Hooks' => $baseDir . '/../lib/Hooks.php',
     'OCA\\Settings\\Mailer\\NewUserMailHelper' => $baseDir . '/../lib/Mailer/NewUserMailHelper.php',
     'OCA\\Settings\\Middleware\\SubadminMiddleware' => $baseDir . '/../lib/Middleware/SubadminMiddleware.php',
-    'OCA\\Settings\\Search\\SectionResult' => $baseDir . '/../lib/Search/SectionResult.php',
     'OCA\\Settings\\Search\\SectionSearch' => $baseDir . '/../lib/Search/SectionSearch.php',
     'OCA\\Settings\\Sections\\Admin\\Additional' => $baseDir . '/../lib/Sections/Admin/Additional.php',
     'OCA\\Settings\\Sections\\Admin\\Groupware' => $baseDir . '/../lib/Sections/Admin/Groupware.php',

+ 0 - 1
apps/settings/composer/composer/autoload_static.php

@@ -46,7 +46,6 @@ class ComposerStaticInitSettings
         'OCA\\Settings\\Hooks' => __DIR__ . '/..' . '/../lib/Hooks.php',
         'OCA\\Settings\\Mailer\\NewUserMailHelper' => __DIR__ . '/..' . '/../lib/Mailer/NewUserMailHelper.php',
         'OCA\\Settings\\Middleware\\SubadminMiddleware' => __DIR__ . '/..' . '/../lib/Middleware/SubadminMiddleware.php',
-        'OCA\\Settings\\Search\\SectionResult' => __DIR__ . '/..' . '/../lib/Search/SectionResult.php',
         'OCA\\Settings\\Search\\SectionSearch' => __DIR__ . '/..' . '/../lib/Search/SectionSearch.php',
         'OCA\\Settings\\Sections\\Admin\\Additional' => __DIR__ . '/..' . '/../lib/Sections/Admin/Additional.php',
         'OCA\\Settings\\Sections\\Admin\\Groupware' => __DIR__ . '/..' . '/../lib/Sections/Admin/Groupware.php',

+ 0 - 29
apps/settings/lib/Search/SectionResult.php

@@ -1,29 +0,0 @@
-<?php
-
-declare(strict_types=1);
-/**
- * @copyright Copyright (c) 2020 Joas Schilling <coding@schilljs.com>
- *
- * @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/>.
- *
- */
-
-namespace OCA\Settings\Search;
-
-use OCP\Search\ASearchResultEntry;
-
-class SectionResult extends ASearchResultEntry {
-}

+ 22 - 8
apps/settings/lib/Search/SectionSearch.php

@@ -30,7 +30,7 @@ use OCP\IUser;
 use OCP\Search\IProvider;
 use OCP\Search\ISearchQuery;
 use OCP\Search\SearchResult;
-use OCP\Settings\IIconSection;
+use OCP\Search\SearchResultEntry;
 use OCP\Settings\ISection;
 use OCP\Settings\IManager;
 
@@ -38,10 +38,13 @@ class SectionSearch implements IProvider {
 
 	/** @var IManager */
 	protected $settingsManager;
+
 	/** @var IGroupManager */
 	protected $groupManager;
+
 	/** @var IURLGenerator */
 	protected $urlGenerator;
+
 	/** @var IL10N */
 	protected $l;
 
@@ -69,6 +72,13 @@ class SectionSearch implements IProvider {
 		return $this->l->t('Settings');
 	}
 
+	/**
+	 * @inheritDoc
+	 */
+	public function getOrder(): int {
+		return 20;
+	}
+
 	/**
 	 * @inheritDoc
 	 */
@@ -115,16 +125,20 @@ class SectionSearch implements IProvider {
 					continue;
 				}
 
-				$iconUrl = '';
-				if ($section instanceof IIconSection) {
-					$iconUrl = $section->getIcon();
-				}
+				/**
+				 * We can't use the icon URL at the moment as they don't invert correctly for dark theme
+				 * $iconUrl = '';
+				 * if ($section instanceof IIconSection) {
+				 * $iconUrl = $section->getIcon();
+				 * }
+				 */
 
-				$result[] = new SectionResult(
-					$iconUrl,
+				$result[] = new SearchResultEntry(
+					'',
 					$section->getName(),
 					$subline,
-					$this->urlGenerator->linkToRouteAbsolute($routeName, ['section' => $section->getID()])
+					$this->urlGenerator->linkToRouteAbsolute($routeName, ['section' => $section->getID()]),
+					'icon-settings'
 				);
 			}
 		}

File diff suppressed because it is too large
+ 0 - 0
core/js/dist/files_client.js


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/files_client.js.map


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/files_fileinfo.js


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/files_fileinfo.js.map


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/files_iedavclient.js


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/files_iedavclient.js.map


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/install.js


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/install.js.map


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/login.js


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/login.js.map


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/main.js


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/main.js.map


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/maintenance.js


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/maintenance.js.map


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/recommendedapps.js


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/recommendedapps.js.map


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/unified-search.js


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/unified-search.js.map


+ 30 - 5
core/src/components/UnifiedSearch/SearchResult.vue

@@ -2,22 +2,28 @@
 	<a :href="resourceUrl || '#'"
 		class="unified-search__result"
 		:class="{
-			'unified-search__result--focused': focused
+			'unified-search__result--focused': focused,
 		}"
 		@click="reEmitEvent"
 		@focus="reEmitEvent">
+
 		<!-- Icon describing the result -->
 		<div class="unified-search__result-icon"
 			:class="{
 				'unified-search__result-icon--rounded': rounded,
 				'unified-search__result-icon--no-preview': !hasValidThumbnail && !loaded,
 				'unified-search__result-icon--with-thumbnail': hasValidThumbnail && loaded,
-				[iconClass]: true
+				[icon]: !loaded && !isIconUrl,
+			}"
+			:style="{
+				backgroundImage: isIconUrl ? `url(${icon})` : '',
 			}"
 			role="img">
+
 			<img v-if="hasValidThumbnail"
+				v-show="loaded"
 				:src="thumbnailUrl"
-				:alt="t('core', 'Thumbnail for {result}', {result: title})"
+				alt=""
 				@error="onError"
 				@load="onLoad">
 		</div>
@@ -59,7 +65,7 @@ export default {
 			type: String,
 			default: null,
 		},
-		iconClass: {
+		icon: {
 			type: String,
 			default: '',
 		},
@@ -90,6 +96,24 @@ export default {
 		}
 	},
 
+	computed: {
+		isIconUrl() {
+			// If we're facing an absolute url
+			if (this.icon.startsWith('/')) {
+				return true
+			}
+
+			// Otherwise, let's check if this is a valid url
+			try {
+				// eslint-disable-next-line no-new
+				new URL(this.icon)
+			} catch {
+				return false
+			}
+			return true
+		},
+	},
+
 	watch: {
 		// Make sure to reset state on change even when vue recycle the component
 		thumbnailUrl() {
@@ -148,6 +172,7 @@ $margin: 10px;
 		width: $clickable-area;
 		height: $clickable-area;
 		border-radius: var(--border-radius);
+		background-repeat: no-repeat;
 		background-position: center center;
 		background-size: 32px;
 		&--rounded {
@@ -195,7 +220,7 @@ $margin: 10px;
 	&-line-two {
 		overflow: hidden;
 		flex: 1 1 100%;
-		margin: 0;
+		margin: 1px 0;
 		white-space: nowrap;
 		text-overflow: ellipsis;
 		// Use the same color as the `a`

+ 68 - 0
core/src/components/UnifiedSearch/SearchResultPlaceholder.vue

@@ -0,0 +1,68 @@
+<template>
+	<svg
+		class="unified-search__result-placeholder"
+		xmlns="http://www.w3.org/2000/svg"
+		fill="url(#unified-search__result-placeholder-gradient)">
+		<defs>
+			<linearGradient id="unified-search__result-placeholder-gradient">
+				<stop offset="0%" stop-color="#ededed"><animate attributeName="stop-color"
+					values="#ededed; #ededed; #cccccc; #cccccc; #ededed"
+					dur="2s"
+					repeatCount="indefinite" /></stop>
+				<stop offset="100%" stop-color="#cccccc"><animate attributeName="stop-color"
+					values="#cccccc; #ededed; #ededed; #cccccc; #cccccc"
+					dur="2s"
+					repeatCount="indefinite" /></stop>
+			</linearGradient>
+		</defs>
+		<rect class="unified-search__result-placeholder-icon" />
+		<rect class="unified-search__result-placeholder-line-one" />
+		<rect class="unified-search__result-placeholder-line-two" :style="{width: `calc(${randWidth}%)`}" />
+	</svg>
+</template>
+
+<script>
+export default {
+	name: 'SearchResultPlaceholder',
+
+	data() {
+		return {
+			randWidth: Math.floor(Math.random() * 20) + 30,
+		}
+	},
+}
+</script>
+
+<style lang="scss" scoped>
+$clickable-area: 44px;
+$margin: 10px;
+
+.unified-search__result-placeholder {
+	width: calc(100% - 2 * #{$margin});
+	height: $clickable-area;
+	margin: $margin;
+
+	&-icon {
+		width: $clickable-area;
+		height: $clickable-area;
+		rx: var(--border-radius);
+		ry: var(--border-radius);
+	}
+
+	&-line-one,
+	&-line-two {
+		width: calc(100% - #{$margin + $clickable-area});
+		height: 1em;
+		x: $margin + $clickable-area;
+	}
+
+	&-line-one {
+		y: 5px;
+	}
+
+	&-line-two {
+		y: 25px;
+	}
+}
+
+</style>

+ 27 - 1
core/src/services/UnifiedSearchService.js

@@ -24,15 +24,18 @@ import { loadState } from '@nextcloud/initial-state'
 import axios from '@nextcloud/axios'
 
 export const defaultLimit = loadState('unified-search', 'limit-default')
+export const activeApp = loadState('core', 'active-app')
 
 /**
  * Get the list of available search providers
+ *
+ * @returns {Array}
  */
 export async function getTypes() {
 	try {
 		const { data } = await axios.get(generateUrl('/search/providers'))
 		if (Array.isArray(data) && data.length > 0) {
-			return data
+			return sortProviders(data)
 		}
 	} catch (error) {
 		console.error(error)
@@ -40,6 +43,29 @@ export async function getTypes() {
 	return []
 }
 
+/**
+ * Sort the providers by the current active app
+ *
+ * @param {Array} providers the providers list
+ * @returns {Array}
+ */
+export function sortProviders(providers) {
+	providers.sort((a, b) => {
+		if (a.id.startsWith(activeApp) && b.id.startsWith(activeApp)) {
+			return a.order - b.order
+		}
+
+		if (a.id.startsWith(activeApp)) {
+			return -1
+		}
+		if (b.id.startsWith(activeApp)) {
+			return 1
+		}
+		return 0
+	})
+	return providers
+}
+
 /**
  * Get the list of available search providers
  *

+ 59 - 14
core/src/views/UnifiedSearch.vue

@@ -27,7 +27,7 @@
 		@close="onClose">
 		<!-- Header icon -->
 		<template #trigger>
-			<span class="icon-search-white" />
+			<Magnify class="unified-search__trigger" :size="20" fill-color="var(--color-primary-text)" />
 		</template>
 
 		<!-- Search input -->
@@ -36,17 +36,20 @@
 				v-model="query"
 				class="unified-search__input"
 				type="search"
-				:placeholder="t('core', 'Search for {types} …', { types: typesNames.join(', ') })"
+				:placeholder="t('core', 'Search {types} …', { types: typesNames.join(', ').toLowerCase() })"
 				@input="onInputDebounced"
 				@keypress.enter.prevent.stop="onInputEnter">
 		</div>
 
-		<EmptyContent v-if="isLoading" icon="icon-loading">
-			{{ t('core', 'Searching …') }}
-		</EmptyContent>
+		<template v-if="!hasResults">
+			<!-- Loading placeholders -->
+			<ul v-if="isLoading">
+				<li v-for="placeholder in [1, 2, 3]" :key="placeholder">
+					<SearchResultPlaceholder />
+				</li>
+			</ul>
 
-		<template v-else-if="!hasResults">
-			<EmptyContent v-if="isValidQuery && isDoneSearching" icon="icon-search">
+			<EmptyContent v-else-if="isValidQuery && isDoneSearching" icon="icon-search">
 				{{ t('core', 'No results for {query}', {query}) }}
 			</EmptyContent>
 
@@ -64,7 +67,7 @@
 
 		<!-- Grouped search results -->
 		<template v-else>
-			<ul v-for="(list, type, typesIndex) in results"
+			<ul v-for="(list, type, typesIndex) in orderedResults"
 				:key="type"
 				class="unified-search__results"
 				:class="`unified-search__results-${type}`"
@@ -94,13 +97,14 @@
 </template>
 
 <script>
-import { getTypes, search, defaultLimit } from '../services/UnifiedSearchService'
+import { getTypes, search, defaultLimit, activeApp } from '../services/UnifiedSearchService'
 import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent'
-
+import Magnify from 'vue-material-design-icons/Magnify'
 import debounce from 'debounce'
 
 import HeaderMenu from '../components/HeaderMenu'
 import SearchResult from '../components/UnifiedSearch/SearchResult'
+import SearchResultPlaceholder from '../components/UnifiedSearch/SearchResultPlaceholder'
 
 const minSearchLength = 2
 
@@ -110,7 +114,9 @@ export default {
 	components: {
 		EmptyContent,
 		HeaderMenu,
+		Magnify,
 		SearchResult,
+		SearchResultPlaceholder,
 	},
 
 	data() {
@@ -126,6 +132,7 @@ export default {
 			query: '',
 			focused: null,
 
+			activeApp,
 			defaultLimit,
 			minSearchLength,
 
@@ -155,6 +162,32 @@ export default {
 			return Object.keys(this.results).length !== 0
 		},
 
+		/**
+		 * Order results by putting the active app first
+		 * @returns {Object}
+		 */
+		orderedResults() {
+			const ordered = {}
+			Object.keys(this.results)
+				.sort((a, b) => {
+					if (a.startsWith(activeApp) && b.startsWith(activeApp)) {
+						return this.typesMap[a].order - this.typesMap[b].order
+					}
+					if (a.startsWith(activeApp)) {
+						return -1
+					}
+					if (b.startsWith(activeApp)) {
+						return 1
+					}
+					return 0
+				})
+				.forEach(type => {
+					ordered[type] = this.results[type]
+				})
+
+			return ordered
+		},
+
 		/**
 		 * Is the current search too short
 		 * @returns {boolean}
@@ -176,7 +209,7 @@ export default {
 		 * @returns {boolean}
 		 */
 		isDoneSearching() {
-			return Object.values(this.reached).indexOf(false) === -1
+			return Object.values(this.reached).every(state => state === false)
 		},
 
 		/**
@@ -184,7 +217,7 @@ export default {
 		 * @returns {boolean}
 		 */
 		isLoading() {
-			return Object.values(this.loading).indexOf(true) !== -1
+			return Object.values(this.loading).some(state => state === true)
 		},
 	},
 
@@ -465,6 +498,11 @@ $margin: 10px;
 $input-padding: 6px;
 
 .unified-search {
+	&__trigger {
+		width: 20px;
+		height: 20px;
+	}
+
 	&__input-wrapper {
 		position: sticky;
 		// above search results
@@ -479,7 +517,14 @@ $input-padding: 6px;
 		height: 34px;
 		margin: $margin;
 		padding: $input-padding;
-		text-overflow: ellipsis;
+		&,
+		&[placeholder],
+		&::placeholder {
+			overflow: hidden;
+			text-overflow:ellipsis;
+			white-space: nowrap;
+		}
+
 	}
 
 	&__results {
@@ -488,7 +533,7 @@ $input-padding: 6px;
 			margin: $margin;
 			margin-left: $margin + $input-padding;
 			content: attr(aria-label);
-			color: var(--color-primary);
+			color: var(--color-primary-element);
 		}
 	}
 

+ 1 - 1
lib/composer/composer/autoload_classmap.php

@@ -439,13 +439,13 @@ return array(
     'OCP\\Route\\IRouter' => $baseDir . '/lib/public/Route/IRouter.php',
     'OCP\\SabrePluginEvent' => $baseDir . '/lib/public/SabrePluginEvent.php',
     'OCP\\SabrePluginException' => $baseDir . '/lib/public/SabrePluginException.php',
-    'OCP\\Search\\ASearchResultEntry' => $baseDir . '/lib/public/Search/ASearchResultEntry.php',
     'OCP\\Search\\IProvider' => $baseDir . '/lib/public/Search/IProvider.php',
     'OCP\\Search\\ISearchQuery' => $baseDir . '/lib/public/Search/ISearchQuery.php',
     'OCP\\Search\\PagedProvider' => $baseDir . '/lib/public/Search/PagedProvider.php',
     'OCP\\Search\\Provider' => $baseDir . '/lib/public/Search/Provider.php',
     'OCP\\Search\\Result' => $baseDir . '/lib/public/Search/Result.php',
     'OCP\\Search\\SearchResult' => $baseDir . '/lib/public/Search/SearchResult.php',
+    'OCP\\Search\\SearchResultEntry' => $baseDir . '/lib/public/Search/SearchResultEntry.php',
     'OCP\\Security\\CSP\\AddContentSecurityPolicyEvent' => $baseDir . '/lib/public/Security/CSP/AddContentSecurityPolicyEvent.php',
     'OCP\\Security\\Events\\GenerateSecurePasswordEvent' => $baseDir . '/lib/public/Security/Events/GenerateSecurePasswordEvent.php',
     'OCP\\Security\\Events\\ValidatePasswordPolicyEvent' => $baseDir . '/lib/public/Security/Events/ValidatePasswordPolicyEvent.php',

+ 1 - 1
lib/composer/composer/autoload_static.php

@@ -468,13 +468,13 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
         'OCP\\Route\\IRouter' => __DIR__ . '/../../..' . '/lib/public/Route/IRouter.php',
         'OCP\\SabrePluginEvent' => __DIR__ . '/../../..' . '/lib/public/SabrePluginEvent.php',
         'OCP\\SabrePluginException' => __DIR__ . '/../../..' . '/lib/public/SabrePluginException.php',
-        'OCP\\Search\\ASearchResultEntry' => __DIR__ . '/../../..' . '/lib/public/Search/ASearchResultEntry.php',
         'OCP\\Search\\IProvider' => __DIR__ . '/../../..' . '/lib/public/Search/IProvider.php',
         'OCP\\Search\\ISearchQuery' => __DIR__ . '/../../..' . '/lib/public/Search/ISearchQuery.php',
         'OCP\\Search\\PagedProvider' => __DIR__ . '/../../..' . '/lib/public/Search/PagedProvider.php',
         'OCP\\Search\\Provider' => __DIR__ . '/../../..' . '/lib/public/Search/Provider.php',
         'OCP\\Search\\Result' => __DIR__ . '/../../..' . '/lib/public/Search/Result.php',
         'OCP\\Search\\SearchResult' => __DIR__ . '/../../..' . '/lib/public/Search/SearchResult.php',
+        'OCP\\Search\\SearchResultEntry' => __DIR__ . '/../../..' . '/lib/public/Search/SearchResultEntry.php',
         'OCP\\Security\\CSP\\AddContentSecurityPolicyEvent' => __DIR__ . '/../../..' . '/lib/public/Security/CSP/AddContentSecurityPolicyEvent.php',
         'OCP\\Security\\Events\\GenerateSecurePasswordEvent' => __DIR__ . '/../../..' . '/lib/public/Security/Events/GenerateSecurePasswordEvent.php',
         'OCP\\Security\\Events\\ValidatePasswordPolicyEvent' => __DIR__ . '/../../..' . '/lib/public/Security/Events/ValidatePasswordPolicyEvent.php',

+ 4 - 17
lib/private/NavigationManager.php

@@ -79,12 +79,7 @@ class NavigationManager implements INavigationManager {
 	}
 
 	/**
-	 * Creates a new navigation entry
-	 *
-	 * @param array|\Closure $entry Array containing: id, name, order, icon and href key
-	 *					The use of a closure is preferred, because it will avoid
-	 * 					loading the routing of your app, unless required.
-	 * @return void
+	 * @inheritDoc
 	 */
 	public function add($entry) {
 		if ($entry instanceof \Closure) {
@@ -106,10 +101,7 @@ class NavigationManager implements INavigationManager {
 	}
 
 	/**
-	 * Get a list of navigation entries
-	 *
-	 * @param string $type type of the navigation entries
-	 * @return array
+	 * @inheritDoc
 	 */
 	public function getAll(string $type = 'link'): array {
 		$this->init();
@@ -171,19 +163,14 @@ class NavigationManager implements INavigationManager {
 	}
 
 	/**
-	 * Sets the current navigation entry of the currently running app
-	 * @param string $id of the app entry to activate (from added $entry)
+	 * @inheritDoc
 	 */
 	public function setActiveEntry($id) {
 		$this->activeEntry = $id;
 	}
 
 	/**
-	 * gets the active Menu entry
-	 * @return string id or empty string
-	 *
-	 * This function returns the id of the active navigation entry (set by
-	 * setActiveEntry
+	 * @inheritDoc
 	 */
 	public function getActiveEntry() {
 		return $this->activeEntry;

+ 1 - 1
lib/private/Search/Provider/File.php

@@ -39,7 +39,7 @@ class File extends \OCP\Search\Provider {
 	/**
 	 * Search for files and folders matching the given query
 	 * @param string $query
-	 * @return \OCP\Search\Result
+	 * @return \OCP\Search\Result[]
 	 * @deprecated 20.0.0
 	 */
 	public function search($query) {

+ 25 - 2
lib/private/Search/Result/File.php

@@ -28,6 +28,8 @@ namespace OC\Search\Result;
 
 use OCP\Files\FileInfo;
 use OCP\Files\Folder;
+use OCP\IPreview;
+use OCP\IUserSession;
 
 /**
  * A found file
@@ -78,6 +80,14 @@ class File extends \OCP\Search\Result {
 	 */
 	public $permissions;
 
+	/**
+	 * Has a preview
+	 *
+	 * @var string
+	 * @deprecated 20.0.0
+	 */
+	public $has_preview;
+
 	/**
 	 * Create a new file search result
 	 * @param FileInfo $data file data given by provider
@@ -101,6 +111,7 @@ class File extends \OCP\Search\Result {
 		$this->size = $data->getSize();
 		$this->modified = $data->getMtime();
 		$this->mime_type = $data->getMimetype();
+		$this->has_preview = $this->hasPreview($data);
 	}
 
 	/**
@@ -118,9 +129,21 @@ class File extends \OCP\Search\Result {
 	 */
 	protected function getRelativePath($path) {
 		if (!isset(self::$userFolderCache)) {
-			$user = \OC::$server->getUserSession()->getUser()->getUID();
-			self::$userFolderCache = \OC::$server->getUserFolder($user);
+			$userSession = \OC::$server->get(IUserSession::class);
+			$userID = $userSession->getUser()->getUID();
+			self::$userFolderCache = \OC::$server->getUserFolder($userID);
 		}
 		return self::$userFolderCache->getRelativePath($path);
 	}
+
+	/**
+	 * Is the preview available
+	 * @param FileInfo $data
+	 * @return bool
+	 * @deprecated 20.0.0
+	 */
+	protected function hasPreview($data) {
+		$previewManager = \OC::$server->get(IPreview::class);
+		return $previewManager->isAvailable($data);
+	}
 }

+ 15 - 6
lib/private/Search/SearchComposer.php

@@ -107,22 +107,31 @@ class SearchComposer {
 
 	/**
 	 * Get a list of all provider IDs & Names for the consecutive calls to `search`
+	 * Sort the list by the order property
 	 *
 	 * @return array
 	 */
 	public function getProviders(): array {
 		$this->loadLazyProviders();
 
-		/**
-		 * Return an array with the IDs, but strip the associative keys
-		 */
-		return array_values(
+		$providers = array_values(
 			array_map(function (IProvider $provider) {
 				return [
 					'id' => $provider->getId(),
-					'name' => $provider->getName()
+					'name' => $provider->getName(),
+					'order' => $provider->getOrder()
 				];
-			}, $this->providers));
+			}, $this->providers)
+		);
+
+		usort($providers, function ($provider1, $provider2) {
+			return $provider1['order'] <=> $provider2['order'];
+		});
+
+		/**
+		 * Return an array with the IDs, but strip the associative keys
+		 */
+		return $providers;
 	}
 
 	/**

+ 13 - 3
lib/private/TemplateLayout.php

@@ -52,6 +52,7 @@ use OCP\AppFramework\Http\TemplateResponse;
 use OCP\Defaults;
 use OCP\IConfig;
 use OCP\IInitialStateService;
+use OCP\INavigationManager;
 use OCP\Support\Subscription\IRegistry;
 use OCP\Util;
 
@@ -64,6 +65,9 @@ class TemplateLayout extends \OC_Template {
 	/** @var IInitialStateService */
 	private $initialState;
 
+	/** @var INavigationManager */
+	private $navigationManager;
+
 	/**
 	 * @param string $renderAs
 	 * @param string $appId application id
@@ -74,7 +78,7 @@ class TemplateLayout extends \OC_Template {
 		$this->config = \OC::$server->get(IConfig::class);
 
 		/** @var IInitialStateService */
-		$this->initialState = \OC::$server->get(InitialStateService::class);
+		$this->initialState = \OC::$server->get(IInitialStateService::class);
 
 		if (Util::isIE()) {
 			Util::addStyle('ie');
@@ -82,6 +86,9 @@ class TemplateLayout extends \OC_Template {
 
 		// Decide which page we show
 		if ($renderAs === TemplateResponse::RENDER_AS_USER) {
+			/** @var INavigationManager */
+			$this->navigationManager = \OC::$server->get(INavigationManager::class);
+
 			parent::__construct('core', 'layout.user');
 			if (in_array(\OC_App::getCurrentApp(), ['settings','admin', 'help']) !== false) {
 				$this->assign('bodyid', 'body-settings');
@@ -89,16 +96,19 @@ class TemplateLayout extends \OC_Template {
 				$this->assign('bodyid', 'body-user');
 			}
 
+			$this->initialState->provideInitialState('core', 'active-app', $this->navigationManager->getActiveEntry());
 			$this->initialState->provideInitialState('unified-search', 'limit-default', SearchQuery::LIMIT_DEFAULT);
 			Util::addScript('dist/unified-search', null, true);
 
 			// Add navigation entry
 			$this->assign('application', '');
 			$this->assign('appid', $appId);
-			$navigation = \OC::$server->getNavigationManager()->getAll();
+
+			$navigation = $this->navigationManager->getAll();
 			$this->assign('navigation', $navigation);
-			$settingsNavigation = \OC::$server->getNavigationManager()->getAll('settings');
+			$settingsNavigation = $this->navigationManager->getAll('settings');
 			$this->assign('settingsnavigation', $settingsNavigation);
+
 			foreach ($navigation as $entry) {
 				if ($entry['active']) {
 					$this->assign('application', $entry['name']);

+ 7 - 0
lib/public/INavigationManager.php

@@ -81,6 +81,13 @@ interface INavigationManager {
 	 */
 	public function setActiveEntry($appId);
 
+	/**
+	 * Get the current navigation entry of the currently running app
+	 * @return string
+	 * @since 20.0.0
+	 */
+	public function getActiveEntry();
+
 	/**
 	 * Get a list of navigation entries
 	 *

+ 10 - 0
lib/public/Search/IProvider.php

@@ -64,6 +64,16 @@ interface IProvider {
 	 */
 	public function getName(): string;
 
+	/**
+	 * Get the search provider order
+	 * The lower the int, the higher it will be sorted (0 will be before 10)
+	 *
+	 * @return int
+	 *
+	 * @since 20.0.0
+	 */
+	public function getOrder(): int;
+
 	/**
 	 * Find matching search entries in an app
 	 *

+ 4 - 4
lib/public/Search/SearchResult.php

@@ -38,7 +38,7 @@ final class SearchResult implements JsonSerializable {
 	/** @var bool */
 	private $isPaginated;
 
-	/** @var ASearchResultEntry[] */
+	/** @var SearchResultEntry[] */
 	private $entries;
 
 	/** @var int|string|null */
@@ -47,7 +47,7 @@ final class SearchResult implements JsonSerializable {
 	/**
 	 * @param string $name the translated name of the result section or group, e.g. "Mail"
 	 * @param bool $isPaginated
-	 * @param ASearchResultEntry[] $entries
+	 * @param SearchResultEntry[] $entries
 	 * @param null $cursor
 	 *
 	 * @since 20.0.0
@@ -63,7 +63,7 @@ final class SearchResult implements JsonSerializable {
 	}
 
 	/**
-	 * @param ASearchResultEntry[] $entries
+	 * @param SearchResultEntry[] $entries
 	 *
 	 * @return static
 	 *
@@ -78,7 +78,7 @@ final class SearchResult implements JsonSerializable {
 	}
 
 	/**
-	 * @param ASearchResultEntry[] $entries
+	 * @param SearchResultEntry[] $entries
 	 * @param int|string $cursor
 	 *
 	 * @return static

+ 7 - 7
lib/public/Search/ASearchResultEntry.php → lib/public/Search/SearchResultEntry.php

@@ -34,7 +34,7 @@ use JsonSerializable;
  * The app providing the results has to extend this class for customization. In
  * most cases apps do not have to add any additional code.
  *
- * @example ``class MailResultEntry extends ASearchResultEntry {}`
+ * @example ``class MailResultEntry extends SearchResultEntry {}`
  *
  * This approach was chosen over a final class as it allows Nextcloud to later
  * add new optional properties of an entry without having to break the usage of
@@ -42,7 +42,7 @@ use JsonSerializable;
  *
  * @since 20.0.0
  */
-abstract class ASearchResultEntry implements JsonSerializable {
+class SearchResultEntry implements JsonSerializable {
 
 	/**
 	 * @var string
@@ -72,7 +72,7 @@ abstract class ASearchResultEntry implements JsonSerializable {
 	 * @var string
 	 * @since 20.0.0
 	 */
-	protected $iconClass;
+	protected $icon;
 
 	/**
 	 * @var boolean
@@ -85,7 +85,7 @@ abstract class ASearchResultEntry implements JsonSerializable {
 	 * @param string $title a main title of the entry
 	 * @param string $subline the secondary line of the entry
 	 * @param string $resourceUrl the URL where the user can find the detail, like a deep link inside the app
-	 * @param string $iconClass the icon class fallback
+	 * @param string $icon the icon class or url to the icon
 	 * @param boolean $rounded is the thumbnail rounded
 	 *
 	 * @since 20.0.0
@@ -94,13 +94,13 @@ abstract class ASearchResultEntry implements JsonSerializable {
 								string $title,
 								string $subline,
 								string $resourceUrl,
-								string $iconClass = '',
+								string $icon = '',
 								bool $rounded = false) {
 		$this->thumbnailUrl = $thumbnailUrl;
 		$this->title = $title;
 		$this->subline = $subline;
 		$this->resourceUrl = $resourceUrl;
-		$this->iconClass = $iconClass;
+		$this->icon = $icon;
 		$this->rounded = $rounded;
 	}
 
@@ -115,7 +115,7 @@ abstract class ASearchResultEntry implements JsonSerializable {
 			'title' => $this->title,
 			'subline' => $this->subline,
 			'resourceUrl' => $this->resourceUrl,
-			'iconClass' => $this->iconClass,
+			'icon' => $this->icon,
 			'rounded' => $this->rounded,
 		];
 	}

+ 5 - 0
package-lock.json

@@ -9888,6 +9888,11 @@
       "resolved": "https://registry.npmjs.org/vue-localstorage/-/vue-localstorage-0.6.2.tgz",
       "integrity": "sha512-29YQVVkIdoS6BZBCJAyu9d0OR0eKSm5gk5OjsLssV1+NM4zJnf9cxhN1AVeXkUHJLqOonECweuaR8PZ2x307dw=="
     },
+    "vue-material-design-icons": {
+      "version": "4.8.0",
+      "resolved": "https://registry.npmjs.org/vue-material-design-icons/-/vue-material-design-icons-4.8.0.tgz",
+      "integrity": "sha512-NNbwK/a14mk92ofBvJa6oBdWi+SO2f27pimoCWziirrbN5Nmt9q0pzELOfvqyy0ncoMJ2BLkd8KfQuXIAhL3Fw=="
+    },
     "vue-multiselect": {
       "version": "2.1.6",
       "resolved": "https://registry.npmjs.org/vue-multiselect/-/vue-multiselect-2.1.6.tgz",

+ 1 - 0
package.json

@@ -79,6 +79,7 @@
     "vue-clipboard2": "^0.3.1",
     "vue-infinite-loading": "^2.4.5",
     "vue-localstorage": "^0.6.2",
+    "vue-material-design-icons": "^4.8.0",
     "vue-multiselect": "^2.1.6",
     "vue-router": "^3.3.4",
     "vuex": "^3.5.1",

+ 3 - 1
webpack.common.js

@@ -81,7 +81,9 @@ module.exports = []
 				{
 					test: /\.vue$/,
 					loader: 'vue-loader',
-					exclude: /node_modules/,
+					exclude: BabelLoaderExcludeNodeModulesExcept([
+						'vue-material-design-icons',
+					]),
 				},
 				{
 					test: /\.js$/,

Some files were not shown because too many files changed in this diff