adding a left sidebar for article summary. changed right sidebar behavior between homepage and in-articles
This commit is contained in:
1 parent
c9cff79d3b
commit
7585802e57
4 files changed
+166
-15
No files matched your search
@@ -1027,6 +1027,64 @@ footer.footer-modern a:hover {
|
||||
margin-right: 0.4rem;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
TABLE OF CONTENTS (left sidebar, article view)
|
||||
======================================== */
|
||||
.toc-sidebar {
|
||||
margin-top: var(--space-xl);
|
||||
position: sticky;
|
||||
top: var(--space-lg);
|
||||
max-height: calc(100vh - var(--space-xl));
|
||||
overflow-y: auto;
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.toc-title {
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.toc-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.toc-item {
|
||||
padding: 0.15rem 0;
|
||||
}
|
||||
|
||||
.toc-h3 {
|
||||
padding-left: 0.75rem;
|
||||
}
|
||||
|
||||
.toc-h4 {
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.toc-link {
|
||||
color: var(--text-muted);
|
||||
text-decoration: none;
|
||||
line-height: 1.5;
|
||||
display: block;
|
||||
transition: color 0.15s ease;
|
||||
}
|
||||
|
||||
.toc-link:hover {
|
||||
color: var(--text-primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.toc-link.active {
|
||||
color: var(--accent);
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
RESPONSIVE
|
||||
======================================== */
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
'priority' => $priority,
|
||||
'index' => $sidebarIndex++,
|
||||
'search' => $isSearch,
|
||||
'nav' => $isNav,
|
||||
'html' => $out,
|
||||
);
|
||||
}
|
||||
@@ -78,32 +79,48 @@
|
||||
return $a['priority'] - $b['priority'];
|
||||
});
|
||||
|
||||
$sidebarFullHtml = ''; // all plugins, ordered (used off the homepage)
|
||||
$sidebarNoSearchHtml = ''; // same, minus search (used on the homepage)
|
||||
$sidebarFullHtml = ''; // all plugins, ordered (used on article/page views)
|
||||
$sidebarNoSearchHtml = ''; // same, minus search (kept for potential future use)
|
||||
$sidebarHomeHtml = ''; // about + categories + hit counter only (homepage, no nav/search)
|
||||
foreach ($sidebarItems as $item) {
|
||||
$sidebarFullHtml .= $item['html'];
|
||||
if (!$item['search']) {
|
||||
$sidebarNoSearchHtml .= $item['html'];
|
||||
}
|
||||
if (!$item['search'] && !$item['nav']) {
|
||||
$sidebarHomeHtml .= $item['html'];
|
||||
}
|
||||
}
|
||||
|
||||
// The hero (and the relocated search) only appear on the blog front page.
|
||||
$heroVisible = ($WHERE_AM_I === 'home' && Paginator::currentPage() == 1);
|
||||
$heroVisible = ($WHERE_AM_I === 'home' && Paginator::currentPage() == 1);
|
||||
// True when viewing a single non-static article.
|
||||
$isArticlePage = ($WHERE_AM_I === 'page' && isset($page) && !$page->isStatic() && !$url->notFound());
|
||||
// Show 3-column layout (left ToC + content + right sidebar) only when the article has headings.
|
||||
$hasHeadings = $isArticlePage && (bool) preg_match('/<h[234]/i', $page->content());
|
||||
?>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
|
||||
<!-- Blog Posts -->
|
||||
<?php if ($hasHeadings): ?>
|
||||
|
||||
<!-- Left ToC Sidebar — visible on md+ only; hidden on mobile -->
|
||||
<div class="col-md-3 d-none d-md-block align-self-start">
|
||||
<?php include(THEME_DIR_PHP.'toc.php'); ?>
|
||||
</div>
|
||||
|
||||
<!-- Article Content (narrower when ToC is present) -->
|
||||
<div class="col-md-6">
|
||||
<?php include(THEME_DIR_PHP.'page.php'); ?>
|
||||
</div>
|
||||
|
||||
<?php else: ?>
|
||||
|
||||
<!-- Blog Posts / Page (full width when no ToC) -->
|
||||
<div class="col-md-8">
|
||||
<?php
|
||||
// Bludit content are pages.
|
||||
// Ordered by date, these pages behave like posts.
|
||||
//
|
||||
// $WHERE_AM_I detects where the visitor is:
|
||||
// "page" -> viewing a single page/post
|
||||
// "home" -> viewing the front page
|
||||
if ($WHERE_AM_I == 'page') {
|
||||
include(THEME_DIR_PHP.'page.php');
|
||||
} else {
|
||||
@@ -112,9 +129,11 @@
|
||||
?>
|
||||
</div>
|
||||
|
||||
<?php endif ?>
|
||||
|
||||
<!-- Right Sidebar (align-self-start: card fits its content, doesn't
|
||||
stretch to match the main column's height) -->
|
||||
<div class="col-md-3 offset-md-1 align-self-start">
|
||||
<div class="<?php echo $hasHeadings ? 'col-md-3' : 'col-md-3 offset-md-1'; ?> align-self-start">
|
||||
<?php include(THEME_DIR_PHP.'sidebar.php'); ?>
|
||||
</div>
|
||||
|
||||
@@ -213,6 +232,74 @@
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- Table of Contents: generate links from article headings + scroll-spy -->
|
||||
<script>
|
||||
(function () {
|
||||
var tocNav = document.getElementById('toc-nav');
|
||||
if (!tocNav) return;
|
||||
|
||||
var content = document.querySelector('.content');
|
||||
if (!content) return;
|
||||
|
||||
var headings = content.querySelectorAll('h2, h3, h4');
|
||||
if (headings.length === 0) return;
|
||||
|
||||
// Ensure every heading has a stable anchor ID
|
||||
var usedIds = {};
|
||||
Array.prototype.forEach.call(headings, function (h) {
|
||||
if (!h.id) {
|
||||
var base = h.textContent.trim().toLowerCase()
|
||||
.replace(/[^a-z0-9\s-]/g, '')
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/^-+|-+$/g, '') || 'heading';
|
||||
var id = base, n = 2;
|
||||
while (usedIds[id]) { id = base + '-' + (n++); }
|
||||
usedIds[id] = true;
|
||||
h.id = id;
|
||||
} else {
|
||||
usedIds[h.id] = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Build the list
|
||||
var ul = document.createElement('ul');
|
||||
ul.className = 'toc-list';
|
||||
Array.prototype.forEach.call(headings, function (h) {
|
||||
var li = document.createElement('li');
|
||||
li.className = 'toc-item toc-' + h.tagName.toLowerCase();
|
||||
var a = document.createElement('a');
|
||||
a.href = '#' + h.id;
|
||||
a.textContent = h.textContent;
|
||||
a.className = 'toc-link';
|
||||
li.appendChild(a);
|
||||
ul.appendChild(li);
|
||||
});
|
||||
tocNav.appendChild(ul);
|
||||
|
||||
// Scroll-spy: highlight the last heading that has scrolled past the top
|
||||
var headingArr = Array.prototype.slice.call(headings);
|
||||
|
||||
function updateActive() {
|
||||
var scrollY = window.scrollY || window.pageYOffset;
|
||||
var threshold = scrollY + 120; // offset for navbar height
|
||||
var active = null;
|
||||
headingArr.forEach(function (h) {
|
||||
var top = h.getBoundingClientRect().top + scrollY;
|
||||
if (top <= threshold) active = h;
|
||||
});
|
||||
var links = tocNav.querySelectorAll('.toc-link');
|
||||
Array.prototype.forEach.call(links, function (a) { a.classList.remove('active'); });
|
||||
if (active) {
|
||||
var link = tocNav.querySelector('a[href="#' + active.id + '"]');
|
||||
if (link) link.classList.add('active');
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', updateActive, { passive: true });
|
||||
updateActive();
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- Load Bludit Plugins: Site Body End -->
|
||||
<?php Theme::plugins('siteBodyEnd'); ?>
|
||||
|
||||
|
||||
+4
-4
@@ -20,10 +20,10 @@
|
||||
<?php
|
||||
// Plugins are rendered in a fixed order (see index.php):
|
||||
// Blog/About > Search > Navigation > Category > … > Hit Counter (bottom)
|
||||
// On the homepage front page the search box is shown under the hero, so
|
||||
// it is omitted from the sidebar there.
|
||||
if ($heroVisible) {
|
||||
echo $sidebarNoSearchHtml;
|
||||
// On the homepage the navigation is redundant (posts are already listed),
|
||||
// and the search box lives under the hero, so show only about/categories/counters.
|
||||
if ($WHERE_AM_I === 'home') {
|
||||
echo $sidebarHomeHtml;
|
||||
} else {
|
||||
echo $sidebarFullHtml;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<aside class="toc-sidebar">
|
||||
<div class="toc-title">On this page</div>
|
||||
<nav id="toc-nav" aria-label="Table of contents">
|
||||
<!-- Filled by the ToC script in index.php -->
|
||||
</nav>
|
||||
</aside>
|
||||
Reference in new issue
Block a user