Implement tag filtering

This commit is contained in:
Tim 2025-05-10 20:44:13 +02:00
parent e2bd1a7c3b
commit b8ae8bb8a7
9 changed files with 90 additions and 34 deletions

View File

@ -15,6 +15,7 @@ readonly class SnipFilterRequest implements CachableDtoInterface
public function __construct( public function __construct(
public ?string $visibility = self::VISIBILITY_VISIBLE, public ?string $visibility = self::VISIBILITY_VISIBLE,
public ?string $sort = self::SORT_DATE, public ?string $sort = self::SORT_DATE,
public ?string $tag = null,
) {} ) {}
public function toArray(): array public function toArray(): array
@ -22,6 +23,7 @@ readonly class SnipFilterRequest implements CachableDtoInterface
return [ return [
'visibility' => $this->visibility, 'visibility' => $this->visibility,
'sort' => $this->sort, 'sort' => $this->sort,
'tag' => $this->tag,
]; ];
} }
} }

View File

@ -79,6 +79,13 @@ class SnipRepository extends ServiceEntityRepository
throw new \InvalidArgumentException('Invalid sort option: ', $request->sort); throw new \InvalidArgumentException('Invalid sort option: ', $request->sort);
} }
if ($request->tag) {
$qb->innerJoin('s.tags', 't')
->andWhere('t.name = :tag')
->setParameter('tag', $request->tag)
;
}
return $qb->getQuery()->getResult(); return $qb->getQuery()->getResult();
} }

View File

@ -5,6 +5,7 @@ namespace App\Repository;
use App\Entity\Tag; use App\Entity\Tag;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Security\Core\User\UserInterface;
/** /**
* @extends ServiceEntityRepository<Tag> * @extends ServiceEntityRepository<Tag>
@ -22,28 +23,8 @@ class TagRepository extends ServiceEntityRepository
$this->getEntityManager()->flush(); $this->getEntityManager()->flush();
} }
// /** public function findAllByUser(UserInterface $user): array
// * @return Tag[] Returns an array of Tag objects {
// */ return $this->findBy(['user' => $user]);
// public function findByExampleField($value): array }
// {
// return $this->createQueryBuilder('t')
// ->andWhere('t.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('t.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?Tag
// {
// return $this->createQueryBuilder('t')
// ->andWhere('t.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
} }

View File

@ -0,0 +1,35 @@
<?php
namespace App\Twig\Extension;
use App\Repository\TagRepository;
use Symfony\Bundle\SecurityBundle\Security;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class SnipFilterExtension extends AbstractExtension
{
public function __construct(
private readonly TagRepository $tagRepository,
private readonly Security $security,
) {}
public function getFunctions(): array
{
return [
new TwigFunction('snipSortOptions', fn() => ['name', 'date']),
new TwigFunction('snipFilterOptions', fn() => ['all', 'visible', 'hidden', 'archived']),
new TwigFunction('snipTagOptions', fn() => $this->getSnipTagOptions()),
];
}
private function getSnipTagOptions(): array
{
$tags[null] = 'All tags';
foreach ($this->tagRepository->findAllByUser($this->security->getUser()) as $tag) {
$tags[(string)$tag] = (string)$tag;
}
return $tags;
}
}

View File

