Start small MVC changes
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
// views/auth/login.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; ?>
|
||||
|
||||
<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>
|
||||
|
||||
<?php include 'views/layouts/footer.php'; ?>
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
// views/auth/register.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: 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>
|
||||
|
||||
<?php include 'views/layouts/footer.php'; ?>
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
// views/books/create.php
|
||||
include 'views/layouts/header.php';
|
||||
?>
|
||||
<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($_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;">
|
||||
<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>
|
||||
<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-top: 1rem;">
|
||||
<label for="published">
|
||||
<input type="checkbox" id="published" name="published" value="1"
|
||||
<?= (!empty($_POST['published']) && $_POST['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 include 'views/layouts/footer.php'; ?>
|
||||
@@ -0,0 +1,259 @@
|
||||
<?php
|
||||
// views/books/edit.php
|
||||
include 'views/layouts/header.php';
|
||||
?>
|
||||
<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="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>
|
||||
<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>
|
||||
</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>
|
||||
<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: 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;">
|
||||
<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="compact-button secondary">
|
||||
📋 Копировать
|
||||
</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="compact-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: 2rem; padding: 1rem; 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">
|
||||
📄 PDF
|
||||
</a>
|
||||
<a href="<?= SITE_URL ?>/export/<?= $book['id'] ?>/docx" class="adaptive-button secondary" target="_blank">
|
||||
📝 DOCX
|
||||
</a>
|
||||
<a href="<?= SITE_URL ?>/export/<?= $book['id'] ?>/html" class="adaptive-button secondary" target="_blank">
|
||||
🌐 HTML
|
||||
</a>
|
||||
<a href="<?= SITE_URL ?>/export/<?= $book['id'] ?>/txt" class="adaptive-button secondary" target="_blank">
|
||||
📄 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: 3rem;">
|
||||
<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">
|
||||
📑 Все главы
|
||||
</a>
|
||||
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/chapters/create" class="adaptive-button secondary">
|
||||
✏️ Добавить главу
|
||||
</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>
|
||||
<?php else: ?>
|
||||
<div style="text-align: center; padding: 2rem; 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">
|
||||
✏️ Добавить первую главу
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div style="margin-top: 2rem; 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="button" style="background: #ff4444; border-color: #ff4444; color: white;">
|
||||
🗑️ Удалить книгу
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<?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() {
|
||||
const shareLink = document.getElementById('share-link');
|
||||
shareLink.select();
|
||||
document.execCommand('copy');
|
||||
const button = event.target;
|
||||
const originalText = '📋 Копировать';
|
||||
button.textContent = '✅ Скопировано';
|
||||
setTimeout(() => {
|
||||
button.textContent = originalText;
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php include 'views/layouts/footer.php'; ?>
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
// views/books/index.php
|
||||
include 'views/layouts/header.php';
|
||||
?>
|
||||
|
||||
<h1>Мои книги</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>
|
||||
<a href="<?= SITE_URL ?>/books/create" class="action-button primary">➕ Новая книга</a>
|
||||
</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="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="Редактировать книгу">
|
||||
✏️
|
||||
</a>
|
||||
<a href="<?= SITE_URL ?>/book/<?= $book['share_token'] ?>" class="compact-button secondary" title="Просмотреть книгу" 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>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php include 'views/layouts/footer.php'; ?>
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
// views/books/view_public.php
|
||||
include 'views/layouts/header.php';
|
||||
?>
|
||||
|
||||
<div class="container">
|
||||
<article style="max-width: 800px; 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_name) ?></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: flex; 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>
|
||||
<a href="<?= SITE_URL ?>/export/shared/<?= $book['share_token'] ?>" class="adaptive-button secondary" style="font-size: 0.8em;">
|
||||
📄 Скачать книгу
|
||||
</a>
|
||||
</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: ?>
|
||||
<h2 style="text-align: center; margin-bottom: 2rem;">Оглавление</h2>
|
||||
|
||||
<div class="chapters-list">
|
||||
<?php foreach ($chapters as $index => $chapter): ?>
|
||||
<article style="margin-bottom: 1rem; padding: 1rem; background: var(--card-background-color); border-radius: 8px;">
|
||||
<h3 style="margin-top: 0;">
|
||||
<a href="#chapter-<?= $chapter['id'] ?>" style="text-decoration: none;">
|
||||
Глава <?= $index + 1 ?>: <?= e($chapter['title']) ?>
|
||||
</a>
|
||||
</h3>
|
||||
<div style="display: flex; gap: 10px; align-items: center;">
|
||||
<small style="color: var(--muted-color);">
|
||||
Слов: <?= $chapter['word_count'] ?>
|
||||
| Обновлено: <?= date('d.m.Y', strtotime($chapter['updated_at'])) ?>
|
||||
</small>
|
||||
</div>
|
||||
</article>
|
||||
<?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;">
|
||||
<?php if ($book['editor_type'] == 'markdown'): ?>
|
||||
<?= $Parsedown->text($chapter['content']) ?>
|
||||
<?php else: ?>
|
||||
<?= $chapter['content'] ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($index < count($chapters) - 1): ?>
|
||||
<div style="text-align: center; margin-top: 2rem;">
|
||||
<a href="#chapter-<?= $chapters[$index + 1]['id'] ?>" class="button">
|
||||
📖 Следующая глава
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</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>
|
||||
</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;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.chapter-content .dialogue {
|
||||
margin-left: 2rem;
|
||||
font-style: italic;
|
||||
color: #2c5aa0;
|
||||
}
|
||||
|
||||
.chapter-content blockquote {
|
||||
border-left: 4px solid var(--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);
|
||||
padding: 1rem;
|
||||
border-radius: 5px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.chapter-content ul, .chapter-content ol {
|
||||
margin-bottom: 1rem;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<?php include 'views/layouts/footer.php'; ?>
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
// views/chapters/create.php
|
||||
include 'views/layouts/header.php';
|
||||
?>
|
||||
|
||||
<h1>Новая глава для: <?= e($book['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($_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>
|
||||
|
||||
<?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; ?>
|
||||
|
||||
<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>
|
||||
</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>
|
||||
|
||||
<script>
|
||||
function previewChapter() {
|
||||
const form = document.getElementById('chapter-form');
|
||||
const formData = new FormData(form);
|
||||
|
||||
// Создаем временную форму для предпросмотра
|
||||
const tempForm = document.createElement('form');
|
||||
tempForm.method = 'POST';
|
||||
tempForm.action = '<?= SITE_URL ?>/chapters/preview';
|
||||
tempForm.target = '_blank';
|
||||
tempForm.style.display = 'none';
|
||||
|
||||
// Добавляем CSRF токен
|
||||
const csrfInput = document.createElement('input');
|
||||
csrfInput.name = 'csrf_token';
|
||||
csrfInput.value = '<?= generate_csrf_token() ?>';
|
||||
tempForm.appendChild(csrfInput);
|
||||
|
||||
// Добавляем контент
|
||||
const contentInput = document.createElement('input');
|
||||
contentInput.name = 'content';
|
||||
contentInput.value = document.getElementById('content').value;
|
||||
tempForm.appendChild(contentInput);
|
||||
|
||||
// Добавляем заголовок
|
||||
const titleInput = document.createElement('input');
|
||||
titleInput.name = 'title';
|
||||
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);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script src="/assets/js/markdown-editor.js"></script>
|
||||
<script src="/assets/js/autosave.js"></script>
|
||||
<?php include 'views/layouts/footer.php'; ?>
|
||||
@@ -0,0 +1,123 @@
|
||||
<?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>
|
||||
|
||||
<?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>
|
||||
|
||||
<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;">
|
||||
Статус главы
|
||||
</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">
|
||||
❌ Отмена
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function previewChapter() {
|
||||
const form = document.getElementById('chapter-form');
|
||||
const formData = new FormData(form);
|
||||
|
||||
const tempForm = document.createElement('form');
|
||||
tempForm.method = 'POST';
|
||||
tempForm.action = '<?= SITE_URL ?>/chapters/preview';
|
||||
tempForm.target = '_blank';
|
||||
tempForm.style.display = 'none';
|
||||
|
||||
const csrfInput = document.createElement('input');
|
||||
csrfInput.name = 'csrf_token';
|
||||
csrfInput.value = '<?= generate_csrf_token() ?>';
|
||||
tempForm.appendChild(csrfInput);
|
||||
|
||||
const contentInput = document.createElement('input');
|
||||
contentInput.name = 'content';
|
||||
contentInput.value = document.getElementById('content').value;
|
||||
tempForm.appendChild(contentInput);
|
||||
|
||||
const titleInput = document.createElement('input');
|
||||
titleInput.name = 'title';
|
||||
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);
|
||||
}
|
||||
</script>
|
||||
<script src="/assets/js/markdown-editor.js"></script>
|
||||
<script src="/assets/js/autosave.js"></script>
|
||||
<?php include 'views/layouts/footer.php'; ?>
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
// views/chapters/index.php
|
||||
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;">
|
||||
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/chapters/create" class="adaptive-button">➕ Новая глава</a>
|
||||
<a href="<?= SITE_URL ?>/books/<?= $book['id'] ?>/edit" class="adaptive-button secondary">✏️ Редактировать книгу</a>
|
||||
<a href="<?= SITE_URL ?>/book/<?= $book['share_token'] ?>" class="adaptive-button secondary" target="_blank">👁️ Просмотреть книгу</a>
|
||||
<a href="<?= SITE_URL ?>/books" class="adaptive-button secondary">📚 Все книги</a>
|
||||
</div>
|
||||
</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['description']): ?>
|
||||
<br><small style="color: var(--muted-color);"><?= e(mb_strimwidth($chapter['description'], 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="Редактировать">
|
||||
✏️
|
||||
</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'; ?>
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
// views/chapters/preview.php
|
||||
include 'views/layouts/header.php';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<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">
|
||||
<style>
|
||||
body {
|
||||
padding: 20px;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
line-height: 1.6;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
}
|
||||
.content {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
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 {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
code {
|
||||
background: var(--card-background-color);
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
}
|
||||
pre {
|
||||
background: var(--card-background-color);
|
||||
padding: 1rem;
|
||||
border-radius: 5px;
|
||||
overflow-x: auto;
|
||||
border-left: 4px solid var(--primary);
|
||||
}
|
||||
pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
blockquote {
|
||||
border-left: 4px solid var(--border-color);
|
||||
padding-left: 1rem;
|
||||
margin-left: 0;
|
||||
color: var(--muted-color);
|
||||
font-style: italic;
|
||||
}
|
||||
strong { font-weight: bold; }
|
||||
em { font-style: italic; }
|
||||
u { text-decoration: underline; }
|
||||
del { text-decoration: line-through; }
|
||||
.dialogue {
|
||||
margin-left: 2rem;
|
||||
font-style: italic;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
table, th, td {
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
th, td {
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
}
|
||||
th {
|
||||
background: var(--card-background-color);
|
||||
}
|
||||
</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') ?> | Markdown Preview</small>
|
||||
<br>
|
||||
<a href="javascript:window.close()" class="button secondary">Закрыть</a>
|
||||
<a href="javascript:window.print()" class="button">Печать</a>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
// views/dashboard/index.php
|
||||
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>
|
||||
<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>
|
||||
<h3 style="margin-bottom: 0.5rem;">
|
||||
<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);"><em><?= e($book['genre']) ?></em></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>
|
||||
<?php else: ?>
|
||||
<article>
|
||||
<p>У вас пока нет серий.</p>
|
||||
<a href="<?= SITE_URL ?>/series/create" class="button">Создать первую серию</a>
|
||||
</article>
|
||||
<?php endif; ?>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'views/layouts/footer.php'; ?>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
// views/errors/404.php
|
||||
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>
|
||||
</div>
|
||||
|
||||
<?php include 'views/layouts/footer.php'; ?>
|
||||
@@ -1,7 +0,0 @@
|
||||
<?php
|
||||
// views/footer.php
|
||||
?>
|
||||
</main>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,64 +0,0 @@
|
||||
<?php
|
||||
// views/header.php
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= e(APP_NAME) ?> - <?= e($page_title ?? 'Платформа для писателей') ?></title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1.5.10/css/pico.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/style.css">
|
||||
<link rel="stylesheet" href="/assets/css/foundation-icons.css" />
|
||||
</head>
|
||||
<body>
|
||||
<nav class="container-fluid black">
|
||||
<ul>
|
||||
<li><strong><a href="/" style="text-decoration: none;"><?= e(APP_NAME) ?></a></strong></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<?php if (is_logged_in()): ?>
|
||||
<li><a href="/dashboard.php">📊 Панель</a></li>
|
||||
<li><a href="/series.php">📚 Мои серии</a></li>
|
||||
<li><a href="/books.php">📚 Мои книги</a></li>
|
||||
<li>
|
||||
<details role="list" dir="rtl">
|
||||
<summary aria-haspopup="listbox" role="link" style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<?php if (!empty($_SESSION['avatar'])): ?>
|
||||
<img src="<?= AVATARS_URL . e($_SESSION['avatar']) ?>"
|
||||
alt="Аватар"
|
||||
style="width: 32px; height: 32px; border-radius: 50%; object-fit: cover;"
|
||||
onerror="this.style.display='none'">
|
||||
<?php endif; ?>
|
||||
👤 <?= e($_SESSION['display_name']) ?>
|
||||
</summary>
|
||||
<ul role="listbox">
|
||||
<li><a href="/profile.php">Настройки профиля</a></li>
|
||||
<li><a href="/author.php?id=<?= $_SESSION['user_id'] ?>" target="_blank">Моя публичная страница</a></li>
|
||||
<?php if ($_SESSION['user_id'] == 1): ?>
|
||||
<li><a href="/admin/users.php">👥 Пользователи</a></li>
|
||||
<?php endif; ?>
|
||||
<li><a href="/logout.php">Выйти</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
<?php else: ?>
|
||||
<li><a href="/login.php">Войти</a></li>
|
||||
<li><a href="/register.php">Регистрация</a></li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</nav>
|
||||
<main class="container">
|
||||
<?php if (isset($_SESSION['info'])): ?>
|
||||
<div class="alert alert-info">
|
||||
<?= e($_SESSION['info']) ?>
|
||||
<?php unset($_SESSION['info']); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_SESSION['warning'])): ?>
|
||||
<div class="alert alert-warning">
|
||||
<?= e($_SESSION['warning']) ?>
|
||||
<?php unset($_SESSION['warning']); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
Executable
+48
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
// views/layouts/footer.php
|
||||
?>
|
||||
</main>
|
||||
|
||||
<footer class="container" style="margin-top: 3rem; padding-top: 1rem; border-top: 1px solid var(--muted-border-color);">
|
||||
<small>
|
||||
© <?= 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>
|
||||
|
||||
<script>
|
||||
// Глобальные функции JavaScript
|
||||
function confirmAction(message) {
|
||||
return confirm(message || 'Вы уверены?');
|
||||
}
|
||||
|
||||
function copyToClipboard(text) {
|
||||
navigator.clipboard.writeText(text).then(function() {
|
||||
alert('Скопировано в буфер обмена');
|
||||
}, function(err) {
|
||||
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>
|
||||
Executable
+69
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
// views/layouts/header.php
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<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">
|
||||
<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>
|
||||
</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 ?>/logout">🚪 Выход</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
<?php else: ?>
|
||||
<li><a href="<?= SITE_URL ?>/login">🔑 Вход</a></li>
|
||||
<li><a href="<?= SITE_URL ?>/register">📝 Регистрация</a></li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<main class="container">
|
||||
<?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; ?>
|
||||
|
||||
<?php if (isset($_SESSION['warning'])): ?>
|
||||
<div class="alert alert-warning">
|
||||
<?= e($_SESSION['warning']) ?>
|
||||
<?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; ?>
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
// views/series/create.php
|
||||
include 'views/layouts/header.php';
|
||||
?>
|
||||
|
||||
<h1>Создание новой серии</h1>
|
||||
|
||||
<?php if (isset($error) && $error): ?>
|
||||
<div class="alert alert-error">
|
||||
<?= 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 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>
|
||||
|
||||
<?php include 'views/layouts/footer.php'; ?>
|
||||
@@ -0,0 +1,126 @@
|
||||
<?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; ?>
|
||||
|
||||
<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>
|
||||
</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>
|
||||
|
||||
<?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; ?>
|
||||
</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; ?>
|
||||
|
||||
<?php include 'views/layouts/footer.php'; ?>
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
// views/series/view_public.php
|
||||
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;">
|
||||
<?= $Parsedown->text($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>
|
||||
</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; ?>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.series-books article {
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.series-books article: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;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<?php include 'views/layouts/footer.php'; ?>
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
// views/user/profile.php
|
||||
include 'views/layouts/header.php';
|
||||
?>
|
||||
|
||||
<h1>Мой профиль</h1>
|
||||
|
||||
<?php if ($message): ?>
|
||||
<div class="alert <?= strpos($message, 'Ошибка') !== false ? 'alert-error' : 'alert-success' ?>">
|
||||
<?= e($message) ?>
|
||||
</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; ?>
|
||||
</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) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</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>
|
||||
|
||||
<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'; ?>
|
||||
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
// views/user/view_public.php
|
||||
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;">
|
||||
<!-- Аватарка автора -->
|
||||
<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;">
|
||||
<?= $Parsedown->text($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);"
|
||||
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>
|
||||
</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>
|
||||
</div>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?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>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.author-books article {
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.author-books article: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;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<?php include 'views/layouts/footer.php'; ?>
|
||||
Reference in New Issue
Block a user