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.
800 lines
22 KiB
800 lines
22 KiB
<script lang="ts"> |
|
import { nip19 } from 'nostr-tools'; |
|
import type { NostrEvent } from '../types/nostr.js'; |
|
import { nostrClient } from '../services/nostr/nostr-client.js'; |
|
import { relayManager } from '../services/nostr/relay-manager.js'; |
|
import EventJsonModal from './modals/EventJsonModal.svelte'; |
|
import PublicationStatusModal from './modals/PublicationStatusModal.svelte'; |
|
import { |
|
isPinned, |
|
isBookmarked, |
|
isHighlighted, |
|
togglePin, |
|
toggleBookmark, |
|
toggleHighlight |
|
} from '../services/user-actions.js'; |
|
import { eventMenuStore } from '../services/event-menu-store.js'; |
|
import { sessionManager } from '../services/auth/session-manager.js'; |
|
import { signAndPublish } from '../services/nostr/auth-handler.js'; |
|
import RelatedEventsModal from './modals/RelatedEventsModal.svelte'; |
|
import { KIND } from '../types/kind-lookup.js'; |
|
import { goto } from '$app/navigation'; |
|
import Icon from './ui/Icon.svelte'; |
|
|
|
interface Props { |
|
event: NostrEvent; |
|
showContentActions?: boolean; // Show pin/bookmark/highlight for notes with content |
|
onReply?: () => void; // Callback for reply action |
|
} |
|
|
|
let { event, showContentActions = false, onReply }: Props = $props(); |
|
|
|
let menuOpen = $state(false); |
|
let jsonModalOpen = $state(false); |
|
let relatedEventsModalOpen = $state(false); |
|
let publicationModalOpen = $state(false); |
|
let publicationResults = $state<{ success: string[]; failed: Array<{ relay: string; error: string }> } | null>(null); |
|
let broadcasting = $state(false); |
|
let deleting = $state(false); |
|
let deleteConfirmOpen = $state(false); |
|
let copied = $state<string | null>(null); |
|
let menuButtonElement: HTMLButtonElement | null = $state(null); |
|
let menuDropdownElement: HTMLDivElement | null = $state(null); |
|
let menuPosition = $state({ top: 0, right: 0 }); |
|
|
|
// Unique ID for this menu instance |
|
let menuId = $derived(event.id); |
|
|
|
// Note: Removed isContentNote check - all events should have the same menu (except profile pages/cards) |
|
|
|
// Check if user is logged in |
|
let isLoggedIn = $derived(sessionManager.isLoggedIn()); |
|
let currentUserPubkey = $derived(sessionManager.getCurrentPubkey()); |
|
let isOwnEvent = $derived(isLoggedIn && currentUserPubkey === event.pubkey); |
|
|
|
// Track pin/bookmark/highlight state |
|
let pinnedState = $state(false); |
|
let bookmarkedState = $state(false); |
|
let highlightedState = $state(false); |
|
let stateUpdateTrigger = $state(0); // Trigger to force state updates |
|
|
|
// Update state when event changes or when trigger changes |
|
$effect(() => { |
|
highlightedState = isHighlighted(event.id); |
|
// Access trigger to make effect reactive to it |
|
void stateUpdateTrigger; |
|
|
|
// Update pin and bookmark state asynchronously |
|
isPinned(event.id).then(pinned => { |
|
pinnedState = pinned; |
|
}); |
|
isBookmarked(event.id).then(bookmarked => { |
|
bookmarkedState = bookmarked; |
|
}); |
|
}); |
|
|
|
function toggleMenu() { |
|
if (menuOpen) { |
|
closeMenu(); |
|
} else { |
|
openMenu(); |
|
} |
|
} |
|
|
|
function openMenu() { |
|
menuOpen = true; |
|
// Register this menu as open - this will close any other open menu |
|
eventMenuStore.openMenu(menuId, closeMenu); |
|
// Position menu after opening |
|
requestAnimationFrame(() => { |
|
positionMenu(); |
|
}); |
|
} |
|
|
|
function positionMenu() { |
|
if (!menuButtonElement || !menuDropdownElement) return; |
|
|
|
const buttonRect = menuButtonElement.getBoundingClientRect(); |
|
const viewportWidth = window.innerWidth; |
|
const viewportHeight = window.innerHeight; |
|
const isMobile = viewportWidth < 640; |
|
const padding = isMobile ? 4 : 8; // Smaller padding on mobile |
|
|
|
// Get dropdown dimensions (estimate or use actual if available) |
|
let dropdownWidth = isMobile ? 180 : 200; // min-width from CSS |
|
let dropdownHeight = 300; // Estimate, will be updated |
|
|
|
// Position below button by default, aligned to right edge |
|
let top = buttonRect.bottom + 4; |
|
let right = viewportWidth - buttonRect.right; |
|
|
|
// Get actual dimensions after rendering |
|
requestAnimationFrame(() => { |
|
if (!menuDropdownElement) return; |
|
const dropdownRect = menuDropdownElement.getBoundingClientRect(); |
|
dropdownWidth = dropdownRect.width; |
|
dropdownHeight = dropdownRect.height; |
|
|
|
// Calculate left position from right |
|
const left = viewportWidth - right - dropdownWidth; |
|
|
|
// Check and adjust for viewport boundaries |
|
let adjustedTop = top; |
|
let adjustedRight = right; |
|
|
|
// Check bottom overflow |
|
if (top + dropdownHeight + padding > viewportHeight) { |
|
// Try positioning above button |
|
const spaceAbove = buttonRect.top; |
|
const spaceBelow = viewportHeight - buttonRect.bottom; |
|
if (spaceAbove >= dropdownHeight + padding || spaceAbove > spaceBelow) { |
|
adjustedTop = buttonRect.top - dropdownHeight - 4; |
|
} else { |
|
// Not enough space above, position at bottom of viewport |
|
adjustedTop = viewportHeight - dropdownHeight - padding; |
|
} |
|
} |
|
|
|
// Check top overflow |
|
if (adjustedTop < padding) { |
|
adjustedTop = padding; |
|
} |
|
|
|
// Check right overflow (menu goes off right edge) |
|
if (left < padding) { |
|
adjustedRight = viewportWidth - padding - dropdownWidth; |
|
} |
|
|
|
// Check left overflow (menu goes off left edge) |
|
const adjustedLeft = viewportWidth - adjustedRight - dropdownWidth; |
|
if (adjustedLeft < padding) { |
|
adjustedRight = viewportWidth - padding - dropdownWidth; |
|
} |
|
|
|
// Ensure menu doesn't go off right edge |
|
if (adjustedRight < padding) { |
|
adjustedRight = padding; |
|
} |
|
|
|
// On mobile, ensure menu doesn't go off left edge |
|
if (isMobile && adjustedLeft < padding) { |
|
adjustedRight = Math.max(padding, viewportWidth - dropdownWidth - padding); |
|
} |
|
|
|
menuPosition = { top: adjustedTop, right: adjustedRight }; |
|
}); |
|
|
|
// Set initial position |
|
menuPosition = { top, right }; |
|
} |
|
|
|
function closeMenu() { |
|
menuOpen = false; |
|
eventMenuStore.closeMenu(menuId); |
|
} |
|
|
|
// Close menu when clicking outside |
|
function handleClickOutside(e: MouseEvent) { |
|
const target = e.target as HTMLElement; |
|
if (!target.closest('.event-menu-container') && !target.closest('.menu-dropdown')) { |
|
eventMenuStore.closeCurrentMenu(); |
|
} |
|
} |
|
|
|
$effect(() => { |
|
if (menuOpen) { |
|
document.addEventListener('click', handleClickOutside); |
|
window.addEventListener('scroll', closeMenu, true); |
|
window.addEventListener('resize', positionMenu); |
|
// Reposition on scroll to keep menu aligned |
|
const handleScroll = () => { |
|
if (menuOpen) { |
|
positionMenu(); |
|
} |
|
}; |
|
window.addEventListener('scroll', handleScroll, true); |
|
return () => { |
|
document.removeEventListener('click', handleClickOutside); |
|
window.removeEventListener('scroll', closeMenu, true); |
|
window.removeEventListener('resize', positionMenu); |
|
window.removeEventListener('scroll', handleScroll, true); |
|
}; |
|
} |
|
}); |
|
|
|
async function copyUserId() { |
|
try { |
|
const npub = nip19.npubEncode(event.pubkey); |
|
await navigator.clipboard.writeText(npub); |
|
copied = 'npub'; |
|
setTimeout(() => { |
|
copied = null; |
|
}, 2000); |
|
closeMenu(); |
|
} catch (error) { |
|
console.error('Failed to copy user ID:', error); |
|
} |
|
} |
|
|
|
async function copyEventId() { |
|
try { |
|
const nevent = nip19.neventEncode({ |
|
id: event.id, |
|
relays: [] |
|
}); |
|
await navigator.clipboard.writeText(nevent); |
|
copied = 'nevent'; |
|
setTimeout(() => { |
|
copied = null; |
|
}, 2000); |
|
closeMenu(); |
|
} catch (error) { |
|
console.error('Failed to copy event ID:', error); |
|
} |
|
} |
|
|
|
function viewJson() { |
|
jsonModalOpen = true; |
|
closeMenu(); |
|
} |
|
|
|
function viewRelatedEvents() { |
|
relatedEventsModalOpen = true; |
|
closeMenu(); |
|
} |
|
|
|
async function broadcastEvent() { |
|
broadcasting = true; |
|
closeMenu(); |
|
|
|
try { |
|
// Get all available relays for broadcasting |
|
const relays = relayManager.getPublishRelays( |
|
[...relayManager.getThreadReadRelays(), ...relayManager.getFeedReadRelays()], |
|
true |
|
); |
|
|
|
const results = await nostrClient.publish(event, { relays }); |
|
publicationResults = results; |
|
publicationModalOpen = true; |
|
} catch (error) { |
|
console.error('Error broadcasting event:', error); |
|
publicationResults = { |
|
success: [], |
|
failed: [{ relay: 'Unknown', error: error instanceof Error ? error.message : 'Unknown error' }] |
|
}; |
|
publicationModalOpen = true; |
|
} finally { |
|
broadcasting = false; |
|
} |
|
} |
|
|
|
async function shareWithaitherboard() { |
|
try { |
|
const url = `${window.location.origin}/event/${event.id}`; |
|
await navigator.clipboard.writeText(url); |
|
copied = 'share'; |
|
setTimeout(() => { |
|
copied = null; |
|
}, 2000); |
|
closeMenu(); |
|
} catch (error) { |
|
console.error('Failed to copy share URL:', error); |
|
} |
|
} |
|
|
|
async function pinNote() { |
|
await togglePin(event.id); |
|
// Force state update |
|
stateUpdateTrigger++; |
|
closeMenu(); |
|
} |
|
|
|
async function bookmarkNote() { |
|
await toggleBookmark(event.id); |
|
// Force state update by re-checking bookmark status |
|
const newBookmarked = await isBookmarked(event.id); |
|
bookmarkedState = newBookmarked; |
|
closeMenu(); |
|
} |
|
|
|
function highlightNote() { |
|
// Extract content and e/a tags for highlight |
|
const content = event.content || ''; |
|
|
|
// Collect all relevant tags: e-tag with the event's ID, a-tag (if available), and p-tag with the event's pubkey |
|
const tagsToInclude: string[][] = []; |
|
|
|
// Always add e-tag with the event ID of the event being highlighted |
|
tagsToInclude.push(['e', event.id]); |
|
|
|
// Also check for a-tag in the original event (for parameterized replaceable events) |
|
const aTag = event.tags.find(tag => tag[0] === 'a'); |
|
if (aTag) { |
|
tagsToInclude.push(aTag); |
|
} |
|
|
|
// Add p-tag with the pubkey of the event being highlighted |
|
tagsToInclude.push(['p', event.pubkey]); |
|
|
|
// Store highlight data in sessionStorage |
|
const highlightData = { |
|
content, |
|
tags: tagsToInclude |
|
}; |
|
sessionStorage.setItem('aitherboard_highlightData', JSON.stringify(highlightData)); |
|
|
|
// Navigate to write form with kind 9802 (highlight) |
|
goto('/write?kind=9802'); |
|
closeMenu(); |
|
} |
|
|
|
function confirmDelete() { |
|
deleteConfirmOpen = true; |
|
closeMenu(); |
|
} |
|
|
|
async function deleteEvent() { |
|
if (!isLoggedIn) return; |
|
|
|
deleting = true; |
|
deleteConfirmOpen = false; |
|
|
|
try { |
|
// Create kind 5 deletion event |
|
const deletionEvent: Omit<NostrEvent, 'sig' | 'id'> = { |
|
kind: KIND.EVENT_DELETION, |
|
pubkey: currentUserPubkey!, // Use the current user's pubkey (the person deleting) |
|
created_at: Math.floor(Date.now() / 1000), |
|
tags: [['e', event.id]], // Reference the deleted event |
|
content: '' |
|
}; |
|
|
|
// Get all available relays for publishing |
|
const relays = relayManager.getPublishRelays( |
|
[...relayManager.getThreadReadRelays(), ...relayManager.getFeedReadRelays()], |
|
true |
|
); |
|
|
|
// Sign and publish |
|
const results = await signAndPublish(deletionEvent, relays); |
|
publicationResults = results; |
|
publicationModalOpen = true; |
|
} catch (error) { |
|
console.error('Error deleting event:', error); |
|
publicationResults = { |
|
success: [], |
|
failed: [{ relay: 'Unknown', error: error instanceof Error ? error.message : 'Unknown error' }] |
|
}; |
|
publicationModalOpen = true; |
|
} finally { |
|
deleting = false; |
|
} |
|
} |
|
</script> |
|
|
|
<div class="event-menu-container"> |
|
<button |
|
bind:this={menuButtonElement} |
|
class="menu-button" |
|
onclick={toggleMenu} |
|
aria-label="Event menu" |
|
aria-expanded={menuOpen} |
|
> |
|
<span class="menu-icon">⋯</span> |
|
</button> |
|
|
|
{#if menuOpen} |
|
<div |
|
bind:this={menuDropdownElement} |
|
class="menu-dropdown" |
|
style="top: {menuPosition.top}px; right: {menuPosition.right}px;" |
|
> |
|
<button class="menu-item" onclick={copyUserId}> |
|
<Icon name="copy" size={16} /> |
|
<span>Copy user ID</span> |
|
{#if copied === 'npub'} |
|
<span class="copied-indicator">✓</span> |
|
{/if} |
|
</button> |
|
<button class="menu-item" onclick={copyEventId}> |
|
<Icon name="copy" size={16} /> |
|
<span>Copy event ID</span> |
|
{#if copied === 'nevent'} |
|
<span class="copied-indicator">✓</span> |
|
{/if} |
|
</button> |
|
<button class="menu-item" onclick={viewJson}> |
|
<Icon name="code" size={16} /> |
|
<span>View JSON</span> |
|
</button> |
|
{#if isLoggedIn} |
|
<button class="menu-item" onclick={viewRelatedEvents}> |
|
<Icon name="search" size={16} /> |
|
<span>View your related events</span> |
|
</button> |
|
{/if} |
|
<button class="menu-item" onclick={broadcastEvent} disabled={broadcasting}> |
|
<Icon name="radio" size={16} /> |
|
<span>{broadcasting ? 'Broadcasting...' : 'Broadcast event'}</span> |
|
</button> |
|
<button class="menu-item" onclick={shareWithaitherboard}> |
|
<Icon name="share" size={16} /> |
|
<span>Share with aitherboard</span> |
|
{#if copied === 'share'} |
|
<span class="copied-indicator">✓</span> |
|
{/if} |
|
</button> |
|
|
|
{#if isLoggedIn && onReply} |
|
<div class="menu-divider"></div> |
|
<button class="menu-item menu-item-reply" onclick={() => { onReply(); closeMenu(); }}> |
|
<Icon name="message-square" size={16} /> |
|
<span>Reply</span> |
|
</button> |
|
{/if} |
|
{#if isLoggedIn && showContentActions} |
|
<div class="menu-divider"></div> |
|
<button class="menu-item" onclick={pinNote} class:active={pinnedState}> |
|
<Icon name="plus" size={16} /> |
|
<span>Pin note</span> |
|
{#if pinnedState} |
|
<span class="action-indicator">✓</span> |
|
{/if} |
|
</button> |
|
<button class="menu-item" onclick={bookmarkNote} class:active={bookmarkedState}> |
|
<Icon name="plus" size={16} /> |
|
<span>Bookmark note</span> |
|
{#if bookmarkedState} |
|
<span class="action-indicator">✓</span> |
|
{/if} |
|
</button> |
|
<button class="menu-item" onclick={highlightNote} class:active={highlightedState}> |
|
<Icon name="edit" size={16} /> |
|
<span>Highlight note</span> |
|
{#if highlightedState} |
|
<span class="action-indicator">✓</span> |
|
{/if} |
|
</button> |
|
{/if} |
|
|
|
{#if isLoggedIn && isOwnEvent} |
|
<div class="menu-divider"></div> |
|
<button class="menu-item menu-item-danger" onclick={confirmDelete} disabled={deleting}> |
|
<Icon name="trash" size={16} /> |
|
<span>{deleting ? 'Deleting...' : 'Delete event'}</span> |
|
</button> |
|
{/if} |
|
</div> |
|
{/if} |
|
</div> |
|
|
|
<EventJsonModal bind:open={jsonModalOpen} event={event} /> |
|
<RelatedEventsModal bind:open={relatedEventsModalOpen} event={event} /> |
|
<PublicationStatusModal bind:open={publicationModalOpen} bind:results={publicationResults} /> |
|
|
|
{#if deleteConfirmOpen} |
|
<div |
|
class="delete-confirm-overlay" |
|
role="dialog" |
|
aria-modal="true" |
|
aria-labelledby="delete-dialog-title" |
|
onclick={(e) => { |
|
if (e.target === e.currentTarget) { |
|
deleteConfirmOpen = false; |
|
} |
|
}} |
|
onkeydown={(e) => { |
|
if (e.key === 'Escape') { |
|
deleteConfirmOpen = false; |
|
} |
|
}} |
|
tabindex="-1" |
|
> |
|
<div class="delete-confirm-dialog"> |
|
<h3 id="delete-dialog-title">Delete Event?</h3> |
|
<p>Are you sure you want to delete this event? This action cannot be undone.</p> |
|
<div class="delete-confirm-buttons"> |
|
<button class="btn-cancel flex items-center gap-2" onclick={() => deleteConfirmOpen = false}> |
|
<Icon name="x" size={16} /> |
|
<span>Cancel</span> |
|
</button> |
|
<button class="btn-delete flex items-center gap-2" onclick={deleteEvent} disabled={deleting}> |
|
<Icon name="trash" size={16} /> |
|
<span>{deleting ? 'Deleting...' : 'Delete'}</span> |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
{/if} |
|
|
|
<style> |
|
.event-menu-container { |
|
position: relative; |
|
display: inline-block; |
|
/* Ensure menu can overflow container */ |
|
overflow: visible; |
|
} |
|
|
|
.menu-button { |
|
background: none; |
|
border: none; |
|
cursor: pointer; |
|
padding: 0.25rem 0.5rem; |
|
color: var(--fog-text-light, #52667a); |
|
font-size: 1.25rem; |
|
line-height: 1; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
border-radius: 4px; |
|
transition: background-color 0.2s, color 0.2s; |
|
filter: grayscale(100%); |
|
} |
|
|
|
.menu-button:hover { |
|
background: var(--fog-highlight, #f3f4f6); |
|
color: var(--fog-text, #1f2937); |
|
} |
|
|
|
:global(.dark) .menu-button { |
|
color: var(--fog-dark-text-light, #a8b8d0); |
|
} |
|
|
|
:global(.dark) .menu-button:hover { |
|
background: var(--fog-dark-highlight, #374151); |
|
color: var(--fog-dark-text, #f1f5f9); |
|
} |
|
|
|
.menu-icon { |
|
user-select: none; |
|
transform: rotate(90deg); |
|
display: inline-block; |
|
filter: grayscale(100%); |
|
} |
|
|
|
.menu-dropdown { |
|
position: fixed; |
|
background: var(--fog-post, #ffffff); |
|
border: 1px solid var(--fog-border, #e5e7eb); |
|
border-radius: 6px; |
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); |
|
min-width: 200px; |
|
max-width: calc(100vw - 16px); |
|
max-height: calc(100vh - 16px); |
|
z-index: 1000; |
|
overflow-y: auto; |
|
overflow-x: hidden; |
|
} |
|
|
|
.menu-item-reply { |
|
display: flex !important; |
|
visibility: visible !important; |
|
} |
|
|
|
@media (max-width: 640px) { |
|
.menu-dropdown { |
|
min-width: 180px; |
|
max-width: calc(100vw - 8px); |
|
font-size: 0.875rem; |
|
} |
|
|
|
.menu-item { |
|
padding: 0.625rem 0.75rem; |
|
font-size: 0.875rem; |
|
min-height: 2.5rem; |
|
} |
|
|
|
.menu-button { |
|
padding: 0.375rem 0.5rem; |
|
min-width: 2.5rem; |
|
min-height: 2.5rem; |
|
} |
|
} |
|
|
|
:global(.dark) .menu-dropdown { |
|
background: var(--fog-dark-post, #1f2937); |
|
border-color: var(--fog-dark-border, #374151); |
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); |
|
} |
|
|
|
.menu-item { |
|
display: flex; |
|
align-items: center; |
|
justify-content: space-between; |
|
width: 100%; |
|
padding: 0.5rem 0.75rem; |
|
background: none; |
|
border: none; |
|
text-align: left; |
|
cursor: pointer; |
|
font-size: 0.875rem; |
|
color: var(--fog-text, #1f2937); |
|
transition: background-color 0.2s; |
|
gap: 0.5rem; |
|
} |
|
|
|
.menu-item :global(.icon) { |
|
flex-shrink: 0; |
|
} |
|
|
|
.menu-item span { |
|
flex: 1; |
|
} |
|
|
|
.menu-item:hover:not(:disabled) { |
|
background: var(--fog-highlight, #f3f4f6); |
|
} |
|
|
|
.menu-item:disabled { |
|
opacity: 0.6; |
|
cursor: not-allowed; |
|
} |
|
|
|
.menu-item.active { |
|
background: var(--fog-highlight, #f3f4f6); |
|
font-weight: 500; |
|
} |
|
|
|
:global(.dark) .menu-item { |
|
color: var(--fog-dark-text, #f1f5f9); |
|
} |
|
|
|
:global(.dark) .menu-item:hover:not(:disabled) { |
|
background: var(--fog-dark-highlight, #374151); |
|
} |
|
|
|
:global(.dark) .menu-item.active { |
|
background: var(--fog-dark-highlight, #374151); |
|
} |
|
|
|
.action-indicator { |
|
color: var(--fog-accent, #64748b); |
|
font-weight: 600; |
|
margin-left: 0.5rem; |
|
} |
|
|
|
:global(.dark) .action-indicator { |
|
color: var(--fog-dark-accent, #94a3b8); |
|
} |
|
|
|
.menu-divider { |
|
height: 1px; |
|
background: var(--fog-border, #e5e7eb); |
|
margin: 0.25rem 0; |
|
} |
|
|
|
:global(.dark) .menu-divider { |
|
background: var(--fog-dark-border, #374151); |
|
} |
|
|
|
.copied-indicator { |
|
color: var(--fog-accent, #64748b); |
|
font-weight: 600; |
|
margin-left: 0.5rem; |
|
} |
|
|
|
:global(.dark) .copied-indicator { |
|
color: var(--fog-dark-accent, #94a3b8); |
|
} |
|
|
|
.menu-item-danger { |
|
color: var(--fog-danger, #dc2626); |
|
} |
|
|
|
:global(.dark) .menu-item-danger { |
|
color: var(--fog-dark-danger, #ef4444); |
|
} |
|
|
|
.menu-item-danger:hover:not(:disabled) { |
|
background: var(--fog-danger-light, #fee2e2); |
|
} |
|
|
|
:global(.dark) .menu-item-danger:hover:not(:disabled) { |
|
background: var(--fog-dark-danger-light, #7f1d1d); |
|
} |
|
|
|
.delete-confirm-overlay { |
|
position: fixed; |
|
top: 0; |
|
left: 0; |
|
right: 0; |
|
bottom: 0; |
|
background: rgba(0, 0, 0, 0.5); |
|
z-index: 2000; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
} |
|
|
|
.delete-confirm-dialog { |
|
background: var(--fog-post, #ffffff); |
|
border-radius: 0.5rem; |
|
padding: 1.5rem; |
|
max-width: 400px; |
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); |
|
} |
|
|
|
:global(.dark) .delete-confirm-dialog { |
|
background: var(--fog-dark-post, #1f2937); |
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); |
|
} |
|
|
|
.delete-confirm-dialog h3 { |
|
margin: 0 0 0.5rem 0; |
|
color: var(--fog-text, #1f2937); |
|
font-size: 1.25rem; |
|
} |
|
|
|
:global(.dark) .delete-confirm-dialog h3 { |
|
color: var(--fog-dark-text, #f9fafb); |
|
} |
|
|
|
.delete-confirm-dialog p { |
|
margin: 0 0 1.5rem 0; |
|
color: var(--fog-text-light, #52667a); |
|
} |
|
|
|
:global(.dark) .delete-confirm-dialog p { |
|
color: var(--fog-dark-text-light, #a8b8d0); |
|
} |
|
|
|
.delete-confirm-buttons { |
|
display: flex; |
|
gap: 0.75rem; |
|
justify-content: flex-end; |
|
} |
|
|
|
.btn-cancel, |
|
.btn-delete { |
|
padding: 0.5rem 1rem; |
|
border-radius: 0.25rem; |
|
border: none; |
|
cursor: pointer; |
|
font-size: 0.875rem; |
|
transition: all 0.2s; |
|
display: inline-flex; |
|
align-items: center; |
|
gap: 0.5rem; |
|
} |
|
|
|
.btn-cancel { |
|
background: var(--fog-highlight, #f3f4f6); |
|
color: var(--fog-text, #1f2937); |
|
} |
|
|
|
:global(.dark) .btn-cancel { |
|
background: var(--fog-dark-highlight, #374151); |
|
color: var(--fog-dark-text, #f9fafb); |
|
} |
|
|
|
.btn-cancel:hover { |
|
background: var(--fog-border, #e5e7eb); |
|
} |
|
|
|
:global(.dark) .btn-cancel:hover { |
|
background: var(--fog-dark-border, #475569); |
|
} |
|
|
|
.btn-delete { |
|
background: var(--fog-danger, #dc2626); |
|
color: white; |
|
} |
|
|
|
:global(.dark) .btn-delete { |
|
background: var(--fog-dark-danger, #ef4444); |
|
} |
|
|
|
.btn-delete:hover:not(:disabled) { |
|
background: var(--fog-danger-dark, #b91c1c); |
|
} |
|
|
|
:global(.dark) .btn-delete:hover:not(:disabled) { |
|
background: var(--fog-dark-danger-dark, #dc2626); |
|
} |
|
|
|
.btn-delete:disabled { |
|
opacity: 0.6; |
|
cursor: not-allowed; |
|
} |
|
</style>
|
|
|