feat(dictionary): add Hachidori backend support

- Add backend selection, setup gating, Anki integration, and external host support
- Add launcher flags, documentation, packaging, and focused tests
- Open on-demand overlay modals on the first attempt
This commit is contained in:
2026-09-22 00:21:19 -07:00
parent 1508863dbb
commit d9fdc7ef6d
446 changed files with 109060 additions and 244 deletions
+55
View File
@@ -0,0 +1,55 @@
/*
* Shared dictionary-group state rules for extension consumers.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
(function () {
"use strict";
function normaliseGroupName(value) {
return (typeof value === "string" ? value : "")
.normalize("NFKC")
.trim()
.replace(/\s+/gu, " ");
}
function groupNameKey(value) {
return normaliseGroupName(value).toLowerCase();
}
function retainedMemberIds(value, installedIds) {
const seen = new Set();
return Array.isArray(value)
? value.filter((id) => {
if (!installedIds.has(id) || seen.has(id)) return false;
seen.add(id);
return true;
})
: [];
}
function normaliseDictionaryGroups(value, installedDictionaries) {
if (!Array.isArray(value)) return [];
const installedIds = new Set(installedDictionaries.map((dictionary) => dictionary.id));
return value.map((group) => {
const id = typeof group?.id === "string" ? group.id : "";
const name = normaliseGroupName(group?.name);
if (id === "" || name === "") return null;
return { id, name, dictionaryIds: retainedMemberIds(group.dictionaryIds, installedIds) };
}).filter((group) => group !== null);
}
function pruneGroupMemberships(value, dictionaries) {
if (!Array.isArray(value)) return [];
const installedIds = new Set(dictionaries.map((dictionary) => dictionary.id));
return value.map((group) => ({
...group,
dictionaryIds: retainedMemberIds(group.dictionaryIds, installedIds),
}));
}
globalThis.HDDictionaryGroups = {
normaliseGroupName, groupNameKey, normaliseDictionaryGroups, pruneGroupMemberships,
};
}());