From 2b1fc188138ab4471edce8ab796948628b60914d Mon Sep 17 00:00:00 2001 From: Innei Date: Tue, 29 Jul 2025 01:08:34 +0800 Subject: [PATCH] fix(ai-daily): can not show entry paper click the daily entry link - Introduced EntryModalPreview for displaying entry content in a modal. - Added EntryMoreActions for handling additional actions related to entries. - Implemented EntryToastPreview to show a toast notification for entries. - Updated usePeekModal hook to integrate new components and streamline entry detail fetching. Signed-off-by: Innei --- .../ui/peek-modal/EntryModalPreview.tsx | 11 + .../ui/peek-modal/EntryMoreActions.tsx | 70 ++++++ .../ui/peek-modal/EntryToastPreview.tsx | 152 ++++++++++++ .../renderer/src/hooks/biz/usePeekModal.tsx | 233 +----------------- 4 files changed, 239 insertions(+), 227 deletions(-) create mode 100644 apps/desktop/layer/renderer/src/components/ui/peek-modal/EntryModalPreview.tsx create mode 100644 apps/desktop/layer/renderer/src/components/ui/peek-modal/EntryMoreActions.tsx create mode 100644 apps/desktop/layer/renderer/src/components/ui/peek-modal/EntryToastPreview.tsx diff --git a/apps/desktop/layer/renderer/src/components/ui/peek-modal/EntryModalPreview.tsx b/apps/desktop/layer/renderer/src/components/ui/peek-modal/EntryModalPreview.tsx new file mode 100644 index 000000000..15a48db77 --- /dev/null +++ b/apps/desktop/layer/renderer/src/components/ui/peek-modal/EntryModalPreview.tsx @@ -0,0 +1,11 @@ +import { Paper } from "~/components/ui/paper" +import { EntryContent } from "~/modules/entry-content/components/entry-content" + +export const EntryModalPreview = ({ entryId }: { entryId: string }) => ( + + + +) diff --git a/apps/desktop/layer/renderer/src/components/ui/peek-modal/EntryMoreActions.tsx b/apps/desktop/layer/renderer/src/components/ui/peek-modal/EntryMoreActions.tsx new file mode 100644 index 000000000..41fdae708 --- /dev/null +++ b/apps/desktop/layer/renderer/src/components/ui/peek-modal/EntryMoreActions.tsx @@ -0,0 +1,70 @@ +import { RootPortal } from "@follow/components/ui/portal/index.js" +import type { FC } from "react" +import { useMemo } from "react" + +import { MenuItemText } from "~/atoms/context-menu" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuTrigger, +} from "~/components/ui/dropdown-menu/dropdown-menu" +import { useSortedEntryActions } from "~/hooks/biz/useEntryActions" +import { getRouteParams } from "~/hooks/biz/useRouteParams" +import { COMMAND_ID } from "~/modules/command/commands/id" +import { hasCommand } from "~/modules/command/hooks/use-command" +import { CommandDropdownMenuItem } from "~/modules/entry-content/actions/more-actions" + +export const EntryMoreActions: FC<{ entryId: string }> = ({ entryId }) => { + const { view } = getRouteParams() + const { moreAction, mainAction } = useSortedEntryActions({ entryId, view }) + + const actionConfigs = useMemo( + () => + [...moreAction, ...mainAction].filter( + (action) => action instanceof MenuItemText && hasCommand(action.id), + ), + [moreAction, mainAction], + ) + + const availableActions = useMemo( + () => + actionConfigs.filter( + (item) => item instanceof MenuItemText && item.id !== COMMAND_ID.settings.customizeToolbar, + ), + [actionConfigs], + ) + + const extraAction = useMemo( + () => + actionConfigs.filter( + (item) => item instanceof MenuItemText && item.id === COMMAND_ID.settings.customizeToolbar, + ), + [actionConfigs], + ) + + if (availableActions.length === 0 && extraAction.length === 0) { + return null + } + + return ( + + + + + + + {availableActions.map((config) => + config instanceof MenuItemText ? ( + + ) : null, + )} + + + + ) +} diff --git a/apps/desktop/layer/renderer/src/components/ui/peek-modal/EntryToastPreview.tsx b/apps/desktop/layer/renderer/src/components/ui/peek-modal/EntryToastPreview.tsx new file mode 100644 index 000000000..9b2652914 --- /dev/null +++ b/apps/desktop/layer/renderer/src/components/ui/peek-modal/EntryToastPreview.tsx @@ -0,0 +1,152 @@ +import { Spring } from "@follow/components/constants/spring.js" +import { useIsEntryStarred } from "@follow/store/collection/hooks" +import { useEntry, usePrefetchEntryDetail } from "@follow/store/entry/hooks" +import { useFeedById } from "@follow/store/feed/hooks" +import { nextFrame, stopPropagation } from "@follow/utils/dom" +import { cn } from "@follow/utils/utils" +import type { Variant } from "motion/react" +import { m, useAnimationControls } from "motion/react" +import { useEffect } from "react" + +import { RelativeTime } from "~/components/ui/datetime" +import { usePreviewMedia } from "~/components/ui/media/hooks" +import { Media } from "~/components/ui/media/Media" +import { StarIcon } from "~/modules/entry-column/star-icon" +import type { FeedIconEntry } from "~/modules/feed/feed-icon" +import { FeedIcon } from "~/modules/feed/feed-icon" + +const variants: Record = { + enter: { + x: 0, + opacity: 1, + }, + initial: { + x: 700, + opacity: 0.9, + }, + exit: { + x: 750, + opacity: 0, + }, +} + +export const EntryToastPreview = ({ entryId }: { entryId: string }) => { + usePrefetchEntryDetail(entryId) + + const entry = useEntry(entryId, (state) => { + const { feedId } = state + const { author, authorAvatar, description, publishedAt } = state + + const media = state.media || [] + const firstPhotoUrl = media.find((a) => a.type === "photo")?.url + const iconEntry: FeedIconEntry = { + firstPhotoUrl, + authorAvatar, + } + + return { + author, + description, + feedId, + iconEntry, + media, + publishedAt, + } + }) + const isInCollection = useIsEntryStarred(entryId) + + const feed = useFeedById(entry?.feedId) + const controller = useAnimationControls() + + const isDisplay = !!entry && !!feed + useEffect(() => { + if (isDisplay) { + nextFrame(() => controller.start("enter")) + } + }, [controller, isDisplay]) + + const previewMedia = usePreviewMedia() + + if (!isDisplay) return null + + return ( + +
+ +
+
+ {entry.author} + · + + + +
+
+
+ {entry.description} + + {!!entry.media?.length && ( +
+ {entry.media.map((media, i, mediaList) => ( + { + e.stopPropagation() + previewMedia(mediaList, i) + }} + /> + ))} +
+ )} +
+ {isInCollection && } +
+ + {/* End right column */} +
+
+
+ ) +} diff --git a/apps/desktop/layer/renderer/src/hooks/biz/usePeekModal.tsx b/apps/desktop/layer/renderer/src/hooks/biz/usePeekModal.tsx index 895ba7482..0da2a2992 100644 --- a/apps/desktop/layer/renderer/src/hooks/biz/usePeekModal.tsx +++ b/apps/desktop/layer/renderer/src/hooks/biz/usePeekModal.tsx @@ -1,37 +1,13 @@ -import { Spring } from "@follow/components/constants/spring.js" -import { RootPortal } from "@follow/components/ui/portal/index.js" -import { useIsEntryStarred } from "@follow/store/collection/hooks" import { useEntry, usePrefetchEntryDetail } from "@follow/store/entry/hooks" -import { useFeedById } from "@follow/store/feed/hooks" -import { nextFrame, stopPropagation } from "@follow/utils/dom" -import { cn } from "@follow/utils/utils" -import type { Variant } from "motion/react" -import { m, useAnimationControls } from "motion/react" -import type { FC } from "react" -import { useCallback, useEffect, useMemo } from "react" +import { useCallback } from "react" -import { MenuItemText } from "~/atoms/context-menu" -import { RelativeTime } from "~/components/ui/datetime" -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuTrigger, -} from "~/components/ui/dropdown-menu/dropdown-menu" -import { usePreviewMedia } from "~/components/ui/media/hooks" -import { Media } from "~/components/ui/media/Media" import { PeekModal } from "~/components/ui/modal/inspire/PeekModal" import { PlainModal } from "~/components/ui/modal/stacked/custom-modal" import { useModalStack } from "~/components/ui/modal/stacked/hooks" -import { Paper } from "~/components/ui/paper" -import { useSortedEntryActions } from "~/hooks/biz/useEntryActions" +import { EntryModalPreview } from "~/components/ui/peek-modal/EntryModalPreview" +import { EntryMoreActions } from "~/components/ui/peek-modal/EntryMoreActions" +import { EntryToastPreview } from "~/components/ui/peek-modal/EntryToastPreview" import { getRouteParams } from "~/hooks/biz/useRouteParams" -import { COMMAND_ID } from "~/modules/command/commands/id" -import { hasCommand } from "~/modules/command/hooks/use-command" -import { StarIcon } from "~/modules/entry-column/star-icon" -import { CommandDropdownMenuItem } from "~/modules/entry-content/actions/more-actions" -import { EntryContent } from "~/modules/entry-content/components/entry-content" -import type { FeedIconEntry } from "~/modules/feed/feed-icon" -import { FeedIcon } from "~/modules/feed/feed-icon" export const usePeekModal = () => { const { present } = useModalStack() @@ -59,7 +35,9 @@ export const usePeekModal = () => { "relative mx-auto mt-[10vh] scrollbar-none max-w-full overflow-auto px-2 lg:max-w-[65rem] lg:p-0", CustomModalComponent: ({ children }) => { + usePrefetchEntryDetail(entryId) const feedId = useEntry(entryId, (state) => state.feedId) + if (!feedId) return null return ( @@ -85,202 +63,3 @@ export const usePeekModal = () => { [present], ) } - -const variants: Record = { - enter: { - x: 0, - opacity: 1, - }, - initial: { - x: 700, - opacity: 0.9, - }, - exit: { - x: 750, - opacity: 0, - }, -} -const EntryToastPreview = ({ entryId }: { entryId: string }) => { - usePrefetchEntryDetail(entryId) - - const entry = useEntry(entryId, (state) => { - const { feedId } = state - const { author, authorAvatar, description, publishedAt } = state - - const media = state.media || [] - const firstPhotoUrl = media.find((a) => a.type === "photo")?.url - const iconEntry: FeedIconEntry = { - firstPhotoUrl, - authorAvatar, - } - - return { - author, - description, - feedId, - iconEntry, - media, - publishedAt, - } - }) - const isInCollection = useIsEntryStarred(entryId) - - const feed = useFeedById(entry?.feedId) - const controller = useAnimationControls() - - const isDisplay = !!entry && !!feed - useEffect(() => { - if (isDisplay) { - nextFrame(() => controller.start("enter")) - } - }, [controller, isDisplay]) - - const previewMedia = usePreviewMedia() - - if (!isDisplay) return null - - return ( - -
- -
-
- {entry.author} - · - - - -
-
-
- {entry.description} - - {!!entry.media?.length && ( -
- {entry.media.map((media, i, mediaList) => ( - { - e.stopPropagation() - previewMedia(mediaList, i) - }} - /> - ))} -
- )} -
- {isInCollection && } -
- - {/* End right column */} -
-
-
- ) -} - -const EntryModalPreview = ({ entryId }: { entryId: string }) => ( - - - -) - -const EntryMoreActions: FC<{ entryId: string }> = ({ entryId }) => { - const { view } = getRouteParams() - const { moreAction, mainAction } = useSortedEntryActions({ entryId, view }) - - const actionConfigs = useMemo( - () => - [...moreAction, ...mainAction].filter( - (action) => action instanceof MenuItemText && hasCommand(action.id), - ), - [moreAction, mainAction], - ) - - const availableActions = useMemo( - () => - actionConfigs.filter( - (item) => item instanceof MenuItemText && item.id !== COMMAND_ID.settings.customizeToolbar, - ), - [actionConfigs], - ) - - const extraAction = useMemo( - () => - actionConfigs.filter( - (item) => item instanceof MenuItemText && item.id === COMMAND_ID.settings.customizeToolbar, - ), - [actionConfigs], - ) - - if (availableActions.length === 0 && extraAction.length === 0) { - return null - } - - return ( - - - - - - - {availableActions.map((config) => - config instanceof MenuItemText ? ( - - ) : null, - )} - - - - ) -}