321 lines
9.0 KiB
JavaScript
321 lines
9.0 KiB
JavaScript
(function () {
|
|
var HISTORY_KEY = "hgdj_search_history";
|
|
var HISTORY_MAX = 10;
|
|
var PLACEHOLDER_IMG = "/static/images/placeholder.png?v=2";
|
|
|
|
var input = document.querySelector("[data-mobile-search-input]");
|
|
var form = input && input.closest("form");
|
|
var clear = document.querySelector("[data-mobile-search-clear]");
|
|
var defaultView = document.querySelector("[data-search-default]");
|
|
var suggestions = document.querySelector("[data-search-suggestions]");
|
|
var suggestList = document.querySelector("[data-search-suggest-list]");
|
|
var history = document.querySelector("[data-search-history]");
|
|
var historyChips = document.querySelector("[data-history-chips]");
|
|
var historyClear = document.querySelector("[data-history-clear]");
|
|
if (!input || !form || !clear || !defaultView || !suggestions || !suggestList) return;
|
|
|
|
var suggestTimer = null;
|
|
var suggestSeq = 0;
|
|
|
|
function searchResultsPath(q) {
|
|
q = String(q || "").trim();
|
|
if (!q) return "/search";
|
|
return "/search/" + encodeURIComponent(q);
|
|
}
|
|
|
|
function escapeHtml(text) {
|
|
return String(text || "")
|
|
.replace(/&/g, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, """);
|
|
}
|
|
|
|
function highlightHTML(text, keyword) {
|
|
var raw = String(text || "");
|
|
var key = String(keyword || "").trim();
|
|
if (!raw) return "";
|
|
if (!key) return escapeHtml(raw);
|
|
var lowerRaw = raw.toLowerCase();
|
|
var lowerKey = key.toLowerCase();
|
|
var idx = lowerRaw.indexOf(lowerKey);
|
|
if (idx < 0) return escapeHtml(raw);
|
|
var before = raw.slice(0, idx);
|
|
var hit = raw.slice(idx, idx + key.length);
|
|
var after = raw.slice(idx + key.length);
|
|
return (
|
|
escapeHtml(before) +
|
|
'<span class="mobile-search__highlight">' +
|
|
escapeHtml(hit) +
|
|
"</span>" +
|
|
escapeHtml(after)
|
|
);
|
|
}
|
|
|
|
function readHistory() {
|
|
try {
|
|
var raw = window.localStorage.getItem(HISTORY_KEY);
|
|
var list = raw ? JSON.parse(raw) : [];
|
|
if (!Array.isArray(list)) return [];
|
|
return list
|
|
.map(function (item) {
|
|
return String(item || "").trim();
|
|
})
|
|
.filter(Boolean)
|
|
.slice(0, HISTORY_MAX);
|
|
} catch (_) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function writeHistory(list) {
|
|
try {
|
|
window.localStorage.setItem(HISTORY_KEY, JSON.stringify(list.slice(0, HISTORY_MAX)));
|
|
} catch (_) {
|
|
/* ignore quota / private mode */
|
|
}
|
|
}
|
|
|
|
function pushHistory(q) {
|
|
q = String(q || "").trim();
|
|
if (!q) return;
|
|
var list = readHistory().filter(function (item) {
|
|
return item !== q;
|
|
});
|
|
list.unshift(q);
|
|
writeHistory(list);
|
|
}
|
|
|
|
function clearHistory() {
|
|
try {
|
|
window.localStorage.removeItem(HISTORY_KEY);
|
|
} catch (_) {
|
|
/* ignore */
|
|
}
|
|
}
|
|
|
|
function renderHistory() {
|
|
if (!history || !historyChips) return;
|
|
var list = readHistory();
|
|
historyChips.innerHTML = "";
|
|
if (!list.length) {
|
|
history.hidden = true;
|
|
return;
|
|
}
|
|
history.hidden = false;
|
|
list.forEach(function (term) {
|
|
var link = document.createElement("a");
|
|
link.className = "mobile-search__chip";
|
|
link.href = searchResultsPath(term);
|
|
link.textContent = term;
|
|
historyChips.appendChild(link);
|
|
});
|
|
}
|
|
|
|
function renderSuggest(data, keyword) {
|
|
var html = "";
|
|
var feature = data && data.feature;
|
|
var items = (data && data.items) || [];
|
|
|
|
if (feature && feature.url) {
|
|
html +=
|
|
'<article class="mobile-search__suggestion-feature">' +
|
|
'<a class="mobile-search__feature-cover" href="' +
|
|
escapeHtml(feature.url) +
|
|
'" aria-label="' +
|
|
escapeHtml(feature.title || "") +
|
|
'">' +
|
|
'<img decoding="async" loading="lazy" src="' +
|
|
PLACEHOLDER_IMG +
|
|
'" data-src="' +
|
|
escapeHtml(feature.cover || "") +
|
|
'" alt="' +
|
|
escapeHtml(feature.title || "") +
|
|
'">' +
|
|
"</a>" +
|
|
'<div class="mobile-search__feature-copy">' +
|
|
'<h2 class="mobile-search__feature-title">' +
|
|
highlightHTML(feature.title, keyword) +
|
|
"</h2>" +
|
|
(feature.meta
|
|
? '<p class="mobile-search__feature-meta">' + escapeHtml(feature.meta) + "</p>"
|
|
: "") +
|
|
"</div>" +
|
|
'<a class="mobile-search__feature-play" href="' +
|
|
escapeHtml(feature.url) +
|
|
'">' +
|
|
'<img decoding="async" loading="lazy" src="' +
|
|
PLACEHOLDER_IMG +
|
|
'" data-src="/static/images/play_hollow.svg" alt="播放">播放</a>' +
|
|
"</article>";
|
|
}
|
|
|
|
items.forEach(function (item) {
|
|
if (!item || !item.url) return;
|
|
html +=
|
|
'<a class="mobile-search__suggestion" href="' +
|
|
escapeHtml(item.url) +
|
|
'" data-suggest-query="' +
|
|
escapeHtml(item.query || item.title || keyword) +
|
|
'">' +
|
|
'<img decoding="async" loading="lazy" class="mobile-search__suggestion-icon" src="' +
|
|
PLACEHOLDER_IMG +
|
|
'" data-src="/static/images/search.svg" alt="搜索">' +
|
|
'<span class="mobile-search__suggestion-text">' +
|
|
highlightHTML(item.title || item.query || "", keyword) +
|
|
"</span>" +
|
|
'<span class="mobile-search__suggestion-type">' +
|
|
escapeHtml(item.kind || "") +
|
|
"</span>" +
|
|
"</a>";
|
|
});
|
|
|
|
if (!html) {
|
|
html =
|
|
'<p class="mobile-search__suggest-empty">暂无相关结果,可直接搜索“' +
|
|
escapeHtml(keyword) +
|
|
'”</p>';
|
|
}
|
|
|
|
suggestList.innerHTML = html;
|
|
if (typeof window.initLazyImages === "function") {
|
|
window.initLazyImages(suggestList, { eager: true });
|
|
}
|
|
}
|
|
|
|
function fetchSuggest(keyword) {
|
|
var q = String(keyword || "").trim();
|
|
if (!q) {
|
|
suggestList.innerHTML = "";
|
|
return;
|
|
}
|
|
var seq = ++suggestSeq;
|
|
suggestList.innerHTML =
|
|
'<p class="mobile-search__suggest-empty">搜索中…</p>';
|
|
|
|
var req;
|
|
if (window.HttpRequest && typeof window.HttpRequest.get === "function") {
|
|
req = window.HttpRequest.get("/api/web/search/suggest", {
|
|
params: { keyword: q },
|
|
});
|
|
} else {
|
|
req = fetch(
|
|
"/api/web/search/suggest?keyword=" + encodeURIComponent(q),
|
|
{ credentials: "include", headers: { Accept: "application/json" } }
|
|
).then(function (res) {
|
|
return res.json();
|
|
});
|
|
}
|
|
|
|
Promise.resolve(req)
|
|
.then(function (data) {
|
|
if (seq !== suggestSeq) return;
|
|
renderSuggest(data || {}, q);
|
|
})
|
|
.catch(function () {
|
|
if (seq !== suggestSeq) return;
|
|
renderSuggest(
|
|
{
|
|
feature: null,
|
|
items: [
|
|
{
|
|
title: q,
|
|
kind: "搜索",
|
|
query: q,
|
|
url: searchResultsPath(q),
|
|
},
|
|
],
|
|
},
|
|
q
|
|
);
|
|
});
|
|
}
|
|
|
|
function syncPanels() {
|
|
var query = input.value.trim();
|
|
var hasQuery = query.length > 0;
|
|
defaultView.hidden = hasQuery;
|
|
suggestions.hidden = !hasQuery;
|
|
clear.hidden = !hasQuery;
|
|
if (!hasQuery) {
|
|
suggestSeq += 1;
|
|
suggestList.innerHTML = "";
|
|
}
|
|
}
|
|
|
|
function scheduleSuggest() {
|
|
syncPanels();
|
|
var q = input.value.trim();
|
|
if (suggestTimer) clearTimeout(suggestTimer);
|
|
if (!q) return;
|
|
suggestTimer = setTimeout(function () {
|
|
suggestTimer = null;
|
|
fetchSuggest(q);
|
|
}, 220);
|
|
}
|
|
|
|
input.addEventListener("input", scheduleSuggest);
|
|
|
|
clear.addEventListener("click", function () {
|
|
input.value = "";
|
|
input.dispatchEvent(new Event("input"));
|
|
input.focus();
|
|
});
|
|
|
|
form.addEventListener("submit", function (event) {
|
|
event.preventDefault();
|
|
var q = input.value.trim();
|
|
if (!q) {
|
|
input.focus();
|
|
return;
|
|
}
|
|
pushHistory(q);
|
|
window.location.href = searchResultsPath(q);
|
|
});
|
|
|
|
if (historyClear && history) {
|
|
historyClear.addEventListener("click", function () {
|
|
clearHistory();
|
|
renderHistory();
|
|
});
|
|
}
|
|
|
|
// 从联想 / 热搜跳转搜索结果时也写入本地历史
|
|
document.addEventListener("click", function (event) {
|
|
var link = event.target.closest("a[data-suggest-query], a[href^='/search/']");
|
|
if (!link || link.hasAttribute("data-search-close")) return;
|
|
var q = String(link.getAttribute("data-suggest-query") || "").trim();
|
|
if (!q) {
|
|
var href = link.getAttribute("href") || "";
|
|
var match = href.match(/^\/search\/([^/?#]+)/);
|
|
if (!match) return;
|
|
try {
|
|
q = decodeURIComponent(match[1]);
|
|
} catch (_) {
|
|
q = match[1];
|
|
}
|
|
}
|
|
if (q) pushHistory(q);
|
|
});
|
|
|
|
document.querySelectorAll("[data-search-close]").forEach(function (link) {
|
|
link.addEventListener("click", function (event) {
|
|
if (!document.referrer) return;
|
|
try {
|
|
if (new URL(document.referrer).origin === window.location.origin) {
|
|
event.preventDefault();
|
|
window.history.back();
|
|
}
|
|
} catch (_) {
|
|
/* Use the link destination when the referrer cannot be read. */
|
|
}
|
|
});
|
|
});
|
|
|
|
renderHistory();
|
|
syncPanels();
|
|
if (input.value.trim()) {
|
|
fetchSuggest(input.value.trim());
|
|
}
|
|
})();
|