OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

show.php

19 KB
<?php
// Ancestors filtern - aktuelles Topic ausschließen
$filteredAncestors = array_filter($ancestors ?? [], fn($a) => $a['id'] != $topic['id']);

$this->layout('layouts/main', [
    'page_title' => $topic['name'],
    'breadcrumbs' => [],  // Eigener Breadcrumb im Hero
    'full_width' => true
]);

$hasImage = !empty($topic['image_path']);
?>

<div class="topic-show-fullwidth">
    <!-- Content Header -->
    <div class="content-header">
        <div>
            <h1><?= $this->e($topic['name']) ?></h1>
            <ol class="breadcrumb">
                <li class="breadcrumb-item"><a href="/"><?= $this->te('Home') ?></a></li>
                <li class="breadcrumb-item"><a href="/topics"><?= $this->te('Themengebiete') ?></a></li>
                <?php foreach ($filteredAncestors as $ancestor): ?>
                    <li class="breadcrumb-item"><a href="/topics/<?= $ancestor['id'] ?>"><?= $this->e($ancestor['name']) ?></a></li>
                <?php endforeach; ?>
                <li class="breadcrumb-item active"><?= $this->e($topic['name']) ?></li>
            </ol>
        </div>
        <div class="content-header-actions">
            <?php if ($this->hasCapabilityForTopic('create_page', $topic['id'])): ?>
                <a href="/pages/create?topic_id=<?= $topic['id'] ?>" class="btn btn-primary">
                    <i class="fas fa-plus"></i>
                    <?= $this->te('Neue Seite') ?>
                </a>
                <a href="/topics/create?parent_id=<?= $topic['id'] ?>" class="btn btn-default">
                    <i class="fas fa-plus"></i>
                    <?= $this->te('Unterthema') ?>
                </a>
            <?php endif; ?>
            <?php if ($this->hasCapability('edit_page')): ?>
                <a href="/topics/<?= $topic['id'] ?>/edit" class="btn btn-default">
                    <i class="fas fa-pencil-alt"></i> <?= $this->te('Bearbeiten') ?>
                </a>
            <?php endif; ?>
        </div>
    </div>

    <div class="topic-body">
        <?php if (!empty($is_passthrough)): ?>
        <div class="alert alert-info">
            <i class="fas fa-route"></i>
            <?= $this->te('Dieses Themengebiet ist für Sie nur der Weg zu den Unterthemen darunter. Eigene Seiten sehen Sie hier nicht.') ?>
        </div>
        <?php endif; ?>

        <!-- Unterthemen -->
        <?php if (!empty($children)): ?>
        <section class="topic-section">
            <div class="section-header">
                <h2><span aria-hidden="true">&#128193;</span> <?= $this->te('Unterthemen') ?></h2>
                <span class="count-badge"><?= count($children) ?></span>
            </div>
            <div class="subtopics-grid">
                <?php foreach ($children as $child): ?>
                    <a href="/topics/<?= $child['id'] ?>" class="subtopic-card">
                        <?php if (!empty($child['image_path'])): ?>
                            <div class="subtopic-image">
                                <img src="/uploads/topics/<?= $this->e($child['image_path']) ?>" alt="">
                            </div>
                        <?php else: ?>
                            <div class="subtopic-icon" aria-hidden="true">&#128193;</div>
                        <?php endif; ?>
                        <div class="subtopic-content">
                            <h3><?= $this->e($child['name']) ?></h3>
                            <?php if (!empty($child['description'])): ?>
                                <p><?= $this->e($this->truncate($child['description'], 80)) ?></p>
                            <?php endif; ?>
                            <span class="subtopic-meta">
                                <?= $this->te(((int)($child['total_page_count'] ?? $child['page_count'] ?? 0)) === 1 ? '{anzahl} Seite' : '{anzahl} Seiten', ['anzahl' => (int)($child['total_page_count'] ?? $child['page_count'] ?? 0)]) ?>
                            </span>
                        </div>
                        <span class="subtopic-arrow" aria-hidden="true">&rarr;</span>
                    </a>
                <?php endforeach; ?>
            </div>
        </section>
        <?php endif; ?>

        <!-- Seiten -->
        <?php $currentViewMode = $view_mode ?? 'cards'; ?>
        <?php $currentSortMode = $sort_mode ?? 'date_desc'; ?>
        <section class="topic-section">
            <div class="section-header">
                <h2><span aria-hidden="true">&#128196;</span> <?= $this->te('Seiten') ?></h2>
                <span class="count-badge"><?= count($pages ?? []) ?></span>
                <?php if (!empty($pages)): ?>
                <div class="sort-select-wrap">
                    <label for="sortSelect" class="sort-select-label"><?= $this->te('Sortierung') ?></label>
                    <select id="sortSelect" class="sort-select" onchange="saveSortMode(this.value)">
                        <option value="date_desc"  <?= $currentSortMode === 'date_desc'  ? 'selected' : '' ?>><?= $this->te('&#128337; Neueste zuerst') ?></option>
                        <option value="date_asc"   <?= $currentSortMode === 'date_asc'   ? 'selected' : '' ?>><?= $this->te('&#128337; Älteste zuerst') ?></option>
                        <option value="title_asc"  <?= $currentSortMode === 'title_asc'  ? 'selected' : '' ?>>A &#8594; Z</option>
                        <option value="title_desc" <?= $currentSortMode === 'title_desc' ? 'selected' : '' ?>>Z &#8594; A</option>
                    </select>
                </div>
                <div class="view-toggle" id="viewToggle">
                    <button class="view-toggle-btn <?= $currentViewMode === 'cards' ? 'active' : '' ?>" data-mode="cards" title="<?= $this->te('Kartenansicht') ?>" aria-label="<?= $this->te('Kartenansicht') ?>">
                        <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><rect x="1" y="1" width="6" height="6" rx="1"/><rect x="9" y="1" width="6" height="6" rx="1"/><rect x="1" y="9" width="6" height="6" rx="1"/><rect x="9" y="9" width="6" height="6" rx="1"/></svg>
                    </button>
                    <button class="view-toggle-btn <?= $currentViewMode === 'list' ? 'active' : '' ?>" data-mode="list" title="<?= $this->te('Listenansicht') ?>" aria-label="<?= $this->te('Listenansicht') ?>">
                        <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><rect x="1" y="2" width="14" height="2.5" rx="0.5"/><rect x="1" y="6.75" width="14" height="2.5" rx="0.5"/><rect x="1" y="11.5" width="14" height="2.5" rx="0.5"/></svg>
                    </button>
                </div>
                <?php endif; ?>
            </div>

            <?php if (!empty($pages)): ?>
                <!-- Kartenansicht -->
                <div class="pages-grid" id="pagesGrid" style="<?= $currentViewMode === 'list' ? 'display:none' : '' ?>">
                    <?php foreach ($pages as $page): ?>
                        <a href="/pages/<?= $page['id'] ?>" class="page-card">
                            <div class="page-card-header">
                                <span class="page-icon" aria-hidden="true">&#128196;</span>
                                <h3><?= $this->e($page['title']) ?></h3>
                            </div>
                            <div class="page-card-meta">
                                <span class="meta-item">
                                    <span class="meta-icon" aria-hidden="true">&#128337;</span>
                                    <?= $this->relativeTime($page['updated_at'] ?? $page['created_at']) ?>
                                </span>
                                <span class="meta-item">
                                    <span class="meta-icon" aria-hidden="true">&#128221;</span>
                                    v<?= $page['version_number'] ?? 1 ?>
                                </span>
                                <?php if (!empty($page['file_count']) && (int)$page['file_count'] > 0): ?>
                                    <span class="meta-item">
                                        <span class="meta-icon" aria-hidden="true">&#128206;</span>
                                        <?= (int)$page['file_count'] ?>
                                    </span>
                                <?php endif; ?>
                            </div>
                            <?php if (!empty($page['is_draft'])): ?>
                                <span class="page-badge"><?= $this->te('Entwurf') ?></span>
                            <?php endif; ?>
                        </a>
                    <?php endforeach; ?>
                </div>

                <!-- Listenansicht -->
                <div class="pages-list" id="pagesList" style="<?= $currentViewMode === 'cards' ? 'display:none' : '' ?>">
                    <?php foreach ($pages as $page): ?>
                        <a href="/pages/<?= $page['id'] ?>" class="page-list-item">
                            <span class="page-list-icon" aria-hidden="true">&#128196;</span>
                            <span class="page-list-title"><?= $this->e($page['title']) ?></span>
                            <?php if (!empty($page['is_draft'])): ?>
                                <span class="page-badge"><?= $this->te('Entwurf') ?></span>
                            <?php endif; ?>
                            <span class="page-list-meta">
                                v<?= $page['version_number'] ?? 1 ?>
                                &middot;
                                <?= $this->relativeTime($page['updated_at'] ?? $page['created_at']) ?>
                                <?php if (!empty($page['file_count']) && (int)$page['file_count'] > 0): ?>
                                    &middot; <?= $this->te('{anzahl} Dateien', ['anzahl' => (int)$page['file_count']]) ?>
                                <?php endif; ?>
                            </span>
                            <span class="page-list-arrow" aria-hidden="true">&rarr;</span>
                        </a>
                    <?php endforeach; ?>
                </div>
            <?php else: ?>
                <div class="empty-state">
                    <div class="empty-icon" aria-hidden="true">&#128196;</div>
                    <h3><?= $this->te('Noch keine Seiten') ?></h3>
                    <p><?= $this->te('Erstellen Sie die erste Dokumentationsseite in diesem Themengebiet.') ?></p>
                    <?php if ($this->hasCapabilityForTopic('create_page', $topic['id'])): ?>
                        <a href="/pages/create?topic_id=<?= $topic['id'] ?>" class="btn btn-primary">
                            <?= $this->te('Erste Seite erstellen') ?>
                        </a>
                    <?php endif; ?>
                </div>
            <?php endif; ?>
        </section>
    </div>
