Files
Pivot/templates/handleiding_bewerken.html
2026-10-14 13:34:37 +02:00

272 lines
15 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}{{ 'Nieuwe handleiding' if nieuw else 'Bewerken: ' ~ h.Titel }}{% endblock %}
{% block page_title %}Handleidingen{% endblock %}
{% block extra_css %}
{% include "_handleiding_stijl.html" %}
<style>
.terug { margin-bottom: 14px; display: inline-block; }
.hl-velden { display: grid; grid-template-columns: 2fr 1fr 1fr 110px; gap: 0 14px; }
@media (max-width: 800px) { .hl-velden { grid-template-columns: 1fr; } }
.editor-wrap { border: 1px solid var(--border-strong); border-radius: var(--radius); background: var(--surface); }
.toolbar { display: flex; flex-wrap: wrap; gap: 4px; padding: 6px; border-bottom: 1px solid var(--border);
background: var(--surface-muted); border-radius: var(--radius) var(--radius) 0 0; position: sticky; top: 0; z-index: 5; }
.toolbar button, .toolbar select { padding: 4px 9px; font-size: 13px; min-width: 32px; background: var(--surface);
color: var(--text); border: 1px solid var(--border); border-radius: 6px; cursor: pointer; width: auto; }
.toolbar button:hover { border-color: var(--primary-color); color: var(--primary-color); }
.toolbar button.aan { background: var(--primary-light); border-color: var(--primary-color); }
.toolbar .sep { width: 1px; background: var(--border-strong); margin: 2px 4px; }
#editor { min-height: 420px; max-height: 70vh; overflow-y: auto; padding: 16px 20px; outline: none; max-width: none; }
#editor:empty::before { content: "Begin hier te typen… Je kan ook schermafbeeldingen plakken (Ctrl+V) of slepen."; color: var(--text-muted); }
#editor img { cursor: pointer; }
#editor img.gekozen { outline: 3px solid var(--primary-color); }
#bron { display: none; width: 100%; min-height: 420px; font-family: Consolas, monospace; font-size: 13px; border: 0;
padding: 12px; border-radius: 0 0 var(--radius) var(--radius); }
.knoppen-onder { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; align-items: center; }
.versies { margin-top: 8px; }
.versies li { margin: 4px 0; }
</style>
{% endblock %}
{% block content %}
<a class="terug" href="{{ url_for('handleidingen.overzicht') if nieuw else url_for('handleidingen.bekijk', slug=h.Slug) }}">← Terug</a>
<div class="card">
<div class="page-head"><h2>{{ '➕ Nieuwe handleiding' if nieuw else '✏️ Handleiding bewerken' }}</h2></div>
<form method="POST" id="hlForm" action="{{ url_for('handleidingen.nieuw') if nieuw else url_for('handleidingen.bewerken', slug=h.Slug) }}">
<div class="hl-velden">
<div class="field">
<label for="titel">Titel</label>
<input type="text" id="titel" name="titel" maxlength="200" required value="{{ h.Titel or '' }}">
</div>
<div class="field">
<label for="categorie">Categorie</label>
<input type="text" id="categorie" name="categorie" maxlength="100" list="cats" value="{{ h.Categorie or '' }}" placeholder="bv. Google Workspace">
<datalist id="cats">{% for c in categorieen %}<option value="{{ c }}">{% endfor %}</datalist>
</div>
<div class="field">
<label for="zichtbaar">Zichtbaar voor</label>
<select id="zichtbaar" name="zichtbaar">
{% for k, v in ZICHTBAAR.items() %}<option value="{{ k }}" {% if h.Zichtbaar == k %}selected{% endif %}>{{ v }}</option>{% endfor %}
</select>
</div>
<div class="field">
<label for="volgorde">Volgorde</label>
<input type="number" id="volgorde" name="volgorde" value="{{ h.Volgorde or 0 }}">
</div>
</div>
<div class="field">
<label for="samenvatting">Korte samenvatting <span class="muted small">(toont in de lijst)</span></label>
<input type="text" id="samenvatting" name="samenvatting" maxlength="300" value="{{ h.Samenvatting or '' }}">
</div>
<label>Inhoud</label>
<div class="editor-wrap">
<div class="toolbar" role="toolbar" aria-label="Opmaak">
<select id="blokstijl" title="Stijl" onchange="blok(this.value); this.value=''">
<option value="">Stijl…</option>
<option value="p">Gewone tekst</option>
<option value="h2">Kop (stap)</option>
<option value="h3">Tussenkop</option>
<option value="pre">Codeblok</option>
</select>
<span class="sep"></span>
<button type="button" data-cmd="bold" title="Vet (Ctrl+B)"><b>B</b></button>
<button type="button" data-cmd="italic" title="Cursief (Ctrl+I)"><i>I</i></button>
<button type="button" data-cmd="underline" title="Onderlijnen (Ctrl+U)"><u>U</u></button>
<button type="button" onclick="omring('code')" title="Code / knopnaam">&lt;/&gt;</button>
<button type="button" onclick="omring('mark')" title="Markeren">🖍️</button>
<span class="sep"></span>
<button type="button" data-cmd="insertOrderedList" title="Genummerde stappen">1.</button>
<button type="button" data-cmd="insertUnorderedList" title="Opsomming">•</button>
<button type="button" data-cmd="outdent" title="Minder inspringen">⇤</button>
<button type="button" data-cmd="indent" title="Meer inspringen">⇥</button>
<span class="sep"></span>
<button type="button" onclick="kader('tip')" title="Tip-kader (groen)">💡 Tip</button>
<button type="button" onclick="kader('let-op')" title="Let op-kader (geel)">⚠️ Let op</button>
<button type="button" onclick="kader('info')" title="Info-kader (blauw)">ℹ️ Info</button>
<span class="sep"></span>
<button type="button" onclick="link()" title="Link">🔗</button>
<button type="button" data-cmd="unlink" title="Link weghalen">⛓️‍💥</button>
<button type="button" onclick="document.getElementById('beeld').click()" title="Afbeelding / schermafbeelding">🖼️</button>
<button type="button" onclick="tabel()" title="Tabel">▦</button>
<button type="button" data-cmd="insertHorizontalRule" title="Scheidingslijn">―</button>
<span class="sep"></span>
<button type="button" data-cmd="removeFormat" title="Opmaak wissen">🧹</button>
<button type="button" data-cmd="undo" title="Ongedaan maken (Ctrl+Z)">↶</button>
<button type="button" data-cmd="redo" title="Opnieuw (Ctrl+Y)">↷</button>
<span class="sep"></span>
<button type="button" id="bronKnop" onclick="wisselBron()" title="HTML bekijken/bewerken">HTML</button>
</div>
<div id="editor" class="hl-inhoud" contenteditable="true" spellcheck="true">{{ h.Inhoud|safe if h.Inhoud else '' }}</div>
<textarea id="bron" spellcheck="false" aria-label="HTML"></textarea>
</div>
<input type="file" id="beeld" accept="image/*" multiple hidden onchange="bestanden(this.files); this.value=''">
<input type="hidden" name="inhoud" id="inhoud">
<p class="field-hint">Tip: plak een schermafbeelding rechtstreeks met Ctrl+V. Klik op een afbeelding en druk Delete om ze te verwijderen.
Grote afbeeldingen worden automatisch verkleind.</p>
<div class="knoppen-onder">
<button type="submit" class="btn-success">💾 Opslaan</button>
{% if not nieuw %}<button type="submit" name="verder" value="1" class="btn-light">💾 Opslaan en verder bewerken</button>{% endif %}
<a class="btn btn-light" href="{{ url_for('handleidingen.overzicht') if nieuw else url_for('handleidingen.bekijk', slug=h.Slug) }}"
onclick="gewijzigd=false">Annuleren</a>
</div>
</form>
{% if not nieuw %}
<form method="POST" action="{{ url_for('handleidingen.verwijderen', slug=h.Slug) }}" style="margin-top:18px"
onsubmit="gewijzigd=false; return confirm('Handleiding \'{{ h.Titel|e }}\' verwijderen?')">
<button type="submit" class="btn-danger btn-sm">🗑️ Handleiding verwijderen</button>
</form>
{% endif %}
</div>
{% if versies %}
<div class="card">
<h3 class="card-title">🕘 Vorige versies</h3>
<p class="muted small">Bij elke opslag wordt de vorige tekst bewaard (de laatste 30). Open een versie om ze te bekijken of terug te zetten.</p>
<ul class="versies">
{% for v in versies %}
<li><a href="{{ url_for('handleidingen.versie', slug=h.Slug, vid=v.Versie_ID) }}">{{ v.Opgeslagen_op.strftime('%d-%m-%Y %H:%M') }}</a>
<span class="muted small">{{ v.Door or '' }}{% if v.Titel != h.Titel %} · titel: {{ v.Titel }}{% endif %}</span></li>
{% endfor %}
</ul>
</div>
{% endif %}
{% endblock %}
{% block scripts %}
<script>
(function () {
var ed = document.getElementById('editor'), bron = document.getElementById('bron');
var form = document.getElementById('hlForm');
var bronAan = false;
window.gewijzigd = false;
var MAX_BREEDTE = 1400;
try { document.execCommand('defaultParagraphSeparator', false, 'p'); } catch (e) {}
ed.addEventListener('input', function () { gewijzigd = true; });
['titel', 'categorie', 'samenvatting', 'zichtbaar', 'volgorde'].forEach(function (id) {
document.getElementById(id).addEventListener('input', function () { gewijzigd = true; });
});
window.addEventListener('beforeunload', function (e) { if (gewijzigd) { e.preventDefault(); e.returnValue = ''; } });
function cmd(c, v) { ed.focus(); document.execCommand(c, false, v || null); gewijzigd = true; }
document.querySelectorAll('.toolbar [data-cmd]').forEach(function (b) {
b.addEventListener('mousedown', function (e) { e.preventDefault(); });
b.addEventListener('click', function () { cmd(b.dataset.cmd); });
});
window.blok = function (tag) { if (tag) cmd('formatBlock', '<' + tag + '>'); };
function selectieHtml() {
var sel = window.getSelection();
if (!sel.rangeCount) return '';
var div = document.createElement('div');
div.appendChild(sel.getRangeAt(0).cloneContents());
return div.innerHTML;
}
window.omring = function (tag) {
var h = selectieHtml() || 'tekst';
cmd('insertHTML', '<' + tag + '>' + h + '</' + tag + '>&#8203;');
};
window.kader = function (soort) {
var h = selectieHtml();
var titel = {tip: 'Tip:', 'let-op': 'Let op:', info: 'Info:'}[soort];
if (!h) h = '<strong>' + titel + '</strong> ';
cmd('insertHTML', '<div class="' + soort + '">' + h + '</div><p><br></p>');
};
window.link = function () {
var url = prompt('Link (bv. https://admin.google.com):', 'https://');
if (!url || url === 'https://') return;
if (!/^(https?:|mailto:|\/|#)/i.test(url)) url = 'https://' + url;
if (window.getSelection().isCollapsed) {
cmd('insertHTML', '<a href="' + url.replace(/"/g, '&quot;') + '" target="_blank">' + url.replace(/</g, '&lt;') + '</a>&nbsp;');
} else {
cmd('createLink', url);
ed.querySelectorAll('a[href="' + CSS.escape(url) + '"]').forEach(function (a) { a.target = '_blank'; });
}
};
window.tabel = function () {
var k = parseInt(prompt('Aantal kolommen?', '3'), 10), r = parseInt(prompt('Aantal rijen (zonder kop)?', '3'), 10);
if (!(k > 0 && r > 0)) return;
var h = '<table><thead><tr>';
for (var i = 0; i < k; i++) h += '<th>Kop ' + (i + 1) + '</th>';
h += '</tr></thead><tbody>';
for (var j = 0; j < r; j++) { h += '<tr>'; for (i = 0; i < k; i++) h += '<td>&nbsp;</td>'; h += '</tr>'; }
cmd('insertHTML', h + '</tbody></table><p><br></p>');
};
// --- afbeeldingen: knop, plakken, slepen → verkleinen → data-URL ---
function voegBeeldIn(file) {
if (!file || !/^image\//.test(file.type) || /svg/.test(file.type)) return;
var r = new FileReader();
r.onload = function () {
var img = new Image();
img.onload = function () {
var schaal = Math.min(1, MAX_BREEDTE / img.width);
var c = document.createElement('canvas');
c.width = Math.round(img.width * schaal); c.height = Math.round(img.height * schaal);
c.getContext('2d').drawImage(img, 0, 0, c.width, c.height);
var png = c.toDataURL('image/png'), jpg = c.toDataURL('image/jpeg', 0.85);
var url = (file.type === 'image/png' && png.length < jpg.length * 1.6) ? png : jpg;
cmd('insertHTML', '<img src="' + url + '" alt="' + (file.name || 'schermafbeelding').replace(/"/g, '') + '"><p><br></p>');
};
img.src = r.result;
};
r.readAsDataURL(file);
}
window.bestanden = function (files) { Array.prototype.forEach.call(files, voegBeeldIn); };
ed.addEventListener('paste', function (e) {
var items = (e.clipboardData || {}).items || [], beeld = false;
Array.prototype.forEach.call(items, function (it) {
if (it.kind === 'file' && /^image\//.test(it.type)) { voegBeeldIn(it.getAsFile()); beeld = true; }
});
if (beeld) e.preventDefault();
});
ed.addEventListener('dragover', function (e) { e.preventDefault(); });
ed.addEventListener('drop', function (e) {
if (e.dataTransfer && e.dataTransfer.files.length) { e.preventDefault(); bestanden(e.dataTransfer.files); }
});
ed.addEventListener('click', function (e) {
ed.querySelectorAll('img.gekozen').forEach(function (i) { i.classList.remove('gekozen'); });
if (e.target.tagName === 'IMG') e.target.classList.add('gekozen');
});
ed.addEventListener('keydown', function (e) {
var g = ed.querySelector('img.gekozen');
if (g && (e.key === 'Delete' || e.key === 'Backspace')) { e.preventDefault(); g.remove(); gewijzigd = true; }
});
// --- HTML-weergave ---
window.wisselBron = function () {
bronAan = !bronAan;
if (bronAan) { bron.value = ed.innerHTML; } else { ed.innerHTML = bron.value; }
ed.style.display = bronAan ? 'none' : '';
bron.style.display = bronAan ? 'block' : 'none';
document.getElementById('bronKnop').classList.toggle('aan', bronAan);
document.querySelectorAll('.toolbar button:not(#bronKnop), .toolbar select').forEach(function (b) { b.disabled = bronAan; });
};
bron.addEventListener('input', function () { gewijzigd = true; });
// Ctrl+S = opslaan en verder bewerken
document.addEventListener('keydown', function (e) {
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 's') {
e.preventDefault();
var verder = form.querySelector('[name=verder]');
if (verder) verder.click(); else form.requestSubmit();
}
});
form.addEventListener('submit', function () {
ed.querySelectorAll('img.gekozen').forEach(function (i) { i.classList.remove('gekozen'); });
document.getElementById('inhoud').value = bronAan ? bron.value : ed.innerHTML;
gewijzigd = false;
if (typeof showLoader === 'function') showLoader();
});
})();
</script>
{% endblock %}