chore: format
This commit is contained in:
parent
4bd4141b54
commit
e490407dd5
225 changed files with 924 additions and 844 deletions
|
|
@ -2,3 +2,4 @@ singleQuote: true
|
||||||
semi: false
|
semi: false
|
||||||
printWidth: 100
|
printWidth: 100
|
||||||
trailingComma: none
|
trailingComma: none
|
||||||
|
plugins: [prettier-plugin-tailwindcss]
|
||||||
|
|
|
||||||
80
package-lock.json
generated
80
package-lock.json
generated
|
|
@ -95,6 +95,7 @@
|
||||||
"globals": "^15.13.0",
|
"globals": "^15.13.0",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.4.49",
|
||||||
"prettier": "3.4.2",
|
"prettier": "3.4.2",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.7.2",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"typescript": "~5.6.2",
|
"typescript": "~5.6.2",
|
||||||
"typescript-eslint": "^8.18.1",
|
"typescript-eslint": "^8.18.1",
|
||||||
|
|
@ -10392,6 +10393,85 @@
|
||||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prettier-plugin-tailwindcss": {
|
||||||
|
"version": "0.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.7.2.tgz",
|
||||||
|
"integrity": "sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.19"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@ianvs/prettier-plugin-sort-imports": "*",
|
||||||
|
"@prettier/plugin-hermes": "*",
|
||||||
|
"@prettier/plugin-oxc": "*",
|
||||||
|
"@prettier/plugin-pug": "*",
|
||||||
|
"@shopify/prettier-plugin-liquid": "*",
|
||||||
|
"@trivago/prettier-plugin-sort-imports": "*",
|
||||||
|
"@zackad/prettier-plugin-twig": "*",
|
||||||
|
"prettier": "^3.0",
|
||||||
|
"prettier-plugin-astro": "*",
|
||||||
|
"prettier-plugin-css-order": "*",
|
||||||
|
"prettier-plugin-jsdoc": "*",
|
||||||
|
"prettier-plugin-marko": "*",
|
||||||
|
"prettier-plugin-multiline-arrays": "*",
|
||||||
|
"prettier-plugin-organize-attributes": "*",
|
||||||
|
"prettier-plugin-organize-imports": "*",
|
||||||
|
"prettier-plugin-sort-imports": "*",
|
||||||
|
"prettier-plugin-svelte": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@ianvs/prettier-plugin-sort-imports": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@prettier/plugin-hermes": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@prettier/plugin-oxc": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@prettier/plugin-pug": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@shopify/prettier-plugin-liquid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@trivago/prettier-plugin-sort-imports": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@zackad/prettier-plugin-twig": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-astro": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-css-order": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-jsdoc": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-marko": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-multiline-arrays": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-organize-attributes": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-organize-imports": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-sort-imports": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-svelte": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pretty-bytes": {
|
"node_modules/pretty-bytes": {
|
||||||
"version": "6.1.1",
|
"version": "6.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -105,10 +105,11 @@
|
||||||
"globals": "^15.13.0",
|
"globals": "^15.13.0",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.4.49",
|
||||||
"prettier": "3.4.2",
|
"prettier": "3.4.2",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.7.2",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"typescript": "~5.6.2",
|
"typescript": "~5.6.2",
|
||||||
"typescript-eslint": "^8.18.1",
|
"typescript-eslint": "^8.18.1",
|
||||||
"vite": "^6.0.3",
|
"vite": "^6.0.3",
|
||||||
"vite-plugin-pwa": "^0.21.1"
|
"vite-plugin-pwa": "^0.21.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -339,11 +339,11 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
|
||||||
>
|
>
|
||||||
<CurrentRelaysProvider>
|
<CurrentRelaysProvider>
|
||||||
<NotificationProvider>
|
<NotificationProvider>
|
||||||
<div className="flex lg:justify-around w-full">
|
<div className="flex w-full lg:justify-around">
|
||||||
<div className="sticky top-0 lg:w-full flex justify-end self-start h-[var(--vh)]">
|
<div className="sticky top-0 flex h-[var(--vh)] justify-end self-start lg:w-full">
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 w-0 bg-background border-x lg:flex-auto lg:w-[640px] lg:shrink-0">
|
<div className="w-0 flex-1 border-x bg-background lg:w-[640px] lg:flex-auto lg:shrink-0">
|
||||||
{!!secondaryStack.length &&
|
{!!secondaryStack.length &&
|
||||||
secondaryStack.map((item, index) => (
|
secondaryStack.map((item, index) => (
|
||||||
<div
|
<div
|
||||||
|
|
@ -369,10 +369,10 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden lg:w-full lg:block" />
|
<div className="hidden lg:block lg:w-full" />
|
||||||
</div>
|
</div>
|
||||||
<TooManyRelaysAlertDialog />
|
<TooManyRelaysAlertDialog />
|
||||||
<BackgroundAudio className="fixed bottom-20 right-0 z-50 w-80 rounded-l-full rounded-r-none overflow-hidden shadow-lg border" />
|
<BackgroundAudio className="fixed bottom-20 right-0 z-50 w-80 overflow-hidden rounded-l-full rounded-r-none border shadow-lg" />
|
||||||
</NotificationProvider>
|
</NotificationProvider>
|
||||||
</CurrentRelaysProvider>
|
</CurrentRelaysProvider>
|
||||||
</SecondaryPageContext.Provider>
|
</SecondaryPageContext.Provider>
|
||||||
|
|
@ -407,20 +407,20 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'grid grid-cols-2 w-full',
|
'grid w-full grid-cols-2',
|
||||||
themeSetting === 'pure-black' ? '' : 'gap-2 pr-2 py-2'
|
themeSetting === 'pure-black' ? '' : 'gap-2 py-2 pr-2'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'bg-background overflow-hidden',
|
'overflow-hidden bg-background',
|
||||||
themeSetting === 'pure-black' ? 'border-l' : 'rounded-2xl shadow-lg'
|
themeSetting === 'pure-black' ? 'border-l' : 'rounded-2xl shadow-lg'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{primaryPages.map(({ name, element, props }) => (
|
{primaryPages.map(({ name, element, props }) => (
|
||||||
<div
|
<div
|
||||||
key={name}
|
key={name}
|
||||||
className="flex flex-col h-full w-full"
|
className="flex h-full w-full flex-col"
|
||||||
style={{
|
style={{
|
||||||
display: currentPrimaryPage === name ? 'block' : 'none'
|
display: currentPrimaryPage === name ? 'block' : 'none'
|
||||||
}}
|
}}
|
||||||
|
|
@ -431,7 +431,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'bg-background overflow-hidden',
|
'overflow-hidden bg-background',
|
||||||
themeSetting === 'pure-black' ? 'border-l' : 'rounded-2xl',
|
themeSetting === 'pure-black' ? 'border-l' : 'rounded-2xl',
|
||||||
themeSetting !== 'pure-black' && secondaryStack.length > 0 && 'shadow-lg',
|
themeSetting !== 'pure-black' && secondaryStack.length > 0 && 'shadow-lg',
|
||||||
secondaryStack.length === 0 ? 'bg-surface' : ''
|
secondaryStack.length === 0 ? 'bg-surface' : ''
|
||||||
|
|
@ -440,7 +440,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
|
||||||
{secondaryStack.map((item, index) => (
|
{secondaryStack.map((item, index) => (
|
||||||
<div
|
<div
|
||||||
key={item.index}
|
key={item.index}
|
||||||
className="flex flex-col h-full w-full"
|
className="flex h-full w-full flex-col"
|
||||||
style={{ display: index === secondaryStack.length - 1 ? 'block' : 'none' }}
|
style={{ display: index === secondaryStack.length - 1 ? 'block' : 'none' }}
|
||||||
>
|
>
|
||||||
{item.element}
|
{item.element}
|
||||||
|
|
@ -451,7 +451,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<TooManyRelaysAlertDialog />
|
<TooManyRelaysAlertDialog />
|
||||||
<BackgroundAudio className="fixed bottom-20 right-0 z-50 w-80 rounded-l-full rounded-r-none overflow-hidden shadow-lg border" />
|
<BackgroundAudio className="fixed bottom-20 right-0 z-50 w-80 overflow-hidden rounded-l-full rounded-r-none border shadow-lg" />
|
||||||
</NotificationProvider>
|
</NotificationProvider>
|
||||||
</CurrentRelaysProvider>
|
</CurrentRelaysProvider>
|
||||||
</SecondaryPageContext.Provider>
|
</SecondaryPageContext.Provider>
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ export default function AboutInfoDialog({ children }: { children: React.ReactNod
|
||||||
<Drawer open={open} onOpenChange={setOpen}>
|
<Drawer open={open} onOpenChange={setOpen}>
|
||||||
<DrawerTrigger asChild>{children}</DrawerTrigger>
|
<DrawerTrigger asChild>{children}</DrawerTrigger>
|
||||||
<DrawerContent>
|
<DrawerContent>
|
||||||
<div className="p-4 space-y-4">{content}</div>
|
<div className="space-y-4 p-4">{content}</div>
|
||||||
</DrawerContent>
|
</DrawerContent>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -37,18 +37,18 @@ export default function AccountList({
|
||||||
.finally(() => setSwitchingAccount(null))
|
.finally(() => setSwitchingAccount(null))
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex justify-between items-center p-2">
|
<div className="flex items-center justify-between p-2">
|
||||||
<div className="flex-1 flex items-center gap-2 relative">
|
<div className="relative flex flex-1 items-center gap-2">
|
||||||
<SimpleUserAvatar userId={act.pubkey} />
|
<SimpleUserAvatar userId={act.pubkey} />
|
||||||
<div className="flex-1 w-0">
|
<div className="w-0 flex-1">
|
||||||
<SimpleUsername userId={act.pubkey} className="font-semibold truncate" />
|
<SimpleUsername userId={act.pubkey} className="truncate font-semibold" />
|
||||||
<div className="text-sm rounded-full bg-muted px-2 w-fit">
|
<div className="w-fit rounded-full bg-muted px-2 text-sm">
|
||||||
{formatPubkey(act.pubkey)}
|
{formatPubkey(act.pubkey)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex items-center gap-2">
|
||||||
<SignerTypeBadge signerType={act.signerType} />
|
<SignerTypeBadge signerType={act.signerType} />
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -65,7 +65,7 @@ export default function AccountList({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{switchingAccount && isSameAccount(act, switchingAccount) && (
|
{switchingAccount && isSameAccount(act, switchingAccount) && (
|
||||||
<div className="absolute top-0 left-0 flex w-full h-full items-center justify-center rounded-lg bg-muted/60">
|
<div className="absolute left-0 top-0 flex h-full w-full items-center justify-center rounded-lg bg-muted/60">
|
||||||
<Loader size={16} className="animate-spin" />
|
<Loader size={16} className="animate-spin" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ export default function BunkerLogin({
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
className={errMsg ? 'border-destructive' : ''}
|
className={errMsg ? 'border-destructive' : ''}
|
||||||
/>
|
/>
|
||||||
{errMsg && <div className="text-xs text-destructive pl-3">{errMsg}</div>}
|
{errMsg && <div className="pl-3 text-xs text-destructive">{errMsg}</div>}
|
||||||
</div>
|
</div>
|
||||||
<Button onClick={handleLogin} disabled={pending}>
|
<Button onClick={handleLogin} disabled={pending}>
|
||||||
<Loader className={pending ? 'animate-spin' : 'hidden'} />
|
<Loader className={pending ? 'animate-spin' : 'hidden'} />
|
||||||
|
|
|
||||||
|
|
@ -186,17 +186,17 @@ export default function NostrConnectLogin({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex flex-col gap-4">
|
<div className="relative flex flex-col gap-4">
|
||||||
<div ref={qrContainerRef} className="flex flex-col items-center w-full space-y-3 mb-3">
|
<div ref={qrContainerRef} className="mb-3 flex w-full flex-col items-center space-y-3">
|
||||||
<a href={loginDetails.connectionString} aria-label="Open with Nostr signer app">
|
<a href={loginDetails.connectionString} aria-label="Open with Nostr signer app">
|
||||||
<QrCode size={qrCodeSize} value={loginDetails.connectionString} />
|
<QrCode size={qrCodeSize} value={loginDetails.connectionString} />
|
||||||
</a>
|
</a>
|
||||||
{nostrConnectionErrMsg && (
|
{nostrConnectionErrMsg && (
|
||||||
<div className="text-xs text-destructive text-center pt-1">{nostrConnectionErrMsg}</div>
|
<div className="pt-1 text-center text-xs text-destructive">{nostrConnectionErrMsg}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-center w-full mb-3">
|
<div className="mb-3 flex w-full justify-center">
|
||||||
<div
|
<div
|
||||||
className="flex items-center gap-2 text-sm text-muted-foreground bg-muted px-3 py-2 rounded-full cursor-pointer transition-all hover:bg-muted/80"
|
className="flex cursor-pointer items-center gap-2 rounded-full bg-muted px-3 py-2 text-sm text-muted-foreground transition-all hover:bg-muted/80"
|
||||||
style={{
|
style={{
|
||||||
width: qrCodeSize > 0 ? `${Math.max(150, Math.min(qrCodeSize, 320))}px` : 'auto'
|
width: qrCodeSize > 0 ? `${Math.max(150, Math.min(qrCodeSize, 320))}px` : 'auto'
|
||||||
}}
|
}}
|
||||||
|
|
@ -204,14 +204,14 @@ export default function NostrConnectLogin({
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<div className="flex-grow min-w-0 truncate select-none">
|
<div className="min-w-0 flex-grow select-none truncate">
|
||||||
{loginDetails.connectionString}
|
{loginDetails.connectionString}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-shrink-0">{copied ? <Check size={14} /> : <Copy size={14} />}</div>
|
<div className="flex-shrink-0">{copied ? <Check size={14} /> : <Copy size={14} />}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center w-full my-4">
|
<div className="my-4 flex w-full items-center">
|
||||||
<div className="flex-grow border-t border-border/40"></div>
|
<div className="flex-grow border-t border-border/40"></div>
|
||||||
<span className="px-3 text-xs text-muted-foreground">OR</span>
|
<span className="px-3 text-xs text-muted-foreground">OR</span>
|
||||||
<div className="flex-grow border-t border-border/40"></div>
|
<div className="flex-grow border-t border-border/40"></div>
|
||||||
|
|
@ -219,7 +219,7 @@ export default function NostrConnectLogin({
|
||||||
|
|
||||||
<div className="w-full space-y-1">
|
<div className="w-full space-y-1">
|
||||||
<div className="flex items-start space-x-2">
|
<div className="flex items-start space-x-2">
|
||||||
<div className="flex-1 relative">
|
<div className="relative flex-1">
|
||||||
<Input
|
<Input
|
||||||
placeholder="bunker://..."
|
placeholder="bunker://..."
|
||||||
value={bunkerInput}
|
value={bunkerInput}
|
||||||
|
|
@ -229,7 +229,7 @@ export default function NostrConnectLogin({
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className="absolute right-1 top-1/2 -translate-y-1/2 h-8 w-8 p-0"
|
className="absolute right-1 top-1/2 h-8 w-8 -translate-y-1/2 p-0"
|
||||||
onClick={startQrScan}
|
onClick={startQrScan}
|
||||||
disabled={pending}
|
disabled={pending}
|
||||||
>
|
>
|
||||||
|
|
@ -237,21 +237,21 @@ export default function NostrConnectLogin({
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Button onClick={() => handleLogin()} disabled={pending}>
|
<Button onClick={() => handleLogin()} disabled={pending}>
|
||||||
<Loader className={pending ? 'animate-spin mr-2' : 'hidden'} />
|
<Loader className={pending ? 'mr-2 animate-spin' : 'hidden'} />
|
||||||
{t('Login')}
|
{t('Login')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{errMsg && <div className="text-xs text-destructive pl-3 pt-1">{errMsg}</div>}
|
{errMsg && <div className="pl-3 pt-1 text-xs text-destructive">{errMsg}</div>}
|
||||||
</div>
|
</div>
|
||||||
<Button variant="secondary" onClick={back} className="w-full">
|
<Button variant="secondary" onClick={back} className="w-full">
|
||||||
{t('Back')}
|
{t('Back')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div className={cn('w-full h-full flex justify-center', isScanning ? '' : 'hidden')}>
|
<div className={cn('flex h-full w-full justify-center', isScanning ? '' : 'hidden')}>
|
||||||
<video
|
<video
|
||||||
ref={videoRef}
|
ref={videoRef}
|
||||||
className="absolute inset-0 w-full h-full bg-background"
|
className="absolute inset-0 h-full w-full bg-background"
|
||||||
autoPlay
|
autoPlay
|
||||||
playsInline
|
playsInline
|
||||||
muted
|
muted
|
||||||
|
|
@ -259,7 +259,7 @@ export default function NostrConnectLogin({
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="absolute top-2 right-2"
|
className="absolute right-2 top-2"
|
||||||
onClick={stopQrScan}
|
onClick={stopQrScan}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ export default function NpubLogin({
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
className={errMsg ? 'border-destructive' : ''}
|
className={errMsg ? 'border-destructive' : ''}
|
||||||
/>
|
/>
|
||||||
{errMsg && <div className="text-xs text-destructive pl-3">{errMsg}</div>}
|
{errMsg && <div className="pl-3 text-xs text-destructive">{errMsg}</div>}
|
||||||
</div>
|
</div>
|
||||||
<Button onClick={handleLogin} disabled={pending}>
|
<Button onClick={handleLogin} disabled={pending}>
|
||||||
<Loader className={pending ? 'animate-spin' : 'hidden'} />
|
<Loader className={pending ? 'animate-spin' : 'hidden'} />
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export default function Signup({
|
||||||
{(['generate', 'password'] as Step[]).map((s, index) => (
|
{(['generate', 'password'] as Step[]).map((s, index) => (
|
||||||
<div key={s} className="flex items-center">
|
<div key={s} className="flex items-center">
|
||||||
<div
|
<div
|
||||||
className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-semibold ${
|
className={`flex h-8 w-8 items-center justify-center rounded-full text-sm font-semibold ${
|
||||||
step === s
|
step === s
|
||||||
? 'bg-primary text-primary-foreground'
|
? 'bg-primary text-primary-foreground'
|
||||||
: step === 'password' && s === 'generate'
|
: step === 'password' && s === 'generate'
|
||||||
|
|
@ -63,7 +63,7 @@ export default function Signup({
|
||||||
>
|
>
|
||||||
{index + 1}
|
{index + 1}
|
||||||
</div>
|
</div>
|
||||||
{index < 1 && <div className="w-12 h-0.5 bg-muted mx-1" />}
|
{index < 1 && <div className="mx-1 h-0.5 w-12 bg-muted" />}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -75,7 +75,7 @@ export default function Signup({
|
||||||
{renderStepIndicator()}
|
{renderStepIndicator()}
|
||||||
|
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<h3 className="text-lg font-semibold mb-2">{t('Create Your Nostr Account')}</h3>
|
<h3 className="mb-2 text-lg font-semibold">{t('Create Your Nostr Account')}</h3>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{t('Generate your unique private key. This is your digital identity.')}
|
{t('Generate your unique private key. This is your digital identity.')}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -110,7 +110,7 @@ export default function Signup({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full flex flex-wrap gap-2">
|
<div className="flex w-full flex-wrap gap-2">
|
||||||
<Button onClick={handleDownload} className="flex-1">
|
<Button onClick={handleDownload} className="flex-1">
|
||||||
<Download />
|
<Download />
|
||||||
{t('Download Backup File')}
|
{t('Download Backup File')}
|
||||||
|
|
@ -129,7 +129,7 @@ export default function Signup({
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2 ml-2">
|
<div className="ml-2 flex items-center gap-2">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="acknowledge-checkbox"
|
id="acknowledge-checkbox"
|
||||||
checked={checkedSaveKey}
|
checked={checkedSaveKey}
|
||||||
|
|
@ -159,7 +159,7 @@ export default function Signup({
|
||||||
{renderStepIndicator()}
|
{renderStepIndicator()}
|
||||||
|
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<h3 className="text-lg font-semibold mb-2">{t('Secure Your Account')}</h3>
|
<h3 className="mb-2 text-lg font-semibold">{t('Secure Your Account')}</h3>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{t('Add an extra layer of protection with a password')}
|
{t('Add an extra layer of protection with a password')}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -201,7 +201,7 @@ export default function Signup({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full flex gap-2">
|
<div className="flex w-full gap-2">
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
|
||||||
|
|
@ -45,10 +45,10 @@ function AccountManagerNav({
|
||||||
return (
|
return (
|
||||||
<div onClick={(e) => e.stopPropagation()} className="flex flex-col gap-8">
|
<div onClick={(e) => e.stopPropagation()} className="flex flex-col gap-8">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-center text-muted-foreground text-sm font-semibold">
|
<div className="text-center text-sm font-semibold text-muted-foreground">
|
||||||
{t('Add an Account')}
|
{t('Add an Account')}
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2 mt-4">
|
<div className="mt-4 space-y-2">
|
||||||
{!!window.nostr && (
|
{!!window.nostr && (
|
||||||
<Button onClick={() => nip07Login().then(() => close?.())} className="w-full">
|
<Button onClick={() => nip07Login().then(() => close?.())} className="w-full">
|
||||||
{t('Login with Browser Extension')}
|
{t('Login with Browser Extension')}
|
||||||
|
|
@ -69,10 +69,10 @@ function AccountManagerNav({
|
||||||
</div>
|
</div>
|
||||||
<Separator />
|
<Separator />
|
||||||
<div>
|
<div>
|
||||||
<div className="text-center text-muted-foreground text-sm font-semibold">
|
<div className="text-center text-sm font-semibold text-muted-foreground">
|
||||||
{t("Don't have an account yet?")}
|
{t("Don't have an account yet?")}
|
||||||
</div>
|
</div>
|
||||||
<Button onClick={() => setPage('signup')} className="w-full mt-4">
|
<Button onClick={() => setPage('signup')} className="mt-4 w-full">
|
||||||
{t('Create New Account')}
|
{t('Create New Account')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -80,7 +80,7 @@ function AccountManagerNav({
|
||||||
<>
|
<>
|
||||||
<Separator />
|
<Separator />
|
||||||
<div>
|
<div>
|
||||||
<div className="text-center text-muted-foreground text-sm font-semibold">
|
<div className="text-center text-sm font-semibold text-muted-foreground">
|
||||||
{t('Logged in Accounts')}
|
{t('Logged in Accounts')}
|
||||||
</div>
|
</div>
|
||||||
<AccountList className="mt-4" afterSwitch={() => close?.()} />
|
<AccountList className="mt-4" afterSwitch={() => close?.()} />
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ export default function AudioPlayer({
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-3 py-2 px-2 border rounded-full max-w-md bg-background',
|
'flex max-w-md items-center gap-3 rounded-full border bg-background px-2 py-2',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
|
@ -137,12 +137,12 @@ export default function AudioPlayer({
|
||||||
<audio ref={audioRef} src={src} preload="metadata" onError={() => setError(false)} />
|
<audio ref={audioRef} src={src} preload="metadata" onError={() => setError(false)} />
|
||||||
|
|
||||||
{/* Play/Pause Button */}
|
{/* Play/Pause Button */}
|
||||||
<Button size="icon" className="rounded-full shrink-0" onClick={togglePlay}>
|
<Button size="icon" className="shrink-0 rounded-full" onClick={togglePlay}>
|
||||||
{isPlaying ? <Pause fill="currentColor" /> : <Play fill="currentColor" />}
|
{isPlaying ? <Pause fill="currentColor" /> : <Play fill="currentColor" />}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{/* Progress Section */}
|
{/* Progress Section */}
|
||||||
<div className="flex-1 relative">
|
<div className="relative flex-1">
|
||||||
<Slider
|
<Slider
|
||||||
value={[currentTime]}
|
value={[currentTime]}
|
||||||
max={duration || 100}
|
max={duration || 100}
|
||||||
|
|
@ -153,14 +153,14 @@ export default function AudioPlayer({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-sm font-mono text-muted-foreground">
|
<div className="font-mono text-sm text-muted-foreground">
|
||||||
{formatTime(Math.max(duration - currentTime, 0))}
|
{formatTime(Math.max(duration - currentTime, 0))}
|
||||||
</div>
|
</div>
|
||||||
{isMinimized ? (
|
{isMinimized ? (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="rounded-full shrink-0 text-muted-foreground"
|
className="shrink-0 rounded-full text-muted-foreground"
|
||||||
onClick={() => mediaManager.stopAudioBackground()}
|
onClick={() => mediaManager.stopAudioBackground()}
|
||||||
>
|
>
|
||||||
<X />
|
<X />
|
||||||
|
|
@ -169,7 +169,7 @@ export default function AudioPlayer({
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="rounded-full shrink-0 text-muted-foreground"
|
className="shrink-0 rounded-full text-muted-foreground"
|
||||||
onClick={() => mediaManager.playAudioBackground(src, audioRef.current?.currentTime || 0)}
|
onClick={() => mediaManager.playAudioBackground(src, audioRef.current?.currentTime || 0)}
|
||||||
>
|
>
|
||||||
<Minimize2 />
|
<Minimize2 />
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ export default function BookmarkButton({ stuff }: { stuff: Event | string }) {
|
||||||
<button
|
<button
|
||||||
className={`flex items-center gap-1 ${
|
className={`flex items-center gap-1 ${
|
||||||
isBookmarked ? 'text-rose-400' : 'text-muted-foreground'
|
isBookmarked ? 'text-rose-400' : 'text-muted-foreground'
|
||||||
} enabled:hover:text-rose-400 px-3 h-full disabled:text-muted-foreground/40 disabled:cursor-default`}
|
} h-full px-3 enabled:hover:text-rose-400 disabled:cursor-default disabled:text-muted-foreground/40`}
|
||||||
onClick={isBookmarked ? handleRemoveBookmark : handleBookmark}
|
onClick={isBookmarked ? handleRemoveBookmark : handleBookmark}
|
||||||
disabled={!event || updating}
|
disabled={!event || updating}
|
||||||
title={isBookmarked ? t('Remove bookmark') : t('Bookmark')}
|
title={isBookmarked ? t('Remove bookmark') : t('Bookmark')}
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ export default function BookmarkList() {
|
||||||
|
|
||||||
if (eventIds.length === 0) {
|
if (eventIds.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="mt-2 text-sm text-center text-muted-foreground">
|
<div className="mt-2 text-center text-sm text-muted-foreground">
|
||||||
{t('no bookmarks found')}
|
{t('no bookmarks found')}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
@ -79,7 +79,7 @@ export default function BookmarkList() {
|
||||||
<NoteCardLoadingSkeleton />
|
<NoteCardLoadingSkeleton />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center text-sm text-muted-foreground mt-2">
|
<div className="mt-2 text-center text-sm text-muted-foreground">
|
||||||
{t('no more bookmarks')}
|
{t('no more bookmarks')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -42,10 +42,10 @@ export default function AccountButton() {
|
||||||
profile ? (
|
profile ? (
|
||||||
<SimpleUserAvatar
|
<SimpleUserAvatar
|
||||||
userId={pubkey}
|
userId={pubkey}
|
||||||
className={cn('size-6', active ? 'ring-primary ring-2' : '')}
|
className={cn('size-6', active ? 'ring-2 ring-primary' : '')}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Skeleton className={cn('size-6 rounded-full', active ? 'ring-primary ring-2' : '')} />
|
<Skeleton className={cn('size-6 rounded-full', active ? 'ring-2 ring-primary' : '')} />
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<UserRound />
|
<UserRound />
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export default function BottomNavigationBarItem({
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex shadow-none items-center bg-transparent w-full h-12 p-3 m-0 rounded-lg [&_svg]:size-6',
|
'm-0 flex h-12 w-full items-center rounded-lg bg-transparent p-3 shadow-none [&_svg]:size-6',
|
||||||
active && 'text-primary hover:text-primary'
|
active && 'text-primary hover:text-primary'
|
||||||
)}
|
)}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export default function NotificationsButton() {
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Bell />
|
<Bell />
|
||||||
{hasNewNotification && (
|
{hasNewNotification && (
|
||||||
<div className="absolute -top-0.5 right-0.5 w-2 h-2 ring-2 ring-background bg-primary rounded-full" />
|
<div className="absolute -top-0.5 right-0.5 h-2 w-2 rounded-full bg-primary ring-2 ring-background" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</BottomNavigationBarItem>
|
</BottomNavigationBarItem>
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,13 @@ import NotificationsButton from './NotificationsButton'
|
||||||
export default function BottomNavigationBar() {
|
export default function BottomNavigationBar() {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn('fixed bottom-0 w-full z-40 bg-background border-t')}
|
className={cn('fixed bottom-0 z-40 w-full border-t bg-background')}
|
||||||
style={{
|
style={{
|
||||||
paddingBottom: 'env(safe-area-inset-bottom)'
|
paddingBottom: 'env(safe-area-inset-bottom)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<BackgroundAudio className="rounded-none border-x-0 border-t-0 border-b bg-background" />
|
<BackgroundAudio className="rounded-none border-x-0 border-b border-t-0 bg-background" />
|
||||||
<div className="w-full flex justify-around items-center [&_svg]:size-4 [&_svg]:shrink-0">
|
<div className="flex w-full items-center justify-around [&_svg]:size-4 [&_svg]:shrink-0">
|
||||||
<HomeButton />
|
<HomeButton />
|
||||||
<ExploreButton />
|
<ExploreButton />
|
||||||
<NotificationsButton />
|
<NotificationsButton />
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ export default function ClientTag({ event }: { event: NostrEvent }) {
|
||||||
if (!usingClient) return null
|
if (!usingClient) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="text-sm text-muted-foreground shrink-0">
|
<span className="shrink-0 text-sm text-muted-foreground">
|
||||||
{t('via {{client}}', { client: usingClient })}
|
{t('via {{client}}', { client: usingClient })}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ export default function Collapsible({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn('relative text-left overflow-hidden', className)}
|
className={cn('relative overflow-hidden text-left', className)}
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
{...props}
|
{...props}
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -57,8 +57,8 @@ export default function Collapsible({
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
{shouldCollapse && !expanded && (
|
{shouldCollapse && !expanded && (
|
||||||
<div className="absolute bottom-0 h-40 w-full z-10 bg-gradient-to-b from-transparent to-background/90 flex items-end justify-center pb-4">
|
<div className="absolute bottom-0 z-10 flex h-40 w-full items-end justify-center bg-gradient-to-b from-transparent to-background/90 pb-4">
|
||||||
<div className="bg-background rounded-lg">
|
<div className="rounded-lg bg-background">
|
||||||
<Button
|
<Button
|
||||||
className="bg-foreground hover:bg-foreground/80"
|
className="bg-foreground hover:bg-foreground/80"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ export default function Content({
|
||||||
let imageIndex = 0
|
let imageIndex = 0
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div ref={contentRef} className={cn('text-wrap break-words whitespace-pre-wrap', className)}>
|
<div ref={contentRef} className={cn('whitespace-pre-wrap text-wrap break-words', className)}>
|
||||||
{nodes.map((node, index) => {
|
{nodes.map((node, index) => {
|
||||||
if (node.type === 'text') {
|
if (node.type === 'text') {
|
||||||
return node.data
|
return node.data
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export default function CommunityDefinitionPreview({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('pointer-events-none', className)}>
|
<div className={cn('pointer-events-none', className)}>
|
||||||
[{t('Community')}] <span className="italic pr-0.5">{metadata.name}</span>
|
[{t('Community')}] <span className="pr-0.5 italic">{metadata.name}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export default function EmojiPackPreview({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('pointer-events-none', className)}>
|
<div className={cn('pointer-events-none', className)}>
|
||||||
[{t('Emoji Pack')}] <span className="italic pr-0.5">{title}</span>
|
[{t('Emoji Pack')}] <span className="pr-0.5 italic">{title}</span>
|
||||||
{emojis.length > 0 && <span>({emojis.length})</span>}
|
{emojis.length > 0 && <span>({emojis.length})</span>}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export default function FollowPackPreview({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('truncate', className)}>
|
<div className={cn('truncate', className)}>
|
||||||
[{t('Follow Pack')}] <span className="italic pr-0.5">{title}</span>
|
[{t('Follow Pack')}] <span className="pr-0.5 italic">{title}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export default function GroupMetadataPreview({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('pointer-events-none', className)}>
|
<div className={cn('pointer-events-none', className)}>
|
||||||
[{t('Group')}] <span className="italic pr-0.5">{metadata.name}</span>
|
[{t('Group')}] <span className="pr-0.5 italic">{metadata.name}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export default function HighlightPreview({
|
||||||
<Content
|
<Content
|
||||||
content={translatedEvent?.content ?? event.content}
|
content={translatedEvent?.content ?? event.content}
|
||||||
emojiInfos={emojiInfos}
|
emojiInfos={emojiInfos}
|
||||||
className="italic pr-0.5"
|
className="pr-0.5 italic"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export default function LiveEventPreview({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('pointer-events-none', className)}>
|
<div className={cn('pointer-events-none', className)}>
|
||||||
[{t('Live event')}] <span className="italic pr-0.5">{metadata.title}</span>
|
[{t('Live event')}] <span className="pr-0.5 italic">{metadata.title}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export default function LongFormArticlePreview({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('pointer-events-none', className)}>
|
<div className={cn('pointer-events-none', className)}>
|
||||||
[{t('Article')}] <span className="italic pr-0.5">{metadata.title}</span>
|
[{t('Article')}] <span className="pr-0.5 italic">{metadata.title}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ export default function PictureNotePreview({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('pointer-events-none', className)}>
|
<div className={cn('pointer-events-none', className)}>
|
||||||
[{t('Image')}] <span className="italic pr-0.5">{event.content}</span>
|
[{t('Image')}] <span className="pr-0.5 italic">{event.content}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export default function PollPreview({ event, className }: { event: Event; classN
|
||||||
<Content
|
<Content
|
||||||
content={translatedEvent?.content ?? event.content}
|
content={translatedEvent?.content ?? event.content}
|
||||||
emojiInfos={emojiInfos}
|
emojiInfos={emojiInfos}
|
||||||
className="italic pr-0.5"
|
className="pr-0.5 italic"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ export default function VideoNotePreview({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('pointer-events-none', className)}>
|
<div className={cn('pointer-events-none', className)}>
|
||||||
[{t('Media')}] <span className="italic pr-0.5">{event.content}</span>
|
[{t('Media')}] <span className="pr-0.5 italic">{event.content}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,23 +85,23 @@ export default function DefaultRelaysSetting() {
|
||||||
/>
|
/>
|
||||||
<Button onClick={saveNewRelayUrl}>{t('Add')}</Button>
|
<Button onClick={saveNewRelayUrl}>{t('Add')}</Button>
|
||||||
</div>
|
</div>
|
||||||
{newRelayUrlError && <div className="text-xs text-destructive mt-1">{newRelayUrlError}</div>}
|
{newRelayUrlError && <div className="mt-1 text-xs text-destructive">{newRelayUrlError}</div>}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function RelayUrl({ url, onRemove }: { url: string; onRemove: () => void }) {
|
function RelayUrl({ url, onRemove }: { url: string; onRemove: () => void }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between pl-1 pr-3 py-1">
|
<div className="flex items-center justify-between py-1 pl-1 pr-3">
|
||||||
<div className="flex gap-3 items-center flex-1 w-0">
|
<div className="flex w-0 flex-1 items-center gap-3">
|
||||||
<RelayIcon url={url} className="w-4 h-4" />
|
<RelayIcon url={url} className="h-4 w-4" />
|
||||||
<div className="text-muted-foreground text-sm truncate">{url}</div>
|
<div className="truncate text-sm text-muted-foreground">{url}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="shrink-0">
|
<div className="shrink-0">
|
||||||
<CircleX
|
<CircleX
|
||||||
size={16}
|
size={16}
|
||||||
onClick={onRemove}
|
onClick={onRemove}
|
||||||
className="text-muted-foreground hover:text-destructive cursor-pointer"
|
className="cursor-pointer text-muted-foreground hover:text-destructive"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,10 @@ export default function PlatinumSponsors() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="font-semibold text-center">{t('Platinum Sponsors')}</div>
|
<div className="text-center font-semibold">{t('Platinum Sponsors')}</div>
|
||||||
<div className="flex flex-col gap-2 items-center">
|
<div className="flex flex-col items-center gap-2">
|
||||||
<div
|
<div
|
||||||
className="flex items-center gap-4 cursor-pointer"
|
className="flex cursor-pointer items-center gap-4"
|
||||||
onClick={() => window.open('https://opensats.org/', '_blank')}
|
onClick={() => window.open('https://opensats.org/', '_blank')}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
|
|
|
||||||
|
|
@ -21,23 +21,23 @@ export default function RecentSupporters() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="font-semibold text-center">{t('Recent Supporters')}</div>
|
<div className="text-center font-semibold">{t('Recent Supporters')}</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
{supporters.map((item, index) => (
|
{supporters.map((item, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="flex items-center justify-between rounded-md border p-2 sm:p-4 gap-2"
|
className="flex items-center justify-between gap-2 rounded-md border p-2 sm:p-4"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2 flex-1 w-0">
|
<div className="flex w-0 flex-1 items-center gap-2">
|
||||||
<UserAvatar userId={item.pubkey} />
|
<UserAvatar userId={item.pubkey} />
|
||||||
<div className="flex-1 w-0">
|
<div className="w-0 flex-1">
|
||||||
<Username className="font-semibold w-fit" userId={item.pubkey} />
|
<Username className="w-fit font-semibold" userId={item.pubkey} />
|
||||||
<div className="text-xs text-muted-foreground line-clamp-3 select-text">
|
<div className="line-clamp-3 select-text text-xs text-muted-foreground">
|
||||||
{item.comment}
|
{item.comment}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="font-semibold text-yellow-400 shrink-0">
|
<div className="shrink-0 font-semibold text-yellow-400">
|
||||||
{formatAmount(item.amount)} {t('sats')}
|
{formatAmount(item.amount)} {t('sats')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,12 @@ export default function Donation({ className }: { className?: string }) {
|
||||||
const [donationAmount, setDonationAmount] = useState<number | undefined>(undefined)
|
const [donationAmount, setDonationAmount] = useState<number | undefined>(undefined)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('p-4 border rounded-lg space-y-4', className)}>
|
<div className={cn('space-y-4 rounded-lg border p-4', className)}>
|
||||||
<div className="text-center font-semibold">{t('Enjoying Jumble?')}</div>
|
<div className="text-center font-semibold">{t('Enjoying Jumble?')}</div>
|
||||||
<div className="text-center text-muted-foreground">
|
<div className="text-center text-muted-foreground">
|
||||||
{t('Your donation helps me maintain Jumble and make it better! 😊')}
|
{t('Your donation helps me maintain Jumble and make it better! 😊')}
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
<div className="grid grid-cols-2 gap-4 lg:grid-cols-4">
|
||||||
{[
|
{[
|
||||||
{ amount: 1000, text: '☕️ 1k' },
|
{ amount: 1000, text: '☕️ 1k' },
|
||||||
{ amount: 10000, text: '🍜 10k' },
|
{ amount: 10000, text: '🍜 10k' },
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ export default function DrawerMenuItem({
|
||||||
<DrawerClose className="w-full">
|
<DrawerClose className="w-full">
|
||||||
<Button
|
<Button
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={cn('w-full p-6 justify-start text-lg gap-4 [&_svg]:size-5', className)}
|
className={cn('w-full justify-start gap-4 p-6 text-lg [&_svg]:size-5', className)}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
||||||
|
|
@ -42,21 +42,21 @@ export function EmbeddedLNInvoice({ invoice, className }: { invoice: string; cla
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn('p-3 border rounded-lg cursor-default flex flex-col gap-3 max-w-sm', className)}
|
className={cn('flex max-w-sm cursor-default flex-col gap-3 rounded-lg border p-3', className)}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Zap className="w-5 h-5 text-yellow-400" />
|
<Zap className="h-5 w-5 text-yellow-400" />
|
||||||
<div className="font-semibold text-sm">{t('Lightning Invoice')}</div>
|
<div className="text-sm font-semibold">{t('Lightning Invoice')}</div>
|
||||||
</div>
|
</div>
|
||||||
{description && (
|
{description && (
|
||||||
<div className="text-sm text-muted-foreground break-words">{description}</div>
|
<div className="break-words text-sm text-muted-foreground">{description}</div>
|
||||||
)}
|
)}
|
||||||
<div className="text-lg font-bold">
|
<div className="text-lg font-bold">
|
||||||
{formatAmount(amount)} {t('sats')}
|
{formatAmount(amount)} {t('sats')}
|
||||||
</div>
|
</div>
|
||||||
<Button onClick={handlePayClick}>
|
<Button onClick={handlePayClick}>
|
||||||
{paying && <Loader className="w-4 h-4 animate-spin" />}
|
{paying && <Loader className="h-4 w-4 animate-spin" />}
|
||||||
{t('Pay')}
|
{t('Pay')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ export function EmbeddedMention({ userId, className }: { userId: string; classNa
|
||||||
<Username
|
<Username
|
||||||
userId={userId}
|
userId={userId}
|
||||||
showAt
|
showAt
|
||||||
className={cn('text-primary font-normal inline', className)}
|
className={cn('inline font-normal text-primary', className)}
|
||||||
withoutSkeleton
|
withoutSkeleton
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -29,18 +29,18 @@ export function EmbeddedNote({ noteId, className }: { noteId: string; className?
|
||||||
function EmbeddedNoteSkeleton({ className }: { className?: string }) {
|
function EmbeddedNoteSkeleton({ className }: { className?: string }) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn('text-left p-2 sm:p-3 border rounded-xl bg-card', className)}
|
className={cn('rounded-xl border bg-card p-2 text-left sm:p-3', className)}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Skeleton className="w-9 h-9 rounded-full" />
|
<Skeleton className="h-9 w-9 rounded-full" />
|
||||||
<div>
|
<div>
|
||||||
<Skeleton className="h-3 w-16 my-1" />
|
<Skeleton className="my-1 h-3 w-16" />
|
||||||
<Skeleton className="h-3 w-16 my-1" />
|
<Skeleton className="my-1 h-3 w-16" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Skeleton className="w-full h-4 my-1 mt-2" />
|
<Skeleton className="my-1 mt-2 h-4 w-full" />
|
||||||
<Skeleton className="w-2/3 h-4 my-1" />
|
<Skeleton className="my-1 h-4 w-2/3" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -49,10 +49,10 @@ function EmbeddedNoteNotFound({ noteId, className }: { noteId: string; className
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('text-left p-2 sm:p-3 border rounded-xl bg-card', className)}>
|
<div className={cn('rounded-xl border bg-card p-2 text-left sm:p-3', className)}>
|
||||||
<div className="flex flex-col items-center text-muted-foreground font-medium gap-2">
|
<div className="flex flex-col items-center gap-2 font-medium text-muted-foreground">
|
||||||
<div>{t('Sorry! The note cannot be found 😔')}</div>
|
<div>{t('Sorry! The note cannot be found 😔')}</div>
|
||||||
<ClientSelect className="w-full mt-2" originalNoteId={noteId} />
|
<ClientSelect className="mt-2 w-full" originalNoteId={noteId} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ export function EmbeddedWebsocketUrl({ url }: { url: string }) {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
[ {url} ]
|
[ {url} ]
|
||||||
<span className="w-2 h-1 bg-primary" />
|
<span className="h-1 w-2 bg-primary" />
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export default function Emoji({
|
||||||
|
|
||||||
if (typeof emoji === 'string') {
|
if (typeof emoji === 'string') {
|
||||||
return emoji === '+' ? (
|
return emoji === '+' ? (
|
||||||
<Heart className={cn('size-5 text-red-400 fill-red-400', classNames?.img)} />
|
<Heart className={cn('size-5 fill-red-400 text-red-400', classNames?.img)} />
|
||||||
) : (
|
) : (
|
||||||
<span className={cn('whitespace-nowrap', classNames?.text)}>{emoji}</span>
|
<span className={cn('whitespace-nowrap', classNames?.text)}>{emoji}</span>
|
||||||
)
|
)
|
||||||
|
|
@ -34,7 +34,7 @@ export default function Emoji({
|
||||||
src={emoji.url}
|
src={emoji.url}
|
||||||
alt={emoji.shortcode}
|
alt={emoji.shortcode}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
className={cn('inline-block size-5 pointer-events-none', classNames?.img)}
|
className={cn('pointer-events-none inline-block size-5', classNames?.img)}
|
||||||
onLoad={() => {
|
onLoad={() => {
|
||||||
setHasError(false)
|
setHasError(false)
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ export default function EmojiPackList() {
|
||||||
|
|
||||||
if (eventIds.length === 0) {
|
if (eventIds.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="mt-2 text-sm text-center text-muted-foreground">
|
<div className="mt-2 text-center text-sm text-muted-foreground">
|
||||||
{t('no emoji packs found')}
|
{t('no emoji packs found')}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ export default function EmojiPickerDialog({
|
||||||
return (
|
return (
|
||||||
<DropdownMenu open={open} onOpenChange={setOpen}>
|
<DropdownMenu open={open} onOpenChange={setOpen}>
|
||||||
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
|
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent side="top" className="p-0 w-fit">
|
<DropdownMenuContent side="top" className="w-fit p-0">
|
||||||
<EmojiPicker
|
<EmojiPicker
|
||||||
onEmojiClick={(emoji, e) => {
|
onEmojiClick={(emoji, e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,9 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
|
||||||
render() {
|
render() {
|
||||||
if (this.state.hasError) {
|
if (this.state.hasError) {
|
||||||
return (
|
return (
|
||||||
<div className="w-screen h-screen flex flex-col items-center justify-center p-4 gap-4">
|
<div className="flex h-screen w-screen flex-col items-center justify-center gap-4 p-4">
|
||||||
<h1 className="text-2xl font-bold">Oops, something went wrong.</h1>
|
<h1 className="text-2xl font-bold">Oops, something went wrong.</h1>
|
||||||
<p className="text-lg text-center max-w-md">
|
<p className="max-w-md text-center text-lg">
|
||||||
Sorry for the inconvenience. If you don't mind helping, you can{' '}
|
Sorry for the inconvenience. If you don't mind helping, you can{' '}
|
||||||
<a
|
<a
|
||||||
href="https://github.com/CodyTseng/jumble/issues/new"
|
href="https://github.com/CodyTseng/jumble/issues/new"
|
||||||
|
|
@ -61,7 +61,7 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
|
||||||
>
|
>
|
||||||
Copy Error Message
|
Copy Error Message
|
||||||
</Button>
|
</Button>
|
||||||
<pre className="bg-destructive/10 text-destructive p-2 rounded text-wrap break-words whitespace-pre-wrap">
|
<pre className="whitespace-pre-wrap text-wrap break-words rounded bg-destructive/10 p-2 text-destructive">
|
||||||
Error: {this.state.error.message}
|
Error: {this.state.error.message}
|
||||||
</pre>
|
</pre>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ export default function Explore() {
|
||||||
<div className="p-4 max-md:border-b">
|
<div className="p-4 max-md:border-b">
|
||||||
<Skeleton className="h-6 w-20" />
|
<Skeleton className="h-6 w-20" />
|
||||||
</div>
|
</div>
|
||||||
<div className="grid md:px-4 md:grid-cols-2 md:gap-2">
|
<div className="grid md:grid-cols-2 md:gap-2 md:px-4">
|
||||||
<RelaySimpleInfoSkeleton className="h-auto px-4 py-3 md:rounded-lg md:border" />
|
<RelaySimpleInfoSkeleton className="h-auto px-4 py-3 md:rounded-lg md:border" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -62,13 +62,13 @@ function RelayCollection({ collection }: { collection: TAwesomeRelayCollection }
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'sticky bg-background z-20 px-4 py-3 text-2xl font-semibold max-md:border-b',
|
'sticky z-20 bg-background px-4 py-3 text-2xl font-semibold max-md:border-b',
|
||||||
deepBrowsing ? 'top-12' : 'top-24'
|
deepBrowsing ? 'top-12' : 'top-24'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{collection.name}
|
{collection.name}
|
||||||
</div>
|
</div>
|
||||||
<div className="grid md:px-4 md:grid-cols-2 md:gap-3">
|
<div className="grid md:grid-cols-2 md:gap-3 md:px-4">
|
||||||
{collection.relays.map((url) => (
|
{collection.relays.map((url) => (
|
||||||
<RelayItem key={url} url={url} />
|
<RelayItem key={url} url={url} />
|
||||||
))}
|
))}
|
||||||
|
|
@ -82,7 +82,7 @@ function RelayItem({ url }: { url: string }) {
|
||||||
const { relayInfo, isFetching } = useFetchRelayInfo(url)
|
const { relayInfo, isFetching } = useFetchRelayInfo(url)
|
||||||
|
|
||||||
if (isFetching) {
|
if (isFetching) {
|
||||||
return <RelaySimpleInfoSkeleton className="h-auto px-4 py-3 border-b md:rounded-lg md:border" />
|
return <RelaySimpleInfoSkeleton className="h-auto border-b px-4 py-3 md:rounded-lg md:border" />
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!relayInfo) {
|
if (!relayInfo) {
|
||||||
|
|
@ -92,7 +92,7 @@ function RelayItem({ url }: { url: string }) {
|
||||||
return (
|
return (
|
||||||
<RelaySimpleInfo
|
<RelaySimpleInfo
|
||||||
key={relayInfo.url}
|
key={relayInfo.url}
|
||||||
className="clickable h-auto px-4 py-3 border-b md:rounded-lg md:border"
|
className="clickable h-auto border-b px-4 py-3 md:rounded-lg md:border"
|
||||||
relayInfo={relayInfo}
|
relayInfo={relayInfo}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ export default function ExternalContent({
|
||||||
|
|
||||||
if (node.type === 'text') {
|
if (node.type === 'text') {
|
||||||
return (
|
return (
|
||||||
<div className={cn('text-wrap break-words whitespace-pre-wrap', className)}>{content}</div>
|
<div className={cn('whitespace-pre-wrap text-wrap break-words', className)}>{content}</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,13 +37,13 @@ export function Tabs({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-fit">
|
<div className="w-fit">
|
||||||
<div className="flex relative">
|
<div className="relative flex">
|
||||||
{TABS.map((tab, index) => (
|
{TABS.map((tab, index) => (
|
||||||
<div
|
<div
|
||||||
key={tab.value}
|
key={tab.value}
|
||||||
ref={(el) => (tabRefs.current[index] = el)}
|
ref={(el) => (tabRefs.current[index] = el)}
|
||||||
className={cn(
|
className={cn(
|
||||||
`text-center px-4 py-2 font-semibold clickable cursor-pointer rounded-lg`,
|
`clickable cursor-pointer rounded-lg px-4 py-2 text-center font-semibold`,
|
||||||
selectedTab === tab.value ? '' : 'text-muted-foreground'
|
selectedTab === tab.value ? '' : 'text-muted-foreground'
|
||||||
)}
|
)}
|
||||||
onClick={() => onTabChange(tab.value)}
|
onClick={() => onTabChange(tab.value)}
|
||||||
|
|
@ -52,7 +52,7 @@ export function Tabs({
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<div
|
<div
|
||||||
className="absolute bottom-0 h-1 bg-primary rounded-full transition-all duration-500"
|
className="absolute bottom-0 h-1 rounded-full bg-primary transition-all duration-500"
|
||||||
style={{
|
style={{
|
||||||
width: `${indicatorStyle.width}px`,
|
width: `${indicatorStyle.width}px`,
|
||||||
left: `${indicatorStyle.left}px`
|
left: `${indicatorStyle.left}px`
|
||||||
|
|
|
||||||
|
|
@ -32,12 +32,12 @@ export default function ExternalContentInteractions({
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<ScrollArea className="flex-1 w-0">
|
<ScrollArea className="w-0 flex-1">
|
||||||
<Tabs selectedTab={type} onTabChange={setType} />
|
<Tabs selectedTab={type} onTabChange={setType} />
|
||||||
<ScrollBar orientation="horizontal" className="opacity-0 pointer-events-none" />
|
<ScrollBar orientation="horizontal" className="pointer-events-none opacity-0" />
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
<Separator orientation="vertical" className="h-6" />
|
<Separator orientation="vertical" className="h-6" />
|
||||||
<div className="size-10 flex items-center justify-center">
|
<div className="flex size-10 items-center justify-center">
|
||||||
<TrustScoreFilter filterId={SPECIAL_TRUST_SCORE_FILTER_ID.INTERACTIONS} />
|
<TrustScoreFilter filterId={SPECIAL_TRUST_SCORE_FILTER_ID.INTERACTIONS} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ export default function ExternalLink({
|
||||||
<div className="py-2">
|
<div className="py-2">
|
||||||
<Button
|
<Button
|
||||||
onClick={handleOpenLink}
|
onClick={handleOpenLink}
|
||||||
className="w-full p-6 justify-start text-lg gap-4 [&_svg]:size-5"
|
className="w-full justify-start gap-4 p-6 text-lg [&_svg]:size-5"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
<ExternalLinkIcon />
|
<ExternalLinkIcon />
|
||||||
|
|
@ -100,7 +100,7 @@ export default function ExternalLink({
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleViewDiscussions}
|
onClick={handleViewDiscussions}
|
||||||
className="w-full p-6 justify-start text-lg gap-4 [&_svg]:size-5"
|
className="w-full justify-start gap-4 p-6 text-lg [&_svg]:size-5"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
<MessageSquare />
|
<MessageSquare />
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ export default function AddNewRelay() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex items-center gap-2">
|
||||||
<Input
|
<Input
|
||||||
placeholder={t('Add a new relay')}
|
placeholder={t('Add a new relay')}
|
||||||
value={input}
|
value={input}
|
||||||
|
|
@ -50,7 +50,7 @@ export default function AddNewRelay() {
|
||||||
/>
|
/>
|
||||||
<Button onClick={saveRelay}>{t('Add')}</Button>
|
<Button onClick={saveRelay}>{t('Add')}</Button>
|
||||||
</div>
|
</div>
|
||||||
{errorMsg && <div className="text-destructive text-sm pl-8">{errorMsg}</div>}
|
{errorMsg && <div className="pl-8 text-sm text-destructive">{errorMsg}</div>}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ export default function AddNewRelaySet() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex items-center gap-2">
|
||||||
<Input
|
<Input
|
||||||
placeholder={t('Add a new relay set')}
|
placeholder={t('Add a new relay set')}
|
||||||
value={newRelaySetName}
|
value={newRelaySetName}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ export default function FavoriteRelayList() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="text-muted-foreground font-semibold select-none">{t('Relays')}</div>
|
<div className="select-none font-semibold text-muted-foreground">{t('Relays')}</div>
|
||||||
<DndContext
|
<DndContext
|
||||||
sensors={sensors}
|
sensors={sensors}
|
||||||
collisionDetection={closestCenter}
|
collisionDetection={closestCenter}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ export default function PullRelaySetsButton() {
|
||||||
const trigger = (
|
const trigger = (
|
||||||
<Button
|
<Button
|
||||||
variant="link"
|
variant="link"
|
||||||
className="text-muted-foreground hover:no-underline hover:text-foreground p-0 h-fit"
|
className="h-fit p-0 text-muted-foreground hover:text-foreground hover:no-underline"
|
||||||
disabled={!pubkey}
|
disabled={!pubkey}
|
||||||
>
|
>
|
||||||
<CloudDownload />
|
<CloudDownload />
|
||||||
|
|
@ -53,7 +53,7 @@ export default function PullRelaySetsButton() {
|
||||||
<Drawer open={open} onOpenChange={setOpen}>
|
<Drawer open={open} onOpenChange={setOpen}>
|
||||||
<DrawerTrigger asChild>{trigger}</DrawerTrigger>
|
<DrawerTrigger asChild>{trigger}</DrawerTrigger>
|
||||||
<DrawerContent className="max-h-[90vh]">
|
<DrawerContent className="max-h-[90vh]">
|
||||||
<div className="flex flex-col p-4 gap-4 overflow-auto">
|
<div className="flex flex-col gap-4 overflow-auto p-4">
|
||||||
<DrawerHeader>
|
<DrawerHeader>
|
||||||
<DrawerTitle>{t('Select the relay sets you want to pull')}</DrawerTitle>
|
<DrawerTitle>{t('Select the relay sets you want to pull')}</DrawerTitle>
|
||||||
<DrawerDescription className="hidden" />
|
<DrawerDescription className="hidden" />
|
||||||
|
|
|
||||||
|
|
@ -20,22 +20,22 @@ export default function RelayItem({ relay }: { relay: string }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="relative group clickable flex gap-2 border rounded-lg p-2 pr-2.5 items-center justify-between select-none"
|
className="clickable group relative flex select-none items-center justify-between gap-2 rounded-lg border p-2 pr-2.5"
|
||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
style={style}
|
style={style}
|
||||||
onClick={() => push(toRelay(relay))}
|
onClick={() => push(toRelay(relay))}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-1 flex-1">
|
<div className="flex flex-1 items-center gap-1">
|
||||||
<div
|
<div
|
||||||
className="cursor-grab active:cursor-grabbing p-2 hover:bg-muted rounded touch-none shrink-0"
|
className="shrink-0 cursor-grab touch-none rounded p-2 hover:bg-muted active:cursor-grabbing"
|
||||||
{...attributes}
|
{...attributes}
|
||||||
{...listeners}
|
{...listeners}
|
||||||
>
|
>
|
||||||
<GripVertical className="size-4 text-muted-foreground" />
|
<GripVertical className="size-4 text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 items-center flex-1">
|
<div className="flex flex-1 items-center gap-2">
|
||||||
<RelayIcon url={relay} />
|
<RelayIcon url={relay} />
|
||||||
<div className="flex-1 w-0 truncate font-semibold">{relay}</div>
|
<div className="w-0 flex-1 truncate font-semibold">{relay}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SaveRelayDropdownMenu urls={[relay]} />
|
<SaveRelayDropdownMenu urls={[relay]} />
|
||||||
|
|
|
||||||
|
|
@ -42,19 +42,19 @@ export default function RelaySet({ relaySet }: { relaySet: TRelaySet }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={setNodeRef} style={style} className="relative group">
|
<div ref={setNodeRef} style={style} className="group relative">
|
||||||
<div className="w-full border rounded-lg px-2 py-2.5">
|
<div className="w-full rounded-lg border px-2 py-2.5">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div
|
<div
|
||||||
className="cursor-grab active:cursor-grabbing p-2 hover:bg-muted rounded touch-none"
|
className="cursor-grab touch-none rounded p-2 hover:bg-muted active:cursor-grabbing"
|
||||||
{...attributes}
|
{...attributes}
|
||||||
{...listeners}
|
{...listeners}
|
||||||
>
|
>
|
||||||
<GripVertical className="size-4 text-muted-foreground" />
|
<GripVertical className="size-4 text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex justify-center items-center w-6 h-6 shrink-0">
|
<div className="flex h-6 w-6 shrink-0 items-center justify-center">
|
||||||
<FolderClosed className="size-4" />
|
<FolderClosed className="size-4" />
|
||||||
</div>
|
</div>
|
||||||
<RelaySetName relaySet={relaySet} />
|
<RelaySetName relaySet={relaySet} />
|
||||||
|
|
@ -98,20 +98,20 @@ function RelaySetName({ relaySet }: { relaySet: TRelaySet }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return renamingRelaySetId === relaySet.id ? (
|
return renamingRelaySetId === relaySet.id ? (
|
||||||
<div className="flex gap-1 items-center">
|
<div className="flex items-center gap-1">
|
||||||
<Input
|
<Input
|
||||||
value={newSetName}
|
value={newSetName}
|
||||||
onChange={handleRenameInputChange}
|
onChange={handleRenameInputChange}
|
||||||
onBlur={saveNewRelaySetName}
|
onBlur={saveNewRelaySetName}
|
||||||
onKeyDown={handleRenameInputKeyDown}
|
onKeyDown={handleRenameInputKeyDown}
|
||||||
className="font-semibold w-28"
|
className="w-28 font-semibold"
|
||||||
/>
|
/>
|
||||||
<Button variant="ghost" size="icon" onClick={saveNewRelaySetName}>
|
<Button variant="ghost" size="icon" onClick={saveNewRelaySetName}>
|
||||||
<Check size={18} className="text-green-500" />
|
<Check size={18} className="text-green-500" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="h-8 font-semibold flex items-center select-none">{relaySet.name}</div>
|
<div className="flex h-8 select-none items-center font-semibold">{relaySet.name}</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,7 +125,7 @@ function RelayUrlsExpandToggle({
|
||||||
const { expandedRelaySetId, setExpandedRelaySetId } = useRelaySetsSettingComponent()
|
const { expandedRelaySetId, setExpandedRelaySetId } = useRelaySetsSettingComponent()
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="text-sm text-muted-foreground flex items-center gap-1 cursor-pointer hover:text-foreground"
|
className="flex cursor-pointer items-center gap-1 text-sm text-muted-foreground hover:text-foreground"
|
||||||
onClick={() => setExpandedRelaySetId((pre) => (pre === relaySetId ? null : relaySetId))}
|
onClick={() => setExpandedRelaySetId((pre) => (pre === relaySetId ? null : relaySetId))}
|
||||||
>
|
>
|
||||||
<div className="select-none">{children}</div>
|
<div className="select-none">{children}</div>
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ export default function RelaySetList() {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||||
<div className="text-muted-foreground font-semibold select-none shrink-0">
|
<div className="shrink-0 select-none font-semibold text-muted-foreground">
|
||||||
{t('Relay sets')}
|
{t('Relay sets')}
|
||||||
</div>
|
</div>
|
||||||
<PullRelaySetsButton />
|
<PullRelaySetsButton />
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ export default function RelayUrls({ relaySetId }: { relaySetId: string }) {
|
||||||
/>
|
/>
|
||||||
<Button onClick={saveNewRelayUrl}>{t('Add')}</Button>
|
<Button onClick={saveNewRelayUrl}>{t('Add')}</Button>
|
||||||
</div>
|
</div>
|
||||||
{newRelayUrlError && <div className="text-xs text-destructive mt-1">{newRelayUrlError}</div>}
|
{newRelayUrlError && <div className="mt-1 text-xs text-destructive">{newRelayUrlError}</div>}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -81,15 +81,15 @@ export default function RelayUrls({ relaySetId }: { relaySetId: string }) {
|
||||||
function RelayUrl({ url, onRemove }: { url: string; onRemove: () => void }) {
|
function RelayUrl({ url, onRemove }: { url: string; onRemove: () => void }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between pl-1 pr-3">
|
<div className="flex items-center justify-between pl-1 pr-3">
|
||||||
<div className="flex gap-3 items-center flex-1 w-0">
|
<div className="flex w-0 flex-1 items-center gap-3">
|
||||||
<RelayIcon url={url} className="w-4 h-4" />
|
<RelayIcon url={url} className="h-4 w-4" />
|
||||||
<div className="text-muted-foreground text-sm truncate">{url}</div>
|
<div className="truncate text-sm text-muted-foreground">{url}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="shrink-0">
|
<div className="shrink-0">
|
||||||
<CircleX
|
<CircleX
|
||||||
size={16}
|
size={16}
|
||||||
onClick={onRemove}
|
onClick={onRemove}
|
||||||
className="text-muted-foreground hover:text-destructive cursor-pointer"
|
className="cursor-pointer text-muted-foreground hover:text-destructive"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,8 @@ export default function FeedSwitcher({ close }: { close?: () => void }) {
|
||||||
close?.()
|
close?.()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex gap-3 items-center">
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex justify-center items-center size-6 shrink-0">
|
<div className="flex size-6 shrink-0 items-center justify-center">
|
||||||
<UsersRound className="size-5" />
|
<UsersRound className="size-5" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">{t('Following')}</div>
|
<div className="flex-1">{t('Following')}</div>
|
||||||
|
|
@ -56,8 +56,8 @@ export default function FeedSwitcher({ close }: { close?: () => void }) {
|
||||||
close?.()
|
close?.()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex gap-3 items-center">
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex justify-center items-center size-6 shrink-0">
|
<div className="flex size-6 shrink-0 items-center justify-center">
|
||||||
<Star className="size-5" />
|
<Star className="size-5" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">{t('Special Follow')}</div>
|
<div className="flex-1">{t('Special Follow')}</div>
|
||||||
|
|
@ -74,7 +74,7 @@ export default function FeedSwitcher({ close }: { close?: () => void }) {
|
||||||
action={
|
action={
|
||||||
<SecondaryPageLink
|
<SecondaryPageLink
|
||||||
to={toRelaySettings()}
|
to={toRelaySettings()}
|
||||||
className="flex items-center gap-1 text-xs text-primary hover:text-primary-hover transition-colors font-medium"
|
className="flex items-center gap-1 text-xs font-medium text-primary transition-colors hover:text-primary-hover"
|
||||||
onClick={() => close?.()}
|
onClick={() => close?.()}
|
||||||
>
|
>
|
||||||
<Settings2 className="size-3" />
|
<Settings2 className="size-3" />
|
||||||
|
|
@ -104,9 +104,9 @@ export default function FeedSwitcher({ close }: { close?: () => void }) {
|
||||||
close?.()
|
close?.()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex gap-3 items-center w-full">
|
<div className="flex w-full items-center gap-3">
|
||||||
<RelayIcon url={relay} className="shrink-0" />
|
<RelayIcon url={relay} className="shrink-0" />
|
||||||
<div className="flex-1 w-0 truncate">{simplifyUrl(relay)}</div>
|
<div className="w-0 flex-1 truncate">{simplifyUrl(relay)}</div>
|
||||||
</div>
|
</div>
|
||||||
</FeedSwitcherItem>
|
</FeedSwitcherItem>
|
||||||
))}
|
))}
|
||||||
|
|
@ -119,8 +119,8 @@ export default function FeedSwitcher({ close }: { close?: () => void }) {
|
||||||
|
|
||||||
function SectionHeader({ title, action }: { title: string; action?: React.ReactNode }) {
|
function SectionHeader({ title, action }: { title: string; action?: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-between items-center px-1 py-1">
|
<div className="flex items-center justify-between px-1 py-1">
|
||||||
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
<h3 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
||||||
{title}
|
{title}
|
||||||
</h3>
|
</h3>
|
||||||
{action}
|
{action}
|
||||||
|
|
@ -142,19 +142,19 @@ function FeedSwitcherItem({
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'group relative w-full border rounded-lg px-3 py-2.5 transition-all duration-200',
|
'group relative w-full rounded-lg border px-3 py-2.5 transition-all duration-200',
|
||||||
disabled && 'opacity-50 pointer-events-none',
|
disabled && 'pointer-events-none opacity-50',
|
||||||
isActive
|
isActive
|
||||||
? 'border-primary bg-primary/5 shadow-sm'
|
? 'border-primary bg-primary/5 shadow-sm'
|
||||||
: 'border-border hover:border-primary/50 hover:bg-accent/50 clickable'
|
: 'clickable border-border hover:border-primary/50 hover:bg-accent/50'
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (disabled) return
|
if (disabled) return
|
||||||
onClick()
|
onClick()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex justify-between items-center gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<div className="font-medium flex-1 min-w-0">{children}</div>
|
<div className="min-w-0 flex-1 font-medium">{children}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export default function FollowButton({ pubkey }: { pubkey: string }) {
|
||||||
<AlertDialog>
|
<AlertDialog>
|
||||||
<AlertDialogTrigger asChild>
|
<AlertDialogTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
className="rounded-full min-w-28"
|
className="min-w-28 rounded-full"
|
||||||
variant={hover ? 'destructive' : 'secondary'}
|
variant={hover ? 'destructive' : 'secondary'}
|
||||||
disabled={updating}
|
disabled={updating}
|
||||||
onMouseEnter={() => setHover(true)}
|
onMouseEnter={() => setHover(true)}
|
||||||
|
|
@ -85,7 +85,7 @@ export default function FollowButton({ pubkey }: { pubkey: string }) {
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Button className="rounded-full min-w-28" onClick={handleFollow} disabled={updating}>
|
<Button className="min-w-28 rounded-full" onClick={handleFollow} disabled={updating}>
|
||||||
{updating ? <Loader className="animate-spin" /> : t('Follow')}
|
{updating ? <Loader className="animate-spin" /> : t('Follow')}
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export default function FollowingBadge({ pubkey, userId }: { pubkey?: string; us
|
||||||
if (!isFollowing) return null
|
if (!isFollowing) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-full bg-muted px-2 py-0.5 flex items-center" title={t('Following')}>
|
<div className="flex items-center rounded-full bg-muted px-2 py-0.5" title={t('Following')}>
|
||||||
<UserRoundCheck className="!size-3" />
|
<UserRoundCheck className="!size-3" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ export default function FollowingFavoriteRelayList() {
|
||||||
{showCount < relays.length && <div ref={bottomRef} />}
|
{showCount < relays.length && <div ref={bottomRef} />}
|
||||||
{loading && <RelaySimpleInfoSkeleton className="p-4" />}
|
{loading && <RelaySimpleInfoSkeleton className="p-4" />}
|
||||||
{!loading && (
|
{!loading && (
|
||||||
<div className="text-center text-muted-foreground text-sm mt-2">
|
<div className="mt-2 text-center text-sm text-muted-foreground">
|
||||||
{relays.length === 0 ? t('no relays found') : t('no more relays')}
|
{relays.length === 0 ? t('no relays found') : t('no more relays')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -81,7 +81,7 @@ function RelayItem({ url, users }: { url: string; users: string[] }) {
|
||||||
key={url}
|
key={url}
|
||||||
relayInfo={relayInfo}
|
relayInfo={relayInfo}
|
||||||
users={users}
|
users={users}
|
||||||
className="clickable p-4 border-b"
|
className="clickable border-b p-4"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
push(toRelay(url))
|
push(toRelay(url))
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ export default function HighlightButton({ onHighlight, containerRef }: Highlight
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="fixed z-50 animate-in fade-in-0 slide-in-from-bottom-4 duration-200"
|
className="fixed z-50 duration-200 animate-in fade-in-0 slide-in-from-bottom-4"
|
||||||
style={{
|
style={{
|
||||||
top: `${position.top}px`,
|
top: `${position.top}px`,
|
||||||
left: `${position.left}px`
|
left: `${position.left}px`
|
||||||
|
|
@ -97,7 +97,7 @@ export default function HighlightButton({ onHighlight, containerRef }: Highlight
|
||||||
ref={buttonRef}
|
ref={buttonRef}
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="default"
|
variant="default"
|
||||||
className="shadow-lg gap-2 -translate-x-1/2"
|
className="-translate-x-1/2 gap-2 shadow-lg"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
onHighlight(selectedText)
|
onHighlight(selectedText)
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ export default function Image({
|
||||||
<img
|
<img
|
||||||
src={`data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='${dim.width}' height='${dim.height}'%3E%3C/svg%3E`}
|
src={`data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='${dim.width}' height='${dim.height}'%3E%3C/svg%3E`}
|
||||||
className={cn(
|
className={cn(
|
||||||
'object-cover transition-opacity pointer-events-none w-full h-full',
|
'pointer-events-none h-full w-full object-cover transition-opacity',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
alt=""
|
alt=""
|
||||||
|
|
@ -91,7 +91,7 @@ export default function Image({
|
||||||
<ThumbHashPlaceholder
|
<ThumbHashPlaceholder
|
||||||
thumbHash={thumbHash}
|
thumbHash={thumbHash}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full h-full transition-opacity',
|
'h-full w-full transition-opacity',
|
||||||
isLoading ? 'opacity-100' : 'opacity-0'
|
isLoading ? 'opacity-100' : 'opacity-0'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
@ -99,14 +99,14 @@ export default function Image({
|
||||||
<BlurHashCanvas
|
<BlurHashCanvas
|
||||||
blurHash={blurHash}
|
blurHash={blurHash}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full h-full transition-opacity',
|
'h-full w-full transition-opacity',
|
||||||
isLoading ? 'opacity-100' : 'opacity-0'
|
isLoading ? 'opacity-100' : 'opacity-0'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Skeleton
|
<Skeleton
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full h-full transition-opacity',
|
'h-full w-full transition-opacity',
|
||||||
isLoading ? 'opacity-100' : 'opacity-0',
|
isLoading ? 'opacity-100' : 'opacity-0',
|
||||||
classNames.skeleton
|
classNames.skeleton
|
||||||
)}
|
)}
|
||||||
|
|
@ -124,8 +124,8 @@ export default function Image({
|
||||||
onLoad={handleLoad}
|
onLoad={handleLoad}
|
||||||
onError={handleError}
|
onError={handleError}
|
||||||
className={cn(
|
className={cn(
|
||||||
'object-cover transition-opacity pointer-events-none w-full h-full',
|
'pointer-events-none h-full w-full object-cover transition-opacity',
|
||||||
isLoading ? 'opacity-0 absolute inset-0' : '',
|
isLoading ? 'absolute inset-0 opacity-0' : '',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
@ -137,12 +137,12 @@ export default function Image({
|
||||||
alt={alt}
|
alt={alt}
|
||||||
decoding="async"
|
decoding="async"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
className={cn('object-cover w-full h-full transition-opacity', className)}
|
className={cn('h-full w-full object-cover transition-opacity', className)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'object-cover flex flex-col items-center justify-center w-full h-full bg-muted',
|
'flex h-full w-full flex-col items-center justify-center bg-muted object-cover',
|
||||||
className,
|
className,
|
||||||
classNames.errorPlaceholder
|
classNames.errorPlaceholder
|
||||||
)}
|
)}
|
||||||
|
|
@ -188,7 +188,7 @@ function BlurHashCanvas({ blurHash, className = '' }: { blurHash: string; classN
|
||||||
ref={canvasRef}
|
ref={canvasRef}
|
||||||
width={blurHashWidth}
|
width={blurHashWidth}
|
||||||
height={blurHashHeight}
|
height={blurHashHeight}
|
||||||
className={cn('w-full h-full object-cover rounded-xl', className)}
|
className={cn('h-full w-full rounded-xl object-cover', className)}
|
||||||
style={{
|
style={{
|
||||||
imageRendering: 'auto',
|
imageRendering: 'auto',
|
||||||
filter: 'blur(0.5px)'
|
filter: 'blur(0.5px)'
|
||||||
|
|
@ -218,7 +218,7 @@ function ThumbHashPlaceholder({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn('w-full h-full object-cover rounded-lg', className)}
|
className={cn('h-full w-full rounded-lg object-cover', className)}
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url(${dataUrl})`,
|
backgroundImage: `url(${dataUrl})`,
|
||||||
backgroundSize: 'cover',
|
backgroundSize: 'cover',
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ export default function ImageGallery({
|
||||||
<ImageWithLightbox
|
<ImageWithLightbox
|
||||||
key={i}
|
key={i}
|
||||||
image={image}
|
image={image}
|
||||||
className="max-h-[80vh] sm:max-h-[50vh] object-contain"
|
className="max-h-[80vh] object-contain sm:max-h-[50vh]"
|
||||||
classNames={{
|
classNames={{
|
||||||
wrapper: cn('w-fit max-w-full border', className)
|
wrapper: cn('w-fit max-w-full border', className)
|
||||||
}}
|
}}
|
||||||
|
|
@ -107,7 +107,7 @@ export default function ImageGallery({
|
||||||
imageContent = (
|
imageContent = (
|
||||||
<Image
|
<Image
|
||||||
key={0}
|
key={0}
|
||||||
className="max-h-[80vh] sm:max-h-[50vh] object-contain"
|
className="max-h-[80vh] object-contain sm:max-h-[50vh]"
|
||||||
classNames={{
|
classNames={{
|
||||||
errorPlaceholder: 'aspect-square h-[30vh]',
|
errorPlaceholder: 'aspect-square h-[30vh]',
|
||||||
wrapper: 'cursor-zoom-in border'
|
wrapper: 'cursor-zoom-in border'
|
||||||
|
|
@ -118,7 +118,7 @@ export default function ImageGallery({
|
||||||
)
|
)
|
||||||
} else if (displayImages.length === 2 || displayImages.length === 4) {
|
} else if (displayImages.length === 2 || displayImages.length === 4) {
|
||||||
imageContent = (
|
imageContent = (
|
||||||
<div className="grid grid-cols-2 gap-2 w-full">
|
<div className="grid w-full grid-cols-2 gap-2">
|
||||||
{displayImages.map((image, i) => (
|
{displayImages.map((image, i) => (
|
||||||
<Image
|
<Image
|
||||||
key={i}
|
key={i}
|
||||||
|
|
@ -132,7 +132,7 @@ export default function ImageGallery({
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
imageContent = (
|
imageContent = (
|
||||||
<div className="grid grid-cols-3 gap-2 w-full">
|
<div className="grid w-full grid-cols-3 gap-2">
|
||||||
{displayImages.map((image, i) => (
|
{displayImages.map((image, i) => (
|
||||||
<Image
|
<Image
|
||||||
key={i}
|
key={i}
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ export default function ImageWithLightbox({
|
||||||
if (!display) {
|
if (!display) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="text-primary hover:underline truncate w-fit cursor-pointer"
|
className="w-fit cursor-pointer truncate text-primary hover:underline"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
setDisplay(true)
|
setDisplay(true)
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ export default function InfoCard({
|
||||||
variant?: 'info' | 'success' | 'alert'
|
variant?: 'info' | 'success' | 'alert'
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={cn('p-3 rounded-lg text-sm [&_svg]:size-4', VARIANT_STYLES[variant])}>
|
<div className={cn('rounded-lg p-3 text-sm [&_svg]:size-4', VARIANT_STYLES[variant])}>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{icon ?? ICON_MAP[variant]}
|
{icon ?? ICON_MAP[variant]}
|
||||||
<div className="font-medium">{title}</div>
|
<div className="font-medium">{title}</div>
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ export default function KindFilter({
|
||||||
>
|
>
|
||||||
<ListFilter size={16} />
|
<ListFilter size={16} />
|
||||||
{isDifferentFromSaved && (
|
{isDifferentFromSaved && (
|
||||||
<div className="absolute size-2 rounded-full bg-primary left-7 top-2 ring-2 ring-background" />
|
<div className="absolute left-7 top-2 size-2 rounded-full bg-primary ring-2 ring-background" />
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
|
|
@ -109,7 +109,7 @@ export default function KindFilter({
|
||||||
<div
|
<div
|
||||||
key={label}
|
key={label}
|
||||||
className={cn(
|
className={cn(
|
||||||
'cursor-pointer grid gap-1.5 rounded-lg border px-4 py-3',
|
'grid cursor-pointer gap-1.5 rounded-lg border px-4 py-3',
|
||||||
checked ? 'border-primary/60 bg-primary/5' : 'clickable'
|
checked ? 'border-primary/60 bg-primary/5' : 'clickable'
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
@ -122,14 +122,14 @@ export default function KindFilter({
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<p className="leading-none font-medium">{t(label)}</p>
|
<p className="font-medium leading-none">{t(label)}</p>
|
||||||
<p className="text-muted-foreground text-xs">kind {kindGroup.join(', ')}</p>
|
<p className="text-xs text-muted-foreground">kind {kindGroup.join(', ')}</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-3 gap-2 mt-4">
|
<div className="mt-4 grid grid-cols-3 gap-2">
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
@ -155,7 +155,7 @@ export default function KindFilter({
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Label className="flex items-center gap-2 cursor-pointer mt-4">
|
<Label className="mt-4 flex cursor-pointer items-center gap-2">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="persistent-filter"
|
id="persistent-filter"
|
||||||
checked={isPersistent}
|
checked={isPersistent}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ export function LoadingBar({ className }: { className?: string }) {
|
||||||
return (
|
return (
|
||||||
<div className={cn('h-0.5 w-full overflow-hidden', className)}>
|
<div className={cn('h-0.5 w-full overflow-hidden', className)}>
|
||||||
<div
|
<div
|
||||||
className="h-full w-full bg-gradient-to-r from-primary/40 from-25% via-primary via-50% to-primary/40 to-75% animate-shimmer"
|
className="h-full w-full animate-shimmer bg-gradient-to-r from-primary/40 from-25% via-primary via-50% to-primary/40 to-75%"
|
||||||
style={{
|
style={{
|
||||||
backgroundSize: '400% 100%'
|
backgroundSize: '400% 100%'
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export default function LoginDialog({
|
||||||
return (
|
return (
|
||||||
<Drawer open={open} onOpenChange={setOpen}>
|
<Drawer open={open} onOpenChange={setOpen}>
|
||||||
<DrawerContent className="max-h-[90vh]">
|
<DrawerContent className="max-h-[90vh]">
|
||||||
<div className="flex flex-col p-4 gap-4 overflow-auto">
|
<div className="flex flex-col gap-4 overflow-auto p-4">
|
||||||
<AccountManager close={() => setOpen(false)} />
|
<AccountManager close={() => setOpen(false)} />
|
||||||
</div>
|
</div>
|
||||||
</DrawerContent>
|
</DrawerContent>
|
||||||
|
|
@ -27,7 +27,7 @@ export default function LoginDialog({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogContent className="w-[520px] max-h-[90vh] py-8 overflow-auto">
|
<DialogContent className="max-h-[90vh] w-[520px] overflow-auto py-8">
|
||||||
<AccountManager close={() => setOpen(false)} />
|
<AccountManager close={() => setOpen(false)} />
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
|
||||||
|
|
@ -38,21 +38,21 @@ export default function MailboxRelay({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={setNodeRef} style={style} className="flex items-center justify-between">
|
<div ref={setNodeRef} style={style} className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2 flex-1 w-0">
|
<div className="flex w-0 flex-1 items-center gap-2">
|
||||||
<div
|
<div
|
||||||
{...attributes}
|
{...attributes}
|
||||||
{...listeners}
|
{...listeners}
|
||||||
className="cursor-grab active:cursor-grabbing p-2 hover:bg-muted rounded touch-none"
|
className="cursor-grab touch-none rounded p-2 hover:bg-muted active:cursor-grabbing"
|
||||||
style={{ touchAction: 'none' }}
|
style={{ touchAction: 'none' }}
|
||||||
>
|
>
|
||||||
<GripVertical size={16} className="text-muted-foreground" />
|
<GripVertical size={16} className="text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="flex items-center gap-2 flex-1 w-0 cursor-pointer"
|
className="flex w-0 flex-1 cursor-pointer items-center gap-2"
|
||||||
onClick={() => push(toRelay(mailboxRelay.url))}
|
onClick={() => push(toRelay(mailboxRelay.url))}
|
||||||
>
|
>
|
||||||
<RelayIcon url={mailboxRelay.url} />
|
<RelayIcon url={mailboxRelay.url} />
|
||||||
<div className="truncate flex-1 w-0">{mailboxRelay.url}</div>
|
<div className="w-0 flex-1 truncate">{mailboxRelay.url}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
|
|
@ -72,7 +72,7 @@ export default function MailboxRelay({
|
||||||
<CircleX
|
<CircleX
|
||||||
size={16}
|
size={16}
|
||||||
onClick={() => removeMailboxRelay(mailboxRelay.url)}
|
onClick={() => removeMailboxRelay(mailboxRelay.url)}
|
||||||
className="text-muted-foreground hover:text-destructive clickable"
|
className="clickable text-muted-foreground hover:text-destructive"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ export default function NewMailboxRelayInput({
|
||||||
/>
|
/>
|
||||||
<Button onClick={save}>{t('Add')}</Button>
|
<Button onClick={save}>{t('Add')}</Button>
|
||||||
</div>
|
</div>
|
||||||
{newRelayUrlError && <div className="text-destructive text-xs mt-1">{newRelayUrlError}</div>}
|
{newRelayUrlError && <div className="mt-1 text-xs text-destructive">{newRelayUrlError}</div>}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ export default function MailboxSetting() {
|
||||||
|
|
||||||
if (!pubkey) {
|
if (!pubkey) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full items-center">
|
<div className="flex w-full flex-col items-center">
|
||||||
<Button size="lg" onClick={() => checkLogin()}>
|
<Button size="lg" onClick={() => checkLogin()}>
|
||||||
{t('Login to set')}
|
{t('Login to set')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -109,7 +109,7 @@ export default function MailboxSetting() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="text-xs text-muted-foreground space-y-1">
|
<div className="space-y-1 text-xs text-muted-foreground">
|
||||||
<div>{t('read relays description')}</div>
|
<div>{t('read relays description')}</div>
|
||||||
<div>{t('write relays description')}</div>
|
<div>{t('write relays description')}</div>
|
||||||
<div>{t('read & write relays notice')}</div>
|
<div>{t('read & write relays notice')}</div>
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ export default function MediaPlayer({
|
||||||
if (!mustLoad && !display) {
|
if (!mustLoad && !display) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="text-primary hover:underline truncate w-fit cursor-pointer"
|
className="w-fit cursor-pointer truncate text-primary hover:underline"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
setDisplay(true)
|
setDisplay(true)
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ export default function MuteButton({ pubkey }: { pubkey: string }) {
|
||||||
<DrawerContent>
|
<DrawerContent>
|
||||||
<div className="py-2">
|
<div className="py-2">
|
||||||
<Button
|
<Button
|
||||||
className="w-full p-6 justify-start text-destructive text-lg gap-4 [&_svg]:size-5 focus:text-destructive"
|
className="w-full justify-start gap-4 p-6 text-lg text-destructive focus:text-destructive [&_svg]:size-5"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={(e) => handleMute(e, true)}
|
onClick={(e) => handleMute(e, true)}
|
||||||
disabled={updating || changing}
|
disabled={updating || changing}
|
||||||
|
|
@ -99,7 +99,7 @@ export default function MuteButton({ pubkey }: { pubkey: string }) {
|
||||||
{updating ? <Loader className="animate-spin" /> : t('Mute user privately')}
|
{updating ? <Loader className="animate-spin" /> : t('Mute user privately')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="w-full p-6 justify-start text-destructive text-lg gap-4 [&_svg]:size-5 focus:text-destructive"
|
className="w-full justify-start gap-4 p-6 text-lg text-destructive focus:text-destructive [&_svg]:size-5"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={(e) => handleMute(e, false)}
|
onClick={(e) => handleMute(e, false)}
|
||||||
disabled={updating || changing}
|
disabled={updating || changing}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ export default function NewNotesButton({
|
||||||
{newEvents.length > 0 && (
|
{newEvents.length > 0 && (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full flex justify-center z-40 pointer-events-none',
|
'pointer-events-none z-40 flex w-full justify-center',
|
||||||
enableSingleColumnLayout ? 'sticky' : 'absolute'
|
enableSingleColumnLayout ? 'sticky' : 'absolute'
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -48,10 +48,10 @@ export default function NewNotesButton({
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className="group rounded-full h-fit py-2 pl-2 pr-3 hover:bg-primary-hover pointer-events-auto"
|
className="group pointer-events-auto h-fit rounded-full py-2 pl-2 pr-3 hover:bg-primary-hover"
|
||||||
>
|
>
|
||||||
{pubkeys.length > 0 && (
|
{pubkeys.length > 0 && (
|
||||||
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
|
<div className="flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background *:data-[slot=avatar]:grayscale">
|
||||||
{pubkeys.map((pubkey) => (
|
{pubkeys.map((pubkey) => (
|
||||||
<SimpleUserAvatar key={pubkey} userId={pubkey} size="small" />
|
<SimpleUserAvatar key={pubkey} userId={pubkey} size="small" />
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -29,24 +29,24 @@ export default function Nip05({ pubkey, append }: { pubkey: string; append?: str
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{nip05Name !== '_' ? (
|
{nip05Name !== '_' ? (
|
||||||
<span className="text-sm text-muted-foreground truncate">@{nip05Name}</span>
|
<span className="truncate text-sm text-muted-foreground">@{nip05Name}</span>
|
||||||
) : null}
|
) : null}
|
||||||
{nip05IsVerified ? (
|
{nip05IsVerified ? (
|
||||||
<Favicon
|
<Favicon
|
||||||
domain={nip05Domain}
|
domain={nip05Domain}
|
||||||
className="w-3.5 h-3.5 rounded-full shrink-0"
|
className="h-3.5 w-3.5 shrink-0 rounded-full"
|
||||||
fallback={<BadgeCheck className="text-primary shrink-0" />}
|
fallback={<BadgeCheck className="shrink-0 text-primary" />}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<BadgeAlert className="text-muted-foreground shrink-0" />
|
<BadgeAlert className="shrink-0 text-muted-foreground" />
|
||||||
)}
|
)}
|
||||||
<SecondaryPageLink
|
<SecondaryPageLink
|
||||||
to={toNoteList({ domain: nip05Domain })}
|
to={toNoteList({ domain: nip05Domain })}
|
||||||
className={`hover:underline truncate text-sm ${nip05IsVerified ? 'text-primary' : 'text-muted-foreground'}`}
|
className={`truncate text-sm hover:underline ${nip05IsVerified ? 'text-primary' : 'text-muted-foreground'}`}
|
||||||
>
|
>
|
||||||
{nip05Domain}
|
{nip05Domain}
|
||||||
</SecondaryPageLink>
|
</SecondaryPageLink>
|
||||||
{append && <span className="text-sm text-muted-foreground truncate">{append}</span>}
|
{append && <span className="truncate text-sm text-muted-foreground">{append}</span>}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ export default function NotFound() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="text-muted-foreground w-full h-full flex flex-col items-center justify-center gap-2">
|
<div className="flex h-full w-full flex-col items-center justify-center gap-2 text-muted-foreground">
|
||||||
<div>{t('Lost in the void')} 🌌</div>
|
<div>{t('Lost in the void')} 🌌</div>
|
||||||
<div>(404)</div>
|
<div>(404)</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,11 @@ export default function CommunityDefinition({
|
||||||
const metadata = useMemo(() => getCommunityDefinitionFromEvent(event), [event])
|
const metadata = useMemo(() => getCommunityDefinitionFromEvent(event), [event])
|
||||||
|
|
||||||
const communityNameComponent = (
|
const communityNameComponent = (
|
||||||
<div className="text-xl font-semibold line-clamp-1">{metadata.name}</div>
|
<div className="line-clamp-1 text-xl font-semibold">{metadata.name}</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
const communityDescriptionComponent = metadata.description && (
|
const communityDescriptionComponent = metadata.description && (
|
||||||
<div className="text-sm text-muted-foreground line-clamp-2">{metadata.description}</div>
|
<div className="line-clamp-2 text-sm text-muted-foreground">{metadata.description}</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -29,16 +29,16 @@ export default function CommunityDefinition({
|
||||||
{metadata.image && autoLoadMedia && (
|
{metadata.image && autoLoadMedia && (
|
||||||
<Image
|
<Image
|
||||||
image={{ url: metadata.image, pubkey: event.pubkey }}
|
image={{ url: metadata.image, pubkey: event.pubkey }}
|
||||||
className="aspect-square bg-foreground h-20"
|
className="aspect-square h-20 bg-foreground"
|
||||||
hideIfError
|
hideIfError
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="flex-1 w-0 space-y-1">
|
<div className="w-0 flex-1 space-y-1">
|
||||||
{communityNameComponent}
|
{communityNameComponent}
|
||||||
{communityDescriptionComponent}
|
{communityDescriptionComponent}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ClientSelect className="w-full mt-2" event={event} />
|
<ClientSelect className="mt-2 w-full" event={event} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ export default function EmojiPack({ event, className }: { event: Event; classNam
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="mb-2 flex items-center justify-between">
|
||||||
<h3 className="text-2xl font-semibold">{title}</h3>
|
<h3 className="text-2xl font-semibold">{title}</h3>
|
||||||
{accountPubkey && (
|
{accountPubkey && (
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -55,7 +55,7 @@ export default function EmojiPack({ event, className }: { event: Event; classNam
|
||||||
className="shrink-0"
|
className="shrink-0"
|
||||||
>
|
>
|
||||||
{updating ? (
|
{updating ? (
|
||||||
<Loader className="animate-spin mr-1" />
|
<Loader className="mr-1 animate-spin" />
|
||||||
) : isCollected ? (
|
) : isCollected ? (
|
||||||
<CheckIcon />
|
<CheckIcon />
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,11 @@ export default function FollowPack({ event, className }: { event: Event; classNa
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<div className="flex items-start gap-2 mb-2">
|
<div className="mb-2 flex items-start gap-2">
|
||||||
{image && (
|
{image && (
|
||||||
<Image
|
<Image
|
||||||
image={{ url: image, pubkey: event.pubkey }}
|
image={{ url: image, pubkey: event.pubkey }}
|
||||||
className="w-24 h-20 object-cover"
|
className="h-20 w-24 object-cover"
|
||||||
classNames={{
|
classNames={{
|
||||||
wrapper: 'w-24 h-20 flex-shrink-0',
|
wrapper: 'w-24 h-20 flex-shrink-0',
|
||||||
errorPlaceholder: 'w-24 h-20'
|
errorPlaceholder: 'w-24 h-20'
|
||||||
|
|
@ -34,15 +34,15 @@ export default function FollowPack({ event, className }: { event: Event; classNa
|
||||||
hideIfError
|
hideIfError
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="flex-1 min-w-0">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<h3 className="text-xl font-semibold mb-1 truncate">{title}</h3>
|
<h3 className="mb-1 truncate text-xl font-semibold">{title}</h3>
|
||||||
<span className="text-xs text-muted-foreground shrink-0">
|
<span className="shrink-0 text-xs text-muted-foreground">
|
||||||
{t('n users', { count: pubkeys.length })}
|
{t('n users', { count: pubkeys.length })}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{description && (
|
{description && (
|
||||||
<p className="text-sm text-muted-foreground line-clamp-2">{description}</p>
|
<p className="line-clamp-2 text-sm text-muted-foreground">{description}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,11 @@ export default function GroupMetadata({
|
||||||
const metadata = useMemo(() => getGroupMetadataFromEvent(event), [event])
|
const metadata = useMemo(() => getGroupMetadataFromEvent(event), [event])
|
||||||
|
|
||||||
const groupNameComponent = (
|
const groupNameComponent = (
|
||||||
<div className="text-xl font-semibold line-clamp-1">{metadata.name}</div>
|
<div className="line-clamp-1 text-xl font-semibold">{metadata.name}</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
const groupAboutComponent = metadata.about && (
|
const groupAboutComponent = metadata.about && (
|
||||||
<div className="text-sm text-muted-foreground line-clamp-2">{metadata.about}</div>
|
<div className="line-clamp-2 text-sm text-muted-foreground">{metadata.about}</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -31,16 +31,16 @@ export default function GroupMetadata({
|
||||||
{metadata.picture && autoLoadMedia && (
|
{metadata.picture && autoLoadMedia && (
|
||||||
<Image
|
<Image
|
||||||
image={{ url: metadata.picture, pubkey: event.pubkey }}
|
image={{ url: metadata.picture, pubkey: event.pubkey }}
|
||||||
className="aspect-square bg-foreground h-20"
|
className="aspect-square h-20 bg-foreground"
|
||||||
hideIfError
|
hideIfError
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="flex-1 w-0 space-y-1">
|
<div className="w-0 flex-1 space-y-1">
|
||||||
{groupNameComponent}
|
{groupNameComponent}
|
||||||
{groupAboutComponent}
|
{groupAboutComponent}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ClientSelect className="w-full mt-2" event={event} originalNoteId={originalNoteId} />
|
<ClientSelect className="mt-2 w-full" event={event} originalNoteId={originalNoteId} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,12 @@ export default function Highlight({ event, className }: { event: Event; classNam
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('text-wrap break-words whitespace-pre-wrap space-y-4', className)}>
|
<div className={cn('space-y-4 whitespace-pre-wrap text-wrap break-words', className)}>
|
||||||
{comment && <Content event={createFakeEvent({ content: comment, tags: event.tags })} />}
|
{comment && <Content event={createFakeEvent({ content: comment, tags: event.tags })} />}
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<div className="w-1 flex-shrink-0 my-1 bg-primary/60 rounded-md" />
|
<div className="my-1 w-1 flex-shrink-0 rounded-md bg-primary/60" />
|
||||||
<div
|
<div
|
||||||
className="italic whitespace-pre-line"
|
className="whitespace-pre-line italic"
|
||||||
style={{
|
style={{
|
||||||
overflowWrap: 'anywhere'
|
overflowWrap: 'anywhere'
|
||||||
}}
|
}}
|
||||||
|
|
@ -112,7 +112,7 @@ function HighlightSource({ event }: { event: Event }) {
|
||||||
{t('From')}{' '}
|
{t('From')}{' '}
|
||||||
<ExternalLink
|
<ExternalLink
|
||||||
url={sourceTag[1]}
|
url={sourceTag[1]}
|
||||||
className="underline italic text-muted-foreground hover:text-foreground"
|
className="italic text-muted-foreground underline hover:text-foreground"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
@ -124,7 +124,7 @@ function HighlightSource({ event }: { event: Event }) {
|
||||||
{pubkey && <UserAvatar userId={pubkey} size="xSmall" className="cursor-pointer" />}
|
{pubkey && <UserAvatar userId={pubkey} size="xSmall" className="cursor-pointer" />}
|
||||||
{referenceEventId && (
|
{referenceEventId && (
|
||||||
<div
|
<div
|
||||||
className="truncate underline pointer-events-auto cursor-pointer hover:text-foreground"
|
className="pointer-events-auto cursor-pointer truncate underline hover:text-foreground"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
push(toNote(referenceEvent ?? referenceEventId))
|
push(toNote(referenceEvent ?? referenceEventId))
|
||||||
|
|
|
||||||
|
|
@ -23,14 +23,14 @@ export default function LiveEvent({ event, className }: { event: Event; classNam
|
||||||
<Badge variant="secondary">{metadata.status}</Badge>
|
<Badge variant="secondary">{metadata.status}</Badge>
|
||||||
))
|
))
|
||||||
|
|
||||||
const titleComponent = <div className="text-xl font-semibold line-clamp-1">{metadata.title}</div>
|
const titleComponent = <div className="line-clamp-1 text-xl font-semibold">{metadata.title}</div>
|
||||||
|
|
||||||
const summaryComponent = metadata.summary && (
|
const summaryComponent = metadata.summary && (
|
||||||
<div className="text-sm text-muted-foreground line-clamp-4">{metadata.summary}</div>
|
<div className="line-clamp-4 text-sm text-muted-foreground">{metadata.summary}</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
const tagsComponent = metadata.tags.length > 0 && (
|
const tagsComponent = metadata.tags.length > 0 && (
|
||||||
<div className="flex gap-1 flex-wrap">
|
<div className="flex flex-wrap gap-1">
|
||||||
{metadata.tags.map((tag) => (
|
{metadata.tags.map((tag) => (
|
||||||
<Badge key={tag} variant="secondary">
|
<Badge key={tag} variant="secondary">
|
||||||
{tag}
|
{tag}
|
||||||
|
|
@ -45,7 +45,7 @@ export default function LiveEvent({ event, className }: { event: Event; classNam
|
||||||
{metadata.image && autoLoadMedia && (
|
{metadata.image && autoLoadMedia && (
|
||||||
<Image
|
<Image
|
||||||
image={{ url: metadata.image, pubkey: event.pubkey }}
|
image={{ url: metadata.image, pubkey: event.pubkey }}
|
||||||
className="w-full aspect-video"
|
className="aspect-video w-full"
|
||||||
hideIfError
|
hideIfError
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
@ -54,7 +54,7 @@ export default function LiveEvent({ event, className }: { event: Event; classNam
|
||||||
{liveStatusComponent}
|
{liveStatusComponent}
|
||||||
{summaryComponent}
|
{summaryComponent}
|
||||||
{tagsComponent}
|
{tagsComponent}
|
||||||
<ClientSelect className="w-full mt-2" event={event} />
|
<ClientSelect className="mt-2 w-full" event={event} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
@ -66,18 +66,18 @@ export default function LiveEvent({ event, className }: { event: Event; classNam
|
||||||
{metadata.image && autoLoadMedia && (
|
{metadata.image && autoLoadMedia && (
|
||||||
<Image
|
<Image
|
||||||
image={{ url: metadata.image, pubkey: event.pubkey }}
|
image={{ url: metadata.image, pubkey: event.pubkey }}
|
||||||
className="aspect-[4/3] xl:aspect-video bg-foreground h-44"
|
className="aspect-[4/3] h-44 bg-foreground xl:aspect-video"
|
||||||
hideIfError
|
hideIfError
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="flex-1 w-0 space-y-1">
|
<div className="w-0 flex-1 space-y-1">
|
||||||
{titleComponent}
|
{titleComponent}
|
||||||
{liveStatusComponent}
|
{liveStatusComponent}
|
||||||
{summaryComponent}
|
{summaryComponent}
|
||||||
{tagsComponent}
|
{tagsComponent}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ClientSelect className="w-full mt-2" event={event} />
|
<ClientSelect className="mt-2 w-full" event={event} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ export default function LongFormArticle({
|
||||||
return (
|
return (
|
||||||
<SecondaryPageLink
|
<SecondaryPageLink
|
||||||
to={toNote(href)}
|
to={toNote(href)}
|
||||||
className="break-words underline text-foreground"
|
className="break-words text-foreground underline"
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</SecondaryPageLink>
|
</SecondaryPageLink>
|
||||||
|
|
@ -53,7 +53,7 @@ export default function LongFormArticle({
|
||||||
return (
|
return (
|
||||||
<SecondaryPageLink
|
<SecondaryPageLink
|
||||||
to={toProfile(href)}
|
to={toProfile(href)}
|
||||||
className="break-words underline text-foreground"
|
className="break-words text-foreground underline"
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</SecondaryPageLink>
|
</SecondaryPageLink>
|
||||||
|
|
@ -65,7 +65,7 @@ export default function LongFormArticle({
|
||||||
href={href}
|
href={href}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
className="break-words inline-flex items-baseline gap-1"
|
className="inline-flex items-baseline gap-1 break-words"
|
||||||
>
|
>
|
||||||
{children} <ExternalLink className="size-3" />
|
{children} <ExternalLink className="size-3" />
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -73,11 +73,11 @@ export default function LongFormArticle({
|
||||||
},
|
},
|
||||||
p: (props) => <p {...props} className="break-words" />,
|
p: (props) => <p {...props} className="break-words" />,
|
||||||
div: (props) => <div {...props} className="break-words" />,
|
div: (props) => <div {...props} className="break-words" />,
|
||||||
code: (props) => <code {...props} className="break-words whitespace-pre-wrap" />,
|
code: (props) => <code {...props} className="whitespace-pre-wrap break-words" />,
|
||||||
img: (props) => (
|
img: (props) => (
|
||||||
<ImageWithLightbox
|
<ImageWithLightbox
|
||||||
image={{ url: props.src || '', pubkey: event.pubkey }}
|
image={{ url: props.src || '', pubkey: event.pubkey }}
|
||||||
className="max-h-[80vh] sm:max-h-[50vh] object-contain my-0"
|
className="my-0 max-h-[80vh] object-contain sm:max-h-[50vh]"
|
||||||
classNames={{
|
classNames={{
|
||||||
wrapper: 'w-fit max-w-full'
|
wrapper: 'w-fit max-w-full'
|
||||||
}}
|
}}
|
||||||
|
|
@ -91,7 +91,7 @@ export default function LongFormArticle({
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
ref={contentRef}
|
ref={contentRef}
|
||||||
className={`prose prose-zinc max-w-none dark:prose-invert break-words overflow-wrap-anywhere ${className || ''}`}
|
className={`overflow-wrap-anywhere prose prose-zinc max-w-none break-words dark:prose-invert ${className || ''}`}
|
||||||
>
|
>
|
||||||
<h1 className="break-words">{metadata.title}</h1>
|
<h1 className="break-words">{metadata.title}</h1>
|
||||||
{metadata.summary && (
|
{metadata.summary && (
|
||||||
|
|
@ -102,7 +102,7 @@ export default function LongFormArticle({
|
||||||
{metadata.image && (
|
{metadata.image && (
|
||||||
<ImageWithLightbox
|
<ImageWithLightbox
|
||||||
image={{ url: metadata.image, pubkey: event.pubkey }}
|
image={{ url: metadata.image, pubkey: event.pubkey }}
|
||||||
className="w-full aspect-[3/1] object-cover my-0"
|
className="my-0 aspect-[3/1] w-full object-cover"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Markdown
|
<Markdown
|
||||||
|
|
@ -118,12 +118,12 @@ export default function LongFormArticle({
|
||||||
{event.content}
|
{event.content}
|
||||||
</Markdown>
|
</Markdown>
|
||||||
{metadata.tags.length > 0 && (
|
{metadata.tags.length > 0 && (
|
||||||
<div className="flex gap-2 flex-wrap pb-2">
|
<div className="flex flex-wrap gap-2 pb-2">
|
||||||
{metadata.tags.map((tag) => (
|
{metadata.tags.map((tag) => (
|
||||||
<div
|
<div
|
||||||
key={tag}
|
key={tag}
|
||||||
title={tag}
|
title={tag}
|
||||||
className="flex items-center rounded-full px-3 bg-muted text-muted-foreground max-w-44 cursor-pointer hover:bg-accent hover:text-accent-foreground"
|
className="flex max-w-44 cursor-pointer items-center rounded-full bg-muted px-3 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
push(toNoteList({ hashtag: tag, kinds: [kinds.LongFormArticle] }))
|
push(toNoteList({ hashtag: tag, kinds: [kinds.LongFormArticle] }))
|
||||||
|
|
|
||||||
|
|
@ -19,14 +19,14 @@ export default function LongFormArticlePreview({
|
||||||
const { autoLoadMedia } = useContentPolicy()
|
const { autoLoadMedia } = useContentPolicy()
|
||||||
const metadata = useMemo(() => getLongFormArticleMetadataFromEvent(event), [event])
|
const metadata = useMemo(() => getLongFormArticleMetadataFromEvent(event), [event])
|
||||||
|
|
||||||
const titleComponent = <div className="text-xl font-semibold line-clamp-2">{metadata.title}</div>
|
const titleComponent = <div className="line-clamp-2 text-xl font-semibold">{metadata.title}</div>
|
||||||
|
|
||||||
const tagsComponent = metadata.tags.length > 0 && (
|
const tagsComponent = metadata.tags.length > 0 && (
|
||||||
<div className="flex gap-1 flex-wrap">
|
<div className="flex flex-wrap gap-1">
|
||||||
{metadata.tags.map((tag) => (
|
{metadata.tags.map((tag) => (
|
||||||
<div
|
<div
|
||||||
key={tag}
|
key={tag}
|
||||||
className="flex items-center rounded-full text-xs px-2.5 py-0.5 bg-muted text-muted-foreground max-w-32 cursor-pointer hover:bg-accent hover:text-accent-foreground"
|
className="flex max-w-32 cursor-pointer items-center rounded-full bg-muted px-2.5 py-0.5 text-xs text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
push(toNoteList({ hashtag: tag, kinds: [kinds.LongFormArticle] }))
|
push(toNoteList({ hashtag: tag, kinds: [kinds.LongFormArticle] }))
|
||||||
|
|
@ -39,7 +39,7 @@ export default function LongFormArticlePreview({
|
||||||
)
|
)
|
||||||
|
|
||||||
const summaryComponent = metadata.summary && (
|
const summaryComponent = metadata.summary && (
|
||||||
<div className="text-sm text-muted-foreground line-clamp-4">{metadata.summary}</div>
|
<div className="line-clamp-4 text-sm text-muted-foreground">{metadata.summary}</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
if (isSmallScreen) {
|
if (isSmallScreen) {
|
||||||
|
|
@ -48,7 +48,7 @@ export default function LongFormArticlePreview({
|
||||||
{metadata.image && autoLoadMedia && (
|
{metadata.image && autoLoadMedia && (
|
||||||
<Image
|
<Image
|
||||||
image={{ url: metadata.image, pubkey: event.pubkey }}
|
image={{ url: metadata.image, pubkey: event.pubkey }}
|
||||||
className="w-full aspect-video"
|
className="aspect-video w-full"
|
||||||
hideIfError
|
hideIfError
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
@ -67,11 +67,11 @@ export default function LongFormArticlePreview({
|
||||||
{metadata.image && autoLoadMedia && (
|
{metadata.image && autoLoadMedia && (
|
||||||
<Image
|
<Image
|
||||||
image={{ url: metadata.image, pubkey: event.pubkey }}
|
image={{ url: metadata.image, pubkey: event.pubkey }}
|
||||||
className="aspect-[4/3] xl:aspect-video object-cover bg-foreground h-44"
|
className="aspect-[4/3] h-44 bg-foreground object-cover xl:aspect-video"
|
||||||
hideIfError
|
hideIfError
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="flex-1 w-0 space-y-1">
|
<div className="w-0 flex-1 space-y-1">
|
||||||
{titleComponent}
|
{titleComponent}
|
||||||
{summaryComponent}
|
{summaryComponent}
|
||||||
{tagsComponent}
|
{tagsComponent}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ export default function MutedNote({ show }: { show: () => void }) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2 items-center text-muted-foreground font-medium my-4">
|
<div className="my-4 flex flex-col items-center gap-2 font-medium text-muted-foreground">
|
||||||
<div>{t('This user has been muted')}</div>
|
<div>{t('This user has been muted')}</div>
|
||||||
<Button
|
<Button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ export default function NsfwNote({ show }: { show: () => void }) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2 items-center text-muted-foreground font-medium my-4">
|
<div className="my-4 flex flex-col items-center gap-2 font-medium text-muted-foreground">
|
||||||
<div>{t('🔞 NSFW 🔞')}</div>
|
<div>{t('🔞 NSFW 🔞')}</div>
|
||||||
<Button
|
<Button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,7 @@ export default function Poll({ event, className }: { event: Event; className?: s
|
||||||
key={option.id}
|
key={option.id}
|
||||||
title={option.label}
|
title={option.label}
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative w-full px-4 py-3 rounded-lg border transition-all flex items-center gap-2 overflow-hidden',
|
'relative flex w-full items-center gap-2 overflow-hidden rounded-lg border px-4 py-3 transition-all',
|
||||||
canVote ? 'cursor-pointer' : 'cursor-not-allowed',
|
canVote ? 'cursor-pointer' : 'cursor-not-allowed',
|
||||||
canVote &&
|
canVote &&
|
||||||
(selectedOptionIds.includes(option.id)
|
(selectedOptionIds.includes(option.id)
|
||||||
|
|
@ -184,7 +184,7 @@ export default function Poll({ event, className }: { event: Event; className?: s
|
||||||
disabled={!canVote}
|
disabled={!canVote}
|
||||||
>
|
>
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="flex items-center gap-2 flex-1 w-0 z-10">
|
<div className="z-10 flex w-0 flex-1 items-center gap-2">
|
||||||
<div className={cn('line-clamp-2 text-left', isMax ? 'font-semibold' : '')}>
|
<div className={cn('line-clamp-2 text-left', isMax ? 'font-semibold' : '')}>
|
||||||
{option.label}
|
{option.label}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -195,7 +195,7 @@ export default function Poll({ event, className }: { event: Event; className?: s
|
||||||
{showResults && (
|
{showResults && (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'text-muted-foreground shrink-0 z-10',
|
'z-10 shrink-0 text-muted-foreground',
|
||||||
isMax ? 'font-semibold text-foreground' : ''
|
isMax ? 'font-semibold text-foreground' : ''
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
@ -217,13 +217,13 @@ export default function Poll({ event, className }: { event: Event; className?: s
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Results Summary */}
|
{/* Results Summary */}
|
||||||
<div className="flex justify-between items-center text-sm text-muted-foreground">
|
<div className="flex items-center justify-between text-sm text-muted-foreground">
|
||||||
<div>{t('{{number}} votes', { number: pollResults?.totalVotes ?? 0 })}</div>
|
<div>{t('{{number}} votes', { number: pollResults?.totalVotes ?? 0 })}</div>
|
||||||
|
|
||||||
{isLoadingResults && t('Loading...')}
|
{isLoadingResults && t('Loading...')}
|
||||||
{!isLoadingResults && showResults && (
|
{!isLoadingResults && showResults && (
|
||||||
<div
|
<div
|
||||||
className="hover:underline cursor-pointer"
|
className="cursor-pointer hover:underline"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
fetchResults()
|
fetchResults()
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ export default function RelayReview({ event, className }: { event: Event; classN
|
||||||
<Stars stars={stars} />
|
<Stars stars={stars} />
|
||||||
<span className="text-sm text-muted-foreground">→</span>
|
<span className="text-sm text-muted-foreground">→</span>
|
||||||
<div
|
<div
|
||||||
className="text-sm text-muted-foreground hover:text-foreground hover:underline cursor-pointer truncate"
|
className="cursor-pointer truncate text-sm text-muted-foreground hover:text-foreground hover:underline"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
push(toRelay(url))
|
push(toRelay(url))
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ export default function UnknownNote({ event, className }: { event: Event; classN
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex flex-col gap-2 items-center text-muted-foreground font-medium my-4',
|
'my-4 flex flex-col items-center gap-2 font-medium text-muted-foreground',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -122,14 +122,14 @@ export default function Note({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<div className="flex justify-between items-start gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<div className="flex items-center space-x-2 flex-1">
|
<div className="flex flex-1 items-center space-x-2">
|
||||||
<UserAvatar userId={event.pubkey} size={size === 'small' ? 'medium' : 'normal'} />
|
<UserAvatar userId={event.pubkey} size={size === 'small' ? 'medium' : 'normal'} />
|
||||||
<div className="flex-1 w-0">
|
<div className="w-0 flex-1">
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex items-center gap-2">
|
||||||
<Username
|
<Username
|
||||||
userId={event.pubkey}
|
userId={event.pubkey}
|
||||||
className={`font-semibold flex truncate ${size === 'small' ? 'text-sm' : ''}`}
|
className={`flex truncate font-semibold ${size === 'small' ? 'text-sm' : ''}`}
|
||||||
skeletonClassName={size === 'small' ? 'h-3' : 'h-4'}
|
skeletonClassName={size === 'small' ? 'h-3' : 'h-4'}
|
||||||
/>
|
/>
|
||||||
<FollowingBadge pubkey={event.pubkey} />
|
<FollowingBadge pubkey={event.pubkey} />
|
||||||
|
|
@ -149,7 +149,7 @@ export default function Note({
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<TranslateButton event={event} className={size === 'normal' ? '' : 'pr-0'} />
|
<TranslateButton event={event} className={size === 'normal' ? '' : 'pr-0'} />
|
||||||
{size === 'normal' && (
|
{size === 'normal' && (
|
||||||
<NoteOptions event={event} className="py-1 shrink-0 [&_svg]:size-5" />
|
<NoteOptions event={event} className="shrink-0 py-1 [&_svg]:size-5" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ export default function MainNoteCard({
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'clickable transition-all duration-200',
|
'clickable transition-all duration-200',
|
||||||
embedded ? 'p-3 sm:p-4 border rounded-xl bg-card' : 'py-3 hover:bg-accent/30'
|
embedded ? 'rounded-xl border bg-card p-3 sm:p-4' : 'py-3 hover:bg-accent/30'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Collapsible alwaysExpand={embedded}>
|
<Collapsible alwaysExpand={embedded}>
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ export default function PinnedButton({ event }: { event: NostrEvent }) {
|
||||||
|
|
||||||
if (event.pubkey !== pubkey) {
|
if (event.pubkey !== pubkey) {
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-1 text-sm items-center text-primary mb-1 px-4 py-0 h-fit">
|
<div className="mb-1 flex h-fit items-center gap-1 px-4 py-0 text-sm text-primary">
|
||||||
<Pin size={16} className="shrink-0" />
|
<Pin size={16} className="shrink-0" />
|
||||||
{t('Pinned')}
|
{t('Pinned')}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -24,7 +24,7 @@ export default function PinnedButton({ event }: { event: NostrEvent }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
className="flex gap-1 text-sm text-primary items-center mb-1 px-4 py-0.5 h-fit"
|
className="mb-1 flex h-fit items-center gap-1 px-4 py-0.5 text-sm text-primary"
|
||||||
variant="link"
|
variant="link"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
@ -36,7 +36,7 @@ export default function PinnedButton({ event }: { event: NostrEvent }) {
|
||||||
onMouseLeave={() => setHovered(false)}
|
onMouseLeave={() => setHovered(false)}
|
||||||
>
|
>
|
||||||
{unpinning ? (
|
{unpinning ? (
|
||||||
<Loader size={16} className="animate-spin shrink-0" />
|
<Loader size={16} className="shrink-0 animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
<Pin size={16} className="shrink-0" />
|
<Pin size={16} className="shrink-0" />
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -22,19 +22,19 @@ export default function RepostDescription({
|
||||||
if (!reposters?.length) return null
|
if (!reposters?.length) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('flex gap-1 text-sm items-center text-muted-foreground mb-1', className)}>
|
<div className={cn('mb-1 flex items-center gap-1 text-sm text-muted-foreground', className)}>
|
||||||
<Repeat2 size={16} className="shrink-0" />
|
<Repeat2 size={16} className="shrink-0" />
|
||||||
<Username
|
<Username
|
||||||
key={reposters[0]}
|
key={reposters[0]}
|
||||||
userId={reposters[0]}
|
userId={reposters[0]}
|
||||||
className={cn('font-semibold truncate', reposters.length > 1 && 'after:content-[","]')}
|
className={cn('truncate font-semibold', reposters.length > 1 && 'after:content-[","]')}
|
||||||
skeletonClassName="h-3"
|
skeletonClassName="h-3"
|
||||||
/>
|
/>
|
||||||
{reposters.length > 1 && (
|
{reposters.length > 1 && (
|
||||||
<Username
|
<Username
|
||||||
key={reposters[1]}
|
key={reposters[1]}
|
||||||
userId={reposters[1]}
|
userId={reposters[1]}
|
||||||
className={cn('font-semibold truncate', reposters.length === 3 && 'after:content-[","]')}
|
className={cn('truncate font-semibold', reposters.length === 3 && 'after:content-[","]')}
|
||||||
skeletonClassName="h-3"
|
skeletonClassName="h-3"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
@ -44,7 +44,7 @@ export default function RepostDescription({
|
||||||
<Username
|
<Username
|
||||||
key={reposters[2]}
|
key={reposters[2]}
|
||||||
userId={reposters[2]}
|
userId={reposters[2]}
|
||||||
className={cn('font-semibold truncate')}
|
className={cn('truncate font-semibold')}
|
||||||
skeletonClassName="h-3"
|
skeletonClassName="h-3"
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
@ -63,7 +63,7 @@ function AndXOthers({ reposters }: { reposters: string[] }) {
|
||||||
{t('and {{x}} others', { x: reposters.length })}
|
{t('and {{x}} others', { x: reposters.length })}
|
||||||
</span>
|
</span>
|
||||||
</HoverCardTrigger>
|
</HoverCardTrigger>
|
||||||
<HoverCardContent className="w-fit max-w-60 flex flex-wrap p-2">
|
<HoverCardContent className="flex w-fit max-w-60 flex-wrap p-2">
|
||||||
{reposters.map((pubkey) => (
|
{reposters.map((pubkey) => (
|
||||||
<div key={pubkey} className="p-2">
|
<div key={pubkey} className="p-2">
|
||||||
<UserAvatar key={pubkey} userId={pubkey} size="small" />
|
<UserAvatar key={pubkey} userId={pubkey} size="small" />
|
||||||
|
|
|
||||||
|
|
@ -56,8 +56,8 @@ export function NoteCardLoadingSkeleton({ className }: { className?: string }) {
|
||||||
return (
|
return (
|
||||||
<div className={cn('px-4 py-3', className)}>
|
<div className={cn('px-4 py-3', className)}>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Skeleton className="w-10 h-10 rounded-full" />
|
<Skeleton className="h-10 w-10 rounded-full" />
|
||||||
<div className={`flex-1 w-0`}>
|
<div className={`w-0 flex-1`}>
|
||||||
<div className="py-1">
|
<div className="py-1">
|
||||||
<Skeleton className="h-4 w-16" />
|
<Skeleton className="h-4 w-16" />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -68,10 +68,10 @@ export function NoteCardLoadingSkeleton({ className }: { className?: string }) {
|
||||||
</div>
|
</div>
|
||||||
<div className="pt-2">
|
<div className="pt-2">
|
||||||
<div className="my-1">
|
<div className="my-1">
|
||||||
<Skeleton className="w-full h-4 my-1 mt-2" />
|
<Skeleton className="my-1 mt-2 h-4 w-full" />
|
||||||
</div>
|
</div>
|
||||||
<div className="my-1">
|
<div className="my-1">
|
||||||
<Skeleton className="w-2/3 h-4 my-1" />
|
<Skeleton className="my-1 h-4 w-2/3" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -39,13 +39,13 @@ export function Tabs({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-fit">
|
<div className="w-fit">
|
||||||
<div className="flex relative">
|
<div className="relative flex">
|
||||||
{TABS.map((tab, index) => (
|
{TABS.map((tab, index) => (
|
||||||
<div
|
<div
|
||||||
key={tab.value}
|
key={tab.value}
|
||||||
ref={(el) => (tabRefs.current[index] = el)}
|
ref={(el) => (tabRefs.current[index] = el)}
|
||||||
className={cn(
|
className={cn(
|
||||||
`text-center px-4 py-2 font-semibold clickable cursor-pointer rounded-lg`,
|
`clickable cursor-pointer rounded-lg px-4 py-2 text-center font-semibold`,
|
||||||
selectedTab === tab.value ? '' : 'text-muted-foreground'
|
selectedTab === tab.value ? '' : 'text-muted-foreground'
|
||||||
)}
|
)}
|
||||||
onClick={() => onTabChange(tab.value)}
|
onClick={() => onTabChange(tab.value)}
|
||||||
|
|
@ -54,7 +54,7 @@ export function Tabs({
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<div
|
<div
|
||||||
className="absolute bottom-0 h-1 bg-primary rounded-full transition-all duration-500"
|
className="absolute bottom-0 h-1 rounded-full bg-primary transition-all duration-500"
|
||||||
style={{
|
style={{
|
||||||
width: `${indicatorStyle.width}px`,
|
width: `${indicatorStyle.width}px`,
|
||||||
left: `${indicatorStyle.left}px`
|
left: `${indicatorStyle.left}px`
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,9 @@ export default function NoteInteractions({ event }: { event: Event }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<ScrollArea className="flex-1 w-0">
|
<ScrollArea className="w-0 flex-1">
|
||||||
<Tabs selectedTab={type} onTabChange={setType} />
|
<Tabs selectedTab={type} onTabChange={setType} />
|
||||||
<ScrollBar orientation="horizontal" className="opacity-0 pointer-events-none" />
|
<ScrollBar orientation="horizontal" className="pointer-events-none opacity-0" />
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
<Separator orientation="vertical" className="h-6" />
|
<Separator orientation="vertical" className="h-6" />
|
||||||
<TrustScoreFilter filterId={SPECIAL_TRUST_SCORE_FILTER_ID.INTERACTIONS} />
|
<TrustScoreFilter filterId={SPECIAL_TRUST_SCORE_FILTER_ID.INTERACTIONS} />
|
||||||
|
|
|
||||||
|
|
@ -492,12 +492,12 @@ const NoteList = forwardRef<
|
||||||
{shouldShowLoadingIndicator || filtering || initialLoading ? (
|
{shouldShowLoadingIndicator || filtering || initialLoading ? (
|
||||||
<NoteCardLoadingSkeleton />
|
<NoteCardLoadingSkeleton />
|
||||||
) : events.length ? (
|
) : events.length ? (
|
||||||
<div className="text-center text-sm text-muted-foreground mt-2">{t('no more notes')}</div>
|
<div className="mt-2 text-center text-sm text-muted-foreground">{t('no more notes')}</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col items-center justify-center w-full mt-8 gap-4">
|
<div className="mt-8 flex w-full flex-col items-center justify-center gap-4">
|
||||||
<div className="text-center text-muted-foreground">
|
<div className="text-center text-muted-foreground">
|
||||||
<div className="text-lg font-medium">{t('No notes found')}</div>
|
<div className="text-lg font-medium">{t('No notes found')}</div>
|
||||||
<div className="text-sm mt-1">{t('Try again later or check your connection')}</div>
|
<div className="mt-1 text-sm">{t('Try again later or check your connection')}</div>
|
||||||
</div>
|
</div>
|
||||||
<Button size="lg" onClick={() => setRefreshCount((count) => count + 1)}>
|
<Button size="lg" onClick={() => setRefreshCount((count) => count + 1)}>
|
||||||
{t('Reload')}
|
{t('Reload')}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ export function MobileMenu({
|
||||||
<Button
|
<Button
|
||||||
key={index}
|
key={index}
|
||||||
onClick={action.onClick}
|
onClick={action.onClick}
|
||||||
className={`w-full p-6 justify-start text-lg gap-4 [&_svg]:size-5 ${action.className || ''}`}
|
className={`w-full justify-start gap-4 p-6 text-lg [&_svg]:size-5 ${action.className || ''}`}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
<Icon />
|
<Icon />
|
||||||
|
|
@ -52,18 +52,18 @@ export function MobileMenu({
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
onClick={goBackToMainMenu}
|
onClick={goBackToMainMenu}
|
||||||
className="w-full p-6 justify-start text-lg gap-4 [&_svg]:size-5 mb-2"
|
className="mb-2 w-full justify-start gap-4 p-6 text-lg [&_svg]:size-5"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
<ArrowLeft />
|
<ArrowLeft />
|
||||||
{subMenuTitle}
|
{subMenuTitle}
|
||||||
</Button>
|
</Button>
|
||||||
<div className="border-t border-border mb-2" />
|
<div className="mb-2 border-t border-border" />
|
||||||
{activeSubMenu.map((subAction, index) => (
|
{activeSubMenu.map((subAction, index) => (
|
||||||
<Button
|
<Button
|
||||||
key={index}
|
key={index}
|
||||||
onClick={subAction.onClick}
|
onClick={subAction.onClick}
|
||||||
className={`w-full p-6 justify-start text-lg gap-4 ${subAction.className || ''}`}
|
className={`w-full justify-start gap-4 p-6 text-lg ${subAction.className || ''}`}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
{subAction.label}
|
{subAction.label}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ export default function RawEventDialog({
|
||||||
<DialogDescription className="hidden" />
|
<DialogDescription className="hidden" />
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<ScrollArea className="h-full">
|
<ScrollArea className="h-full">
|
||||||
<pre className="text-sm text-muted-foreground select-text">
|
<pre className="select-text text-sm text-muted-foreground">
|
||||||
{JSON.stringify(event, null, 2)}
|
{JSON.stringify(event, null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
<ScrollBar orientation="horizontal" />
|
<ScrollBar orientation="horizontal" />
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue