diff --git a/assets/js/widget-set.js b/assets/js/widget-set.js index 0bba475a..8726220f 100644 --- a/assets/js/widget-set.js +++ b/assets/js/widget-set.js @@ -17,6 +17,39 @@ var tabs = widget.querySelectorAll('.widget-tab-posts__tab'); var panels = widget.querySelectorAll('.widget-tab-posts__panel'); if (!tabs.length || !panels.length) return; + var rest = widget.getAttribute('data-rest') || ''; + var defaultCount = widget.getAttribute('data-count') || '5'; + + function loadPanel(panel, tab) { + if (!panel || panel.getAttribute('data-loaded') === '1' || !rest) return; + var orderby = tab.getAttribute('data-orderby') || 'date'; + var cat = tab.getAttribute('data-cat') || '0'; + var tabCount = tab.getAttribute('data-tab-count') || '0'; + var qs = new URLSearchParams({ + count: defaultCount, + orderby: orderby, + cat: cat, + tab_count: tabCount, + }); + var url = rest + (rest.indexOf('?') > -1 ? '&' : '?') + qs.toString(); + panel.classList.add('is-ajax-loading'); + var headers = { Accept: 'application/json' }; + if (window.aksrtData && aksrtData.restNonce) { + headers['X-WP-Nonce'] = aksrtData.restNonce; + } + fetch(url, { credentials: 'same-origin', headers: headers }) + .then(function (r) { return r.json(); }) + .then(function (j) { + var d = (j && j.data) || j || {}; + var html = d.html || ''; + panel.innerHTML = html || '

暂无文章

'; + panel.setAttribute('data-loaded', '1'); + panel.classList.remove('is-ajax-loading'); + }) + .catch(function () { + panel.classList.remove('is-ajax-loading'); + }); + } tabs.forEach(function (tab) { tab.addEventListener('click', function () { @@ -33,6 +66,7 @@ panel.classList.toggle('active', active); if (active) { panel.removeAttribute('hidden'); + loadPanel(panel, tab); } else { panel.setAttribute('hidden', ''); } diff --git a/src/AKSRT/Widgets/TabPostsWidget.php b/src/AKSRT/Widgets/TabPostsWidget.php index 3805acfe..22cdbabb 100644 --- a/src/AKSRT/Widgets/TabPostsWidget.php +++ b/src/AKSRT/Widgets/TabPostsWidget.php @@ -1,5 +1,7 @@ esc_html__('Tab 切换显示不同分类的文章列表', 'aksrt'), - 'customize_selective_refresh' => true, - ] - ); - } + public function __construct() { + parent::__construct( + 'aksrt_tab_posts', + esc_html__('AKSRT 多栏目文章', 'aksrt'), + [ + 'description' => esc_html__('Tab 切换显示不同分类/排序的文章(懒加载)。', 'aksrt'), + 'customize_selective_refresh' => true, + ] + ); + } - /** - * Normalize tabs from legacy comma-separated string or structured array. - * - * @param mixed $tabs Raw tabs data. - * @return array Normalized structured tabs. - */ - private function normalize_tabs($tabs): array { - // Legacy comma-separated format - if (is_string($tabs) && $tabs !== '') { - $legacy_keys = array_map('trim', explode(',', $tabs)); - $legacy_defs = [ - 'latest' => ['title' => '最新', 'orderby' => 'date'], - 'comments' => ['title' => '热评', 'orderby' => 'comments'], - 'random' => ['title' => '随机', 'orderby' => 'rand'], - 'views' => ['title' => '热门', 'orderby' => 'views'], - ]; - $result = []; - foreach ($legacy_keys as $key) { - if (isset($legacy_defs[$key])) { - $result[] = [ - 'title' => __($legacy_defs[$key]['title'], 'aksrt'), - 'category_id' => 0, - 'orderby' => $legacy_defs[$key]['orderby'], - 'count' => 0, - 'enabled' => true, - ]; - } elseif (is_numeric($key) && absint($key) > 0) { - $term = get_term(absint($key), 'category'); - $result[] = [ - 'title' => ($term && !is_wp_error($term)) ? $term->name : '', - 'category_id' => absint($key), - 'orderby' => 'date', - 'count' => 0, - 'enabled' => true, - ]; - } - } - return $result; - } + /** + * @param mixed $tabs + * @return list + */ + public static function normalize_tabs_static($tabs): array { + if (is_string($tabs) && $tabs !== '') { + $legacy_keys = array_map('trim', explode(',', $tabs)); + $legacy_defs = [ + 'latest' => ['title' => '最新', 'orderby' => 'date'], + 'comments' => ['title' => '热评', 'orderby' => 'comments'], + 'random' => ['title' => '随机', 'orderby' => 'rand'], + 'views' => ['title' => '热门', 'orderby' => 'views'], + ]; + $result = []; + foreach ($legacy_keys as $key) { + if (isset($legacy_defs[$key])) { + $result[] = [ + 'title' => __($legacy_defs[$key]['title'], 'aksrt'), + 'category_id' => 0, + 'orderby' => $legacy_defs[$key]['orderby'], + 'count' => 0, + 'enabled' => true, + ]; + } elseif (is_numeric($key) && absint($key) > 0) { + $term = get_term(absint($key), 'category'); + $result[] = [ + 'title' => ($term && !is_wp_error($term)) ? $term->name : '', + 'category_id' => absint($key), + 'orderby' => 'date', + 'count' => 0, + 'enabled' => true, + ]; + } + } + return $result; + } + if (is_array($tabs)) { + $allowed = ['date', 'modified', 'views', 'likes', 'comments', 'favorites', 'downloads', 'rand']; + $result = []; + foreach ($tabs as $tab) { + if (!is_array($tab)) { + continue; + } + $orderby = sanitize_key($tab['orderby'] ?? 'date'); + if (!in_array($orderby, $allowed, true)) { + $orderby = 'date'; + } + $result[] = [ + 'title' => sanitize_text_field((string) ($tab['title'] ?? '')), + 'category_id' => absint($tab['category_id'] ?? 0), + 'orderby' => $orderby, + 'count' => max(0, min(20, (int) ($tab['count'] ?? 0))), + 'enabled' => !empty($tab['enabled']), + ]; + } + return $result; + } + return []; + } - // Structured array format - if (is_array($tabs)) { - $allowed_orderby = ['date', 'modified', 'views', 'likes', 'comments', 'favorites', 'downloads', 'rand']; - $result = []; - foreach ($tabs as $tab) { - if (!is_array($tab)) continue; - $orderby = sanitize_key($tab['orderby'] ?? 'date'); - if (!in_array($orderby, $allowed_orderby, true)) $orderby = 'date'; - $result[] = [ - 'title' => sanitize_text_field($tab['title'] ?? ''), - 'category_id' => absint($tab['category_id'] ?? 0), - 'orderby' => $orderby, - 'count' => max(0, min(20, (int) ($tab['count'] ?? 0))), - 'enabled' => !empty($tab['enabled']), - ]; - } - return $result; - } + private function normalize_tabs($tabs): array { + return self::normalize_tabs_static($tabs); + } - return []; - } + /** + * Build query for a tab → reuse main posts payload renderer. + * + * @param array $tab + * @param int $default_count + * @return array + */ + public static function tab_to_main_instance(array $tab, int $default_count): array { + $count = !empty($tab['count']) ? (int) $tab['count'] : $default_count; + return [ + 'count' => max(1, min(30, $count)), + 'orderby' => sanitize_key((string) ($tab['orderby'] ?? 'date')), + 'cat' => absint($tab['category_id'] ?? 0), + 'style' => 'mini', + 'show_thumb' => 1, + 'show_meta' => 1, + 'show_number'=> 0, + 'paginate' => 'none', + ]; + } - /** - * Build \WP_Query args for a tab's orderby. - */ - private function build_tab_query_args(array $tab, int $default_count): array { - $count = $tab['count'] > 0 ? $tab['count'] : $default_count; - $args = [ - 'post_type' => 'post', - 'post_status' => 'publish', - 'posts_per_page' => $count, - 'ignore_sticky_posts' => true, - 'no_found_rows' => true, - ]; + /** + * REST helper: render one tab list HTML. + */ + public static function render_tab_html(array $tab, int $default_count): string { + $inst = self::tab_to_main_instance($tab, $default_count); + if (class_exists('\AKSRT\Widgets\WidgetMainPosts')) { + $payload = WidgetMainPosts::query_payload($inst, 1); + return (string) ($payload['html'] ?? ''); + } + return ''; + } - if ($tab['category_id'] > 0) { - $args['cat'] = $tab['category_id']; - } + public function widget($args, $instance): void { + echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - switch ($tab['orderby']) { - case 'views': - $args['meta_key'] = 'aksrt_post_views'; - $args['orderby'] = 'meta_value_num'; - $args['order'] = 'DESC'; - break; - case 'likes': - $args['meta_key'] = '_aksrt_like_count'; - $args['orderby'] = 'meta_value_num'; - $args['order'] = 'DESC'; - break; - case 'favorites': - $args['meta_key'] = '_aksrt_favorite_count'; - $args['orderby'] = 'meta_value_num'; - $args['order'] = 'DESC'; - break; - case 'downloads': - $args['meta_key'] = '_aksrt_res_download_count'; - $args['orderby'] = 'meta_value_num'; - $args['order'] = 'DESC'; - break; - case 'comments': - $args['orderby'] = 'comment_count'; - $args['order'] = 'DESC'; - break; - case 'rand': - $args['orderby'] = 'rand'; - break; - case 'modified': - $args['orderby'] = 'modified'; - $args['order'] = 'DESC'; - break; - case 'date': - default: - $args['orderby'] = 'date'; - $args['order'] = 'DESC'; - break; - } + $title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base); + $count = absint($instance['count'] ?? 5); + $tabs = $this->normalize_tabs($instance['tabs'] ?? 'latest,comments,random'); + $tabs = array_values(array_filter($tabs, static fn($t) => !empty($t['enabled']))); + if (!$tabs) { + echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + return; + } - return $args; - } + if ($title !== '') { + echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } - public function widget($args, $instance): void { - echo $args['before_widget']; + $widget_id = $this->id; + $rest = rest_url('aksrt/v1/widgets/tab-posts'); + $use_shared = function_exists('aksrt_render_mini_list'); + ?> +
+ - $title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base); - $count = absint($instance['count'] ?? 5); - $tabs = $this->normalize_tabs($instance['tabs'] ?? 'latest,comments,random'); + $tab) : + $tab_key = 'tab-' . $i; + ?> +
> + ' . $html . '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } else { + // Fallback to old mini list path. + $q = new \WP_Query(WidgetMainPosts::build_query_args(self::tab_to_main_instance($tab, $count), 1)); + if ($q->have_posts() && $use_shared) { + echo '
'; + aksrt_render_mini_list($q, ['context' => 'sidebar', 'show_thumb' => true, 'show_meta' => true], true); + echo '
'; + } else { + echo function_exists('aksrt_get_widget_null') ? aksrt_get_widget_null(__('暂无文章', 'aksrt')) : '

' . esc_html__('暂无文章', 'aksrt') . '

'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + wp_reset_postdata(); + } + else : ?> +
+ +
+ + + !empty($t['enabled'])); - if (empty($tabs)) { - echo $args['after_widget']; - return; - } + public function form($instance): void { + $title = $instance['title'] ?? ''; + $count = (int) ($instance['count'] ?? 5); + $tabs = $this->normalize_tabs($instance['tabs'] ?? 'latest,comments,random'); + $orderby_options = [ + 'date' => __('最新', 'aksrt'), + 'modified' => __('更新', 'aksrt'), + 'views' => __('浏览', 'aksrt'), + 'likes' => __('点赞', 'aksrt'), + 'comments' => __('评论', 'aksrt'), + 'favorites' => __('收藏', 'aksrt'), + 'downloads' => __('下载', 'aksrt'), + 'rand' => __('随机', 'aksrt'), + ]; + ?> +

+ + +

+

+ + +

+
+

+ '', 'category_id' => 0, 'orderby' => 'date', 'count' => 0, 'enabled' => false]; + } + foreach ($tabs as $i => $tab) : + ?> +
+

