mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-09 15:07:10 +02:00
fix: event publication
This commit is contained in:
@@ -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);
|
||||
* }
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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({
|
||||
<User className="size-2.5" />
|
||||
<span>you</span>
|
||||
</span>
|
||||
) : (
|
||||
) : owner ? (
|
||||
<span className="flex items-center gap-0.5" title="Others' spellbook">
|
||||
<Users className="size-2.5" />
|
||||
<span>other</span>
|
||||
<UserName pubkey={owner} />
|
||||
</span>
|
||||
)}
|
||||
) : null}
|
||||
<span className="opacity-50">•</span>
|
||||
{/* 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}
|
||||
</div>
|
||||
<SpellbookStatus
|
||||
owner={owner}
|
||||
isOwner={isOwner}
|
||||
isPublished={activeSpellbook.isPublished}
|
||||
isLocal={isInLibrary}
|
||||
@@ -359,6 +364,7 @@ export function SpellbookDropdown() {
|
||||
{activeSpellbook.title}
|
||||
</div>
|
||||
<SpellbookStatus
|
||||
owner={activeSpellbook.pubkey}
|
||||
isOwner={isOwner}
|
||||
isPublished={activeSpellbook.isPublished}
|
||||
isLocal={isInLibrary}
|
||||
@@ -461,12 +467,12 @@ export function SpellbookDropdown() {
|
||||
{sb.isPublished ? (
|
||||
<Cloud
|
||||
className="size-3 text-green-600 flex-shrink-0"
|
||||
title="Published"
|
||||
aria-label="Published"
|
||||
/>
|
||||
) : (
|
||||
<Lock
|
||||
className="size-3 text-muted-foreground flex-shrink-0"
|
||||
title="Local only"
|
||||
aria-label="Local only"
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import accountManager from "./accounts";
|
||||
*
|
||||
* @param event - The signed Nostr event to publish
|
||||
*/
|
||||
async function publishEvent(event: NostrEvent): Promise<void> {
|
||||
export async function publishEvent(event: NostrEvent): Promise<void> {
|
||||
// Try to get author's outbox relays from EventStore (kind 10002)
|
||||
let relays = await relayListCache.getOutboxRelays(event.pubkey);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user