diff --git a/assets/bootstrap.js b/assets/bootstrap.js index ea1a1c0..8004e10 100644 --- a/assets/bootstrap.js +++ b/assets/bootstrap.js @@ -3,6 +3,7 @@ import ArticleCommentsController from './controllers/article_comments_controller import CommentReplyController from './controllers/comment_reply_controller.js'; import CopyTextController from './controllers/copy_text_controller.js'; import UserHighlightTooltipController from './controllers/user_highlight_tooltip_controller.js'; +import NostrShareMenuController from './controllers/nostr_share_menu_controller.js'; const app = startStimulusApp(); if (typeof app.debug === 'boolean') { app.debug = false; @@ -29,3 +30,8 @@ try { } catch { /* already registered by the bundle */ } +try { + app.register('nostr-share-menu', NostrShareMenuController); +} catch { + /* already registered by the bundle */ +} diff --git a/assets/controllers/nostr_share_menu_controller.js b/assets/controllers/nostr_share_menu_controller.js new file mode 100644 index 0000000..fb964f0 --- /dev/null +++ b/assets/controllers/nostr_share_menu_controller.js @@ -0,0 +1,16 @@ +import { Controller } from '@hotwired/stimulus'; + +/** + * Closes the
dropdown after a list action (link or button) so the panel does not stay open. + */ +export default class extends Controller { + closeAfterMenuAction(event) { + const action = event.target.closest('.nostr-share-menu__list .nostr-share-menu__action'); + if (!action || !this.element.contains(action)) { + return; + } + queueMicrotask(() => { + this.element.removeAttribute('open'); + }); + } +} diff --git a/src/Service/NostrArticleDiscussionSupport.php b/src/Service/NostrArticleDiscussionSupport.php index 77f433c..73c7404 100644 --- a/src/Service/NostrArticleDiscussionSupport.php +++ b/src/Service/NostrArticleDiscussionSupport.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Service; use App\Enum\KindsEnum; +use App\Nostr\Nip19Codec; use swentel\nostr\Filter\Filter; /** @@ -13,6 +14,10 @@ use swentel\nostr\Filter\Filter; */ final class NostrArticleDiscussionSupport { + public function __construct( + private readonly Nip19Codec $nip19Codec, + ) { + } /** * @return array */ @@ -103,6 +108,10 @@ final class NostrArticleDiscussionSupport if ((int) ($event->kind ?? 0) !== KindsEnum::TEXT_NOTE->value) { return false; } + // Kind 1 “quotes” often lack `q`; clients embed nostr:naddr… in content instead. Those are not thread replies. + if ($this->isKind1NaddrBodyQuote($event, $coordinate)) { + return false; + } foreach ($event->tags ?? [] as $tag) { if (!\is_array($tag) || \count($tag) < 2) { continue; @@ -163,6 +172,72 @@ final class NostrArticleDiscussionSupport } } } + if ($kind === KindsEnum::TEXT_NOTE->value && $this->isKind1NaddrBodyQuote($event, $coordinate)) { + return true; + } + + return false; + } + + /** + * Kind-1 note that cites this article via nostr:naddr… in .content (no `q` tag) — not a threaded reply. + * Requires no {@code e} tags; if {@code e} tags are present, NIP-10 treats it as a thread reply. + */ + private function isKind1NaddrBodyQuote(object $event, string $coordinate): bool + { + if ($this->kind1HasThreadETag($event)) { + return false; + } + $content = (string) ($event->content ?? ''); + + return $this->contentNaddrReferencesCoordinate($content, $coordinate); + } + + private function kind1HasThreadETag(object $event): bool + { + foreach ($event->tags ?? [] as $tag) { + if (!\is_array($tag) || \count($tag) < 2) { + continue; + } + if (strtolower((string) ($tag[0] ?? '')) !== 'e') { + continue; + } + $id = strtolower(trim((string) ($tag[1] ?? ''))); + if (64 === \strlen($id) && ctype_xdigit($id)) { + return true; + } + } + + return false; + } + + private function contentNaddrReferencesCoordinate(string $content, string $coordinate): bool + { + if ($content === '' || !preg_match_all('/(?:nostr:)?(naddr1[a-z0-9]+)/i', $content, $matches)) { + return false; + } + $want = strtolower($coordinate); + foreach ($matches[1] as $bech) { + try { + $decoded = $this->nip19Codec->decode((string) $bech); + } catch (\Throwable) { + continue; + } + if ($decoded->type !== 'naddr' || !isset($decoded->data)) { + continue; + } + $d = $decoded->data; + $kind = (int) ($d->kind ?? 0); + $pk = strtolower((string) ($d->pubkey ?? '')); + $identifier = (string) ($d->identifier ?? ''); + if ($pk === '' || $identifier === '' || (64 !== \strlen($pk) || !ctype_xdigit($pk))) { + continue; + } + $built = $kind.':'.$pk.':'.$identifier; + if ($built === $want) { + return true; + } + } return false; } diff --git a/templates/components/Molecules/ArticleReplyComposer.html.twig b/templates/components/Molecules/ArticleReplyComposer.html.twig index a7aa1f9..0951c6f 100644 --- a/templates/components/Molecules/ArticleReplyComposer.html.twig +++ b/templates/components/Molecules/ArticleReplyComposer.html.twig @@ -18,7 +18,7 @@ >
-

Reply to this article on Nostr (NIP-22 kind 1111).

+

Comment on this article.

diff --git a/templates/components/Molecules/NostrPreview.html.twig b/templates/components/Molecules/NostrPreview.html.twig index 3c1c0cd..fc63972 100644 --- a/templates/components/Molecules/NostrPreview.html.twig +++ b/templates/components/Molecules/NostrPreview.html.twig @@ -17,7 +17,18 @@ {% if preview.data is not null %}
{% if preview.data.kind is defined %} - Kind: {{ preview.data.kind }} + {% set _pk = preview.data.kind %} + + {%- if _pk == 0 -%}Profile + {%- elseif _pk == 1 -%}Note + {%- elseif _pk == 6 or _pk == 16 -%}Repost + {%- elseif _pk == 7 -%}Reaction + {%- elseif _pk == 1111 -%}Comment + {%- elseif _pk == 9802 -%}Highlight + {%- elseif _pk == 30023 or _pk == 30024 -%}Article + {%- else -%}Event + {%- endif -%} + {% endif %}
{% endif %} diff --git a/templates/components/Molecules/NostrShareMenu.html.twig b/templates/components/Molecules/NostrShareMenu.html.twig index 5cc098e..982da46 100644 --- a/templates/components/Molecules/NostrShareMenu.html.twig +++ b/templates/components/Molecules/NostrShareMenu.html.twig @@ -2,7 +2,11 @@ {% set share = nostr_share_menu() %} {% endif %} {% if share is not null %} -
+
{% if event_menu|default(false) %} diff --git a/templates/components/Organisms/Comments.html.twig b/templates/components/Organisms/Comments.html.twig index ad46111..6bd1844 100644 --- a/templates/components/Organisms/Comments.html.twig +++ b/templates/components/Organisms/Comments.html.twig @@ -10,11 +10,11 @@ {% endif %} + {% if not is_nip18_repost %}
- {% if is_nip18_repost %} -

Repost

- {% else %} - - {% endif %} +
+ {% endif %} {% if not is_nip18_repost and cid != '' and commentLinks[cid] is defined and commentLinks[cid]|length > 0 %}