feat: Open blossom server file lists directly from menus

**User Menu & Profile Viewer Improvements:**

1. **Enhanced Click Behavior**:
   - Clicking a blossom server now opens the file list for that server
   - Shows blobs uploaded by the user (user menu) or profile owner (profile viewer)
   - Pre-selects the clicked server in the dropdown

2. **UX Improvements**:
   - Removed server count from user menu label (cleaner UI)
   - Added `cursor-crosshair` to blossom server items (consistent with other clickable items)
   - Removed external link icon (not opening external URL anymore)

3. **Technical Changes**:
   - Updated `ListBlobsView` to accept optional `serverUrl` prop for pre-selection
   - User menu: Opens `blossom list` with `serverUrl` for active user
   - Profile viewer: Opens `blossom list` with both `pubkey` and `serverUrl`

**Flow:**
- User menu → Click server → Opens files for active user on that server
- Profile viewer → Click server → Opens files for viewed user on that server
This commit is contained in:
Claude
2026-01-14 11:24:27 +00:00
parent fc56dc9388
commit 37004dc83f
3 changed files with 41 additions and 23 deletions

View File

@@ -72,7 +72,7 @@ export function BlossomViewer({
case "upload":
return <UploadView />;
case "list":
return <ListBlobsView pubkey={pubkey} />;
return <ListBlobsView pubkey={pubkey} serverUrl={serverUrl} />;
case "blob":
return <BlobDetailView sha256={sha256!} serverUrl={serverUrl} />;
case "mirror":
@@ -753,7 +753,13 @@ function formatSize(bytes: number): string {
/**
* ListBlobsView - List blobs for a user
*/
function ListBlobsView({ pubkey }: { pubkey?: string }) {
function ListBlobsView({
pubkey,
serverUrl,
}: {
pubkey?: string;
serverUrl?: string;
}) {
const { state } = useGrimoire();
const eventStore = useEventStore();
const accountPubkey = state.activeAccount?.pubkey;
@@ -762,7 +768,9 @@ function ListBlobsView({ pubkey }: { pubkey?: string }) {
const [servers, setServers] = useState<string[]>([]);
const [blobs, setBlobs] = useState<BlobDescriptor[]>([]);
const [loading, setLoading] = useState(true);
const [selectedServer, setSelectedServer] = useState<string | null>(null);
const [selectedServer, setSelectedServer] = useState<string | null>(
serverUrl || null,
);
const [selectedBlob, setSelectedBlob] = useState<BlobDescriptor | null>(null);
// Fetch servers for the target pubkey
@@ -780,7 +788,8 @@ function ListBlobsView({ pubkey }: { pubkey?: string }) {
if (event) {
const s = getServersFromEvent(event);
setServers(s);
if (s.length > 0 && !selectedServer) {
// Only set default server if no serverUrl was provided and no server is selected
if (s.length > 0 && !selectedServer && !serverUrl) {
setSelectedServer(s[0]);
}
}
@@ -799,7 +808,8 @@ function ListBlobsView({ pubkey }: { pubkey?: string }) {
if (e) {
const s = getServersFromEvent(e);
setServers(s);
if (s.length > 0 && !selectedServer) {
// Only set default server if no serverUrl was provided and no server is selected
if (s.length > 0 && !selectedServer && !serverUrl) {
setSelectedServer(s[0]);
}
}

View File

@@ -10,7 +10,6 @@ import {
Send,
Wifi,
HardDrive,
ExternalLink,
} from "lucide-react";
import { kinds, nip19 } from "nostr-tools";
import { useEventStore, use$ } from "applesauce-react/hooks";
@@ -43,7 +42,7 @@ export interface ProfileViewerProps {
* Shows profile metadata, inbox/outbox relays, and raw JSON
*/
export function ProfileViewer({ pubkey }: ProfileViewerProps) {
const { state } = useGrimoire();
const { state, addWindow } = useGrimoire();
const accountPubkey = state.activeAccount?.pubkey;
// Resolve $me alias
@@ -336,14 +335,25 @@ export function ProfileViewer({ pubkey }: ProfileViewerProps) {
{blossomServers.map((url) => (
<DropdownMenuItem
key={url}
className="flex items-center justify-between gap-2"
onClick={() => window.open(url, "_blank")}
className="flex items-center justify-between gap-2 cursor-crosshair"
onClick={() => {
if (resolvedPubkey) {
addWindow(
"blossom",
{
subcommand: "list",
pubkey: resolvedPubkey,
serverUrl: url,
},
`Files on ${url}`,
);
}
}}
>
<div className="flex items-center gap-1.5 flex-1 min-w-0">
<HardDrive className="size-3 text-muted-foreground flex-shrink-0" />
<span className="font-mono text-xs truncate">{url}</span>
</div>
<ExternalLink className="size-3 text-muted-foreground flex-shrink-0" />
</DropdownMenuItem>
))}
</DropdownMenuContent>

View File

@@ -1,4 +1,4 @@
import { User, HardDrive, ExternalLink } from "lucide-react";
import { User, HardDrive } from "lucide-react";
import accounts from "@/services/accounts";
import { useProfile } from "@/hooks/useProfile";
import { use$ } from "applesauce-react/hooks";
@@ -131,23 +131,21 @@ export default function UserMenu() {
<DropdownMenuLabel className="text-xs text-muted-foreground font-normal flex items-center gap-1.5">
<HardDrive className="size-3.5" />
<span>Blossom Servers</span>
<span className="ml-auto">({blossomServers.length})</span>
</DropdownMenuLabel>
{blossomServers.map((server) => (
<DropdownMenuItem
key={server}
className="cursor-pointer"
asChild
className="cursor-crosshair"
onClick={() => {
addWindow(
"blossom",
{ subcommand: "list", serverUrl: server },
`Files on ${server}`,
);
}}
>
<a
href={server}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2"
>
<ExternalLink className="size-4 text-muted-foreground" />
<span className="text-sm truncate">{server}</span>
</a>
<HardDrive className="size-4 text-muted-foreground mr-2" />
<span className="text-sm truncate">{server}</span>
</DropdownMenuItem>
))}
</DropdownMenuGroup>