refactor: remove electron-related code
This commit is contained in:
parent
bed8df06e8
commit
2b1e6fe8f5
200 changed files with 2771 additions and 8432 deletions
|
|
@ -1,8 +1,8 @@
|
|||
import 'yet-another-react-lightbox/styles.css'
|
||||
import './assets/main.css'
|
||||
import './index.css'
|
||||
|
||||
import { Toaster } from '@renderer/components/ui/toaster'
|
||||
import { ThemeProvider } from '@renderer/providers/ThemeProvider'
|
||||
import { Toaster } from '@/components/ui/toaster'
|
||||
import { ThemeProvider } from '@/providers/ThemeProvider'
|
||||
import { PageManager } from './PageManager'
|
||||
import NoteListPage from './pages/primary/NoteListPage'
|
||||
import { FollowListProvider } from './providers/FollowListProvider'
|
||||
|
|
@ -1,11 +1,7 @@
|
|||
import Sidebar from '@renderer/components/Sidebar'
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup
|
||||
} from '@renderer/components/ui/resizable'
|
||||
import { cn } from '@renderer/lib/utils'
|
||||
import HomePage from '@renderer/pages/secondary/HomePage'
|
||||
import Sidebar from '@/components/Sidebar'
|
||||
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable'
|
||||
import { cn } from '@/lib/utils'
|
||||
import HomePage from '@/pages/secondary/HomePage'
|
||||
import { cloneElement, createContext, useContext, useEffect, useState } from 'react'
|
||||
import { useScreenSize } from './providers/ScreenSizeProvider'
|
||||
import { routes } from './routes'
|
||||
|
|
@ -187,7 +183,9 @@ export function SecondaryPageLink({
|
|||
<span
|
||||
className={cn('cursor-pointer', className)}
|
||||
onClick={(e) => {
|
||||
onClick && onClick(e)
|
||||
if (onClick) {
|
||||
onClick(e)
|
||||
}
|
||||
push(to)
|
||||
}}
|
||||
>
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
import { ElectronAPI } from '@electron-toolkit/preload'
|
||||
import { Event } from 'nostr-tools'
|
||||
|
||||
export type TRelayGroup = {
|
||||
groupName: string
|
||||
relayUrls: string[]
|
||||
isActive: boolean
|
||||
}
|
||||
|
||||
export type TConfig = {
|
||||
relayGroups: TRelayGroup[]
|
||||
theme: TThemeSetting
|
||||
}
|
||||
|
||||
export type TThemeSetting = 'light' | 'dark' | 'system'
|
||||
export type TTheme = 'light' | 'dark'
|
||||
|
||||
export type TDraftEvent = Pick<Event, 'content' | 'created_at' | 'kind' | 'tags'>
|
||||
|
||||
export interface ISigner {
|
||||
getPublicKey: () => Promise<string | null>
|
||||
signEvent: (draftEvent: TDraftEvent) => Promise<Event | null>
|
||||
}
|
||||
|
||||
export type TElectronWindow = {
|
||||
electron: ElectronAPI
|
||||
api: {
|
||||
system: {
|
||||
isEncryptionAvailable: () => Promise<boolean>
|
||||
getSelectedStorageBackend: () => Promise<string>
|
||||
}
|
||||
theme: {
|
||||
addChangeListener: (listener: (theme: TTheme) => void) => void
|
||||
removeChangeListener: () => void
|
||||
current: () => Promise<TTheme>
|
||||
}
|
||||
storage: {
|
||||
getItem: (key: string) => Promise<string>
|
||||
setItem: (key: string, value: string) => Promise<void>
|
||||
removeItem: (key: string) => Promise<void>
|
||||
}
|
||||
nostr: {
|
||||
login: (nsec: string) => Promise<{
|
||||
pubkey?: string
|
||||
reason?: string
|
||||
}>
|
||||
logout: () => Promise<void>
|
||||
}
|
||||
}
|
||||
nostr: ISigner
|
||||
}
|
||||
|
||||
export type TAccount = {
|
||||
pubkey: string
|
||||
signerType: 'nsec' | 'browser-nsec' | 'nip-07' | 'bunker'
|
||||
nsec?: string
|
||||
bunker?: string
|
||||
bunkerClientSecretKey?: string
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ import {
|
|||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger
|
||||
} from '@renderer/components/ui/dialog'
|
||||
} from '@/components/ui/dialog'
|
||||
import Username from '../Username'
|
||||
|
||||
export default function AboutInfoDialog({ children }: { children: React.ReactNode }) {
|
||||
|
|
@ -38,17 +38,6 @@ export default function AboutInfoDialog({ children }: { children: React.ReactNod
|
|||
GitHub
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
Desktop app:{' '}
|
||||
<a
|
||||
href="https://github.com/CodyTseng/jumble/releases"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
Download
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
If you like this project, you can buy me a coffee ☕️ <br />
|
||||
<div className="font-semibold">⚡️ codytseng@getalby.com ⚡️</div>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { Button } from '@renderer/components/ui/button'
|
||||
import { useNostr } from '@renderer/providers/NostrProvider'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { LogIn } from 'lucide-react'
|
||||
|
||||
export default function LoginButton({
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
import { Avatar, AvatarFallback, AvatarImage } from '@renderer/components/ui/avatar'
|
||||
import { Button } from '@renderer/components/ui/button'
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger
|
||||
} from '@renderer/components/ui/dropdown-menu'
|
||||
import { useFetchProfile } from '@renderer/hooks'
|
||||
import { toProfile } from '@renderer/lib/link'
|
||||
import { formatPubkey, generateImageByPubkey } from '@renderer/lib/pubkey'
|
||||
import { useSecondaryPage } from '@renderer/PageManager'
|
||||
import { useNostr } from '@renderer/providers/NostrProvider'
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { useFetchProfile } from '@/hooks'
|
||||
import { toProfile } from '@/lib/link'
|
||||
import { formatPubkey, generateImageByPubkey } from '@/lib/pubkey'
|
||||
import { useSecondaryPage } from '@/PageManager'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function ProfileButton({
|
||||
|
|
@ -69,9 +69,7 @@ export default function ProfileButton({
|
|||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger className="non-draggable" asChild>
|
||||
{triggerComponent}
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuTrigger asChild>{triggerComponent}</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem onClick={() => push(toProfile(pubkey))}>{t('Profile')}</DropdownMenuItem>
|
||||
<DropdownMenuItem className="text-destructive focus:text-destructive" onClick={logout}>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { useNostr } from '@renderer/providers/NostrProvider'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import LoginButton from './LoginButton'
|
||||
import ProfileButton from './ProfileButton'
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { Button } from '@renderer/components/ui/button'
|
||||
import { useSecondaryPage } from '@renderer/PageManager'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useSecondaryPage } from '@/PageManager'
|
||||
import { ChevronLeft } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { isNsfwEvent } from '@renderer/lib/event'
|
||||
import { cn } from '@renderer/lib/utils'
|
||||
import { isNsfwEvent } from '@/lib/event'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { memo } from 'react'
|
||||
import {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { toNoteList } from '@renderer/lib/link'
|
||||
import { SecondaryPageLink } from '@renderer/PageManager'
|
||||
import { toNoteList } from '@/lib/link'
|
||||
import { SecondaryPageLink } from '@/PageManager'
|
||||
import { TEmbeddedRenderer } from './types'
|
||||
|
||||
export function EmbeddedHashtag({ hashtag }: { hashtag: string }) {
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { useFetchEvent } from '@renderer/hooks'
|
||||
import { toNoStrudelArticle, toNoStrudelNote, toNoStrudelStream } from '@renderer/lib/link'
|
||||
import { cn } from '@renderer/lib/utils'
|
||||
import { useFetchEvent } from '@/hooks'
|
||||
import { toNoStrudelArticle, toNoStrudelNote, toNoStrudelStream } from '@/lib/link'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { kinds } from 'nostr-tools'
|
||||
import ShortTextNoteCard from '../NoteCard/ShortTextNoteCard'
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { useSecondaryPage } from '@renderer/PageManager'
|
||||
import { toNoteList } from '@renderer/lib/link'
|
||||
import { useSecondaryPage } from '@/PageManager'
|
||||
import { toNoteList } from '@/lib/link'
|
||||
import { TEmbeddedRenderer } from './types'
|
||||
|
||||
export function EmbeddedWebsocketUrl({ url }: { url: string }) {
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { Button } from '@renderer/components/ui/button'
|
||||
import { useToast } from '@renderer/hooks'
|
||||
import { useFollowList } from '@renderer/providers/FollowListProvider'
|
||||
import { useNostr } from '@renderer/providers/NostrProvider'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useToast } from '@/hooks'
|
||||
import { useFollowList } from '@/providers/FollowListProvider'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { Loader } from 'lucide-react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { Image } from '@nextui-org/image'
|
||||
import { ScrollArea, ScrollBar } from '@renderer/components/ui/scroll-area'
|
||||
import { cn } from '@renderer/lib/utils'
|
||||
import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useState } from 'react'
|
||||
import Lightbox from 'yet-another-react-lightbox'
|
||||
import Zoom from 'yet-another-react-lightbox/plugins/zoom'
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { Button } from '@renderer/components/ui/button'
|
||||
import { Input } from '@renderer/components/ui/input'
|
||||
import { useNostr } from '@renderer/providers/NostrProvider'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { Loader } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
import { Button } from '@renderer/components/ui/button'
|
||||
import { Input } from '@renderer/components/ui/input'
|
||||
import { IS_ELECTRON, isElectron } from '@renderer/lib/env'
|
||||
import { useNostr } from '@renderer/providers/NostrProvider'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function PrivateKeyLogin({ onLoginSuccess }: { onLoginSuccess: () => void }) {
|
||||
|
|
@ -10,17 +9,6 @@ export default function PrivateKeyLogin({ onLoginSuccess }: { onLoginSuccess: ()
|
|||
const { nsecLogin } = useNostr()
|
||||
const [nsec, setNsec] = useState('')
|
||||
const [errMsg, setErrMsg] = useState<string | null>(null)
|
||||
const [storageBackend, setStorageBackend] = useState('unknown')
|
||||
|
||||
useEffect(() => {
|
||||
const init = async () => {
|
||||
if (!isElectron(window)) return
|
||||
|
||||
const backend = await window.api.system.getSelectedStorageBackend()
|
||||
setStorageBackend(backend)
|
||||
}
|
||||
init()
|
||||
}, [])
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setNsec(e.target.value)
|
||||
|
|
@ -40,15 +28,9 @@ export default function PrivateKeyLogin({ onLoginSuccess }: { onLoginSuccess: ()
|
|||
return (
|
||||
<>
|
||||
<div className="text-orange-400">
|
||||
{!IS_ELECTRON
|
||||
? t(
|
||||
'Using private key login is insecure. It is recommended to use a browser extension for login, such as alby, nostr-keyx or nos2x.'
|
||||
)
|
||||
: ['unknown', 'basic_text'].includes(storageBackend)
|
||||
? t('There are no secret keys stored on this device. Your nsec will be unprotected.')
|
||||
: t('Your nsec will be encrypted using the {{backend}}.', {
|
||||
backend: storageBackend
|
||||
})}
|
||||
{t(
|
||||
'Using private key login is insecure. It is recommended to use a browser extension for login, such as alby, nostr-keyx or nos2x.'
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Input
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
import { Button } from '@renderer/components/ui/button'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle
|
||||
} from '@renderer/components/ui/dialog'
|
||||
import { IS_ELECTRON } from '@renderer/lib/env'
|
||||
import { useNostr } from '@renderer/providers/NostrProvider'
|
||||
} from '@/components/ui/dialog'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { ArrowLeft } from 'lucide-react'
|
||||
import { Dispatch, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
|
@ -54,7 +53,7 @@ export default function LoginDialog({
|
|||
</>
|
||||
) : (
|
||||
<>
|
||||
{!IS_ELECTRON && !!window.nostr && (
|
||||
{!!window.nostr && (
|
||||
<Button onClick={() => nip07Login().then(() => setOpen(false))} className="w-full">
|
||||
{t('Login with Browser Extension')}
|
||||
</Button>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { useFetchNip05 } from '@renderer/hooks/useFetchNip05'
|
||||
import { useFetchNip05 } from '@/hooks/useFetchNip05'
|
||||
import { BadgeAlert, BadgeCheck } from 'lucide-react'
|
||||
|
||||
export default function Nip05({ nip05, pubkey }: { nip05?: string; pubkey: string }) {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { useSecondaryPage } from '@renderer/PageManager'
|
||||
import { toNote } from '@renderer/lib/link'
|
||||
import { useSecondaryPage } from '@/PageManager'
|
||||
import { toNote } from '@/lib/link'
|
||||
import { Event } from 'nostr-tools'
|
||||
import Content from '../Content'
|
||||
import { FormattedTimestamp } from '../FormattedTimestamp'
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import client from '@renderer/services/client.service'
|
||||
import client from '@/services/client.service'
|
||||
import { Event, kinds, verifyEvent } from 'nostr-tools'
|
||||
import { useMemo } from 'react'
|
||||
import ShortTextNoteCard from './ShortTextNoteCard'
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import { useFetchEvent } from '@renderer/hooks'
|
||||
import { getParentEventId, getRootEventId } from '@renderer/lib/event'
|
||||
import { toNote } from '@renderer/lib/link'
|
||||
import { cn } from '@renderer/lib/utils'
|
||||
import { useSecondaryPage } from '@renderer/PageManager'
|
||||
import { useFetchEvent } from '@/hooks'
|
||||
import { getParentEventId, getRootEventId } from '@/lib/event'
|
||||
import { toNote } from '@/lib/link'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useSecondaryPage } from '@/PageManager'
|
||||
import { Repeat2 } from 'lucide-react'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import { Event } from 'nostr-tools'
|
||||
import { kinds } from 'nostr-tools'
|
||||
import { Event, kinds } from 'nostr-tools'
|
||||
import RepostNoteCard from './RepostNoteCard'
|
||||
import ShortTextNoteCard from './ShortTextNoteCard'
|
||||
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import { Button } from '@renderer/components/ui/button'
|
||||
import { Switch } from '@renderer/components/ui/switch'
|
||||
import { useFetchRelayInfos } from '@renderer/hooks'
|
||||
import { isReplyNoteEvent } from '@renderer/lib/event'
|
||||
import { cn } from '@renderer/lib/utils'
|
||||
import { useNostr } from '@renderer/providers/NostrProvider'
|
||||
import { useScreenSize } from '@renderer/providers/ScreenSizeProvider'
|
||||
import client from '@renderer/services/client.service'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { useFetchRelayInfos } from '@/hooks'
|
||||
import { isReplyNoteEvent } from '@/lib/event'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||
import client from '@/services/client.service'
|
||||
import dayjs from 'dayjs'
|
||||
import { Event, Filter, kinds } from 'nostr-tools'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
|
|
@ -34,7 +34,6 @@ export default function NoteList({
|
|||
const [hasMore, setHasMore] = useState<boolean>(true)
|
||||
const [initialized, setInitialized] = useState(false)
|
||||
const [displayReplies, setDisplayReplies] = useState(false)
|
||||
const observer = useRef<IntersectionObserver | null>(null)
|
||||
const bottomRef = useRef<HTMLDivElement | null>(null)
|
||||
const noteFilter = useMemo(() => {
|
||||
return {
|
||||
|
|
@ -109,19 +108,21 @@ export default function NoteList({
|
|||
threshold: 1
|
||||
}
|
||||
|
||||
observer.current = new IntersectionObserver((entries) => {
|
||||
const observerInstance = new IntersectionObserver((entries) => {
|
||||
if (entries[0].isIntersecting && hasMore) {
|
||||
loadMore()
|
||||
}
|
||||
}, options)
|
||||
|
||||
if (bottomRef.current) {
|
||||
observer.current.observe(bottomRef.current)
|
||||
const currentBottomRef = bottomRef.current
|
||||
|
||||
if (currentBottomRef) {
|
||||
observerInstance.observe(currentBottomRef)
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (observer.current && bottomRef.current) {
|
||||
observer.current.unobserve(bottomRef.current)
|
||||
if (observerInstance && currentBottomRef) {
|
||||
observerInstance.unobserve(currentBottomRef)
|
||||
}
|
||||
}
|
||||
}, [initialized, hasMore, events, timelineKey])
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import { createReactionDraftEvent } from '@renderer/lib/draft-event'
|
||||
import { cn } from '@renderer/lib/utils'
|
||||
import { useNostr } from '@renderer/providers/NostrProvider'
|
||||
import { useNoteStats } from '@renderer/providers/NoteStatsProvider'
|
||||
import client from '@renderer/services/client.service'
|
||||
import { createReactionDraftEvent } from '@/lib/draft-event'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { useNoteStats } from '@/providers/NoteStatsProvider'
|
||||
import client from '@/services/client.service'
|
||||
import { Heart, Loader } from 'lucide-react'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
|
|
@ -37,7 +37,7 @@ export default function LikeButton({
|
|||
if (hasLiked === undefined) {
|
||||
fetchNoteLikedStatus(event)
|
||||
}
|
||||
}, [])
|
||||
}, [canFetch, event])
|
||||
|
||||
const like = async (e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
|
|
@ -4,8 +4,8 @@ import {
|
|||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle
|
||||
} from '@renderer/components/ui/dialog'
|
||||
import { ScrollArea, ScrollBar } from '@renderer/components/ui/scroll-area'
|
||||
} from '@/components/ui/dialog'
|
||||
import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area'
|
||||
import { Event } from 'nostr-tools'
|
||||
|
||||
export default function RawEventDialog({
|
||||
|
|
@ -25,9 +25,7 @@ export default function RawEventDialog({
|
|||
<DialogDescription className="hidden" />
|
||||
</DialogHeader>
|
||||
<ScrollArea className="h-full">
|
||||
<pre className="text-sm overflow-x-auto text-muted-foreground">
|
||||
{JSON.stringify(event, null, 2)}
|
||||
</pre>
|
||||
<pre className="text-sm text-muted-foreground">{JSON.stringify(event, null, 2)}</pre>
|
||||
<ScrollBar orientation="horizontal" />
|
||||
</ScrollArea>
|
||||
</DialogContent>
|
||||
|
|
@ -3,8 +3,8 @@ import {
|
|||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger
|
||||
} from '@renderer/components/ui/dropdown-menu'
|
||||
import { getSharableEventId } from '@renderer/lib/event'
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { getSharableEventId } from '@/lib/event'
|
||||
import { Code, Copy, Ellipsis } from 'lucide-react'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { useState } from 'react'
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { useNostr } from '@renderer/providers/NostrProvider'
|
||||
import { useNoteStats } from '@renderer/providers/NoteStatsProvider'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { useNoteStats } from '@/providers/NoteStatsProvider'
|
||||
import { MessageCircle } from 'lucide-react'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { useMemo, useState } from 'react'
|
||||
|
|
@ -3,13 +3,13 @@ import {
|
|||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger
|
||||
} from '@renderer/components/ui/dropdown-menu'
|
||||
import { createRepostDraftEvent } from '@renderer/lib/draft-event'
|
||||
import { getSharableEventId } from '@renderer/lib/event'
|
||||
import { cn } from '@renderer/lib/utils'
|
||||
import { useNostr } from '@renderer/providers/NostrProvider'
|
||||
import { useNoteStats } from '@renderer/providers/NoteStatsProvider'
|
||||
import client from '@renderer/services/client.service'
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { createRepostDraftEvent } from '@/lib/draft-event'
|
||||
import { getSharableEventId } from '@/lib/event'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { useNoteStats } from '@/providers/NoteStatsProvider'
|
||||
import client from '@/services/client.service'
|
||||
import { Loader, PencilLine, Repeat } from 'lucide-react'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
|
|
@ -45,7 +45,7 @@ export default function RepostButton({
|
|||
if (hasReposted === undefined) {
|
||||
fetchNoteRepostedStatus(event)
|
||||
}
|
||||
}, [])
|
||||
}, [canFetch, event])
|
||||
|
||||
const repost = async (e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { cn } from '@renderer/lib/utils'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Event } from 'nostr-tools'
|
||||
import LikeButton from './LikeButton'
|
||||
import NoteOptions from './NoteOptions'
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { Button } from '@renderer/components/ui/button'
|
||||
import { toNotifications } from '@renderer/lib/link'
|
||||
import { useSecondaryPage } from '@renderer/PageManager'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { toNotifications } from '@/lib/link'
|
||||
import { useSecondaryPage } from '@/PageManager'
|
||||
import { Bell } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
import { useFetchEvent } from '@renderer/hooks'
|
||||
import { toNote } from '@renderer/lib/link'
|
||||
import { tagNameEquals } from '@renderer/lib/tag'
|
||||
import { useSecondaryPage } from '@renderer/PageManager'
|
||||
import { useNostr } from '@renderer/providers/NostrProvider'
|
||||
import client from '@renderer/services/client.service'
|
||||
import { useFetchEvent } from '@/hooks'
|
||||
import { toNote } from '@/lib/link'
|
||||
import { tagNameEquals } from '@/lib/tag'
|
||||
import { useSecondaryPage } from '@/PageManager'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import client from '@/services/client.service'
|
||||
import dayjs from 'dayjs'
|
||||
import { Heart, MessageCircle, Repeat } from 'lucide-react'
|
||||
import { Event, kinds, nip19, validateEvent } from 'nostr-tools'
|
||||
|
|
@ -22,7 +22,6 @@ export default function NotificationList() {
|
|||
const [notifications, setNotifications] = useState<Event[]>([])
|
||||
const [until, setUntil] = useState<number | undefined>(dayjs().unix())
|
||||
const bottomRef = useRef<HTMLDivElement | null>(null)
|
||||
const observer = useRef<IntersectionObserver | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!pubkey) {
|
||||
|
|
@ -74,19 +73,21 @@ export default function NotificationList() {
|
|||
threshold: 1
|
||||
}
|
||||
|
||||
observer.current = new IntersectionObserver((entries) => {
|
||||
const observerInstance = new IntersectionObserver((entries) => {
|
||||
if (entries[0].isIntersecting) {
|
||||
loadMore()
|
||||
}
|
||||
}, options)
|
||||
|
||||
if (bottomRef.current) {
|
||||
observer.current.observe(bottomRef.current)
|
||||
const currentBottomRef = bottomRef.current
|
||||
|
||||
if (currentBottomRef) {
|
||||
observerInstance.observe(currentBottomRef)
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (observer.current && bottomRef.current) {
|
||||
observer.current.unobserve(bottomRef.current)
|
||||
if (observerInstance && currentBottomRef) {
|
||||
observerInstance.unobserve(currentBottomRef)
|
||||
}
|
||||
}
|
||||
}, [until, initialized, timelineKey])
|
||||
|
|
@ -141,7 +142,7 @@ function ReactionNotification({ notification }: { notification: Event }) {
|
|||
return eventId
|
||||
? nip19.neventEncode(author ? { id: eventId, author } : { id: eventId })
|
||||
: undefined
|
||||
}, [notification.id])
|
||||
}, [notification])
|
||||
const { event } = useFetchEvent(bech32Id)
|
||||
if (!event || !bech32Id || event.kind !== kinds.ShortTextNote) return null
|
||||
|
||||
|
|
@ -191,7 +192,7 @@ function RepostNotification({ notification }: { notification: Event }) {
|
|||
} catch {
|
||||
return null
|
||||
}
|
||||
}, [])
|
||||
}, [notification.content])
|
||||
if (!event) return null
|
||||
|
||||
return (
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { cn } from '@renderer/lib/utils'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function NsfwOverlay({ className }: { className?: string }) {
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { cn } from '@/lib/utils'
|
||||
import { Event } from 'nostr-tools'
|
||||
import UserAvatar from '../UserAvatar'
|
||||
import { cn } from '@renderer/lib/utils'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import UserAvatar from '../UserAvatar'
|
||||
|
||||
export default function ParentNotePreview({
|
||||
event,
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import PostDialog from '@renderer/components/PostDialog'
|
||||
import { Button } from '@renderer/components/ui/button'
|
||||
import PostDialog from '@/components/PostDialog'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { PencilLine } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { Button } from '@renderer/components/ui/button'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@renderer/components/ui/popover'
|
||||
import { extractMentions } from '@renderer/lib/event'
|
||||
import { useNostr } from '@renderer/providers/NostrProvider'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { extractMentions } from '@/lib/event'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { useEffect, useState } from 'react'
|
||||
import UserAvatar from '../UserAvatar'
|
||||
|
|
@ -23,7 +23,7 @@ export default function Mentions({
|
|||
extractMentions(content, parentEvent).then(({ pubkeys }) =>
|
||||
setPubkeys(pubkeys.filter((p) => p !== pubkey))
|
||||
)
|
||||
}, [content])
|
||||
}, [content, parentEvent, pubkey])
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { Card } from '@renderer/components/ui/card'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import dayjs from 'dayjs'
|
||||
import Content from '../Content'
|
||||
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import { Button } from '@renderer/components/ui/button'
|
||||
import { useToast } from '@renderer/hooks/use-toast'
|
||||
import { useNostr } from '@renderer/providers/NostrProvider'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useToast } from '@/hooks/use-toast'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { ImageUp, LoaderCircle } from 'lucide-react'
|
||||
import { useRef, useState } from 'react'
|
||||
import { z } from 'zod'
|
||||
|
||||
export default function Uploader({
|
||||
setContent
|
||||
|
|
@ -38,7 +39,8 @@ export default function Uploader({
|
|||
}
|
||||
|
||||
const data = await response.json()
|
||||
const imageUrl = data.nip94_event?.tags.find(([tagName]) => tagName === 'url')?.[1]
|
||||
const tags = z.array(z.array(z.string())).parse(data.nip94_event?.tags ?? [])
|
||||
const imageUrl = tags.find(([tagName]) => tagName === 'url')?.[1]
|
||||
if (imageUrl) {
|
||||
setContent((prevContent) => `${prevContent}\n${imageUrl}`)
|
||||
} else {
|
||||
|
|
@ -1,17 +1,17 @@
|
|||
import { Button } from '@renderer/components/ui/button'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle
|
||||
} from '@renderer/components/ui/dialog'
|
||||
import { ScrollArea } from '@renderer/components/ui/scroll-area'
|
||||
import { Textarea } from '@renderer/components/ui/textarea'
|
||||
import { useToast } from '@renderer/hooks/use-toast'
|
||||
import { createShortTextNoteDraftEvent } from '@renderer/lib/draft-event'
|
||||
import { useNostr } from '@renderer/providers/NostrProvider'
|
||||
import client from '@renderer/services/client.service'
|
||||
} from '@/components/ui/dialog'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { useToast } from '@/hooks/use-toast'
|
||||
import { createShortTextNoteDraftEvent } from '@/lib/draft-event'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import client from '@/services/client.service'
|
||||
import { LoaderCircle } from 'lucide-react'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { Dispatch, useState } from 'react'
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { Image } from '@nextui-org/image'
|
||||
import { generateImageByPubkey } from '@renderer/lib/pubkey'
|
||||
import { cn } from '@renderer/lib/utils'
|
||||
import { generateImageByPubkey } from '@/lib/pubkey'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
|
||||
export default function ProfileBanner({
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { Avatar, AvatarFallback, AvatarImage } from '@renderer/components/ui/avatar'
|
||||
import { useFetchProfile } from '@renderer/hooks'
|
||||
import { generateImageByPubkey } from '@renderer/lib/pubkey'
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import { useFetchProfile } from '@/hooks'
|
||||
import { generateImageByPubkey } from '@/lib/pubkey'
|
||||
import { useMemo } from 'react'
|
||||
import FollowButton from '../FollowButton'
|
||||
import Nip05 from '../Nip05'
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { Button } from '@renderer/components/ui/button'
|
||||
import { usePrimaryPage } from '@renderer/PageManager'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { usePrimaryPage } from '@/PageManager'
|
||||
import { RefreshCcw } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
import { Button } from '@renderer/components/ui/button'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger
|
||||
} from '@renderer/components/ui/dropdown-menu'
|
||||
import { Input } from '@renderer/components/ui/input'
|
||||
import { useRelaySettings } from '@renderer/providers/RelaySettingsProvider'
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { useRelaySettings } from '@/providers/RelaySettingsProvider'
|
||||
import { Check, ChevronDown, Circle, CircleCheck, EllipsisVertical } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import RelayUrls from './RelayUrl'
|
||||
|
|
@ -1,19 +1,20 @@
|
|||
import { Button } from '@renderer/components/ui/button'
|
||||
import { Input } from '@renderer/components/ui/input'
|
||||
import { useFetchRelayInfos } from '@renderer/hooks'
|
||||
import { isWebsocketUrl, normalizeUrl } from '@renderer/lib/url'
|
||||
import { useRelaySettings } from '@renderer/providers/RelaySettingsProvider'
|
||||
import client from '@renderer/services/client.service'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { useFetchRelayInfos } from '@/hooks'
|
||||
import { isWebsocketUrl, normalizeUrl } from '@/lib/url'
|
||||
import { useRelaySettings } from '@/providers/RelaySettingsProvider'
|
||||
import client from '@/services/client.service'
|
||||
import { CircleX, SearchCheck } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function RelayUrls({ groupName }: { groupName: string }) {
|
||||
const { t } = useTranslation()
|
||||
const { relayGroups, updateRelayGroupRelayUrls } = useRelaySettings()
|
||||
const rawRelayUrls = relayGroups.find((group) => group.groupName === groupName)?.relayUrls ?? []
|
||||
const isActive = relayGroups.find((group) => group.groupName === groupName)?.isActive ?? false
|
||||
|
||||
const isActive = useMemo(
|
||||
() => relayGroups.find((group) => group.groupName === groupName)?.isActive ?? false,
|
||||
[relayGroups, groupName]
|
||||
)
|
||||
const [newRelayUrl, setNewRelayUrl] = useState('')
|
||||
const [newRelayUrlError, setNewRelayUrlError] = useState<string | null>(null)
|
||||
const [relays, setRelays] = useState<
|
||||
|
|
@ -21,7 +22,11 @@ export default function RelayUrls({ groupName }: { groupName: string }) {
|
|||
url: string
|
||||
isConnected: boolean
|
||||
}[]
|
||||
>(rawRelayUrls.map((url) => ({ url, isConnected: false })))
|
||||
>(
|
||||
relayGroups
|
||||
.find((group) => group.groupName === groupName)
|
||||
?.relayUrls.map((url) => ({ url, isConnected: false })) ?? []
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { Button } from '@renderer/components/ui/button'
|
||||
import { useFetchRelayInfos } from '@renderer/hooks'
|
||||
import { useRelaySettings } from '@renderer/providers/RelaySettingsProvider'
|
||||
import client from '@renderer/services/client.service'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useFetchRelayInfos } from '@/hooks'
|
||||
import { useRelaySettings } from '@/providers/RelaySettingsProvider'
|
||||
import client from '@/services/client.service'
|
||||
import { Save, SearchCheck } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { Button } from '@renderer/components/ui/button'
|
||||
import { Input } from '@renderer/components/ui/input'
|
||||
import { Separator } from '@renderer/components/ui/separator'
|
||||
import { useRelaySettings } from '@renderer/providers/RelaySettingsProvider'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { useRelaySettings } from '@/providers/RelaySettingsProvider'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { RelaySettingsComponentProvider } from './provider'
|
||||
import RelayGroup from './RelayGroup'
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import RelaySettings from '@renderer/components/RelaySettings'
|
||||
import { Button } from '@renderer/components/ui/button'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@renderer/components/ui/popover'
|
||||
import { ScrollArea } from '@renderer/components/ui/scroll-area'
|
||||
import { toRelaySettings } from '@renderer/lib/link'
|
||||
import { SecondaryPageLink } from '@renderer/PageManager'
|
||||
import { useScreenSize } from '@renderer/providers/ScreenSizeProvider'
|
||||
import RelaySettings from '@/components/RelaySettings'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { toRelaySettings } from '@/lib/link'
|
||||
import { SecondaryPageLink } from '@/PageManager'
|
||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||
import { Server } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import { Separator } from '@renderer/components/ui/separator'
|
||||
import { isReplyNoteEvent } from '@renderer/lib/event'
|
||||
import { isReplyETag, isRootETag } from '@renderer/lib/tag'
|
||||
import { cn } from '@renderer/lib/utils'
|
||||
import { useNostr } from '@renderer/providers/NostrProvider'
|
||||
import { useNoteStats } from '@renderer/providers/NoteStatsProvider'
|
||||
import client from '@renderer/services/client.service'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { isReplyNoteEvent } from '@/lib/event'
|
||||
import { isReplyETag, isRootETag } from '@/lib/tag'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { useNoteStats } from '@/providers/NoteStatsProvider'
|
||||
import client from '@/services/client.service'
|
||||
import dayjs from 'dayjs'
|
||||
import { Event as NEvent, kinds } from 'nostr-tools'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
|
@ -43,7 +43,7 @@ export default function ReplyNoteList({ event, className }: { event: NEvent; cla
|
|||
return () => {
|
||||
client.removeEventListener('eventPublished', handleEventPublished)
|
||||
}
|
||||
}, [])
|
||||
}, [event])
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) return
|
||||
|
|
@ -87,7 +87,7 @@ export default function ReplyNoteList({ event, className }: { event: NEvent; cla
|
|||
return () => {
|
||||
promise.then((closer) => closer?.())
|
||||
}
|
||||
}, [])
|
||||
}, [event])
|
||||
|
||||
useEffect(() => {
|
||||
updateNoteReplyCount(event.id, replies.length)
|
||||
|
|
@ -123,7 +123,7 @@ export default function ReplyNoteList({ event, className }: { event: NEvent; cla
|
|||
replyMap[reply.id] = { event: reply, level: level + 1, parent }
|
||||
}
|
||||
setReplyMap(replyMap)
|
||||
}, [replies])
|
||||
}, [replies, event.id, updateNoteReplyCount])
|
||||
|
||||
const loadMore = async () => {
|
||||
if (loading || !until || !timelineKey) return
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { Button } from '@renderer/components/ui/button'
|
||||
import { cn } from '@renderer/lib/utils'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { ChevronUp } from 'lucide-react'
|
||||
|
||||
export default function ScrollToTopButton({
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { Button } from '@renderer/components/ui/button'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Search } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
|
@ -1,17 +1,11 @@
|
|||
import { SecondaryPageLink } from '@renderer/PageManager'
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@renderer/components/ui/avatar'
|
||||
import {
|
||||
CommandDialog,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList
|
||||
} from '@renderer/components/ui/command'
|
||||
import { useSearchProfiles } from '@renderer/hooks'
|
||||
import { isMacOS } from '@renderer/lib/env'
|
||||
import { toNote, toNoteList, toProfile, toProfileList } from '@renderer/lib/link'
|
||||
import { generateImageByPubkey } from '@renderer/lib/pubkey'
|
||||
import { useRelaySettings } from '@renderer/providers/RelaySettingsProvider'
|
||||
import { TProfile } from '@renderer/types'
|
||||
import { SecondaryPageLink } from '@/PageManager'
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import { CommandDialog, CommandInput, CommandItem, CommandList } from '@/components/ui/command'
|
||||
import { useSearchProfiles } from '@/hooks'
|
||||
import { toNote, toNoteList, toProfile, toProfileList } from '@/lib/link'
|
||||
import { generateImageByPubkey } from '@/lib/pubkey'
|
||||
import { useRelaySettings } from '@/providers/RelaySettingsProvider'
|
||||
import { TProfile } from '@/types'
|
||||
import { Hash, Notebook, UserRound } from 'lucide-react'
|
||||
import { nip19 } from 'nostr-tools'
|
||||
import { Dispatch, useEffect, useMemo, useState } from 'react'
|
||||
|
|
@ -68,7 +62,7 @@ export function SearchDialog({ open, setOpen }: { open: boolean; setOpen: Dispat
|
|||
)}
|
||||
</>
|
||||
)
|
||||
}, [input, profiles])
|
||||
}, [input, profiles, setOpen])
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
|
|
@ -81,11 +75,7 @@ export function SearchDialog({ open, setOpen }: { open: boolean; setOpen: Dispat
|
|||
}, [input])
|
||||
|
||||
return (
|
||||
<CommandDialog
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
classNames={{ content: isMacOS() ? 'max-sm:top-9' : 'max-sm:top-0' }}
|
||||
>
|
||||
<CommandDialog open={open} onOpenChange={setOpen} classNames={{ content: 'max-sm:top-0' }}>
|
||||
<CommandInput value={input} onValueChange={setInput} />
|
||||
<CommandList>{list}</CommandList>
|
||||
</CommandDialog>
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import Logo from '@renderer/assets/Logo'
|
||||
import { Button } from '@renderer/components/ui/button'
|
||||
import { IS_ELECTRON } from '@renderer/lib/env'
|
||||
import Logo from '@/assets/Logo'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Info } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import AboutInfoDialog from '../AboutInfoDialog'
|
||||
|
|
@ -15,24 +14,22 @@ export default function PrimaryPageSidebar() {
|
|||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className="w-52 h-full shrink-0 hidden xl:flex flex-col pb-8 pt-10 pl-4 justify-between relative">
|
||||
<div className="draggable absolute top-0 left-0 h-11 w-full" />
|
||||
<div className="absolute top-0 left-0 h-11 w-full" />
|
||||
<div className="space-y-2">
|
||||
<div className="draggable ml-4 mb-8 w-40">
|
||||
<div className="ml-4 mb-8 w-40">
|
||||
<Logo />
|
||||
</div>
|
||||
<PostButton variant="sidebar" />
|
||||
<RelaySettingsButton variant="sidebar" />
|
||||
<NotificationButton variant="sidebar" />
|
||||
<SearchButton variant="sidebar" />
|
||||
{IS_ELECTRON && <RefreshButton variant="sidebar" />}
|
||||
{!IS_ELECTRON && (
|
||||
<AboutInfoDialog>
|
||||
<Button variant="sidebar" size="sidebar">
|
||||
<Info />
|
||||
{t('About')}
|
||||
</Button>
|
||||
</AboutInfoDialog>
|
||||
)}
|
||||
<RefreshButton variant="sidebar" />
|
||||
<AboutInfoDialog>
|
||||
<Button variant="sidebar" size="sidebar">
|
||||
<Info />
|
||||
{t('About')}
|
||||
</Button>
|
||||
</AboutInfoDialog>
|
||||
</div>
|
||||
<AccountButton variant="sidebar" />
|
||||
</div>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { Button } from '@renderer/components/ui/button'
|
||||
import { useTheme } from '@renderer/providers/ThemeProvider'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useTheme } from '@/providers/ThemeProvider'
|
||||
import { Moon, Sun, SunMoon } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
23
src/components/Titlebar/index.tsx
Normal file
23
src/components/Titlebar/index.tsx
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { cn } from '@/lib/utils'
|
||||
|
||||
export function Titlebar({
|
||||
children,
|
||||
className,
|
||||
visible = true
|
||||
}: {
|
||||
children?: React.ReactNode
|
||||
className?: string
|
||||
visible?: boolean
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'absolute top-0 w-full h-9 max-sm:h-11 z-50 bg-background/80 backdrop-blur-md flex items-center font-semibold gap-1 px-2 duration-700 transition-transform',
|
||||
visible ? '' : '-translate-y-full',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import { Avatar, AvatarFallback, AvatarImage } from '@renderer/components/ui/avatar'
|
||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@renderer/components/ui/hover-card'
|
||||
import { Skeleton } from '@renderer/components/ui/skeleton'
|
||||
import { useFetchProfile } from '@renderer/hooks'
|
||||
import { generateImageByPubkey } from '@renderer/lib/pubkey'
|
||||
import { toProfile } from '@renderer/lib/link'
|
||||
import { cn } from '@renderer/lib/utils'
|
||||
import { SecondaryPageLink } from '@renderer/PageManager'
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { useFetchProfile } from '@/hooks'
|
||||
import { generateImageByPubkey } from '@/lib/pubkey'
|
||||
import { toProfile } from '@/lib/link'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { SecondaryPageLink } from '@/PageManager'
|
||||
import ProfileCard from '../ProfileCard'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import FollowButton from '@renderer/components/FollowButton'
|
||||
import Nip05 from '@renderer/components/Nip05'
|
||||
import UserAvatar from '@renderer/components/UserAvatar'
|
||||
import Username from '@renderer/components/Username'
|
||||
import { useFetchProfile } from '@renderer/hooks'
|
||||
import FollowButton from '@/components/FollowButton'
|
||||
import Nip05 from '@/components/Nip05'
|
||||
import UserAvatar from '@/components/UserAvatar'
|
||||
import Username from '@/components/Username'
|
||||
import { useFetchProfile } from '@/hooks'
|
||||
|
||||
export default function UserItem({ pubkey }: { pubkey: string }) {
|
||||
const { profile } = useFetchProfile(pubkey)
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@renderer/components/ui/hover-card'
|
||||
import { Skeleton } from '@renderer/components/ui/skeleton'
|
||||
import { useFetchProfile } from '@renderer/hooks'
|
||||
import { toProfile } from '@renderer/lib/link'
|
||||
import { cn } from '@renderer/lib/utils'
|
||||
import { SecondaryPageLink } from '@renderer/PageManager'
|
||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { useFetchProfile } from '@/hooks'
|
||||
import { toProfile } from '@/lib/link'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { SecondaryPageLink } from '@/PageManager'
|
||||
import ProfileCard from '../ProfileCard'
|
||||
|
||||
export default function Username({
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { cn } from '@renderer/lib/utils'
|
||||
import { cn } from '@/lib/utils'
|
||||
import NsfwOverlay from '../NsfwOverlay'
|
||||
|
||||
export default function VideoPlayer({
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { Image } from '@nextui-org/image'
|
||||
import { useFetchWebMetadata } from '@renderer/hooks/useFetchWebMetadata'
|
||||
import { cn } from '@renderer/lib/utils'
|
||||
import { useFetchWebMetadata } from '@/hooks/useFetchWebMetadata'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
export default function WebPreview({
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import * as React from "react"
|
||||
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
||||
import * as React from 'react'
|
||||
import * as AvatarPrimitive from '@radix-ui/react-avatar'
|
||||
|
||||
import { cn } from "@renderer/lib/utils"
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const Avatar = React.forwardRef<
|
||||
React.ElementRef<typeof AvatarPrimitive.Root>,
|
||||
|
|
@ -9,10 +9,7 @@ const Avatar = React.forwardRef<
|
|||
>(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
|
||||
className
|
||||
)}
|
||||
className={cn('relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
|
|
@ -24,7 +21,7 @@ const AvatarImage = React.forwardRef<
|
|||
>(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Image
|
||||
ref={ref}
|
||||
className={cn("aspect-square h-full w-full", className)}
|
||||
className={cn('aspect-square h-full w-full', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
|
|
@ -37,7 +34,7 @@ const AvatarFallback = React.forwardRef<
|
|||
<AvatarPrimitive.Fallback
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-full w-full items-center justify-center rounded-full bg-muted",
|
||||
'flex h-full w-full items-center justify-center rounded-full bg-muted',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -2,29 +2,30 @@ import * as React from 'react'
|
|||
import { Slot } from '@radix-ui/react-slot'
|
||||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
|
||||
import { cn } from '@renderer/lib/utils'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
||||
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
||||
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
|
||||
secondary: 'bg-secondary text-secondary-foreground hover:bg-muted/80',
|
||||
default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
|
||||
destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
|
||||
outline:
|
||||
'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
|
||||
secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
|
||||
'secondary-2': 'bg-secondary text-secondary-foreground hover:bg-highlight',
|
||||
ghost: 'text-muted-foreground hover:bg-accent hover:text-foreground',
|
||||
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
titlebar: 'non-draggable hover:bg-accent hover:text-accent-foreground',
|
||||
sidebar: 'non-draggable hover:bg-accent hover:text-accent-foreground',
|
||||
'small-screen-titlebar': 'non-draggable hover:bg-accent hover:text-accent-foreground'
|
||||
titlebar: 'hover:bg-accent hover:text-accent-foreground',
|
||||
sidebar: 'hover:bg-accent hover:text-accent-foreground',
|
||||
'small-screen-titlebar': 'hover:bg-accent hover:text-accent-foreground'
|
||||
},
|
||||
size: {
|
||||
default: 'h-8 rounded-lg px-3',
|
||||
sm: 'h-8 rounded-lg px-2',
|
||||
lg: 'h-10 px-4 py-2',
|
||||
icon: 'h-8 w-8 rounded-full',
|
||||
default: 'h-9 px-4 py-2',
|
||||
sm: 'h-8 rounded-md px-3 text-xs',
|
||||
lg: 'h-10 rounded-md px-8',
|
||||
icon: 'h-9 w-9',
|
||||
titlebar: 'h-7 w-7 rounded-full',
|
||||
sidebar: 'w-full flex py-2 px-4 rounded-full justify-start gap-4 text-lg font-semibold',
|
||||
'small-screen-titlebar': 'h-8 w-8 rounded-full'
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import * as React from 'react'
|
||||
|
||||
import { cn } from '@renderer/lib/utils'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
|
|
@ -20,23 +20,22 @@ const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDiv
|
|||
)
|
||||
CardHeader.displayName = 'CardHeader'
|
||||
|
||||
const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
|
||||
const CardTitle = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<h3
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn('text-2xl font-semibold leading-none tracking-tight', className)}
|
||||
className={cn('font-semibold leading-none tracking-tight', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
)
|
||||
CardTitle.displayName = 'CardTitle'
|
||||
|
||||
const CardDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<p ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} />
|
||||
))
|
||||
const CardDescription = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} />
|
||||
)
|
||||
)
|
||||
CardDescription.displayName = 'CardDescription'
|
||||
|
||||
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
|
|
@ -9,9 +9,9 @@ import {
|
|||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle
|
||||
} from '@renderer/components/ui/dialog'
|
||||
import { ScrollArea } from '@renderer/components/ui/scroll-area'
|
||||
import { cn } from '@renderer/lib/utils'
|
||||
} from '@/components/ui/dialog'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const Command = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive>,
|
||||
|
|
@ -65,7 +65,7 @@ const CommandInput = React.forwardRef<
|
|||
<CommandPrimitive.Input
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50 pr-6',
|
||||
'flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50 pr-6',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -130,7 +130,7 @@ const CommandItem = React.forwardRef<
|
|||
<CommandPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-pointer gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
'relative flex cursor-pointer gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -2,7 +2,7 @@ import * as React from 'react'
|
|||
import * as DialogPrimitive from '@radix-ui/react-dialog'
|
||||
import { X } from 'lucide-react'
|
||||
|
||||
import { cn } from '@renderer/lib/utils'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const Dialog = DialogPrimitive.Root
|
||||
|
||||
|
|
@ -94,8 +94,8 @@ export {
|
|||
Dialog,
|
||||
DialogPortal,
|
||||
DialogOverlay,
|
||||
DialogClose,
|
||||
DialogTrigger,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogFooter,
|
||||
|
|
@ -2,7 +2,7 @@ import * as React from 'react'
|
|||
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
|
||||
import { Check, ChevronRight, Circle } from 'lucide-react'
|
||||
|
||||
import { cn } from '@renderer/lib/utils'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const DropdownMenu = DropdownMenuPrimitive.Root
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import * as React from 'react'
|
||||
import * as HoverCardPrimitive from '@radix-ui/react-hover-card'
|
||||
|
||||
import { cn } from '@renderer/lib/utils'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const HoverCard = HoverCardPrimitive.Root
|
||||
|
||||
22
src/components/ui/input.tsx
Normal file
22
src/components/ui/input.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import * as React from 'react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Input.displayName = 'Input'
|
||||
|
||||
export { Input }
|
||||
|
|
@ -1,12 +1,14 @@
|
|||
import * as React from 'react'
|
||||
import * as PopoverPrimitive from '@radix-ui/react-popover'
|
||||
|
||||
import { cn } from '@renderer/lib/utils'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const Popover = PopoverPrimitive.Root
|
||||
|
||||
const PopoverTrigger = PopoverPrimitive.Trigger
|
||||
|
||||
const PopoverAnchor = PopoverPrimitive.Anchor
|
||||
|
||||
const PopoverContent = React.forwardRef<
|
||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||
|
|
@ -27,4 +29,4 @@ const PopoverContent = React.forwardRef<
|
|||
))
|
||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent }
|
||||
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
|
||||
|
|
@ -1,17 +1,14 @@
|
|||
import { GripVertical } from "lucide-react"
|
||||
import * as ResizablePrimitive from "react-resizable-panels"
|
||||
import { GripVertical } from 'lucide-react'
|
||||
import * as ResizablePrimitive from 'react-resizable-panels'
|
||||
|
||||
import { cn } from "@renderer/lib/utils"
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const ResizablePanelGroup = ({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => (
|
||||
<ResizablePrimitive.PanelGroup
|
||||
className={cn(
|
||||
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
|
||||
className
|
||||
)}
|
||||
className={cn('flex h-full w-full data-[panel-group-direction=vertical]:flex-col', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
|
@ -27,7 +24,7 @@ const ResizableHandle = ({
|
|||
}) => (
|
||||
<ResizablePrimitive.PanelResizeHandle
|
||||
className={cn(
|
||||
"relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
|
||||
'relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import * as React from 'react'
|
||||
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area'
|
||||
|
||||
import { cn } from '@renderer/lib/utils'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const ScrollArea = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||
24
src/components/ui/separator.tsx
Normal file
24
src/components/ui/separator.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import * as React from 'react'
|
||||
import * as SeparatorPrimitive from '@radix-ui/react-separator'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const Separator = React.forwardRef<
|
||||
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
||||
>(({ className, orientation = 'horizontal', decorative = true, ...props }, ref) => (
|
||||
<SeparatorPrimitive.Root
|
||||
ref={ref}
|
||||
decorative={decorative}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
'shrink-0 bg-border',
|
||||
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Separator.displayName = SeparatorPrimitive.Root.displayName
|
||||
|
||||
export { Separator }
|
||||
7
src/components/ui/skeleton.tsx
Normal file
7
src/components/ui/skeleton.tsx
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { cn } from '@/lib/utils'
|
||||
|
||||
function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return <div className={cn('animate-pulse rounded-md bg-primary/10', className)} {...props} />
|
||||
}
|
||||
|
||||
export { Skeleton }
|
||||
27
src/components/ui/switch.tsx
Normal file
27
src/components/ui/switch.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import * as React from 'react'
|
||||
import * as SwitchPrimitives from '@radix-ui/react-switch'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const Switch = React.forwardRef<
|
||||
React.ElementRef<typeof SwitchPrimitives.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SwitchPrimitives.Root
|
||||
className={cn(
|
||||
'peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
>
|
||||
<SwitchPrimitives.Thumb
|
||||
className={cn(
|
||||
'pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0'
|
||||
)}
|
||||
/>
|
||||
</SwitchPrimitives.Root>
|
||||
))
|
||||
Switch.displayName = SwitchPrimitives.Root.displayName
|
||||
|
||||
export { Switch }
|
||||
21
src/components/ui/textarea.tsx
Normal file
21
src/components/ui/textarea.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import * as React from 'react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const Textarea = React.forwardRef<HTMLTextAreaElement, React.ComponentProps<'textarea'>>(
|
||||
({ className, ...props }, ref) => {
|
||||
return (
|
||||
<textarea
|
||||
className={cn(
|
||||
'flex min-h-[80px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Textarea.displayName = 'Textarea'
|
||||
|
||||
export { Textarea }
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
import * as React from "react"
|
||||
import * as ToastPrimitives from "@radix-ui/react-toast"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import { X } from "lucide-react"
|
||||
import * as React from 'react'
|
||||
import * as ToastPrimitives from '@radix-ui/react-toast'
|
||||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
import { X } from 'lucide-react'
|
||||
|
||||
import { cn } from "@renderer/lib/utils"
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const ToastProvider = ToastPrimitives.Provider
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ const ToastViewport = React.forwardRef<
|
|||
<ToastPrimitives.Viewport
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
|
||||
'fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -23,25 +23,24 @@ const ToastViewport = React.forwardRef<
|
|||
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
|
||||
|
||||
const toastVariants = cva(
|
||||
"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
|
||||
'group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "border bg-background text-foreground",
|
||||
default: 'border bg-background text-foreground',
|
||||
destructive:
|
||||
"destructive group border-destructive bg-destructive text-destructive-foreground",
|
||||
},
|
||||
'destructive group border-destructive bg-destructive text-destructive-foreground'
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
variant: 'default'
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const Toast = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
|
||||
VariantProps<typeof toastVariants>
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & VariantProps<typeof toastVariants>
|
||||
>(({ className, variant, ...props }, ref) => {
|
||||
return (
|
||||
<ToastPrimitives.Root
|
||||
|
|
@ -60,7 +59,7 @@ const ToastAction = React.forwardRef<
|
|||
<ToastPrimitives.Action
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
|
||||
'inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -75,7 +74,7 @@ const ToastClose = React.forwardRef<
|
|||
<ToastPrimitives.Close
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
|
||||
'absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600',
|
||||
className
|
||||
)}
|
||||
toast-close=""
|
||||
|
|
@ -92,7 +91,7 @@ const ToastTitle = React.forwardRef<
|
|||
>(({ className, ...props }, ref) => (
|
||||
<ToastPrimitives.Title
|
||||
ref={ref}
|
||||
className={cn("text-sm font-semibold", className)}
|
||||
className={cn('text-sm font-semibold [&+div]:text-xs', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
|
|
@ -104,7 +103,7 @@ const ToastDescription = React.forwardRef<
|
|||
>(({ className, ...props }, ref) => (
|
||||
<ToastPrimitives.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm opacity-90", className)}
|
||||
className={cn('text-sm opacity-90', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
|
|
@ -123,5 +122,5 @@ export {
|
|||
ToastTitle,
|
||||
ToastDescription,
|
||||
ToastClose,
|
||||
ToastAction,
|
||||
ToastAction
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { useToast } from '@renderer/hooks/use-toast'
|
||||
import { useToast } from '@/hooks/use-toast'
|
||||
import {
|
||||
Toast,
|
||||
ToastClose,
|
||||
|
|
@ -6,7 +6,7 @@ import {
|
|||
ToastProvider,
|
||||
ToastTitle,
|
||||
ToastViewport
|
||||
} from '@renderer/components/ui/toast'
|
||||
} from '@/components/ui/toast'
|
||||
|
||||
export function Toaster() {
|
||||
const { toasts } = useToast()
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
"use client"
|
||||
'use client'
|
||||
|
||||
// Inspired by react-hot-toast library
|
||||
import * as React from "react"
|
||||
import * as React from 'react'
|
||||
|
||||
import type {
|
||||
ToastActionElement,
|
||||
ToastProps,
|
||||
} from "@renderer/components/ui/toast"
|
||||
import type { ToastActionElement, ToastProps } from '@/components/ui/toast'
|
||||
|
||||
const TOAST_LIMIT = 1
|
||||
const TOAST_REMOVE_DELAY = 1000000
|
||||
|
|
@ -19,10 +16,10 @@ type ToasterToast = ToastProps & {
|
|||
}
|
||||
|
||||
const actionTypes = {
|
||||
ADD_TOAST: "ADD_TOAST",
|
||||
UPDATE_TOAST: "UPDATE_TOAST",
|
||||
DISMISS_TOAST: "DISMISS_TOAST",
|
||||
REMOVE_TOAST: "REMOVE_TOAST",
|
||||
ADD_TOAST: 'ADD_TOAST',
|
||||
UPDATE_TOAST: 'UPDATE_TOAST',
|
||||
DISMISS_TOAST: 'DISMISS_TOAST',
|
||||
REMOVE_TOAST: 'REMOVE_TOAST'
|
||||
} as const
|
||||
|
||||
let count = 0
|
||||
|
|
@ -36,20 +33,20 @@ type ActionType = typeof actionTypes
|
|||
|
||||
type Action =
|
||||
| {
|
||||
type: ActionType["ADD_TOAST"]
|
||||
type: ActionType['ADD_TOAST']
|
||||
toast: ToasterToast
|
||||
}
|
||||
| {
|
||||
type: ActionType["UPDATE_TOAST"]
|
||||
type: ActionType['UPDATE_TOAST']
|
||||
toast: Partial<ToasterToast>
|
||||
}
|
||||
| {
|
||||
type: ActionType["DISMISS_TOAST"]
|
||||
toastId?: ToasterToast["id"]
|
||||
type: ActionType['DISMISS_TOAST']
|
||||
toastId?: ToasterToast['id']
|
||||
}
|
||||
| {
|
||||
type: ActionType["REMOVE_TOAST"]
|
||||
toastId?: ToasterToast["id"]
|
||||
type: ActionType['REMOVE_TOAST']
|
||||
toastId?: ToasterToast['id']
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
|
@ -66,8 +63,8 @@ const addToRemoveQueue = (toastId: string) => {
|
|||
const timeout = setTimeout(() => {
|
||||
toastTimeouts.delete(toastId)
|
||||
dispatch({
|
||||
type: "REMOVE_TOAST",
|
||||
toastId: toastId,
|
||||
type: 'REMOVE_TOAST',
|
||||
toastId: toastId
|
||||
})
|
||||
}, TOAST_REMOVE_DELAY)
|
||||
|
||||
|
|
@ -76,21 +73,19 @@ const addToRemoveQueue = (toastId: string) => {
|
|||
|
||||
export const reducer = (state: State, action: Action): State => {
|
||||
switch (action.type) {
|
||||
case "ADD_TOAST":
|
||||
case 'ADD_TOAST':
|
||||
return {
|
||||
...state,
|
||||
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
|
||||
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT)
|
||||
}
|
||||
|
||||
case "UPDATE_TOAST":
|
||||
case 'UPDATE_TOAST':
|
||||
return {
|
||||
...state,
|
||||
toasts: state.toasts.map((t) =>
|
||||
t.id === action.toast.id ? { ...t, ...action.toast } : t
|
||||
),
|
||||
toasts: state.toasts.map((t) => (t.id === action.toast.id ? { ...t, ...action.toast } : t))
|
||||
}
|
||||
|
||||
case "DISMISS_TOAST": {
|
||||
case 'DISMISS_TOAST': {
|
||||
const { toastId } = action
|
||||
|
||||
// ! Side effects ! - This could be extracted into a dismissToast() action,
|
||||
|
|
@ -109,22 +104,22 @@ export const reducer = (state: State, action: Action): State => {
|
|||
t.id === toastId || toastId === undefined
|
||||
? {
|
||||
...t,
|
||||
open: false,
|
||||
open: false
|
||||
}
|
||||
: t
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
case "REMOVE_TOAST":
|
||||
case 'REMOVE_TOAST':
|
||||
if (action.toastId === undefined) {
|
||||
return {
|
||||
...state,
|
||||
toasts: [],
|
||||
toasts: []
|
||||
}
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
toasts: state.toasts.filter((t) => t.id !== action.toastId),
|
||||
toasts: state.toasts.filter((t) => t.id !== action.toastId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -140,34 +135,34 @@ function dispatch(action: Action) {
|
|||
})
|
||||
}
|
||||
|
||||
type Toast = Omit<ToasterToast, "id">
|
||||
type Toast = Omit<ToasterToast, 'id'>
|
||||
|
||||
function toast({ ...props }: Toast) {
|
||||
const id = genId()
|
||||
|
||||
const update = (props: ToasterToast) =>
|
||||
dispatch({
|
||||
type: "UPDATE_TOAST",
|
||||
toast: { ...props, id },
|
||||
type: 'UPDATE_TOAST',
|
||||
toast: { ...props, id }
|
||||
})
|
||||
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
|
||||
const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id })
|
||||
|
||||
dispatch({
|
||||
type: "ADD_TOAST",
|
||||
type: 'ADD_TOAST',
|
||||
toast: {
|
||||
...props,
|
||||
id,
|
||||
open: true,
|
||||
onOpenChange: (open) => {
|
||||
if (!open) dismiss()
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
id: id,
|
||||
dismiss,
|
||||
update,
|
||||
update
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -187,7 +182,7 @@ function useToast() {
|
|||
return {
|
||||
...state,
|
||||
toast,
|
||||
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
|
||||
dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import client from '@renderer/services/client.service'
|
||||
import client from '@/services/client.service'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { tagNameEquals } from '@renderer/lib/tag'
|
||||
import client from '@renderer/services/client.service'
|
||||
import { tagNameEquals } from '@/lib/tag'
|
||||
import client from '@/services/client.service'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { verifyNip05 } from '@renderer/lib/nip05'
|
||||
import { verifyNip05 } from '@/lib/nip05'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
export function useFetchNip05(nip05?: string, pubkey?: string) {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import client from '@renderer/services/client.service'
|
||||
import { TProfile } from '@renderer/types'
|
||||
import client from '@/services/client.service'
|
||||
import { TProfile } from '@/types'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
export function useFetchProfile(id?: string) {
|
||||
|
|
@ -1,12 +1,13 @@
|
|||
import { checkAlgoRelay } from '@renderer/lib/relay'
|
||||
import client from '@renderer/services/client.service'
|
||||
import { TRelayInfo } from '@renderer/types'
|
||||
import { checkAlgoRelay } from '@/lib/relay'
|
||||
import client from '@/services/client.service'
|
||||
import { TRelayInfo } from '@/types'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
export function useFetchRelayInfos(urls: string[]) {
|
||||
const [isFetching, setIsFetching] = useState(true)
|
||||
const [relayInfos, setRelayInfos] = useState<(TRelayInfo | undefined)[]>([])
|
||||
const [areAlgoRelays, setAreAlgoRelays] = useState(false)
|
||||
const urlsString = JSON.stringify(urls)
|
||||
|
||||
useEffect(() => {
|
||||
const fetchRelayInfos = async () => {
|
||||
|
|
@ -30,7 +31,7 @@ export function useFetchRelayInfos(urls: string[]) {
|
|||
}
|
||||
|
||||
fetchRelayInfos()
|
||||
}, [JSON.stringify(urls)])
|
||||
}, [urlsString])
|
||||
|
||||
return { relayInfos, isFetching, areAlgoRelays }
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { TRelayList } from '@renderer/types'
|
||||
import { TRelayList } from '@/types'
|
||||
import { useEffect, useState } from 'react'
|
||||
import client from '@renderer/services/client.service'
|
||||
import client from '@/services/client.service'
|
||||
|
||||
export function useFetchRelayList(pubkey?: string | null) {
|
||||
const [relayList, setRelayList] = useState<TRelayList>({ write: [], read: [] })
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { TWebMetadata } from '@renderer/types'
|
||||
import { TWebMetadata } from '@/types'
|
||||
import { useEffect, useState } from 'react'
|
||||
import webService from '@renderer/services/web.service'
|
||||
import webService from '@/services/web.service'
|
||||
|
||||
export function useFetchWebMetadata(url: string) {
|
||||
const [metadata, setMetadata] = useState<TWebMetadata>({})
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { useRelaySettings } from '@renderer/providers/RelaySettingsProvider'
|
||||
import client from '@renderer/services/client.service'
|
||||
import { TProfile } from '@renderer/types'
|
||||
import { useRelaySettings } from '@/providers/RelaySettingsProvider'
|
||||
import client from '@/services/client.service'
|
||||
import { TProfile } from '@/types'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
export function useSearchProfiles(search: string, limit: number) {
|
||||
|
|
@ -79,10 +79,6 @@ export default {
|
|||
notifications: 'notifications',
|
||||
Notifications: 'Notifications',
|
||||
'no more notifications': 'no more notifications',
|
||||
'There are no secret keys stored on this device. Your nsec will be unprotected.':
|
||||
'There are no secret keys stored on this device. Your nsec will be unprotected.',
|
||||
'Your nsec will be encrypted using the {{backend}}.':
|
||||
'Your nsec will be encrypted using the {{backend}}.',
|
||||
'Using private key login is insecure. It is recommended to use a browser extension for login, such as alby, nostr-keyx or nos2x.':
|
||||
'Using private key login is insecure. It is recommended to use a browser extension for login, such as alby, nostr-keyx or nos2x.',
|
||||
'Login with Browser Extension': 'Login with Browser Extension',
|
||||
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