refactor(desktop): use translation and readability from shared store (#3902)
This commit is contained in:
parent
c2e0c3d20d
commit
d3f889a42d
|
|
@ -1,45 +1,12 @@
|
|||
import { getStorageNS } from "@follow/utils/ns"
|
||||
import { atom } from "jotai"
|
||||
import { atomWithStorage } from "jotai/utils"
|
||||
|
||||
import { createAtomHooks } from "~/lib/jotai"
|
||||
|
||||
type Readability = {
|
||||
title?: string | null
|
||||
content?: string | null
|
||||
textContent?: string | null
|
||||
length?: number | null
|
||||
excerpt?: string | null
|
||||
byline?: string | null
|
||||
dir?: string | null
|
||||
siteName?: string | null
|
||||
lang?: string | null
|
||||
publishedTime?: string | null
|
||||
}
|
||||
|
||||
const mergeObjectSetter =
|
||||
<T>(setter: (prev: T) => void, getter: () => T) =>
|
||||
(value: Partial<T>) =>
|
||||
setter({ ...getter(), ...value })
|
||||
|
||||
export const [
|
||||
,
|
||||
,
|
||||
useReadabilityContent,
|
||||
,
|
||||
getReadabilityContent,
|
||||
__setReadabilityContent,
|
||||
useReadabilityContentSelector,
|
||||
] = createAtomHooks(
|
||||
atomWithStorage<Record<string, Readability>>(getStorageNS("readability-content"), {}, undefined, {
|
||||
getOnInit: true,
|
||||
}),
|
||||
)
|
||||
export const setReadabilityContent = mergeObjectSetter(
|
||||
__setReadabilityContent,
|
||||
getReadabilityContent,
|
||||
)
|
||||
|
||||
export enum ReadabilityStatus {
|
||||
INITIAL = 1,
|
||||
WAITING = 2,
|
||||
|
|
@ -77,5 +44,3 @@ export const useEntryInReadabilityStatus = (entryId?: string) =>
|
|||
|
||||
export const isInReadability = (status: ReadabilityStatus) =>
|
||||
status !== ReadabilityStatus.INITIAL && !!status
|
||||
export const useEntryReadabilityContent = (entryId: string) =>
|
||||
useReadabilityContentSelector((map) => map[entryId], [entryId])
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ import { isMobile } from "@follow/components/hooks/useMobile.js"
|
|||
import { FeedViewType, UserRole, views } from "@follow/constants"
|
||||
import { IN_ELECTRON } from "@follow/shared/constants"
|
||||
import { useIsEntryStarred } from "@follow/store/collection/hooks"
|
||||
import { getEntry } from "@follow/store/entry/getter"
|
||||
import { useEntry } from "@follow/store/entry/hooks"
|
||||
import { entrySyncServices } from "@follow/store/entry/store"
|
||||
import type { EntryModel } from "@follow/store/entry/types"
|
||||
import { useFeedById } from "@follow/store/feed/hooks"
|
||||
import { useIsInbox } from "@follow/store/inbox/hooks"
|
||||
|
|
@ -13,16 +15,13 @@ import { useShowAISummaryAuto, useShowAISummaryOnce } from "~/atoms/ai-summary"
|
|||
import { useShowAITranslationAuto, useShowAITranslationOnce } from "~/atoms/ai-translation"
|
||||
import { MENU_ITEM_SEPARATOR, MenuItemSeparator, MenuItemText } from "~/atoms/context-menu"
|
||||
import {
|
||||
getReadabilityContent,
|
||||
getReadabilityStatus,
|
||||
ReadabilityStatus,
|
||||
setReadabilityContent,
|
||||
setReadabilityStatus,
|
||||
useEntryIsInReadability,
|
||||
} from "~/atoms/readability"
|
||||
import { useShowSourceContent } from "~/atoms/source-content"
|
||||
import { useUserRole, whoami } from "~/atoms/user"
|
||||
import { apiClient } from "~/lib/api-fetch"
|
||||
import { ipcServices } from "~/lib/client"
|
||||
import { COMMAND_ID } from "~/modules/command/commands/id"
|
||||
import { getCommand, useRunCommandFn } from "~/modules/command/hooks/use-command"
|
||||
|
|
@ -46,39 +45,22 @@ export const toggleEntryReadability = async ({ id, url }: { id: string; url: str
|
|||
[id]: ReadabilityStatus.WAITING,
|
||||
})
|
||||
try {
|
||||
let data = getReadabilityContent()[id]
|
||||
const data = getEntry(id)?.readabilityContent
|
||||
|
||||
if (!data) {
|
||||
const result = await apiClient.entries.readability.$get({ query: { id } })
|
||||
if (result.data) {
|
||||
data = result.data
|
||||
}
|
||||
await entrySyncServices.fetchEntryReadabilityContent(id, async () => {
|
||||
const res = await ipcServices?.reader.readability({ url })
|
||||
return res?.content
|
||||
})
|
||||
}
|
||||
|
||||
if (data) {
|
||||
const status = getReadabilityStatus()[id]
|
||||
if (status !== ReadabilityStatus.WAITING) return
|
||||
setReadabilityStatus({
|
||||
[id]: ReadabilityStatus.SUCCESS,
|
||||
})
|
||||
setReadabilityContent({
|
||||
[id]: data,
|
||||
})
|
||||
}
|
||||
setReadabilityStatus({
|
||||
[id]: ReadabilityStatus.SUCCESS,
|
||||
})
|
||||
} catch {
|
||||
const result = await ipcServices?.reader.readability({ url })
|
||||
if (result) {
|
||||
setReadabilityContent({
|
||||
[id]: result,
|
||||
})
|
||||
setReadabilityStatus({
|
||||
[id]: ReadabilityStatus.SUCCESS,
|
||||
})
|
||||
} else {
|
||||
setReadabilityStatus({
|
||||
[id]: ReadabilityStatus.FAILURE,
|
||||
})
|
||||
}
|
||||
setReadabilityStatus({
|
||||
[id]: ReadabilityStatus.FAILURE,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
setReadabilityStatus({
|
||||
|
|
|
|||
|
|
@ -1,15 +1,9 @@
|
|||
import { parseHtml } from "@follow/components/ui/markdown/parse-html.js"
|
||||
import { views } from "@follow/constants"
|
||||
import type { SupportedActionLanguage } from "@follow/shared"
|
||||
import { ACTION_LANGUAGE_MAP } from "@follow/shared"
|
||||
import { getEntry } from "@follow/store/entry/getter"
|
||||
import { duplicateIfLengthLessThan } from "@follow/utils/utils"
|
||||
import { franc } from "franc-min"
|
||||
|
||||
import { getReadabilityContent } from "~/atoms/readability"
|
||||
|
||||
import { apiClient } from "./api-fetch"
|
||||
|
||||
export const checkLanguage = ({
|
||||
content,
|
||||
language,
|
||||
|
|
@ -32,77 +26,3 @@ export const checkLanguage = ({
|
|||
|
||||
return sourceLanguage === code
|
||||
}
|
||||
|
||||
export async function translate({
|
||||
entryId,
|
||||
view,
|
||||
language,
|
||||
extraFields,
|
||||
part,
|
||||
}: {
|
||||
entryId?: string | null
|
||||
view?: number | null
|
||||
language?: SupportedActionLanguage
|
||||
extraFields?: string[]
|
||||
part?: string
|
||||
}) {
|
||||
if (!language || !entryId) {
|
||||
return null
|
||||
}
|
||||
let fields = language && typeof view === "number" ? views[view!]!.translation.split(",") : []
|
||||
if (extraFields) {
|
||||
fields = [...fields, ...extraFields]
|
||||
}
|
||||
|
||||
const readabilityContent = getReadabilityContent()[entryId]?.content
|
||||
const entries = getEntry(entryId)
|
||||
fields = fields.filter((field) => {
|
||||
if (language && field === "readabilityContent") {
|
||||
if (!readabilityContent) return false
|
||||
const isLanguageMatch = checkLanguage({
|
||||
content: readabilityContent,
|
||||
language,
|
||||
})
|
||||
return !isLanguageMatch
|
||||
}
|
||||
|
||||
if (language && entries?.[field]) {
|
||||
const isLanguageMatch = checkLanguage({
|
||||
content: entries[field],
|
||||
language,
|
||||
})
|
||||
return !isLanguageMatch
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
if (fields.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const res = await apiClient.ai.translation.$get({
|
||||
query: {
|
||||
id: entryId,
|
||||
language,
|
||||
fields: fields?.join(",") || "title",
|
||||
part,
|
||||
},
|
||||
})
|
||||
|
||||
const data: {
|
||||
description?: string
|
||||
title?: string
|
||||
content?: string
|
||||
readabilityContent?: string
|
||||
} = {}
|
||||
|
||||
fields.forEach((field) => {
|
||||
const content = field === "readabilityContent" ? readabilityContent : entries?.[field]
|
||||
if (content !== res.data?.[field]) {
|
||||
data[field] = res.data?.[field]
|
||||
}
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import { ofetch } from "ofetch"
|
|||
import { useTranslation } from "react-i18next"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import { getReadabilityContent, getReadabilityStatus, ReadabilityStatus } from "~/atoms/readability"
|
||||
import { getReadabilityStatus, ReadabilityStatus } from "~/atoms/readability"
|
||||
import { getActionLanguage } from "~/atoms/settings/general"
|
||||
import { getIntegrationSettings, useIntegrationSettingKey } from "~/atoms/settings/integration"
|
||||
import { useRouteParams } from "~/hooks/biz/useRouteParams"
|
||||
|
|
@ -252,8 +252,7 @@ const useRegisterInstapaperCommands = () => {
|
|||
|
||||
const getEntryContentAsMarkdown = async (entry: EntryModel) => {
|
||||
const isReadabilityReady = getReadabilityStatus()[entry.id] === ReadabilityStatus.SUCCESS
|
||||
const content =
|
||||
(isReadabilityReady ? getReadabilityContent()[entry.id]!.content : entry.content) || ""
|
||||
const content = (isReadabilityReady ? entry.readabilityContent || "" : entry.content) || ""
|
||||
const [toMarkdown, toMdast, gfmTableToMarkdown] = await Promise.all([
|
||||
import("mdast-util-to-markdown").then((m) => m.toMarkdown),
|
||||
import("hast-util-to-mdast").then((m) => m.toMdast),
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ import { useMasonryColumn } from "@follow/components/ui/masonry/hooks.js"
|
|||
import { Masonry } from "@follow/components/ui/masonry/index.js"
|
||||
import { useScrollViewElement } from "@follow/components/ui/scroll-area/hooks.js"
|
||||
import { Skeleton } from "@follow/components/ui/skeleton/index.jsx"
|
||||
import { FeedViewType } from "@follow/constants"
|
||||
import { useRefValue } from "@follow/hooks"
|
||||
import { getEntry } from "@follow/store/entry/getter"
|
||||
import { useEntryTranslation } from "@follow/store/translation/hooks"
|
||||
import { clsx } from "@follow/utils/utils"
|
||||
import type { RenderComponentProps } from "masonic"
|
||||
import { useInfiniteLoader } from "masonic"
|
||||
|
|
@ -30,9 +30,8 @@ import {
|
|||
} from "react"
|
||||
import { useEventCallback } from "usehooks-ts"
|
||||
|
||||
import { useGeneralSettingKey } from "~/atoms/settings/general"
|
||||
import { useActionLanguage, useGeneralSettingKey } from "~/atoms/settings/general"
|
||||
import { MediaContainerWidthProvider } from "~/components/ui/media"
|
||||
import { useEntryTranslation } from "~/store/ai/hook"
|
||||
import { imageActions } from "~/store/image"
|
||||
|
||||
import { getMasonryColumnValue, setMasonryColumnValue, useMasonryColumnValue } from "../atoms"
|
||||
|
|
@ -259,7 +258,8 @@ const MasonryRender: React.ComponentType<
|
|||
}>
|
||||
> = ({ data, index }) => {
|
||||
const firstScreenReady = use(FirstScreenReadyContext)
|
||||
const translation = useEntryTranslation({ entryId: data.entryId, view: FeedViewType.Pictures })
|
||||
const actionLanguage = useActionLanguage()
|
||||
const translation = useEntryTranslation(data.entryId, actionLanguage)
|
||||
|
||||
if (data.entryId.startsWith("placeholder")) {
|
||||
return <LoadingSkeletonItem />
|
||||
|
|
@ -273,7 +273,7 @@ const MasonryRender: React.ComponentType<
|
|||
)}
|
||||
entryId={data.entryId}
|
||||
index={index}
|
||||
translation={translation.data}
|
||||
translation={translation}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { isMobile } from "@follow/components/hooks/useMobile.js"
|
||||
import { Skeleton } from "@follow/components/ui/skeleton/index.jsx"
|
||||
import { FeedViewType } from "@follow/constants"
|
||||
import { IN_ELECTRON } from "@follow/shared/constants"
|
||||
import { useEntry } from "@follow/store/entry/hooks"
|
||||
import { useEntryTranslation, usePrefetchEntryTranslation } from "@follow/store/translation/hooks"
|
||||
import { stopPropagation } from "@follow/utils/dom"
|
||||
import { formatDuration } from "@follow/utils/duration"
|
||||
import { transformVideoUrl } from "@follow/utils/url-for-video"
|
||||
|
|
@ -11,6 +11,7 @@ import { useHover } from "@use-gesture/react"
|
|||
import { useEffect, useMemo, useRef, useState } from "react"
|
||||
|
||||
import { AudioPlayer } from "~/atoms/player"
|
||||
import { useActionLanguage, useGeneralSettingKey } from "~/atoms/settings/general"
|
||||
import { m } from "~/components/common/Motion"
|
||||
import { RelativeTime } from "~/components/ui/datetime"
|
||||
import { HTML } from "~/components/ui/markdown/HTML"
|
||||
|
|
@ -22,9 +23,9 @@ import { PlainModal } from "~/components/ui/modal/stacked/custom-modal"
|
|||
import { useModalStack } from "~/components/ui/modal/stacked/hooks"
|
||||
import { useRenderStyle } from "~/hooks/biz/useRenderStyle"
|
||||
import { useRouteParamsSelector } from "~/hooks/biz/useRouteParams"
|
||||
import { checkLanguage } from "~/lib/translate"
|
||||
import { FeedIcon } from "~/modules/feed/feed-icon"
|
||||
import { FeedTitle } from "~/modules/feed/feed-title"
|
||||
import { useEntryTranslation } from "~/store/ai/hook"
|
||||
|
||||
import { GridItem } from "../templates/grid-item-template"
|
||||
import type { EntryItemStatelessProps, UniversalItemProps } from "../types"
|
||||
|
|
@ -169,12 +170,18 @@ const PreviewVideoModalContent: ModalContentComponent<{
|
|||
entryId: string
|
||||
}> = ({ dismiss, src, entryId }) => {
|
||||
const entry = useEntry(entryId, (state) => ({ content: state.content }))
|
||||
const translation = useEntryTranslation({
|
||||
entryId,
|
||||
extraFields: ["content"],
|
||||
view: FeedViewType.Videos,
|
||||
|
||||
const actionLanguage = useActionLanguage()
|
||||
const enableTranslation = useGeneralSettingKey("translation")
|
||||
const translation = useEntryTranslation(entryId, actionLanguage)
|
||||
usePrefetchEntryTranslation({
|
||||
entryIds: [entryId],
|
||||
checkLanguage,
|
||||
translation: enableTranslation,
|
||||
language: actionLanguage,
|
||||
withContent: true,
|
||||
})
|
||||
const content = translation.data?.content || entry?.content
|
||||
const content = translation?.content || entry?.content
|
||||
const currentAudioPlayerIsPlay = useRef(AudioPlayer.get().status === "playing")
|
||||
|
||||
const renderStyle = useRenderStyle()
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import type { FeedViewType } from "@follow/constants"
|
||||
import { useEntry } from "@follow/store/entry/hooks"
|
||||
import { useEntryTranslation, usePrefetchEntryTranslation } from "@follow/store/translation/hooks"
|
||||
import type { FC } from "react"
|
||||
import { memo } from "react"
|
||||
|
||||
import { useEntryTranslation } from "~/store/ai/hook"
|
||||
import { useActionLanguage, useGeneralSettingKey } from "~/atoms/settings/general"
|
||||
import { checkLanguage } from "~/lib/translate"
|
||||
|
||||
import { getItemComponentByView } from "./Items/getItemComponentByView"
|
||||
import { EntryItemWrapper } from "./layouts/EntryItemWrapper"
|
||||
|
|
@ -20,13 +22,21 @@ const EntryItemImpl = memo(function EntryItemImpl({
|
|||
entryId: string
|
||||
view: FeedViewType
|
||||
}) {
|
||||
const translation = useEntryTranslation({ entryId, view })
|
||||
const actionLanguage = useActionLanguage()
|
||||
const enableTranslation = useGeneralSettingKey("translation")
|
||||
const translation = useEntryTranslation(entryId, actionLanguage)
|
||||
usePrefetchEntryTranslation({
|
||||
entryIds: [entryId],
|
||||
checkLanguage,
|
||||
translation: enableTranslation,
|
||||
language: actionLanguage,
|
||||
})
|
||||
|
||||
const Item: EntryListItemFC = getItemComponentByView(view)
|
||||
|
||||
return (
|
||||
<EntryItemWrapper itemClassName={Item.wrapperClassName} entryId={entryId} view={view}>
|
||||
<Item entryId={entryId} translation={translation.data} />
|
||||
<Item entryId={entryId} translation={translation} />
|
||||
</EntryItemWrapper>
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { HTML } from "~/components/ui/markdown/HTML"
|
|||
|
||||
export const EntryTranslation: Component<{
|
||||
source?: string | null
|
||||
target?: string
|
||||
target?: string | null
|
||||
isHTML?: boolean
|
||||
inline?: boolean
|
||||
bilingual?: boolean
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import type {
|
|||
FeedModel,
|
||||
FeedOrListRespModel,
|
||||
} from "@follow/models/types"
|
||||
import type { EntryTranslation } from "@follow/store/translation/types"
|
||||
import type { FC } from "react"
|
||||
|
||||
export type UniversalItemProps = {
|
||||
|
|
@ -13,11 +14,7 @@ export type UniversalItemProps = {
|
|||
feedId: string
|
||||
inboxId: string
|
||||
}
|
||||
translation?: {
|
||||
title?: string
|
||||
description?: string
|
||||
content?: string
|
||||
} | null
|
||||
translation?: EntryTranslation
|
||||
}
|
||||
|
||||
export type EntryListItemFC<P extends object = object> = FC<P & UniversalItemProps> & {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import { useEntry, useEntryReadHistory } from "@follow/store/entry/hooks"
|
||||
import { useFeedById } from "@follow/store/feed/hooks"
|
||||
import { useInboxById } from "@follow/store/inbox/hooks"
|
||||
import { useEntryTranslation } from "@follow/store/translation/hooks"
|
||||
import { formatEstimatedMins, formatTimeToSeconds } from "@follow/utils"
|
||||
import { titleCase } from "title-case"
|
||||
|
||||
import { useActionLanguage } from "~/atoms/settings/general"
|
||||
import { useUISettingKey } from "~/atoms/settings/ui"
|
||||
import { useWhoami } from "~/atoms/user"
|
||||
import { RelativeTime } from "~/components/ui/datetime"
|
||||
|
|
@ -11,7 +13,6 @@ import { useNavigateEntry } from "~/hooks/biz/useNavigateEntry"
|
|||
import { useFeedSafeUrl } from "~/hooks/common/useFeedSafeUrl"
|
||||
import type { FeedIconEntry } from "~/modules/feed/feed-icon"
|
||||
import { FeedIcon } from "~/modules/feed/feed-icon"
|
||||
import { useEntryTranslation } from "~/store/ai/hook"
|
||||
import { getPreferredTitle } from "~/store/feed/hooks"
|
||||
|
||||
import { EntryTranslation } from "../../entry-column/translation"
|
||||
|
|
@ -56,7 +57,9 @@ export const EntryTitle = ({ entryId, compact }: EntryLinkProps) => {
|
|||
const data = useEntryReadHistory(entryId)
|
||||
const entryHistory = data?.entryReadHistories
|
||||
const populatedFullHref = useFeedSafeUrl(entryId)
|
||||
const translation = useEntryTranslation({ entryId, extraFields: ["title"], view: undefined })
|
||||
const actionLanguage = useActionLanguage()
|
||||
|
||||
const translation = useEntryTranslation(entryId, actionLanguage)
|
||||
|
||||
const dateFormat = useUISettingKey("dateFormat")
|
||||
|
||||
|
|
@ -88,7 +91,7 @@ export const EntryTitle = ({ entryId, compact }: EntryLinkProps) => {
|
|||
>
|
||||
<EntryTranslation
|
||||
source={titleCase(entry.title ?? "")}
|
||||
target={titleCase(translation.data?.title ?? "")}
|
||||
target={titleCase(translation?.title ?? "")}
|
||||
className="text-text inline-block select-text hyphens-auto duration-200"
|
||||
inline={false}
|
||||
bilingual
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
import { useEntry, usePrefetchEntryDetail } from "@follow/store/entry/hooks"
|
||||
import { useEntryTranslation, usePrefetchEntryTranslation } from "@follow/store/translation/hooks"
|
||||
import { tracker } from "@follow/tracker"
|
||||
import { createElement, useCallback, useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import {
|
||||
useEntryIsInReadability,
|
||||
useEntryIsInReadabilitySuccess,
|
||||
useEntryReadabilityContent,
|
||||
} from "~/atoms/readability"
|
||||
import { useEntryIsInReadability, useEntryIsInReadabilitySuccess } from "~/atoms/readability"
|
||||
import { useActionLanguage, useGeneralSettingKey } from "~/atoms/settings/general"
|
||||
import { useModalStack } from "~/components/ui/modal/stacked/hooks"
|
||||
import { useEntryTranslation } from "~/store/ai/hook"
|
||||
import { checkLanguage } from "~/lib/translate"
|
||||
|
||||
import { ImageGalleryContent } from "./components/ImageGalleryContent"
|
||||
|
||||
|
|
@ -40,27 +38,33 @@ export const useGalleryModal = () => {
|
|||
|
||||
export const useEntryContent = (entryId: string) => {
|
||||
const entry = useEntry(entryId, (state) => {
|
||||
const { inboxHandle, content } = state
|
||||
return { inboxId: inboxHandle, content }
|
||||
const { inboxHandle, content, readabilityContent } = state
|
||||
return { inboxId: inboxHandle, content, readabilityContent }
|
||||
})
|
||||
const { error, data, isPending } = usePrefetchEntryDetail(entryId)
|
||||
|
||||
const isInReadabilityMode = useEntryIsInReadability(entryId)
|
||||
const isReadabilitySuccess = useEntryIsInReadabilitySuccess(entryId)
|
||||
const readabilityContent = useEntryReadabilityContent(entryId)
|
||||
const contentTranslated = useEntryTranslation({
|
||||
entryId,
|
||||
extraFields: isReadabilitySuccess ? ["readabilityContent"] : ["content"],
|
||||
view: undefined,
|
||||
|
||||
const actionLanguage = useActionLanguage()
|
||||
const enableTranslation = useGeneralSettingKey("translation")
|
||||
const contentTranslated = useEntryTranslation(entryId, actionLanguage)
|
||||
usePrefetchEntryTranslation({
|
||||
entryIds: [entryId],
|
||||
checkLanguage,
|
||||
translation: enableTranslation,
|
||||
language: actionLanguage,
|
||||
withContent: true,
|
||||
target: isReadabilitySuccess ? "readabilityContent" : "content",
|
||||
})
|
||||
|
||||
return useMemo(() => {
|
||||
const entryContent = isInReadabilityMode
|
||||
? readabilityContent?.content
|
||||
? entry?.readabilityContent
|
||||
: (entry?.content ?? data?.content)
|
||||
const translatedContent = isInReadabilityMode
|
||||
? contentTranslated.data?.readabilityContent
|
||||
: contentTranslated.data?.content
|
||||
? contentTranslated?.readabilityContent
|
||||
: contentTranslated?.content
|
||||
const content = translatedContent || entryContent
|
||||
return {
|
||||
content,
|
||||
|
|
@ -68,14 +72,14 @@ export const useEntryContent = (entryId: string) => {
|
|||
isPending,
|
||||
}
|
||||
}, [
|
||||
contentTranslated.data?.content,
|
||||
contentTranslated.data?.readabilityContent,
|
||||
contentTranslated?.content,
|
||||
contentTranslated?.readabilityContent,
|
||||
data?.content,
|
||||
entry?.content,
|
||||
error,
|
||||
isInReadabilityMode,
|
||||
isPending,
|
||||
readabilityContent?.content,
|
||||
entry?.readabilityContent,
|
||||
])
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import {
|
|||
setReadabilityStatus,
|
||||
useEntryInReadabilityStatus,
|
||||
useEntryIsInReadability,
|
||||
useEntryReadabilityContent,
|
||||
} from "~/atoms/readability"
|
||||
import { enableShowSourceContent } from "~/atoms/source-content"
|
||||
import type { TocRef } from "~/components/ui/markdown/components/Toc"
|
||||
|
|
@ -81,7 +80,7 @@ export const TitleMetaHandler: Component<{
|
|||
export const ReadabilityNotice = ({ entryId }: { entryId: string }) => {
|
||||
const { t } = useTranslation()
|
||||
const { t: T } = useTranslation("common")
|
||||
const result = useEntryReadabilityContent(entryId)
|
||||
const result = useEntry(entryId, (state) => state.readabilityContent)
|
||||
const isInReadability = useEntryIsInReadability(entryId)
|
||||
const status = useEntryInReadabilityStatus(entryId)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,29 +1,11 @@
|
|||
import type { SupportedLanguages } from "@follow/models/types"
|
||||
import { getEntry } from "@follow/store/entry/getter"
|
||||
|
||||
import { getReadabilityContent } from "~/atoms/readability"
|
||||
import { apiClient } from "~/lib/api-fetch"
|
||||
import { defineQuery } from "~/lib/defineQuery"
|
||||
import { parseHtml } from "~/lib/parse-html"
|
||||
import { translate } from "~/lib/translate"
|
||||
|
||||
export const ai = {
|
||||
translation: ({
|
||||
entryId,
|
||||
view,
|
||||
language,
|
||||
extraFields,
|
||||
part,
|
||||
}: {
|
||||
entryId?: string | null
|
||||
view?: number | null
|
||||
language?: SupportedLanguages
|
||||
extraFields?: string[]
|
||||
part?: string
|
||||
}) =>
|
||||
defineQuery(["translation", entryId, view, language, extraFields, part], () =>
|
||||
translate({ entryId, view, language, extraFields, part }),
|
||||
),
|
||||
summary: ({
|
||||
entryId,
|
||||
language,
|
||||
|
|
@ -34,10 +16,8 @@ export const ai = {
|
|||
target?: "content" | "readabilityContent"
|
||||
}) =>
|
||||
defineQuery(["summary", entryId, language, target], async () => {
|
||||
const content =
|
||||
target === "readabilityContent"
|
||||
? getReadabilityContent()[entryId]?.content
|
||||
: getEntry(entryId)?.content
|
||||
const entry = getEntry(entryId)
|
||||
const content = target === "readabilityContent" ? entry?.readabilityContent : entry?.content
|
||||
if (!content) {
|
||||
return null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,53 +0,0 @@
|
|||
import type { FeedViewType } from "@follow/constants"
|
||||
import { useEntry } from "@follow/store/entry/hooks"
|
||||
import { useMemo } from "react"
|
||||
|
||||
import { useShowAITranslation, useShowAITranslationAuto } from "~/atoms/ai-translation"
|
||||
import { useActionLanguage } from "~/atoms/settings/general"
|
||||
import { useAuthQuery } from "~/hooks/common/useBizQuery"
|
||||
import { Queries } from "~/queries"
|
||||
|
||||
export function useEntryTranslation({
|
||||
entryId,
|
||||
extraFields,
|
||||
view,
|
||||
}: {
|
||||
entryId?: string
|
||||
extraFields?: string[]
|
||||
view: FeedViewType | undefined
|
||||
}) {
|
||||
const entry = useEntry(entryId, (state) => ({
|
||||
translation: state.settings?.translation,
|
||||
}))
|
||||
const actionLanguage = useActionLanguage()
|
||||
const showAITranslationFinal = useShowAITranslation(!!entry?.translation)
|
||||
const showAITranslationAuto = useShowAITranslationAuto(!!entry?.translation)
|
||||
const showAITranslation =
|
||||
!extraFields || extraFields.length === 0 ? showAITranslationAuto : showAITranslationFinal
|
||||
|
||||
const res = useAuthQuery(
|
||||
Queries.ai.translation({
|
||||
entryId,
|
||||
view,
|
||||
language: actionLanguage,
|
||||
extraFields,
|
||||
}),
|
||||
{
|
||||
enabled: showAITranslation,
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
meta: {
|
||||
persist: true,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
...res,
|
||||
// with persist option enabled, we need to explicitly set data to null when showAITranslation is false
|
||||
data: showAITranslation ? res.data : null,
|
||||
}),
|
||||
[res, showAITranslation],
|
||||
)
|
||||
}
|
||||
|
|
@ -536,19 +536,33 @@ class EntrySyncServices {
|
|||
return entry
|
||||
}
|
||||
|
||||
async fetchEntryReadabilityContent(entryId: EntryId) {
|
||||
async fetchEntryReadabilityContent(
|
||||
entryId: EntryId,
|
||||
fallBack?: () => Promise<string | null | undefined>,
|
||||
) {
|
||||
const entry = getEntry(entryId)
|
||||
|
||||
if (entry?.url && entry?.readabilityContent === null) {
|
||||
const { data: contentByFetch } = await apiClient().entries.readability.$get({
|
||||
query: {
|
||||
id: entryId,
|
||||
},
|
||||
})
|
||||
if (contentByFetch?.content && entry?.readabilityContent !== contentByFetch.content) {
|
||||
let readabilityContent: string | null | undefined
|
||||
|
||||
try {
|
||||
const { data: contentByFetch } = await apiClient().entries.readability.$get({
|
||||
query: {
|
||||
id: entryId,
|
||||
},
|
||||
})
|
||||
readabilityContent = contentByFetch?.content || null
|
||||
} catch (error) {
|
||||
if (fallBack) {
|
||||
readabilityContent = await fallBack()
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
if (readabilityContent && entry?.readabilityContent !== readabilityContent) {
|
||||
await entryActions.updateEntryContent({
|
||||
entryId,
|
||||
readabilityContent: contentByFetch.content,
|
||||
readabilityContent,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue