Merge pull request #2543 from wallabag/search-engine

Added a simple search engine
This commit is contained in:
Nicolas Lœuillet 2016-11-19 20:42:27 +01:00 committed by GitHub
commit 6f85bed294
34 changed files with 307 additions and 64 deletions

View file

@ -0,0 +1,7 @@
$(document).ready(() => {
Mousetrap.bind('s', () => {
$('#search').trigger('click');
$('#search_entry_term').focus();
return false;
});
});

View file

@ -322,11 +322,13 @@ nav input {
color: #444;
}
.input-field.nav-panel-add label {
.input-field.nav-panel-add label,
.input-field.nav-panel-search label {
left: 1rem;
}
.input-field.nav-panel-add .close {
.input-field.nav-panel-add .close,
.input-field.nav-panel-search .close {
position: absolute;
top: 0;
right: 1rem;
@ -345,7 +347,9 @@ nav input {
}
.input-field.nav-panel-add,
.input-field.nav-panel-add form {
.input-field.nav-panel-add form,
.input-field.nav-panel-search,
.input-field.nav-panel-search form {
height: 100%;
}

View file

@ -55,7 +55,7 @@ $(document).ready(() => {
$('.nav-panels .action').hide(100);
$('.nav-panel-menu').addClass('hidden');
$('.nav-panels').css('background', 'white');
$('#searchfield').focus();
$('#search_entry_term').focus();
return false;
});
$('.close').on('click', () => {

View file

@ -30,6 +30,12 @@ $(document).ready(() => {
/* Actions */
Mousetrap.bind('g n', () => {
$('#nav-btn-add').trigger('click');
return false;
});
Mousetrap.bind('s', () => {
$('#nav-btn-search').trigger('click');
return false;
});
Mousetrap.bind('esc', () => {

View file

@ -364,3 +364,4 @@ fos_js_routing:
- developer
- howto
- logout
- new

View file

@ -15,9 +15,34 @@ use Wallabag\CoreBundle\Form\Type\NewEntryType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
use Wallabag\CoreBundle\Event\EntrySavedEvent;
use Wallabag\CoreBundle\Event\EntryDeletedEvent;
use Wallabag\CoreBundle\Form\Type\SearchEntryType;
class EntryController extends Controller
{
/**
* @param Request $request
* @param int $page
*
* @Route("/search/{page}", name="search", defaults={"page" = "1"})
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function searchFormAction(Request $request, $page, $currentRoute)
{
$form = $this->createForm(SearchEntryType::class);
$form->handleRequest($request);
if ($form->isValid()) {
return $this->showEntries('search', $request, $page);
}
return $this->render('WallabagCoreBundle:Entry:search_form.html.twig', [
'form' => $form->createView(),
'currentRoute' => $currentRoute,
]);
}
/**
* Fetch content and update entry.
* In case it fails, entry will return to avod loosing the data.
@ -244,8 +269,14 @@ class EntryController extends Controller
private function showEntries($type, Request $request, $page)
{
$repository = $this->get('wallabag_core.entry_repository');
$searchTerm = (isset($request->get('search_entry')['term']) ? $request->get('search_entry')['term'] : '');
$currentRoute = (!is_null($request->get('currentRoute')) ? $request->get('currentRoute') : '');
switch ($type) {
case 'search':
$qb = $repository->getBuilderForSearchByUser($this->getUser()->getId(), $searchTerm, $currentRoute);
break;
case 'untagged':
$qb = $repository->getBuilderForUntaggedByUser($this->getUser()->getId());
@ -294,11 +325,11 @@ class EntryController extends Controller
}
return $this->render(
'WallabagCoreBundle:Entry:entries.html.twig',
[
'WallabagCoreBundle:Entry:entries.html.twig', [
'form' => $form->createView(),
'entries' => $entries,
'currentPage' => $page,
'searchTerm' => $searchTerm,
]
);
}

View file

@ -48,7 +48,7 @@ class ExportController extends Controller
*
* @Route("/export/{category}.{format}", name="export_entries", requirements={
* "format": "epub|mobi|pdf|json|xml|txt|csv",
* "category": "all|unread|starred|archive|tag_entries|untagged"
* "category": "all|unread|starred|archive|tag_entries|untagged|search"
* })
*
* @return \Symfony\Component\HttpFoundation\Response

View file

@ -0,0 +1,29 @@
<?php
namespace Wallabag\CoreBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class SearchEntryType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->setMethod('GET')
->add('term', TextType::class, [
'required' => true,
'label' => 'entry.new.form_search.term_label',
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'csrf_protection' => false,
]);
}
}

View file

@ -85,6 +85,36 @@ class EntryRepository extends EntityRepository
;
}
/**
* Retrieves entries filtered with a search term for a user.
*
* @param int $userId
* @param string $term
* @param strint $currentRoute
*
* @return QueryBuilder
*/
public function getBuilderForSearchByUser($userId, $term, $currentRoute)
{
$qb = $this
->getBuilderByUser($userId);
if ('starred' === $currentRoute) {
$qb->andWhere('e.isStarred = true');
} elseif ('unread' === $currentRoute) {
$qb->andWhere('e.isArchived = false');
} elseif ('archive' === $currentRoute) {
$qb->andWhere('e.isArchived = true');
}
$qb
->andWhere('e.content LIKE :term OR e.title LIKE :term')->setParameter('term', '%'.$term.'%')
->leftJoin('e.tags', 't')
->groupBy('e.id');
return $qb;
}
/**
* Retrieves untagged entries for a user.
*

View file

@ -162,6 +162,7 @@ entry:
# archived: 'Archived entries'
# filtered: 'Filtered entries'
# filtered_tags: 'Filtered by tags:'
# filtered_search: 'Filtered by search:'
# untagged: 'Untagged entries'
list:
# number_on_the_page: '{0} There is no entry.|{1} There is one entry.|]1,Inf[ There are %count% entries.'
@ -227,6 +228,8 @@ entry:
placeholder: 'http://website.com'
form_new:
url_label: Url
search:
# placeholder: 'What are you looking for?'
edit:
# page_title: 'Edit an entry'
# title_label: 'Title'

View file

@ -162,6 +162,7 @@ entry:
archived: 'Archivierte Einträge'
filtered: 'Gefilterte Einträge'
filtered_tags: 'Gefiltert nach Tags:'
# filtered_search: 'Filtered by search:'
untagged: 'Nicht getaggte Einträge'
list:
number_on_the_page: '{0} Es gibt keine Einträge.|{1} Es gibt einen Eintrag.|]1,Inf[ Es gibt %count% Einträge.'
@ -227,6 +228,8 @@ entry:
placeholder: 'https://website.de'
form_new:
url_label: URL
search:
# placeholder: 'What are you looking for?'
edit:
page_title: 'Eintrag bearbeiten'
title_label: 'Titel'

View file

@ -162,6 +162,7 @@ entry:
archived: 'Archived entries'
filtered: 'Filtered entries'
filtered_tags: 'Filtered by tags:'
filtered_search: 'Filtered by search:'
untagged: 'Untagged entries'
list:
number_on_the_page: '{0} There are no entries.|{1} There is one entry.|]1,Inf[ There are %count% entries.'
@ -227,6 +228,8 @@ entry:
placeholder: 'http://website.com'
form_new:
url_label: Url
search:
placeholder: 'What are you looking for?'
edit:
page_title: 'Edit an entry'
title_label: 'Title'

View file

@ -162,6 +162,7 @@ entry:
archived: 'Artículos archivados'
filtered: 'Artículos filtrados'
# filtered_tags: 'Filtered by tags:'
# filtered_search: 'Filtered by search:'
# untagged: 'Untagged entries'
list:
number_on_the_page: '{0} No hay artículos.|{1} Hay un artículo.|]1,Inf[ Hay %count% artículos.'
@ -227,6 +228,8 @@ entry:
placeholder: 'http://website.com'
form_new:
url_label: Url
search:
# placeholder: 'What are you looking for?'
edit:
page_title: 'Editar un artículo'
title_label: 'Título'

View file

@ -162,6 +162,7 @@ entry:
archived: 'مقاله‌های بایگانی‌شده'
filtered: 'مقاله‌های فیلترشده'
# filtered_tags: 'Filtered by tags:'
# filtered_search: 'Filtered by search:'
# untagged: 'Untagged entries'
list:
number_on_the_page: '{0} هیج مقاله‌ای نیست.|{1} یک مقاله هست.|]1,Inf[ %count% مقاله هست.'
@ -227,6 +228,8 @@ entry:
placeholder: 'http://website.com'
form_new:
url_label: نشانی
search:
# placeholder: 'What are you looking for?'
edit:
page_title: 'ویرایش مقاله'
title_label: 'عنوان'

View file

@ -162,6 +162,7 @@ entry:
archived: "Articles lus"
filtered: "Articles filtrés"
filtered_tags: "Articles filtrés par tags :"
filtered_search: 'Articles filtrés par recherche :'
untagged: "Article sans tag"
list:
number_on_the_page: "{0} Il ny a pas darticles.|{1} Il y a un article.|]1,Inf[ Il y a %count% articles."
@ -227,6 +228,8 @@ entry:
placeholder: "http://website.com"
form_new:
url_label: "Adresse"
search:
placeholder: "Que recherchez-vous ?"
edit:
page_title: "Éditer un article"
title_label: "Titre"

View file

@ -162,6 +162,7 @@ entry:
archived: 'Contenuti archiviati'
filtered: 'Contenuti filtrati'
# filtered_tags: 'Filtered by tags:'
# filtered_search: 'Filtered by search:'
# untagged: 'Untagged entries'
list:
number_on_the_page: "{0} Non ci sono contenuti.|{1} C'è un contenuto.|]1,Inf[ Ci sono %count% contenuti."
@ -227,6 +228,8 @@ entry:
placeholder: 'http://website.com'
form_new:
url_label: Url
search:
# placeholder: 'What are you looking for?'
edit:
page_title: 'Modifica voce'
title_label: 'Titolo'

View file

@ -162,6 +162,7 @@ entry:
archived: 'Articles legits'
filtered: 'Articles filtrats'
filtered_tags: 'Filtats per etiquetas:'
# filtered_search: 'Filtered by search:'
untagged: 'Articles sens etiqueta'
list:
number_on_the_page: "{0} I a pas cap d'article.|{1} I a un article.|]1,Inf[ I a %count% articles."
@ -227,6 +228,8 @@ entry:
placeholder: 'http://website.com'
form_new:
url_label: Url
search:
# placeholder: 'What are you looking for?'
edit:
page_title: 'Modificar un article'
title_label: 'Títol'

View file

@ -162,6 +162,7 @@ entry:
archived: 'Zarchiwizowane wpisy'
filtered: 'Odfiltrowane wpisy'
filtered_tags: 'Filtrowane po tagach:'
# filtered_search: 'Filtered by search:'
untagged: 'Odtaguj wpisy'
list:
number_on_the_page: '{0} Nie ma wpisów.|{1} Jest jeden wpis.|]1,Inf[ Są %count% wpisy.'
@ -227,6 +228,8 @@ entry:
placeholder: 'http://website.com'
form_new:
url_label: Url
search:
# placeholder: 'What are you looking for?'
edit:
page_title: 'Edytuj wpis'
title_label: 'Tytuł'

View file

@ -162,6 +162,7 @@ entry:
archived: 'Entradas arquivadas'
filtered: 'Entradas filtradas'
filtered_tags: 'Filtrar por tags:'
# filtered_search: 'Filtered by search:'
untagged: 'Entradas sem tags'
list:
number_on_the_page: '{0} Não existem entradas.|{1} Existe uma entrada.|]1,Inf[ Existem %count% entradas.'
@ -227,6 +228,8 @@ entry:
placeholder: 'http://website.com'
form_new:
url_label: Url
search:
# placeholder: 'What are you looking for?'
edit:
page_title: 'Editar uma entrada'
title_label: 'Título'

View file

@ -162,6 +162,7 @@ entry:
# archived: 'Archived entries'
# filtered: 'Filtered entries'
# filtered_tags: 'Filtered by tags:'
# filtered_search: 'Filtered by search:'
# untagged: 'Untagged entries'
list:
# number_on_the_page: '{0} There is no entry.|{1} There is one entry.|]1,Inf[ There are %count% entries.'
@ -227,6 +228,8 @@ entry:
placeholder: 'http://website.com'
form_new:
url_label: Url
search:
# placeholder: 'What are you looking for?'
edit:
# page_title: 'Edit an entry'
# title_label: 'Title'

View file

@ -162,6 +162,7 @@ entry:
# archived: 'Archived entries'
# filtered: 'Filtered entries'
# filtered_tags: 'Filtered by tags:'
# filtered_search: 'Filtered by search:'
# untagged: 'Untagged entries'
list:
number_on_the_page: '{0} Herhangi bir makale yok.|{1} Burada bir adet makale var.|]1,Inf[ Burada %count% adet makale var.'
@ -227,6 +228,8 @@ entry:
placeholder: 'http://website.com'
form_new:
url_label: Url
search:
# placeholder: 'What are you looking for?'
edit:
page_title: 'Makaleyi düzenle'
title_label: 'Başlık'

View file

@ -1,11 +1,14 @@
{% extends "WallabagCoreBundle::layout.html.twig" %}
{% block title %}
{% set currentTag = '' %}
{% set filter = '' %}
{% if tag is defined %}
{% set currentTag = tag %}
{% set filter = tag %}
{% endif %}
{% include "@WallabagCore/themes/common/Entry/_title.html.twig" with {'currentTag': currentTag} %}
{% if searchTerm is defined and searchTerm is not empty %}
{% set filter = searchTerm %}
{% endif %}
{% include "@WallabagCore/themes/common/Entry/_title.html.twig" with {'filter': filter} %}
{% endblock %}
{% block content %}

View file

@ -0,0 +1,17 @@
<form name="search" method="GET" action="{{ path('search')}}">
<h2>{{ 'menu.left.search'|trans }}</h2>
<a href="javascript: void(null);" id="search-form-close" class="close-button--popup close-button">&times;</a>
{% if form_errors(form) %}
<span class="black-text">{{ form_errors(form) }}</span>
{% endif %}
{% if form_errors(form.term) %}
<span class="black-text">{{ form_errors(form.term) }}</span>
{% endif %}
<input type="hidden" name="currentRoute" value="{{ currentRoute }}" />
{{ form_widget(form.term, { 'attr': {'autocomplete': 'off', 'placeholder': 'entry.search.placeholder'} }) }}
{{ form_rest(form) }}
</form>

View file

@ -31,17 +31,11 @@
<li class="menu all"><a href="{{ path('all') }}">{{ 'menu.left.all_articles'|trans }}</a></li>
<li class="menu tag"><a href="{{ path('tag') }}">{{ 'menu.left.tags'|trans }}</a></li>
<li class="menu new"><a href="{{ path('new') }}">{{ 'menu.left.save_link'|trans }}</a></li>
<!--<li style="position: relative;"><a href="javascript: void(null);" id="search">{{ 'menu.left.search'|trans }}</a>
<li style="position: relative;"><a href="javascript: void(null);" id="search">{{ 'menu.left.search'|trans }}</a>
<div id="search-form" class="messages info popup-form">
<form method="get" action="index.php">
<h2>{{ 'menu.left.search'|trans }}</h2>
<a href="javascript: void(null);" id="search-form-close" class="close-button--popup close-button">&times;</a>
<input type="hidden" name="view" value="search">
<input required placeholder="{{ 'menu.search_form.input_label'|trans }}" type="text" name="search" id="searchfield"><br>
<input id="submit-search" type="submit" value="{{ 'menu.left.search'|trans }}">
</form>
{{ render(controller("WallabagCoreBundle:Entry:searchForm", {'currentRoute': app.request.attributes.get('_route')})) }}
</div>
</li>-->
</li>
<li class="menu config"><a href="{{ path('config') }}">{{ 'menu.left.config'|trans }}</a></li>
{% if is_granted('ROLE_SUPER_ADMIN') %}
<li class="menu users"><a href="{{ path('user_index') }}">{{ 'menu.left.users_management'|trans }}</a></li>

View file

@ -6,8 +6,10 @@
{{ 'entry.page_titles.archived'|trans }}
{% elseif currentRoute == 'all' %}
{{ 'entry.page_titles.filtered'|trans }}
{% elseif currentRoute == 'search' %}
{{ 'entry.page_titles.filtered_search'|trans }} {{ filter }}
{% elseif currentRoute == 'tag_entries' %}
{{ 'entry.page_titles.filtered_tags'|trans }} {{ currentTag }}
{{ 'entry.page_titles.filtered_tags'|trans }} {{ filter }}
{% elseif currentRoute == 'untagged' %}
{{ 'entry.page_titles.untagged'|trans }}
{% else %}

View file

@ -1,11 +1,14 @@
{% extends "WallabagCoreBundle::layout.html.twig" %}
{% block title %}
{% set currentTag = '' %}
{% set filter = '' %}
{% if tag is defined %}
{% set currentTag = tag %}
{% set filter = tag %}
{% endif %}
{% include "@WallabagCore/themes/common/Entry/_title.html.twig" with {'currentTag': currentTag} %}
{% if searchTerm is defined and searchTerm is not empty %}
{% set filter = searchTerm %}
{% endif %}
{% include "@WallabagCore/themes/common/Entry/_title.html.twig" with {'filter': filter} %}
{% endblock %}
{% block content %}

View file

@ -0,0 +1,15 @@
<form name="search" method="GET" action="{{ path('search')}}">
{% if form_errors(form) %}
<span class="black-text">{{ form_errors(form) }}</span>
{% endif %}
{% if form_errors(form.term) %}
<span class="black-text">{{ form_errors(form.term) }}</span>
{% endif %}
<input type="hidden" name="currentRoute" value="{{ currentRoute }}" />
{{ form_widget(form.term, { 'attr': {'autocomplete': 'off', 'placeholder': 'entry.search.placeholder'} }) }}
{{ form_rest(form) }}
</form>

View file

@ -89,11 +89,11 @@
<i class="material-icons">add</i>
</a>
</li>
<!--<li>
<li>
<a title="{{ 'menu.top.search'|trans }}" class="waves-effect" href="javascript: void(null);" id="nav-btn-search">
<i class="material-icons">search</i>
</a>
</li>-->
</li>
<li id="button_filters">
<a class="nav-panel-menu button-collapse-right tooltipped" data-position="bottom" data-delay="50" data-tooltip="{{ 'menu.top.filter_entries'|trans }}" href="#" data-activates="filters">
<i class="material-icons">filter_list</i>
@ -106,13 +106,11 @@
</li>
</ul>
</div>
<form method="get" action="index.php">
<div class="input-field nav-panel-search" style="display: none">
<input name="search" id="searchfield" type="search" required placeholder="{{ 'menu.search_form.input_label'|trans }}">
<label for="search"><i class="material-icons search">search</i></label>
<i class="material-icons close">clear</i>
</div>
</form>
<div class="input-field nav-panel-search" style="display: none">
{{ render(controller("WallabagCoreBundle:Entry:searchForm", {'currentRoute': app.request.attributes.get('_route')})) }}
<label for="search" class="active"><i class="material-icons search">search</i></label>
<i class="material-icons close">clear</i>
</div>
<div class="input-field nav-panel-add" style="display: none">
{{ render(controller("WallabagCoreBundle:Entry:addEntryForm")) }}
<label for="add" class="active"><i class="material-icons add">add</i></label>

View file

@ -1018,4 +1018,71 @@ class EntryControllerTest extends WallabagCoreTestCase
$this->assertCount(7, $crawler->filter('div[class=entry]'));
}
public function testSearch()
{
$this->logInAs('admin');
$client = $this->getClient();
// Search on unread list
$crawler = $client->request('GET', '/unread/list');
$form = $crawler->filter('form[name=search]')->form();
$data = [
'search_entry[term]' => 'title',
];
$crawler = $client->submit($form, $data);
$this->assertCount(5, $crawler->filter('div[class=entry]'));
// Search on starred list
$crawler = $client->request('GET', '/starred/list');
$form = $crawler->filter('form[name=search]')->form();
$data = [
'search_entry[term]' => 'title',
];
$crawler = $client->submit($form, $data);
$this->assertCount(1, $crawler->filter('div[class=entry]'));
// Added new article to test on archive list
$crawler = $client->request('GET', '/new');
$form = $crawler->filter('form[name=entry]')->form();
$data = [
'entry[url]' => $this->url,
];
$client->submit($form, $data);
$content = $client->getContainer()
->get('doctrine.orm.entity_manager')
->getRepository('WallabagCoreBundle:Entry')
->findByUrlAndUserId($this->url, $this->getLoggedInUserId());
$client->request('GET', '/archive/'.$content->getId());
$crawler = $client->request('GET', '/archive/list');
$form = $crawler->filter('form[name=search]')->form();
$data = [
'search_entry[term]' => 'manège',
];
$crawler = $client->submit($form, $data);
$this->assertCount(1, $crawler->filter('div[class=entry]'));
$client->request('GET', '/delete/'.$content->getId());
// test on list of all articles
$crawler = $client->request('GET', '/all/list');
$form = $crawler->filter('form[name=search]')->form();
$data = [
'search_entry[term]' => 'wxcvbnqsdf', // a string not available in the database
];
$crawler = $client->submit($form, $data);
$this->assertCount(0, $crawler->filter('div[class=entry]'));
}
}

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