Change CSS framework from PicoCSS to Bootstrap 5.3

This commit is contained in:
2025-11-27 14:46:51 +08:00
parent e51768622f
commit 39cda7814f
245 changed files with 678223 additions and 2750 deletions
+103 -78
View File
@@ -2,86 +2,111 @@
// views/books/create.php
include 'views/layouts/header.php';
?>
<h1>Создание новой книги</h1>
<?php if (isset($_SESSION['error'])): ?>
<div class="alert alert-error">
<?= e($_SESSION['error']) ?>
<?php unset($_SESSION['error']); ?>
</div>
<?php endif; ?>
<?php if (isset($error) && $error): ?>
<div class="alert alert-error">
<?= e($error) ?>
</div>
<?php endif; ?>
<?php if (isset($cover_error) && $cover_error): ?>
<div class="alert alert-error">
Ошибка загрузки обложки: <?= e($cover_error) ?>
</div>
<?php endif; ?>
<form method="post" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<div style="max-width: 100%; margin-bottom: 0.5rem;">
<label for="title" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Название книги *
</label>
<input type="text" id="title" name="title"
value="<?= e($_POST['title'] ?? '') ?>"
placeholder="Введите название книги"
style="width: 100%; margin-bottom: 1.5rem;"
required>
<label for="genre" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Жанр
</label>
<input type="text" id="genre" name="genre"
value="<?= e($_POST['genre'] ?? '') ?>"
placeholder="Например: Фантастика, Роман, Детектив..."
style="width: 100%; margin-bottom: 1.5rem;">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h2">Создание новой книги</h1>
<a href="<?= SITE_URL ?>/books" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left"></i> Назад к книгам
</a>
</div>
<label for="series_id" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Серия
</label>
<select id="series_id" name="series_id" style="width: 100%; margin-bottom: 1rem;">
<option value="">-- Без серии --</option>
<?php foreach ($series as $ser): ?>
<option value="<?= $ser['id'] ?>" <?= (($_POST['series_id'] ?? '') == $ser['id']) ? 'selected' : '' ?>>
<?= e($ser['title']) ?>
</option>
<?php endforeach; ?>
</select>
<label for="description" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Описание книги
</label>
<textarea id="description" name="description"
placeholder="Краткое описание сюжета или аннотация..."
rows="6"
style="width: 100;"><?= e($_POST['description'] ?? '') ?></textarea>
<div style="margin-bottom: 1rem;">
<label for="cover_image" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Обложка книги
</label>
<input type="file" id="cover_image" name="cover_image"
accept="image/jpeg,image/png,image/gif,image/webp">
<small style="color: var(--muted-color);">
Разрешены форматы: JPG, PNG, GIF, WebP. Максимальный размер: 5MB.
</small>
</div>
<div style="margin-top: 1rem;">
<label for="published">
<input type="checkbox" id="published" name="published" value="1"
<?= (!empty($_POST['published']) && $_POST['published']) ? 'checked' : '' ?>>
Опубликовать книгу (показывать на публичной странице автора)
</label>
<?php if (isset($_SESSION['error'])): ?>
<div class="alert alert-danger">
<?= e($_SESSION['error']) ?>
<?php unset($_SESSION['error']); ?>
</div>
<?php endif; ?>
<?php if (isset($error) && $error): ?>
<div class="alert alert-danger">
<?= e($error) ?>
</div>
<?php endif; ?>
<?php if (isset($cover_error) && $cover_error): ?>
<div class="alert alert-danger">
Ошибка загрузки обложки: <?= e($cover_error) ?>
</div>
<?php endif; ?>
<div class="card">
<div class="card-body">
<form method="post" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<div class="mb-3">
<label for="title" class="form-label">Название книги *</label>
<input type="text" class="form-control" id="title" name="title"
value="<?= e($_POST['title'] ?? '') ?>"
placeholder="Введите название книги" required>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="genre" class="form-label">Жанр</label>
<input type="text" class="form-control" id="genre" name="genre"
value="<?= e($_POST['genre'] ?? '') ?>"
placeholder="Например: Фантастика, Роман, Детектив...">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="series_id" class="form-label">Серия</label>
<select class="form-select" id="series_id" name="series_id">
<option value="">-- Без серии --</option>
<?php foreach ($series as $ser): ?>
<option value="<?= $ser['id'] ?>" <?= (($_POST['series_id'] ?? '') == $ser['id']) ? 'selected' : '' ?>>
<?= e($ser['title']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
</div>
<div class="mb-3">
<label for="description" class="form-label">Описание книги</label>
<textarea class="form-control" id="description" name="description"
placeholder="Краткое описание сюжета или аннотация..."
rows="4"><?= e($_POST['description'] ?? '') ?></textarea>
</div>
<div class="mb-3">
<label for="cover_image" class="form-label">Обложка книги</label>
<input type="file" class="form-control" id="cover_image" name="cover_image"
accept="image/jpeg,image/png,image/gif,image/webp">
<div class="form-text">
Разрешены форматы: JPG, PNG, GIF, WebP. Максимальный размер: 5MB.
</div>
</div>
<div class="mb-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="published" name="published" value="1"
<?= (!empty($_POST['published']) && $_POST['published']) ? 'checked' : '' ?>>
<label class="form-check-label" for="published">
Опубликовать книгу (показывать на публичной странице автора)
</label>
</div>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">
<i class="bi bi-journal-plus"></i> Создать книгу
</button>
<a href="<?= SITE_URL ?>/books" class="btn btn-outline-secondary">
<i class="bi bi-x-circle"></i> Отмена
</a>
</div>
</form>
</div>
</div>
</div>
</div>
<div style="display: flex; gap: 10px; flex-wrap: wrap; margin-top: 1.5rem;">
<button type="submit" class="contrast">
📖 Создать книгу
</button>
<a href="<?= SITE_URL ?>/books" role="button" class="secondary">
❌ Отмена
</a>
</div>
</form>
</div>
<?php include 'views/layouts/footer.php'; ?>
+262 -212
View File
@@ -2,229 +2,279 @@
// views/books/edit.php
include 'views/layouts/header.php';
?>
<?php if (isset($_SESSION['cover_error'])): ?>
<div class="alert alert-error">
Ошибка загрузки обложки: <?= e($_SESSION['cover_error']) ?>
<?php unset($_SESSION['cover_error']); ?>
<div class="container">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h2">Редактирование книги</h1>
<a href="<?= SITE_URL ?>/books" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left"></i> Назад к книгам
</a>
</div>
<?php endif; ?>
<h1>Редактирование книги</h1>
<form method="post" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<div style="max-width: 100%; margin-bottom: 0.5rem;">
<label for="title" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Название книги *
</label>
<input type="text" id="title" name="title"
value="<?= e($book['title'] ?? '') ?>"
placeholder="Введите название книги"
style="width: 100%; margin-bottom: 1.5rem;"
required>
<label for="genre" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Жанр
</label>
<input type="text" id="genre" name="genre"
value="<?= e($book['genre'] ?? '') ?>"
placeholder="Например: Фантастика, Роман, Детектив..."
style="width: 100%; margin-bottom: 1.5rem;">
<label for="series_id" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Серия
</label>
<select id="series_id" name="series_id" style="width: 100%; margin-bottom: 1rem;">
<option value="">-- Без серии --</option>
<?php foreach ($series as $ser): ?>
<option value="<?= $ser['id'] ?>" <?= ($ser['id'] == ($book['series_id'] ?? 0)) ? 'selected' : '' ?>>
<?= e($ser['title']) ?>
</option>
<?php endforeach; ?>
</select>
<label for="sort_order_in_series" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Порядок в серии
</label>
<input type="number" id="sort_order_in_series" name="sort_order_in_series"
value="<?= e($book['sort_order_in_series'] ?? '') ?>"
placeholder="Номер по порядку в серии"
min="1"
style="width: 100%; margin-bottom: 1.5rem;">
<!-- Обложка -->
<div style="margin-bottom: 1.5rem;">
<label for="cover_image" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Обложка книги
</label>
<?php if (!empty($book['cover_image'])): ?>
<div style="margin-bottom: 1rem;">
<p><strong>Текущая обложка:</strong></p>
<img src="<?= COVERS_URL . e($book['cover_image']) ?>"
alt="Обложка"
style="max-width: 200px; height: auto; border-radius: 4px; border: 1px solid var(--border-color);">
<div style="margin-top: 0.5rem;">
<label style="display: inline-flex; align-items: center; gap: 0.5rem;">
<input type="checkbox" name="delete_cover" value="1">
Удалить обложку
</label>
<?php if (isset($_SESSION['cover_error'])): ?>
<div class="alert alert-danger">
Ошибка загрузки обложки: <?= e($_SESSION['cover_error']) ?>
<?php unset($_SESSION['cover_error']); ?>
</div>
<?php endif; ?>
<div class="row">
<div class="col-lg-8">
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title mb-0">Основная информация</h5>
</div>
<div class="card-body">
<form method="post" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<div class="mb-3">
<label for="title" class="form-label">Название книги *</label>
<input type="text" class="form-control" id="title" name="title"
value="<?= e($book['title'] ?? '') ?>"
placeholder="Введите название книги" required>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="genre" class="form-label">Жанр</label>
<input type="text" class="form-control" id="genre" name="genre"
value="<?= e($book['genre'] ?? '') ?>"
placeholder="Например: Фантастика, Роман, Детектив...">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="series_id" class="form-label">Серия</label>
<select class="form-select" id="series_id" name="series_id">
<option value="">-- Без серии --</option>
<?php foreach ($series as $ser): ?>
<option value="<?= $ser['id'] ?>" <?= ($ser['id'] == ($book['series_id'] ?? 0)) ? 'selected' : '' ?>>
<?= e($ser['title']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
</div>
<div class="mb-3">
<label for="sort_order_in_series" class="form-label">Порядок в серии</label>
<input type="number" class="form-control" id="sort_order_in_series" name="sort_order_in_series"
value="<?= e($book['sort_order_in_series'] ?? '') ?>"
placeholder="Номер по порядку в серии" min="1">
</div>
<div class="mb-3">
<label for="description" class="form-label">Описание книги</label>
<textarea class="form-control" id="description" name="description"
placeholder="Краткое описание сюжета или аннотация..."
rows="4"><?= e($book['description'] ?? '') ?></textarea>
</div>
<div class="mb-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="published" name="published" value="1"
<?= !empty($book['published']) ? 'checked' : '' ?>>
<label class="form-check-label" for="published">
Опубликовать книгу (показывать на публичной странице автора)
</label>
</div>
</div>
<button type="submit" class="btn btn-primary">
<i class="bi bi-check-circle"></i> Сохранить изменения
</button>
</form>
</div>
</div>
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title mb-0">Главы книги</h5>
</div>
<div class="card-body">
<?php if (!empty($chapters)): ?>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Название</th>
<th>Статус</th>
<th>Слов</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
<?php foreach ($chapters as $chapter): ?>
<tr>
<td><?= e($chapter['title']) ?></td>
<td>
<span class="badge <?= $chapter['status'] == 'published' ? 'bg-success' : 'bg-warning' ?>">
<?= $chapter['status'] == 'published' ? 'Опубликована' : 'Черновик' ?>
</span>
</td>
<td><?= $chapter['word_count'] ?></td>
<td>
<a href="<?= SITE_URL ?>/chapters/<?= $chapter['id'] ?>/edit" class="btn btn-sm btn-outline-primary">
<i class="bi bi-pencil"></i> Редактировать
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php else: ?>
<div class="text-center py-4">
<i class="bi bi-file-text fs-1 text-muted"></i>
<p class="text-muted mt-2">В этой книге пока нет глав</p>
</div>
<?php endif; ?>
<div class="d-flex gap-2">
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/chapters" class="btn btn-outline-primary">
<i class="bi bi-list-ul"></i> Все главы
</a>
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/chapters/create" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> Добавить главу
</a>
</div>
</div>
<?php endif; ?>
<input type="file" id="cover_image" name="cover_image"
accept="image/jpeg, image/png, image/gif, image/webp"
style="height: 2.6rem;">
<small style="color: var(--muted-color);">
Разрешены: JPG, PNG, GIF, WebP. Максимальный размер: 5MB.
Рекомендуемый размер: 300×450 пикселей.
</small>
<?php if (!empty($cover_error)): ?>
<div style="color: #d32f2f; margin-top: 0.5rem;">
❌ <?= e($cover_error) ?>
</div>
<?php endif; ?>
</div>
</div>
<label for="description" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Описание книги
</label>
<textarea id="description" name="description"
placeholder="Краткое описание сюжета или аннотация..."
rows="6"
style="width: 100%;"><?= e($book['description'] ?? '') ?></textarea>
<div style="margin-top: 1rem;">
<label for="published">
<input type="checkbox" id="published" name="published" value="1"
<?= !empty($book['published']) ? 'checked' : '' ?>>
Опубликовать книгу (показывать на публичной странице автора)
</label>
</div>
</div>
<div style="display: flex; gap: 10px; flex-wrap: wrap; margin-top: 1.5rem;">
<button type="submit" class="contrast">
💾 Сохранить изменения
</button>
<!-- <a href="<?= SITE_URL ?>/books" role="button" class="secondary">
❌ Отмена
</a> -->
</div>
</form>
<?php if ($book): ?>
<div style="margin-top: 0.5rem; padding: 0rem; background: var(--card-background-color); border-radius: 5px;">
<h3>Публичная ссылка для чтения</h3>
<div style="display: flex; gap: 5px; align-items: center; flex-wrap: wrap;">
<input type="text"
id="share-link"
value="<?= e(SITE_URL . '/book/' . $book['share_token']) ?>"
readonly
style="flex: 1; padding: 8px; border: 1px solid var(--border-color); border-radius: 4px; background: white;">
<button type="button" onclick="copyShareLink()" class="adaptive-button">
📋 Копировать
</button>
<form method="post" action="<?= SITE_URL ?>/books/<?= $book['id'] ?>/regenerate-token" style="display: inline;">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<button type="submit" class="adaptive-button secondary" onclick="return confirm('Создать новую ссылку? Старая ссылка перестанет работать.')">
🔄 Обновить
</button>
</form>
</div>
<p style="margin-top: 0.5rem; font-size: 0.8em; color: var(--muted-color);">
<strong>Примечание:</strong> В публичном просмотре отображаются только главы со статусом "Опубликована"
</p>
</div>
<div style="margin-top: 0.5rem; padding: 0rem; background: var(--card-background-color); border-radius: 5px;">
<h3>Экспорт книги</h3>
<p style="margin-bottom: 0.5rem;">Экспортируйте книгу в различные форматы:</p>
<div style="display: flex; gap: 5px; flex-wrap: wrap;">
<a href="<?= SITE_URL ?>/export/<?= $book['id'] ?>/pdf" class="adaptive-button secondary" target="_blank" role="button">
📄 PDF
</a>
<a href="<?= SITE_URL ?>/export/<?= $book['id'] ?>/docx" class="adaptive-button secondary" target="_blank" role="button">
📝 DOCX
</a>
<a href="<?= SITE_URL ?>/export/<?= $book['id'] ?>/html" class="adaptive-button secondary" target="_blank" role="button">
🌐 HTML
</a>
<a href="<?= SITE_URL ?>/export/<?= $book['id'] ?>/txt" class="adaptive-button secondary" target="_blank" role="button">
📄 TXT
</a>
</div>
<p style="margin-top: 0.5rem; font-size: 0.9em; color: var(--muted-color);">
<strong>Примечание:</strong> Экспортируются все главы книги (включая черновики)
</p>
</div>
<div style="margin-top: 0.5rem;">
<h2>Главы этой книги</h2>
<div style="display: flex; gap: 5px; flex-wrap: wrap; margin-bottom: 1rem;">
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/chapters" class="adaptive-button secondary" role="button">
📑 Все главы
</a>
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/chapters/create" class="adaptive-button secondary" role="button">
✏️ Добавить главу
</a>
</div>
<?php if (!empty($chapters)): ?>
<div style="overflow-x: auto;">
<table style="width: 100%;">
<thead>
<tr>
<th style="text-align: left; padding: 12px 8px;">Название</th>
<th style="text-align: left; padding: 12px 8px;">Статус</th>
<th style="text-align: left; padding: 12px 8px;">Слов</th>
<th style="text-align: left; padding: 12px 8px;">Действия</th>
</tr>
</thead>
<tbody>
<?php foreach ($chapters as $chapter): ?>
<tr style="border-bottom: 1px solid var(--border-color);">
<td style="padding: 12px 8px;"><?= e($chapter['title']) ?></td>
<td style="padding: 12px 8px;">
<span style="color: <?= $chapter['status'] == 'published' ? 'green' : 'orange' ?>">
<?= $chapter['status'] == 'published' ? 'Опубликована' : 'Черновик' ?>
</span>
</td>
<td style="padding: 12px 8px;"><?= $chapter['word_count'] ?></td>
<td style="padding: 12px 8px;">
<a href="<?= SITE_URL ?>/chapters/<?= $chapter['id'] ?>/edit" class="compact-button secondary">
Редактировать
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<div class="col-lg-4">
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title mb-0">Обложка книги</h5>
</div>
<div class="card-body">
<?php if (!empty($book['cover_image'])): ?>
<div class="text-center mb-3">
<img src="<?= COVERS_URL . e($book['cover_image']) ?>"
alt="Обложка"
class="img-fluid rounded"
style="max-height: 200px;">
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" name="delete_cover" value="1" id="delete_cover">
<label class="form-check-label" for="delete_cover">
Удалить обложку
</label>
</div>
<?php endif; ?>
<div class="mb-3">
<label for="cover_image" class="form-label">Загрузить новую обложку</label>
<input type="file" class="form-control" id="cover_image" name="cover_image"
accept="image/jpeg, image/png, image/gif, image/webp">
<div class="form-text">
Разрешены: JPG, PNG, GIF, WebP. Максимальный размер: 5MB.
</div>
</div>
<?php if (!empty($cover_error)): ?>
<div class="alert alert-danger">
<i class="bi bi-exclamation-triangle"></i> <?= e($cover_error) ?>
</div>
<?php endif; ?>
</div>
</div>
<?php else: ?>
<div style="text-align: center; padding: 0.5rem; background: var(--card-background-color); border-radius: 5px;">
<p style="margin-bottom: 1rem;">В этой книге пока нет глав.</p>
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/chapters/create" class="adaptive-button secondary" role="button">
✏️ Добавить первую главу
</a>
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title mb-0">Публичная ссылка</h5>
</div>
<div class="card-body">
<div class="input-group mb-2">
<input type="text"
id="share-link"
value="<?= e(SITE_URL . '/book/' . $book['share_token']) ?>"
readonly
class="form-control">
<button type="button" onclick="copyShareLink()" class="btn btn-outline-secondary">
<i class="bi bi-clipboard"></i>
</button>
</div>
<form method="post" action="<?= SITE_URL ?>/books/<?= $book['id'] ?>/regenerate-token" class="d-inline">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<button type="submit" class="btn btn-outline-warning btn-sm" onclick="return confirm('Создать новую ссылку? Старая ссылка перестанет работать.')">
<i class="bi bi-arrow-repeat"></i> Обновить ссылку
</button>
</form>
<div class="form-text">
В публичном просмотре отображаются только опубликованные главы
</div>
</div>
</div>
<?php endif; ?>
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title mb-0">Экспорт книги</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
<a href="<?= SITE_URL ?>/export/<?= $book['id'] ?>/pdf" class="btn btn-outline-danger" target="_blank">
<i class="bi bi-file-pdf"></i> PDF
</a>
<a href="<?= SITE_URL ?>/export/<?= $book['id'] ?>/docx" class="btn btn-outline-primary" target="_blank">
<i class="bi bi-file-word"></i> DOCX
</a>
<a href="<?= SITE_URL ?>/export/<?= $book['id'] ?>/html" class="btn btn-outline-success" target="_blank">
<i class="bi bi-file-code"></i> HTML
</a>
<a href="<?= SITE_URL ?>/export/<?= $book['id'] ?>/txt" class="btn btn-outline-secondary" target="_blank">
<i class="bi bi-file-text"></i> TXT
</a>
</div>
<div class="form-text mt-2">
Экспортируются все главы книги (включая черновики)
</div>
</div>
</div>
<div class="card border-danger">
<div class="card-header bg-danger text-white">
<h5 class="card-title mb-0">Опасная зона</h5>
</div>
<div class="card-body">
<p class="card-text small text-muted">
Удаление книги приведет к удалению всех глав и обложки. Это действие нельзя отменить.
</p>
<form method="post" action="<?= SITE_URL ?>/books/<?= $book['id'] ?>/delete"
onsubmit="return confirm('Вы уверены, что хотите удалить книгу «<?= e($book['title']) ?>»? Все главы также будут удалены.');">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<button type="submit" class="btn btn-danger w-100">
<i class="bi bi-trash"></i> Удалить книгу
</button>
</form>
</div>
</div>
</div>
</div>
<div style="margin-top: 0.5rem; text-align: center;">
<form method="post" action="<?= SITE_URL ?>/books/<?= $book['id'] ?>/delete" style="display: inline;" onsubmit="return confirm('Вы уверены, что хотите удалить книгу «<?= e($book['title']) ?>»? Все главы также будут удалены.');">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<button type="submit" class="adaptive-button red-btn">
🗑️ Удалить книгу
</button>
</form>
</div>
<?php endif; ?>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
function copyShareLink() {
const shareLink = document.getElementById('share-link');
shareLink.select();
document.execCommand('copy');
// Копирование ссылки для чтения
window.copyShareLink = function() {
const shareLink = document.getElementById('share-link');
shareLink.select();
document.execCommand('copy');
const button = event.target;
const originalText = '📋 Копировать';
button.textContent = '✅ Скопировано';
setTimeout(() => {
button.textContent = originalText;
}, 2000);
}
});
// Показать уведомление
const button = event.target;
const originalHTML = button.innerHTML;
button.innerHTML = '<i class="bi bi-check"></i>';
button.classList.remove('btn-outline-secondary');
button.classList.add('btn-success');
setTimeout(() => {
button.innerHTML = originalHTML;
button.classList.remove('btn-success');
button.classList.add('btn-outline-secondary');
}, 2000);
}
</script>
<?php include 'views/layouts/footer.php'; ?>
+144 -108
View File
@@ -3,120 +3,156 @@
include 'views/layouts/header.php';
?>
<h1>Мои книги <small style="color: #ccc; font-size:1rem;">(Всего книг: <?= count($books) ?>)</small></h1>
<div class="container">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="h2">Мои книги</h1>
<p class="text-muted mb-0">Всего книг: <?= count($books) ?></p>
</div>
<div>
<a href="<?= SITE_URL ?>/books/create" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> Новая книга
</a>
<?php if (!empty($books)): ?>
<button type="button" onclick="showDeleteAllConfirmation()" class="btn btn-outline-danger">
<i class="bi bi-trash"></i> Удалить все
</button>
<?php endif; ?>
</div>
</div>
<?php if (empty($books)): ?>
<div class="text-center py-5">
<div class="mb-4">
<i class="bi bi-journal-bookmark fs-1 text-muted"></i>
</div>
<h3 class="h4 text-muted">У вас пока нет книг</h3>
<p class="text-muted mb-4">Создайте свою первую книгу и начните писать!</p>
<a href="<?= SITE_URL ?>/books/create" class="btn btn-primary btn-lg">
<i class="bi bi-plus-circle"></i> Создать первую книгу
</a>
</div>
<?php else: ?>
<div class="row g-4">
<?php foreach ($books as $book): ?>
<div class="col-xl-4 col-lg-6">
<div class="card h-100 book-card">
<div class="card-body">
<div class="d-flex align-items-start mb-3">
<?php if (!empty($book['cover_image'])): ?>
<img src="<?= COVERS_URL . e($book['cover_image']) ?>"
alt="<?= e($book['title']) ?>"
class="rounded me-3"
style="width: 60px; height: 80px; object-fit: cover;"
onerror="this.style.display='none'">
<?php endif; ?>
<div class="flex-grow-1">
<div class="d-flex justify-content-between align-items-start">
<h5 class="card-title mb-1">
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/edit" class="text-decoration-none">
<?= e($book['title']) ?>
</a>
</h5>
<span class="badge <?= $book['published'] ? 'bg-success' : 'bg-warning' ?>">
<?= $book['published'] ? 'Опубликована' : 'Черновик' ?>
</span>
</div>
<?php if (!empty($book['genre'])): ?>
<p class="text-muted small mb-2"><?= e($book['genre']) ?></p>
<?php endif; ?>
</div>
</div>
<?php if (!empty($book['description'])): ?>
<p class="card-text text-muted small">
<?= e(mb_strimwidth($book['description'], 0, 120, '...')) ?>
</p>
<?php endif; ?>
<div class="row text-center mb-3">
<div class="col-4">
<div class="border-end">
<div class="fw-bold text-primary"><?= $book['chapter_count'] ?? 0 ?></div>
<small class="text-muted">глав</small>
</div>
</div>
<div class="col-4">
<div class="border-end">
<div class="fw-bold text-success"><?= number_format($book['total_words'] ?? 0) ?></div>
<small class="text-muted">слов</small>
</div>
</div>
<div class="col-4">
<div>
<div class="fw-bold text-info">
<?= $book['chapter_count'] > 0 ? number_format(($book['total_words'] ?? 0) / $book['chapter_count']) : 0 ?>
</div>
<small class="text-muted">слов/глава</small>
</div>
</div>
</div>
<div class="d-grid gap-2">
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/edit" class="btn btn-outline-primary btn-sm">
<i class="bi bi-pencil"></i> Редактировать
</a>
<div class="btn-group" role="group">
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/chapters" class="btn btn-outline-secondary btn-sm">
<i class="bi bi-file-text"></i> Главы
</a>
<a href="<?= SITE_URL ?>/book/<?= $book['share_token'] ?>" class="btn btn-outline-success btn-sm" target="_blank">
<i class="bi bi-eye"></i> Просмотр
</a>
</div>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<div style="display: flex; justify-content: left; margin-bottom: 1rem; flex-wrap: wrap; gap: 1rem;">
<a href="<?= SITE_URL ?>/books/create" class="action-button primary" role="button"> Новая книга</a>
<?php if (!empty($books)): ?>
<a href="#" onclick="showDeleteAllConfirmation()" class="action-button delete">🗑️ Удалить все книги</a>
<div class="mt-4 p-3 bg-light rounded">
<div class="row text-center">
<div class="col-md-3 col-6 mb-2">
<strong class="text-primary"><?= count($books) ?></strong>
<small class="text-muted d-block">Книг</small>
</div>
<div class="col-md-3 col-6 mb-2">
<strong class="text-success"><?= array_sum(array_column($books, 'chapter_count')) ?></strong>
<small class="text-muted d-block">Глав</small>
</div>
<div class="col-md-3 col-6 mb-2">
<strong class="text-warning"><?= number_format(array_sum(array_column($books, 'total_words'))) ?></strong>
<small class="text-muted d-block">Слов</small>
</div>
<div class="col-md-3 col-6 mb-2">
<strong class="text-info"><?= count(array_filter($books, function($book) { return $book['published']; })) ?></strong>
<small class="text-muted d-block">Опубликовано</small>
</div>
</div>
</div>
<?php endif; ?>
</div>
<?php if (empty($books)): ?>
<article style="text-align: center; padding: 2rem;">
<h3>У вас пока нет книг</h3>
<p>Создайте свою первую книгу и начните писать!</p>
<a href="<?= SITE_URL ?>/books/create" role="button">📖 Создать первую книгу</a>
</article>
<?php else: ?>
<div class="books-grid">
<?php foreach ($books as $book): ?>
<article class="book-card">
<!-- Обложка книги -->
<div class="book-cover-container">
<?php if (!empty($book['cover_image'])): ?>
<img src="<?= COVERS_URL . e($book['cover_image']) ?>"
alt="<?= e($book['title']) ?>"
class="book-cover"
onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
<div class="cover-placeholder" style="display: none;">
📚
</div>
<?php else: ?>
<div class="cover-placeholder">
📚
</div>
<?php endif; ?>
<!-- Статус книги -->
<div class="book-status <?= $book['published'] ? 'published' : 'draft' ?>">
<?= $book['published'] ? '✅' : '📝' ?>
</div>
</div>
<!-- Информация о книге -->
<div class="book-info">
<h3 class="book-title">
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/edit">
<?= e($book['title']) ?>
</a>
</h3>
<?php if (!empty($book['genre'])): ?>
<p class="book-genre"><?= e($book['genre']) ?></p>
<?php endif; ?>
<?php if (!empty($book['description'])): ?>
<p class="book-description">
<?= e(mb_strimwidth($book['description'], 0, 120, '...')) ?>
</p>
<?php endif; ?>
<!-- Статистика -->
<div class="book-stats">
<span class="stat-item">
<strong><?= $book['chapter_count'] ?? 0 ?></strong> глав
</span>
<span class="stat-item">
<strong><?= number_format($book['total_words'] ?? 0) ?></strong> слов
</span>
</div>
<!-- Действия -->
<div class="book-actions">
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/edit" class="compact-button primary-btn">
✏️ Редактировать
</a>
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/chapters" class="compact-button secondary-btn">
📑 Главы
</a>
<a href="<?= SITE_URL ?>/book/<?= $book['share_token'] ?>" class="compact-button green-btn" target="_blank">
👁️ Просмотр
</a>
</div>
</div>
</article>
<?php endforeach; ?>
</div>
<!-- Статистика внизу -->
<div class="books-stats-footer">
<strong>Общая статистика:</strong>
Книг: <?= count($books) ?> |
Глав: <?= array_sum(array_column($books, 'chapter_count')) ?> |
Слов: <?= number_format(array_sum(array_column($books, 'total_words'))) ?> |
Опубликовано: <?= count(array_filter($books, function($book) { return $book['published']; })) ?>
</div>
<?php endif; ?>
<?php if (!empty($books)): ?>
<script>
function showDeleteAllConfirmation() {
if (confirm('Вы уверены, что хотите удалить ВСЕ книги? Это действие также удалит все главы и обложки книг. Действие нельзя отменить!')) {
const form = document.createElement('form');
form.method = 'POST';
form.action = '<?= SITE_URL ?>/books/delete-all';
const csrfInput = document.createElement('input');
csrfInput.type = 'hidden';
csrfInput.name = 'csrf_token';
csrfInput.value = '<?= generate_csrf_token() ?>';
form.appendChild(csrfInput);
document.body.appendChild(form);
form.submit();
}
<script>
function showDeleteAllConfirmation() {
if (confirm('Вы уверены, что хотите удалить ВСЕ книги? Это действие также удалит все главы и обложки книг. Действие нельзя отменить!')) {
const form = document.createElement('form');
form.method = 'POST';
form.action = '<?= SITE_URL ?>/books/delete-all';
const csrfInput = document.createElement('input');
csrfInput.type = 'hidden';
csrfInput.name = 'csrf_token';
csrfInput.value = '<?= generate_csrf_token() ?>';
form.appendChild(csrfInput);
document.body.appendChild(form);
form.submit();
}
</script>
}
</script>
<?php endif; ?>
<?php include 'views/layouts/footer.php'; ?>
+150 -128
View File
@@ -3,159 +3,181 @@
include 'views/layouts/header.php';
?>
<div class="container" style="padding: 0em; margin: 0em auto; width: 90%;">
<article style="margin: 0 auto;">
<header style="text-align: center; margin-bottom: 2rem;">
<?php if (!empty($book['cover_image'])): ?>
<div style="margin-bottom: 1rem;">
<img src="<?= COVERS_URL . e($book['cover_image']) ?>"
alt="<?= e($book['title']) ?>"
style="max-width: 200px; height: auto; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"
onerror="this.style.display='none'">
</div>
<?php endif; ?>
<h1 style="margin-bottom: 0.5rem;"><?= e($book['title']) ?></h1>
<p style="color: #666; font-style: italic; margin-bottom: 0.5rem;">
Автор: <a href="<?= SITE_URL ?>/author/<?= $book['user_id'] ?>"><?= e($author['display_name']??$author['username']) ?></a>
</p>
<?php if (!empty($book['genre'])): ?>
<p style="color: #666; font-style: italic; margin-bottom: 1rem;">
<?= e($book['genre']) ?>
</p>
<?php endif; ?>
<?php if (!empty($book['description'])): ?>
<div style="background: var(--card-background-color); padding: 1.5rem; border-radius: 8px; margin: 1rem 0; text-align: left;">
<?= nl2br(e($book['description'])) ?>
</div>
<?php endif; ?>
<div style="display: block; justify-content: center; gap: 1rem; flex-wrap: wrap; font-size: 0.9em; color: #666;">
<span>Глав: <?= count($chapters) ?></span>
<span>Слов: <?= array_sum(array_column($chapters, 'word_count')) ?></span>
<p>
<?php if (!is_logged_in()): ?>
<div style="display: flex; gap: 5px; flex-wrap: wrap; justify-content: center;">
<a href="<?= SITE_URL ?>/export/shared/<?= $book['share_token'] ?>/pdf" class="adaptive-button secondary" target="_blank" role="button">
📄 PDF
</a>
<a href="<?= SITE_URL ?>/export/shared/<?= $book['share_token'] ?>/docx" class="adaptive-button secondary" target="_blank" role="button">
📝 DOCX
</a>
<a href="<?= SITE_URL ?>/export/shared/<?= $book['share_token'] ?>/html" class="adaptive-button secondary" target="_blank" role="button">
🌐 HTML
</a>
<a href="<?= SITE_URL ?>/export/shared/<?= $book['share_token'] ?>/txt" class="adaptive-button secondary" target="_blank" role="button">
📄 TXT
<div class="container py-4">
<div class="row justify-content-center">
<div class="col-lg-10">
<article class="card border-0 shadow-sm">
<div class="card-body p-4">
<header class="text-center mb-4">
<?php if (!empty($book['cover_image'])): ?>
<div class="mb-4">
<img src="<?= COVERS_URL . e($book['cover_image']) ?>"
alt="<?= e($book['title']) ?>"
class="img-fluid rounded shadow"
style="max-height: 300px;"
onerror="this.style.display='none'">
</div>
<?php endif; ?>
<h1 class="display-5 mb-2"><?= e($book['title']) ?></h1>
<p class="lead text-muted mb-2">
Автор: <a href="<?= SITE_URL ?>/author/<?= $book['user_id'] ?>" class="text-decoration-none">
<?= e($author['display_name']??$author['username']) ?>
</a>
</p>
<?php if (!empty($book['genre'])): ?>
<p class="text-muted mb-3">
<i class="bi bi-tags"></i> <?= e($book['genre']) ?>
</p>
<?php endif; ?>
<?php if (!empty($book['description'])): ?>
<div class="bg-light p-4 rounded mb-4">
<?= nl2br(e($book['description'])) ?>
</div>
<?php endif; ?>
<div class="d-flex justify-content-center gap-4 flex-wrap mb-4">
<div class="text-center">
<div class="h4 text-primary mb-0"><?= count($chapters) ?></div>
<small class="text-muted">Глав</small>
</div>
<div class="text-center">
<div class="h4 text-success mb-0"><?= array_sum(array_column($chapters, 'word_count')) ?></div>
<small class="text-muted">Слов</small>
</div>
</div>
<?php endif; ?>
<?php if (is_logged_in()): ?>
<div style="display: flex; gap: 5px; flex-wrap: wrap; justify-content: center;">
<a href="<?= SITE_URL ?>/export/<?= $book['id'] ?>/pdf" class="adaptive-button secondary" target="_blank" role="button">
📄 PDF
</a>
<a href="<?= SITE_URL ?>/export/<?= $book['id'] ?>/docx" class="adaptive-button secondary" target="_blank" role="button">
📝 DOCX
</a>
<a href="<?= SITE_URL ?>/export/<?= $book['id'] ?>/html" class="adaptive-button secondary" target="_blank" role="button">
🌐 HTML
</a>
<a href="<?= SITE_URL ?>/export/<?= $book['id'] ?>/txt" class="adaptive-button secondary" target="_blank" role="button">
📄 TXT
</a>
<div class="d-flex justify-content-center gap-2 flex-wrap">
<?php if (!is_logged_in()): ?>
<a href="<?= SITE_URL ?>/export/shared/<?= $book['share_token'] ?>/pdf" class="btn btn-outline-danger" target="_blank">
<i class="bi bi-file-pdf"></i> PDF
</a>
<a href="<?= SITE_URL ?>/export/shared/<?= $book['share_token'] ?>/docx" class="btn btn-outline-primary" target="_blank">
<i class="bi bi-file-word"></i> DOCX
</a>
<a href="<?= SITE_URL ?>/export/shared/<?= $book['share_token'] ?>/html" class="btn btn-outline-success" target="_blank">
<i class="bi bi-file-code"></i> HTML
</a>
<a href="<?= SITE_URL ?>/export/shared/<?= $book['share_token'] ?>/txt" class="btn btn-outline-secondary" target="_blank">
<i class="bi bi-file-text"></i> TXT
</a>
<?php else: ?>
<a href="<?= SITE_URL ?>/export/<?= $book['id'] ?>/pdf" class="btn btn-outline-danger" target="_blank">
<i class="bi bi-file-pdf"></i> PDF
</a>
<a href="<?= SITE_URL ?>/export/<?= $book['id'] ?>/docx" class="btn btn-outline-primary" target="_blank">
<i class="bi bi-file-word"></i> DOCX
</a>
<a href="<?= SITE_URL ?>/export/<?= $book['id'] ?>/html" class="btn btn-outline-success" target="_blank">
<i class="bi bi-file-code"></i> HTML
</a>
<a href="<?= SITE_URL ?>/export/<?= $book['id'] ?>/txt" class="btn btn-outline-secondary" target="_blank">
<i class="bi bi-file-text"></i> TXT
</a>
<?php endif; ?>
</div>
</header>
<?php if (empty($chapters)): ?>
<div class="text-center py-5">
<i class="bi bi-file-text fs-1 text-muted"></i>
<h3 class="h4 text-muted mt-3">В этой книге пока нет глав</h3>
<p class="text-muted">Автор еще не опубликовал содержание книги</p>
</div>
<?php else: ?>
<div class="mb-5">
<h3 class="h4 text-center mb-4">
<i class="bi bi-list-ul"></i> Оглавление
</h3>
<div class="list-group">
<?php foreach ($chapters as $index => $chapter): ?>
<a href="#chapter-<?= $chapter['id'] ?>" class="list-group-item list-group-item-action border-0">
<div class="d-flex justify-content-between align-items-center">
<span>
<strong>Глава <?= $index + 1 ?>:</strong> <?= e($chapter['title']) ?>
</span>
<small class="text-muted"><?= $chapter['word_count'] ?> слов</small>
</div>
</a>
<?php endforeach; ?>
</div>
</div>
<hr class="my-5">
<?php foreach ($chapters as $index => $chapter): ?>
<div class="chapter-content mb-5" id="chapter-<?= $chapter['id'] ?>">
<h2 class="border-bottom pb-2 mb-4">
Глава <?= $index + 1 ?>: <?= e($chapter['title']) ?>
</h2>
<div class="chapter-text" style="line-height: 1.8; font-size: 1.1em;">
<?= $chapter['content'] ?>
</div>
<div class="text-center mt-4">
<a href="#top" class="btn btn-outline-primary btn-sm">
<i class="bi bi-arrow-up"></i> Наверх
</a>
</div>
</div>
<?php if ($index < count($chapters) - 1): ?>
<hr class="my-5">
<?php endif; ?>
<?php endforeach; ?>
<?php endif; ?>
</p>
</div>
</header>
<?php if (empty($chapters)): ?>
<div style="text-align: center; padding: 3rem; background: var(--card-background-color); border-radius: 5px;">
<h3>В этой книге пока нет глав</h3>
<p>Автор еще не опубликовал содержание книги</p>
</div>
<?php else: ?>
<h3 style="text-align: center; margin-bottom: 2rem; margin-top: 0em;">Оглавление</h3>
<div class="chapters-list">
<?php foreach ($chapters as $index => $chapter): ?>
<h6 style="margin-top: 0; margin-bottom: 0em;">
<a href="#chapter-<?= $chapter['id'] ?>" style="text-decoration: none;">
Глава <?= $index + 1 ?>: <?= e($chapter['title']) ?>
</a>
</h6>
<?php endforeach; ?>
</div>
<hr style="margin: 2rem 0;">
<?php foreach ($chapters as $index => $chapter): ?>
<div class="chapter-content" id="chapter-<?= $chapter['id'] ?>" style="margin-bottom: 3rem;">
<h2 style="border-bottom: 2px solid var(--primary); padding-bottom: 0.5rem;">
Глава <?= $index + 1 ?>: <?= e($chapter['title']) ?>
</h2>
<div style="margin-top: 1.5rem; line-height: 1.6;">
<?= $chapter['content'] ?>
</div>
<footer class="text-center mt-5 pt-4 border-top">
<p class="text-muted">
Книга создана в <?= e(APP_NAME) ?> • <?= date('Y') ?>
</p>
</footer>
</div>
<?php endforeach; ?>
<?php endif; ?>
<footer style="margin-top: 3rem; padding-top: 1rem; border-top: 2px solid var(--muted-border-color); text-align: center;">
<p style="color: var(--muted-color);">
Книга создана в <?= e(APP_NAME) ?> •
<?= date('Y') ?>
</p>
</footer>
</article>
</article>
</div>
</div>
</div>
<style>
.chapter-content h1, .chapter-content h2, .chapter-content h3 {
margin-top: 1.5em;
margin-bottom: 0.5em;
}
.chapter-content p {
margin-bottom: 1em;
.chapter-text p {
margin-bottom: 1.5em;
text-align: justify;
}
.chapter-content .dialogue {
.chapter-text .dialogue {
margin-left: 2rem;
font-style: italic;
color: #2c5aa0;
}
.chapter-content blockquote {
border-left: 4px solid var(--primary);
.chapter-text blockquote {
border-left: 4px solid var(--bs-primary);
padding-left: 1rem;
margin-left: 0;
color: #555;
font-style: italic;
}
.chapter-content code {
background: var(--card-background-color);
padding: 2px 4px;
border-radius: 3px;
}
.chapter-content pre {
background: var(--card-background-color);
background: var(--bs-light);
padding: 1rem;
border-radius: 5px;
border-radius: 0 0.5rem 0.5rem 0;
}
.chapter-text pre {
background: var(--bs-dark);
color: var(--bs-light);
padding: 1rem;
border-radius: 0.5rem;
overflow-x: auto;
}
.chapter-content ul, .chapter-content ol {
margin-bottom: 1rem;
padding-left: 2rem;
.chapter-text code {
background: var(--bs-light);
padding: 0.2em 0.4em;
border-radius: 0.3rem;
font-size: 0.9em;
}
</style>