Files
wordpress-theme-aksrt/assets/js/core/search.js
T
kate522 c2be36acd7 feat(search): fill sidebar widget search history like zibll
Render local/remote history into [data-aksrt-search-history-list],
mark widget forms for history capture, and style clear control.
2026-07-19 02:17:53 +08:00

500 lines
18 KiB
JavaScript

/**
* AKSRT Search Panel — desktop panel, mobile overlay, history, suggestions
*/
(function () {
"use strict";
function initSearchPanel() {
var mobileOverlay = document.getElementById("tabbar-search-overlay");
var mobileInput = document.getElementById("tabbar-search-input");
var mobileClose = document.getElementById("tabbar-search-close");
var mobileForm = mobileOverlay ? mobileOverlay.querySelector("form") : null;
var mobileTypeInput = document.getElementById("tabbar-search-type");
var mobileTypeButtons = document.querySelectorAll("#tabbar-search-types [data-search-type]");
var historyWrap = document.getElementById("tabbar-search-history");
var historySection = document.getElementById("tabbar-search-history-section");
var historyClear = document.getElementById("tabbar-search-history-clear");
var suggestionsWrap = document.getElementById("tabbar-search-suggestions");
var suggestionsSection = document.getElementById("tabbar-search-suggestions-section");
var searchForms = Array.prototype.slice.call(document.querySelectorAll("[data-aksrt-search-form]"));
if (!searchForms.length && !mobileForm) return;
var HISTORY_KEY = "aksrt-mobile-search-history";
var suggestTimer = null;
var searchConfig = (window.aksrtData && aksrtData.search) || {};
var historyEnabled = searchConfig.historyEnabled !== false;
var suggestionsEnabled = !!searchConfig.suggestionsEnabled;
if (historySection && !historyEnabled) {
historySection.hidden = true;
}
if (suggestionsSection && !suggestionsEnabled) {
suggestionsSection.hidden = true;
}
function getDesktopSearchType() {
if (!searchForms.length) return searchConfig.defaultType || "post";
var input = searchForms[0].querySelector('input[name="aksrt_search_type"]');
var value = input ? (input.value || "") : "";
return value || searchConfig.defaultType || "post";
}
function getDesktopCategory() {
if (!searchForms.length) return "0";
var input = searchForms[0].querySelector('input[name="aksrt_category"]');
return input ? (input.value || "0") : "0";
}
function syncSearchForms(type, category) {
searchForms.forEach(function (form) {
var typeInput = form.querySelector('input[name="aksrt_search_type"]');
if (typeInput) {
typeInput.value = type || searchConfig.defaultType || "post";
}
var categoryInput = form.querySelector('input[name="aksrt_category"]');
if (categoryInput) {
if ((type || searchConfig.defaultType || "post") === "post" && category && category !== "0") {
categoryInput.value = category;
} else {
categoryInput.value = "";
}
}
form.querySelectorAll("[data-search-type]").forEach(function (button) {
button.classList.toggle("is-active", button.getAttribute("data-search-type") === (type || searchConfig.defaultType || "post"));
});
});
}
function syncCategorySelectors(type, category) {
document.querySelectorAll("[data-search-category-select]").forEach(function (select) {
select.value = category && category !== "" ? category : "0";
select.disabled = (type || searchConfig.defaultType || "post") !== "post";
});
}
function getSearchType() {
if (mobileTypeInput) {
return mobileTypeInput.value || getDesktopSearchType();
}
return getDesktopSearchType();
}
function setSearchType(type) {
var nextType = type || searchConfig.defaultType || getDesktopSearchType();
if (mobileTypeInput) {
mobileTypeInput.value = nextType;
}
mobileTypeButtons.forEach(function (button) {
button.classList.toggle("is-active", button.getAttribute("data-search-type") === nextType);
});
syncSearchForms(nextType, getDesktopCategory());
syncCategorySelectors(nextType, getDesktopCategory());
requestSuggestions();
}
function readHistory() {
if (!historyEnabled) return [];
if (aksrtData.isLoggedIn) return [];
try {
var raw = localStorage.getItem(HISTORY_KEY);
var list = raw ? JSON.parse(raw) : [];
return Array.isArray(list) ? list : [];
} catch (e) {
return [];
}
}
function writeHistory(items) {
if (!historyEnabled) return;
if (aksrtData.isLoggedIn) return;
try {
localStorage.setItem(HISTORY_KEY, JSON.stringify(items.slice(0, 8)));
} catch (e) {}
}
function requestHistorySync(mode, items, callback) {
if (!historyEnabled) {
if (historySection) historySection.hidden = true;
if (typeof callback === "function") callback([]);
return;
}
if (!aksrtData.isLoggedIn) {
if (typeof callback === "function") callback(mode === "save" ? (items || []) : readHistory());
return;
}
var xhr = new XMLHttpRequest();
xhr.open("POST", aksrtData.ajaxUrl, true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
xhr.onload = function () {
if (typeof callback !== "function") return;
if (xhr.status < 200 || xhr.status >= 300) {
callback([]);
return;
}
try {
var json = JSON.parse(xhr.responseText);
callback(json.success && json.data && Array.isArray(json.data.items) ? json.data.items : []);
} catch (e) {
callback([]);
}
};
var body = "action=aksrt_search_history&nonce=" + encodeURIComponent(aksrtData.nonce) + "&mode=" + encodeURIComponent(mode);
if (mode === "save") {
body += "&items=" + encodeURIComponent(JSON.stringify(items || []));
}
xhr.send(body);
}
function pushHistory(keyword, type) {
if (!historyEnabled) return;
if (!keyword) return;
if (aksrtData.isLoggedIn) {
requestHistorySync("get", null, function (remoteItems) {
var nextRemote = (remoteItems || []).filter(function (item) {
return !(item && item.keyword === keyword && item.type === type);
});
nextRemote.unshift({ keyword: keyword, type: type });
requestHistorySync("save", nextRemote.slice(0, 8), function (savedItems) {
renderHistory(savedItems);
});
});
return;
}
var next = readHistory().filter(function (item) {
return !(item && item.keyword === keyword && item.type === type);
});
next.unshift({ keyword: keyword, type: type });
writeHistory(next);
renderHistory();
}
function renderHistory(providedItems) {
if (!historyWrap) return;
if (!historyEnabled) {
if (historySection) historySection.hidden = true;
historyWrap.innerHTML = "";
return;
}
if (historySection) {
historySection.hidden = false;
}
var items = Array.isArray(providedItems) ? providedItems : readHistory();
if (!items.length) {
historyWrap.innerHTML = '<div class="tabbar-search-overlay__empty">暂无搜索历史</div>';
if (historyClear) historyClear.style.display = "none";
return;
}
if (historyClear) historyClear.style.display = "inline-flex";
historyWrap.innerHTML = items.map(function (item) {
return '<button type="button" class="tabbar-search-overlay__history-item" data-history-keyword="' + encodeURIComponent(item.keyword) + '" data-history-type="' + encodeURIComponent(item.type || 'post') + '">' +
'<i class="fas fa-history"></i><span>' + item.keyword + '</span>' +
'</button>';
}).join("");
}
function renderSuggestions(items) {
if (!suggestionsWrap || !suggestionsSection) return;
if (!suggestionsEnabled) {
suggestionsSection.hidden = true;
suggestionsWrap.innerHTML = "";
return;
}
if (!items || !items.length) {
suggestionsWrap.innerHTML = '<div class="tabbar-search-overlay__empty">暂无匹配结果</div>';
suggestionsSection.hidden = false;
return;
}
suggestionsWrap.innerHTML = items.map(function (item) {
var meta = item.meta ? '<small>' + item.meta + '</small>' : '';
return '<a href="' + item.url + '" class="tabbar-search-overlay__suggestion-item">' +
'<span>' + item.title + '</span>' + meta +
'</a>';
}).join("");
suggestionsSection.hidden = false;
}
function requestSuggestions() {
if (!mobileInput || !suggestionsWrap || !suggestionsSection) return;
if (!suggestionsEnabled) {
suggestionsSection.hidden = true;
return;
}
var keyword = (mobileInput.value || "").trim();
if (suggestTimer) clearTimeout(suggestTimer);
if (keyword.length < 2) {
suggestionsSection.hidden = true;
return;
}
suggestTimer = setTimeout(function () {
var xhr = new XMLHttpRequest();
xhr.open("POST", aksrtData.ajaxUrl, true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
xhr.onload = function () {
if (xhr.status < 200 || xhr.status >= 300) return;
try {
var json = JSON.parse(xhr.responseText);
if (json.success && json.data && json.data.items) {
renderSuggestions(json.data.items);
}
} catch (e) {}
};
xhr.send(
"action=aksrt_search_suggestions&nonce=" + encodeURIComponent(aksrtData.nonce) +
"&q=" + encodeURIComponent(keyword) +
"&type=" + encodeURIComponent(getSearchType())
);
}, 180);
}
function setOpen(open) {
if (!mobileOverlay) return;
var toggles = document.querySelectorAll(".main-search-btn, .nav-search-toggle, [data-aksrt-open-search]");
if (open) {
mobileOverlay.removeAttribute("hidden");
mobileOverlay.setAttribute("aria-hidden", "false");
mobileOverlay.classList.add("is-open", "show");
document.body.classList.add("tabbar-search-open");
if (mobileInput) {
try { mobileInput.focus(); } catch (e) {}
}
if (historyEnabled) {
if (aksrtData.isLoggedIn) {
requestHistorySync("get", null, function (items) { renderHistory(items); });
} else {
renderHistory();
}
}
if (suggestionsEnabled) requestSuggestions();
toggles.forEach(function (btn) { btn.setAttribute("aria-expanded", "true"); });
} else {
mobileOverlay.setAttribute("hidden", "");
mobileOverlay.setAttribute("aria-hidden", "true");
mobileOverlay.classList.remove("is-open", "show");
document.body.classList.remove("tabbar-search-open");
if (suggestionsSection) suggestionsSection.hidden = true;
toggles.forEach(function (btn) { btn.setAttribute("aria-expanded", "false"); });
}
}
if (mobileOverlay) {
mobileOverlay.addEventListener("click", function (e) {
if (e.target === mobileOverlay) setOpen(false);
});
}
if (mobileClose) {
mobileClose.addEventListener("click", function () {
setOpen(false);
});
}
document.querySelectorAll(".main-search-btn, .nav-search-toggle, [data-aksrt-open-search]").forEach(function (btn) {
btn.addEventListener("click", function (e) {
e.preventDefault();
setOpen(true);
});
});
// Zibll-compatible data-toggle-class for .navbar-search
document.addEventListener("click", function (e) {
var toggle = e.target.closest('[data-toggle-class][data-target=".navbar-search"], [data-toggle-class][data-target=".main-search"]');
if (!toggle) return;
e.preventDefault();
var isOpen = mobileOverlay && !mobileOverlay.hasAttribute("hidden");
setOpen(!isOpen);
});
if (historyClear) {
historyClear.addEventListener("click", function () {
if (aksrtData.isLoggedIn) {
requestHistorySync("save", [], function (savedItems) {
renderHistory(savedItems);
});
return;
}
writeHistory([]);
renderHistory();
});
}
if (historyWrap) {
historyWrap.addEventListener("click", function (e) {
var item = e.target.closest("[data-history-keyword]");
if (!item || !mobileInput) return;
mobileInput.value = decodeURIComponent(item.getAttribute("data-history-keyword") || "");
setSearchType(decodeURIComponent(item.getAttribute("data-history-type") || "post"));
requestSuggestions();
});
}
document.querySelectorAll("[data-search-category-select]").forEach(function (select) {
select.addEventListener("change", function () {
var nextCategory = this.value || "0";
syncSearchForms(getSearchType(), nextCategory);
syncCategorySelectors(getSearchType(), nextCategory);
});
});
mobileTypeButtons.forEach(function (button) {
button.addEventListener("click", function () {
setSearchType(button.getAttribute("data-search-type") || "post");
});
});
// Panel type buttons should also sync desktop/drawer forms
document.querySelectorAll("#tabbar-search-types [data-search-type], [data-aksrt-search-form] [data-search-type]").forEach(function (button) {
button.addEventListener("click", function () {
setSearchType(button.getAttribute("data-search-type") || "post");
});
});
if (mobileInput) {
mobileInput.addEventListener("input", requestSuggestions);
}
if (mobileForm) {
mobileForm.addEventListener("submit", function () {
if (mobileInput) {
pushHistory((mobileInput.value || "").trim(), getSearchType());
}
});
}
searchForms.forEach(function (form) {
if (form === mobileForm) return;
form.addEventListener("submit", function () {
if (!historyEnabled) return;
var input = form.querySelector('input[name="s"]');
var typeInput = form.querySelector('input[name="aksrt_search_type"]');
var keyword = input ? (input.value || "").trim() : "";
var type = typeInput ? (typeInput.value || searchConfig.defaultType || "post") : (searchConfig.defaultType || "post");
pushHistory(keyword, type);
});
});
syncSearchForms(getSearchType(), getDesktopCategory());
syncCategorySelectors(getSearchType(), getDesktopCategory());
setSearchType(getSearchType());
if (historyEnabled && aksrtData.isLoggedIn) {
requestHistorySync("get", null, function (items) {
renderHistory(items);
});
}
document.addEventListener("keydown", function (e) {
if (e.key === "Escape" && mobileOverlay && !mobileOverlay.hasAttribute("hidden")) {
setOpen(false);
}
});
window.__aksrtOpenSearchPanel = function () { setOpen(true); };
window.__aksrtCloseSearchPanel = function () { setOpen(false); };
// Sidebar / widget search history (zibll widget_ui_search show_history).
initWidgetSearchHistory({
historyEnabled: historyEnabled,
readHistory: readHistory,
writeHistory: writeHistory,
requestHistorySync: requestHistorySync,
pushHistory: pushHistory,
getSearchType: getSearchType,
searchConfig: searchConfig,
});
}
function initWidgetSearchHistory(ctx) {
var lists = document.querySelectorAll("[data-aksrt-search-history-list]");
if (!lists.length) return;
if (!ctx.historyEnabled) {
document.querySelectorAll("[data-aksrt-search-history]").forEach(function (el) {
el.hidden = true;
});
return;
}
function escapeHtml(s) {
return String(s == null ? "" : s)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
function paint(items) {
var list = Array.isArray(items) ? items : [];
lists.forEach(function (wrap) {
var section = wrap.closest("[data-aksrt-search-history]") || wrap.parentElement;
var clearBtn = section ? section.querySelector("[data-aksrt-search-history-clear]") : null;
if (!list.length) {
wrap.innerHTML = '<span class="aksrt-search-widget__history-empty">' + escapeHtml("暂无搜索历史") + "</span>";
if (clearBtn) clearBtn.hidden = true;
return;
}
if (clearBtn) clearBtn.hidden = false;
wrap.innerHTML = list
.map(function (item) {
var kw = item && item.keyword ? String(item.keyword) : "";
if (!kw) return "";
var type = (item && item.type) || "post";
var href = "?s=" + encodeURIComponent(kw);
return (
'<a class="aksrt-search-widget__kw aksrt-search-widget__history-item" href="' +
href +
'" data-history-keyword="' +
encodeURIComponent(kw) +
'" data-history-type="' +
encodeURIComponent(type) +
'">' +
escapeHtml(kw) +
"</a>"
);
})
.join("");
});
}
function load() {
if (window.aksrtData && aksrtData.isLoggedIn) {
ctx.requestHistorySync("get", null, function (items) {
paint(items);
});
return;
}
paint(ctx.readHistory());
}
document.querySelectorAll("[data-aksrt-search-history-clear]").forEach(function (btn) {
btn.addEventListener("click", function (e) {
e.preventDefault();
if (window.aksrtData && aksrtData.isLoggedIn) {
ctx.requestHistorySync("save", [], function (items) {
paint(items);
});
return;
}
ctx.writeHistory([]);
paint([]);
});
});
// Also push history when submitting bare widget forms without data-aksrt-search-form.
document.querySelectorAll(".aksrt-search-widget form").forEach(function (form) {
if (form.hasAttribute("data-aksrt-search-form")) return;
form.addEventListener("submit", function () {
var input = form.querySelector('input[name="s"]');
var typeInput = form.querySelector('input[name="aksrt_search_type"]');
var keyword = input ? (input.value || "").trim() : "";
var type = typeInput
? typeInput.value || ctx.searchConfig.defaultType || "post"
: ctx.searchConfig.defaultType || "post";
if (keyword) ctx.pushHistory(keyword, type);
});
});
load();
}
window.AksrtFrontend.register("aksrt-search-panel", function () { initSearchPanel(); });
})();