From 15fe31d1b6db4507b7f796b8c8ce5c88939590e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nu=C5=A1a=20Puk=C5=A1i=C4=8D?= Date: Sat, 27 Sep 2025 18:25:25 +0200 Subject: [PATCH] Bugfixes all round --- assets/styles/app.css | 2 +- src/Controller/ArticleController.php | 4 +- src/Controller/EventController.php | 8 +- src/Service/NostrClient.php | 6 +- src/Util/CommonMark/Converter.php | 8 +- .../ImagesExtension/RawImageLinkExtension.php | 2 +- .../ImagesExtension/RawImageLinkParser.php | 13 ++-- .../NostrEmbeddedCard.php | 25 ++++++ .../NostrEmbeddedCardRenderer.php | 20 +++++ .../NostrSchemeExtension.php | 12 ++- .../NostrSchemeParser.php | 44 +++++++++-- templates/components/event_card.html.twig | 77 +++++++++++++++++++ templates/event/index.html.twig | 15 +--- templates/pages/author.html.twig | 2 +- 14 files changed, 197 insertions(+), 41 deletions(-) create mode 100644 src/Util/CommonMark/NostrSchemeExtension/NostrEmbeddedCard.php create mode 100644 src/Util/CommonMark/NostrSchemeExtension/NostrEmbeddedCardRenderer.php create mode 100644 templates/components/event_card.html.twig diff --git a/assets/styles/app.css b/assets/styles/app.css index 14c02d7..3a5097b 100644 --- a/assets/styles/app.css +++ b/assets/styles/app.css @@ -144,7 +144,7 @@ svg.icon { background-color: var(--color-bg); color: var(--color-text); padding: 0; - margin: 0 0 50px 0; + margin: 0 0 2rem 0; border-radius: 0; /* Sharp edges */ } diff --git a/src/Controller/ArticleController.php b/src/Controller/ArticleController.php index 27dac1f..1792f2c 100644 --- a/src/Controller/ArticleController.php +++ b/src/Controller/ArticleController.php @@ -100,10 +100,10 @@ class ArticleController extends AbstractController $cacheKey = 'article_' . $article->getEventId(); $cacheItem = $articlesCache->getItem($cacheKey); - if (!$cacheItem->isHit()) { + //if (!$cacheItem->isHit()) { $cacheItem->set($converter->convertToHTML($article->getContent())); $articlesCache->save($cacheItem); - } + //} $key = new Key(); $npub = $key->convertPublicKeyToBech32($article->getPubkey()); diff --git a/src/Controller/EventController.php b/src/Controller/EventController.php index 003e051..4a6a3d1 100644 --- a/src/Controller/EventController.php +++ b/src/Controller/EventController.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Controller; +use App\Enum\KindsEnum; use App\Service\NostrClient; use App\Service\NostrLinkParser; use App\Service\RedisCacheService; @@ -52,7 +53,7 @@ class EventController extends AbstractController case 'nevent': // Handle nevent identifier (event with additional metadata) - $relays = $data->relays ?? []; + $relays = $data->relays ?? null; $event = $nostrClient->getEventById($data->id, $relays); break; @@ -65,6 +66,11 @@ class EventController extends AbstractController 'relays' => $data->relays ?? [] ]; $event = $nostrClient->getEventByNaddr($decodedData); + if ($data->kind === KindsEnum::LONGFORM->value) { + // If it's a long-form content, redirect to the article page + $logger->info('Redirecting to article', ['identifier' => $data->identifier]); + return $this->redirectToRoute('article-slug', ['slug' => $data->identifier]); + } break; default: diff --git a/src/Service/NostrClient.php b/src/Service/NostrClient.php index a352a34..2a961b6 100644 --- a/src/Service/NostrClient.php +++ b/src/Service/NostrClient.php @@ -276,7 +276,7 @@ class NostrClient // Create request using the helper method $request = $this->createNostrRequest( - kinds: [], // Leave empty to accept any kind + kinds: [], filters: ['ids' => [$eventId]], relaySet: $relaySet ); @@ -691,7 +691,9 @@ class NostrClient { $subscription = new Subscription(); $filter = new Filter(); - $filter->setKinds($kinds); + if (!empty($kinds)) { + $filter->setKinds($kinds); + } foreach ($filters as $key => $value) { $method = 'set' . ucfirst($key); diff --git a/src/Util/CommonMark/Converter.php b/src/Util/CommonMark/Converter.php index b037b6c..e09f7f8 100644 --- a/src/Util/CommonMark/Converter.php +++ b/src/Util/CommonMark/Converter.php @@ -2,6 +2,7 @@ namespace App\Util\CommonMark; +use App\Service\NostrClient; use App\Service\RedisCacheService; use App\Util\CommonMark\ImagesExtension\RawImageLinkExtension; use App\Util\CommonMark\NostrSchemeExtension\NostrSchemeExtension; @@ -21,11 +22,14 @@ use League\CommonMark\Extension\Table\TableExtension; use League\CommonMark\Extension\TableOfContents\TableOfContentsExtension; use League\CommonMark\MarkdownConverter; use League\CommonMark\Renderer\HtmlDecorator; +use Twig\Environment as TwigEnvironment; readonly class Converter { public function __construct( - private RedisCacheService $redisCacheService + private RedisCacheService $redisCacheService, + private NostrClient $nostrClient, + private TwigEnvironment $twig ){} /** @@ -64,7 +68,7 @@ readonly class Converter $environment->addExtension(new TableExtension()); $environment->addExtension(new StrikethroughExtension()); // create a custom extension, that handles nostr mentions - $environment->addExtension(new NostrSchemeExtension($this->redisCacheService)); + $environment->addExtension(new NostrSchemeExtension($this->redisCacheService, $this->nostrClient, $this->twig)); $environment->addExtension(new SmartPunctExtension()); $environment->addExtension(new EmbedExtension()); $environment->addRenderer(Embed::class, new HtmlDecorator(new EmbedRenderer(), 'div', ['class' => 'embedded-content'])); diff --git a/src/Util/CommonMark/ImagesExtension/RawImageLinkExtension.php b/src/Util/CommonMark/ImagesExtension/RawImageLinkExtension.php index 049b247..55cd1aa 100644 --- a/src/Util/CommonMark/ImagesExtension/RawImageLinkExtension.php +++ b/src/Util/CommonMark/ImagesExtension/RawImageLinkExtension.php @@ -9,6 +9,6 @@ class RawImageLinkExtension implements ExtensionInterface { public function register(EnvironmentBuilderInterface $environment): void { - $environment->addInlineParser(new RawImageLinkParser()); + $environment->addInlineParser(new RawImageLinkParser(),201); } } diff --git a/src/Util/CommonMark/ImagesExtension/RawImageLinkParser.php b/src/Util/CommonMark/ImagesExtension/RawImageLinkParser.php index ad89224..2295c57 100644 --- a/src/Util/CommonMark/ImagesExtension/RawImageLinkParser.php +++ b/src/Util/CommonMark/ImagesExtension/RawImageLinkParser.php @@ -2,10 +2,8 @@ namespace App\Util\CommonMark\ImagesExtension; -use League\CommonMark\Node\Block\Paragraph; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Extension\CommonMark\Node\Inline\Image; -use League\CommonMark\Extension\CommonMark\Node\Inline\SoftBreak; use League\CommonMark\Parser\Inline\InlineParserMatch; use League\CommonMark\Parser\InlineParserContext; @@ -21,13 +19,12 @@ class RawImageLinkParser implements InlineParserInterface { $cursor = $inlineContext->getCursor(); $match = $inlineContext->getFullMatch(); - // Create an element instead of a text link - $image = new Image($match, ''); - $paragraph = new Paragraph(); - $paragraph->appendChild($image); - $inlineContext->getContainer()->appendChild($paragraph); - // Advance the cursor to consume the matched part (important!) + // Create an inline Image element directly (not wrapped in a paragraph) + $image = new Image($match, '', $match); + $inlineContext->getContainer()->appendChild($image); + + // Advance the cursor to consume the matched part $cursor->advanceBy(strlen($match)); return true; diff --git a/src/Util/CommonMark/NostrSchemeExtension/NostrEmbeddedCard.php b/src/Util/CommonMark/NostrSchemeExtension/NostrEmbeddedCard.php new file mode 100644 index 0000000..8d35900 --- /dev/null +++ b/src/Util/CommonMark/NostrSchemeExtension/NostrEmbeddedCard.php @@ -0,0 +1,25 @@ +htmlContent = $htmlContent; + } + + public function getHtmlContent(): string + { + return $this->htmlContent; + } +} diff --git a/src/Util/CommonMark/NostrSchemeExtension/NostrEmbeddedCardRenderer.php b/src/Util/CommonMark/NostrSchemeExtension/NostrEmbeddedCardRenderer.php new file mode 100644 index 0000000..150114d --- /dev/null +++ b/src/Util/CommonMark/NostrSchemeExtension/NostrEmbeddedCardRenderer.php @@ -0,0 +1,20 @@ +getHtmlContent(); + } +} diff --git a/src/Util/CommonMark/NostrSchemeExtension/NostrSchemeExtension.php b/src/Util/CommonMark/NostrSchemeExtension/NostrSchemeExtension.php index 65e767e..3441716 100644 --- a/src/Util/CommonMark/NostrSchemeExtension/NostrSchemeExtension.php +++ b/src/Util/CommonMark/NostrSchemeExtension/NostrSchemeExtension.php @@ -2,25 +2,31 @@ namespace App\Util\CommonMark\NostrSchemeExtension; +use App\Service\NostrClient; use App\Service\RedisCacheService; use League\CommonMark\Environment\EnvironmentBuilderInterface; use League\CommonMark\Extension\ExtensionInterface; +use Twig\Environment; class NostrSchemeExtension implements ExtensionInterface { - public function __construct(private readonly RedisCacheService $redisCacheService) - { + public function __construct( + private readonly RedisCacheService $redisCacheService, + private readonly NostrClient $nostrClient, + private readonly Environment $twig + ) { } public function register(EnvironmentBuilderInterface $environment): void { $environment ->addInlineParser(new NostrMentionParser($this->redisCacheService), 200) - ->addInlineParser(new NostrSchemeParser($this->redisCacheService), 199) + ->addInlineParser(new NostrSchemeParser($this->redisCacheService, $this->nostrClient, $this->twig), 199) ->addInlineParser(new NostrRawNpubParser($this->redisCacheService), 198) ->addRenderer(NostrSchemeData::class, new NostrEventRenderer(), 2) + ->addRenderer(NostrEmbeddedCard::class, new NostrEmbeddedCardRenderer(), 3) ->addRenderer(NostrMentionLink::class, new NostrMentionRenderer(), 1) ; } diff --git a/src/Util/CommonMark/NostrSchemeExtension/NostrSchemeParser.php b/src/Util/CommonMark/NostrSchemeExtension/NostrSchemeParser.php index c221d19..f86a999 100644 --- a/src/Util/CommonMark/NostrSchemeExtension/NostrSchemeParser.php +++ b/src/Util/CommonMark/NostrSchemeExtension/NostrSchemeParser.php @@ -2,6 +2,7 @@ namespace App\Util\CommonMark\NostrSchemeExtension; +use App\Service\NostrClient; use App\Service\RedisCacheService; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Parser\Inline\InlineParserMatch; @@ -11,16 +12,22 @@ use nostriphant\NIP19\Data\NAddr; use nostriphant\NIP19\Data\NEvent; use nostriphant\NIP19\Data\NProfile; use nostriphant\NIP19\Data\NPub; +use Twig\Environment; +use swentel\nostr\Key\Key; class NostrSchemeParser implements InlineParserInterface { private RedisCacheService $redisCacheService; + private NostrClient $nostrClient; + private Environment $twig; - public function __construct(RedisCacheService $redisCacheService) + public function __construct(RedisCacheService $redisCacheService, NostrClient $nostrClient, Environment $twig) { $this->redisCacheService = $redisCacheService; + $this->nostrClient = $nostrClient; + $this->twig = $twig; } public function getMatchDefinition(): InlineParserMatch @@ -45,8 +52,8 @@ class NostrSchemeParser implements InlineParserInterface /** @var NPub $object */ $object = $decoded->data; $profile = $this->redisCacheService->getMetadata($bechEncoded); - if (isset($profile['name'])) { - $inlineContext->getContainer()->appendChild(new NostrMentionLink($profile['name'], $bechEncoded)); + if (isset($profile->name)) { + $inlineContext->getContainer()->appendChild(new NostrMentionLink($profile->name, $bechEncoded)); } else { $inlineContext->getContainer()->appendChild(new NostrMentionLink(null, $bechEncoded)); } @@ -59,11 +66,32 @@ class NostrSchemeParser implements InlineParserInterface case 'nevent': /** @var NEvent $decodedEvent */ $decodedEvent = $decoded->data; - $eventId = $decodedEvent->id; - $relays = $decodedEvent->relays; - $author = $decodedEvent->author; - $kind = $decodedEvent->kind; - $inlineContext->getContainer()->appendChild(new NostrSchemeData('nevent', $bechEncoded, $relays, $author, $kind)); + + // Fetch the actual event data using the same logic as EventController + $event = $this->nostrClient->getEventById($decodedEvent->id, $decodedEvent->relays); + + if ($event) { + // Get author metadata if available + $authorMetadata = null; + if (isset($event->pubkey)) { + $key = new Key(); + $npub = $key->convertPublicKeyToBech32($event->pubkey); + $authorMetadata = $this->redisCacheService->getMetadata($npub); + } + + // Render the embedded event card + $eventCardHtml = $this->twig->render('components/event_card.html.twig', [ + 'event' => $event, + 'author' => $authorMetadata, + 'nevent' => $bechEncoded + ]); + + // Create a new node type for embedded HTML content + $inlineContext->getContainer()->appendChild(new NostrEmbeddedCard($eventCardHtml)); + } else { + // Fallback to simple link if event not found + $inlineContext->getContainer()->appendChild(new NostrSchemeData('nevent', $bechEncoded, $decodedEvent->relays, $decodedEvent->author, $decodedEvent->kind)); + } break; case 'naddr': /** @var NAddr $decodedEvent */ diff --git a/templates/components/event_card.html.twig b/templates/components/event_card.html.twig new file mode 100644 index 0000000..5af927d --- /dev/null +++ b/templates/components/event_card.html.twig @@ -0,0 +1,77 @@ +{# Embedded event card component #} +
+
+ {% if author %} + {% if author.image is defined %} + {{ author.name }} + {% endif %} +
+ {{ author.name ?? 'Anonymous' }} + {% if author.nip05 is defined %} + {{ author.nip05 }} + {% endif %} +
+ {% endif %} +
+ {{ event.created_at|date('M j, Y H:i') }} +
+
+
+ {{ event.content|markdown_to_html|mentionify }} +
+ +
+ + diff --git a/templates/event/index.html.twig b/templates/event/index.html.twig index eb44796..b09b9ba 100644 --- a/templates/event/index.html.twig +++ b/templates/event/index.html.twig @@ -12,11 +12,6 @@ {% endif %} -
- {% if author.about is defined %} - {{ author.about|markdown_to_html|mentionify }} - {% endif %} -

{% endif %}
@@ -70,7 +65,6 @@ .event-container { max-width: 800px; margin: 2rem auto; - padding: 1.5rem; background: #fff; border-radius: 8px; } @@ -81,14 +75,13 @@ align-items: flex-start; margin-bottom: 1.5rem; border-bottom: 1px solid #eee; - padding-bottom: 1rem; + padding: 1rem; } .event-content { + padding: 1rem; font-size: 1.1rem; line-height: 1.6; - margin-bottom: 2rem; - white-space: pre-wrap; } .nostr-links { @@ -104,7 +97,6 @@ } .link-list li { - margin-bottom: 0.5rem; word-break: break-all; } @@ -117,8 +109,7 @@ .event-footer { display: flex; justify-content: space-between; - margin-top: 1.5rem; - padding-top: 1rem; + padding: 1rem; border-top: 1px solid #eee; } diff --git a/templates/pages/author.html.twig b/templates/pages/author.html.twig index 3387ea9..99abb7c 100644 --- a/templates/pages/author.html.twig +++ b/templates/pages/author.html.twig @@ -9,7 +9,7 @@

{% if author.about is defined %} - {{ author.about|markdown_to_html|mentionify }} + {{ author.about|markdown_to_html|mentionify|linkify }} {% endif %}