176 lines
5.6 KiB
JavaScript
176 lines
5.6 KiB
JavaScript
(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);
|
||
}
|
||
})();
|