You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

164 lines
5.0 KiB

<script lang="ts">
import ProfileBadge from '../layout/ProfileBadge.svelte';
import type { Highlight } from '../../services/nostr/highlight-service.js';
import type { NostrEvent } from '../../types/nostr.js';
import { goto } from '$app/navigation';
interface Props {
highlights: Array<{ start: number; end: number; highlight: Highlight }>;
content: string;
event: NostrEvent;
children: import('svelte').Snippet;
}
let { highlights, content, event, children }: Props = $props();
let containerRef = $state<HTMLElement | null>(null);
let hoveredHighlight = $state<Highlight | null>(null);
let tooltipPosition = $state({ top: 0, left: 0 });
function openHighlight(highlight: Highlight) {
// Store event in sessionStorage so the event page can use it without re-fetching
sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(highlight.event));
goto(`/event/${highlight.event.id}`);
}
// Apply highlights to rendered HTML content
// This runs after the HTML is rendered
$effect(() => {
if (!containerRef || highlights.length === 0) return;
// Wait for content to be rendered
const timeoutId = setTimeout(() => {
if (!containerRef) return;
// For each highlight, try to find and wrap the text in the rendered HTML
for (const { start, end, highlight } of highlights) {
const highlightText = content.substring(start, end);
if (!highlightText) continue;
// Search for this text in the rendered HTML
const walker = document.createTreeWalker(
containerRef,
NodeFilter.SHOW_TEXT,
null
);
let node;
while ((node = walker.nextNode())) {
const text = node.textContent || '';
const index = text.indexOf(highlightText);
if (index !== -1) {
// Found the text, wrap it
const span = document.createElement('span');
span.className = 'highlight-span';
span.setAttribute('data-highlight-id', highlight.event.id);
span.setAttribute('data-pubkey', highlight.pubkey);
span.textContent = highlightText;
// Add event listeners
span.addEventListener('mouseenter', (e) => {
const rect = (e.target as HTMLElement).getBoundingClientRect();
tooltipPosition = { top: rect.top - 40, left: rect.left };
hoveredHighlight = highlight;
});
span.addEventListener('mouseleave', () => {
hoveredHighlight = null;
});
span.addEventListener('click', () => {
openHighlight(highlight);
});
// Replace text node
const beforeText = text.substring(0, index);
const afterText = text.substring(index + highlightText.length);
if (beforeText) {
node.parentNode?.insertBefore(document.createTextNode(beforeText), node);
}
node.parentNode?.insertBefore(span, node);
if (afterText) {
node.parentNode?.insertBefore(document.createTextNode(afterText), node);
}
node.parentNode?.removeChild(node);
break; // Only wrap first occurrence
}
}
}
}, 500);
return () => clearTimeout(timeoutId);
});
</script>
<div class="highlight-overlay" bind:this={containerRef}>
{@render children()}
{#if hoveredHighlight}
<div
class="highlight-tooltip"
style="top: {tooltipPosition.top}px; left: {tooltipPosition.left}px;"
>
<ProfileBadge pubkey={hoveredHighlight.pubkey} />
<button class="view-highlight-button" onclick={() => hoveredHighlight && openHighlight(hoveredHighlight)}>
View the highlight
</button>
</div>
{/if}
</div>
<style>
:global(.highlight-span) {
background: rgba(255, 255, 0, 0.3);
cursor: pointer;
transition: background 0.2s;
}
:global(.highlight-span:hover) {
background: rgba(255, 255, 0, 0.5);
}
.highlight-tooltip {
position: fixed;
background: var(--fog-post, #ffffff);
border: 1px solid var(--fog-border, #e5e7eb);
border-radius: 0.5rem;
padding: 0.75rem;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 1000;
display: flex;
flex-direction: column;
gap: 0.5rem;
min-width: 200px;
}
:global(.dark) .highlight-tooltip {
background: var(--fog-dark-post, #1f2937);
border-color: var(--fog-dark-border, #374151);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.view-highlight-button {
padding: 0.5rem;
background: var(--fog-accent, #64748b);
color: white;
border: none;
border-radius: 0.25rem;
cursor: pointer;
font-size: 0.875rem;
}
:global(.dark) .view-highlight-button {
background: var(--fog-dark-accent, #94a3b8);
}
.view-highlight-button:hover {
opacity: 0.9;
}
</style>