feat(widgets): wire PostsNavs TOC shell to page-template scrollspy
Match built-in single sidebar .posts-nav-box.toc-widget/.toc-list so the sidebar widget is filled (≥3 headings), supports multiple TOC shells, and removes empty chrome.
This commit is contained in:
@@ -364,6 +364,10 @@ html.dark .article-end-separator span {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.toc-list .toc-h1 a {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.toc-list .toc-h3 a {
|
||||
padding-left: 12px;
|
||||
}
|
||||
@@ -372,6 +376,30 @@ html.dark .article-end-separator span {
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
/* WidgetPostsNavs + built-in shell share .posts-nav-box.toc-widget */
|
||||
.widget .posts-nav-box.toc-widget,
|
||||
.widget_aksrt_posts_navs .posts-nav-box.toc-widget {
|
||||
margin: 0;
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.widget .posts-nav-box.toc-widget > h3,
|
||||
.widget .posts-nav-box.toc-widget .toc-widget__title,
|
||||
.widget_aksrt_posts_navs .posts-nav-box.toc-widget > h3 {
|
||||
margin: 0 0 10px;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
body.single .sidebar .posts-nav-box.toc-widget.is-affix,
|
||||
body.single .sidebar .posts-nav-box.toc-widget[data-affix="true"] {
|
||||
position: sticky;
|
||||
top: calc(var(--nav-height, 60px) + 16px);
|
||||
}
|
||||
|
||||
.toc-float-btn {
|
||||
position: fixed;
|
||||
right: 18px;
|
||||
|
||||
+59
-17
@@ -19,9 +19,31 @@
|
||||
}
|
||||
|
||||
function initPostToc() {
|
||||
var content = document.querySelector('.wp-posts-content[data-nav="posts"][data-aksrt-toc="1"]');
|
||||
var tocWidget = document.querySelector('.posts-nav-box.toc-widget');
|
||||
var tocList = tocWidget ? tocWidget.querySelector('.toc-list') : null;
|
||||
// Accept either explicit data-aksrt-toc="1" or any [data-nav] article body.
|
||||
var content =
|
||||
document.querySelector('.wp-posts-content[data-nav="posts"][data-aksrt-toc="1"]') ||
|
||||
document.querySelector('.wp-posts-content[data-nav]') ||
|
||||
document.querySelector('[data-nav="posts"]') ||
|
||||
document.querySelector('[data-nav]');
|
||||
|
||||
// Support multiple TOC shells (built-in single sidebar + WidgetPostsNavs).
|
||||
var tocWidgets = Array.prototype.slice.call(
|
||||
document.querySelectorAll('.posts-nav-box.toc-widget, .posts-nav-box[data-title]')
|
||||
);
|
||||
var tocLists = [];
|
||||
tocWidgets.forEach(function (box) {
|
||||
if (!box.classList.contains('toc-widget')) {
|
||||
box.classList.add('toc-widget');
|
||||
}
|
||||
var list = box.querySelector('.toc-list');
|
||||
if (!list) {
|
||||
list = document.createElement('ol');
|
||||
list.className = 'toc-list';
|
||||
box.appendChild(list);
|
||||
}
|
||||
tocLists.push(list);
|
||||
});
|
||||
|
||||
var mobileToggle = document.querySelector('.toc-float-btn');
|
||||
var drawer = document.querySelector('.toc-drawer');
|
||||
var overlay = document.querySelector('.toc-drawer-overlay');
|
||||
@@ -29,16 +51,25 @@
|
||||
var closeBtn = drawer ? drawer.querySelector('.toc-drawer__close') : null;
|
||||
|
||||
if (!content || content.dataset.aksrtTocBound === '1') return;
|
||||
if (!tocList && !drawerList && !mobileToggle) return;
|
||||
if (!tocLists.length && !drawerList && !mobileToggle) return;
|
||||
|
||||
content.dataset.aksrtTocBound = '1';
|
||||
|
||||
var maxDepth = parseInt(content.dataset.aksrtTocDepth || '3', 10) || 3;
|
||||
// zibll section-navs: only fill when heading count > 2 (≥3).
|
||||
var MIN_HEADINGS = 3;
|
||||
|
||||
function getHeadings() {
|
||||
return Array.prototype.slice.call(content.querySelectorAll('h2, h3, h4')).filter(function (heading) {
|
||||
// zibll scans h1–h4 inside [data-nav]; depth setting still caps lower levels.
|
||||
return Array.prototype.slice.call(content.querySelectorAll('h1, h2, h3, h4')).filter(function (heading) {
|
||||
if (heading.closest('.no-nav') || heading.classList.contains('item-heading')) {
|
||||
return false;
|
||||
}
|
||||
var level = parseInt((heading.tagName || 'H2').replace('H', ''), 10) || 2;
|
||||
return level <= maxDepth;
|
||||
// toc_depth is typically 2–4 meaning deepest heading level from h2 baseline;
|
||||
// allow h1 always, then h2..h(1+maxDepth) capped at h4.
|
||||
if (level === 1) return true;
|
||||
return level <= Math.min(4, maxDepth + 1);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -46,7 +77,7 @@
|
||||
if (!container) return false;
|
||||
|
||||
var headings = getHeadings();
|
||||
if (!headings.length) return false;
|
||||
if (headings.length < MIN_HEADINGS) return false;
|
||||
|
||||
var html = '';
|
||||
headings.forEach(function (heading) {
|
||||
@@ -55,7 +86,9 @@
|
||||
}
|
||||
|
||||
var levelClass = '';
|
||||
if (heading.tagName === 'H3') {
|
||||
if (heading.tagName === 'H1') {
|
||||
levelClass = 'toc-h1';
|
||||
} else if (heading.tagName === 'H3') {
|
||||
levelClass = 'toc-h3';
|
||||
} else if (heading.tagName === 'H4') {
|
||||
levelClass = 'toc-h4';
|
||||
@@ -68,13 +101,24 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
var hasToc = buildToc(tocList);
|
||||
if (drawerList && drawerList !== tocList) {
|
||||
buildToc(drawerList);
|
||||
var hasToc = false;
|
||||
tocLists.forEach(function (list) {
|
||||
if (buildToc(list)) hasToc = true;
|
||||
});
|
||||
if (drawerList && tocLists.indexOf(drawerList) === -1) {
|
||||
if (buildToc(drawerList)) hasToc = true;
|
||||
}
|
||||
|
||||
if (!hasToc) {
|
||||
if (tocWidget) tocWidget.remove();
|
||||
tocWidgets.forEach(function (box) {
|
||||
var host = box.closest('.widget') || box.parentElement;
|
||||
// Prefer removing the whole widget chrome when empty.
|
||||
if (host && host !== document.body && host.querySelectorAll('.posts-nav-box').length <= 1) {
|
||||
host.remove();
|
||||
} else {
|
||||
box.remove();
|
||||
}
|
||||
});
|
||||
if (mobileToggle) mobileToggle.remove();
|
||||
if (drawer) drawer.remove();
|
||||
if (overlay) overlay.remove();
|
||||
@@ -94,7 +138,7 @@
|
||||
}
|
||||
|
||||
function trackActiveHeading() {
|
||||
var headings = content.querySelectorAll('h2[id], h3[id], h4[id]');
|
||||
var headings = content.querySelectorAll('h1[id], h2[id], h3[id], h4[id]');
|
||||
if (!headings.length) return;
|
||||
|
||||
var activeId = '';
|
||||
@@ -117,10 +161,8 @@
|
||||
if (!target) return;
|
||||
|
||||
event.preventDefault();
|
||||
target.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
var top = target.getBoundingClientRect().top + window.pageYOffset - 105;
|
||||
window.scrollTo({ top: Math.max(0, top), behavior: 'smooth' });
|
||||
closeDrawer();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
/**
|
||||
* In-article / topic TOC shell (zibll widget_ui_posts_navs parity).
|
||||
*
|
||||
* Frontend JS fills `.posts-nav-box` from heading outline when present.
|
||||
* Shell matches the built-in single sidebar TOC so page-template.js can fill
|
||||
* `.posts-nav-box.toc-widget > .toc-list` from `[data-nav]` headings.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
@@ -18,8 +19,8 @@ class WidgetPostsNavs extends \WP_Widget {
|
||||
'aksrt_posts_navs',
|
||||
esc_html__('AKSRT 文章目录树', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('显示当前文章/帖子标题目录,非详情页不输出。标题超过 3 个才显示。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_posts_navs',
|
||||
'description' => esc_html__('显示当前文章/帖子标题目录,非详情页不输出。标题超过 2 个才填充。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_posts_navs',
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -35,16 +36,28 @@ class WidgetPostsNavs extends \WP_Widget {
|
||||
|
||||
$data_title = $title;
|
||||
if ($mini_title !== '') {
|
||||
$data_title .= ' · ' . $mini_title;
|
||||
$data_title .= ($data_title !== '' ? ' · ' : '') . $mini_title;
|
||||
}
|
||||
if ($data_title === '') {
|
||||
$data_title = __('文章目录', 'aksrt');
|
||||
}
|
||||
|
||||
// Mirror aksrt-single-render built-in TOC shell so page-template.js binds.
|
||||
$classes = 'zib-widget toc-widget posts-nav-box';
|
||||
if ($in_affix) {
|
||||
$classes .= ' is-affix';
|
||||
}
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '<div class="widget-container">';
|
||||
printf(
|
||||
'<div class="posts-nav-box"%s%s></div>',
|
||||
'<div class="%s"%s data-title="%s" aria-label="%s">',
|
||||
esc_attr($classes),
|
||||
$in_affix ? ' data-affix="true"' : '',
|
||||
$data_title !== '' ? ' data-title="' . esc_attr($data_title) . '"' : ''
|
||||
esc_attr($data_title),
|
||||
esc_attr($data_title)
|
||||
);
|
||||
echo '<h3 class="widget-title toc-widget__title">' . esc_html($data_title) . '</h3>';
|
||||
echo '<ol class="toc-list"></ol>';
|
||||
echo '</div>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
@@ -68,7 +81,7 @@ class WidgetPostsNavs extends \WP_Widget {
|
||||
<?php esc_html_e('侧栏随动(仅侧边栏)', 'aksrt'); ?>
|
||||
</label>
|
||||
</p>
|
||||
<p class="description"><?php esc_html_e('请添加到文章/帖子侧栏。正文 H2–H4 超过 3 个时前端才会填充目录。', 'aksrt'); ?></p>
|
||||
<p class="description"><?php esc_html_e('请添加到文章/帖子侧栏。正文标题 ≥3 个时由前端填充目录;无标题则自动移除。', 'aksrt'); ?></p>
|
||||
<?php
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user