continue
This commit is contained in:
Executable
+88
@@ -0,0 +1,88 @@
|
||||
<?php include 'views/layouts/header.php'; ?>
|
||||
|
||||
<div class="container">
|
||||
<h1>Добавление пользователя</h1>
|
||||
|
||||
<?php if (isset($error) && $error): ?>
|
||||
<div class="alert alert-error">
|
||||
<?= e($error) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($success) && $success): ?>
|
||||
<div class="alert alert-success">
|
||||
<?= 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>
|
||||
<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>
|
||||
|
||||
<?php include 'views/layouts/footer.php'; ?>
|
||||
Executable
+96
@@ -0,0 +1,96 @@
|
||||
<?php include 'views/layouts/header.php'; ?>
|
||||
|
||||
<div class="container">
|
||||
<h1>Управление пользователями</h1>
|
||||
|
||||
<?php if (isset($_SESSION['success'])): ?>
|
||||
<div class="alert alert-success">
|
||||
<?= e($_SESSION['success']) ?>
|
||||
<?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; ?>
|
||||
|
||||
<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>
|
||||
<?php else: ?>
|
||||
<div style="overflow-x: auto;">
|
||||
<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>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php include 'views/layouts/footer.php'; ?>
|
||||
Regular → Executable
Regular → Executable
Regular → Executable
+28
-7
@@ -3,6 +3,23 @@
|
||||
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;">
|
||||
@@ -21,13 +38,7 @@ include 'views/layouts/header.php';
|
||||
value="<?= e($_POST['genre'] ?? '') ?>"
|
||||
placeholder="Например: Фантастика, Роман, Детектив..."
|
||||
style="width: 100%; margin-bottom: 1.5rem;">
|
||||
<label for="editor_type" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
|
||||
Режим редактора
|
||||
</label>
|
||||
<select id="editor_type" name="editor_type" style="width: 100%; margin-bottom: 1.5rem;">
|
||||
<option value="markdown" <?= ($_POST['editor_type'] ?? 'markdown') == 'markdown' ? 'selected' : '' ?>>Markdown редактор</option>
|
||||
<option value="html" <?= ($_POST['editor_type'] ?? '') == 'html' ? 'selected' : '' ?>>HTML редактор (TinyMCE)</option>
|
||||
</select>
|
||||
|
||||
<label for="series_id" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
|
||||
Серия
|
||||
</label>
|
||||
@@ -46,6 +57,16 @@ include 'views/layouts/header.php';
|
||||
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"
|
||||
|
||||
Regular → Executable
+7
-36
@@ -2,6 +2,12 @@
|
||||
// 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>
|
||||
<?php endif; ?>
|
||||
<h1>Редактирование книги</h1>
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
|
||||
@@ -21,19 +27,6 @@ include 'views/layouts/header.php';
|
||||
value="<?= e($book['genre'] ?? '') ?>"
|
||||
placeholder="Например: Фантастика, Роман, Детектив..."
|
||||
style="width: 100%; margin-bottom: 1.5rem;">
|
||||
<label for="editor_type" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
|
||||
Режим редактора
|
||||
</label>
|
||||
<select id="editor_type" name="editor_type" style="width: 100%; margin-bottom: 1.5rem;" onchange="showEditorWarning(this)">
|
||||
<?php foreach ($editor_types as $type => $label): ?>
|
||||
<option value="<?= e($type) ?>" <?= ($book['editor_type'] ?? 'markdown') == $type ? 'selected' : '' ?>>
|
||||
<?= e($label) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<div id="editor_warning" style="display: none; background: #fff3cd; border: 1px solid #ffeaa7; padding: 10px; border-radius: 4px; margin-bottom: 1rem;">
|
||||
<strong>Внимание:</strong> При смене редактора содержимое всех глав будет автоматически сконвертировано в новый формат.
|
||||
</div>
|
||||
<label for="series_id" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
|
||||
Серия
|
||||
</label>
|
||||
@@ -111,15 +104,6 @@ include 'views/layouts/header.php';
|
||||
</form>
|
||||
|
||||
<?php if ($book): ?>
|
||||
<div style="margin-top: 2rem;">
|
||||
<form method="post" action="<?= SITE_URL ?>/books/<?= $book['id'] ?>/normalize" onsubmit="return confirm('Нормализовать контент всех глав книги? Это действие нельзя отменить.')">
|
||||
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
|
||||
<button type="submit" class="button secondary">🔄 Нормализовать контент глав</button>
|
||||
<p style="margin-top: 0.5rem; font-size: 0.8em; color: var(--muted-color);">
|
||||
Если контент глав отображается неправильно после смены редактора, можно нормализовать его структуру.
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
<div style="margin-top: 2rem; padding: 1rem; background: var(--card-background-color); border-radius: 5px;">
|
||||
<h3>Публичная ссылка для чтения</h3>
|
||||
<div style="display: flex; gap: 5px; align-items: center; flex-wrap: wrap;">
|
||||
@@ -224,22 +208,9 @@ include 'views/layouts/header.php';
|
||||
<?php endif; ?>
|
||||
|
||||
<script>
|
||||
function showEditorWarning(select) {
|
||||
const warning = document.getElementById('editor_warning');
|
||||
const currentEditor = '<?= $book['editor_type'] ?? 'markdown' ?>';
|
||||
if (select.value !== currentEditor) {
|
||||
warning.style.display = 'block';
|
||||
} else {
|
||||
warning.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const currentEditor = '<?= $book['editor_type'] ?? 'markdown' ?>';
|
||||
const selectedEditor = document.getElementById('editor_type').value;
|
||||
if (currentEditor !== selectedEditor) {
|
||||
document.getElementById('editor_warning').style.display = 'block';
|
||||
}
|
||||
|
||||
// Копирование ссылки для чтения
|
||||
window.copyShareLink = function() {
|
||||
|
||||
Regular → Executable
+97
-41
@@ -3,11 +3,14 @@
|
||||
include 'views/layouts/header.php';
|
||||
?>
|
||||
|
||||
<h1>Мои книги</h1>
|
||||
<h1>Мои книги <small style="color: #ccc; font-size:1rem;">(Всего книг: <?= count($books) ?>)</small></h1>
|
||||
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; flex-wrap: wrap; gap: 1rem;">
|
||||
<h2 style="margin: 0;">Всего книг: <?= count($books) ?></h2>
|
||||
|
||||
<div style="display: flex; justify-content: right; align-items: center; margin-bottom: 1rem; flex-wrap: wrap; gap: 1rem;">
|
||||
<a href="<?= SITE_URL ?>/books/create" class="action-button primary">➕ Новая книга</a>
|
||||
<?php if (!empty($books)): ?>
|
||||
<a href="#" onclick="showDeleteAllConfirmation()" class="action-button delete">🗑️ Удалить все книги</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if (empty($books)): ?>
|
||||
@@ -17,50 +20,103 @@ include 'views/layouts/header.php';
|
||||
<a href="<?= SITE_URL ?>/books/create" role="button">📖 Создать первую книгу</a>
|
||||
</article>
|
||||
<?php else: ?>
|
||||
<div class="grid">
|
||||
<div class="books-grid">
|
||||
<?php foreach ($books as $book): ?>
|
||||
<article>
|
||||
<header>
|
||||
<h3 style="margin-bottom: 0.5rem;">
|
||||
<?= e($book['title']) ?>
|
||||
<div style="float: right; display: flex; gap: 3px;">
|
||||
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/edit" class="compact-button secondary" title="Редактировать книгу">
|
||||
✏️
|
||||
<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>
|
||||
<a href="<?= SITE_URL ?>/book/<?= $book['share_token'] ?>" class="compact-button secondary" title="Просмотреть книгу" target="_blank">
|
||||
👁️
|
||||
</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 secondary-btn" target="_blank">
|
||||
👁️ Просмотр
|
||||
</a>
|
||||
</div>
|
||||
</h3>
|
||||
<?php if ($book['genre']): ?>
|
||||
<p style="margin: 0; color: var(--muted-color);"><em><?= e($book['genre']) ?></em></p>
|
||||
<?php endif; ?>
|
||||
</header>
|
||||
|
||||
<?php if ($book['description']): ?>
|
||||
<p><?= e(mb_strimwidth($book['description'], 0, 200, '...')) ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<footer>
|
||||
<div>
|
||||
<small>
|
||||
Глав: <?= $book['chapter_count'] ?> |
|
||||
Слов: <?= $book['total_words'] ?> |
|
||||
Статус: <?= $book['published'] ? '✅ Опубликована' : '📝 Черновик' ?>
|
||||
</small>
|
||||
</div>
|
||||
<div style="margin-top: 0.5rem; display: flex; gap: 5px; flex-wrap: wrap;">
|
||||
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/chapters" class="adaptive-button secondary">
|
||||
📑 Главы
|
||||
</a>
|
||||
<a href="<?= SITE_URL ?>/export/<?= $book['id'] ?>" class="adaptive-button secondary" target="_blank">
|
||||
📄 Экспорт
|
||||
</a>
|
||||
</div>
|
||||
</footer>
|
||||
</article>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Статистика внизу -->
|
||||
<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>
|
||||
<?php endif; ?>
|
||||
<?php include 'views/layouts/footer.php'; ?>
|
||||
Regular → Executable
Regular → Executable
+5
-33
@@ -27,36 +27,9 @@ include 'views/layouts/header.php';
|
||||
<label for="content" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
|
||||
Содержание главы *
|
||||
</label>
|
||||
|
||||
<?php if (($book['editor_type'] ?? 'markdown') == 'html'): ?>
|
||||
<textarea id="content" name="content" class="html-editor"
|
||||
placeholder="Начните писать вашу главу..."
|
||||
rows="20"
|
||||
style="width: 100%;"><?= e($_POST['content'] ?? '') ?></textarea>
|
||||
<?php else: ?>
|
||||
<div style="margin-bottom: 1rem; padding: 0.5rem; background: var(--card-background-color); border-radius: 5px;">
|
||||
<div style="display: flex; gap: 3px; flex-wrap: nowrap; overflow-x: auto; padding: 5px 0;">
|
||||
<button type="button" onclick="insertMarkdown('**')" class="compact-button secondary" title="Жирный текст" style="white-space: nowrap; flex-shrink: 0;">**B**</button>
|
||||
<button type="button" onclick="insertMarkdown('*')" class="compact-button secondary" title="Курсив" style="white-space: nowrap; flex-shrink: 0;">*I*</button>
|
||||
<button type="button" onclick="insertMarkdown('~~')" class="compact-button secondary" title="Зачеркнутый" style="white-space: nowrap; flex-shrink: 0;">~~S~~</button>
|
||||
<button type="button" onclick="insertMarkdown('`')" class="compact-button secondary" title="Код" style="white-space: nowrap; flex-shrink: 0;">`code`</button>
|
||||
<button type="button" onclick="insertMarkdown('\n\n- ')" class="compact-button secondary" title="Список" style="white-space: nowrap; flex-shrink: 0;">- список</button>
|
||||
<button type="button" onclick="insertMarkdown('\n\n> ')" class="compact-button secondary" title="Цитата" style="white-space: nowrap; flex-shrink: 0;">> цитата</button>
|
||||
<button type="button" onclick="insertMarkdown('\n\n# ')" class="compact-button secondary" title="Заголовок" style="white-space: nowrap; flex-shrink: 0;"># Заголовок</button>
|
||||
<button type="button" onclick="insertMarkdown('\n— ')" class="compact-button secondary" title="Диалог" style="white-space: nowrap; flex-shrink: 0;">— диалог</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<textarea id="content" name="content"
|
||||
placeholder="Начните писать вашу главу... Поддерживается Markdown разметка."
|
||||
rows="20"
|
||||
style="width: 100%; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 14px; line-height: 1.5;"><?= e($_POST['content'] ?? '') ?></textarea>
|
||||
|
||||
<div style="margin-top: 0.5rem; font-size: 0.9em; color: var(--muted-color);">
|
||||
<strong>Подсказка:</strong> Используйте Markdown для форматирования.
|
||||
<a href="https://commonmark.org/help/" target="_blank">Справка по Markdown</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<textarea id="content" name="content" class="writer-editor" style="display: none;">
|
||||
<?= e($_POST['content'] ?? '') ?>
|
||||
</textarea>
|
||||
|
||||
<div style="margin-top: 1rem;">
|
||||
<label for="status" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
|
||||
@@ -86,7 +59,7 @@ include 'views/layouts/header.php';
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<link href="/assets/css/quill_reset.css" rel="stylesheet">
|
||||
<script>
|
||||
function previewChapter() {
|
||||
const form = document.getElementById('chapter-form');
|
||||
@@ -128,7 +101,6 @@ function previewChapter() {
|
||||
document.body.removeChild(tempForm);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script src="/assets/js/markdown-editor.js"></script>
|
||||
<script src="/assets/js/editor.js"></script>
|
||||
<script src="/assets/js/autosave.js"></script>
|
||||
<?php include 'views/layouts/footer.php'; ?>
|
||||
Regular → Executable
+5
-18
@@ -27,23 +27,10 @@ include 'views/layouts/header.php';
|
||||
<label for="content" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
|
||||
Содержание главы *
|
||||
</label>
|
||||
|
||||
<?php if (($book['editor_type'] ?? 'markdown') == 'html'): ?>
|
||||
<textarea id="content" name="content" class="html-editor"
|
||||
placeholder="Начните писать вашу главу..."
|
||||
rows="20"
|
||||
style="width: 100%;"><?= e($chapter['content']) ?></textarea>
|
||||
<?php else: ?>
|
||||
<textarea id="content" name="content"
|
||||
placeholder="Начните писать вашу главу... Поддерживается Markdown разметка."
|
||||
rows="20"
|
||||
style="width: 100%; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 14px; line-height: 1.5;"><?= e($chapter['content']) ?></textarea>
|
||||
<textarea id="content" name="content" class="writer-editor" style="display: none;">
|
||||
<?= e($chapter['content'] ?? '') ?>
|
||||
</textarea>
|
||||
|
||||
<div style="margin-top: 0.5rem; font-size: 0.9em; color: var(--muted-color);">
|
||||
<strong>Подсказка:</strong> Используйте Markdown для форматирования.
|
||||
<a href="https://commonmark.org/help/" target="_blank">Справка по Markdown</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div style="margin-top: 1rem;">
|
||||
<label for="status" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
|
||||
@@ -81,7 +68,7 @@ include 'views/layouts/header.php';
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<link href="/assets/css/quill_reset.css" rel="stylesheet">
|
||||
<script>
|
||||
function previewChapter() {
|
||||
const form = document.getElementById('chapter-form');
|
||||
@@ -118,6 +105,6 @@ function previewChapter() {
|
||||
document.body.removeChild(tempForm);
|
||||
}
|
||||
</script>
|
||||
<script src="/assets/js/markdown-editor.js"></script>
|
||||
<script src="/assets/js/editor.js"></script>
|
||||
<script src="/assets/js/autosave.js"></script>
|
||||
<?php include 'views/layouts/footer.php'; ?>
|
||||
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
@@ -11,7 +11,7 @@
|
||||
<?php endif; ?>
|
||||
</small>
|
||||
</footer>
|
||||
|
||||
|
||||
<script>
|
||||
// Глобальные функции JavaScript
|
||||
function confirmAction(message) {
|
||||
@@ -25,24 +25,6 @@
|
||||
console.error('Ошибка копирования: ', err);
|
||||
});
|
||||
}
|
||||
|
||||
// Инициализация TinyMCE если есть текстовые редакторы
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const htmlEditors = document.querySelectorAll('.html-editor');
|
||||
htmlEditors.forEach(function(editor) {
|
||||
if (typeof tinymce !== 'undefined') {
|
||||
tinymce.init({
|
||||
selector: '#' + editor.id,
|
||||
plugins: 'advlist autolink lists link image charmap preview anchor',
|
||||
toolbar: 'undo redo | formatselect | bold italic | alignleft aligncenter alignright | bullist numlist outdent indent | link image',
|
||||
language: 'ru',
|
||||
height: 400,
|
||||
menubar: false,
|
||||
statusbar: false
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -9,7 +9,8 @@
|
||||
<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">
|
||||
<link rel="stylesheet" href="<?= SITE_URL ?>/assets/css/style.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/tinymce/6.8.6/tinymce.min.js" referrerpolicy="origin"></script>
|
||||
<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
|
||||
<script src="https://cdn.quilljs.com/1.3.6/quill.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="container-fluid">
|
||||
@@ -28,6 +29,10 @@
|
||||
</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>
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
Regular → Executable
Regular → Executable
+205
-113
@@ -1,126 +1,218 @@
|
||||
<?php
|
||||
// views/series/edit.php
|
||||
include 'views/layouts/header.php';
|
||||
?>
|
||||
|
||||
<h1>Редактирование серии: <?= e($series['title']) ?></h1>
|
||||
|
||||
<?php if (isset($error) && $error): ?>
|
||||
<div class="alert alert-error">
|
||||
<?= e($error) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="grid">
|
||||
<div>
|
||||
<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>
|
||||
|
||||
<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($series['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($series['description']) ?></textarea>
|
||||
<article>
|
||||
<h2>Добавить книгу в серию</h2>
|
||||
<?php
|
||||
$available_books = $bookModel->getBooksNotInSeries($_SESSION['user_id'], $series['id']);
|
||||
?>
|
||||
|
||||
<?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">Создать новую книгу</a>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
</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>
|
||||
<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">
|
||||
<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">
|
||||
<a href="/books/<?= $book['id'] ?>/edit" class="compact-button">Редактировать</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 delete-btn">Удалить</button>
|
||||
</form>
|
||||
</div>
|
||||
<input type="hidden" name="order[]" value="<?= $book['id'] ?>">
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="secondary-btn" id="save-order-btn" style="display: none;">
|
||||
Сохранить порядок
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<p>В этой серии пока нет книг. Добавьте книги с помощью формы слева.</p>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
|
||||
<?php if ($series): ?>
|
||||
<div style="margin-top: 3rem;">
|
||||
<h3>Книги в этой серии</h3>
|
||||
|
||||
<?php if (empty($books_in_series)): ?>
|
||||
<div style="text-align: center; padding: 2rem; background: var(--card-background-color); border-radius: 5px;">
|
||||
<p>В этой серии пока нет книг.</p>
|
||||
<a href="<?= SITE_URL ?>/books" class="adaptive-button">📚 Добавить книги</a>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div style="overflow-x: auto;">
|
||||
<table class="compact-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 10%;">Порядок</th>
|
||||
<th style="width: 40%;">Название книги</th>
|
||||
<th style="width: 20%;">Жанр</th>
|
||||
<th style="width: 15%;">Статус</th>
|
||||
<th style="width: 15%;">Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($books_in_series as $book): ?>
|
||||
<tr>
|
||||
<td><?= $book['sort_order_in_series'] ?></td>
|
||||
<td>
|
||||
<strong><?= e($book['title']) ?></strong>
|
||||
<?php if ($book['description']): ?>
|
||||
<br><small style="color: var(--muted-color);"><?= e(mb_strimwidth($book['description'], 0, 100, '...')) ?></small>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?= e($book['genre']) ?></td>
|
||||
<td>
|
||||
<span style="color: <?= $book['published'] ? 'green' : 'orange' ?>">
|
||||
<?= $book['published'] ? '✅ Опубликована' : '📝 Черновик' ?>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/edit" class="compact-button secondary">
|
||||
Редактировать
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
// Вычисляем общую статистику
|
||||
$total_chapters = 0;
|
||||
$total_words = 0;
|
||||
foreach ($books_in_series as $book) {
|
||||
$bookModel = new Book($pdo);
|
||||
$stats = $bookModel->getBookStats($book['id']);
|
||||
$total_chapters += $stats['chapter_count'] ?? 0;
|
||||
$total_words += $stats['total_words'] ?? 0;
|
||||
}
|
||||
?>
|
||||
|
||||
<div style="margin-top: 1rem; padding: 0.5rem; background: var(--card-background-color); border-radius: 3px;">
|
||||
<strong>Статистика серии:</strong>
|
||||
Книг: <?= count($books_in_series) ?> |
|
||||
Глав: <?= $total_chapters ?> |
|
||||
Слов: <?= $total_words ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<article>
|
||||
<h2>Статистика серии</h2>
|
||||
<div class="stats-list">
|
||||
<p><strong>Количество книг:</strong> <?= count($books_in_series) ?></p>
|
||||
<?php
|
||||
$total_words = 0;
|
||||
$total_chapters = 0;
|
||||
foreach ($books_in_series as $book) {
|
||||
$stats = $bookModel->getBookStats($book['id']);
|
||||
$total_words += $stats['total_words'] ?? 0;
|
||||
$total_chapters += $stats['chapter_count'] ?? 0;
|
||||
}
|
||||
?>
|
||||
<p><strong>Всего глав:</strong> <?= $total_chapters ?></p>
|
||||
<p><strong>Всего слов:</strong> <?= $total_words ?></p>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 2rem; text-align: center;">
|
||||
<form method="post" action="<?= SITE_URL ?>/series/<?= $series['id'] ?>/delete" style="display: inline;" onsubmit="return confirm('Вы уверены, что хотите удалить серию «<?= e($series['title']) ?>»? Книги останутся, но будут убраны из серии.');">
|
||||
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
|
||||
<button type="submit" class="button" style="background: #ff4444; border-color: #ff4444; color: white;">
|
||||
🗑️ Удалить серию
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<style>
|
||||
.books-list {
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
<?php include 'views/layouts/footer.php'; ?>
|
||||
.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;
|
||||
}
|
||||
|
||||
.book-item:hover {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.book-item.sortable-ghost {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.book-item.sortable-chosen {
|
||||
background: #e3f2fd;
|
||||
}
|
||||
|
||||
.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>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const booksList = document.querySelector('.books-list');
|
||||
const saveOrderBtn = document.getElementById('save-order-btn');
|
||||
|
||||
if (booksList) {
|
||||
const sortable = new Sortable(booksList, {
|
||||
handle: '.book-drag-handle',
|
||||
ghostClass: 'sortable-ghost',
|
||||
chosenClass: 'sortable-chosen',
|
||||
animation: 150,
|
||||
onUpdate: function() {
|
||||
saveOrderBtn.style.display = 'block';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Автосохранение порядка через 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(() => {
|
||||
document.getElementById('reorder-form').submit();
|
||||
}, 2000);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php
|
||||
include 'views/layouts/footer.php';
|
||||
?>
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
include 'views/layouts/header.php';
|
||||
?>
|
||||
|
||||
<div style="display: flex; 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">➕ Создать серию</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">Создать серию</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">
|
||||
<a href="/series/<?= $ser['id'] ?>/edit" class="compact-button primary-btn">
|
||||
✏️ Управление
|
||||
</a>
|
||||
<a href="/series/<?= $ser['id'] ?>/view" class="compact-button secondary-btn" target="_blank">
|
||||
👁️ Публично
|
||||
</a>
|
||||
<form method="post" action="/series/<?= $ser['id'] ?>/delete"
|
||||
style="display: inline;"
|
||||
onsubmit="return confirm('Удалить серию? Книги останутся, но будут удалены из серии.')">
|
||||
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
|
||||
<button type="submit" class="compact-button delete-btn">🗑️</button>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
include 'views/layouts/footer.php';
|
||||
?>
|
||||
Regular → Executable
+1
-1
@@ -14,7 +14,7 @@ include 'views/layouts/header.php';
|
||||
|
||||
<?php if ($series['description']): ?>
|
||||
<div style="background: var(--card-background-color); padding: 1rem; border-radius: 5px; margin: 1rem 0; text-align: left;">
|
||||
<?= $Parsedown->text($series['description']) ?>
|
||||
<?= e($series['description']) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
Regular → Executable
Regular → Executable
+1
-1
@@ -25,7 +25,7 @@ include 'views/layouts/header.php';
|
||||
<!-- Биография автора -->
|
||||
<?php if (!empty($user['bio'])): ?>
|
||||
<div style="background: var(--card-background-color); padding: 1.5rem; border-radius: 8px; margin: 1rem 0; text-align: left;">
|
||||
<?= $Parsedown->text($user['bio']) ?>
|
||||
<?= e($user['bio']) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user