Browse Source

Bugfixes all round

imwald
Nuša Pukšič 4 months ago
parent
commit
15fe31d1b6
  1. 2
      assets/styles/app.css
  2. 4
      src/Controller/ArticleController.php
  3. 8
      src/Controller/EventController.php
  4. 4
      src/Service/NostrClient.php
  5. 8
      src/Util/CommonMark/Converter.php
  6. 2
      src/Util/CommonMark/ImagesExtension/RawImageLinkExtension.php
  7. 13
      src/Util/CommonMark/ImagesExtension/RawImageLinkParser.php
  8. 25
      src/Util/CommonMark/NostrSchemeExtension/NostrEmbeddedCard.php
  9. 20
      src/Util/CommonMark/NostrSchemeExtension/NostrEmbeddedCardRenderer.php
  10. 12
      src/Util/CommonMark/NostrSchemeExtension/NostrSchemeExtension.php
  11. 44
      src/Util/CommonMark/NostrSchemeExtension/NostrSchemeParser.php
  12. 77
      templates/components/event_card.html.twig
  13. 15
      templates/event/index.html.twig
  14. 2
      templates/pages/author.html.twig

2
assets/styles/app.css

@ -144,7 +144,7 @@ svg.icon { @@ -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 */
}

4
src/Controller/ArticleController.php

@ -100,10 +100,10 @@ class ArticleController extends AbstractController @@ -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());

8
src/Controller/EventController.php

@ -4,6 +4,7 @@ declare(strict_types=1); @@ -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 @@ -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 @@ -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:

4
src/Service/NostrClient.php

@ -276,7 +276,7 @@ class NostrClient @@ -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 @@ -691,7 +691,9 @@ class NostrClient
{
$subscription = new Subscription();
$filter = new Filter();
if (!empty($kinds)) {
$filter->setKinds($kinds);
}
foreach ($filters as $key => $value) {
$method = 'set' . ucfirst($key);

8
src/Util/CommonMark/Converter.php

@ -2,6 +2,7 @@ @@ -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; @@ -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 @@ -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']));

2
src/Util/CommonMark/ImagesExtension/RawImageLinkExtension.php

@ -9,6 +9,6 @@ class RawImageLinkExtension implements ExtensionInterface @@ -9,6 +9,6 @@ class RawImageLinkExtension implements ExtensionInterface
{
public function register(EnvironmentBuilderInterface $environment): void
{
$environment->addInlineParser(new RawImageLinkParser());
$environment->addInlineParser(new RawImageLinkParser(),201);
}
}

13
src/Util/CommonMark/ImagesExtension/RawImageLinkParser.php

@ -2,10 +2,8 @@ @@ -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 @@ -21,13 +19,12 @@ class RawImageLinkParser implements InlineParserInterface
{
$cursor = $inlineContext->getCursor();
$match = $inlineContext->getFullMatch();
// Create an <img> 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;

25
src/Util/CommonMark/NostrSchemeExtension/NostrEmbeddedCard.php

@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
<?php
namespace App\Util\CommonMark\NostrSchemeExtension;
use League\CommonMark\Node\Inline\AbstractInline;
/**
* Class NostrEmbeddedCard
* Represents an embedded HTML card for Nostr events
*/
class NostrEmbeddedCard extends AbstractInline
{
private string $htmlContent;
public function __construct(string $htmlContent)
{
parent::__construct();
$this->htmlContent = $htmlContent;
}
public function getHtmlContent(): string
{
return $this->htmlContent;
}
}

20
src/Util/CommonMark/NostrSchemeExtension/NostrEmbeddedCardRenderer.php

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
<?php
namespace App\Util\CommonMark\NostrSchemeExtension;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
class NostrEmbeddedCardRenderer implements NodeRendererInterface
{
public function render(Node $node, ChildNodeRendererInterface $childRenderer)
{
if (!($node instanceof NostrEmbeddedCard)) {
throw new \InvalidArgumentException('Incompatible inline node type: ' . get_class($node));
}
// Return the raw HTML content for the embedded card
return $node->getHtmlContent();
}
}

12
src/Util/CommonMark/NostrSchemeExtension/NostrSchemeExtension.php

@ -2,25 +2,31 @@ @@ -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)
;
}

44
src/Util/CommonMark/NostrSchemeExtension/NostrSchemeParser.php

@ -2,6 +2,7 @@ @@ -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; @@ -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 @@ -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 @@ -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 */

77
templates/components/event_card.html.twig

@ -0,0 +1,77 @@ @@ -0,0 +1,77 @@
{# Embedded event card component #}
<div class="embedded-event-card" data-nevent="{{ nevent }}">
<div class="event-header">
{% if author %}
{% if author.image is defined %}
<img src="{{ author.image }}" class="avatar-small" alt="{{ author.name }}" onerror="this.style.display = 'none'" />
{% endif %}
<div class="author-info">
<strong>{{ author.name ?? 'Anonymous' }}</strong>
{% if author.nip05 is defined %}
<span class="nip05">{{ author.nip05 }}</span>
{% endif %}
</div>
{% endif %}
<div class="event-meta">
<span class="event-date">{{ event.created_at|date('M j, Y H:i') }}</span>
</div>
</div>
<div class="event-content line-clamp-5">
{{ event.content|markdown_to_html|mentionify }}
</div>
<div class="event-footer">
<a href="/e/{{ nevent }}" class="view-full">View full event</a>
</div>
</div>
<style>
.embedded-event-card {
border: 1px solid #e1e5e9;
border-radius: 8px;
padding: 12px;
margin: 8px 0;
background: #f8f9fa;
}
.embedded-event-card .event-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.embedded-event-card .avatar-small {
width: 24px;
height: 24px;
border-radius: 50%;
}
.embedded-event-card .author-info {
flex: 1;
}
.embedded-event-card .nip05 {
color: #6c757d;
font-size: 0.85em;
}
.embedded-event-card .event-meta {
color: #6c757d;
font-size: 0.8em;
}
.embedded-event-card .event-content {
margin: 8px 0;
line-height: 1.4;
}
.embedded-event-card .event-footer {
text-align: right;
}
.embedded-event-card .view-full {
color: #007bff;
text-decoration: none;
font-size: 0.85em;
}
</style>

15
templates/event/index.html.twig

@ -12,11 +12,6 @@ @@ -12,11 +12,6 @@
{% endif %}
<twig:Molecules:UserFromNpub ident="{{ event.pubkey }}" />
<div>
{% if author.about is defined %}
{{ author.about|markdown_to_html|mentionify }}
{% endif %}
</div>
<hr />
{% endif %}
<div class="event-meta">
@ -70,7 +65,6 @@ @@ -70,7 +65,6 @@
.event-container {
max-width: 800px;
margin: 2rem auto;
padding: 1.5rem;
background: #fff;
border-radius: 8px;
}
@ -81,14 +75,13 @@ @@ -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 @@ @@ -104,7 +97,6 @@
}
.link-list li {
margin-bottom: 0.5rem;
word-break: break-all;
}
@ -117,8 +109,7 @@ @@ -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;
}

2
templates/pages/author.html.twig

@ -9,7 +9,7 @@ @@ -9,7 +9,7 @@
<h1><twig:Atoms:NameOrNpub :author="author" :npub="npub"></twig:Atoms:NameOrNpub></h1>
<div>
{% if author.about is defined %}
{{ author.about|markdown_to_html|mentionify }}
{{ author.about|markdown_to_html|mentionify|linkify }}
{% endif %}
</div>

Loading…
Cancel
Save