Compare commits

...

2 Commits

  1. 6
      assets/bootstrap.js
  2. 30
      assets/controllers/copy_text_controller.js
  3. 23
      assets/styles/app.css
  4. 9
      src/Controller/ArticleController.php
  5. 5
      templates/components/Organisms/Comments.html.twig
  6. 15
      templates/partial/author_profile_header.html.twig

6
assets/bootstrap.js vendored

@ -1,6 +1,7 @@
import { startStimulusApp } from '@symfony/stimulus-bundle'; import { startStimulusApp } from '@symfony/stimulus-bundle';
import ArticleCommentsController from './controllers/article_comments_controller.js'; import ArticleCommentsController from './controllers/article_comments_controller.js';
import CommentReplyController from './controllers/comment_reply_controller.js'; import CommentReplyController from './controllers/comment_reply_controller.js';
import CopyTextController from './controllers/copy_text_controller.js';
const app = startStimulusApp(); const app = startStimulusApp();
@ -15,3 +16,8 @@ try {
} catch { } catch {
/* already registered by the bundle */ /* already registered by the bundle */
} }
try {
app.register('copy-text', CopyTextController);
} catch {
/* already registered by the bundle */
}

30
assets/controllers/copy_text_controller.js

@ -0,0 +1,30 @@
import { Controller } from '@hotwired/stimulus';
/**
* Copies data-copy-text-text-value to the clipboard (e.g. full payment URI for wallets).
*/
export default class extends Controller {
static values = { text: String };
static targets = ['button'];
async copy() {
const t = this.textValue ?? '';
if (t === '') {
return;
}
try {
await navigator.clipboard.writeText(t);
const btn = this.hasButtonTarget ? this.buttonTarget : this.element.querySelector('button');
if (btn) {
const prev = btn.textContent;
btn.textContent = 'Copied';
window.setTimeout(() => {
btn.textContent = prev;
}, 2000);
}
} catch (e) {
console.warn('Copy failed', e);
}
}
}

23
assets/styles/app.css

