mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-06-05 10:11:12 +02:00
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:
@@ -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>
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user