Merge pull request #22099 from nextcloud/fix/unified-search
This commit is contained in:
commit
a4d511d827
|
@ -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',
|
||||
);
|
||||
|
|
|
@ -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
|
@ -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 {
|
||||
|
|
|
@ -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,
|
|
@ -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 {
|
||||
}
|
|
@ -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'
|
||||
|
|
|
@ -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, $)
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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 {
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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 {
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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 {
|
||||
}
|
|
@ -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']);
|
||||
}
|
||||
|
||||
|
|
|
@ -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']);
|
||||
}
|
||||
|
||||
|
|
|
@ -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']);
|
||||
}
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
"operationprogressbar.js",
|
||||
"recentfilelist.js",
|
||||
"recentplugin.js",
|
||||
"search.js",
|
||||
"semaphore.js",
|
||||
"sidebarpreviewmanager.js",
|
||||
"sidebarpreviewtext.js",
|
||||
|
|
|
@ -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();
|
||||
})();
|
|
@ -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()))
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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 {
|
||||
}
|
|
@ -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
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
})();
|
|
@ -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 = {};
|
|
@ -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,
|
||||
|
|
|
@ -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
|
|
@ -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`
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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']);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
];
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -81,7 +81,9 @@ module.exports = []
|
|||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader',
|
||||
exclude: /node_modules/,
|
||||
exclude: BabelLoaderExcludeNodeModulesExcept([
|
||||
'vue-material-design-icons',
|
||||
]),
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
|
|
Loading…
Reference in New Issue