- Reorganize admin into admin/theme/default/ (views + assets) - Rename GuideNav to Navigation plugin (essential, protected) - Plugin assets support (SCSS/CSS) loaded after theme CSS - User roles: Admin, Content Manager, BI Manager, Site Admin - Role-based access control (RBAC) for admin routes and sidebar - Guide restructure: sub-topics in separate folders with sidebar nav - Dynamic breadcrumb for homepage and subdirectories - Fix theme path traversal (../../ -> ../) in admin.php - Fix CodeMirror mode load order (xml -> css -> js -> htmlmixed -> php) - Fix editor-toolbar.js null checks for plugin edit pages - Layout select from theme.json with live frontmatter update - Footer sticky at bottom of viewport (min-height: 100vh) - Breadcrumb color fix (var(--nav-font) -> var(--header-bg)) - Remove language switcher from guide pages - Update README.md and README.en.md - Bump version to 2.5.1
210 lines
11 KiB
Twig
210 lines
11 KiB
Twig
{% extends "layouts/admin.twig" %}
|
|
|
|
{% block title %}Content - CodePress Admin{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h2><i class="bi bi-file-earmark-text"></i> Content</h2>
|
|
<div>
|
|
<button type="button" class="btn btn-outline-success btn-sm me-1" data-bs-toggle="collapse" data-bs-target="#uploadForm">
|
|
<i class="bi bi-cloud-upload"></i> Upload
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary btn-sm me-1" data-bs-toggle="modal" data-bs-target="#createDirModal">
|
|
<i class="bi bi-folder-plus"></i> Nieuwe map
|
|
</button>
|
|
<a href="/admin/content-new?dir={{ subdir|url_encode }}" class="btn btn-primary btn-sm">
|
|
<i class="bi bi-plus-lg"></i> Nieuw bestand
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="collapse mb-4" id="uploadForm">
|
|
<div class="card shadow-sm">
|
|
<div class="card-body">
|
|
<form method="POST" action="/admin/content?dir={{ subdir|url_encode }}" enctype="multipart/form-data">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<div class="mb-3">
|
|
<label for="file" class="form-label">Bestanden selecteren</label>
|
|
<input type="file" class="form-control" id="file" name="file[]" multiple accept="image/jpeg,image/png,image/gif,image/webp,image/svg+xml,application/pdf,application/zip,video/mp4,video/webm,audio/mpeg,audio/wav">
|
|
<small class="form-text text-muted">Toegestaan: JPG, PNG, GIF, WebP, SVG, PDF, ZIP, MP4, WebM, MP3, WAV</small>
|
|
</div>
|
|
<button type="submit" class="btn btn-success">
|
|
<i class="bi bi-cloud-upload"></i> Uploaden naar deze map
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% if subdir %}
|
|
{% set parentDir = subdir|split('/')|slice(0, -1)|join('/') %}
|
|
<nav aria-label="breadcrumb" class="mb-3">
|
|
<ol class="breadcrumb">
|
|
<li class="breadcrumb-item"><a href="/admin/content"><i class="bi bi-house"></i></a></li>
|
|
{% set crumbPath = '' %}
|
|
{% for crumb in subdir|split('/') %}
|
|
{% set crumbPath = crumbPath ? crumbPath ~ '/' ~ crumb : crumb %}
|
|
<li class="breadcrumb-item {{ crumbPath == subdir ? 'active' : '' }}">
|
|
{% if crumbPath == subdir %}
|
|
{{ crumb }}
|
|
{% else %}
|
|
<a href="/admin/content?dir={{ crumbPath|url_encode }}">{{ crumb }}</a>
|
|
{% endif %}
|
|
</li>
|
|
{% endfor %}
|
|
</ol>
|
|
</nav>
|
|
{% endif %}
|
|
|
|
<div class="card shadow-sm">
|
|
<div class="card-header bg-white py-2">
|
|
<div class="input-group input-group-sm">
|
|
<span class="input-group-text bg-white border-end-0"><i class="bi bi-search text-muted"></i></span>
|
|
<input type="search" id="contentFilter" class="form-control border-start-0" placeholder="Filter op bestands- of mapnaam…" autocomplete="off" aria-label="Filter content">
|
|
<span class="input-group-text bg-white text-muted" id="contentFilterCount"></span>
|
|
</div>
|
|
</div>
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>Naam</th>
|
|
<th>Type</th>
|
|
<th>Grootte</th>
|
|
<th>Gewijzigd</th>
|
|
<th style="width: 200px;">Acties</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% if items is empty %}
|
|
<tr><td colspan="5" class="text-muted text-center py-4">Geen bestanden gevonden.</td></tr>
|
|
{% else %}
|
|
{% for item in items %}
|
|
<tr data-name="{{ item.name|lower }}">
|
|
<td>
|
|
{% if item.is_dir %}
|
|
<a href="/admin/content?dir={{ item.path|url_encode }}">
|
|
<i class="bi bi-folder-fill text-warning"></i> {{ item.name }}
|
|
</a>
|
|
{% else %}
|
|
<a href="/admin/content-edit?file={{ item.path|url_encode }}">
|
|
{% set icon = item.extension == 'md' ? 'bi-file-text text-primary' : (item.extension == 'php' ? 'bi-file-code text-success' : (item.extension == 'html' ? 'bi-file-earmark text-info' : 'bi-file text-muted')) %}
|
|
<i class="bi {{ icon }}"></i> {{ item.name }}
|
|
</a>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if item.is_dir %}
|
|
<span class="badge bg-warning text-dark">Map</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary">{{ item.extension|upper }}</span>
|
|
{% endif %}
|
|
</td>
|
|
<td class="text-muted">{{ item.size }}</td>
|
|
<td class="text-muted">{{ item.modified }}</td>
|
|
<td>
|
|
{% if item.is_dir %}
|
|
<a href="/admin/content-dir-rename?dir={{ item.path|url_encode }}" class="btn btn-sm btn-outline-secondary" title="Hernoemen">
|
|
<i class="bi bi-pencil"></i>
|
|
</a>
|
|
<a href="/admin/content-move?item={{ item.path|url_encode }}" class="btn btn-sm btn-outline-info" title="Verplaatsen">
|
|
<i class="bi bi-arrows-move"></i>
|
|
</a>
|
|
<form method="POST" action="/admin/content-dir-delete?dir={{ item.path|url_encode }}" class="d-inline" onsubmit="return confirm('Weet je zeker dat je deze map wilt verwijderen? De map moet leeg zijn.')">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<button type="submit" class="btn btn-sm btn-outline-danger" title="Verwijderen">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</form>
|
|
{% else %}
|
|
<a href="/admin/content-edit?file={{ item.path|url_encode }}" class="btn btn-sm btn-outline-primary" title="Bewerken">
|
|
<i class="bi bi-pencil"></i>
|
|
</a>
|
|
<a href="/admin/content-move?item={{ item.path|url_encode }}" class="btn btn-sm btn-outline-info" title="Verplaatsen">
|
|
<i class="bi bi-arrows-move"></i>
|
|
</a>
|
|
<form method="POST" action="/admin/content-delete?file={{ item.path|url_encode }}" class="d-inline" onsubmit="return confirm('Weet je zeker dat je dit bestand wilt verwijderen?')">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<button type="submit" class="btn btn-sm btn-outline-danger" title="Verwijderen">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</form>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
{% endif %}
|
|
<tr id="contentFilterEmpty" class="d-none">
|
|
<td colspan="5" class="text-muted text-center py-4">Geen resultaten voor deze filter.</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
var input = document.getElementById('contentFilter');
|
|
var counter = document.getElementById('contentFilterCount');
|
|
var emptyRow = document.getElementById('contentFilterEmpty');
|
|
if (!input) return;
|
|
|
|
var rows = Array.prototype.slice.call(document.querySelectorAll('tbody tr[data-name]'));
|
|
|
|
function apply() {
|
|
var q = input.value.trim().toLowerCase();
|
|
var shown = 0;
|
|
|
|
rows.forEach(function (row) {
|
|
var match = q === '' || row.getAttribute('data-name').indexOf(q) !== -1;
|
|
row.classList.toggle('d-none', !match);
|
|
if (match) shown++;
|
|
});
|
|
|
|
if (emptyRow) {
|
|
emptyRow.classList.toggle('d-none', shown > 0 || rows.length === 0);
|
|
}
|
|
if (counter) {
|
|
counter.textContent = q === '' ? rows.length + ' items' : shown + ' van ' + rows.length;
|
|
}
|
|
}
|
|
|
|
input.addEventListener('input', apply);
|
|
input.addEventListener('keydown', function (e) {
|
|
if (e.key === 'Escape') {
|
|
input.value = '';
|
|
apply();
|
|
}
|
|
});
|
|
|
|
apply();
|
|
})();
|
|
</script>
|
|
|
|
<!-- Create Directory Modal -->
|
|
<div class="modal fade" id="createDirModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<form method="POST" action="/admin/content-dir-create?dir={{ subdir|url_encode }}">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title"><i class="bi bi-folder-plus"></i> Nieuwe map aanmaken</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label for="dirname" class="form-label">Mapnaam</label>
|
|
<input type="text" class="form-control" id="dirname" name="dirname" required autofocus>
|
|
<div class="form-text">Alleen letters, cijfers, punten, underscores en streepjes.</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuleren</button>
|
|
<button type="submit" class="btn btn-primary"><i class="bi bi-check-lg"></i> Aanmaken</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|