forked from wallabag/wallabag
Merge pull request #5794 from wallabag/2.5.0
Merge branch 2.5.0 in master
This commit is contained in:
@ -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
|
||||
*/
|
||||
@ -517,6 +531,20 @@ class EntryController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* List the entries with the same domain as the current one.
|
||||
*
|
||||
* @param int $page
|
||||
*
|
||||
* @Route("/domain/{id}/{page}", requirements={"id" = ".+"}, defaults={"page" = 1}, name="same_domain")
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function getSameDomainEntries(Request $request, $page = 1)
|
||||
{
|
||||
return $this->showEntries('same-domain', $request, $page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Global method to retrieve entries depending on the given type
|
||||
* It returns the response to be send.
|
||||
@ -549,10 +577,16 @@ 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;
|
||||
break;
|
||||
case 'same-domain':
|
||||
$qb = $repository->getBuilderForSameDomainByUser($this->getUser()->getId(), $request->get('id'));
|
||||
break;
|
||||
case 'all':
|
||||
$qb = $repository->getBuilderForAllByUser($this->getUser()->getId());
|
||||
break;
|
||||
|
||||
@ -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"
|
||||
* "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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,7 +39,34 @@ class EntryRepository extends EntityRepository
|
||||
return $this
|
||||
->getSortedQueryBuilderByUser($userId)
|
||||
->andWhere('e.isArchived = false')
|
||||
;
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves entries with the same domain.
|
||||
*
|
||||
* @param int $userId
|
||||
* @param int $entryId
|
||||
*
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function getBuilderForSameDomainByUser($userId, $entryId)
|
||||
{
|
||||
$queryBuilder = $this->createQueryBuilder('e');
|
||||
|
||||
return $this
|
||||
->getSortedQueryBuilderByUser($userId)
|
||||
->andWhere('e.id <> :entryId')->setParameter('entryId', $entryId)
|
||||
->andWhere(
|
||||
$queryBuilder->expr()->in(
|
||||
'e.domainName',
|
||||
$this
|
||||
->createQueryBuilder('e2')
|
||||
->select('e2.domainName')
|
||||
->where('e2.id = :entryId')->setParameter('entryId', $entryId)
|
||||
->getDQL()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -115,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.
|
||||
*
|
||||
@ -552,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,10 +221,12 @@ 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
|
||||
all: All entries
|
||||
same_domain: Same domain
|
||||
list:
|
||||
number_on_the_page: '{0} There are no entries.|{1} There is one entry.|]1,Inf[ There are %count% entries.'
|
||||
reading_time: estimated reading time
|
||||
@ -237,6 +240,7 @@ entry:
|
||||
toogle_as_star: Toggle starred
|
||||
delete: Delete
|
||||
export_title: Export
|
||||
show_same_domain: Show articles with the same domain
|
||||
assign_search_tag: Assign this search as a tag to each result
|
||||
filters:
|
||||
title: Filters
|
||||
@ -244,6 +248,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
|
||||
@ -481,12 +486,12 @@ import:
|
||||
description: Pocket import isn't configured.
|
||||
admin_message: You need to define %keyurls%a pocket_consumer_key%keyurle%.
|
||||
user_message: Your server admin needs to define an API Key for Pocket.
|
||||
authorize_message: You can import your data from your Pocket account. You just have to click on the below button and authorize the application to connect to getpocket.com.
|
||||
authorize_message: You can import your data from your Pocket account. You just have to click on the button below and authorize the application to connect to getpocket.com.
|
||||
connect_to_pocket: Connect to Pocket and import data
|
||||
wallabag_v1:
|
||||
page_title: Import > Wallabag v1
|
||||
description: This importer will import all your wallabag v1 articles. On your config page, click on "JSON export" in the "Export your wallabag data" section. You will have a "wallabag-export-1-xxxx-xx-xx.json" file.
|
||||
how_to: Please select your wallabag export and click on the below button to upload and import it.
|
||||
how_to: Please select your wallabag export and click on the button below to upload and import it.
|
||||
wallabag_v2:
|
||||
page_title: Import > Wallabag v2
|
||||
description: This importer will import all your wallabag v2 articles. Go to All articles, then, on the export sidebar, click on "JSON". You will have a "All articles.json" file.
|
||||
@ -496,7 +501,7 @@ import:
|
||||
readability:
|
||||
page_title: Import > Readability
|
||||
description: This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).
|
||||
how_to: Please select your Readability export and click on the below button to upload and import it.
|
||||
how_to: Please select your Readability export and click on the button below to upload and import it.
|
||||
worker:
|
||||
enabled: 'Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:'
|
||||
download_images_warning: You enabled downloading images for your articles. Combined with classic import it can take ages to proceed (or maybe failed). We <strong>strongly recommend</strong> to enable asynchronous import to avoid errors.
|
||||
@ -511,11 +516,15 @@ import:
|
||||
instapaper:
|
||||
page_title: Import > Instapaper
|
||||
description: This importer will import all your Instapaper articles. On the settings (https://www.instapaper.com/user) page, click on "Download .CSV file" in the "Export" section. A CSV file will be downloaded (like "instapaper-export.csv").
|
||||
how_to: Please select your Instapaper export and click on the below button to upload and import it.
|
||||
how_to: Please select your Instapaper export and click on the button below to upload and import it.
|
||||
pinboard:
|
||||
page_title: Import > Pinboard
|
||||
description: This importer will import all your Pinboard articles. On the backup (https://pinboard.in/settings/backup) page, click on "JSON" in the "Bookmarks" section. A JSON file will be downloaded (like "pinboard_export").
|
||||
how_to: Please select your Pinboard export and click on the below button to upload and import it.
|
||||
how_to: Please select your Pinboard export and click on the button below to upload and import it.
|
||||
delicious:
|
||||
page_title: Import > del.icio.us
|
||||
description: This importer will import all your Delicious bookmarks. Since 2021, you can export again your data from it using the export page (https://del.icio.us/export). Choose the "JSON" format and download it (like "delicious_export.2021.02.06_21.10.json").
|
||||
how_to: Please select your Delicious export and click on the button below to upload and import it.
|
||||
developer:
|
||||
page_title: API clients management
|
||||
welcome_message: Welcome to the wallabag API
|
||||
|
||||
@ -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
|
||||
|
||||
@ -12,6 +12,10 @@
|
||||
{{ 'entry.page_titles.filtered_tags'|trans }} {{ filter }}
|
||||
{% elseif currentRoute == 'untagged' %}
|
||||
{{ '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 %}
|
||||
|
||||
@ -8,6 +8,9 @@
|
||||
</div>
|
||||
|
||||
<ul class="tools right">
|
||||
<li>
|
||||
<a title="{{ 'entry.list.show_same_domain'|trans }}" class="tool grey-text" href="{{ path('same_domain', { 'id': entry.id }) }}" data-action="same_domain" data-entry-id="{{ entry.id }}"><i class="material-icons">language</i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a title="{{ 'entry.list.toogle_as_read'|trans }}" class="tool grey-text" href="{{ path('archive_entry', { 'id': entry.id }) }}" data-action="archived" data-entry-id="{{ entry.id }}"><i class="material-icons">{% if entry.isArchived == 0 %}done{% else %}unarchive{% endif %}</i></a>
|
||||
</li>
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
{% include "@WallabagCore/themes/material/Entry/Card/_content.html.twig" with {'entry': entry, 'withMetadata': true, 'subClass': 'metadata'} only %}
|
||||
<ul class="tools-list hide-on-small-only">
|
||||
<li>
|
||||
<a title="{{ 'entry.list.show_same_domain'|trans }}" class="tool grey-text" href="{{ path('same_domain', { 'id': entry.id }) }}"><i class="material-icons">language</i></a>
|
||||
<a title="{{ 'entry.list.toogle_as_read'|trans }}" class="tool grey-text" href="{{ path('archive_entry', { 'id': entry.id }) }}"><i class="material-icons">{% if entry.isArchived == 0 %}done{% else %}unarchive{% endif %}</i></a>
|
||||
<a title="{{ 'entry.list.toogle_as_star'|trans }}" class="tool grey-text" href="{{ path('star_entry', { 'id': entry.id }) }}"><i class="material-icons">{% if entry.isStarred == 0 %}star_border{% else %}star{% endif %}</i></a>
|
||||
<a title="{{ 'entry.list.delete'|trans }}" onclick="return confirm('{{ 'entry.confirm.delete'|trans|escape('js') }}')" class="tool grey-text delete" href="{{ path('delete_entry', { 'id': entry.id }) }}"><i class="material-icons">delete</i></a>
|
||||
|
||||
@ -134,6 +134,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;
|
||||
|
||||
Reference in New Issue
Block a user