Quelltext des aktuellen Standes
<?php
$isEdit = isset($page) && $page !== null;
$pageTitle = $isEdit ? 'Seite bearbeiten' : 'Neue Seite';
$_wurzel = dirname(__DIR__, 2);
$this->layout('layouts/main', [
'page_title' => $pageTitle,
'breadcrumbs' => [],
'full_width' => true,
'extra_css' => ['/assets/css/page-editor.css?v=' . filemtime($_wurzel . '/public/assets/css/page-editor.css')],
'extra_js' => ['/assets/js/page-editor.js?v=' . filemtime($_wurzel . '/public/assets/js/page-editor.js')],
]);
?>
<div class="page-editor">
<!-- Custom Breadcrumb -->
<nav class="editor-breadcrumb">
<a href="/"><?= $this->te('Start') ?></a>
<span class="sep">/</span>
<a href="/topics"><?= $this->te('Themengebiete') ?></a>
<?php if (isset($topic)): ?>
<span class="sep">/</span>
<a href="/topics/<?= $topic['id'] ?>"><?= $this->e($topic['name']) ?></a>
<?php endif; ?>
<?php if ($isEdit): ?>
<span class="sep">/</span>
<a href="/pages/<?= $page['id'] ?>"><?= $this->e($page['title']) ?></a>
<span class="sep">/</span>
<span class="current"><?= $this->te('Bearbeiten') ?></span>
<?php else: ?>
<span class="sep">/</span>
<span class="current"><?= $this->te('Neue Seite') ?></span>
<?php endif; ?>
</nav>
<form action="<?= $isEdit ? '/pages/' . $page['id'] : '/pages' ?>" method="POST"
class="editor-form" id="page-form" enctype="multipart/form-data">
<input type="hidden" name="_csrf" value="<?= $this->csrf() ?>">
<?php if ($isEdit): ?>
<input type="hidden" name="_method" value="PUT">
<?php endif; ?>
<!-- Header Bar -->
<header class="editor-topbar">
<div class="topbar-left">
<a href="<?= $isEdit ? '/pages/' . $page['id'] : '/topics/' . ($topic['id'] ?? '') ?>" class="btn-back" title="<?= $this->te('Zurueck') ?>">
←
</a>
<div class="page-title-wrapper">
<input type="text" name="title" id="title" class="page-title-input"
value="<?= $this->e($page['title'] ?? '') ?>"
placeholder="<?= $this->te('Seitentitel eingeben...') ?>"
required maxlength="500" autofocus>
</div>
</div>
<div class="topbar-right">
<?php if ($isEdit): ?>
<span id="autosave-status" class="autosave-status autosave-status-idle" style="display:none;"></span>
<span id="change-reason-hint" class="change-reason-hint" style="display:none;">
<i class="fas fa-pen"></i> <?= $this->te('Änderungsgrund wird abgefragt') ?>
</span>
<span class="version-indicator">v<?= $page['version_number'] ?? 1 ?></span>
<?php endif; ?>
<button type="submit" name="action" value="save" class="btn btn-primary btn-save">
<i class="fas fa-save"></i> <?= $isEdit ? 'Speichern' : 'Erstellen' ?>
</button>
</div>
</header>
<!-- Main Editor Area -->
<div class="editor-layout">
<!-- Editor Main -->
<div class="editor-main">
<!-- TinyMCE Editor -->
<div class="editor-wrapper">
<textarea name="content" id="content-editor"><?= $this->e($this->fixRelativeUrls($page['content_html'] ?? ($template_content ?? ''))) ?></textarea>
</div>
<?php if ($isEdit): ?>
<!-- Verstecktes Feld – wird per Modal befüllt -->
<input type="hidden" id="change_reason" name="change_reason" value="<?= $this->e($pending_change_reason ?? '') ?>">
<?php endif; ?>
</div>
<!-- Sidebar -->
<aside class="editor-sidebar" id="editorSidebar">
<div class="sidebar-toggle">
<button type="button" class="sidebar-toggle-btn" id="sidebarToggleBtn" title="<?= $this->te('Sidebar ein-/ausklappen') ?>">
<span class="toggle-icon">»</span>
</button>
</div>
<!-- Vorlagen-Auswahl (nur bei neuer Seite) -->
<?php if (!$isEdit && !empty($page_templates ?? [])): ?>
<div class="sidebar-section">
<div class="section-header">
<span class="section-icon"><i class="fas fa-clipboard-list" aria-hidden="true"></i></span>
<?= $this->te('Vorlage') ?>
</div>
<div class="section-content">
<select id="template-select" class="form-select">
<option value=""><?= $this->te('Keine Vorlage') ?></option>
<?php foreach ($page_templates as $category => $tpls): ?>
<optgroup label="<?= $this->e($category) ?>">
<?php foreach ($tpls as $tpl): ?>
<option value="<?= $tpl['id'] ?>" data-icon="<?= $this->e($tpl['icon']) ?>" title="<?= $this->e($tpl['description'] ?? '') ?>">
<?= $tpl['icon'] ?> <?= $this->e($tpl['name']) ?>
</option>
<?php endforeach; ?>
</optgroup>
<?php endforeach; ?>
</select>
<small class="form-help"><?= $this->te('Vorlage als Startinhalt laden.') ?></small>
</div>
</div>
<?php endif; ?>
<!-- Topic Selection -->
<div class="sidebar-section">
<div class="section-header">
<span class="section-icon"><i class="fas fa-folder" aria-hidden="true"></i></span>
<?= $this->te('Themengebiet') ?>
</div>
<?php if ($isEdit): ?>
<div class="section-content">
<select name="topic_id" class="form-select">
<?php foreach ($topics ?? [] as $t): ?>
<?php
$indent = str_repeat('-- ', $t['depth'] ?? 0);
$selected = ($t['id'] == ($topic['id'] ?? '')) ? 'selected' : '';
?>
<option value="<?= $t['id'] ?>" <?= $selected ?>>
<?= $indent ?><?= $this->e($t['name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php else: ?>
<div class="section-content">
<select name="topic_id" id="topic_id" class="form-select" required>
<option value=""><?= $this->te('Themengebiet waehlen...') ?></option>
<?php foreach ($topics ?? [] as $t): ?>
<?php
$indent = str_repeat('-- ', $t['depth'] ?? 0);
$selected = ($t['id'] == ($topic['id'] ?? (isset($_GET['topic_id']) ? (int)$_GET['topic_id'] : null))) ? 'selected' : '';
?>
<option value="<?= $t['id'] ?>" <?= $selected ?>>
<?= $indent ?><?= $this->e($t['name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php endif; ?>
</div>
<!-- Self-Service-Portal: Freigabe je Einzelseite (keine Vererbung) -->
<?php if (!empty($portal_enabled)): ?>
<div class="sidebar-section">
<div class="section-header">
<span class="section-icon"><i class="fas fa-home" aria-hidden="true"></i></span>
<?= $this->te('Self-Service-Portal') ?>
</div>
<div class="section-content">
<label style="display:flex;gap:.5rem;align-items:flex-start;font-weight:normal;cursor:pointer;">
<input type="checkbox" name="portal_published" value="1" style="margin-top:.2rem;"
<?= !empty($page['portal_published']) ? 'checked' : '' ?>>
<span><?= $this->te('Als Anleitung veröffentlichen') ?>
<small style="display:block;color:var(--color-text-muted);"><?= $this->te('Für alle Portal-Nutzer sichtbar. Gilt nur für diese Seite – Unterseiten bleiben intern.') ?></small>
</span>
</label>
</div>
</div>
<?php endif; ?>
<!-- Ansprechpartner -->
<?php if ($this->hasCapability('view_contacts')): ?>
<div class="sidebar-section">
<div class="section-header">
<span class="section-icon"><i class="fas fa-user" aria-hidden="true"></i></span>
<?= $this->te('Ansprechpartner') ?>
</div>
<div class="section-content">
<input type="hidden" name="ansprechpartner_type" id="ap-type"
value="<?= $this->e(($ansprechpartner['_type'] ?? '') ?: '') ?>">
<input type="hidden" name="ansprechpartner_id" id="ap-id"
value="<?= (int)($ansprechpartner['id'] ?? 0) ?>">
<!-- Aktuelle Auswahl -->
<div id="ap-current" style="<?= empty($ansprechpartner) ? 'display:none' : '' ?>">
<div class="ap-selected">
<span class="ap-selected-name" id="ap-selected-name">
<?php if (!empty($ansprechpartner)): ?>
<?php if (($ansprechpartner['_type'] ?? '') === 'contact'): ?>
<?= $this->e($ansprechpartner['first_name'] . ' ' . $ansprechpartner['last_name']) ?>
<?php if (!empty($ansprechpartner['company_name'])): ?>
<small>(<?= $this->e($ansprechpartner['company_name']) ?>)</small>
<?php endif; ?>
<?php elseif (($ansprechpartner['_type'] ?? '') === 'user'): ?>
<?= $this->e($ansprechpartner['display_name'] ?? $ansprechpartner['name'] ?? '') ?>
<small style="color:var(--gray-500);"><?= $this->te('(Intern)') ?></small>
<?php else: ?>
<?= $this->e($ansprechpartner['name'] ?? '') ?>
<?php endif; ?>
<?php endif; ?>
</span>
<button type="button" class="ap-remove-btn" id="ap-remove" title="<?= $this->te('Entfernen') ?>">×</button>
</div>
</div>
<!-- Suchfeld -->
<div id="ap-search-wrapper" style="<?= !empty($ansprechpartner) ? 'display:none' : '' ?>">
<input type="text" id="ap-search" placeholder="<?= $this->te('Kontakt oder Firma suchen...') ?>"
class="ap-search-input" autocomplete="off">
<div id="ap-search-results" class="ap-search-results" style="display:none;"></div>
</div>
</div>
</div>
<?php endif; ?>
<!-- Tags -->
<div class="sidebar-section">
<div class="section-header">
<span class="section-icon"><i class="fas fa-tags" aria-hidden="true"></i></span>
<?= $this->te('Tags') ?>
</div>
<div class="section-content">
<div class="tag-select-wrapper" id="tagSelectWrapper">
<div class="tag-selected-list" id="tagSelectedList">
<?php foreach ($page_tags ?? [] as $tag): ?>
<?php $tagColor = $this->safeHexColor($tag['color'] ?? null); ?>
<span class="tag-chip" data-id="<?= $tag['id'] ?>" style="background:<?= $tagColor ?>20;color:<?= $tagColor ?>;border:1px solid <?= $tagColor ?>40;">
<?= $this->e($tag['name']) ?>
<button aria-label="<?= $this->te('Löschen') ?>" type="button" class="tag-chip-remove" data-id="<?= $tag['id'] ?>">×</button>
<input type="hidden" name="tags[]" value="<?= $tag['id'] ?>">
</span>
<?php endforeach; ?>
</div>
<input type="text" id="tagSearchInput" class="tag-search-input"
placeholder="<?= $this->te('Tag suchen oder erstellen...') ?>" autocomplete="off">
<div class="tag-dropdown" id="tagDropdown" style="display:none;">
<?php foreach ($all_tags ?? [] as $tag): ?>
<?php $tagColor = $this->safeHexColor($tag['color'] ?? null); ?>
<div class="tag-dropdown-item" data-id="<?= $tag['id'] ?>" data-name="<?= $this->e($tag['name']) ?>" data-color="<?= $tagColor ?>">
<span class="tag-dropdown-color" style="background:<?= $tagColor ?>;"></span>
<?= $this->e($tag['name']) ?>
<span class="tag-dropdown-count"><?= $tag['usage_count'] ?? 0 ?></span>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
</div>
<!-- Attachments -->
<div class="sidebar-section">
<div class="section-header">
<span class="section-icon"><i class="fas fa-paperclip" aria-hidden="true"></i></span>
<?= $this->te('Anhaenge') ?> <span class="file-counter" id="file-counter">(<?= count($files ?? []) ?>)</span>
</div>
<div class="section-content">
<ul class="file-list" id="file-list">
<?php if (!empty($files)): ?>
<?php foreach ($files as $file): ?>
<li class="file-item" data-file-id="<?= $file['id'] ?>">
<span class="file-icon"><i class="fas fa-file" aria-hidden="true"></i></span>
<a href="/files/<?= $file['id'] ?>" class="file-name" target="_blank"><?= $this->e($file['original_filename']) ?></a>
<span class="file-size"><?= number_format($file['file_size'] / 1024, 1) ?> KB</span>
<button type="button" class="file-delete" data-file-id="<?= $file['id'] ?>" title="<?= $this->te('Loeschen') ?>">×</button>
</li>
<?php endforeach; ?>
<?php endif; ?>
</ul>
<?php if ($isEdit): ?>
<div class="file-dropzone" id="file-dropzone">
<input type="file" id="file-input" multiple class="file-input">
<label for="file-input" class="dropzone-label">
<span class="dropzone-icon"><i class="fas fa-upload" aria-hidden="true"></i></span>
<span><?= $this->te('Dateien hochladen') ?></span>
</label>
</div>
<div class="upload-progress" id="upload-progress" style="display: none;">
<div class="progress-bar"><div class="progress-fill" id="progress-fill"></div></div>
<span class="progress-text" id="progress-text"><?= $this->te('Hochladen...') ?></span>
</div>
<?php else: ?>
<p class="upload-hint"><?= $this->te('Speichern Sie die Seite zuerst, um Dateien hochzuladen.') ?></p>
<?php endif; ?>
</div>
</div>
<!-- Version History -->
<?php if ($isEdit): ?>
<div class="sidebar-section">
<div class="section-header">
<span class="section-icon"><i class="fas fa-history" aria-hidden="true"></i></span>
<?= $this->te('Versionen') ?>
</div>
<div class="section-content">
<a href="/pages/<?= $page['id'] ?>/versions" class="btn btn-secondary btn-sm btn-block">
<?= $this->te('Versionshistorie anzeigen') ?>
</a>
</div>
</div>
<?php endif; ?>
<!-- Seite löschen (via JS, nicht als nested form!) -->
<?php if ($isEdit && ($can_delete ?? false)): ?>
<div class="sidebar-section sidebar-section-danger">
<div class="section-header section-header-danger">
<span class="section-icon"><i class="fas fa-exclamation-triangle" aria-hidden="true"></i></span>
<?= $this->te('Gefahrenzone') ?>
</div>
<div class="section-content">
<button type="button" class="btn btn-danger btn-sm btn-block" onclick="deletePageAction()">
<i class="fas fa-trash" aria-hidden="true"></i> <?= $this->te('Seite löschen') ?>
</button>
</div>
</div>
<?php endif; ?>
</aside>
</div>
</form>
<?php if ($isEdit && !empty($draft_content) && !empty($draft_saved_at)): ?>
<!-- Entwurf-Wiederherstellungs-Modal (öffnet automatisch beim Laden) -->
<div id="draft-modal" class="modal-overlay cr-modal" role="dialog" aria-modal="true">
<div class="modal-card">
<div class="modal-header">
<h2><i class="fas fa-history"></i> <?= $this->te('Ungespeicherter Entwurf gefunden') ?></h2>
</div>
<div class="modal-body">
<p class="cr-modal-desc">
<?= $this->t('Es gibt noch einen automatisch gespeicherten Entwurf vom<br> <strong>{zeitpunkt}</strong>.', ['zeitpunkt' => date('d.m.Y \u\m H:i \U\h\r', strtotime($draft_saved_at))]) ?>
</p>
<p class="cr-modal-desc" style="color:var(--gray-500);font-size:0.82rem;margin-bottom:0;">
<?= $this->te('Möchten Sie diesen Entwurf wiederherstellen oder mit dem aktuell gespeicherten Inhalt weiterarbeiten?') ?>
</p>
</div>
<div class="modal-footer">
<button type="button" id="draft-discard-btn" class="btn btn-default">
<i class="fas fa-times"></i> <?= $this->te('Nein, verwerfen') ?>
</button>
<button type="button" id="draft-restore-btn" class="btn btn-warning">
<i class="fas fa-undo"></i> <?= $this->te('Ja, wiederherstellen') ?>
</button>
</div>
</div>
</div>
<?php endif; ?>
<?php if ($isEdit): ?>
<!-- Änderungsgrund-Modal -->
<div id="change-reason-modal" class="modal-overlay cr-modal" style="display:none;" role="dialog" aria-modal="true" aria-labelledby="cr-modal-title">
<div class="modal-card">
<div class="modal-header">
<h2 id="cr-modal-title"><i class="fas fa-edit"></i> <?= $this->te('Was wurde geändert?') ?></h2>
</div>
<div class="modal-body">
<p class="cr-modal-desc"><?= $this->te('Kurze Beschreibung der Änderungen für die Versionshistorie:') ?></p>
<label for="cr-modal-input"><?= $this->te('Änderungsgrund') ?> <span class="text-danger">*</span></label>
<input type="text" id="cr-modal-input" class="form-control"
placeholder="<?= $this->te('z.B. Tippfehler korrigiert, Abschnitt ergänzt…') ?>"
minlength="3" maxlength="500" autocomplete="off">
<div id="cr-modal-error" class="cr-modal-error" style="display:none;">
<i class="fas fa-exclamation-circle"></i> <?= $this->te('Bitte einen Änderungsgrund mit mindestens 3 Zeichen angeben.') ?>
</div>
</div>
<div class="modal-footer">
<button type="button" id="cr-modal-cancel" class="btn btn-default"><?= $this->te('Abbrechen') ?></button>
<button type="button" id="cr-modal-confirm" class="btn btn-primary">
<i class="fas fa-save"></i> <?= $this->te('Speichern') ?>
</button>
</div>
</div>
</div>
<?php endif; ?>
</div>
<?php if ($topics_ai_enabled ?? false): ?>
<!-- KI-Assistent Modal -->
<div id="ai-editor-modal" class="modal-overlay ai-modal" style="display:none;" role="dialog" aria-modal="true" aria-labelledby="ai-modal-title">
<div class="modal-card">
<div class="modal-header">
<h2 id="ai-modal-title"><?= $this->te('✨ KI-Assistent') ?></h2>
<button type="button" class="modal-close" id="ai-modal-close" title="<?= $this->te('Schließen') ?>">×</button>
</div>
<div class="modal-body">
<!-- Prompt -->
<div class="form-group">
<label for="ai-prompt"><?= $this->te('Anweisung') ?></label>
<p class="form-hint" style="margin:0 0 0.5rem;"><?= $this->te('Beschreibe was du möchten – Inhalt erstellen, bearbeiten oder ergänzen') ?></p>
<textarea id="ai-prompt" class="form-control"
placeholder="<?= $this->te('z.B.: Erstelle eine Schritt-für-Schritt-Anleitung für das Backup-Verfahren z.B.: Ergänze einen Abschnitt über Troubleshooting z.B.: Formuliere den Text professioneller und kürzer') ?>"
rows="5" maxlength="1000"></textarea>
<small class="form-hint"><?= $this->te('Ctrl+Enter zum Absenden · Max. 1000 Zeichen · Der gesamte Editor-Inhalt wird als Kontext übergeben') ?></small>
</div>
<!-- Fehler -->
<div id="ai-modal-error" class="alert alert-danger" style="display:none;margin:0;"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" id="ai-modal-cancel"><?= $this->te('Abbrechen') ?></button>
<button type="button" class="btn btn-primary" id="ai-modal-submit">
<span id="ai-modal-submit-text"><?= $this->te('✨ Generieren') ?></span>
<span id="ai-modal-loading" style="display:none;">
<span class="ai-spinner"></span> <?= $this->te('KI arbeitet…') ?>
</span>
</button>
</div>
</div>
</div>
<?php endif; // topics_ai_enabled ?>
<?php
/**
* Angaben, die das Editor-Skript vom Server braucht. Als JSON im Markup, damit
* das Skript selbst statisch ausgeliefert und zwischengespeichert werden kann.
*/
$_editorDaten = [
'isEdit' => $isEdit,
'tinymce' => TINYMCE_INSTALLED,
'aiEnabled' => (bool)($topics_ai_enabled ?? false),
'canDelete' => $isEdit && (bool)($can_delete ?? false),
'pageId' => $isEdit ? (int)$page['id'] : 0,
'draftContent' => $draft_content ?? null,
];
?>
<script type="application/json" id="page-editor-data">
<?= json_encode($_editorDaten, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>
</script>
<?php if (TINYMCE_INSTALLED): ?>
<!-- TinyMCE -->
<script src="<?= $this->asset('/assets/tinymce/tinymce.min.js') ?>"></script>
<?php else: ?>
<!-- wysiwyg.js Fallback -->
<link rel="stylesheet" href="<?= $this->asset('/assets/css/wysiwyg.css') ?>">
<script src="<?= $this->asset('/assets/js/wysiwyg.js') ?>"></script>
<?php endif; ?>
<?php if ($isEdit): ?>
<div class="lock-banner" id="lock-banner" style="display:none">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
<span id="lock-banner-text"><?= $this->te('Seite wird gesperrt…') ?></span>
</div>
<?php endif; ?>