mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-09 23:16:50 +02:00
feat: nip-13
This commit is contained in:
@@ -52,6 +52,8 @@ import {
|
||||
getFavoriteConfig,
|
||||
FALLBACK_FAVORITE_CONFIG,
|
||||
} from "@/config/favorite-lists";
|
||||
import { getPowDifficulty } from "@/lib/nip13-helpers";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
/**
|
||||
* Universal event properties and utilities shared across all kind renderers
|
||||
@@ -489,6 +491,7 @@ export function BaseEventContainer({
|
||||
};
|
||||
}) {
|
||||
const { locale } = useGrimoire();
|
||||
const addWindow = useAddWindow();
|
||||
const { canSign, signer, pubkey } = useAccount();
|
||||
const { settings } = useSettings();
|
||||
const [emojiPickerOpen, setEmojiPickerOpen] = useState(false);
|
||||
@@ -527,6 +530,8 @@ export function BaseEventContainer({
|
||||
[signer, pubkey, event],
|
||||
);
|
||||
|
||||
const powDifficulty = getPowDifficulty(event);
|
||||
|
||||
const relativeTime = formatTimestamp(
|
||||
event.created_at,
|
||||
"relative",
|
||||
@@ -568,6 +573,14 @@ export function BaseEventContainer({
|
||||
>
|
||||
{relativeTime}
|
||||
</span>
|
||||
{powDifficulty !== undefined && (
|
||||
<Label
|
||||
className="text-[10px] tabular-nums cursor-crosshair hover:text-foreground"
|
||||
onClick={() => addWindow("nip", { number: 13 }, "NIP 13")}
|
||||
>
|
||||
PoW {powDifficulty}
|
||||
</Label>
|
||||
)}
|
||||
</div>
|
||||
<EventMenu
|
||||
event={event}
|
||||
|
||||
@@ -9,15 +9,22 @@ interface LabelProps {
|
||||
* - md: px-3 py-1
|
||||
*/
|
||||
size?: "sm" | "md";
|
||||
onClick?: (e: React.MouseEvent) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Label/Badge component with dotted border styling
|
||||
* Used for tags, language indicators, and metadata labels
|
||||
*/
|
||||
export function Label({ children, className, size = "sm" }: LabelProps) {
|
||||
export function Label({
|
||||
children,
|
||||
className,
|
||||
size = "sm",
|
||||
onClick,
|
||||
}: LabelProps) {
|
||||
return (
|
||||
<span
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
"truncate line-clamp-1 border border-muted border-dotted text-muted-foreground text-xs",
|
||||
size === "sm" && "px-2 py-0.5",
|
||||
|
||||
33
src/lib/nip13-helpers.ts
Normal file
33
src/lib/nip13-helpers.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { NostrEvent } from "@/types/nostr";
|
||||
import { getOrComputeCachedValue } from "applesauce-core/helpers";
|
||||
|
||||
const PowDifficultySymbol = Symbol("powDifficulty");
|
||||
|
||||
/**
|
||||
* Count leading zero bits in a hex string (NIP-13 difficulty).
|
||||
*/
|
||||
function countLeadingZeroBits(hex: string): number {
|
||||
let count = 0;
|
||||
for (let i = 0; i < hex.length; i++) {
|
||||
const nibble = parseInt(hex[i], 16);
|
||||
if (nibble === 0) {
|
||||
count += 4;
|
||||
} else {
|
||||
count += Math.clz32(nibble) - 28;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PoW difficulty of an event, or undefined if it has no nonce tag.
|
||||
* Cached on the event object via applesauce helpers.
|
||||
*/
|
||||
export function getPowDifficulty(event: NostrEvent): number | undefined {
|
||||
return getOrComputeCachedValue(event, PowDifficultySymbol, () => {
|
||||
const nonceTag = event.tags.find((t) => t[0] === "nonce");
|
||||
if (!nonceTag) return undefined;
|
||||
return countLeadingZeroBits(event.id);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user