mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-08 06:27:17 +02:00
chore: setup eslint and prettier with code formatting
This commit is contained in:
5
.prettierignore
Normal file
5
.prettierignore
Normal file
@@ -0,0 +1,5 @@
|
||||
dist
|
||||
node_modules
|
||||
.claude
|
||||
*.md
|
||||
package-lock.json
|
||||
10
.prettierrc
Normal file
10
.prettierrc
Normal 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
41
eslint.config.js
Normal 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
105
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -40,7 +40,7 @@ export default function EncodeViewer({ args }: EncodeViewerProps) {
|
||||
...parsed,
|
||||
relays: relays.length > 0 ? relays : undefined,
|
||||
});
|
||||
} catch (err) {
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}, [parsed, relays]);
|
||||
|
||||
@@ -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)}>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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) ? (
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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>}
|
||||
|
||||
@@ -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>}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>}
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -38,7 +38,7 @@ export function useCopy(timeout = 3000) {
|
||||
setCopied(false);
|
||||
}
|
||||
},
|
||||
[timeout]
|
||||
[timeout],
|
||||
);
|
||||
|
||||
return { copy, copied };
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
},
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user