</div>

<style>
.topic-show-fullwidth {
}

/* Body */
.topic-body {
    padding: 2rem;
    background: var(--slate-50);
    min-height: calc(100vh - 300px);
}

.topic-section {
    margin-bottom: 2rem;
}

.section-header {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-bottom: 1rem;
}

.section-header h2 {
    margin: 0;
    font-size: 1.1rem;
    font-weight: 600;
    color: var(--gray-900);
}

.count-badge {
    background: var(--gray-200);
    color: var(--gray-700);
    padding: 0.25rem 0.625rem;
    border-radius: 9999px;
    font-size: 0.75rem;
    font-weight: 600;
}

/* Subtopics Grid */
.subtopics-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: 1rem;
}

.subtopic-card {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 1rem 1.25rem;
    background: var(--color-bg);
    border-radius: 0.25rem;
    text-decoration: none;
    box-shadow: 0 1px 3px rgba(0,0,0,0.05);
    transition: all 0.2s;
}

.subtopic-card:hover {
    box-shadow: 0 4px 12px rgba(0,0,0,0.1);

    text-decoration: none;
}

.subtopic-image {
    width: 56px;
    height: 56px;
    border-radius: 0.25rem;
    overflow: hidden;
    flex-shrink: 0;
}

.subtopic-image img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.subtopic-icon {
    width: 56px;
    height: 56px;
    background: linear-gradient(135deg, var(--blue-50) 0%, var(--blue-100) 100%);
    border-radius: 0.25rem;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.75rem;
    flex-shrink: 0;
}

