chore: setup eslint and prettier with code formatting

This commit is contained in:
Alejandro Gómez
2025-12-11 13:00:25 +01:00
parent 189da9d141
commit 38f5461b54
34 changed files with 317 additions and 131 deletions

5
.prettierignore Normal file
View File

@@ -0,0 +1,5 @@
dist
node_modules
.claude
*.md
package-lock.json

10
.prettierrc Normal file
View File

@@ -0,0 +1,10 @@
{
"semi": true,
"trailingComma": "all",
"singleQuote": false,
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"arrowParens": "always",
"endOfLine": "lf"
}

41
eslint.config.js Normal file
View File

@@ -0,0 +1,41 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import prettier from 'eslint-plugin-prettier'
import prettierConfig from 'eslint-config-prettier'
export default tseslint.config(
{ ignores: ['dist', 'node_modules', '.claude'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
'prettier': prettier,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'prettier/prettier': 'error',
},
},
prettierConfig,
)

105
package-lock.json generated
View File

@@ -47,10 +47,13 @@
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"eslint": "^9.17.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.16",
"globals": "^15.14.0",
"postcss": "^8.4.49",
"prettier": "^3.7.4",
"tailwindcss": "^3.4.17",
"typescript": "~5.6.2",
"typescript-eslint": "^8.18.2",
@@ -1549,6 +1552,19 @@
"node": ">=14"
}
},
"node_modules/@pkgr/core": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
"integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/pkgr"
}
},
"node_modules/@radix-ui/number": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz",
@@ -4734,6 +4750,53 @@
}
}
},
"node_modules/eslint-config-prettier": {
"version": "10.1.8",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz",
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
"dev": true,
"license": "MIT",
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
"funding": {
"url": "https://opencollective.com/eslint-config-prettier"
},
"peerDependencies": {
"eslint": ">=7.0.0"
}
},
"node_modules/eslint-plugin-prettier": {
"version": "5.5.4",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz",
"integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==",
"dev": true,
"license": "MIT",
"dependencies": {
"prettier-linter-helpers": "^1.0.0",
"synckit": "^0.11.7"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint-plugin-prettier"
},
"peerDependencies": {
"@types/eslint": ">=8.0.0",
"eslint": ">=8.0.0",
"eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0",
"prettier": ">=3.0.0"
},
"peerDependenciesMeta": {
"@types/eslint": {
"optional": true
},
"eslint-config-prettier": {
"optional": true
}
}
},
"node_modules/eslint-plugin-react-hooks": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz",
@@ -4886,6 +4949,13 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT"
},
"node_modules/fast-diff": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/fast-glob": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
@@ -7255,9 +7325,9 @@
}
},
"node_modules/prettier": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"version": "3.7.4",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz",
"integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
"dev": true,
"license": "MIT",
"bin": {
@@ -7270,6 +7340,19 @@
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prettier-linter-helpers": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-diff": "^1.1.2"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/proc-log": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz",
@@ -8227,6 +8310,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/synckit": {
"version": "0.11.11",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz",
"integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@pkgr/core": "^0.2.9"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/synckit"
}
},
"node_modules/tailwind-merge": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz",

View File

@@ -7,6 +7,9 @@
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,css}\"",
"format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json,css}\"",
"preview": "vite preview"
},
"dependencies": {
@@ -49,10 +52,13 @@
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"eslint": "^9.17.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.16",
"globals": "^15.14.0",
"postcss": "^8.4.49",
"prettier": "^3.7.4",
"tailwindcss": "^3.4.17",
"typescript": "~5.6.2",
"typescript-eslint": "^8.18.2",

View File

@@ -40,7 +40,7 @@ export default function EncodeViewer({ args }: EncodeViewerProps) {
...parsed,
relays: relays.length > 0 ? relays : undefined,
});
} catch (err) {
} catch {
return null;
}
}, [parsed, relays]);

View File

