fix: reply buttons on dynamic webmentions + owner comment form

- Add .wm-reply-btn button and .wm-owner-reply-slot to dynamically
  created reply elements (parity with build-time Nunjucks template)
- Extract wireReplyButtons() so buttons are wired both on owner:detected
  and after dynamic replies are appended (fixes timing gap)
- Use data-wired attribute to prevent double-wiring
- Show comment form for site owner (isOwner) not just IndieAuth users
- Fix "Signed in as" display to use ownerProfile when user is null

Confab-Link: http://localhost:8080/sessions/184584f4-67e1-485a-aba8-02ac34a600fe
This commit is contained in:
Ricardo
2026-03-14 20:12:06 +01:00
parent a9b4300d7b
commit 0fe6ab0195
2 changed files with 39 additions and 9 deletions

View File

@@ -46,12 +46,13 @@
</form>
</div>
{# Comment form (shown when authenticated) #}
<div x-show="user" x-cloak>
{# Comment form (shown when authenticated via IndieAuth OR as site owner) #}
<div x-show="user || isOwner" x-cloak>
<div class="flex items-center gap-2 mb-3 text-sm text-surface-600 dark:text-surface-400">
<span>Signed in as</span>
<a x-bind:href="user?.url" class="font-medium hover:underline" x-text="user?.name || user?.url" target="_blank" rel="noopener"></a>
<button x-on:click="signOut()" class="text-xs underline hover:no-underline">Sign out</button>
<a x-bind:href="user?.url || ownerProfile?.url" class="font-medium hover:underline"
x-text="user?.name || ownerProfile?.name || user?.url || 'Owner'" target="_blank" rel="noopener"></a>
<button x-show="user" x-on:click="signOut()" class="text-xs underline hover:no-underline">Sign out</button>
</div>
<form x-on:submit.prevent="submitComment()">

View File

@@ -337,16 +337,36 @@
replyDiv.className = 'text-surface-700 dark:text-surface-300 prose dark:prose-invert prose-sm max-w-none';
replyDiv.textContent = content.text || '';
// Reply button (hidden by default, shown for owner)
const replyBtn = document.createElement('button');
replyBtn.className = 'wm-reply-btn hidden text-xs text-primary-600 dark:text-primary-400 hover:underline mt-2';
replyBtn.dataset.replyUrl = item.url || '';
replyBtn.dataset.platform = platform;
replyBtn.textContent = 'Reply';
contentDiv.appendChild(headerDiv);
contentDiv.appendChild(replyDiv);
contentDiv.appendChild(replyBtn);
wrapper.appendChild(avatarLink);
wrapper.appendChild(contentDiv);
li.appendChild(wrapper);
// Owner reply slot for threaded replies
const ownerReplySlot = document.createElement('div');
ownerReplySlot.className = 'wm-owner-reply-slot ml-13 mt-2';
li.appendChild(ownerReplySlot);
// Also set data attributes for build-time parity
li.dataset.wmSource = item['wm-source'] || '';
li.dataset.authorUrl = author.url || '';
// Prepend to show newest first
list.insertBefore(li, list.firstChild);
});
// Wire up new reply buttons if owner is already detected
wireReplyButtons();
}
function appendMentions(selector, items) {
@@ -595,13 +615,15 @@
}
});
// Show reply buttons and wire click handlers if owner is detected
// Listen for custom event dispatched by comments.js after async owner check
document.addEventListener('owner:detected', function() {
// Wire reply buttons: unhide and attach click handlers for unwired buttons
// Called from owner:detected handler AND after dynamic replies are appended
function wireReplyButtons() {
var ownerStore = Alpine.store && Alpine.store('owner');
if (!ownerStore || !ownerStore.isOwner) return;
document.querySelectorAll('.wm-reply-btn').forEach(function(btn) {
if (btn.dataset.wired) return; // already wired
btn.dataset.wired = 'true';
btn.classList.remove('hidden');
btn.addEventListener('click', function() {
var replyUrl = btn.dataset.replyUrl;
@@ -614,10 +636,8 @@
var commentsEl = document.querySelector('[x-data*="commentsSection"]');
if (commentsEl && commentsEl.__x) {
commentsEl.__x.$data.startReply(replyUrl, platform, replyUrl, syndicateTo);
// Scroll to the comments section where the form will appear
commentsEl.scrollIntoView({ behavior: 'smooth', block: 'start' });
} else if (window.Alpine) {
// Alternative: dispatch event for Alpine component to handle
var evt = new CustomEvent('owner-reply', {
detail: { replyUrl: replyUrl, platform: platform, syndicateTo: syndicateTo },
bubbles: true
@@ -626,6 +646,15 @@
}
});
});
}
// Show reply buttons and wire click handlers if owner is detected
// Listen for custom event dispatched by comments.js after async owner check
document.addEventListener('owner:detected', function() {
var ownerStore = Alpine.store && Alpine.store('owner');
if (!ownerStore || !ownerStore.isOwner) return;
wireReplyButtons();
// Render threaded owner replies under matching webmention cards
var ownerReplies = ownerStore.replies || [];