diff --git a/assets/css/frontend/widget-batch5.css b/assets/css/frontend/widget-batch5.css index d4bd5059..685bdea4 100644 --- a/assets/css/frontend/widget-batch5.css +++ b/assets/css/frontend/widget-batch5.css @@ -434,6 +434,23 @@ padding: 0; } .aksrt-search-widget__history-clear:hover { color: var(--color-primary, #3b82f6); } +.aksrt-search-widget__kw-refresh { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + padding: 0; + border: 0; + border-radius: 50%; + background: transparent; + color: var(--muted-2-color, #999); + cursor: pointer; +} +.aksrt-search-widget__kw-refresh:hover, +.aksrt-search-widget__kw-refresh:focus-visible { color: var(--color-primary, #3b82f6); } +.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, @@ -450,8 +467,6 @@ font-size: 12px; color: var(--muted-2-color, #999); } -.aksrt-search-widget__more { margin-top: 8px; text-align: right; font-size: 12px; } -.aksrt-search-widget__more a { color: var(--color-primary, #3b82f6); text-decoration: none; } /* Product list widgets (batch6) */ .aksrt-product-widget { margin-bottom: 8px; } diff --git a/assets/js/core/search.js b/assets/js/core/search.js index e6737e2f..9e53d9ad 100644 --- a/assets/js/core/search.js +++ b/assets/js/core/search.js @@ -402,6 +402,67 @@ getSearchType: getSearchType, searchConfig: searchConfig, }); + initWidgetKeywordRefresh(); + } + + function initWidgetKeywordRefresh() { + document.querySelectorAll("[data-aksrt-search-keywords]").forEach(function (list) { + if (list.dataset.aksrtSearchKeywordsBound === "1") return; + list.dataset.aksrtSearchKeywordsBound = "1"; + + var keywords; + try { + keywords = JSON.parse(list.getAttribute("data-keywords") || "[]"); + } catch (e) { + keywords = []; + } + if (!Array.isArray(keywords) || !keywords.length) return; + + keywords = keywords + .map(function (keyword) { return String(keyword || "").trim(); }) + .filter(function (keyword, index, values) { return keyword && values.indexOf(keyword) === index; }); + var limit = Math.max(1, parseInt(list.getAttribute("data-keyword-limit") || "12", 10) || 12); + var searchType = list.getAttribute("data-search-type") || "post"; + var searchUrl = list.getAttribute("data-search-url") || window.location.href; + var refreshButton = list.parentElement + ? list.parentElement.querySelector("[data-aksrt-search-keyword-refresh]") + : null; + + function shuffledKeywords() { + var shuffled = keywords.slice(); + for (var i = shuffled.length - 1; i > 0; i--) { + var j = Math.floor(Math.random() * (i + 1)); + var current = shuffled[i]; + shuffled[i] = shuffled[j]; + shuffled[j] = current; + } + return shuffled.slice(0, limit); + } + + function renderKeywords() { + list.replaceChildren(); + shuffledKeywords().forEach(function (keyword) { + var link = document.createElement("a"); + var url = new URL(searchUrl, window.location.href); + url.searchParams.set("s", keyword); + url.searchParams.set("aksrt_search_type", searchType); + link.className = "aksrt-search-widget__kw"; + link.href = url.toString(); + link.textContent = keyword; + list.appendChild(link); + }); + } + + if (refreshButton) { + refreshButton.addEventListener("click", function () { + refreshButton.classList.add("is-refreshing"); + renderKeywords(); + window.setTimeout(function () { + refreshButton.classList.remove("is-refreshing"); + }, 200); + }); + } + }); } function initWidgetSearchHistory(ctx) { diff --git a/src/Widgets/SearchWidget.php b/src/Widgets/SearchWidget.php index fd4fec35..601c4565 100644 --- a/src/Widgets/SearchWidget.php +++ b/src/Widgets/SearchWidget.php @@ -1,6 +1,6 @@ esc_html__('搜索框:类型切换、分类范围、热门关键词与搜索历史。', 'aksrt'), + 'description' => esc_html__('搜索框:热门关键词与搜索历史。', 'aksrt'), 'classname' => 'widget_aksrt_search', ]); } @@ -34,74 +34,14 @@ class SearchWidget extends \WP_Widget { } $show_kw = !empty($instance['show_keywords']); $show_history = !empty($instance['show_history']); - $show_types = !empty($instance['show_type']) || !isset($instance['show_type']); - // NOTE: SearchWidget owns a business `show_type` checkbox (search-type - // toggle) that shares the key name with the common `show_type` (device - // visibility). To avoid an admin-form collision, this widget does NOT - // render the shared common-form section (render_common_form_section is - // intentionally not called). The shared affix helper is still wired in - // widget() via aksrt_widget_affix_attrs(), and update_common_fields() - // persists common keys — but note the shared show_type cannot be set - // here, so display_callback treats it as 'all' (always visible). - $show_cat = !empty($instance['show_input_cat']); $kw_title = sanitize_text_field((string) ($instance['keywords_title'] ?? __('热门搜索', 'aksrt'))); - $more_but = sanitize_text_field((string) ($instance['more_but'] ?? '')); - $more_url = esc_url_raw(trim((string) ($instance['more_but_url'] ?? ''))); + $keyword_limit = max(1, min(50, (int) ($instance['keyword_limit'] ?? 12))); // Affix helper (shared layer sidebar_affix). $affix = function_exists('aksrt_widget_affix_attrs') ? aksrt_widget_affix_attrs($instance) : ['class' => '', 'attr' => '']; - $state = function_exists('aksrt_get_search_form_state') - ? aksrt_get_search_form_state() - : [ - 'type' => 'post', - 'types' => ['post' => __('文章', 'aksrt')], - 'category_id' => 0, - 'category_enabled' => false, - 'categories' => [], - ]; - - $type_options = is_array($state['types'] ?? null) ? $state['types'] : ['post' => __('文章', 'aksrt')]; - $in_type = sanitize_key((string) ($instance['in_type'] ?? $state['type'] ?? 'post')); - if (!isset($type_options[$in_type])) { - $in_type = array_key_first($type_options) ?: 'post'; - } - - // Category scope. - $in_cat = max(0, (int) ($instance['in_cat'] ?? 0)); - if ($in_cat <= 0) { - $in_cat = (int) ($state['category_id'] ?? 0); - } - $cat_enabled = $show_cat && ($state['category_enabled'] || !empty($instance['show_input_cat'])); - $categories = []; - if ($cat_enabled) { - $more_raw = trim((string) ($instance['more_cats'] ?? '')); - if ($more_raw !== '') { - $ids = array_values(array_filter(array_map('intval', preg_split('/[,,\s]+/', $more_raw) ?: []))); - if ($ids) { - $got = get_categories([ - 'taxonomy' => 'category', - 'include' => $ids, - 'hide_empty' => false, - 'orderby' => 'include', - ]); - $categories = is_array($got) ? $got : []; - } - } - if (!$categories) { - $categories = is_array($state['categories'] ?? null) ? $state['categories'] : []; - } - if (!$categories && function_exists('aksrt_get_search_categories')) { - $categories = aksrt_get_search_categories(); - } - } - - // Only show type UI when more than one type is configured, unless forced. - if ($show_types && count($type_options) < 2 && empty($instance['force_show_type'])) { - // Still keep hidden type field for default. - $show_types_ui = false; - } else { - $show_types_ui = $show_types && count($type_options) >= 1; - } + $search_type = function_exists('aksrt_get_default_search_type') + ? aksrt_get_default_search_type() + : 'post'; $keywords = []; if ($show_kw) { @@ -117,7 +57,17 @@ class SearchWidget extends \WP_Widget { if ($custom !== '') { $keywords = array_values(array_filter(array_map('trim', preg_split('/[\r\n,,]+/', $custom) ?: []))); } - $keywords = array_slice(is_array($keywords) ? $keywords : [], 0, 12); + $keywords = array_values(array_filter(array_map(static function ($keyword): string { + return is_array($keyword) + ? trim((string) ($keyword['keyword'] ?? $keyword['text'] ?? '')) + : trim((string) $keyword); + }, is_array($keywords) ? $keywords : []))); + $keywords = array_values(array_unique($keywords)); + } + $keyword_pool = $keywords; + if (count($keywords) > $keyword_limit) { + shuffle($keywords); + $keywords = array_slice($keywords, 0, $keyword_limit); } $uid = 'aksrt-sw-' . esc_attr($this->id); @@ -129,45 +79,9 @@ class SearchWidget extends \WP_Widget { class="aksrt-search-widget__form search-form" action="" data-aksrt-search-form="widget" - data-default-search-type="" - data-default-category="" + data-default-search-type="" > - -
- $type_label) : ?> - - -
- -
- -
- - -
- -
- - - - + @@ -203,18 +114,26 @@ class SearchWidget extends \WP_Widget {
- -
- -
+
+ +
+ + +
+
$in_type]) - : add_query_arg(['s' => $kw, 'aksrt_search_type' => $in_type], home_url('/')); + ? aksrt_build_search_url($kw, ['aksrt_search_type' => $search_type]) + : add_query_arg(['s' => $kw, 'aksrt_search_type' => $search_type], home_url('/')); ?> @@ -222,21 +141,12 @@ class SearchWidget extends \WP_Widget {
- -
- -
-
__('文章', 'aksrt')]; - $in_type = sanitize_key((string) ($instance['in_type'] ?? 'post')); ?>

-
-

-

- -

-

- -

-

- -

-

-

- -

- sanitize_text_field((string) ($new_instance['placeholder'] ?? '')), 'show_keywords' => !empty($new_instance['show_keywords']) ? 1 : 0, 'show_history' => !empty($new_instance['show_history']) ? 1 : 0, - 'show_type' => !empty($new_instance['show_type']) ? 1 : 0, - 'show_input_cat' => !empty($new_instance['show_input_cat']) ? 1 : 0, - 'in_type' => sanitize_key((string) ($new_instance['in_type'] ?? 'post')), - 'in_cat' => max(0, (int) ($new_instance['in_cat'] ?? 0)), - 'more_cats' => sanitize_text_field((string) ($new_instance['more_cats'] ?? '')), 'keywords_title' => sanitize_text_field((string) ($new_instance['keywords_title'] ?? '')), 'keywords' => sanitize_textarea_field((string) ($new_instance['keywords'] ?? '')), - 'more_but' => sanitize_text_field((string) ($new_instance['more_but'] ?? '')), - 'more_but_url' => esc_url_raw(trim((string) ($new_instance['more_but_url'] ?? ''))), + 'keyword_limit' => max(1, min(50, (int) ($new_instance['keyword_limit'] ?? 12))), ]); } }