Skip to content

Commit

Permalink
feat: manual action (RSSNext#1867)
Browse files Browse the repository at this point in the history
  • Loading branch information
hyoban authored Nov 28, 2024
1 parent 97dee6e commit 0eedbba
Show file tree
Hide file tree
Showing 24 changed files with 368 additions and 169 deletions.
11 changes: 11 additions & 0 deletions apps/renderer/src/atoms/ai-summary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { atom } from "jotai"

import { createAtomHooks } from "~/lib/jotai"

export const [, , useShowAISummary, , getShowAISummary, setShowAISummary] = createAtomHooks(
atom<boolean>(false),
)

export const toggleShowAISummary = () => setShowAISummary(!getShowAISummary())
export const enableShowAISummary = () => setShowAISummary(true)
export const disableShowAISummary = () => setShowAISummary(false)
10 changes: 10 additions & 0 deletions apps/renderer/src/atoms/ai-translation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { atom } from "jotai"

import { createAtomHooks } from "~/lib/jotai"

export const [, , useShowAITranslation, , getShowAITranslation, setShowAITranslation] =
createAtomHooks(atom<boolean>(false))

export const toggleShowAITranslation = () => setShowAITranslation(!getShowAITranslation())
export const enableShowAITranslation = () => setShowAITranslation(true)
export const disableShowAITranslation = () => setShowAITranslation(false)
2 changes: 2 additions & 0 deletions apps/renderer/src/atoms/settings/general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const createDefaultSettings = (): GeneralSettings => ({
// App
appLaunchOnStartup: false,
language: "en",
translationLanguage: "zh-CN",

// mobile app
startupScreen: "timeline",
// Data control
Expand Down
13 changes: 5 additions & 8 deletions apps/renderer/src/components/ui/markdown/HTML.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { MemoedDangerousHTMLStyle } from "@follow/components/common/MemoedDangerousHTMLStyle.js"
import katexStyle from "katex/dist/katex.min.css?raw"
import { createElement, Fragment, memo, useEffect, useMemo, useRef, useState } from "react"
import { createElement, Fragment, memo, useEffect, useMemo, useState } from "react"

import { useShowAITranslation } from "~/atoms/ai-translation"
import { ENTRY_CONTENT_RENDER_CONTAINER_ID } from "~/constants/dom"
import { parseHtml } from "~/lib/parse-html"
import { useWrappedElementSize } from "~/providers/wrapped-element-provider"
Expand Down Expand Up @@ -53,15 +54,11 @@ const HTMLImpl = <A extends keyof JSX.IntrinsicElements = "div">(props: HTMLProp

const [refElement, setRefElement] = useState<HTMLElement | null>(null)

const onceRef = useRef(false)
useEffect(() => {
if (onceRef.current || !refElement) {
return
}
const showAITranslation = useShowAITranslation()

useEffect(() => {
translate?.(refElement)
onceRef.current = true
}, [translate, refElement])
}, [refElement, showAITranslation, translate])

const markdownElement = useMemo(
() =>
Expand Down
29 changes: 28 additions & 1 deletion apps/renderer/src/hooks/biz/useEntryActions.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { isMobile } from "@follow/components/hooks/useMobile.js"
import type { FeedViewType } from "@follow/constants"
import { FeedViewType } from "@follow/constants"
import type { ReactNode } from "react"
import { useCallback, useMemo } from "react"

import { useShowAISummary } from "~/atoms/ai-summary"
import { useShowAITranslation } from "~/atoms/ai-translation"
import {
getReadabilityStatus,
ReadabilityStatus,
Expand Down Expand Up @@ -82,6 +84,9 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view?: Fee
const isInbox = !!inbox

const isShowSourceContent = useShowSourceContent()
const isShowAISummary = useShowAISummary()
const isShowAITranslation = useShowAITranslation()

const getCmd = useGetCommand()
const runCmdFn = useRunCommandFn()
const actionConfigs = useMemo(() => {
Expand Down Expand Up @@ -156,6 +161,26 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view?: Fee
hide: !isShowSourceContent,
active: true,
},
{
id: COMMAND_ID.entry.toggleAISummary,
onClick: runCmdFn(COMMAND_ID.entry.toggleAISummary, []),
hide:
!!entry?.settings?.summary ||
([FeedViewType.SocialMedia, FeedViewType.Videos] as (number | undefined)[]).includes(
entry?.view,
),
active: isShowAISummary,
},
{
id: COMMAND_ID.entry.toggleAITranslation,
onClick: runCmdFn(COMMAND_ID.entry.toggleAITranslation, []),
hide:
!!entry?.settings?.translation ||
([FeedViewType.SocialMedia, FeedViewType.Videos] as (number | undefined)[]).includes(
entry?.view,
),
active: isShowAITranslation,
},
{
id: COMMAND_ID.entry.share,
onClick: runCmdFn(COMMAND_ID.entry.share, [{ entryId }]),
Expand Down Expand Up @@ -194,6 +219,8 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view?: Fee
getCmd,
inList,
isInbox,
isShowAISummary,
isShowAITranslation,
isShowSourceContent,
runCmdFn,
view,
Expand Down
4 changes: 4 additions & 0 deletions apps/renderer/src/hooks/biz/useNavigateEntry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { FeedViewType } from "@follow/constants"
import { isUndefined } from "es-toolkit/compat"
import { useCallback } from "react"

import { disableShowAISummary } from "~/atoms/ai-summary"
import { disableShowAITranslation } from "~/atoms/ai-translation"
import { setSidebarActiveView } from "~/atoms/sidebar"
import { resetShowSourceContent } from "~/atoms/source-content"
import {
Expand Down Expand Up @@ -69,6 +71,8 @@ export const navigateEntry = (options: NavigateEntryOptions) => {
setSidebarActiveView(view)
}
resetShowSourceContent()
disableShowAISummary()
disableShowAITranslation()

const finalView = nextSearchParams.get("view")

Expand Down
39 changes: 33 additions & 6 deletions apps/renderer/src/lib/immersive-translate.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { SupportedLanguages } from "@follow/models/types"
import { franc } from "franc-min"

import type { FlatEntryModel } from "~/store/entry"
Expand All @@ -22,15 +23,34 @@ export function immersiveTranslate({
html,
entry,
cache,
targetLanguage,
}: {
html?: HTMLElement
entry: FlatEntryModel
cache?: {
get: (key: string) => string | undefined
set: (key: string, value: string) => void
}
targetLanguage?: SupportedLanguages
}) {
if (!html || !entry.settings?.translation) {
if (!html) {
return
}

const translation = entry.settings?.translation ?? targetLanguage

const immersiveTranslateMark = html.querySelectorAll("[data-immersive-translate-mark=true]")
if (immersiveTranslateMark.length > 0) {
if (translation) {
return
}

for (const mark of immersiveTranslateMark) {
mark.remove()
}
}

if (!translation) {
return
}

Expand All @@ -42,7 +62,7 @@ export function immersiveTranslate({

translate({
entry,
language: entry.settings?.translation,
language: translation,
part: textNode.textContent,
extraFields: ["content"],
}).then((transformed) => {
Expand All @@ -52,8 +72,13 @@ export function immersiveTranslate({

const p = document.createElement("p")
p.append(document.createTextNode(textNode.textContent!))
p.append(document.createElement("br"))
p.append(document.createTextNode(transformed.content))

const fontTag = document.createElement("font")
fontTag.dataset["immersiveTranslateMark"] = "true"
fontTag.append(document.createElement("br"))
fontTag.append(document.createTextNode(transformed.content))

p.append(fontTag)

textNode.replaceWith(p)
})
Expand All @@ -70,14 +95,15 @@ export function immersiveTranslate({

for (const tag of tags) {
const sourceLanguage = franc(tag.textContent ?? "")
if (sourceLanguage === LanguageMap[entry.settings?.translation].code) {
if (sourceLanguage === LanguageMap[translation].code) {
return
}

const children = Array.from(tag.childNodes)
tag.dataset.childCount = children.filter((child) => child.textContent).length.toString()

const fontTag = document.createElement("font")
fontTag.dataset["immersiveTranslateMark"] = "true"

if (children.length > 0) {
fontTag.style.display = "none"
Expand Down Expand Up @@ -121,7 +147,7 @@ export function immersiveTranslate({

translate({
entry,
language: entry.settings?.translation,
language: translation,
part: textContent,
extraFields: ["content"],
}).then((transformed) => {
Expand All @@ -140,6 +166,7 @@ export function immersiveTranslate({
}

const parentFontTag = document.createElement("font")
parentFontTag.dataset["immersiveTranslateMark"] = "true"
parentFontTag.append(document.createElement("br"))
parentFontTag.append(fontTag)

Expand Down
17 changes: 13 additions & 4 deletions apps/renderer/src/lib/translate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,29 @@ import { apiClient } from "./api-fetch"
export const LanguageMap: Record<
SupportedLanguages,
{
label: string
value: string
code: string
}
> = {
en: {
value: "en",
label: "English",
code: "eng",
},
ja: {
value: "ja",
label: "Japanese",
code: "jpn",
},
"zh-CN": {
value: "zh-CN",
label: "Simplified Chinese",
code: "cmn",
},
"zh-TW": {
value: "zh-TW",
label: "Traditional Chinese(Taiwan)",
code: "cmn",
},
}
Expand All @@ -40,18 +50,17 @@ export async function translate({
if (!language) {
return null
}
let fields =
entry.settings?.translation && view !== undefined ? views[view!].translation.split(",") : []
let fields = language && view !== undefined ? views[view!].translation.split(",") : []
if (extraFields) {
fields = [...fields, ...extraFields]
}
const { franc } = await import("franc-min")

fields = fields.filter((field) => {
if (entry.settings?.translation && entry.entries[field]) {
if (language && entry.entries[field]) {
const sourceLanguage = franc(entry.entries[field])

if (sourceLanguage === LanguageMap[entry.settings?.translation].code) {
if (sourceLanguage === LanguageMap[language].code) {
return false
} else {
return true
Expand Down
18 changes: 18 additions & 0 deletions apps/renderer/src/modules/command/commands/entry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { useMutation } from "@tanstack/react-query"
import { useTranslation } from "react-i18next"
import { toast } from "sonner"

import { toggleShowAISummary } from "~/atoms/ai-summary"
import { toggleShowAITranslation } from "~/atoms/ai-translation"
import { setShowSourceContent, useSourceContentModal } from "~/atoms/source-content"
import { navigateEntry } from "~/hooks/biz/useNavigateEntry"
import { getRouteParams } from "~/hooks/biz/useRouteParams"
Expand Down Expand Up @@ -290,5 +292,21 @@ export const useRegisterEntryCommands = () => {
unread.mutate({ entryId, feedId: entry.feedId })
},
},
{
id: COMMAND_ID.entry.toggleAISummary,
label: t("entry_actions.toggle_ai_summary"),
icon: <i className="i-mgc-magic-2-cute-re" />,
run: () => {
toggleShowAISummary()
},
},
{
id: COMMAND_ID.entry.toggleAITranslation,
label: t("entry_actions.toggle_ai_translation"),
icon: <i className="i-mgc-translate-2-cute-re" />,
run: () => {
toggleShowAITranslation()
},
},
])
}
2 changes: 2 additions & 0 deletions apps/renderer/src/modules/command/commands/id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export const COMMAND_ID = {
share: "entry:share",
read: "entry:read",
unread: "entry:unread",
toggleAISummary: "entry:toggle-ai-summary",
toggleAITranslation: "entry:toggle-ai-translation",
},
integration: {
saveToEagle: "integration:save-to-eagle",
Expand Down
12 changes: 12 additions & 0 deletions apps/renderer/src/modules/command/commands/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ export type UnReadCommand = Command<{
fn: ({ entryId }) => void
}>

export type ToggleAISummaryCommand = Command<{
id: typeof COMMAND_ID.entry.toggleAISummary
fn: () => void
}>

export type ToggleAITranslationCommand = Command<{
id: typeof COMMAND_ID.entry.toggleAITranslation
fn: () => void
}>

export type EntryCommand =
| TipCommand
| StarCommand
Expand All @@ -76,6 +86,8 @@ export type EntryCommand =
| ShareCommand
| ReadCommand
| UnReadCommand
| ToggleAISummaryCommand
| ToggleAITranslationCommand

// Integration commands

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { useFeedActions } from "~/hooks/biz/useFeedActions"
import { useNavigateEntry } from "~/hooks/biz/useNavigateEntry"
import { useRouteParamsSelector } from "~/hooks/biz/useRouteParams"
import { useContextMenu } from "~/hooks/common/useContextMenu"
import { COMMAND_ID } from "~/modules/command/commands/id"
import type { FlatEntryModel } from "~/store/entry"
import { entryActions } from "~/store/entry"

Expand Down Expand Up @@ -77,12 +78,23 @@ export const EntryItemWrapper: FC<
setIsContextMenuOpen(true)
await showContextMenu(
[
...actionConfigs.map((item) => ({
type: "text" as const,
label: item.name,
click: () => item.onClick(),
shortcut: item.shortcut,
})),
...actionConfigs
.filter(
(item) =>
!(
[
COMMAND_ID.entry.viewSourceContent,
COMMAND_ID.entry.toggleAISummary,
COMMAND_ID.entry.toggleAITranslation,
] as string[]
).includes(item.id),
)
.map((item) => ({
type: "text" as const,
label: item.name,
click: () => item.onClick(),
shortcut: item.shortcut,
})),
{
type: "separator" as const,
},
Expand Down
Loading

0 comments on commit 0eedbba

Please sign in to comment.