diff --git a/assets/css/frontend/widget-batch3e.css b/assets/css/frontend/widget-batch3e.css new file mode 100644 index 00000000..471ebebf --- /dev/null +++ b/assets/css/frontend/widget-batch3e.css @@ -0,0 +1,109 @@ +/** + * Batch3e: author card, forum topic terms, forum tab topics. + */ + +.aksrt-author-card { + border-radius: 12px; + overflow: hidden; + border: 1px solid var(--color-line, #e5e7eb); + background: var(--surface-1, #fff); + margin-bottom: 12px; + text-align: center; +} +.aksrt-author-card__cover { + height: 84px; + background: linear-gradient(135deg, #93c5fd, #c4b5fd) center/cover no-repeat; +} +.aksrt-author-card__body { padding: 0 14px 16px; } +.aksrt-author-card__avatar { + display: inline-block; + margin-top: -36px; + border-radius: 50%; + overflow: hidden; + border: 3px solid var(--surface-1, #fff); + width: 72px; + height: 72px; +} +.aksrt-author-card__avatar img { width: 100%; height: 100%; object-fit: cover; display: block; } +.aksrt-author-card__name { + display: block; + margin-top: 8px; + font-weight: 700; + font-size: 16px; + color: inherit; + text-decoration: none; +} +.aksrt-author-card__bio { + margin-top: 6px; + font-size: 12px; + color: var(--muted-2-color, #888); + line-height: 1.5; +} +.aksrt-author-card__stats { + display: flex; + justify-content: center; + gap: 16px; + margin-top: 12px; + font-size: 12px; + color: var(--muted-2-color, #888); +} +.aksrt-author-card__stats b { + display: block; + font-size: 16px; + color: var(--color-ink, #111); +} +.aksrt-author-card__btn { + display: inline-block; + margin-top: 12px; + padding: 6px 16px; + border-radius: 999px; + background: var(--color-primary, #3b82f6); + color: #fff !important; + text-decoration: none; + font-size: 13px; +} + +.aksrt-ftag-list { display: flex; flex-direction: column; gap: 6px; } +.aksrt-ftag-list.is-card { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; +} +.aksrt-ftag-item { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 10px; + border-radius: 8px; + text-decoration: none; + color: inherit; + background: var(--surface-2, #f8fafc); + font-size: 13px; +} +.aksrt-ftag-item__name { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.aksrt-ftag-item__count { font-size: 11px; color: var(--muted-2-color, #999); } + +.aksrt-ftab { margin-bottom: 10px; } +.aksrt-ftab__nav { + list-style: none; + display: flex; + flex-wrap: wrap; + gap: 6px; + margin: 0 0 10px; + padding: 0; +} +.aksrt-ftab__btn { + border: 0; + background: var(--surface-2, #f3f4f6); + border-radius: 999px; + padding: 5px 12px; + font-size: 12px; + cursor: pointer; +} +.aksrt-ftab__btn.is-active { + background: var(--color-primary, #3b82f6); + color: #fff; +} +.aksrt-ftab__panel { display: none; } +.aksrt-ftab__panel.is-active { display: block; } +.aksrt-ftab__empty { font-size: 12px; color: var(--muted-2-color, #999); margin: 0; } diff --git a/assets/css/frontend/widgets.css b/assets/css/frontend/widgets.css index 88c6fa77..08a76fb6 100644 --- a/assets/css/frontend/widgets.css +++ b/assets/css/frontend/widgets.css @@ -6,3 +6,4 @@ @import url("widget-batch3.css"); @import url("widget-batch3c.css"); @import url("widget-batch3d.css"); +@import url("widget-batch3e.css"); diff --git a/inc/modules/widgets-register.php b/inc/modules/widgets-register.php index 7d7690df..d0c15335 100644 --- a/inc/modules/widgets-register.php +++ b/inc/modules/widgets-register.php @@ -71,6 +71,7 @@ function aksrt_register_widgets() { register_widget('\AKSRT\Widgets\WidgetFeatureIntro'); register_widget('\AKSRT\Widgets\WidgetTermCards'); register_widget('\AKSRT\Widgets\WidgetPaidPosts'); + register_widget('\AKSRT\Widgets\WidgetAuthorCard'); // Forum widgets @@ -95,6 +96,12 @@ function aksrt_register_widgets() { if (class_exists('\AKSRT\Widgets\WidgetForumTopicLists')) { register_widget('\AKSRT\Widgets\WidgetForumTopicLists'); } + if (class_exists('\AKSRT\Widgets\WidgetForumTopicTerms')) { + register_widget('\AKSRT\Widgets\WidgetForumTopicTerms'); + } + if (class_exists('\AKSRT\Widgets\WidgetForumTabTopics')) { + register_widget('\AKSRT\Widgets\WidgetForumTabTopics'); + } // Shop product widgets (§6.9) register_widget('\AKSRT\Widgets\WidgetProductList'); diff --git a/src/AKSRT/Widgets/WidgetAuthorCard.php b/src/AKSRT/Widgets/WidgetAuthorCard.php new file mode 100644 index 00000000..70c11018 --- /dev/null +++ b/src/AKSRT/Widgets/WidgetAuthorCard.php @@ -0,0 +1,131 @@ + esc_html__('在文章/帖子侧栏显示当前内容作者信息。', 'aksrt'), + 'classname' => 'widget_aksrt_author_card', + ] + ); + } + + public function widget($args, $instance): void { + $author_id = 0; + if (is_singular()) { + $post = get_queried_object(); + if ($post instanceof \WP_Post) { + $author_id = (int) $post->post_author; + } + } + if ($author_id <= 0 && !empty($instance['user_id'])) { + $author_id = (int) $instance['user_id']; + } + if ($author_id <= 0) { + return; + } + $user = get_userdata($author_id); + if (!$user) { + return; + } + + $show_bg = !isset($instance['show_img_bg']) || !empty($instance['show_img_bg']); + $show_stats = !isset($instance['show_stats']) || !empty($instance['show_stats']); + + $avatar = function_exists('aksrt_get_avatar_url') + ? aksrt_get_avatar_url($author_id, ['size' => 120]) + : get_avatar_url($author_id, ['size' => 120]); + $home = function_exists('aksrt_get_user_home_url') + ? aksrt_get_user_home_url($author_id) + : get_author_posts_url($author_id); + $bio = get_user_meta($author_id, 'description', true); + if (!is_string($bio) || $bio === '') { + $bio = (string) $user->description; + } + $cover = (string) get_user_meta($author_id, 'aksrt_cover', true); + if ($cover === '') { + $cover = (string) get_user_meta($author_id, 'custom_avatar_cover', true); + } + + $post_count = count_user_posts($author_id, 'post', true); + $topic_count = post_type_exists('aksrt_forum_topic') + ? count_user_posts($author_id, 'aksrt_forum_topic', true) + : 0; + $followers = 0; + if (class_exists('\AKSRT\User\Follow')) { + $followers = \AKSRT\User\Follow::get_instance()->get_follower_count($author_id); + } + + echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base); + if ($title !== '') { + echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + echo '
'; + if ($show_bg) { + $bg = $cover !== '' ? ' style="background-image:url(' . esc_url($cover) . ')"' : ''; + echo '
'; + } + echo '
'; + echo ''; + echo '' . esc_html($user->display_name) . ''; + if ($bio !== '') { + echo '
' . esc_html(wp_trim_words($bio, 28)) . '
'; + } + if ($show_stats) { + echo '
'; + echo '' . esc_html((string) $post_count) . '' . esc_html__('文章', 'aksrt') . ''; + if ($topic_count > 0) { + echo '' . esc_html((string) $topic_count) . '' . esc_html__('帖子', 'aksrt') . ''; + } + echo '' . esc_html((string) $followers) . '' . esc_html__('粉丝', 'aksrt') . ''; + echo '
'; + } + echo '' . esc_html__('查看主页', 'aksrt') . ''; + echo '
'; + + echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + public function form($instance): void { + ?> +

+

+ +

+

+ +

+

+
+ +

+ sanitize_text_field((string) ($new_instance['title'] ?? '')), + 'user_id' => max(0, (int) ($new_instance['user_id'] ?? 0)), + 'show_img_bg' => !empty($new_instance['show_img_bg']) ? 1 : 0, + 'show_stats' => !empty($new_instance['show_stats']) ? 1 : 0, + ]; + } +} diff --git a/src/AKSRT/Widgets/WidgetForumTabTopics.php b/src/AKSRT/Widgets/WidgetForumTabTopics.php new file mode 100644 index 00000000..1ffbfb60 --- /dev/null +++ b/src/AKSRT/Widgets/WidgetForumTabTopics.php @@ -0,0 +1,192 @@ + esc_html__('热门/最新/精华等栏目切换的帖子列表。', 'aksrt'), + 'classname' => 'widget_aksrt_forum_tab_topics', + ] + ); + } + + public function widget($args, $instance): void { + if (!post_type_exists('aksrt_forum_topic')) { + return; + } + $tabs = $this->normalize_tabs($instance['tabs'] ?? []); + if (!$tabs) { + $tabs = [ + ['title' => __('最新', 'aksrt'), 'orderby' => 'date'], + ['title' => __('热门', 'aksrt'), 'orderby' => 'views'], + ['title' => __('精华', 'aksrt'), 'orderby' => 'essence'], + ]; + } + $count = max(1, min(20, (int) ($instance['count'] ?? 6))); + $uid = 'aksrt-ftab-' . esc_attr($this->id); + + echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base); + if ($title !== '') { + echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + echo '
'; + echo ''; + + foreach ($tabs as $i => $tab) { + $cls = $i === 0 ? ' is-active' : ''; + echo '
'; + $this->render_list($tab, $count); + echo '
'; + } + echo '
'; + + ?> + + 'aksrt_forum_topic', + 'post_status' => 'publish', + 'posts_per_page' => $count, + 'order' => 'DESC', + ]; + switch ($tab['orderby']) { + case 'views': + $query['meta_key'] = '_aksrt_views'; + $query['orderby'] = 'meta_value_num'; + break; + case 'comments': + $query['orderby'] = 'comment_count'; + break; + case 'essence': + $query['meta_query'] = [[ + 'key' => '_aksrt_essence', + 'value' => '1', + ]]; + $query['orderby'] = 'date'; + break; + default: + $query['orderby'] = 'date'; + } + $q = new \WP_Query($query); + if (!$q->have_posts()) { + echo '

' . esc_html__('暂无内容', 'aksrt') . '

'; + wp_reset_postdata(); + return; + } + echo ''; + wp_reset_postdata(); + } + + /** + * @param mixed $raw + * @return array + */ + private function normalize_tabs($raw): array { + if (is_string($raw)) { + $decoded = json_decode($raw, true); + $raw = is_array($decoded) ? $decoded : []; + } + if (!is_array($raw)) { + return []; + } + $out = []; + foreach ($raw as $row) { + if (!is_array($row)) { + continue; + } + $title = sanitize_text_field((string) ($row['title'] ?? '')); + if ($title === '') { + continue; + } + $orderby = sanitize_key((string) ($row['orderby'] ?? 'date')); + if (!in_array($orderby, ['date', 'views', 'comments', 'essence'], true)) { + $orderby = 'date'; + } + $out[] = ['title' => $title, 'orderby' => $orderby]; + if (count($out) >= 6) { + break; + } + } + return $out; + } + + public function form($instance): void { + $tabs = is_string($instance['tabs'] ?? null) + ? (string) $instance['tabs'] + : wp_json_encode($this->normalize_tabs($instance['tabs'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + ?> +

+ +

+

+ + +

+

+ +

+ sanitize_text_field((string) ($new_instance['title'] ?? '')), + 'tabs' => $this->normalize_tabs($new_instance['tabs'] ?? []), + 'count' => max(1, min(20, (int) ($new_instance['count'] ?? 6))), + ]; + } +} diff --git a/src/AKSRT/Widgets/WidgetForumTopicTerms.php b/src/AKSRT/Widgets/WidgetForumTopicTerms.php new file mode 100644 index 00000000..26f426af --- /dev/null +++ b/src/AKSRT/Widgets/WidgetForumTopicTerms.php @@ -0,0 +1,160 @@ + esc_html__('展示论坛话题标签(aksrt_forum_tag)。', 'aksrt'), + 'classname' => 'widget_aksrt_forum_topic_terms', + ] + ); + } + + public function widget($args, $instance): void { + $tax = taxonomy_exists('aksrt_forum_tag') ? 'aksrt_forum_tag' : (taxonomy_exists('aksrt_forum_topic_cat') ? 'aksrt_forum_topic_cat' : ''); + if ($tax === '') { + return; + } + + $number = max(1, min(40, (int) ($instance['number'] ?? 12))); + $orderby = sanitize_key((string) ($instance['orderby'] ?? 'count')); + $hide_empty = !isset($instance['hide_empty']) || !empty($instance['hide_empty']); + $include = $this->parse_ids((string) ($instance['include'] ?? '')); + $style = sanitize_key((string) ($instance['style'] ?? 'mini')); + + $term_args = [ + 'taxonomy' => $tax, + 'number' => $number, + 'hide_empty' => $hide_empty, + ]; + if ($include) { + $term_args['include'] = $include; + $term_args['orderby'] = 'include'; + } else { + if ($orderby === 'name') { + $term_args['orderby'] = 'name'; + $term_args['order'] = 'ASC'; + } elseif ($orderby === 'views') { + $term_args['meta_key'] = '_aksrt_views'; + $term_args['orderby'] = 'meta_value_num'; + $term_args['order'] = 'DESC'; + } else { + $term_args['orderby'] = 'count'; + $term_args['order'] = 'DESC'; + } + } + + $terms = get_terms($term_args); + if (is_wp_error($terms) || !$terms) { + return; + } + + echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base); + if ($title !== '') { + echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + $list_cls = $style === 'card' ? 'aksrt-ftag-list is-card' : 'aksrt-ftag-list is-mini'; + echo '
'; + foreach ($terms as $term) { + $link = get_term_link($term); + if (is_wp_error($link)) { + continue; + } + $icon = (string) get_term_meta($term->term_id, '_aksrt_icon', true); + echo ''; + if ($icon !== '') { + echo ' '; + } + echo '' . esc_html($term->name) . ''; + echo '' . esc_html((string) (int) $term->count) . ''; + echo ''; + } + echo '
'; + + echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + /** @return array */ + private function parse_ids(string $raw): array { + if ($raw === '') { + return []; + } + $ids = []; + foreach (preg_split('/[\s,]+/', $raw) ?: [] as $p) { + $id = (int) $p; + if ($id > 0) { + $ids[] = $id; + } + } + return array_values(array_unique($ids)); + } + + public function form($instance): void { + ?> +

+ +

+

+ +

+

+ +

+

+ +

+

+ +

+

+ +

+ sanitize_text_field((string) ($new_instance['title'] ?? '')), + 'number' => max(1, min(40, (int) ($new_instance['number'] ?? 12))), + 'orderby' => $orderby, + 'style' => ($new_instance['style'] ?? '') === 'card' ? 'card' : 'mini', + 'include' => sanitize_text_field((string) ($new_instance['include'] ?? '')), + 'hide_empty' => !empty($new_instance['hide_empty']) ? 1 : 0, + ]; + } +}