Browse Source

comments to threads fixed

master
Silberengel 1 month ago
parent
commit
30c2cef9ee
  1. 72
      src/lib/components/modals/PublicationStatusModal.svelte
  2. 39
      src/lib/modules/comments/CommentForm.svelte
  3. 76
      src/lib/modules/comments/CommentThread.svelte

72
src/lib/components/modals/PublicationStatusModal.svelte

@ -12,16 +12,24 @@ @@ -12,16 +12,24 @@
let autoCloseTimeout: ReturnType<typeof setTimeout> | null = null;
$effect(() => {
// Clear any existing timeout when modal state changes
if (autoCloseTimeout) {
clearTimeout(autoCloseTimeout);
autoCloseTimeout = null;
}
if (open && results) {
// Auto-close after 30 seconds
autoCloseTimeout = setTimeout(() => {
open = false;
autoCloseTimeout = null;
}, 30000);
}
return () => {
if (autoCloseTimeout) {
clearTimeout(autoCloseTimeout);
autoCloseTimeout = null;
}
};
});
@ -33,6 +41,37 @@ @@ -33,6 +41,37 @@
autoCloseTimeout = null;
}
}
/**
* Convert URLs in text to clickable links
*/
function linkify(text: string): Array<string | { type: 'link'; url: string; text: string }> {
const urlRegex = /(https?:\/\/[^\s]+)/g;
const parts: Array<string | { type: 'link'; url: string; text: string }> = [];
let lastIndex = 0;
let match;
while ((match = urlRegex.exec(text)) !== null) {
// Add text before the URL
if (match.index > lastIndex) {
parts.push(text.substring(lastIndex, match.index));
}
// Add the URL as a link object
parts.push({
type: 'link',
url: match[0],
text: match[0]
});
lastIndex = match.index + match[0].length;
}
// Add remaining text
if (lastIndex < text.length) {
parts.push(text.substring(lastIndex));
}
return parts.length > 0 ? parts : [text];
}
</script>
{#if open && results}
@ -68,7 +107,16 @@ @@ -68,7 +107,16 @@
<ul>
{#each results.failed as { relay, error }}
<li>
<strong>{relay}:</strong> {error}
<strong>{relay}:</strong>
<span class="error-message">
{#each linkify(error) as part}
{#if typeof part === 'object' && part.type === 'link'}
<a href={part.url} target="_blank" rel="noopener noreferrer" class="error-link">{part.text}</a>
{:else}
{part}
{/if}
{/each}
</span>
</li>
{/each}
</ul>
@ -164,6 +212,28 @@ @@ -164,6 +212,28 @@
padding: 0.25rem 0;
}
.error-message {
word-break: break-word;
}
.error-link {
color: #3b82f6;
text-decoration: underline;
cursor: pointer;
}
.error-link:hover {
color: #2563eb;
}
:global(.dark) .error-link {
color: #60a5fa;
}
:global(.dark) .error-link:hover {
color: #93c5fd;
}
.modal-footer {
padding: 1rem;
border-top: 1px solid #cbd5e1;

39
src/lib/modules/comments/CommentForm.svelte

@ -2,6 +2,9 @@ @@ -2,6 +2,9 @@
import { sessionManager } from '../../services/auth/session-manager.js';
import { signAndPublish } from '../../services/nostr/auth-handler.js';
import { nostrClient } from '../../services/nostr/nostr-client.js';
import { relayManager } from '../../services/nostr/relay-manager.js';
import { fetchRelayLists } from '../../services/user-data.js';
import PublicationStatusModal from '../../components/modals/PublicationStatusModal.svelte';
import type { NostrEvent } from '../../types/nostr.js';
interface Props {
@ -17,6 +20,8 @@ @@ -17,6 +20,8 @@
let content = $state('');
let publishing = $state(false);
let includeClientTag = $state(true);
let showStatusModal = $state(false);
let publicationResults: { success: string[]; failed: Array<{ relay: string; error: string }> } | null = $state(null);
/**
* Determine what kind of reply to create
@ -106,18 +111,40 @@ @@ -106,18 +111,40 @@
content: content.trim()
};
const config = nostrClient.getConfig();
const result = await signAndPublish(event, [...config.defaultRelays]);
// Get target's inbox if replying to someone
let targetInbox: string[] | undefined;
const targetPubkey = parentEvent?.pubkey || rootEvent?.pubkey;
if (targetPubkey && targetPubkey !== sessionManager.getCurrentPubkey()) {
try {
const { inbox } = await fetchRelayLists(targetPubkey);
targetInbox = inbox;
} catch (error) {
console.warn('Failed to fetch target inbox relays:', error);
// Continue without target inbox
}
}
// Use proper relay selection for comments
const publishRelays = relayManager.getCommentPublishRelays(targetInbox);
const result = await signAndPublish(event, publishRelays);
// Show publication status modal
publicationResults = result;
showStatusModal = true;
if (result.success.length > 0) {
content = '';
onPublished?.();
} else {
alert('Failed to publish comment');
}
} catch (error) {
console.error('Error publishing comment:', error);
alert('Error publishing comment');
// Show error in modal if possible, otherwise alert
const errorMessage = error instanceof Error ? error.message : String(error);
publicationResults = {
success: [],
failed: [{ relay: 'Unknown', error: errorMessage }]
};
showStatusModal = true;
} finally {
publishing = false;
}
@ -162,6 +189,8 @@ @@ -162,6 +189,8 @@
</button>
</div>
</div>
<PublicationStatusModal bind:open={showStatusModal} bind:results={publicationResults} />
</div>
<style>

76
src/lib/modules/comments/CommentThread.svelte

@ -397,9 +397,79 @@ @@ -397,9 +397,79 @@
replyingTo = replyEvent;
}
function handleCommentPublished() {
async function handleCommentPublished() {
replyingTo = null;
loadComments();
// Wait a short delay to allow the comment to propagate to relays
await new Promise(resolve => setTimeout(resolve, 1500));
// Reload comments without using cache to ensure we get the new comment
await loadCommentsFresh();
}
async function loadCommentsFresh() {
if (!threadId) {
loading = false;
return;
}
loading = true;
try {
// Use all relay sources: profileRelays + defaultRelays + user's inboxes + user's localrelays
const allRelays = relayManager.getProfileReadRelays();
const replyFilters: any[] = [];
// Always fetch kind 1111 comments - check both e and E tags, and a and A tags
replyFilters.push(
{ kinds: [1111], '#e': [threadId] }, // Lowercase e tag
{ kinds: [1111], '#E': [threadId] }, // Uppercase E tag (NIP-22)
{ kinds: [1111], '#a': [threadId] }, // Lowercase a tag (some clients use wrong tags)
{ kinds: [1111], '#A': [threadId] } // Uppercase A tag (NIP-22 for addressable events)
);
// For kind 1 events, fetch kind 1 replies
// Also fetch kind 1 replies for any event (some apps use kind 1 for everything)
replyFilters.push({ kinds: [1], '#e': [threadId] });
// Fetch yak backs (kind 1244) - voice replies
replyFilters.push({ kinds: [1244], '#e': [threadId] });
// Fetch zap receipts (kind 9735)
replyFilters.push({ kinds: [9735], '#e': [threadId] });
console.log('CommentThread: Reloading comments after publish for threadId:', threadId);
// Don't use cache when reloading after publishing - we want fresh data
const allReplies = await nostrClient.fetchEvents(
replyFilters,
allRelays,
{ useCache: false, cacheResults: true, timeout: 10000 }
);
console.log('CommentThread: Fetched', allReplies.length, 'replies (fresh)');
// Filter to only replies that reference the root
const rootReplies = allReplies.filter(reply => referencesRoot(reply));
console.log('CommentThread: Root replies:', rootReplies.length);
// Separate by type
comments = rootReplies.filter(e => e.kind === 1111);
kind1Replies = rootReplies.filter(e => e.kind === 1);
yakBacks = rootReplies.filter(e => e.kind === 1244);
zapReceipts = rootReplies.filter(e => e.kind === 9735);
console.log('CommentThread: Separated - comments:', comments.length, 'kind1Replies:', kind1Replies.length, 'yakBacks:', yakBacks.length, 'zapReceipts:', zapReceipts.length);
// Recursively fetch all nested replies (non-blocking - let it run in background)
fetchNestedReplies().catch((error) => {
console.error('Error fetching nested replies:', error);
});
} catch (error) {
console.error('Error reloading comments:', error);
} finally {
loading = false;
}
}
/**
@ -448,7 +518,7 @@ @@ -448,7 +518,7 @@
comment={item.event}
parentEvent={parent}
onReply={handleReply}
rootEventKind={rootKind}
rootEventKind={rootKind ?? undefined}
/>
{:else if item.type === 'reply'}
<!-- Kind 1 reply - render as FeedPost -->

Loading…
Cancel
Save