feat(forum): detail/mini topic list item 1:1 structure
Expand render_topic_row into zibll-like detail cards (author card, cover, excerpt, multi-image grid, vote strip, plate + action-meta) and keep mini compact variant for dense lists.
This commit is contained in:
@@ -586,6 +586,104 @@
|
||||
text-shadow: 0 1px 2px rgba(0,0,0,.35);
|
||||
}
|
||||
|
||||
/* detail list card (zibll forum-posts detail) */
|
||||
.aksrt-forum-topic-row.detail,
|
||||
.aksrt-forum-posts.detail {
|
||||
display: block;
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
border-radius: 14px;
|
||||
background: var(--surface-1, #fff);
|
||||
box-shadow: var(--shadow-sm, 0 1px 4px rgba(0,0,0,.06));
|
||||
}
|
||||
.aksrt-forum-topic-row__author-card { gap: 10px; }
|
||||
.aksrt-forum-topic-row__author-name { font-weight: 700; font-size: 14px; }
|
||||
.aksrt-forum-topic-row.detail .aksrt-forum-topic-row__cover,
|
||||
.aksrt-forum-posts.detail .forum-lists-cover {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: 280px;
|
||||
margin: 0 0 12px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.aksrt-forum-topic-row.detail .aksrt-forum-topic-row__cover img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: 280px;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
.aksrt-forum-topic-row .excerpt {
|
||||
margin-top: 6px;
|
||||
font-size: 14px;
|
||||
line-height: 1.65;
|
||||
color: var(--color-ink-2, #4b5563);
|
||||
}
|
||||
.aksrt-forum-topic-row .lists-imgs {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.aksrt-forum-topic-row .lists-imgs.count-1 { grid-template-columns: 1fr; max-width: 280px; }
|
||||
.aksrt-forum-topic-row .lists-imgs.count-2 { grid-template-columns: 1fr 1fr; }
|
||||
.aksrt-forum-topic-row .lists-imgs.count-3,
|
||||
.aksrt-forum-topic-row .lists-imgs.count-4 { grid-template-columns: 1fr 1fr; }
|
||||
.aksrt-forum-topic-row .lists-imgs__item {
|
||||
display: block;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
aspect-ratio: 1;
|
||||
background: var(--surface-2, #f3f4f6);
|
||||
}
|
||||
.aksrt-forum-topic-row .lists-imgs__item img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
.aksrt-forum-topic-row__vote { display: grid; gap: 6px; }
|
||||
.aksrt-forum-topic-row__vote-opt {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(80px, 2fr) 42px;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
.aksrt-forum-topic-row__vote-bar {
|
||||
height: 8px;
|
||||
border-radius: 999px;
|
||||
background: var(--surface-4, #eef0f3);
|
||||
overflow: hidden;
|
||||
}
|
||||
.aksrt-forum-topic-row__vote-bar i {
|
||||
display: block;
|
||||
height: 100%;
|
||||
background: var(--color-primary, #f04494);
|
||||
}
|
||||
.aksrt-forum-topic-row__footer .but-plate,
|
||||
.aksrt-forum-topic-row__board.but {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
max-width: 46%;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
background: var(--surface-4, #f3f4f6);
|
||||
color: var(--color-ink-2, #5c6370);
|
||||
text-decoration: none;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.aksrt-forum-topic-row__actions {
|
||||
display: inline-flex;
|
||||
gap: 12px;
|
||||
color: var(--color-ink-3, #8a909c);
|
||||
font-size: 13px;
|
||||
}
|
||||
.aksrt-forum-topic-row__actions a { color: inherit; text-decoration: none; }
|
||||
.aksrt-forum-topic-row.mini { display: flex; gap: 12px; align-items: flex-start; }
|
||||
|
||||
.aksrt-forum-board-card__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
|
||||
@@ -250,8 +250,8 @@ class Forum {
|
||||
$this->view_builder()->render_board_cards($boards);
|
||||
}
|
||||
|
||||
public function render_topic_row(array $topic): void {
|
||||
$this->view_builder()->render_topic_row($topic);
|
||||
public function render_topic_row(array $topic, string $style = 'detail'): void {
|
||||
$this->view_builder()->render_topic_row($topic, $style);
|
||||
}
|
||||
|
||||
public function get_board_view(int $board_id, array $args = []): array {
|
||||
|
||||
+181
-37
@@ -435,58 +435,97 @@ class ViewBuilder {
|
||||
|
||||
/**
|
||||
* Render a single topic row (reusable for AJAX and initial render).
|
||||
*
|
||||
* @param array $topic Topic payload.
|
||||
* @param string $style detail|mini (zibll forum-posts parity).
|
||||
*/
|
||||
public function render_topic_row(array $topic): void {
|
||||
public function render_topic_row(array $topic, string $style = 'detail'): void {
|
||||
$style = $style === 'mini' ? 'mini' : 'detail';
|
||||
$board = !empty($topic['boards'][0]) ? $topic['boards'][0] : null;
|
||||
$names = $this->forum->get_config()['names'] ?? [];
|
||||
$sticky_level = (int) ($topic['sticky_level'] ?? (!empty($topic['sticky']) ? 1 : 0));
|
||||
$row_class = 'aksrt-forum-topic-row';
|
||||
$permalink = (string) ($topic['permalink'] ?? '#');
|
||||
$cover = is_array($topic['cover'] ?? null) ? $topic['cover'] : null;
|
||||
$vote = is_array($topic['vote'] ?? null) ? $topic['vote'] : null;
|
||||
$excerpt = trim((string) ($topic['excerpt'] ?? ''));
|
||||
$topic_cat = is_array($topic['topic_cat'] ?? null) ? $topic['topic_cat'] : null;
|
||||
|
||||
$row_class = 'aksrt-forum-topic-row aksrt-forum-posts forum-posts ajax-item ' . $style;
|
||||
if ($sticky_level > 0) {
|
||||
$row_class .= ' is-sticky is-sticky-' . $sticky_level;
|
||||
}
|
||||
if (!empty($topic['featured'])) {
|
||||
$row_class .= ' is-featured';
|
||||
}
|
||||
$cover = $topic['cover'] ?? null;
|
||||
|
||||
$badges = '';
|
||||
if ($sticky_level > 0) {
|
||||
$badges .= '<span class="aksrt-forum-topic-row__badge aksrt-forum-topic-row__badge--sticky badge-topping">'
|
||||
. esc_html((string) ($topic['sticky_label'] ?? __('置顶', 'aksrt'))) . '</span>';
|
||||
}
|
||||
if (!empty($topic['featured'])) {
|
||||
$badges .= '<span class="aksrt-forum-topic-row__badge aksrt-forum-topic-row__badge--featured badge-essence">'
|
||||
. esc_html__('精华', 'aksrt') . '</span>';
|
||||
}
|
||||
if (($topic['bbs_type'] ?? '') === 'question') {
|
||||
$q_status = (int) ($topic['question_status'] ?? 0);
|
||||
$badges .= '<span class="aksrt-forum-topic-row__badge aksrt-forum-topic-row__badge--question'
|
||||
. ($q_status === 1 ? ' is-solved' : '') . '">'
|
||||
. esc_html($q_status === 1
|
||||
? (string) ($names['question_ok_badge'] ?? __('已解决', 'aksrt'))
|
||||
: (string) ($names['question_badge'] ?? __('提问', 'aksrt')))
|
||||
. '</span>';
|
||||
}
|
||||
|
||||
$title_html = $badges
|
||||
. '<h3 class="aksrt-forum-topic-row__title forum-title">'
|
||||
. '<a href="' . esc_url($permalink) . '">' . esc_html((string) ($topic['title'] ?? '')) . '</a></h3>';
|
||||
|
||||
if ($style === 'mini') {
|
||||
?>
|
||||
<article class="<?php echo esc_attr($row_class); ?>" data-topic-id="<?php echo esc_attr((string) ($topic['id'] ?? 0)); ?>">
|
||||
<div class="aksrt-forum-topic-row__avatar forum-user">
|
||||
<?php if (!empty($topic['author']['avatar_url'])) : ?>
|
||||
<img src="<?php echo esc_url((string) $topic['author']['avatar_url']); ?>" alt="" width="40" height="40" loading="lazy" class="forum-avatar">
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="aksrt-forum-topic-row__body entry-info">
|
||||
<div class="aksrt-forum-topic-row__title-wrap"><?php echo $title_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
|
||||
<div class="aksrt-forum-topic-row__meta item-meta flex ac jsb">
|
||||
<div class="meta-left">
|
||||
<span class="aksrt-forum-topic-row__author"><?php echo esc_html((string) ($topic['author']['display_name'] ?? '')); ?></span>
|
||||
<span class="aksrt-forum-topic-row__time icon-circle"><?php echo esc_html((string) ($topic['date'] ?? '')); ?></span>
|
||||
</div>
|
||||
<div class="meta-right action-meta">
|
||||
<span title="<?php esc_attr_e('浏览', 'aksrt'); ?>"><i class="fas fa-eye"></i> <?php echo esc_html(number_format_i18n((int) ($topic['views'] ?? 0))); ?></span>
|
||||
<span title="<?php esc_attr_e('回复', 'aksrt'); ?>"><i class="fas fa-comment"></i> <?php echo esc_html(number_format_i18n((int) ($topic['comment_count'] ?? 0))); ?></span>
|
||||
<span title="<?php esc_attr_e('评分', 'aksrt'); ?>"><i class="fas fa-thumbs-up"></i> <?php echo esc_html(number_format_i18n((int) ($topic['score'] ?? 0))); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<?php
|
||||
return;
|
||||
}
|
||||
|
||||
// detail
|
||||
$list_imgs = $this->extract_topic_list_images($topic, 4);
|
||||
?>
|
||||
<article class="<?php echo esc_attr($row_class); ?>">
|
||||
<div class="aksrt-forum-topic-row__avatar">
|
||||
<?php if (!empty($topic['author']['avatar_url'])) : ?>
|
||||
<img src="<?php echo esc_url((string) $topic['author']['avatar_url']); ?>" alt="" width="40" height="40" loading="lazy">
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="aksrt-forum-topic-row__body">
|
||||
<div class="aksrt-forum-topic-row__title-wrap">
|
||||
<?php if ($sticky_level > 0) : ?>
|
||||
<span class="aksrt-forum-topic-row__badge aksrt-forum-topic-row__badge--sticky"><?php echo esc_html((string) ($topic['sticky_label'] ?? __('置顶', 'aksrt'))); ?></span>
|
||||
<article class="<?php echo esc_attr($row_class); ?>" data-topic-id="<?php echo esc_attr((string) ($topic['id'] ?? 0)); ?>">
|
||||
<div class="aksrt-forum-topic-row__author-card mb10 flex ac">
|
||||
<div class="aksrt-forum-topic-row__avatar">
|
||||
<?php if (!empty($topic['author']['avatar_url'])) : ?>
|
||||
<img src="<?php echo esc_url((string) $topic['author']['avatar_url']); ?>" alt="" width="44" height="44" loading="lazy">
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($topic['featured'])) : ?>
|
||||
<span class="aksrt-forum-topic-row__badge aksrt-forum-topic-row__badge--featured"><?php esc_html_e('精华', 'aksrt'); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if (($topic['bbs_type'] ?? '') === 'question') : ?>
|
||||
<?php $q_status = (int) ($topic['question_status'] ?? 0); ?>
|
||||
<span class="aksrt-forum-topic-row__badge aksrt-forum-topic-row__badge--question<?php echo $q_status === 1 ? ' is-solved' : ''; ?>">
|
||||
<?php echo $q_status === 1
|
||||
? esc_html($names['question_ok_badge'] ?? __('已解决', 'aksrt'))
|
||||
: esc_html($names['question_badge'] ?? __('提问', 'aksrt')); ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<h3 class="aksrt-forum-topic-row__title">
|
||||
<a href="<?php echo esc_url((string) ($topic['permalink'] ?? '#')); ?>"><?php echo esc_html((string) ($topic['title'] ?? '')); ?></a>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="aksrt-forum-topic-row__meta">
|
||||
<?php if ($board) : ?>
|
||||
<a class="aksrt-forum-topic-row__board" href="<?php echo esc_url((string) ($board['url'] ?? '#')); ?>"><?php echo esc_html((string) ($board['name'] ?? '')); ?></a>
|
||||
<?php endif; ?>
|
||||
<span class="aksrt-forum-topic-row__author"><?php echo esc_html((string) ($topic['author']['display_name'] ?? '')); ?></span>
|
||||
<span class="aksrt-forum-topic-row__time"><?php echo esc_html((string) ($topic['date'] ?? '')); ?></span>
|
||||
<span><i class="fas fa-eye"></i> <?php echo esc_html(number_format_i18n((int) ($topic['views'] ?? 0))); ?></span>
|
||||
<span><i class="fas fa-comment"></i> <?php echo esc_html(number_format_i18n((int) ($topic['comment_count'] ?? 0))); ?></span>
|
||||
<div class="aksrt-forum-topic-row__author-meta">
|
||||
<div class="aksrt-forum-topic-row__author-name"><?php echo esc_html((string) ($topic['author']['display_name'] ?? '')); ?></div>
|
||||
<div class="aksrt-forum-topic-row__time muted-2-color em09"><?php echo esc_html((string) ($topic['date'] ?? '')); ?></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (is_array($cover) && !empty($cover['type'])) : ?>
|
||||
<div class="aksrt-forum-topic-row__cover">
|
||||
<a class="aksrt-forum-topic-row__cover forum-lists-cover" href="<?php echo esc_url($permalink); ?>">
|
||||
<?php if (($cover['type'] ?? '') === 'video') : ?>
|
||||
<?php if (!empty($cover['poster'])) : ?>
|
||||
<img src="<?php echo esc_url((string) $cover['poster']); ?>" alt="" loading="lazy">
|
||||
@@ -498,12 +537,117 @@ class ViewBuilder {
|
||||
<?php elseif (!empty($cover['url'])) : ?>
|
||||
<img src="<?php echo esc_url((string) $cover['url']); ?>" alt="" loading="lazy">
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="aksrt-forum-topic-row__body">
|
||||
<div class="aksrt-forum-topic-row__title-wrap"><?php echo $title_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
|
||||
|
||||
<div class="content mt6">
|
||||
<?php if ($topic_cat && !empty($topic_cat['name'])) : ?>
|
||||
<a class="aksrt-forum-topic-row__topic-link focus-color mr3" href="<?php echo esc_url((string) ($topic_cat['url'] ?? '#')); ?>">
|
||||
<i class="fas fa-hashtag"></i><?php echo esc_html((string) $topic_cat['name']); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php if ($excerpt !== '') : ?>
|
||||
<div class="excerpt"><?php echo esc_html($excerpt); ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($list_imgs) && empty($cover)) : ?>
|
||||
<div class="imgbox-container lists-imgs mt6 count-<?php echo esc_attr((string) count($list_imgs)); ?>">
|
||||
<?php foreach ($list_imgs as $img_url) : ?>
|
||||
<a class="lists-imgs__item cursor-zoom-in" href="<?php echo esc_url($permalink); ?>">
|
||||
<img src="<?php echo esc_url($img_url); ?>" alt="" loading="lazy">
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($vote['enabled']) && !empty($vote['options']) && is_array($vote['options'])) : ?>
|
||||
<div class="aksrt-forum-topic-row__vote mt10">
|
||||
<?php
|
||||
$total_votes = 0;
|
||||
foreach ($vote['options'] as $opt) {
|
||||
$total_votes += (int) ($opt['count'] ?? 0);
|
||||
}
|
||||
foreach (array_slice($vote['options'], 0, 4) as $opt) :
|
||||
$cnt = (int) ($opt['count'] ?? 0);
|
||||
$pct = $total_votes > 0 ? round($cnt * 100 / $total_votes) : 0;
|
||||
?>
|
||||
<div class="aksrt-forum-topic-row__vote-opt">
|
||||
<span class="aksrt-forum-topic-row__vote-label"><?php echo esc_html((string) ($opt['label'] ?? $opt['text'] ?? '')); ?></span>
|
||||
<span class="aksrt-forum-topic-row__vote-bar"><i style="width:<?php echo esc_attr((string) $pct); ?>%"></i></span>
|
||||
<span class="aksrt-forum-topic-row__vote-pct"><?php echo esc_html((string) $pct); ?>%</span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="flex ac jsb mt10 aksrt-forum-topic-row__footer">
|
||||
<?php if ($board) : ?>
|
||||
<a class="but but-plate aksrt-forum-topic-row__board" href="<?php echo esc_url((string) ($board['url'] ?? '#')); ?>">
|
||||
<?php echo esc_html((string) ($board['name'] ?? '')); ?>
|
||||
<i class="fa fa-angle-right ml6"></i>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<span class="plate-null"></span>
|
||||
<?php endif; ?>
|
||||
<div class="action-meta flex0 flex jsb aksrt-forum-topic-row__actions">
|
||||
<span title="<?php esc_attr_e('评分', 'aksrt'); ?>"><i class="fas fa-thumbs-up"></i> <?php echo esc_html(number_format_i18n((int) ($topic['score'] ?? 0))); ?></span>
|
||||
<a href="<?php echo esc_url($permalink); ?>#comments" title="<?php esc_attr_e('回复', 'aksrt'); ?>"><i class="fas fa-comment"></i> <?php echo esc_html(number_format_i18n((int) ($topic['comment_count'] ?? 0))); ?></a>
|
||||
<span title="<?php esc_attr_e('浏览', 'aksrt'); ?>"><i class="fas fa-eye"></i> <?php echo esc_html(number_format_i18n((int) ($topic['views'] ?? 0))); ?></span>
|
||||
<span title="<?php esc_attr_e('收藏', 'aksrt'); ?>"><i class="fas fa-star"></i> <?php echo esc_html(number_format_i18n((int) ($topic['favorite_count'] ?? 0))); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull up to N image URLs from cover slides or content HTML for list grids.
|
||||
*
|
||||
* @return array<int,string>
|
||||
*/
|
||||
private function extract_topic_list_images(array $topic, int $max = 4): array {
|
||||
$max = max(1, min(6, $max));
|
||||
$out = [];
|
||||
$cover = is_array($topic['cover'] ?? null) ? $topic['cover'] : null;
|
||||
if ($cover) {
|
||||
if (($cover['type'] ?? '') === 'slide' && !empty($cover['images']) && is_array($cover['images'])) {
|
||||
foreach ($cover['images'] as $u) {
|
||||
$u = esc_url_raw((string) $u);
|
||||
if ($u !== '') {
|
||||
$out[] = $u;
|
||||
}
|
||||
if (count($out) >= $max) {
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
} elseif (!empty($cover['url'])) {
|
||||
$out[] = esc_url_raw((string) $cover['url']);
|
||||
}
|
||||
}
|
||||
if (count($out) >= $max) {
|
||||
return $out;
|
||||
}
|
||||
$content = (string) ($topic['content'] ?? '');
|
||||
if ($content !== '' && preg_match_all('/<img[^>]+src=["\']([^"\']+)["\']/i', $content, $m)) {
|
||||
foreach ($m[1] as $src) {
|
||||
$src = esc_url_raw((string) $src);
|
||||
if ($src === '' || in_array($src, $out, true)) {
|
||||
continue;
|
||||
}
|
||||
$out[] = $src;
|
||||
if (count($out) >= $max) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function get_user_forum_topics(int $user_id, array $args = []): array {
|
||||
$status = $args['status'] ?? 'publish';
|
||||
$page = max(1, (int) ($args['page'] ?? 1));
|
||||
|
||||
Reference in New Issue
Block a user