Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
244 lines
7.6 KiB
JavaScript
244 lines
7.6 KiB
JavaScript
(function (global) {
|
|
var definitions = {
|
|
download: {
|
|
endpoint: "/ui/dialog/download",
|
|
elementId: "downloadAppDialog",
|
|
css: "/static/css/downloadAppDialog.css?v=2",
|
|
js: "/static/js/pages/downloadAppDialog.js?v=4",
|
|
open: "openDownloadAppDialog",
|
|
},
|
|
login: {
|
|
endpoint: "/ui/dialog/login",
|
|
elementId: "loginDialog",
|
|
css: "/static/css/loginDialog.css?v=2",
|
|
js: "/static/js/pages/loginDialog.js?v=3",
|
|
open: "openLoginDialog",
|
|
},
|
|
register: {
|
|
endpoint: "/ui/dialog/register",
|
|
elementId: "registerDialog",
|
|
css: "/static/css/registerDialog.css?v=2",
|
|
js: "/static/js/pages/registerDialog.js?v=2",
|
|
open: "openRegisterDialog",
|
|
},
|
|
};
|
|
var pending = Object.create(null);
|
|
var stubOpeners = Object.create(null);
|
|
|
|
function hasRealOpener(name) {
|
|
var definition = definitions[name];
|
|
return (
|
|
!!definition &&
|
|
typeof global[definition.open] === "function" &&
|
|
global[definition.open] !== stubOpeners[name]
|
|
);
|
|
}
|
|
|
|
function loadStylesheet(href) {
|
|
var existing = document.querySelector('link[rel="stylesheet"][href="' + href + '"]');
|
|
if (existing) return Promise.resolve();
|
|
return new Promise(function (resolve, reject) {
|
|
var link = document.createElement("link");
|
|
var settled = false;
|
|
function finish(ok, err) {
|
|
if (settled) return;
|
|
settled = true;
|
|
if (ok) resolve();
|
|
else reject(err || new Error("dialog stylesheet failed"));
|
|
}
|
|
link.rel = "stylesheet";
|
|
link.href = href;
|
|
link.onload = function () {
|
|
finish(true);
|
|
};
|
|
link.onerror = function () {
|
|
finish(false, new Error("dialog stylesheet failed"));
|
|
};
|
|
document.head.appendChild(link);
|
|
// 部分浏览器对缓存 CSS 不触发 onload,避免一直卡住弹窗
|
|
requestAnimationFrame(function () {
|
|
try {
|
|
if (link.sheet) finish(true);
|
|
} catch (e) {}
|
|
});
|
|
setTimeout(function () {
|
|
finish(true);
|
|
}, 1500);
|
|
});
|
|
}
|
|
|
|
function loadScript(src) {
|
|
var existing = document.querySelector('script[src="' + src + '"]');
|
|
if (existing) {
|
|
if (existing.getAttribute("data-loaded") === "1") return Promise.resolve();
|
|
return new Promise(function (resolve, reject) {
|
|
existing.addEventListener("load", resolve, { once: true });
|
|
existing.addEventListener("error", function () {
|
|
reject(new Error("dialog script failed"));
|
|
}, { once: true });
|
|
});
|
|
}
|
|
return new Promise(function (resolve, reject) {
|
|
var script = document.createElement("script");
|
|
script.src = src;
|
|
script.async = true;
|
|
script.onload = function () {
|
|
script.setAttribute("data-loaded", "1");
|
|
resolve();
|
|
};
|
|
script.onerror = function () {
|
|
reject(new Error("dialog script failed"));
|
|
};
|
|
document.head.appendChild(script);
|
|
});
|
|
}
|
|
|
|
function fetchFragment(definition) {
|
|
if (document.getElementById(definition.elementId)) {
|
|
return Promise.resolve();
|
|
}
|
|
return fetch(definition.endpoint, {
|
|
credentials: "same-origin",
|
|
headers: { "X-Requested-With": "XMLHttpRequest" },
|
|
}).then(function (response) {
|
|
if (!response.ok) throw new Error("dialog fragment failed");
|
|
return response.text();
|
|
}).then(function (html) {
|
|
if (document.getElementById(definition.elementId)) return;
|
|
var host = document.createElement("div");
|
|
host.innerHTML = html;
|
|
var dialog = host.querySelector("#" + definition.elementId);
|
|
if (!dialog) throw new Error("dialog fragment is invalid");
|
|
document.body.appendChild(dialog);
|
|
});
|
|
}
|
|
|
|
function ensureDialog(name) {
|
|
var definition = definitions[name];
|
|
if (!definition) return Promise.reject(new Error("unknown dialog"));
|
|
if (document.getElementById(definition.elementId) && hasRealOpener(name)) {
|
|
return Promise.resolve();
|
|
}
|
|
if (pending[name]) return pending[name];
|
|
|
|
pending[name] = loadStylesheet(definition.css)
|
|
.then(function () {
|
|
return fetchFragment(definition);
|
|
})
|
|
.then(function () {
|
|
if (hasRealOpener(name)) return;
|
|
return loadScript(definition.js);
|
|
})
|
|
.then(function () {
|
|
if (!document.getElementById(definition.elementId) || !hasRealOpener(name)) {
|
|
throw new Error("dialog opener is unavailable");
|
|
}
|
|
})
|
|
.catch(function (error) {
|
|
delete pending[name];
|
|
throw error;
|
|
});
|
|
return pending[name];
|
|
}
|
|
|
|
function loginFallbackURL() {
|
|
var redirect = "";
|
|
try {
|
|
redirect = global.location.pathname || "/";
|
|
} catch (e) {
|
|
redirect = "/";
|
|
}
|
|
var url = "/login/";
|
|
if (
|
|
typeof redirect === "string" &&
|
|
redirect.charAt(0) === "/" &&
|
|
redirect.charAt(1) !== "/" &&
|
|
redirect !== "/" &&
|
|
redirect.indexOf("/?auth=") !== 0
|
|
) {
|
|
url = "/login/return" + redirect;
|
|
}
|
|
return url;
|
|
}
|
|
|
|
function openDialog(name, value) {
|
|
return ensureDialog(name)
|
|
.then(function () {
|
|
var opener = global[definitions[name].open];
|
|
if (typeof opener !== "function" || opener === stubOpeners[name]) {
|
|
throw new Error("dialog opener is unavailable");
|
|
}
|
|
return opener(value);
|
|
})
|
|
.catch(function (error) {
|
|
if (name === "login") {
|
|
try {
|
|
var params = new URLSearchParams(global.location.search || "");
|
|
// 已在登录入口仍失败时避免死循环跳转
|
|
if (params.get("auth") !== "login") {
|
|
global.location.href = loginFallbackURL();
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
throw error;
|
|
});
|
|
}
|
|
|
|
Object.keys(definitions).forEach(function (name) {
|
|
var apiName = definitions[name].open;
|
|
var stub = function (value) {
|
|
return openDialog(name, value);
|
|
};
|
|
stubOpeners[name] = stub;
|
|
if (typeof global[apiName] !== "function") global[apiName] = stub;
|
|
});
|
|
|
|
function triggerName(target) {
|
|
if (!target || !target.closest) return "";
|
|
if (target.closest("[data-download-app-open]")) return "download";
|
|
if (target.closest("[data-register-dialog-open], a[href=\"#register\"], a[href=\"/register/\"]")) {
|
|
return "register";
|
|
}
|
|
if (target.closest("[data-login-dialog-open], a[href=\"#login\"], a[href=\"/login/\"]")) {
|
|
return "login";
|
|
}
|
|
return "";
|
|
}
|
|
|
|
function isMobileViewport() {
|
|
return typeof global.matchMedia === "function"
|
|
? global.matchMedia("(max-width: 767px)").matches
|
|
: (global.innerWidth || 0) <= 767;
|
|
}
|
|
|
|
document.addEventListener("click", function (event) {
|
|
var name = triggerName(event.target);
|
|
if (!name) return;
|
|
// 移动端下载入口保留原生链接行为,直接访问 appAddr,不加载下载弹窗。
|
|
if (name === "download" && isMobileViewport()) return;
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
if (event.stopImmediatePropagation) event.stopImmediatePropagation();
|
|
openDialog(name).catch(function () {});
|
|
}, true);
|
|
|
|
function openFromLocation() {
|
|
var params = new URLSearchParams(global.location.search || "");
|
|
var auth = params.get("auth");
|
|
if (auth === "login" || auth === "register") {
|
|
openDialog(auth).catch(function () {});
|
|
return;
|
|
}
|
|
if (global.location.hash === "#login") openDialog("login").catch(function () {});
|
|
if (global.location.hash === "#register") openDialog("register").catch(function () {});
|
|
}
|
|
|
|
if (document.readyState === "loading") {
|
|
document.addEventListener("DOMContentLoaded", openFromLocation, { once: true });
|
|
} else {
|
|
openFromLocation();
|
|
}
|
|
|
|
global.ensureDialog = ensureDialog;
|
|
})(window);
|