feat: add quick account switch interaction
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { isSameAccount } from '@/lib/account'
|
||||
import { formatPubkey } from '@/lib/pubkey'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { TAccountPointer, TSignerType } from '@/types'
|
||||
import { TAccountPointer } from '@/types'
|
||||
import { Loader, Trash2 } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import SignerTypeBadge from '../SignerTypeBadge'
|
||||
import { SimpleUserAvatar } from '../UserAvatar'
|
||||
import { SimpleUsername } from '../Username'
|
||||
|
||||
@@ -74,17 +74,3 @@ export default function AccountList({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function SignerTypeBadge({ signerType }: { signerType: TSignerType }) {
|
||||
if (signerType === 'nip-07') {
|
||||
return <Badge className=" bg-green-400 hover:bg-green-400/80">NIP-07</Badge>
|
||||
} else if (signerType === 'bunker') {
|
||||
return <Badge className=" bg-blue-400 hover:bg-blue-400/80">Bunker</Badge>
|
||||
} else if (signerType === 'ncryptsec') {
|
||||
return <Badge>NCRYPTSEC</Badge>
|
||||
} else if (signerType === 'nsec') {
|
||||
return <Badge className=" bg-orange-400 hover:bg-orange-400/80">NSEC</Badge>
|
||||
} else if (signerType === 'npub') {
|
||||
return <Badge className=" bg-yellow-400 hover:bg-yellow-400/80">NPUB</Badge>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,57 @@
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { LONG_PRESS_THRESHOLD } from '@/constants'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { usePrimaryPage } from '@/PageManager'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { UserRound } from 'lucide-react'
|
||||
import { useMemo } from 'react'
|
||||
import { useMemo, useRef, useState } from 'react'
|
||||
import LoginDialog from '../LoginDialog'
|
||||
import { SimpleUserAvatar } from '../UserAvatar'
|
||||
import BottomNavigationBarItem from './BottomNavigationBarItem'
|
||||
|
||||
export default function AccountButton() {
|
||||
const { navigate, current, display } = usePrimaryPage()
|
||||
const { pubkey, profile } = useNostr()
|
||||
const [loginDialogOpen, setLoginDialogOpen] = useState(false)
|
||||
const active = useMemo(() => current === 'me' && display, [display, current])
|
||||
const pressTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
|
||||
const handlePointerDown = () => {
|
||||
pressTimerRef.current = setTimeout(() => {
|
||||
setLoginDialogOpen(true)
|
||||
pressTimerRef.current = null
|
||||
}, LONG_PRESS_THRESHOLD)
|
||||
}
|
||||
|
||||
const handlePointerUp = () => {
|
||||
if (pressTimerRef.current) {
|
||||
clearTimeout(pressTimerRef.current)
|
||||
navigate('me')
|
||||
pressTimerRef.current = null
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<BottomNavigationBarItem
|
||||
onClick={() => {
|
||||
navigate('me')
|
||||
}}
|
||||
active={active}
|
||||
>
|
||||
{pubkey ? (
|
||||
profile ? (
|
||||
<SimpleUserAvatar
|
||||
userId={pubkey}
|
||||
className={cn('w-7 h-7', active ? 'ring-primary ring-1' : '')}
|
||||
/>
|
||||
<>
|
||||
<BottomNavigationBarItem
|
||||
onPointerDown={handlePointerDown}
|
||||
onPointerUp={handlePointerUp}
|
||||
active={active}
|
||||
>
|
||||
{pubkey ? (
|
||||
profile ? (
|
||||
<SimpleUserAvatar
|
||||
userId={pubkey}
|
||||
className={cn('w-7 h-7', active ? 'ring-primary ring-1' : '')}
|
||||
/>
|
||||
) : (
|
||||
<Skeleton className={cn('w-7 h-7 rounded-full', active ? 'ring-primary ring-1' : '')} />
|
||||
)
|
||||
) : (
|
||||
<Skeleton className={cn('w-7 h-7 rounded-full', active ? 'ring-primary ring-1' : '')} />
|
||||
)
|
||||
) : (
|
||||
<UserRound />
|
||||
)}
|
||||
</BottomNavigationBarItem>
|
||||
<UserRound />
|
||||
)}
|
||||
</BottomNavigationBarItem>
|
||||
<LoginDialog open={loginDialogOpen} setOpen={setLoginDialogOpen} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Button } from '../ui/button'
|
||||
import { MouseEventHandler } from 'react'
|
||||
|
||||
export default function BottomNavigationBarItem({
|
||||
children,
|
||||
active = false,
|
||||
onClick
|
||||
onClick,
|
||||
onPointerDown,
|
||||
onPointerUp
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
active?: boolean
|
||||
onClick: MouseEventHandler
|
||||
onClick?: MouseEventHandler
|
||||
onPointerDown?: MouseEventHandler
|
||||
onPointerUp?: MouseEventHandler
|
||||
}) {
|
||||
return (
|
||||
<Button
|
||||
@@ -19,6 +23,8 @@ export default function BottomNavigationBarItem({
|
||||
)}
|
||||
variant="ghost"
|
||||
onClick={onClick}
|
||||
onPointerDown={onPointerDown}
|
||||
onPointerUp={onPointerUp}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { LONG_PRESS_THRESHOLD } from '@/constants'
|
||||
import { useNoteStatsById } from '@/hooks/useNoteStatsById'
|
||||
import { getLightningAddressFromProfile } from '@/lib/lightning'
|
||||
import { cn } from '@/lib/utils'
|
||||
@@ -86,7 +87,7 @@ export default function ZapButton({ event }: { event: Event }) {
|
||||
setOpenZapDialog(true)
|
||||
setZapping(true)
|
||||
})
|
||||
}, 500)
|
||||
}, LONG_PRESS_THRESHOLD)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { toWallet } from '@/lib/link'
|
||||
import { formatPubkey, generateImageByPubkey } from '@/lib/pubkey'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { usePrimaryPage, useSecondaryPage } from '@/PageManager'
|
||||
import { useSecondaryPage } from '@/PageManager'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { ArrowDownUp, LogIn, LogOut, UserRound, Wallet } from 'lucide-react'
|
||||
import { LogIn, LogOut, Plus, Wallet } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import LoginDialog from '../LoginDialog'
|
||||
import LogoutDialog from '../LogoutDialog'
|
||||
import SignerTypeBadge from '../SignerTypeBadge'
|
||||
import { SimpleUserAvatar } from '../UserAvatar'
|
||||
import { SimpleUsername } from '../Username'
|
||||
import SidebarItem from './SidebarItem'
|
||||
|
||||
export default function AccountButton({ collapse }: { collapse: boolean }) {
|
||||
@@ -31,17 +33,13 @@ export default function AccountButton({ collapse }: { collapse: boolean }) {
|
||||
|
||||
function ProfileButton({ collapse }: { collapse: boolean }) {
|
||||
const { t } = useTranslation()
|
||||
const { account, profile } = useNostr()
|
||||
const { account, accounts, switchAccount } = useNostr()
|
||||
const pubkey = account?.pubkey
|
||||
const { navigate } = usePrimaryPage()
|
||||
const { push } = useSecondaryPage()
|
||||
const [loginDialogOpen, setLoginDialogOpen] = useState(false)
|
||||
const [logoutDialogOpen, setLogoutDialogOpen] = useState(false)
|
||||
if (!pubkey) return null
|
||||
|
||||
const defaultAvatar = generateImageByPubkey(pubkey)
|
||||
const { username, avatar } = profile || { username: formatPubkey(pubkey), avatar: defaultAvatar }
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
@@ -53,37 +51,68 @@ function ProfileButton({ collapse }: { collapse: boolean }) {
|
||||
)}
|
||||
>
|
||||
<div className="flex gap-2 items-center flex-1 w-0">
|
||||
<Avatar className="w-8 h-8">
|
||||
<AvatarImage src={avatar} />
|
||||
<AvatarFallback>
|
||||
<img src={defaultAvatar} />
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
{!collapse && <div className="truncate font-semibold text-lg">{username}</div>}
|
||||
<SimpleUserAvatar size="medium" userId={pubkey} />
|
||||
{!collapse && (
|
||||
<SimpleUsername className="truncate font-semibold text-lg" userId={pubkey} />
|
||||
)}
|
||||
</div>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent side="top">
|
||||
<DropdownMenuItem onClick={() => navigate('profile')}>
|
||||
<UserRound />
|
||||
{t('Profile')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuContent side="top" className="w-72">
|
||||
<DropdownMenuItem onClick={() => push(toWallet())}>
|
||||
<Wallet />
|
||||
{t('Wallet')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={() => setLoginDialogOpen(true)}>
|
||||
<ArrowDownUp />
|
||||
{t('Switch account')}
|
||||
<DropdownMenuLabel>{t('Switch account')}</DropdownMenuLabel>
|
||||
{accounts.map((act) => (
|
||||
<DropdownMenuItem
|
||||
className={act.pubkey === pubkey ? 'cursor-default focus:bg-background' : ''}
|
||||
key={`${act.pubkey}:${act.signerType}`}
|
||||
onClick={() => {
|
||||
if (act.pubkey !== pubkey) {
|
||||
switchAccount(act)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex gap-2 items-center flex-1">
|
||||
<SimpleUserAvatar userId={act.pubkey} />
|
||||
<div className="flex-1 w-0">
|
||||
<SimpleUsername
|
||||
userId={act.pubkey}
|
||||
className="font-medium truncate"
|
||||
skeletonClassName="h-3"
|
||||
/>
|
||||
<SignerTypeBadge signerType={act.signerType} />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
'border border-muted-foreground rounded-full size-3.5',
|
||||
act.pubkey === pubkey && 'size-4 border-4 border-primary'
|
||||
)}
|
||||
/>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
<DropdownMenuItem
|
||||
onClick={() => setLoginDialogOpen(true)}
|
||||
className="border border-dashed m-2 focus:border-muted-foreground focus:bg-background"
|
||||
>
|
||||
<div className="flex gap-2 items-center justify-center w-full py-2">
|
||||
<Plus />
|
||||
{t('Add an Account')}
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="text-destructive focus:text-destructive"
|
||||
onClick={() => setLogoutDialogOpen(true)}
|
||||
>
|
||||
<LogOut />
|
||||
{t('Logout')}
|
||||
<span className="shrink-0">{t('Logout')}</span>
|
||||
<SimpleUsername
|
||||
userId={pubkey}
|
||||
className="text-muted-foreground border border-muted-foreground px-1 rounded-md text-xs truncate"
|
||||
/>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
<LoginDialog open={loginDialogOpen} setOpen={setLoginDialogOpen} />
|
||||
|
||||
23
src/components/SignerTypeBadge/index.tsx
Normal file
23
src/components/SignerTypeBadge/index.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { TSignerType } from '@/types'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function SignerTypeBadge({ signerType }: { signerType: TSignerType }) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
if (signerType === 'nip-07') {
|
||||
return <Badge className=" bg-green-400 hover:bg-green-400 px-1 py-0">{t('Extension')}</Badge>
|
||||
} else if (signerType === 'bunker') {
|
||||
return <Badge className=" bg-blue-400 hover:bg-blue-400 px-1 py-0">{t('Remote')}</Badge>
|
||||
} else if (signerType === 'ncryptsec') {
|
||||
return (
|
||||
<Badge className="bg-violet-400 hover:bg-violet-400 px-1 py-0">{t('Encrypted Key')}</Badge>
|
||||
)
|
||||
} else if (signerType === 'nsec') {
|
||||
return (
|
||||
<Badge className=" bg-orange-400 hover:bg-orange-400 px-1 py-0">{t('Private Key')}</Badge>
|
||||
)
|
||||
} else if (signerType === 'npub') {
|
||||
return <Badge className=" bg-yellow-400 hover:bg-yellow-400 px-1 py-0">NPUB</Badge>
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,8 @@ export default function Username({
|
||||
skeletonClassName?: string
|
||||
withoutSkeleton?: boolean
|
||||
}) {
|
||||
const { profile } = useFetchProfile(userId)
|
||||
if (!profile && !withoutSkeleton) {
|
||||
const { profile, isFetching } = useFetchProfile(userId)
|
||||
if (!profile && isFetching && !withoutSkeleton) {
|
||||
return (
|
||||
<div className="py-1">
|
||||
<Skeleton className={cn('w-16', skeletonClassName)} />
|
||||
@@ -63,8 +63,8 @@ export function SimpleUsername({
|
||||
skeletonClassName?: string
|
||||
withoutSkeleton?: boolean
|
||||
}) {
|
||||
const { profile } = useFetchProfile(userId)
|
||||
if (!profile && !withoutSkeleton) {
|
||||
const { profile, isFetching } = useFetchProfile(userId)
|
||||
if (!profile && isFetching && !withoutSkeleton) {
|
||||
return (
|
||||
<div className="py-1">
|
||||
<Skeleton className={cn('w-16', skeletonClassName)} />
|
||||
|
||||
@@ -432,3 +432,5 @@ export const PRIMARY_COLORS = {
|
||||
}
|
||||
} as const
|
||||
export type TPrimaryColor = keyof typeof PRIMARY_COLORS
|
||||
|
||||
export const LONG_PRESS_THRESHOLD = 500
|
||||
|
||||
@@ -480,6 +480,10 @@ export default {
|
||||
Layout: 'التخطيط',
|
||||
'Two-column': 'عمودين',
|
||||
'Single-column': 'عمود واحد',
|
||||
Reviews: 'المراجعات'
|
||||
Reviews: 'المراجعات',
|
||||
Extension: 'امتداد',
|
||||
Remote: 'عن بُعد',
|
||||
'Encrypted Key': 'مفتاح مشفر',
|
||||
'Private Key': 'مفتاح خاص'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,6 +494,10 @@ export default {
|
||||
Layout: 'Layout',
|
||||
'Two-column': 'Zweispaltig',
|
||||
'Single-column': 'Einspaltig',
|
||||
Reviews: 'Bewertungen'
|
||||
Reviews: 'Bewertungen',
|
||||
Extension: 'Erweiterung',
|
||||
Remote: 'Remote',
|
||||
'Encrypted Key': 'Verschlüsselter Schlüssel',
|
||||
'Private Key': 'Privater Schlüssel'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,6 +479,10 @@ export default {
|
||||
Layout: 'Layout',
|
||||
'Two-column': 'Two-column',
|
||||
'Single-column': 'Single-column',
|
||||
Reviews: 'Reviews'
|
||||
Reviews: 'Reviews',
|
||||
Extension: 'Extension',
|
||||
Remote: 'Remote',
|
||||
'Encrypted Key': 'Encrypted Key',
|
||||
'Private Key': 'Private Key'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,6 +488,10 @@ export default {
|
||||
Layout: 'Diseño',
|
||||
'Two-column': 'Doble columna',
|
||||
'Single-column': 'Columna única',
|
||||
Reviews: 'Reseñas'
|
||||
Reviews: 'Reseñas',
|
||||
Extension: 'Extensión',
|
||||
Remote: 'Remoto',
|
||||
'Encrypted Key': 'Clave privada cifrada',
|
||||
'Private Key': 'Clave privada'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,6 +483,10 @@ export default {
|
||||
Layout: 'چیدمان',
|
||||
'Two-column': 'دو ستونی',
|
||||
'Single-column': 'تک ستونی',
|
||||
Reviews: 'نقدها'
|
||||
Reviews: 'نقدها',
|
||||
Extension: 'افزونه',
|
||||
Remote: 'از راه دور',
|
||||
'Encrypted Key': 'رمزگذاری شده کلید',
|
||||
'Private Key': 'کلید خصوصی'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -493,6 +493,10 @@ export default {
|
||||
Layout: 'Disposition',
|
||||
'Two-column': 'Deux colonnes',
|
||||
'Single-column': 'Une seule colonne',
|
||||
Reviews: 'Avis'
|
||||
Reviews: 'Avis',
|
||||
Extension: 'Extension',
|
||||
Remote: 'Distant',
|
||||
'Encrypted Key': 'Clé chiffrée',
|
||||
'Private Key': 'Clé privée'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,6 +485,10 @@ export default {
|
||||
Layout: 'लेआउट',
|
||||
'Two-column': 'दोहरा स्तंभ',
|
||||
'Single-column': 'एकल स्तंभ',
|
||||
Reviews: 'समीक्षाएं'
|
||||
Reviews: 'समीक्षाएं',
|
||||
Extension: 'एक्सटेंशन',
|
||||
Remote: 'रिमोट',
|
||||
'Encrypted Key': 'एन्क्रिप्टेड की',
|
||||
'Private Key': 'प्राइवेट की'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,6 +488,10 @@ export default {
|
||||
Layout: 'Layout',
|
||||
'Two-column': 'Doppia colonna',
|
||||
'Single-column': 'Colonna singola',
|
||||
Reviews: 'Recensioni'
|
||||
Reviews: 'Recensioni',
|
||||
Extension: 'Estensione',
|
||||
Remote: 'Remoto',
|
||||
'Encrypted Key': 'Chiave Crittografata',
|
||||
'Private Key': 'Chiave Privata'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,6 +484,10 @@ export default {
|
||||
Layout: 'レイアウト',
|
||||
'Two-column': '2列',
|
||||
'Single-column': '1列',
|
||||
Reviews: 'レビュー'
|
||||
Reviews: 'レビュー',
|
||||
Extension: '拡張機能',
|
||||
Remote: 'リモート',
|
||||
'Encrypted Key': '暗号化キー',
|
||||
'Private Key': '暗号化されたキー'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,6 +484,10 @@ export default {
|
||||
Layout: '레이아웃',
|
||||
'Two-column': '두 열',
|
||||
'Single-column': '한 열',
|
||||
Reviews: '리뷰'
|
||||
Reviews: '리뷰',
|
||||
Extension: '확장 프로그램',
|
||||
Remote: '원격',
|
||||
'Encrypted Key': '암호화된 키',
|
||||
'Private Key': '개인 키'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,6 +488,10 @@ export default {
|
||||
Layout: 'Układ',
|
||||
'Two-column': 'Dwie kolumny',
|
||||
'Single-column': 'Jedna kolumna',
|
||||
Reviews: 'Opinie'
|
||||
Reviews: 'Opinie',
|
||||
Extension: 'Rozszerzenie',
|
||||
Remote: 'Zdalne',
|
||||
'Encrypted Key': 'Zaszyfrowany Klucz',
|
||||
'Private Key': 'Zaszyfrowany Klucz'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,6 +485,10 @@ export default {
|
||||
Layout: 'Layout',
|
||||
'Two-column': 'Coluna dupla',
|
||||
'Single-column': 'Coluna única',
|
||||
Reviews: 'Avaliações'
|
||||
Reviews: 'Avaliações',
|
||||
Extension: 'Extensão',
|
||||
Remote: 'Remoto',
|
||||
'Encrypted Key': 'Chave Criptografada',
|
||||
'Private Key': 'Chave Privada'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,6 +488,10 @@ export default {
|
||||
Layout: 'Layout',
|
||||
'Two-column': 'Coluna dupla',
|
||||
'Single-column': 'Coluna única',
|
||||
Reviews: 'Avaliações'
|
||||
Reviews: 'Avaliações',
|
||||
Extension: 'Extensão',
|
||||
Remote: 'Remoto',
|
||||
'Encrypted Key': 'Chave Criptografada',
|
||||
'Private Key': 'Chave Privada'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,6 +490,10 @@ export default {
|
||||
Layout: 'Макет',
|
||||
'Two-column': 'Две колонки',
|
||||
'Single-column': 'Одна колонка',
|
||||
Reviews: 'Отзывы'
|
||||
Reviews: 'Отзывы',
|
||||
Extension: 'Расширение',
|
||||
Remote: 'Удалённый',
|
||||
'Encrypted Key': 'Зашифрованный ключ',
|
||||
'Private Key': 'Приватный ключ'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,6 +478,10 @@ export default {
|
||||
Layout: 'เค้าโครง',
|
||||
'Two-column': 'สองคอลัมน์',
|
||||
'Single-column': 'คอลัมน์เดียว',
|
||||
Reviews: 'รีวิว'
|
||||
Reviews: 'รีวิว',
|
||||
Extension: 'ส่วนขยาย',
|
||||
Remote: 'ระยะไกล',
|
||||
'Encrypted Key': 'คีย์ที่เข้ารหัส',
|
||||
'Private Key': 'คีย์ส่วนตัว'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,6 +476,10 @@ export default {
|
||||
Layout: '布局',
|
||||
'Two-column': '双栏',
|
||||
'Single-column': '单栏',
|
||||
Reviews: '评价'
|
||||
Reviews: '评价',
|
||||
Extension: '扩展',
|
||||
Remote: '远程',
|
||||
'Encrypted Key': '加密私钥',
|
||||
'Private Key': '私钥'
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user