refactor: extract component from EntryContent (#617)
* refactor: extract EntryTitle component from EntryContent * refactor: extract SupportCreator component from EntryContent
This commit is contained in:
parent
f96cf01bde
commit
39ef8c9f0e
|
|
@ -0,0 +1,85 @@
|
|||
import { useMemo } from "react"
|
||||
|
||||
import { useWhoami } from "~/atoms/user"
|
||||
import { useAuthQuery } from "~/hooks/common"
|
||||
import type { FeedModel } from "~/models"
|
||||
import { Queries } from "~/queries"
|
||||
import { useEntry, useEntryReadHistory } from "~/store/entry"
|
||||
import { getPreferredTitle, useFeedById } from "~/store/feed"
|
||||
|
||||
import { EntryTranslation } from "../../entry-column/translation"
|
||||
|
||||
interface EntryLinkProps {
|
||||
entryId: string
|
||||
}
|
||||
|
||||
const safeUrl = (url: string, baseUrl: string) => {
|
||||
try {
|
||||
return new URL(url, baseUrl).href
|
||||
} catch {
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
export const EntryTitle = ({ entryId }: EntryLinkProps) => {
|
||||
const user = useWhoami()
|
||||
const entry = useEntry(entryId)
|
||||
const feed = useFeedById(entry?.feedId) as FeedModel
|
||||
const entryHistory = useEntryReadHistory(entryId)
|
||||
|
||||
const populatedFullHref = useMemo(() => {
|
||||
const href = entry?.entries.url
|
||||
if (!href) return "#"
|
||||
|
||||
if (href.startsWith("http")) return href
|
||||
const feedSiteUrl = feed?.siteUrl
|
||||
if (href.startsWith("/") && feedSiteUrl) return safeUrl(href, feedSiteUrl)
|
||||
return href
|
||||
}, [entry?.entries.url, feed?.siteUrl])
|
||||
|
||||
const translation = useAuthQuery(
|
||||
Queries.ai.translation({
|
||||
entry: entry!,
|
||||
language: entry?.settings?.translation,
|
||||
extraFields: ["title"],
|
||||
}),
|
||||
{
|
||||
enabled: !!entry?.settings?.translation,
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
meta: {
|
||||
persist: true,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if (!entry) return null
|
||||
|
||||
return (
|
||||
<a
|
||||
href={populatedFullHref || void 0}
|
||||
target="_blank"
|
||||
className="-mx-6 block cursor-button rounded-lg p-6 transition-colors hover:bg-theme-item-hover focus-visible:bg-theme-item-hover focus-visible:!outline-none @sm:-mx-3 @sm:p-3"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<div className="select-text break-words text-3xl font-bold">
|
||||
<EntryTranslation source={entry.entries.title} target={translation.data?.title} />
|
||||
</div>
|
||||
<div className="mt-2 text-[13px] font-medium text-zinc-500">{getPreferredTitle(feed)}</div>
|
||||
<div className="flex items-center gap-2 text-[13px] text-zinc-500">
|
||||
{entry.entries.publishedAt && new Date(entry.entries.publishedAt).toLocaleString()}
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<i className="i-mgc-eye-2-cute-re" />
|
||||
<span>
|
||||
{(
|
||||
(entryHistory?.readCount ?? 0) +
|
||||
(entryHistory?.userIds?.every((id) => id !== user?.id) ? 1 : 0)
|
||||
) // if no me, +1
|
||||
.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { Button } from "~/components/ui/button"
|
||||
import { Divider } from "~/components/ui/divider"
|
||||
import { UserAvatar } from "~/components/user-button"
|
||||
import { useEntry } from "~/store/entry"
|
||||
import { useFeedById } from "~/store/feed"
|
||||
|
||||
import { useTipModal } from "../../wallet/hooks"
|
||||
|
||||
export const SupportCreator = ({ entryId }: { entryId: string }) => {
|
||||
const { t } = useTranslation()
|
||||
const entry = useEntry(entryId)
|
||||
const feed = useFeedById(entry?.feedId)
|
||||
|
||||
const openTipModal = useTipModal({
|
||||
userId: feed?.ownerUserId ?? undefined,
|
||||
feedId: entry?.feedId,
|
||||
entryId,
|
||||
})
|
||||
|
||||
if (!feed || !feed.ownerUserId || feed.type !== "feed") return null
|
||||
|
||||
return (
|
||||
<>
|
||||
<Divider />
|
||||
|
||||
<div className="my-16 flex flex-col items-center gap-8">
|
||||
<UserAvatar
|
||||
className="w-40 flex-col gap-3 p-0"
|
||||
avatarClassName="size-12"
|
||||
userId={feed.ownerUserId}
|
||||
enableModal
|
||||
/>
|
||||
<Button className="text-base" onClick={() => openTipModal()}>
|
||||
<i className="i-mgc-power-outline mr-1.5 text-lg" />
|
||||
{t("entry_content.support_creator")}
|
||||
</Button>
|
||||
{!!feed.tipUsers?.length && (
|
||||
<>
|
||||
<div className="text-sm text-zinc-500">
|
||||
{t("entry_content.support_amount", { amount: feed.tipUsers.length })}
|
||||
</div>
|
||||
<div className="flex w-fit max-w-80 flex-wrap gap-4">
|
||||
{feed.tipUsers?.map((user) => (
|
||||
<div key={user.id} className="size-8">
|
||||
<UserAvatar
|
||||
className="h-auto p-0"
|
||||
avatarClassName="size-8"
|
||||
userId={user?.id}
|
||||
enableModal={true}
|
||||
hideName={true}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -13,18 +13,14 @@ import {
|
|||
useEntryReadabilityContent,
|
||||
} from "~/atoms/readability"
|
||||
import { useUISettingKey } from "~/atoms/settings/ui"
|
||||
import { useWhoami } from "~/atoms/user"
|
||||
import { m } from "~/components/common/Motion"
|
||||
import { ShadowDOM } from "~/components/common/ShadowDOM"
|
||||
import { AutoResizeHeight } from "~/components/ui/auto-resize-height"
|
||||
import { Button } from "~/components/ui/button"
|
||||
import { Divider } from "~/components/ui/divider"
|
||||
import { HTML } from "~/components/ui/markdown"
|
||||
import { Toc } from "~/components/ui/markdown/components/Toc"
|
||||
import { useInPeekModal } from "~/components/ui/modal/inspire/PeekModal"
|
||||
import { RootPortal } from "~/components/ui/portal"
|
||||
import { ScrollArea } from "~/components/ui/scroll-area"
|
||||
import { UserAvatar } from "~/components/user-button"
|
||||
import { isWebBuild, ROUTE_FEED_PENDING } from "~/constants"
|
||||
import { shortcuts } from "~/constants/shortcuts"
|
||||
import { useEntryReadabilityToggle } from "~/hooks/biz/useEntryActions"
|
||||
|
|
@ -41,27 +37,19 @@ import {
|
|||
WrappedElementProvider,
|
||||
} from "~/providers/wrapped-element-provider"
|
||||
import { Queries } from "~/queries"
|
||||
import { useEntry, useEntryReadHistory } from "~/store/entry"
|
||||
import { getPreferredTitle, useFeedById, useFeedHeaderTitle } from "~/store/feed"
|
||||
import { useEntry } from "~/store/entry"
|
||||
import { useFeedById, useFeedHeaderTitle } from "~/store/feed"
|
||||
|
||||
import { LoadingWithIcon } from "../../components/ui/loading"
|
||||
import { EntryPlaceholderDaily } from "../ai/ai-daily/EntryPlaceholderDaily"
|
||||
import { EntryTranslation } from "../entry-column/translation"
|
||||
import { useTipModal } from "../wallet/hooks"
|
||||
import { setEntryContentScrollToTop, setEntryTitleMeta } from "./atoms"
|
||||
import { EntryPlaceholderLogo } from "./components/EntryPlaceholderLogo"
|
||||
import { EntryTitle } from "./components/EntryTitle"
|
||||
import { SupportCreator } from "./components/SupportCreator"
|
||||
import { EntryHeader } from "./header"
|
||||
import { EntryContentLoading } from "./loading"
|
||||
import { EntryContentProvider } from "./provider"
|
||||
|
||||
const safeUrl = (url: string, baseUrl: string) => {
|
||||
try {
|
||||
return new URL(url, baseUrl).href
|
||||
} catch {
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
export const EntryContent = ({ entryId }: { entryId: ActiveEntryId }) => {
|
||||
const title = useFeedHeaderTitle()
|
||||
const { feedId, view } = useRouteParams()
|
||||
|
|
@ -87,7 +75,6 @@ export const EntryContent = ({ entryId }: { entryId: ActiveEntryId }) => {
|
|||
|
||||
export const EntryContentRender: Component<{ entryId: string }> = ({ entryId, className }) => {
|
||||
const { t } = useTranslation()
|
||||
const user = useWhoami()
|
||||
|
||||
const { error, data, isPending } = useAuthQuery(Queries.entries.byId(entryId), {
|
||||
staleTime: 300_000,
|
||||
|
|
@ -97,27 +84,7 @@ export const EntryContentRender: Component<{ entryId: string }> = ({ entryId, cl
|
|||
useTitle(entry?.entries.title)
|
||||
|
||||
const feed = useFeedById(entry?.feedId) as FeedModel
|
||||
|
||||
const entryHistory = useEntryReadHistory(entryId)
|
||||
|
||||
const readerRenderInlineStyle = useUISettingKey("readerRenderInlineStyle")
|
||||
|
||||
const translation = useAuthQuery(
|
||||
Queries.ai.translation({
|
||||
entry: entry!,
|
||||
language: entry?.settings?.translation,
|
||||
extraFields: ["title"],
|
||||
}),
|
||||
{
|
||||
enabled: !!entry?.settings?.translation,
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
meta: {
|
||||
persist: true,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
const summary = useAuthQuery(
|
||||
Queries.ai.summary({
|
||||
entryId,
|
||||
|
|
@ -166,22 +133,6 @@ export const EntryContentRender: Component<{ entryId: string }> = ({ entryId, cl
|
|||
[readerFontFamily],
|
||||
)
|
||||
|
||||
const populatedFullHref = useMemo(() => {
|
||||
const href = entry?.entries.url
|
||||
if (!href) return "#"
|
||||
|
||||
if (href.startsWith("http")) return href
|
||||
const feedSiteUrl = feed?.siteUrl
|
||||
if (href.startsWith("/") && feedSiteUrl) return safeUrl(href, feedSiteUrl)
|
||||
return href
|
||||
}, [entry?.entries.url, feed?.siteUrl])
|
||||
|
||||
const openTipModal = useTipModal({
|
||||
userId: feed?.ownerUserId ?? undefined,
|
||||
feedId: entry?.feedId,
|
||||
entryId,
|
||||
})
|
||||
|
||||
if (!entry) return null
|
||||
|
||||
const content = entry?.entries.content ?? data?.entries.content
|
||||
|
|
@ -216,33 +167,7 @@ export const EntryContentRender: Component<{ entryId: string }> = ({ entryId, cl
|
|||
onContextMenu={stopPropagation}
|
||||
className="relative m-auto min-w-0 max-w-[550px] @3xl:max-w-[70ch]"
|
||||
>
|
||||
<a
|
||||
href={populatedFullHref || void 0}
|
||||
target="_blank"
|
||||
className="-mx-6 block cursor-button rounded-lg p-6 transition-colors hover:bg-theme-item-hover focus-visible:bg-theme-item-hover focus-visible:!outline-none @sm:-mx-3 @sm:p-3"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<div className="select-text break-words text-3xl font-bold">
|
||||
<EntryTranslation source={entry.entries.title} target={translation.data?.title} />
|
||||
</div>
|
||||
<div className="mt-2 text-[13px] font-medium text-zinc-500">
|
||||
{getPreferredTitle(feed)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-[13px] text-zinc-500">
|
||||
{entry.entries.publishedAt && new Date(entry.entries.publishedAt).toLocaleString()}
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<i className="i-mgc-eye-2-cute-re" />
|
||||
<span>
|
||||
{(
|
||||
(entryHistory?.readCount ?? 0) +
|
||||
(entryHistory?.userIds?.every((id) => id !== user?.id) ? 1 : 0)
|
||||
) // if no me, +1
|
||||
.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<EntryTitle entryId={entryId} />
|
||||
|
||||
<WrappedElementProvider boundingDetection>
|
||||
<div className="mx-auto mb-32 mt-8 max-w-full cursor-auto select-text break-all text-[0.94rem]">
|
||||
|
|
@ -301,44 +226,7 @@ export const EntryContentRender: Component<{ entryId: string }> = ({ entryId, cl
|
|||
</div>
|
||||
)}
|
||||
|
||||
{feed?.ownerUserId && (
|
||||
<>
|
||||
<Divider />
|
||||
|
||||
<div className="my-16 flex flex-col items-center gap-8">
|
||||
<UserAvatar
|
||||
className="w-40 flex-col gap-3 p-0"
|
||||
avatarClassName="size-12"
|
||||
userId={feed.ownerUserId}
|
||||
enableModal
|
||||
/>
|
||||
<Button className="text-base" onClick={() => openTipModal()}>
|
||||
<i className="i-mgc-power-outline mr-1.5 text-lg" />
|
||||
{t("entry_content.support_creator")}
|
||||
</Button>
|
||||
{!!feed.tipUsers?.length && (
|
||||
<>
|
||||
<div className="text-sm text-zinc-500">
|
||||
{t("entry_content.support_amount", { amount: feed.tipUsers.length })}
|
||||
</div>
|
||||
<div className="flex w-fit max-w-80 flex-wrap gap-4">
|
||||
{feed.tipUsers?.map((user) => (
|
||||
<div key={user.id} className="size-8">
|
||||
<UserAvatar
|
||||
className="h-auto p-0"
|
||||
avatarClassName="size-8"
|
||||
userId={user?.id}
|
||||
enableModal={true}
|
||||
hideName={true}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{feed?.ownerUserId && <SupportCreator entryId={entryId} />}
|
||||
</article>
|
||||
</div>
|
||||
</ScrollArea.ScrollArea>
|
||||
|
|
|
|||
Loading…
Reference in New Issue