Files
Letters/js/notice-banner.js
T
2026-05-01 08:25:29 +08:00

176 lines
5.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function () {
var currentScript = document.currentScript;
var source = currentScript && currentScript.getAttribute("data-notice-src");
if (!source && currentScript && currentScript.src) {
source = currentScript.src.replace(/js\/notice-banner\.js(?:\?.*)?$/, "notices.json");
}
if (!source) {
source = "notices.json";
}
var cacheKey = Math.floor(Date.now() / (5 * 60 * 1000));
var joiner = source.indexOf("?") === -1 ? "?" : "&";
fetch(source + joiner + "v=" + cacheKey, { cache: "no-store" })
.then(function (response) {
if (!response.ok) {
throw new Error("Notice source unavailable");
}
return response.json();
})
.then(function (payload) {
var notice = selectNotice(payload);
if (notice) {
renderNotice(notice);
}
})
.catch(function () {
// The page should stay quiet when the optional notice source is missing.
});
function selectNotice(payload) {
var notices = Array.isArray(payload) ? payload : payload && payload.notices;
if (!Array.isArray(notices)) {
return null;
}
var now = Date.now();
var path = normalizePath(window.location.pathname);
return notices.find(function (notice) {
if (!notice || notice.enabled === false) {
return false;
}
if (notice.startsAt && Date.parse(notice.startsAt) > now) {
return false;
}
if (notice.endsAt && Date.parse(notice.endsAt) < now) {
return false;
}
if (notice.pages && notice.pages.length) {
return notice.pages.some(function (page) {
return pathMatches(path, normalizePath(page));
});
}
return true;
});
}
function renderNotice(notice) {
var dismissedId = window.localStorage.getItem("dismissedNoticeId");
var noticeId = String(notice.id || "");
if (notice.dismissible !== false && noticeId && dismissedId === noticeId) {
return;
}
var body = document.body;
var originalPadding = window.getComputedStyle(body).paddingTop || "0px";
body.style.setProperty("--notice-body-pad-top", originalPadding);
var banner = document.createElement("div");
banner.className = "notice-banner";
banner.setAttribute("role", "status");
banner.setAttribute("aria-live", "polite");
banner.dataset.level = notice.level || "info";
var inner = document.createElement("div");
inner.className = "notice-banner__inner";
var badge = document.createElement("span");
badge.className = "notice-banner__badge";
badge.textContent = notice.badge || levelLabel(notice.level);
var content = document.createElement("div");
content.className = "notice-banner__content";
if (notice.title) {
var title = document.createElement("span");
title.className = "notice-banner__title";
title.textContent = notice.title;
content.appendChild(title);
}
var message = document.createElement("span");
message.className = "notice-banner__message";
message.textContent = notice.message || "";
content.appendChild(message);
inner.appendChild(badge);
inner.appendChild(content);
if (notice.url) {
var link = document.createElement("a");
link.className = "notice-banner__link";
link.href = resolveUrl(notice.url, source);
link.textContent = notice.linkText || "查看详情";
inner.appendChild(link);
}
var close = document.createElement("button");
close.className = "notice-banner__close";
close.type = "button";
close.setAttribute("aria-label", "关闭通知");
close.textContent = "×";
close.addEventListener("click", function () {
if (notice.dismissible !== false && noticeId) {
window.localStorage.setItem("dismissedNoticeId", noticeId);
}
banner.remove();
body.classList.remove("has-notice-banner");
body.style.removeProperty("--notice-banner-height");
});
inner.appendChild(close);
banner.appendChild(inner);
body.insertBefore(banner, body.firstChild);
requestAnimationFrame(function () {
banner.classList.add("is-visible");
body.style.setProperty("--notice-banner-height", banner.offsetHeight + "px");
body.classList.add("has-notice-banner");
});
}
function levelLabel(level) {
var labels = {
warning: "提醒",
error: "紧急",
success: "完成"
};
return labels[level] || "通知";
}
function normalizePath(path) {
return String(path || "").replace(/\\/g, "/").replace(/^.*?:\/\/[^/]+/, "");
}
function resolveUrl(url, base) {
try {
return new URL(url, new URL(base, window.location.href)).href;
} catch (error) {
return url;
}
}
function pathMatches(path, pattern) {
if (!pattern || pattern === "*") {
return true;
}
if (pattern.charAt(pattern.length - 1) === "*") {
return path.indexOf(pattern.slice(0, -1)) === 0;
}
return path === pattern || path.endsWith(pattern);
}
})();