Media browser: recursive scan entire content/ tree, /-media/ URL prefix, editor change detection fix
- handleMediaList() now scans content/ recursively for all media files
- URLs use /-media/ prefix mapping directly to content/ (no special cases)
- index.php: added /-media/ route, kept /-assets/ for backward compat
- editor-toolbar.js: fixed editor.on('change') placement (was inside switchMode)
- content-edit.php and content-new.php: back-btn unsaved-changes detection
- Removed unused __editorCleanup global
This commit is contained in:
@@ -1,13 +1,8 @@
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2><i class="bi bi-plus-lg"></i> Nieuwe pagina</h2>
|
||||
<a href="admin.php?route=content&dir=<?= urlencode($dir ?? '') ?>" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="bi bi-arrow-left"></i> Terug
|
||||
</a>
|
||||
</div>
|
||||
<h2 class="mb-4"><i class="bi bi-plus-lg"></i> Nieuwe pagina</h2>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
<form method="POST" action="admin.php?route=content-new&dir=<?= urlencode($dir ?? '') ?>">
|
||||
<form method="POST" action="admin.php?route=content-new&dir=<?= urlencode($dir ?? '') ?>" id="editor-form" data-new-page>
|
||||
<input type="hidden" name="csrf_token" value="<?= $csrf ?>">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8">
|
||||
@@ -17,7 +12,7 @@
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="type" class="form-label">Type</label>
|
||||
<select class="form-select" id="type" name="type">
|
||||
<select class="form-select" id="type" name="type" data-editor-mode>
|
||||
<option value="md" selected>Markdown (.md)</option>
|
||||
<option value="php">PHP (.php)</option>
|
||||
<option value="html">HTML (.html)</option>
|
||||
@@ -29,18 +24,286 @@
|
||||
<small class="text-muted">Map: <?= htmlspecialchars($dir) ?></small>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="mb-3">
|
||||
<label for="content" class="form-label">Inhoud</label>
|
||||
<textarea name="content" id="content" class="form-control font-monospace" rows="20" style="font-size: 0.9rem; tab-size: 4;" placeholder="# Mijn nieuwe pagina
|
||||
|
||||
Schrijf hier je inhoud..."></textarea>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label for="layout" class="form-label">Sjabloon / Layout</label>
|
||||
<select class="form-select" id="layout" name="layout">
|
||||
<option value="sidebar-content">Sidebar + Inhoud (standaard)</option>
|
||||
<option value="content">Alleen inhoud (full-width)</option>
|
||||
<option value="sidebar">Alleen sidebar (full-width)</option>
|
||||
<option value="content-sidebar">Inhoud links + sidebar rechts</option>
|
||||
<option value="content-sidebar-reverse">Inhoud rechts + sidebar links</option>
|
||||
</select>
|
||||
</div>
|
||||
<?php if (!empty($availablePlugins)): ?>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label d-block">Zichtbare plugins</label>
|
||||
<div class="d-flex flex-wrap gap-1">
|
||||
<?php foreach ($availablePlugins as $plugin): ?>
|
||||
<input type="checkbox" class="btn-check" id="plugin-<?= $plugin ?>" name="plugins[]" value="<?= htmlspecialchars($plugin) ?>" autocomplete="off" checked>
|
||||
<label class="btn btn-outline-primary btn-sm" for="plugin-<?= $plugin ?>"><?= htmlspecialchars($plugin) ?></label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<div class="editor-toolbar" id="editor-toolbar"></div>
|
||||
<div class="editor-wrapper">
|
||||
<textarea name="content" id="editor-textarea" data-ext="md"></textarea>
|
||||
</div>
|
||||
<div class="d-flex gap-2 mt-3">
|
||||
<button type="submit" class="btn btn-primary" id="content-create-btn" disabled>
|
||||
<i class="bi bi-check-lg"></i> Aanmaken
|
||||
</button>
|
||||
<a href="admin.php?route=content&dir=<?= urlencode($dir ?? '') ?>" class="btn btn-outline-secondary">Annuleren</a>
|
||||
<a href="admin.php?route=content&dir=<?= urlencode($dir ?? '') ?>" class="btn btn-outline-secondary" id="back-btn">Terug</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Media Modal -->
|
||||
<div class="modal fade" id="mediaModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title"><i class="bi bi-images"></i> Media</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="collapse mb-3" id="mediaUploadForm">
|
||||
<div class="card card-body">
|
||||
<form id="media-upload-form" enctype="multipart/form-data">
|
||||
<input type="hidden" name="csrf_token" value="<?= $csrf ?>">
|
||||
<div class="mb-2">
|
||||
<input type="file" class="form-control" name="file[]" multiple accept="image/jpeg,image/png,image/gif,image/webp,image/svg+xml,video/mp4,video/webm,audio/mpeg,audio/wav" id="media-file-input">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success btn-sm" id="media-upload-btn" disabled>
|
||||
<i class="bi bi-cloud-upload"></i> Uploaden
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<button type="button" class="btn btn-outline-primary btn-sm" data-bs-toggle="collapse" data-bs-target="#mediaUploadForm">
|
||||
<i class="bi bi-cloud-upload"></i> Uploaden
|
||||
</button>
|
||||
<small class="text-muted" id="media-count"></small>
|
||||
</div>
|
||||
<div id="media-grid" class="row g-2">
|
||||
<div class="col-12 text-center text-muted py-4">
|
||||
<div class="spinner-border spinner-border-sm me-2"></div> Laden...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Size form (shown when clicking an image) -->
|
||||
<div id="media-size-form" class="d-none">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center gap-3 mb-3">
|
||||
<img id="size-preview" src="" alt="" style="width:80px;height:60px;object-fit:cover;border-radius:4px;">
|
||||
<div>
|
||||
<strong id="size-filename" class="d-block"></strong>
|
||||
<small class="text-muted">Geef de gewenste afmetingen (optioneel)</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-4">
|
||||
<label class="form-label small">Breedte (px)</label>
|
||||
<input type="number" class="form-control form-control-sm" id="size-width" placeholder="auto" min="1">
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<label class="form-label small">Hoogte (px)</label>
|
||||
<input type="number" class="form-control form-control-sm" id="size-height" placeholder="auto" min="1">
|
||||
</div>
|
||||
<div class="col-4 d-flex align-items-end gap-1">
|
||||
<button type="button" class="btn btn-primary btn-sm" id="size-insert-btn">
|
||||
<i class="bi bi-check-lg"></i> Invoegen
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" id="size-cancel-btn">
|
||||
Annuleren
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var backBtn = document.getElementById('back-btn');
|
||||
var filenameInput = document.getElementById('filename');
|
||||
var createBtn = document.getElementById('content-create-btn');
|
||||
|
||||
if (backBtn) {
|
||||
var changed = false;
|
||||
function markChanged() {
|
||||
if (changed) return;
|
||||
changed = true;
|
||||
backBtn.innerHTML = '<i class="bi bi-x-circle"></i> Annuleren';
|
||||
backBtn.classList.remove('btn-outline-secondary');
|
||||
backBtn.classList.add('btn-outline-danger');
|
||||
}
|
||||
if (filenameInput) {
|
||||
filenameInput.addEventListener('input', markChanged);
|
||||
}
|
||||
window.__onContentChange = markChanged;
|
||||
}
|
||||
|
||||
if (filenameInput && createBtn) {
|
||||
filenameInput.addEventListener('input', function () {
|
||||
createBtn.disabled = this.value.trim() === '';
|
||||
});
|
||||
}
|
||||
|
||||
var mediaModal = document.getElementById('mediaModal');
|
||||
if (!mediaModal) return;
|
||||
|
||||
function getCurrentMode() {
|
||||
var ta = document.getElementById('editor-textarea');
|
||||
var ext = ta ? ta.dataset.ext || 'md' : 'md';
|
||||
return ext === 'md' ? 'markdown' : 'html';
|
||||
}
|
||||
|
||||
var pendingFile = null;
|
||||
|
||||
mediaModal.addEventListener('show.bs.modal', function () {
|
||||
document.getElementById('media-size-form').classList.add('d-none');
|
||||
document.getElementById('media-grid').classList.remove('d-none');
|
||||
pendingFile = null;
|
||||
fetch('admin.php?route=media-list')
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (files) {
|
||||
var grid = document.getElementById('media-grid');
|
||||
document.getElementById('media-count').textContent = files.length + ' bestand(en)';
|
||||
if (files.length === 0) {
|
||||
grid.innerHTML = '<div class="col-12 text-center text-muted py-4">Geen media bestanden gevonden.</div>';
|
||||
return;
|
||||
}
|
||||
grid.innerHTML = '';
|
||||
files.forEach(function (f) {
|
||||
var col = document.createElement('div');
|
||||
col.className = 'col-6 col-md-4 col-lg-3';
|
||||
var card = document.createElement('div');
|
||||
card.className = 'card card-media-item';
|
||||
card.style.cursor = 'pointer';
|
||||
card.title = 'Klik om in te voegen';
|
||||
|
||||
var preview;
|
||||
if (f.is_image) {
|
||||
preview = '<img src="' + f.url + '" alt="' + f.name + '" class="card-img-top" style="height:100px;object-fit:cover;">';
|
||||
} else if (f.is_video) {
|
||||
preview = '<div class="d-flex align-items-center justify-content-center" style="height:100px;background:#f8f9fa;"><i class="bi bi-film fs-1 text-muted"></i></div>';
|
||||
} else if (f.is_audio) {
|
||||
preview = '<div class="d-flex align-items-center justify-content-center" style="height:100px;background:#f8f9fa;"><i class="bi bi-music-note-beamed fs-1 text-muted"></i></div>';
|
||||
} else {
|
||||
preview = '<div class="d-flex align-items-center justify-content-center" style="height:100px;background:#f8f9fa;"><span class="badge bg-secondary fs-5">' + f.ext.toUpperCase() + '</span></div>';
|
||||
}
|
||||
|
||||
card.innerHTML = preview +
|
||||
'<div class="card-body p-2"><small class="text-truncate d-block">' + f.name + '</small></div>';
|
||||
|
||||
card.addEventListener('click', function () {
|
||||
var mode = getCurrentMode();
|
||||
if (f.is_image && mode !== 'markdown') {
|
||||
showSizeForm(f);
|
||||
} else {
|
||||
insertMedia(f, mode);
|
||||
}
|
||||
});
|
||||
col.appendChild(card);
|
||||
grid.appendChild(col);
|
||||
});
|
||||
})
|
||||
.catch(function () {
|
||||
document.getElementById('media-grid').innerHTML = '<div class="col-12 text-center text-danger py-4">Fout bij laden van media.</div>';
|
||||
});
|
||||
});
|
||||
|
||||
function showSizeForm(f) {
|
||||
pendingFile = f;
|
||||
document.getElementById('media-grid').classList.add('d-none');
|
||||
document.getElementById('media-size-form').classList.remove('d-none');
|
||||
document.getElementById('size-preview').src = f.url;
|
||||
document.getElementById('size-filename').textContent = f.name;
|
||||
document.getElementById('size-width').value = '';
|
||||
document.getElementById('size-height').value = '';
|
||||
}
|
||||
|
||||
document.getElementById('size-insert-btn').addEventListener('click', function () {
|
||||
if (!pendingFile) return;
|
||||
var w = document.getElementById('size-width').value;
|
||||
var h = document.getElementById('size-height').value;
|
||||
insertMedia(pendingFile, getCurrentMode(), w, h);
|
||||
});
|
||||
|
||||
document.getElementById('size-cancel-btn').addEventListener('click', function () {
|
||||
document.getElementById('media-size-form').classList.add('d-none');
|
||||
document.getElementById('media-grid').classList.remove('d-none');
|
||||
pendingFile = null;
|
||||
});
|
||||
|
||||
function insertMedia(f, mode, w, h) {
|
||||
var editorEl = document.querySelector('.CodeMirror');
|
||||
if (!editorEl || typeof CodeMirror === 'undefined') return;
|
||||
var cm = editorEl.CodeMirror;
|
||||
if (!cm) return;
|
||||
|
||||
var sizeAttr = '';
|
||||
if (w || h) {
|
||||
if (w) sizeAttr += ' width="' + parseInt(w) + '"';
|
||||
if (h) sizeAttr += ' height="' + parseInt(h) + '"';
|
||||
}
|
||||
|
||||
var tag;
|
||||
if (mode === 'markdown') {
|
||||
if (f.is_image) {
|
||||
tag = '';
|
||||
} else {
|
||||
tag = '[' + f.name + '](' + f.url + ')';
|
||||
}
|
||||
} else {
|
||||
if (f.is_image) {
|
||||
tag = '<img src="' + f.url + '" alt="' + f.name + '"' + sizeAttr + '>';
|
||||
} else if (f.is_video) {
|
||||
tag = '<video controls src="' + f.url + '" style="max-width:100%;"></video>';
|
||||
} else if (f.is_audio) {
|
||||
tag = '<audio controls src="' + f.url + '"></audio>';
|
||||
} else {
|
||||
tag = '<a href="' + f.url + '">' + f.name + '</a>';
|
||||
}
|
||||
}
|
||||
cm.replaceSelection(tag);
|
||||
cm.focus();
|
||||
|
||||
var modal = bootstrap.Modal.getInstance(mediaModal);
|
||||
if (modal) modal.hide();
|
||||
}
|
||||
|
||||
document.getElementById('media-upload-form').addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
var form = this;
|
||||
var formData = new FormData(form);
|
||||
formData.append('csrf_token', '<?= $csrf ?>');
|
||||
|
||||
fetch('admin.php?route=media', { method: 'POST', body: formData })
|
||||
.then(function () {
|
||||
form.reset();
|
||||
document.getElementById('media-upload-btn').disabled = true;
|
||||
var modal = bootstrap.Modal.getInstance(mediaModal);
|
||||
if (modal) modal.hide();
|
||||
setTimeout(function () { modal.show(); }, 100);
|
||||
})
|
||||
.catch(function () {
|
||||
alert('Upload mislukt.');
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('media-file-input').addEventListener('change', function () {
|
||||
document.getElementById('media-upload-btn').disabled = this.files.length === 0;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user