feat: fold the long text content of social media

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei 2024-12-02 16:49:04 +08:00
parent 27b3bf4f50
commit e46b30a07f
No known key found for this signature in database
GPG Key ID: 0F62D33977F021F7
4 changed files with 88 additions and 9 deletions

View File

@ -1,7 +1,9 @@
import { useMobile } from "@follow/components/hooks/useMobile.js"
import { ActionButton } from "@follow/components/ui/button/index.js"
import { AutoResizeHeight } from "@follow/components/ui/auto-resize-height/index.js"
import { ActionButton, MotionButtonBase } from "@follow/components/ui/button/index.js"
import { Skeleton } from "@follow/components/ui/skeleton/index.jsx"
import type { MediaModel } from "@follow/shared/hono"
import { LRUCache } from "@follow/utils/lru-cache"
import { cn } from "@follow/utils/utils"
import { atom } from "jotai"
import { useLayoutEffect, useMemo, useRef, useState } from "react"
@ -50,7 +52,7 @@ export const SocialMediaItem: EntryListItemFC = ({ entryId, entryPreview, transl
if (ref.current) {
jotaiStore.set(socialMediaContentWidthAtom, ref.current.offsetWidth)
}
}, [ref.current])
}, [])
// NOTE: prevent 0 height element, react virtuoso will not stop render any more
if (!entry || !feed) return <ReactVirtuosoItemPlaceholder />
@ -101,12 +103,14 @@ export const SocialMediaItem: EntryListItemFC = ({ entryId, entryPreview, transl
</span>
</div>
<div className={cn("relative mt-1 text-base", !!entry.collections && "pr-5")}>
<EntryTranslation
className="cursor-auto select-text text-sm leading-relaxed prose-blockquote:mt-0 [&_br:last-child]:hidden"
source={content}
target={translation?.content}
isHTML
/>
<CollapsedSocialMediaItem entryId={entryId}>
<EntryTranslation
className="cursor-auto select-text text-sm leading-relaxed prose-blockquote:mt-0 [&_br:last-child]:hidden"
source={content}
target={translation?.content}
isHTML
/>
</CollapsedSocialMediaItem>
{!!entry.collections && <StarIcon className="absolute right-0 top-0" />}
</div>
</div>
@ -355,3 +359,50 @@ const SocialMediaGallery = ({ media }: { media: MediaModel[] }) => {
</div>
)
}
const collapsedHeight = 300
const collapsedItemCache = new LRUCache<string, boolean>(100)
const CollapsedSocialMediaItem: Component<{
entryId: string
}> = ({ children, entryId }) => {
const [isOverflow, setIsOverflow] = useState(false)
const [isShowMore, setIsShowMore] = useState(() => collapsedItemCache.get(entryId) ?? false)
const ref = useRef<HTMLDivElement>(null)
useLayoutEffect(() => {
if (ref.current) {
setIsOverflow(ref.current.scrollHeight > collapsedHeight)
}
}, [children])
return (
<AutoResizeHeight spring className="relative">
<div
className={cn(
"relative",
!isShowMore && "max-h-[300px] overflow-hidden",
isShowMore && "h-auto",
!isShowMore && isOverflow && "mask-b-xl",
)}
ref={ref}
>
{children}
</div>
{isOverflow && !isShowMore && (
<div className="absolute inset-x-0 bottom-0 flex justify-center py-2 duration-200">
<MotionButtonBase
type="button"
onClick={() => {
setIsShowMore(true)
collapsedItemCache.put(entryId, true)
}}
aria-hidden
className="flex items-center justify-center text-xs"
>
<i className="i-mingcute-arrow-to-down-line" />
<span className="ml-2">Show more</span>
</MotionButtonBase>
</div>
)}
</AutoResizeHeight>
)
}

View File

@ -1,6 +1,6 @@
import { useMobile } from "@follow/components/hooks/useMobile.js"
import { Button } from "@follow/components/ui/button/index.js"
import { views } from "@follow/constants"
import { FeedViewType, views } from "@follow/constants"
import { useTitle } from "@follow/hooks"
import type { FeedModel } from "@follow/models/types"
import { isBizId } from "@follow/utils/utils"
@ -187,6 +187,7 @@ function EntryColumnImpl() {
)
) : (
<ListComponent
gap={view === FeedViewType.SocialMedia ? 10 : undefined}
listRef={listRef}
onRangeChange={handleRangeChange}
hasNextPage={entries.hasNextPage}

View File

@ -70,6 +70,7 @@ export type EntryListProps = {
refetch: () => void
groupCounts?: number[]
gap?: number
Footer?: FC | ReactNode
@ -93,6 +94,7 @@ export const EntryList: FC<EntryListProps> = memo(
Footer,
listRef,
onRangeChange,
gap,
}) => {
// Prevent scroll list move when press up/down key, the up/down key should be taken over by the shortcut key we defined.
const handleKeyDown: React.KeyboardEventHandler<HTMLDivElement> = useCallback((e) => {
@ -121,6 +123,7 @@ export const EntryList: FC<EntryListProps> = memo(
count: entriesIds.length + 1,
estimateSize: () => 112,
overscan: 5,
gap,
getScrollElement: () => scrollRef,
initialOffset: offsetCache.get(feedId) ?? 0,
initialMeasurementsCache: measurementsCache.get(feedId) ?? [],

View File

@ -395,3 +395,27 @@
animation: rocketAnimation 1s infinite ease-out;
}
}
@layer components {
.mask-b {
mask-image: linear-gradient(rgb(255, 255, 255) calc(100% - 20px), rgba(255, 255, 255, 0) 100%);
}
.mask-b-lg {
mask-image: linear-gradient(rgb(255, 255, 255) calc(100% - 50px), rgba(255, 255, 255, 0) 100%);
}
.mask-b-xl {
mask-image: linear-gradient(rgb(255, 255, 255) calc(100% - 70px), rgba(255, 255, 255, 0) 100%);
}
.mask-horizontal {
mask-image: linear-gradient(
90deg,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 1) 14%,
rgba(255, 255, 255, 1) 86%,
rgba(255, 255, 255, 0) 100%
);
}
}