Refactor code structure for improved readability and maintainability
This commit is contained in:
1 parent
3b31711b49
commit
fa211d243f
14 files changed
+428
-11
No files matched your search
+190
@@ -3,6 +3,38 @@
|
||||
Monochrome palette with light & dark modes
|
||||
======================================== */
|
||||
|
||||
/* ----------------------------------------
|
||||
INTER — self-hosted (no third-party font request)
|
||||
---------------------------------------- */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('../fonts/inter-400.woff2') format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url('../fonts/inter-500.woff2') format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('../fonts/inter-600.woff2') format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('../fonts/inter-700.woff2') format('woff2');
|
||||
}
|
||||
|
||||
/* ----------------------------------------
|
||||
THEME TOKENS — Light Mode (default)
|
||||
---------------------------------------- */
|
||||
@@ -2212,6 +2244,164 @@ button:focus:not(:focus-visible) {
|
||||
.toc-drawer { display: none !important; }
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
SEARCH OVERLAY
|
||||
======================================== */
|
||||
.search-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 2500;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
padding: 12vh 1rem 1rem;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.15s ease, visibility 0s linear 0.15s;
|
||||
}
|
||||
|
||||
.search-overlay.is-open {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
width: 100%;
|
||||
max-width: 560px;
|
||||
overflow: hidden;
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
padding: 1rem 1.2rem;
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
background: transparent;
|
||||
color: var(--text-primary);
|
||||
font-family: var(--font-sans);
|
||||
font-size: 1.05rem;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.search-input::placeholder { color: var(--text-muted); }
|
||||
|
||||
.search-results {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0.4rem;
|
||||
max-height: 50vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.search-result a {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.6rem;
|
||||
padding: 0.55rem 0.8rem;
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.search-result a:hover,
|
||||
.search-result.is-active a {
|
||||
background: var(--bg-hover);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.search-result-title { font-weight: 600; }
|
||||
.search-result-meta {
|
||||
margin-left: auto;
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--text-muted);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.search-empty { padding: 1rem 1.2rem; color: var(--text-muted); }
|
||||
|
||||
/* ========================================
|
||||
SERIES BOX
|
||||
======================================== */
|
||||
.series-box {
|
||||
margin: 0 0 var(--space-lg);
|
||||
padding: var(--space-sm) var(--space-md);
|
||||
border: 1px solid var(--border-color);
|
||||
border-left: 4px solid var(--accent);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--bg-subtle);
|
||||
}
|
||||
|
||||
.series-box-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
margin-bottom: 0.4rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.series-box-title .bd-icon { color: var(--accent); flex: 0 0 auto; }
|
||||
.series-list { margin: 0; padding-left: 1.5rem; }
|
||||
.series-item { padding: 0.12rem 0; color: var(--text-secondary); }
|
||||
.series-item a { color: var(--text-secondary); }
|
||||
.series-item a:hover { color: var(--accent); }
|
||||
.series-item.is-current span { color: var(--accent); font-weight: 600; }
|
||||
|
||||
/* ========================================
|
||||
CONTENT EXTRAS (kbd, mark, task lists, footnotes, external links)
|
||||
======================================== */
|
||||
.content kbd {
|
||||
display: inline-block;
|
||||
padding: 0.1em 0.45em;
|
||||
font-family: var(--font-sans);
|
||||
font-size: 0.8em;
|
||||
line-height: 1.4;
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-subtle);
|
||||
border: 1px solid var(--border-color);
|
||||
border-bottom-width: 2px;
|
||||
border-radius: 5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.content mark {
|
||||
padding: 0.05em 0.2em;
|
||||
border-radius: 3px;
|
||||
background: rgba(245, 200, 66, 0.35);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* Small ↗ after outbound links (JS adds target=_blank to external links) */
|
||||
.content a[target="_blank"]::after {
|
||||
content: "↗";
|
||||
margin-left: 0.15em;
|
||||
font-size: 0.8em;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Task lists (- [ ] / - [x]) — rendered by JS into real checkboxes */
|
||||
.content ul.task-list { list-style: none; padding-left: 0.2rem; }
|
||||
.content .task-list-item { display: flex; align-items: flex-start; gap: 0.5rem; }
|
||||
.content .task-list-item input[type="checkbox"] { margin-top: 0.35em; flex: 0 0 auto; }
|
||||
|
||||
/* Footnotes (when the parser emits them) */
|
||||
.content .footnotes {
|
||||
margin-top: var(--space-lg);
|
||||
padding-top: var(--space-md);
|
||||
border-top: 1px solid var(--border-subtle);
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* Definition lists */
|
||||
.content dt { font-weight: 700; }
|
||||
.content dd { margin: 0 0 0.6rem; color: var(--text-secondary); }
|
||||
|
||||
/* ========================================
|
||||
RESPONSIVE
|
||||
======================================== */
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -191,6 +191,7 @@
|
||||
// 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 = {
|
||||
@@ -203,7 +204,11 @@
|
||||
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')); ?>
|
||||
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
|
||||
|
||||
+152
-1
@@ -23,7 +23,10 @@
|
||||
prevImage: I18N.prevImage || 'Previous image',
|
||||
nextImage: I18N.nextImage || 'Next image',
|
||||
imageViewer: I18N.imageViewer || 'Image viewer',
|
||||
onThisPage: I18N.onThisPage || 'On this page'
|
||||
onThisPage: I18N.onThisPage || 'On this page',
|
||||
search: I18N.search || 'Search',
|
||||
searchPlaceholder: I18N.searchPlaceholder || 'Search posts…',
|
||||
noResults: I18N.noResults || 'No results'
|
||||
};
|
||||
|
||||
// Clipboard helper (Clipboard API with a legacy fallback).
|
||||
@@ -1144,4 +1147,152 @@
|
||||
});
|
||||
})();
|
||||
|
||||
/* --------------------------------------------------------
|
||||
Task lists — turn "- [ ] / - [x]" list items into real checkboxes.
|
||||
-------------------------------------------------------- */
|
||||
(function () {
|
||||
var content = document.querySelector('.content');
|
||||
if (!content) return;
|
||||
Array.prototype.forEach.call(content.querySelectorAll('li'), function (li) {
|
||||
var m = li.innerHTML.match(/^\s*\[([ xX])\]\s+/);
|
||||
if (!m) return;
|
||||
var checked = m[1].toLowerCase() === 'x';
|
||||
li.innerHTML = li.innerHTML.replace(m[0], '');
|
||||
li.classList.add('task-list-item');
|
||||
if (li.parentNode && li.parentNode.tagName === 'UL') {
|
||||
li.parentNode.classList.add('task-list');
|
||||
}
|
||||
var box = document.createElement('input');
|
||||
box.type = 'checkbox';
|
||||
box.disabled = true;
|
||||
box.checked = checked;
|
||||
li.insertBefore(box, li.firstChild);
|
||||
});
|
||||
})();
|
||||
|
||||
/* --------------------------------------------------------
|
||||
Search overlay — client-side fuzzy search over a JSON index of posts
|
||||
(embedded in the footer). Opened by the navbar button or the "/" key.
|
||||
-------------------------------------------------------- */
|
||||
(function () {
|
||||
var data = [];
|
||||
var el = document.getElementById('blowdit-search-index');
|
||||
if (el) { try { data = JSON.parse(el.textContent) || []; } catch (e) { data = []; } }
|
||||
|
||||
var overlay = document.createElement('div');
|
||||
overlay.className = 'search-overlay';
|
||||
overlay.innerHTML =
|
||||
'<div class="search-box" role="dialog" aria-modal="true" aria-label="' + T.search + '">' +
|
||||
'<input type="text" class="search-input" autocomplete="off" spellcheck="false" placeholder="' + T.searchPlaceholder + '" aria-label="' + T.search + '">' +
|
||||
'<ul class="search-results"></ul>' +
|
||||
'<div class="search-empty" hidden>' + T.noResults + '</div>' +
|
||||
'</div>';
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
var input = overlay.querySelector('.search-input');
|
||||
var results = overlay.querySelector('.search-results');
|
||||
var empty = overlay.querySelector('.search-empty');
|
||||
var active = -1;
|
||||
var current = [];
|
||||
|
||||
function open() {
|
||||
overlay.classList.add('is-open');
|
||||
document.body.style.overflow = 'hidden';
|
||||
input.value = '';
|
||||
render([]);
|
||||
setTimeout(function () { input.focus(); }, 30);
|
||||
}
|
||||
function close() {
|
||||
overlay.classList.remove('is-open');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
function isOpen() { return overlay.classList.contains('is-open'); }
|
||||
|
||||
function score(item, q) {
|
||||
var hay = (item.t + ' ' + (item.c || '') + ' ' + (item.g || []).join(' ') + ' ' + (item.d || '')).toLowerCase();
|
||||
return hay.indexOf(q) !== -1;
|
||||
}
|
||||
|
||||
function render(list) {
|
||||
current = list;
|
||||
active = list.length ? 0 : -1;
|
||||
results.innerHTML = '';
|
||||
empty.hidden = !(input.value.trim() && list.length === 0);
|
||||
list.forEach(function (item, i) {
|
||||
var li = document.createElement('li');
|
||||
li.className = 'search-result' + (i === 0 ? ' is-active' : '');
|
||||
li.innerHTML = '<a href="' + item.u + '"><span class="search-result-title"></span>' +
|
||||
(item.c ? '<span class="search-result-meta"></span>' : '') + '</a>';
|
||||
li.querySelector('.search-result-title').textContent = item.t;
|
||||
if (item.c) li.querySelector('.search-result-meta').textContent = item.c;
|
||||
results.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
function update() {
|
||||
var q = input.value.trim().toLowerCase();
|
||||
if (!q) { render([]); return; }
|
||||
render(data.filter(function (item) { return score(item, q); }).slice(0, 8));
|
||||
}
|
||||
|
||||
function move(delta) {
|
||||
var items = results.querySelectorAll('.search-result');
|
||||
if (!items.length) return;
|
||||
items[active] && items[active].classList.remove('is-active');
|
||||
active = (active + delta + items.length) % items.length;
|
||||
items[active].classList.add('is-active');
|
||||
items[active].scrollIntoView({ block: 'nearest' });
|
||||
}
|
||||
|
||||
function go() {
|
||||
var items = results.querySelectorAll('.search-result');
|
||||
if (items[active]) { var a = items[active].querySelector('a'); if (a) window.location.href = a.href; }
|
||||
}
|
||||
|
||||
input.addEventListener('input', update);
|
||||
input.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'ArrowDown') { e.preventDefault(); move(1); }
|
||||
else if (e.key === 'ArrowUp') { e.preventDefault(); move(-1); }
|
||||
else if (e.key === 'Enter') { e.preventDefault(); go(); }
|
||||
});
|
||||
overlay.addEventListener('click', function (e) { if (e.target === overlay) close(); });
|
||||
|
||||
Array.prototype.forEach.call(document.querySelectorAll('.js-search-open'), function (b) {
|
||||
b.addEventListener('click', function (e) { e.preventDefault(); open(); });
|
||||
});
|
||||
|
||||
// Expose for the keyboard-shortcuts handler.
|
||||
window.BLOWDIT_SEARCH = { open: open, close: close, isOpen: isOpen };
|
||||
})();
|
||||
|
||||
/* --------------------------------------------------------
|
||||
Keyboard shortcuts: / search · g h home · t top · Esc close.
|
||||
-------------------------------------------------------- */
|
||||
(function () {
|
||||
var lastG = 0;
|
||||
function typing(t) {
|
||||
return t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable);
|
||||
}
|
||||
document.addEventListener('keydown', function (e) {
|
||||
var s = window.BLOWDIT_SEARCH;
|
||||
if (e.key === 'Escape' || e.key === 'Esc') {
|
||||
if (s && s.isOpen()) s.close();
|
||||
return;
|
||||
}
|
||||
if (typing(e.target) || e.metaKey || e.ctrlKey || e.altKey) return;
|
||||
|
||||
if (e.key === '/') { e.preventDefault(); if (s) s.open(); return; }
|
||||
if (e.key === 't' || e.key === 'T') {
|
||||
var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
window.scrollTo({ top: 0, behavior: reduce ? 'auto' : 'smooth' });
|
||||
return;
|
||||
}
|
||||
// "g" then "h" -> home
|
||||
if (e.key === 'g' || e.key === 'G') { lastG = Date.now(); return; }
|
||||
if ((e.key === 'h' || e.key === 'H') && Date.now() - lastG < 600) {
|
||||
window.location.href = (window.BLOWDIT_HOME || '/');
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
})();
|
||||
@@ -18,6 +18,10 @@
|
||||
"Copy link": "Copy link",
|
||||
"Link copied": "Link copied",
|
||||
"Related posts": "Related posts",
|
||||
"Search": "Search",
|
||||
"Search posts…": "Search posts…",
|
||||
"No results": "No results",
|
||||
"Part of a series": "Part of a series",
|
||||
"Home": "Home",
|
||||
"On this page": "On this page",
|
||||
"Page not found": "Page not found",
|
||||
|
||||
+3
-3
@@ -2,9 +2,9 @@
|
||||
"author": "spleenftw",
|
||||
"email": "",
|
||||
"website": "https://github.com/Spleenftw/blowdit",
|
||||
"version": "1.5.0",
|
||||
"releaseDate": "2026-06-02",
|
||||
"version": "1.6.0",
|
||||
"releaseDate": "2026-06-03",
|
||||
"license": "MIT",
|
||||
"compatible": "3.22.0",
|
||||
"notes": "A Bludit theme inspired by the Blowfish Hugo theme. Five-theme swatch picker (Light/Dark/Nord/Dracula/Catppuccin), sticky ToC sidebar on articles, image lightbox with zoom, image carousel (```carousel fence), tabbed code blocks (```tabs fence), smart sidebar per page type, and clean responsive typography. v1.3.0: SEO (Open Graph, Twitter Card, JSON-LD, theme-color, cover preload), per-theme syntax highlighting (self-hosted highlight.js), copy-to-clipboard on code blocks, numbered pagination, inline-SVG icons (dropped the Bootstrap Icons webfont), removed jQuery/Bootstrap JS, deferred cached JS bundle, reading progress bar, back-to-top, randomised sidebar selection, lazy-loaded images, accessibility (skip link, ARIA, :focus-visible) and a print stylesheet. v1.4.0: canonical URL, WebSite + BreadcrumbList + author sameAs JSON-LD, og:image:alt / twitter:site+creator, cover-image aspect-ratio (CLS), heading anchor links, prev/next article nav, related posts, share buttons (X/LinkedIn/copy-link), mailto social icon, PWA web app manifest, 'updated on' date, and outbound-link hardening (noopener noreferrer). v1.5.0: callout/admonition boxes (> [!NOTE]/[!TIP]/[!WARNING]/...), code-block header bar with language label + copy button, visual breadcrumbs, custom 404 page, image figure/figcaption from the markdown title, a mobile table-of-contents drawer, and third-party resource hints."
|
||||
"notes": "A Bludit theme inspired by the Blowfish Hugo theme. Five-theme swatch picker (Light/Dark/Nord/Dracula/Catppuccin), sticky ToC sidebar on articles, image lightbox with zoom, image carousel (```carousel fence), tabbed code blocks (```tabs fence), smart sidebar per page type, and clean responsive typography. v1.3.0: SEO (Open Graph, Twitter Card, JSON-LD, theme-color, cover preload), per-theme syntax highlighting (self-hosted highlight.js), copy-to-clipboard on code blocks, numbered pagination, inline-SVG icons (dropped the Bootstrap Icons webfont), removed jQuery/Bootstrap JS, deferred cached JS bundle, reading progress bar, back-to-top, randomised sidebar selection, lazy-loaded images, accessibility (skip link, ARIA, :focus-visible) and a print stylesheet. v1.4.0: canonical URL, WebSite + BreadcrumbList + author sameAs JSON-LD, og:image:alt / twitter:site+creator, cover-image aspect-ratio (CLS), heading anchor links, prev/next article nav, related posts, share buttons (X/LinkedIn/copy-link), mailto social icon, PWA web app manifest, 'updated on' date, and outbound-link hardening (noopener noreferrer). v1.5.0: callout/admonition boxes (> [!NOTE]/[!TIP]/[!WARNING]/...), code-block header bar with language label + copy button, visual breadcrumbs, custom 404 page, image figure/figcaption from the markdown title, a mobile table-of-contents drawer, and third-party resource hints. v1.6.0: self-hosted Inter (no Google Fonts), client-side search overlay (\"/\" key + navbar button), keyboard shortcuts (/ , g h, t, Esc), a tabbed-code copy button, series navigation (series-* tag), and content typography (keycaps, task-list checkboxes, <mark>, external-link icon, footnote/definition-list styling)."
|
||||
}
|
||||
@@ -18,3 +18,28 @@
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<?php
|
||||
// Client-side search index (all published posts) for the search overlay.
|
||||
// Guarded so any Pages-API difference degrades to an empty index, not an error.
|
||||
try {
|
||||
global $pages;
|
||||
$bdSearchIndex = array();
|
||||
if (isset($pages) && method_exists($pages, 'getList')) {
|
||||
foreach ($pages->getList(1, 1000, true) as $bdSk) {
|
||||
$bdSp = new Page($bdSk);
|
||||
if ($bdSp->isStatic()) { continue; } // posts only
|
||||
$bdSearchIndex[] = array(
|
||||
't' => blowdit_plain($bdSp->title()),
|
||||
'u' => $bdSp->permalink(),
|
||||
'd' => blowdit_plain($bdSp->description()),
|
||||
'c' => blowdit_plain($bdSp->category()),
|
||||
'g' => array_values(array_map('blowdit_plain', $bdSp->tags(true))),
|
||||
);
|
||||
}
|
||||
}
|
||||
echo '<script type="application/json" id="blowdit-search-index">'
|
||||
. json_encode($bdSearchIndex, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
|
||||
. '</script>';
|
||||
} catch (\Throwable $e) {}
|
||||
?>
|
||||
+3
-6
@@ -244,12 +244,9 @@
|
||||
<link rel="manifest" href="<?php echo DOMAIN_THEME . 'manifest.webmanifest'; ?>">
|
||||
<link rel="apple-touch-icon" href="<?php echo DOMAIN_THEME . 'img/favicon.png'; ?>">
|
||||
|
||||
<!-- Inter typeface (Blowfish-like typography) — loaded non-render-blocking:
|
||||
fetched as print media, then flipped to all once it arrives. -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" media="print" onload="this.media='all'">
|
||||
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"></noscript>
|
||||
<!-- Inter typeface — self-hosted (see @font-face in style.css). No third-party
|
||||
request; preload the body weight for a faster first text paint. -->
|
||||
<link rel="preload" as="font" type="font/woff2" href="<?php echo DOMAIN_THEME . 'fonts/inter-400.woff2'; ?>" crossorigin>
|
||||
|
||||
<!-- Include Bootstrap CSS file bootstrap.css -->
|
||||
<?php echo Theme::cssBootstrap(); ?>
|
||||
|
||||
@@ -21,6 +21,7 @@ if (!function_exists('blowdit_icon')) {
|
||||
'chevron-right' => '<path d="M9 18l6-6-6-6"/>',
|
||||
'house' => '<path d="M3 10.5 12 3l9 7.5"/><path d="M5 9.5V21h14V9.5"/>',
|
||||
'link' => '<path d="M9 17H7A5 5 0 0 1 7 7h2"/><path d="M15 7h2a5 5 0 0 1 0 10h-2"/><path d="M8 12h8"/>',
|
||||
'search' => '<circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/>',
|
||||
'share' => '<circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><path d="M8.6 13.5l6.8 4M15.4 6.5l-6.8 4"/>',
|
||||
// --- Theme picker (mirror in blowdit.js) ---
|
||||
'sun' => '<circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/>',
|
||||
|
||||
@@ -59,6 +59,13 @@
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Search (opens the overlay; also bound to the "/" key) -->
|
||||
<li class="nav-item ml-md-2 mt-2 mt-md-0">
|
||||
<button type="button" class="theme-toggle js-search-open" aria-label="<?php echo $L->get('Search'); ?>" title="<?php echo $L->get('Search'); ?> (/)">
|
||||
<?php echo blowdit_icon('search'); ?>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<!-- Theme picker -->
|
||||
<li class="nav-item ml-md-2 mt-2 mt-md-0 theme-picker-wrap">
|
||||
<button type="button" id="theme-toggle" class="theme-toggle" aria-label="<?php echo $L->get('Toggle theme') ?>" title="<?php echo $L->get('Toggle theme') ?>" aria-haspopup="true" aria-expanded="false">
|
||||
|
||||
@@ -54,6 +54,43 @@
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<!-- Series box: posts sharing a tag whose key starts with "series-"
|
||||
(e.g. tag "Series: Homelab" -> key "series-homelab"), ordered oldest-first. -->
|
||||
<?php if (!$page->isStatic() && !$url->notFound()) :
|
||||
$bdSeriesTag = ''; $bdSeriesName = '';
|
||||
foreach ($page->tags(true) as $bdTk => $bdTn) {
|
||||
if (preg_match('/^series[-_]/i', $bdTk)) { $bdSeriesTag = $bdTk; $bdSeriesName = $bdTn; break; }
|
||||
}
|
||||
$bdSeriesPosts = array();
|
||||
if ($bdSeriesTag) {
|
||||
try {
|
||||
global $pages;
|
||||
if (isset($pages) && method_exists($pages, 'getList')) {
|
||||
foreach ($pages->getList(1, 1000, true) as $bdSk) {
|
||||
$bdSp = new Page($bdSk);
|
||||
if (in_array($bdSeriesTag, array_keys($bdSp->tags(true)), true)) { $bdSeriesPosts[] = $bdSp; }
|
||||
}
|
||||
usort($bdSeriesPosts, function ($a, $b) { return strcmp($a->getValue('date'), $b->getValue('date')); });
|
||||
}
|
||||
} catch (\Throwable $e) { $bdSeriesPosts = array(); }
|
||||
}
|
||||
$bdSeriesLabel = trim(preg_replace('/^series\s*[:\-_]?\s*/i', '', blowdit_plain($bdSeriesName)));
|
||||
?>
|
||||
<?php if (count($bdSeriesPosts) > 1) : ?>
|
||||
<aside class="series-box">
|
||||
<div class="series-box-title"><?php echo blowdit_icon('folder'); ?><span><?php echo $L->get('Part of a series'); ?><?php echo $bdSeriesLabel !== '' ? ': ' . htmlspecialchars($bdSeriesLabel, ENT_QUOTES, 'UTF-8') : ''; ?></span></div>
|
||||
<ol class="series-list">
|
||||
<?php foreach ($bdSeriesPosts as $bdSp) : $bdIsCur = ($bdSp->key() === $page->key()); ?>
|
||||
<li class="series-item<?php echo $bdIsCur ? ' is-current' : ''; ?>">
|
||||
<?php if ($bdIsCur) : ?><span><?php echo blowdit_text($bdSp->title()); ?></span>
|
||||
<?php else : ?><a href="<?php echo $bdSp->permalink(); ?>"><?php echo blowdit_text($bdSp->title()); ?></a><?php endif ?>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ol>
|
||||
</aside>
|
||||
<?php endif ?>
|
||||
<?php endif ?>
|
||||
|
||||
<!-- Full content -->
|
||||
<div class="content">
|
||||
<?php
|
||||
|
||||
Reference in new issue
Block a user