ajout d'un mode "brouillon" pour les chapitres
ils ne sont visibles que par les admins
This commit is contained in:
parent
dd2913337f
commit
1fa578e312
@ -5,16 +5,20 @@ require_once '../../includes/stories.php';
|
||||
|
||||
if (!Auth::check()) {
|
||||
http_response_code(401);
|
||||
exit('Non autorisé');
|
||||
exit(json_encode(['success' => false, 'error' => 'Non autorisé']));
|
||||
}
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$storyId = $input['storyId'] ?? null;
|
||||
$chapterId = $input['chapterId'] ?? null;
|
||||
$title = $input['title'] ?? '';
|
||||
$content = $input['content'];
|
||||
$content = $input['content'] ?? '';
|
||||
$draft = $input['draft'] ?? false;
|
||||
|
||||
try {
|
||||
// Ajout de logs pour déboguer
|
||||
error_log('Données reçues: ' . print_r($input, true));
|
||||
|
||||
$story = Stories::get($storyId);
|
||||
if (!$story) {
|
||||
throw new Exception('Roman non trouvé');
|
||||
@ -22,28 +26,42 @@ try {
|
||||
|
||||
if ($chapterId) {
|
||||
// Mise à jour d'un chapitre existant
|
||||
$chapterUpdated = false;
|
||||
foreach ($story['chapters'] as &$chapter) {
|
||||
if ($chapter['id'] == $chapterId) {
|
||||
if ($chapter['id'] === $chapterId) {
|
||||
$chapter['title'] = $title;
|
||||
$chapter['content'] = $content;
|
||||
$chapter['draft'] = $draft;
|
||||
$chapter['updated'] = date('Y-m-d');
|
||||
$chapterUpdated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$chapterUpdated) {
|
||||
throw new Exception('Chapitre non trouvé');
|
||||
}
|
||||
} else {
|
||||
// Nouveau chapitre
|
||||
$story['chapters'][] = [
|
||||
'id' => uniqid(),
|
||||
'title' => $title,
|
||||
'content' => $content,
|
||||
'draft' => $draft,
|
||||
'created' => date('Y-m-d'),
|
||||
'updated' => date('Y-m-d')
|
||||
];
|
||||
}
|
||||
|
||||
Stories::save($story);
|
||||
if (!Stories::save($story)) {
|
||||
throw new Exception('Erreur lors de la sauvegarde du roman');
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true]);
|
||||
} catch (Exception $e) {
|
||||
error_log('Erreur dans save-chapter.php: ' . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
@ -149,6 +149,10 @@ function generateSlug($title) {
|
||||
<div class="modal-header">
|
||||
<h2>Éditer un chapitre</h2>
|
||||
<input type="text" id="chapterTitle" placeholder="Titre du chapitre">
|
||||
<label class="draft-toggle">
|
||||
<input type="checkbox" id="chapterDraft">
|
||||
<span class="toggle-label">Mode brouillon</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="editor-container">
|
||||
|
@ -328,6 +328,62 @@
|
||||
margin-top: var(--spacing-sm);
|
||||
}
|
||||
|
||||
/* Styles pour le mode brouillon */
|
||||
.draft-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
margin-top: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.draft-toggle input[type="checkbox"] {
|
||||
appearance: none;
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 10px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
|
||||
.draft-toggle input[type="checkbox"]::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: var(--text-primary);
|
||||
left: 2px;
|
||||
top: 2px;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
|
||||
.draft-toggle input[type="checkbox"]:checked {
|
||||
background: var(--accent-primary);
|
||||
}
|
||||
|
||||
.draft-toggle input[type="checkbox"]:checked::before {
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
.chapter-item.draft {
|
||||
opacity: 0.7;
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
.chapter-item.draft::after {
|
||||
content: 'Brouillon';
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
background: var(--accent-primary);
|
||||
color: var(--text-tertiary);
|
||||
padding: 0.2rem 0.5rem;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* Media queries */
|
||||
@media (max-width: 768px) {
|
||||
.story-cover {
|
||||
|
@ -1,9 +1,29 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Récupération des paramètres et variables globales
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const storyId = document.querySelector('input[name="id"]')?.value || urlParams.get('id');
|
||||
let currentChapterId = null;
|
||||
let hasUnsavedChanges = false;
|
||||
|
||||
// Fonction de notification
|
||||
// Références DOM pour l'éditeur
|
||||
const modal = document.getElementById('chapterEditor');
|
||||
const coverModal = document.getElementById('chapterCoverEditor');
|
||||
const addChapterBtn = document.getElementById('addChapter');
|
||||
const saveChapterBtn = document.getElementById('saveChapter');
|
||||
const cancelEditBtn = document.getElementById('cancelEdit');
|
||||
const chapterTitleInput = document.getElementById('chapterTitle');
|
||||
const chaptersList = document.getElementById('chaptersList');
|
||||
const chapterDraftToggle = document.getElementById('chapterDraft');
|
||||
|
||||
// Éléments pour la modale de couverture
|
||||
const coverPreview = document.querySelector('.current-cover-preview');
|
||||
const chapterCoverInput = document.getElementById('chapterCover');
|
||||
const saveCoverBtn = document.getElementById('saveCover');
|
||||
const cancelCoverBtn = document.getElementById('cancelCoverEdit');
|
||||
const deleteCoverBtn = document.getElementById('deleteCover');
|
||||
let currentChapterCover = null;
|
||||
|
||||
// Notification système
|
||||
function showNotification(message, type = 'success') {
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `notification ${type}`;
|
||||
@ -11,13 +31,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// Animation d'entrée
|
||||
setTimeout(() => {
|
||||
notification.style.opacity = '1';
|
||||
notification.style.transform = 'translateY(0)';
|
||||
}, 10);
|
||||
|
||||
// Auto-suppression après 3 secondes
|
||||
setTimeout(() => {
|
||||
notification.style.opacity = '0';
|
||||
notification.style.transform = 'translateY(-100%)';
|
||||
@ -25,33 +43,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Création de l'icône SVG pour le séparateur
|
||||
const icons = {
|
||||
divider: `
|
||||
<svg viewBox="0 0 18 18">
|
||||
<line class="ql-stroke" x1="3" x2="15" y1="9" y2="9"></line>
|
||||
</svg>
|
||||
`
|
||||
};
|
||||
|
||||
// Ajout de l'icône à Quill
|
||||
const Block = Quill.import('blots/block');
|
||||
const icons_list = Quill.import('ui/icons');
|
||||
icons_list['divider'] = icons.divider;
|
||||
|
||||
// Définition du format pour le séparateur
|
||||
class DividerBlot extends Block {
|
||||
static create() {
|
||||
const node = super.create();
|
||||
node.className = 'chapter-divider';
|
||||
return node;
|
||||
}
|
||||
}
|
||||
DividerBlot.blotName = 'divider';
|
||||
DividerBlot.tagName = 'hr';
|
||||
Quill.register(DividerBlot);
|
||||
|
||||
// Configuration et initialisation de Quill
|
||||
// Configuration de l'éditeur Quill
|
||||
const quill = new Quill('#editor', {
|
||||
theme: 'snow',
|
||||
modules: {
|
||||
@ -115,39 +107,241 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
keyboard: {
|
||||
bindings: {
|
||||
tab: false,
|
||||
'indent backwards': false
|
||||
}
|
||||
}
|
||||
},
|
||||
placeholder: 'Commencez à écrire votre chapitre ici...'
|
||||
});
|
||||
|
||||
// Gestion des chapitres
|
||||
const modal = document.getElementById('chapterEditor');
|
||||
const addChapterBtn = document.getElementById('addChapter');
|
||||
const saveChapterBtn = document.getElementById('saveChapter');
|
||||
const cancelEditBtn = document.getElementById('cancelEdit');
|
||||
const chapterTitleInput = document.getElementById('chapterTitle');
|
||||
const chaptersList = document.getElementById('chaptersList');
|
||||
// Détection des changements non sauvegardés
|
||||
function detectUnsavedChanges() {
|
||||
quill.on('text-change', () => {
|
||||
hasUnsavedChanges = true;
|
||||
});
|
||||
|
||||
if (chapterTitleInput) {
|
||||
chapterTitleInput.addEventListener('input', () => {
|
||||
hasUnsavedChanges = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Éléments de la modale de couverture
|
||||
const coverModal = document.getElementById('chapterCoverEditor');
|
||||
const coverPreview = document.querySelector('.current-cover-preview');
|
||||
const chapterCoverInput = document.getElementById('chapterCover');
|
||||
const saveCoverBtn = document.getElementById('saveCover');
|
||||
const cancelCoverBtn = document.getElementById('cancelCoverEdit');
|
||||
const deleteCoverBtn = document.getElementById('deleteCover');
|
||||
let currentChapterCover = null;
|
||||
// Configuration de Sortable pour la réorganisation des chapitres
|
||||
if (chaptersList) {
|
||||
new Sortable(chaptersList, {
|
||||
animation: 150,
|
||||
handle: '.chapter-number',
|
||||
ghostClass: 'sortable-ghost',
|
||||
onEnd: async function(evt) {
|
||||
const chapters = Array.from(chaptersList.children).map((item, index) => ({
|
||||
id: item.dataset.id,
|
||||
position: index
|
||||
}));
|
||||
|
||||
// Gestionnaire pour le bouton de couverture
|
||||
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');
|
||||
}
|
||||
|
||||
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);
|
||||
showNotification('Erreur lors de la réorganisation des chapitres', 'error');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Gestion de la sauvegarde du chapitre
|
||||
if (saveChapterBtn) {
|
||||
saveChapterBtn.addEventListener('click', async () => {
|
||||
const title = chapterTitleInput.value.trim();
|
||||
if (!title) {
|
||||
showNotification('Le titre est requis', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
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,
|
||||
draft: chapterDraftToggle?.checked || false
|
||||
})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
hasUnsavedChanges = false;
|
||||
showNotification('Chapitre sauvegardé avec succès');
|
||||
setTimeout(() => location.reload(), 500);
|
||||
} else {
|
||||
throw new Error(result.error || 'Erreur lors de la sauvegarde');
|
||||
}
|
||||
} else {
|
||||
throw new Error('Erreur réseau');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur:', error);
|
||||
showNotification('Erreur lors de la sauvegarde du chapitre', 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Gestionnaires d'événements pour l'édition des chapitres
|
||||
if (addChapterBtn) {
|
||||
addChapterBtn.addEventListener('click', () => {
|
||||
currentChapterId = null;
|
||||
chapterTitleInput.value = '';
|
||||
quill.setContents([]);
|
||||
if (chapterDraftToggle) {
|
||||
chapterDraftToggle.checked = false;
|
||||
}
|
||||
hasUnsavedChanges = false;
|
||||
modal.style.display = 'block';
|
||||
});
|
||||
}
|
||||
|
||||
if (cancelEditBtn) {
|
||||
cancelEditBtn.addEventListener('click', () => {
|
||||
if (hasUnsavedChanges) {
|
||||
confirmDialog.show({
|
||||
title: 'Modifications non sauvegardées',
|
||||
message: 'Des modifications non sauvegardées seront perdues. Voulez-vous vraiment fermer ?',
|
||||
confirmText: 'Fermer sans sauvegarder',
|
||||
onConfirm: () => {
|
||||
modal.style.display = 'none';
|
||||
hasUnsavedChanges = false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Gestion des clics sur la liste des chapitres
|
||||
if (chaptersList) {
|
||||
chaptersList.addEventListener('click', async (e) => {
|
||||
if (e.target.matches('.edit-cover')) {
|
||||
const chapterItem = e.target.closest('.chapter-item');
|
||||
const target = e.target;
|
||||
|
||||
if (target.matches('.edit-chapter')) {
|
||||
const chapterItem = target.closest('.chapter-item');
|
||||
currentChapterId = chapterItem.dataset.id;
|
||||
|
||||
try {
|
||||
const response = await fetch(`api/get-chapter.php?storyId=${storyId}&chapterId=${currentChapterId}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Erreur réseau');
|
||||
}
|
||||
|
||||
const chapter = await response.json();
|
||||
|
||||
// Mise à jour du titre
|
||||
chapterTitleInput.value = chapter.title || '';
|
||||
|
||||
// Gestion du contenu
|
||||
if (chapter.html) {
|
||||
quill.root.innerHTML = chapter.html;
|
||||
} else if (chapter.content) {
|
||||
try {
|
||||
const content = typeof chapter.content === 'string'
|
||||
? JSON.parse(chapter.content)
|
||||
: chapter.content;
|
||||
quill.setContents(content);
|
||||
} catch (contentError) {
|
||||
console.error('Erreur de parsing du contenu:', contentError);
|
||||
quill.setText(chapter.content || '');
|
||||
}
|
||||
} else {
|
||||
quill.setContents([]);
|
||||
}
|
||||
|
||||
// Mise à jour du mode brouillon
|
||||
if (chapterDraftToggle) {
|
||||
chapterDraftToggle.checked = chapter.draft || false;
|
||||
}
|
||||
|
||||
modal.style.display = 'block';
|
||||
hasUnsavedChanges = false;
|
||||
} catch (error) {
|
||||
console.error('Erreur détaillée:', error);
|
||||
showNotification('Erreur lors du chargement du chapitre', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Gestion de la suppression
|
||||
if (target.matches('.delete-chapter')) {
|
||||
const chapterItem = target.closest('.chapter-item');
|
||||
const chapterId = chapterItem.dataset.id;
|
||||
const chapterTitle = chapterItem.querySelector('.chapter-title').textContent;
|
||||
|
||||
confirmDialog.show({
|
||||
title: 'Suppression du chapitre',
|
||||
message: `Voulez-vous vraiment supprimer le chapitre "${chapterTitle}" ? Cette action est irréversible.`,
|
||||
confirmText: 'Supprimer',
|
||||
confirmClass: 'danger',
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
const response = await fetch('api/delete-chapter.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
storyId: storyId,
|
||||
chapterId: chapterId
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Erreur réseau');
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
chapterItem.style.opacity = '0';
|
||||
chapterItem.style.transform = 'translateX(-100%)';
|
||||
setTimeout(() => {
|
||||
chapterItem.remove();
|
||||
showNotification('Chapitre supprimé avec succès');
|
||||
}, 300);
|
||||
} else {
|
||||
throw new Error(result.error || 'Erreur lors de la suppression');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur:', error);
|
||||
showNotification(error.message, 'error');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Gestion de la couverture
|
||||
if (target.matches('.edit-cover')) {
|
||||
const chapterItem = target.closest('.chapter-item');
|
||||
const chapterId = chapterItem.dataset.id;
|
||||
const chapterTitle = chapterItem.querySelector('.chapter-title').textContent;
|
||||
|
||||
@ -179,7 +373,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
}
|
||||
|
||||
// Prévisualisation de l'image
|
||||
// Prévisualisation de l'image de couverture
|
||||
if (chapterCoverInput) {
|
||||
chapterCoverInput.addEventListener('change', (e) => {
|
||||
const file = e.target.files[0];
|
||||
@ -231,6 +425,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
showNotification('Couverture mise à jour avec succès');
|
||||
coverModal.style.display = 'none';
|
||||
chapterCoverInput.value = '';
|
||||
setTimeout(() => location.reload(), 500);
|
||||
} else {
|
||||
throw new Error(result.error || 'Erreur lors de la mise à jour');
|
||||
}
|
||||
@ -270,6 +465,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
showNotification('Couverture supprimée avec succès');
|
||||
coverPreview.innerHTML = '';
|
||||
coverModal.style.display = 'none';
|
||||
setTimeout(() => location.reload(), 500);
|
||||
} else {
|
||||
throw new Error(result.error || 'Erreur lors de la suppression');
|
||||
}
|
||||
@ -282,208 +478,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
}
|
||||
|
||||
// Configuration de Sortable pour la réorganisation des chapitres
|
||||
if (chaptersList) {
|
||||
new Sortable(chaptersList, {
|
||||
animation: 150,
|
||||
handle: '.chapter-number',
|
||||
ghostClass: 'sortable-ghost',
|
||||
onEnd: async function(evt) {
|
||||
const chapters = Array.from(chaptersList.children).map((item, index) => ({
|
||||
id: item.dataset.id,
|
||||
position: 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');
|
||||
}
|
||||
|
||||
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);
|
||||
showNotification('Erreur lors de la réorganisation des chapitres', 'error');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Gestionnaires d'événements pour l'édition des chapitres
|
||||
if (addChapterBtn) {
|
||||
addChapterBtn.addEventListener('click', () => {
|
||||
currentChapterId = null;
|
||||
chapterTitleInput.value = '';
|
||||
quill.setContents([]);
|
||||
modal.style.display = 'block';
|
||||
});
|
||||
}
|
||||
|
||||
if (cancelEditBtn) {
|
||||
cancelEditBtn.addEventListener('click', () => {
|
||||
if (hasUnsavedChanges()) {
|
||||
confirmDialog.show({
|
||||
title: 'Modifications non sauvegardées',
|
||||
message: 'Des modifications non sauvegardées seront perdues. Voulez-vous vraiment fermer ?',
|
||||
confirmText: 'Fermer sans sauvegarder',
|
||||
onConfirm: () => {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
} else {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Gestion des clics sur la liste des chapitres
|
||||
if (chaptersList) {
|
||||
chaptersList.addEventListener('click', async (e) => {
|
||||
const target = e.target;
|
||||
|
||||
if (target.matches('.edit-chapter')) {
|
||||
const chapterItem = target.closest('.chapter-item');
|
||||
currentChapterId = chapterItem.dataset.id;
|
||||
|
||||
try {
|
||||
const response = await fetch(`api/get-chapter.php?storyId=${storyId}&chapterId=${currentChapterId}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Erreur réseau');
|
||||
}
|
||||
|
||||
const chapter = await response.json();
|
||||
|
||||
// Mise à jour du titre
|
||||
chapterTitleInput.value = chapter.title || '';
|
||||
|
||||
// Gestion du contenu
|
||||
if (chapter.html) {
|
||||
quill.root.innerHTML = chapter.html;
|
||||
} else if (chapter.content) {
|
||||
try {
|
||||
const content = typeof chapter.content === 'string'
|
||||
? JSON.parse(chapter.content)
|
||||
: chapter.content;
|
||||
quill.setContents(content);
|
||||
} catch (contentError) {
|
||||
console.error('Erreur de parsing du contenu:', contentError);
|
||||
quill.setText(chapter.content || '');
|
||||
}
|
||||
} else {
|
||||
quill.setContents([]);
|
||||
}
|
||||
|
||||
modal.style.display = 'block';
|
||||
} catch (error) {
|
||||
console.error('Erreur détaillée:', error);
|
||||
showNotification('Erreur lors du chargement du chapitre', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Gestion de la suppression
|
||||
if (target.matches('.delete-chapter')) {
|
||||
const chapterItem = target.closest('.chapter-item');
|
||||
const chapterId = chapterItem.dataset.id;
|
||||
const chapterTitle = chapterItem.querySelector('.chapter-title').textContent;
|
||||
|
||||
confirmDialog.show({
|
||||
title: 'Suppression du chapitre',
|
||||
message: `Voulez-vous vraiment supprimer le chapitre "${chapterTitle}" ? Cette action est irréversible.`,
|
||||
confirmText: 'Supprimer',
|
||||
confirmClass: 'danger',
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
const response = await fetch('api/delete-chapter.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
storyId: storyId,
|
||||
chapterId: chapterId
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Erreur réseau');
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
chapterItem.style.opacity = '0';
|
||||
chapterItem.style.transform = 'translateX(-100%)';
|
||||
setTimeout(() => {
|
||||
chapterItem.remove();
|
||||
showNotification('Chapitre supprimé avec succès');
|
||||
}, 300);
|
||||
} else {
|
||||
throw new Error(result.error || 'Erreur lors de la suppression');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur:', error);
|
||||
showNotification(error.message, 'error');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Sauvegarde d'un chapitre
|
||||
if (saveChapterBtn) {
|
||||
saveChapterBtn.addEventListener('click', async () => {
|
||||
const title = chapterTitleInput.value.trim();
|
||||
if (!title) {
|
||||
showNotification('Le titre est requis', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
showNotification('Chapitre sauvegardé avec succès');
|
||||
setTimeout(() => location.reload(), 500);
|
||||
} else {
|
||||
throw new Error('Erreur lors de la sauvegarde');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur:', error);
|
||||
showNotification('Erreur lors de la sauvegarde du chapitre', 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function hasUnsavedChanges() {
|
||||
if (!currentChapterId) {
|
||||
return chapterTitleInput.value !== '' || quill.getLength() > 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Initialisation
|
||||
detectUnsavedChanges();
|
||||
});
|
24
chapitre.php
24
chapitre.php
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
require_once 'includes/config.php';
|
||||
require_once 'includes/auth.php';
|
||||
require_once 'includes/stories.php';
|
||||
|
||||
// Récupération des paramètres
|
||||
@ -22,6 +23,9 @@ if (!$story) {
|
||||
$currentChapter = null;
|
||||
$currentIndex = -1;
|
||||
foreach ($story['chapters'] as $index => $chapter) {
|
||||
if (($chapter['draft'] ?? false) && !Auth::check()) {
|
||||
continue;
|
||||
}
|
||||
if ($chapter['id'] === $chapterId) {
|
||||
$currentChapter = $chapter;
|
||||
$currentIndex = $index;
|
||||
@ -35,8 +39,14 @@ if (!$currentChapter) {
|
||||
}
|
||||
|
||||
// Récupération des chapitres précédent et suivant
|
||||
$prevChapter = $currentIndex > 0 ? $story['chapters'][$currentIndex - 1] : null;
|
||||
$nextChapter = $currentIndex < count($story['chapters']) - 1 ? $story['chapters'][$currentIndex + 1] : null;
|
||||
$visibleChapters = array_filter($story['chapters'], function($ch) {
|
||||
return !($ch['draft'] ?? false) || Auth::check();
|
||||
});
|
||||
$visibleChapters = array_values($visibleChapters);
|
||||
|
||||
$currentVisibleIndex = array_search($currentChapter, $visibleChapters);
|
||||
$prevChapter = $currentVisibleIndex > 0 ? $visibleChapters[$currentVisibleIndex - 1] : null;
|
||||
$nextChapter = $currentVisibleIndex < count($visibleChapters) - 1 ? $visibleChapters[$currentVisibleIndex + 1] : null;
|
||||
|
||||
$config = Config::load();
|
||||
?>
|
||||
@ -91,10 +101,16 @@ $config = Config::load();
|
||||
<aside class="chapters-menu">
|
||||
<h2>Chapitres</h2>
|
||||
<ul class="chapters-list">
|
||||
<?php foreach ($story['chapters'] as $chapter): ?>
|
||||
<?php
|
||||
$visibleChapters = array_filter($story['chapters'], function($chapter) {
|
||||
return !($chapter['draft'] ?? false) || Auth::check();
|
||||
});
|
||||
|
||||
foreach ($visibleChapters as $chapter):
|
||||
?>
|
||||
<li>
|
||||
<a href="?story=<?= urlencode($storyId) ?>&chapter=<?= urlencode($chapter['id']) ?>"
|
||||
class="<?= $chapter['id'] === $chapterId ? 'current-chapter' : '' ?>">
|
||||
class="<?= $chapter['id'] === $chapterId ? 'current-chapter' : '' ?>">
|
||||
<?= htmlspecialchars($chapter['title']) ?>
|
||||
</a>
|
||||
</li>
|
||||
|
25
index.php
25
index.php
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
require_once 'includes/config.php';
|
||||
require_once 'includes/auth.php';
|
||||
require_once 'includes/stories.php';
|
||||
|
||||
$config = Config::load();
|
||||
@ -77,16 +78,22 @@ function formatDate($date) {
|
||||
class="novel-cover"
|
||||
loading="lazy">
|
||||
|
||||
<div class="novel-info">
|
||||
<h2><?= htmlspecialchars($story['title']) ?></h2>
|
||||
<p>
|
||||
<?= count($story['chapters'] ?? []) ?>
|
||||
chapitre<?= count($story['chapters'] ?? []) > 1 ? 's' : '' ?>
|
||||
</p>
|
||||
<div class="novel-date">
|
||||
Mis à jour le <?= formatDate($story['updated']) ?>
|
||||
<div class="novel-info">
|
||||
<h2><?= htmlspecialchars($story['title']) ?></h2>
|
||||
<?php
|
||||
$visibleChapters = array_filter($story['chapters'] ?? [], function($chapter) {
|
||||
return !($chapter['draft'] ?? false) || Auth::check();
|
||||
});
|
||||
$chapterCount = count($visibleChapters);
|
||||
?>
|
||||
<p>
|
||||
<?= $chapterCount ?>
|
||||
chapitre<?= $chapterCount > 1 ? 's' : '' ?>
|
||||
</p>
|
||||
<div class="novel-date">
|
||||
Mis à jour le <?= formatDate($story['updated']) ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
require_once 'includes/config.php';
|
||||
require_once 'includes/auth.php';
|
||||
require_once 'includes/stories.php';
|
||||
|
||||
// Récupération de l'ID du roman depuis l'URL
|
||||
@ -53,7 +54,13 @@ $config = Config::load();
|
||||
<h2>Chapitres</h2>
|
||||
<?php if (!empty($story['chapters'])): ?>
|
||||
<ul class="chapters-list">
|
||||
<?php foreach ($story['chapters'] as $index => $chapter): ?>
|
||||
<?php
|
||||
$visibleChapters = array_filter($story['chapters'], function($chapter) {
|
||||
return !($chapter['draft'] ?? false) || Auth::check();
|
||||
});
|
||||
|
||||
foreach ($visibleChapters as $chapter):
|
||||
?>
|
||||
<li>
|
||||
<a href="chapitre.php?story=<?= urlencode($story['id']) ?>&chapter=<?= urlencode($chapter['id']) ?>">
|
||||
<?= htmlspecialchars($chapter['title']) ?>
|
||||
|
@ -1 +1 @@
|
||||
1.1.7
|
||||
1.2.0
|
Loading…
x
Reference in New Issue
Block a user