Merge pull request #22099 from nextcloud/fix/unified-search

This commit is contained in:
John Molakvoæ 2020-08-05 14:17:42 +02:00 committed by GitHub
commit a4d511d827
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
74 changed files with 437 additions and 1458 deletions

View File

@ -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',
);

View File

@ -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 one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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 {

View File

@ -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,

View File

@ -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 {
}

View File

@ -7,7 +7,6 @@ import './commentstabview'
import './commentsmodifymenu'
import './filesplugin'
import './activitytabviewplugin'
import './search'
import './vendor/Caret.js/dist/jquery.caret.min'
import './vendor/At.js/dist/js/jquery.atwho.min'

View File

@ -1,137 +0,0 @@
/* eslint-disable */
/*
* Copyright (c) 2014
*
* This file is licensed under the Affero General Public License version 3
* or later.
*
* See the COPYING-README file.
*
*/
(function(OC, OCA, $) {
'use strict'
/**
* Construct a new FileActions instance
* @constructs Files
*/
var Comment = function() {
this.initialize()
}
Comment.prototype = {
fileList: null,
/**
* Initialize the file search
*/
initialize: function() {
var self = this
this.fileAppLoaded = function() {
return !!OCA.Files && !!OCA.Files.App
}
function inFileList($row, result) {
return false
if (!self.fileAppLoaded()) {
return false
}
var dir = self.fileList.getCurrentDirectory().replace(/\/+$/, '')
var resultDir = OC.dirname(result.path)
return dir === resultDir && self.fileList.inList(result.name)
}
function hideNoFilterResults() {
var $nofilterresults = $('.nofilterresults')
if (!$nofilterresults.hasClass('hidden')) {
$nofilterresults.addClass('hidden')
}
}
/**
*
* @param {jQuery} $row
* @param {Object} result
* @param {int} result.id
* @param {string} result.comment
* @param {string} result.authorId
* @param {string} result.authorName
* @param {string} result.link
* @param {string} result.fileName
* @param {string} result.path
* @returns {*}
*/
this.renderCommentResult = function($row, result) {
if (inFileList($row, result)) {
return null
}
hideNoFilterResults()
/* render preview icon, show path beneath filename,
show size and last modified date on the right */
this.updateLegacyMimetype(result)
var $pathDiv = $('<div>').addClass('path').text(result.path)
var $avatar = $('<div>')
$avatar.addClass('avatar')
.css('display', 'inline-block')
.css('vertical-align', 'middle')
.css('margin', '0 5px 2px 3px')
if (result.authorName) {
$avatar.avatar(result.authorId, 21, undefined, false, undefined, result.authorName)
} else {
$avatar.avatar(result.authorId, 21)
}
$row.find('td.info div.name').after($pathDiv).text(result.comment).prepend($('<span>').addClass('path').css('margin-right', '5px').text(result.authorName)).prepend($avatar)
$row.find('td.result a').attr('href', result.link)
$row.find('td.icon')
.css('background-image', 'url(' + OC.imagePath('core', 'actions/comment') + ')')
.css('opacity', '.4')
var dir = OC.dirname(result.path)
// "result.path" does not include a leading "/", so "OC.dirname"
// returns the path itself for files or folders in the root.
if (dir === result.path) {
dir = '/'
}
$row.find('td.info a').attr('href',
OC.generateUrl('/apps/files/?dir={dir}&scrollto={scrollto}', { dir: dir, scrollto: result.fileName })
)
return $row
}
this.handleCommentClick = function($row, result, event) {
if (self.fileAppLoaded() && self.fileList.id === 'files') {
self.fileList.changeDirectory(OC.dirname(result.path))
self.fileList.scrollTo(result.name)
return false
} else {
return true
}
}
this.updateLegacyMimetype = function(result) {
// backward compatibility:
if (!result.mime && result.mime_type) {
result.mime = result.mime_type
}
}
this.setFileList = function(fileList) {
this.fileList = fileList
}
OC.Plugins.register('OCA.Search.Core', this)
},
attach: function(search) {
search.setRenderer('comment', this.renderCommentResult.bind(this))
search.setHandler('comment', this.handleCommentClick.bind(this))
}
}
OCA.Search.comment = new Comment()
})(OC, OCA, $)

View File

@ -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',

View File

@ -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',

View File

@ -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(

View File

@ -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 {
}

View File

@ -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(

View File

@ -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 {
}

View File

@ -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(

View File

@ -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 {
}

View File

@ -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']);
}

View File

@ -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']);
}

View File

@ -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']);
}

View File

@ -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',

View File

@ -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',

View File

@ -383,8 +383,6 @@
}
});
this.updateSearch();
this.$fileList.on('click','td.filename>a.name, td.filesize, td.date', _.bind(this._onClickFile, this));
this.$fileList.on("droppedOnFavorites", function (event, file) {
@ -1126,7 +1124,6 @@
if ($targetDir !== undefined && e.which === 1) {
e.preventDefault();
this.changeDirectory($targetDir, true, true);
this.updateSearch();
}
},
@ -3260,17 +3257,7 @@
getFilter:function(filter) {
return this._filter;
},
/**
* update the search object to use this filelist when filtering
*/
updateSearch:function() {
if (OCA.Search.files) {
OCA.Search.files.setFileList(this);
}
if (OC.Search) {
OC.Search.clear();
}
},
/**
* Update UI based on the current selection
*/

View File

@ -24,7 +24,6 @@
"operationprogressbar.js",
"recentfilelist.js",
"recentplugin.js",
"search.js",
"semaphore.js",
"sidebarpreviewmanager.js",
"sidebarpreviewtext.js",

View File

@ -1,176 +0,0 @@
/*
* Copyright (c) 2014
*
* This file is licensed under the Affero General Public License version 3
* or later.
*
* See the COPYING-README file.
*
*/
(function() {
/**
* Construct a new FileActions instance
* @constructs Files
*/
var Files = function() {
this.initialize();
};
/**
* @memberof OCA.Search
*/
Files.prototype = {
fileList: null,
/**
* Initialize the file search
*/
initialize: function() {
var self = this;
this.fileAppLoaded = function() {
return !!OCA.Files && !!OCA.Files.App;
};
function inFileList($row, result) {
if (! self.fileAppLoaded()) {
return false;
}
var dir = self.fileList.getCurrentDirectory().replace(/\/+$/,'');
var resultDir = OC.dirname(result.path);
return dir === resultDir && self.fileList.inList(result.name);
}
function updateLegacyMimetype(result) {
// backward compatibility:
if (!result.mime && result.mime_type) {
result.mime = result.mime_type;
}
}
function hideNoFilterResults() {
var $nofilterresults = $('.nofilterresults');
if ( ! $nofilterresults.hasClass('hidden') ) {
$nofilterresults.addClass('hidden');
}
}
this.renderFolderResult = function($row, result) {
if (inFileList($row, result)) {
return null;
}
hideNoFilterResults();
/*render folder icon, show path beneath filename,
show size and last modified date on the right */
this.updateLegacyMimetype(result);
var $pathDiv = $('<div class="path"></div>').text(result.path.substr(1, result.path.lastIndexOf("/")));
$row.find('td.info div.name').after($pathDiv).text(result.name);
$row.find('td.result a').attr('href', result.link);
$row.find('td.icon').css('background-image', 'url(' + OC.MimeType.getIconUrl(result.mime) + ')');
return $row;
};
this.renderFileResult = function($row, result) {
if (inFileList($row, result)) {
return null;
}
hideNoFilterResults();
/*render preview icon, show path beneath filename,
show size and last modified date on the right */
this.updateLegacyMimetype(result);
var $pathDiv = $('<div class="path"></div>').text(result.path.substr(1, result.path.lastIndexOf("/")));
$row.find('td.info div.name').after($pathDiv).text(result.name);
$row.find('td.result a').attr('href', result.link);
if (self.fileAppLoaded()) {
self.fileList.lazyLoadPreview({
path: result.path,
mime: result.mime,
callback: function (url) {
$row.find('td.icon').css('background-image', 'url(' + url + ')');
}
});
} else {
// FIXME how to get mime icon if not in files app
var mimeicon = result.mime.replace('/', '-');
$row.find('td.icon').css('background-image', 'url(' + OC.MimeType.getIconUrl(result.mime) + ')');
var dir = OC.dirname(result.path);
if (dir === '') {
dir = '/';
}
$row.find('td.info a').attr('href',
OC.generateUrl('/apps/files/?dir={dir}&scrollto={scrollto}', {dir: dir, scrollto: result.name})
);
}
return $row;
};
this.handleFolderClick = function($row, result, event) {
// open folder
if (self.fileAppLoaded() && self.fileList.id === 'files') {
self.fileList.changeDirectory(result.path);
return false;
} else {
return true;
}
};
this.handleFileClick = function($row, result, event) {
if (self.fileAppLoaded() && self.fileList.id === 'files') {
self.fileList.changeDirectory(OC.dirname(result.path));
self.fileList.scrollTo(result.name);
return false;
} else {
return true;
}
};
this.updateLegacyMimetype = function (result) {
// backward compatibility:
if (!result.mime && result.mime_type) {
result.mime = result.mime_type;
}
};
this.setFileList = function (fileList) {
this.fileList = fileList;
};
OC.Plugins.register('OCA.Search.Core', this);
},
attach: function(search) {
var self = this;
search.setFilter('files', function (query) {
if (self.fileAppLoaded()) {
self.fileList.setFilter(query);
if (query.length > 1) {
//search is not started until 500msec have passed
window.setTimeout(function() {
$('.nofilterresults').addClass('hidden');
}, 500);
}
}
});
search.setRenderer('folder', this.renderFolderResult.bind(this));
search.setRenderer('file', this.renderFileResult.bind(this));
search.setRenderer('image', this.renderFileResult.bind(this));
search.setRenderer('audio', this.renderFileResult.bind(this));
search.setHandler('folder', this.handleFolderClick.bind(this));
search.setHandler(['file', 'audio', 'image'], this.handleFileClick.bind(this));
if (self.fileAppLoaded()) {
// hide results when switching directory outside of search results
$('#app-content').delegate('>div', 'changeDirectory', function() {
search.clear();
});
}
}
};
OCA.Search.Files = Files;
OCA.Search.files = new Files();
})();

View File

@ -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()))
);

