refactor: enhance timeline handling and view parsing

- Updated `useNavigateEntry` to utilize `getTimelineIdByView` for better clarity in timeline ID generation.
- Introduced `VIEW_SLUG_BY_VIEW` and `VIEW_PARAM_ALIAS_MAP` in `useRouteParams` for improved mapping of feed views to their respective routes.
- Refactored `computeTimelineTabLists` in `useTimelineList` to normalize and filter timeline IDs, enhancing the handling of known timeline values.

This refactor improves maintainability and readability of the timeline navigation logic.

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei 2025-11-07 21:32:26 +08:00
parent bffe2cd0df
commit 4a1fb17eae
No known key found for this signature in database
GPG Key ID: 0F62D33977F021F7
3 changed files with 79 additions and 19 deletions

View File

@ -1,7 +1,7 @@
import { getReadonlyRoute, getStableRouterNavigate } from "@follow/components/atoms/route.js"
import { useMobile } from "@follow/components/hooks/useMobile.js"
import { useSheetContext } from "@follow/components/ui/sheet/context.js"
import { FeedViewType } from "@follow/constants"
import type { FeedViewType } from "@follow/constants"
import { getEntry } from "@follow/store/entry/getter"
import { getSubscriptionByFeedId } from "@follow/store/subscription/getter"
import { tracker } from "@follow/tracker"
@ -18,11 +18,9 @@ import {
ROUTE_FEED_IN_INBOX,
ROUTE_FEED_IN_LIST,
ROUTE_FEED_PENDING,
ROUTE_TIMELINE_OF_VIEW,
ROUTE_VIEW_ALL,
} from "~/constants"
import { useRouteParamsSelector } from "./useRouteParams"
import { getTimelineIdByView, useRouteParamsSelector } from "./useRouteParams"
export type NavigateEntryOptions = Partial<{
timelineId: string
@ -86,8 +84,7 @@ const parseNavigateEntryOptions = (options: NavigateEntryOptions): ParsedNavigat
finalFeedId = encodeURIComponent(finalFeedId)
if (finalView !== undefined && !timelineId) {
finalTimelineId =
finalView === FeedViewType.All ? ROUTE_VIEW_ALL : `${ROUTE_TIMELINE_OF_VIEW}${finalView}`
finalTimelineId = getTimelineIdByView(finalView)
}
return {
@ -109,7 +106,7 @@ export function getNavigateEntryPath(options: NavigateEntryOptions | ParsedNavig
/*
* /timeline/:timelineId/:feedId/:entryId
* timelineId: view-1
* timelineId: articles | social-media | view-1 (legacy) | ...
* feedId: xxx, folder-xxx, list-xxx, inbox-xxx
* entryId: xxx
*/

View File

@ -43,14 +43,59 @@ export interface BizRouteParams {
timelineId?: string
}
const VIEW_SLUG_BY_VIEW: Record<FeedViewType, string> = {
[FeedViewType.All]: ROUTE_VIEW_ALL,
[FeedViewType.Articles]: "articles",
[FeedViewType.SocialMedia]: "social-media",
[FeedViewType.Pictures]: "pictures",
[FeedViewType.Videos]: "videos",
[FeedViewType.Audios]: "audios",
[FeedViewType.Notifications]: "notifications",
}
const VIEW_PARAM_ALIAS_MAP: Record<string, FeedViewType> = Object.entries(VIEW_SLUG_BY_VIEW).reduce(
(acc, [view, slug]) => {
if (slug === ROUTE_VIEW_ALL) return acc
const numericView = Number(view)
if (Number.isNaN(numericView)) return acc
acc[slug] = numericView as FeedViewType
return acc
},
{} as Record<string, FeedViewType>,
)
const FEED_VIEW_VALUES = new Set<FeedViewType>(
Object.values(FeedViewType).filter((value): value is FeedViewType => typeof value === "number"),
)
const isFeedViewTypeValue = (value: number): value is FeedViewType =>
Number.isInteger(value) && FEED_VIEW_VALUES.has(value as FeedViewType)
export const getTimelineIdByView = (view: FeedViewType) =>
VIEW_SLUG_BY_VIEW[view] ?? `${ROUTE_TIMELINE_OF_VIEW}${view}`
export function parseView(input: string | undefined): FeedViewType | undefined {
if (input === ROUTE_VIEW_ALL) return FeedViewType.All
if (input?.startsWith(ROUTE_TIMELINE_OF_VIEW)) {
const view = Number.parseInt(input?.slice(ROUTE_TIMELINE_OF_VIEW.length), 10)
if (Object.values(FeedViewType).includes(view)) {
return view as FeedViewType
if (!input) return undefined
const normalizedInput = input.toLowerCase()
if (normalizedInput === ROUTE_VIEW_ALL) return FeedViewType.All
const aliasView = VIEW_PARAM_ALIAS_MAP[normalizedInput]
if (aliasView !== undefined) return aliasView
if (normalizedInput.startsWith(ROUTE_TIMELINE_OF_VIEW)) {
const view = Number.parseInt(normalizedInput.slice(ROUTE_TIMELINE_OF_VIEW.length), 10)
if (isFeedViewTypeValue(view)) {
return view
}
}
const numericView = Number.parseInt(normalizedInput, 10)
if (isFeedViewTypeValue(numericView)) {
return numericView
}
}
const parseRouteParams = (params: Params<any>, _searchParams: URLSearchParams): BizRouteParams => {

View File

@ -4,12 +4,29 @@ import { useSubscriptionStore } from "@follow/store/subscription/store"
import { useMemo } from "react"
import { useUISettingKey } from "~/atoms/settings/ui"
import { ROUTE_TIMELINE_OF_VIEW, ROUTE_VIEW_ALL } from "~/constants/app"
import { ROUTE_VIEW_ALL } from "~/constants/app"
import { getTimelineIdByView, parseView } from "./useRouteParams"
const ALL_TIMELINE_IDS = getViewList({ includeAll: true }).map((view) =>
view.view === FeedViewType.All ? ROUTE_VIEW_ALL : `${ROUTE_TIMELINE_OF_VIEW}${view.view}`,
getTimelineIdByView(view.view),
)
const normalizeTimelineId = (id: string) => {
const view = parseView(id)
return view !== undefined ? getTimelineIdByView(view) : id
}
const filterKnownTimelineIds = (ids: string[]) => {
const seen = new Set<string>()
return ids.filter((id) => {
if (!ALL_TIMELINE_IDS.includes(id)) return false
if (seen.has(id)) return false
seen.add(id)
return true
})
}
export const computeTimelineTabLists = ({
timelineTabs,
hasAudiosSubscription,
@ -19,16 +36,17 @@ export const computeTimelineTabLists = ({
hasAudiosSubscription: boolean
hasNotificationsSubscription: boolean
}) => {
const savedVisible = (timelineTabs?.visible ?? []).filter((id) => ALL_TIMELINE_IDS.includes(id))
const savedHidden = (timelineTabs?.hidden ?? []).filter((id) => ALL_TIMELINE_IDS.includes(id))
const savedVisible = filterKnownTimelineIds(
(timelineTabs?.visible ?? []).map(normalizeTimelineId),
)
const savedHidden = filterKnownTimelineIds((timelineTabs?.hidden ?? []).map(normalizeTimelineId))
const extras = ALL_TIMELINE_IDS.filter(
(id) => !savedVisible.includes(id) && !savedHidden.includes(id),
)
const isDefaultHidden = (id: string) => {
if (id === `${ROUTE_TIMELINE_OF_VIEW}${FeedViewType.Audios}`) return !hasAudiosSubscription
if (id === `${ROUTE_TIMELINE_OF_VIEW}${FeedViewType.Notifications}`)
return !hasNotificationsSubscription
if (id === getTimelineIdByView(FeedViewType.Audios)) return !hasAudiosSubscription
if (id === getTimelineIdByView(FeedViewType.Notifications)) return !hasNotificationsSubscription
return false
}