@ -614,13 +614,34 @@ footer a {
.author-profile__meta-value, .author-profile__meta-value,
.author-profile__identity-link, .author-profile__identity-link,
.author-profile__payment-link { .author-profile__payment-addr {
min-width: 0; min-width: 0;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.author-profile__payment-value {
display: flex;
align-items: center;
gap: 0.4rem;
min-width: 0;
max-width: 100%;
}
.author-profile__payment-addr {
flex: 1 1 auto;
color: inherit;
text-decoration: none;
}
.author-profile__copy-btn {
flex-shrink: 0;
font-size: 0.75rem;
padding: 0.2rem 0.5rem;
line-height: 1.2;
}
/* NIP-05: ellipsis long addresses; keep ✓ immediately after the (truncated) text, not at the column edge. */ /* NIP-05: ellipsis long addresses; keep ✓ immediately after the (truncated) text, not at the column edge. */
.author-profile__nip05-value { .author-profile__nip05-value {
display: flex; display: flex;

9
src/Controller/ArticleController.php

@ -137,7 +137,7 @@ class ArticleController extends AbstractController
): array { ): array {
$coordparts = explode(':', $coordinate, 3); $coordparts = explode(':', $coordinate, 3);
$articleKind = isset($coordparts[0]) && ctype_digit($coordparts[0]) ? (int) $coordparts[0] : 30023; $articleKind = isset($coordparts[0]) && ctype_digit($coordparts[0]) ? (int) $coordparts[0] : 30023;
$articleAuthorPubkey = $coordparts[1] ?? ''; $articleAuthorPubkey = strtolower(trim((string) ($coordparts[1] ?? '')));
$articleReplyTags = null; $articleReplyTags = null;
if ($articleAuthorPubkey !== '' && 64 === \strlen($articleAuthorPubkey) && ctype_xdigit($articleAuthorPubkey)) { if ($articleAuthorPubkey !== '' && 64 === \strlen($articleAuthorPubkey) && ctype_xdigit($articleAuthorPubkey)) {
@ -162,6 +162,9 @@ class ArticleController extends AbstractController
'authorPubkey' => $articleAuthorPubkey, 'authorPubkey' => $articleAuthorPubkey,
'expectedTags' => $articleReplyTags, 'expectedTags' => $articleReplyTags,
]; ];
}
if ($userMayReply) {
/** @var array<int, object> $list */ /** @var array<int, object> $list */
$list = $data['list'] ?? []; $list = $data['list'] ?? [];
foreach ($list as $row) { foreach ($list as $row) {
@ -172,8 +175,8 @@ class ArticleController extends AbstractController
if ($k !== KindsEnum::COMMENTS->value) { if ($k !== KindsEnum::COMMENTS->value) {
continue; continue;
} }
$cid = (string) ($row->id ?? ''); $cid = strtolower(trim((string) ($row->id ?? '')));
$cpk = (string) ($row->pubkey ?? ''); $cpk = strtolower(trim((string) ($row->pubkey ?? '')));
if ($cid === '' || 64 !== \strlen($cid) || !ctype_xdigit($cid)) { if ($cid === '' || 64 !== \strlen($cid) || !ctype_xdigit($cid)) {
continue; continue;
} }

5
templates/components/Organisms/Comments.html.twig

@ -1,5 +1,6 @@
{% set ctx = comment_reply_context|default(null) %} {% set ctx = comment_reply_context|default(null) %}
{% if ctx and ctx.can_publish|default(false) and ctx.rows|default([])|length > 0 %} {# `rows` can be non-empty for nested replies when the article-level NIP-22 row is missing; do not require length > 0. #}
{% if ctx and ctx.can_publish|default(false) %}
{% for row in ctx.rows %} {% for row in ctx.rows %}
{% if row.mode|default('') == 'article' %} {% if row.mode|default('') == 'article' %}
<div <div
@ -59,7 +60,7 @@
<div class="comments"> <div class="comments">
{% for item in list %} {% for item in list %}
{% set cid = item.id|default('') %} {% set cid = item.id|default('')|lower %}
{% set cpk = item.pubkey|default('') %} {% set cpk = item.pubkey|default('') %}
{% set cts = item.created_at|default(null) %} {% set cts = item.created_at|default(null) %}
{% set cdepth = item.unfold_depth|default(0) %} {% set cdepth = item.unfold_depth|default(0) %}

15
templates/partial/author_profile_header.html.twig

@ -51,7 +51,20 @@
{% for row in profile_payment_links %} {% for row in profile_payment_links %}
<li class="author-profile__payment author-profile__meta-line"> <li class="author-profile__payment author-profile__meta-line">
<span class="author-profile__payment-type"{% if row.display_type_label|default('')|trim == '' %} aria-hidden="true"{% endif %}>{{ row.display_type_label|default('')|e }}</span> <span class="author-profile__payment-type"{% if row.display_type_label|default('')|trim == '' %} aria-hidden="true"{% endif %}>{{ row.display_type_label|default('')|e }}</span>
<a class="author-profile__payment-link author-profile__meta-value" href="{{ row.href|e('html_attr') }}" target="_blank" rel="nofollow noopener noreferrer">{{ row.label|e }}</a> <div
class="author-profile__payment-value"
data-controller="copy-text"
data-copy-text-text-value="{{ row.href|e('html_attr') }}"
>
<span class="author-profile__payment-addr author-profile__meta-value" title="{{ row.href|e('html_attr') }}">{{ row.label|e }}</span>
<button
type="button"
class="author-profile__copy-btn btn btn-secondary btn-sm"
data-copy-text-target="button"
data-action="click->copy-text#copy"
title="Copy full payment address / URI for your wallet"
>Copy</button>
</div>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>

Loading…
Cancel
Save