mirror of
https://github.com/wallabag/wallabag.git
synced 2024-12-16 12:46:27 +00:00
Merge pull request #4341 from wallabag/with-annotations-route
This commit is contained in:
commit
49d5f27cd0
14 changed files with 141 additions and 10 deletions
|
@ -69,11 +69,11 @@ security:
|
|||
- { path: ^/logout, roles: [IS_AUTHENTICATED_ANONYMOUSLY, IS_AUTHENTICATED_2FA_IN_PROGRESS] }
|
||||
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||
- { path: /(unread|starred|archive|all).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||
- { path: /(unread|starred|archive|annotated|all).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||
- { path: ^/locale, role: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||
- { path: /tags/(.*).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||
- { path: ^/feed, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||
- { path: /(unread|starred|archive).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY } # For backwards compatibility
|
||||
- { path: /(unread|starred|archive|annotated).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY } # For backwards compatibility
|
||||
- { path: ^/share, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||
- { path: ^/settings, roles: ROLE_SUPER_ADMIN }
|
||||
- { path: ^/annotations, roles: ROLE_USER }
|
||||
|
|
|
@ -277,12 +277,26 @@ class EntryController extends Controller
|
|||
return $this->showEntries('untagged', $request, $page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows entries with annotations for current user.
|
||||
*
|
||||
* @param int $page
|
||||
*
|
||||
* @Route("/annotated/list/{page}", name="annotated", defaults={"page" = "1"})
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function showWithAnnotationsEntriesAction(Request $request, $page)
|
||||
{
|
||||
return $this->showEntries('annotated', $request, $page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows random entry depending on the given type.
|
||||
*
|
||||
* @param string $type
|
||||
*
|
||||
* @Route("/{type}/random", name="random_entry", requirements={"type": "unread|starred|archive|untagged|all"})
|
||||
* @Route("/{type}/random", name="random_entry", requirements={"type": "unread|starred|archive|untagged|annotated|all"})
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||
*/
|
||||
|
@ -563,6 +577,9 @@ class EntryController extends Controller
|
|||
$qb = $repository->getBuilderForArchiveByUser($this->getUser()->getId());
|
||||
$formOptions['filter_archived'] = true;
|
||||
break;
|
||||
case 'annotated':
|
||||
$qb = $repository->getBuilderForAnnotationsByUser($this->getUser()->getId());
|
||||
break;
|
||||
case 'unread':
|
||||
$qb = $repository->getBuilderForUnreadByUser($this->getUser()->getId());
|
||||
$formOptions['filter_unread'] = true;
|
||||
|
|
|
@ -47,7 +47,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|search|same_domain"
|
||||
* "category": "all|unread|starred|archive|tag_entries|untagged|search|annotated|same_domain"
|
||||
* })
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
|
@ -80,6 +80,13 @@ class ExportController extends Controller
|
|||
->getResult();
|
||||
|
||||
$title = 'Search ' . $searchTerm;
|
||||
} elseif ('annotated' === $category) {
|
||||
$entries = $repository->getBuilderForAnnotationsByUser(
|
||||
$this->getUser()->getId()
|
||||
)->getQuery()
|
||||
->getResult();
|
||||
|
||||
$title = 'With annotations';
|
||||
} else {
|
||||
$entries = $repository
|
||||
->$methodBuilder($this->getUser()->getId())
|
||||
|
|
|
@ -93,7 +93,7 @@ class EntryFilterType extends AbstractType
|
|||
'apply_filter' => function (QueryInterface $filterQuery, $field, $values) {
|
||||
$value = $values['value'];
|
||||
if (\strlen($value) <= 2 || empty($value)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
$expression = $filterQuery->getExpr()->like($field, $filterQuery->getExpr()->lower($filterQuery->getExpr()->literal('%' . $value . '%')));
|
||||
|
||||
|
@ -105,7 +105,7 @@ class EntryFilterType extends AbstractType
|
|||
'apply_filter' => function (QueryInterface $filterQuery, $field, $values) {
|
||||
$value = $values['value'];
|
||||
if (false === \array_key_exists($value, Response::$statusTexts)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
$paramName = sprintf('%s', str_replace('.', '_', $field));
|
||||
|
@ -129,7 +129,7 @@ class EntryFilterType extends AbstractType
|
|||
'data' => $options['filter_unread'],
|
||||
'apply_filter' => function (QueryInterface $filterQuery, $field, $values) {
|
||||
if (false === $values['value']) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
$expression = $filterQuery->getExpr()->eq('e.isArchived', 'false');
|
||||
|
@ -137,10 +137,22 @@ class EntryFilterType extends AbstractType
|
|||
return $filterQuery->createCondition($expression);
|
||||
},
|
||||
])
|
||||
->add('isAnnotated', CheckboxFilterType::class, [
|
||||
'label' => 'entry.filters.annotated_label',
|
||||
'data' => $options['filter_annotated'],
|
||||
'apply_filter' => function (QueryInterface $filterQuery, $field, $values) {
|
||||
if (false === $values['value']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$qb = $filterQuery->getQueryBuilder();
|
||||
$qb->innerJoin('e.annotations', 'a');
|
||||
},
|
||||
])
|
||||
->add('previewPicture', CheckboxFilterType::class, [
|
||||
'apply_filter' => function (QueryInterface $filterQuery, $field, $values) {
|
||||
if (false === $values['value']) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
$expression = $filterQuery->getExpr()->isNotNull($field);
|
||||
|
@ -152,7 +164,7 @@ class EntryFilterType extends AbstractType
|
|||
->add('isPublic', CheckboxFilterType::class, [
|
||||
'apply_filter' => function (QueryInterface $filterQuery, $field, $values) {
|
||||
if (false === $values['value']) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// is_public isn't a real field
|
||||
|
@ -183,6 +195,7 @@ class EntryFilterType extends AbstractType
|
|||
'filter_archived' => false,
|
||||
'filter_starred' => false,
|
||||
'filter_unread' => false,
|
||||
'filter_annotated' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -142,6 +142,21 @@ class EntryRepository extends EntityRepository
|
|||
return $this->sortQueryBuilder($this->getRawBuilderForUntaggedByUser($userId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve entries with annotations for a user.
|
||||
*
|
||||
* @param int $userId
|
||||
*
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function getBuilderForAnnotationsByUser($userId)
|
||||
{
|
||||
return $this
|
||||
->getSortedQueryBuilderByUser($userId)
|
||||
->innerJoin('e.annotations', 'a')
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve untagged entries for a user.
|
||||
*
|
||||
|
@ -579,6 +594,10 @@ class EntryRepository extends EntityRepository
|
|||
$qb->leftJoin('e.tags', 't');
|
||||
$qb->andWhere('t.id is null');
|
||||
break;
|
||||
case 'annotated':
|
||||
$qb->leftJoin('e.annotations', 'a');
|
||||
$qb->andWhere('a.id is not null');
|
||||
break;
|
||||
}
|
||||
|
||||
$ids = $qb->getQuery()->getArrayResult();
|
||||
|
|
|
@ -19,6 +19,7 @@ menu:
|
|||
starred: Starred
|
||||
archive: Archive
|
||||
all_articles: All entries
|
||||
with_annotations: With annotations
|
||||
config: Config
|
||||
tags: Tags
|
||||
internal_settings: Internal Settings
|
||||
|
@ -220,6 +221,7 @@ entry:
|
|||
starred: Starred entries
|
||||
archived: Archived entries
|
||||
filtered: Filtered entries
|
||||
with_annotations: Entries with annotations
|
||||
filtered_tags: 'Filtered by tags:'
|
||||
filtered_search: 'Filtered by search:'
|
||||
untagged: Untagged entries
|
||||
|
@ -245,6 +247,7 @@ entry:
|
|||
archived_label: Archived
|
||||
starred_label: Starred
|
||||
unread_label: Unread
|
||||
annotated_label: Annotated
|
||||
preview_picture_label: Has a preview picture
|
||||
preview_picture_help: Preview picture
|
||||
is_public_label: Has a public link
|
||||
|
|
|
@ -32,6 +32,7 @@ menu:
|
|||
site_credentials: اعتبارنامههای وبگاه
|
||||
users_management: مدیریت کاربران
|
||||
developer: مدیریت کارخواههای API
|
||||
quickstart: "Quickstart"
|
||||
top:
|
||||
add_new_entry: افزودن مقالهٔ تازه
|
||||
search: جستجو
|
||||
|
|
|
@ -19,6 +19,7 @@ menu:
|
|||
starred: Favoris
|
||||
archive: Lus
|
||||
all_articles: Tous les articles
|
||||
with_annotations: Avec annotations
|
||||
config: Configuration
|
||||
tags: Étiquettes
|
||||
internal_settings: Configuration interne
|
||||
|
@ -220,6 +221,7 @@ entry:
|
|||
starred: Articles favoris
|
||||
archived: Articles lus
|
||||
filtered: Articles filtrés
|
||||
with_annotations: Articles avec annotations
|
||||
filtered_tags: 'Articles filtrés par étiquettes :'
|
||||
filtered_search: 'Articles filtrés par recherche :'
|
||||
untagged: Article sans étiquette
|
||||
|
@ -243,6 +245,7 @@ entry:
|
|||
archived_label: Lus
|
||||
starred_label: Favoris
|
||||
unread_label: Non lus
|
||||
annotated_label: Annotés
|
||||
preview_picture_label: A une photo
|
||||
preview_picture_help: Photo
|
||||
is_public_label: A un lien public
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
{{ 'entry.page_titles.untagged'|trans }}
|
||||
{% elseif currentRoute == 'same_domain' %}
|
||||
{{ 'entry.page_titles.same_domain'|trans }}
|
||||
{% elseif currentRoute == 'annotated' %}
|
||||
{{ 'entry.page_titles.with_annotations'|trans }}
|
||||
{% else %}
|
||||
{{ 'entry.page_titles.unread'|trans }}
|
||||
{% endif %}
|
||||
|
|
|
@ -131,6 +131,11 @@
|
|||
{{ form_label(form.isUnread) }}
|
||||
</div>
|
||||
|
||||
<div class="input-field col s12 with-checkbox">
|
||||
{{ form_widget(form.isAnnotated) }}
|
||||
{{ form_label(form.isAnnotated) }}
|
||||
</div>
|
||||
|
||||
<div class="col s12">
|
||||
<label>{{ 'entry.filters.preview_picture_help'|trans }}</label>
|
||||
</div>
|
||||
|
|
|
@ -40,6 +40,8 @@
|
|||
{% set activeRoute = null %}
|
||||
{% if currentRoute == 'all' or currentRouteFromQueryParams == 'all' %}
|
||||
{% set activeRoute = 'all' %}
|
||||
{% elseif currentRoute == 'annotated' or currentRouteFromQueryParams == 'annotated' %}
|
||||
{% set activeRoute = 'annotated' %}
|
||||
{% elseif currentRoute == 'archive' or currentRouteFromQueryParams == 'archive' %}
|
||||
{% set activeRoute = 'archive' %}
|
||||
{% elseif currentRoute == 'starred' or currentRouteFromQueryParams == 'starred' %}
|
||||
|
@ -59,6 +61,9 @@
|
|||
<li class="bold {% if activeRoute == 'archive' %}active{% endif %}">
|
||||
<a class="waves-effect" href="{{ path('archive') }}">{{ 'menu.left.archive'|trans }} <span class="numberItems grey-text">{{ count_entries('archive') }}</span></a>
|
||||
</li>
|
||||
<li class="bold {% if activeRoute == 'annotated' %}active{% endif %}">
|
||||
<a class="waves-effect" href="{{ path('annotated') }}">{{ 'menu.left.with_annotations'|trans }} <span class="numberItems grey-text">{{ count_entries('annotated') }}</span></a>
|
||||
</li>
|
||||
<li class="bold {% if activeRoute == 'all' %}active{% endif %}">
|
||||
<a class="waves-effect" href="{{ path('all') }}">{{ 'menu.left.all_articles'|trans }} <span class="numberItems grey-text">{{ count_entries('all') }}</span></a>
|
||||
</li>
|
||||
|
|
|
@ -95,6 +95,9 @@ class WallabagExtension extends AbstractExtension implements GlobalsInterface
|
|||
case 'unread':
|
||||
$qb = $this->entryRepository->getBuilderForUnreadByUser($user->getId());
|
||||
break;
|
||||
case 'annotated':
|
||||
$qb = $this->entryRepository->getBuilderForAnnotationsByUser($user->getId());
|
||||
break;
|
||||
case 'all':
|
||||
$qb = $this->entryRepository->getBuilderForAllByUser($user->getId());
|
||||
break;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace Tests\Wallabag\CoreBundle\Controller;
|
||||
|
||||
use Tests\Wallabag\CoreBundle\WallabagCoreTestCase;
|
||||
use Wallabag\AnnotationBundle\Entity\Annotation;
|
||||
use Wallabag\CoreBundle\Entity\Config;
|
||||
use Wallabag\CoreBundle\Entity\Entry;
|
||||
use Wallabag\CoreBundle\Entity\SiteCredential;
|
||||
|
@ -411,6 +412,16 @@ class EntryControllerTest extends WallabagCoreTestCase
|
|||
$this->assertSame(200, $client->getResponse()->getStatusCode());
|
||||
}
|
||||
|
||||
public function testWithAnnotations()
|
||||
{
|
||||
$this->logInAs('admin');
|
||||
$client = $this->getClient();
|
||||
|
||||
$crawler = $client->request('GET', '/annotated/list');
|
||||
$this->assertSame(200, $client->getResponse()->getStatusCode());
|
||||
$this->assertCount(2, $crawler->filter('li.entry'));
|
||||
}
|
||||
|
||||
public function testRangeException()
|
||||
{
|
||||
$this->logInAs('admin');
|
||||
|
@ -878,6 +889,44 @@ class EntryControllerTest extends WallabagCoreTestCase
|
|||
$this->assertCount(0, $crawler->filter('li.entry'));
|
||||
}
|
||||
|
||||
public function testFilterOnAnnotatedStatus()
|
||||
{
|
||||
$this->logInAs('admin');
|
||||
$client = $this->getClient();
|
||||
|
||||
$crawler = $client->request('GET', '/all/list');
|
||||
|
||||
$form = $crawler->filter('button[id=submit-filter]')->form();
|
||||
|
||||
$data = [
|
||||
'entry_filter[isAnnotated]' => true,
|
||||
];
|
||||
|
||||
$crawler = $client->submit($form, $data);
|
||||
|
||||
$this->assertCount(2, $crawler->filter('li.entry'));
|
||||
|
||||
$entry = new Entry($this->getLoggedInUser());
|
||||
$entry->setUrl($this->url);
|
||||
|
||||
$em = $this->getClient()->getContainer()->get('doctrine.orm.entity_manager');
|
||||
$user = $em
|
||||
->getRepository('WallabagUserBundle:User')
|
||||
->findOneByUserName('admin');
|
||||
|
||||
$annotation = new Annotation($user);
|
||||
$annotation->setEntry($entry);
|
||||
$annotation->setText('This is my annotation /o/');
|
||||
$annotation->setQuote('content');
|
||||
|
||||
$this->getEntityManager()->persist($entry);
|
||||
$this->getEntityManager()->flush();
|
||||
|
||||
$crawler = $client->submit($form, $data);
|
||||
|
||||
$this->assertCount(3, $crawler->filter('li.entry'));
|
||||
}
|
||||
|
||||
public function testPaginationWithFilter()
|
||||
{
|
||||
$this->logInAs('admin');
|
||||
|
@ -1590,6 +1639,10 @@ class EntryControllerTest extends WallabagCoreTestCase
|
|||
$this->assertSame(302, $client->getResponse()->getStatusCode());
|
||||
$this->assertStringContainsString('/view/', $client->getResponse()->getTargetUrl(), 'Untagged random');
|
||||
|
||||
$client->request('GET', '/annotated/random');
|
||||
$this->assertSame(302, $client->getResponse()->getStatusCode());
|
||||
$this->assertStringContainsString('/view/', $client->getResponse()->getTargetUrl(), 'With annotations random');
|
||||
|
||||
$client->request('GET', '/all/random');
|
||||
$this->assertSame(302, $client->getResponse()->getStatusCode());
|
||||
$this->assertStringContainsString('/view/', $client->getResponse()->getTargetUrl(), 'All random');
|
||||
|
|
|
@ -120,7 +120,7 @@ class DeliciousControllerTest extends WallabagCoreTestCase
|
|||
|
||||
$this->assertInstanceOf('Wallabag\CoreBundle\Entity\Entry', $content);
|
||||
|
||||
$tags = $content->getTags();
|
||||
$tags = $content->getTagsLabel();
|
||||
$this->assertContains('osx', $tags, 'It includes the "osx" tag');
|
||||
$this->assertGreaterThanOrEqual(4, \count($tags));
|
||||
|
||||
|
|
Loading…
Reference in a new issue