feat: 🎸 update dicts (#1062)
This commit is contained in:
23
enjoy/assets/styles/mdict-theme.css
Normal file
23
enjoy/assets/styles/mdict-theme.css
Normal file
@@ -0,0 +1,23 @@
|
||||
html {
|
||||
--background-color: #fff;
|
||||
--text-color: #505050;
|
||||
--white: #fff;
|
||||
--black: #000;
|
||||
|
||||
padding-right: 4px;
|
||||
background: var(--background-color);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
background-color: var(--background-color);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: var(--background-color);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 6px;
|
||||
background-color: #cccccc;
|
||||
}
|
||||
@@ -89,6 +89,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@andrkrn/ffprobe-static": "^5.2.0",
|
||||
"@divisey/js-mdict": "^5.0.0",
|
||||
"@electron-forge/publisher-s3": "^7.4.0",
|
||||
"@hookform/resolvers": "^3.9.0",
|
||||
"@langchain/community": "^0.2.32",
|
||||
|
||||
@@ -783,7 +783,7 @@
|
||||
"selectDir": "Select Folder",
|
||||
"selectFile": "Select File",
|
||||
"importMdictFile": "Import the original dictionary file",
|
||||
"mdictFileTip": "Directly import .mdx .mdd format files (.mdx files are required, .mdd files are optional and can have multiple), but there may be problems with style and usability.",
|
||||
"mdictFileTip": "Directly import .mdx .mdd format files (.mdx files are required, .mdd files are optional and can have multiple).",
|
||||
"authorizationExpired": "Your authorization has expired. Please login again.",
|
||||
"selectUser": "Select user",
|
||||
"export": "Export",
|
||||
|
||||
@@ -783,7 +783,7 @@
|
||||
"selectDir": "选择文件夹",
|
||||
"selectFile": "选择文件",
|
||||
"importMdictFile": "导入原词典文件",
|
||||
"mdictFileTip": "直接导入 .mdx .mdd 格式的文件 (.mdx 文件是必须的,.mdd 文件是可选的且可以有多个),不过样式和可用性可能存在问题。",
|
||||
"mdictFileTip": "直接导入 .mdx .mdd 格式的文件 (.mdx 文件是必须的,.mdd 文件是可选的且可以有多个)。",
|
||||
"authorizationExpired": "您的登录授权已过期,请重新登录。",
|
||||
"selectUser": "选择用户",
|
||||
"export": "导出",
|
||||
|
||||
143
enjoy/src/main/mdict.ts
Normal file
143
enjoy/src/main/mdict.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import log from "@main/logger";
|
||||
import path from "path";
|
||||
import fs from "fs-extra";
|
||||
import { ipcMain } from "electron";
|
||||
import { LRUCache } from "lru-cache";
|
||||
import { Mdict as MdictReader } from "@divisey/js-mdict";
|
||||
import { hashFile } from "@/main/utils";
|
||||
import settings from "./settings";
|
||||
|
||||
const logger = log.scope("dict");
|
||||
|
||||
export class MDictHandler {
|
||||
private cache = new LRUCache({ max: 20 });
|
||||
private mdx: MdictReader;
|
||||
private mdds: Record<string, MdictReader>;
|
||||
private currentDictHash: string;
|
||||
|
||||
addition =
|
||||
'<link href="/assets/styles/mdict-theme.css" rel="stylesheet" type="text/css" />';
|
||||
|
||||
get dictsPath() {
|
||||
const _path = path.join(settings.libraryPath(), "dictionaries");
|
||||
fs.ensureDirSync(_path);
|
||||
|
||||
return _path;
|
||||
}
|
||||
|
||||
async import(pathes: string[]) {
|
||||
const mdxs = pathes.filter((_path) => _path.match(/\.mdx$/));
|
||||
const mdds = pathes.filter((_path) => _path.match(/\.mdd$/));
|
||||
|
||||
if (mdxs.length > 1) {
|
||||
throw new Error("Multi mdx file found, only one is allowed");
|
||||
}
|
||||
|
||||
if (mdxs.length < 1) {
|
||||
throw new Error("No mdx file found");
|
||||
}
|
||||
|
||||
const mdx = mdxs[0];
|
||||
const hash = await hashFile(mdx, { algo: "md5" });
|
||||
|
||||
for (let _path of pathes) {
|
||||
await fs.copy(
|
||||
_path,
|
||||
path.join(this.dictsPath, hash, path.basename(_path))
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
title: path.basename(mdx, ".mdx"),
|
||||
mdx,
|
||||
mdds,
|
||||
hash,
|
||||
};
|
||||
}
|
||||
|
||||
async remove(mdict: MDict) {
|
||||
await fs.remove(path.join(this.dictsPath, mdict.hash));
|
||||
}
|
||||
|
||||
lookup(word: string, mdict: MDict): string {
|
||||
if (mdict.hash !== this.currentDictHash) {
|
||||
this.mdx = new MdictReader(mdict.mdx);
|
||||
}
|
||||
|
||||
let result = this.mdx.lookup(word)?.definition ?? null;
|
||||
|
||||
if (result?.startsWith("@@@LINK=")) {
|
||||
return this.lookup(result.substring(8), mdict);
|
||||
}
|
||||
|
||||
return result ? `${this.addition}${result}` : null;
|
||||
}
|
||||
|
||||
getResource(key: string, mdict: MDict) {
|
||||
const cacheKey = `${mdict.hash}-${key}`;
|
||||
const cachedValue = this.cache.get(cacheKey);
|
||||
|
||||
if (cachedValue) {
|
||||
return cachedValue;
|
||||
}
|
||||
|
||||
if (mdict.hash !== this.currentDictHash) {
|
||||
this.mdds = {};
|
||||
}
|
||||
|
||||
if (key.match(/\.(css|js)$/)) {
|
||||
const _path = path.join(this.dictsPath, mdict.hash, key);
|
||||
|
||||
if (fs.existsSync(_path)) {
|
||||
const data = fs.readFileSync(_path, { encoding: "base64" });
|
||||
|
||||
this.cache.set(cacheKey, data);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
for (let _path of mdict.mdds) {
|
||||
if (!this.mdds[_path]) {
|
||||
this.mdds[_path] = new MdictReader(_path);
|
||||
}
|
||||
|
||||
const parsedKey =
|
||||
"\\" + key.replace(/(^[/\\])|([/]$)/, "").replace(/\//g, "\\");
|
||||
const data = this.mdds[_path].locate(parsedKey);
|
||||
|
||||
if (data) {
|
||||
this.cache.set(cacheKey, data.definition);
|
||||
return data.definition;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(`Failed to read resource ${key}`, err);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
registerIpcHandlers() {
|
||||
ipcMain.handle("mdict-import", async (_event, pathes: string[]) =>
|
||||
this.import(pathes)
|
||||
);
|
||||
|
||||
ipcMain.handle("mdict-remove", async (_event, mdict: MDict) =>
|
||||
this.remove(mdict)
|
||||
);
|
||||
|
||||
ipcMain.handle("mdict-lookup", async (_event, word: string, mdict: MDict) =>
|
||||
this.lookup(word, mdict)
|
||||
);
|
||||
|
||||
ipcMain.handle(
|
||||
"mdict-read-file",
|
||||
async (_event, word: string, mdict: MDict) =>
|
||||
this.getResource(word, mdict)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default new MDictHandler();
|
||||
@@ -23,6 +23,7 @@ import url from "url";
|
||||
import echogarden from "./echogarden";
|
||||
import camdict from "./camdict";
|
||||
import dict from "./dict";
|
||||
import mdict from "./mdict";
|
||||
import decompresser from "./decompresser";
|
||||
import { UserSetting } from "@main/db/models";
|
||||
|
||||
@@ -53,6 +54,7 @@ main.init = async () => {
|
||||
|
||||
camdict.registerIpcHandlers();
|
||||
dict.registerIpcHandlers();
|
||||
mdict.registerIpcHandlers();
|
||||
|
||||
// Prepare Settings
|
||||
settings.registerIpcHandlers();
|
||||
|
||||
@@ -249,6 +249,14 @@ contextBridge.exposeInMainWorld("__ENJOY_APP__", {
|
||||
return ipcRenderer.invoke("camdict-lookup", word);
|
||||
},
|
||||
},
|
||||
mdict: {
|
||||
remove: (dict: Dict) => ipcRenderer.invoke("mdict-remove", dict),
|
||||
getResource: (key: string, dict: Dict) =>
|
||||
ipcRenderer.invoke("mdict-read-file", key, dict),
|
||||
lookup: (word: string, dict: Dict) =>
|
||||
ipcRenderer.invoke("mdict-lookup", word, dict),
|
||||
import: (pathes: string[]) => ipcRenderer.invoke("mdict-import", pathes),
|
||||
},
|
||||
dict: {
|
||||
getDicts: () => ipcRenderer.invoke("dict-list"),
|
||||
remove: (dict: Dict) => ipcRenderer.invoke("dict-remove", dict),
|
||||
|
||||
@@ -16,7 +16,7 @@ import { t } from "i18next";
|
||||
import { LoaderIcon } from "lucide-react";
|
||||
|
||||
export const DictImportButton = () => {
|
||||
const { reload } = useContext(DictProviderContext);
|
||||
const { reload, importMDict } = useContext(DictProviderContext);
|
||||
const { EnjoyApp } = useContext(AppSettingsProviderContext);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -49,8 +49,21 @@ export const DictImportButton = () => {
|
||||
const handleOriginDictImport = async () => {
|
||||
const pathes = await EnjoyApp.dialog.showOpenDialog({
|
||||
title: t("selectMdictFileOrDirTitle"),
|
||||
properties: ["multiSelections", "openFile", "openDirectory"],
|
||||
properties: ["multiSelections", "openFile"],
|
||||
});
|
||||
|
||||
if (!pathes[0]) return;
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const mdict = await EnjoyApp.mdict.import(pathes);
|
||||
await importMDict(mdict);
|
||||
setOpen(false);
|
||||
} catch (err) {
|
||||
toast.error(err.message);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -97,7 +110,7 @@ export const DictImportButton = () => {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* <div className="flex items-center justify-between py-4">
|
||||
<div className="flex items-center justify-between py-4">
|
||||
<div className="mr-4">
|
||||
<div className="mb-2">{t("importMdictFile")}</div>
|
||||
<div className="text-xs text-muted-foreground mb-2">
|
||||
@@ -106,9 +119,9 @@ export const DictImportButton = () => {
|
||||
</div>
|
||||
|
||||
<Button size="sm" onClick={handleOriginDictImport}>
|
||||
{t("selectDir")}
|
||||
{t("selectFile")}
|
||||
</Button>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
|
||||
@@ -57,7 +57,7 @@ export const InstalledDictList = function () {
|
||||
))}
|
||||
|
||||
{installedDicts.map((item) => (
|
||||
<InstalledDictItem key={item.name} dict={item} />
|
||||
<InstalledDictItem key={item.value} dict={item} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
@@ -79,14 +79,14 @@ const DecompressDictItem = function ({ task }: { task: DecompressTask }) {
|
||||
);
|
||||
};
|
||||
|
||||
const InstalledDictItem = function ({ dict }: { dict: Dict }) {
|
||||
const InstalledDictItem = function ({ dict }: { dict: DictItem }) {
|
||||
const { EnjoyApp } = useContext(AppSettingsProviderContext);
|
||||
const { settings, setDefault, reload, remove, removed } =
|
||||
const { settings, setDefault, reload, remove } =
|
||||
useContext(DictProviderContext);
|
||||
const [removing, setRemoving] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (settings.removing?.find((v) => v === dict.name)) {
|
||||
if (settings.removing?.find((v) => v === dict.value)) {
|
||||
handleRemove();
|
||||
}
|
||||
}, []);
|
||||
@@ -113,9 +113,7 @@ const InstalledDictItem = function ({ dict }: { dict: Dict }) {
|
||||
setRemoving(true);
|
||||
|
||||
try {
|
||||
remove(dict);
|
||||
await EnjoyApp.dict.remove(dict);
|
||||
removed(dict);
|
||||
await remove(dict);
|
||||
toast.success(t("dictRemoved"));
|
||||
} catch (err) {
|
||||
toast.error(err.message);
|
||||
@@ -135,64 +133,62 @@ const InstalledDictItem = function ({ dict }: { dict: Dict }) {
|
||||
);
|
||||
}
|
||||
|
||||
if (dict.state === "installed") {
|
||||
return (
|
||||
<div className="hidden group-hover:inline-flex ">
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
className="text-destructive mr-2"
|
||||
>
|
||||
{t("remove")}
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{t("removeDictTitle")}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{t("removeDictDescription")}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>{t("cancel")}</AlertDialogCancel>
|
||||
<AlertDialogAction asChild>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
className="text-destructive mr-2"
|
||||
onClick={handleRemove}
|
||||
>
|
||||
{t("remove")}
|
||||
</Button>
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
return (
|
||||
<div className="hidden group-hover:inline-flex ">
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
className="text-destructive mr-2"
|
||||
>
|
||||
{t("remove")}
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{t("removeDictTitle")}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{t("removeDictDescription")}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>{t("cancel")}</AlertDialogCancel>
|
||||
<AlertDialogAction asChild>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
className="text-destructive mr-2"
|
||||
onClick={handleRemove}
|
||||
>
|
||||
{t("remove")}
|
||||
</Button>
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
{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>
|
||||
);
|
||||
}
|
||||
{settings.default === dict.value ? (
|
||||
<Button size="sm" variant="secondary" onClick={handleRemoveDefault}>
|
||||
{t("removeDefault")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button size="sm" variant="secondary" onClick={handleSetDefault}>
|
||||
{t("setDefault")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={dict.name}
|
||||
key={dict.value}
|
||||
className="flex justify-between items-center group cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center text-sm text-left h-8 hover:opacity-80">
|
||||
<span className="mr-2">{dict.title}</span>
|
||||
{settings.default === dict.name && (
|
||||
<span className="mr-2">{dict.text}</span>
|
||||
{settings.default === dict.value && (
|
||||
<span className="text-indigo bg-secondary text-xs py-1 px-2 rounded">
|
||||
{t("default")}
|
||||
</span>
|
||||
|
||||
@@ -32,8 +32,7 @@ export function DictLookupResult({
|
||||
}) {
|
||||
const { colorScheme } = useContext(ThemeProviderContext);
|
||||
const initialContent = `<!DOCTYPE html><html class=${colorScheme}><head></head><body></body></html>`;
|
||||
const { EnjoyApp } = useContext(AppSettingsProviderContext);
|
||||
const { currentDict } = useContext(DictProviderContext);
|
||||
const { currentDict, lookup } = useContext(DictProviderContext);
|
||||
const [definition, setDefinition] = useState("");
|
||||
const [looking, setLooking] = useState(false);
|
||||
const [notFound, setNotFound] = useState(false);
|
||||
@@ -42,11 +41,11 @@ export function DictLookupResult({
|
||||
|
||||
useEffect(() => {
|
||||
if (currentDict && word) {
|
||||
lookup();
|
||||
handleLookup();
|
||||
}
|
||||
}, [currentDict, word]);
|
||||
|
||||
async function lookup() {
|
||||
async function handleLookup() {
|
||||
revoke();
|
||||
setLooking(true);
|
||||
|
||||
@@ -56,8 +55,7 @@ export function DictLookupResult({
|
||||
|
||||
const _word = word.trim().indexOf(" ") > -1 ? word : word.toLowerCase();
|
||||
|
||||
EnjoyApp.dict
|
||||
.lookup(_word, currentDict)
|
||||
lookup(_word, currentDict)
|
||||
.then((result) => {
|
||||
if (!result) {
|
||||
setNotFound(true);
|
||||
@@ -134,8 +132,7 @@ export const DictLookupResultInner = ({
|
||||
onJump?: (v: string) => void;
|
||||
onResize?: (v: number) => void;
|
||||
}) => {
|
||||
const { EnjoyApp } = useContext(AppSettingsProviderContext);
|
||||
const { currentDict } = useContext(DictProviderContext);
|
||||
const { currentDict, getResource } = useContext(DictProviderContext);
|
||||
const { document: innerDocument } = useFrame();
|
||||
const [html, setHtml] = useState("");
|
||||
const [hash, setHash] = useState("");
|
||||
@@ -178,7 +175,7 @@ export const DictLookupResultInner = ({
|
||||
};
|
||||
|
||||
const handleReadResource = async (key: string) => {
|
||||
return EnjoyApp.dict.getResource(key, currentDict);
|
||||
return getResource(key, currentDict);
|
||||
};
|
||||
|
||||
const normalizer = new DictDefinitionNormalizer({
|
||||
|
||||
@@ -8,35 +8,38 @@ import { UserSettingKeyEnum } from "@/types/enums";
|
||||
|
||||
type DictProviderState = {
|
||||
settings: DictSettingType;
|
||||
dicts: Dict[];
|
||||
installedDicts: Dict[];
|
||||
dictSelectItems: { text: string; value: string }[];
|
||||
installedDicts: DictItem[];
|
||||
dictSelectItems: DictItem[];
|
||||
reload?: () => void;
|
||||
remove?: (v: Dict) => void;
|
||||
removed?: (v: Dict) => void;
|
||||
currentDict?: Dict | null;
|
||||
importMDict?: (mdict: MDict) => Promise<void>;
|
||||
lookup?: (word: string, dict: DictItem) => Promise<string | null>;
|
||||
getResource?: (key: string, dict: DictItem) => Promise<string | null>;
|
||||
remove?: (v: DictItem) => Promise<void>;
|
||||
currentDict?: DictItem | null;
|
||||
currentDictValue?: string;
|
||||
handleSetCurrentDict?: (v: string) => void;
|
||||
setDefault?: (v: Dict) => Promise<void>;
|
||||
setDefault?: (v: DictItem) => Promise<void>;
|
||||
};
|
||||
|
||||
const AIDict = {
|
||||
type: "preset" as DictType,
|
||||
text: t("aiLookup"),
|
||||
value: "ai",
|
||||
};
|
||||
|
||||
const CamDict = {
|
||||
type: "preset" as DictType,
|
||||
text: t("cambridgeDictionary"),
|
||||
value: "cambridge",
|
||||
};
|
||||
|
||||
const initialState: DictProviderState = {
|
||||
dicts: [],
|
||||
installedDicts: [],
|
||||
dictSelectItems: [AIDict],
|
||||
settings: {
|
||||
default: "",
|
||||
removing: [],
|
||||
mdicts: [],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -49,47 +52,50 @@ export const DictProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const [settings, setSettings] = useState<DictSettingType>({
|
||||
default: "",
|
||||
removing: [],
|
||||
mdicts: [],
|
||||
});
|
||||
const [currentDictValue, setCurrentDictValue] = useState<string>("");
|
||||
const [currentDict, setCurrentDict] = useState<Dict | null>();
|
||||
const [currentDict, setCurrentDict] = useState<DictItem | null>();
|
||||
const { state: dbState } = useContext(DbProviderContext);
|
||||
|
||||
const availableDicts = useMemo(
|
||||
() =>
|
||||
dicts.filter((dict) => {
|
||||
return (
|
||||
dict.state === "installed" &&
|
||||
!settings.removing?.find((v) => v === dict.name)
|
||||
);
|
||||
}),
|
||||
[dicts, settings]
|
||||
);
|
||||
const installedDicts = useMemo<DictItem[]>(() => {
|
||||
const _dicts = dicts
|
||||
.filter((dict) => dict.state === "installed")
|
||||
.map((dict) => ({
|
||||
type: "dict" as DictType,
|
||||
text: dict.title,
|
||||
value: dict.name,
|
||||
}));
|
||||
const _mdicts = settings.mdicts.map((mdict) => ({
|
||||
type: "mdict" as DictType,
|
||||
text: mdict.title,
|
||||
value: mdict.hash,
|
||||
}));
|
||||
|
||||
return [..._dicts, ..._mdicts];
|
||||
}, [dicts, settings]);
|
||||
|
||||
const availableDicts = useMemo(() => {
|
||||
return installedDicts.filter(
|
||||
({ value }) => !settings.removing.find((v) => v === value)
|
||||
);
|
||||
}, [installedDicts, settings]);
|
||||
|
||||
const dictSelectItems = useMemo(() => {
|
||||
const presets = learningLanguage.startsWith("en")
|
||||
? [CamDict, AIDict]
|
||||
: [AIDict];
|
||||
|
||||
return [
|
||||
...presets,
|
||||
...availableDicts.map((item) => ({
|
||||
text: item.title,
|
||||
value: item.name,
|
||||
})),
|
||||
];
|
||||
return [...presets, ...availableDicts];
|
||||
}, [availableDicts, learningLanguage]);
|
||||
|
||||
const installedDicts = useMemo(() => {
|
||||
return dicts.filter((dict) => dict.state === "installed");
|
||||
}, [dicts]);
|
||||
|
||||
useEffect(() => {
|
||||
const defaultDict = availableDicts.find(
|
||||
(dict) => dict.name === settings.default
|
||||
(dict) => dict.value === settings.default
|
||||
);
|
||||
|
||||
if (defaultDict) {
|
||||
handleSetCurrentDict(defaultDict.name);
|
||||
handleSetCurrentDict(defaultDict.value);
|
||||
} else {
|
||||
setCurrentDictValue(
|
||||
learningLanguage.startsWith("en") ? CamDict.value : AIDict.value
|
||||
@@ -110,6 +116,12 @@ export const DictProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const updateSettings = async (_settings: DictSettingType) => {
|
||||
return EnjoyApp.userSettings
|
||||
.set(UserSettingKeyEnum.DICTS, _settings)
|
||||
.then(() => setSettings(_settings));
|
||||
};
|
||||
|
||||
const fetchDicts = async () => {
|
||||
return EnjoyApp.dict.getDicts().then((dicts) => {
|
||||
setDicts(dicts);
|
||||
@@ -119,46 +131,78 @@ export const DictProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const handleSetCurrentDict = (value: string) => {
|
||||
setCurrentDictValue(value);
|
||||
|
||||
const dict = dicts.find((dict) => dict.name === value);
|
||||
const dict = availableDicts.find((dict) => dict.value === value);
|
||||
if (dict) setCurrentDict(dict);
|
||||
};
|
||||
|
||||
const setDefault = async (dict: Dict | null) => {
|
||||
const _settings = { ...settings, default: dict?.name ?? "" };
|
||||
|
||||
EnjoyApp.userSettings
|
||||
.set(UserSettingKeyEnum.DICTS, _settings)
|
||||
.then(() => setSettings(_settings));
|
||||
const setDefault = async (dict: DictItem | null) => {
|
||||
updateSettings({ ...settings, default: dict?.value ?? "" });
|
||||
};
|
||||
|
||||
const remove = (dict: Dict) => {
|
||||
if (!settings.removing?.find((name) => dict.name === name)) {
|
||||
const removing = [...(settings.removing ?? []), dict.name];
|
||||
const _settings = { ...settings, removing };
|
||||
const remove = async (dict: DictItem) => {
|
||||
const isRemoving = settings.removing?.find((value) => dict.value === value);
|
||||
if (isRemoving) return;
|
||||
|
||||
EnjoyApp.userSettings
|
||||
.set(UserSettingKeyEnum.DICTS, _settings)
|
||||
.then(() => setSettings(_settings));
|
||||
await updateSettings({
|
||||
...settings,
|
||||
removing: [...(settings.removing ?? []), dict.value],
|
||||
});
|
||||
|
||||
if (dict.type === "dict") {
|
||||
const _dict = dicts.find(({ name }) => name === dict.value);
|
||||
await EnjoyApp.dict.remove(_dict);
|
||||
} else if (dict.type === "mdict") {
|
||||
const _dict = settings.mdicts.find(({ hash }) => hash === dict.value);
|
||||
await EnjoyApp.mdict.remove(_dict);
|
||||
}
|
||||
|
||||
await updateSettings({
|
||||
...settings,
|
||||
mdicts: settings.mdicts?.filter(({ hash }) => dict.value !== hash) ?? [],
|
||||
removing:
|
||||
settings.removing?.filter((value) => value !== dict.value) ?? [],
|
||||
});
|
||||
};
|
||||
|
||||
const lookup = async (word: string, dict: DictItem) => {
|
||||
if (dict.type === "dict") {
|
||||
const _dict = dicts.find(({ name }) => name === dict.value);
|
||||
return EnjoyApp.dict.lookup(word, _dict);
|
||||
} else if (dict.type === "mdict") {
|
||||
const _dict = settings.mdicts.find(({ hash }) => hash === dict.value);
|
||||
return EnjoyApp.mdict.lookup(word, _dict);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const removed = (dict: Dict) => {
|
||||
const removing =
|
||||
settings.removing?.filter((name) => name !== dict.name) ?? [];
|
||||
const _settings = { ...settings, removing };
|
||||
const getResource = async (key: string, dict: DictItem) => {
|
||||
if (dict.type === "dict") {
|
||||
const _dict = dicts.find(({ name }) => name === dict.value);
|
||||
return EnjoyApp.dict.getResource(key, _dict);
|
||||
} else if (dict.type === "mdict") {
|
||||
const _dict = settings.mdicts.find(({ hash }) => hash === dict.value);
|
||||
return EnjoyApp.mdict.getResource(key, _dict);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
EnjoyApp.userSettings
|
||||
.set(UserSettingKeyEnum.DICTS, _settings)
|
||||
.then(() => setSettings(_settings));
|
||||
const importMDict = async (mdict: MDict) => {
|
||||
const mdicts = settings.mdicts.filter(({ hash }) => hash !== mdict.hash);
|
||||
const _settings = { ...settings, mdicts: [mdict, ...mdicts] };
|
||||
|
||||
await updateSettings(_settings);
|
||||
};
|
||||
|
||||
return (
|
||||
<DictProviderContext.Provider
|
||||
value={{
|
||||
settings,
|
||||
dicts,
|
||||
importMDict,
|
||||
remove,
|
||||
removed,
|
||||
lookup,
|
||||
getResource,
|
||||
reload: fetchDicts,
|
||||
dictSelectItems,
|
||||
installedDicts,
|
||||
|
||||
@@ -29,6 +29,8 @@ export class DictDefinitionNormalizer {
|
||||
}
|
||||
|
||||
async createUrl(mime: string, data: string) {
|
||||
if (!data) return "";
|
||||
|
||||
const resp = await fetch(`data:${mime};base64,${data}`);
|
||||
const blob = await resp.blob();
|
||||
const url = URL.createObjectURL(blob);
|
||||
@@ -61,7 +63,13 @@ export class DictDefinitionNormalizer {
|
||||
.toArray()
|
||||
.map(async (link) => {
|
||||
const $link = this.$(link);
|
||||
const data = await this.onReadResource($link.attr("href"));
|
||||
const href = $link.attr("href");
|
||||
|
||||
if (href.startsWith("/assets/styles")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await this.onReadResource(href);
|
||||
const url = await this.createUrl(MIME["css"], data);
|
||||
|
||||
$link.replaceWith(
|
||||
|
||||
6
enjoy/src/types/enjoy-app.d.ts
vendored
6
enjoy/src/types/enjoy-app.d.ts
vendored
@@ -148,6 +148,12 @@ type EnjoyAppType = {
|
||||
camdict: {
|
||||
lookup: (word: string) => Promise<CamdictWordType | null>;
|
||||
};
|
||||
mdict: {
|
||||
remove: (mdict: MDict) => Promise<void>;
|
||||
getResource: (key: string, mdict: MDict) => Promise<string | null>;
|
||||
lookup: (word: string, mdict: MDict) => Promise<string | null>;
|
||||
import: (pathes: string[]) => Promise<MDict>;
|
||||
};
|
||||
dict: {
|
||||
getDicts: () => Promise<Dict[]>;
|
||||
remove: (dict: Dict) => Promise<void>;
|
||||
|
||||
14
enjoy/src/types/index.d.ts
vendored
14
enjoy/src/types/index.d.ts
vendored
@@ -213,16 +213,16 @@ type RecorderConfigType = {
|
||||
sampleSize: number;
|
||||
};
|
||||
|
||||
type DictSettingItem = {
|
||||
name: string;
|
||||
path: string;
|
||||
description: string;
|
||||
isKeyCaseSensitive: string;
|
||||
title: string;
|
||||
resources: string[];
|
||||
type DictType = "dict" | "mdict" | "preset";
|
||||
|
||||
type DictItem = {
|
||||
type: DictType;
|
||||
text: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
type DictSettingType = {
|
||||
default: string;
|
||||
removing: string[];
|
||||
mdicts: MDict[];
|
||||
};
|
||||
|
||||
6
enjoy/src/types/mdict.d.ts
vendored
Normal file
6
enjoy/src/types/mdict.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
type MDict = {
|
||||
title: string;
|
||||
mdx: string;
|
||||
mdds: string[];
|
||||
hash: string;
|
||||
};
|
||||
113
yarn.lock
113
yarn.lock
@@ -1667,6 +1667,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@divisey/js-mdict@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "@divisey/js-mdict@npm:5.0.0"
|
||||
dependencies:
|
||||
"@xmldom/xmldom": "npm:^0.8.6"
|
||||
bl: "npm:^2.2.1"
|
||||
buffer-to-arraybuffer: "npm:0.0.6"
|
||||
lemmatizer: "npm:0.0.1"
|
||||
levenshtein-edit-distance: "npm:^3.0.1"
|
||||
read-chunk: "npm:^3.2.0"
|
||||
checksum: 10c0/7c6ee53bd5b160d918b236cbb411cddb363b68b54496f1f7d24cd29e1cb33c3a9075b90cbfb075563fb14865f973d85e753486ee3f0705f3c1e643a97c2780e4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@docsearch/css@npm:3.6.1, @docsearch/css@npm:^3.6.1":
|
||||
version: 3.6.1
|
||||
resolution: "@docsearch/css@npm:3.6.1"
|
||||
@@ -9175,7 +9189,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@xmldom/xmldom@npm:^0.8.8":
|
||||
"@xmldom/xmldom@npm:^0.8.6, @xmldom/xmldom@npm:^0.8.8":
|
||||
version: 0.8.10
|
||||
resolution: "@xmldom/xmldom@npm:0.8.10"
|
||||
checksum: 10c0/c7647c442502720182b0d65b17d45d2d95317c1c8c497626fe524bda79b4fb768a9aa4fae2da919f308e7abcff7d67c058b102a9d641097e9a57f0b80187851f
|
||||
@@ -9966,6 +9980,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"bl@npm:^2.2.1":
|
||||
version: 2.2.1
|
||||
resolution: "bl@npm:2.2.1"
|
||||
dependencies:
|
||||
readable-stream: "npm:^2.3.5"
|
||||
safe-buffer: "npm:^5.1.1"
|
||||
checksum: 10c0/37481260f1661755253b6205fcdd64b6d852147aaf61628947d2193fcdb78e625ee061dae0094ac16e7c7f10b12c90110fb2b08826815d47a28f9628bebb5a8f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"bl@npm:^4.0.3, bl@npm:^4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "bl@npm:4.1.0"
|
||||
@@ -10120,6 +10144,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"buffer-to-arraybuffer@npm:0.0.6":
|
||||
version: 0.0.6
|
||||
resolution: "buffer-to-arraybuffer@npm:0.0.6"
|
||||
checksum: 10c0/fcfca8c9e1c465c33a22173f1e981aa564a7669c8098153b0c35f80a2b479243751e249468a89c197e2b19198da0c7fd1af27bc03b2af59fea5e9602ea811ccd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"buffer@npm:5.6.0":
|
||||
version: 5.6.0
|
||||
resolution: "buffer@npm:5.6.0"
|
||||
@@ -12751,6 +12782,31 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"en-inflectors@npm:^1.0.11, en-inflectors@npm:^1.0.7":
|
||||
version: 1.0.12
|
||||
resolution: "en-inflectors@npm:1.0.12"
|
||||
dependencies:
|
||||
en-stemmer: "npm:^1.0.2"
|
||||
checksum: 10c0/c399cee8e9d03879730f91e2f20f0c9c4efd01b1bd4f8e713c04862a61b5e383c07c2a7e4e3633bcb3bfe987e0f680fd9c6cb83435082751ac7faa2b31639c97
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"en-lexicon@npm:^1.0.10":
|
||||
version: 1.0.11
|
||||
resolution: "en-lexicon@npm:1.0.11"
|
||||
dependencies:
|
||||
en-inflectors: "npm:^1.0.7"
|
||||
checksum: 10c0/6356feab9a9043e1c94e58988ab047a1afae0d291c5f98fe7ddf119408a9f50ebfa9e7cd790e0e970cff3fc49c0baedd546b0891cee532e9b47916bb3c0c4794
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"en-stemmer@npm:^1.0.2, en-stemmer@npm:^1.0.3":
|
||||
version: 1.0.3
|
||||
resolution: "en-stemmer@npm:1.0.3"
|
||||
checksum: 10c0/f82a4d7ed3bb3e5f28c0b7722092038d068980e001a1ae0d79697180a984b05030d2e4acc2ce89b451183936f3a9bde7ea8b4195d8b31e3ed8643599f6902951
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"encode-utf8@npm:^1.0.3":
|
||||
version: 1.0.3
|
||||
resolution: "encode-utf8@npm:1.0.3"
|
||||
@@ -12808,6 +12864,7 @@ __metadata:
|
||||
resolution: "enjoy@workspace:enjoy"
|
||||
dependencies:
|
||||
"@andrkrn/ffprobe-static": "npm:^5.2.0"
|
||||
"@divisey/js-mdict": "npm:^5.0.0"
|
||||
"@electron-forge/cli": "npm:^7.4.0"
|
||||
"@electron-forge/maker-deb": "npm:^7.4.0"
|
||||
"@electron-forge/maker-dmg": "npm:^7.4.0"
|
||||
@@ -17117,6 +17174,26 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lemmatizer@npm:0.0.1":
|
||||
version: 0.0.1
|
||||
resolution: "lemmatizer@npm:0.0.1"
|
||||
dependencies:
|
||||
en-inflectors: "npm:^1.0.11"
|
||||
en-lexicon: "npm:^1.0.10"
|
||||
en-stemmer: "npm:^1.0.3"
|
||||
checksum: 10c0/7a9741ea1c915878c3c266b099c1b25b27063ba55e9076a6a3e50dd7b9d4f058a0475c1a32dfc3b87541fe4b349e20ea8f7be5f52d8454490230f89447d90ae8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"levenshtein-edit-distance@npm:^3.0.1":
|
||||
version: 3.0.1
|
||||
resolution: "levenshtein-edit-distance@npm:3.0.1"
|
||||
bin:
|
||||
levenshtein-edit-distance: cli.js
|
||||
checksum: 10c0/7d443d4f76bc89d2ccea49ebb6e32d0fa9a003c3511a21d5749d9af17b5bbcf92b9c2f513662535093bae465101bdfb1e7f1f067de4d3f8f9c1fab1b480728ff
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"levn@npm:^0.4.1":
|
||||
version: 0.4.1
|
||||
resolution: "levn@npm:0.4.1"
|
||||
@@ -19844,7 +19921,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"p-try@npm:^2.0.0":
|
||||
"p-try@npm:^2.0.0, p-try@npm:^2.1.0":
|
||||
version: 2.2.0
|
||||
resolution: "p-try@npm:2.2.0"
|
||||
checksum: 10c0/c36c19907734c904b16994e6535b02c36c2224d433e01a2f1ab777237f4d86e6289fd5fd464850491e940379d4606ed850c03e0f9ab600b0ebddb511312e177f
|
||||
@@ -20326,6 +20403,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pify@npm:^4.0.1":
|
||||
version: 4.0.1
|
||||
resolution: "pify@npm:4.0.1"
|
||||
checksum: 10c0/6f9d404b0d47a965437403c9b90eca8bb2536407f03de165940e62e72c8c8b75adda5516c6b9b23675a5877cc0bcac6bdfb0ef0e39414cd2476d5495da40e7cf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pirates@npm:^4.0.1":
|
||||
version: 4.0.6
|
||||
resolution: "pirates@npm:4.0.6"
|
||||
@@ -21580,6 +21664,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"read-chunk@npm:^3.2.0":
|
||||
version: 3.2.0
|
||||
resolution: "read-chunk@npm:3.2.0"
|
||||
dependencies:
|
||||
pify: "npm:^4.0.1"
|
||||
with-open-file: "npm:^0.1.6"
|
||||
checksum: 10c0/cf62522dc85a96d54f1e0886cadcdfdd491bdcd250408695c8d63cea2eae5b1a31da8317c9a58a095ee9b15c67e84e0bed1a917c820bcccf1c74749a9fcad797
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"read-pkg-up@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "read-pkg-up@npm:2.0.0"
|
||||
@@ -21624,7 +21718,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"readable-stream@npm:^2.0.2, readable-stream@npm:^2.0.5, readable-stream@npm:~2.3.6":
|
||||
"readable-stream@npm:^2.0.2, readable-stream@npm:^2.0.5, readable-stream@npm:^2.3.5, readable-stream@npm:~2.3.6":
|
||||
version: 2.3.8
|
||||
resolution: "readable-stream@npm:2.3.8"
|
||||
dependencies:
|
||||
@@ -22181,7 +22275,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:~5.2.0":
|
||||
"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:~5.2.0":
|
||||
version: 5.2.1
|
||||
resolution: "safe-buffer@npm:5.2.1"
|
||||
checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3
|
||||
@@ -25362,6 +25456,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"with-open-file@npm:^0.1.6":
|
||||
version: 0.1.7
|
||||
resolution: "with-open-file@npm:0.1.7"
|
||||
dependencies:
|
||||
p-finally: "npm:^1.0.0"
|
||||
p-try: "npm:^2.1.0"
|
||||
pify: "npm:^4.0.1"
|
||||
checksum: 10c0/28a0c67718f0b9f14669636d5a69fa66bc87a4bdbc5913a245b68f0cbd0c96a3f83559147d647dc30332f3eee76688924654587cc58e4d5b944ef04155f9fe36
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"wkx@npm:^0.5.0":
|
||||
version: 0.5.0
|
||||
resolution: "wkx@npm:0.5.0"
|
||||
|
||||
Reference in New Issue
Block a user