+ +

+

+ + " value="" placeholder=""> +

+

+ + " value="" placeholder="0"> +

+

+ + +

+

+ + " value=""> +

+
+ id; - $use_shared = function_exists('aksrt_render_mini_list'); - ?> -
- - - - - $tab) : - $tab_key = 'tab-' . $i; - $q = new \WP_Query($this->build_tab_query_args($tab, $count)); - ?> -
> - have_posts()) : ?> - -
- 'sidebar', 'show_thumb' => true, 'show_meta' => true], true); ?> -
- - - - - ' . esc_html__('暂无文章', 'aksrt') . '

'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> - -
- -
- normalize_tabs($instance['tabs'] ?? 'latest,comments,random'); - $orderby_options = [ - 'date' => __('最新', 'aksrt'), - 'modified' => __('更新', 'aksrt'), - 'views' => __('浏览', 'aksrt'), - 'likes' => __('点赞', 'aksrt'), - 'comments' => __('评论', 'aksrt'), - 'favorites' => __('收藏', 'aksrt'), - 'downloads' => __('下载', 'aksrt'), - 'rand' => __('随机', 'aksrt'), - ]; - ?> -

- - -

-

- - -

-
-

- '', 'category_id' => 0, 'orderby' => 'date', 'count' => 0, 'enabled' => false]; - } - foreach ($tabs as $i => $tab) : - ?> -
-

