refactor: extract component from EntryContent (#617)

* refactor: extract EntryTitle component from EntryContent

* refactor: extract SupportCreator component from EntryContent
This commit is contained in:
Whitewater 2024-09-27 01:48:01 +08:00 committed by GitHub
parent f96cf01bde
commit 39ef8c9f0e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 153 additions and 118 deletions

View File

@ -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>
)
}

View File

@ -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>
</>
)
}

View File

@ -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>