feat: fold the long text content of social media
Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
parent
27b3bf4f50
commit
e46b30a07f
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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) ?? [],
|
||||
|
|
|
|||
|
|
@ -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%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue