feat: render social media with full text
This commit is contained in:
parent
18de381343
commit
1531d2b709
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export type UniversalItemProps = {
|
|||
translation?: {
|
||||
title?: string
|
||||
description?: string
|
||||
content?: string
|
||||
} | null
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue