Compare commits
2 Commits
bd59582428
...
1d00a00146
Author | SHA1 | Date | |
---|---|---|---|
1d00a00146 | |||
c3f5e9afc0 |
39
admin/api/delete-chapter.php
Normal file
39
admin/api/delete-chapter.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../includes/auth.php';
|
||||
require_once '../../includes/stories.php';
|
||||
|
||||
// Vérification de l'authentification
|
||||
if (!Auth::check()) {
|
||||
http_response_code(401);
|
||||
exit(json_encode(['error' => 'Non autorisé']));
|
||||
}
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$storyId = $input['storyId'] ?? '';
|
||||
$chapterId = $input['chapterId'] ?? '';
|
||||
|
||||
if (!$storyId || !$chapterId) {
|
||||
http_response_code(400);
|
||||
exit(json_encode(['error' => 'Paramètres manquants']));
|
||||
}
|
||||
|
||||
try {
|
||||
$story = Stories::get($storyId);
|
||||
if (!$story) {
|
||||
throw new Exception('Roman non trouvé');
|
||||
}
|
||||
|
||||
// Filtrer les chapitres pour retirer celui à supprimer
|
||||
$story['chapters'] = array_values(array_filter($story['chapters'], function($chapter) use ($chapterId) {
|
||||
return $chapter['id'] !== $chapterId;
|
||||
}));
|
||||
|
||||
// Sauvegarder les modifications
|
||||
Stories::save($story);
|
||||
|
||||
echo json_encode(['success' => true]);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
}
|
43
admin/api/get-chapter.php
Normal file
43
admin/api/get-chapter.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../includes/auth.php';
|
||||
require_once '../../includes/stories.php';
|
||||
|
||||
// Vérification de l'authentification
|
||||
if (!Auth::check()) {
|
||||
http_response_code(401);
|
||||
exit(json_encode(['error' => 'Non autorisé']));
|
||||
}
|
||||
|
||||
$storyId = $_GET['storyId'] ?? '';
|
||||
$chapterId = $_GET['chapterId'] ?? '';
|
||||
|
||||
if (!$storyId || !$chapterId) {
|
||||
http_response_code(400);
|
||||
exit(json_encode(['error' => 'Paramètres manquants']));
|
||||
}
|
||||
|
||||
try {
|
||||
$story = Stories::get($storyId);
|
||||
if (!$story) {
|
||||
throw new Exception('Roman non trouvé');
|
||||
}
|
||||
|
||||
$chapter = null;
|
||||
foreach ($story['chapters'] as $ch) {
|
||||
if ($ch['id'] === $chapterId) {
|
||||
$chapter = $ch;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$chapter) {
|
||||
throw new Exception('Chapitre non trouvé');
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($chapter);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
}
|
48
admin/api/reorder-chapters.php
Normal file
48
admin/api/reorder-chapters.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../includes/auth.php';
|
||||
require_once '../../includes/stories.php';
|
||||
|
||||
// Vérification de l'authentification
|
||||
if (!Auth::check()) {
|
||||
http_response_code(401);
|
||||
exit(json_encode(['error' => 'Non autorisé']));
|
||||
}
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$storyId = $input['storyId'] ?? '';
|
||||
$newOrder = $input['chapters'] ?? [];
|
||||
|
||||
if (!$storyId || empty($newOrder)) {
|
||||
http_response_code(400);
|
||||
exit(json_encode(['error' => 'Paramètres manquants']));
|
||||
}
|
||||
|
||||
try {
|
||||
$story = Stories::get($storyId);
|
||||
if (!$story) {
|
||||
throw new Exception('Roman non trouvé');
|
||||
}
|
||||
|
||||
// Créer un tableau temporaire pour le nouvel ordre
|
||||
$reorderedChapters = [];
|
||||
foreach ($newOrder as $item) {
|
||||
foreach ($story['chapters'] as $chapter) {
|
||||
if ($chapter['id'] === $item['id']) {
|
||||
$reorderedChapters[] = $chapter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mettre à jour l'ordre des chapitres
|
||||
$story['chapters'] = $reorderedChapters;
|
||||
|
||||
// Sauvegarder les modifications
|
||||
Stories::save($story);
|
||||
|
||||
echo json_encode(['success' => true]);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
}
|
@ -17,7 +17,7 @@ $stories = Stories::getAll();
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Administration</title>
|
||||
<link rel="stylesheet" href="../assets/css/admin.css">
|
||||
<link rel="stylesheet" href="../assets/css/main.css">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="admin-nav">
|
||||
@ -39,7 +39,10 @@ $stories = Stories::getAll();
|
||||
<img src="<?= htmlspecialchars('../' . $story['cover']) ?>" alt="" class="story-cover">
|
||||
<div class="story-info">
|
||||
<h2><?= htmlspecialchars($story['title']) ?></h2>
|
||||
<p>Dernière modification : <?= htmlspecialchars($story['updated']) ?></p>
|
||||
<p>
|
||||
<?= count($story['chapters'] ?? []) ?> chapitre<?= count($story['chapters'] ?? []) > 1 ? 's' : '' ?><br>
|
||||
Dernière modification : <?= htmlspecialchars(Stories::formatDate($story['updated'])) ?>
|
||||
</p>
|
||||
</div>
|
||||
<div class="story-actions">
|
||||
<a href="story-edit.php?id=<?= htmlspecialchars($story['id']) ?>" class="button">Modifier</a>
|
||||
@ -52,4 +55,4 @@ $stories = Stories::getAll();
|
||||
|
||||
<script src="../assets/js/admin.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
@ -22,7 +22,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Connexion - Administration</title>
|
||||
<link rel="stylesheet" href="../assets/css/admin.css">
|
||||
<link rel="stylesheet" href="../assets/css/main.css">
|
||||
</head>
|
||||
<body class="login-page">
|
||||
<div class="login-container">
|
||||
|
@ -62,7 +62,7 @@ function generateSlug($title) {
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= $story ? 'Modifier' : 'Nouveau' ?> roman - Administration</title>
|
||||
<link rel="stylesheet" href="../assets/css/admin.css">
|
||||
<link rel="stylesheet" href="../assets/css/main.css">
|
||||
<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
@ -74,7 +74,7 @@ function generateSlug($title) {
|
||||
</nav>
|
||||
|
||||
<main class="admin-main">
|
||||
<h1><?= $story ? 'Modifier' : 'Nouveau' ?> roman</h1>
|
||||
<h1><?= $story ? 'Modifier le' : 'Nouveau' ?> roman</h1>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="error-message"><?= htmlspecialchars($error) ?></div>
|
||||
@ -121,16 +121,13 @@ function generateSlug($title) {
|
||||
|
||||
<div id="chaptersList" class="chapters-list">
|
||||
<?php foreach ($story['chapters'] ?? [] as $index => $chapter): ?>
|
||||
<div class="chapter-item" data-id="<?= $chapter['id'] ?>">
|
||||
<div class="chapter-header">
|
||||
<span class="chapter-number"><?= $index + 1 ?></span>
|
||||
<input type="text" class="chapter-title"
|
||||
value="<?= htmlspecialchars($chapter['title']) ?>">
|
||||
<div class="chapter-item" data-id="<?= htmlspecialchars($chapter['id']) ?>">
|
||||
<span class="chapter-number"><?= $index + 1 ?></span>
|
||||
<h3 class="chapter-title"><?= htmlspecialchars($chapter['title']) ?></h3>
|
||||
<div class="chapter-actions">
|
||||
<button type="button" class="button edit-chapter">Éditer</button>
|
||||
<button type="button" class="button delete-chapter">Supprimer</button>
|
||||
</div>
|
||||
<div class="chapter-content">
|
||||
<div class="editor"><?= $chapter['content'] ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
@ -1,406 +0,0 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* Login Page */
|
||||
.login-page {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
background-color: #2c1810;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.login-form .form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.login-form label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.login-form input {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.login-form button {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
background-color: #8b4513;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.login-form button:hover {
|
||||
background-color: #703610;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background-color: #ffebee;
|
||||
color: #c62828;
|
||||
padding: 0.75rem;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Admin Dashboard */
|
||||
.admin-nav {
|
||||
background-color: #2c1810;
|
||||
color: white;
|
||||
padding: 1rem 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nav-brand {
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: #8b4513;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: #703610;
|
||||
}
|
||||
|
||||
.admin-main {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.stories-list {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.story-item {
|
||||
background: white;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.story-cover {
|
||||
width: 100px;
|
||||
height: 150px;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.story-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.story-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.delete-story {
|
||||
background-color: #c62828;
|
||||
}
|
||||
|
||||
.delete-story:hover {
|
||||
background-color: #b71c1c;
|
||||
}
|
||||
|
||||
/* Formulaire d'édition du roman */
|
||||
.story-form {
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #2c1810;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-group input[type="text"],
|
||||
.form-group textarea {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.form-group textarea {
|
||||
resize: vertical;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.current-cover {
|
||||
display: block;
|
||||
max-width: 200px;
|
||||
margin: 0.5rem 0;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Section des chapitres */
|
||||
.chapters-section {
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.chapters-section h2 {
|
||||
margin-top: 0;
|
||||
color: #2c1810;
|
||||
}
|
||||
|
||||
.chapters-list {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.chapter-item {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 1rem;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.chapter-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
.chapter-number {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: #8b4513;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
cursor: move;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.chapter-title {
|
||||
flex: 1;
|
||||
padding: 0.5rem;
|
||||
margin-right: 1rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.chapter-content {
|
||||
padding: 1rem;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.chapter-content .editor {
|
||||
min-height: 100px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Modal d'édition */
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
width: 90%;
|
||||
max-width: 800px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal-content h2 {
|
||||
margin-top: 0;
|
||||
color: #2c1810;
|
||||
}
|
||||
|
||||
#chapterTitle {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* Éditeur Quill personnalisé */
|
||||
.ql-container {
|
||||
min-height: 300px;
|
||||
font-size: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.ql-toolbar {
|
||||
border-radius: 4px 4px 0 0;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.ql-container {
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
|
||||
/* Boutons et actions */
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
background-color: #e8f5e9;
|
||||
color: #2e7d32;
|
||||
padding: 1rem;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Styles pour le drag & drop */
|
||||
.chapter-item.sortable-ghost {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.chapter-item.sortable-drag {
|
||||
cursor: move;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.story-form,
|
||||
.chapters-section {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.chapter-header {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.chapter-number {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.chapter-title {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
width: 95%;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.ql-container {
|
||||
min-height: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Thème personnalisé pour l'éditeur */
|
||||
.ql-snow .ql-toolbar button:hover,
|
||||
.ql-snow .ql-toolbar button.ql-active {
|
||||
color: #8b4513;
|
||||
}
|
||||
|
||||
.ql-snow .ql-toolbar button:hover .ql-stroke,
|
||||
.ql-snow .ql-toolbar button.ql-active .ql-stroke {
|
||||
stroke: #8b4513;
|
||||
}
|
||||
|
||||
.ql-snow .ql-toolbar button:hover .ql-fill,
|
||||
.ql-snow .ql-toolbar button.ql-active .ql-fill {
|
||||
fill: #8b4513;
|
||||
}
|
||||
|
||||
/* Animation de transition */
|
||||
.chapter-item {
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.chapter-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.button {
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
/* États de survol pour les éléments interactifs */
|
||||
.chapter-title:hover,
|
||||
.chapter-title:focus {
|
||||
border-color: #8b4513;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.delete-chapter {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
padding: 0.5rem 1rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.delete-chapter:hover {
|
||||
background-color: #c82333;
|
||||
}
|
255
assets/css/components.css
Normal file
255
assets/css/components.css
Normal file
@ -0,0 +1,255 @@
|
||||
/* Cartes des romans */
|
||||
.story-item {
|
||||
background: var(--bg-tertiary);
|
||||
padding: var(--spacing-lg);
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-lg);
|
||||
transition: transform var(--transition-fast), box-shadow var(--transition-fast);
|
||||
}
|
||||
|
||||
.story-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.story-cover {
|
||||
width: 200px;
|
||||
height: 120px;
|
||||
object-fit: cover;
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.current-cover {
|
||||
max-height: 300px;
|
||||
width: auto;
|
||||
display: block;
|
||||
margin-bottom: var(--spacing-sm);
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.story-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.story-info p {
|
||||
color: var(--text-secondary);
|
||||
margin: 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.story-info h2 {
|
||||
color: var(--text-primary);
|
||||
margin: 0 0 var(--spacing-sm) 0;
|
||||
}
|
||||
|
||||
/* Cartes des chapitres */
|
||||
.chapters-section {
|
||||
margin-top: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.chapters-section h2 {
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.chapters-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-md);
|
||||
margin-top: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.chapter-item {
|
||||
background: var(--bg-tertiary);
|
||||
padding: var(--spacing-lg);
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-lg);
|
||||
transition: transform var(--transition-fast), box-shadow var(--transition-fast);
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.chapter-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.chapter-item.sortable-ghost {
|
||||
opacity: 0.5;
|
||||
background: var(--accent-primary);
|
||||
}
|
||||
|
||||
.chapter-number {
|
||||
background: var(--accent-primary);
|
||||
color: var(--text-primary);
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-sm);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.chapter-title {
|
||||
flex: 1;
|
||||
font-size: 1.1rem;
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.chapter-actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.chapter-actions button {
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
}
|
||||
|
||||
.edit-chapter {
|
||||
background-color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.delete-chapter {
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.delete-chapter:hover {
|
||||
background-color: var(--error-color);
|
||||
}
|
||||
|
||||
/* Messages système */
|
||||
.error-message,
|
||||
.success-message {
|
||||
padding: var(--spacing-sm);
|
||||
border-radius: var(--radius-sm);
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background-color: var(--error-color);
|
||||
border: 1px solid #a33;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
background-color: var(--success-color);
|
||||
border: 1px solid #373;
|
||||
}
|
||||
|
||||
/* Modal */
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: var(--bg-tertiary);
|
||||
padding: var(--spacing-xl);
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--border-color);
|
||||
width: 90%;
|
||||
max-width: 800px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal-content h2 {
|
||||
margin-top: 0;
|
||||
margin-bottom: var(--spacing-lg);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
#chapterTitle {
|
||||
width: 35%;
|
||||
padding: var(--spacing-sm);
|
||||
background-color: var(--input-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text-primary);
|
||||
font-size: 1rem;
|
||||
margin-bottom: var(--spacing-md);
|
||||
transition: border-color var(--transition-fast);
|
||||
}
|
||||
|
||||
#chapterTitle:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-primary);
|
||||
box-shadow: 0 0 0 2px rgba(139, 69, 19, 0.2);
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
margin-top: var(--spacing-lg);
|
||||
display: flex;
|
||||
gap: var(--spacing-md);
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/* Animation de la modale */
|
||||
@keyframes modalFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
animation: modalFadeIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
/* Media queries */
|
||||
@media (max-width: 768px) {
|
||||
.story-cover {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.current-cover {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.modal-actions button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chapter-item {
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.chapter-actions {
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
55
assets/css/editor.css
Normal file
55
assets/css/editor.css
Normal file
@ -0,0 +1,55 @@
|
||||
/* Personnalisation de l'éditeur Quill */
|
||||
.ql-toolbar {
|
||||
background: var(--bg-secondary) !important;
|
||||
border-color: var(--border-color) !important;
|
||||
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
||||
}
|
||||
|
||||
.ql-container {
|
||||
background: var(--input-bg) !important;
|
||||
border-color: var(--border-color) !important;
|
||||
color: var(--text-primary) !important;
|
||||
border-radius: 0 0 var(--radius-sm) var(--radius-sm);
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.ql-editor {
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
/* Boutons de la barre d'outils */
|
||||
.ql-snow .ql-stroke {
|
||||
stroke: var(--text-secondary) !important;
|
||||
}
|
||||
|
||||
.ql-snow .ql-fill {
|
||||
fill: var(--text-secondary) !important;
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker {
|
||||
color: var(--text-secondary) !important;
|
||||
}
|
||||
|
||||
/* Menu déroulant */
|
||||
.ql-snow .ql-picker-options {
|
||||
background-color: var(--bg-secondary) !important;
|
||||
border-color: var(--border-color) !important;
|
||||
}
|
||||
|
||||
/* États au survol */
|
||||
.ql-snow .ql-toolbar button:hover,
|
||||
.ql-snow .ql-toolbar button.ql-active {
|
||||
.ql-stroke {
|
||||
stroke: var(--accent-primary) !important;
|
||||
}
|
||||
.ql-fill {
|
||||
fill: var(--accent-primary) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.ql-container {
|
||||
min-height: 200px;
|
||||
}
|
||||
}
|
75
assets/css/forms.css
Normal file
75
assets/css/forms.css
Normal file
@ -0,0 +1,75 @@
|
||||
/* Styles communs des formulaires */
|
||||
.form-group {
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: var(--spacing-sm);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* Inputs et textareas */
|
||||
.form-group input[type="text"],
|
||||
.form-group input[type="password"],
|
||||
.form-group textarea {
|
||||
width: 100%;
|
||||
padding: var(--spacing-sm);
|
||||
background-color: var(--input-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text-primary);
|
||||
font-size: 1rem;
|
||||
transition: border-color var(--transition-fast);
|
||||
}
|
||||
|
||||
.form-group textarea {
|
||||
resize: vertical;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
/* Input file personnalisé */
|
||||
.form-group input[type="file"] {
|
||||
background-color: var(--input-bg);
|
||||
padding: var(--spacing-sm);
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-secondary);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* États des champs */
|
||||
.form-group input:focus,
|
||||
.form-group textarea:focus {
|
||||
border-color: var(--accent-primary);
|
||||
box-shadow: 0 0 0 2px rgba(139, 69, 19, 0.2);
|
||||
}
|
||||
|
||||
/* Conteneurs de formulaire */
|
||||
.story-form,
|
||||
.login-form {
|
||||
background: var(--bg-tertiary);
|
||||
padding: var(--spacing-xl);
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--border-color);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
|
||||
/* Page de connexion */
|
||||
.login-page {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.login-container {
|
||||
background: var(--bg-tertiary);
|
||||
padding: var(--spacing-xl);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
62
assets/css/layout.css
Normal file
62
assets/css/layout.css
Normal file
@ -0,0 +1,62 @@
|
||||
/* Navigation */
|
||||
.admin-nav {
|
||||
background-color: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
padding: var(--spacing-md) var(--spacing-xl);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.nav-brand {
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
display: flex;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.logout-form button {
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
background-color: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
transition: background-color var(--transition-fast), border-color var(--transition-fast);
|
||||
}
|
||||
|
||||
.logout-form button:hover {
|
||||
background-color: var(--error-color);
|
||||
border-color: var(--error-color);
|
||||
}
|
||||
|
||||
/* Conteneur principal */
|
||||
.admin-main {
|
||||
padding: var(--spacing-xl);
|
||||
}
|
||||
|
||||
/* Grilles et listes */
|
||||
.stories-list {
|
||||
display: grid;
|
||||
gap: var(--spacing-lg);
|
||||
margin-top: var(--spacing-xl);
|
||||
}
|
||||
|
||||
/* Media queries */
|
||||
@media (max-width: 768px) {
|
||||
.admin-main {
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.stories-list {
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
}
|
6
assets/css/main.css
Normal file
6
assets/css/main.css
Normal file
@ -0,0 +1,6 @@
|
||||
/* Import des différents modules CSS */
|
||||
@import 'theme.css';
|
||||
@import 'layout.css';
|
||||
@import 'components.css';
|
||||
@import 'forms.css';
|
||||
@import 'editor.css';
|
75
assets/css/theme.css
Normal file
75
assets/css/theme.css
Normal file
@ -0,0 +1,75 @@
|
||||
/* Thème et variables globales */
|
||||
:root {
|
||||
/* Couleurs principales */
|
||||
--bg-primary: #1a1512;
|
||||
--bg-secondary: #2c1810;
|
||||
--bg-tertiary: #382218;
|
||||
|
||||
/* Textes */
|
||||
--text-primary: #e0d6cc;
|
||||
--text-secondary: #b3a69b;
|
||||
|
||||
/* Accents */
|
||||
--accent-primary: #8b4513;
|
||||
--accent-secondary: #d4691e;
|
||||
|
||||
/* Utilitaires */
|
||||
--border-color: #483225;
|
||||
--success-color: #2d5a27;
|
||||
--error-color: #802020;
|
||||
--input-bg: #241610;
|
||||
|
||||
/* Espacements */
|
||||
--spacing-xs: 0.5rem;
|
||||
--spacing-sm: 0.75rem;
|
||||
--spacing-md: 1rem;
|
||||
--spacing-lg: 1.5rem;
|
||||
--spacing-xl: 2rem;
|
||||
|
||||
/* Transitions */
|
||||
--transition-fast: 0.2s ease-in-out;
|
||||
--transition-normal: 0.3s ease-in-out;
|
||||
|
||||
/* Border radius */
|
||||
--radius-sm: 4px;
|
||||
--radius-md: 8px;
|
||||
--radius-lg: 12px;
|
||||
}
|
||||
|
||||
/* Reset et styles de base */
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
button, input, textarea {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
/* Utilitaires globaux */
|
||||
.button {
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
background-color: var(--accent-primary);
|
||||
color: var(--text-primary);
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
transition: background-color var(--transition-fast);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: var(--accent-secondary);
|
||||
}
|
||||
|
||||
/* États de focus globaux */
|
||||
:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
:focus-visible {
|
||||
box-shadow: 0 0 0 2px rgba(139, 69, 19, 0.2);
|
||||
}
|
@ -4,141 +4,202 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
theme: 'snow',
|
||||
modules: {
|
||||
toolbar: [
|
||||
[{ 'header': [1, 2, 3, false] }],
|
||||
['bold', 'italic', 'underline'],
|
||||
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
|
||||
['link', 'blockquote'],
|
||||
['clean']
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
// Gestion du tri des chapitres
|
||||
// Variables globales
|
||||
const chaptersList = document.getElementById('chaptersList');
|
||||
const modal = document.getElementById('chapterEditor');
|
||||
const addChapterBtn = document.getElementById('addChapter');
|
||||
let currentChapterId = null;
|
||||
const storyId = document.querySelector('input[name="id"]')?.value;
|
||||
|
||||
// Initialisation du tri par glisser-déposer
|
||||
if (chaptersList) {
|
||||
new Sortable(chaptersList, {
|
||||
animation: 150,
|
||||
handle: '.chapter-number',
|
||||
onEnd: updateChaptersOrder
|
||||
ghostClass: 'sortable-ghost',
|
||||
onEnd: async function() {
|
||||
// Récupérer le nouvel ordre des chapitres
|
||||
const chapters = Array.from(chaptersList.children).map((item, index) => ({
|
||||
id: item.dataset.id,
|
||||
order: index
|
||||
}));
|
||||
|
||||
try {
|
||||
const response = await fetch('api/reorder-chapters.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
storyId: storyId,
|
||||
chapters: chapters
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Erreur lors de la réorganisation');
|
||||
}
|
||||
|
||||
// Mettre à jour les numéros affichés
|
||||
chapters.forEach((chapter, index) => {
|
||||
const element = document.querySelector(`[data-id="${chapter.id}"] .chapter-number`);
|
||||
if (element) {
|
||||
element.textContent = index + 1;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erreur:', error);
|
||||
alert('Erreur lors de la réorganisation des chapitres');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Gestion des événements
|
||||
const modal = document.getElementById('chapterEditor');
|
||||
const addChapterBtn = document.getElementById('addChapter');
|
||||
const saveChapterBtn = document.getElementById('saveChapter');
|
||||
const cancelEditBtn = document.getElementById('cancelEdit');
|
||||
let currentEditingChapter = null;
|
||||
|
||||
// Gestion de l'ajout d'un nouveau chapitre
|
||||
if (addChapterBtn) {
|
||||
addChapterBtn.addEventListener('click', () => {
|
||||
currentEditingChapter = null;
|
||||
currentChapterId = null;
|
||||
document.getElementById('chapterTitle').value = '';
|
||||
quill.setContents([]);
|
||||
quill.setContents([{ insert: '\n' }]);
|
||||
modal.style.display = 'block';
|
||||
});
|
||||
}
|
||||
|
||||
// Sauvegarde d'un chapitre
|
||||
if (saveChapterBtn) {
|
||||
saveChapterBtn.addEventListener('click', async () => {
|
||||
const title = document.getElementById('chapterTitle').value;
|
||||
const content = quill.root.innerHTML;
|
||||
const storyId = document.querySelector('input[name="id"]').value;
|
||||
|
||||
// Gestion de l'édition d'un chapitre
|
||||
chaptersList?.addEventListener('click', async (e) => {
|
||||
if (e.target.matches('.edit-chapter')) {
|
||||
const chapterItem = e.target.closest('.chapter-item');
|
||||
currentChapterId = chapterItem.dataset.id;
|
||||
|
||||
try {
|
||||
const response = await fetch('api/save-chapter.php', {
|
||||
const response = await fetch(`api/get-chapter.php?storyId=${storyId}&chapterId=${currentChapterId}`);
|
||||
if (!response.ok) throw new Error('Erreur lors de la récupération du chapitre');
|
||||
|
||||
const chapter = await response.json();
|
||||
document.getElementById('chapterTitle').value = chapter.title;
|
||||
try {
|
||||
if (typeof chapter.content === 'string') {
|
||||
quill.root.innerHTML = chapter.content;
|
||||
} else {
|
||||
quill.setContents(chapter.content);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du chargement du contenu:', error);
|
||||
quill.root.innerHTML = chapter.content;
|
||||
}
|
||||
modal.style.display = 'block';
|
||||
} catch (error) {
|
||||
console.error('Erreur:', error);
|
||||
alert('Erreur lors de la récupération du chapitre');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Gestion de la suppression d'un chapitre
|
||||
chaptersList?.addEventListener('click', async (e) => {
|
||||
if (e.target.matches('.delete-chapter')) {
|
||||
if (!confirm('Voulez-vous vraiment supprimer ce chapitre ?')) return;
|
||||
|
||||
const chapterItem = e.target.closest('.chapter-item');
|
||||
const chapterId = chapterItem.dataset.id;
|
||||
|
||||
try {
|
||||
const response = await fetch('api/delete-chapter.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
storyId,
|
||||
chapterId: currentEditingChapter,
|
||||
title,
|
||||
content
|
||||
storyId: storyId,
|
||||
chapterId: chapterId
|
||||
})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
throw new Error('Erreur lors de la sauvegarde');
|
||||
}
|
||||
if (!response.ok) throw new Error('Erreur lors de la suppression');
|
||||
|
||||
chapterItem.remove();
|
||||
|
||||
// Mettre à jour les numéros des chapitres
|
||||
document.querySelectorAll('.chapter-number').forEach((num, index) => {
|
||||
num.textContent = index + 1;
|
||||
});
|
||||
} catch (error) {
|
||||
alert('Erreur lors de la sauvegarde du chapitre');
|
||||
console.error('Erreur:', error);
|
||||
alert('Erreur lors de la suppression du chapitre');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Fermeture du modal
|
||||
if (cancelEditBtn) {
|
||||
cancelEditBtn.addEventListener('click', () => {
|
||||
modal.style.display = 'none';
|
||||
});
|
||||
}
|
||||
// Gestion de la sauvegarde d'un chapitre
|
||||
document.getElementById('saveChapter')?.addEventListener('click', async () => {
|
||||
const title = document.getElementById('chapterTitle').value;
|
||||
if (!title.trim()) {
|
||||
alert('Le titre du chapitre est requis');
|
||||
return;
|
||||
}
|
||||
|
||||
// Écoute des clics sur la liste des chapitres
|
||||
if (chaptersList) {
|
||||
chaptersList.addEventListener('click', (e) => {
|
||||
if (e.target.matches('.delete-chapter')) {
|
||||
if (confirm('Voulez-vous vraiment supprimer ce chapitre ?')) {
|
||||
const chapterId = e.target.closest('.chapter-item').dataset.id;
|
||||
deleteChapter(chapterId);
|
||||
try {
|
||||
const response = await fetch('api/save-chapter.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
storyId: storyId,
|
||||
chapterId: currentChapterId,
|
||||
title: title,
|
||||
content: quill.root.innerHTML
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Erreur lors de la sauvegarde');
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!currentChapterId) {
|
||||
// Nouveau chapitre - ajouter à la liste
|
||||
const newChapter = document.createElement('div');
|
||||
newChapter.className = 'chapter-item';
|
||||
newChapter.dataset.id = result.chapterId;
|
||||
newChapter.innerHTML = `
|
||||
<span class="chapter-number">${document.querySelectorAll('.chapter-item').length + 1}</span>
|
||||
<h3 class="chapter-title">${title}</h3>
|
||||
<div class="chapter-actions">
|
||||
<button type="button" class="button edit-chapter">Éditer</button>
|
||||
<button type="button" class="button delete-chapter">Supprimer</button>
|
||||
</div>
|
||||
`;
|
||||
chaptersList.appendChild(newChapter);
|
||||
} else {
|
||||
// Mise à jour du titre dans la liste
|
||||
const chapterItem = document.querySelector(`[data-id="${currentChapterId}"] .chapter-title`);
|
||||
if (chapterItem) {
|
||||
chapterItem.textContent = title;
|
||||
}
|
||||
} else if (e.target.matches('.chapter-title')) {
|
||||
const chapterItem = e.target.closest('.chapter-item');
|
||||
currentEditingChapter = chapterItem.dataset.id;
|
||||
document.getElementById('chapterTitle').value = e.target.value;
|
||||
quill.root.innerHTML = chapterItem.querySelector('.editor').innerHTML;
|
||||
modal.style.display = 'block';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Mise à jour de l'ordre des chapitres
|
||||
async function updateChaptersOrder() {
|
||||
const chapters = Array.from(document.querySelectorAll('.chapter-item'))
|
||||
.map((item, index) => ({
|
||||
id: item.dataset.id,
|
||||
order: index
|
||||
}));
|
||||
|
||||
try {
|
||||
const response = await fetch('api/update-chapters-order.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(chapters)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Erreur lors de la mise à jour');
|
||||
modal.style.display = 'none';
|
||||
} catch (error) {
|
||||
console.error('Erreur:', error);
|
||||
alert('Erreur lors de la sauvegarde du chapitre');
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Erreur lors de la mise à jour de l\'ordre des chapitres');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Suppression d'un chapitre
|
||||
async function deleteChapter(chapterId) {
|
||||
try {
|
||||
const response = await fetch('api/delete-chapter.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ chapterId })
|
||||
});
|
||||
// Fermeture de la modale
|
||||
document.getElementById('cancelEdit')?.addEventListener('click', () => {
|
||||
modal.style.display = 'none';
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
throw new Error('Erreur lors de la suppression');
|
||||
// Fermeture de la modale en cliquant en dehors
|
||||
modal?.addEventListener('click', (e) => {
|
||||
if (e.target === modal) {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Erreur lors de la suppression du chapitre');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
@ -3,7 +3,6 @@ class Stories {
|
||||
private static $storiesDir = __DIR__ . '/../stories/';
|
||||
|
||||
public static function getAll() {
|
||||
self::ensureDirectoryExists();
|
||||
$stories = [];
|
||||
foreach (glob(self::$storiesDir . '*.json') as $file) {
|
||||
$story = json_decode(file_get_contents($file), true);
|
||||
@ -13,7 +12,6 @@ class Stories {
|
||||
}
|
||||
|
||||
public static function get($id) {
|
||||
self::ensureDirectoryExists();
|
||||
$file = self::$storiesDir . $id . '.json';
|
||||
if (!file_exists($file)) {
|
||||
return null;
|
||||
@ -22,40 +20,20 @@ class Stories {
|
||||
}
|
||||
|
||||
public static function save($story) {
|
||||
self::ensureDirectoryExists();
|
||||
// Ajout de l'heure à la date de mise à jour
|
||||
$story['updated'] = date('Y-m-d H:i:s');
|
||||
|
||||
// Vérifier que l'ID est valide
|
||||
if (empty($story['id'])) {
|
||||
throw new Exception('L\'ID du roman est requis');
|
||||
}
|
||||
|
||||
// Créer le chemin complet du fichier
|
||||
$file = self::$storiesDir . $story['id'] . '.json';
|
||||
|
||||
// Encoder en JSON avec options de formatage
|
||||
$jsonContent = json_encode($story, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
if ($jsonContent === false) {
|
||||
throw new Exception('Erreur lors de l\'encodage JSON: ' . json_last_error_msg());
|
||||
}
|
||||
|
||||
// Écrire le fichier
|
||||
$bytesWritten = file_put_contents($file, $jsonContent);
|
||||
if ($bytesWritten === false) {
|
||||
throw new Exception('Erreur lors de l\'écriture du fichier: ' . error_get_last()['message']);
|
||||
}
|
||||
|
||||
return $bytesWritten;
|
||||
return file_put_contents($file, json_encode($story, JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
private static function ensureDirectoryExists() {
|
||||
if (!file_exists(self::$storiesDir)) {
|
||||
if (!mkdir(self::$storiesDir, 0755, true)) {
|
||||
throw new Exception('Impossible de créer le dossier stories/');
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_writable(self::$storiesDir)) {
|
||||
throw new Exception('Le dossier stories/ n\'est pas accessible en écriture');
|
||||
public static function formatDate($date) {
|
||||
if (strlen($date) > 10) { // Si la date contient aussi l'heure
|
||||
$datetime = DateTime::createFromFormat('Y-m-d H:i:s', $date);
|
||||
return $datetime ? $datetime->format('d/m/Y H:i') : '';
|
||||
} else { // Pour les anciennes dates sans heure
|
||||
$datetime = DateTime::createFromFormat('Y-m-d', $date);
|
||||
return $datetime ? $datetime->format('d/m/Y') : '';
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user