Merge pull request 'feat(pages): 1:1 文档导航 + 文章归档 (zibll documentnav/archives)' (#215) from worktree-docnav-archives-1to1 into main

Reviewed-on: CYL/wordpress-theme-aksrt#215
This commit is contained in:
2026-07-18 21:55:15 +08:00
9 changed files with 1072 additions and 207 deletions
+83
View File
@@ -0,0 +1,83 @@
/**
* Archives page (zibll archives) styles.
*
* @package AKSRT
*/
.page-template-archives .content-wrap {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.page-template-archives .content-layout {
flex: 1 1 0;
min-width: 0;
}
.page-template-archives .sidebar {
flex: 0 0 300px;
max-width: 100%;
}
.archives-month-list {
list-style: none;
margin: 0;
padding: 0 8px 8px;
display: flex;
flex-wrap: wrap;
align-items: baseline;
}
.archives-month-list .author-set-left {
width: 3.2em;
flex: 0 0 3.2em;
padding: 6px 0;
font-size: 13px;
}
.archives-month-list .author-set-right {
flex: 1 1 calc(100% - 3.5em);
min-width: 0;
padding: 6px 0;
border-bottom: 1px dashed var(--main-border-color, rgba(0, 0, 0, 0.06));
}
.archives-month-list .author-set-right a {
color: inherit;
text-decoration: none;
}
.archives-month-list .author-set-right a:hover {
color: var(--focus-color, var(--color-primary));
}
.title-h-center {
position: relative;
padding-bottom: 8px;
margin: 12px 0 10px;
text-align: center;
}
.title-h-center:before {
content: '';
position: absolute;
left: 0;
right: 0;
margin: auto;
bottom: 3px;
width: 40px;
height: 3px;
border-radius: 5px;
background: var(--theme-color, var(--color-primary));
}
.ml6 {
margin-left: 6px;
}
@media (max-width: 991px) {
.page-template-archives .sidebar {
flex: 1 1 100%;
}
}
+240
View File
@@ -0,0 +1,240 @@
/**
* Document navigation page (zibll documentnav) styles.
*
* @package AKSRT
*/
.page-template-documentnav .document-nav-row {
display: flex;
flex-wrap: wrap;
gap: 16px;
margin: 0;
}
.page-template-documentnav .document-nav-aside {
flex: 0 0 32%;
max-width: 100%;
min-width: 220px;
}
.page-template-documentnav .document-nav-main {
flex: 1 1 0;
min-width: 0;
}
.document-nav .panel-group {
margin: 0;
}
.document-nav .panel {
border: none;
box-shadow: none;
border-top: 1px solid var(--main-border-color, #eee);
}
.document-nav .panel-title {
margin: 0;
font-size: 1em;
}
.document-nav .panel-toggle {
padding: 10px;
display: block;
color: inherit;
text-decoration: none;
}
.document-nav .panel-toggle:not(.collapsed) {
color: var(--focus-color, var(--color-primary));
}
.document-nav .panel-toggle .fa {
transition: transform 0.2s;
}
.document-nav .panel-toggle:not(.collapsed) .fa {
transform: rotate(90deg);
}
.document-nav .panel-collapse {
display: none;
}
.document-nav .panel-collapse.is-open,
.document-nav .panel-collapse.in {
display: block;
}
.document-nav .panel-child,
.document-nav .category {
padding-left: 15px;
}
.document-nav .cat-load {
padding: 10px;
display: block;
color: inherit;
text-decoration: none;
position: relative;
}
.document-nav > .panel-group > .cat-load {
padding-left: 2px;
}
.document-nav > .panel-group > .cat-load::before {
content: '';
width: 0.5em;
height: 0.5em;
border: 0.1em solid var(--focus-color, var(--color-primary));
border-radius: 1em;
margin: 0 0.5em;
vertical-align: 0.1em;
display: inline-block;
}
.document-nav .cat-load.active {
background: var(--main-border-color, rgba(0, 0, 0, 0.06));
color: var(--focus-color, var(--color-primary));
border-radius: 4px;
}
.document-nav .loading {
overflow: hidden;
width: 0;
opacity: 0;
transition: 0.3s;
display: inline-block;
}
.document-nav .is-loading .loading,
.document-nav .cat-load.is-loading .loading {
width: 0.7em;
opacity: 1;
margin-right: 6px;
}
.document-nav-container {
transition: 0.3s;
min-height: 120px;
}
.document-nav-container.is-loading {
opacity: 0.55;
pointer-events: none;
}
.document-search {
position: relative;
margin-bottom: 16px;
padding: 12px;
}
.document-search .line-form {
position: relative;
}
.document-search .line-form-input {
width: 100%;
border: 1px solid var(--main-border-color, #ddd);
border-radius: 8px;
padding: 10px 40px 10px 12px;
box-sizing: border-box;
background: var(--main-bg-color, #fff);
}
.document-search .abs-right {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
border: 0;
background: transparent;
cursor: pointer;
color: var(--muted-2-color, #888);
}
.document-search .search-loading {
inset: 0;
display: none;
align-items: center;
justify-content: center;
border-radius: 8px;
background: color-mix(in srgb, var(--main-bg-color, #fff) 88%, transparent);
z-index: 2;
}
.document-search.is-loading .search-loading {
display: flex;
}
.list-group {
border-radius: 8px;
overflow: hidden;
}
.list-group-item {
display: block;
padding: 12px 14px;
background: var(--main-bg-color, #fff);
border-bottom: 1px solid var(--muted-border-color, #eee);
color: var(--main-color, inherit);
text-decoration: none;
}
.list-group-item:last-child {
border-bottom: 0;
}
a.list-group-item:hover,
a.list-group-item:focus {
background: var(--body-bg-color, #f7f7f8);
color: var(--focus-color, var(--color-primary));
}
.list-group-item .pull-right {
float: right;
}
.list-group-item .badg-sm {
font-size: 12px;
opacity: 0.75;
}
.list-group-item .focus-color {
color: var(--focus-color, var(--color-primary));
}
.document-nav-container .pagenav a {
display: inline-block;
min-width: 32px;
padding: 4px 8px;
margin: 2px;
border-radius: 4px;
text-decoration: none;
color: inherit;
background: var(--surface-2, #f0f0f0);
}
.document-nav-container .pagenav a.current {
background: var(--focus-color, var(--color-primary));
color: #fff;
}
.history-search .search_keywords {
display: inline-block;
margin: 0 6px 6px 0;
padding: 2px 8px;
border-radius: 999px;
background: var(--surface-2, #f0f0f0);
color: inherit;
text-decoration: none;
font-size: 12px;
}
@media (max-width: 767px) {
.page-template-documentnav .document-nav-aside,
.page-template-documentnav .document-nav-main {
flex: 1 1 100%;
}
}
+197
View File
@@ -0,0 +1,197 @@
/**
* Document navigation AJAX (zibll page-template documentnav subset).
*
* @package AKSRT
*/
(function () {
'use strict';
function ajaxUrl() {
if (window.aksrtData && window.aksrtData.ajaxUrl) return window.aksrtData.ajaxUrl;
return '/wp-admin/admin-ajax.php';
}
function toast(msg, type) {
if (window.aksrt && typeof window.aksrt.toast === 'function') {
window.aksrt.toast(msg, type || 'info');
return;
}
// eslint-disable-next-line no-alert
window.alert(msg);
}
function qs(sel, root) {
return (root || document).querySelector(sel);
}
function qsa(sel, root) {
return Array.prototype.slice.call((root || document).querySelectorAll(sel));
}
function documentAjax(triggerEl, data, url) {
var container = qs('.document-nav-container');
var searchBox = qs('.document-search');
if (!container) return;
container.classList.add('is-loading');
if (searchBox && data && data.search) searchBox.classList.add('is-loading');
data = data || {};
data.one_cat = container.getAttribute('data-one-cat') || '';
data.action = 'aksrt_documentnav_posts';
var body = new URLSearchParams();
Object.keys(data).forEach(function (k) {
if (data[k] !== undefined && data[k] !== null) body.append(k, data[k]);
});
fetch(url || ajaxUrl(), {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
body: body.toString(),
})
.then(function (r) { return r.json(); })
.then(function (n) {
if (!n || n.error || !n.html) {
toast((n && n.msg) || '加载失败,请重试', 'danger');
}
if (n && n.html) container.innerHTML = n.html;
if (n && n.history_search) {
var hist = qs('.history-search > div');
if (hist) hist.innerHTML = n.history_search;
}
})
.catch(function () {
toast('网络错误', 'danger');
})
.finally(function () {
container.classList.remove('is-loading');
if (searchBox) searchBox.classList.remove('is-loading');
if (triggerEl && triggerEl.classList) triggerEl.classList.remove('is-loading');
});
}
function initAccordion() {
var nav = qs('.document-nav');
if (!nav) return;
nav.addEventListener('click', function (e) {
var toggle = e.target.closest('.panel-toggle[data-toggle="collapse"]');
if (!toggle) return;
e.preventDefault();
var targetSel = toggle.getAttribute('data-target');
var panel = targetSel ? qs(targetSel, nav) : null;
if (!panel) return;
var parentSel = toggle.getAttribute('data-parent');
if (parentSel) {
var parent = qs(parentSel, nav);
if (parent) {
qsa('.panel-collapse.is-open, .panel-collapse.in', parent).forEach(function (p) {
if (p !== panel) {
p.classList.remove('is-open', 'in');
var t = parent.querySelector('[data-target="#' + p.id + '"]');
if (t) t.classList.add('collapsed');
}
});
}
}
var open = panel.classList.contains('is-open') || panel.classList.contains('in');
if (open) {
panel.classList.remove('is-open', 'in');
toggle.classList.add('collapsed');
} else {
panel.classList.add('is-open', 'in');
toggle.classList.remove('collapsed');
}
});
}
function initSearch() {
var form = qs('.document-search-form');
if (!form) return;
form.addEventListener('submit', function (e) {
e.preventDefault();
var input = form.querySelector('[name="s"]');
var search = input ? String(input.value || '').trim() : '';
if (!search.length) {
toast('请输入搜索关键词', 'warning');
return;
}
if (search.length < 2) {
toast('关键词太短', 'warning');
return;
}
qsa('.cat-load').forEach(function (el) {
el.classList.remove('is-loading', 'active');
});
documentAjax(qs('.document-search'), { search: search });
});
document.addEventListener('click', function (e) {
var kw = e.target.closest('.document-search .search_keywords');
if (!kw) return;
e.preventDefault();
var text = (kw.textContent || '').trim();
var input = qs('.document-search-form [name="s"]');
if (input) {
input.value = text;
input.dispatchEvent(new Event('input', { bubbles: true }));
}
if (form) form.dispatchEvent(new Event('submit', { cancelable: true, bubbles: true }));
});
}
function initCatLoad() {
document.addEventListener('click', function (e) {
var a = e.target.closest('.cat-load');
if (!a) return;
e.preventDefault();
qsa('.cat-load').forEach(function (el) {
el.classList.remove('is-loading', 'active');
});
a.classList.add('is-loading', 'active');
var catId = a.getAttribute('data-cat-id') || a.getAttribute('cat-id') || '';
documentAjax(a, { cat_id: catId });
});
}
function initPager() {
document.addEventListener('click', function (e) {
var a = e.target.closest('.document-nav-container .pagenav a');
if (!a) return;
e.preventDefault();
var paged = a.getAttribute('data-paged') || '';
var href = a.getAttribute('href') || '';
if (paged) {
// Prefer current context from last active cat or search.
var active = qs('.cat-load.active');
var searchInput = qs('.document-search-form [name="s"]');
var data = { paged: paged };
if (active) {
data.cat_id = active.getAttribute('data-cat-id') || '';
} else if (searchInput && searchInput.value) {
data.search = searchInput.value.trim();
}
documentAjax(a, data);
return;
}
if (href && href.indexOf('admin-ajax.php') !== -1) {
documentAjax(a, {}, href);
}
});
}
function init() {
if (!qs('.document-nav-container') && !qs('.document-nav')) return;
initAccordion();
initSearch();
initCatLoad();
initPager();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
+3
View File
@@ -261,6 +261,9 @@ if (class_exists('\AKSRT\Core\PostsNavs')) {
if (class_exists('\AKSRT\User\AuthApply')) {
\AKSRT\User\AuthApply::get_instance()->init();
}
if (class_exists('\AKSRT\Core\DocumentNav')) {
\AKSRT\Core\DocumentNav::get_instance()->init();
}
if (class_exists('\AKSRT\Messages\SiteNotice')) {
\AKSRT\Messages\SiteNotice::get_instance()->init();
}
+316
View File
@@ -0,0 +1,316 @@
<?php
/**
* Document navigation (zibll documentnav) — category tree + AJAX posts.
*
* @package AKSRT
*/
namespace AKSRT\Core;
defined('ABSPATH') || exit;
class DocumentNav {
private static $instance = null;
public static function get_instance(): self {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
public function init(): void {
add_action('wp_ajax_aksrt_documentnav_posts', [$this, 'ajax_posts']);
add_action('wp_ajax_nopriv_aksrt_documentnav_posts', [$this, 'ajax_posts']);
add_action('add_meta_boxes', [$this, 'register_meta_box']);
add_action('save_post_page', [$this, 'save_meta_box']);
}
/**
* @return array{cat:int,initial_content:string}
*/
public static function get_options(int $post_id): array {
$raw = get_post_meta($post_id, 'documentnav_options', true);
if (!is_array($raw)) {
$raw = [];
}
$cat = isset($raw['cat']) ? absint($raw['cat']) : absint(get_post_meta($post_id, 'documentnav_cat', true));
$initial = isset($raw['initial_content']) ? sanitize_key((string) $raw['initial_content']) : sanitize_key((string) get_post_meta($post_id, 'documentnav_initial_content', true));
if (!in_array($initial, ['page_content', 'date_posts', 'updated_posts', 'views_posts'], true)) {
$initial = 'updated_posts';
}
return [
'cat' => $cat,
'initial_content' => $initial,
];
}
/**
* Render posts list HTML (zibll zib_get_documentnav_posts).
*/
public static function render_posts_html(\WP_Query $query): string {
if (!$query->have_posts()) {
$empty = function_exists('aksrt_get_null')
? aksrt_get_null(__('暂无相应内容', 'aksrt'), 40)
: '<p class="muted-2-color text-center">' . esc_html__('暂无相应内容', 'aksrt') . '</p>';
return '<div class="zib-widget">' . $empty . '</div>';
}
$html = '<div class="list-group main-shadow">';
while ($query->have_posts()) {
$query->the_post();
$time_ago = human_time_diff(get_the_time('U'), current_time('timestamp')) . __('前', 'aksrt');
$subtitle = (string) get_post_meta(get_the_ID(), 'subtitle', true);
if ($subtitle === '') {
$subtitle = (string) get_post_meta(get_the_ID(), 'aksrt_subtitle', true);
}
$html .= '<a class="list-group-item" href="' . esc_url(get_permalink()) . '">';
$html .= '<span class="badg badg-sm pull-right" title="' . esc_attr(get_the_time('Y-m-d H:i:s')) . '">' . esc_html($time_ago) . '</span>';
$html .= esc_html(get_the_title());
if ($subtitle !== '') {
$html .= '<span class="focus-color"> ' . esc_html($subtitle) . '</span>';
}
$html .= '</a>';
}
$html .= '</div>';
wp_reset_postdata();
return $html;
}
/**
* Simple paginate links for document nav container.
*/
public static function render_paginate(int $total, int $current, int $page_size, array $args = []): string {
$max = (int) ceil(max(0, $total) / max(1, $page_size));
if ($max <= 1) {
return '';
}
$current = max(1, min($current, $max));
$html = '<div class="pagenav notop text-center mt15">';
for ($i = 1; $i <= $max; $i++) {
$url = add_query_arg(array_merge($args, ['paged' => $i]));
$cls = $i === $current ? 'current' : '';
$html .= '<a class="' . esc_attr($cls) . '" href="' . esc_url($url) . '" data-paged="' . (int) $i . '">' . (int) $i . '</a> ';
}
$html .= '</div>';
return $html;
}
/**
* Recursive category accordion HTML.
*/
public static function render_panels(int $cat_id, string $data_parent = 'accordion', string $class = 'panel', bool &$is_on_open = true): string {
if ($cat_id <= 0) {
return '';
}
$terms = _get_term_hierarchy('category');
if (empty($terms[$cat_id]) || !is_array($terms[$cat_id])) {
return '';
}
$html = '<div class="panel-group posts-panel-group" id="' . esc_attr($data_parent) . '">';
foreach ($terms[$cat_id] as $child) {
$child = (int) $child;
if ($child === $cat_id) {
continue;
}
$cat_obj = get_category($child);
if (!$cat_obj || is_wp_error($cat_obj)) {
continue;
}
$has_kids = !empty($terms[$child]);
if ($has_kids) {
$open = $is_on_open;
$is_on_open = false;
$html .= '<div class="' . esc_attr($class) . '">';
$html .= '<h4 class="panel-title">';
$html .= '<a class="panel-toggle' . ($open ? '' : ' collapsed') . ' text-ellipsis" href="javascript:;" data-toggle="collapse" data-target="#dosc-nav-catid_' . $child . '" data-parent="#' . esc_attr($data_parent) . '">';
$html .= '<i class="fa fa-angle-right mr10" aria-hidden="true"></i>' . esc_html($cat_obj->name);
$html .= '</a></h4>';
$html .= '<div id="dosc-nav-catid_' . $child . '" class="panel-collapse' . ($open ? ' collapse in is-open' : ' collapse') . '">';
$html .= '<div class="category">';
$html .= self::render_panels($child, 'dosc-cat-nav-panel-' . $child, 'panel panel-child', $is_on_open);
$html .= '</div></div></div>';
} else {
$html .= '<a class="cat-load text-ellipsis" data-cat-id="' . $child . '" href="javascript:;" title="' . esc_attr($cat_obj->name) . '">';
$html .= '<i class="loading" aria-hidden="true"></i>' . esc_html($cat_obj->name);
$html .= '</a>';
}
}
$html .= '</div>';
return $html;
}
public function ajax_posts(): void {
$search = isset($_REQUEST['search']) ? sanitize_text_field(wp_unslash((string) $_REQUEST['search'])) : '';
$cat_id = isset($_REQUEST['cat_id']) ? absint($_REQUEST['cat_id']) : 0;
$one_cat = isset($_REQUEST['one_cat']) ? absint($_REQUEST['one_cat']) : 0;
$paged = isset($_REQUEST['paged']) ? max(1, absint($_REQUEST['paged'])) : 1;
$page_size = 12;
if ($search === '' && $cat_id <= 0) {
wp_send_json(['error' => 1, 'ys' => 'danger', 'msg' => __('参数传入错误!', 'aksrt')]);
}
$args = [
'ignore_sticky_posts' => true,
'paged' => $paged,
'posts_per_page' => $page_size,
'post_status' => 'publish',
'post_type' => 'post',
];
if ($search !== '') {
$args['s'] = $search;
$args['cat'] = $one_cat > 0 ? $one_cat : 0;
// Persist simple history (user meta or cookie-less option list is optional).
$this->remember_search($search);
} else {
$args['cat'] = $cat_id;
}
$query = new \WP_Query($args);
$html = self::render_posts_html($query);
$html .= self::render_paginate(
(int) $query->found_posts,
$paged,
$page_size,
[
'action' => 'aksrt_documentnav_posts',
'cat_id' => $cat_id,
'search' => $search,
'one_cat' => $one_cat,
]
);
wp_send_json([
'error' => 0,
'html' => $html,
'total' => (int) $query->found_posts,
'history_search' => $this->history_buttons_html(),
]);
}
private function remember_search(string $term): void {
$term = trim($term);
if ($term === '' || mb_strlen($term) < 2) {
return;
}
$key = is_user_logged_in() ? 'aksrt_doc_search_history_' . get_current_user_id() : '';
$list = [];
if ($key !== '') {
$saved = get_user_meta(get_current_user_id(), 'aksrt_doc_search_history', true);
$list = is_array($saved) ? $saved : [];
} else {
$cookie = isset($_COOKIE['aksrt_doc_search_history']) ? wp_unslash((string) $_COOKIE['aksrt_doc_search_history']) : '';
$decoded = json_decode($cookie, true);
$list = is_array($decoded) ? $decoded : [];
}
$list = array_values(array_unique(array_filter(array_map('strval', $list))));
array_unshift($list, $term);
$list = array_slice(array_values(array_unique($list)), 0, 8);
if ($key !== '') {
update_user_meta(get_current_user_id(), 'aksrt_doc_search_history', $list);
} elseif (!headers_sent()) {
setcookie('aksrt_doc_search_history', wp_json_encode($list), time() + WEEK_IN_SECONDS, COOKIEPATH ?: '/', COOKIE_DOMAIN, is_ssl(), true);
}
}
/**
* @return list<string>
*/
private function get_history(): array {
if (is_user_logged_in()) {
$saved = get_user_meta(get_current_user_id(), 'aksrt_doc_search_history', true);
return is_array($saved) ? array_values(array_filter(array_map('strval', $saved))) : [];
}
$cookie = isset($_COOKIE['aksrt_doc_search_history']) ? wp_unslash((string) $_COOKIE['aksrt_doc_search_history']) : '';
$decoded = json_decode($cookie, true);
return is_array($decoded) ? array_values(array_filter(array_map('strval', $decoded))) : [];
}
public function history_buttons_html(): string {
$items = $this->get_history();
if (!$items) {
return '';
}
$html = '';
foreach ($items as $term) {
$html .= '<a href="javascript:;" class="search_keywords but badg mr6 mb6">' . esc_html($term) . '</a>';
}
return $html;
}
public function register_meta_box(): void {
add_meta_box(
'aksrt_documentnav_meta',
__('文档导航设置', 'aksrt'),
[$this, 'render_meta_box'],
'page',
'side',
'default'
);
}
public function render_meta_box(\WP_Post $post): void {
$tpl = (string) get_post_meta($post->ID, '_wp_page_template', true);
if ($tpl !== 'templates/pages/page-documentnav.php') {
echo '<p class="description">' . esc_html__('将模板设为「AKSRT - 文档导航」后配置。', 'aksrt') . '</p>';
return;
}
$opt = self::get_options((int) $post->ID);
wp_nonce_field('aksrt_documentnav_meta', 'aksrt_documentnav_meta_nonce');
$cats = get_categories(['hide_empty' => false, 'parent' => 0]);
?>
<p>
<label for="documentnav_cat"><strong><?php esc_html_e('一级分类', 'aksrt'); ?></strong></label>
<select name="documentnav_cat" id="documentnav_cat" class="widefat">
<option value="0"><?php esc_html_e('请选择', 'aksrt'); ?></option>
<?php foreach ($cats as $c) : ?>
<option value="<?php echo (int) $c->term_id; ?>" <?php selected($opt['cat'], (int) $c->term_id); ?>>
<?php echo esc_html($c->name); ?>
</option>
<?php endforeach; ?>
</select>
</p>
<p>
<label for="documentnav_initial_content"><strong><?php esc_html_e('初始内容', 'aksrt'); ?></strong></label>
<select name="documentnav_initial_content" id="documentnav_initial_content" class="widefat">
<?php
$opts = [
'page_content' => __('显示页面内容', 'aksrt'),
'date_posts' => __('最近发布文章', 'aksrt'),
'updated_posts' => __('最近更新文章', 'aksrt'),
'views_posts' => __('查看最多文章', 'aksrt'),
];
foreach ($opts as $k => $label) :
?>
<option value="<?php echo esc_attr($k); ?>" <?php selected($opt['initial_content'], $k); ?>><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
</select>
</p>
<?php
}
public function save_meta_box(int $post_id): void {
if (!isset($_POST['aksrt_documentnav_meta_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash((string) $_POST['aksrt_documentnav_meta_nonce'])), 'aksrt_documentnav_meta')) {
return;
}
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return;
}
if (!current_user_can('edit_post', $post_id)) {
return;
}
$cat = isset($_POST['documentnav_cat']) ? absint($_POST['documentnav_cat']) : 0;
$initial = isset($_POST['documentnav_initial_content']) ? sanitize_key((string) wp_unslash($_POST['documentnav_initial_content'])) : 'updated_posts';
if (!in_array($initial, ['page_content', 'date_posts', 'updated_posts', 'views_posts'], true)) {
$initial = 'updated_posts';
}
update_post_meta($post_id, 'documentnav_options', [
'cat' => $cat,
'initial_content' => $initial,
]);
}
}
+36
View File
@@ -458,6 +458,42 @@ trait ScriptsPart1b {
}
}
// 文章归档页
if (is_page_template('templates/pages/page-archives.php')) {
$ar_css = get_template_directory() . '/assets/css/routes/archives.css';
if (file_exists($ar_css)) {
wp_enqueue_style(
'aksrt-route-archives',
$base_uri . '/assets/css/routes/archives.css',
['aksrt-tokens'],
(string) filemtime($ar_css)
);
}
}
// 文档导航页(zibll documentnav.php)
if (is_page_template('templates/pages/page-documentnav.php')) {
$dn_css = get_template_directory() . '/assets/css/routes/documentnav.css';
if (file_exists($dn_css)) {
wp_enqueue_style(
'aksrt-route-documentnav',
$base_uri . '/assets/css/routes/documentnav.css',
['aksrt-tokens'],
(string) filemtime($dn_css)
);
}
$dn_js = get_template_directory() . '/assets/js/documentnav-page.js';
if (file_exists($dn_js)) {
wp_enqueue_script(
'aksrt-documentnav-page',
$base_uri . '/assets/js/documentnav-page.js',
[],
(string) filemtime($dn_js),
true
);
}
}
// 用户身份认证页(zibll user-auth.php)
if (is_page_template('templates/pages/page-user-auth.php')) {
$ua_css = get_template_directory() . '/assets/css/routes/user-auth.css';
+1
View File
@@ -40,6 +40,7 @@ class Templates {
'templates/pages/page-profile.php' => __('AKSRT - 个人中心', 'aksrt'),
'templates/pages/page-user-home.php' => __('AKSRT - 用户主页', 'aksrt'),
'templates/pages/page-archives.php' => __('AKSRT - 文章归档', 'aksrt'),
'templates/pages/page-documentnav.php' => __('AKSRT - 文档导航', 'aksrt'),
'templates/pages/page-categories.php' => __('AKSRT - 分类汇总', 'aksrt'),
'templates/pages/page-newposts.php' => __('AKSRT - 最新文章', 'aksrt'),
// 注意:page-newposts = 最新文章列表;投稿页是 page-submit(写文章/投稿)
+98 -58
View File
@@ -1,9 +1,10 @@
<?php
/**
* Template Name: AKSRT - Archives
* Template Name: AKSRT - 文章归档
* Template Post Type: page
*
* 文章归档页:按年月分组展示全站文章。
* Zibll pages/archives.php 1:1 — month groups with view/comment/like meta,
* theme-box shell, sidebar + comments.
*
* @package AKSRT
*/
@@ -11,65 +12,104 @@
defined('ABSPATH') || exit;
get_header();
global $wpdb;
if (function_exists('aksrt_archive_open_shell')) {
aksrt_archive_open_shell([
'main_class' => 'site-main container content-shell content-shell--page page-content-default',
'has_sidebar' => false,
'page_kind' => 'archives',
]);
}
$header_style = (string) get_post_meta(get_queried_object_id(), 'page_header_style', true);
?>
<div class="content-main content-main--page content-main--archives">
<?php
get_template_part('templates/parts/page-header', null, [
'title' => esc_html__('文章归档', 'aksrt'),
]);
?>
<main class="container page-template-archives">
<div class="content-wrap">
<div class="content-layout">
<?php if ($header_style !== '1' && $header_style !== 'not') : ?>
<div class="theme-box radius8 main-bg main-shadow box-body mb20">
<h1 class="article-title"><?php echo esc_html(get_the_title() ?: __('文章归档', 'aksrt')); ?></h1>
</div>
<?php endif; ?>
<div class="aksrt-archives">
<?php
$years = $wpdb->get_results(
"SELECT DISTINCT YEAR(post_date) AS year, MONTH(post_date) AS month, COUNT(*) AS count
FROM {$wpdb->posts}
WHERE post_type = 'post' AND post_status = 'publish'
GROUP BY YEAR(post_date), MONTH(post_date)
ORDER BY year DESC, month DESC"
); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
$current_year = 0;
foreach ($years as $row) :
if ((int) $row->year !== $current_year) :
$current_year = (int) $row->year;
?>
<h2 class="aksrt-archives__year"><?php echo esc_html((string) $current_year); ?></h2>
<?php endif; ?>
<h3 class="aksrt-archives__month">
<?php echo esc_html(sprintf('%d 月 (%d 篇)', (int) $row->month, (int) $row->count)); ?>
</h3>
<ul class="aksrt-archives__list">
<?php
$month_posts = get_posts([
'year' => (int) $row->year,
'monthnum' => (int) $row->month,
'posts_per_page' => -1,
'post_status' => 'publish',
]);
foreach ($month_posts as $mp) :
?>
<li class="aksrt-archives__item">
<span class="aksrt-archives__date"><?php echo esc_html(get_the_date('m-d', $mp)); ?></span>
<a href="<?php echo esc_url(get_permalink($mp->ID)); ?>" class="aksrt-archives__link"><?php echo esc_html($mp->post_title); ?></a>
</li>
<?php endforeach; ?>
</ul>
<?php endforeach; ?>
</div>
<div class="theme-box radius8 main-bg main-shadow">
<?php if ($header_style === '1') : ?>
<div class="box-body">
<h1 class="article-title"><?php echo esc_html(get_the_title() ?: __('文章归档', 'aksrt')); ?></h1>
</div>
<?php
if (function_exists('aksrt_archive_close_shell')) {
aksrt_archive_close_shell(['has_sidebar' => false]);
}
<?php endif; ?>
<article class="box-body">
<?php
$previous_year = 0;
$previous_month = 0;
$ul_open = false;
$myposts = get_posts([
'numberposts' => -1,
'orderby' => 'date',
'order' => 'DESC',
'post_status' => 'publish',
'post_type' => 'post',
'suppress_filters' => false,
]);
foreach ($myposts as $post) :
setup_postdata($post);
$year = (int) get_the_time('Y', $post);
$month = (int) get_the_time('n', $post);
$day = (int) get_the_time('j', $post);
if ($year !== $previous_year || $month !== $previous_month) :
if ($ul_open) {
echo '</ul></div>';
}
echo '<div class="zib-widget mb20"><h4 class="text-center title-h-center">';
echo esc_html(get_the_time('Y年n月', $post));
echo '</h4><ul class="list-inline archives-month-list">';
$ul_open = true;
$previous_year = $year;
$previous_month = $month;
endif;
$views = function_exists('aksrt_get_post_views') ? (int) aksrt_get_post_views($post->ID) : 0;
$likes = (int) get_post_meta($post->ID, 'like', true);
if ($likes <= 0) {
$likes = (int) get_post_meta($post->ID, 'aksrt_like', true);
}
$comments = (int) get_comments_number($post->ID);
?>
<li class="author-set-left muted-color">
<time datetime="<?php echo esc_attr(get_the_date('c', $post)); ?>"><?php echo esc_html((string) $day); ?><?php esc_html_e('日', 'aksrt'); ?></time>
</li>
<li class="author-set-right">
<a href="<?php echo esc_url(get_permalink($post)); ?>"><?php echo esc_html(get_the_title($post)); ?></a>
<span class="muted-2-color ml6" title="<?php esc_attr_e('阅读', 'aksrt'); ?>">
<i class="fa fa-eye" aria-hidden="true"></i><?php echo esc_html((string) $views); ?>
</span>
<?php if ($comments > 0) : ?>
<span class="muted-2-color ml6" title="<?php esc_attr_e('评论', 'aksrt'); ?>">
<i class="fa fa-comment-o" aria-hidden="true"></i><?php echo esc_html((string) $comments); ?>
</span>
<?php endif; ?>
<?php if ($likes > 0) : ?>
<span class="muted-2-color ml6" title="<?php esc_attr_e('点赞', 'aksrt'); ?>">
<i class="fa fa-thumbs-o-up" aria-hidden="true"></i><?php echo esc_html((string) $likes); ?>
</span>
<?php endif; ?>
</li>
<?php
endforeach;
wp_reset_postdata();
if ($ul_open) {
echo '</ul></div>';
}
if (empty($myposts)) {
echo '<p class="muted-2-color text-center">' . esc_html__('暂无文章', 'aksrt') . '</p>';
}
?>
</article>
</div>
<?php
if (comments_open() || get_comments_number()) {
comments_template();
}
?>
</div>
</div>
<?php get_sidebar(); ?>
</main>
<?php
get_footer();
+98 -149
View File
@@ -1,167 +1,116 @@
<?php
/**
* Template Name: AKSRT - Document Navigation
* Template Name: AKSRT - 文档导航
* Template Post Type: page
*
* 文档导航页:文档站式导航。
* 支持侧边章节树、当前章节高亮、索引卡片、最近更新列表、基础搜索。
* Zibll pages/documentnav.php 1:1 — category accordion + search + AJAX posts.
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
use AKSRT\Core\DocumentNav;
$post_id = (int) get_queried_object_id();
$opt = class_exists(DocumentNav::class)
? DocumentNav::get_options($post_id)
: ['cat' => 0, 'initial_content' => 'updated_posts'];
$one_cat = (int) ($opt['cat'] ?? 0);
$initial = (string) ($opt['initial_content'] ?? 'updated_posts');
get_header();
// Read doc mode settings
$doc_settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_docs_mode_settings() : [];
$default_order = $doc_settings['default_order'] ?? 'menu_order';
$presenter = class_exists('\AKSRT\Core\DocsPresenter') ? \AKSRT\Core\DocsPresenter::get_instance() : null;
// Get root-level pages
$pages = $presenter
? $presenter->get_root_pages($default_order)
: get_pages([
'sort_column' => $default_order === 'title' ? 'post_title' : ($default_order === 'date' ? 'post_date' : 'menu_order, post_title'),
'sort_order' => 'ASC',
'parent' => 0,
'hierarchical' => 0,
'post_status' => 'publish',
]);
// Recent updates
$recent_pages = $presenter
? $presenter->get_recent_pages(8)
: get_pages([
'sort_column' => 'post_modified',
'sort_order' => 'DESC',
'number' => 8,
'post_status' => 'publish',
]);
$show_sidebar = !empty($doc_settings['toc_enabled']) || !empty($recent_pages);
?>
<main class="container page-template-documentnav">
<div class="content-wrap">
<div class="content-layout">
<div class="theme-box radius8 main-bg main-shadow box-body mb20">
<h1 class="article-title"><?php echo esc_html(get_the_title() ?: __('文档导航', 'aksrt')); ?></h1>
</div>
<main id="main-content" class="container page-content-default" tabindex="-1">
<div class="row document-nav-row">
<div class="col-sm-4 document-nav-aside">
<div class="theme-box zib-widget dosc-nav document-nav">
<?php
if ($one_cat > 0 && class_exists(DocumentNav::class)) {
$open = true;
echo DocumentNav::render_panels($one_cat, 'accordion', 'panel', $open); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
} elseif (current_user_can('edit_pages')) {
echo '<p class="muted-2-color box-body">' . esc_html__('请在页面设置中选择一级分类。', 'aksrt') . '</p>';
} else {
echo '<p class="muted-2-color box-body">' . esc_html__('暂无文档分类', 'aksrt') . '</p>';
}
?>
</div>
</div>
<div class="col-sm-8 document-nav-main" role="main">
<div class="theme-box zib-widget document-search relative-h">
<form class="document-search-form" action="javascript:;" method="get">
<div class="line-form">
<input type="search" name="s" class="line-form-input document-search-input" placeholder="<?php esc_attr_e('搜索文档…', 'aksrt'); ?>" autocomplete="off" required minlength="2">
<button type="submit" class="abs-right null" aria-label="<?php esc_attr_e('搜索', 'aksrt'); ?>">
<i class="fa fa-search" aria-hidden="true"></i>
</button>
</div>
</form>
<div class="history-search mt10">
<div>
<?php
if (class_exists(DocumentNav::class)) {
echo DocumentNav::get_instance()->history_buttons_html(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
?>
</div>
</div>
<div class="search-loading absolute main-bg flex jc" hidden>
<i class="loading mr10 em12" aria-hidden="true"></i> <?php esc_html_e('正在搜索', 'aksrt'); ?>
</div>
</div>
<header class="aksrt-doc-nav-header">
<h1 class="aksrt-doc-nav-header__title"><?php echo esc_html(get_the_title() ?: __('文档导航', 'aksrt')); ?></h1>
<?php $desc = get_the_excerpt(); if ($desc) : ?>
<p class="aksrt-doc-nav-header__desc"><?php echo esc_html($desc); ?></p>
<?php endif; ?>
</header>
<div class="document-nav-container" data-one-cat="<?php echo (int) $one_cat; ?>">
<?php
if ($initial === 'page_content') {
while (have_posts()) {
the_post();
echo '<div class="nopw-sm box-body theme-box radius8 main-bg main-shadow">';
echo '<article class="article wp-posts-content">';
the_content();
echo '</article></div>';
}
} elseif ($one_cat > 0 && class_exists(DocumentNav::class)) {
$args = [
'cat' => $one_cat,
'order' => 'DESC',
'orderby' => 'modified',
'posts_per_page' => 12,
'no_found_rows' => true,
'ignore_sticky_posts' => true,
'post_status' => 'publish',
];
if ($initial === 'date_posts') {
$args['orderby'] = 'date';
} elseif ($initial === 'views_posts') {
$args['orderby'] = 'meta_value_num';
$args['meta_key'] = 'aksrt_post_views';
}
$q = new WP_Query($args);
echo DocumentNav::render_posts_html($q); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
} else {
echo '<div class="zib-widget"><p class="muted-2-color text-center">' . esc_html__('请配置文档分类', 'aksrt') . '</p></div>';
}
?>
</div>
</div>
</div>
<div class="aksrt-doc-nav container" data-aksrt-doc-page-id="<?php echo (int) get_the_ID(); ?>">
<div class="content-wrap">
<div class="content-layout<?php echo $show_sidebar && !empty($recent_pages) ? '' : ' content-layout--no-sidebar'; ?>">
<div class="content-layout__primary">
<!-- Quick search -->
<div class="aksrt-doc-nav__search">
<input type="text" class="aksrt-doc-nav__search-input" placeholder="<?php esc_attr_e('搜索文档...', 'aksrt'); ?>" id="aksrt-doc-search">
<i class="fas fa-search aksrt-doc-nav__search-icon"></i>
</div>
<div class="aksrt-doc-search__results" data-aksrt-doc-search-results hidden></div>
<?php if (!empty($pages)) : ?>
<!-- Toolbar: category chips -->
<div class="aksrt-doc-toolbar">
<div class="aksrt-doc-toolbar__chips">
<button type="button" class="aksrt-doc-toolbar__chip is-active" data-cat-id="0"><?php esc_html_e('全部', 'aksrt'); ?></button>
<?php foreach ($pages as $p) : ?>
<button type="button" class="aksrt-doc-toolbar__chip" data-cat-id="<?php echo (int) $p->ID; ?>"><?php echo esc_html($p->post_title); ?></button>
<?php endforeach; ?>
</div>
<span class="aksrt-doc-toolbar__meta"><?php printf(esc_html__('%d 篇文档', 'aksrt'), count($pages)); ?></span>
</div>
<?php endif; ?>
<div class="aksrt-doc-nav__layout">
<!-- Document index cards -->
<div class="aksrt-doc-nav__main">
<?php if (!empty($pages)) : ?>
<div class="aksrt-doc-nav__grid">
<?php foreach ($pages as $p) :
$children = $presenter
? $presenter->get_flat_tree($p->ID, 1)
: get_pages([
'child_of' => $p->ID,
'sort_column' => 'menu_order',
'sort_order' => 'ASC',
'parent' => $p->ID,
'post_status' => 'publish',
]);
$child_count = count($children);
$thumb = get_the_post_thumbnail_url($p->ID, 'medium');
$excerpt = wp_trim_words($p->post_excerpt, 30, '...');
?>
<a href="<?php echo esc_url(get_permalink($p->ID)); ?>" class="aksrt-doc-index-card" data-cat-id="<?php echo (int) $p->ID; ?>">
<?php if ($thumb) : ?>
<div class="aksrt-doc-index-card__thumb">
<img src="<?php echo esc_url($thumb); ?>" alt=""<?php echo function_exists('aksrt_lazy_loading') ? aksrt_lazy_loading('other') : ' loading="lazy"'; ?>>
</div>
<?php else : ?>
<div class="aksrt-doc-index-card__icon">
<i class="fas fa-book-open"></i>
</div>
<?php endif; ?>
<div class="aksrt-doc-index-card__body">
<h3 class="aksrt-doc-index-card__title"><?php echo esc_html($p->post_title); ?></h3>
<?php if ($excerpt) : ?>
<p class="aksrt-doc-index-card__desc"><?php echo esc_html($excerpt); ?></p>
<?php endif; ?>
<div class="aksrt-doc-index-card__meta">
<?php if ($child_count > 0) : ?>
<span class="aksrt-doc-index-card__count">
<i class="fas fa-file-alt"></i>
<?php printf(esc_html__('%d 篇文章', 'aksrt'), $child_count); ?>
</span>
<?php endif; ?>
<span class="aksrt-doc-index-card__date">
<i class="fas fa-clock"></i>
<?php echo esc_html(get_the_date('', $p->ID)); ?>
</span>
</div>
</div>
<i class="fas fa-chevron-right aksrt-doc-index-card__arrow"></i>
</a>
<?php endforeach; ?>
</div>
<?php else : ?>
<div class="aksrt-doc-nav__empty">
<i class="fas fa-book-open"></i>
<p><?php esc_html_e('暂无文档页面。', 'aksrt'); ?></p>
</div>
<?php endif; ?>
</div>
<!-- Sidebar: Recent updates -->
<?php if ($show_sidebar && !empty($recent_pages)) : ?>
<aside class="aksrt-doc-nav__sidebar sidebar">
<div class="aksrt-doc-nav__recent">
<h3 class="aksrt-doc-nav__recent-title">
<i class="fas fa-clock"></i>
<?php esc_html_e('最近更新', 'aksrt'); ?>
</h3>
<ul class="aksrt-doc-nav__recent-list">
<?php foreach ($recent_pages as $rp) : ?>
<li class="aksrt-doc-nav__recent-item">
<a href="<?php echo esc_url(get_permalink($rp->ID)); ?>">
<span class="aksrt-doc-nav__recent-name"><?php echo esc_html($rp->post_title); ?></span>
<time class="aksrt-doc-nav__recent-date"><?php echo esc_html(get_the_date('', $rp->ID)); ?></time>
</a>
</li>
<?php endforeach; ?>
</ul>
</div>
</aside>
<?php endif; ?>
</div>
</div>
</div>
<?php
if (comments_open() || get_comments_number()) {
comments_template();
}
?>
</div>
</div>
<?php get_sidebar(); ?>
</main>
<?php
get_footer();