View File

@ -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);
}
}

View File

@ -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',

View File

@ -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',

View File

@ -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 {
}

View File

@ -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 one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,103 +0,0 @@
/*
* @copyright Copyright (c) 2018 Jan-Christoph Borchardt <hey@jancborchardt.net>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Jan-Christoph Borchardt <hey@jancborchardt.net>
*
* @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/>.
*
*/
#searchresults {
overflow-x: hidden;
text-overflow: ellipsis;
padding-top: 51px; /* table row is 51px height */
box-sizing: border-box;
z-index: 75;
/* account for margin-bottom in files list */
margin-top: -250px;
table {
border-spacing: 0;
padding-left: 41px;
table-layout: fixed;
top: 0;
width: 100%;
}
tr {
&.result {
border-bottom: 1px solid var(--color-border);
* {
cursor: pointer;
}
}
&.template {
display: none;
}
&:hover,
&.current {
background-color: var(--color-primary-light);
}
td {
padding: 5px 9px;
font-style: normal;
vertical-align: middle;
border-bottom: none;
&.icon {
text-align: right;
width: 40px;
height: 40px;
padding: 5px 0;
background-position: right center;
background-repeat: no-repeat;
}
.has-selection:not(.hidden) ~ &.icon {
width: 50px;
padding-left: 41px;
background-size: 32px;
}
}
.name,
.text,
.path {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.text {
white-space: normal;
color: var(--color-primary-text);
}
.path {
opacity: 0.5;
}
.text em {
color: var(--color-primary-text);
font-weight: bold;
opacity: 1;
}
}
.hidden {
display: none;
}
&.filter-empty {
/* remove whitespace on bottom when no search results, to fix layout */
margin-top: 0 !important;
}
.status.summary .info {
margin-left: 100px;
}
}

