feat: community mode (#738)
Co-authored-by: CXPLAY <62034099+cxplay@users.noreply.github.com>
This commit is contained in:
parent
686b1f9998
commit
ed8a22d5bc
21 changed files with 303 additions and 101 deletions
16
README.md
16
README.md
|
|
@ -50,6 +50,22 @@ docker compose up --build -d
|
|||
|
||||
After finishing, access: http://localhost:8089
|
||||
|
||||
## Community mode (Optional)
|
||||
|
||||
If you want to run Jumble in community mode (with pre-configured relay sets and relays), you can set the following environment variables in a `.env` file at the root of the project:
|
||||
|
||||
- `VITE_COMMUNITY_RELAY_SETS`: Environment variable. Set the default relay sets for Jumble. Multiple relay sets can be configured. If configured, the first preset group will be displayed to visitors by default upon opening. Visitors cannot delete relay sets preset by administrators. This is ideal for communities wishing to host their own Jumble instances or for setting default feeds for family members. Examples:
|
||||
|
||||
```
|
||||
VITE_COMMUNITY_RELAY_SETS=[{"id": "example.com", "name": "The Example Feed", "relayUrls": ["wss://relay.example.com/", "wss://relay.example.org/"]},{"id": "dailynews", "name": "News", "relayUrls": ["wss://news.example.com/", "wss://news.example.org/"]}]
|
||||
```
|
||||
|
||||
- `VITE_COMMUNITY_RELAYS`: Environment variable. Set additional default relays for Jumble. Multiple relays can be configured, separated by commas. These relays will be added to the preset relay sets and cannot be removed by visitors. Examples:
|
||||
|
||||
```
|
||||
VITE_COMMUNITY_RELAYS="wss://relay.example.com/,wss://relay.example.org/"
|
||||
```
|
||||
|
||||
## Sponsors
|
||||
|
||||
<a target="_blank" href="https://opensats.org/">
|
||||
|
|
|
|||
16
src/components/BottomNavigationBar/FollowingButton.tsx
Normal file
16
src/components/BottomNavigationBar/FollowingButton.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { usePrimaryPage } from '@/PageManager'
|
||||
import { UsersRound } from 'lucide-react'
|
||||
import BottomNavigationBarItem from './BottomNavigationBarItem'
|
||||
|
||||
export default function FollowingButton() {
|
||||
const { navigate, current, display } = usePrimaryPage()
|
||||
|
||||
return (
|
||||
<BottomNavigationBarItem
|
||||
active={current === 'following' && display}
|
||||
onClick={() => navigate('following')}
|
||||
>
|
||||
<UsersRound />
|
||||
</BottomNavigationBarItem>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
import { IS_COMMUNITY_MODE } from '@/constants'
|
||||
import { cn } from '@/lib/utils'
|
||||
import BackgroundAudio from '../BackgroundAudio'
|
||||
import AccountButton from './AccountButton'
|
||||
import ExploreButton from './ExploreButton'
|
||||
import FollowingButton from './FollowingButton'
|
||||
import HomeButton from './HomeButton'
|
||||
import NotificationsButton from './NotificationsButton'
|
||||
|
||||
|
|
@ -16,7 +18,8 @@ export default function BottomNavigationBar() {
|
|||
<BackgroundAudio className="rounded-none border-x-0 border-b border-t-0 bg-background" />
|
||||
<div className="flex w-full items-center justify-around [&_svg]:size-4 [&_svg]:shrink-0">
|
||||
<HomeButton />
|
||||
<ExploreButton />
|
||||
{!IS_COMMUNITY_MODE && <ExploreButton />}
|
||||
{IS_COMMUNITY_MODE && <FollowingButton />}
|
||||
<NotificationsButton />
|
||||
<AccountButton />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { IS_COMMUNITY_MODE } from '@/constants'
|
||||
import { toRelay } from '@/lib/link'
|
||||
import { useSecondaryPage } from '@/PageManager'
|
||||
import { useSortable } from '@dnd-kit/sortable'
|
||||
|
|
@ -38,7 +39,7 @@ export default function RelayItem({ relay }: { relay: string }) {
|
|||
<div className="w-0 flex-1 truncate font-semibold">{relay}</div>
|
||||
</div>
|
||||
</div>
|
||||
<SaveRelayDropdownMenu urls={[relay]} />
|
||||
{!IS_COMMUNITY_MODE && <SaveRelayDropdownMenu urls={[relay]} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { IS_COMMUNITY_MODE, COMMUNITY_RELAY_SETS, COMMUNITY_RELAYS } from '@/constants'
|
||||
import { toRelaySettings } from '@/lib/link'
|
||||
import { simplifyUrl } from '@/lib/url'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
|
@ -18,12 +19,45 @@ export default function FeedSwitcher({ close }: { close?: () => void }) {
|
|||
const { relaySets, favoriteRelays } = useFavoriteRelays()
|
||||
const { feedInfo, switchFeed } = useFeed()
|
||||
const { pinnedPubkeySet } = usePinnedUsers()
|
||||
const filteredRelaySets = useMemo(
|
||||
() => relaySets.filter((set) => set.relayUrls.length > 0),
|
||||
[relaySets]
|
||||
)
|
||||
const filteredRelaySets = useMemo(() => {
|
||||
return relaySets.filter((set) => set.relayUrls.length > 0)
|
||||
}, [relaySets])
|
||||
const hasRelays = filteredRelaySets.length > 0 || favoriteRelays.length > 0
|
||||
|
||||
if (IS_COMMUNITY_MODE) {
|
||||
return (
|
||||
<div className="space-y-1.5">
|
||||
{COMMUNITY_RELAY_SETS.map((set) => (
|
||||
<RelaySetCard
|
||||
key={set.id}
|
||||
relaySet={set}
|
||||
select={feedInfo?.feedType === 'relays' && set.id === feedInfo.id}
|
||||
onSelectChange={(select) => {
|
||||
if (!select) return
|
||||
switchFeed('relays', { activeRelaySetId: set.id })
|
||||
close?.()
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
{COMMUNITY_RELAYS.map((relay) => (
|
||||
<FeedSwitcherItem
|
||||
key={relay}
|
||||
isActive={feedInfo?.feedType === 'relay' && feedInfo.id === relay}
|
||||
onClick={() => {
|
||||
switchFeed('relay', { relay })
|
||||
close?.()
|
||||
}}
|
||||
>
|
||||
<div className="flex w-full items-center gap-3">
|
||||
<RelayIcon url={relay} className="shrink-0" />
|
||||
<div className="w-0 flex-1 truncate">{simplifyUrl(relay)}</div>
|
||||
</div>
|
||||
</FeedSwitcherItem>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Personal Feeds Section */}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area'
|
||||
import { IS_COMMUNITY_MODE } from '@/constants'
|
||||
import { useFetchRelayInfo } from '@/hooks'
|
||||
import { createFakeEvent } from '@/lib/event'
|
||||
import { checkNip43Support } from '@/lib/relay'
|
||||
import { normalizeHttpUrl } from '@/lib/url'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
|
@ -10,6 +12,7 @@ import { Check, Copy, GitBranch, Mail, Share2, SquareCode } from 'lucide-react'
|
|||
import { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
import Content from '../Content'
|
||||
import PostEditor from '../PostEditor'
|
||||
import RelayIcon from '../RelayIcon'
|
||||
import RelayMembershipControl from '../RelayMembershipControl'
|
||||
|
|
@ -17,8 +20,6 @@ import SaveRelayDropdownMenu from '../SaveRelayDropdownMenu'
|
|||
import UserAvatar from '../UserAvatar'
|
||||
import Username from '../Username'
|
||||
import RelayReviewsPreview from './RelayReviewsPreview'
|
||||
import Content from '../Content'
|
||||
import { createFakeEvent } from '@/lib/event'
|
||||
|
||||
export default function RelayInfo({ url, className }: { url: string; className?: string }) {
|
||||
const { t } = useTranslation()
|
||||
|
|
@ -161,7 +162,7 @@ function RelayControls({ url }: { url: string }) {
|
|||
<Button variant="ghost" size="titlebar-icon" onClick={handleCopyUrl}>
|
||||
{copiedUrl ? <Check /> : <Copy />}
|
||||
</Button>
|
||||
<SaveRelayDropdownMenu urls={[url]} bigButton />
|
||||
{!IS_COMMUNITY_MODE && <SaveRelayDropdownMenu urls={[url]} bigButton />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { IS_COMMUNITY_MODE } from '@/constants'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { TRelayInfo } from '@/types'
|
||||
import { HTMLProps } from 'react'
|
||||
|
|
@ -30,7 +31,7 @@ export default function RelaySimpleInfo({
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
{relayInfo && <SaveRelayDropdownMenu urls={[relayInfo.url]} />}
|
||||
{relayInfo && !IS_COMMUNITY_MODE && <SaveRelayDropdownMenu urls={[relayInfo.url]} />}
|
||||
</div>
|
||||
{!!relayInfo?.description && (
|
||||
<div
|
||||
|
|
|
|||
20
src/components/Sidebar/FollowingButton.tsx
Normal file
20
src/components/Sidebar/FollowingButton.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { usePrimaryPage } from '@/PageManager'
|
||||
import { Users2 } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import SidebarItem from './SidebarItem'
|
||||
|
||||
export default function FollowingButton({ collapse }: { collapse: boolean }) {
|
||||
const { t } = useTranslation()
|
||||
const { navigate, current, display } = usePrimaryPage()
|
||||
|
||||
return (
|
||||
<SidebarItem
|
||||
title={t('Following')}
|
||||
onClick={() => navigate('following')}
|
||||
active={display && current === 'following'}
|
||||
collapse={collapse}
|
||||
>
|
||||
<Users2 />
|
||||
</SidebarItem>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import Icon from '@/assets/Icon'
|
||||
import Logo from '@/assets/Logo'
|
||||
import { IS_COMMUNITY_MODE } from '@/constants'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { usePrimaryPage } from '@/PageManager'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
|
|
@ -10,6 +11,7 @@ import { ChevronsLeft, ChevronsRight } from 'lucide-react'
|
|||
import AccountButton from './AccountButton'
|
||||
import BookmarkButton from './BookmarkButton'
|
||||
import RelaysButton from './ExploreButton'
|
||||
import FollowingButton from './FollowingButton'
|
||||
import HomeButton from './HomeButton'
|
||||
import LayoutSwitcher from './LayoutSwitcher'
|
||||
import NotificationsButton from './NotificationButton'
|
||||
|
|
@ -53,7 +55,8 @@ export default function PrimaryPageSidebar() {
|
|||
</button>
|
||||
)}
|
||||
<HomeButton collapse={sidebarCollapse} />
|
||||
<RelaysButton collapse={sidebarCollapse} />
|
||||
{!IS_COMMUNITY_MODE && <RelaysButton collapse={sidebarCollapse} />}
|
||||
{IS_COMMUNITY_MODE && <FollowingButton collapse={sidebarCollapse} />}
|
||||
<NotificationsButton collapse={sidebarCollapse} />
|
||||
<SearchButton collapse={sidebarCollapse} />
|
||||
<ProfileButton collapse={sidebarCollapse} />
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { kinds } from 'nostr-tools'
|
||||
import { TRelaySet } from './types'
|
||||
|
||||
export const JUMBLE_API_BASE_URL = 'https://api.jumble.social'
|
||||
|
||||
|
|
@ -483,3 +484,8 @@ export const SPECIAL_TRUST_SCORE_FILTER_ID = {
|
|||
NAK: 'nak',
|
||||
TRENDING: 'trending'
|
||||
}
|
||||
|
||||
export const COMMUNITY_RELAY_SETS = import.meta.env.VITE_COMMUNITY_RELAY_SETS as TRelaySet[]
|
||||
export const COMMUNITY_RELAYS = import.meta.env.VITE_COMMUNITY_RELAYS as string[]
|
||||
|
||||
export const IS_COMMUNITY_MODE = COMMUNITY_RELAY_SETS.length > 0 || COMMUNITY_RELAYS.length > 0
|
||||
|
|
|
|||
32
src/pages/primary/FollowingPage/index.tsx
Normal file
32
src/pages/primary/FollowingPage/index.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import FollowingFeed from '@/components/FollowingFeed'
|
||||
import PrimaryPageLayout from '@/layouts/PrimaryPageLayout'
|
||||
import { TPageRef } from '@/types'
|
||||
import { UsersRound } from 'lucide-react'
|
||||
import { forwardRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const FollowingPage = forwardRef<TPageRef>((_, ref) => {
|
||||
return (
|
||||
<PrimaryPageLayout
|
||||
pageName="following"
|
||||
titlebar={<FollowingPageTitlebar />}
|
||||
displayScrollToTopButton
|
||||
ref={ref}
|
||||
>
|
||||
<FollowingFeed />
|
||||
</PrimaryPageLayout>
|
||||
)
|
||||
})
|
||||
FollowingPage.displayName = 'FollowingPage'
|
||||
export default FollowingPage
|
||||
|
||||
function FollowingPageTitlebar() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="flex h-full items-center gap-2 pl-3">
|
||||
<UsersRound />
|
||||
<div className="text-lg font-semibold">{t('Following')}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ import FeedSwitcher from '@/components/FeedSwitcher'
|
|||
import RelayIcon from '@/components/RelayIcon'
|
||||
import { Drawer, DrawerContent } from '@/components/ui/drawer'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { IS_COMMUNITY_MODE, COMMUNITY_RELAY_SETS, COMMUNITY_RELAYS } from '@/constants'
|
||||
import { simplifyUrl } from '@/lib/url'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
|
||||
|
|
@ -15,6 +16,10 @@ export default function FeedButton({ className }: { className?: string }) {
|
|||
const { isSmallScreen } = useScreenSize()
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
if (IS_COMMUNITY_MODE && COMMUNITY_RELAY_SETS.length + COMMUNITY_RELAYS.length <= 1) {
|
||||
return <FeedSwitcherTrigger className={className} />
|
||||
}
|
||||
|
||||
if (isSmallScreen) {
|
||||
return (
|
||||
<>
|
||||
|
|
@ -61,7 +66,8 @@ const FeedSwitcherTrigger = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivEle
|
|||
const { relaySets } = useFavoriteRelays()
|
||||
const activeRelaySet = useMemo(() => {
|
||||
return feedInfo?.feedType === 'relays' && feedInfo.id
|
||||
? relaySets.find((set) => set.id === feedInfo.id)
|
||||
? (relaySets.find((set) => set.id === feedInfo.id) ??
|
||||
COMMUNITY_RELAY_SETS.find((set) => set.id === feedInfo.id))
|
||||
: undefined
|
||||
}, [feedInfo, relaySets])
|
||||
const title = useMemo(() => {
|
||||
|
|
@ -78,7 +84,7 @@ const FeedSwitcherTrigger = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivEle
|
|||
return simplifyUrl(feedInfo?.id ?? '')
|
||||
}
|
||||
if (feedInfo?.feedType === 'relays') {
|
||||
return activeRelaySet?.name ?? activeRelaySet?.id
|
||||
return feedInfo.name ?? activeRelaySet?.name ?? activeRelaySet?.id
|
||||
}
|
||||
}, [feedInfo, activeRelaySet])
|
||||
|
||||
|
|
@ -92,15 +98,22 @@ const FeedSwitcherTrigger = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivEle
|
|||
return <Server />
|
||||
}, [feedInfo])
|
||||
|
||||
const clickable =
|
||||
!IS_COMMUNITY_MODE || COMMUNITY_RELAY_SETS.length + COMMUNITY_RELAYS.length > 1
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn('clickable flex h-full items-center gap-2 rounded-xl px-3', className)}
|
||||
className={cn(
|
||||
'flex h-full items-center gap-2 rounded-xl px-3',
|
||||
clickable && 'clickable',
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
{icon}
|
||||
<div className="truncate text-lg font-semibold">{title}</div>
|
||||
<ChevronDown />
|
||||
{clickable && <ChevronDown />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { usePrimaryPage, useSecondaryPage } from '@/PageManager'
|
||||
import FollowingFeed from '@/components/FollowingFeed'
|
||||
import PostEditor from '@/components/PostEditor'
|
||||
import RelayInfo from '@/components/RelayInfo'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
|
@ -21,7 +22,6 @@ import {
|
|||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import FeedButton from './FeedButton'
|
||||
import FollowingFeed from './FollowingFeed'
|
||||
import PinnedFeed from './PinnedFeed'
|
||||
import RelaysFeed from './RelaysFeed'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import MailboxSetting from '@/components/MailboxSetting'
|
||||
import FavoriteRelaysSetting from '@/components/FavoriteRelaysSetting'
|
||||
import MailboxSetting from '@/components/MailboxSetting'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { IS_COMMUNITY_MODE } from '@/constants'
|
||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||
import { forwardRef, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
|
@ -20,6 +21,16 @@ const RelaySettingsPage = forwardRef(({ index }: { index?: number }, ref) => {
|
|||
}
|
||||
}, [])
|
||||
|
||||
if (IS_COMMUNITY_MODE) {
|
||||
return (
|
||||
<SecondaryPageLayout ref={ref} index={index} title={t('Relay settings')}>
|
||||
<div className="space-y-4 px-4 py-3">
|
||||
<MailboxSetting />
|
||||
</div>
|
||||
</SecondaryPageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<SecondaryPageLayout ref={ref} index={index} title={t('Relay settings')}>
|
||||
<Tabs value={tabValue} onValueChange={setTabValue} className="space-y-4 px-4 py-3">
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { IS_COMMUNITY_MODE, COMMUNITY_RELAY_SETS } from '@/constants'
|
||||
import { createFavoriteRelaysDraftEvent, createRelaySetDraftEvent } from '@/lib/draft-event'
|
||||
import { formatError } from '@/lib/error'
|
||||
import { getReplaceableEventIdentifier } from '@/lib/event'
|
||||
|
|
@ -44,6 +45,10 @@ export function FavoriteRelaysProvider({ children }: { children: React.ReactNode
|
|||
const [relaySets, setRelaySets] = useState<TRelaySet[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
if (IS_COMMUNITY_MODE) {
|
||||
setRelaySets(COMMUNITY_RELAY_SETS)
|
||||
return
|
||||
}
|
||||
if (!favoriteRelaysEvent) {
|
||||
const favoriteRelays: string[] = []
|
||||
const storedRelaySets = storage.getRelaySets()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { IS_COMMUNITY_MODE, COMMUNITY_RELAY_SETS, COMMUNITY_RELAYS } from '@/constants'
|
||||
import { getRelaySetFromEvent } from '@/lib/event-metadata'
|
||||
import { isWebsocketUrl, normalizeUrl } from '@/lib/url'
|
||||
import indexedDb from '@/services/indexed-db.service'
|
||||
import storage from '@/services/local-storage.service'
|
||||
import { TFeedInfo, TFeedType } from '@/types'
|
||||
import { TFeedInfo, TFeedType, TRelaySet } from '@/types'
|
||||
import { kinds } from 'nostr-tools'
|
||||
import { createContext, useContext, useEffect, useRef, useState } from 'react'
|
||||
import { useFavoriteRelays } from './FavoriteRelaysProvider'
|
||||
|
|
@ -48,9 +49,21 @@ export function FeedProvider({ children }: { children: React.ReactNode }) {
|
|||
if (storedFeedInfo) {
|
||||
feedInfo = storedFeedInfo
|
||||
} else {
|
||||
feedInfo = { feedType: 'following' }
|
||||
if (!IS_COMMUNITY_MODE) {
|
||||
feedInfo = { feedType: 'following' }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!feedInfo && IS_COMMUNITY_MODE) {
|
||||
feedInfo =
|
||||
COMMUNITY_RELAY_SETS.length > 0
|
||||
? {
|
||||
feedType: 'relays',
|
||||
id: COMMUNITY_RELAY_SETS[0].id,
|
||||
name: COMMUNITY_RELAY_SETS[0].name
|
||||
}
|
||||
: { feedType: 'relay', id: COMMUNITY_RELAYS[0] }
|
||||
}
|
||||
|
||||
if (feedInfo?.feedType === 'relays') {
|
||||
return await switchFeed('relays', { activeRelaySetId: feedInfo.id })
|
||||
|
|
@ -109,24 +122,33 @@ export function FeedProvider({ children }: { children: React.ReactNode }) {
|
|||
}
|
||||
if (feedType === 'relays') {
|
||||
const relaySetId = options.activeRelaySetId ?? (relaySets.length > 0 ? relaySets[0].id : null)
|
||||
if (!relaySetId || !pubkey) {
|
||||
setIsReady(true)
|
||||
return
|
||||
}
|
||||
let relaySet: TRelaySet | null = null
|
||||
if (IS_COMMUNITY_MODE) {
|
||||
relaySet =
|
||||
COMMUNITY_RELAY_SETS.find((set) => set.id === relaySetId) ??
|
||||
(COMMUNITY_RELAY_SETS.length > 0 ? COMMUNITY_RELAY_SETS[0] : null)
|
||||
} else {
|
||||
if (!relaySetId || !pubkey) {
|
||||
setIsReady(true)
|
||||
return
|
||||
}
|
||||
|
||||
let relaySet =
|
||||
relaySets.find((set) => set.id === relaySetId) ??
|
||||
(relaySets.length > 0 ? relaySets[0] : null)
|
||||
if (!relaySet) {
|
||||
const storedRelaySetEvent = await indexedDb.getReplaceableEvent(
|
||||
pubkey,
|
||||
kinds.Relaysets,
|
||||
relaySetId
|
||||
)
|
||||
if (storedRelaySetEvent) {
|
||||
relaySet = getRelaySetFromEvent(storedRelaySetEvent)
|
||||
relaySet =
|
||||
relaySets.find((set) => set.id === relaySetId) ??
|
||||
(relaySets.length > 0 ? relaySets[0] : null)
|
||||
|
||||
if (!relaySet) {
|
||||
const storedRelaySetEvent = await indexedDb.getReplaceableEvent(
|
||||
pubkey,
|
||||
kinds.Relaysets,
|
||||
relaySetId
|
||||
)
|
||||
if (storedRelaySetEvent) {
|
||||
relaySet = getRelaySetFromEvent(storedRelaySetEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (relaySet) {
|
||||
const newFeedInfo = { feedType, id: relaySet.id }
|
||||
setFeedInfo(newFeedInfo)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import BookmarkPage from '@/pages/primary/BookmarkPage'
|
||||
import ExplorePage from '@/pages/primary/ExplorePage'
|
||||
import FollowingPage from '@/pages/primary/FollowingPage'
|
||||
import MePage from '@/pages/primary/MePage'
|
||||
import NoteListPage from '@/pages/primary/NoteListPage'
|
||||
import NotificationListPage from '@/pages/primary/NotificationListPage'
|
||||
|
|
@ -13,6 +14,7 @@ import { createRef } from 'react'
|
|||
const PRIMARY_ROUTE_CONFIGS = [
|
||||
{ key: 'home', component: NoteListPage },
|
||||
{ key: 'explore', component: ExplorePage },
|
||||
{ key: 'following', component: FollowingPage },
|
||||
{ key: 'notifications', component: NotificationListPage },
|
||||
{ key: 'me', component: MePage },
|
||||
{ key: 'profile', component: ProfilePage },
|
||||
|
|
|
|||
2
src/types/index.d.ts
vendored
2
src/types/index.d.ts
vendored
|
|
@ -114,7 +114,7 @@ export type TAccount = {
|
|||
export type TAccountPointer = Pick<TAccount, 'pubkey' | 'signerType'>
|
||||
|
||||
export type TFeedType = 'following' | 'pinned' | 'relays' | 'relay'
|
||||
export type TFeedInfo = { feedType: TFeedType; id?: string } | null
|
||||
export type TFeedInfo = { feedType: TFeedType; id?: string; name?: string } | null
|
||||
|
||||
export type TLanguage = 'en' | 'zh' | 'pl'
|
||||
|
||||
|
|
|
|||
4
src/vite-env.d.ts
vendored
4
src/vite-env.d.ts
vendored
|
|
@ -6,3 +6,7 @@ declare global {
|
|||
nostr?: TNip07
|
||||
}
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv
|
||||
}
|
||||
|
|
|
|||
144
vite.config.ts
144
vite.config.ts
|
|
@ -1,9 +1,10 @@
|
|||
import react from '@vitejs/plugin-react'
|
||||
import { execSync } from 'child_process'
|
||||
import path from 'path'
|
||||
import { defineConfig } from 'vite'
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import { VitePWA } from 'vite-plugin-pwa'
|
||||
import packageJson from './package.json'
|
||||
import { normalizeUrl } from './src/lib/url'
|
||||
|
||||
const getGitHash = () => {
|
||||
try {
|
||||
|
|
@ -24,70 +25,81 @@ const getAppVersion = () => {
|
|||
}
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
define: {
|
||||
'import.meta.env.GIT_COMMIT': getGitHash(),
|
||||
'import.meta.env.APP_VERSION': getAppVersion()
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src')
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
react(),
|
||||
VitePWA({
|
||||
registerType: 'autoUpdate',
|
||||
workbox: {
|
||||
globPatterns: ['**/*.{js,css,html,png,jpg,svg}'],
|
||||
globDirectory: 'dist/',
|
||||
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
|
||||
cleanupOutdatedCaches: true
|
||||
},
|
||||
devOptions: {
|
||||
enabled: true
|
||||
},
|
||||
manifest: {
|
||||
name: 'Jumble',
|
||||
short_name: 'Jumble',
|
||||
icons: [
|
||||
{
|
||||
src: '/pwa-512x512.png',
|
||||
sizes: '512x512',
|
||||
type: 'image/png',
|
||||
purpose: 'any'
|
||||
},
|
||||
{
|
||||
src: '/pwa-192x192.png',
|
||||
sizes: '192x192',
|
||||
type: 'image/png',
|
||||
purpose: 'any'
|
||||
},
|
||||
{
|
||||
src: '/pwa-512x512.png',
|
||||
sizes: '512x512',
|
||||
type: 'image/png',
|
||||
purpose: 'maskable'
|
||||
},
|
||||
{
|
||||
src: '/pwa-192x192.png',
|
||||
sizes: '192x192',
|
||||
type: 'image/png',
|
||||
purpose: 'maskable'
|
||||
},
|
||||
{
|
||||
src: '/pwa-monochrome.svg',
|
||||
sizes: '512x512',
|
||||
type: 'image/svg+xml',
|
||||
purpose: 'monochrome'
|
||||
}
|
||||
],
|
||||
start_url: '/',
|
||||
display: 'standalone',
|
||||
background_color: '#FFFFFF',
|
||||
theme_color: '#FFFFFF',
|
||||
description: packageJson.description
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), '')
|
||||
|
||||
return {
|
||||
define: {
|
||||
'import.meta.env.GIT_COMMIT': getGitHash(),
|
||||
'import.meta.env.APP_VERSION': getAppVersion(),
|
||||
'import.meta.env.VITE_COMMUNITY_RELAY_SETS': JSON.parse(
|
||||
JSON.stringify(env.VITE_COMMUNITY_RELAY_SETS ?? '[]')
|
||||
),
|
||||
'import.meta.env.VITE_COMMUNITY_RELAYS': (env.VITE_COMMUNITY_RELAYS ?? '')
|
||||
.split(',')
|
||||
.map((url) => normalizeUrl(url))
|
||||
.filter(Boolean)
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src')
|
||||
}
|
||||
})
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
react(),
|
||||
VitePWA({
|
||||
registerType: 'autoUpdate',
|
||||
workbox: {
|
||||
globPatterns: ['**/*.{js,css,html,png,jpg,svg}'],
|
||||
globDirectory: 'dist/',
|
||||
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
|
||||
cleanupOutdatedCaches: true
|
||||
},
|
||||
devOptions: {
|
||||
enabled: true
|
||||
},
|
||||
manifest: {
|
||||
name: 'Jumble',
|
||||
short_name: 'Jumble',
|
||||
icons: [
|
||||
{
|
||||
src: '/pwa-512x512.png',
|
||||
sizes: '512x512',
|
||||
type: 'image/png',
|
||||
purpose: 'any'
|
||||
},
|
||||
{
|
||||
src: '/pwa-192x192.png',
|
||||
sizes: '192x192',
|
||||
type: 'image/png',
|
||||
purpose: 'any'
|
||||
},
|
||||
{
|
||||
src: '/pwa-512x512.png',
|
||||
sizes: '512x512',
|
||||
type: 'image/png',
|
||||
purpose: 'maskable'
|
||||
},
|
||||
{
|
||||
src: '/pwa-192x192.png',
|
||||
sizes: '192x192',
|
||||
type: 'image/png',
|
||||
purpose: 'maskable'
|
||||
},
|
||||
{
|
||||
src: '/pwa-monochrome.svg',
|
||||
sizes: '512x512',
|
||||
type: 'image/svg+xml',
|
||||
purpose: 'monochrome'
|
||||
}
|
||||
],
|
||||
start_url: '/',
|
||||
display: 'standalone',
|
||||
background_color: '#FFFFFF',
|
||||
theme_color: '#FFFFFF',
|
||||
description: packageJson.description
|
||||
}
|
||||
})
|
||||
]
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue