diff --git a/AGENTS.md b/AGENTS.md index cb9fe44..197b4ce 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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'`. - `handlePluginsNew` moet het bestand aanmaken als `.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 - Essentiële plugins gedefinieerd in `getProtectedPlugins()` in `public/admin.php` (huidige: `Navigation`). - Deze plugins kunnen NIET worden: gedeactiveerd (`handlePluginsToggle`), verwijderd (`handlePluginsDelete`), bewerkt (`handlePluginsEdit`). diff --git a/admin/theme/default/views/pages/content-edit.twig b/admin/theme/default/views/pages/content-edit.twig index 0fe0726..334eaeb 100644 --- a/admin/theme/default/views/pages/content-edit.twig +++ b/admin/theme/default/views/pages/content-edit.twig @@ -89,6 +89,7 @@ document.addEventListener('DOMContentLoaded', function () { var filenameInput = document.getElementById('filename'); var layoutSelect = document.getElementById('layout'); var pluginSection = document.getElementById('plugin-section'); + var pluginList = document.getElementById('plugin-order-list'); if (!backBtn) return; var sidebarLayouts = {{ sidebarLayouts|json_encode|raw }}; @@ -106,86 +107,138 @@ document.addEventListener('DOMContentLoaded', function () { filenameInput.addEventListener('input', markChanged); } - // Show/hide plugin section based on layout function updatePluginVisibility() { if (!layoutSelect || !pluginSection) return; var hasSidebar = sidebarLayouts.indexOf(layoutSelect.value) !== -1; pluginSection.style.display = hasSidebar ? '' : 'none'; - // Disable checkboxes if no sidebar pluginSection.querySelectorAll('.plugin-checkbox').forEach(function(cb) { 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() { if (!pluginList) return; var items = pluginList.querySelectorAll('li[data-plugin]'); items.forEach(function(item, i) { var upBtn = item.querySelector('.plugin-up'); var downBtn = item.querySelector('.plugin-down'); - if (upBtn) upBtn.style.visibility = (i === 0) ? 'hidden' : ''; - if (downBtn) downBtn.style.visibility = (i === items.length - 1) ? 'hidden' : ''; + var upDisabled = (i === 0); + 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 - if (layoutSelect) { - layoutSelect.addEventListener('change', function () { - var newLayout = this.value; - var editor = document.getElementById('editor-textarea'); - updatePluginVisibility(); + // Update frontmatter in the editor (layout and plugins together) + function updateFrontmatter() { + var editor = document.getElementById('editor-textarea'); + if (!editor) return; - if (!editor) return; + var cm = window.codeMirrorEditor; + var content = cm ? cm.getValue() : editor.value; + var newLayout = layoutSelect ? layoutSelect.value : ''; - var cm = window.codeMirrorEditor; - var content = cm ? cm.getValue() : editor.value; + // Get current plugin order (only checked, in DOM order) + 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---/); - if (fmMatch) { - var frontmatter = fmMatch[1]; + var fmMatch = content.match(/^---\n([\s\S]*?)\n---/); + if (fmMatch) { + var frontmatter = fmMatch[1]; + + if (newLayout) { if (/^layout:\s*.+$/m.test(frontmatter)) { frontmatter = frontmatter.replace(/^layout:\s*.+$/m, 'layout: ' + newLayout); } else { 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) { - cm.setValue(content); - } else { - editor.value = content; + if (/^plugins:\s*.+$/m.test(frontmatter)) { + if (pluginsValue) { + frontmatter = frontmatter.replace(/^plugins:\s*.+$/m, 'plugins: ' + pluginsValue); + } 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(); }); } window.__onContentChange = markChanged; - // Plugin order: move list items up/down - var pluginList = document.getElementById('plugin-order-list'); + // Plugin order: move up/down + checkbox change if (pluginList) { pluginList.addEventListener('click', function (e) { var upBtn = e.target.closest('.plugin-up'); var downBtn = e.target.closest('.plugin-down'); if (!upBtn && !downBtn) return; + if (upBtn && upBtn.disabled) return; + if (downBtn && downBtn.disabled) return; var item = e.target.closest('li[data-plugin]'); if (!item) return; if (upBtn && item.previousElementSibling) { pluginList.insertBefore(item, item.previousElementSibling); - markChanged(); } else if (downBtn && item.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(); } - updateArrows(); }); }