diff --git a/assets/css/routes/posts-navs.css b/assets/css/routes/posts-navs.css
new file mode 100644
index 00000000..fc601cb8
--- /dev/null
+++ b/assets/css/routes/posts-navs.css
@@ -0,0 +1,261 @@
+/**
+ * AKSRT 文章导航页 — zibll postsnavs shell + admin settings.
+ *
+ * @package AKSRT
+ */
+
+/* header card */
+.page-template-posts-navs .content-wrap,
+main.page-template-posts-navs {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 20px;
+}
+
+main.page-template-posts-navs .content-wrap {
+ flex: 1 1 0;
+ min-width: 0;
+}
+
+main.page-template-posts-navs .sidebar {
+ flex: 0 0 260px;
+ max-width: 100%;
+}
+
+.posts-navs .title-theme h2 {
+ margin: 0;
+ font-size: 1.15em;
+}
+
+.posts-navs__list {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.posts-navs__item {
+ margin: 0 0 10px;
+ padding: 0;
+}
+
+.posts-navs__item .text-ellipsis {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.posts-navs__item .icon-circle {
+ display: inline-block;
+ width: 4px;
+ height: 4px;
+ border-radius: 50%;
+ background: var(--muted-3-color, #ccc);
+ margin: 0 0.55em;
+ vertical-align: middle;
+}
+
+.posts-navs__item a {
+ color: var(--color-ink, inherit);
+ text-decoration: none;
+}
+
+.posts-navs__item a:hover {
+ color: var(--focus-color, var(--color-primary));
+}
+
+.posts-navs .theme-box {
+ margin-bottom: 20px;
+}
+
+.posts-navs .box-body.notop {
+ padding-top: 0;
+}
+
+/* TOC sidebar filled by section-navs.js */
+.posts-nav-box .posts-nav-lists,
+.posts-nav-box .aksrt-section-nav {
+ max-height: 400px;
+ padding-left: 10px;
+ overflow-y: auto;
+}
+
+.posts-nav-box .zib-widget {
+ background: var(--main-bg-color, var(--surface-1, #fff));
+ border-radius: 8px;
+ box-shadow: 0 1px 4px var(--main-shadow, rgba(0, 0, 0, 0.06));
+ padding: 10px;
+}
+
+.posts-nav-lists .bl {
+ border-left: 1px solid var(--main-border-color, #eee);
+ padding: 12px 10px 12px 0;
+ list-style: none;
+ margin: 0;
+ position: relative;
+}
+
+.posts-nav-lists li {
+ position: relative;
+ list-style: none;
+}
+
+.posts-nav-lists li a {
+ margin: 0 10px;
+ display: block;
+ border-radius: 4px;
+ padding: 8px;
+ margin-right: 20px;
+ color: var(--muted-2-color, #666);
+ text-decoration: none;
+}
+
+.posts-nav-lists li a:hover,
+.posts-nav-lists li.active a {
+ background: var(--main-shadow, rgba(0, 0, 0, 0.04));
+ color: var(--focus-color, var(--color-primary));
+}
+
+.posts-nav-lists li.n-H1 a,
+.posts-nav-lists li.n-H2 a {
+ font-weight: 700;
+}
+
+/* settings modal */
+#post-navs-sz.modal {
+ position: fixed;
+ inset: 0;
+ z-index: 1050;
+ display: none;
+ align-items: center;
+ justify-content: center;
+ background: rgba(0, 0, 0, 0.45);
+ padding: 16px;
+}
+
+#post-navs-sz.modal.is-open,
+#post-navs-sz.modal.show {
+ display: flex;
+}
+
+#post-navs-sz .modal-dialog {
+ width: min(640px, 100%);
+ max-height: 90vh;
+ overflow: auto;
+}
+
+#post-navs-sz .modal-content {
+ background: var(--main-bg-color, #fff);
+ border-radius: 10px;
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.18);
+}
+
+#post-navs-sz .modal-header,
+#post-navs-sz .modal-body,
+#post-navs-sz .modal-footer {
+ padding: 16px 20px;
+}
+
+#post-navs-sz .modal-header {
+ border-bottom: 1px solid var(--main-border-color, #eee);
+}
+
+#post-navs-sz .modal-footer {
+ border-top: 1px solid var(--main-border-color, #eee);
+ text-align: right;
+}
+
+#post-navs-sz .close {
+ float: right;
+ border: 0;
+ background: transparent;
+ cursor: pointer;
+}
+
+#post-navs-sz .form-control {
+ width: 100%;
+ padding: 8px 10px;
+ border: 1px solid var(--main-border-color, #ddd);
+ border-radius: 6px;
+ box-sizing: border-box;
+}
+
+#post-navs-sz .form-group {
+ margin-bottom: 14px;
+}
+
+#post-navs-sz .row {
+ display: flex;
+ flex-wrap: wrap;
+ margin: 0 -8px;
+}
+
+#post-navs-sz .col-xs-6,
+#post-navs-sz .col-sm-6 {
+ width: 50%;
+ padding: 0 8px;
+ box-sizing: border-box;
+}
+
+.cat-group {
+ height: 200px;
+ background: var(--body-bg-color, var(--surface-2, #f6f7f9));
+ border: 1px solid var(--main-border-color, #ddd);
+ border-radius: 4px;
+ overflow: hidden;
+ padding: 5px;
+ overflow-y: auto;
+}
+
+.sortable-cat-item {
+ background: var(--main-border-color, #e8e8e8);
+ padding: 4px 8px;
+ transition: 0.2s;
+ border-radius: 4px;
+ margin: 2px;
+ display: inline-block;
+ cursor: grab;
+ width: calc(100% - 15px);
+ min-width: 100px;
+ box-sizing: border-box;
+ user-select: none;
+}
+
+.sortable-cat-item .badge {
+ margin-left: 4px;
+ background: rgba(0, 0, 0, 0.08);
+ color: inherit;
+ border-radius: 10px;
+ padding: 0 6px;
+ font-size: 12px;
+}
+
+#cat-sortable-ok .sortable-cat-item {
+ background: rgba(73, 214, 253, 0.12);
+ color: #069cf3;
+}
+
+.sortable-cat-item.is-dragging {
+ opacity: 0.5;
+}
+
+.sortable-cat-item.sortable-selected {
+ background: rgba(251, 82, 82, 0.1) !important;
+ color: #fb5454 !important;
+}
+
+.cat-group.is-drop-target {
+ outline: 2px dashed var(--focus-color, #f04494);
+}
+
+@media (max-width: 991px) {
+ main.page-template-posts-navs .sidebar {
+ flex: 1 1 100%;
+ order: -1;
+ }
+
+ #post-navs-sz .col-xs-6,
+ #post-navs-sz .col-sm-6 {
+ width: 100%;
+ margin-bottom: 12px;
+ }
+}
diff --git a/assets/js/posts-navs-page.js b/assets/js/posts-navs-page.js
new file mode 100644
index 00000000..aa81a3fc
--- /dev/null
+++ b/assets/js/posts-navs-page.js
@@ -0,0 +1,255 @@
+/**
+ * AKSRT 文章导航页 — admin settings modal + HTML5 drag-sort (no SortableJS dependency).
+ * TOC generation is handled by section-navs.js via [data-nav] + .posts-nav-box.
+ *
+ * @package AKSRT
+ */
+(function () {
+ 'use strict';
+
+ function qs(sel, root) {
+ return (root || document).querySelector(sel);
+ }
+
+ function qsa(sel, root) {
+ return Array.prototype.slice.call((root || document).querySelectorAll(sel));
+ }
+
+ function ajaxUrl() {
+ if (window.aksrtData && window.aksrtData.ajaxUrl) return window.aksrtData.ajaxUrl;
+ if (window.ajaxurl) return window.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 initModal() {
+ var modal = qs('#post-navs-sz');
+ if (!modal) return;
+
+ function openModal(e) {
+ if (e) e.preventDefault();
+ modal.classList.add('is-open', 'show');
+ modal.setAttribute('aria-hidden', 'false');
+ }
+
+ function closeModal(e) {
+ if (e) e.preventDefault();
+ modal.classList.remove('is-open', 'show');
+ modal.setAttribute('aria-hidden', 'true');
+ }
+
+ qsa('[data-toggle="modal"][data-target="#post-navs-sz"], a[href="#post-navs-sz"]').forEach(function (el) {
+ el.addEventListener('click', openModal);
+ });
+ // Title [设置] uses data-target.
+ qsa('[data-target="#post-navs-sz"]').forEach(function (el) {
+ el.addEventListener('click', openModal);
+ });
+ qsa('[data-dismiss="modal"]', modal).forEach(function (btn) {
+ btn.addEventListener('click', closeModal);
+ });
+ modal.addEventListener('click', function (e) {
+ if (e.target === modal) closeModal(e);
+ });
+ document.addEventListener('keydown', function (e) {
+ if (e.key === 'Escape' && modal.classList.contains('is-open')) closeModal(e);
+ });
+ }
+
+ function initDragSort() {
+ var left = qs('#cat-sortable');
+ var right = qs('#cat-sortable-ok');
+ if (!left || !right) return;
+
+ var dragEl = null;
+
+ function onDragStart(e) {
+ dragEl = e.currentTarget;
+ dragEl.classList.add('is-dragging');
+ try {
+ e.dataTransfer.effectAllowed = 'move';
+ e.dataTransfer.setData('text/plain', dragEl.getAttribute('data-id') || '');
+ } catch (err) { /* IE */ }
+ }
+
+ function onDragEnd() {
+ if (dragEl) dragEl.classList.remove('is-dragging');
+ dragEl = null;
+ qsa('.cat-group').forEach(function (g) {
+ g.classList.remove('is-drop-target');
+ });
+ }
+
+ function bindItem(el) {
+ el.setAttribute('draggable', 'true');
+ el.addEventListener('dragstart', onDragStart);
+ el.addEventListener('dragend', onDragEnd);
+ }
+
+ qsa('.sortable-cat-item', left).forEach(bindItem);
+ qsa('.sortable-cat-item', right).forEach(bindItem);
+
+ [left, right].forEach(function (group) {
+ group.addEventListener('dragover', function (e) {
+ e.preventDefault();
+ group.classList.add('is-drop-target');
+ if (!dragEl) return;
+ var after = getDragAfterElement(group, e.clientY);
+ if (after == null) {
+ group.appendChild(dragEl);
+ } else {
+ group.insertBefore(dragEl, after);
+ }
+ });
+ group.addEventListener('dragleave', function () {
+ group.classList.remove('is-drop-target');
+ });
+ group.addEventListener('drop', function (e) {
+ e.preventDefault();
+ group.classList.remove('is-drop-target');
+ });
+ });
+
+ function getDragAfterElement(container, y) {
+ var els = qsa('.sortable-cat-item:not(.is-dragging)', container);
+ var closest = { offset: Number.NEGATIVE_INFINITY, element: null };
+ els.forEach(function (child) {
+ var box = child.getBoundingClientRect();
+ var offset = y - box.top - box.height / 2;
+ if (offset < 0 && offset > closest.offset) {
+ closest = { offset: offset, element: child };
+ }
+ });
+ return closest.element;
+ }
+ }
+
+ function initSave() {
+ var form = qs('#aksrt-posts-navs-settings');
+ if (!form) return;
+
+ form.addEventListener('submit', function (e) {
+ e.preventDefault();
+ var ids = [];
+ qsa('#cat-sortable-ok .sortable-cat-item').forEach(function (item) {
+ var id = item.getAttribute('data-id');
+ if (id) ids.push(id);
+ });
+ if (!ids.length) {
+ toast('请选择需要显示的分类', 'danger');
+ return;
+ }
+
+ var fd = new FormData(form);
+ fd.set('action', 'aksrt_post_navs_settings');
+ // PHP accepts array; append each.
+ fd.delete('navs_show_cat_id');
+ ids.forEach(function (id) {
+ fd.append('navs_show_cat_id[]', id);
+ });
+
+ var btn = qs('button[type="submit"]', form);
+ if (btn) btn.disabled = true;
+
+ fetch(ajaxUrl(), {
+ method: 'POST',
+ body: fd,
+ credentials: 'same-origin',
+ })
+ .then(function (r) { return r.json(); })
+ .then(function (data) {
+ if (!data || data.error) {
+ toast((data && data.msg) || '保存失败', 'danger');
+ return;
+ }
+ toast(data.msg || '保存成功', 'success');
+ if (data.reload) {
+ window.setTimeout(function () { window.location.reload(); }, 400);
+ }
+ })
+ .catch(function () {
+ toast('网络错误', 'danger');
+ })
+ .finally(function () {
+ if (btn) btn.disabled = false;
+ });
+ });
+ }
+
+ /**
+ * Build simple TOC into .posts-nav-box if section-navs hasn't already.
+ * Uses category h2 headings inside .posts-navs.
+ */
+ function initFallbackToc() {
+ var box = qs('.posts-nav-box');
+ var root = qs('.posts-navs[data-nav]');
+ if (!box || !root) return;
+ if (box.querySelector('.posts-nav-lists, .aksrt-section-nav, .toc-list')) return;
+
+ var headings = qsa('.title-theme h2, h2', root);
+ if (headings.length < 1) return;
+
+ var title = box.getAttribute('data-title') || '导航目录';
+ var list = '';
+ headings.forEach(function (h, i) {
+ if (!h.id) h.id = 'navs-sec-' + i;
+ list += '
' + (h.textContent || '').trim() + '';
+ });
+
+ box.innerHTML =
+ '' +
+ '';
+
+ qsa('a[href^="#"]', box).forEach(function (a) {
+ a.addEventListener('click', function (e) {
+ var id = a.getAttribute('href');
+ var el = id ? document.getElementById(id.replace(/^#/, '')) : null;
+ if (!el) return;
+ e.preventDefault();
+ var top = el.getBoundingClientRect().top + window.pageYOffset - 90;
+ window.scrollTo({ top: top, behavior: 'smooth' });
+ });
+ });
+
+ function highlight() {
+ var y = window.pageYOffset + 120;
+ var current = headings[0];
+ headings.forEach(function (h) {
+ if (h.offsetTop <= y) current = h;
+ });
+ qsa('li', box).forEach(function (li) {
+ var a = li.querySelector('a');
+ if (a && current && a.getAttribute('href') === '#' + current.id) {
+ li.classList.add('active');
+ } else {
+ li.classList.remove('active');
+ }
+ });
+ }
+ window.addEventListener('scroll', highlight, { passive: true });
+ highlight();
+ }
+
+ function init() {
+ initModal();
+ initDragSort();
+ initSave();
+ // Defer so section-navs.js can own TOC first.
+ window.setTimeout(initFallbackToc, 50);
+ }
+
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', init);
+ } else {
+ init();
+ }
+})();
diff --git a/functions.php b/functions.php
index f7cce329..4a31e905 100644
--- a/functions.php
+++ b/functions.php
@@ -255,6 +255,9 @@ if (class_exists('\AKSRT\Core\LinksSubmit')) {
if (class_exists('\AKSRT\Core\LinksPageMeta')) {
\AKSRT\Core\LinksPageMeta::get_instance()->init();
}
+if (class_exists('\AKSRT\Core\PostsNavs')) {
+ \AKSRT\Core\PostsNavs::get_instance()->init();
+}
if (class_exists('\AKSRT\Messages\SiteNotice')) {
\AKSRT\Messages\SiteNotice::get_instance()->init();
}
diff --git a/src/AKSRT/Core/PostsNavs.php b/src/AKSRT/Core/PostsNavs.php
new file mode 100644
index 00000000..4021fb78
--- /dev/null
+++ b/src/AKSRT/Core/PostsNavs.php
@@ -0,0 +1,77 @@
+ 1, 'ys' => 'danger', 'msg' => __('权限不足', 'aksrt')]);
+ }
+ if (!check_ajax_referer('aksrt_post_navs_settings', '_wpnonce', false)) {
+ wp_send_json(['error' => 1, 'ys' => 'danger', 'msg' => __('安全校验失败', 'aksrt')]);
+ }
+
+ $page_id = isset($_POST['page_id']) ? absint($_POST['page_id']) : 0;
+ if ($page_id <= 0 || get_post_type($page_id) !== 'page') {
+ wp_send_json(['error' => 1, 'ys' => 'danger', 'msg' => __('无效页面', 'aksrt')]);
+ }
+
+ $cat_ids = [];
+ if (isset($_POST['navs_show_cat_id'])) {
+ $raw = wp_unslash($_POST['navs_show_cat_id']);
+ if (is_array($raw)) {
+ $cat_ids = array_values(array_filter(array_map('intval', $raw)));
+ } elseif (is_string($raw) && $raw !== '') {
+ $decoded = json_decode($raw, true);
+ if (is_array($decoded)) {
+ $cat_ids = array_values(array_filter(array_map('intval', $decoded)));
+ } else {
+ $cat_ids = array_values(array_filter(array_map('intval', explode(',', $raw))));
+ }
+ }
+ }
+
+ if (empty($cat_ids)) {
+ wp_send_json(['error' => 1, 'ys' => 'danger', 'msg' => __('请选择需要显示的分类', 'aksrt')]);
+ }
+
+ if (isset($_POST['navs_page_name'])) {
+ update_post_meta($page_id, 'navs_page_name', sanitize_text_field(wp_unslash((string) $_POST['navs_page_name'])));
+ }
+ if (isset($_POST['navs_page_desc'])) {
+ update_post_meta($page_id, 'navs_page_desc', sanitize_text_field(wp_unslash((string) $_POST['navs_page_desc'])));
+ }
+ update_post_meta($page_id, 'navs_show_cat_id', $cat_ids);
+
+ wp_send_json([
+ 'error' => 0,
+ 'msg' => __('保存成功', 'aksrt'),
+ 'reload'=> true,
+ 'ids' => $cat_ids,
+ ]);
+ }
+}
diff --git a/src/AKSRT/Core/ScriptsPart1b.php b/src/AKSRT/Core/ScriptsPart1b.php
index 6791cb17..eb17842a 100644
--- a/src/AKSRT/Core/ScriptsPart1b.php
+++ b/src/AKSRT/Core/ScriptsPart1b.php
@@ -404,6 +404,39 @@ trait ScriptsPart1b {
}
}
+ // 文章导航页(zibll postsnavs.php)
+ if (is_page_template('templates/pages/page-posts-navs.php')) {
+ $pn_css = get_template_directory() . '/assets/css/routes/posts-navs.css';
+ if (file_exists($pn_css)) {
+ wp_enqueue_style(
+ 'aksrt-route-posts-navs',
+ $base_uri . '/assets/css/routes/posts-navs.css',
+ ['aksrt-tokens'],
+ (string) filemtime($pn_css)
+ );
+ }
+ $section_navs = get_template_directory() . '/assets/js/section-navs.js';
+ if (file_exists($section_navs)) {
+ wp_enqueue_script(
+ 'aksrt-section-navs',
+ $base_uri . '/assets/js/section-navs.js',
+ ['jquery'],
+ (string) filemtime($section_navs),
+ true
+ );
+ }
+ $pn_js = get_template_directory() . '/assets/js/posts-navs-page.js';
+ if (file_exists($pn_js)) {
+ wp_enqueue_script(
+ 'aksrt-posts-navs-page',
+ $base_uri . '/assets/js/posts-navs-page.js',
+ ['jquery'],
+ (string) filemtime($pn_js),
+ true
+ );
+ }
+ }
+
// 交易中心共享壳层(购物车 / 订单 / 收益)
$is_commerce_center = is_page_template('templates/pages/page-orders.php')
|| is_page_template('templates/pages/page-cart.php')
diff --git a/templates/pages/page-posts-navs.php b/templates/pages/page-posts-navs.php
index 28e84ebe..2d371d28 100644
--- a/templates/pages/page-posts-navs.php
+++ b/templates/pages/page-posts-navs.php
@@ -1,65 +1,228 @@
ID : (int) get_queried_object_id();
-$categories = get_categories(['hide_empty' => true, 'orderby' => 'count', 'order' => 'DESC', 'number' => 20]);
-$tags = get_tags(['hide_empty' => true, 'orderby' => 'count', 'order' => 'DESC', 'number' => 40]);
-
-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' => 'posts-navs',
- ]);
+$show_cat_id = get_post_meta($post_id, 'navs_show_cat_id', true);
+if (!is_array($show_cat_id)) {
+ $show_cat_id = $show_cat_id !== '' && $show_cat_id !== null
+ ? array_values(array_filter(array_map('intval', (array) $show_cat_id)))
+ : [];
+} else {
+ $show_cat_id = array_values(array_filter(array_map('intval', $show_cat_id)));
}
+
+$page_desc = (string) get_post_meta($post_id, 'navs_page_desc', true);
+$page_name = (string) get_post_meta($post_id, 'navs_page_name', true);
+$title = get_the_title($post_id);
+$page_name = $page_name !== '' ? $page_name : $title;
+
+$all_cats = get_categories([
+ 'orderby' => 'term_group',
+ 'order' => 'ASC',
+ 'hide_empty' => false,
+]);
+
+$can_admin = is_user_logged_in() && current_user_can('manage_options');
+
+get_header();
?>
-
- esc_html(get_the_title() ?: __('文章导航', 'aksrt')),
+
+
+
+
+
+
+
+
+
+
+ []
+
+
+
+
+
+
+
+
+
+ true]);
+ } elseif (function_exists('aksrt_get_share_html')) {
+ echo aksrt_get_share_html(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+
+ (int) $c_id,
+ 'numberposts' => -1,
+ 'post_status' => 'publish',
+ 'orderby' => 'date',
+ 'order' => 'DESC',
+ 'suppress_filters' => false,
]);
- ?>
+ if (empty($posts)) {
+ continue;
+ }
+ $section_id = 'navs-cat-' . (int) $c_id;
+ echo '
';
+ echo '
' . esc_html($the_cat->name) . '
';
+ echo '
';
+ foreach ($posts as $p) {
+ $time_tip = get_the_time('Y年m月d日 H:i', $p);
+ $time_ago = human_time_diff(get_the_time('U', $p), current_time('timestamp')) . __('前', 'aksrt');
+ $views = function_exists('aksrt_get_post_views') ? (int) aksrt_get_post_views($p->ID) : 0;
+ $likes = (int) get_post_meta($p->ID, 'like', true);
+ if ($likes <= 0) {
+ $likes = (int) get_post_meta($p->ID, 'aksrt_like', true);
+ }
+ $post_tip = sprintf(
+ /* translators: 1: views 2: likes */
+ __('阅读(%1$s) 点赞(%2$s)', 'aksrt'),
+ (string) $views,
+ (string) $likes
+ );
+ $subtitle = (string) get_post_meta($p->ID, 'subtitle', true);
+ if ($subtitle === '') {
+ $subtitle = (string) get_post_meta($p->ID, 'aksrt_subtitle', true);
+ }
+ echo '- ';
+ echo '
';
+ }
+ echo '
';
+ }
+ } elseif ($can_admin) {
+ echo '
';
+ echo '
' . esc_html__('尚未选择分类,请点击标题旁「设置」配置要展示的分类。', 'aksrt') . '
';
+ echo '
';
+ } else {
+ echo '
';
+ echo '
' . esc_html__('暂无导航内容', 'aksrt') . '
';
+ echo '
';
+ }
+ ?>
+
+
+
+
+
-
-
-
-
-
-
-
- name); ?>
- count); ?>
-
-
+
+
+
+
false]);
-}
+endif;
get_footer();