adding a left sidebar for article summary. changed right sidebar behavior between homepage and in-articles

This commit is contained in:
interlope committed 2026-05-29 17:44:58 +02:00
1 parent c9cff79d3b
commit 7585802e57
4 files changed
+166 -15

No files matched your search

+58
View File
@@ -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
======================================== */
+98 -11
View File
@@ -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
View File
@@ -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;
}
+6
View File
@@ -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>