@@ -46,6 +46,18 @@ export function RichText({
className = "",
depth = 1,
}: RichTextProps) {
// Call hook unconditionally - it will handle undefined/null
const trimmedEvent = event
? {
...event,
content: event.content.trim(),
}
: undefined;
const renderedContent = useRenderedContent(
trimmedEvent as NostrEvent,
contentComponents,
);
// If plain content is provided, just render it
if (content && !event) {
const lines = content.trim().split("\n");
@@ -62,11 +74,6 @@ export function RichText({
// Render event content with rich formatting
if (event) {
const trimmedEvent = {
...event,
content: event.content.trim(),
};
const renderedContent = useRenderedContent(trimmedEvent, contentComponents);
return (
<DepthContext.Provider value={depth}>
<div className={cn("leading-relaxed break-words", className)}>

View File

@@ -16,7 +16,7 @@ export function Kind0Renderer({ event }: BaseEventProps) {
const website = profile?.website;
return (
<BaseEventContainer event={event} >
<BaseEventContainer event={event}>
<div className="flex flex-col gap-3">
{/* Profile Info */}
<div className="flex flex-col gap-2 p-3 border border-muted bg-muted/20">

View File

@@ -29,7 +29,7 @@ export function Kind1063Renderer({ event }: BaseEventProps) {
event.tags.find((t) => t[0] === "summary")?.[1] || event.content;
return (
<BaseEventContainer event={event} >
<BaseEventContainer event={event}>
<div className="flex flex-col gap-3">
{/* File preview */}
{metadata.url && (isImage || isVideo || isAudio) ? (

View File

@@ -15,7 +15,7 @@ export function Kind20Renderer({ event }: BaseEventProps) {
const title = event.tags.find((t) => t[0] === "title")?.[1];
return (
<BaseEventContainer event={event} >
<BaseEventContainer event={event}>
<div className="flex flex-col gap-2">
{/* Title if present */}
{title && (

View File

@@ -15,7 +15,7 @@ export function Kind21Renderer({ event }: BaseEventProps) {
const title = event.tags.find((t) => t[0] === "title")?.[1];
return (
<BaseEventContainer event={event} >
<BaseEventContainer event={event}>
<div className="flex flex-col gap-2">
{/* Title if present */}
{title && <h3 className="text-base font-semibold">{title}</h3>}

View File

@@ -15,7 +15,7 @@ export function Kind22Renderer({ event }: BaseEventProps) {
const title = event.tags.find((t) => t[0] === "title")?.[1];
return (
<BaseEventContainer event={event} >
<BaseEventContainer event={event}>
<div className="flex flex-col gap-2">
{/* Title if present */}
{title && <h3 className="text-base font-semibold">{title}</h3>}

View File

@@ -182,7 +182,7 @@ export function Kind30023DetailRenderer({ event }: { event: NostrEvent }) {
/>
) : null,
// Handle nostr: links
a: ({ node, href, children, ...props }) => {
a: ({ href, children, ...props }) => {
if (!href) return null;
// Render nostr: mentions inline
@@ -204,43 +204,43 @@ export function Kind30023DetailRenderer({ event }: { event: NostrEvent }) {
);
},
// Make pre elements display inline
pre: ({ node, children, ...props }) => (
pre: ({ children, ...props }) => (
<span className="inline" {...props}>
{children}
</span>
),
// Style adjustments for dark theme
h1: ({ node, ...props }) => (
h1: ({ ...props }) => (
<h1 className="text-2xl font-bold mt-8 mb-4" {...props} />
),
h2: ({ node, ...props }) => (
h2: ({ ...props }) => (
<h2 className="text-xl font-bold mt-6 mb-3" {...props} />
),
h3: ({ node, ...props }) => (
h3: ({ ...props }) => (
<h3 className="text-lg font-bold mt-4 mb-2" {...props} />
),
p: ({ node, ...props }) => (
p: ({ ...props }) => (
<p className="text-sm leading-relaxed mb-4" {...props} />
),
code: ({ node, inline, ...props }: any) => (
code: ({ ...props }: any) => (
<code
className="bg-muted px-0.5 py-0.5 rounded text-xs font-mono"
{...props}
/>
),
blockquote: ({ node, ...props }) => (
blockquote: ({ ...props }) => (
<blockquote
className="border-l-4 border-muted pl-4 italic text-muted-foreground my-4"
{...props}
/>
),
ul: ({ node, ...props }) => (
ul: ({ ...props }) => (
<ul
className="text-sm list-disc list-inside my-4 space-y-2"
{...props}
/>
),
ol: ({ node, ...props }) => (
ol: ({ ...props }) => (
<ol
className="text-sm list-decimal list-inside my-4 space-y-2"
{...props}

View File

@@ -14,7 +14,7 @@ export function Kind30023Renderer({ event }: BaseEventProps) {
const summary = useMemo(() => getArticleSummary(event), [event]);
return (
<BaseEventContainer event={event} >
<BaseEventContainer event={event}>
<div className="flex flex-col gap-2">
{/* Title */}
{title && (

View File

@@ -20,7 +20,7 @@ export function Kind3Renderer({ event }: BaseEventProps) {
: false;
return (
<BaseEventContainer event={event} >
<BaseEventContainer event={event}>
<div className="flex flex-col gap-2 text-xs">
<span className="flex items-center gap-1">
<Users className="size-3 text-muted-foreground" />

View File

@@ -15,7 +15,7 @@ export function Kind6Renderer({ event }: BaseEventProps) {
const repostedEventId = eTag?.[1];
return (
<BaseEventContainer event={event} >
<BaseEventContainer event={event}>
<div className="flex flex-col gap-3">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Repeat2 className="size-4" />

View File

@@ -108,7 +108,7 @@ export function Kind7Renderer({ event }: BaseEventProps) {
};
return (
<BaseEventContainer event={event} >
<BaseEventContainer event={event}>
<div className="flex flex-col gap-2">
{/* Reaction indicator */}
<div className="flex items-center gap-2">

View File

@@ -47,14 +47,6 @@ export function Kind9735Renderer({ event }: BaseEventProps) {
return Math.floor(zapAmount / 1000);
}, [zapAmount]);
if (!isValid) {
return (
<BaseEventContainer event={event} >
<div className="text-xs text-muted-foreground">Invalid zap receipt</div>
</BaseEventContainer>
);
}
// Override event.pubkey to show zap sender instead of receipt pubkey
const displayEvent = useMemo(
() => ({
@@ -64,8 +56,16 @@ export function Kind9735Renderer({ event }: BaseEventProps) {
[event, zapSender],
);
if (!isValid) {
return (
<BaseEventContainer event={event}>
<div className="text-xs text-muted-foreground">Invalid zap receipt</div>
</BaseEventContainer>
);
}
return (
<BaseEventContainer event={displayEvent} >
<BaseEventContainer event={displayEvent}>
<div className="flex flex-col gap-2">
{/* Zap indicator */}
<div className="flex items-center gap-2">

View File

@@ -17,7 +17,7 @@ export function Kind9802Renderer({ event }: BaseEventProps) {
const comment = useMemo(() => getHighlightComment(event), [event]);
return (
<BaseEventContainer event={event} >
<BaseEventContainer event={event}>
<div className="flex flex-col gap-2">
{/* Comment */}
{comment && <p className="text-sm text-foreground">{comment}</p>}

View File

@@ -1,7 +1,7 @@
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import * as React from "react";
import * as AvatarPrimitive from "@radix-ui/react-avatar";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
@@ -11,12 +11,12 @@ const Avatar = React.forwardRef<
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className
className,
)}
{...props}
/>
))
Avatar.displayName = AvatarPrimitive.Root.displayName
));
Avatar.displayName = AvatarPrimitive.Root.displayName;
const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
@@ -27,8 +27,8 @@ const AvatarImage = React.forwardRef<
className={cn("aspect-square h-full w-full", className)}
{...props}
/>
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName
));
AvatarImage.displayName = AvatarPrimitive.Image.displayName;
const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
@@ -38,11 +38,11 @@ const AvatarFallback = React.forwardRef<
ref={ref}
className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted",
className
className,
)}
{...props}
/>
))
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
));
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
export { Avatar, AvatarImage, AvatarFallback }
export { Avatar, AvatarImage, AvatarFallback };

View File

@@ -1,8 +1,8 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
@@ -31,27 +31,28 @@ const buttonVariants = cva(
variant: "default",
size: "default",
},
}
)
},
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
extends
React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
const Comp = asChild ? Slot : "button";
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
);
},
);
Button.displayName = "Button";
export { Button, buttonVariants }
export { Button, buttonVariants };

View File

@@ -1,16 +1,16 @@
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"
import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { X } from "lucide-react";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
const Dialog = DialogPrimitive.Root
const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger
const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = DialogPrimitive.Portal
const DialogPortal = DialogPrimitive.Portal;
const DialogClose = DialogPrimitive.Close
const DialogClose = DialogPrimitive.Close;
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
@@ -20,12 +20,12 @@ const DialogOverlay = React.forwardRef<
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
className,
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
@@ -37,7 +37,7 @@ const DialogContent = React.forwardRef<
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
className,
)}
{...props}
>
@@ -48,8 +48,8 @@ const DialogContent = React.forwardRef<
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
));
DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogHeader = ({
className,
@@ -58,12 +58,12 @@ const DialogHeader = ({
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
className,
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
);
DialogHeader.displayName = "DialogHeader";
const DialogFooter = ({
className,
@@ -72,12 +72,12 @@ const DialogFooter = ({
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
className,
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
);
DialogFooter.displayName = "DialogFooter";
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
@@ -87,12 +87,12 @@ const DialogTitle = React.forwardRef<
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
className,
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
));
DialogTitle.displayName = DialogPrimitive.Title.displayName;
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
@@ -103,8 +103,8 @@ const DialogDescription = React.forwardRef<
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
));
DialogDescription.displayName = DialogPrimitive.Description.displayName;
export {
Dialog,
@@ -117,4 +117,4 @@ export {
DialogFooter,
DialogTitle,
DialogDescription,
}
};

View File

@@ -1,6 +1,6 @@
import * as React from "react"
import * as React from "react";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
({ className, type, ...props }, ref) => {
@@ -9,14 +9,14 @@ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
type={type}
className={cn(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
className,
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
);
},
);
Input.displayName = "Input";
export { Input }
export { Input };

View File

@@ -1,7 +1,7 @@
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import * as React from "react";
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
@@ -18,8 +18,8 @@ const ScrollArea = React.forwardRef<
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
))
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
));
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
@@ -34,13 +34,13 @@ const ScrollBar = React.forwardRef<
"h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
className
className,
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
))
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
));
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
export { ScrollArea, ScrollBar }
export { ScrollArea, ScrollBar };

View File

@@ -128,7 +128,7 @@ export const removeWindow = (
const newWindowIds = ws.windowIds.filter((id) => id !== windowId);
// Remove from global windows object
const { [windowId]: removedWindow, ...remainingWindows } = state.windows;
const { [windowId]: _removedWindow, ...remainingWindows } = state.windows;
return {
...state,

View File

@@ -38,7 +38,7 @@ export function useCopy(timeout = 3000) {
setCopied(false);
}
},
[timeout]
[timeout],
);
return { copy, copied };

View File

@@ -34,7 +34,8 @@ export function useLocale(): LocaleConfig {
const formatted = testDate.toLocaleTimeString(browserLocale, {
hour: "numeric",
});
const timeFormat = formatted.includes("PM") || formatted.includes("AM") ? "12h" : "24h";
const timeFormat =
formatted.includes("PM") || formatted.includes("AM") ? "12h" : "24h";
return {
locale: browserLocale,

View File

@@ -23,8 +23,10 @@ export function useProfile(pubkey: string): ProfileContent | undefined {
if (!fetchedEvent || !fetchedEvent.content) return;
try {
const profileData = JSON.parse(fetchedEvent.content) as ProfileContent;
const profileData = JSON.parse(
fetchedEvent.content,
) as ProfileContent;
// Save to IndexedDB
await db.profiles.put({
...profileData,

View File

@@ -9,7 +9,9 @@ import db from "../services/db";
* @param wsUrl - WebSocket URL of the relay (ws:// or wss://)
* @returns Relay information or undefined if not yet loaded
*/
export function useRelayInfo(wsUrl: string | undefined): RelayInformation | undefined {
export function useRelayInfo(
wsUrl: string | undefined,
): RelayInformation | undefined {
const cached = useLiveQuery(
() => (wsUrl ? db.relayInfo.get(wsUrl) : undefined),
[wsUrl],

View File

@@ -1,17 +1,18 @@
import { EVENT_KINDS } from '@/constants/kinds'
import { EVENT_KINDS } from "@/constants/kinds";
/**
* Get all event kinds defined in a specific NIP
*/
export function getKindsForNip(nipId: string): number[] {
const kinds: number[] = []
const kinds: number[] = [];
for (const [kindKey, kindInfo] of Object.entries(EVENT_KINDS)) {
if (kindInfo.nip === nipId) {
const kindNum = typeof kindInfo.kind === 'number' ? kindInfo.kind : parseInt(kindKey)
kinds.push(kindNum)
const kindNum =
typeof kindInfo.kind === "number" ? kindInfo.kind : parseInt(kindKey);
kinds.push(kindNum);
}
}
return kinds.sort((a, b) => a - b)
return kinds.sort((a, b) => a - b);
}

View File

@@ -17,7 +17,8 @@ export function isNip05(value: string): boolean {
if (!value) return false;
// Match user@domain format
const userAtDomain = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9][\w.-]+\.[a-zA-Z]{2,}$/.test(value);
const userAtDomain =
/^[a-zA-Z0-9._-]+@[a-zA-Z0-9][\w.-]+\.[a-zA-Z]{2,}$/.test(value);
// Match bare domain format (domain.com -> _@domain.com)
const bareDomain = /^[a-zA-Z0-9][\w.-]+\.[a-zA-Z]{2,}$/.test(value);
@@ -66,7 +67,9 @@ export async function resolveNip05(nip05: string): Promise<string | null> {
return null;
}
console.log(`NIP-05: Resolved ${nip05}${normalized}${profile.pubkey}`);
console.log(
`NIP-05: Resolved ${nip05}${normalized}${profile.pubkey}`,
);
return profile.pubkey.toLowerCase();
} catch (error) {
console.warn(`NIP-05: Resolution failed for ${normalized}:`, error);

View File

@@ -1,5 +1,9 @@
import { nip19 } from "nostr-tools";
import { isValidHexEventId, isValidHexPubkey, normalizeHex } from "./nostr-validation";
import {
isValidHexEventId,
isValidHexPubkey,
normalizeHex,
} from "./nostr-validation";
// Define pointer types locally since they're not exported from nostr-tools
export interface EventPointer {

View File

@@ -1,7 +1,11 @@
import { nip19 } from "nostr-tools";
import type { NostrFilter } from "@/types/nostr";
import { isNip05 } from "./nip05";
import { isValidHexPubkey, isValidHexEventId, normalizeHex } from "./nostr-validation";
import {
isValidHexPubkey,
isValidHexEventId,
normalizeHex,
} from "./nostr-validation";
export interface ParsedReqCommand {
filter: NostrFilter;
@@ -264,7 +268,7 @@ function parseNpubOrHex(value: string): string | null {
if (decoded.type === "npub") {
return decoded.data;
}
} catch (e) {
} catch {
// Not valid npub, continue
}
}
@@ -290,7 +294,7 @@ function parseNoteOrHex(value: string): string | null {
if (decoded.type === "note") {
return decoded.data;
}
} catch (e) {
} catch {
// Not valid note, continue
}
}

View File

@@ -1,16 +1,16 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
"@": path.resolve(__dirname, "./src"),
},
},
})
});