feat(widget): simplify search and user card widgets

This commit is contained in:
2026-08-15 16:35:47 +08:00
parent 8a23a3d526
commit 1067b9717b
3 changed files with 118 additions and 171 deletions
+17 -2
View File
@@ -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; }
+61
View File
@@ -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) {
+40 -169
View File
@@ -1,6 +1,6 @@
<?php
/**
* Search box with hot keywords + type/cat scope (zibll widget_ui_search / zib_get_search_box).
* Search box with hot keywords and search history.
*
* @package AKSRT
*/
@@ -16,7 +16,7 @@ class SearchWidget extends \WP_Widget {
public function __construct() {
parent::__construct('aksrt_search', esc_html__('AKSRT 搜索', 'aksrt'), [
'description' => 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="<?php echo esc_url(home_url('/')); ?>"
data-aksrt-search-form="widget"
data-default-search-type="<?php echo esc_attr($in_type); ?>"
data-default-category="<?php echo esc_attr((string) $in_cat); ?>"
data-default-search-type="<?php echo esc_attr($search_type); ?>"
>
<?php if ($show_types_ui) : ?>
<div class="aksrt-search-widget__types" role="tablist" aria-label="<?php esc_attr_e('搜索类型', 'aksrt'); ?>">
<?php foreach ($type_options as $type_key => $type_label) : ?>
<button
type="button"
class="aksrt-search-widget__type<?php echo $in_type === $type_key ? ' is-active' : ''; ?>"
data-search-type="<?php echo esc_attr((string) $type_key); ?>"
><?php echo esc_html((string) $type_label); ?></button>
<?php endforeach; ?>
</div>
<?php endif; ?>
<div class="aksrt-search-widget__line line-form">
<?php if ($cat_enabled && !empty($categories)) : ?>
<div class="aksrt-search-widget__cat option-dropdown">
<label class="screen-reader-text" for="<?php echo $uid; ?>-cat"><?php esc_html_e('分类', 'aksrt'); ?></label>
<select
id="<?php echo $uid; ?>-cat"
class="aksrt-search-widget__cat-select"
data-search-category-select
<?php disabled($in_type !== 'post'); ?>
>
<option value="0"><?php esc_html_e('全部分类', 'aksrt'); ?></option>
<?php foreach ($categories as $term) :
if (!$term instanceof \WP_Term) {
continue;
}
?>
<option value="<?php echo esc_attr((string) $term->term_id); ?>" <?php selected($in_cat, (int) $term->term_id); ?>>
<?php echo esc_html($term->name); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php endif; ?>
<div class="aksrt-search-widget__field search-input-text">
<label class="screen-reader-text" for="<?php echo $uid; ?>-s"><?php esc_html_e('搜索关键词', 'aksrt'); ?></label>
<input
@@ -185,10 +99,7 @@ class SearchWidget extends \WP_Widget {
</div>
</div>
<input type="hidden" name="aksrt_search_type" value="<?php echo esc_attr($in_type); ?>">
<?php if ($cat_enabled) : ?>
<input type="hidden" name="aksrt_category" value="<?php echo esc_attr($in_type === 'post' && $in_cat > 0 ? (string) $in_cat : ''); ?>">
<?php endif; ?>
<input type="hidden" name="aksrt_search_type" value="<?php echo esc_attr($search_type); ?>">
</form>
<?php if ($show_history) : ?>
@@ -203,18 +114,26 @@ class SearchWidget extends \WP_Widget {
<?php if ($keywords) : ?>
<div class="aksrt-search-widget__keywords">
<?php if ($kw_title !== '') : ?>
<div class="aksrt-search-widget__kw-title"><?php echo esc_html($kw_title); ?></div>
<?php endif; ?>
<div class="aksrt-search-widget__kw-list">
<div class="aksrt-search-widget__kw-head">
<?php if ($kw_title !== '') : ?>
<div class="aksrt-search-widget__kw-title"><?php echo esc_html($kw_title); ?></div>
<?php endif; ?>
<button type="button" class="aksrt-search-widget__kw-refresh" data-aksrt-search-keyword-refresh aria-label="<?php esc_attr_e('刷新热门搜索', 'aksrt'); ?>">
<i class="fas fa-sync-alt" aria-hidden="true"></i>
</button>
</div>
<div
class="aksrt-search-widget__kw-list"
data-aksrt-search-keywords
data-keywords="<?php echo esc_attr((string) wp_json_encode($keyword_pool)); ?>"
data-keyword-limit="<?php echo esc_attr((string) $keyword_limit); ?>"
data-search-type="<?php echo esc_attr($search_type); ?>"
data-search-url="<?php echo esc_url(home_url('/')); ?>"
>
<?php foreach ($keywords as $kw) :
$kw = is_array($kw) ? (string) ($kw['keyword'] ?? $kw['text'] ?? '') : (string) $kw;
if ($kw === '') {
continue;
}
$url = function_exists('aksrt_build_search_url')
? aksrt_build_search_url($kw, ['aksrt_search_type' => $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('/'));
?>
<a class="aksrt-search-widget__kw" href="<?php echo esc_url($url); ?>"><?php echo esc_html($kw); ?></a>
<?php endforeach; ?>
@@ -222,21 +141,12 @@ class SearchWidget extends \WP_Widget {
</div>
<?php endif; ?>
<?php if ($more_but !== '' && $more_url !== '') : ?>
<div class="aksrt-search-widget__more">
<a href="<?php echo esc_url($more_url); ?>"><?php echo esc_html($more_but); ?></a>
</div>
<?php endif; ?>
</div>
<?php
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
public function form($instance): void {
$type_options = function_exists('aksrt_get_search_type_options')
? aksrt_get_search_type_options()
: ['post' => __('文章', 'aksrt')];
$in_type = sanitize_key((string) ($instance['in_type'] ?? 'post'));
?>
<p>
<label><?php esc_html_e('标题(可选)', 'aksrt'); ?>
@@ -249,30 +159,9 @@ class SearchWidget extends \WP_Widget {
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_type')); ?>" value="1" <?php checked(!empty($instance['show_type']) || !isset($instance['show_type'])); ?>> <?php esc_html_e('显示类型切换(≥2 种时)', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_input_cat')); ?>" value="1" <?php checked(!empty($instance['show_input_cat'])); ?>> <?php esc_html_e('显示分类筛选', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_keywords')); ?>" value="1" <?php checked(!empty($instance['show_keywords'])); ?>> <?php esc_html_e('显示热门关键词', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_history')); ?>" value="1" <?php checked(!empty($instance['show_history'])); ?>> <?php esc_html_e('显示搜索历史', 'aksrt'); ?></label>
</p>
<p>
<label><?php esc_html_e('默认类型', 'aksrt'); ?>
<select class="widefat" name="<?php echo esc_attr($this->get_field_name('in_type')); ?>">
<?php foreach ($type_options as $k => $label) : ?>
<option value="<?php echo esc_attr($k); ?>" <?php selected($in_type, $k); ?>><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('默认分类 ID(0=全部)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('in_cat')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['in_cat'] ?? 0))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('可选分类 ID(逗号;空=后台/全部分类)', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('more_cats')); ?>" value="<?php echo esc_attr((string) ($instance['more_cats'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('关键词标题', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('keywords_title')); ?>" value="<?php echo esc_attr((string) ($instance['keywords_title'] ?? __('热门搜索', 'aksrt'))); ?>">
@@ -284,22 +173,10 @@ class SearchWidget extends \WP_Widget {
</label>
</p>
<p>
<label><?php esc_html_e('更多按钮文案', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('more_but')); ?>" value="<?php echo esc_attr((string) ($instance['more_but'] ?? '')); ?>">
<label><?php esc_html_e('关键词最多显示数量', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="50" name="<?php echo esc_attr($this->get_field_name('keyword_limit')); ?>" value="<?php echo esc_attr((string) max(1, min(50, (int) ($instance['keyword_limit'] ?? 12)))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('更多按钮链接', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('more_but_url')); ?>" value="<?php echo esc_attr((string) ($instance['more_but_url'] ?? '')); ?>">
</label>
</p>
<?php // Common-form section intentionally skipped: SearchWidget owns a
// business `show_type` checkbox (search-type toggle) that collides
// with the shared common `show_type` (device visibility). The
// shared affix/display/animation/layout fields are therefore not
// rendered here; update_common_fields() below still persists any
// common keys submitted through other surfaces, and the affix
// helper is wired in widget() via aksrt_widget_affix_attrs(). ?>
<?php
}
@@ -309,15 +186,9 @@ class SearchWidget extends \WP_Widget {
'placeholder' => 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))),
]);
}
}