feat(nav): finish remaining parity gaps (image_select, AJAX search, senior UI)

- Layout image_select previews (zibll PNGs under assets/images/header-layout)
- Localize aksrtAdminData.themeUri for admin SPA asset URLs
- Layout-2 inline search AJAX suggestions via aksrt_search_suggestions
- Appearance→Menus visual senior submenu editor (cards/columns)
- Acceptance checklist + automated source contract report
This commit is contained in:
2026-07-22 12:04:44 +08:00
parent e3848ddf8c
commit 2a11a889c3
20 changed files with 522 additions and 19 deletions
+140
View File
@@ -0,0 +1,140 @@
/**
* Enhance layout-2 inline search with AJAX suggestions (aksrt_search_suggestions).
* Extends existing panel search without dual systems.
*/
(function () {
"use strict";
function initInlineSearchSuggest() {
var form = document.querySelector('form[data-aksrt-search-form="inline"]');
if (!form || !window.aksrtData || !aksrtData.ajaxUrl) return;
var input = form.querySelector(".search-input");
var group = form.querySelector(".form-group.relative, .form-group");
if (!input || !group) return;
var panel = group.querySelector(".navbar-inline-search-panel");
if (!panel) {
panel = document.createElement("div");
panel.className = "dropdown-menu hover-show-con navbar-inline-search-panel";
group.appendChild(panel);
}
var timer = null;
var searchConfig = (window.aksrtData && aksrtData.search) || {};
var suggestionsEnabled = searchConfig.suggestionsEnabled !== false;
function typeValue() {
var t = form.querySelector('input[name="aksrt_search_type"]');
return (t && t.value) || searchConfig.defaultType || "post";
}
function renderItems(items) {
var html = "";
if (items && items.length) {
html += '<div class="muted-2-color em09 mb6">' + (searchConfig.suggestTitle || "搜索建议") + "</div>";
html += '<div class="navbar-inline-search-suggest">';
items.forEach(function (it) {
html +=
'<a class="navbar-inline-search-suggest__item" href="' +
(it.url || "#") +
'"><span class="title">' +
(it.title || "") +
'</span>' +
(it.meta ? '<span class="meta muted-2-color em09">' + it.meta + "</span>" : "") +
"</a>";
});
html += "</div>";
}
// Keep hot keywords block if present as static sibling content was replaced —
// re-append hots from data attribute if we stored them.
var hots = form.getAttribute("data-hots");
if (hots) {
try {
var arr = JSON.parse(hots);
if (arr && arr.length) {
html += '<div class="muted-2-color em09 mb6 mt10">热门搜索</div><div class="navbar-inline-search-hots">';
arr.forEach(function (kw) {
html +=
'<a class="but jb-sm" href="' +
(window.location.origin || "") +
"/?s=" +
encodeURIComponent(kw) +
'">' +
kw +
"</a> ";
});
html += "</div>";
}
} catch (e) {}
}
panel.innerHTML = html || panel.innerHTML;
panel.classList.add("is-open");
panel.style.display = "block";
panel.style.visibility = "visible";
}
// Persist hot keywords for re-render after suggestions
var hotLinks = panel.querySelectorAll(".navbar-inline-search-hots a");
if (hotLinks.length) {
var kws = [];
hotLinks.forEach(function (a) {
kws.push((a.textContent || "").trim());
});
form.setAttribute("data-hots", JSON.stringify(kws));
}
function request(q) {
if (!suggestionsEnabled || q.length < 2) {
// restore hots-only
if (form.getAttribute("data-hots")) {
renderItems([]);
}
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 (xhr.status < 200 || xhr.status >= 300) return;
try {
var json = JSON.parse(xhr.responseText);
if (json.success && json.data && json.data.items) {
renderItems(json.data.items);
}
} catch (e) {}
};
xhr.send(
"action=aksrt_search_suggestions&nonce=" +
encodeURIComponent(aksrtData.nonce) +
"&q=" +
encodeURIComponent(q) +
"&type=" +
encodeURIComponent(typeValue())
);
}
input.addEventListener("input", function () {
var q = (input.value || "").trim();
if (timer) clearTimeout(timer);
timer = setTimeout(function () {
request(q);
}, 180);
});
input.addEventListener("focus", function () {
panel.style.display = "block";
panel.style.visibility = "visible";
});
}
if (window.AksrtFrontend && typeof window.AksrtFrontend.register === "function") {
window.AksrtFrontend.register("aksrt-inline-search-suggest", function () {
initInlineSearchSuggest();
});
} else if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initInlineSearchSuggest);
} else {
initInlineSearchSuggest();
}
})();