diff --git a/assets/css/frontend/widget-batch5.css b/assets/css/frontend/widget-batch5.css index 7f952b51..e626266b 100644 --- a/assets/css/frontend/widget-batch5.css +++ b/assets/css/frontend/widget-batch5.css @@ -410,8 +410,23 @@ /* Search keywords + history */ -.aksrt-search-widget__keywords, -.aksrt-search-widget__history { margin-top: 10px; } +.aksrt-search-widget__keywords { margin-top: 10px; } +.aksrt-search-widget__field { position: relative; } +.aksrt-search-widget__history { + position: absolute; + z-index: 20; + top: calc(100% + 6px); + right: 0; + left: 0; + max-height: 240px; + overflow-y: auto; + padding: 10px; + border: 1px solid var(--border-color, rgba(0, 0, 0, .08)); + border-radius: var(--main-radius, 8px); + background: var(--main-bg-color, #fff); + box-shadow: 0 8px 24px rgba(0, 0, 0, .12); +} +.aksrt-search-widget__history[hidden] { display: none; } .aksrt-search-widget__kw-head { display: flex; align-items: center; @@ -453,10 +468,8 @@ .aksrt-search-widget__kw-refresh:focus-visible { color: var(--color-primary, #3b82f6); opacity: 1; } .aksrt-search-widget__kw-refresh.is-refreshing i { animation: aksrt-search-keyword-refresh .2s linear; } @keyframes aksrt-search-keyword-refresh { to { transform: rotate(360deg); } } -.aksrt-search-widget__kw-list, -.aksrt-search-widget__history-list { display: flex; flex-wrap: wrap; gap: 6px; } -.aksrt-search-widget__kw, -.aksrt-search-widget__history-item { +.aksrt-search-widget__kw-list { display: flex; flex-wrap: wrap; gap: 6px; } +.aksrt-search-widget__kw { display: inline-block; padding: 3px 10px; border-radius: 999px; @@ -465,6 +478,21 @@ text-decoration: none; font-size: 12px; } +.aksrt-search-widget__history-list { display: grid; gap: 2px; } +.aksrt-search-widget__history-item { + display: block; + padding: 7px 8px; + border-radius: 6px; + color: inherit; + font-size: 13px; + line-height: 1.35; + text-decoration: none; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.aksrt-search-widget__history-item:hover, +.aksrt-search-widget__history-item:focus-visible { background: var(--surface-2, #f3f4f6); color: var(--color-primary, #3b82f6); } .aksrt-search-widget__history-empty { font-size: 12px; color: var(--muted-2-color, #999); diff --git a/assets/js/core/search.js b/assets/js/core/search.js index 9e53d9ad..f16e0aa4 100644 --- a/assets/js/core/search.js +++ b/assets/js/core/search.js @@ -466,14 +466,15 @@ } function initWidgetSearchHistory(ctx) { - var lists = document.querySelectorAll("[data-aksrt-search-history-list]"); - if (!lists.length) return; + var panels = document.querySelectorAll("[data-aksrt-search-history]"); + if (!panels.length) return; if (!ctx.historyEnabled) { - document.querySelectorAll("[data-aksrt-search-history]").forEach(function (el) { + panels.forEach(function (el) { el.hidden = true; }); return; } + var activePanel = null; function escapeHtml(s) { return String(s == null ? "" : s) @@ -483,78 +484,113 @@ .replace(/"/g, """); } - 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 = '"; - 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 ( - '' + - escapeHtml(kw) + - "" - ); - }) - .join(""); + function close(panel) { + if (!panel) return; + panel.hidden = true; + var input = panel.parentElement ? panel.parentElement.querySelector('input[name="s"]') : null; + if (input) input.setAttribute("aria-expanded", "false"); + if (activePanel === panel) activePanel = null; + } + + function closeAll(except) { + panels.forEach(function (panel) { + if (panel !== except) close(panel); }); } - function load() { + function buildSearchUrl(form, keyword, type) { + var url = new URL(form.getAttribute("action") || window.location.href, window.location.href); + url.searchParams.set("s", keyword); + if (type) url.searchParams.set("aksrt_search_type", type); + return url.toString(); + } + + function paint(panel, items) { + var wrap = panel.querySelector("[data-aksrt-search-history-list]"); + var clearBtn = panel.querySelector("[data-aksrt-search-history-clear]"); + var form = panel.closest("form"); + var limit = parseInt(panel.getAttribute("data-history-limit"), 10); + var list = (Array.isArray(items) ? items : []).filter(function (item) { + return item && item.keyword; + }).slice(0, limit > 0 ? Math.min(limit, 8) : 8); + if (!wrap || !form || !list.length) { + if (clearBtn) clearBtn.hidden = true; + close(panel); + return; + } + if (clearBtn) clearBtn.hidden = false; + wrap.innerHTML = list.map(function (item) { + var keyword = String(item.keyword); + var type = item.type || "post"; + return ( + '' + escapeHtml(keyword) + "" + ); + }).join(""); + panel.hidden = false; + var input = panel.parentElement ? panel.parentElement.querySelector('input[name="s"]') : null; + if (input) input.setAttribute("aria-expanded", "true"); + } + + function load(panel) { if (window.aksrtData && aksrtData.isLoggedIn) { ctx.requestHistorySync("get", null, function (items) { - paint(items); + if (activePanel === panel) paint(panel, items); }); return; } - paint(ctx.readHistory()); + if (activePanel === panel) paint(panel, ctx.readHistory()); } + panels.forEach(function (panel) { + var field = panel.parentElement; + var form = panel.closest("form"); + var input = field ? field.querySelector('input[name="s"]') : null; + if (!input || !form) return; + + function open() { + closeAll(panel); + activePanel = panel; + load(panel); + } + + input.addEventListener("focus", open); + input.addEventListener("click", open); + input.addEventListener("keydown", function (e) { + if (e.key === "Escape") { + close(panel); + input.focus(); + } + }); + form.addEventListener("submit", function () { + close(panel); + }); + panel.addEventListener("click", function (e) { + if (e.target.closest(".aksrt-search-widget__history-item")) close(panel); + }); + }); + document.querySelectorAll("[data-aksrt-search-history-clear]").forEach(function (btn) { btn.addEventListener("click", function (e) { e.preventDefault(); + e.stopPropagation(); + var panel = btn.closest("[data-aksrt-search-history]"); + if (!panel) return; if (window.aksrtData && aksrtData.isLoggedIn) { ctx.requestHistorySync("save", [], function (items) { - paint(items); + paint(panel, items); }); return; } ctx.writeHistory([]); - paint([]); + paint(panel, []); }); }); - // 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); - }); + document.addEventListener("pointerdown", function (e) { + if (activePanel && !activePanel.parentElement.contains(e.target)) close(activePanel); }); - - load(); } window.AksrtFrontend.register("aksrt-search-panel", function () { initSearchPanel(); }); diff --git a/src/Widgets/SearchWidget.php b/src/Widgets/SearchWidget.php index 601c4565..13244d10 100644 --- a/src/Widgets/SearchWidget.php +++ b/src/Widgets/SearchWidget.php @@ -32,16 +32,20 @@ class SearchWidget extends \WP_Widget { if ($placeholder === '' && function_exists('aksrt_get_search_placeholder')) { $placeholder = aksrt_get_search_placeholder(); } - $show_kw = !empty($instance['show_keywords']); - $show_history = !empty($instance['show_history']); - $kw_title = sanitize_text_field((string) ($instance['keywords_title'] ?? __('热门搜索', 'aksrt'))); + $show_kw = !empty($instance['show_keywords']); + $show_history = !empty($instance['show_history']); + $kw_title = sanitize_text_field((string) ($instance['keywords_title'] ?? __('热门搜索', 'aksrt'))); $keyword_limit = max(1, min(50, (int) ($instance['keyword_limit'] ?? 12))); + $history_limit = max(1, min(8, (int) ($instance['history_limit'] ?? 8))); // Affix helper (shared layer sidebar_affix). $affix = function_exists('aksrt_widget_affix_attrs') ? aksrt_widget_affix_attrs($instance) : ['class' => '', 'attr' => '']; $search_type = function_exists('aksrt_get_default_search_type') ? aksrt_get_default_search_type() : 'post'; + $history_enabled = function_exists('aksrt_search_history_enabled') + ? aksrt_search_history_enabled() + : true; $keywords = []; if ($show_kw) { @@ -92,26 +96,36 @@ class SearchWidget extends \WP_Widget { placeholder="" value="" autocomplete="off" + aria-haspopup="true" + aria-controls="-history" + aria-expanded="false" > + +
+ - - - -