refactor(navigation): improve route parameter handling and introduce isPreviewFeed hook

- Updated `useNavigateEntry` to fix a typo in variable name.
- Enhanced `parseRouteParams` to accept search parameters and removed the isPreview logic from it.
- Introduced `useIsPreviewFeed` hook to encapsulate preview logic based on route parameters.
- Updated components to utilize the new `useIsPreviewFeed` hook for cleaner code and better separation of concerns.

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei 2025-05-07 21:55:03 +08:00
parent 9a90b2cb4d
commit 37e43ee32a
No known key found for this signature in database
GPG Key ID: 0F62D33977F021F7
7 changed files with 159 additions and 129 deletions

View File

@ -58,8 +58,8 @@ export const navigateEntry = (options: NavigateEntryOptions) => {
let finalFeedId = feedId || params.feedId || ROUTE_FEED_PENDING
let finalTimelineId = timelineId || params.timelineId || ROUTE_FEED_PENDING
const finalEntryId = entryId || ROUTE_ENTRY_PENDING
const subsctiption = getSubscriptionByFeedId(finalFeedId)
const finalView = subsctiption?.view || view
const subscription = getSubscriptionByFeedId(finalFeedId)
const finalView = subscription?.view || view
if (backPath) {
setPreviewBackPath(backPath)

View File

@ -4,7 +4,6 @@ import {
useReadonlyRouteSelector,
} from "@follow/components/atoms/route.js"
import { FeedViewType } from "@follow/constants"
import { isBizId } from "@follow/utils/utils"
import { useMemo } from "react"
import type { Params } from "react-router"
import { useParams } from "react-router"
@ -19,7 +18,6 @@ import {
ROUTE_TIMELINE_OF_VIEW,
} from "~/constants"
import { getListById } from "~/store/list"
import { getSubscriptionByFeedId } from "~/store/subscription"
export const useRouteEntryId = () => {
const { entryId } = useParams()
@ -42,22 +40,14 @@ export interface BizRouteParams {
inboxId?: string
listId?: string
timelineId?: string
isPreview?: boolean
}
const parseRouteParams = (params: Params<any>): BizRouteParams => {
const parseRouteParams = (params: Params<any>, _searchParams: URLSearchParams): BizRouteParams => {
const listId = params.feedId?.startsWith(ROUTE_FEED_IN_LIST)
? params.feedId.slice(ROUTE_FEED_IN_LIST.length)
: undefined
const list = listId ? getListById(listId) : undefined
let isPreview = false
if (listId) {
isPreview = !getSubscriptionByFeedId(listId)
} else if (params.feedId) {
isPreview = isBizId(params.feedId) && !getSubscriptionByFeedId(params.feedId)
}
return {
view: params.timelineId?.startsWith(ROUTE_TIMELINE_OF_VIEW)
? (Number.parseInt(
@ -79,13 +69,15 @@ const parseRouteParams = (params: Params<any>): BizRouteParams => {
: undefined,
listId,
timelineId: params.timelineId,
isPreview,
}
}
export const useRouteParams = () => {
const route = useReadonlyRoute()
return useMemo(() => parseRouteParams(route.params), [route.params])
return useMemo(
() => parseRouteParams(route.params, route.searchParams),
[route.params, route.searchParams],
)
}
const noop = [] as any[]
@ -95,13 +87,13 @@ export const useRouteParamsSelector = <T>(
deps = noop,
): T =>
useReadonlyRouteSelector((route) => {
const { params } = route
const { params, searchParams } = route
return selector(parseRouteParams(params))
return selector(parseRouteParams(params, searchParams))
}, deps)
export const getRouteParams = () => {
const route = getReadonlyRoute()
const { params } = route
return parseRouteParams(params)
const { params, searchParams } = route
return parseRouteParams(params, searchParams)
}

View File

@ -14,6 +14,8 @@ import { entryActions, getEntry, useEntryIdsByFeedIdOrView } from "~/store/entry
import { useFolderFeedsByFeedId } from "~/store/subscription"
import { feedUnreadActions } from "~/store/unread"
import { useIsPreviewFeed } from "./useIsPreviewFeed"
interface UseEntriesReturn {
entriesIds: string[]
hasNext: boolean
@ -46,7 +48,8 @@ const fallbackReturn: UseEntriesReturn = {
error: null,
}
const useRemoteEntries = (): UseEntriesReturn => {
const { feedId, view, inboxId, listId, isPreview } = useRouteParams()
const { feedId, view, inboxId, listId } = useRouteParams()
const isPreview = useIsPreviewFeed()
const unreadOnly = useGeneralSettingKey("unreadOnly")

View File

@ -0,0 +1,22 @@
import { isBizId } from "@follow/utils/utils"
import { useCallback } from "react"
import { useRouteParamsSelector } from "~/hooks/biz/useRouteParams"
import { getSubscriptionByFeedId, useSubscriptionStore } from "~/store/subscription"
export const useIsPreviewFeed = () => {
const listId = useRouteParamsSelector((s) => s.listId)
const feedId = useRouteParamsSelector((s) => s.feedId)
return useSubscriptionStore(
useCallback(() => {
let isPreview = false
if (listId) {
isPreview = !getSubscriptionByFeedId(listId)
} else if (feedId) {
isPreview = isBizId(feedId) && !getSubscriptionByFeedId(feedId)
}
return isPreview
}, [listId, feedId]),
)
}

View File

@ -1,4 +1,4 @@
import { ActionButton, Button } from "@follow/components/ui/button/index.js"
import { ActionButton } from "@follow/components/ui/button/index.js"
import { DividerVertical } from "@follow/components/ui/divider/index.js"
import { RotatingRefreshIcon } from "@follow/components/ui/loading/index.jsx"
import { EllipsisHorizontalTextWithTooltip } from "@follow/components/ui/typography/index.js"
@ -17,12 +17,13 @@ import { useWhoami } from "~/atoms/user"
import { FEED_COLLECTION_LIST, ROUTE_ENTRY_PENDING } from "~/constants"
import { shortcuts } from "~/constants/shortcuts"
import { useFollow } from "~/hooks/biz/useFollow"
import { useRouteParams } from "~/hooks/biz/useRouteParams"
import { getRouteParams, useRouteParams } from "~/hooks/biz/useRouteParams"
import { EntryHeader } from "~/modules/entry-content/header"
import { useRefreshFeedMutation } from "~/queries/feed"
import { useFeedById, useFeedHeaderTitle } from "~/store/feed"
import { getFeedById, useFeedById, useFeedHeaderTitle } from "~/store/feed"
import { MarkAllReadButton } from "../components/mark-all-button"
import { useIsPreviewFeed } from "../hooks/useIsPreviewFeed"
import {
AppendTaildingDivider,
DailyReportButton,
@ -37,11 +38,11 @@ export const EntryListHeader: FC<{
}> = ({ refetch, isRefreshing, hasUpdate }) => {
const routerParams = useRouteParams()
const { t } = useTranslation()
const { t: tCommon } = useTranslation("common")
const unreadOnly = useGeneralSettingKey("unreadOnly")
const { feedId, entryId, view, isPreview, listId } = routerParams
const { feedId, entryId, view } = routerParams
const isPreview = useIsPreviewFeed()
const headerTitle = useFeedHeaderTitle()
@ -61,123 +62,133 @@ export const EntryListHeader: FC<{
const feed = useFeedById(feedId)
const follow = useFollow()
const titleStyleBasedView = ["pl-6", "pl-7", "pl-7", "pl-7", "px-5", "pl-6"]
const feedColumnShow = useTimelineColumnShow()
const navigate = useNavigate()
return (
<div
className={cn(
"mb-2 flex w-full flex-col pr-4 pt-2.5 transition-[padding] duration-300 ease-in-out",
!feedColumnShow && "macos:mt-4 macos:pt-margin-macos-traffic-light-y",
titleStyleBasedView[view],
isPreview && "px-4",
)}
>
<div className={"flex w-full justify-between"}>
{titleInfo}
<div
className={cn(
"relative z-[1] flex items-center gap-1 self-baseline text-zinc-500",
(isInCollectionList || !headerTitle) && "pointer-events-none opacity-0",
{isPreview ? <PreviewHeaderInfoWrapper>{titleInfo}</PreviewHeaderInfoWrapper> : titleInfo}
{!isPreview && (
<div
className={cn(
"text-text-secondary relative z-[1] flex items-center gap-1 self-baseline",
(isInCollectionList || !headerTitle) && "pointer-events-none opacity-0",
"translate-x-[6px]",
)}
onClick={stopPropagation}
>
{views[view]!.wideMode && entryId && entryId !== ROUTE_ENTRY_PENDING && (
<>
<EntryHeader view={view} entryId={entryId} />
<DividerVertical className="mx-2 w-px" />
</>
)}
<AppendTaildingDivider>
{!views[view]!.wideMode && <WideModeButton />}
{view === FeedViewType.SocialMedia && <DailyReportButton />}
{view === FeedViewType.Pictures && <SwitchToMasonryButton />}
</AppendTaildingDivider>
{isOnline &&
(feed?.ownerUserId === user?.id &&
isBizId(routerParams.feedId!) &&
feed?.type === "feed" ? (
<ActionButton
tooltip="Refresh"
onClick={() => {
refreshFeed()
}}
>
<RotatingRefreshIcon isRefreshing={isPending} />
</ActionButton>
) : (
<ActionButton
tooltip={
hasUpdate
? t("entry_list_header.new_entries_available")
: t("entry_list_header.refetch")
}
onClick={() => {
refetch()
}}
>
<RotatingRefreshIcon
className={cn(hasUpdate && "text-accent")}
isRefreshing={isRefreshing}
/>
</ActionButton>
))}
<ActionButton
tooltip={
!unreadOnly
? t("entry_list_header.show_unread_only")
: t("entry_list_header.show_all")
}
shortcut={shortcuts.entries.toggleUnreadOnly.key}
onClick={() => setGeneralSetting("unreadOnly", !unreadOnly)}
>
{unreadOnly ? (
<i className="i-mgc-round-cute-fi" />
) : (
<i className="i-mgc-round-cute-re" />
"translate-x-[6px]",
)}
</ActionButton>
<MarkAllReadButton shortcut />
</div>
</div>
{isPreview && (
<div className="mt-4 flex items-center justify-center gap-2">
<Button
size="lg"
buttonClassName="flex-1 max-w-72"
variant="outline"
onClick={(e) => {
e.stopPropagation()
navigate(previewBackPath() || "/")
}}
onClick={stopPropagation}
>
{tCommon("words.back")}
</Button>
<Button
size="lg"
buttonClassName="flex-1 max-w-72"
onClick={() => {
follow({
isList: !!listId,
id: listId ?? feedId,
url: feed?.url,
})
}}
>
{tCommon("words.follow")}
</Button>
</div>
)}
{views[view]!.wideMode && entryId && entryId !== ROUTE_ENTRY_PENDING && (
<>
<EntryHeader view={view} entryId={entryId} />
<DividerVertical className="mx-2 w-px" />
</>
)}
{/* <TimelineTabs /> */}
<AppendTaildingDivider>
{!views[view]!.wideMode && <WideModeButton />}
{view === FeedViewType.SocialMedia && <DailyReportButton />}
{view === FeedViewType.Pictures && <SwitchToMasonryButton />}
</AppendTaildingDivider>
{isOnline &&
(feed?.ownerUserId === user?.id &&
isBizId(routerParams.feedId!) &&
feed?.type === "feed" ? (
<ActionButton
tooltip="Refresh"
onClick={() => {
refreshFeed()
}}
>
<RotatingRefreshIcon isRefreshing={isPending} />
</ActionButton>
) : (
<ActionButton
tooltip={
hasUpdate
? t("entry_list_header.new_entries_available")
: t("entry_list_header.refetch")
}
onClick={() => {
refetch()
}}
>
<RotatingRefreshIcon
className={cn(hasUpdate && "text-accent")}
isRefreshing={isRefreshing}
/>
</ActionButton>
))}
<ActionButton
tooltip={
!unreadOnly
? t("entry_list_header.show_unread_only")
: t("entry_list_header.show_all")
}
shortcut={shortcuts.entries.toggleUnreadOnly.key}
onClick={() => setGeneralSetting("unreadOnly", !unreadOnly)}
>
{unreadOnly ? (
<i className="i-mgc-round-cute-fi" />
) : (
<i className="i-mgc-round-cute-re" />
)}
</ActionButton>
<MarkAllReadButton shortcut />
</div>
)}
</div>
</div>
)
}
const PreviewHeaderInfoWrapper: Component = ({ children }) => {
const { t: tCommon } = useTranslation("common")
const follow = useFollow()
const navigate = useNavigate()
return (
<div className="grid w-full grid-cols-[auto_1fr_auto] items-center gap-2">
<button
type="button"
className="cursor-button text-text-secondary hover:text-accent inline-flex items-center gap-1 duration-200"
onClick={(e) => {
e.stopPropagation()
navigate(previewBackPath() || "/")
}}
>
<i className="i-mingcute-left-line size-4" />
{tCommon("words.back")}
</button>
<div className="relative flex justify-center">
<div className="absolute inset-0 flex items-center justify-center">{children}</div>
</div>
<button
type="button"
className="text-accent cursor-button hover:bg-fill-quaternary -mr-2 rounded px-2 py-0.5 font-semibold"
onClick={() => {
const { feedId, listId } = getRouteParams()
if (!feedId) return
const feed = getFeedById(feedId)
if (!feed) return
follow({
isList: !!listId,
id: listId ?? feedId,
url: feed.type === "feed" ? feed.url : undefined,
})
}}
>
{tCommon("words.follow")}
</button>
</div>
)
}

View File

@ -18,6 +18,7 @@ import {
useListsGroupedData,
} from "~/store/subscription"
import { useIsPreviewFeed } from "../entry-column/hooks/useIsPreviewFeed"
import {
resetSelectedFeedIds,
setFeedAreaScrollProgressValue,
@ -117,7 +118,8 @@ const FeedListImpl = ({ ref, className, view }: FeedListProps) => {
const shouldFreeUpSpace = useShouldFreeUpSpace()
const routerParams = useRouteParams()
const { listId, isPreview, feedId } = routerParams
const { listId, feedId } = routerParams
const isPreview = useIsPreviewFeed()
const isFeedPreview = isPreview && !listId
const isListPreview = isPreview && listId

View File

@ -23,7 +23,7 @@ export default resolveConfig(
"color-scheme": "light",
primary: "#007AFF", //#0A84FF
accent: "#ff760a",
accent: "#FF5C00",
"accent-content": "#fff",
neutral: "#212427",
@ -43,7 +43,7 @@ export default resolveConfig(
"color-scheme": "dark",
primary: "#0A84FF",
accent: "#ff760a",
accent: "#FF5C00",
"accent-content": "#fff",
neutral: "#2a2a2a",
"base-100": "#121212",