.subtopic-content {
    flex: 1;
    min-width: 0;
}

.subtopic-content h3 {
    margin: 0 0 0.25rem 0;
    font-size: 1rem;
    font-weight: 600;
    color: var(--gray-900);
}

.subtopic-content p {
    margin: 0 0 0.35rem 0;
    font-size: 0.8rem;
    color: var(--gray-500);
    line-height: 1.4;
}

.subtopic-meta {
    font-size: 0.75rem;
    color: var(--gray-400);
}

.subtopic-arrow {
    color: var(--gray-300);
    font-size: 1.25rem;
    transition: all 0.15s;
}

.subtopic-card:hover .subtopic-arrow {
    color: var(--color-primary);
    transform: translateX(3px);
}

/* Pages Grid */
.pages-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 1rem;
}

.page-card {
    display: block;
    padding: 1.25rem;
    background: var(--color-bg);
    border-radius: 0.25rem;
    text-decoration: none;
    box-shadow: 0 1px 3px rgba(0,0,0,0.05);
    transition: all 0.2s;
    position: relative;
}

.page-card:hover {
    box-shadow: 0 4px 12px rgba(0,0,0,0.1);

    text-decoration: none;
}

.page-card-header {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    margin-bottom: 0.75rem;
}

.page-icon {
    font-size: 1.5rem;
    flex-shrink: 0;
}

.page-card-header h3 {
    margin: 0;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--gray-900);
    line-height: 1.4;
}

.page-card:hover h3 {
    color: var(--color-primary);
}

.page-card-meta {
    display: flex;
    gap: 1rem;
}

.page-card-meta .meta-item {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    font-size: 0.75rem;
    color: var(--gray-400);
}

.page-card-meta .meta-icon {
    font-size: 0.85rem;
}

.page-badge {
    position: absolute;
    top: 0.75rem;
    right: 0.75rem;
    background: var(--amber-100);
    color: var(--amber-800);
    padding: 0.2rem 0.5rem;
    border-radius: 9999px;
    font-size: 0.65rem;
    font-weight: 600;
}

/* View Toggle */
.sort-select-wrap {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 0.4rem;
}
.sort-select-label {
    font-size: 0.78rem;
    font-weight: 500;
    color: var(--gray-500);
    margin: 0;
}
.sort-select {
    height: 32px;
    padding: 0 0.6rem;
    background: var(--gray-100);
    border: 1px solid var(--gray-200);
    border-radius: 0.375rem;
    font-size: 0.78rem;
    font-weight: 500;
    color: var(--gray-700);
    cursor: pointer;
    outline: none;
    transition: border-color 0.15s, box-shadow 0.15s;
    appearance: none;
    -webkit-appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%236b7280' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.5rem center;
    padding-right: 1.75rem;
}
.sort-select:hover { border-color: var(--gray-300); background-color: var(--gray-50); }
.sort-select:focus { border-color: var(--blue-300); box-shadow: 0 0 0 2px rgba(var(--color-primary-rgb), 0.15); }

.view-toggle {
    display: flex;
    gap: 0.25rem;
    background: var(--gray-100);
    border-radius: 0.375rem;
    padding: 0.2rem;
}
.view-toggle-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 28px;
    border: none;
    background: transparent;
    border-radius: 0.25rem;
    color: var(--gray-400);
    cursor: pointer;
    transition: all 0.15s;
}
.view-toggle-btn:hover { color: var(--gray-500); }
.view-toggle-btn.active {
    background: var(--color-bg);
    color: var(--color-primary);
    box-shadow: 0 1px 2px rgba(0,0,0,0.1);
}

/* Listenansicht */
.pages-list {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    background: var(--color-bg);
    border-radius: 0.25rem;
    box-shadow: 0 1px 3px rgba(0,0,0,0.05);
    overflow: hidden;
}
.page-list-item {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.875rem 1.25rem;
    text-decoration: none;
    border-bottom: 1px solid var(--gray-100);
    transition: background 0.15s;
}
.page-list-item:last-child { border-bottom: none; }
.page-list-item:hover {
    background: var(--gray-50);
    text-decoration: none;
}
.page-list-icon { font-size: 1.1rem; flex-shrink: 0; }
.page-list-title {
    flex: 1;
    font-weight: 500;
    color: var(--gray-900);
    font-size: 0.95rem;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.page-list-item:hover .page-list-title { color: var(--color-primary); }
.page-list-meta {
    font-size: 0.75rem;
    color: var(--gray-400);
    white-space: nowrap;
    flex-shrink: 0;
}
.page-list-arrow {
    color: var(--gray-300);
    font-size: 1rem;
    transition: all 0.15s;
    flex-shrink: 0;
}
.page-list-item:hover .page-list-arrow {
    color: var(--color-primary);
    transform: translateX(2px);
}
.empty-state h3 {
    margin: 0 0 0.5rem 0;
    font-size: 1.1rem;
    color: var(--gray-700);
}
/* Responsive */
@media (max-width: 768px) {
    .topic-show-fullwidth {
        overflow-x: hidden;
    }

    .topic-body {
        padding: 1rem;
    }

    .subtopics-grid,
    .pages-grid {
        grid-template-columns: 1fr;
    }

    .subtopic-card {
        padding: 0.875rem 1rem;
    }

    .page-card {
        padding: 1rem;
    }

    .section-header h2 {
        font-size: 1rem;
    }

    .empty-state {
        padding: 2rem 1rem;
    }
}

@media (max-width: 480px) {
    .topic-body {
        padding: 0.75rem;
    }

    .subtopic-icon,
    .subtopic-image {
        width: 44px;
        height: 44px;
    }

    .subtopic-icon {
        font-size: 1.25rem;
    }

    .page-list-meta { display: none; }
}
</style>

<script>
(function() {
    var toggle = document.getElementById('viewToggle');
    if (!toggle) return;

    var grid = document.getElementById('pagesGrid');
    var list = document.getElementById('pagesList');
    var buttons = toggle.querySelectorAll('.view-toggle-btn');

    buttons.forEach(function(btn) {
        btn.addEventListener('click', function() {
            var mode = this.getAttribute('data-mode');
            buttons.forEach(function(b) { b.classList.remove('active'); });
            this.classList.add('active');

            if (mode === 'list') {
                grid.style.display = 'none';
                list.style.display = '';
            } else {
                grid.style.display = '';
                list.style.display = 'none';
            }

            // Einstellung per AJAX speichern
            var fd = new FormData();
            fd.append('key', 'topic_view_mode');
            fd.append('value', mode);
            fd.append('_csrf', document.querySelector('meta[name="csrf-token"]')?.content || '<?= $this->csrf() ?>');
            fetch('/api/user-settings', { method: 'POST', body: fd });
        });
    });
})();

function saveSortMode(mode) {
    var fd = new FormData();
    fd.append('mode', mode);
    fd.append('_csrf', document.querySelector('meta[name="csrf-token"]')?.content || '<?= $this->csrf() ?>');
    fetch('/topics/save-pages-sort', { method: 'POST', body: fd })
        .then(function() { window.location.reload(); });
}
</script>