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.
 
 
 
 
 

260 lines
5.5 KiB

<script lang="ts">
interface Props {
open?: boolean;
results?: {
success: string[];
failed: Array<{ relay: string; error: string }>;
} | null;
}
let { open = $bindable(false), results = $bindable(null) }: Props = $props();
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;
}
};
});
function close() {
open = false;
if (autoCloseTimeout) {
clearTimeout(autoCloseTimeout);
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}
<div
class="modal-overlay"
onclick={close}
onkeydown={(e) => e.key === 'Escape' && close()}
role="dialog"
aria-modal="true"
tabindex="-1"
>
<div class="modal-content">
<div class="modal-header">
<h2>Publication Status</h2>
<button onclick={close} class="close-button">×</button>
</div>
<div class="modal-body">
{#if results.success.length > 0}
<div class="success-section">
<h3>Success ({results.success.length})</h3>
<ul>
{#each results.success as relay}
<li>{relay}</li>
{/each}
</ul>
</div>
{/if}
{#if results.failed.length > 0}
<div class="failed-section">
<h3>Failed ({results.failed.length})</h3>
<ul>
{#each results.failed as { relay, error }}
<li>
<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>
</div>
{/if}
</div>
<div class="modal-footer">
<button onclick={close}>Close</button>
</div>
</div>
</div>
{/if}
<style>
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(15, 23, 42, 0.4);
backdrop-filter: blur(4px);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background: #f8fafc;
border: 1px solid #cbd5e1;
border-radius: 8px;
max-width: 600px;
width: 90%;
max-height: 80vh;
overflow: auto;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
}
:global(.dark) .modal-content {
background: #1e293b;
border-color: #475569;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
border-bottom: 1px solid #cbd5e1;
}
:global(.dark) .modal-header {
border-bottom-color: #475569;
}
.close-button {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
padding: 0;
width: 2rem;
height: 2rem;
}
.modal-body {
padding: 1rem;
}
.success-section,
.failed-section {
margin-bottom: 1rem;
}
.success-section h3 {
color: #64748b;
}
.failed-section h3 {
color: #dc2626;
}
.modal-body ul {
list-style: none;
padding: 0;
margin: 0.5rem 0;
}
.modal-body li {
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;
text-align: right;
}
:global(.dark) .modal-footer {
border-top-color: #475569;
}
.modal-footer button {
padding: 0.5rem 1rem;
background: #94a3b8;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
}
.modal-footer button:hover {
background: #64748b;
}
</style>