Lectures/assets/js/story-edit.js

489 lines
20 KiB
JavaScript
Raw Permalink Normal View History

2025-02-14 18:15:23 +01:00
document.addEventListener('DOMContentLoaded', function() {
const urlParams = new URLSearchParams(window.location.search);
const storyId = document.querySelector('input[name="id"]')?.value || urlParams.get('id');
2025-02-15 13:03:10 +01:00
let currentChapterId = null;
// Fonction de notification
function showNotification(message, type = 'success') {
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.textContent = message;
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%)';
setTimeout(() => notification.remove(), 300);
}, 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
2025-02-14 18:15:23 +01:00
const quill = new Quill('#editor', {
theme: 'snow',
modules: {
toolbar: {
container: [
[{ 'header': [1, 2, 3, false] }],
['bold', 'italic', 'underline', 'strike'],
[{ 'color': [] }, { 'background': [] }],
[{ 'font': [] }],
[{ 'align': [] }],
['blockquote', 'code-block'],
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
[{ 'script': 'sub'}, { 'script': 'super' }],
[{ 'indent': '-1'}, { 'indent': '+1' }],
[{ 'direction': 'rtl' }],
['link', 'image', 'video'],
['divider'],
['clean']
],
handlers: {
2025-02-15 19:24:18 +01:00
image: function() {
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/*');
input.click();
input.onchange = async () => {
const file = input.files[0];
if (file) {
const formData = new FormData();
formData.append('image', file);
formData.append('storyId', storyId);
try {
const response = await fetch('api/upload-image.php', {
method: 'POST',
body: formData
});
if (!response.ok) throw new Error('Upload failed');
const result = await response.json();
if (result.success) {
const range = quill.getSelection(true);
quill.insertEmbed(range.index, 'image', result.url);
const insertOp = quill.getContents().ops.find(op =>
op.insert && op.insert.image === result.url
);
if (insertOp) {
insertOp.insert.image = result.storage_url;
}
2025-02-15 19:24:18 +01:00
quill.setSelection(range.index + 1);
} else {
showNotification(result.error || 'Erreur lors de l\'upload', 'error');
}
} catch (error) {
console.error('Error:', error);
showNotification('Erreur lors de l\'upload de l\'image', 'error');
}
}
};
}
}
},
2025-02-15 13:03:10 +01:00
keyboard: {
bindings: {
tab: false,
'indent backwards': false
}
}
},
placeholder: 'Commencez à écrire votre chapitre ici...'
2025-02-14 18:15:23 +01:00
});
2025-02-15 13:03:10 +01:00
// Gestion des chapitres
const modal = document.getElementById('chapterEditor');
const addChapterBtn = document.getElementById('addChapter');
2025-02-15 13:03:10 +01:00
const saveChapterBtn = document.getElementById('saveChapter');
const cancelEditBtn = document.getElementById('cancelEdit');
const chapterTitleInput = document.getElementById('chapterTitle');
const chaptersList = document.getElementById('chaptersList');
// É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;
// Gestionnaire pour le bouton de couverture
if (chaptersList) {
chaptersList.addEventListener('click', async (e) => {
if (e.target.matches('.edit-cover')) {
const chapterItem = e.target.closest('.chapter-item');
const chapterId = chapterItem.dataset.id;
const chapterTitle = chapterItem.querySelector('.chapter-title').textContent;
// Mise à jour du titre dans la modale
coverModal.querySelector('.chapter-title-display').textContent = chapterTitle;
currentChapterCover = chapterId;
try {
const response = await fetch(`api/get-chapter.php?storyId=${storyId}&chapterId=${chapterId}`);
if (!response.ok) throw new Error('Erreur réseau');
const chapter = await response.json();
// Afficher l'image existante si elle existe
if (chapter.cover) {
coverPreview.innerHTML = `<img src="../${chapter.cover}" alt="Couverture actuelle">`;
deleteCoverBtn.style.display = 'block';
} else {
coverPreview.innerHTML = '';
deleteCoverBtn.style.display = 'none';
}
coverModal.style.display = 'block';
} catch (error) {
console.error('Erreur:', error);
showNotification('Erreur lors du chargement de la couverture', 'error');
}
}
});
}
// Prévisualisation de l'image
if (chapterCoverInput) {
chapterCoverInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
coverPreview.innerHTML = `<img src="${e.target.result}" alt="Prévisualisation">`;
deleteCoverBtn.style.display = 'none';
};
reader.readAsDataURL(file);
}
});
}
// Fermeture de la modale de couverture
if (cancelCoverBtn) {
cancelCoverBtn.addEventListener('click', () => {
coverModal.style.display = 'none';
chapterCoverInput.value = '';
});
}
// Sauvegarde de la couverture
if (saveCoverBtn) {
saveCoverBtn.addEventListener('click', async () => {
const file = chapterCoverInput.files[0];
if (!file && !coverPreview.querySelector('img')) {
showNotification('Veuillez sélectionner une image', 'error');
return;
}
const formData = new FormData();
formData.append('storyId', storyId);
formData.append('chapterId', currentChapterCover);
if (file) {
formData.append('cover', file);
}
try {
const response = await fetch('api/update-chapter-cover.php', {
method: 'POST',
body: formData
});
if (!response.ok) throw new Error('Erreur lors de l\'upload');
const result = await response.json();
if (result.success) {
showNotification('Couverture mise à jour avec succès');
coverModal.style.display = 'none';
chapterCoverInput.value = '';
} else {
throw new Error(result.error || 'Erreur lors de la mise à jour');
}
} catch (error) {
console.error('Erreur:', error);
showNotification(error.message, 'error');
}
});
}
// Suppression de la couverture
if (deleteCoverBtn) {
deleteCoverBtn.addEventListener('click', () => {
confirmDialog.show({
title: 'Supprimer la couverture',
message: 'Voulez-vous vraiment supprimer la couverture de ce chapitre ?',
confirmText: 'Supprimer',
confirmClass: 'danger',
onConfirm: async () => {
try {
const response = await fetch('api/update-chapter-cover.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
storyId: storyId,
chapterId: currentChapterCover,
delete: true
})
});
if (!response.ok) throw new Error('Erreur réseau');
const result = await response.json();
if (result.success) {
showNotification('Couverture supprimée avec succès');
coverPreview.innerHTML = '';
coverModal.style.display = 'none';
} else {
throw new Error(result.error || 'Erreur lors de la suppression');
}
} catch (error) {
console.error('Erreur:', error);
showNotification(error.message, 'error');
}
}
});
});
}
2025-02-15 13:03:10 +01:00
// Configuration de Sortable pour la réorganisation des chapitres
2025-02-14 18:15:23 +01:00
if (chaptersList) {
new Sortable(chaptersList, {
animation: 150,
handle: '.chapter-number',
ghostClass: 'sortable-ghost',
2025-02-15 13:03:10 +01:00
onEnd: async function(evt) {
const chapters = Array.from(chaptersList.children).map((item, index) => ({
id: item.dataset.id,
2025-02-15 13:03:10 +01:00
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;
}
});
2025-02-15 13:03:10 +01:00
} catch (error) {
console.error('Erreur:', error);
2025-02-15 13:03:10 +01:00
showNotification('Erreur lors de la réorganisation des chapitres', 'error');
}
}
2025-02-14 18:15:23 +01:00
});
}
2025-02-15 13:03:10 +01:00
// Gestionnaires d'événements pour l'édition des chapitres
2025-02-14 18:15:23 +01:00
if (addChapterBtn) {
addChapterBtn.addEventListener('click', () => {
currentChapterId = null;
2025-02-15 13:03:10 +01:00
chapterTitleInput.value = '';
quill.setContents([]);
2025-02-14 18:15:23 +01:00
modal.style.display = 'block';
});
}
2025-02-15 13:03:10 +01:00
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';
2025-02-15 13:03:10 +01:00
}
});
}
// Gestion des clics sur la liste des chapitres
2025-02-15 13:03:10 +01:00
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 {
2025-02-15 13:03:10 +01:00
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 {
2025-02-15 13:03:10 +01:00
quill.setContents([]);
}
2025-02-15 13:03:10 +01:00
modal.style.display = 'block';
} catch (error) {
2025-02-15 13:03:10 +01:00
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');
}
}
});
}
2025-02-15 13:03:10 +01:00
});
}
2025-02-15 13:03:10 +01:00
// 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 {
2025-02-15 13:03:10 +01:00
const response = await fetch('api/save-chapter.php', {
2025-02-14 18:15:23 +01:00
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
storyId: storyId,
2025-02-15 13:03:10 +01:00
chapterId: currentChapterId,
title: title,
content: quill.root.innerHTML
2025-02-14 18:15:23 +01:00
})
});
2025-02-15 13:03:10 +01:00
if (response.ok) {
showNotification('Chapitre sauvegardé avec succès');
setTimeout(() => location.reload(), 500);
} else {
throw new Error('Erreur lors de la sauvegarde');
}
2025-02-14 18:15:23 +01:00
} catch (error) {
console.error('Erreur:', error);
2025-02-15 13:03:10 +01:00
showNotification('Erreur lors de la sauvegarde du chapitre', 'error');
2025-02-14 18:15:23 +01:00
}
2025-02-15 13:03:10 +01:00
});
}
2025-02-14 18:15:23 +01:00
function hasUnsavedChanges() {
if (!currentChapterId) {
return chapterTitleInput.value !== '' || quill.getLength() > 1;
2025-02-14 18:15:23 +01:00
}
return true;
}
});