mirror of
https://github.com/chylex/Nextcloud-News.git
synced 2025-04-26 14:15:48 +02:00
Implement item search
The search result can only link to the feed. Signed-off-by: Benjamin Brahmer <info@b-brahmer.de>
This commit is contained in:
parent
e5f75d7a4c
commit
69681d12cb
@ -7,6 +7,7 @@ The format is mostly based on [Keep a Changelog](https://keepachangelog.com/en/1
|
||||
### Changed
|
||||
- Drop support for Nextcloud 23 (#2077 )
|
||||
- Make the "open" keyboard shortcut work faster (#2080)
|
||||
- Implemented search for articles, results can only link to the feed. (#2075)
|
||||
|
||||
### Fixed
|
||||
- Stop errors from the favicon library over empty values
|
||||
|
@ -23,6 +23,7 @@ use OCA\News\Config\FetcherConfig;
|
||||
use OCA\News\Hooks\UserDeleteHook;
|
||||
use OCA\News\Search\FeedSearchProvider;
|
||||
use OCA\News\Search\FolderSearchProvider;
|
||||
use OCA\News\Search\ItemSearchProvider;
|
||||
|
||||
use OCP\AppFramework\Bootstrap\IBootContext;
|
||||
use OCP\AppFramework\Bootstrap\IBootstrap;
|
||||
@ -82,6 +83,8 @@ class Application extends App implements IBootstrap
|
||||
|
||||
$context->registerSearchProvider(FolderSearchProvider::class);
|
||||
$context->registerSearchProvider(FeedSearchProvider::class);
|
||||
$context->registerSearchProvider(ItemSearchProvider::class);
|
||||
|
||||
|
||||
$context->registerEventListener(BeforeUserDeletedEvent::class, UserDeleteHook::class);
|
||||
|
||||
|
@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
namespace OCA\News\Search;
|
||||
|
||||
use OCA\News\Service\FeedServiceV2;
|
||||
use OCA\News\Service\FolderServiceV2;
|
||||
use OCA\News\AppInfo\Application;
|
||||
use OCP\IL10N;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUser;
|
||||
@ -48,7 +48,7 @@ class FeedSearchProvider implements IProvider
|
||||
|
||||
public function getOrder(string $route, array $routeParameters): int
|
||||
{
|
||||
if ($route === 'news.page.index') {
|
||||
if (strpos($route, Application::NAME . '.') === 0) {
|
||||
// Active app, prefer my results
|
||||
return -1;
|
||||
}
|
||||
@ -67,7 +67,7 @@ class FeedSearchProvider implements IProvider
|
||||
}
|
||||
|
||||
$list[] = new SearchResultEntry(
|
||||
$this->urlGenerator->imagePath('core', 'filetypes/text.svg'),
|
||||
$this->urlGenerator->imagePath('core', 'rss.svg'),
|
||||
$feed->getTitle(),
|
||||
$this->l10n->t('Unread articles') . ': ' . $feed->getUnreadCount(),
|
||||
$this->urlGenerator->linkToRoute('news.page.index') . '#/items/feeds/' . $feed->getId()
|
||||
|
@ -49,9 +49,9 @@ class FolderSearchProvider implements IProvider
|
||||
|
||||
public function getOrder(string $route, array $routeParameters): int
|
||||
{
|
||||
if ($route === 'news.page.index') {
|
||||
if (strpos($route, Application::NAME . '.') === 0) {
|
||||
// Active app, prefer my results
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 55;
|
||||
|
109
lib/Search/ItemSearchProvider.php
Normal file
109
lib/Search/ItemSearchProvider.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\News\Search;
|
||||
|
||||
use OCA\News\Service\ItemServiceV2;
|
||||
use OCA\News\AppInfo\Application;
|
||||
use OCA\News\Db\ListType;
|
||||
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 ItemSearchProvider
|
||||
*
|
||||
* @package OCA\News\Search
|
||||
*/
|
||||
class ItemSearchProvider implements IProvider
|
||||
{
|
||||
/** @var IL10N */
|
||||
private $l10n;
|
||||
|
||||
/** @var IURLGenerator */
|
||||
private $urlGenerator;
|
||||
|
||||
/** @var ItemServiceV2 */
|
||||
private $service;
|
||||
|
||||
public function __construct(IL10N $l10n, IURLGenerator $urlGenerator, ItemServiceV2 $service)
|
||||
{
|
||||
$this->l10n = $l10n;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
return 'news_item';
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->l10n->t('News articles');
|
||||
}
|
||||
|
||||
public function getOrder(string $route, array $routeParameters): int
|
||||
{
|
||||
if (strpos($route, Application::NAME . '.') === 0) {
|
||||
// Active app, prefer my results
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 65;
|
||||
}
|
||||
|
||||
private function stripTruncate(string $string, int $length = 50): string
|
||||
{
|
||||
$string = strip_tags(trim($string));
|
||||
|
||||
if (strlen($string) > $length) {
|
||||
$string = wordwrap($string, $length);
|
||||
$string = explode("\n", $string, 2);
|
||||
$string = $string[0];
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
public function search(IUser $user, ISearchQuery $query): SearchResult
|
||||
{
|
||||
$list = [];
|
||||
$offset = (int) ($query->getCursor() ?? 0);
|
||||
$limit = $query->getLimit();
|
||||
|
||||
$search_result = $this->service->findAllWithFilters(
|
||||
$user->getUID(),
|
||||
ListType::ALL_ITEMS,
|
||||
$limit,
|
||||
$offset,
|
||||
false,
|
||||
[$query->getTerm()]
|
||||
);
|
||||
|
||||
$last = end($search_result);
|
||||
if ($last === false) {
|
||||
return SearchResult::complete(
|
||||
$this->l10n->t('News'),
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
$icon = $this->urlGenerator->imagePath('core', 'filetypes/text.svg');
|
||||
|
||||
foreach ($search_result as $item) {
|
||||
$list[] = new SearchResultEntry(
|
||||
$icon,
|
||||
$item->getTitle(),
|
||||
$this->stripTruncate($item->getBody(), 50),
|
||||
$this->urlGenerator->linkToRoute('news.page.index') . '#/items/feeds/' . $item->getFeedId()
|
||||
);
|
||||
}
|
||||
|
||||
return SearchResult::paginated($this->l10n->t('News'), $list, $last->getId());
|
||||
}
|
||||
}
|
@ -110,7 +110,7 @@ class FeedSearchProviderTest extends TestCase
|
||||
|
||||
$this->generator->expects($this->once())
|
||||
->method('imagePath')
|
||||
->with('core', 'filetypes/text.svg')
|
||||
->with('core', 'rss.svg')
|
||||
->willReturn('folderpath.svg');
|
||||
|
||||
$this->generator->expects($this->once())
|
||||
|
@ -77,7 +77,7 @@ class FolderSearchProviderTest extends TestCase
|
||||
|
||||
public function testGetOrderInternal()
|
||||
{
|
||||
$this->assertSame(-1, $this->class->getOrder('news.page.index', []));
|
||||
$this->assertSame(0, $this->class->getOrder('news.page.index', []));
|
||||
}
|
||||
|
||||
public function testSearch()
|
||||
|
147
tests/Unit/Search/ItemSearchProviderTest.php
Normal file
147
tests/Unit/Search/ItemSearchProviderTest.php
Normal file
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace OCA\News\Search;
|
||||
|
||||
use OCA\News\Db\Item;
|
||||
use OCA\News\Db\ListType;
|
||||
use OCA\News\Service\ItemServiceV2;
|
||||
use OCP\IL10N;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUser;
|
||||
use OCP\Search\ISearchQuery;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ItemSearchProviderTest extends TestCase
|
||||
{
|
||||
|
||||
/**
|
||||
* @var MockObject|ItemServiceV2
|
||||
*/
|
||||
private $itemService;
|
||||
|
||||
/**
|
||||
* @var MockObject|IL10N
|
||||
*/
|
||||
private $l10n;
|
||||
|
||||
/**
|
||||
* @var MockObject|IURLGenerator
|
||||
*/
|
||||
private $generator;
|
||||
|
||||
/**
|
||||
* @var ItemSearchProvider
|
||||
*/
|
||||
private $class;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->l10n = $this->getMockBuilder(IL10N::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->generator = $this->getMockBuilder(IURLGenerator::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->itemService = $this->getMockBuilder(ItemServiceV2::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$this->class = new ItemSearchProvider(
|
||||
$this->l10n,
|
||||
$this->generator,
|
||||
$this->itemService
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetId()
|
||||
{
|
||||
$this->assertSame('news_item', $this->class->getId());
|
||||
}
|
||||
|
||||
public function testGetName()
|
||||
{
|
||||
$this->l10n->expects($this->once())
|
||||
->method('t')
|
||||
->with('News articles')
|
||||
->willReturnArgument(0);
|
||||
|
||||
$this->assertSame('News articles', $this->class->getName());
|
||||
}
|
||||
|
||||
public function testGetOrderExternal()
|
||||
{
|
||||
$this->assertSame(65, $this->class->getOrder('contacts.Page.index', []));
|
||||
}
|
||||
|
||||
public function testGetOrderInternal()
|
||||
{
|
||||
$this->assertSame(1, $this->class->getOrder('news.page.index', []));
|
||||
}
|
||||
|
||||
public function testSearch()
|
||||
{
|
||||
$user = $this->getMockBuilder(IUser::class)
|
||||
->getMock();
|
||||
$query = $this->getMockBuilder(ISearchQuery::class)
|
||||
->getMock();
|
||||
|
||||
$query->expects($this->once())
|
||||
->method('getCursor')
|
||||
->willReturn(null);
|
||||
|
||||
$query->expects($this->once())
|
||||
->method('getLimit')
|
||||
->willReturn(10);
|
||||
|
||||
$user->expects($this->once())
|
||||
->method('getUID')
|
||||
->willReturn('user');
|
||||
|
||||
$query->expects($this->once())
|
||||
->method('getTerm')
|
||||
->willReturn('some text');
|
||||
|
||||
|
||||
$items = [
|
||||
Item::fromRow(['id' => 1,'title' => 'some_tErm', 'body' => 'some text', 'feedId' => 1]),
|
||||
Item::fromRow(['id' => 2,'title' => 'nothing', 'body' => 'some text', 'feedId' => 1])
|
||||
];
|
||||
|
||||
$this->itemService->expects($this->once())
|
||||
->method('findAllWithFilters')
|
||||
->with(
|
||||
'user',
|
||||
ListType::ALL_ITEMS,
|
||||
10,
|
||||
0,
|
||||
false,
|
||||
['some text'])
|
||||
->willReturn($items);
|
||||
|
||||
|
||||
$this->l10n->expects($this->once())
|
||||
->method('t')
|
||||
->with('News')
|
||||
->willReturnArgument(0);
|
||||
|
||||
$this->generator->expects($this->once())
|
||||
->method('imagePath')
|
||||
->with('core', 'filetypes/text.svg')
|
||||
->willReturn('folderpath.svg');
|
||||
|
||||
$this->generator->expects($this->exactly(2))
|
||||
->method('linkToRoute')
|
||||
->with('news.page.index')
|
||||
->willReturn('/news');
|
||||
|
||||
|
||||
$result = $this->class->search($user, $query)->jsonSerialize();
|
||||
$entry = $result['entries'][0]->jsonSerialize();
|
||||
$this->assertSame('News', $result['name']);
|
||||
$this->assertSame('some_tErm', $entry['title']);
|
||||
$this->assertSame('folderpath.svg', $entry['thumbnailUrl']);
|
||||
$this->assertSame('some text', $entry['subline']);
|
||||
$this->assertSame('/news#/items/feeds/1', $entry['resourceUrl']);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user