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
+88 -71
View File
@@ -1,10 +1,15 @@
<?php include 'views/layouts/header.php'; ?>
<div class="container">
<h1>Добавление пользователя</h1>
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h2">Добавление пользователя</h1>
<a href="<?= SITE_URL ?>/admin/users" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left"></i> Назад к пользователям
</a>
</div>
<?php if (isset($error) && $error): ?>
<div class="alert alert-error">
<div class="alert alert-danger">
<?= e($error) ?>
</div>
<?php endif; ?>
@@ -14,75 +19,87 @@
<?= e($success) ?>
</div>
<?php endif; ?>
<form method="post" style="max-width: 500px; margin: 0 auto;">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<div style="margin-bottom: 1rem;">
<label for="username" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Имя пользователя *
</label>
<input type="text" id="username" name="username"
value="<?= e($_POST['username'] ?? '') ?>"
placeholder="Введите имя пользователя"
style="width: 100%;"
required
pattern="[a-zA-Z0-9_]+"
title="Только латинские буквы, цифры и символ подчеркивания">
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="card">
<div class="card-body">
<form method="post">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="username" class="form-label">Имя пользователя *</label>
<input type="text" class="form-control" id="username" name="username"
value="<?= e($_POST['username'] ?? '') ?>"
placeholder="Введите имя пользователя"
required
pattern="[a-zA-Z0-9_]+"
title="Только латинские буквы, цифры и символ подчеркивания">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="display_name" class="form-label">Отображаемое имя</label>
<input type="text" class="form-control" id="display_name" name="display_name"
value="<?= e($_POST['display_name'] ?? '') ?>"
placeholder="Введите отображаемое имя">
</div>
</div>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" name="email"
value="<?= e($_POST['email'] ?? '') ?>"
placeholder="Введите email">
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="password" class="form-label">Пароль *</label>
<input type="password" class="form-control" id="password" name="password"
placeholder="Введите пароль (минимум 6 символов)"
required
minlength="6">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="password_confirm" class="form-label">Подтверждение пароля *</label>
<input type="password" class="form-control" id="password_confirm" name="password_confirm"
placeholder="Повторите пароль"
required
minlength="6">
</div>
</div>
</div>
<div class="mb-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="is_active" name="is_active" value="1"
<?= isset($_POST['is_active']) ? 'checked' : 'checked' ?>>
<label class="form-check-label" for="is_active">
Активировать пользователя сразу
</label>
</div>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">
<i class="bi bi-person-plus"></i> Добавить пользователя
</button>
<a href="<?= SITE_URL ?>/admin/users" class="btn btn-outline-danger">
<i class="bi bi-x-circle"></i> Отмена
</a>
</div>
</form>
</div>
</div>
</div>
<div style="margin-bottom: 1rem;">
<label for="display_name" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Отображаемое имя
</label>
<input type="text" id="display_name" name="display_name"
value="<?= e($_POST['display_name'] ?? '') ?>"
placeholder="Введите отображаемое имя"
style="width: 100%;">
</div>
<div style="margin-bottom: 1rem;">
<label for="email" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Email
</label>
<input type="email" id="email" name="email"
value="<?= e($_POST['email'] ?? '') ?>"
placeholder="Введите email"
style="width: 100%;">
</div>
<div style="margin-bottom: 1rem;">
<label for="password" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Пароль *
</label>
<input type="password" id="password" name="password"
placeholder="Введите пароль (минимум 6 символов)"
style="width: 100%;"
required
minlength="6">
</div>
<div style="margin-bottom: 1rem;">
<label for="password_confirm" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Подтверждение пароля *
</label>
<input type="password" id="password_confirm" name="password_confirm"
placeholder="Повторите пароль"
style="width: 100%;"
required
minlength="6">
</div>
<div style="margin-bottom: 1.5rem;">
<label for="is_active">
<input type="checkbox" id="is_active" name="is_active" value="1"
<?= isset($_POST['is_active']) ? 'checked' : 'checked' ?>>
Активировать пользователя сразу
</label>
</div>
<div style="display: flex; gap: 10px;">
<button type="submit" class="contrast" style="flex: 1;">
👥 Добавить пользователя
</button>
<a href="<?= SITE_URL ?>/admin/users" class="secondary" style="display: flex; align-items: center; justify-content: center; padding: 0.75rem; text-decoration: none;">
❌ Отмена
</a>
</div>
</form>
</div>
</div>
<?php include 'views/layouts/footer.php'; ?>
+97 -76
View File
@@ -1,94 +1,115 @@
<?php include 'views/layouts/header.php'; ?>
<div class="container" style="margin:0; width: auto;">
<h1>Управление пользователями</h1>
<div class="container-fluid">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="h2">Управление пользователями</h1>
<p class="text-muted mb-0">Всего пользователей: <?= count($users) ?></p>
</div>
<a href="<?= SITE_URL ?>/admin/add-user" class="btn btn-primary">
<i class="bi bi-person-plus"></i> Добавить пользователя
</a>
</div>
<?php if (isset($_SESSION['success'])): ?>
<div class="alert alert-success">
<div class="alert alert-success alert-dismissible fade show">
<?= e($_SESSION['success']) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
<?php unset($_SESSION['success']); ?>
</div>
<?php endif; ?>
<?php if (isset($_SESSION['error'])): ?>
<div class="alert alert-error">
<div class="alert alert-danger alert-dismissible fade show">
<?= e($_SESSION['error']) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
<?php unset($_SESSION['error']); ?>
</div>
<?php endif; ?>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<h2 style="margin: 0;">Всего пользователей: <?= count($users) ?></h2>
<a href="<?= SITE_URL ?>/admin/add-user" class="action-button primary"> Добавить пользователя</a>
</div>
<?php if (empty($users)): ?>
<article style="text-align: center; padding: 2rem;">
<h3>Пользователи не найдены</h3>
<p>Зарегистрируйте первого пользователя</p>
<a href="<?= SITE_URL ?>/admin/add-user" role="button">📝 Добавить пользователя</a>
</article>
<div class="text-center py-5">
<div class="mb-4">
<i class="bi bi-people fs-1 text-muted"></i>
</div>
<h3 class="h4 text-muted">Пользователи не найдены</h3>
<p class="text-muted mb-4">Зарегистрируйте первого пользователя</p>
<a href="<?= SITE_URL ?>/admin/add-user" class="btn btn-primary">
<i class="bi bi-person-plus"></i> Добавить пользователя
</a>
</div>
<?php else: ?>
<div style="overflow-x: auto; width:100%;">
<table class="compact-table">
<thead>
<tr>
<th style="width: 5%;">ID</th>
<th style="width: 15%;">Имя пользователя</th>
<th style="width: 20%;">Отображаемое имя</th>
<th style="width: 20%;">Email</th>
<th style="width: 15%;">Дата регистрации</th>
<th style="width: 10%;">Статус</th>
<th style="width: 15%;">Действия</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $user): ?>
<tr>
<td><?= $user['id'] ?></td>
<td>
<strong><a href="<?= SITE_URL ?>/author/<?= $user['id'] ?>"><?= e($user['username']) ?></a></strong>
<?php if ($user['id'] == $_SESSION['user_id']): ?>
<br><small style="color: #666;">(Вы)</small>
<?php endif; ?>
</td>
<td><?= e($user['display_name']) ?></td>
<td><?= e($user['email']) ?></td>
<td>
<small><?= date('d.m.Y H:i', strtotime($user['created_at'])) ?></small>
<?php if ($user['last_login']): ?>
<br><small style="color: #666;">Вход: <?= date('d.m.Y H:i', strtotime($user['last_login'])) ?></small>
<?php endif; ?>
</td>
<td>
<span style="color: <?= $user['is_active'] ? 'green' : 'red' ?>">
<?= $user['is_active'] ? '✅ Активен' : '❌ Неактивен' ?>
</span>
</td>
<td>
<?php if ($user['id'] != $_SESSION['user_id']): ?>
<div style="display: flex; gap: 3px; flex-wrap: wrap;">
<form method="post" action="<?= SITE_URL ?>/admin/user/<?= $user['id'] ?>/toggle-status" style="display: inline;">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<button type="submit" class="compact-button secondary" title="<?= $user['is_active'] ? 'Деактивировать' : 'Активировать' ?>">
<?= $user['is_active'] ? '⏸️' : '▶️' ?>
</button>
</form>
<form method="post" action="<?= SITE_URL ?>/admin/user/<?= $user['id'] ?>/delete" style="display: inline;" onsubmit="return confirm('Вы уверены, что хотите удалить пользователя «<?= e($user['username']) ?>»? Все его книги и главы также будут удалены.');">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<button type="submit" class="compact-button secondary" style="background: #ff4444; border-color: #ff4444; color: white;" title="Удалить">
🗑️
</button>
</form>
</div>
<?php else: ?>
<small style="color: #666;">Текущий пользователь</small>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<div class="card">
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>ID</th>
<th>Имя пользователя</th>
<th>Отображаемое имя</th>
<th>Email</th>
<th>Дата регистрации</th>
<th>Статус</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $user): ?>
<tr>
<td><?= $user['id'] ?></td>
<td>
<strong>
<a href="<?= SITE_URL ?>/author/<?= $user['id'] ?>" class="text-decoration-none">
<?= e($user['username']) ?>
</a>
</strong>
<?php if ($user['id'] == $_SESSION['user_id']): ?>
<br><small class="text-muted">(Вы)</small>
<?php endif; ?>
</td>
<td><?= e($user['display_name']) ?></td>
<td><?= e($user['email']) ?></td>
<td>
<small><?= date('d.m.Y H:i', strtotime($user['created_at'])) ?></small>
<?php if ($user['last_login']): ?>
<br><small class="text-muted">Вход: <?= date('d.m.Y H:i', strtotime($user['last_login'])) ?></small>
<?php endif; ?>
</td>
<td>
<span class="badge <?= $user['is_active'] ? 'bg-success' : 'bg-danger' ?>">
<?= $user['is_active'] ? 'Активен' : 'Неактивен' ?>
</span>
</td>
<td>
<?php if ($user['id'] != $_SESSION['user_id']): ?>
<div class="btn-group btn-group-sm">
<form method="post" action="<?= SITE_URL ?>/admin/user/<?= $user['id'] ?>/toggle-status" class="d-inline">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<button type="submit" class="btn btn-outline-<?= $user['is_active'] ? 'warning' : 'success' ?>"
title="<?= $user['is_active'] ? 'Деактивировать' : 'Активировать' ?>">
<i class="bi bi-<?= $user['is_active'] ? 'pause' : 'play' ?>"></i>
</button>
</form>
<form method="post" action="<?= SITE_URL ?>/admin/user/<?= $user['id'] ?>/delete"
onsubmit="return confirm('Вы уверены, что хотите удалить пользователя «<?= e($user['username']) ?>»? Все его книги и главы также будут удалены.');"
class="d-inline">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<button type="submit" class="btn btn-outline-danger" title="Удалить">
<i class="bi bi-trash"></i>
</button>
</form>
</div>
<?php else: ?>
<small class="text-muted">Текущий пользователь</small>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php endif; ?>
</div>
+42 -37
View File
@@ -4,45 +4,50 @@ include 'views/layouts/header.php';
?>
<div class="container">
<h1>Вход в систему</h1>
<div class="row justify-content-center">
<div class="col-md-6 col-lg-5">
<div class="card shadow border-0">
<div class="card-body p-5">
<div class="text-center mb-4">
<i class="bi bi-person-check fs-1 text-primary"></i>
<h1 class="h3 mt-2">Вход в систему</h1>
</div>
<?php if (isset($error) && $error): ?>
<div class="alert alert-error">
<?= e($error) ?>
<?php if (isset($error) && $error): ?>
<div class="alert alert-danger">
<?= e($error) ?>
</div>
<?php endif; ?>
<form method="post">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<div class="mb-3">
<label for="username" class="form-label">Имя пользователя</label>
<input type="text" class="form-control" id="username" name="username"
value="<?= e($_POST['username'] ?? '') ?>"
placeholder="Введите имя пользователя" required>
</div>
<div class="mb-4">
<label for="password" class="form-label">Пароль</label>
<input type="password" class="form-control" id="password" name="password"
placeholder="Введите пароль" required>
</div>
<button type="submit" class="btn btn-primary w-100 py-2">
<i class="bi bi-box-arrow-in-right"></i> Войти
</button>
</form>
<div class="text-center mt-4">
<p class="text-muted">Нет аккаунта?
<a href="<?= SITE_URL ?>/register" class="text-decoration-none">Зарегистрируйтесь здесь</a>
</p>
</div>
</div>
</div>
</div>
<?php endif; ?>
<form method="post" style="max-width: 400px; margin: 0 auto;">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<div style="margin-bottom: 1rem;">
<label for="username" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Имя пользователя
</label>
<input type="text" id="username" name="username"
value="<?= e($_POST['username'] ?? '') ?>"
placeholder="Введите имя пользователя"
style="width: 100%;"
required>
</div>
<div style="margin-bottom: 1.5rem;">
<label for="password" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Пароль
</label>
<input type="password" id="password" name="password"
placeholder="Введите пароль"
style="width: 100%;"
required>
</div>
<button type="submit" class="contrast" style="width: 100%;">
🔑 Войти
</button>
</form>
<div style="text-align: center; margin-top: 1rem;">
<p>Нет аккаунта? <a href="<?= SITE_URL ?>/register">Зарегистрируйтесь здесь</a></p>
</div>
</div>
+79 -73
View File
@@ -1,84 +1,90 @@
// views/auth/register.php
<?php
// views/auth/register.php
include 'views/layouts/header.php';
?>
<div class="container">
<h1>Регистрация</h1>
<div class="row justify-content-center">
<div class="col-md-8 col-lg-6">
<div class="card shadow border-0">
<div class="card-body p-5">
<div class="text-center mb-4">
<i class="bi bi-person-plus fs-1 text-primary"></i>
<h1 class="h3 mt-2">Регистрация</h1>
</div>
<?php if (isset($error) && $error): ?>
<div class="alert alert-error">
<?= e($error) ?>
<?php if (isset($error) && $error): ?>
<div class="alert alert-danger">
<?= e($error) ?>
</div>
<?php endif; ?>
<?php if (isset($success) && $success): ?>
<div class="alert alert-success">
<?= e($success) ?>
</div>
<?php endif; ?>
<form method="post">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="username" class="form-label">Имя пользователя *</label>
<input type="text" class="form-control" id="username" name="username"
value="<?= e($_POST['username'] ?? '') ?>"
placeholder="Введите имя пользователя" required>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="display_name" class="form-label">Отображаемое имя</label>
<input type="text" class="form-control" id="display_name" name="display_name"
value="<?= e($_POST['display_name'] ?? '') ?>"
placeholder="Как вас будут видеть другие">
</div>
</div>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" name="email"
value="<?= e($_POST['email'] ?? '') ?>"
placeholder="email@example.com">
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="password" class="form-label">Пароль *</label>
<input type="password" class="form-control" id="password" name="password"
placeholder="Не менее 6 символов" required>
</div>
</div>
<div class="col-md-6">
<div class="mb-4">
<label for="password_confirm" class="form-label">Подтверждение пароля *</label>
<input type="password" class="form-control" id="password_confirm" name="password_confirm"
placeholder="Повторите пароль" required>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary w-100 py-2">
<i class="bi bi-person-plus"></i> Зарегистрироваться
</button>
</form>
<div class="text-center mt-4">
<p class="text-muted">Уже есть аккаунт?
<a href="<?= SITE_URL ?>/login" class="text-decoration-none">Войдите здесь</a>
</p>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php if (isset($success) && $success): ?>
<div class="alert alert-success">
<?= e($success) ?>
</div>
<?php endif; ?>
<form method="post" style="max-width: 400px; margin: 0 auto;">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<div style="margin-bottom: 1rem;">
<label for="username" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Имя пользователя *
</label>
<input type="text" id="username" name="username"
value="<?= e($_POST['username'] ?? '') ?>"
placeholder="Введите имя пользователя"
style="width: 100%;"
required>
</div>
<div style="margin-bottom: 1rem;">
<label for="display_name" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Отображаемое имя
</label>
<input type="text" id="display_name" name="display_name"
value="<?= e($_POST['display_name'] ?? '') ?>"
placeholder="Как вас будут видеть другие"
style="width: 100%;">
</div>
<div style="margin-bottom: 1rem;">
<label for="email" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Email
</label>
<input type="email" id="email" name="email"
value="<?= e($_POST['email'] ?? '') ?>"
placeholder="email@example.com"
style="width: 100%;">
</div>
<div style="margin-bottom: 1rem;">
<label for="password" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Пароль *
</label>
<input type="password" id="password" name="password"
placeholder="Не менее 6 символов"
style="width: 100%;"
required>
</div>
<div style="margin-bottom: 1.5rem;">
<label for="password_confirm" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Подтверждение пароля *
</label>
<input type="password" id="password_confirm" name="password_confirm"
placeholder="Повторите пароль"
style="width: 100%;"
required>
</div>
<button type="submit" class="contrast" style="width: 100%;">
📝 Зарегистрироваться
</button>
</form>
<div style="text-align: center; margin-top: 1rem;">
<p>Уже есть аккаунт? <a href="<?= SITE_URL ?>/login">Войдите здесь</a></p>
</div>
</div>
+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>
+60 -49
View File
@@ -2,61 +2,72 @@
include 'views/layouts/header.php';
?>
<h1>Новая глава для: <?= e($book['title']) ?></h1>
<?php if (isset($error) && $error): ?>
<div class="alert alert-error">
<?= e($error) ?>
<div class="container">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h2">Новая глава для: <?= e($book['title']) ?></h1>
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/chapters" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left"></i> Назад к главам
</a>
</div>
<?php endif; ?>
<form method="post" id="chapter-form">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<div style="margin-bottom: 1rem;">
<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="content" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Содержание главы *
</label>
<!-- Контейнер Quill -->
<div id="quill-editor"
class="writer-editor-container"
style="height:500px;"
data-content="<?= htmlspecialchars($_POST['content'] ?? '', ENT_QUOTES) ?>">
<?php if (isset($error) && $error): ?>
<div class="alert alert-danger">
<?= e($error) ?>
</div>
<!-- Скрытый textarea для формы -->
<textarea id="content" name="content" style="display:none;"><?= e($_POST['content'] ?? '') ?></textarea>
<?php endif; ?>
<div style="margin-top: 1rem;">
<label for="status" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Статус главы
</label>
<select id="status" name="status" style="width: 100%;">
<option value="draft" <?= (($_POST['status'] ?? 'draft') == 'draft') ? 'selected' : '' ?>>📝 Черновик</option>
<option value="published" <?= (($_POST['status'] ?? '') == 'published') ? 'selected' : '' ?>>✅ Опубликована</option>
</select>
<small style="color: var(--muted-color);">
Опубликованные главы видны в публичном доступе
</small>
<div class="card">
<div class="card-body">
<form method="post" id="chapter-form">
<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="mb-3">
<label for="content" class="form-label">Содержание главы *</label>
<!-- Контейнер Quill -->
<div id="quill-editor"
class="writer-editor-container"
style="height:500px;"
data-content="<?= htmlspecialchars($_POST['content'] ?? '', ENT_QUOTES) ?>">
</div>
<!-- Скрытый textarea для формы -->
<textarea id="content" name="content" style="display:none;"><?= e($_POST['content'] ?? '') ?></textarea>
</div>
<div class="mb-4">
<label for="status" class="form-label">Статус главы</label>
<select class="form-select" id="status" name="status">
<option value="draft" <?= (($_POST['status'] ?? 'draft') == 'draft') ? 'selected' : '' ?>>📝 Черновик</option>
<option value="published" <?= (($_POST['status'] ?? '') == 'published') ? 'selected' : '' ?>>✅ Опубликована</option>
</select>
<div class="form-text">
Опубликованные главы видны в публичном доступе
</div>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">
<i class="bi bi-check-circle"></i> Сохранить главу
</button>
<button type="button" onclick="previewChapter()" class="btn btn-outline-secondary">
<i class="bi bi-eye"></i> Предпросмотр
</button>
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/chapters" class="btn btn-outline-danger">
<i class="bi bi-x-circle"></i> Отмена
</a>
</div>
</form>
</div>
</div>
</div>
<div style="display: flex; gap: 10px; flex-wrap: wrap; margin-top: 1.5rem;">
<button type="submit" class="contrast">💾 Сохранить главу</button>
<button type="button" onclick="previewChapter()" class="secondary">👁️ Предпросмотр</button>
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/chapters" role="button" class="secondary">❌ Отмена</a>
</div>
</form>
<link href="/assets/css/quill_reset.css" rel="stylesheet">
<!-- <link href="/assets/css/quill_reset.css" rel="stylesheet"> -->
<script>
function previewChapter() {
const form = document.getElementById('chapter-form');
@@ -94,4 +105,4 @@ function previewChapter() {
}
</script>
<script src="/assets/js/editor.js"></script>
<?php include 'views/layouts/footer.php'; ?>
<?php include 'views/layouts/footer.php'; ?>
+110 -73
View File
@@ -1,80 +1,122 @@
// views/chapters/edit.php
<?php
// views/chapters/edit.php
include 'views/layouts/header.php';
?>
<h1>Редактирование главы: <?= e($chapter['title']) ?></h1>
<?php if (isset($error) && $error): ?>
<div class="alert alert-error">
<?= e($error) ?>
</div>
<?php endif; ?>
<form method="post" id="chapter-form">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<div style="margin-bottom: 1rem;">
<label for="title" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Название главы *
</label>
<input type="text" id="title" name="title"
value="<?= e($chapter['title']) ?>"
placeholder="Введите название главы"
style="width: 100%; margin-bottom: 1.5rem;"
required>
<label for="content" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Содержание главы *
</label>
<!-- Контейнер Quill -->
<div id="quill-editor"
class="writer-editor-container"
style="height:500px;"
data-content="<?= htmlspecialchars($chapter['content'] ?? '', ENT_QUOTES) ?>">
</div>
<!-- Скрытый textarea для формы -->
<textarea id="content" name="content" style="display:none;"></textarea>
<div style="margin-top: 1rem;">
<label for="status" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Статус главы
</label>
<select id="status" name="status" style="width: 100%;">
<option value="draft" <?= ($chapter['status'] == 'draft') ? 'selected' : '' ?>>📝 Черновик</option>
<option value="published" <?= ($chapter['status'] == 'published') ? 'selected' : '' ?>>✅ Опубликована</option>
</select>
<small style="color: var(--muted-color);">
Опубликованные главы видны в публичном доступе
</small>
</div>
</div>
<div style="display: flex; gap: 10px; flex-wrap: wrap; margin-top: 1.5rem;">
<button type="submit" class="contrast">
💾 Сохранить изменения
</button>
<button type="button" onclick="previewChapter()" class="secondary">
👁️ Предпросмотр
</button>
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/chapters" role="button" class="secondary">
❌ Отмена
<div class="container">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h2">Редактирование главы: <?= e($chapter['title']) ?></h1>
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/chapters" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left"></i> Назад к главам
</a>
</div>
</form>
<div style="margin-top: 2rem; padding: 1rem; background: var(--card-background-color); border-radius: 5px;">
<h3>Информация о главе</h3>
<p><strong>Книга:</strong> <a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/edit"><?= e($book['title']) ?></a></p>
<p><strong>Количество слов:</strong> <?= $chapter['word_count'] ?></p>
<p><strong>Создана:</strong> <?= date('d.m.Y H:i', strtotime($chapter['created_at'])) ?></p>
<p><strong>Обновлена:</strong> <?= date('d.m.Y H:i', strtotime($chapter['updated_at'])) ?></p>
<?php if (isset($error) && $error): ?>
<div class="alert alert-danger">
<?= e($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" id="chapter-form">
<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($chapter['title']) ?>"
placeholder="Введите название главы" required>
</div>
<div class="mb-3">
<label for="content" class="form-label">Содержание главы *</label>
<!-- Контейнер Quill -->
<div id="quill-editor"
class="writer-editor-container"
style="height:500px;"
data-content="<?= htmlspecialchars($chapter['content'] ?? '', ENT_QUOTES) ?>">
</div>
<!-- Скрытый textarea для формы -->
<textarea id="content" name="content" style="display:none;"></textarea>
</div>
<div class="mb-4">
<label for="status" class="form-label">Статус главы</label>
<select class="form-select" id="status" name="status">
<option value="draft" <?= ($chapter['status'] == 'draft') ? 'selected' : '' ?>>📝 Черновик</option>
<option value="published" <?= ($chapter['status'] == 'published') ? 'selected' : '' ?>>✅ Опубликована</option>
</select>
<div class="form-text">
Опубликованные главы видны в публичном доступе
</div>
</div>
<div class="d-flex gap-2 flex-wrap">
<button type="submit" class="btn btn-primary">
<i class="bi bi-check-circle"></i> Сохранить изменения
</button>
<button type="button" onclick="previewChapter()" class="btn btn-outline-secondary">
<i class="bi bi-eye"></i> Предпросмотр
</button>
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/chapters" class="btn btn-outline-danger">
<i class="bi bi-x-circle"></i> Отмена
</a>
</div>
</form>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Информация о главе</h5>
</div>
<div class="card-body">
<div class="mb-3">
<strong>Книга:</strong><br>
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/edit" class="text-decoration-none">
<?= e($book['title']) ?>
</a>
</div>
<div class="mb-3">
<strong>Количество слов:</strong><br>
<span class="text-primary fw-bold"><?= $chapter['word_count'] ?></span>
</div>
<div class="mb-3">
<strong>Создана:</strong><br>
<small class="text-muted"><?= date('d.m.Y H:i', strtotime($chapter['created_at'])) ?></small>
</div>
<div class="mb-3">
<strong>Обновлена:</strong><br>
<small class="text-muted"><?= date('d.m.Y H:i', strtotime($chapter['updated_at'])) ?></small>
</div>
<div class="alert alert-info mt-3">
<small>
<i class="bi bi-info-circle"></i>
Автосохранение включено. Изменения сохраняются автоматически каждые 2 минуты.
</small>
</div>
</div>
</div>
</div>
</div>
</div>
<link href="/assets/css/quill_reset.css" rel="stylesheet">
<!-- <link href="/assets/css/quill_reset.css" rel="stylesheet"> -->
<script>
function previewChapter() {
const form = document.getElementById('chapter-form');
@@ -101,11 +143,6 @@ function previewChapter() {
titleInput.value = document.getElementById('title').value || 'Предпросмотр главы';
tempForm.appendChild(titleInput);
const editorTypeInput = document.createElement('input');
editorTypeInput.name = 'editor_type';
editorTypeInput.value = '<?= $book['editor_type'] ?? 'markdown' ?>';
tempForm.appendChild(editorTypeInput);
document.body.appendChild(tempForm);
tempForm.submit();
document.body.removeChild(tempForm);
+108 -72
View File
@@ -3,79 +3,115 @@
include 'views/layouts/header.php';
?>
<div style="margin-bottom: 1rem;">
<h1 style="margin: 0 0 0.5rem 0; font-size: 1.5rem;">Главы книги: <?= e($book['title']) ?></h1>
<div style="display: flex; gap: 5px; flex-wrap: wrap; justify-content:center;">
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/chapters/create" class="adaptive-button" role="button"> Новая глава</a>
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/edit" class="adaptive-button secondary" role="button">✏️ Редактировать книгу</a>
<a href="<?= SITE_URL ?>/book/<?= $book['share_token'] ?>" class="adaptive-button green-btn" role="button" target="_blank">👁️ Публичный доступ</a>
<a href="<?= SITE_URL ?>/book/all/<?= $book['id'] ?>" class="adaptive-button" role="button" target="_blank">👁️ Полный обзор</a>
<div class="container">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="h2">Главы книги: <?= e($book['title']) ?></h1>
<p class="text-muted mb-0">Управление главами вашей книги</p>
</div>
<div class="btn-group">
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/chapters/create" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> Новая глава
</a>
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/edit" class="btn btn-outline-secondary">
<i class="bi bi-pencil"></i> Редактировать книгу
</a>
</div>
</div>
<div class="d-flex flex-wrap gap-2 mb-4">
<a href="<?= SITE_URL ?>/book/<?= $book['share_token'] ?>" class="btn btn-outline-success" target="_blank">
<i class="bi bi-eye"></i> Публичный доступ
</a>
<a href="<?= SITE_URL ?>/book/all/<?= $book['id'] ?>" class="btn btn-outline-info" target="_blank">
<i class="bi bi-list-ul"></i> Полный обзор
</a>
</div>
<?php if (empty($chapters)): ?>
<div class="text-center py-5">
<div class="mb-4">
<i class="bi bi-file-text fs-1 text-muted"></i>
</div>
<h3 class="h4 text-muted">В этой книге пока нет глав</h3>
<p class="text-muted mb-4">Создайте первую главу для вашей книги</p>
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/chapters/create" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> Создать первую главу
</a>
</div>
<?php else: ?>
<div class="card">
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th style="width: 5%;">№</th>
<th style="width: 40%;">Название главы</th>
<th style="width: 15%;">Статус</th>
<th style="width: 10%;">Слов</th>
<th style="width: 20%;">Обновлено</th>
<th style="width: 10%;">Действия</th>
</tr>
</thead>
<tbody>
<?php foreach ($chapters as $index => $chapter): ?>
<tr>
<td><?= $index + 1 ?></td>
<td>
<strong><?= e($chapter['title']) ?></strong>
<?php if ($chapter['content']): ?>
<br><small class="text-muted"><?= e(mb_strimwidth($chapter['content'], 0, 100, '...')) ?></small>
<?php endif; ?>
</td>
<td>
<span class="badge <?= $chapter['status'] == 'published' ? 'bg-success' : 'bg-warning' ?>">
<?= $chapter['status'] == 'published' ? 'Опубликована' : 'Черновик' ?>
</span>
</td>
<td><?= $chapter['word_count'] ?></td>
<td>
<small><?= date('d.m.Y H:i', strtotime($chapter['updated_at'])) ?></small>
</td>
<td>
<div class="btn-group btn-group-sm">
<a href="<?= SITE_URL ?>/chapters/<?= $chapter['id'] ?>/edit" class="btn btn-outline-primary" title="Редактировать">
<i class="bi bi-pencil"></i>
</a>
<form method="post" action="<?= SITE_URL ?>/chapters/<?= $chapter['id'] ?>/delete"
onsubmit="return confirm('Вы уверены, что хотите удалить эту главу? Это действие нельзя отменить.');">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<button type="submit" class="btn btn-outline-danger" title="Удалить">
<i class="bi bi-trash"></i>
</button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<div class="mt-3 p-3 bg-light rounded">
<div class="row text-center">
<div class="col-md-4 col-6 mb-2">
<strong class="text-primary"><?= count($chapters) ?></strong>
<small class="text-muted d-block">Всего глав</small>
</div>
<div class="col-md-4 col-6 mb-2">
<strong class="text-success"><?= array_sum(array_column($chapters, 'word_count')) ?></strong>
<small class="text-muted d-block">Всего слов</small>
</div>
<div class="col-md-4 col-12 mb-2">
<strong class="text-info"><?= count(array_filter($chapters, function($ch) { return $ch['status'] == 'published'; })) ?></strong>
<small class="text-muted d-block">Опубликовано</small>
</div>
</div>
</div>
<?php endif; ?>
</div>
<?php if (empty($chapters)): ?>
<div style="text-align: center; padding: 2rem; background: var(--card-background-color); border-radius: 5px; margin-top: 1rem;">
<h3>В этой книге пока нет глав</h3>
<p>Создайте первую главу для вашей книги</p>
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/chapters/create" class="adaptive-button">📝 Создать первую главу</a>
</div>
<?php else: ?>
<div style="overflow-x: auto; margin-top: 1rem;">
<table class="compact-table">
<thead>
<tr>
<th style="width: 5%;">№</th>
<th style="width: 40%;">Название главы</th>
<th style="width: 15%;">Статус</th>
<th style="width: 10%;">Слов</th>
<th style="width: 20%;">Обновлено</th>
<th style="width: 10%;">Действия</th>
</tr>
</thead>
<tbody>
<?php foreach ($chapters as $index => $chapter): ?>
<tr>
<td><?= $index + 1 ?></td>
<td>
<strong><?= e($chapter['title']) ?></strong>
<?php if ($chapter['content']): ?>
<br><small style="color: var(--muted-color);"><?= e(mb_strimwidth($chapter['content'], 0, 100, '...')) ?></small>
<?php endif; ?>
</td>
<td>
<span style="color: <?= $chapter['status'] == 'published' ? 'green' : 'orange' ?>">
<?= $chapter['status'] == 'published' ? '✅ Опубликована' : '📝 Черновик' ?>
</span>
</td>
<td><?= $chapter['word_count'] ?></td>
<td>
<small><?= date('d.m.Y H:i', strtotime($chapter['updated_at'])) ?></small>
</td>
<td>
<div style="display: flex; gap: 3px; flex-wrap: wrap;">
<a href="<?= SITE_URL ?>/chapters/<?= $chapter['id'] ?>/edit" class="compact-button secondary" title="Редактировать" role="button">
✏️
</a>
<form method="post" action="<?= SITE_URL ?>/chapters/<?= $chapter['id'] ?>/delete" style="display: inline;" onsubmit="return confirm('Вы уверены, что хотите удалить эту главу? Это действие нельзя отменить.');">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<button type="submit" class="compact-button secondary" style="background: #ff4444; border-color: #ff4444; color: white;" title="Удалить">
🗑️
</button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div style="margin-top: 1rem; padding: 0.5rem; background: var(--card-background-color); border-radius: 3px;">
<strong>Статистика:</strong>
Всего глав: <?= count($chapters) ?> |
Всего слов: <?= array_sum(array_column($chapters, 'word_count')) ?> |
Опубликовано: <?= count(array_filter($chapters, function($ch) { return $ch['status'] == 'published'; })) ?>
</div>
<?php endif; ?>
<?php include 'views/layouts/footer.php'; ?>
+81 -46
View File
@@ -4,104 +4,139 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= e($page_title) ?></title>
<link rel="stylesheet" href="<?= SITE_URL ?>/assets/css/pico.min.css">
<link rel="stylesheet" href="<?= SITE_URL ?>/assets/css/style.css">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<style>
body {
padding: 20px;
max-width: 800px;
margin: 0 auto;
background-color: #f8f9fa;
line-height: 1.6;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
.content {
margin-top: 2rem;
.preview-container {
max-width: 800px;
margin: 0 auto;
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1, h2, h3, h4, h5, h6 {
.chapter-header {
border-bottom: 3px solid var(--bs-primary);
padding-bottom: 1rem;
margin-bottom: 2rem;
}
.chapter-content {
font-size: 1.1em;
}
.chapter-content h1, .chapter-content h2, .chapter-content h3 {
margin-top: 1.5em;
margin-bottom: 0.5em;
}
h1 { border-bottom: 2px solid var(--primary); padding-bottom: 0.3em; }
h2 { border-bottom: 1px solid var(--border-color); padding-bottom: 0.3em; }
p {
.chapter-content h1 {
border-bottom: 2px solid var(--bs-primary);
padding-bottom: 0.3em;
}
.chapter-content h2 {
border-bottom: 1px solid var(--bs-border-color);
padding-bottom: 0.3em;
}
.chapter-content p {
margin-bottom: 1em;
text-align: justify;
}
code {
background: var(--card-background-color);
.chapter-content code {
background: var(--bs-light);
padding: 2px 4px;
border-radius: 3px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
}
pre {
background: var(--card-background-color);
.chapter-content pre {
background: var(--bs-dark);
color: var(--bs-light);
padding: 1rem;
border-radius: 5px;
overflow-x: auto;
border-left: 4px solid var(--primary);
border-left: 4px solid var(--bs-primary);
}
pre code {
.chapter-content pre code {
background: none;
padding: 0;
display: block;
color: inherit;
}
blockquote {
border-left: 4px solid var(--border-color);
.chapter-content blockquote {
border-left: 4px solid var(--bs-border-color);
padding-left: 1rem;
margin-left: 0;
color: var(--muted-color);
color: var(--bs-secondary);
font-style: italic;
background: var(--bs-light);
padding: 1rem;
border-radius: 0 0.5rem 0.5rem 0;
}
strong { font-weight: bold; }
em { font-style: italic; }
u { text-decoration: underline; }
del { text-decoration: line-through; }
.dialogue {
.chapter-content strong { font-weight: bold; }
.chapter-content em { font-style: italic; }
.chapter-content u { text-decoration: underline; }
.chapter-content del { text-decoration: line-through; }
.chapter-content .dialogue {
margin-left: 2rem;
font-style: italic;
color: #2c5aa0;
}
table {
.chapter-content table {
border-collapse: collapse;
width: 100%;
margin: 1rem 0;
}
table, th, td {
border: 1px solid var(--border-color);
.chapter-content table, .chapter-content th, .chapter-content td {
border: 1px solid var(--bs-border-color);
}
th, td {
.chapter-content th, .chapter-content td {
padding: 8px 12px;
text-align: left;
}
th {
background: var(--card-background-color);
.chapter-content th {
background: var(--bs-light);
}
ul, ol {
.chapter-content ul, .chapter-content ol {
margin-bottom: 1rem;
padding-left: 2rem;
}
li {
.chapter-content li {
margin-bottom: 0.3rem;
}
img {
.chapter-content img {
max-width: 100%;
height: auto;
}
</style>
</head>
<body>
<header>
<h1><?= e($title) ?></h1>
<hr>
</header>
<main class="content">
<?= $content ?>
</main>
<footer style="margin-top: 3rem; padding-top: 1rem; border-top: 1px solid var(--border-color);">
<small>Сгенерировано <?= date('d.m.Y H:i') ?> | Предпросмотр</small>
<br>
<a href="javascript:window.close()" class="button secondary">Закрыть</a>
<a href="javascript:window.print()" class="button">Печать</a>
</footer>
<div class="preview-container">
<header class="chapter-header text-center">
<h1 class="display-6"><?= e($title) ?></h1>
</header>
<main class="chapter-content">
<?= $content ?>
</main>
<footer class="mt-5 pt-4 border-top text-center">
<div class="d-flex justify-content-center gap-2 flex-wrap">
<button onclick="window.print()" class="btn btn-primary">
<i class="bi bi-printer"></i> Печать
</button>
<button onclick="window.close()" class="btn btn-outline-secondary">
<i class="bi bi-x-circle"></i> Закрыть
</button>
</div>
<p class="text-muted mt-3 small">
Сгенерировано <?= date('d.m.Y H:i') ?> | Предпросмотр
</p>
</footer>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
+293 -108
View File
@@ -3,118 +3,303 @@
include 'views/layouts/header.php';
?>
<h1>Панель управления</h1>
<div class="grid" style="margin-bottom: 2rem;">
<article style="text-align: center;">
<h2>📚 Книги</h2>
<div style="font-size: 2rem; font-weight: bold; color: var(--primary);">
<?= $total_books ?>
<div class="container">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h2">Панель управления</h1>
<div class="btn-group">
<a href="<?= SITE_URL ?>/books/create" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> Новая книга
</a>
<a href="<?= SITE_URL ?>/series/create" class="btn btn-outline-primary">
<i class="bi bi-collection"></i> Новая серия
</a>
</div>
<small>Всего книг</small>
</article>
<article style="text-align: center;">
<h2>📑 Главы</h2>
<div style="font-size: 2rem; font-weight: bold; color: var(--success);">
<?= $total_chapters ?>
</div>
<small>Всего глав</small>
</article>
<article style="text-align: center;">
<h2>📝 Слова</h2>
<div style="font-size: 2rem; font-weight: bold; color: var(--warning);">
<?= number_format($total_words) ?>
</div>
<small>Всего слов</small>
</article>
<article style="text-align: center;">
<h2>🌐 Публикации</h2>
<div style="font-size: 2rem; font-weight: bold; color: var(--info);">
<?= $published_books_count ?>
</div>
<small>Опубликовано книг</small>
</article>
</div>
<div class="grid">
<div>
<h2>Недавние книги</h2>
<?php if (!empty($recent_books)): ?>
<?php foreach ($recent_books as $book): ?>
<article style="margin-bottom: 1em; padding-top: 0.5em;">
<h3 style="margin-bottom: 0.5rem; margin-top: 0.5em;">
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/edit">
<?= e($book['title']) ?>
</a>
</h3>
<?php if ($book['genre']): ?>
<p style="margin: 0; color: var(--muted-color); font-size:small;"><em><?= e($book['genre']) ?></em></p>
<?php endif; ?>
<?php if ($book['description']): ?>
<p style="margin: 0; color: var(--muted-color);"><?= e($book['description']) ?></p>
<?php endif; ?>
<footer>
<small>
Глав: <?= $book['chapter_count'] ?? 0 ?> |
Слов: <?= $book['total_words'] ?? 0 ?> |
Статус: <?= $book['published'] ? '✅ Опубликована' : '📝 Черновик' ?>
</small>
</footer>
</article>
<?php endforeach; ?>
<?php if (count($recent_books) < count($books)): ?>
<div style="text-align: center; margin-top: 1rem;">
<a href="<?= SITE_URL ?>/books" class="button">Все книги</a>
</div>
<?php endif; ?>
<?php else: ?>
<article>
<p>У вас пока нет книг.</p>
<a href="<?= SITE_URL ?>/books/create" class="button">Создать первую книгу</a>
</article>
<?php endif; ?>
</div>
<div>
<h2>Мои серии</h2>
<?php if (!empty($series)): ?>
<?php foreach ($series as $ser): ?>
<article>
<h3 style="margin-bottom: 0.5rem;">
<a href="<?= SITE_URL ?>/series/<?= $ser['id'] ?>/edit">
<?= e($ser['title']) ?>
</a>
</h3>
<?php if ($ser['description']): ?>
<p><?= e(mb_strimwidth($ser['description'], 0, 100, '...')) ?></p>
<?php endif; ?>
<footer>
<small>
Книг: <?= $ser['book_count'] ?> |
Слов: <?= $ser['total_words'] ?>
</small>
</footer>
</article>
<?php endforeach; ?>
<div style="text-align: center; margin-top: 1rem;">
<a href="<?= SITE_URL ?>/series" class="button">Все серии</a>
<!-- Статистика -->
<div class="row g-4 mb-5">
<div class="col-xl-3 col-md-6">
<div class="card border-0 bg-primary bg-opacity-10">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<h3 class="card-title text-primary"><?= $total_books ?></h3>
<p class="card-text text-muted mb-0">Всего книг</p>
</div>
<div class="flex-shrink-0">
<i class="bi bi-journal-bookmark fs-1 text-primary"></i>
</div>
</div>
</div>
</div>
<?php else: ?>
<article>
<p>У вас пока нет серий.</p>
<a href="<?= SITE_URL ?>/series/create" class="button">Создать первую серию</a>
</article>
<?php endif; ?>
</div>
<h2 style="margin-top: 2rem;">Быстрые действия</h2>
<div class="button-group">
<a href="<?= SITE_URL ?>/books/create" class="button">📖 Новая книга</a>
<a href="<?= SITE_URL ?>/series/create" class="button secondary">📚 Новая серия</a>
<div class="col-xl-3 col-md-6">
<div class="card border-0 bg-success bg-opacity-10">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<h3 class="card-title text-success"><?= $total_chapters ?></h3>
<p class="card-text text-muted mb-0">Всего глав</p>
</div>
<div class="flex-shrink-0">
<i class="bi bi-file-text fs-1 text-success"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="card border-0 bg-warning bg-opacity-10">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<h3 class="card-title text-warning"><?= number_format($total_words) ?></h3>
<p class="card-text text-muted mb-0">Всего слов</p>
</div>
<div class="flex-shrink-0">
<i class="bi bi-fonts fs-1 text-warning"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="card border-0 bg-info bg-opacity-10">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<h3 class="card-title text-info"><?= $published_books_count ?></h3>
<p class="card-text text-muted mb-0">Опубликовано книг</p>
</div>
<div class="flex-shrink-0">
<i class="bi bi-globe fs-1 text-info"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row g-4">
<!-- Недавние книги -->
<div class="col-lg-6">
<div class="card h-100">
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0">Недавние книги</h5>
<a href="<?= SITE_URL ?>/books" class="btn btn-sm btn-outline-primary">Все книги</a>
</div>
<div class="card-body">
<?php if (!empty($recent_books)): ?>
<div class="list-group list-group-flush">
<?php foreach ($recent_books as $book): ?>
<div class="list-group-item px-0">
<div class="d-flex justify-content-between align-items-start mb-2">
<h6 class="mb-1">
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/edit" class="text-decoration-none">
<?= e($book['title']) ?>
</a>
</h6>
<span class="badge <?= $book['published'] ? 'bg-success' : 'bg-warning' ?>">
<?= $book['published'] ? 'Опубликована' : 'Черновик' ?>
</span>
</div>
<?php if ($book['genre']): ?>
<p class="text-muted small mb-1"><em><?= e($book['genre']) ?></em></p>
<?php endif; ?>
<?php if ($book['description']): ?>
<p class="text-muted small mb-2"><?= e($book['description']) ?></p>
<?php endif; ?>
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">
Глав: <?= $book['chapter_count'] ?? 0 ?> |
Слов: <?= $book['total_words'] ?? 0 ?>
</small>
<div class="btn-group btn-group-sm">
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/edit" class="btn btn-outline-primary btn-sm">
<i class="bi bi-pencil"></i>
</a>
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/chapters" class="btn btn-outline-secondary btn-sm">
<i class="bi bi-file-text"></i>
</a>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php if (count($recent_books) < $total_books): ?>
<div class="text-center mt-3">
<a href="<?= SITE_URL ?>/books" class="btn btn-outline-primary">Все книги</a>
</div>
<?php endif; ?>
<?php else: ?>
<div class="text-center py-4">
<i class="bi bi-journal-bookmark fs-1 text-muted"></i>
<p class="text-muted mt-2">У вас пока нет книг</p>
<a href="<?= SITE_URL ?>/books/create" class="btn btn-primary">Создать первую книгу</a>
</div>
<?php endif; ?>
</div>
</div>
</div>
<!-- Мои серии и быстрые действия -->
<div class="col-lg-6">
<div class="card h-100">
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0">Мои серии</h5>
<a href="<?= SITE_URL ?>/series" class="btn btn-sm btn-outline-primary">Все серии</a>
</div>
<div class="card-body">
<?php if (!empty($series)): ?>
<div class="list-group list-group-flush">
<?php foreach ($series as $ser): ?>
<div class="list-group-item px-0">
<div class="d-flex justify-content-between align-items-start mb-2">
<h6 class="mb-1">
<a href="<?= SITE_URL ?>/series/<?= $ser['id'] ?>/edit" class="text-decoration-none">
<?= e($ser['title']) ?>
</a>
</h6>
<span class="badge bg-primary"><?= $ser['book_count'] ?> книг</span>
</div>
<?php if ($ser['description']): ?>
<p class="text-muted small mb-2"><?= e(mb_strimwidth($ser['description'], 0, 100, '...')) ?></p>
<?php endif; ?>
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">
Книг: <?= $ser['book_count'] ?> |
Слов: <?= $ser['total_words'] ?>
</small>
<div class="btn-group btn-group-sm">
<a href="<?= SITE_URL ?>/series/<?= $ser['id'] ?>/edit" class="btn btn-outline-primary btn-sm">
<i class="bi bi-pencil"></i>
</a>
<a href="<?= SITE_URL ?>/series/<?= $ser['id'] ?>/view" class="btn btn-outline-success btn-sm" target="_blank">
<i class="bi bi-eye"></i>
</a>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php else: ?>
<div class="text-center py-4">
<i class="bi bi-collection fs-1 text-muted"></i>
<p class="text-muted mt-2">У вас пока нет серий</p>
<a href="<?= SITE_URL ?>/series/create" class="btn btn-primary">Создать первую серию</a>
</div>
<?php endif; ?>
</div>
</div>
</div>
<div class="row g-4">
<!-- Быстрые действия -->
<div class="card mt-4">
<div class="card-header bg-white">
<h5 class="card-title mb-0">Быстрые действия</h5>
</div>
<div class="card-body">
<div class="row g-2">
<div class="col-md-6">
<a href="<?= SITE_URL ?>/books/create" class="btn btn-outline-primary w-100 d-flex align-items-center justify-content-center py-3">
<i class="bi bi-journal-plus fs-4 me-2"></i>
<div>
<div>Новая книга</div>
<small class="text-muted">Создать книгу</small>
</div>
</a>
</div>
<div class="col-md-6">
<a href="<?= SITE_URL ?>/series/create" class="btn btn-outline-secondary w-100 d-flex align-items-center justify-content-center py-3">
<i class="bi bi-collection fs-4 me-2"></i>
<div>
<div>Новая серия</div>
<small class="text-muted">Создать серию</small>
</div>
</a>
</div>
<div class="col-md-6">
<a href="<?= SITE_URL ?>/books" class="btn btn-outline-success w-100 d-flex align-items-center justify-content-center py-3">
<i class="bi bi-journal-bookmark fs-4 me-2"></i>
<div>
<div>Все книги</div>
<small class="text-muted">Управление</small>
</div>
</a>
</div>
<div class="col-md-6">
<a href="<?= SITE_URL ?>/profile" class="btn btn-outline-info w-100 d-flex align-items-center justify-content-center py-3">
<i class="bi bi-person fs-4 me-2"></i>
<div>
<div>Профиль</div>
<small class="text-muted">Настройки</small>
</div>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Дополнительная статистика -->
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header bg-white">
<h5 class="card-title mb-0">Прогресс писателя</h5>
</div>
<div class="card-body">
<div class="row text-center">
<div class="col-md-2 col-6 mb-3">
<div class="h4 text-primary mb-1"><?= $total_books ?></div>
<small class="text-muted">Книг</small>
</div>
<div class="col-md-2 col-6 mb-3">
<div class="h4 text-success mb-1"><?= $total_chapters ?></div>
<small class="text-muted">Глав</small>
</div>
<div class="col-md-2 col-6 mb-3">
<div class="h4 text-warning mb-1"><?= number_format($total_words) ?></div>
<small class="text-muted">Слов</small>
</div>
<div class="col-md-2 col-6 mb-3">
<div class="h4 text-info mb-1"><?= $published_books_count ?></div>
<small class="text-muted">Опубликовано</small>
</div>
<div class="col-md-2 col-6 mb-3">
<div class="h4 text-secondary mb-1"><?= $total_books > 0 ? round($total_words / $total_books) : 0 ?></div>
<small class="text-muted">Слов/книга</small>
</div>
<div class="col-md-2 col-6 mb-3">
<div class="h4 text-dark mb-1"><?= $total_chapters > 0 ? round($total_words / $total_chapters) : 0 ?></div>
<small class="text-muted">Слов/глава</small>
</div>
</div>
<?php if ($total_words > 0): ?>
<div class="mt-3">
<div class="d-flex justify-content-between mb-1">
<small>Прогресс этого месяца</small>
<small><?= number_format($total_words) ?> слов</small>
</div>
<div class="progress" style="height: 8px;">
<div class="progress-bar bg-success" style="width: <?= min(100, ($total_words / 50000) * 100) ?>%"></div>
</div>
<small class="text-muted">Цель: 50,000 слов в месяц</small>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
+27 -11
View File
@@ -3,17 +3,33 @@
include 'views/layouts/header.php';
?>
<div class="container" style="text-align: center; padding: 4rem 1rem;">
<h1>404 - Страница не найдена</h1>
<p style="font-size: 1.2rem; margin-bottom: 2rem;">
Запрашиваемая страница не существует или была перемещена.
</p>
<div style="display: flex; gap: 10px; justify-content: center; flex-wrap: wrap;">
<a href="<?= SITE_URL ?>/" class="button">🏠 На главную</a>
<a href="<?= SITE_URL ?>/books" class="button secondary">📚 К книгам</a>
<?php if (!is_logged_in()): ?>
<a href="<?= SITE_URL ?>/login" class="button secondary">🔑 Войти</a>
<?php endif; ?>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8 text-center">
<div class="py-5">
<div class="mb-4">
<i class="bi bi-exclamation-triangle display-1 text-warning"></i>
</div>
<h1 class="display-4 fw-bold text-muted">404</h1>
<h2 class="h3 mb-4">Страница не найдена</h2>
<p class="lead text-muted mb-5">
Запрашиваемая страница не существует или была перемещена.
</p>
<div class="d-flex gap-3 justify-content-center flex-wrap">
<a href="<?= SITE_URL ?>/" class="btn btn-primary btn-lg">
<i class="bi bi-house"></i> На главную
</a>
<a href="<?= SITE_URL ?>/books" class="btn btn-outline-secondary btn-lg">
<i class="bi bi-journal-bookmark"></i> К книгам
</a>
<?php if (!is_logged_in()): ?>
<a href="<?= SITE_URL ?>/login" class="btn btn-outline-primary btn-lg">
<i class="bi bi-box-arrow-in-right"></i> Войти
</a>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
+77 -8
View File
@@ -1,18 +1,87 @@
<?php
// views/layouts/footer.php
?>
</div>
</main>
<footer class="container" style="margin-top: 3rem; padding-top: 1rem; border-top: 1px solid var(--muted-border-color);">
<small>
&copy; <?= date('Y') ?> <?= e(APP_NAME) ?>.
<?php if (is_logged_in()): ?>
<a href="<?= SITE_URL ?>/author/<?= $_SESSION['user_id'] ?>" target="_blank">Моя публичная страница</a>
<?php endif; ?>
</small>
<footer class="bg-light mt-5 py-4 border-top">
<div class="container">
<div class="row align-items-center">
<div class="col-md-6">
<small class="text-muted">
&copy; <?= date('Y') ?> <?= e(APP_NAME) ?>
</small>
</div>
<div class="col-md-6 text-md-end">
<?php if (is_logged_in()): ?>
<a href="<?= SITE_URL ?>/author/<?= $_SESSION['user_id'] ?>" target="_blank" class="text-muted text-decoration-none">
<i class="bi bi-eye"></i> Моя публичная страница
</a>
<?php endif; ?>
</div>
</div>
</div>
</footer>
<script src="<?= SITE_URL ?>/assets/js/quill.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Функция для установки темы
function setTheme(themeName) {
// Устанавливаем cookie на 30 дней
const date = new Date();
date.setTime(date.getTime() + (30 * 24 * 60 * 60 * 1000));
const expires = "expires=" + date.toUTCString();
document.cookie = "bs_theme=" + themeName + ";" + expires + ";path=/";
// Перезагружаем страницу для применения темы
window.location.reload();
}
// Функция для получения цвета темы (для превью)
function getThemeColor(theme) {
const themeColors = {
'default': '#0d6efd',
'cerulean': '#2fa4e7',
'cosmo': '#2780e3',
'cyborg': '#060606',
'darkly': '#375a7f',
'flatly': '#2c3e50',
'journal': '#eb6864',
'litera': '#4582ec',
'lumen': '#158cba',
'lux': '#1a1a1a',
'materia': '#2196f3',
'minty': '#78c2ad',
'morph': '#6750a4',
'pulse': '#593196',
'quartz': '#d7ccc8',
'sandstone': '#325d88',
'simplex': '#d9230f',
'sketchy': '#333333',
'slate': '#484e5a',
'solar': '#b58900',
'spacelab': '#446e9b',
'superhero': '#4e5d6c',
'united': '#e95420',
'vapor': '#0d6efd',
'yeti': '#008cba',
'zephyr': '#0d6efd'
};
return themeColors[theme] || '#0d6efd';
}
// Применяем цвет превью для всех элементов
document.addEventListener('DOMContentLoaded', function() {
const themePreviews = document.querySelectorAll('.theme-preview');
themePreviews.forEach(preview => {
const themeOption = preview.closest('.theme-option');
if (themeOption) {
const theme = themeOption.getAttribute('onclick').match(/setTheme\('([^']+)'\)/)[1];
preview.style.backgroundColor = getThemeColor(theme);
}
});
});
// Глобальные функции JavaScript
function confirmAction(message) {
return confirm(message || 'Вы уверены?');
+171 -55
View File
@@ -1,5 +1,36 @@
<?php
// views/layouts/header.php
// Получаем текущую тему из cookies
$current_theme = $_COOKIE['bs_theme'] ?? 'default';
$available_themes = [
'default' => 'Bootstrap Default',
'cerulean' => 'Cerulean',
'cosmo' => 'Cosmo',
'cyborg' => 'Cyborg',
'darkly' => 'Darkly',
'flatly' => 'Flatly',
'journal' => 'Journal',
'litera' => 'Litera',
'lumen' => 'Lumen',
'lux' => 'Lux',
'materia' => 'Materia',
'minty' => 'Minty',
'morph' => 'Morph',
'pulse' => 'Pulse',
'quartz' => 'Quartz',
'sandstone' => 'Sandstone',
'simplex' => 'Simplex',
'sketchy' => 'Sketchy',
'slate' => 'Slate',
'solar' => 'Solar',
'spacelab' => 'Spacelab',
'superhero' => 'Superhero',
'united' => 'United',
'vapor' => 'Vapor',
'yeti' => 'Yeti',
'zephyr' => 'Zephyr'
];
?>
<!DOCTYPE html>
<html lang="ru">
@@ -7,68 +38,153 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= e($page_title ?? 'Web Writer') ?></title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1.5.10/css/pico.min.css">
<!-- Подключаем выбранную тему Bootstrap -->
<?php if ($current_theme === 'default'): ?>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<?php else: ?>
<link href="<?= SITE_URL ?>/assets/bs/<?= e($current_theme) ?>/bootstrap.min.css" rel="stylesheet">
<?php endif; ?>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<link rel="stylesheet" href="<?= SITE_URL ?>/assets/css/style.css">
<link href="<?= SITE_URL ?>/assets/css/quill.snow.css" rel="stylesheet">
<script src="<?= SITE_URL ?>/assets/js/quill.js"></script>
<style>
.navbar-brand { font-weight: 600; }
.dropdown-menu { min-width: 200px; }
.alert { border: none; border-radius: 8px; }
.main-container { min-height: calc(100vh - 120px); }
.theme-option { padding: 8px 12px; cursor: pointer; }
.theme-option:hover { background-color: var(--bs-light); }
.theme-option.active { background-color: var(--bs-primary); color: white; }
.theme-preview { width: 20px; height: 20px; border-radius: 3px; display: inline-block; margin-right: 8px; border: 1px solid #dee2e6; }
</style>
</head>
<body>
<nav class="container-fluid">
<ul>
<li><strong><a href="<?= SITE_URL ?>/"><?= e(APP_NAME) ?></a></strong></li>
</ul>
<ul>
<?php if (is_logged_in()): ?>
<li><a href="<?= SITE_URL ?>/dashboard">📊 Панель управления</a></li>
<li><a href="<?= SITE_URL ?>/books">📚 Мои книги</a></li>
<li><a href="<?= SITE_URL ?>/series">📑 Серии</a></li>
<li>
<details role="list" dir="rtl">
<summary aria-haspopup="listbox" role="link">
👤 <?= e($_SESSION['display_name']) ?>
</summary>
<ul role="listbox">
<li><a href="<?= SITE_URL ?>/profile">⚙️ Профиль</a></li>
<li><a href="<?= SITE_URL ?>/author/<?= $_SESSION['user_id'] ?>" target="_blank">👤 Моя публичная страница</a></li>
<?php if ($_SESSION['user_id'] == 1): // Проверка на администратора ?>
<li><a href="<?= SITE_URL ?>/admin/users">👥 Управление пользователями</a></li>
<?php endif; ?>
<li><a href="<?= SITE_URL ?>/logout">🚪 Выход</a></li>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="<?= SITE_URL ?>/"><?= e(APP_NAME) ?></a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<?php if (is_logged_in()): ?>
<li class="nav-item">
<a class="nav-link" href="<?= SITE_URL ?>/dashboard">
<i class="bi bi-speedometer2"></i> Панель управления
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="<?= SITE_URL ?>/books">
<i class="bi bi-journal-bookmark"></i> Мои книги
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="<?= SITE_URL ?>/series">
<i class="bi bi-collection"></i> Серии
</a>
</li>
<?php endif; ?>
</ul>
<ul class="navbar-nav">
<!-- Выбор темы -->
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="bi bi-palette"></i> Тема
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li><h6 class="dropdown-header">Выбор темы</h6></li>
<?php foreach ($available_themes as $theme_key => $theme_name): ?>
<li>
<a class="dropdown-item theme-option <?= $current_theme === $theme_key ? 'active' : '' ?>"
href="#"
onclick="setTheme('<?= $theme_key ?>')">
<span class="theme-preview" style="background-color: getThemeColor('<?= $theme_key ?>')"></span>
<?= e($theme_name) ?>
<?php if ($current_theme === $theme_key): ?>
<i class="bi bi-check float-end"></i>
<?php endif; ?>
</a>
</li>
<?php endforeach; ?>
</ul>
</details>
</li>
<?php else: ?>
<li><a href="<?= SITE_URL ?>/login">🔑 Вход</a></li>
<li><a href="<?= SITE_URL ?>/register">📝 Регистрация</a></li>
<?php endif; ?>
</ul>
</li>
<?php if (is_logged_in()): ?>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="bi bi-person-circle"></i> <?= e($_SESSION['display_name']) ?>
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="<?= SITE_URL ?>/profile">
<i class="bi bi-gear"></i> Профиль
</a></li>
<li><a class="dropdown-item" href="<?= SITE_URL ?>/author/<?= $_SESSION['user_id'] ?>" target="_blank">
<i class="bi bi-eye"></i> Публичная страница
</a></li>
<?php if ($_SESSION['user_id'] == 1): ?>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="<?= SITE_URL ?>/admin/users">
<i class="bi bi-people"></i> Управление пользователями
</a></li>
<?php endif; ?>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="<?= SITE_URL ?>/logout">
<i class="bi bi-box-arrow-right"></i> Выход
</a></li>
</ul>
</li>
<?php else: ?>
<li class="nav-item">
<a class="nav-link" href="<?= SITE_URL ?>/login">
<i class="bi bi-box-arrow-in-right"></i> Вход
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="<?= SITE_URL ?>/register">
<i class="bi bi-person-plus"></i> Регистрация
</a>
</li>
<?php endif; ?>
</ul>
</div>
</div>
</nav>
<main class="container">
<?php if (isset($_SESSION['success'])): ?>
<div class="alert alert-success">
<?= e($_SESSION['success']) ?>
<?php unset($_SESSION['success']); ?>
</div>
<?php endif; ?>
<main class="main-container">
<div class="container mt-4">
<?php if (isset($_SESSION['success'])): ?>
<div class="alert alert-success alert-dismissible fade show">
<?= e($_SESSION['success']) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
<?php unset($_SESSION['success']); ?>
</div>
<?php endif; ?>
<?php if (isset($_SESSION['error'])): ?>
<div class="alert alert-error">
<?= e($_SESSION['error']) ?>
<?php unset($_SESSION['error']); ?>
</div>
<?php endif; ?>
<?php if (isset($_SESSION['error'])): ?>
<div class="alert alert-danger alert-dismissible fade show">
<?= e($_SESSION['error']) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
<?php unset($_SESSION['error']); ?>
</div>
<?php endif; ?>
<?php if (isset($_SESSION['warning'])): ?>
<div class="alert alert-warning">
<?= e($_SESSION['warning']) ?>
<?php unset($_SESSION['warning']); ?>
</div>
<?php endif; ?>
<?php if (isset($_SESSION['warning'])): ?>
<div class="alert alert-warning alert-dismissible fade show">
<?= e($_SESSION['warning']) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
<?php unset($_SESSION['warning']); ?>
</div>
<?php endif; ?>
<?php if (isset($_SESSION['info'])): ?>
<div class="alert alert-info">
<?= e($_SESSION['info']) ?>
<?php unset($_SESSION['info']); ?>
</div>
<?php endif; ?>
<?php if (isset($_SESSION['info'])): ?>
<div class="alert alert-info alert-dismissible fade show">
<?= e($_SESSION['info']) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
<?php unset($_SESSION['info']); ?>
</div>
<?php endif; ?>
+61 -47
View File
@@ -3,56 +3,70 @@
include 'views/layouts/header.php';
?>
<h1>Создание новой серии</h1>
<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 ?>/series" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left"></i> Назад к сериям
</a>
</div>
<?php if (isset($error) && $error): ?>
<div class="alert alert-error">
<?= e($error) ?>
</div>
<?php endif; ?>
<?php if (isset($error) && $error): ?>
<div class="alert alert-danger">
<?= e($error) ?>
</div>
<?php endif; ?>
<form method="post">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<div style="max-width: 100%; margin-bottom: 1rem;">
<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="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>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<button type="submit" class="contrast">
📚 Создать серию
</button>
<a href="<?= SITE_URL ?>/series" role="button" class="secondary">
❌ Отмена
</a>
</div>
</form>
<div class="card">
<div class="card-body">
<form method="post">
<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="mb-4">
<label for="description" class="form-label">Описание серии</label>
<textarea class="form-control" id="description" name="description"
placeholder="Описание сюжета серии, общая концепция..."
rows="6"><?= e($_POST['description'] ?? '') ?></textarea>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">
<i class="bi bi-collection"></i> Создать серию
</button>
<a href="<?= SITE_URL ?>/series" class="btn btn-outline-danger">
<i class="bi bi-x-circle"></i> Отмена
</a>
</div>
</form>
</div>
</div>
<div style="margin-top: 2rem; padding: 1rem; background: var(--card-background-color); border-radius: 5px;">
<h3>Что такое серия?</h3>
<p>Серия позволяет объединить несколько книг в одну тематическую коллекцию. Это полезно для:</p>
<ul>
<li>Циклов книг с общим сюжетом</li>
<li>Книг в одном мире или вселенной</li>
<li>Организации книг по темам или жанрам</li>
</ul>
<p>Вы сможете добавить книги в серию после её создания.</p>
<div class="card mt-4">
<div class="card-header">
<h5 class="card-title mb-0">Что такое серия?</h5>
</div>
<div class="card-body">
<p>Серия позволяет объединить несколько книг в одну тематическую коллекцию. Это полезно для:</p>
<ul>
<li>Циклов книг с общим сюжетом</li>
<li>Книг в одном мире или вселенной</li>
<li>Организации книг по темам или жанрам</li>
</ul>
<p>Вы сможете добавить книги в серию после её создания.</p>
</div>
</div>
</div>
</div>
</div>
<?php include 'views/layouts/footer.php'; ?>
+127 -124
View File
@@ -2,115 +2,147 @@
include 'views/layouts/header.php';
?>
<h1>Редактирование серии: <?= e($series['title']) ?></h1>
<article>
<h2>Основная информация</h2>
<form method="post" action="/series/<?= $series['id'] ?>/edit">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<label for="title">
Название серии *
<input type="text" id="title" name="title" value="<?= e($series['title']) ?>" required>
</label>
<label for="description">
Описание серии
<textarea id="description" name="description" rows="4"><?= e($series['description'] ?? '') ?></textarea>
</label>
<button type="submit" class="primary-btn">Сохранить изменения</button>
</form>
</article>
<div class="grid">
<div>
<article>
<h2>Добавить книгу в серию</h2>
<?php if (!empty($available_books)): ?>
<form method="post" action="/series/<?= $series['id'] ?>/add-book">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<label for="book_id">
Выберите книгу
<select id="book_id" name="book_id" required>
<option value="">-- Выберите книгу --</option>
<?php foreach ($available_books as $book): ?>
<option value="<?= $book['id'] ?>"><?= e($book['title']) ?></option>
<?php endforeach; ?>
</select>
</label>
<label for="sort_order">
Порядковый номер в серии
<input type="number" id="sort_order" name="sort_order" value="<?= count($books_in_series) + 1 ?>" min="1">
</label>
<button type="submit" class="secondary-btn">Добавить в серию</button>
</form>
<?php else: ?>
<p>Все ваши книги уже добавлены в эту серию или у вас нет доступных книг.</p>
<a href="/books/create" class="primary-btn" role="button">Создать новую книгу</a>
<?php endif; ?>
</article>
<div class="container">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h2">Редактирование серии: <?= e($series['title']) ?></h1>
<a href="<?= SITE_URL ?>/series" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left"></i> Назад к сериям
</a>
</div>
<div>
<article>
<h2>Книги в серии (<?= count($books_in_series) ?>)</h2>
<?php if (!empty($books_in_series)): ?>
<div id="series-books-list">
<form id="reorder-form" method="post" action="/series/<?= $series['id'] ?>/update-order">
<div class="row">
<div class="col-lg-6">
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title mb-0">Основная информация</h5>
</div>
<div class="card-body">
<form method="post" action="/series/<?= $series['id'] ?>/edit">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<div class="books-list">
<?php foreach ($books_in_series as $index => $book): ?>
<div class="book-item" data-book-id="<?= $book['id'] ?>">
<div class="book-drag-handle" style="cursor: move;">☰</div>
<div class="book-info">
<strong><?= e($book['title']) ?></strong>
<small>Порядок: <?= $book['sort_order_in_series'] ?></small>
</div>
<div class="book-actions" style="display: grid; min-width: 2rem; margin-top: 1rem;">
<a href="/books/<?= $book['id'] ?>/edit" class="compact-button" role="button" style="margin-top: 0em;">✏️</a>
<form method="post" action="/series/<?= $series['id'] ?>/remove-book/<?= $book['id'] ?>"
style="display: inline;" onsubmit="return confirm('Удалить книгу из серии?')">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<button type="submit" class="compact-button red-btn" style="margin-top: 0em;">🗑️</button>
</form>
</div>
<input type="hidden" name="order[]" value="<?= $book['id'] ?>">
</div>
<?php endforeach; ?>
<div class="mb-3">
<label for="title" class="form-label">Название серии *</label>
<input type="text" class="form-control" id="title" name="title"
value="<?= e($series['title']) ?>" required>
</div>
<button type="submit" class="secondary-btn" id="save-order-btn" style="display: none;">
Сохранить порядок
<div class="mb-3">
<label for="description" class="form-label">Описание серии</label>
<textarea class="form-control" id="description" name="description"
rows="4"><?= e($series['description'] ?? '') ?></textarea>
</div>
<button type="submit" class="btn btn-primary">
<i class="bi bi-check-circle"></i> Сохранить изменения
</button>
</form>
</div>
<?php else: ?>
<p>В этой серии пока нет книг. Добавьте книги с помощью формы слева.</p>
<?php endif; ?>
</article>
</div>
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Добавить книгу в серию</h5>
</div>
<div class="card-body">
<?php if (!empty($available_books)): ?>
<form method="post" action="/series/<?= $series['id'] ?>/add-book">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<div class="mb-3">
<label for="book_id" class="form-label">Выберите книгу</label>
<select class="form-select" id="book_id" name="book_id" required>
<option value="">-- Выберите книгу --</option>
<?php foreach ($available_books as $book): ?>
<option value="<?= $book['id'] ?>"><?= e($book['title']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label for="sort_order" class="form-label">Порядковый номер в серии</label>
<input type="number" class="form-control" id="sort_order" name="sort_order"
value="<?= count($books_in_series) + 1 ?>" min="1">
</div>
<button type="submit" class="btn btn-outline-primary">
<i class="bi bi-plus-circle"></i> Добавить в серию
</button>
</form>
<?php else: ?>
<div class="text-center py-3">
<i class="bi bi-journal-bookmark fs-1 text-muted"></i>
<p class="text-muted mt-2">Все ваши книги уже добавлены в эту серию или у вас нет доступных книг.</p>
<a href="/books/create" class="btn btn-primary">Создать новую книгу</a>
</div>
<?php endif; ?>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Книги в серии (<?= count($books_in_series) ?>)</h5>
</div>
<div class="card-body">
<?php if (!empty($books_in_series)): ?>
<div id="series-books-list">
<form id="reorder-form" method="post" action="/series/<?= $series['id'] ?>/update-order">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<div class="books-list">
<?php foreach ($books_in_series as $index => $book): ?>
<div class="book-item border rounded p-3 mb-2 d-flex align-items-center"
data-book-id="<?= $book['id'] ?>">
<div class="book-drag-handle text-muted me-3" style="cursor: move;">
<i class="bi bi-grip-vertical"></i>
</div>
<div class="book-info flex-grow-1">
<strong><?= e($book['title']) ?></strong>
<br>
<small class="text-muted">Порядок: <?= $book['sort_order_in_series'] ?></small>
</div>
<div class="book-actions ms-3">
<div class="btn-group btn-group-sm">
<a href="/books/<?= $book['id'] ?>/edit" class="btn btn-outline-primary" title="Редактировать">
<i class="bi bi-pencil"></i>
</a>
<form method="post" action="/series/<?= $series['id'] ?>/remove-book/<?= $book['id'] ?>"
onsubmit="return confirm('Удалить книгу из серии?')" class="d-inline">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<button type="submit" class="btn btn-outline-danger" title="Удалить из серии">
<i class="bi bi-trash"></i>
</button>
</form>
</div>
</div>
<input type="hidden" name="order[]" value="<?= $book['id'] ?>">
</div>
<?php endforeach; ?>
</div>
<button type="submit" class="btn btn-outline-success w-100 mt-3" id="save-order-btn" style="display: none;">
<i class="bi bi-check-circle"></i> Сохранить порядок
</button>
</form>
</div>
<?php else: ?>
<div class="text-center py-4">
<i class="bi bi-journal-bookmark fs-1 text-muted"></i>
<p class="text-muted mt-2">В этой серии пока нет книг.</p>
<p class="text-muted small">Добавьте книги с помощью формы слева.</p>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
<style>
.books-list {
border: 1px solid #e0e0e0;
border-radius: 4px;
}
.book-item {
display: flex;
align-items: center;
padding: 10px;
border-bottom: 1px solid #f0f0f0;
background: white;
transition: background-color 0.2s ease;
}
.book-item:last-child {
border-bottom: none;
background: white;
}
.book-item:hover {
@@ -126,30 +158,8 @@ include 'views/layouts/header.php';
}
.book-drag-handle {
padding: 0 10px;
color: #666;
font-size: 1.2rem;
}
.book-info {
flex: 1;
padding: 0 10px;
}
.book-info strong {
display: block;
margin-bottom: 2px;
}
.book-info small {
color: #666;
font-size: 0.8rem;
}
.book-actions {
display: flex;
gap: 5px;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.15.0/Sortable.min.js"></script>
@@ -172,13 +182,6 @@ document.addEventListener('DOMContentLoaded', function() {
// Автосохранение порядка через 2 секунды после изменения
let saveTimeout;
saveOrderBtn.addEventListener('click', function(e) {
e.preventDefault();
clearTimeout(saveTimeout);
document.getElementById('reorder-form').submit();
});
// Автоматическое сохранение при изменении порядка
booksList.addEventListener('sortupdate', function() {
clearTimeout(saveTimeout);
saveTimeout = setTimeout(() => {
+90 -75
View File
@@ -2,82 +2,97 @@
include 'views/layouts/header.php';
?>
<div style="display: block; justify-content: between; align-items: center; margin-bottom: 2rem; flex-wrap: wrap; gap: 1rem;">
<h1 style="margin: 0;">Мои серии книг</h1>
<a href="/series/create" class="action-button primary" role="button"> Создать серию</a>
</div>
<?php if (empty($series)): ?>
<article class="series-empty-state">
<div class="series-empty-icon">📚</div>
<h2>Пока нет серий</h2>
<p style="color: #666; margin-bottom: 2rem;">
Создайте свою первую серию, чтобы организовать книги в циклы и сериалы.
</p>
<div style="display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap;">
<a href="/series/create" class="action-button primary" role="button">Создать серию</a>
<a href="/books" class="action-button secondary">Перейти к книгам</a>
</div>
</article>
<?php else: ?>
<div class="series-grid">
<?php foreach ($series as $ser): ?>
<article class="series-card">
<div class="series-header">
<h3 class="series-title">
<a href="/series/<?= $ser['id'] ?>/edit"><?= e($ser['title']) ?></a>
</h3>
<div class="series-meta">
Создана <?= date('d.m.Y', strtotime($ser['created_at'])) ?>
<?php if ($ser['updated_at'] != $ser['created_at']): ?>
• Обновлена <?= date('d.m.Y', strtotime($ser['updated_at'])) ?>
<?php endif; ?>
</div>
</div>
<?php if (!empty($ser['description'])): ?>
<div class="series-description">
<?= e($ser['description']) ?>
</div>
<?php endif; ?>
<div class="series-stats-grid">
<div class="series-stat">
<span class="series-stat-number"><?= $ser['book_count'] ?? 0 ?></span>
<span class="series-stat-label">книг</span>
</div>
<div class="series-stat">
<span class="series-stat-number"><?= number_format($ser['total_words'] ?? 0) ?></span>
<span class="series-stat-label">слов</span>
</div>
<div class="series-stat">
<span class="series-stat-number">
<?php
$avg_words = $ser['book_count'] > 0 ? round($ser['total_words'] / $ser['book_count']) : 0;
echo number_format($avg_words);
?>
</span>
<span class="series-stat-label">слов/книга</span>
</div>
</div>
<div class="series-actions" style="display:grid;">
<a href="/series/<?= $ser['id'] ?>/edit" class="compact-button primary-btn" role="button">
✏️ Управление
</a>
<a href="/series/<?= $ser['id'] ?>/view" class="compact-button secondary-btn" target="_blank" role="button">
👁️ Публично
</a>
<form method="post" action="/series/<?= $ser['id'] ?>/delete"
onsubmit="return confirm('Удалить серию? Книги останутся, но будут удалены из серии.')">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<button type="submit" class="compact-button red-btn">🗑️ Удалить</button>
</form>
</div>
</article>
<?php endforeach; ?>
<div class="container">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h2">Мои серии книг</h1>
<a href="/series/create" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> Создать серию
</a>
</div>
<?php endif; ?>
<?php if (empty($series)): ?>
<div class="text-center py-5">
<div class="mb-4">
<i class="bi bi-collection fs-1 text-muted"></i>
</div>
<h3 class="h4 text-muted">Пока нет серий</h3>
<p class="text-muted mb-4">
Создайте свою первую серию, чтобы организовать книги в циклы и сериалы.
</p>
<div class="d-flex gap-2 justify-content-center">
<a href="/series/create" class="btn btn-primary">Создать серию</a>
<a href="/books" class="btn btn-outline-secondary">Перейти к книгам</a>
</div>
</div>
<?php else: ?>
<div class="row g-4">
<?php foreach ($series as $ser): ?>
<div class="col-lg-6">
<div class="card h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<h5 class="card-title">
<a href="/series/<?= $ser['id'] ?>/edit" class="text-decoration-none"><?= e($ser['title']) ?></a>
</h5>
<span class="badge bg-primary"><?= $ser['book_count'] ?? 0 ?> книг</span>
</div>
<div class="text-muted small mb-3">
Создана <?= date('d.m.Y', strtotime($ser['created_at'])) ?>
<?php if ($ser['updated_at'] != $ser['created_at']): ?>
• Обновлена <?= date('d.m.Y', strtotime($ser['updated_at'])) ?>
<?php endif; ?>
</div>
<?php if (!empty($ser['description'])): ?>
<p class="card-text"><?= e($ser['description']) ?></p>
<?php endif; ?>
<div class="row text-center mb-3">
<div class="col-4">
<div class="border-end">
<div class="fw-bold text-primary"><?= $ser['book_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($ser['total_words'] ?? 0) ?></div>
<small class="text-muted">слов</small>
</div>
</div>
<div class="col-4">
<div>
<div class="fw-bold text-info">
<?= $ser['book_count'] > 0 ? number_format(round($ser['total_words'] / $ser['book_count'])) : 0 ?>
</div>
<small class="text-muted">слов/книга</small>
</div>
</div>
</div>
<div class="d-grid gap-2">
<a href="/series/<?= $ser['id'] ?>/edit" class="btn btn-outline-primary btn-sm">
<i class="bi bi-pencil"></i> Управление
</a>
<a href="/series/<?= $ser['id'] ?>/view" class="btn btn-outline-success btn-sm" target="_blank">
<i class="bi bi-eye"></i> Публично
</a>
<form method="post" action="/series/<?= $ser['id'] ?>/delete"
onsubmit="return confirm('Удалить серию? Книги останутся, но будут удалены из серии.')">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<button type="submit" class="btn btn-outline-danger btn-sm">
<i class="bi bi-trash"></i> Удалить
</button>
</form>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<?php
include 'views/layouts/footer.php';
+116 -103
View File
@@ -3,119 +3,132 @@
include 'views/layouts/header.php';
?>
<div class="container">
<article style="max-width: 800px; margin: 0 auto;">
<header style="text-align: center; margin-bottom: 2rem; border-bottom: 2px solid var(--muted-border-color); padding-bottom: 1rem;">
<h1 style="margin-bottom: 0.5rem;"><?= e($series['title']) ?></h1>
<p style="color: var(--muted-color); font-style: italic; margin-bottom: 0.5rem;">
Серия книг от
<a href="<?= SITE_URL ?>/author/<?= $author['id'] ?>"><?= e($author['display_name'] ?: $author['username']) ?></a>
</p>
<?php if ($series['description']): ?>
<div style="background: var(--card-background-color); padding: 1rem; border-radius: 5px; margin: 1rem 0; text-align: left;">
<?= e($series['description']) ?>
</div>
<?php endif; ?>
<div style="display: flex; justify-content: center; gap: 1rem; flex-wrap: wrap; font-size: 0.9em; color: var(--muted-color);">
<span>Книг: <?= count($books) ?></span>
<span>Глав: <?= $total_chapters ?></span>
<span>Слов: <?= $total_words ?></span>
</div>
</header>
<?php if (empty($books)): ?>
<div style="text-align: center; padding: 3rem; background: var(--card-background-color); border-radius: 5px;">
<h3>В этой серии пока нет опубликованных книг</h3>
<p>Автор еще не опубликовал книги из этой серии</p>
</div>
<?php else: ?>
<div class="series-books">
<h2 style="text-align: center; margin-bottom: 2rem;">Книги серии</h2>
<?php foreach ($books as $book): ?>
<article style="display: flex; gap: 1rem; align-items: flex-start; margin-bottom: 2rem; padding: 1rem; background: var(--card-background-color); border-radius: 8px;">
<?php if ($book['cover_image']): ?>
<div style="flex-shrink: 0;">
<img src="<?= COVERS_URL . e($book['cover_image']) ?>"
alt="<?= e($book['title']) ?>"
style="max-width: 120px; height: auto; border-radius: 4px; border: 1px solid var(--border-color);"
onerror="this.style.display='none'">
</div>
<?php else: ?>
<div style="flex-shrink: 0;">
<div class="cover-placeholder" style="width: 120px; height: 160px; background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); border-radius: 4px; display: flex; align-items: center; justify-content: center; color: white; font-size: 2rem;">
📚
<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-5">
<h1 class="display-5 mb-3"><?= e($series['title']) ?></h1>
<p class="lead text-muted mb-3">
Серия книг от
<a href="<?= SITE_URL ?>/author/<?= $author['id'] ?>" class="text-decoration-none">
<?= e($author['display_name'] ?: $author['username']) ?>
</a>
</p>
<?php if ($series['description']): ?>
<div class="bg-light p-4 rounded mb-4">
<?= e($series['description']) ?>
</div>
<?php endif; ?>
<div class="d-flex justify-content-center gap-4 flex-wrap">
<div class="text-center">
<div class="h4 text-primary mb-0"><?= count($books) ?></div>
<small class="text-muted">Книг</small>
</div>
<div class="text-center">
<div class="h4 text-success mb-0"><?= $total_chapters ?></div>
<small class="text-muted">Глав</small>
</div>
<div class="text-center">
<div class="h4 text-warning mb-0"><?= $total_words ?></div>
<small class="text-muted">Слов</small>
</div>
</div>
<?php endif; ?>
<div style="flex: 1;">
<h3 style="margin-top: 0;">
<?php if ($book['sort_order_in_series']): ?>
<small style="color: var(--muted-color);">Книга <?= $book['sort_order_in_series'] ?></small><br>
<?php endif; ?>
<?= e($book['title']) ?>
</h3>
<?php if ($book['genre']): ?>
<p style="color: var(--muted-color); margin: 0.5rem 0;"><em><?= e($book['genre']) ?></em></p>
<?php endif; ?>
<?php if ($book['description']): ?>
<p style="margin-bottom: 1rem;"><?= nl2br(e($book['description'])) ?></p>
<?php endif; ?>
<div style="display: flex; gap: 10px; flex-wrap: wrap; align-items: center;">
<a href="<?= SITE_URL ?>/book/<?= e($book['share_token']) ?>" class="adaptive-button">
Читать
</a>
<?php
$bookModel = new Book($pdo);
$book_stats = $bookModel->getBookStats($book['id'], true);
?>
<small style="color: var(--muted-color);">
Глав: <?= $book_stats['chapter_count'] ?? 0 ?> | Слов: <?= $book_stats['total_words'] ?? 0 ?>
</small>
</div>
</div>
</article>
<?php endforeach; ?>
</div>
<?php endif; ?>
</header>
<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) ?> •
Автор: <a href="<?= SITE_URL ?>/author/<?= $author['id'] ?>"><?= e($author['display_name'] ?: $author['username']) ?></a>
</p>
</footer>
</article>
<?php if (empty($books)): ?>
<div class="text-center py-5">
<i class="bi bi-collection 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">
<h2 class="h3 text-center mb-4">
<i class="bi bi-book"></i> Книги серии
</h2>
<?php foreach ($books as $book): ?>
<div class="card mb-4">
<div class="card-body">
<div class="row align-items-start">
<?php if ($book['cover_image']): ?>
<div class="col-md-3 text-center mb-3 mb-md-0">
<img src="<?= COVERS_URL . e($book['cover_image']) ?>"
alt="<?= e($book['title']) ?>"
class="img-fluid rounded shadow"
style="max-height: 200px;"
onerror="this.style.display='none'">
</div>
<div class="col-md-9">
<?php else: ?>
<div class="col-12">
<?php endif; ?>
<div class="d-flex justify-content-between align-items-start mb-2">
<h3 class="h4">
<?php if ($book['sort_order_in_series']): ?>
<small class="text-muted">Книга <?= $book['sort_order_in_series'] ?></small><br>
<?php endif; ?>
<?= e($book['title']) ?>
</h3>
</div>
<?php if ($book['genre']): ?>
<p class="text-muted mb-2"><em><?= e($book['genre']) ?></em></p>
<?php endif; ?>
<?php if ($book['description']): ?>
<p class="mb-3"><?= nl2br(e($book['description'])) ?></p>
<?php endif; ?>
<div class="d-flex justify-content-between align-items-center">
<div>
<a href="<?= SITE_URL ?>/book/<?= e($book['share_token']) ?>" class="btn btn-primary">
<i class="bi bi-book"></i> Читать
</a>
</div>
<div class="text-end">
<?php
$book_stats = $book_model->getBookStats($book['id'], true);
?>
<small class="text-muted">
Глав: <?= $book_stats['chapter_count'] ?? 0 ?> |
Слов: <?= $book_stats['total_words'] ?? 0 ?>
</small>
</div>
</div>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<footer class="text-center mt-5 pt-4 border-top">
<p class="text-muted">
Серия создана в <?= e(APP_NAME) ?> •
Автор: <a href="<?= SITE_URL ?>/author/<?= $author['id'] ?>"><?= e($author['display_name'] ?: $author['username']) ?></a>
</p>
</footer>
</div>
</article>
</div>
</div>
</div>
<style>
.series-books article {
.card {
transition: transform 0.2s ease, box-shadow 0.2s ease;
border: 1px solid var(--border-color);
}
.series-books article:hover {
.card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
@media (max-width: 768px) {
.series-books article {
flex-direction: column;
text-align: center;
}
.series-books .book-cover {
align-self: center;
}
box-shadow: 0 4px 12px rgba(0,0,0,0.1) !important;
}
</style>
+128 -128
View File
@@ -3,139 +3,139 @@
include 'views/layouts/header.php';
?>
<h1>Мой профиль</h1>
<?php if ($message): ?>
<div class="alert <?= strpos($message, 'Ошибка') !== false ? 'alert-error' : 'alert-success' ?>">
<?= e($message) ?>
<div class="container">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h2">Мой профиль</h1>
<a href="<?= SITE_URL ?>/dashboard" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left"></i> Назад в панель
</a>
</div>
<?php endif; ?>
<div class="grid">
<article>
<h2>Основная информация</h2>
<form method="post" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<div style="margin-bottom: 1rem;">
<label for="username" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Имя пользователя (нельзя изменить)
</label>
<input type="text" id="username" value="<?= e($user['username']) ?>" disabled style="width: 100%;">
</div>
<div style="margin-bottom: 1rem;">
<label for="display_name" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Отображаемое имя *
</label>
<input type="text" id="display_name" name="display_name"
value="<?= e($user['display_name'] ?? $user['username']) ?>"
style="width: 100%;" required>
</div>
<div style="margin-bottom: 1.5rem;">
<label for="email" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Email
</label>
<input type="email" id="email" name="email"
value="<?= e($user['email'] ?? '') ?>"
style="width: 100%;">
</div>
<div style="margin-bottom: 1.5rem;">
<label for="bio" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
О себе (отображается на вашей публичной странице)
</label>
<textarea id="bio" name="bio"
placeholder="Расскажите о себе, своих интересах, стиле письма..."
rows="6"
style="width: 100%;"><?= e($user['bio'] ?? '') ?></textarea>
<small style="color: var(--muted-color);">
Поддерживается Markdown форматирование
</small>
</div>
<div class="profile-buttons">
<button type="submit" class="profile-button primary">
💾 Сохранить изменения
</button>
<a href="<?= SITE_URL ?>/dashboard" class="profile-button secondary">
↩️ Назад
</a>
</div>
</form>
</article>
<article>
<h2>Аватарка</h2>
<div style="text-align: center; margin-bottom: 1.5rem;">
<?php if (!empty($user['avatar'])): ?>
<img src="<?= AVATARS_URL . e($user['avatar']) ?>"
alt="Аватарка"
style="max-width: 200px; height: auto; border-radius: 50%; border: 3px solid var(--primary);"
onerror="this.style.display='none'">
<?php else: ?>
<div style="width: 200px; height: 200px; border-radius: 50%; background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); display: flex; align-items: center; justify-content: center; color: white; font-size: 4rem; margin: 0 auto;">
<?= mb_substr(e($user['display_name'] ?? $user['username']), 0, 1) ?>
</div>
<?php endif; ?>
<?php if ($message): ?>
<div class="alert <?= strpos($message, 'Ошибка') !== false ? 'alert-danger' : 'alert-success' ?>">
<?= e($message) ?>
</div>
<form method="post" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<div style="margin-bottom: 1rem;">
<label for="avatar" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Загрузить новую аватарку
</label>
<input type="file" id="avatar" name="avatar"
accept="image/jpeg, image/png, image/gif, image/webp"
style="height: 2.6rem;">
<small style="color: var(--muted-color);">
Разрешены: JPG, PNG, GIF, WebP. Максимальный размер: 2MB.
Рекомендуемый размер: 200×200 пикселей.
</small>
<?php if (!empty($avatar_error)): ?>
<div style="color: #d32f2f; margin-top: 0.5rem;">
❌ <?= e($avatar_error) ?>
<?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="username" class="form-label">Имя пользователя (нельзя изменить)</label>
<input type="text" class="form-control" id="username" value="<?= e($user['username']) ?>" disabled>
</div>
<div class="mb-3">
<label for="display_name" class="form-label">Отображаемое имя *</label>
<input type="text" class="form-control" id="display_name" name="display_name"
value="<?= e($user['display_name'] ?? $user['username']) ?>" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" name="email"
value="<?= e($user['email'] ?? '') ?>">
</div>
<div class="mb-4">
<label for="bio" class="form-label">О себе</label>
<textarea class="form-control" id="bio" name="bio"
placeholder="Расскажите о себе, своих интересах, стиле письма..."
rows="6"><?= e($user['bio'] ?? '') ?></textarea>
<div class="form-text">
Отображается на вашей публичной странице. Поддерживается Markdown форматирование.
</div>
</div>
<button type="submit" class="btn btn-primary">
<i class="bi bi-check-circle"></i> Сохранить изменения
</button>
</form>
</div>
</div>
</div>
<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 text-center">
<?php if (!empty($user['avatar'])): ?>
<img src="<?= AVATARS_URL . e($user['avatar']) ?>"
alt="Аватарка"
class="rounded-circle mb-3 border border-3 border-primary"
style="width: 150px; height: 150px; object-fit: cover;"
onerror="this.style.display='none'">
<?php else: ?>
<div class="rounded-circle bg-primary bg-gradient d-flex align-items-center justify-content-center mx-auto mb-3"
style="width: 150px; height: 150px;">
<span class="text-white fw-bold fs-2">
<?= mb_substr(e($user['display_name'] ?? $user['username']), 0, 1) ?>
</span>
</div>
<?php endif; ?>
<form method="post" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<div class="mb-3">
<label for="avatar" class="form-label">Загрузить новую аватарку</label>
<input type="file" class="form-control" id="avatar" name="avatar"
accept="image/jpeg, image/png, image/gif, image/webp">
<div class="form-text">
Разрешены: JPG, PNG, GIF, WebP. Максимальный размер: 2MB.
</div>
<?php if (!empty($avatar_error)): ?>
<div class="alert alert-danger mt-2">
<i class="bi bi-exclamation-triangle"></i> <?= e($avatar_error) ?>
</div>
<?php endif; ?>
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">
<i class="bi bi-upload"></i> Загрузить аватарку
</button>
<?php if (!empty($user['avatar'])): ?>
<button type="submit" name="delete_avatar" value="1" class="btn btn-outline-danger">
<i class="bi bi-trash"></i> Удалить аватарку
</button>
<?php endif; ?>
</div>
</form>
</div>
</div>
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Информация об аккаунте</h5>
</div>
<div class="card-body">
<div class="mb-3">
<a href="<?= SITE_URL ?>/author/<?= $_SESSION['user_id'] ?>" target="_blank" class="btn btn-outline-primary w-100">
<i class="bi bi-eye"></i> Посмотреть публичную страницу
</a>
</div>
<?php endif; ?>
<p><strong>Дата регистрации:</strong><br>
<small class="text-muted"><?= date('d.m.Y H:i', strtotime($user['created_at'])) ?></small></p>
<?php if ($user['last_login']): ?>
<p><strong>Последний вход:</strong><br>
<small class="text-muted"><?= date('d.m.Y H:i', strtotime($user['last_login'])) ?></small></p>
<?php endif; ?>
</div>
</div>
<div style="display: flex; gap: 10px;">
<button type="submit" class="contrast" style="flex: 1;">
📤 Загрузить аватарку
</button>
<?php if (!empty($user['avatar'])): ?>
<button type="submit" name="delete_avatar" value="1" class="secondary" style="flex: 1; background: #ff4444; border-color: #ff4444; color: white;">
🗑️ Удалить аватарку
</button>
<?php endif; ?>
</div>
</form>
<?php if (!empty($user['avatar'])): ?>
<div style="margin-top: 1rem; padding: 1rem; background: var(--card-background-color); border-radius: 5px;">
<p style="margin: 0; font-size: 0.9em; color: var(--muted-color);">
<strong>Примечание:</strong> Аватарка отображается на вашей публичной странице автора
</p>
</div>
<?php endif; ?>
</article>
</div>
</div>
</div>
<article>
<h3>Информация об аккаунте</h3>
<p><a href="<?= SITE_URL ?>/author/<?= $_SESSION['user_id'] ?>" target="_blank" class="adaptive-button secondary">
👁️ Посмотреть мою публичную страницу
</a></p>
<p><strong>Дата регистрации:</strong> <?= date('d.m.Y H:i', strtotime($user['created_at'])) ?></p>
<?php if ($user['last_login']): ?>
<p><strong>Последний вход:</strong> <?= date('d.m.Y H:i', strtotime($user['last_login'])) ?></p>
<?php endif; ?>
</article>
<?php include 'views/layouts/footer.php'; ?>
+116 -132
View File
@@ -3,153 +3,137 @@
include 'views/layouts/header.php';
?>
<div class="container" style="width:100%; margin-left: 0em; margin-right: 0em auto;">
<article>
<header style="text-align: center; margin-bottom: 2rem; border-bottom: 2px solid var(--muted-border-color); padding-bottom: 1rem;">
<!-- Аватарка автора -->
<div style="margin-bottom: 1rem;">
<?php if (!empty($user['avatar'])): ?>
<img src="<?= AVATARS_URL . e($user['avatar']) ?>"
alt="<?= e($user['display_name'] ?: $user['username']) ?>"
style="width: 150px; height: 150px; border-radius: 50%; border: 3px solid var(--primary); object-fit: cover;"
onerror="this.style.display='none'">
<?php else: ?>
<div style="width: 150px; height: 150px; border-radius: 50%; background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); display: flex; align-items: center; justify-content: center; color: white; font-size: 3rem; margin: 0 auto;">
<?= mb_substr(e($user['display_name'] ?: $user['username']), 0, 1) ?>
</div>
<?php endif; ?>
</div>
<h1 style="margin-bottom: 0.5rem;"><?= e($user['display_name'] ?: $user['username']) ?></h1>
<!-- Биография автора -->
<?php if (!empty($user['bio'])): ?>
<div style="background: var(--card-background-color); padding: 1.5rem; border-radius: 8px; margin: 1rem 0; text-align: left;">
<?= e($user['bio']) ?>
</div>
<?php endif; ?>
<!-- Статистика автора -->
<div style="display: flex; justify-content: center; gap: 2rem; flex-wrap: wrap; font-size: 0.9em; color: var(--muted-color);">
<div style="text-align: center;">
<div style="font-size: 1.5em; font-weight: bold; color: var(--primary);"><?= $total_books ?></div>
<div>Книг</div>
</div>
<div style="text-align: center;">
<div style="font-size: 1.5em; font-weight: bold; color: var(--success);"><?= $total_chapters ?></div>
<div>Глав</div>
</div>
<div style="text-align: center;">
<div style="font-size: 1.5em; font-weight: bold; color: var(--warning);"><?= $total_words ?></div>
<div>Слов</div>
</div>
</div>
</header>
<h2 style="text-align: center; margin-bottom: 2rem;">Публикации автора</h2>
<?php if (empty($books)): ?>
<div style="text-align: center; padding: 3rem; background: var(--card-background-color); border-radius: 5px;">
<h3>У этого автора пока нет опубликованных книг</h3>
<p>Следите за обновлениями, скоро здесь появятся новые произведения!</p>
</div>
<?php else: ?>
<div class="author-books">
<?php foreach ($books as $book): ?>
<article style="display: flex; gap: 1rem; align-items: flex-start; margin-bottom: 2rem; padding: 1.5rem; background: var(--card-background-color); border-radius: 8px;">
<?php if ($book['cover_image']): ?>
<div style="flex-shrink: 0;">
<img src="<?= COVERS_URL . e($book['cover_image']) ?>"
alt="<?= e($book['title']) ?>"
style="max-width: 120px; height: auto; border-radius: 4px; border: 1px solid var(--border-color);"
<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-5">
<!-- Аватарка автора -->
<div class="mb-4">
<?php if (!empty($user['avatar'])): ?>
<img src="<?= AVATARS_URL . e($user['avatar']) ?>"
alt="<?= e($user['display_name'] ?: $user['username']) ?>"
class="rounded-circle border border-4 border-primary"
style="width: 150px; height: 150px; object-fit: cover;"
onerror="this.style.display='none'">
</div>
<?php else: ?>
<div style="flex-shrink: 0;">
<div class="cover-placeholder" style="width: 120px; height: 160px; background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); border-radius: 4px; display: flex; align-items: center; justify-content: center; color: white; font-size: 2rem;">
📚
<?php else: ?>
<div class="rounded-circle bg-primary bg-gradient d-flex align-items-center justify-content-center mx-auto"
style="width: 150px; height: 150px;">
<span class="text-white fw-bold display-6">
<?= mb_substr(e($user['display_name'] ?: $user['username']), 0, 1) ?>
</span>
</div>
<?php endif; ?>
</div>
<h1 class="display-5 mb-2"><?= e($user['display_name'] ?: $user['username']) ?></h1>
<!-- Биография автора -->
<?php if (!empty($user['bio'])): ?>
<div class="bg-light p-4 rounded mb-4">
<?= e($user['bio']) ?>
</div>
<?php endif; ?>
<div style="flex: 1;">
<h3 style="margin-top: 0;"><?= e($book['title']) ?></h3>
<?php if ($book['genre']): ?>
<p style="color: var(--muted-color); margin: 0.5rem 0;"><em><?= e($book['genre']) ?></em></p>
<?php endif; ?>
<?php if ($book['description']): ?>
<p style="margin-bottom: 1rem;"><?= nl2br(e($book['description'])) ?></p>
<?php endif; ?>
<?php
$book_stats = $bookModel->getBookStats($book['id'], true);
$chapter_count = $book_stats['chapter_count'] ?? 0;
$word_count = $book_stats['total_words'] ?? 0;
?>
<div style="display: flex; gap: 10px; flex-wrap: wrap; align-items: center;">
<a href="<?= SITE_URL ?>/book/<?= e($book['share_token']) ?>" class="adaptive-button">
Читать книгу
</a>
<small style="color: var(--muted-color);">
Глав: <?= $chapter_count ?> | Слов: <?= $word_count ?>
</small>
<!-- Статистика автора -->
<div class="d-flex justify-content-center gap-4 flex-wrap">
<div class="text-center">
<div class="h3 text-primary mb-0"><?= $total_books ?></div>
<small class="text-muted">Книг</small>
</div>
<div class="text-center">
<div class="h3 text-success mb-0"><?= $total_chapters ?></div>
<small class="text-muted">Глав</small>
</div>
<div class="text-center">
<div class="h3 text-warning mb-0"><?= $total_words ?></div>
<small class="text-muted">Слов</small>
</div>
</div>
</article>
<?php endforeach; ?>
</div>
<?php endif; ?>
</header>
<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>
<h2 class="h3 text-center mb-4">Публикации автора</h2>
<?php if (empty($books)): ?>
<div class="text-center py-5">
<i class="bi bi-journal-bookmark fs-1 text-muted"></i>
<h3 class="h4 text-muted mt-3">У этого автора пока нет опубликованных книг</h3>
<p class="text-muted">Следите за обновлениями, скоро здесь появятся новые произведения!</p>
</div>
<?php else: ?>
<div class="row g-4">
<?php foreach ($books as $book): ?>
<div class="col-md-6">
<div class="card h-100">
<div class="card-body">
<div class="d-flex align-items-start mb-3">
<?php if ($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 else: ?>
<div class="bg-primary bg-gradient rounded d-flex align-items-center justify-content-center me-3"
style="width: 60px; height: 80px;">
<i class="bi bi-book text-white"></i>
</div>
<?php endif; ?>
<div class="flex-grow-1">
<h5 class="card-title"><?= e($book['title']) ?></h5>
<?php if ($book['genre']): ?>
<p class="text-muted small mb-1"><em><?= e($book['genre']) ?></em></p>
<?php endif; ?>
</div>
</div>
<?php if ($book['description']): ?>
<p class="card-text text-muted small mb-3">
<?= nl2br(e($book['description'])) ?>
</p>
<?php endif; ?>
<?php
$book_stats = $book_model->getBookStats($book['id'], true);
$chapter_count = $book_stats['chapter_count'] ?? 0;
$word_count = $book_stats['total_words'] ?? 0;
?>
<div class="d-flex justify-content-between align-items-center">
<a href="<?= SITE_URL ?>/book/<?= e($book['share_token']) ?>" class="btn btn-primary btn-sm">
<i class="bi bi-book"></i> Читать книгу
</a>
<small class="text-muted">
Глав: <?= $chapter_count ?> | Слов: <?= $word_count ?>
</small>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<footer class="text-center mt-5 pt-4 border-top">
<p class="text-muted">
Страница автора создана в <?= e(APP_NAME) ?> • <?= date('Y') ?>
</p>
</footer>
</div>
</article>
</div>
</div>
</div>
<style>
.author-books article {
.card {
transition: transform 0.2s ease, box-shadow 0.2s ease;
border: 1px solid var(--border-color);
}
.author-books article:hover {
.card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.cover-placeholder {
width: 120px;
height: 160px;
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 2rem;
}
@media (max-width: 768px) {
.author-books article {
flex-direction: column;
text-align: center;
}
.author-books .book-cover {
align-self: center;
}
header .author-stats {
flex-direction: column;
gap: 1rem;
}
box-shadow: 0 4px 12px rgba(0,0,0,0.1) !important;
}
</style>