diff --git a/public/healthz.json b/public/healthz.json index 72d0ad1..7ed82e5 100644 --- a/public/healthz.json +++ b/public/healthz.json @@ -2,7 +2,7 @@ "status": "ok", "service": "aitherboard", "version": "0.1.0", - "buildTime": "2026-02-03T14:14:13.893Z", + "buildTime": "2026-02-03T14:56:41.652Z", "gitCommit": "unknown", - "timestamp": 1770128053893 + "timestamp": 1770130601652 } \ No newline at end of file diff --git a/src/lib/components/content/MarkdownRenderer.svelte b/src/lib/components/content/MarkdownRenderer.svelte index 061932a..101c7ec 100644 --- a/src/lib/components/content/MarkdownRenderer.svelte +++ b/src/lib/components/content/MarkdownRenderer.svelte @@ -34,9 +34,10 @@ const pubkey = el.getAttribute('data-pubkey'); const placeholder = el.getAttribute('data-placeholder'); if (pubkey && placeholder && profileBadges.has(placeholder)) { - // Clear the element and mount component - el.innerHTML = ''; - mountComponent(el as HTMLElement, ProfileBadge as any, { pubkey }); + // Don't clear if already mounted + if (el.children.length === 0) { + mountComponent(el as HTMLElement, ProfileBadge as any, { pubkey }); + } } }); @@ -46,9 +47,10 @@ const eventId = el.getAttribute('data-event-id'); const placeholder = el.getAttribute('data-placeholder'); if (eventId && placeholder && embeddedEvents.has(placeholder)) { - // Clear the element and mount component - el.innerHTML = ''; - mountComponent(el as HTMLElement, EmbeddedEvent as any, { eventId }); + // Don't clear if already mounted + if (el.children.length === 0) { + mountComponent(el as HTMLElement, EmbeddedEvent as any, { eventId }); + } } }); }); @@ -118,8 +120,9 @@ const sortedLinks = [...links].sort((a, b) => b.start - a.start); for (const link of sortedLinks) { - // Use a unique placeholder that won't be processed by markdown - const placeholder = `\u200B\u200B\u200BNIP21_LINK_${offset}\u200B\u200B\u200B`; + // Use a special marker that will be replaced after markdown parsing + // Use a format that markdown won't process: a code-like structure + const placeholder = `\`NIP21PLACEHOLDER${offset}\``; const before = processed.slice(0, link.start); const after = processed.slice(link.end); processed = before + placeholder + after; @@ -145,7 +148,7 @@ const eventId = parsed.data; const eventPlaceholder = `EMBEDDED_EVENT_${eventId.slice(0, 8)}_${Date.now()}_${Math.random()}`; embeddedEvents.set(eventPlaceholder, eventId); - replacement = `
`; + replacement = ``; } else { const decoded: any = nip19.decode(parsed.data); if (decoded.type === 'npub' || decoded.type === 'nprofile') { @@ -158,7 +161,7 @@ // Use custom element that will be replaced with ProfileBadge component const badgePlaceholder = `PROFILE_BADGE_${pubkey.slice(0, 8)}_${Date.now()}_${Math.random()}`; profileBadges.set(badgePlaceholder, pubkey); - replacement = ``; + replacement = ``; } else { replacement = `${uri}`; } @@ -167,17 +170,17 @@ // Use custom element for embedded event const eventPlaceholder = `EMBEDDED_EVENT_${eventId.slice(0, 8)}_${Date.now()}_${Math.random()}`; embeddedEvents.set(eventPlaceholder, eventId); - replacement = ``; + replacement = ``; } else if (decoded.type === 'nevent' && decoded.data && typeof decoded.data === 'object' && 'id' in decoded.data) { const eventId = String(decoded.data.id); const eventPlaceholder = `EMBEDDED_EVENT_${eventId.slice(0, 8)}_${Date.now()}_${Math.random()}`; embeddedEvents.set(eventPlaceholder, eventId); - replacement = ``; + replacement = ``; } else if (decoded.type === 'naddr' && decoded.data && typeof decoded.data === 'object') { // For naddr, we'd need to fetch by kind+pubkey+d, but for now use the bech32 string const eventPlaceholder = `EMBEDDED_EVENT_NADDR_${Date.now()}_${Math.random()}`; embeddedEvents.set(eventPlaceholder, parsed.data); // Store the bech32 string - replacement = ``; + replacement = ``; } else { replacement = `${uri}`; } @@ -197,7 +200,7 @@ if (pubkey) { const badgePlaceholder = `PROFILE_BADGE_${pubkey.slice(0, 8)}_${Date.now()}_${Math.random()}`; profileBadges.set(badgePlaceholder, pubkey); - replacement = ``; + replacement = ``; } else { replacement = `${uri}`; } @@ -209,13 +212,17 @@ } } - // Escape placeholder for regex replacement + // Replace placeholder - it will be in a tag after markdown parsing
+ const codePlaceholder = `${placeholder.replace(/`/g, '')}`;
+ finalHtml = finalHtml.replace(new RegExp(codePlaceholder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), replacement);
+ // Also try without code tag (in case markdown didn't process it)
const escapedPlaceholder = placeholder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
finalHtml = finalHtml.replace(new RegExp(escapedPlaceholder, 'g'), replacement);
}
- // Clean up any remaining placeholders (fallback)
- finalHtml = finalHtml.replace(/\u200B\u200B\u200BNIP21_LINK_\d+\u200B\u200B\u200B/g, '');
+ // Clean up any remaining placeholders (fallback) - look for code tags with our placeholder
+ finalHtml = finalHtml.replace(/NIP21PLACEHOLDER\d+<\/code>/g, '');
+ finalHtml = finalHtml.replace(/NIP21PLACEHOLDER\d+/g, '');
rendered = finalHtml;
});
@@ -235,7 +242,7 @@
const eventId = parsed.data;
const eventPlaceholder = `EMBEDDED_EVENT_${eventId.slice(0, 8)}_${Date.now()}_${Math.random()}`;
embeddedEvents.set(eventPlaceholder, eventId);
- replacement = ``;
+ replacement = ``;
} else {
const decoded: any = nip19.decode(parsed.data);
if (decoded.type === 'npub' || decoded.type === 'nprofile') {
@@ -247,7 +254,7 @@
if (pubkey) {
const badgePlaceholder = `PROFILE_BADGE_${pubkey.slice(0, 8)}_${Date.now()}_${Math.random()}`;
profileBadges.set(badgePlaceholder, pubkey);
- replacement = ``;
+ replacement = ``;
} else {
replacement = `${uri}`;
}
@@ -255,16 +262,16 @@
const eventId = String(decoded.data);
const eventPlaceholder = `EMBEDDED_EVENT_${eventId.slice(0, 8)}_${Date.now()}_${Math.random()}`;
embeddedEvents.set(eventPlaceholder, eventId);
- replacement = ``;
+ replacement = ``;
} else if (decoded.type === 'nevent' && decoded.data && typeof decoded.data === 'object' && 'id' in decoded.data) {
const eventId = String(decoded.data.id);
const eventPlaceholder = `EMBEDDED_EVENT_${eventId.slice(0, 8)}_${Date.now()}_${Math.random()}`;
embeddedEvents.set(eventPlaceholder, eventId);
- replacement = ``;
+ replacement = ``;
} else if (decoded.type === 'naddr' && decoded.data && typeof decoded.data === 'object') {
const eventPlaceholder = `EMBEDDED_EVENT_NADDR_${Date.now()}_${Math.random()}`;
embeddedEvents.set(eventPlaceholder, parsed.data);
- replacement = ``;
+ replacement = ``;
} else {
replacement = `${uri}`;
}
@@ -387,6 +394,8 @@
.markdown-content :global(img) {
max-width: 100%;
height: auto;
+ display: block;
+ margin: 0.5em 0;
filter: grayscale(100%) sepia(15%) hue-rotate(200deg) saturate(60%) brightness(0.95);
}
@@ -394,6 +403,12 @@
filter: grayscale(100%) sepia(20%) hue-rotate(200deg) saturate(70%) brightness(0.9);
}
+ .markdown-content :global(.nostr-profile-badge-placeholder),
+ .markdown-content :global(.nostr-embedded-event-placeholder) {
+ display: inline-block;
+ vertical-align: middle;
+ }
+
/* Style emojis in content */
.markdown-content :global(span[role="img"]),
.markdown-content :global(.emoji) {
diff --git a/src/lib/components/content/QuotedContext.svelte b/src/lib/components/content/QuotedContext.svelte
index 42bed2c..10187f7 100644
--- a/src/lib/components/content/QuotedContext.svelte
+++ b/src/lib/components/content/QuotedContext.svelte
@@ -9,9 +9,10 @@
quotedEventId?: string; // Optional - used to load quoted event if not provided
targetId?: string; // Optional ID to scroll to (defaults to quoted event ID)
onQuotedLoaded?: (event: NostrEvent) => void; // Callback when quoted event is loaded
+ onOpenEvent?: (event: NostrEvent) => void; // Callback to open event in drawer
}
- let { quotedEvent: providedQuotedEvent, quotedEventId, targetId, onQuotedLoaded }: Props = $props();
+ let { quotedEvent: providedQuotedEvent, quotedEventId, targetId, onQuotedLoaded, onOpenEvent }: Props = $props();
let loadedQuotedEvent = $state(null);
let loadingQuoted = $state(false);
@@ -50,8 +51,6 @@
if (onQuotedLoaded && typeof onQuotedLoaded === 'function') {
onQuotedLoaded(loadedQuotedEvent);
}
- // After loading, try to scroll to it
- setTimeout(() => scrollToQuoted(), 100);
}
} catch (error) {
console.error('Error loading quoted event:', error);
@@ -69,45 +68,10 @@
return plaintext.slice(0, 100) + (plaintext.length > 100 ? '...' : '');
}
- async function scrollToQuoted() {
- const eventId = quotedEvent?.id || quotedEventId;
- if (!eventId) return;
-
- // If quoted event not loaded yet, load it first
- if (!quotedEvent && quotedEventId) {
- await loadQuotedEvent();
- }
-
- const elementId = targetId || `event-${eventId}`;
- let element = document.getElementById(elementId) || document.querySelector(`[data-event-id="${eventId}"]`);
-
- // If still not found, wait a bit for DOM to update
- if (!element) {
- await new Promise(resolve => setTimeout(resolve, 200));
- element = document.getElementById(elementId) || document.querySelector(`[data-event-id="${eventId}"]`);
- }
-
- if (element) {
- element.scrollIntoView({ behavior: 'smooth', block: 'center' });
- element.classList.add('highlight-quoted');
- setTimeout(() => {
- element?.classList.remove('highlight-quoted');
- }, 2000);
- }
- }
{
- if (e.key === 'Enter' || e.key === ' ') {
- e.preventDefault();
- scrollToQuoted();
- }
- }}
+ class="quoted-context mb-2 p-2 bg-fog-highlight dark:bg-fog-dark-highlight rounded text-xs text-fog-text-light dark:text-fog-dark-text-light"
>
Quoting: {getQuotedPreview()}
{#if loadingQuoted}
diff --git a/src/lib/components/content/ReplyContext.svelte b/src/lib/components/content/ReplyContext.svelte
index 5292f11..311011a 100644
--- a/src/lib/components/content/ReplyContext.svelte
+++ b/src/lib/components/content/ReplyContext.svelte
@@ -9,9 +9,10 @@
parentEventId?: string; // Optional - used to load parent if not provided
targetId?: string; // Optional ID to scroll to (defaults to parent event ID)
onParentLoaded?: (event: NostrEvent) => void; // Callback when parent is loaded
+ onOpenEvent?: (event: NostrEvent) => void; // Callback to open event in drawer
}
- let { parentEvent: providedParentEvent, parentEventId, targetId, onParentLoaded }: Props = $props();
+ let { parentEvent: providedParentEvent, parentEventId, targetId, onParentLoaded, onOpenEvent }: Props = $props();
let loadedParentEvent = $state(null);
let loadingParent = $state(false);
@@ -50,8 +51,6 @@
if (onParentLoaded && typeof onParentLoaded === 'function') {
onParentLoaded(loadedParentEvent);
}
- // After loading, try to scroll to it
- setTimeout(() => scrollToParent(), 100);
}
} catch (error) {
console.error('Error loading parent event:', error);
@@ -69,45 +68,10 @@
return plaintext.slice(0, 100) + (plaintext.length > 100 ? '...' : '');
}
- async function scrollToParent() {
- const eventId = parentEvent?.id || parentEventId;
- if (!eventId) return;
-
- // If parent not loaded yet, load it first
- if (!parentEvent && parentEventId) {
- await loadParentEvent();
- }
-
- const elementId = targetId || `event-${eventId}`;
- let element = document.getElementById(elementId) || document.querySelector(`[data-event-id="${eventId}"]`);
-
- // If still not found, wait a bit for DOM to update
- if (!element) {
- await new Promise(resolve => setTimeout(resolve, 200));
- element = document.getElementById(elementId) || document.querySelector(`[data-event-id="${eventId}"]`);
- }
-
- if (element) {
- element.scrollIntoView({ behavior: 'smooth', block: 'center' });
- element.classList.add('highlight-parent');
- setTimeout(() => {
- element?.classList.remove('highlight-parent');
- }, 2000);
- }
- }