From 111b54843a045e28bf3be1a22ab19d58bf2f2f22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nu=C5=A1a=20Puk=C5=A1i=C4=8D?= Date: Tue, 16 Sep 2025 20:09:53 +0200 Subject: [PATCH] Curated lists --- src/Controller/ReadingListController.php | 57 +++++++++++++++++- src/Twig/Components/ReadingListList.php | 59 +++++++++++++++++++ .../Organisms/ReadingListList.html.twig | 16 +++++ .../components/Organisms/ZineList.html.twig | 4 ++ templates/home.html.twig | 3 + templates/pages/category.html.twig | 2 +- templates/reading_list/index.html.twig | 32 +++++++++- 7 files changed, 169 insertions(+), 4 deletions(-) create mode 100644 src/Twig/Components/ReadingListList.php create mode 100644 templates/components/Organisms/ReadingListList.html.twig diff --git a/src/Controller/ReadingListController.php b/src/Controller/ReadingListController.php index 9452280..77084ba 100644 --- a/src/Controller/ReadingListController.php +++ b/src/Controller/ReadingListController.php @@ -4,6 +4,9 @@ declare(strict_types=1); namespace App\Controller; +use App\Entity\Event; +use Doctrine\ORM\EntityManagerInterface; +use swentel\nostr\Key\Key; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; @@ -11,9 +14,59 @@ use Symfony\Component\Routing\Attribute\Route; class ReadingListController extends AbstractController { #[Route('/reading-list', name: 'reading_list_index')] - public function index(): Response + public function index(EntityManagerInterface $em): Response { - return $this->render('reading_list/index.html.twig'); + $lists = []; + $user = $this->getUser(); + $pubkeyHex = null; + if ($user && method_exists($user, 'getUserIdentifier')) { + try { + $key = new Key(); + $pubkeyHex = $key->convertToHex($user->getUserIdentifier()); + } catch (\Throwable $e) { + $pubkeyHex = null; + } + } + + if ($pubkeyHex) { + $repo = $em->getRepository(Event::class); + $events = $repo->findBy(['kind' => 30040, 'pubkey' => $pubkeyHex], ['created_at' => 'DESC']); + $seenSlugs = []; + foreach ($events as $ev) { + if (!$ev instanceof Event) continue; + $tags = $ev->getTags(); + $isReadingList = false; + $title = null; $slug = null; $summary = null; + foreach ($tags as $t) { + if (is_array($t)) { + if (($t[0] ?? null) === 'type' && ($t[1] ?? null) === 'reading-list') { $isReadingList = true; } + if (($t[0] ?? null) === 'title') { $title = (string)$t[1]; } + if (($t[0] ?? null) === 'summary') { $summary = (string)$t[1]; } + if (($t[0] ?? null) === 'd') { $slug = (string)$t[1]; } + } + } + if ($isReadingList) { + // Collapse by slug: keep only newest per slug + $keySlug = $slug ?: ('__no_slug__:' . $ev->getId()); + if (isset($seenSlugs[$slug ?? $keySlug])) { + continue; + } + $seenSlugs[$slug ?? $keySlug] = true; + + $lists[] = [ + 'id' => $ev->getId(), + 'title' => $title ?: '(untitled)', + 'summary' => $summary, + 'slug' => $slug, + 'createdAt' => $ev->getCreatedAt(), + ]; + } + } + } + + return $this->render('reading_list/index.html.twig', [ + 'lists' => $lists, + ]); } #[Route('/reading-list/compose', name: 'reading_list_compose')] diff --git a/src/Twig/Components/ReadingListList.php b/src/Twig/Components/ReadingListList.php new file mode 100644 index 0000000..8265c63 --- /dev/null +++ b/src/Twig/Components/ReadingListList.php @@ -0,0 +1,59 @@ + + */ + public function getLists(): array + { + $repo = $this->em->getRepository(Event::class); + // Fetch more than we need to allow collapsing by slug + /** @var Event[] $events */ + $events = $repo->findBy(['kind' => 30040], ['created_at' => 'DESC'], 200); + + $out = []; + $seen = []; + foreach ($events as $ev) { + $tags = $ev->getTags(); + $isReadingList = false; + $title = null; $slug = null; + foreach ($tags as $t) { + if (!is_array($t)) continue; + if (($t[0] ?? null) === 'type' && ($t[1] ?? null) === 'reading-list') { $isReadingList = true; } + if (($t[0] ?? null) === 'title') { $title = (string)$t[1]; } + if (($t[0] ?? null) === 'd') { $slug = (string)$t[1]; } + } + if (!$isReadingList) continue; + // Require slug; skip malformed events without slug + if (!$slug) continue; + + // Collapse newest by slug + if (isset($seen[$slug])) continue; + $seen[$slug] = true; + + $out[] = [ + 'title' => $title ?: '(untitled)', + 'slug' => $slug, + 'createdAt' => $ev->getCreatedAt(), + 'pubkey' => $ev->getPubkey(), + ]; + if (count($out) >= $this->limit) break; + } + + return $out; + } +} diff --git a/templates/components/Organisms/ReadingListList.html.twig b/templates/components/Organisms/ReadingListList.html.twig new file mode 100644 index 0000000..fca58d2 --- /dev/null +++ b/templates/components/Organisms/ReadingListList.html.twig @@ -0,0 +1,16 @@ +
+ {% set items = this.lists %} + {% if items is not empty %} + + {% else %} +

No reading lists yet.

+ {% endif %} +
+ diff --git a/templates/components/Organisms/ZineList.html.twig b/templates/components/Organisms/ZineList.html.twig index b8eb23c..40aaf88 100644 --- a/templates/components/Organisms/ZineList.html.twig +++ b/templates/components/Organisms/ZineList.html.twig @@ -1,4 +1,5 @@
+ {% if nzines is not empty %} {% for item in nzines %} {% set idx = indices[item.npub] is defined ? indices[item.npub] : null %} @@ -19,4 +20,7 @@
{% endfor %} + {% else %} +

No magazines yet.

+ {% endif %}
diff --git a/templates/home.html.twig b/templates/home.html.twig index 4dac114..b2065f1 100644 --- a/templates/home.html.twig +++ b/templates/home.html.twig @@ -18,4 +18,7 @@ {% block aside %}
Magazines
+ +
Lists
+ {% endblock %} diff --git a/templates/pages/category.html.twig b/templates/pages/category.html.twig index 1638cc4..95d90ef 100644 --- a/templates/pages/category.html.twig +++ b/templates/pages/category.html.twig @@ -4,7 +4,7 @@ - + {% endblock %} diff --git a/templates/reading_list/index.html.twig b/templates/reading_list/index.html.twig index 54770b1..ed450aa 100644 --- a/templates/reading_list/index.html.twig +++ b/templates/reading_list/index.html.twig @@ -3,8 +3,38 @@ {% block body %}

Your Reading Lists

Create and share curated reading lists.

+ + {% if lists is defined and lists|length %} +
    + {% for item in lists %} +
  • +
    +
    +

    {{ item.title }}

    + {% if item.summary %}

    {{ item.summary }}

    {% endif %} + slug: {{ item.slug ?: '—' }} • created: {{ item.createdAt|date('Y-m-d H:i') }} +
    +
    + Open Composer + {% if item.slug %} + View + + + + + {% endif %} +
    +
    +
  • + {% endfor %} +
+ {% else %} +

No reading lists found.

+ {% endif %} + {% endblock %}