Plugin sidebar order + directory layout from index.md

- PluginManager: iterate allowedPlugins in user-defined order (frontmatter order)
- content-edit.twig: plugin list with up/down arrows to set order
- Directory listing: read layout/plugins from index.md if present
- Directory default layout: full_content (no sidebar) unless index.md says otherwise
This commit is contained in:
2026-08-11 17:04:27 +02:00
parent 342112cd21
commit 03a14126ca
4 changed files with 143 additions and 33 deletions
@@ -29,13 +29,33 @@
</div>
{% if availablePlugins is not empty %}
<div class="col-md-4">
<label class="form-label d-block">Zichtbare plugins</label>
<div class="d-flex flex-wrap gap-1">
{% for plugin in availablePlugins %}
<input type="checkbox" class="btn-check" id="plugin-{{ plugin }}" name="plugins[]" value="{{ plugin }}" autocomplete="off" {{ plugin in selectedPlugins ? 'checked' : '' }}>
<label class="btn btn-outline-primary btn-sm" for="plugin-{{ plugin }}">{{ plugin }}</label>
<label class="form-label d-block">Zichtbare plugins (volgorde = sidebar volgorde)</label>
<ul class="list-group list-group-flush" id="plugin-order-list">
{% for plugin in selectedPlugins %}
<li class="list-group-item d-flex align-items-center justify-content-between px-2 py-1" data-plugin="{{ plugin }}">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="plugin-{{ plugin }}" name="plugins[]" value="{{ plugin }}" autocomplete="off" checked>
<label class="form-check-label" for="plugin-{{ plugin }}">{{ plugin }}</label>
</div>
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-outline-secondary py-0 px-1 plugin-up" title="Omhoog"><i class="bi bi-arrow-up"></i></button>
<button type="button" class="btn btn-outline-secondary py-0 px-1 plugin-down" title="Omlaag"><i class="bi bi-arrow-down"></i></button>
</div>
</li>
{% endfor %}
</div>
{% for plugin in availablePlugins if plugin not in selectedPlugins %}
<li class="list-group-item d-flex align-items-center justify-content-between px-2 py-1" data-plugin="{{ plugin }}">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="plugin-{{ plugin }}" name="plugins[]" value="{{ plugin }}" autocomplete="off">
<label class="form-check-label" for="plugin-{{ plugin }}">{{ plugin }}</label>
</div>
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-outline-secondary py-0 px-1 plugin-up" title="Omhoog"><i class="bi bi-arrow-up"></i></button>
<button type="button" class="btn btn-outline-secondary py-0 px-1 plugin-down" title="Omlaag"><i class="bi bi-arrow-down"></i></button>
</div>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% endif %}
@@ -119,6 +139,36 @@ document.addEventListener('DOMContentLoaded', function () {
}
window.__onContentChange = markChanged;
// Plugin order: move list items up/down
var pluginList = document.getElementById('plugin-order-list');
if (pluginList) {
pluginList.addEventListener('click', function (e) {
var upBtn = e.target.closest('.plugin-up');
var downBtn = e.target.closest('.plugin-down');
if (!upBtn && !downBtn) return;
var item = e.target.closest('li[data-plugin]');
if (!item) return;
if (upBtn && item.previousElementSibling) {
pluginList.insertBefore(item, item.previousElementSibling);
markChanged();
} else if (downBtn && item.nextElementSibling) {
pluginList.insertBefore(item, item.nextElementSibling.nextElementSibling);
markChanged();
}
});
// On form submit, reorder the checkboxes to match the visual order
var form = document.getElementById('editor-form');
if (form) {
form.addEventListener('submit', function () {
// The DOM order of the <li> elements already determines the form submission order
// because the checkboxes are inside the <li> elements
});
}
}
});
</script>
{% endblock %}
+14 -1
View File
@@ -1141,9 +1141,22 @@ class CodePressCMS {
$content .= '<p>' . $this->t('directory_empty') . '.</p>';
}
// Check for index.md in this directory for layout/plugins metadata
$indexFile = $dirPath . '/index.md';
$layout = 'full_content';
$metadata = [];
if (file_exists($indexFile)) {
$indexContent = file_get_contents($indexFile);
$parsed = $this->parseMetadata($indexContent);
$metadata = $parsed['metadata'];
$layout = $metadata['layout'] ?? 'full_content';
}
return [
'title' => $title,
'content' => $content
'content' => $content,
'layout' => $layout,
'metadata' => $metadata,
];
}
+46 -18
View File
@@ -141,27 +141,26 @@ class PluginManager
{
$sidebarContent = '';
foreach ($this->plugins as $pluginName => $plugin) {
if (!$this->isPluginViewable($plugin) || !method_exists($plugin, 'getSidebarContent')) {
continue;
}
// If allowedPlugins is specified, iterate in that order (user-defined order)
if ($allowedPlugins !== null) {
foreach ($allowedPlugins as $pluginName) {
$plugin = $this->plugins[$pluginName] ?? null;
if (!$plugin || !$this->isPluginViewable($plugin) || !method_exists($plugin, 'getSidebarContent')) {
continue;
}
if ($allowedPlugins !== null && !in_array($pluginName, $allowedPlugins, true)) {
continue;
}
$content = $plugin->getSidebarContent();
if (trim($content) === '') {
continue;
}
$content = $plugin->getSidebarContent();
if (trim($content) === '') {
continue;
}
$title = 'Plugin';
if (method_exists($plugin, 'getConfig')) {
$config = $plugin->getConfig();
$title = $config['title'] ?? 'Plugin';
}
$title = 'Plugin';
if (method_exists($plugin, 'getConfig')) {
$config = $plugin->getConfig();
$title = $config['title'] ?? 'Plugin';
}
$sidebarContent .= '
$sidebarContent .= '
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0">' . htmlspecialchars($title) . '</h5>
@@ -170,6 +169,35 @@ class PluginManager
' . $content . '
</div>
</div>';
}
} else {
// No filter — show all viewable plugins in load order
foreach ($this->plugins as $pluginName => $plugin) {
if (!$this->isPluginViewable($plugin) || !method_exists($plugin, 'getSidebarContent')) {
continue;
}
$content = $plugin->getSidebarContent();
if (trim($content) === '') {
continue;
}
$title = 'Plugin';
if (method_exists($plugin, 'getConfig')) {
$config = $plugin->getConfig();
$title = $config['title'] ?? 'Plugin';
}
$sidebarContent .= '
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0">' . htmlspecialchars($title) . '</h5>
</div>
<div class="card-body">
' . $content . '
</div>
</div>';
}
}
return $sidebarContent;
+22 -3
View File
@@ -22,6 +22,8 @@ class HTMLBlock
$currentPage = $this->api ? $this->api->getCurrentPageTitle() : 'Onbekend';
$isHomepage = $this->api ? $this->api->isHomepage() : false;
$currentLang = $this->api ? $this->api->getCurrentLanguage() : 'nl';
$currentPath = $_GET['page'] ?? '';
$currentPath = preg_replace('/\.(md|php|html)$/', '', $currentPath);
$content = '
<p class="mb-2"><strong>Huidige pagina:</strong> ' . htmlspecialchars($currentPage) . '</p>
@@ -42,7 +44,7 @@ class HTMLBlock
</div>';
}
// Add quick navigation
// Add dynamic quick navigation
$menu = $this->api->getMenu();
if (!empty($menu)) {
$content .= '
@@ -51,8 +53,25 @@ class HTMLBlock
foreach ($menu as $item) {
if ($item['type'] === 'file') {
$url = $this->api->createUrl($item['path']);
$content .= '<li><a href="' . htmlspecialchars($url) . '" class="text-decoration-none">📄 ' . htmlspecialchars($item['title']) . '</a></li>';
$url = '/' . $currentLang . '/' . $item['path'];
$isActive = ($item['path'] === $currentPath) ? ' fw-bold' : '';
$content .= '<li><a href="' . htmlspecialchars($url, ENT_QUOTES, 'UTF-8') . '" class="text-decoration-none' . $isActive . '"><i class="bi bi-file-earmark"></i> ' . htmlspecialchars($item['title'], ENT_QUOTES, 'UTF-8') . '</a></li>';
} elseif ($item['type'] === 'folder' && !empty($item['children'])) {
$url = '/' . $currentLang . '/' . $item['path'];
$isActive = strpos($currentPath, $item['path']) === 0 ? ' fw-bold' : '';
$content .= '<li><a href="' . htmlspecialchars($url, ENT_QUOTES, 'UTF-8') . '" class="text-decoration-none' . $isActive . '"><i class="bi bi-folder"></i> ' . htmlspecialchars($item['title'], ENT_QUOTES, 'UTF-8') . '</a></li>';
// Show children if current page is in this folder
if (strpos($currentPath, $item['path']) === 0) {
$content .= '<ul class="list-unstyled ms-3 mb-1">';
foreach ($item['children'] as $child) {
if ($child['type'] === 'file') {
$childUrl = '/' . $currentLang . '/' . $child['path'];
$childActive = ($child['path'] === $currentPath) ? ' fw-bold' : '';
$content .= '<li><a href="' . htmlspecialchars($childUrl, ENT_QUOTES, 'UTF-8') . '" class="text-decoration-none' . $childActive . '"><i class="bi bi-file-earmark"></i> ' . htmlspecialchars($child['title'], ENT_QUOTES, 'UTF-8') . '</a></li>';
}
}
$content .= '</ul>';
}
}
}