226 lines
9.2 KiB
PHP
226 lines
9.2 KiB
PHP
<?php
|
|
// Inline-SVG icon helper (replaces the Bootstrap Icons webfont). Included
|
|
// first so blowdit_icon() is available to head.php and every body template.
|
|
include(THEME_DIR_PHP.'icons.php');
|
|
|
|
// ---- Theme contact / social config ----
|
|
// The mailto icon uses Bludit's "Sender email" (Settings -> Advanced ->
|
|
// Email account settings). Leave it to auto-detect, or hard-set $blowditEmail
|
|
// below to override. Empty -> the icon is hidden.
|
|
$blowditEmail = '';
|
|
if (isset($site)) {
|
|
// Try Bludit's site settings (accessor is getValue(); getField() as backup).
|
|
$bdGet = method_exists($site, 'getValue') ? 'getValue'
|
|
: (method_exists($site, 'getField') ? 'getField' : null);
|
|
if ($bdGet) {
|
|
$blowditEmail = $site->$bdGet('emailFromAddress'); // Bludit's "Sender email"
|
|
if (empty($blowditEmail)) { $blowditEmail = $site->$bdGet('email'); }
|
|
}
|
|
}
|
|
// Bludit's sender email isn't always exposed to themes (it's an SMTP/plugin
|
|
// setting), so fall back to a fixed address. Set to '' to hide the icon.
|
|
if (empty($blowditEmail)) { $blowditEmail = 'contact@interlope.xyz'; }
|
|
$blowditTwitterHandle = '@spleenftw'; // twitter:site / twitter:creator; '' to omit
|
|
|
|
// Read the theme from a cookie so the root element can be rendered
|
|
// pre-coloured. This paints the page in the theme colour from the first
|
|
// byte of every response, eliminating the white flash between navigations.
|
|
$blowditThemeBg = array(
|
|
'light' => '#ffffff',
|
|
'dark' => '#171717',
|
|
'nord' => '#2e3440',
|
|
'dracula' => '#282a36',
|
|
'catppuccin' => '#1e1e2e',
|
|
);
|
|
$blowditTheme = (isset($_COOKIE['blowdit-theme']) && isset($blowditThemeBg[$_COOKIE['blowdit-theme']]))
|
|
? $_COOKIE['blowdit-theme']
|
|
: 'light';
|
|
$blowditBg = $blowditThemeBg[$blowditTheme];
|
|
$blowditScheme = ($blowditTheme === 'light') ? 'light' : 'dark';
|
|
|
|
// Pre-compute before <body> so we can stamp the has-toc class on it,
|
|
// which lets CSS align the navbar container with the wider article container.
|
|
$blowditIsArticle = ($WHERE_AM_I === 'page' && isset($page) && !$page->isStatic() && !$url->notFound());
|
|
$blowditHasHeadings = $blowditIsArticle && (bool) preg_match('/<h[234]/i', $page->content());
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="<?php echo Theme::lang() ?>" data-theme="<?php echo $blowditTheme ?>" style="background-color: <?php echo $blowditBg ?>; color-scheme: <?php echo $blowditScheme ?>;">
|
|
<head>
|
|
<?php include(THEME_DIR_PHP.'head.php'); ?>
|
|
</head>
|
|
<body<?php echo $blowditHasHeadings ? ' class="has-toc"' : ''; ?>>
|
|
|
|
<!-- Skip to content (keyboard / screen-reader navigation) -->
|
|
<a class="skip-link" href="#main-content"><?php echo $L->get('Skip to content'); ?></a>
|
|
|
|
<!-- Load Bludit Plugins: Site Body Begin -->
|
|
<?php Theme::plugins('siteBodyBegin'); ?>
|
|
|
|
<!-- Navbar -->
|
|
<?php include(THEME_DIR_PHP.'navbar.php'); ?>
|
|
|
|
<?php
|
|
// Split the siteSidebar plugins into "search" and "everything else".
|
|
// This lets us relocate the search box under the hero on the homepage
|
|
// (Popeye-style) while keeping the other plugins in the right sidebar.
|
|
// Mirrors Bludit's own Theme::plugins() loop, but captures the output.
|
|
global $plugins;
|
|
$sidebarSearchHtml = ''; // search box, rendered under the hero on the homepage
|
|
$sidebarItems = array(); // every sidebar plugin, to be sorted into a fixed order
|
|
$sidebarIndex = 0;
|
|
if (isset($plugins['siteSidebar'])) {
|
|
foreach ($plugins['siteSidebar'] as $plugin) {
|
|
$out = $plugin->siteSidebar();
|
|
|
|
$isSearch = (strpos($out, 'plugin-search') !== false);
|
|
$isNav = (strpos($out, 'plugin-navigation') !== false || strpos($out, 'plugin-pages') !== false);
|
|
|
|
// Fixed sidebar order (lower = higher up). Unknown plugins such as
|
|
// the Hit Counter default to 90, i.e. pinned to the bottom.
|
|
$priority = 90;
|
|
if (strpos($out, 'plugin-about') !== false) $priority = 10; // Blog / About
|
|
elseif ($isSearch) $priority = 20; // Search
|
|
elseif ($isNav) $priority = 30; // Navigation
|
|
elseif (strpos($out, 'plugin-categories') !== false) $priority = 40; // Category
|
|
|
|
if ($isSearch) {
|
|
$sidebarSearchHtml .= $out;
|
|
}
|
|
// Mark the navigation / pages plugin so its list can be shuffled
|
|
// client-side (see the script near the body end).
|
|
if ($isNav) {
|
|
$out = '<div class="js-random-nav">' . $out . '</div>';
|
|
}
|
|
|
|
$sidebarItems[] = array(
|
|
'priority' => $priority,
|
|
'index' => $sidebarIndex++,
|
|
'search' => $isSearch,
|
|
'nav' => $isNav,
|
|
'html' => $out,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Stable sort by priority, then original order (PHP < 8 usort isn't stable).
|
|
usort($sidebarItems, function ($a, $b) {
|
|
if ($a['priority'] === $b['priority']) {
|
|
return $a['index'] - $b['index'];
|
|
}
|
|
return $a['priority'] - $b['priority'];
|
|
});
|
|
|
|
$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) appear on every paginated home page.
|
|
$heroVisible = ($WHERE_AM_I === 'home');
|
|
// Reuse the values pre-computed before <body> (avoids calling $page->content() twice).
|
|
$isArticlePage = $blowditIsArticle;
|
|
$hasHeadings = $blowditHasHeadings;
|
|
?>
|
|
|
|
<!-- Content -->
|
|
<div id="main-content" tabindex="-1" class="<?php echo $hasHeadings ? 'container-wide' : 'container'; ?>">
|
|
<div class="row">
|
|
|
|
<?php if ($hasHeadings): ?>
|
|
|
|
<!-- Left ToC Sidebar — visible on md+ only; hidden on mobile.
|
|
No align-self-start so the column stretches to article height, enabling sticky. -->
|
|
<div class="col-md-3 d-none d-md-block">
|
|
<?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
|
|
if ($WHERE_AM_I == 'page') {
|
|
include(THEME_DIR_PHP.'page.php');
|
|
} else {
|
|
include(THEME_DIR_PHP.'home.php');
|
|
}
|
|
?>
|
|
</div>
|
|
|
|
<?php endif ?>
|
|
|
|
<!-- Right Sidebar (align-self-start: card fits its content, doesn't
|
|
stretch to match the main column's height) -->
|
|
<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>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<?php include(THEME_DIR_PHP.'footer.php'); ?>
|
|
|
|
<!-- Javascript -->
|
|
<?php
|
|
// jQuery and Bootstrap's JS bundle are intentionally NOT loaded: every
|
|
// interactive piece in this theme is vanilla JS (theme picker, tabs,
|
|
// carousel, lightbox), and the only Bootstrap-JS feature used — the
|
|
// mobile navbar collapse — is reimplemented in blowdit.js. This drops
|
|
// ~115 KB. Re-add Theme::jquery() / Theme::jsBootstrap() here if you
|
|
// install a plugin that depends on them.
|
|
?>
|
|
|
|
<!-- Theme behaviour: deferred, cached external bundle.
|
|
The anti-flash theme bootstrap stays inline in head.php — it must
|
|
run before the first paint and so cannot be deferred. -->
|
|
<script>
|
|
// Theme directory URL, so the deferred bundle can load self-hosted assets
|
|
// (highlight.js + its theme CSS) relative to the theme.
|
|
window.BLOWDIT_THEME_URL = <?php echo json_encode(DOMAIN_THEME); ?>;
|
|
window.BLOWDIT_HOME = <?php echo json_encode(Theme::siteUrl()); ?>;
|
|
// Localised UI strings for the deferred bundle. $L->get() falls back to
|
|
// the key itself when a translation is missing, so this is safe in any language.
|
|
window.BLOWDIT_I18N = {
|
|
copy: <?php echo json_encode($L->get('Copy')); ?>,
|
|
copied: <?php echo json_encode($L->get('Copied')); ?>,
|
|
copyCode: <?php echo json_encode($L->get('Copy code')); ?>,
|
|
backToTop: <?php echo json_encode($L->get('Back to top')); ?>,
|
|
prevSlide: <?php echo json_encode($L->get('Previous slide')); ?>,
|
|
nextSlide: <?php echo json_encode($L->get('Next slide')); ?>,
|
|
goToSlide: <?php echo json_encode($L->get('Go to slide')); ?>,
|
|
prevImage: <?php echo json_encode($L->get('Previous image')); ?>,
|
|
nextImage: <?php echo json_encode($L->get('Next image')); ?>,
|
|
imageViewer: <?php echo json_encode($L->get('Image viewer')); ?>,
|
|
onThisPage: <?php echo json_encode($L->get('On this page')); ?>,
|
|
search: <?php echo json_encode($L->get('Search')); ?>,
|
|
searchPlaceholder: <?php echo json_encode($L->get('Search posts…')); ?>,
|
|
noResults: <?php echo json_encode($L->get('No results')); ?>
|
|
};
|
|
</script>
|
|
<?php
|
|
// Cache-busted by file mtime (mirrors the style.css approach in head.php).
|
|
$jsVersion = @filemtime(THEME_DIR_JS . 'blowdit.js');
|
|
$jsSrc = DOMAIN_THEME . 'js/blowdit.js' . ($jsVersion ? '?v=' . $jsVersion : '');
|
|
?>
|
|
<script defer src="<?php echo $jsSrc; ?>"></script>
|
|
|
|
<!-- Load Bludit Plugins: Site Body End -->
|
|
<?php Theme::plugins('siteBodyEnd'); ?>
|
|
|
|
</body>
|
|
</html>
|