feat: 🎸 update dicts (#1026)
This commit is contained in:
@@ -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: '<link href="theme.css" rel="stylesheet" type="text/css" />',
|
||||
},
|
||||
|
||||
{
|
||||
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: '<link href="theme.css" rel="stylesheet" type="text/css" />',
|
||||
},
|
||||
{
|
||||
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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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": "不再设置为默认"
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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: {
|
||||
})}
|
||||
</div>
|
||||
|
||||
{learningLanguage.startsWith("en") && (
|
||||
{currentDictValue === "cambridge" ? (
|
||||
<>
|
||||
<Separator className="my-2" />
|
||||
<CamdictLookupResult word={word} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Separator className="my-2" />
|
||||
<DictLookupResult word={word} autoHeight={true} />
|
||||
</>
|
||||
)}
|
||||
|
||||
<Separator className="my-2" />
|
||||
|
||||
@@ -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 <LoaderSpin />;
|
||||
if (loading)
|
||||
return (
|
||||
<div>
|
||||
<LoaderIcon className="text-muted-foreground animate-spin" />
|
||||
</div>
|
||||
);
|
||||
|
||||
if (
|
||||
dict.downloadState?.state === "progressing" &&
|
||||
|
||||
@@ -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 }) {
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
<Button size="sm" variant="secondary" onClick={handleSetDefault}>
|
||||
{t("setDefault")}
|
||||
</Button>
|
||||
{settings.default === dict.name ? (
|
||||
<Button size="sm" variant="secondary" onClick={handleRemoveDefault}>
|
||||
{t("removeDefault")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button size="sm" variant="secondary" onClick={handleSetDefault}>
|
||||
{t("setDefault")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,10 +23,12 @@ const MIME: Record<string, string> = {
|
||||
|
||||
export function DictLookupResult({
|
||||
word,
|
||||
autoHeight,
|
||||
onJump,
|
||||
}: {
|
||||
word: string;
|
||||
onJump: (v: string) => void;
|
||||
autoHeight?: boolean;
|
||||
onJump?: (v: string) => void;
|
||||
}) {
|
||||
const { colorScheme } = useContext(ThemeProviderContext);
|
||||
const initialContent = `<!DOCTYPE html><html class=${colorScheme}><head></head><body></body></html>`;
|
||||
@@ -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<number>();
|
||||
|
||||
useEffect(() => {
|
||||
if (currentDict && word) {
|
||||
@@ -72,6 +75,10 @@ export function DictLookupResult({
|
||||
setError(false);
|
||||
}
|
||||
|
||||
function handleResize(h: number) {
|
||||
setHeight(h);
|
||||
}
|
||||
|
||||
if (looking) {
|
||||
return (
|
||||
<div className="text-center">
|
||||
@@ -100,9 +107,14 @@ export function DictLookupResult({
|
||||
<Frame
|
||||
initialContent={initialContent}
|
||||
mountTarget="body"
|
||||
className="h-full"
|
||||
style={{ height: autoHeight ? `${height}px` : "100%" }}
|
||||
>
|
||||
<DictLookupResultInner text={definition} onJump={onJump} />
|
||||
<DictLookupResultInner
|
||||
text={definition}
|
||||
onJump={onJump}
|
||||
autoHeight={autoHeight}
|
||||
onResize={handleResize}
|
||||
/>
|
||||
</Frame>
|
||||
);
|
||||
}
|
||||
@@ -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(() => {
|
||||
normalize();
|
||||
if (autoHeight) {
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
const html = innerDocument.getElementsByTagName("html")[0];
|
||||
onResize(html.scrollHeight);
|
||||
});
|
||||
|
||||
resizeObserver.observe(innerDocument.getElementById("inner-dict"));
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
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 <div dangerouslySetInnerHTML={{ __html: html }}></div>;
|
||||
return <div id="inner-dict" dangerouslySetInnerHTML={{ __html: html }}></div>;
|
||||
};
|
||||
|
||||
@@ -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 (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverAnchor
|
||||
className="absolute w-0 h-0"
|
||||
className="fixed w-0 h-0"
|
||||
style={{
|
||||
top: selected?.position?.y,
|
||||
left: selected?.position?.x,
|
||||
@@ -152,6 +152,8 @@ export const LookupWidget = () => {
|
||||
sourceId={selected?.sourceId}
|
||||
sourceType={selected?.sourceType}
|
||||
/>
|
||||
) : currentDictValue === "cambridge" ? (
|
||||
<CamdictLookupResult word={selected?.word} />
|
||||
) : (
|
||||
<DictLookupResult word={current} onJump={handleLookup} />
|
||||
)}
|
||||
|
||||
@@ -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<typeof setTimeout>;
|
||||
|
||||
let [timer, setTimer] = useState<ReturnType<typeof setTimeout>>();
|
||||
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}
|
||||
>
|
||||
<span>{word || children}</span>
|
||||
{word || children}
|
||||
</span>
|
||||
) : (
|
||||
<span>{word || children}</span>
|
||||
|
||||
@@ -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<DictProviderState>(initialState);
|
||||
|
||||
export const DictProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const { EnjoyApp } = useContext(AppSettingsProviderContext);
|
||||
const { EnjoyApp, learningLanguage } = useContext(AppSettingsProviderContext);
|
||||
const [dicts, setDicts] = useState<Dict[]>([]);
|
||||
const [settings, setSettings] = useState<DictSettingType>({
|
||||
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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user