View File

@ -1,197 +0,0 @@
/**
* @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.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/>.
*
*/
(function() {
'use strict';
/**
* @class Search
* @memberOf OCA
*
* The Search class manages a search
*
* This is a simple method. Register a new search with your function as references.
* The events will forward the search or reset directly
*
* @param {Function} searchCallback the function to run on a query search
* @param {Function} resetCallback the function to run when the user reset the form
*/
var Search = function(searchCallback, resetCallback) {
this.initialize(searchCallback, resetCallback);
};
/**
* @memberof OC
*/
Search.prototype = {
/**
* Initialize the search box
*
* @param {Function} searchCallback the function to run on a query search
* @param {Function} resetCallback the function to run when the user reset the form
*/
initialize: function(searchCallback, resetCallback) {
var self = this;
if (typeof searchCallback !== 'function') {
throw new Error('searchCallback must be a function');
}
if (typeof resetCallback !== 'function') {
throw new Error('resetCallback must be a function');
}
if (!document.getElementById('searchbox')) {
throw new Error('searchBox not available');
}
this.searchCallback = searchCallback;
this.resetCallback = resetCallback;
console.debug('New search handler registered');
/**
* Search
*/
this._search = function(event) {
event.preventDefault();
var query = document.getElementById('searchbox').value;
self.searchCallback(query);
};
/**
* Reset form
*/
this._reset = function(event) {
event.preventDefault();
document.getElementById('searchbox').value = '';
self.resetCallback();
};
/**
* Submit event handler
*/
this._submitHandler = function(event) {
// Avoid form submit
event.preventDefault();
_.debounce(self.search, 500);
}
/**
* keyUp event handler for the escape key
*/
this._keyUpHandler = function(event) {
if (event.defaultPrevented) {
return;
}
var key = event.key || event.keyCode;
if (
document.getElementById('searchbox') === document.activeElement &&
document.getElementById('searchbox').value === ''
) {
if (key === 'Escape' || key === 'Esc' || key === 27) {
document.activeElement.blur(); // remove focus on searchbox
_.debounce(self.reset, 500);
}
}
}
this._searchDebounced = _.debounce(self._search, 500)
this._resetDebounced = _.debounce(self._reset, 500)
/**
* keyDown event handler for the ctrl+F shortcut
*/
this._keyDownHandler = function(event) {
if (event.defaultPrevented) {
return;
}
var key = event.key || event.keyCode;
if (document.getElementById('searchbox') !== document.activeElement) {
if (
(event.ctrlKey || event.metaKey) && // CTRL or mac CMD
!event.shiftKey && // Not SHIFT
(key === 'f' || key === 70) // F
) {
event.preventDefault();
document.getElementById('searchbox').focus();
document.getElementById('searchbox').select();
}
}
}
// Show search
document.getElementById('searchbox').style.display = 'block';
// Register input event
document
.getElementById('searchbox')
.addEventListener('input', this._searchDebounced, true);
document
.querySelector('form.searchbox')
.addEventListener('submit', this._submitHandler, true);
// Register reset
document
.querySelector('form.searchbox')
.addEventListener('reset', this._resetDebounced, true);
// Register esc key shortcut reset if focused
document.addEventListener('keyup', this._keyUpHandler);
// Register ctrl+F key shortcut to focus
document.addEventListener('keydown', this._keyDownHandler);
},
unregister: function() {
// Hide search
document.getElementById('searchbox').style.display = 'none';
// Reset search
document.getElementById('searchbox').value = '';
this.resetCallback();
// Unregister input event
document
.getElementById('searchbox')
.removeEventListener('input', this._searchDebounced, true);
document
.querySelector('form.searchbox')
.removeEventListener('submit', this._submitHandler, true);
// Unregister reset
document
.querySelector('form.searchbox')
.removeEventListener('reset', this._resetDebounced, true);
// Unregister esc key shortcut reset if focused
document.removeEventListener('keyup', this._keyUpHandler);
// Unregister ctrl+F key shortcut to focus
document.removeEventListener('keydown', this._keyDownHandler);
console.debug('Search handler unregistered');
}
};
OCA.Search = Search;
})();