@ -0,0 +1,17 @@
{% extends 'base/container.html.twig' %}
{% block container %}
<div class="row">
<div class="col-sm mx-auto">
{% if title is defined %}<h3>{{ title }}</h3>{% endif %}
</div>
</div>
<div class="row">
<div class="col-8">
{% block column1 %}{% endblock %}
</div>
<div class="col-4">
{% block column2 %}{% endblock %}
</div>
</div>
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'base/two.column.html.twig' %} {% extends 'base/two.column.8-4.html.twig' %}
{% set title = 'My Snips' %} {% set title = 'My Snips' %}
@ -15,6 +15,9 @@
<span> <span>
{{ include('snip/badge.html.twig', {snip: snip}) }} {{ include('snip/badge.html.twig', {snip: snip}) }}
{{ snip }} {{ snip }}
{% for tag in snip.tags %}
<span class="badge bg-secondary">{{ tag }}</span>
{% endfor %}
</span> </span>
</a> </a>
{% endfor %} {% endfor %}
@ -24,12 +27,12 @@
{% block column2 %} {% block column2 %}
<h3>Filters</h3> <h3>Filters</h3>
<h5>Ordering</h5> <h5>Sort by</h5>
<div class="list-group"> <div class="list-group">
{% for sortOption in ['name', 'date'] %} {% for sortOption in snipSortOptions() %}
<a href="{{ path('snip_index', {sort: sortOption}) }}" <a href="{{ path('snip_index', {sort: sortOption}) }}"
class="list-group-item list-group-item-action {% if sortOption is same as(request.sort) %}list-group-item-primary{% endif %}"> class="list-group-item list-group-item-action {% if sortOption is same as(request.sort) %}list-group-item-primary{% endif %}">
Sort by {{ sortOption|capitalize }} {{ sortOption|capitalize }}
</a> </a>
{% endfor %} {% endfor %}
</div> </div>
@ -37,11 +40,22 @@
<br> <br>
<h5>Visibility</h5> <h5>Visibility</h5>
<div class="list-group"> <div class="list-group">
{% for visibilityOption in ['all', 'visible', 'hidden', 'archived'] %} {% for visibilityOption in snipFilterOptions() %}
<a href="{{ path('snip_index', {visibility: visibilityOption}) }}" <a href="{{ path('snip_index', {visibility: visibilityOption}) }}"
class="list-group-item list-group-item-action {% if request.visibility is same as(visibilityOption) %}list-group-item-primary{% endif %}"> class="list-group-item list-group-item-action {% if request.visibility is same as(visibilityOption) %}list-group-item-primary{% endif %}">
Show {{ visibilityOption|capitalize }} Show {{ visibilityOption|capitalize }}
</a> </a>
{% endfor %} {% endfor %}
</div> </div>
<br>
<h5>Tags</h5>
<div class="list-group">
{% for key,tagOption in snipTagOptions() %}
<a href="{{ path('snip_index', {tag: key}) }}"
class="list-group-item list-group-item-action {% if request.tag is same as(key) %}list-group-item-primary{% endif %}">
{{ tagOption|capitalize }}
</a>
{% endfor %}
</div>
{% endblock %} {% endblock %}

View File

@ -37,10 +37,7 @@
<div class="card" style="width: 100%;"> <div class="card" style="width: 100%;">
<h4 class="card-header"> <h4 class="card-header">
{{ include('snip/badge.html.twig', {snip: snip}) }} {{ include('snip/badge.html.twig', {snip: snip}) }}
{{ snip }} <small class="text-muted">#{{ snip.id }}</small><br> {{ snip }} <small class="text-muted">#{{ snip.id }}</small>
{% for tag in snip.tags %}
<span class="badge bg-secondary">{{ tag }}</span>
{% endfor %}
</h4> </h4>
<div class="card-body"> <div class="card-body">
{{ content|raw }} {{ content|raw }}
@ -53,6 +50,9 @@
Created at {{ snip.activeVersion.id.dateTime|date('Y-m-d H:i:s') }} Created at {{ snip.activeVersion.id.dateTime|date('Y-m-d H:i:s') }}
{{ include('user/badge.html.twig', {user: snip.createdBy}) }} {{ include('user/badge.html.twig', {user: snip.createdBy}) }}
{% for tag in snip.tags %}
<span class="badge bg-secondary">{{ tag }}</span>
{% endfor %}
</p> </p>
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
{% extends 'base/two.column.html.twig' %} {% extends 'base/two.column.6-6.html.twig' %}
{% set title = app.user.name %} {% set title = app.user.name %}