diff --git a/src/actions/publish-spellbook.ts b/src/actions/publish-spellbook.ts index 849f60d..0eeb01b 100644 --- a/src/actions/publish-spellbook.ts +++ b/src/actions/publish-spellbook.ts @@ -35,7 +35,9 @@ export interface PublishSpellbookOptions { * @example * ```typescript * // Publish via ActionHub with proper side-effect handling - * for await (const event of hub.exec(PublishSpellbook, options)) { + * const event = await lastValueFrom(hub.exec(PublishSpellbook, options)); + * if (event) { + * await publishEvent(event); * // Only mark as published AFTER successful relay publish * await markSpellbookPublished(localId, event as SpellbookEvent); * } diff --git a/src/components/ReqViewer.tsx b/src/components/ReqViewer.tsx index 69a0255..ed78813 100644 --- a/src/components/ReqViewer.tsx +++ b/src/components/ReqViewer.tsx @@ -71,7 +71,6 @@ import { SyntaxHighlight } from "@/components/SyntaxHighlight"; import { getConnectionIcon, getAuthIcon } from "@/lib/relay-status-utils"; import { resolveFilterAliases, getTagValues } from "@/lib/nostr-utils"; import { useNostrEvent } from "@/hooks/useNostrEvent"; -import { cn } from "@/lib/utils"; import { MemoizedCompactEventRow } from "./nostr/CompactEventRow"; import type { ViewMode } from "@/lib/req-parser"; diff --git a/src/components/SaveSpellbookDialog.tsx b/src/components/SaveSpellbookDialog.tsx index 73b5afd..7035d15 100644 --- a/src/components/SaveSpellbookDialog.tsx +++ b/src/components/SaveSpellbookDialog.tsx @@ -19,10 +19,11 @@ import { markSpellbookPublished, } from "@/services/spellbook-storage"; import { PublishSpellbook } from "@/actions/publish-spellbook"; -import { hub } from "@/services/hub"; +import { hub, publishEvent } from "@/services/hub"; import { createSpellbook } from "@/lib/spellbook-manager"; import { Loader2, Save, Send } from "lucide-react"; import type { SpellbookEvent } from "@/types/spell"; +import { lastValueFrom } from "rxjs"; interface SaveSpellbookDialogProps { open: boolean; @@ -112,17 +113,22 @@ export function SaveSpellbookDialog({ // 4. Optionally publish if (shouldPublish) { const localId = existingSpellbook?.localId || localSpellbook.id; - // Use hub.exec() to get the event and handle side effects after successful publish - for await (const event of hub.exec(PublishSpellbook, { - state, - title, - description, - workspaceIds: selectedWorkspaces, - content: localSpellbook.content, - })) { + const event = await lastValueFrom( + hub.exec(PublishSpellbook, { + state, + title, + description, + workspaceIds: selectedWorkspaces, + content: localSpellbook.content, + }), + ); + + if (event) { + await publishEvent(event); // Only mark as published AFTER successful relay publish await markSpellbookPublished(localId, event as SpellbookEvent); } + toast.success( isUpdateMode ? "Spellbook updated and published to Nostr" diff --git a/src/components/SpellbookDropdown.tsx b/src/components/SpellbookDropdown.tsx index da2c29a..0394a43 100644 --- a/src/components/SpellbookDropdown.tsx +++ b/src/components/SpellbookDropdown.tsx @@ -34,16 +34,19 @@ import { import { cn } from "@/lib/utils"; import { SaveSpellbookDialog } from "./SaveSpellbookDialog"; import { toast } from "sonner"; +import { UserName } from "./nostr/UserName"; /** * Status indicator component for spellbook state */ function SpellbookStatus({ + owner, isOwner, isPublished, isLocal, className, }: { + owner?: string; isOwner: boolean; isPublished?: boolean; isLocal?: boolean; @@ -62,12 +65,12 @@ function SpellbookStatus({ you - ) : ( + ) : owner ? ( - other + - )} + ) : null} {/* Storage status */} {isPublished ? ( @@ -187,6 +190,7 @@ export function SpellbookDropdown() { ); }, [localSpellbooks, networkEvents, activeAccount]); + const owner = activeSpellbook?.pubkey; // Derived states for clearer UX const isOwner = useMemo(() => { if (!activeSpellbook) return false; @@ -311,6 +315,7 @@ export function SpellbookDropdown() { {activeSpellbook.title} ) : ( )} diff --git a/src/components/SpellbooksViewer.tsx b/src/components/SpellbooksViewer.tsx index 04ef4cb..0bc9bb8 100644 --- a/src/components/SpellbooksViewer.tsx +++ b/src/components/SpellbooksViewer.tsx @@ -35,7 +35,7 @@ import { import type { LocalSpellbook } from "@/services/db"; import { PublishSpellbook } from "@/actions/publish-spellbook"; import { DeleteEventAction } from "@/actions/delete-event"; -import { hub } from "@/services/hub"; +import { hub, publishEvent } from "@/services/hub"; import { useGrimoire } from "@/core/state"; import { cn } from "@/lib/utils"; import { useReqTimeline } from "@/hooks/useReqTimeline"; @@ -44,6 +44,7 @@ import type { SpellbookEvent, ParsedSpellbook } from "@/types/spell"; import { SPELLBOOK_KIND } from "@/constants/kinds"; import { UserName } from "./nostr/UserName"; import { AGGREGATOR_RELAYS } from "@/services/loaders"; +import { lastValueFrom } from "rxjs"; interface SpellbookCardProps { spellbook: LocalSpellbook; @@ -380,16 +381,22 @@ export function SpellbooksViewer() { const handlePublish = async (spellbook: LocalSpellbook) => { try { // Use hub.exec() to get the event and handle side effects after successful publish - for await (const event of hub.exec(PublishSpellbook, { - state, - title: spellbook.title, - description: spellbook.description, - workspaceIds: Object.keys(spellbook.content.workspaces), - content: spellbook.content, - })) { + const event = await lastValueFrom( + hub.exec(PublishSpellbook, { + state, + title: spellbook.title, + description: spellbook.description, + workspaceIds: Object.keys(spellbook.content.workspaces), + content: spellbook.content, + }), + ); + + if (event) { + await publishEvent(event); // Only mark as published AFTER successful relay publish await markSpellbookPublished(spellbook.id, event as SpellbookEvent); } + toast.success("Spellbook published"); } catch (error) { toast.error( diff --git a/src/components/nostr/compact/ReactionCompactPreview.tsx b/src/components/nostr/compact/ReactionCompactPreview.tsx index 84a4293..c5307b1 100644 --- a/src/components/nostr/compact/ReactionCompactPreview.tsx +++ b/src/components/nostr/compact/ReactionCompactPreview.tsx @@ -2,7 +2,6 @@ import type { NostrEvent } from "@/types/nostr"; import { useMemo } from "react"; import { Heart, ThumbsUp, ThumbsDown, Flame, Smile } from "lucide-react"; import { useNostrEvent } from "@/hooks/useNostrEvent"; -import { getContentPreview } from "./index"; import { UserName } from "../UserName"; import { RichText } from "../RichText"; @@ -83,9 +82,6 @@ export function ReactionCompactPreview({ event }: { event: NostrEvent }) { // Fetch the reacted event const reactedEvent = useNostrEvent(eventPointer); - // Get content preview - const preview = reactedEvent ? getContentPreview(reactedEvent, 50) : null; - // Map common reactions to icons for compact display const getReactionDisplay = (content: string) => { switch (content) { diff --git a/src/components/nostr/compact/RepostCompactPreview.tsx b/src/components/nostr/compact/RepostCompactPreview.tsx index 3e12c0e..749857a 100644 --- a/src/components/nostr/compact/RepostCompactPreview.tsx +++ b/src/components/nostr/compact/RepostCompactPreview.tsx @@ -1,8 +1,6 @@ import type { NostrEvent } from "@/types/nostr"; import { useMemo } from "react"; -import { Repeat2 } from "lucide-react"; import { useNostrEvent } from "@/hooks/useNostrEvent"; -import { getContentPreview } from "./index"; import { UserName } from "../UserName"; import { RichText } from "../RichText"; diff --git a/src/components/nostr/kinds/RepositoryStateRenderer.tsx b/src/components/nostr/kinds/RepositoryStateRenderer.tsx index 9aef180..2044bf5 100644 --- a/src/components/nostr/kinds/RepositoryStateRenderer.tsx +++ b/src/components/nostr/kinds/RepositoryStateRenderer.tsx @@ -10,7 +10,7 @@ import { parseHeadBranch, getRepositoryStateHead, } from "@/lib/nip34-helpers"; -import { Label } from "@/components/ui/Label"; +import { Label } from "@/components/ui/label"; import { RepositoryLink } from "../RepositoryLink"; /** diff --git a/src/components/ui/skeleton/InlineReplySkeleton.tsx b/src/components/ui/skeleton/InlineReplySkeleton.tsx index b1a97d8..ea06200 100644 --- a/src/components/ui/skeleton/InlineReplySkeleton.tsx +++ b/src/components/ui/skeleton/InlineReplySkeleton.tsx @@ -3,7 +3,7 @@ import { Skeleton } from "./Skeleton"; export interface InlineReplySkeletonProps { /** Icon to show on the left (Reply, MessageCircle, etc.) */ - icon: React.ReactNode; + icon?: React.ReactNode; className?: string; } diff --git a/src/services/hub.ts b/src/services/hub.ts index 9a07942..dcbc33c 100644 --- a/src/services/hub.ts +++ b/src/services/hub.ts @@ -13,7 +13,7 @@ import accountManager from "./accounts"; * * @param event - The signed Nostr event to publish */ -async function publishEvent(event: NostrEvent): Promise { +export async function publishEvent(event: NostrEvent): Promise { // Try to get author's outbox relays from EventStore (kind 10002) let relays = await relayListCache.getOutboxRelays(event.pubkey);