- -

-

- - " value="" placeholder=""> -

-

- - " value="" placeholder="0"> -

-

- - -

-

- - " value=""> -

-
- - " value=""> - sanitize_text_field($new_instance['title'] ?? ''), - 'count' => max(1, min(20, (int) ($new_instance['count'] ?? 5))), - ]; - - $tabs = []; - if (isset($new_instance['tabs']) && is_array($new_instance['tabs'])) { - $allowed_orderby = ['date', 'modified', 'views', 'likes', 'comments', 'favorites', 'downloads', 'rand']; - foreach ($new_instance['tabs'] as $tab) { - if (!is_array($tab)) continue; - $orderby = sanitize_key($tab['orderby'] ?? 'date'); - if (!in_array($orderby, $allowed_orderby, true)) $orderby = 'date'; - $tabs[] = [ - 'title' => sanitize_text_field($tab['title'] ?? ''), - 'category_id' => absint($tab['category_id'] ?? 0), - 'orderby' => $orderby, - 'count' => max(0, min(20, (int) ($tab['count'] ?? 0))), - 'enabled' => !empty($tab['enabled']), - ]; - } - } - - $result['tabs'] = !empty($tabs) ? $tabs : sanitize_text_field($new_instance['tabs'] ?? 'latest,comments,random'); - return $result; - } + public function update($new_instance, $old_instance): array { + $result = [ + 'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')), + 'count' => max(1, min(20, (int) ($new_instance['count'] ?? 5))), + ]; + $tabs = []; + if (isset($new_instance['tabs']) && is_array($new_instance['tabs'])) { + $allowed = ['date', 'modified', 'views', 'likes', 'comments', 'favorites', 'downloads', 'rand']; + foreach ($new_instance['tabs'] as $tab) { + if (!is_array($tab)) { + continue; + } + $orderby = sanitize_key($tab['orderby'] ?? 'date'); + if (!in_array($orderby, $allowed, true)) { + $orderby = 'date'; + } + $tabs[] = [ + 'title' => sanitize_text_field((string) ($tab['title'] ?? '')), + 'category_id' => absint($tab['category_id'] ?? 0), + 'orderby' => $orderby, + 'count' => max(0, min(20, (int) ($tab['count'] ?? 0))), + 'enabled' => !empty($tab['enabled']), + ]; + } + } + $result['tabs'] = !empty($tabs) ? $tabs : 'latest,comments,random'; + return $result; + } }