Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
569 lines
20 KiB
JavaScript
569 lines
20 KiB
JavaScript
(function () {
|
|
var SEARCH_HISTORY_KEY = "searchHistory";
|
|
var SEARCH_HISTORY_LIMIT = 10;
|
|
|
|
function readSearchHistory() {
|
|
try {
|
|
var stored = window.localStorage.getItem(SEARCH_HISTORY_KEY);
|
|
var parsed = stored ? JSON.parse(stored) : [];
|
|
if (!Array.isArray(parsed)) return [];
|
|
return parsed
|
|
.filter(function (item) {
|
|
return typeof item === "string" && item.trim() !== "";
|
|
})
|
|
.map(function (item) {
|
|
return item.trim();
|
|
})
|
|
.slice(0, SEARCH_HISTORY_LIMIT);
|
|
} catch (_) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function saveSearchHistory(keyword) {
|
|
keyword = String(keyword || "").trim();
|
|
if (!keyword) return readSearchHistory();
|
|
|
|
var compareKeyword = keyword.toLocaleLowerCase();
|
|
var history = readSearchHistory().filter(function (item) {
|
|
return item.toLocaleLowerCase() !== compareKeyword;
|
|
});
|
|
history.unshift(keyword);
|
|
history = history.slice(0, SEARCH_HISTORY_LIMIT);
|
|
|
|
try {
|
|
window.localStorage.setItem(SEARCH_HISTORY_KEY, JSON.stringify(history));
|
|
} catch (_) {
|
|
// 隐私模式或存储空间不可用时不阻断搜索跳转。
|
|
}
|
|
return history;
|
|
}
|
|
|
|
window.SearchHistory = {
|
|
key: SEARCH_HISTORY_KEY,
|
|
limit: SEARCH_HISTORY_LIMIT,
|
|
get: readSearchHistory,
|
|
add: saveSearchHistory,
|
|
clear: function () {
|
|
try {
|
|
window.localStorage.removeItem(SEARCH_HISTORY_KEY);
|
|
} catch (_) {
|
|
// localStorage 不可用时保持静默。
|
|
}
|
|
},
|
|
};
|
|
|
|
if (!document.querySelector('meta[name="viewport"]')) {
|
|
var viewport = document.createElement("meta");
|
|
viewport.name = "viewport";
|
|
viewport.content = "width=device-width, initial-scale=1.0";
|
|
document.head.appendChild(viewport);
|
|
}
|
|
|
|
var mobileMenu = document.getElementById("homeMobileMenu");
|
|
var mobileMenuButton = document.querySelector(".header-menu-button");
|
|
var mobileMenuCloseButtons = mobileMenu
|
|
? mobileMenu.querySelectorAll("[data-home-menu-close]")
|
|
: [];
|
|
|
|
function setMobileMenu(open) {
|
|
if (!mobileMenu || !mobileMenuButton) return;
|
|
mobileMenu.hidden = !open;
|
|
mobileMenuButton.setAttribute("aria-expanded", open ? "true" : "false");
|
|
document.body.classList.toggle("home-mobile-menu-open", open);
|
|
if (open) {
|
|
var closeButton = mobileMenu.querySelector(".home-mobile-menu-close");
|
|
if (closeButton) closeButton.focus({ preventScroll: true });
|
|
} else {
|
|
mobileMenuButton.focus({ preventScroll: true });
|
|
}
|
|
}
|
|
|
|
if (mobileMenu && mobileMenuButton) {
|
|
mobileMenuButton.addEventListener("click", function () {
|
|
setMobileMenu(true);
|
|
});
|
|
Array.prototype.forEach.call(mobileMenuCloseButtons, function (button) {
|
|
button.addEventListener("click", function () {
|
|
setMobileMenu(false);
|
|
});
|
|
});
|
|
document.addEventListener("keydown", function (event) {
|
|
if (event.key === "Escape" && !mobileMenu.hidden) setMobileMenu(false);
|
|
});
|
|
}
|
|
|
|
var DEFAULT_AVATAR = "/static/images/avatar.png";
|
|
var PLACEHOLDER = "/static/images/placeholder.png?v=2";
|
|
|
|
function usableAvatar(url) {
|
|
var u = String(url || "").trim();
|
|
if (!u) return "";
|
|
if (/^https?:\/\//i.test(u) || u.indexOf("/static/") === 0) return u;
|
|
return "";
|
|
}
|
|
|
|
function setAvatarImg(img, url) {
|
|
if (!img) return;
|
|
var src = usableAvatar(url);
|
|
img.removeAttribute("data-decrypted");
|
|
if (src && typeof shouldDecryptImage === "function" && shouldDecryptImage(src)) {
|
|
img.classList.add("lazy");
|
|
img.src = PLACEHOLDER;
|
|
img.setAttribute("data-src", src);
|
|
} else {
|
|
img.classList.remove("lazy");
|
|
img.removeAttribute("data-src");
|
|
img.src = src || DEFAULT_AVATAR;
|
|
}
|
|
}
|
|
|
|
function refreshDecrypt(root) {
|
|
if (typeof window.initLazyImages === "function") {
|
|
window.initLazyImages(root || document);
|
|
}
|
|
}
|
|
|
|
/** 用 cookie 登录态同步 header-account */
|
|
function parseExpireTime(value) {
|
|
if (value == null || value === "") return NaN;
|
|
if (typeof value === "number") {
|
|
return value < 1e12 ? value * 1000 : value;
|
|
}
|
|
var text = String(value).trim();
|
|
if (!text) return NaN;
|
|
if (/^\d+$/.test(text)) {
|
|
var num = Number(text);
|
|
return num < 1e12 ? num * 1000 : num;
|
|
}
|
|
return Date.parse(text);
|
|
}
|
|
|
|
// vipType≠0 且 currentVipExpire 未过期(与 91ly 一致)
|
|
function isVipActive(user) {
|
|
if (!user) return false;
|
|
var type = Number(user.vipType);
|
|
if (!type || type === 0) return false;
|
|
var expireMs = parseExpireTime(user.currentVipExpire);
|
|
if (!isFinite(expireMs) || expireMs <= 0) return false;
|
|
return expireMs > Date.now();
|
|
}
|
|
|
|
function syncHeaderVip(session) {
|
|
var active = isVipActive(session);
|
|
document.querySelectorAll("[data-header-vip], [data-header-vip-sep]").forEach(function (el) {
|
|
el.hidden = !active;
|
|
});
|
|
}
|
|
|
|
function syncHeaderAccount() {
|
|
var account = document.querySelector("[data-header-account]");
|
|
if (!account || !window.CookieUtil) return;
|
|
|
|
// 有 token 即展示个人信息(含游客)
|
|
var loggedIn = CookieUtil.isLoggedIn();
|
|
var session = loggedIn ? CookieUtil.getAuthSession() : null;
|
|
var nameEl = account.querySelector("[data-header-username]");
|
|
|
|
if (loggedIn && session) {
|
|
var displayName =
|
|
session.nickName || session.accountNo || session.email || "用户";
|
|
var avatar = usableAvatar(session.avatar);
|
|
|
|
account.href = "/user-center/";
|
|
account.removeAttribute("data-login-dialog-open");
|
|
|
|
var guest = account.querySelector("[data-header-guest-avatar]");
|
|
if (guest) guest.remove();
|
|
|
|
var photo = account.querySelector("[data-header-avatar]");
|
|
if (!photo) {
|
|
photo = document.createElement("img");
|
|
photo.className = "account-avatar-photo";
|
|
photo.setAttribute("data-header-avatar", "");
|
|
photo.alt = displayName + "的头像";
|
|
account.insertBefore(photo, nameEl || account.firstChild);
|
|
}
|
|
setAvatarImg(photo, avatar);
|
|
|
|
if (nameEl) {
|
|
nameEl.textContent = displayName;
|
|
nameEl.classList.add("account-label--logged");
|
|
}
|
|
|
|
refreshDecrypt(account.parentElement || document);
|
|
syncHeaderVip(session);
|
|
return;
|
|
}
|
|
|
|
account.href = "#login";
|
|
account.setAttribute("data-login-dialog-open", "");
|
|
|
|
var loggedPhoto = account.querySelector("[data-header-avatar]");
|
|
if (loggedPhoto) loggedPhoto.remove();
|
|
if (!account.querySelector("[data-header-guest-avatar]")) {
|
|
var wrap = document.createElement("div");
|
|
wrap.className = "account-avatar";
|
|
wrap.setAttribute("data-header-guest-avatar", "");
|
|
wrap.innerHTML =
|
|
'<img class="account-avatar-ring" src="' +
|
|
DEFAULT_AVATAR +
|
|
'" alt="未登录用户头像" />' +
|
|
'<div class="account-avatar-body"></div>' +
|
|
'<div class="account-avatar-head"></div>';
|
|
account.insertBefore(wrap, nameEl || account.firstChild);
|
|
}
|
|
if (nameEl) {
|
|
nameEl.textContent = "未登录";
|
|
nameEl.classList.remove("account-label--logged");
|
|
}
|
|
|
|
syncHeaderVip(null);
|
|
}
|
|
|
|
syncHeaderAccount();
|
|
window.syncHeaderAccount = syncHeaderAccount;
|
|
document.addEventListener("auth:login", function () {
|
|
syncHeaderAccount();
|
|
});
|
|
document.addEventListener("auth:logout", function () {
|
|
syncHeaderAccount();
|
|
});
|
|
|
|
document.addEventListener(
|
|
"click",
|
|
function (event) {
|
|
var el = event.target;
|
|
if (el && el.nodeType === 3) {
|
|
el = el.parentElement;
|
|
}
|
|
if (!el || !el.closest) return;
|
|
var tag = el.closest(".poster-card-tag[data-tag-href], .ranking-card-tag[data-tag-href], .detail-tag[data-tag-href]");
|
|
if (!tag) return;
|
|
var href = tag.getAttribute("data-tag-href");
|
|
if (!href) return;
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
if (event.stopImmediatePropagation) event.stopImmediatePropagation();
|
|
window.location.href = href;
|
|
},
|
|
true
|
|
);
|
|
|
|
function initDesktopSearchPopover() {
|
|
var form = document.querySelector('form.header-search[action="/search/"]');
|
|
var input = form && form.querySelector('input[name="q"]');
|
|
var popover = document.querySelector("[data-header-search-popover]");
|
|
if (!form || !input || !popover) return;
|
|
|
|
var desktopMedia = window.matchMedia("(min-width: 768px)");
|
|
var historyList = popover.querySelector("[data-search-history-list]");
|
|
var hotList = popover.querySelector("[data-search-hot-list]");
|
|
var guessList = popover.querySelector("[data-search-guess-list]");
|
|
var historySection = popover.querySelector(".header-search-history");
|
|
var hotSection = popover.querySelector(".header-search-hot");
|
|
var guessSection = popover.querySelector(".header-search-guess");
|
|
var dividers = popover.querySelectorAll(".header-search-popover-divider");
|
|
var clearButton = popover.querySelector("[data-search-history-clear]");
|
|
var closeButton = popover.querySelector("[data-header-search-close]");
|
|
var changeButton = popover.querySelector("[data-search-guess-change]");
|
|
var hotLikeData = null;
|
|
var hotLikeRequest = null;
|
|
var guessOffset = 0;
|
|
var searchActive = false;
|
|
|
|
function syncPopoverVisibility() {
|
|
var hasHistory = historySection && !historySection.hidden;
|
|
var hasHot = hotSection && !hotSection.hidden;
|
|
var hasGuess = guessSection && !guessSection.hidden;
|
|
var hasContent = hasHistory || hasHot || hasGuess;
|
|
|
|
if (dividers[0]) dividers[0].hidden = !(hasHistory && (hasHot || hasGuess));
|
|
if (dividers[1]) dividers[1].hidden = !(hasHot && hasGuess);
|
|
|
|
popover.hidden = !(searchActive && hasContent);
|
|
form.classList.toggle("is-search-open", searchActive && hasContent);
|
|
input.setAttribute("aria-expanded", searchActive && hasContent ? "true" : "false");
|
|
if (!popover.hidden) positionPopover();
|
|
}
|
|
|
|
function searchKeyword(keyword) {
|
|
keyword = String(keyword || "").trim();
|
|
if (!keyword) return;
|
|
saveSearchHistory(keyword);
|
|
window.location.href = "/search/" + encodeURIComponent(keyword) + "/";
|
|
}
|
|
|
|
function renderHistory() {
|
|
if (!historyList) return;
|
|
historyList.textContent = "";
|
|
var history = readSearchHistory();
|
|
if (historySection) historySection.hidden = history.length === 0;
|
|
history.forEach(function (keyword) {
|
|
var button = document.createElement("button");
|
|
button.type = "button";
|
|
button.className = "header-search-history-tag";
|
|
button.textContent = keyword;
|
|
button.addEventListener("click", function () {
|
|
searchKeyword(keyword);
|
|
});
|
|
historyList.appendChild(button);
|
|
});
|
|
syncPopoverVisibility();
|
|
}
|
|
|
|
function normalizeHotItems(data) {
|
|
var hot = data && Array.isArray(data.hotSearch) ? data.hotSearch : [];
|
|
return hot
|
|
.map(function (item) {
|
|
return { keyword: String((item && item.keyword) || "").trim() };
|
|
})
|
|
.filter(function (item) {
|
|
return item.keyword !== "";
|
|
})
|
|
.slice(0, 6);
|
|
}
|
|
|
|
function renderHotSearch() {
|
|
if (!hotList) return;
|
|
hotList.textContent = "";
|
|
var items = normalizeHotItems(hotLikeData);
|
|
if (hotSection) hotSection.hidden = items.length === 0;
|
|
items.forEach(function (item, index) {
|
|
var button = document.createElement("button");
|
|
var rank = document.createElement("span");
|
|
var keyword = document.createElement("span");
|
|
button.type = "button";
|
|
button.className = "header-search-hot-item";
|
|
rank.className = "header-search-hot-rank";
|
|
keyword.className = "header-search-hot-keyword";
|
|
rank.textContent = String(index + 1);
|
|
keyword.textContent = item.keyword;
|
|
button.appendChild(rank);
|
|
button.appendChild(keyword);
|
|
button.addEventListener("click", function () {
|
|
searchKeyword(item.keyword);
|
|
});
|
|
hotList.appendChild(button);
|
|
});
|
|
syncPopoverVisibility();
|
|
}
|
|
|
|
function detailHref(item) {
|
|
var slug = String((item && item.slug) || "").trim();
|
|
var id = Number(item && item.id);
|
|
var key = slug || (id > 0 ? String(id) : "");
|
|
return key ? "/detail/" + encodeURIComponent(key) + "/" : "#";
|
|
}
|
|
|
|
function firstCardTag(item) {
|
|
var tags = item && Array.isArray(item.tagNames) ? item.tagNames : [];
|
|
var themes = item && Array.isArray(item.themeNames) ? item.themeNames : [];
|
|
return String(tags[0] || themes[0] || "热门").trim();
|
|
}
|
|
|
|
function setCardCover(img, rawURL, title) {
|
|
var cover = String(rawURL || "").trim();
|
|
var imageTitle = String(title || "精彩短剧").trim();
|
|
img.alt = imageTitle;
|
|
img.title = imageTitle;
|
|
img.addEventListener("error", function () {
|
|
img.classList.remove("lazy");
|
|
img.removeAttribute("data-src");
|
|
img.src = PLACEHOLDER;
|
|
}, { once: true });
|
|
if (cover && typeof window.shouldDecryptImage === "function" && window.shouldDecryptImage(cover)) {
|
|
img.className = "lazy";
|
|
img.src = PLACEHOLDER;
|
|
img.setAttribute("data-src", cover);
|
|
return;
|
|
}
|
|
img.src = cover || PLACEHOLDER;
|
|
}
|
|
|
|
function createGuessCard(item) {
|
|
var card = document.createElement("a");
|
|
var cover = document.createElement("div");
|
|
var image = document.createElement("img");
|
|
var episode = document.createElement("span");
|
|
var copy = document.createElement("div");
|
|
var title = document.createElement("div");
|
|
var desc = document.createElement("div");
|
|
var tagRow = document.createElement("div");
|
|
var tag = document.createElement("span");
|
|
var totalEpisode = Math.max(0, Number(item && item.totalEpisode) || 0);
|
|
|
|
card.className = "header-search-guess-card";
|
|
card.href = detailHref(item);
|
|
card.setAttribute("aria-label", String((item && item.title) || "查看短剧"));
|
|
cover.className = "header-search-guess-cover";
|
|
episode.className = "header-search-guess-episode";
|
|
copy.className = "header-search-guess-copy";
|
|
title.className = "header-search-guess-title";
|
|
desc.className = "header-search-guess-desc";
|
|
tagRow.className = "header-search-guess-tag-row";
|
|
tag.className = "header-search-guess-tag";
|
|
|
|
setCardCover(image, item && item.cover, item && item.title);
|
|
episode.textContent = totalEpisode ? totalEpisode + "集" : "更新中";
|
|
title.textContent = String((item && item.title) || "精彩短剧");
|
|
desc.textContent = String((item && item.desc) || "精彩内容等你来看");
|
|
tag.textContent = firstCardTag(item);
|
|
|
|
cover.appendChild(image);
|
|
cover.appendChild(episode);
|
|
tagRow.appendChild(tag);
|
|
copy.appendChild(title);
|
|
copy.appendChild(desc);
|
|
copy.appendChild(tagRow);
|
|
card.appendChild(cover);
|
|
card.appendChild(copy);
|
|
return card;
|
|
}
|
|
|
|
function currentGuessBatch(items) {
|
|
if (items.length <= 4) return items.slice(0, 4);
|
|
var batch = [];
|
|
for (var index = 0; index < 4; index += 1) {
|
|
batch.push(items[(guessOffset + index) % items.length]);
|
|
}
|
|
return batch;
|
|
}
|
|
|
|
function renderGuessLike() {
|
|
if (!guessList) return;
|
|
guessList.textContent = "";
|
|
var items = hotLikeData && Array.isArray(hotLikeData.guessLike)
|
|
? hotLikeData.guessLike.filter(function (item) { return item && (item.id || item.slug); })
|
|
: [];
|
|
if (guessSection) guessSection.hidden = items.length === 0;
|
|
currentGuessBatch(items).forEach(function (item) {
|
|
guessList.appendChild(createGuessCard(item));
|
|
});
|
|
if (items.length) refreshDecrypt(guessList);
|
|
syncPopoverVisibility();
|
|
}
|
|
|
|
function renderHotLike() {
|
|
renderHotSearch();
|
|
renderGuessLike();
|
|
}
|
|
|
|
function loadHotLike() {
|
|
if (hotLikeData) {
|
|
renderHotLike();
|
|
return Promise.resolve(hotLikeData);
|
|
}
|
|
if (hotLikeRequest) return hotLikeRequest;
|
|
if (hotSection) hotSection.hidden = true;
|
|
if (guessSection) guessSection.hidden = true;
|
|
syncPopoverVisibility();
|
|
if (!window.HttpRequest || typeof window.HttpRequest.get !== "function") {
|
|
hotLikeData = {};
|
|
renderHotLike();
|
|
return Promise.resolve(hotLikeData);
|
|
}
|
|
hotLikeRequest = window.HttpRequest.get("/api/playlet/hot-like")
|
|
.then(function (response) {
|
|
hotLikeData = response && response.data ? response.data : {};
|
|
renderHotLike();
|
|
return hotLikeData;
|
|
})
|
|
.catch(function () {
|
|
hotLikeData = {};
|
|
renderHotLike();
|
|
return hotLikeData;
|
|
});
|
|
return hotLikeRequest;
|
|
}
|
|
|
|
function positionPopover() {
|
|
if (popover.hidden || !desktopMedia.matches) return;
|
|
var rect = form.getBoundingClientRect();
|
|
var header = form.closest(".site-header");
|
|
var headerRect = header ? header.getBoundingClientRect() : rect;
|
|
var viewportPadding = 24;
|
|
var width = popover.offsetWidth;
|
|
var left = rect.right - width;
|
|
var top = Math.max(rect.bottom + 12, headerRect.bottom + 12);
|
|
left = Math.max(viewportPadding, Math.min(left, window.innerWidth - width - viewportPadding));
|
|
popover.style.left = Math.round(left) + "px";
|
|
popover.style.top = Math.round(top) + "px";
|
|
popover.style.maxHeight = Math.max(240, window.innerHeight - top - 16) + "px";
|
|
}
|
|
|
|
function setPopover(open, options) {
|
|
options = options || {};
|
|
searchActive = Boolean(open && desktopMedia.matches);
|
|
if (searchActive) {
|
|
renderHistory();
|
|
if (hotLikeData) renderHotLike();
|
|
else loadHotLike();
|
|
} else if (options.blur) {
|
|
input.blur();
|
|
}
|
|
syncPopoverVisibility();
|
|
}
|
|
|
|
input.addEventListener("focus", function () {
|
|
setPopover(true);
|
|
});
|
|
input.addEventListener("click", function () {
|
|
setPopover(true);
|
|
});
|
|
clearButton.addEventListener("click", function () {
|
|
window.SearchHistory.clear();
|
|
renderHistory();
|
|
input.focus({ preventScroll: true });
|
|
});
|
|
closeButton.addEventListener("click", function () {
|
|
setPopover(false, { blur: true });
|
|
});
|
|
changeButton.addEventListener("click", function () {
|
|
var items = hotLikeData && Array.isArray(hotLikeData.guessLike) ? hotLikeData.guessLike : [];
|
|
if (items.length > 4) guessOffset = (guessOffset + 4) % items.length;
|
|
renderGuessLike();
|
|
});
|
|
document.addEventListener("pointerdown", function (event) {
|
|
if (!searchActive || form.contains(event.target) || popover.contains(event.target)) return;
|
|
setPopover(false);
|
|
});
|
|
document.addEventListener("keydown", function (event) {
|
|
if (event.key === "Escape" && searchActive) {
|
|
setPopover(false, { blur: true });
|
|
}
|
|
});
|
|
window.addEventListener("resize", function () {
|
|
if (!desktopMedia.matches) {
|
|
setPopover(false);
|
|
return;
|
|
}
|
|
positionPopover();
|
|
});
|
|
window.addEventListener("scroll", positionPopover, { passive: true });
|
|
if (typeof desktopMedia.addEventListener === "function") {
|
|
desktopMedia.addEventListener("change", function (event) {
|
|
if (!event.matches) setPopover(false);
|
|
});
|
|
}
|
|
}
|
|
|
|
initDesktopSearchPopover();
|
|
|
|
document.addEventListener("submit", function (event) {
|
|
var form = event.target;
|
|
if (!form || !form.matches || !form.matches('form[action="/search/"]')) return;
|
|
var input = form.querySelector('input[name="q"]');
|
|
var keyword = String((input && input.value) || "").trim();
|
|
if (!keyword) return;
|
|
saveSearchHistory(keyword);
|
|
event.preventDefault();
|
|
window.location.href = "/search/" + encodeURIComponent(keyword) + "/";
|
|
});
|
|
|
|
if (document.body && document.body.classList.contains("search-layout")) {
|
|
var currentSearchInput = document.querySelector('.header-search input[name="q"]');
|
|
saveSearchHistory(currentSearchInput && currentSearchInput.value);
|
|
}
|
|
})();
|