feat(req): add --view flag for list/compact display mode

- Add -v, --view <list|compact> flag to req command
- Remove UI toggle controls from ReqViewer header
- View mode is now set via command flag instead of runtime toggle
- Update man page with new flag documentation and example

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Alejandro Gómez
2025-12-21 20:29:32 +01:00
parent 78a8c8e5b2
commit 3346a2077d
3 changed files with 32 additions and 73 deletions

View File

@@ -16,9 +16,6 @@ import {
Loader2,
Mail,
Send,
List,
Rows3,
Braces,
} from "lucide-react";
import { Virtuoso } from "react-virtuoso";
import { useReqTimeline } from "@/hooks/useReqTimeline";
@@ -76,10 +73,7 @@ import { resolveFilterAliases, getTagValues } from "@/lib/nostr-utils";
import { useNostrEvent } from "@/hooks/useNostrEvent";
import { cn } from "@/lib/utils";
import { MemoizedCompactEventRow } from "./nostr/CompactEventRow";
import { MemoizedJsonEventRow } from "./nostr/JsonEventRow";
// View mode type
type ViewMode = "list" | "compact" | "json";
import type { ViewMode } from "@/lib/req-parser";
// Memoized FeedEvent to prevent unnecessary re-renders during scroll
const MemoizedFeedEvent = memo(
@@ -91,6 +85,7 @@ interface ReqViewerProps {
filter: NostrFilter;
relays?: string[];
closeOnEose?: boolean;
view?: ViewMode;
nip05Authors?: string[];
nip05PTags?: string[];
needsAccount?: boolean;
@@ -643,6 +638,7 @@ export default function ReqViewer({
filter,
relays,
closeOnEose = false,
view = "list",
nip05Authors,
nip05PTags,
needsAccount = false,
@@ -756,7 +752,6 @@ export default function ReqViewer({
const [exportFilename, setExportFilename] = useState("");
const [isExporting, setIsExporting] = useState(false);
const [exportProgress, setExportProgress] = useState(0);
const [viewMode, setViewMode] = useState<ViewMode>("list");
// Freeze timeline after EOSE to prevent auto-scrolling on new events
const [freezePoint, setFreezePoint] = useState<string | null>(null);
@@ -1115,61 +1110,6 @@ export default function ReqViewer({
</DropdownMenuContent>
</DropdownMenu>
{/* View Mode Toggle */}
<div className="flex items-center gap-0.5 border-l border-border pl-3">
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={() => setViewMode("list")}
className={cn(
"p-1 rounded transition-colors",
viewMode === "list"
? "text-foreground bg-muted"
: "text-muted-foreground hover:text-foreground",
)}
aria-label="List view"
>
<List className="size-3" />
</button>
</TooltipTrigger>
<TooltipContent>List view</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={() => setViewMode("compact")}
className={cn(
"p-1 rounded transition-colors",
viewMode === "compact"
? "text-foreground bg-muted"
: "text-muted-foreground hover:text-foreground",
)}
aria-label="Compact view"
>
<Rows3 className="size-3" />
</button>
</TooltipTrigger>
<TooltipContent>Compact view</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={() => setViewMode("json")}
className={cn(
"p-1 rounded transition-colors",
viewMode === "json"
? "text-foreground bg-muted"
: "text-muted-foreground hover:text-foreground",
)}
aria-label="JSON view"
>
<Braces className="size-3" />
</button>
</TooltipTrigger>
<TooltipContent>JSON view</TooltipContent>
</Tooltip>
</div>
{/* Query (Clickable) */}
<button
onClick={() => setShowQuery(!showQuery)}
@@ -1263,16 +1203,13 @@ export default function ReqViewer({
style={{ height: "100%" }}
data={visibleEvents}
computeItemKey={(_index, item) => item.id}
itemContent={(_index, event) => {
switch (viewMode) {
case "compact":
return <MemoizedCompactEventRow event={event} />;
case "json":
return <MemoizedJsonEventRow event={event} />;
default:
return <MemoizedFeedEvent event={event} />;
}
}}
itemContent={(_index, event) =>
view === "compact" ? (
<MemoizedCompactEventRow event={event} />
) : (
<MemoizedFeedEvent event={event} />
)
}
/>
)}
</div>

View File

@@ -8,10 +8,13 @@ import {
} from "./nostr-validation";
import { normalizeRelayURL } from "./relay-url";
export type ViewMode = "list" | "compact";
export interface ParsedReqCommand {
filter: NostrFilter;
relays?: string[];
closeOnEose?: boolean;
view?: ViewMode; // Display mode for results
nip05Authors?: string[]; // NIP-05 identifiers that need async resolution
nip05PTags?: string[]; // NIP-05 identifiers for #p tags that need async resolution
nip05PTagsUppercase?: string[]; // NIP-05 identifiers for #P tags that need async resolution
@@ -73,6 +76,7 @@ export function parseReqCommand(args: string[]): ParsedReqCommand {
const genericTags = new Map<string, Set<string>>();
let closeOnEose = false;
let view: ViewMode | undefined;
let i = 0;
@@ -342,6 +346,17 @@ export function parseReqCommand(args: string[]): ParsedReqCommand {
break;
}
case "--view":
case "-v": {
if (nextArg === "list" || nextArg === "compact") {
view = nextArg;
i += 2;
} else {
i++;
}
break;
}
case "-T":
case "--tag": {
// Generic tag filter: --tag <letter> <value>
@@ -417,6 +432,7 @@ export function parseReqCommand(args: string[]): ParsedReqCommand {
filter,
relays: relays.length > 0 ? relays : undefined,
closeOnEose,
view,
nip05Authors: nip05Authors.size > 0 ? Array.from(nip05Authors) : undefined,
nip05PTags: nip05PTags.size > 0 ? Array.from(nip05PTags) : undefined,
nip05PTagsUppercase:

View File

@@ -220,6 +220,11 @@ export const manPages: Record<string, ManPageEntry> = {
description:
"Close connection after EOSE (End Of Stored Events). By default, streams stay open for real-time updates.",
},
{
flag: "-v, --view <list|compact>",
description:
"Display mode for results. 'list' shows full event cards, 'compact' shows condensed single-line rows. Defaults to 'list'.",
},
{
flag: "[relay...]",
description:
@@ -253,6 +258,7 @@ export const manPages: Record<string, ManPageEntry> = {
"req -k 30023 --tag d badges,grimoire Get specific replaceable events by d-tag",
"req --search bitcoin -k 1 Search notes for 'bitcoin'",
"req -k 1 theforest.nostr1.com relay.damus.io Query specific relays (overrides auto-selection)",
"req -k 1 -l 100 --view compact Get notes in compact view mode",
],
seeAlso: ["kind", "nip"],
appId: "req",