feat: render social media with full text

This commit is contained in:
DIYgod 2024-09-05 22:34:31 +08:00
parent 18de381343
commit 1531d2b709
No known key found for this signature in database
8 changed files with 82 additions and 27 deletions

View File

@ -2752,6 +2752,7 @@ declare const _routes: hono_hono_base.HonoBase<Env, {
data?: {
description?: string | undefined;
title?: string | undefined;
content?: string | undefined;
} | undefined;
};
outputFormat: "json" | "text";
@ -3304,6 +3305,7 @@ declare const _routes: hono_hono_base.HonoBase<Env, {
collected?: boolean | undefined;
isCollection?: boolean | undefined;
isArchived?: boolean | undefined;
withContent?: boolean | undefined;
};
};
output: {

View File

@ -40,13 +40,14 @@ export const HTML = <A extends keyof JSX.IntrinsicElements = "div">(
as: A
accessory?: React.ReactNode
noMedia?: boolean
} & JSX.IntrinsicElements[A] &
Partial<{
renderInlineStyle: boolean
}>,
) => {
const { children, renderInlineStyle, as = "div", accessory, ...rest } = props
const [remarkOptions, setRemarkOptions] = useState({ renderInlineStyle })
const { children, renderInlineStyle, as = "div", accessory, noMedia, ...rest } = props
const [remarkOptions, setRemarkOptions] = useState({ renderInlineStyle, noMedia })
const [shouldForceReMountKey, setShouldForceReMountKey] = useState(0)
useEffect(() => {
@ -54,9 +55,9 @@ export const HTML = <A extends keyof JSX.IntrinsicElements = "div">(
if (renderInlineStyle === options.renderInlineStyle) return options
setShouldForceReMountKey((key) => key + 1)
return { ...options, renderInlineStyle }
return { ...options, renderInlineStyle, noMedia }
})
}, [renderInlineStyle])
}, [renderInlineStyle, noMedia])
const [refElement, setRefElement] = useState<HTMLElement | null>(null)

View File

@ -15,7 +15,7 @@ export const views = [
className: "text-sky-600 dark:text-sky-500",
peerClassName: "peer-checked:text-sky-600 peer-checked:dark:text-sky-500",
wideMode: true,
translation: "description",
translation: "content",
view: FeedViewType.SocialMedia,
},
{

View File

@ -27,26 +27,32 @@ export const parseHtml = (
content: string,
options?: Partial<{
renderInlineStyle: boolean
noMedia?: boolean
}>,
) => {
const file = new VFile(content)
const { renderInlineStyle = false } = options || {}
const { renderInlineStyle = false, noMedia = false } = options || {}
const pipeline = unified()
.use(rehypeParse, { fragment: true })
.use(rehypeSanitize, {
...defaultSchema,
tagNames: [...defaultSchema.tagNames!, "video", "style"],
attributes: {
...defaultSchema.attributes,
.use(rehypeSanitize, noMedia ?
{
...defaultSchema,
tagNames: defaultSchema.tagNames?.filter((tag) => tag !== "img" && tag !== "picture"),
} :
{
...defaultSchema,
tagNames: [...defaultSchema.tagNames!, "video", "style"],
attributes: {
...defaultSchema.attributes,
"*": renderInlineStyle ?
[...defaultSchema.attributes!["*"], "style", "class"] :
defaultSchema.attributes!["*"],
"*": renderInlineStyle ?
[...defaultSchema.attributes!["*"], "style", "class"] :
defaultSchema.attributes!["*"],
"video": ["src", "poster"],
},
})
"video": ["src", "poster"],
},
})
.use(rehypeInferDescriptionMeta)
.use(rehypeStringify)

View File

@ -26,13 +26,17 @@ export function getEntriesParams({
const params: {
feedId?: string
feedIdList?: string[]
collected?: boolean
isCollection?: boolean
withContent?: boolean
} = {}
if (id === FEED_COLLECTION_LIST) {
params.collected = true
params.isCollection = true
} else if (id && id !== ROUTE_FEED_PENDING) {
params.feedIdList = `${id}`.split(",")
}
if (view === FeedViewType.SocialMedia) {
params.withContent = true
}
return {
view,
...params,

View File

@ -31,6 +31,8 @@ export const SocialMediaItem: EntryListItemFC = ({
// NOTE: prevent 0 height element, react virtuoso will not stop render any more
if (!entry || !feed) return <ReactVirtuosoItemPlaceholder />
const content = entry.entries.content || entry.entries.description
return (
<div
@ -52,7 +54,7 @@ export const SocialMediaItem: EntryListItemFC = ({
<div
className={cn(
"-mt-0.5 flex-1 text-sm",
entry.entries.description && "line-clamp-5",
content && "line-clamp-[10]",
)}
>
<div className="w-[calc(100%-10rem)] space-x-1">
@ -69,8 +71,9 @@ export const SocialMediaItem: EntryListItemFC = ({
)}
>
<EntryTranslation
source={entry.entries.description}
target={translation?.description}
source={content}
target={translation?.content}
isHTML
/>
{!!entry.collections && <StarIcon />}
</div>

View File

@ -1,4 +1,5 @@
import * as HoverCard from "@radix-ui/react-hover-card"
import { HTML } from "@renderer/components/ui/markdown"
import { ScrollArea } from "@renderer/components/ui/scroll-area"
import { tooltipStyle } from "@renderer/components/ui/tooltip/styles"
import { useMeasure } from "@renderer/hooks/common"
@ -11,7 +12,8 @@ export const EntryTranslation: Component<{
side?: "top" | "bottom"
useOverlay?: boolean
}> = ({ source, target, className, side, useOverlay }) => {
isHTML?: boolean
}> = ({ source, target, className, side, useOverlay, isHTML }) => {
let nextTarget = target
if (source === target) {
nextTarget = undefined
@ -20,13 +22,31 @@ export const EntryTranslation: Component<{
const [ref, bounds] = useMeasure({ debounce: 60 })
if (!nextTarget && source) {
return <div className={className}>{source}</div>
return isHTML ?
(
<HTML
as="div"
className={cn("prose dark:prose-invert", className)}
noMedia
>
{source}
</HTML>
) :
<div className={className}>{source}</div>
}
return (
<HoverCard.Root>
<HoverCard.Trigger className={className} ref={ref}>
<i className="i-mgc-translate-2-cute-re mr-1 align-middle" />
<span className="align-middle">{nextTarget}</span>
{isHTML ? (
<HTML
as="span"
className="align-middle"
noMedia
>
{nextTarget}
</HTML>
) : <span className="align-middle">{nextTarget}</span>}
</HoverCard.Trigger>
<HoverCard.Portal>
<HoverCard.Content
@ -49,11 +69,29 @@ export const EntryTranslation: Component<{
)}
viewportClassName="max-h-[12ch]"
>
<span className="align-middle">{source}</span>
{isHTML ? (
<HTML
as="span"
className="align-middle"
noMedia
>
{source}
</HTML>
) : <span className="align-middle">{source}</span>}
</ScrollArea.ScrollArea>
</div>
) : (
<span className={cn(tooltipStyle.content)}>{source}</span>
isHTML ?
(
<HTML
as="span"
className={cn(tooltipStyle.content)}
noMedia
>
{source}
</HTML>
) :
<span className={cn(tooltipStyle.content)}>{source}</span>
)}
</HoverCard.Content>
</HoverCard.Portal>

View File

@ -10,6 +10,7 @@ export type UniversalItemProps = {
translation?: {
title?: string
description?: string
content?: string
} | null
}