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.
 
 
 
 
 

513 lines
13 KiB

<script lang="ts">
import { sessionManager } from '../../services/auth/session-manager.js';
import { signAndPublish } from '../../services/nostr/auth-handler.js';
import { relayManager } from '../../services/nostr/relay-manager.js';
import { cacheEvent } from '../../services/cache/event-cache.js';
import PublicationStatusModal from '../modals/PublicationStatusModal.svelte';
import AdvancedEditor from './AdvancedEditor.svelte';
import { goto } from '$app/navigation';
import type { NostrEvent } from '../../types/nostr.js';
interface Props {
event: NostrEvent;
}
let { event }: Props = $props();
let content = $state('');
let tags = $state<string[][]>([]);
let publishing = $state(false);
let publicationModalOpen = $state(false);
let publicationResults = $state<{ success: string[]; failed: Array<{ relay: string; error: string }> } | null>(null);
let showAdvancedEditor = $state(false);
// Determine editor mode based on event kind
const editorMode = $derived(
event.kind === 30818 || event.kind === 30041 ? 'asciidoc' : 'markdown'
);
// Sync state when event prop changes
$effect(() => {
content = event.content || '';
tags = [...event.tags];
});
function addTag() {
tags = [...tags, ['', '']];
}
function removeTag(index: number) {
tags = tags.filter((_, i) => i !== index);
}
function updateTag(index: number, field: number, value: string) {
const newTags = [...tags];
if (!newTags[index]) {
newTags[index] = ['', ''];
}
newTags[index] = [...newTags[index]];
newTags[index][field] = value;
// Ensure tag has enough elements
while (newTags[index].length <= field) {
newTags[index].push('');
}
tags = newTags;
}
async function publish() {
const session = sessionManager.getSession();
if (!session) {
alert('You must be logged in to publish events');
return;
}
publishing = true;
try {
// Process content to add "nostr:" prefix to valid Nostr addresses
const { processNostrLinks } = await import('../../utils/nostr-link-processor.js');
const processedContent = processNostrLinks(content);
// Create new event (id, sig, created_at will be generated)
const eventTemplate = {
kind: event.kind,
pubkey: session.pubkey,
created_at: Math.floor(Date.now() / 1000),
tags: tags.filter(t => t[0] && t[1]), // Filter out empty tags
content: processedContent
};
// Sign event
const signedEvent = await session.signer(eventTemplate);
// Cache event
await cacheEvent(signedEvent);
// Publish to write relays
const relays = relayManager.getPublishRelays(
relayManager.getProfileReadRelays(),
true
);
const results = await signAndPublish(eventTemplate, relays);
publicationResults = results;
publicationModalOpen = true;
// If successful, wait 5 seconds and navigate
if (results.success.length > 0) {
setTimeout(() => {
// Store event in sessionStorage so the event page can use it without re-fetching
sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(signedEvent));
goto(`/event/${signedEvent.id}`);
}, 5000);
}
} catch (error) {
console.error('Error publishing event:', error);
publicationResults = {
success: [],
failed: [{ relay: 'Unknown', error: error instanceof Error ? error.message : 'Unknown error' }]
};
publicationModalOpen = true;
} finally {
publishing = false;
}
}
async function republishFromCache() {
if (!publicationResults) return;
publishing = true;
try {
const relays = relayManager.getPublishRelays(
relayManager.getProfileReadRelays(),
true
);
// Process content to add "nostr:" prefix to valid Nostr addresses
const { processNostrLinks } = await import('../../utils/nostr-link-processor.js');
const processedContent = processNostrLinks(content);
const results = await signAndPublish(
{
kind: event.kind,
pubkey: event.pubkey,
created_at: event.created_at,
tags: tags.filter(t => t[0] && t[1]),
content: processedContent
},
relays
);
publicationResults = results;
} catch (error) {
console.error('Error republishing:', error);
} finally {
publishing = false;
}
}
</script>
<div class="edit-form">
<h2 class="form-title">Edit/Fork Event</h2>
<p class="form-description">Edit the event content and tags. ID, kind, pubkey, sig, and created_at are generated on publish.</p>
<div class="form-group">
<div class="content-header">
<label for="content-textarea" class="form-label">Content</label>
<button
type="button"
class="advanced-editor-button"
onclick={() => showAdvancedEditor = true}
disabled={publishing}
title="Open advanced editor"
>
Advanced Editor
</button>
</div>
<textarea
id="content-textarea"
bind:value={content}
class="content-input"
rows="10"
placeholder="Event content..."
></textarea>
</div>
<div class="form-group">
<fieldset>
<legend class="form-label">Tags</legend>
<div class="tags-list">
{#each tags as tag, index (index)}
<div class="tag-row">
<input
type="text"
value={tag[0] || ''}
oninput={(e) => updateTag(index, 0, e.currentTarget.value)}
placeholder="Tag name"
class="tag-input"
/>
{#each tag.slice(1) as value, valueIndex}
<input
type="text"
value={value || ''}
oninput={(e) => updateTag(index, valueIndex + 1, e.currentTarget.value)}
placeholder="Tag value"
class="tag-input"
/>
{/each}
<button class="tag-add-value" onclick={() => {
const newTags = [...tags];
newTags[index] = [...newTags[index], ''];
tags = newTags;
}}>+</button>
<button class="tag-remove" onclick={() => removeTag(index)}>×</button>
</div>
{/each}
<button class="add-tag-button" onclick={addTag}>Add Tag</button>
</div>
</fieldset>
</div>
<div class="form-actions">
<button
class="publish-button"
onclick={publish}
disabled={publishing}
>
{publishing ? 'Publishing...' : 'Publish'}
</button>
</div>
</div>
<PublicationStatusModal bind:open={publicationModalOpen} bind:results={publicationResults} />
{#if showAdvancedEditor}
<AdvancedEditor
value={content}
mode={editorMode}
onUpdate={(newContent) => {
content = newContent;
}}
onClose={() => showAdvancedEditor = false}
/>
{/if}
{#if publicationResults && publicationResults.success.length === 0 && publicationResults.failed.length > 0}
<div class="republish-section">
<p class="republish-text">All relays failed. You can attempt to republish from cache.</p>
<button class="republish-button" onclick={republishFromCache} disabled={publishing}>
Republish from Cache
</button>
</div>
{/if}
<style>
.edit-form {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.form-title {
margin: 0;
font-size: 1.5rem;
font-weight: 600;
color: var(--fog-text, #1f2937);
}
:global(.dark) .form-title {
color: var(--fog-dark-text, #f9fafb);
}
.form-description {
margin: 0;
color: var(--fog-text-light, #52667a);
font-size: 0.875rem;
}
:global(.dark) .form-description {
color: var(--fog-dark-text-light, #a8b8d0);
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.content-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.5rem;
}
.advanced-editor-button {
padding: 0.5rem 1rem;
border: 1px solid var(--fog-border, #e5e7eb);
border-radius: 0.25rem;
background: var(--fog-highlight, #f3f4f6);
color: var(--fog-text, #475569);
cursor: pointer;
font-size: 0.875rem;
font-weight: 500;
transition: all 0.2s;
}
:global(.dark) .advanced-editor-button {
border-color: var(--fog-dark-border, #374151);
background: var(--fog-dark-highlight, #374151);
color: var(--fog-dark-text, #cbd5e1);
}
.advanced-editor-button:hover:not(:disabled) {
background: var(--fog-accent, #64748b);
color: var(--fog-text, #f1f5f9);
border-color: var(--fog-accent, #64748b);
}
:global(.dark) .advanced-editor-button:hover:not(:disabled) {
background: var(--fog-dark-accent, #94a3b8);
border-color: var(--fog-dark-accent, #94a3b8);
}
.advanced-editor-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
@media (max-width: 768px) {
.content-header {
flex-direction: column;
align-items: flex-start;
}
.advanced-editor-button {
width: 100%;
}
}
.form-label {
font-weight: 500;
color: var(--fog-text, #1f2937);
font-size: 0.875rem;
}
:global(.dark) .form-label {
color: var(--fog-dark-text, #f9fafb);
}
.content-input {
padding: 0.75rem;
border: 1px solid var(--fog-border, #e5e7eb);
border-radius: 0.25rem;
background: var(--fog-post, #ffffff);
color: var(--fog-text, #1f2937);
font-size: 0.875rem;
font-family: monospace;
resize: vertical;
}
:global(.dark) .content-input {
border-color: var(--fog-dark-border, #374151);
background: var(--fog-dark-post, #1f2937);
color: var(--fog-dark-text, #f9fafb);
}
.tags-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.tag-row {
display: flex;
gap: 0.5rem;
align-items: center;
}
.tag-input {
flex: 1;
padding: 0.5rem;
border: 1px solid var(--fog-border, #e5e7eb);
border-radius: 0.25rem;
background: var(--fog-post, #ffffff);
color: var(--fog-text, #1f2937);
font-size: 0.875rem;
}
:global(.dark) .tag-input {
border-color: var(--fog-dark-border, #374151);
background: var(--fog-dark-post, #1f2937);
color: var(--fog-dark-text, #f9fafb);
}
.tag-add-value,
.tag-remove {
padding: 0.5rem;
border: 1px solid var(--fog-border, #e5e7eb);
border-radius: 0.25rem;
background: var(--fog-highlight, #f3f4f6);
color: var(--fog-text, #1f2937);
cursor: pointer;
font-size: 0.875rem;
min-width: 2rem;
}
:global(.dark) .tag-add-value,
:global(.dark) .tag-remove {
border-color: var(--fog-dark-border, #374151);
background: var(--fog-dark-highlight, #374151);
color: var(--fog-dark-text, #f9fafb);
}
.tag-add-value:hover,
.tag-remove:hover {
background: var(--fog-border, #e5e7eb);
}
:global(.dark) .tag-add-value:hover,
:global(.dark) .tag-remove:hover {
background: var(--fog-dark-border, #475569);
}
.add-tag-button {
padding: 0.5rem 1rem;
border: 1px solid var(--fog-border, #e5e7eb);
border-radius: 0.25rem;
background: var(--fog-highlight, #f3f4f6);
color: var(--fog-text, #1f2937);
cursor: pointer;
font-size: 0.875rem;
align-self: flex-start;
}
:global(.dark) .add-tag-button {
border-color: var(--fog-dark-border, #374151);
background: var(--fog-dark-highlight, #374151);
color: var(--fog-dark-text, #f9fafb);
}
.add-tag-button:hover {
background: var(--fog-border, #e5e7eb);
}
:global(.dark) .add-tag-button:hover {
background: var(--fog-dark-border, #475569);
}
.form-actions {
display: flex;
gap: 0.5rem;
}
.publish-button {
padding: 0.75rem 1.5rem;
background: var(--fog-accent, #64748b);
color: white;
border: none;
border-radius: 0.25rem;
cursor: pointer;
font-size: 0.875rem;
font-weight: 500;
}
:global(.dark) .publish-button {
background: var(--fog-dark-accent, #94a3b8);
}
.publish-button:hover:not(:disabled) {
opacity: 0.9;
}
.publish-button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.republish-section {
margin-top: 1rem;
padding: 1rem;
border: 1px solid var(--fog-border, #e5e7eb);
border-radius: 0.25rem;
background: var(--fog-highlight, #f3f4f6);
}
:global(.dark) .republish-section {
border-color: var(--fog-dark-border, #374151);
background: var(--fog-dark-highlight, #374151);
}
.republish-text {
margin: 0 0 0.5rem 0;
color: var(--fog-text, #1f2937);
font-size: 0.875rem;
}
:global(.dark) .republish-text {
color: var(--fog-dark-text, #f9fafb);
}
.republish-button {
padding: 0.5rem 1rem;
background: var(--fog-accent, #64748b);
color: white;
border: none;
border-radius: 0.25rem;
cursor: pointer;
font-size: 0.875rem;
}
:global(.dark) .republish-button {
background: var(--fog-dark-accent, #94a3b8);
}
.republish-button:hover:not(:disabled) {
opacity: 0.9;
}
.republish-button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
</style>