feat: 🎸 update dicts (#1062)

This commit is contained in:
divisey
2024-09-12 10:40:52 +08:00
committed by GitHub
parent fbc1394a70
commit 8148a8679d
16 changed files with 492 additions and 140 deletions

View 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;
}

View File

@@ -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",

View File

@@ -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",

View File

@@ -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
View 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();

View File

@@ -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();

View File

@@ -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),

View File

@@ -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>

View File

@@ -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>

View File

@@ -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({

View File

@@ -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,

View File

@@ -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(

View File

@@ -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>;

View File

@@ -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
View File

@@ -0,0 +1,6 @@
type MDict = {
title: string;
mdx: string;
mdds: string[];
hash: string;
};

113
yarn.lock
View File

@@ -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"