Files
CodePress/admin/templates/pages/content-new.php
Edwin Noorlander dc0d370e65 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
2026-06-24 17:00:59 +02:00

310 lines
15 KiB
PHP

<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 ?? '') ?>" id="editor-form" data-new-page>
<input type="hidden" name="csrf_token" value="<?= $csrf ?>">
<div class="row mb-3">
<div class="col-md-8">
<label for="filename" class="form-label">Bestandsnaam</label>
<input type="text" class="form-control" id="filename" name="filename" placeholder="bijv. mijn-pagina" required>
<small class="form-text text-muted">Extensie wordt automatisch toegevoegd.</small>
</div>
<div class="col-md-4">
<label for="type" class="form-label">Type</label>
<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>
</select>
</div>
</div>
<?php if (!empty($dir)): ?>
<div class="mb-3">
<small class="text-muted">Map: <?= htmlspecialchars($dir) ?></small>
</div>
<?php endif; ?>
<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="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" 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 = '![' + f.name + '](' + f.url + ')';
} 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>