View File

@ -1,455 +0,0 @@
/*
* @copyright Copyright (c) 2014 Jörn Friedrich Dreyer <jfd@owncloud.com>
*
* @author Jörn Friedrich Dreyer <jfd@owncloud.com>
* @author John Molakvoæ <skjnldsv@protonmail.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/>.
*
*/
(function() {
/**
* @class OCA.Search.Core
* @classdesc
*
* The Search class manages a search queries and their results
*
* @param $searchBox container element with existing markup for the #searchbox form
* @param $searchResults container element for results und status message
*/
var Search = function($searchBox, $searchResults) {
this.initialize($searchBox, $searchResults);
};
/**
* @memberof OC
*/
Search.prototype = {
/**
* Initialize the search box
*
* @param $searchBox container element with existing markup for the #searchbox form
* @param $searchResults container element for results und status message
* @private
*/
initialize: function($searchBox, $searchResults) {
var self = this;
/**
* contains closures that are called to filter the current content
*/
var filters = {};
this.setFilter = function(type, filter) {
filters[type] = filter;
};
this.hasFilter = function(type) {
return typeof filters[type] !== 'undefined';
};
this.getFilter = function(type) {
return filters[type];
};
/**
* contains closures that are called to render search results
*/
var renderers = {};
this.setRenderer = function(type, renderer) {
renderers[type] = renderer;
};
this.hasRenderer = function(type) {
return typeof renderers[type] !== 'undefined';
};
this.getRenderer = function(type) {
return renderers[type];
};
/**
* contains closures that are called when a search result has been clicked
*/
var handlers = {};
this.setHandler = function(type, handler) {
handlers[type] = handler;
};
this.hasHandler = function(type) {
return typeof handlers[type] !== 'undefined';
};
this.getHandler = function(type) {
return handlers[type];
};
var currentResult = -1;
var lastQuery = '';
var lastInApps = [];
var lastPage = 0;
var lastSize = 30;
var lastResults = [];
var timeoutID = null;
this.getLastQuery = function() {
return lastQuery;
};
/**
* Do a search query and display the results
* @param {string} query the search query
* @param inApps
* @param page
* @param size
*/
this.search = function(query, inApps, page, size) {
if (query) {
if (typeof page !== 'number') {
page = 1;
}
if (typeof size !== 'number') {
size = 30;
}
if (typeof inApps !== 'object') {
var currentApp = getCurrentApp();
if (currentApp) {
inApps = [currentApp];
} else {
inApps = [];
}
}
// prevent double pages
if (
$searchResults &&
query === lastQuery &&
page === lastPage &&
size === lastSize
) {
return;
}
window.clearTimeout(timeoutID);
timeoutID = window.setTimeout(function() {
lastQuery = query;
lastInApps = inApps;
lastPage = page;
lastSize = size;
//show spinner
$searchResults.removeClass('hidden');
$status.addClass('status emptycontent');
$status.html('<div class="icon-loading"></div><h2>' + t('core', 'Searching other places') + '</h2>');
// do the actual search query
$.getJSON(
OC.generateUrl('core/search'),
{
query: query,
inApps: inApps,
page: page,
size: size
},
function(results) {
lastResults = results;
if (page === 1) {
showResults(results);
} else {
addResults(results);
}
}
);
}, 500);
}
};
//TODO should be a core method, see https://github.com/owncloud/core/issues/12557
function getCurrentApp() {
var content = document.getElementById('content');
if (content) {
var classList = document.getElementById('content').className.split(/\s+/);
for (var i = 0; i < classList.length; i++) {
if (classList[i].indexOf('app-') === 0) {
return classList[i].substr(4);
}
}
}
return false;
}
var $status = $searchResults.find('#status');
// summaryAndStatusHeight is a constant
var summaryAndStatusHeight = 118;
function isStatusOffScreen() {
return (
$searchResults.position() &&
$searchResults.position().top + summaryAndStatusHeight >
window.innerHeight
);
}
function placeStatus() {
if (isStatusOffScreen()) {
$status.addClass('fixed');
} else {
$status.removeClass('fixed');
}
}
function showResults(results) {
lastResults = results;
$searchResults.find('tr.result').remove();
$searchResults.removeClass('hidden');
addResults(results);
}
function addResults(results) {
var $template = $searchResults.find('tr.template');
jQuery.each(results, function(i, result) {
var $row = $template.clone();
$row.removeClass('template');
$row.addClass('result');
$row.data('result', result);
// generic results only have four attributes
$row.find('td.info div.name').text(result.name);
$row.find('td.info a').attr('href', result.link);
/**
* Give plugins the ability to customize the search results. see result.js for examples
*/
if (self.hasRenderer(result.type)) {
$row = self.getRenderer(result.type)($row, result);
} else {
// for backward compatibility add text div
$row.find('td.info div.name').addClass('result');
$row.find('td.result div.name').after('<div class="text"></div>');
$row.find('td.result div.text').text(result.name);
if (OC.search.customResults && OC.search.customResults[result.type]) {
OC.search.customResults[result.type]($row, result);
}
}
if ($row) {
$searchResults.find('tbody').append($row);
}
});
var count = $searchResults.find('tr.result').length;
$status.data('count', count);
if (count === 0) {
$status.addClass('emptycontent').removeClass('status');
$status.html('');
$status.append($('<div>').addClass('icon-search'));
var error = t('core', 'No search results in other folders for {tag}{filter}{endtag}', { filter: lastQuery });
$status.append($('<h2>').html(error.replace('{tag}', '<strong>').replace('{endtag}', '</strong>')));
} else {
$status.removeClass('emptycontent').addClass('status summary');
$status.text(n('core','{count} search result in another folder','{count} search results in other folders', count,{ count: count }));
$status.html('<span class="info">' + n( 'core', '{count} search result in another folder', '{count} search results in other folders', count, { count: count } ) + '</span>');
}
}
function renderCurrent() {
var result = $searchResults.find('tr.result')[currentResult];
if (result) {
var $result = $(result);
var currentOffset = $(window).scrollTop();
$(window).animate(
{
// Scrolling to the top of the new result
scrollTop:
currentOffset +
$result.offset().top -
$result.height() * 2
},
{
duration: 100
}
);
$searchResults.find('tr.result.current').removeClass('current');
$result.addClass('current');
}
}
this.hideResults = function() {
$searchResults.addClass('hidden');
$searchResults.find('tr.result').remove();
lastQuery = false;
};
this.clear = function() {
self.hideResults();
if (self.hasFilter(getCurrentApp())) {
self.getFilter(getCurrentApp())('');
}
$searchBox.val('');
$searchBox.blur();
};
/**
* Event handler for when scrolling the list container.
* This appends/renders the next page of entries when reaching the bottom.
*/
function onScroll() {
if (
$searchResults &&
lastQuery !== false &&
lastResults.length > 0
) {
if ($(window).scrollTop() + $(window).height() > $searchResults.height() - 300) {
self.search(lastQuery, lastInApps, lastPage + 1);
}
placeStatus();
}
}
$(window).on('scroll', _.bind(onScroll, this)); // For desktop browser
$("body").on('scroll', _.bind(onScroll, this)); // For mobile browser
/**
* scrolls the search results to the top
*/
function scrollToResults() {
setTimeout(function() {
if (isStatusOffScreen()) {
var newScrollTop = $(window).prop('scrollHeight') - $searchResults.height();
console.log('scrolling to ' + newScrollTop);
$(window).animate(
{
scrollTop: newScrollTop
},
{
duration: 100,
complete: function() {
scrollToResults();
}
}
);
}
}, 150);
}
$searchBox.keyup(function(event) {
if (event.keyCode === 13) {
//enter
if (currentResult > -1) {
var result = $searchResults.find('tr.result a')[currentResult];
window.location = $(result).attr('href');
}
} else if (event.keyCode === 38) {
//up
if (currentResult > 0) {
currentResult--;
renderCurrent();
}
} else if (event.keyCode === 40) {
//down
if (lastResults.length > currentResult + 1) {
currentResult++;
renderCurrent();
}
}
});
$searchResults.on('click', 'tr.result', function(event) {
var $row = $(this);
var item = $row.data('result');
if (self.hasHandler(item.type)) {
var result = self.getHandler(item.type)($row, item, event);
$searchBox.val('');
if (self.hasFilter(getCurrentApp())) {
self.getFilter(getCurrentApp())('');
}
self.hideResults();
return result;
}
});
$searchResults.on('click', '#status', function(event) {
event.preventDefault();
scrollToResults();
return false;
});
placeStatus();
OC.Plugins.attach('OCA.Search.Core', this);
// Finally use default Search registration
return new OCA.Search(
// Search handler
function(query) {
if (lastQuery !== query) {
currentResult = -1;
if (query.length > 1) {
self.search(query);
} else {
self.hideResults();
}
if (self.hasFilter(getCurrentApp())) {
self.getFilter(getCurrentApp())(query);
}
}
},
// Reset handler
function() {
if ($searchBox.val() === '') {
if (self.hasFilter(getCurrentApp())) {
self.getFilter(getCurrentApp())('');
}
self.hideResults();
}
}
);
}
};
OCA.Search.Core = Search;
})();
window.addEventListener('DOMContentLoaded', function() {
var $searchResults = $('#searchresults');
var $searchBox = $('#searchbox');
if ($searchResults.length > 0 && $searchBox.length > 0) {
$searchResults.addClass('hidden');
$searchResults.html('<table>\n' +
'\t<tbody>\n' +
'\t\t<tr class="template">\n' +
'\t\t\t<td class="icon"></td>\n' +
'\t\t\t<td class="info">\n' +
'\t\t\t\t<a class="link">\n' +
'\t\t\t\t\t<div class="name"></div>\n' +
'\t\t\t\t</a>\n' +
'\t\t\t</td>\n' +
'\t\t</tr>\n' +
'\t</tbody>\n' +
'</table>\n' +
'<div id="status"><span></span></div>');
OC.Search = new OCA.Search.Core(
$searchBox,
$searchResults
);
} else {
// check again later
_.defer(function() {
if ($searchResults.length > 0 && $searchBox.length > 0) {
OC.Search = new OCA.Search.Core(
$searchBox,
$searchResults
);
}
});
}
});
/**
* @deprecated use get/setRenderer() instead
*/
OC.search.customResults = {};
/**
* @deprecated use get/setRenderer() instead
*/
OC.search.resultTypes = {};

