- Thema editor (thema's net zo bewerkbaar maken als plugins): bestandsbrowser zijbalk, nieuw bestand, uploaden, verwijderen, verplaatsen, SCSS compileren vanuit editor, thema activeren/ verwijderen, nieuw thema met basis-kopie, uniforme thema-structuur - Content editor (content consistent met plugins en thema's): bestandsbrowser zijbalk naast bestaande lijst weergave, nieuw bestand/map, uploaden, verwijderen, verplaatsen, mappen beheer, layout/plugins selectie, git/backup integratie in editor zijbalk - Uniformiteit: scanEditorFiles() unified scanner (scanPluginFiles/scanThemeFiles/scanContentFiles als dunne wrappers) - Media invoegen in editor: ?theme= scope voor media-list endpoint - Bug fix: thema-naam niet overgenomen bij kopiëren - title in theme.json wordt overschreven met nieuwe themanaam - Handleidingen bijgewerkt (thema-beheer.md + content-beheer.md NL/EN) - Release notes: docs/release-notes/v2.6.2.md - Pentest 30/30, WCAG 25/25
439 lines
27 KiB
Twig
439 lines
27 KiB
Twig
{% extends "layouts/admin.twig" %}
|
|
|
|
{% block title %}{{ ta.content_editor|default('Content editor') }} - {{ ta.admin_title|default('CodePress Admin') }}{% endblock %}
|
|
|
|
{# Recursively render the content file tree as a nested, collapsible list. #}
|
|
{% macro tree(nodes, relFile, editableExts, csrfToken) %}
|
|
{% import _self as macros %}
|
|
<ul class="plugin-tree">
|
|
{% for n in nodes %}
|
|
{% set isActive = n.path == relFile %}
|
|
{% set containsActive = n.is_dir and relFile starts with (n.path ~ '/') %}
|
|
{% set icon = n.is_dir ? 'bi-folder-fill text-warning' : (n.extension == 'md' ? 'bi-file-text text-primary' : (n.extension == 'php' ? 'bi-file-code text-success' : (n.extension == 'html' ? 'bi-file-earmark text-info' : (n.extension in ['jpg','jpeg','png','gif','webp','svg'] ? 'bi-file-image text-info' : (n.extension == 'pdf' ? 'bi-file-pdf text-danger' : 'bi-file text-muted'))))) %}
|
|
<li>
|
|
{% if n.is_dir %}
|
|
{% set collapseId = 'content-tree-' ~ n.path|replace({'/':'-','.':'-'}) %}
|
|
<div class="tree-dir-row">
|
|
<button type="button" class="tree-link tree-toggle btn-toggle {{ containsActive ? '' : 'collapsed' }}"
|
|
data-bs-toggle="collapse"
|
|
data-bs-target="#{{ collapseId }}"
|
|
aria-expanded="{{ containsActive ? 'true' : 'false' }}"
|
|
title="{{ n.path }}">
|
|
<i class="bi bi-chevron-down tree-chevron"></i>
|
|
<i class="bi {{ icon }}"></i>
|
|
<span class="text-truncate">{{ n.name }}</span>
|
|
</button>
|
|
<span class="tree-dir-actions">
|
|
<button type="button" class="btn btn-link btn-sm p-0 tree-newfile-btn" title="{{ ta.new_file|default('Nieuw bestand hier') }}" aria-label="{{ ta.new_file|default('Nieuw bestand') }}" data-bs-toggle="modal" data-bs-target="#newFileModal" data-in-dir="{{ n.path }}">
|
|
<i class="bi bi-file-earmark-plus"></i>
|
|
</button>
|
|
<button type="button" class="btn btn-link btn-sm p-0 tree-newdir-btn" title="{{ ta.new_folder|default('Nieuwe map') }}" aria-label="{{ ta.new_folder|default('Nieuwe map') }}" data-bs-toggle="modal" data-bs-target="#newDirModal" data-in-dir="{{ n.path }}">
|
|
<i class="bi bi-folder-plus"></i>
|
|
</button>
|
|
<a href="/admin/content-dir-rename-in?dir={{ n.path|url_encode }}" class="tree-rename-btn" title="{{ ta.rename|default('Hernoemen') }}" aria-label="{{ ta.rename|default('Hernoemen') }}">
|
|
<i class="bi bi-pencil"></i>
|
|
</a>
|
|
<form method="POST" action="/admin/content-dir-delete-in" class="d-inline" onsubmit="return confirm('{{ ta.confirm_delete_folder|default('Weet je zeker dat je deze map wilt verwijderen? De map moet leeg zijn.') }}')">
|
|
<input type="hidden" name="csrf_token" value="{{ csrfToken }}">
|
|
<input type="hidden" name="dir" value="{{ n.path }}">
|
|
<button type="submit" class="btn btn-link btn-sm p-0 text-danger tree-delete-btn" title="{{ ta.delete|default('Verwijderen') }}" aria-label="{{ ta.delete|default('Verwijderen') }}">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</form>
|
|
</span>
|
|
</div>
|
|
<div id="{{ collapseId }}"
|
|
class="tree-collapse collapse {{ containsActive ? 'show' : '' }}">
|
|
{% if n.children is not empty %}
|
|
{{ macros.tree(n.children, relFile, editableExts, csrfToken) }}
|
|
{% endif %}
|
|
</div>
|
|
{% else %}
|
|
<a href="/admin/content-files?file={{ n.path|url_encode }}"
|
|
class="tree-link {{ isActive ? 'is-active' : '' }}"
|
|
title="{{ n.path }}">
|
|
<span class="tree-chevron-spacer"></span>
|
|
<i class="bi {{ icon }}"></i>
|
|
<span class="text-truncate">{{ n.name }}</span>
|
|
{% if n.extension in editableExts %}
|
|
<span class="badge bg-secondary badge-ext">{{ n.extension|upper }}</span>
|
|
{% endif %}
|
|
</a>
|
|
<a href="/admin/content-file-move?file={{ n.path|url_encode }}"
|
|
class="tree-move-btn" title="{{ ta.move|default('Verplaatsen') }}" aria-label="{{ ta.move|default('Verplaatsen') }}">
|
|
<i class="bi bi-arrows-move"></i>
|
|
</a>
|
|
<form method="POST" action="/admin/content-file-delete" class="tree-delete-form" onsubmit="return confirm('{{ ta.confirm_delete_file|default('Weet je zeker dat je dit bestand wilt verwijderen?') }}')">
|
|
<input type="hidden" name="csrf_token" value="{{ csrfToken }}">
|
|
<input type="hidden" name="file" value="{{ n.path }}">
|
|
<button type="submit" class="btn btn-link btn-sm p-0 text-danger tree-delete-btn" title="{{ ta.delete|default('Verwijderen') }}" aria-label="{{ ta.delete|default('Verwijderen') }}">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</form>
|
|
{% endif %}
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
{% endmacro %}
|
|
{% import _self as tree_macros %}
|
|
|
|
{% block extra_css %}
|
|
<link rel="stylesheet" href="/admin/assets/codemirror/codemirror.min.css">
|
|
<link rel="stylesheet" href="/admin/assets/css/editor.css">
|
|
<style>
|
|
.plugin-editor-layout { display: flex; gap: 1rem; align-items: stretch; }
|
|
.plugin-file-tree { width: 300px; flex: 0 0 300px; }
|
|
.plugin-editor-main { flex: 1 1 auto; min-width: 0; }
|
|
.plugin-tree { list-style: none; padding-left: 0; margin: 0; }
|
|
.plugin-tree ul { list-style: none; padding-left: 1.1rem; margin: 0; }
|
|
.plugin-tree li { padding: 0; position: relative; }
|
|
.plugin-tree li > .tree-link { display: flex; align-items: center; gap: .3rem; padding: .2rem .4rem; border-radius: .25rem; text-decoration: none; color: inherit; font-size: .9rem; line-height: 1.4; }
|
|
.plugin-tree li > .tree-link:hover { background-color: rgba(13,110,253,.08); }
|
|
.plugin-tree li > .tree-link.is-active { background-color: var(--bs-primary-bg-subtle, #cfe2ff); font-weight: 600; }
|
|
.plugin-tree .tree-toggle { cursor: pointer; user-select: none; width: 100%; text-align: left; background: transparent; border: 0; color: inherit; }
|
|
.plugin-tree .tree-chevron { transition: transform .15s ease; flex: 0 0 auto; }
|
|
.plugin-tree .tree-chevron-spacer { display: inline-block; width: .9rem; flex: 0 0 auto; }
|
|
.plugin-tree .btn-toggle.collapsed .tree-chevron { transform: rotate(-90deg); }
|
|
.plugin-tree .badge-ext { font-size: .6rem; margin-left: auto; }
|
|
.plugin-tree .text-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
.plugin-tree .tree-delete-form { position: absolute; right: .25rem; top: 50%; transform: translateY(-50%); margin: 0; }
|
|
.plugin-tree .tree-delete-btn { font-size: .9rem; line-height: 1; padding: .15rem; color: #dc3545; background: transparent; border: 0; }
|
|
.plugin-tree .tree-delete-btn:hover { color: #b02a37; }
|
|
.plugin-tree .tree-move-btn { position: absolute; right: 1.75rem; top: 50%; transform: translateY(-50%); font-size: .9rem; line-height: 1; padding: .15rem; color: #6c757d; text-decoration: none; }
|
|
.plugin-tree .tree-move-btn:hover { color: #0d6efd; }
|
|
.plugin-tree li > .tree-link { padding-right: 3.5rem; }
|
|
.plugin-file-tree .card-body { max-height: 70vh; overflow-y: auto; }
|
|
/* Directory action buttons (new file/dir, rename, delete) */
|
|
.tree-dir-row { position: relative; }
|
|
.tree-dir-row .tree-toggle { padding-right: 7rem; }
|
|
.tree-dir-actions { position: absolute; right: .25rem; top: 50%; transform: translateY(-50%); display: flex; gap: .15rem; align-items: center; }
|
|
.tree-dir-actions .btn-link { color: #6c757d; }
|
|
.tree-dir-actions .btn-link:hover { color: #0d6efd; }
|
|
.tree-dir-actions .tree-delete-btn { color: #dc3545; }
|
|
.tree-dir-actions .tree-delete-btn:hover { color: #b02a37; }
|
|
/* Git status badge */
|
|
.git-status { font-size: .75rem; }
|
|
.git-status .badge { font-weight: 400; }
|
|
@media (max-width: 768px) {
|
|
.plugin-editor-layout { flex-direction: column; }
|
|
.plugin-file-tree { width: 100%; flex-basis: auto; max-height: 320px; }
|
|
.plugin-file-tree .card-body { max-height: 260px; }
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="d-flex justify-content-between align-items-center mb-4 flex-wrap gap-2">
|
|
<h2 class="mb-0"><i class="bi bi-pencil"></i> {{ ta.content_editor|default('Content editor') }}</h2>
|
|
<div class="d-flex gap-2 flex-wrap">
|
|
{# Fase 5: backup/git integratie in zijbalk header #}
|
|
{% if gitStatus.available %}
|
|
<a href="/admin/content-backup" class="btn btn-outline-info btn-sm" title="{{ ta.backup|default('Backup') }}">
|
|
<i class="bi bi-archive"></i> {{ ta.backup|default('Backup') }}
|
|
</a>
|
|
{% if gitStatus.dirty %}
|
|
<form method="POST" action="/admin/content-git-commit" class="d-inline" onsubmit="return confirm('{{ ta.git_commit_confirm|default('Wijzigingen committen naar git?') }}')">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<button type="submit" class="btn btn-outline-success btn-sm" title="{{ ta.git_commit|default('Git commit') }}">
|
|
<i class="bi bi-git"></i> {{ ta.git_commit|default('Commit') }}
|
|
</button>
|
|
</form>
|
|
{% endif %}
|
|
{% else %}
|
|
<form method="POST" action="/admin/content-git-init" class="d-inline" onsubmit="return confirm('{{ ta.git_init_confirm|default('Git repository initialiseren in content/?') }}')">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<button type="submit" class="btn btn-outline-secondary btn-sm" title="{{ ta.git_init|default('Git init') }}">
|
|
<i class="bi bi-git"></i> {{ ta.git_init|default('Git init') }}
|
|
</button>
|
|
</form>
|
|
<a href="/admin/content-backup" class="btn btn-outline-info btn-sm" title="{{ ta.backup|default('Backup') }}">
|
|
<i class="bi bi-archive"></i> {{ ta.backup|default('Backup') }}
|
|
</a>
|
|
{% endif %}
|
|
<button type="button" class="btn btn-outline-success btn-sm" data-bs-toggle="collapse" data-bs-target="#contentUploadForm">
|
|
<i class="bi bi-cloud-upload"></i> {{ ta.upload|default('Upload') }}
|
|
</button>
|
|
<button type="button" class="btn btn-outline-primary btn-sm" data-bs-toggle="modal" data-bs-target="#newFileModal" data-in-dir="">
|
|
<i class="bi bi-file-earmark-plus"></i> {{ ta.new_file|default('Nieuw bestand') }}
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#newDirModal" data-in-dir="">
|
|
<i class="bi bi-folder-plus"></i> {{ ta.new_folder|default('Nieuwe map') }}
|
|
</button>
|
|
<a href="/admin/content" class="btn btn-outline-secondary btn-sm">
|
|
<i class="bi bi-list"></i> {{ ta.list_view|default('Lijst weergave') }}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
{# Fase 5: git status badge bovenaan #}
|
|
{% if gitStatus.available %}
|
|
<div class="git-status mb-2 d-flex gap-2 align-items-center flex-wrap">
|
|
<span class="badge bg-secondary"><i class="bi bi-git"></i> {{ gitStatus.branch|default('git') }}</span>
|
|
{% if gitStatus.dirty %}
|
|
<span class="badge bg-warning text-dark">{{ ta.git_dirty|default('niet-committed wijzigingen') }}</span>
|
|
{% else %}
|
|
<span class="badge bg-success">{{ ta.git_clean|default('schone werkmap') }}</span>
|
|
{% endif %}
|
|
{% if gitStatus.lastCommit %}<span class="text-muted small">{{ ta.git_last_commit|default('laatste commit:') }} {{ gitStatus.lastCommit }}</span>{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# Upload form (collapsed by default) #}
|
|
<div class="collapse mb-3" id="contentUploadForm">
|
|
<div class="card shadow-sm">
|
|
<div class="card-body">
|
|
<form method="POST" action="/admin/content-file-upload" enctype="multipart/form-data">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<input type="hidden" name="dir" value="" id="contentUploadDir">
|
|
<div class="mb-3">
|
|
<label for="contentUploadFile" class="form-label">{{ ta.select_files|default('Bestanden selecteren') }}</label>
|
|
<input type="file" class="form-control" id="contentUploadFile" name="file[]" multiple accept="image/jpeg,image/png,image/gif,image/webp,image/svg+xml,application/pdf,application/zip,video/mp4,video/webm,audio/mpeg,audio/wav,.css,.scss,.js,.json,.html,.md">
|
|
<small class="form-text text-muted">{{ ta.content_upload_help|default('Bestanden worden geüpload naar content/. Toegestaan: afbeeldingen, video, audio, PDF, ZIP, office docs, CSS, SCSS, JS, JSON, HTML, MD.') }}</small>
|
|
</div>
|
|
<button type="submit" class="btn btn-success">
|
|
<i class="bi bi-cloud-upload"></i> {{ ta.upload_to_folder|default('Uploaden') }}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="plugin-editor-layout">
|
|
{# File tree sidebar #}
|
|
<div class="plugin-file-tree">
|
|
<div class="card shadow-sm h-100">
|
|
<div class="card-header bg-white py-2 d-flex justify-content-between align-items-center">
|
|
<span class="small text-muted fw-semibold"><i class="bi bi-folder2-open"></i> {{ ta.content_files|default('Content bestanden') }}</span>
|
|
</div>
|
|
<div class="card-body p-2" id="pluginFileTree">
|
|
{% if files is empty %}
|
|
<div class="small text-muted p-2">{{ ta.content_no_files|default('Geen bestanden gevonden.') }}</div>
|
|
{% else %}
|
|
{{ tree_macros.tree(files, relFile, editableExts, csrf_token) }}
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# Editor main panel #}
|
|
<div class="plugin-editor-main">
|
|
{% if relFile %}
|
|
<nav aria-label="breadcrumb" class="mb-2">
|
|
<ol class="breadcrumb mb-0">
|
|
<li class="breadcrumb-item"><i class="bi bi-folder2-open"></i> content</li>
|
|
{% set crumbPath = '' %}
|
|
{% set parts = relFile|split('/') %}
|
|
{% for part in parts %}
|
|
{% set crumbPath = crumbPath ? crumbPath ~ '/' ~ part : part %}
|
|
{% if loop.last %}
|
|
<li class="breadcrumb-item active" aria-current="page">{{ part }}</li>
|
|
{% else %}
|
|
<li class="breadcrumb-item"><a href="/admin/content-files?file={{ crumbPath|url_encode }}">{{ part }}</a></li>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</ol>
|
|
</nav>
|
|
{% endif %}
|
|
|
|
{% if isEditable %}
|
|
<form method="POST" action="/admin/content-files?file={{ relFile|url_encode }}" id="editor-form">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<div class="card shadow-sm">
|
|
<div class="card-body">
|
|
{# Metadata row: filename display, layout selector, plugins #}
|
|
<div class="row mb-3">
|
|
<div class="col-md-4">
|
|
<label class="form-label text-muted small mb-1">{{ ta.filename|default('Bestandsnaam') }}</label>
|
|
<p class="form-control-plaintext form-control-sm mb-0"><code>{{ fileName }}</code></p>
|
|
</div>
|
|
{% if fileExt == 'md' %}
|
|
<div class="col-md-4">
|
|
<label for="layout" class="form-label">{{ ta.template_layout|default('Sjabloon / Layout') }} <small class="text-muted">({{ activeThemeName|default('default') }})</small></label>
|
|
<select class="form-select form-select-sm" id="layout" name="layout">
|
|
{% for key, layoutFile in themeLayouts %}
|
|
<option value="{{ key }}" {{ currentLayout == key ? 'selected' : '' }}>{{ key|replace({'_': ' '})|capitalize }}{% if key == themeDefaultLayout %} (standaard){% endif %}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
{% if availablePlugins is not empty %}
|
|
<div class="col-md-4">
|
|
<label class="form-label d-block">{{ ta.visible_plugins|default('Zichtbare plugins') }}</label>
|
|
<div class="d-flex flex-wrap gap-1">
|
|
{% for plugin in availablePlugins %}
|
|
<input type="checkbox" class="btn-check" id="plugin-{{ plugin }}" name="plugins[]" value="{{ plugin }}" autocomplete="off" {{ plugin in selectedPlugins ? 'checked' : '' }}>
|
|
<label class="btn btn-outline-primary btn-sm" for="plugin-{{ plugin }}">{{ plugin }}</label>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
{% endif %}
|
|
</div>
|
|
{% if currentAuthorName or currentAuthorEmail or currentCreated %}
|
|
<div class="row mb-3">
|
|
<div class="col-md-4">
|
|
<label class="form-label text-muted"><i class="bi bi-person"></i> {{ ta.author_name|default('Auteur naam') }}</label>
|
|
<input type="text" class="form-control-plaintext form-control-sm" value="{{ currentAuthorName }}" readonly>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label text-muted"><i class="bi bi-envelope"></i> {{ ta.author_email|default('Auteur e-mail') }}</label>
|
|
<input type="text" class="form-control-plaintext form-control-sm" value="{{ currentAuthorEmail }}" readonly>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label text-muted"><i class="bi bi-calendar"></i> {{ ta.created|default('Aangemaakt') }}</label>
|
|
<input type="text" class="form-control-plaintext form-control-sm" value="{{ currentCreated }}" readonly>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
<div class="editor-toolbar" id="editor-toolbar"></div>
|
|
<div class="editor-wrapper">
|
|
<textarea name="content" id="editor-textarea" data-ext="{{ fileExt }}">{{ fileContent }}</textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="d-flex gap-2 mt-3">
|
|
<button type="submit" class="btn btn-primary" title="{{ ta.save_ctrl_s|default('Opslaan (Ctrl+S)') }}">
|
|
<i class="bi bi-check-lg"></i> {{ ta.save|default('Opslaan') }}
|
|
</button>
|
|
<a href="/{{ currentLang }}{% if fileDir %}/{{ fileDir }}{% endif %}/{{ fileName|replace({'.md': '', '.php': '', '.html': ''}) }}" target="_blank" class="btn btn-outline-info" title="{{ ta.open_new_tab|default('Open in nieuw tabblad') }}">
|
|
<i class="bi bi-eye"></i> {{ ta.preview|default('Preview') }}
|
|
</a>
|
|
<a href="/admin/content?dir={{ fileDir|url_encode }}" class="btn btn-outline-secondary">{{ ta.list_view|default('Lijst weergave') }}</a>
|
|
</div>
|
|
</form>
|
|
{% else %}
|
|
<div class="card shadow-sm">
|
|
<div class="card-body text-center py-5 text-muted">
|
|
<i class="bi bi-file-earmark-slash display-6 d-block mb-2"></i>
|
|
{% if relFile %}
|
|
{{ ta.content_not_editable|default('Dit bestandstype kan niet in de editor bewerkt worden.') }}
|
|
{% else %}
|
|
{{ ta.content_select_file|default('Selecteer een bestand uit de zijbalk om te bewerken.') }}
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
{# New file modal #}
|
|
<div class="modal fade" id="newFileModal" tabindex="-1" aria-labelledby="newFileModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<form method="POST" action="/admin/content-files">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<input type="hidden" name="action" value="new_file">
|
|
<input type="hidden" name="in_dir" id="newFileDir" value="">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="newFileModalLabel"><i class="bi bi-file-earmark-plus"></i> {{ ta.new_file_title|default('Nieuw bestand aanmaken') }}</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label for="newFilenameInput" class="form-label">{{ ta.content_file_path|default('Bestandspad binnen content') }}</label>
|
|
<div class="input-group">
|
|
<input type="text" class="form-control" id="newFilenameInput" name="new_filename" placeholder="Bijv. nl.pagina of blog/nl.post" required autofocus>
|
|
</div>
|
|
<div class="form-text">{{ ta.content_file_path_help|default('Alleen letters, cijfers, punten, underscores, streepjes en slashes. Submappen worden automatisch aangemaakt. De extensie wordt hieronder gekozen.') }}</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="newExtSelect" class="form-label">{{ ta.file_type|default('Bestandstype') }}</label>
|
|
<select class="form-select" id="newExtSelect" name="new_ext">
|
|
<option value="md">Markdown (.md)</option>
|
|
<option value="php">PHP (.php)</option>
|
|
<option value="html">HTML (.html)</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ ta.cancel|default('Annuleren') }}</button>
|
|
<button type="submit" class="btn btn-primary"><i class="bi bi-check-lg"></i> {{ ta.create|default('Aanmaken') }}</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# New directory modal #}
|
|
<div class="modal fade" id="newDirModal" tabindex="-1" aria-labelledby="newDirModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<form method="POST" action="/admin/content-dir-create-in">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<input type="hidden" name="in_dir" id="newDirInDir" value="">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="newDirModalLabel"><i class="bi bi-folder-plus"></i> {{ ta.new_folder_title|default('Nieuwe map aanmaken') }}</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label for="dirnameInput" class="form-label">{{ ta.folder_name|default('Mapnaam') }}</label>
|
|
<input type="text" class="form-control" id="dirnameInput" name="dirname" required autofocus>
|
|
<div class="form-text">{{ ta.name_help|default('Alleen letters, cijfers, punten, underscores en streepjes.') }}</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ ta.cancel|default('Annuleren') }}</button>
|
|
<button type="submit" class="btn btn-primary"><i class="bi bi-check-lg"></i> {{ ta.create|default('Aanmaken') }}</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Pass the in-dir value from the tree button to the modal hidden fields
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
var newFileModal = document.getElementById('newFileModal');
|
|
var newDirModal = document.getElementById('newDirModal');
|
|
if (newFileModal) {
|
|
newFileModal.addEventListener('show.bs.modal', function (event) {
|
|
var trigger = event.relatedTarget;
|
|
var inDir = trigger ? trigger.getAttribute('data-in-dir') : '';
|
|
var hidden = document.getElementById('newFileDir');
|
|
if (hidden) hidden.value = inDir || '';
|
|
// Show the in-dir prefix as a hint in the placeholder
|
|
var input = document.getElementById('newFilenameInput');
|
|
if (input) {
|
|
input.value = '';
|
|
input.placeholder = inDir ? inDir + '/naam' : 'Bijv. nl.pagina of blog/nl.post';
|
|
}
|
|
});
|
|
}
|
|
if (newDirModal) {
|
|
newDirModal.addEventListener('show.bs.modal', function (event) {
|
|
var trigger = event.relatedTarget;
|
|
var inDir = trigger ? trigger.getAttribute('data-in-dir') : '';
|
|
var hidden = document.getElementById('newDirInDir');
|
|
if (hidden) hidden.value = inDir;
|
|
});
|
|
}
|
|
|
|
// Layout select → update frontmatter (spiegel van content-edit.twig)
|
|
var layoutSelect = document.getElementById('layout');
|
|
if (layoutSelect) {
|
|
layoutSelect.addEventListener('change', function () {
|
|
var newLayout = this.value;
|
|
var cm = window.codeMirrorEditor;
|
|
var editor = document.getElementById('editor-textarea');
|
|
var content = cm ? cm.getValue() : (editor ? editor.value : '');
|
|
var fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
if (fmMatch) {
|
|
var frontmatter = fmMatch[1];
|
|
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 if (editor) { editor.value = content; }
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
{% endblock %} |