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:
parent
9a90b2cb4d
commit
37e43ee32a
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -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]),
|
||||
)
|
||||
}
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in New Issue