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:
233
public/assets/js/editor-toolbar.js
Normal file
233
public/assets/js/editor-toolbar.js
Normal file
@@ -0,0 +1,233 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var textarea = document.getElementById('editor-textarea');
|
||||
if (!textarea || typeof CodeMirror === 'undefined') return;
|
||||
|
||||
var ext = textarea.dataset.ext || 'md';
|
||||
|
||||
var modeMap = { md: 'markdown', html: 'htmlmixed', php: 'php' };
|
||||
|
||||
var editor = CodeMirror.fromTextArea(textarea, {
|
||||
mode: modeMap[ext] || 'markdown',
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
matchBrackets: true,
|
||||
autoCloseBrackets: true,
|
||||
styleActiveLine: true,
|
||||
indentUnit: 4,
|
||||
tabSize: 4,
|
||||
indentWithTabs: true,
|
||||
viewportMargin: Infinity,
|
||||
extraKeys: {
|
||||
'Ctrl-S': function () { document.getElementById('editor-form').submit(); },
|
||||
'Cmd-S': function () { document.getElementById('editor-form').submit(); }
|
||||
}
|
||||
});
|
||||
|
||||
var commands = {
|
||||
md: [
|
||||
{ cmd: 'bold', icon: 'bi-type-bold', title: 'Vet' },
|
||||
{ cmd: 'italic', icon: 'bi-type-italic', title: 'Cursief' },
|
||||
{ cmd: 'heading', icon: 'bi-type-h2', title: 'Kop' },
|
||||
{ cmd: 'link', icon: 'bi-link-45deg', title: 'Link' },
|
||||
null,
|
||||
{ cmd: 'ulist', icon: 'bi-list-ul', title: 'Ongenummerde lijst' },
|
||||
{ cmd: 'olist', icon: 'bi-list-ol', title: 'Genummerde lijst' },
|
||||
{ cmd: 'code', icon: 'bi-code-slash', title: 'Code' },
|
||||
{ cmd: 'quote', icon: 'bi-chat-quote', title: 'Citaat' },
|
||||
null,
|
||||
{ cmd: 'hr', icon: 'bi-hr', title: 'Horizontale lijn' },
|
||||
null,
|
||||
{ cmd: 'media', icon: 'bi-images', title: 'Media invoegen' }
|
||||
],
|
||||
html: [
|
||||
{ cmd: 'strong', icon: 'bi-type-bold', title: '<strong>' },
|
||||
{ cmd: 'em', icon: 'bi-type-italic', title: '<em>' },
|
||||
null,
|
||||
{ cmd: 'link', icon: 'bi-link-45deg', title: 'Link' },
|
||||
{ cmd: 'image', icon: 'bi-image', title: 'Afbeelding' },
|
||||
null,
|
||||
{ cmd: 'comment', icon: 'bi-chat-square-dots', title: 'Commentaar' },
|
||||
null,
|
||||
{ cmd: 'media', icon: 'bi-images', title: 'Media invoegen' }
|
||||
],
|
||||
php: [
|
||||
{ cmd: 'comment_php', icon: 'bi-slash-circle', title: '// Commentaar' },
|
||||
{ cmd: 'docblock', icon: 'bi-blockquote-left', title: '/** DocBlock */' },
|
||||
null,
|
||||
{ cmd: 'media', icon: 'bi-images', title: 'Media invoegen' }
|
||||
]
|
||||
};
|
||||
|
||||
function wrap(editor, before, after) {
|
||||
var sel = editor.getSelection();
|
||||
editor.replaceSelection(before + sel + after);
|
||||
if (!sel) {
|
||||
var cur = editor.getCursor();
|
||||
editor.setCursor({ line: cur.line, ch: cur.ch - after.length });
|
||||
}
|
||||
editor.focus();
|
||||
}
|
||||
|
||||
function insert(editor, text, cursorOffset) {
|
||||
editor.replaceSelection(text);
|
||||
if (cursorOffset !== undefined) {
|
||||
var cur = editor.getCursor();
|
||||
editor.setCursor({ line: cur.line, ch: cur.ch - text.length + cursorOffset });
|
||||
}
|
||||
editor.focus();
|
||||
}
|
||||
|
||||
function prependLine(editor, prefix) {
|
||||
var cur = editor.getCursor();
|
||||
editor.replaceRange(prefix, { line: cur.line, ch: 0 }, { line: cur.line, ch: 0 });
|
||||
editor.setCursor({ line: cur.line, ch: prefix.length });
|
||||
editor.focus();
|
||||
}
|
||||
|
||||
function execute(cmd) {
|
||||
var sel = editor.getSelection();
|
||||
|
||||
switch (cmd) {
|
||||
case 'bold':
|
||||
sel ? wrap(editor, '**', '**') : insert(editor, '****', 2);
|
||||
break;
|
||||
case 'italic':
|
||||
sel ? wrap(editor, '*', '*') : insert(editor, '**', 1);
|
||||
break;
|
||||
case 'heading':
|
||||
prependLine(editor, '## ');
|
||||
break;
|
||||
case 'link':
|
||||
sel ? wrap(editor, '[', '](url)') : insert(editor, '[linktekst](url)', 1);
|
||||
break;
|
||||
case 'ulist':
|
||||
prependLine(editor, '- ');
|
||||
break;
|
||||
case 'olist':
|
||||
prependLine(editor, '1. ');
|
||||
break;
|
||||
case 'code':
|
||||
sel ? wrap(editor, '`', '`') : insert(editor, '``', 1);
|
||||
break;
|
||||
case 'quote':
|
||||
prependLine(editor, '> ');
|
||||
break;
|
||||
case 'hr':
|
||||
insert(editor, '\n---\n', 0);
|
||||
break;
|
||||
case 'strong':
|
||||
sel ? wrap(editor, '<strong>', '</strong>') : insert(editor, '<strong></strong>', 8);
|
||||
break;
|
||||
case 'em':
|
||||
sel ? wrap(editor, '<em>', '</em>') : insert(editor, '<em></em>', 4);
|
||||
break;
|
||||
case 'image':
|
||||
insert(editor, '<img src="" alt="">', 10);
|
||||
break;
|
||||
case 'comment':
|
||||
sel ? wrap(editor, '<!-- ', ' -->') : insert(editor, '<!-- -->', 4);
|
||||
break;
|
||||
case 'comment_php':
|
||||
if (sel) {
|
||||
sel.indexOf('\n') !== -1
|
||||
? insert(editor, '/* ' + sel + ' */', 0)
|
||||
: insert(editor, '// ' + sel, 0);
|
||||
} else {
|
||||
prependLine(editor, '// ');
|
||||
}
|
||||
break;
|
||||
case 'docblock':
|
||||
insert(editor, '/**\n * \n */', 7);
|
||||
break;
|
||||
case 'media':
|
||||
var modal = new bootstrap.Modal(document.getElementById('mediaModal'));
|
||||
if (modal) modal.show();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function buildToolbar(ext) {
|
||||
var toolbar = document.getElementById('editor-toolbar');
|
||||
if (!toolbar) return;
|
||||
|
||||
toolbar.innerHTML = '';
|
||||
var cmds = commands[ext] || [];
|
||||
cmds.forEach(function (item) {
|
||||
if (item === null) {
|
||||
var sep = document.createElement('div');
|
||||
sep.className = 'vr';
|
||||
toolbar.appendChild(sep);
|
||||
return;
|
||||
}
|
||||
var btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'btn btn-sm btn-outline-secondary';
|
||||
btn.title = item.title;
|
||||
btn.innerHTML = '<i class="bi ' + item.icon + '"></i>';
|
||||
btn.addEventListener('click', function () { execute(item.cmd); });
|
||||
toolbar.appendChild(btn);
|
||||
});
|
||||
}
|
||||
|
||||
var templates = {
|
||||
md: '# Nieuwe Pagina\n\nSchrijf hier je inhoud...\n',
|
||||
html: '<h1>Nieuwe Pagina</h1>\n<p>Dit is een HTML content pagina.</p>\n',
|
||||
php: '---\ntitle: Nieuwe Pagina\n---\n\n<?php\n$pageTitle = "Nieuwe Pagina";\n?>\n\n<h1><?= $pageTitle ?></h1>\n<p>PHP content - alles wat je hier echo't wordt weergegeven.</p>\n\n<?php\n$items = [\'Item 1\', \'Item 2\', \'Item 3\'];\n?>\n<ul>\n<?php foreach ($items as $item): ?>\n <li><?= $item ?></li>\n<?php endforeach; ?>\n</ul>\n'
|
||||
};
|
||||
|
||||
var defaultContents = {};
|
||||
|
||||
function getTemplate(ext) {
|
||||
return templates[ext] || '';
|
||||
}
|
||||
|
||||
function setDefaultContent(ext) {
|
||||
var tpl = getTemplate(ext);
|
||||
editor.setValue(tpl);
|
||||
editor.setCursor({ line: 0, ch: 0 });
|
||||
editor.focus();
|
||||
defaultContents[ext] = tpl;
|
||||
}
|
||||
|
||||
function switchMode(ext, forceContent) {
|
||||
var mode = modeMap[ext] || 'markdown';
|
||||
editor.setOption('mode', mode);
|
||||
textarea.dataset.ext = ext;
|
||||
|
||||
if (forceContent) {
|
||||
setDefaultContent(ext);
|
||||
} else {
|
||||
var cur = editor.getValue().trim();
|
||||
var expected = defaultContents[ext];
|
||||
if (cur === '' || (expected && cur === expected.trim())) {
|
||||
setDefaultContent(ext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
editor.on('change', function () {
|
||||
if (window.__onContentChange) window.__onContentChange();
|
||||
});
|
||||
|
||||
buildToolbar(ext);
|
||||
|
||||
var form = document.getElementById('editor-form');
|
||||
var isNewPage = form && form.hasAttribute('data-new-page');
|
||||
|
||||
if (isNewPage && editor.getValue().trim() === '') {
|
||||
setDefaultContent(ext);
|
||||
}
|
||||
|
||||
var typeSelect = document.querySelector('[data-editor-mode]');
|
||||
if (typeSelect && isNewPage) {
|
||||
typeSelect.addEventListener('change', function () {
|
||||
switchMode(this.value, true);
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('editor-form').addEventListener('submit', function () {
|
||||
editor.save();
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user