Browse Source

gray bar navigation

master
silberengel 7 months ago
parent
commit
b6c30352e2
  1. 123
      src/lib/components/Notifications.svelte
  2. 34
      src/lib/components/RelayInfoList.svelte
  3. 28
      src/lib/utils/kind24_utils.ts

123
src/lib/components/Notifications.svelte

@ -17,6 +17,22 @@ @@ -17,6 +17,22 @@
const { event } = $props<{ event: NDKEvent }>();
// Handle navigation events from quoted messages
$effect(() => {
if (typeof window !== 'undefined') {
const handleJumpToMessage = (e: Event) => {
const customEvent = e as CustomEvent;
jumpToMessageInFeed(customEvent.detail);
};
window.addEventListener('jump-to-message', handleJumpToMessage);
return () => {
window.removeEventListener('jump-to-message', handleJumpToMessage);
};
}
});
// Component state
let notifications = $state<NDKEvent[]>([]);
let publicMessages = $state<NDKEvent[]>([]);
@ -76,7 +92,9 @@ @@ -76,7 +92,9 @@
function getNeventUrl(event: NDKEvent): string {
const relays = getAvailableRelays();
return neventEncode(event, relays);
const nevent = neventEncode(event, relays);
console.log('Generated nevent for event:', event.id, '→', nevent);
return nevent;
}
function formatDate(timestamp: number): string {
@ -97,15 +115,28 @@ @@ -97,15 +115,28 @@
}
function renderContentWithLinks(content: string): string {
console.log("[Notifications] Rendering content:", content);
// Parse markdown links [text](url) and convert to HTML
let rendered = content.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" class="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-200 underline">$1</a>');
// Also handle the new quote format: "> LINK: nevent://..." and convert to button
rendered = rendered.replace(/> LINK: (nevent:\/\/[^\s\n]+)/g, '> <button onclick="window.location.href=\'$1\'" class="inline-block px-2 py-1 text-xs bg-blue-600 hover:bg-blue-700 dark:bg-blue-600 dark:hover:bg-blue-700 text-white rounded font-medium transition-colors">View Original Message</button>');
// Handle quote format and convert to small gray bars like Jumble
const patterns = [
/> QUOTED: ([^•]*?) • LINK:\s*\n(nevent[^\s]*)/g,
/> QUOTED: ([^\n]*?)\n> LINK: (nevent[^\s]*)/g,
/> QUOTED: ([^•]*?) • LINK:\s*(nevent[^\s]*)/g,
];
for (const pattern of patterns) {
const beforeReplace = rendered;
rendered = rendered.replace(pattern, (match, quotedText, neventUrl) => {
const encodedUrl = neventUrl.replace(/'/g, '&#39;');
const cleanQuotedText = quotedText.trim();
return `<div class="block w-fit my-2 px-3 py-2 bg-gray-200 dark:bg-gray-700 border-l-2 border-gray-400 dark:border-gray-500 rounded cursor-pointer hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors text-sm text-gray-600 dark:text-gray-300" onclick="window.dispatchEvent(new CustomEvent('jump-to-message', { detail: '${encodedUrl}' }))">${cleanQuotedText}</div>`;
});
if (beforeReplace !== rendered) {
break;
}
}
console.log("[Notifications] Rendered content:", rendered);
return rendered;
}
@ -122,9 +153,43 @@ @@ -122,9 +153,43 @@
}
function navigateToEvent(nevent: string) {
// Navigate to the events search page with this specific event
goto(`/events?id=${nevent}`);
}
function jumpToMessageInFeed(nevent: string) {
// Switch to public messages tab and scroll to the specific message
notificationMode = "public-messages";
// Try to find and scroll to the specific message
setTimeout(() => {
try {
// Decode the nevent to get the event ID
const decoded = nip19.decode(nevent);
if (decoded.type === 'nevent' && decoded.data.id) {
const eventId = decoded.data.id;
// Find the message in our public messages
const targetMessage = publicMessages.find(msg => msg.id === eventId);
if (targetMessage) {
// Try to scroll to the element if it exists in the DOM
const element = document.querySelector(`[data-event-id="${eventId}"]`);
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
// Briefly highlight the message
element.classList.add('ring-2', 'ring-blue-500');
setTimeout(() => {
element.classList.remove('ring-2', 'ring-blue-500');
}, 2000);
}
}
}
} catch (error) {
console.warn('Failed to jump to message:', error);
}
}, 100);
}
function filterByUser(pubkey: string) {
filteredByUser = filteredByUser === pubkey ? null : pubkey;
}
@ -256,9 +321,7 @@ @@ -256,9 +321,7 @@
.filter(relay => replyRelaySet.has(relay))
.slice(0, 3);
console.log('[Notifications] Got relay set:', result);
console.log('[Notifications] Filtered sender outbox relays:', senderOutboxRelays);
console.log('[Notifications] Filtered recipient inbox relays:', recipientInboxRelays);
}
} catch (error) {
console.error("Error getting relay information:", error);
@ -545,7 +608,7 @@ @@ -545,7 +608,7 @@
{#each filteredMessages.slice(0, 20) as message}
{@const authorProfile = authorProfiles.get(message.pubkey)}
{@const isFromUser = message.pubkey === $userStore.pubkey}
<div class="p-4 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
<div class="p-4 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors" data-event-id="{message.id}">
<div class="flex items-start gap-3 {isFromUser ? 'flex-row-reverse' : ''}">
<!-- Author Profile Picture -->
<div class="flex-shrink-0 relative">
@ -602,6 +665,13 @@ @@ -602,6 +665,13 @@
<span class="text-xs text-gray-500 dark:text-gray-400">
{message.created_at ? formatDate(message.created_at) : "Unknown date"}
</span>
<button
class="text-xs text-primary-600 dark:text-primary-400 hover:text-primary-800 dark:hover:text-primary-200 underline font-mono"
onclick={() => navigateToEvent(getNeventUrl(message))}
title="Click to view event"
>
{getNeventUrl(message).slice(0, 16)}...
</button>
</div>
<!-- Author Name -->
@ -622,17 +692,7 @@ @@ -622,17 +692,7 @@
</div>
{/if}
<div class="flex items-center gap-2 {isFromUser ? 'justify-end' : ''}">
<button
class="text-xs text-primary-600 dark:text-primary-400 hover:text-primary-800 dark:hover:text-primary-200 underline font-medium"
onclick={() => navigateToEvent(getNeventUrl(message))}
>
View Event
</button>
<span class="text-xs text-gray-500 dark:text-gray-400 font-mono">
{getNeventUrl(message).slice(0, 16)}...
</span>
</div>
</div>
</div>
@ -676,7 +736,7 @@ @@ -676,7 +736,7 @@
<!-- Relay Information -->
<div class="mt-3 p-3 bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg">
{#if replyRelays.length > 0}
{@const debugInfo = console.log('[Notifications] Rendering RelayInfoList with:', { replyRelays, recipientInboxRelays, senderOutboxRelays })}
<RelayInfoList
relays={replyRelays}
inboxRelays={recipientInboxRelays}
@ -743,6 +803,13 @@ @@ -743,6 +803,13 @@
<span class="text-xs text-gray-500 dark:text-gray-400">
{notification.created_at ? formatDate(notification.created_at) : "Unknown date"}
</span>
<button
class="text-xs text-primary-600 dark:text-primary-400 hover:text-primary-800 dark:hover:text-primary-200 underline font-mono"
onclick={() => navigateToEvent(getNeventUrl(notification))}
title="Click to view event"
>
{getNeventUrl(notification).slice(0, 16)}...
</button>
</div>
<!-- Author Name -->
@ -763,17 +830,7 @@ @@ -763,17 +830,7 @@
</div>
{/if}
<div class="flex items-center gap-2">
<button
class="text-xs text-primary-600 dark:text-primary-400 hover:text-primary-800 dark:hover:text-primary-200 underline font-medium"
onclick={() => navigateToEvent(getNeventUrl(notification))}
>
View Event
</button>
<span class="text-xs text-gray-500 dark:text-gray-400 font-mono">
{getNeventUrl(notification).slice(0, 16)}...
</span>
</div>
</div>
</div>
</div>

34
src/lib/components/RelayInfoList.svelte

@ -25,18 +25,10 @@ @@ -25,18 +25,10 @@
label: string;
};
// AI-NOTE: Updated to show only top-3 inboxes and top-3 outboxes as intended
// Categorize relays by their function (inbox/outbox/both)
const categorizedRelays = $derived(() => {
const inbox = new Set(inboxRelays);
const outbox = new Set(outboxRelays);
console.log('[RelayInfoList] Categorizing relays:', {
relays: relays.length,
inboxRelays: inboxRelays.length,
outboxRelays: outboxRelays.length
});
// Create a map of all relays with their categories
const relayCategories = new Map<string, CategorizedRelay>();
// Process inbox relays (up to top 3)
@ -58,29 +50,19 @@ @@ -58,29 +50,19 @@
}
});
// Only include relays that are actually in the top-3 lists
// This ensures we only show the intended top-3 inboxes and top-3 outboxes
const categorized = Array.from(relayCategories.values());
console.log('[RelayInfoList] Categorized relays count:', categorized.length);
return categorized;
return Array.from(relayCategories.values());
});
// Group by category
// Group by category for display
const groupedRelays = $derived(() => {
const categorized = categorizedRelays();
console.log('[RelayInfoList] Grouping categorized relays');
const groups = {
return {
both: categorized.filter((r: CategorizedRelay) => r.category === 'both'),
inbox: categorized.filter((r: CategorizedRelay) => r.category === 'inbox'),
outbox: categorized.filter((r: CategorizedRelay) => r.category === 'outbox'),
other: categorized.filter((r: CategorizedRelay) => r.category === 'other')
};
console.log('[RelayInfoList] Grouped relays:', Object.fromEntries(
Object.entries(groups).map(([key, relays]) => [key, relays.length])
));
return groups;
});
async function loadRelayInfos() {
@ -99,12 +81,6 @@ @@ -99,12 +81,6 @@
// Load relay info when categorized relays change
$effect(() => {
const categorized = categorizedRelays();
console.log('[RelayInfoList] Categorized relays changed:', {
total: categorized.length,
byCategory: Object.fromEntries(
Object.entries(groupedRelays()).map(([key, relays]) => [key, relays.length])
)
});
if (categorized.length > 0) {
loadRelayInfos();
}
@ -134,7 +110,6 @@ @@ -134,7 +110,6 @@
<div class="space-y-2">
{#if showLabels && !compact}
{@const categorizedCount = categorizedRelays().length}
{@const debugCategorized = console.log('[RelayInfoList] Debug - categorized relays:', categorizedRelays())}
<div class="text-sm font-medium text-gray-700 dark:text-gray-300">
Publishing to {categorizedCount} relay(s):
</div>
@ -147,7 +122,6 @@ @@ -147,7 +122,6 @@
</div>
{:else}
{@const categorized = categorizedRelays()}
{@const debugCategorized = console.log('[RelayInfoList] Debug - categorized relays:', categorized)}
<div class="space-y-1">
{#each categorized as { relay, category, label }}

28
src/lib/utils/kind24_utils.ts

@ -13,7 +13,7 @@ import { nip19 } from "nostr-tools"; @@ -13,7 +13,7 @@ import { nip19 } from "nostr-tools";
*/
async function getUseroutboxRelays(ndk: NDK, user: NDKUser): Promise<string[]> {
try {
console.debug('[kind24_utils] Fetching outbox relays for user:', user.pubkey);
const relayList = await ndk.fetchEvent(
{
kinds: [10002],
@ -22,16 +22,11 @@ async function getUseroutboxRelays(ndk: NDK, user: NDKUser): Promise<string[]> { @@ -22,16 +22,11 @@ async function getUseroutboxRelays(ndk: NDK, user: NDKUser): Promise<string[]> {
);
if (!relayList) {
console.debug('[kind24_utils] No relay list found for user');
return [];
}
console.debug('[kind24_utils] Found relay list event:', relayList.id);
console.debug('[kind24_utils] Relay list tags:', relayList.tags);
const outboxRelays: string[] = [];
relayList.tags.forEach((tag) => {
console.debug('[kind24_utils] Processing tag:', tag);
if (tag[0] === 'r' && tag[1]) {
// NIP-65: r tags with optional inbox/outbox markers
const marker = tag[2];
@ -39,15 +34,15 @@ async function getUseroutboxRelays(ndk: NDK, user: NDKUser): Promise<string[]> { @@ -39,15 +34,15 @@ async function getUseroutboxRelays(ndk: NDK, user: NDKUser): Promise<string[]> {
// If no marker or marker is 'outbox', it's a outbox relay
// If marker is 'inbox', it's also a outbox relay (NIP-65 allows both)
outboxRelays.push(tag[1]);
console.debug('[kind24_utils] Added outbox relay:', tag[1]);
}
}
});
console.debug('[kind24_utils] Final outbox relays:', outboxRelays);
return outboxRelays;
} catch (error) {
console.info('[kind24_utils] Error fetching user outbox relays:', error);
return [];
}
}
@ -60,7 +55,7 @@ async function getUseroutboxRelays(ndk: NDK, user: NDKUser): Promise<string[]> { @@ -60,7 +55,7 @@ async function getUseroutboxRelays(ndk: NDK, user: NDKUser): Promise<string[]> {
*/
async function getUserinboxRelays(ndk: NDK, user: NDKUser): Promise<string[]> {
try {
console.debug('[kind24_utils] Fetching inbox relays for user:', user.pubkey);
const relayList = await ndk.fetchEvent(
{
kinds: [10002],
@ -69,16 +64,11 @@ async function getUserinboxRelays(ndk: NDK, user: NDKUser): Promise<string[]> { @@ -69,16 +64,11 @@ async function getUserinboxRelays(ndk: NDK, user: NDKUser): Promise<string[]> {
);
if (!relayList) {
console.debug('[kind24_utils] No relay list found for user');
return [];
}
console.debug('[kind24_utils] Found relay list event:', relayList.id);
console.debug('[kind24_utils] Relay list tags:', relayList.tags);
const inboxRelays: string[] = [];
relayList.tags.forEach((tag) => {
console.debug('[kind24_utils] Processing tag:', tag);
if (tag[0] === 'r' && tag[1]) {
// NIP-65: r tags with optional inbox/outbox markers
const marker = tag[2];
@ -86,15 +76,15 @@ async function getUserinboxRelays(ndk: NDK, user: NDKUser): Promise<string[]> { @@ -86,15 +76,15 @@ async function getUserinboxRelays(ndk: NDK, user: NDKUser): Promise<string[]> {
// If no marker or marker is 'inbox', it's a inbox relay
// If marker is 'outbox', it's also a inbox relay (NIP-65 allows both)
inboxRelays.push(tag[1]);
console.debug('[kind24_utils] Added inbox relay:', tag[1]);
}
}
});
console.debug('[kind24_utils] Final inbox relays:', inboxRelays);
return inboxRelays;
} catch (error) {
console.info('[kind24_utils] Error fetching user inbox relays:', error);
return [];
}
}
@ -165,7 +155,7 @@ export async function createKind24Reply( @@ -165,7 +155,7 @@ export async function createKind24Reply(
const quotedContent = originalEvent.content ? originalEvent.content.slice(0, 200) : "No content";
// Use a more visible quote format with a clickable link
finalContent = `> QUOTED: ${quotedContent}\n> LINK: ${nevent}\n\n${content}`;
console.log("[kind24_utils] Reply content:", finalContent);
}
event.content = finalContent;

Loading…
Cancel
Save