feat(timeline): add feed icon to timeline header
- Added `currentFeedTitle` prop to various entry components, including `EntryItemImpl`, `EntryVirtualListItem`, and `AllItem`, to improve context awareness in rendering. - Integrated `useFeedHeaderTitle` hook in `EntryList` to fetch the current feed title dynamically. - Updated related types and interfaces to accommodate the new prop, enhancing type safety and clarity. These changes aim to provide a more contextual user experience when interacting with entry items in the application. Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
parent
d4b1333617
commit
f067404151
|
|
@ -28,3 +28,4 @@ buildServer.json
|
|||
apps/desktop/build/appxmanifest.xml
|
||||
|
||||
.claude/settings.local.json
|
||||
.serena
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ const entrySelector = (state: EntryModel) => {
|
|||
video,
|
||||
}
|
||||
}
|
||||
export function AllItem({ entryId, translation }: UniversalItemProps) {
|
||||
export function AllItem({ entryId, translation, currentFeedTitle }: UniversalItemProps) {
|
||||
const entry = useEntry(entryId, entrySelector)
|
||||
const simple = true
|
||||
|
||||
|
|
@ -132,32 +132,37 @@ export function AllItem({ entryId, translation }: UniversalItemProps) {
|
|||
|
||||
const related = feed || inbox
|
||||
|
||||
const thisFeedTitle = getPreferredTitle(related, entry?.titleEntry)
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"cursor-menu group relative flex items-center py-2",
|
||||
"cursor-menu group relative flex items-start py-2",
|
||||
!isRead &&
|
||||
"before:bg-accent before:absolute before:-left-4 before:top-[14px] before:block before:size-2 before:rounded-full",
|
||||
)}
|
||||
>
|
||||
<FeedIcon target={related} fallback entry={entry?.iconEntry} size={20} />
|
||||
<div className={cn("flex h-fit min-w-0 flex-1 flex-row items-center text-sm leading-tight")}>
|
||||
<div
|
||||
className={cn(
|
||||
"mr-4 flex w-20 shrink-0 gap-1 text-xs",
|
||||
"text-text-secondary",
|
||||
isInCollection && "text-text-secondary",
|
||||
isRead && dimRead && "text-text-tertiary",
|
||||
)}
|
||||
>
|
||||
<EllipsisHorizontalTextWithTooltip className="truncate">
|
||||
<FeedTitle
|
||||
feed={related}
|
||||
title={getPreferredTitle(related, entry?.titleEntry)}
|
||||
className="space-x-0.5"
|
||||
/>
|
||||
</EllipsisHorizontalTextWithTooltip>
|
||||
{currentFeedTitle !== thisFeedTitle && (
|
||||
<div className="flex min-w-0 shrink-0 items-center gap-1">
|
||||
<FeedIcon target={related} fallback entry={entry?.iconEntry} size={20} />
|
||||
<div
|
||||
className={cn(
|
||||
"mr-4 flex w-20 shrink-0 gap-1 text-xs",
|
||||
"text-text-secondary",
|
||||
isInCollection && "text-text-secondary",
|
||||
isRead && dimRead && "text-text-tertiary",
|
||||
)}
|
||||
>
|
||||
<EllipsisHorizontalTextWithTooltip className="truncate">
|
||||
<FeedTitle
|
||||
feed={related}
|
||||
title={getPreferredTitle(related, entry?.titleEntry)}
|
||||
className="space-x-0.5"
|
||||
/>
|
||||
</EllipsisHorizontalTextWithTooltip>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className={cn("flex h-fit min-w-0 flex-1 flex-row items-start text-sm leading-tight")}>
|
||||
{entry.firstMedia && (
|
||||
<Tooltip>
|
||||
<TooltipRoot>
|
||||
|
|
@ -201,22 +206,26 @@ export function AllItem({ entryId, translation }: UniversalItemProps) {
|
|||
isRead && dimRead && "text-text-secondary",
|
||||
)}
|
||||
>
|
||||
{entry?.title ? (
|
||||
<EntryTranslation
|
||||
className={cn(
|
||||
"inline-flex min-w-0 items-center hyphens-auto font-medium",
|
||||
lineClamp.title,
|
||||
)}
|
||||
source={titleCase(entry?.title ?? "")}
|
||||
target={titleCase(translation?.title ?? "")}
|
||||
/>
|
||||
) : (
|
||||
<EntryTranslation
|
||||
className={cn("inline-flex items-center hyphens-auto", lineClamp.description)}
|
||||
source={entry?.description}
|
||||
target={translation?.description}
|
||||
/>
|
||||
)}
|
||||
<EllipsisHorizontalTextWithTooltip>
|
||||
{entry?.title ? (
|
||||
<EntryTranslation
|
||||
inline={false}
|
||||
className={cn(
|
||||
"flex min-w-0 flex-col justify-center hyphens-auto font-medium",
|
||||
lineClamp.title,
|
||||
)}
|
||||
source={titleCase(entry?.title ?? "")}
|
||||
target={titleCase(translation?.title ?? "")}
|
||||
/>
|
||||
) : (
|
||||
<EntryTranslation
|
||||
inline={false}
|
||||
className={cn("inline-flex items-center hyphens-auto", lineClamp.description)}
|
||||
source={entry?.description}
|
||||
target={translation?.description}
|
||||
/>
|
||||
)}
|
||||
</EllipsisHorizontalTextWithTooltip>
|
||||
{!!isInCollection && <StarIcon className="absolute right-0 top-0" />}
|
||||
</div>
|
||||
{!simple && (
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ interface VirtualRowItemProps {
|
|||
isStickyItem: boolean
|
||||
isActiveStickyItem: boolean
|
||||
measureElement: (element: Element | null) => void
|
||||
currentFeedTitle?: string
|
||||
}
|
||||
|
||||
const EntryHeadDateItem: FC<{
|
||||
|
|
@ -50,6 +51,7 @@ export const VirtualRowItem: FC<VirtualRowItemProps> = memo(
|
|||
isStickyItem,
|
||||
isActiveStickyItem,
|
||||
measureElement,
|
||||
currentFeedTitle,
|
||||
}) => {
|
||||
return (
|
||||
<Fragment key={virtualRowKey}>
|
||||
|
|
@ -76,6 +78,7 @@ export const VirtualRowItem: FC<VirtualRowItemProps> = memo(
|
|||
)}
|
||||
|
||||
<EntryVirtualListItem
|
||||
currentFeedTitle={currentFeedTitle}
|
||||
entryId={entriesIds[virtualRowIndex]!}
|
||||
view={view}
|
||||
data-index={virtualRowIndex}
|
||||
|
|
|
|||
|
|
@ -14,14 +14,13 @@ import type { EntryListItemFC } from "./types"
|
|||
interface EntryItemProps {
|
||||
entryId: string
|
||||
view: FeedViewType
|
||||
currentFeedTitle?: string
|
||||
}
|
||||
const EntryItemImpl = memo(function EntryItemImpl({
|
||||
entryId,
|
||||
view,
|
||||
}: {
|
||||
entryId: string
|
||||
view: FeedViewType
|
||||
}) {
|
||||
currentFeedTitle,
|
||||
}: EntryItemProps) {
|
||||
const enableTranslation = useGeneralSettingKey("translation")
|
||||
const actionLanguage = useActionLanguage()
|
||||
const translation = useEntryTranslation({
|
||||
|
|
@ -41,16 +40,16 @@ const EntryItemImpl = memo(function EntryItemImpl({
|
|||
|
||||
return (
|
||||
<EntryItemWrapper itemClassName={Item.wrapperClassName} entryId={entryId} view={view}>
|
||||
<Item entryId={entryId} translation={translation} />
|
||||
<Item entryId={entryId} translation={translation} currentFeedTitle={currentFeedTitle} />
|
||||
</EntryItemWrapper>
|
||||
)
|
||||
})
|
||||
|
||||
export const EntryItem: FC<EntryItemProps> = memo(({ entryId, view }) => {
|
||||
export const EntryItem: FC<EntryItemProps> = memo(({ entryId, view, currentFeedTitle }) => {
|
||||
const hasEntry = useHasEntry(entryId)
|
||||
|
||||
if (!hasEntry) return null
|
||||
return <EntryItemImpl entryId={entryId} view={view} />
|
||||
return <EntryItemImpl entryId={entryId} view={view} currentFeedTitle={currentFeedTitle} />
|
||||
})
|
||||
|
||||
export const EntryVirtualListItem = ({
|
||||
|
|
@ -58,6 +57,7 @@ export const EntryVirtualListItem = ({
|
|||
entryId,
|
||||
view,
|
||||
className,
|
||||
currentFeedTitle,
|
||||
...props
|
||||
}: EntryItemProps &
|
||||
React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> & {
|
||||
|
|
@ -69,7 +69,7 @@ export const EntryVirtualListItem = ({
|
|||
|
||||
return (
|
||||
<div className="absolute left-0 top-0 w-full will-change-transform" ref={ref} {...props}>
|
||||
<EntryItemImpl entryId={entryId} view={view} />
|
||||
<EntryItemImpl entryId={entryId} view={view} currentFeedTitle={currentFeedTitle} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,11 @@ import { useFollow } from "~/hooks/biz/useFollow"
|
|||
import { getRouteParams, useRouteParams } from "~/hooks/biz/useRouteParams"
|
||||
import { COMMAND_ID } from "~/modules/command/commands/id"
|
||||
import { useRunCommandFn } from "~/modules/command/hooks/use-command"
|
||||
import { useCommandShortcuts } from "~/modules/command/hooks/use-command-binding"
|
||||
import { useCommandShortcut } from "~/modules/command/hooks/use-command-binding"
|
||||
import { EntryHeader } from "~/modules/entry-content/components/entry-header"
|
||||
import { FeedIcon } from "~/modules/feed/feed-icon"
|
||||
import { useRefreshFeedMutation } from "~/queries/feed"
|
||||
import { useFeedHeaderTitle } from "~/store/feed/hooks"
|
||||
import { useFeedHeaderIcon, useFeedHeaderTitle } from "~/store/feed/hooks"
|
||||
|
||||
import { MarkAllReadButton } from "../components/mark-all-button"
|
||||
import { useIsPreviewFeed } from "../hooks/useIsPreviewFeed"
|
||||
|
|
@ -47,9 +48,11 @@ export const EntryListHeader: FC<{
|
|||
const isPreview = useIsPreviewFeed()
|
||||
|
||||
const headerTitle = useFeedHeaderTitle()
|
||||
const feedIcon = useFeedHeaderIcon()
|
||||
|
||||
const titleInfo = !!headerTitle && (
|
||||
<div className="flex min-w-0 items-center break-all text-lg font-bold leading-tight">
|
||||
{feedIcon && <FeedIcon target={feedIcon} fallback size={20} />}
|
||||
<EllipsisHorizontalTextWithTooltip className="inline-block !w-auto max-w-full">
|
||||
{headerTitle}
|
||||
</EllipsisHorizontalTextWithTooltip>
|
||||
|
|
@ -73,7 +76,7 @@ export const EntryListHeader: FC<{
|
|||
}
|
||||
|
||||
const feedColumnShow = useSubscriptionColumnShow()
|
||||
const commandShortcuts = useCommandShortcuts()
|
||||
const toggleUnreadOnlyShortcut = useCommandShortcut(COMMAND_ID.timeline.unreadOnly)
|
||||
const runCmdFn = useRunCommandFn()
|
||||
|
||||
const aiEnabled = useFeature("ai")
|
||||
|
|
@ -149,7 +152,7 @@ export const EntryListHeader: FC<{
|
|||
? t("entry_list_header.show_unread_only")
|
||||
: t("entry_list_header.show_all")
|
||||
}
|
||||
shortcut={commandShortcuts[COMMAND_ID.timeline.unreadOnly]}
|
||||
shortcut={toggleUnreadOnlyShortcut}
|
||||
onClick={() => runCmdFn(COMMAND_ID.timeline.unreadOnly, [!unreadOnly])()}
|
||||
>
|
||||
{unreadOnly ? (
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { useEventCallback } from "usehooks-ts"
|
|||
|
||||
import { useGeneralSettingKey } from "~/atoms/settings/general"
|
||||
import { m } from "~/components/common/Motion"
|
||||
import { useFeedHeaderTitle } from "~/store/feed/hooks"
|
||||
|
||||
import { VirtualRowItem } from "./components/VirtualRowItem"
|
||||
import { EntryColumnShortcutHandler } from "./EntryColumnShortcutHandler"
|
||||
|
|
@ -187,6 +188,8 @@ export const EntryList: FC<EntryListProps> = memo(
|
|||
})
|
||||
}, [])
|
||||
|
||||
const currentFeedTitle = useFeedHeaderTitle()!
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
|
|
@ -240,6 +243,7 @@ export const EntryList: FC<EntryListProps> = memo(
|
|||
isStickyItem={isStickyItem}
|
||||
isActiveStickyItem={isActiveStickyItem}
|
||||
measureElement={rowVirtualizer.measureElement}
|
||||
currentFeedTitle={currentFeedTitle}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import type { FC } from "react"
|
|||
export type UniversalItemProps = {
|
||||
entryId: string
|
||||
translation?: EntryTranslation
|
||||
currentFeedTitle?: string
|
||||
}
|
||||
|
||||
export type EntryListItemFC<P extends object = object> = FC<P & UniversalItemProps> & {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import { views } from "@follow/constants"
|
||||
import type { EntryModel } from "@follow/store/entry/types"
|
||||
import { useFeedById, usePrefetchFeed } from "@follow/store/feed/hooks"
|
||||
import { feedIconSelector } from "@follow/store/feed/selectors"
|
||||
import { useListById, usePrefetchListById } from "@follow/store/list/hooks"
|
||||
import { getSubscriptionByFeedId } from "@follow/store/subscription/getter"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useShallow } from "zustand/shallow"
|
||||
|
||||
import {
|
||||
FEED_COLLECTION_LIST,
|
||||
|
|
@ -64,3 +66,22 @@ export const useFeedHeaderTitle = () => {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const useFeedHeaderIcon = () => {
|
||||
const { feedId: currentFeedId, listId: currentListId } = useRouteParams()
|
||||
|
||||
const feedIcon = useFeedById(currentFeedId, useShallow(feedIconSelector))
|
||||
const listIcon = useListById(
|
||||
currentListId,
|
||||
useShallow((feed) => ({
|
||||
type: feed.type,
|
||||
ownerUserId: feed.ownerUserId,
|
||||
id: feed.id,
|
||||
title: feed.title,
|
||||
url: (feed as any).url || "",
|
||||
image: feed.image,
|
||||
})),
|
||||
)
|
||||
|
||||
return feedIcon || listIcon
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue