mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-22 17:16:19 -07:00
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:
@@ -0,0 +1,65 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// A narrow request/result contract for an embedding host. Browser mode keeps
|
||||
// using the service worker and chrome.tabs.create; overlay mode uses this only
|
||||
// after external-links.js has normalized the URL.
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
const REQUEST_EVENT = "hachidori-open-external";
|
||||
const RESULT_EVENT = "hachidori-open-external-result";
|
||||
const DEFAULT_TIMEOUT_MS = 5000;
|
||||
let nextRequestId = 0;
|
||||
|
||||
function requestId(windowRef) {
|
||||
if (typeof windowRef.crypto?.randomUUID === "function") return windowRef.crypto.randomUUID();
|
||||
nextRequestId += 1;
|
||||
return `hachidori-external-${Date.now().toString(36)}-${nextRequestId.toString(36)}`;
|
||||
}
|
||||
|
||||
function open(windowRef, value, { timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
|
||||
const url = globalThis.HDExternalLinks.normaliseExternalUrl(value?.url);
|
||||
if (!url) return Promise.reject(new TypeError("external link URL is invalid"));
|
||||
const active = value?.active === undefined ? true : value.active;
|
||||
if (typeof active !== "boolean") {
|
||||
return Promise.reject(new TypeError("external link activation is invalid"));
|
||||
}
|
||||
if (!Number.isFinite(timeoutMs) || timeoutMs < 0) {
|
||||
return Promise.reject(new TypeError("external link timeout is invalid"));
|
||||
}
|
||||
|
||||
const id = requestId(windowRef);
|
||||
return new Promise((resolve, reject) => {
|
||||
let timer = null;
|
||||
const finish = (callback, result) => {
|
||||
if (timer !== null) windowRef.clearTimeout(timer);
|
||||
windowRef.removeEventListener(RESULT_EVENT, onResult);
|
||||
callback(result);
|
||||
};
|
||||
const onResult = (event) => {
|
||||
const detail = event?.detail;
|
||||
if (!detail || detail.requestId !== id) return;
|
||||
if (detail.ok === true) {
|
||||
finish(resolve, { opened: true });
|
||||
return;
|
||||
}
|
||||
const message = typeof detail.error === "string" && detail.error
|
||||
? detail.error : "the overlay host could not open the link";
|
||||
finish(reject, new Error(message));
|
||||
};
|
||||
windowRef.addEventListener(RESULT_EVENT, onResult);
|
||||
timer = windowRef.setTimeout(() => {
|
||||
finish(reject, new Error("the overlay host did not answer the external link request"));
|
||||
}, timeoutMs);
|
||||
windowRef.dispatchEvent(new windowRef.CustomEvent(REQUEST_EVENT, {
|
||||
detail: { requestId: id, url, active },
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
globalThis.HDExternalLinkHost = Object.freeze({
|
||||
REQUEST_EVENT,
|
||||
RESULT_EVENT,
|
||||
open,
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user