Merge pull request #12 from purrgrammer/claude/kind-utilities-EeWQZ

feat: add centralized nostr kind utilities
This commit is contained in:
Alejandro
2025-12-22 13:12:52 +01:00
committed by GitHub
4 changed files with 254 additions and 64 deletions

View File

@@ -1,5 +1,4 @@
import { getKindInfo } from "@/constants/kinds";
import { kinds } from "nostr-tools";
import { NIPBadge } from "./NIPBadge";
import { Copy, CopyCheck } from "lucide-react";
import { Button } from "./ui/button";
@@ -10,14 +9,11 @@ import {
getContentTypeDescription,
} from "@/lib/nostr-schema";
import { CenteredContent } from "./ui/CenteredContent";
// NIP-01 Kind ranges
const REPLACEABLE_START = 10000;
const REPLACEABLE_END = 20000;
const EPHEMERAL_START = 20000;
const EPHEMERAL_END = 30000;
const PARAMETERIZED_REPLACEABLE_START = 30000;
const PARAMETERIZED_REPLACEABLE_END = 40000;
import {
isReplaceableKind,
isEphemeralKind,
isParameterizedReplaceableKind,
} from "@/lib/nostr-kinds";
export default function KindRenderer({ kind }: { kind: number }) {
const kindInfo = getKindInfo(kind);
@@ -83,12 +79,11 @@ export default function KindRenderer({ kind }: { kind: number }) {
<div>{eventType}</div>
<div className="text-muted-foreground">Storage</div>
<div>
{kind >= EPHEMERAL_START && kind < EPHEMERAL_END
{isEphemeralKind(kind)
? "Not stored (ephemeral)"
: "Stored by relays"}
</div>
{kind >= PARAMETERIZED_REPLACEABLE_START &&
kind < PARAMETERIZED_REPLACEABLE_END && (
{isParameterizedReplaceableKind(kind) && (
<>
<div className="text-muted-foreground">Identifier</div>
<code className="font-mono text-xs">d-tag</code>
@@ -194,15 +189,9 @@ function getKindCategory(kind: number): string {
if (kind >= 20 && kind <= 39) return "Media & Content";
if (kind >= 40 && kind <= 49) return "Channels";
if (kind >= 1000 && kind <= 9999) return "Application Specific";
if (kind >= REPLACEABLE_START && kind < REPLACEABLE_END)
return "Regular Lists";
if (kind >= EPHEMERAL_START && kind < EPHEMERAL_END)
return "Ephemeral Events";
if (
kind >= PARAMETERIZED_REPLACEABLE_START &&
kind < PARAMETERIZED_REPLACEABLE_END
)
return "Parameterized Replaceable";
if (isReplaceableKind(kind)) return "Replaceable Events";
if (isEphemeralKind(kind)) return "Ephemeral Events";
if (isParameterizedReplaceableKind(kind)) return "Parameterized Replaceable";
if (kind >= 40000) return "Custom/Experimental";
return "Other";
}
@@ -211,20 +200,14 @@ function getKindCategory(kind: number): string {
* Determine the replaceability of an event kind
*/
function getEventType(kind: number): string {
if (
kind === kinds.Metadata ||
kind === kinds.Contacts ||
(kind >= REPLACEABLE_START && kind < REPLACEABLE_END)
) {
// nostr-tools' isReplaceableKind already includes kinds 0 (Metadata) and 3 (Contacts)
if (isReplaceableKind(kind)) {
return "Replaceable";
}
if (
kind >= PARAMETERIZED_REPLACEABLE_START &&
kind < PARAMETERIZED_REPLACEABLE_END
) {
if (isParameterizedReplaceableKind(kind)) {
return "Parameterized Replaceable";
}
if (kind >= EPHEMERAL_START && kind < EPHEMERAL_END) {
if (isEphemeralKind(kind)) {
return "Ephemeral";
}
return "Regular";

View File

@@ -2,7 +2,6 @@ import { useState } from "react";
import { NostrEvent } from "@/types/nostr";
import { UserName } from "../UserName";
import { KindBadge } from "@/components/KindBadge";
// import { kinds } from "nostr-tools";
import {
DropdownMenu,
DropdownMenuContent,
@@ -20,17 +19,7 @@ import { nip19 } from "nostr-tools";
import { getTagValue } from "applesauce-core/helpers";
import { EventFooter } from "@/components/EventFooter";
import { cn } from "@/lib/utils";
// import { RichText } from "../RichText";
// import { getEventReply } from "@/lib/nostr-utils";
// import { useNostrEvent } from "@/hooks/useNostrEvent";
// import type { EventPointer, AddressPointer } from "nostr-tools/nip19";
// import { Skeleton } from "@/components/ui/skeleton";
// NIP-01 Kind ranges
const REPLACEABLE_START = 10000;
const REPLACEABLE_END = 20000;
const PARAMETERIZED_REPLACEABLE_START = 30000;
const PARAMETERIZED_REPLACEABLE_END = 40000;
import { isAddressableKind } from "@/lib/nostr-kinds";
/**
* Universal event properties and utilities shared across all kind renderers
@@ -116,14 +105,9 @@ export function EventMenu({ event }: { event: NostrEvent }) {
const [jsonDialogOpen, setJsonDialogOpen] = useState(false);
const openEventDetail = () => {
// For replaceable/parameterized replaceable events, use AddressPointer
const isAddressable =
(event.kind >= REPLACEABLE_START && event.kind < REPLACEABLE_END) ||
(event.kind >= PARAMETERIZED_REPLACEABLE_START &&
event.kind < PARAMETERIZED_REPLACEABLE_END);
let pointer;
if (isAddressable) {
// For replaceable/parameterized replaceable events, use AddressPointer
if (isAddressableKind(event.kind)) {
// Find d-tag for identifier
const dTag = getTagValue(event, "d") || "";
pointer = {
@@ -143,12 +127,7 @@ export function EventMenu({ event }: { event: NostrEvent }) {
const copyEventId = () => {
// For replaceable/parameterized replaceable events, encode as naddr
const isAddressable =
(event.kind >= REPLACEABLE_START && event.kind < REPLACEABLE_END) ||
(event.kind >= PARAMETERIZED_REPLACEABLE_START &&
event.kind < PARAMETERIZED_REPLACEABLE_END);
if (isAddressable) {
if (isAddressableKind(event.kind)) {
// Find d-tag for identifier
const dTag = getTagValue(event, "d") || "";
const naddr = nip19.naddrEncode({
@@ -242,16 +221,10 @@ export function ClickableEventTitle({
const handleClick = (e: React.MouseEvent) => {
e.stopPropagation();
// Determine if event is addressable/replaceable
const isAddressable =
(event.kind >= REPLACEABLE_START && event.kind < REPLACEABLE_END) ||
(event.kind >= PARAMETERIZED_REPLACEABLE_START &&
event.kind < PARAMETERIZED_REPLACEABLE_END);
let pointer;
if (isAddressable) {
// For replaceable/parameterized replaceable events, use AddressPointer
// For replaceable/parameterized replaceable events, use AddressPointer
if (isAddressableKind(event.kind)) {
const dTag = getTagValue(event, "d") || "";
pointer = {
kind: event.kind,