Fix plugin arrows (grey/disable not hide) + live frontmatter update on plugin change

- Arrow buttons: opacity 0.4 + pointer-events none at first/last (not hidden)
- updateFrontmatter(): updates both layout AND plugins in editor live
- Plugin order change -> frontmatter updated immediately in CodeMirror
- Plugin checkbox toggle -> frontmatter updated immediately
- Label is 'Plugins' (not 'Zichtbare plugins')
- Update AGENTS.md with plugin types (content vs system)
This commit is contained in:
2026-08-11 17:29:54 +02:00
parent ec7fec1c1e
commit 6ed29e2c6c
2 changed files with 91 additions and 29 deletions
+9
View File
@@ -271,6 +271,15 @@ Gedefinieerd in `AdminAuth::ROLE_PERMISSIONS` als een mapping van rol → toeges
- `handlePluginsEdit` in `admin.php` moet `$pluginDir . '/' . $plugin . '.php'` gebruiken, NIET `$pluginDir . '/plugin.php'`. - `handlePluginsEdit` in `admin.php` moet `$pluginDir . '/' . $plugin . '.php'` gebruiken, NIET `$pluginDir . '/plugin.php'`.
- `handlePluginsNew` moet het bestand aanmaken als `<PluginName>.php`, NIET `plugin.php`. - `handlePluginsNew` moet het bestand aanmaken als `<PluginName>.php`, NIET `plugin.php`.
## CRITICAL: Plugin types (content vs systeem)
- `plugin.json` heeft een `type` veld: `"type": "content"` of `"type": "system"`.
- **Content plugins** (type=content): Verschijnen in de sidebar en in de plugin selectie op content-edit paginas.
- **Systeem plugins** (type=system): Worden geladen door PluginManager (voor API/hooks) maar verschijnen NIET in de sidebar. Bieden functionaliteit aan via de CMSAPI.
- `PluginManager::isPluginViewable()` checkt `type === 'system'` -> niet viewable in sidebar.
- `handleContentEdit` toont alleen content-plugins in de plugin selectie.
- De admin plugins pagina toont een "Content" of "Systeem" badge.
- Bij geen `type` veld in plugin.json wordt default `content` aangenomen.
## CRITICAL: Essentiële plugins beschermen ## CRITICAL: Essentiële plugins beschermen
- Essentiële plugins gedefinieerd in `getProtectedPlugins()` in `public/admin.php` (huidige: `Navigation`). - Essentiële plugins gedefinieerd in `getProtectedPlugins()` in `public/admin.php` (huidige: `Navigation`).
- Deze plugins kunnen NIET worden: gedeactiveerd (`handlePluginsToggle`), verwijderd (`handlePluginsDelete`), bewerkt (`handlePluginsEdit`). - Deze plugins kunnen NIET worden: gedeactiveerd (`handlePluginsToggle`), verwijderd (`handlePluginsDelete`), bewerkt (`handlePluginsEdit`).
@@ -89,6 +89,7 @@ document.addEventListener('DOMContentLoaded', function () {
var filenameInput = document.getElementById('filename'); var filenameInput = document.getElementById('filename');
var layoutSelect = document.getElementById('layout'); var layoutSelect = document.getElementById('layout');
var pluginSection = document.getElementById('plugin-section'); var pluginSection = document.getElementById('plugin-section');
var pluginList = document.getElementById('plugin-order-list');
if (!backBtn) return; if (!backBtn) return;
var sidebarLayouts = {{ sidebarLayouts|json_encode|raw }}; var sidebarLayouts = {{ sidebarLayouts|json_encode|raw }};
@@ -106,86 +107,138 @@ document.addEventListener('DOMContentLoaded', function () {
filenameInput.addEventListener('input', markChanged); filenameInput.addEventListener('input', markChanged);
} }
// Show/hide plugin section based on layout
function updatePluginVisibility() { function updatePluginVisibility() {
if (!layoutSelect || !pluginSection) return; if (!layoutSelect || !pluginSection) return;
var hasSidebar = sidebarLayouts.indexOf(layoutSelect.value) !== -1; var hasSidebar = sidebarLayouts.indexOf(layoutSelect.value) !== -1;
pluginSection.style.display = hasSidebar ? '' : 'none'; pluginSection.style.display = hasSidebar ? '' : 'none';
// Disable checkboxes if no sidebar
pluginSection.querySelectorAll('.plugin-checkbox').forEach(function(cb) { pluginSection.querySelectorAll('.plugin-checkbox').forEach(function(cb) {
cb.disabled = !hasSidebar; cb.disabled = !hasSidebar;
}); });
} }
// Update arrow visibility — hide up on first, down on last // Arrow state: grey out and disable at first/last position (not hidden)
function updateArrows() { function updateArrows() {
if (!pluginList) return; if (!pluginList) return;
var items = pluginList.querySelectorAll('li[data-plugin]'); var items = pluginList.querySelectorAll('li[data-plugin]');
items.forEach(function(item, i) { items.forEach(function(item, i) {
var upBtn = item.querySelector('.plugin-up'); var upBtn = item.querySelector('.plugin-up');
var downBtn = item.querySelector('.plugin-down'); var downBtn = item.querySelector('.plugin-down');
if (upBtn) upBtn.style.visibility = (i === 0) ? 'hidden' : ''; var upDisabled = (i === 0);
if (downBtn) downBtn.style.visibility = (i === items.length - 1) ? 'hidden' : ''; var downDisabled = (i === items.length - 1);
if (upBtn) {
upBtn.disabled = upDisabled;
upBtn.style.opacity = upDisabled ? '0.4' : '';
upBtn.style.pointerEvents = upDisabled ? 'none' : '';
}
if (downBtn) {
downBtn.disabled = downDisabled;
downBtn.style.opacity = downDisabled ? '0.4' : '';
downBtn.style.pointerEvents = downDisabled ? 'none' : '';
}
}); });
} }
// Update the layout value in the editor's frontmatter when the select changes // Update frontmatter in the editor (layout and plugins together)
if (layoutSelect) { function updateFrontmatter() {
layoutSelect.addEventListener('change', function () { var editor = document.getElementById('editor-textarea');
var newLayout = this.value; if (!editor) return;
var editor = document.getElementById('editor-textarea');
updatePluginVisibility();
if (!editor) return; var cm = window.codeMirrorEditor;
var content = cm ? cm.getValue() : editor.value;
var newLayout = layoutSelect ? layoutSelect.value : '';
var cm = window.codeMirrorEditor; // Get current plugin order (only checked, in DOM order)
var content = cm ? cm.getValue() : editor.value; var pluginOrder = [];
if (pluginList) {
pluginList.querySelectorAll('li[data-plugin]').forEach(function(item) {
var cb = item.querySelector('.plugin-checkbox');
if (cb && cb.checked) {
pluginOrder.push(item.getAttribute('data-plugin'));
}
});
}
var pluginsValue = pluginOrder.join(', ');
// Check if frontmatter exists var fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
var fmMatch = content.match(/^---\n([\s\S]*?)\n---/); if (fmMatch) {
if (fmMatch) { var frontmatter = fmMatch[1];
var frontmatter = fmMatch[1];
if (newLayout) {
if (/^layout:\s*.+$/m.test(frontmatter)) { if (/^layout:\s*.+$/m.test(frontmatter)) {
frontmatter = frontmatter.replace(/^layout:\s*.+$/m, 'layout: ' + newLayout); frontmatter = frontmatter.replace(/^layout:\s*.+$/m, 'layout: ' + newLayout);
} else { } else {
frontmatter = 'layout: ' + newLayout + '\n' + frontmatter; frontmatter = 'layout: ' + newLayout + '\n' + frontmatter;
} }
content = content.replace(/^---\n([\s\S]*?)\n---/, '---\n' + frontmatter + '\n---');
} else {
content = '---\nlayout: ' + newLayout + '\n---\n\n' + content;
} }
if (cm) { if (/^plugins:\s*.+$/m.test(frontmatter)) {
cm.setValue(content); if (pluginsValue) {
} else { frontmatter = frontmatter.replace(/^plugins:\s*.+$/m, 'plugins: ' + pluginsValue);
editor.value = content; } else {
frontmatter = frontmatter.replace(/^plugins:\s*.*\n?/m, '');
}
} else if (pluginsValue) {
frontmatter = 'plugins: ' + pluginsValue + '\n' + frontmatter;
} }
content = content.replace(/^---\n([\s\S]*?)\n---/, '---\n' + frontmatter + '\n---');
} else {
var fm = '';
if (newLayout) fm += 'layout: ' + newLayout + '\n';
if (pluginsValue) fm += 'plugins: ' + pluginsValue + '\n';
if (fm) {
content = '---\n' + fm + '---\n\n' + content;
}
}
if (cm) {
cm.setValue(content);
} else {
editor.value = content;
}
}
// Layout change
if (layoutSelect) {
layoutSelect.addEventListener('change', function () {
updatePluginVisibility();
updateFrontmatter();
markChanged(); markChanged();
}); });
} }
window.__onContentChange = markChanged; window.__onContentChange = markChanged;
// Plugin order: move list items up/down // Plugin order: move up/down + checkbox change
var pluginList = document.getElementById('plugin-order-list');
if (pluginList) { if (pluginList) {
pluginList.addEventListener('click', function (e) { pluginList.addEventListener('click', function (e) {
var upBtn = e.target.closest('.plugin-up'); var upBtn = e.target.closest('.plugin-up');
var downBtn = e.target.closest('.plugin-down'); var downBtn = e.target.closest('.plugin-down');
if (!upBtn && !downBtn) return; if (!upBtn && !downBtn) return;
if (upBtn && upBtn.disabled) return;
if (downBtn && downBtn.disabled) return;
var item = e.target.closest('li[data-plugin]'); var item = e.target.closest('li[data-plugin]');
if (!item) return; if (!item) return;
if (upBtn && item.previousElementSibling) { if (upBtn && item.previousElementSibling) {
pluginList.insertBefore(item, item.previousElementSibling); pluginList.insertBefore(item, item.previousElementSibling);
markChanged();
} else if (downBtn && item.nextElementSibling) { } else if (downBtn && item.nextElementSibling) {
pluginList.insertBefore(item, item.nextElementSibling.nextElementSibling); pluginList.insertBefore(item, item.nextElementSibling.nextElementSibling);
}
updateArrows();
updateFrontmatter();
markChanged();
});
// Plugin checkbox toggle
pluginList.addEventListener('change', function (e) {
if (e.target.classList.contains('plugin-checkbox')) {
updateFrontmatter();
markChanged(); markChanged();
} }
updateArrows();
}); });
} }