diff --git a/enjoy/src/constants/dicts.ts b/enjoy/src/constants/dicts.ts index d3c9ae50d..f55a686bf 100644 --- a/enjoy/src/constants/dicts.ts +++ b/enjoy/src/constants/dicts.ts @@ -1,4 +1,27 @@ export const DICTS = [ + { + name: "ccalecd", + fileName: "ccalecd.zip", + title: "Collins COBUILD Advanced British EN-CN Dictionary", + pronunciation: true, + lang: "En-CN", + downloadUrl: "https://dl.enjoy.bot/dicts/ccalecd.zip", + size: "13.879MB", + hash: "96940f85e52df4586b287e1859723a39", + addition: '', + }, + + { + name: "ccabeld", + fileName: "ccabeld.zip", + title: "Collins COBUILD Advanced British English Learners Dictionary", + pronunciation: true, + lang: "En-En", + downloadUrl: "https://dl.enjoy.bot/dicts/ccabeld.zip", + size: "485.6MB", + hash: "5b53498536f3ce3ed173752b7888ca51", + addition: '', + }, { name: "ldoce5", fileName: "ldoce5.zip", @@ -13,7 +36,7 @@ export const DICTS = [ { name: "oxford_en_mac", fileName: "oxford_en_mac.zip", - title: "Oxford Dictionary of English (Mac)", + title: "Oxford Dictionary of English", pronunciation: false, lang: "En-En", downloadUrl: "https://dl.enjoy.bot/dicts/oxford_en_mac.zip", @@ -24,7 +47,7 @@ export const DICTS = [ { name: "koen_mac", fileName: "koen_mac.zip", - title: "Korean English Dictionary (Mac)", + title: "Korean English Dictionary", pronunciation: false, lang: "Ko-En", downloadUrl: "https://dl.enjoy.bot/dicts/koen_mac.zip", @@ -46,7 +69,7 @@ export const DICTS = [ { name: "deen_mac", fileName: "deen_mac.zip", - title: "German English Dictionary (Mac)", + title: "German English Dictionary", pronunciation: false, lang: "Ge-En", downloadUrl: "https://dl.enjoy.bot/dicts/deen_mac.zip", @@ -57,7 +80,7 @@ export const DICTS = [ { name: "ruen_mac", fileName: "ruen_mac.zip", - title: "Russian English Dictionary (Mac)", + title: "Russian English Dictionary", pronunciation: false, lang: "Ru-En", downloadUrl: "https://dl.enjoy.bot/dicts/ruen_mac.zip", diff --git a/enjoy/src/i18n/en.json b/enjoy/src/i18n/en.json index 0ed5e3a8a..9a89cbb3a 100644 --- a/enjoy/src/i18n/en.json +++ b/enjoy/src/i18n/en.json @@ -751,7 +751,8 @@ "dictFileExist": "{{name}} has already been imported", "dictFileAddSuccess": "Add {{name}} successfully", "dictFileRemoveSuccess": "Remove {{name}} successfully", - "dictFileSetDefaultSuccess": "Set default successfully", + "dictFileSetDefaultSuccess": "Successfully set as default dictionary", + "dictFileRemoveDefaultSuccess": "Operation successful", "dictionaries": "Dictionaries", "import": "Import", "default": "Default", @@ -769,5 +770,6 @@ "removing": "Removing", "removeDictTitle": "Are you sure you want to delete this dictionary? ", "removeDictDescription": "It will delete the dictionary file from your local computer and you will have to download it again next time.", - "downloadingDict": "Downloading" + "downloadingDict": "Downloading", + "removeDefault": "No longer as Default" } diff --git a/enjoy/src/i18n/zh-CN.json b/enjoy/src/i18n/zh-CN.json index 8f614180a..a51cc072c 100644 --- a/enjoy/src/i18n/zh-CN.json +++ b/enjoy/src/i18n/zh-CN.json @@ -751,7 +751,8 @@ "dictFileExist": "{{name}} 已经存在了", "dictFileAddSuccess": "已添加 {{name}}", "dictFileRemoveSuccess": "已删除 {{name}}", - "dictFileSetDefaultSuccess": "设置成功", + "dictFileSetDefaultSuccess": "成功设置为默认词典", + "dictFileRemoveDefaultSuccess": "操作成功", "dictionaries": "词典", "import": "导入", "default": "默认", @@ -769,5 +770,6 @@ "removing": "正在删除", "removeDictTitle": "你确定要删除词典吗?", "removeDictDescription": "此操作将会从本地删除词典文件,下次安装需要重新下载", - "downloadingDict": "正在下载" + "downloadingDict": "正在下载", + "removeDefault": "不再设置为默认" } diff --git a/enjoy/src/main/downloader.ts b/enjoy/src/main/downloader.ts index 6720f3596..5bde5f457 100644 --- a/enjoy/src/main/downloader.ts +++ b/enjoy/src/main/downloader.ts @@ -130,7 +130,9 @@ class Downloader { resume(filename: string) { this.tasks .filter( - (t) => t.getFilename() === filename && t.getState() === "progressing" + (t) => + t.getFilename() === filename && + ["progressing", "interrupted"].includes(t.getState()) ) .forEach((t) => { t.resume(); diff --git a/enjoy/src/renderer/components/medias/media-captions/tab-content-translation.tsx b/enjoy/src/renderer/components/medias/media-captions/tab-content-translation.tsx index 0582ea072..5ea06ba12 100644 --- a/enjoy/src/renderer/components/medias/media-captions/tab-content-translation.tsx +++ b/enjoy/src/renderer/components/medias/media-captions/tab-content-translation.tsx @@ -2,6 +2,7 @@ import { useContext } from "react"; import { AppSettingsProviderContext, MediaPlayerProviderContext, + DictProviderContext, } from "@renderer/context"; import { TabsContent, Separator } from "@renderer/components/ui"; import { t } from "i18next"; @@ -9,6 +10,7 @@ import { TimelineEntry } from "echogarden/dist/utilities/Timeline.d.js"; import { convertWordIpaToNormal } from "@/utils"; import { CamdictLookupResult, + DictLookupResult, AiLookupResult, TranslateResult, } from "@renderer/components"; @@ -40,6 +42,7 @@ const SelectedWords = (props: { }) => { const { selectedIndices, caption } = props; + const { currentDictValue } = useContext(DictProviderContext); const { transcription } = useContext(MediaPlayerProviderContext); const { learningLanguage, ipaMappings } = useContext( AppSettingsProviderContext @@ -100,11 +103,16 @@ const SelectedWords = (props: { })} - {learningLanguage.startsWith("en") && ( + {currentDictValue === "cambridge" ? ( <> + ) : ( + <> + + + )} diff --git a/enjoy/src/renderer/components/preferences/dict-settings/downloading-dict-list.tsx b/enjoy/src/renderer/components/preferences/dict-settings/downloading-dict-list.tsx index 6124067ba..767b41c78 100644 --- a/enjoy/src/renderer/components/preferences/dict-settings/downloading-dict-list.tsx +++ b/enjoy/src/renderer/components/preferences/dict-settings/downloading-dict-list.tsx @@ -5,7 +5,7 @@ import { import { useContext, useEffect, useState } from "react"; import { Button, toast } from "@renderer/components/ui"; import { t } from "i18next"; -import { LoaderSpin } from "@renderer/components"; +import { LoaderIcon } from "lucide-react"; export const DownloadingDictList = function () { const { EnjoyApp } = useContext(AppSettingsProviderContext); @@ -129,7 +129,12 @@ const DownloadingDictItem = function ({ dict }: { dict: Dict }) { } function renderActions() { - if (loading) return ; + if (loading) + return ( +
+ +
+ ); if ( dict.downloadState?.state === "progressing" && diff --git a/enjoy/src/renderer/components/preferences/dict-settings/installed-dict-list.tsx b/enjoy/src/renderer/components/preferences/dict-settings/installed-dict-list.tsx index dd6d820e5..8ee55dc39 100644 --- a/enjoy/src/renderer/components/preferences/dict-settings/installed-dict-list.tsx +++ b/enjoy/src/renderer/components/preferences/dict-settings/installed-dict-list.tsx @@ -62,6 +62,15 @@ const InstalledDictItem = function ({ dict }: { dict: Dict }) { } } + async function handleRemoveDefault() { + try { + await setDefault(null); + toast.success(t("dictFileRemoveDefaultSuccess")); + } catch (err) { + toast.error(err.message); + } + } + async function handleRemove() { setRemoving(true); @@ -120,9 +129,15 @@ const InstalledDictItem = function ({ dict }: { dict: Dict }) { - + {settings.default === dict.name ? ( + + ) : ( + + )} ); } diff --git a/enjoy/src/renderer/components/widgets/lookup/dict-lookup-result.tsx b/enjoy/src/renderer/components/widgets/lookup/dict-lookup-result.tsx index 66f9bf7b5..10372be64 100644 --- a/enjoy/src/renderer/components/widgets/lookup/dict-lookup-result.tsx +++ b/enjoy/src/renderer/components/widgets/lookup/dict-lookup-result.tsx @@ -23,10 +23,12 @@ const MIME: Record = { export function DictLookupResult({ word, + autoHeight, onJump, }: { word: string; - onJump: (v: string) => void; + autoHeight?: boolean; + onJump?: (v: string) => void; }) { const { colorScheme } = useContext(ThemeProviderContext); const initialContent = ``; @@ -36,6 +38,7 @@ export function DictLookupResult({ const [looking, setLooking] = useState(false); const [notFound, setNotFound] = useState(false); const [error, setError] = useState(false); + const [height, setHeight] = useState(); useEffect(() => { if (currentDict && word) { @@ -72,6 +75,10 @@ export function DictLookupResult({ setError(false); } + function handleResize(h: number) { + setHeight(h); + } + if (looking) { return (
@@ -100,9 +107,14 @@ export function DictLookupResult({ - + ); } @@ -110,17 +122,38 @@ export function DictLookupResult({ export const DictLookupResultInner = ({ text, onJump, + onResize, + autoHeight, }: { text: string; - onJump: (v: string) => void; + autoHeight: boolean; + onJump?: (v: string) => void; + onResize?: (v: number) => void; }) => { const { EnjoyApp } = useContext(AppSettingsProviderContext); const { currentDict } = useContext(DictProviderContext); const { document: innerDocument } = useFrame(); const [html, setHtml] = useState(""); + const [hash, setHash] = useState(""); + + useEffect(() => { + if (autoHeight) { + const resizeObserver = new ResizeObserver(() => { + const html = innerDocument.getElementsByTagName("html")[0]; + onResize(html.scrollHeight); + }); + + resizeObserver.observe(innerDocument.getElementById("inner-dict")); + } + }, []); useEffect(() => { - normalize(); + normalize().then(() => { + if (hash) { + handleScroll(); + setHash(""); + } + }); return () => { normalizer.revoke(); @@ -166,7 +199,18 @@ export const DictLookupResultInner = ({ function handleJump(el: Element) { const word = el.getAttribute("data-word"); - onJump(word); + const hash = el.getAttribute("data-hash"); + onJump?.(word); + setHash(hash); + } + + function handleScroll() { + setTimeout(() => { + const el = innerDocument.querySelector(`a[name='${hash}']`); + if (el) { + el.scrollIntoView(); + } + }, 200); } const registerAudioHandler = () => { @@ -199,5 +243,5 @@ export const DictLookupResultInner = ({ }; }; - return
; + return
; }; diff --git a/enjoy/src/renderer/components/widgets/lookup/lookup-widget.tsx b/enjoy/src/renderer/components/widgets/lookup/lookup-widget.tsx index 4b1e4f41b..b8b733ef4 100644 --- a/enjoy/src/renderer/components/widgets/lookup/lookup-widget.tsx +++ b/enjoy/src/renderer/components/widgets/lookup/lookup-widget.tsx @@ -14,6 +14,7 @@ import { DictLookupResult, DictSelect, AiLookupResult, + CamdictLookupResult, } from "@renderer/components"; import { ChevronLeft, ChevronFirst } from "lucide-react"; @@ -41,12 +42,12 @@ export const LookupWidget = () => { ) => { let word = _word; let context = _context; + let sourceType; + let sourceId; - if (word) { - if (word.indexOf(" ") > -1) return; - setSelected({ word, context, position }); - } else { - const selection = document.getSelection(); + const selection = document.getSelection(); + + if (!word) { if (!selection?.anchorNode?.parentElement) return; word = selection @@ -54,24 +55,23 @@ export const LookupWidget = () => { .trim() .replace(/[.,/#!$%^&*;:{}=\-_`~()]+$/, ""); - if (!word) return; // can only lookup single word - if (word.indexOf(" ") > -1) return; + if (!word || word.indexOf(" ") > -1) return; + } + if (!context) { context = selection?.anchorNode.parentElement .closest(".sentence, h2, p, div") ?.textContent?.trim(); - - const sourceType = selection?.anchorNode.parentElement + sourceType = selection?.anchorNode.parentElement .closest("[data-source-type]") ?.getAttribute("data-source-type"); - const sourceId = selection?.anchorNode.parentElement + sourceId = selection?.anchorNode.parentElement .closest("[data-source-id]") ?.getAttribute("data-source-id"); - - setSelected({ word, context, position, sourceType, sourceId }); } + setSelected({ word, context, position, sourceType, sourceId }); handleLookup(word); setOpen(true); }; @@ -102,7 +102,7 @@ export const LookupWidget = () => { return ( { sourceId={selected?.sourceId} sourceType={selected?.sourceType} /> + ) : currentDictValue === "cambridge" ? ( + ) : ( )} diff --git a/enjoy/src/renderer/components/widgets/vocabulary.tsx b/enjoy/src/renderer/components/widgets/vocabulary.tsx index ec95e60b2..a51c1e632 100644 --- a/enjoy/src/renderer/components/widgets/vocabulary.tsx +++ b/enjoy/src/renderer/components/widgets/vocabulary.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from "react"; +import React, { useContext, useState } from "react"; import { AppSettingsProviderContext } from "@renderer/context"; export const Vocabulary = ({ @@ -10,18 +10,28 @@ export const Vocabulary = ({ context?: string; children?: React.ReactNode; }) => { - let timeout: ReturnType; - + let [timer, setTimer] = useState>(); const { vocabularyConfig, EnjoyApp } = useContext(AppSettingsProviderContext); - const handleMouseEnter = (e: React.MouseEvent) => { - timeout = setTimeout(() => { - EnjoyApp.lookup(word, context, { x: e.clientX, y: e.clientY }); - }, 500); + const handleMouseEnter = (e: any) => { + let _timer = setTimeout(() => { + if (!context) { + context = e.target?.parentElement + .closest(".sentence, h2, p, div") + ?.textContent?.trim(); + } + + const { x, bottom: y } = e.target.getBoundingClientRect(); + const _word = word.replace(/[^\w\s]|_/g, ""); + + EnjoyApp.lookup(_word, context, { x, y }); + }, 1000); + + setTimer(_timer); }; const handleMouseLeave = () => { - clearTimeout(timeout); + clearTimeout(timer); }; return vocabularyConfig.lookupOnMouseOver ? ( @@ -30,7 +40,7 @@ export const Vocabulary = ({ onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} > - {word || children} + {word || children} ) : ( {word || children} diff --git a/enjoy/src/renderer/context/dict-provider.tsx b/enjoy/src/renderer/context/dict-provider.tsx index a2f1ce9a2..cd692be99 100644 --- a/enjoy/src/renderer/context/dict-provider.tsx +++ b/enjoy/src/renderer/context/dict-provider.tsx @@ -23,6 +23,11 @@ const AIDict = { value: "ai", }; +const CamDict = { + text: t("cambridgeDictionary"), + value: "cambridge", +}; + const initialState: DictProviderState = { dicts: [], downloadingDicts: [], @@ -39,7 +44,7 @@ export const DictProviderContext = createContext(initialState); export const DictProvider = ({ children }: { children: React.ReactNode }) => { - const { EnjoyApp } = useContext(AppSettingsProviderContext); + const { EnjoyApp, learningLanguage } = useContext(AppSettingsProviderContext); const [dicts, setDicts] = useState([]); const [settings, setSettings] = useState({ default: "", @@ -60,14 +65,18 @@ export const DictProvider = ({ children }: { children: React.ReactNode }) => { ); const dictSelectItems = useMemo(() => { + const presets = learningLanguage.startsWith("en") + ? [CamDict, AIDict] + : [AIDict]; + return [ - AIDict, + ...presets, ...availableDicts.map((item) => ({ text: item.title, value: item.name, })), ]; - }, [availableDicts]); + }, [availableDicts, learningLanguage]); const downloadingDicts = useMemo(() => { return dicts.filter( @@ -84,18 +93,16 @@ export const DictProvider = ({ children }: { children: React.ReactNode }) => { }, [dicts]); useEffect(() => { - if (availableDicts.length) { - const _currentDict = availableDicts.find( - (dict) => dict.name === settings.default - ); + const defaultDict = availableDicts.find( + (dict) => dict.name === settings.default + ); - if (_currentDict) { - handleSetCurrentDict(_currentDict.name); - } else { - setDefault(availableDicts[0]); - } + if (defaultDict) { + handleSetCurrentDict(defaultDict.name); } else { - setCurrentDictValue(AIDict.value); + setCurrentDictValue( + learningLanguage.startsWith("en") ? CamDict.value : AIDict.value + ); } }, [availableDicts, settings]); @@ -123,7 +130,7 @@ export const DictProvider = ({ children }: { children: React.ReactNode }) => { if (dict) setCurrentDict(dict); }; - const setDefault = async (dict: Dict) => { + const setDefault = async (dict: Dict | null) => { const _settings = { ...settings, default: dict?.name ?? "" }; EnjoyApp.settings diff --git a/enjoy/src/renderer/lib/dict.ts b/enjoy/src/renderer/lib/dict.ts index 48e752ea0..8cb32356c 100644 --- a/enjoy/src/renderer/lib/dict.ts +++ b/enjoy/src/renderer/lib/dict.ts @@ -104,9 +104,16 @@ export class DictDefinitionNormalizer { ...this.$('a[href^="bword://"]').toArray(), ].map((link) => { const $link = this.$(link); - const word = $link.attr("_href") || $link.attr("href").substring(8); - $link.attr("data-type", "jump").attr("data-word", word); + const href = $link.attr("_href") || $link.attr("href").substring(8); + const [word, hash] = href.split("#"); + + if (word) { + $link + .attr("data-type", "jump") + .attr("data-word", word) + .attr("data-hash", hash); + } }) ); }