View File

@ -103,7 +103,6 @@ import msg from './msg'
import Notification from './notification'
import PasswordConfirmation from './password-confirmation'
import Plugins from './plugins'
import search from './search'
import { theme } from './theme'
import Util from './util'
import { debug } from './debug'
@ -249,7 +248,6 @@ export default {
Notification,
PasswordConfirmation,
Plugins,
search,
theme,
Util,
debug,

View File

@ -1,41 +0,0 @@
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 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/>.
*/
import OC from './index'
/**
* Do a search query and display the results
* @param {string} query the search query
*/
const search = function(query) {
OC.Search.search(query, null, 0, 30)
}
/**
* @namespace OC.search
*/
search.customResults = {}
/**
* @deprecated use get/setFormatter() instead
*/
search.resultTypes = {}
export default search

View File

@ -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`

View File

@ -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>

View File

@ -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
*

View File

@ -32,6 +32,18 @@ __webpack_nonce__ = btoa(getRequestToken())
// eslint-disable-next-line camelcase
__webpack_public_path__ = generateFilePath('core', '', 'js/')
// TODO: remove with nc22
if (!OCA.Search) {
class Search {
constructor() {
console.warn('OCA.Search is deprecated. Please use the unified search API instead')
}
}
OCA.Search = Search
}
Vue.mixin({
methods: {
t,

View File

@ -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);
}
}

View File

@ -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',

View File

@ -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',

View File

@ -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;

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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;
}
/**

View File

@ -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']);

View File

@ -115,9 +115,6 @@ class OC_Template extends \OC\Template\Base {
OC_Util::addTranslations('core', null, true);
if (\OC::$server->getSystemConfig()->getValue('installed', false)) {
OC_Util::addStyle('search', 'results');
OC_Util::addScript('search', 'search', true);
OC_Util::addScript('search', 'searchprovider');
OC_Util::addScript('merged-template-prepend', null, true);
OC_Util::addScript('dist/files_client', null, true);
OC_Util::addScript('dist/files_fileinfo', null, true);

View File

@ -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
*

View File

@ -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
*

View File

@ -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

View File

@ -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
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

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