clone of repo on github
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.
 
 
 
 

291 lines
8.2 KiB

<script lang="ts">
import { get } from "svelte/store";
import { userStore } from "$lib/stores/userStore";
import { goto } from "$app/navigation";
import { Button } from "flowbite-svelte";
import EventForm from "./event_input/EventForm.svelte";
import TagManager from "./event_input/TagManager.svelte";
import EventPreview from "./event_input/EventPreview.svelte";
import type { EventData, TagData } from "./event_input/types";
import { publishEvent, loadEvent } from "./event_input/eventServices";
import { getNdkContext } from "$lib/ndk";
// AI-NOTE: 2025-01-24 - Main EventInput component refactored for better separation of concerns
// This component now serves as a container that orchestrates the form, tags, preview, and publishing
// Get NDK context at component level (can only be called during initialization)
const ndk = getNdkContext();
// Main event state
let eventData = $state<EventData>({
kind: 1,
content: "",
createdAt: Math.floor(Date.now() / 1000),
});
// Tag state
let tags = $state<TagData[]>([]);
// UI state
let loading = $state(false);
let error = $state<string | null>(null);
let success = $state<string | null>(null);
let showJsonPreview = $state(false);
// Publishing state
let publishedRelays = $state<string[]>([]);
let lastPublishedEventId = $state<string | null>(null);
// Event loading state
let eventIdSearch = $state("");
let loadingEvent = $state(false);
// Session storage loading
let hasLoadedFromStorage = $state(false);
// Load content from sessionStorage if available (from ZettelEditor)
$effect(() => {
if (hasLoadedFromStorage) return; // Prevent multiple loads
const storedContent = sessionStorage.getItem('zettelEditorContent');
const storedSource = sessionStorage.getItem('zettelEditorSource');
if (storedContent && storedSource === 'publication-format') {
eventData.content = storedContent;
hasLoadedFromStorage = true;
// Clear the stored content after loading
sessionStorage.removeItem('zettelEditorContent');
sessionStorage.removeItem('zettelEditorSource');
}
});
/**
* Handles form validation
*/
function handleValidate(isValid: boolean, error?: string, warning?: string) {
if (!isValid && error) {
// Validation failed - error is already shown in EventForm
return;
}
if (warning) {
// Validation passed with warning - user can proceed
console.log("Validation warning:", warning);
}
// Validation passed - form is ready for publishing
console.log("Form validation passed");
}
/**
* Handles the publishing process
*/
async function handlePublish() {
loading = true;
error = null;
success = null;
publishedRelays = [];
try {
const result = await publishEvent(ndk, eventData, tags);
if (result.success) {
publishedRelays = result.relays || [];
lastPublishedEventId = result.eventId || null;
success = `Published to ${result.relays?.length || 0} relay(s).`;
} else {
error = result.error || "Failed to publish event.";
}
} catch (err) {
console.error("Error in handlePublish:", err);
error = `Publishing failed: ${err instanceof Error ? err.message : "Unknown error"}`;
} finally {
loading = false;
}
}
/**
* Loads an event by its hex ID for editing
*/
async function loadEventById(): Promise<void> {
if (!eventIdSearch.trim()) {
error = "Please enter an event ID.";
return;
}
const eventId = eventIdSearch.trim();
// Validate hex format
if (!/^[a-fA-F0-9]{64}$/.test(eventId)) {
error = "Invalid event ID format. Must be a 64-character hex string.";
return;
}
loadingEvent = true;
error = null;
try {
const loadedEvent = await loadEvent(ndk, eventId);
if (loadedEvent) {
eventData = loadedEvent.eventData;
tags = loadedEvent.tags;
success = `Loaded event ${eventId.substring(0, 8)}...`;
} else {
error = `Event ${eventId} not found on any relay.`;
}
} catch (err) {
console.error("Error loading event:", err);
error = `Failed to load event: ${err instanceof Error ? err.message : "Unknown error"}`;
} finally {
loadingEvent = false;
}
}
/**
* Clears all form fields and resets to initial state
*/
function clearForm(): void {
eventData = {
kind: 1,
content: "",
createdAt: Math.floor(Date.now() / 1000),
};
tags = [];
error = null;
success = null;
publishedRelays = [];
lastPublishedEventId = null;
eventIdSearch = "";
showJsonPreview = false;
}
/**
* Navigate to view the published event
*/
function viewPublishedEvent() {
if (lastPublishedEventId) {
goto(`/events?id=${encodeURIComponent(lastPublishedEventId)}`);
}
}
</script>
<div class="w-full max-w-2xl mx-auto my-8 p-6 bg-white dark:bg-gray-900 rounded-lg shadow-lg">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold text-gray-900 dark:text-gray-100">Publish Nostr Event</h2>
<div class="flex gap-2">
<button
type="button"
class="btn btn-outline btn-secondary btn-sm"
onclick={clearForm}
>
Clear Form
</button>
<button
type="button"
class="btn btn-primary btn-sm border border-primary-600"
onclick={() => {
// Trigger validation by submitting the form
const form = document.querySelector('form');
if (form) {
form.dispatchEvent(new Event('submit', { bubbles: true }));
}
}}
>
Validate Form
</button>
</div>
</div>
<!-- Event ID Search Section -->
<div class="mb-6 p-4 bg-gray-50 dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-600">
<h3 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Load Existing Event</h3>
<div class="flex gap-2">
<input
type="text"
class="input input-bordered flex-1"
placeholder="Enter 64-character hex event ID"
bind:value={eventIdSearch}
maxlength="64"
onkeydown={(e) => {
if (e.key === 'Enter' && !loadingEvent && eventIdSearch.trim()) {
e.preventDefault();
loadEventById();
}
}}
/>
<button
type="button"
class="btn btn-secondary"
onclick={loadEventById}
disabled={loadingEvent || !eventIdSearch.trim()}
>
{loadingEvent ? 'Loading...' : 'Load Event'}
</button>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
Load an existing event to edit and publish as a replacement with your signature.
</p>
</div>
<!-- Main Form -->
<EventForm
bind:eventData
{tags}
onvalidate={handleValidate}
/>
<!-- Tag Management -->
<TagManager
bind:tags
kind={eventData.kind}
content={eventData.content}
/>
<!-- Action Buttons -->
<div class="flex justify-end gap-2 mt-4">
<button
type="button"
class="btn btn-primary border border-primary-600 px-4 py-2"
onclick={handlePublish}
disabled={loading}
>
Publish
</button>
</div>
<!-- Status Messages -->
{#if loading}
<div class="mt-2 text-gray-500 dark:text-gray-400">Publishing...</div>
{/if}
{#if error}
<div class="mt-2 text-red-600 dark:text-red-400">{error}</div>
{/if}
{#if success}
<div class="mt-2 text-green-600 dark:text-green-400">{success}</div>
<div class="text-xs text-gray-500 dark:text-gray-400">
Relays: {publishedRelays.join(", ")}
</div>
{#if lastPublishedEventId}
<div class="mt-2 text-green-700 dark:text-green-300">
Event ID: <span class="font-mono">{lastPublishedEventId}</span>
<Button
onclick={viewPublishedEvent}
class="text-blue-600 dark:text-blue-500 hover:underline ml-2"
>
View your event
</Button>
</div>
{/if}
{/if}
<!-- Event Preview -->
<EventPreview
{ndk}
{eventData}
{tags}
{showJsonPreview}
onTogglePreview={() => showJsonPreview = !showJsonPreview}
/>
</div>