mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-09 15:07:10 +02:00
refactor: repo link
This commit is contained in:
111
src/components/nostr/RepositoryLink.tsx
Normal file
111
src/components/nostr/RepositoryLink.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import { useMemo } from "react";
|
||||
import { FolderGit2 } from "lucide-react";
|
||||
import { useGrimoire } from "@/core/state";
|
||||
import { useNostrEvent } from "@/hooks/useNostrEvent";
|
||||
import {
|
||||
getRepositoryName,
|
||||
getRepositoryIdentifier,
|
||||
} from "@/lib/nip34-helpers";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface RepoPointer {
|
||||
kind: number;
|
||||
pubkey: string;
|
||||
identifier: string;
|
||||
}
|
||||
|
||||
interface RepositoryLinkProps {
|
||||
/** Repository address in "kind:pubkey:identifier" format */
|
||||
repoAddress?: string;
|
||||
/** Direct repository pointer (takes precedence over repoAddress) */
|
||||
repoPointer?: RepoPointer | null;
|
||||
/** Additional CSS classes */
|
||||
className?: string;
|
||||
/** Icon size class (default: "size-3") */
|
||||
iconSize?: string;
|
||||
/** Whether to show inline (no wrapping div) */
|
||||
inline?: boolean;
|
||||
/** Whether to show the icon */
|
||||
showIcon?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reusable repository link component for git-related events.
|
||||
* Fetches repository metadata and renders a clickable link.
|
||||
*/
|
||||
export function RepositoryLink({
|
||||
repoAddress,
|
||||
repoPointer: externalPointer,
|
||||
className,
|
||||
iconSize = "size-3",
|
||||
inline = false,
|
||||
showIcon = true,
|
||||
}: RepositoryLinkProps) {
|
||||
const { addWindow } = useGrimoire();
|
||||
|
||||
// Parse repository address to get the pointer (if not provided directly)
|
||||
const repoPointer = useMemo(() => {
|
||||
if (externalPointer) return externalPointer;
|
||||
if (!repoAddress) return null;
|
||||
|
||||
try {
|
||||
const [kindStr, pubkey, identifier] = repoAddress.split(":");
|
||||
return {
|
||||
kind: parseInt(kindStr),
|
||||
pubkey,
|
||||
identifier,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}, [externalPointer, repoAddress]);
|
||||
|
||||
// Fetch the repository event to get its name
|
||||
const repoEvent = useNostrEvent(repoPointer || undefined);
|
||||
|
||||
// Get repository display name
|
||||
const repoName = useMemo(() => {
|
||||
if (repoEvent) {
|
||||
return (
|
||||
getRepositoryName(repoEvent) ||
|
||||
getRepositoryIdentifier(repoEvent) ||
|
||||
"Repository"
|
||||
);
|
||||
}
|
||||
// Fall back to identifier from address or pointer
|
||||
if (repoPointer?.identifier) return repoPointer.identifier;
|
||||
if (repoAddress) return repoAddress.split(":")[2] || "Unknown Repository";
|
||||
return "Unknown Repository";
|
||||
}, [repoEvent, repoPointer, repoAddress]);
|
||||
|
||||
const handleClick = () => {
|
||||
if (!repoPointer) return;
|
||||
addWindow("open", { pointer: repoPointer });
|
||||
};
|
||||
|
||||
if (!repoAddress && !externalPointer) return null;
|
||||
|
||||
const linkContent = (
|
||||
<>
|
||||
{showIcon && <FolderGit2 className={cn(iconSize, "flex-shrink-0")} />}
|
||||
<span>{repoName}</span>
|
||||
</>
|
||||
);
|
||||
|
||||
const baseClasses =
|
||||
"flex items-center gap-1 text-muted-foreground cursor-crosshair underline decoration-dotted hover:text-primary";
|
||||
|
||||
if (inline) {
|
||||
return (
|
||||
<span onClick={handleClick} className={cn(baseClasses, className)}>
|
||||
{linkContent}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div onClick={handleClick} className={cn(baseClasses, className)}>
|
||||
{linkContent}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,55 +1,25 @@
|
||||
import { useMemo } from "react";
|
||||
import { Tag, FolderGit2 } from "lucide-react";
|
||||
import { Tag } from "lucide-react";
|
||||
import { UserName } from "../UserName";
|
||||
import { MarkdownContent } from "../MarkdownContent";
|
||||
import { useGrimoire } from "@/core/state";
|
||||
import { useNostrEvent } from "@/hooks/useNostrEvent";
|
||||
import type { NostrEvent } from "@/types/nostr";
|
||||
import {
|
||||
getIssueTitle,
|
||||
getIssueLabels,
|
||||
getIssueRepositoryAddress,
|
||||
getRepositoryName,
|
||||
getRepositoryIdentifier,
|
||||
} from "@/lib/nip34-helpers";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { RepositoryLink } from "../RepositoryLink";
|
||||
|
||||
/**
|
||||
* Detail renderer for Kind 1621 - Issue (NIP-34)
|
||||
* Full view with repository context and markdown description
|
||||
*/
|
||||
export function IssueDetailRenderer({ event }: { event: NostrEvent }) {
|
||||
const { addWindow } = useGrimoire();
|
||||
|
||||
const title = useMemo(() => getIssueTitle(event), [event]);
|
||||
const labels = useMemo(() => getIssueLabels(event), [event]);
|
||||
const repoAddress = useMemo(() => getIssueRepositoryAddress(event), [event]);
|
||||
|
||||
// Parse repository address if present
|
||||
const repoPointer = useMemo(() => {
|
||||
if (!repoAddress) return null;
|
||||
try {
|
||||
const [kindStr, pubkey, identifier] = repoAddress.split(":");
|
||||
return {
|
||||
kind: parseInt(kindStr),
|
||||
pubkey,
|
||||
identifier,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}, [repoAddress]);
|
||||
|
||||
// Fetch repository event
|
||||
const repoEvent = useNostrEvent(repoPointer || undefined);
|
||||
|
||||
// Get repository display name
|
||||
const repoName = repoEvent
|
||||
? getRepositoryName(repoEvent) ||
|
||||
getRepositoryIdentifier(repoEvent) ||
|
||||
"Repository"
|
||||
: repoPointer?.identifier || "Unknown Repository";
|
||||
|
||||
// Format created date
|
||||
const createdDate = new Date(event.created_at * 1000).toLocaleDateString(
|
||||
"en-US",
|
||||
@@ -60,11 +30,6 @@ export function IssueDetailRenderer({ event }: { event: NostrEvent }) {
|
||||
},
|
||||
);
|
||||
|
||||
const handleRepoClick = () => {
|
||||
if (!repoPointer || !repoEvent) return;
|
||||
addWindow("open", { pointer: repoPointer });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 p-4 max-w-3xl mx-auto">
|
||||
{/* Issue Header */}
|
||||
@@ -76,18 +41,11 @@ export function IssueDetailRenderer({ event }: { event: NostrEvent }) {
|
||||
{repoAddress && (
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<span className="text-muted-foreground">Repository:</span>
|
||||
<button
|
||||
onClick={repoEvent ? handleRepoClick : undefined}
|
||||
disabled={!repoEvent}
|
||||
className={`flex items-center gap-2 font-mono ${
|
||||
repoEvent
|
||||
? "text-muted-foreground underline decoration-dotted cursor-crosshair hover:text-primary"
|
||||
: "text-muted-foreground cursor-not-allowed"
|
||||
}`}
|
||||
>
|
||||
<FolderGit2 className="size-4" />
|
||||
{repoName}
|
||||
</button>
|
||||
<RepositoryLink
|
||||
repoAddress={repoAddress}
|
||||
iconSize="size-4"
|
||||
className="font-mono"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -3,69 +3,23 @@ import {
|
||||
type BaseEventProps,
|
||||
ClickableEventTitle,
|
||||
} from "./BaseEventRenderer";
|
||||
import { FolderGit2 } from "lucide-react";
|
||||
import { useGrimoire } from "@/core/state";
|
||||
import { useNostrEvent } from "@/hooks/useNostrEvent";
|
||||
import {
|
||||
getIssueTitle,
|
||||
getIssueLabels,
|
||||
getIssueRepositoryAddress,
|
||||
} from "@/lib/nip34-helpers";
|
||||
import {
|
||||
getRepositoryName,
|
||||
getRepositoryIdentifier,
|
||||
} from "@/lib/nip34-helpers";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { RepositoryLink } from "../RepositoryLink";
|
||||
|
||||
/**
|
||||
* Renderer for Kind 1621 - Issue
|
||||
* Displays as a compact issue card in feed view
|
||||
*/
|
||||
export function IssueRenderer({ event }: BaseEventProps) {
|
||||
const { addWindow } = useGrimoire();
|
||||
const title = getIssueTitle(event);
|
||||
const labels = getIssueLabels(event);
|
||||
const repoAddress = getIssueRepositoryAddress(event);
|
||||
|
||||
// Parse repository address to get the pointer
|
||||
const repoPointer = repoAddress
|
||||
? (() => {
|
||||
try {
|
||||
// Address format: "kind:pubkey:identifier"
|
||||
const [kindStr, pubkey, identifier] = repoAddress.split(":");
|
||||
return {
|
||||
kind: parseInt(kindStr),
|
||||
pubkey,
|
||||
identifier,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})()
|
||||
: null;
|
||||
|
||||
// Fetch the repository event to get its name
|
||||
const repoEvent = useNostrEvent(
|
||||
repoPointer
|
||||
? {
|
||||
kind: repoPointer.kind,
|
||||
pubkey: repoPointer.pubkey,
|
||||
identifier: repoPointer.identifier,
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
|
||||
// Get repository display name
|
||||
const repoName = repoEvent
|
||||
? getRepositoryName(repoEvent) ||
|
||||
getRepositoryIdentifier(repoEvent) ||
|
||||
"Repository"
|
||||
: repoAddress?.split(":")[2] || "Unknown Repository";
|
||||
|
||||
const handleRepoClick = () => {
|
||||
addWindow("open", { pointer: repoPointer });
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseEventContainer event={event}>
|
||||
<div className="flex flex-col gap-2">
|
||||
@@ -79,17 +33,9 @@ export function IssueRenderer({ event }: BaseEventProps) {
|
||||
</ClickableEventTitle>
|
||||
|
||||
{/* Repository Reference */}
|
||||
{repoAddress && repoPointer && (
|
||||
{repoAddress && (
|
||||
<div className="text-xs line-clamp-1">
|
||||
<div
|
||||
onClick={handleRepoClick}
|
||||
className={`flex items-center gap-1 text-muted-foreground
|
||||
cursor-crosshair underline decoration-dotted hover:text-primary
|
||||
`}
|
||||
>
|
||||
<FolderGit2 className="size-3" />
|
||||
<span>{repoName}</span>
|
||||
</div>
|
||||
<RepositoryLink repoAddress={repoAddress} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { useMemo } from "react";
|
||||
import { GitCommit, FolderGit2, User, Copy, CopyCheck } from "lucide-react";
|
||||
import { GitCommit, User, Copy, CopyCheck } from "lucide-react";
|
||||
import { UserName } from "../UserName";
|
||||
import { CodeCopyButton } from "@/components/CodeCopyButton";
|
||||
import { useCopy } from "@/hooks/useCopy";
|
||||
import { useGrimoire } from "@/core/state";
|
||||
import { useNostrEvent } from "@/hooks/useNostrEvent";
|
||||
import { SyntaxHighlight } from "@/components/SyntaxHighlight";
|
||||
import type { NostrEvent } from "@/types/nostr";
|
||||
import {
|
||||
@@ -16,17 +14,13 @@ import {
|
||||
isPatchRoot,
|
||||
isPatchRootRevision,
|
||||
} from "@/lib/nip34-helpers";
|
||||
import {
|
||||
getRepositoryName,
|
||||
getRepositoryIdentifier,
|
||||
} from "@/lib/nip34-helpers";
|
||||
import { RepositoryLink } from "../RepositoryLink";
|
||||
|
||||
/**
|
||||
* Detail renderer for Kind 1617 - Patch
|
||||
* Displays full patch metadata and content
|
||||
*/
|
||||
export function PatchDetailRenderer({ event }: { event: NostrEvent }) {
|
||||
const { addWindow } = useGrimoire();
|
||||
const { copy, copied } = useCopy();
|
||||
|
||||
const subject = useMemo(() => getPatchSubject(event), [event]);
|
||||
@@ -37,36 +31,6 @@ export function PatchDetailRenderer({ event }: { event: NostrEvent }) {
|
||||
const isRoot = useMemo(() => isPatchRoot(event), [event]);
|
||||
const isRootRevision = useMemo(() => isPatchRootRevision(event), [event]);
|
||||
|
||||
// Parse repository address
|
||||
const repoPointer = useMemo(() => {
|
||||
if (!repoAddress) return null;
|
||||
try {
|
||||
const [kindStr, pubkey, identifier] = repoAddress.split(":");
|
||||
return {
|
||||
kind: parseInt(kindStr),
|
||||
pubkey,
|
||||
identifier,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}, [repoAddress]);
|
||||
|
||||
// Fetch repository event
|
||||
const repoEvent = useNostrEvent(repoPointer || undefined);
|
||||
|
||||
// Get repository display name
|
||||
const repoName = repoEvent
|
||||
? getRepositoryName(repoEvent) ||
|
||||
getRepositoryIdentifier(repoEvent) ||
|
||||
"Repository"
|
||||
: repoPointer?.identifier || "Unknown Repository";
|
||||
|
||||
const handleRepoClick = () => {
|
||||
if (!repoPointer || !repoEvent) return;
|
||||
addWindow("open", { pointer: repoPointer });
|
||||
};
|
||||
|
||||
// Format created date
|
||||
const createdDate = new Date(event.created_at * 1000).toLocaleDateString(
|
||||
"en-US",
|
||||
@@ -104,18 +68,11 @@ export function PatchDetailRenderer({ event }: { event: NostrEvent }) {
|
||||
{repoAddress && (
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<span className="text-muted-foreground">Repository:</span>
|
||||
<button
|
||||
onClick={repoEvent ? handleRepoClick : undefined}
|
||||
disabled={!repoEvent}
|
||||
className={`flex items-center gap-2 font-mono ${
|
||||
repoEvent
|
||||
? "text-muted-foreground underline decoration-dotted cursor-crosshair hover:text-primary"
|
||||
: "text-muted-foreground cursor-not-allowed"
|
||||
}`}
|
||||
>
|
||||
<FolderGit2 className="size-4" />
|
||||
{repoName}
|
||||
</button>
|
||||
<RepositoryLink
|
||||
repoAddress={repoAddress}
|
||||
iconSize="size-4"
|
||||
className="font-mono"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -3,69 +3,22 @@ import {
|
||||
type BaseEventProps,
|
||||
ClickableEventTitle,
|
||||
} from "./BaseEventRenderer";
|
||||
import { FolderGit2 } from "lucide-react";
|
||||
import { useGrimoire } from "@/core/state";
|
||||
import { useNostrEvent } from "@/hooks/useNostrEvent";
|
||||
import {
|
||||
getPatchSubject,
|
||||
getPatchCommitId,
|
||||
getPatchRepositoryAddress,
|
||||
} from "@/lib/nip34-helpers";
|
||||
import {
|
||||
getRepositoryName,
|
||||
getRepositoryIdentifier,
|
||||
} from "@/lib/nip34-helpers";
|
||||
import { RepositoryLink } from "../RepositoryLink";
|
||||
|
||||
/**
|
||||
* Renderer for Kind 1617 - Patch
|
||||
* Displays as a compact patch card in feed view
|
||||
*/
|
||||
export function PatchRenderer({ event }: BaseEventProps) {
|
||||
const { addWindow } = useGrimoire();
|
||||
const subject = getPatchSubject(event);
|
||||
const commitId = getPatchCommitId(event);
|
||||
const repoAddress = getPatchRepositoryAddress(event);
|
||||
|
||||
// Parse repository address to get the pointer
|
||||
const repoPointer = repoAddress
|
||||
? (() => {
|
||||
try {
|
||||
// Address format: "kind:pubkey:identifier"
|
||||
const [kindStr, pubkey, identifier] = repoAddress.split(":");
|
||||
return {
|
||||
kind: parseInt(kindStr),
|
||||
pubkey,
|
||||
identifier,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})()
|
||||
: null;
|
||||
|
||||
// Fetch the repository event to get its name
|
||||
const repoEvent = useNostrEvent(
|
||||
repoPointer
|
||||
? {
|
||||
kind: repoPointer.kind,
|
||||
pubkey: repoPointer.pubkey,
|
||||
identifier: repoPointer.identifier,
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
|
||||
// Get repository display name
|
||||
const repoName = repoEvent
|
||||
? getRepositoryName(repoEvent) ||
|
||||
getRepositoryIdentifier(repoEvent) ||
|
||||
"Repository"
|
||||
: repoAddress?.split(":")[2] || "Unknown Repository";
|
||||
|
||||
const handleRepoClick = () => {
|
||||
if (!repoPointer) return;
|
||||
addWindow("open", { pointer: repoPointer });
|
||||
};
|
||||
|
||||
// Shorten commit ID for display
|
||||
const shortCommitId = commitId ? commitId.slice(0, 7) : undefined;
|
||||
|
||||
@@ -84,15 +37,7 @@ export function PatchRenderer({ event }: BaseEventProps) {
|
||||
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs">
|
||||
<span>in</span>
|
||||
{/* Repository */}
|
||||
{repoAddress && repoPointer && (
|
||||
<div
|
||||
onClick={handleRepoClick}
|
||||
className="flex items-center gap-1 text-muted-foreground cursor-crosshair underline decoration-dotted hover:text-primary"
|
||||
>
|
||||
<FolderGit2 className="size-3" />
|
||||
<span>{repoName}</span>
|
||||
</div>
|
||||
)}
|
||||
{repoAddress && <RepositoryLink repoAddress={repoAddress} />}
|
||||
|
||||
{/* Commit ID */}
|
||||
{shortCommitId && (
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { useMemo } from "react";
|
||||
import { GitBranch, FolderGit2, Tag, Copy, CopyCheck } from "lucide-react";
|
||||
import { GitBranch, Tag, Copy, CopyCheck } from "lucide-react";
|
||||
import { UserName } from "../UserName";
|
||||
import { MarkdownContent } from "../MarkdownContent";
|
||||
import { useCopy } from "@/hooks/useCopy";
|
||||
import { useGrimoire } from "@/core/state";
|
||||
import { useNostrEvent } from "@/hooks/useNostrEvent";
|
||||
import type { NostrEvent } from "@/types/nostr";
|
||||
import {
|
||||
getPullRequestSubject,
|
||||
@@ -15,18 +13,14 @@ import {
|
||||
getPullRequestMergeBase,
|
||||
getPullRequestRepositoryAddress,
|
||||
} from "@/lib/nip34-helpers";
|
||||
import {
|
||||
getRepositoryName,
|
||||
getRepositoryIdentifier,
|
||||
} from "@/lib/nip34-helpers";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { RepositoryLink } from "../RepositoryLink";
|
||||
|
||||
/**
|
||||
* Detail renderer for Kind 1618 - Pull Request
|
||||
* Displays full PR content with markdown rendering
|
||||
*/
|
||||
export function PullRequestDetailRenderer({ event }: { event: NostrEvent }) {
|
||||
const { addWindow } = useGrimoire();
|
||||
const { copy, copied } = useCopy();
|
||||
|
||||
const subject = useMemo(() => getPullRequestSubject(event), [event]);
|
||||
@@ -40,31 +34,6 @@ export function PullRequestDetailRenderer({ event }: { event: NostrEvent }) {
|
||||
[event],
|
||||
);
|
||||
|
||||
// Parse repository address
|
||||
const repoPointer = useMemo(() => {
|
||||
if (!repoAddress) return null;
|
||||
try {
|
||||
const [kindStr, pubkey, identifier] = repoAddress.split(":");
|
||||
return {
|
||||
kind: parseInt(kindStr),
|
||||
pubkey,
|
||||
identifier,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}, [repoAddress]);
|
||||
|
||||
// Fetch repository event
|
||||
const repoEvent = useNostrEvent(repoPointer || undefined);
|
||||
|
||||
// Get repository display name
|
||||
const repoName = repoEvent
|
||||
? getRepositoryName(repoEvent) ||
|
||||
getRepositoryIdentifier(repoEvent) ||
|
||||
"Repository"
|
||||
: repoPointer?.identifier || "Unknown Repository";
|
||||
|
||||
// Format created date
|
||||
const createdDate = new Date(event.created_at * 1000).toLocaleDateString(
|
||||
"en-US",
|
||||
@@ -75,11 +44,6 @@ export function PullRequestDetailRenderer({ event }: { event: NostrEvent }) {
|
||||
},
|
||||
);
|
||||
|
||||
const handleRepoClick = () => {
|
||||
if (!repoPointer || !repoEvent) return;
|
||||
addWindow("open", { pointer: repoPointer });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 p-4 max-w-3xl mx-auto">
|
||||
{/* PR Header */}
|
||||
@@ -93,18 +57,11 @@ export function PullRequestDetailRenderer({ event }: { event: NostrEvent }) {
|
||||
{repoAddress && (
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<span className="text-muted-foreground">Repository:</span>
|
||||
<button
|
||||
onClick={repoEvent ? handleRepoClick : undefined}
|
||||
disabled={!repoEvent}
|
||||
className={`flex items-center gap-2 font-mono ${
|
||||
repoEvent
|
||||
? "text-muted-foreground underline decoration-dotted cursor-crosshair hover:text-primary"
|
||||
: "text-muted-foreground cursor-not-allowed"
|
||||
}`}
|
||||
>
|
||||
<FolderGit2 className="size-4" />
|
||||
{repoName}
|
||||
</button>
|
||||
<RepositoryLink
|
||||
repoAddress={repoAddress}
|
||||
iconSize="size-4"
|
||||
className="font-mono"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -3,72 +3,26 @@ import {
|
||||
type BaseEventProps,
|
||||
ClickableEventTitle,
|
||||
} from "./BaseEventRenderer";
|
||||
import { FolderGit2, GitBranch } from "lucide-react";
|
||||
import { useGrimoire } from "@/core/state";
|
||||
import { useNostrEvent } from "@/hooks/useNostrEvent";
|
||||
import { GitBranch } from "lucide-react";
|
||||
import {
|
||||
getPullRequestSubject,
|
||||
getPullRequestLabels,
|
||||
getPullRequestBranchName,
|
||||
getPullRequestRepositoryAddress,
|
||||
} from "@/lib/nip34-helpers";
|
||||
import {
|
||||
getRepositoryName,
|
||||
getRepositoryIdentifier,
|
||||
} from "@/lib/nip34-helpers";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { RepositoryLink } from "../RepositoryLink";
|
||||
|
||||
/**
|
||||
* Renderer for Kind 1618 - Pull Request
|
||||
* Displays as a compact PR card in feed view
|
||||
*/
|
||||
export function PullRequestRenderer({ event }: BaseEventProps) {
|
||||
const { addWindow } = useGrimoire();
|
||||
const subject = getPullRequestSubject(event);
|
||||
const labels = getPullRequestLabels(event);
|
||||
const branchName = getPullRequestBranchName(event);
|
||||
const repoAddress = getPullRequestRepositoryAddress(event);
|
||||
|
||||
// Parse repository address to get the pointer
|
||||
const repoPointer = repoAddress
|
||||
? (() => {
|
||||
try {
|
||||
// Address format: "kind:pubkey:identifier"
|
||||
const [kindStr, pubkey, identifier] = repoAddress.split(":");
|
||||
return {
|
||||
kind: parseInt(kindStr),
|
||||
pubkey,
|
||||
identifier,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})()
|
||||
: null;
|
||||
|
||||
// Fetch the repository event to get its name
|
||||
const repoEvent = useNostrEvent(
|
||||
repoPointer
|
||||
? {
|
||||
kind: repoPointer.kind,
|
||||
pubkey: repoPointer.pubkey,
|
||||
identifier: repoPointer.identifier,
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
|
||||
// Get repository display name
|
||||
const repoName = repoEvent
|
||||
? getRepositoryName(repoEvent) ||
|
||||
getRepositoryIdentifier(repoEvent) ||
|
||||
"Repository"
|
||||
: repoAddress?.split(":")[2] || "Unknown Repository";
|
||||
|
||||
const handleRepoClick = () => {
|
||||
if (!repoPointer) return;
|
||||
addWindow("open", { pointer: repoPointer });
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseEventContainer event={event}>
|
||||
<div className="flex flex-col gap-2">
|
||||
@@ -82,14 +36,11 @@ export function PullRequestRenderer({ event }: BaseEventProps) {
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
{/* Repository */}
|
||||
{repoAddress && repoPointer && (
|
||||
<div
|
||||
onClick={handleRepoClick}
|
||||
className="flex items-center gap-1 text-muted-foreground cursor-crosshair underline decoration-dotted hover:text-primary truncate line-clamp-1 text-xs"
|
||||
>
|
||||
<FolderGit2 className="size-3 flex-shrink-0" />
|
||||
<span>{repoName}</span>
|
||||
</div>
|
||||
{repoAddress && (
|
||||
<RepositoryLink
|
||||
repoAddress={repoAddress}
|
||||
className="truncate line-clamp-1 text-xs"
|
||||
/>
|
||||
)}
|
||||
{/* Branch Name */}
|
||||
{branchName && (
|
||||
|
||||
@@ -3,24 +3,21 @@ import {
|
||||
type BaseEventProps,
|
||||
ClickableEventTitle,
|
||||
} from "./BaseEventRenderer";
|
||||
import { GitCommit, FolderGit2 } from "lucide-react";
|
||||
import { useGrimoire } from "@/core/state";
|
||||
import { useNostrEvent } from "@/hooks/useNostrEvent";
|
||||
import { GitCommit } from "lucide-react";
|
||||
import {
|
||||
getRepositoryIdentifier,
|
||||
getRepositoryStateHeadCommit,
|
||||
parseHeadBranch,
|
||||
getRepositoryStateHead,
|
||||
getRepositoryName,
|
||||
} from "@/lib/nip34-helpers";
|
||||
import { Label } from "@/components/ui/Label";
|
||||
import { RepositoryLink } from "../RepositoryLink";
|
||||
|
||||
/**
|
||||
* Renderer for Kind 30618 - Repository State
|
||||
* Displays as a compact git push notification in feed view
|
||||
*/
|
||||
export function RepositoryStateRenderer({ event }: BaseEventProps) {
|
||||
const { addWindow } = useGrimoire();
|
||||
const repoId = getRepositoryIdentifier(event);
|
||||
const headRef = getRepositoryStateHead(event);
|
||||
const branch = parseHeadBranch(headRef);
|
||||
@@ -35,48 +32,32 @@ export function RepositoryStateRenderer({ event }: BaseEventProps) {
|
||||
}
|
||||
: null;
|
||||
|
||||
// Fetch the repository event to get its name
|
||||
const repoEvent = useNostrEvent(repoPointer || undefined);
|
||||
|
||||
// Get repository display name
|
||||
const repoName = repoEvent
|
||||
? getRepositoryName(repoEvent) || repoId || "Repository"
|
||||
: repoId || "repository";
|
||||
|
||||
const shortHash = commitHash?.substring(0, 8) || "unknown";
|
||||
const branchName = branch || "unknown";
|
||||
|
||||
const handleRepoClick = () => {
|
||||
if (repoPointer) {
|
||||
addWindow("open", { pointer: repoPointer });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseEventContainer event={event}>
|
||||
<div className="flex flex-col gap-2">
|
||||
{/* Push notification */}
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<div className="text-sm font-medium text-foreground">
|
||||
<ClickableEventTitle event={event} className="" as="span">
|
||||
pushed{" "}
|
||||
<code className="px-1.5 py-0.5 rounded bg-muted text-xs font-mono">
|
||||
{shortHash}
|
||||
</code>
|
||||
</ClickableEventTitle>{" "}
|
||||
to <Label className="inline">{branchName}</Label> in{" "}
|
||||
{repoPointer ? (
|
||||
<span
|
||||
onClick={handleRepoClick}
|
||||
className="inline-flex items-center gap-1 cursor-crosshair underline decoration-dotted hover:text-primary"
|
||||
>
|
||||
<FolderGit2 className="size-3" />
|
||||
<span className="font-semibold">{repoName}</span>
|
||||
</span>
|
||||
) : (
|
||||
<span className="font-semibold">{repoName}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 flex-wrap text-sm font-medium text-foreground">
|
||||
<GitCommit className="size-4 text-muted-foreground flex-shrink-0" />
|
||||
<ClickableEventTitle event={event} className="" as="span">
|
||||
pushed{" "}
|
||||
<code className="px-1.5 py-0.5 rounded bg-muted text-xs font-mono">
|
||||
{shortHash}
|
||||
</code>{" "}
|
||||
to
|
||||
</ClickableEventTitle>{" "}
|
||||
<Label className="inline">{branchName}</Label> in{" "}
|
||||
{repoPointer ? (
|
||||
<RepositoryLink
|
||||
repoPointer={repoPointer}
|
||||
inline
|
||||
className="inline-flex font-semibold"
|
||||
/>
|
||||
) : (
|
||||
<span className="font-semibold">{repoId || "repository"}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</BaseEventContainer>
|
||||
|
||||
Reference in New Issue
Block a user