/** * FavMasToKey - Script JavaScript principal */ // Attendre que le DOM soit chargé document.addEventListener('DOMContentLoaded', function() { // Éléments DOM const uploadForm = document.getElementById('upload-form'); const jsonFileInput = document.getElementById('json-file'); const step1 = document.getElementById('step1'); const step2 = document.getElementById('step2'); const step3 = document.getElementById('step3'); const fileSummary = document.getElementById('file-summary'); const backToStep1 = document.getElementById('back-to-step1'); const startMigration = document.getElementById('start-migration'); const pauseMigration = document.getElementById('pause-migration'); const cancelMigration = document.getElementById('cancel-migration'); const globalProgress = document.getElementById('global-progress'); const currentProgress = document.getElementById('current-progress'); const operationLog = document.getElementById('operation-log'); // Variables globales let favoritesList = []; let currentIndex = 0; let totalItems = 0; let isProcessing = false; let isPaused = false; let successCount = 0; let errorCount = 0; let skippedCount = 0; let migration = { status: 'not_started', // not_started, in_progress, paused, completed, error startTime: null, lastUpdateTime: null, progress: { current: 0, total: 0, percentage: 0 }, stats: { success: 0, error: 0, skipped: 0 } }; // Gérer le téléchargement et l'analyse du fichier JSON if (uploadForm) { uploadForm.addEventListener('submit', function(e) { e.preventDefault(); const file = jsonFileInput.files[0]; if (!file) { alert('Veuillez sélectionner un fichier JSON.'); return; } // Vérifier l'extension du fichier if (!file.name.endsWith('.json')) { alert('Le fichier doit être au format JSON.'); return; } // Lire le fichier const reader = new FileReader(); reader.onload = function(event) { try { const json = JSON.parse(event.target.result); // Vérifier la structure du fichier if (!json['@context'] || !json.type || !json.orderedItems) { alert('Le format du fichier JSON n\'est pas celui attendu pour un export de favoris Mastodon.'); return; } favoritesList = json.orderedItems; totalItems = favoritesList.length; // Afficher un résumé fileSummary.innerHTML = ` <strong>${totalItems}</strong> favoris trouvés dans votre fichier Mastodon. `; // Passer à l'étape 2 step1.classList.add('d-none'); step2.classList.remove('d-none'); // Stocker les données dans localStorage pour les conserver localStorage.setItem('favmastokey_favorites', JSON.stringify(favoritesList)); // Initialiser les données de migration migration = { status: 'not_started', startTime: null, lastUpdateTime: null, progress: { current: 0, total: totalItems, percentage: 0 }, stats: { success: 0, error: 0, skipped: 0 } }; // Sauvegarder les données de migration localStorage.setItem('favmastokey_migration', JSON.stringify(migration)); } catch (error) { alert('Erreur lors de l\'analyse du fichier JSON: ' + error.message); } }; reader.onerror = function() { alert('Erreur lors de la lecture du fichier.'); }; reader.readAsText(file); }); } // Gestion du retour à l'étape 1 if (backToStep1) { backToStep1.addEventListener('click', function() { step2.classList.add('d-none'); step1.classList.remove('d-none'); }); } // Vérifier si nous sommes à l'étape 3 (basé sur l'ancre dans l'URL) if (window.location.hash === '#step3' && document.getElementById('step3')) { // Récupérer les favoris du localStorage if (localStorage.getItem('favmastokey_favorites')) { favoritesList = JSON.parse(localStorage.getItem('favmastokey_favorites')); totalItems = favoritesList.length; // Montrer l'étape 3 step1.classList.add('d-none'); step2.classList.add('d-none'); step3.classList.remove('d-none'); // Récupérer les données de migration du localStorage if (localStorage.getItem('favmastokey_migration')) { migration = JSON.parse(localStorage.getItem('favmastokey_migration')); currentIndex = migration.progress.current; updateProgress(migration.progress.percentage); } } } /** * Met à jour les données de migration dans localStorage */ function updateMigrationData(status, progress = null) { migration.status = status; migration.lastUpdateTime = Date.now(); if (progress) { migration.progress = progress; } else { migration.progress = { current: currentIndex, total: totalItems, percentage: (currentIndex / totalItems) * 100 }; } migration.stats = { success: successCount, error: errorCount, skipped: skippedCount }; // Sauvegarder dans localStorage localStorage.setItem('favmastokey_migration', JSON.stringify(migration)); } /** * Réinitialise les données de migration */ function resetMigration() { currentIndex = 0; successCount = 0; errorCount = 0; skippedCount = 0; migration = { status: 'not_started', startTime: null, lastUpdateTime: null, progress: { current: 0, total: totalItems, percentage: 0 }, stats: { success: 0, error: 0, skipped: 0 } }; localStorage.setItem('favmastokey_migration', JSON.stringify(migration)); updateProgress(0); } // Gérer le processus de migration if (startMigration) { startMigration.addEventListener('click', function() { if (isProcessing) return; // Récupérer les favoris et les données de migration depuis localStorage si nécessaire if (favoritesList.length === 0 && localStorage.getItem('favmastokey_favorites')) { favoritesList = JSON.parse(localStorage.getItem('favmastokey_favorites')); totalItems = favoritesList.length; } // Vérifier s'il y a une migration en cours à reprendre if (localStorage.getItem('favmastokey_migration')) { const savedMigration = JSON.parse(localStorage.getItem('favmastokey_migration')); // Si la migration était en cours ou en pause, proposer de la reprendre if (savedMigration.status === 'in_progress' || savedMigration.status === 'paused') { const resumeConfirm = confirm(`Une migration précédente a été trouvée (${savedMigration.progress.percentage.toFixed(1)}% terminée). Voulez-vous la reprendre?`); if (resumeConfirm) { // Restaurer l'état de la migration migration = savedMigration; currentIndex = migration.progress.current; // Mettre à jour l'interface avec les données sauvegardées updateProgress(migration.progress.percentage); // Restaurer les statistiques successCount = migration.stats.success; errorCount = migration.stats.error; skippedCount = migration.stats.skipped; // Afficher un résumé addLogEntry(`Reprise de la migration: ${successCount} réussis, ${errorCount} échecs, ${skippedCount} ignorés.`, 'info'); } else { // Réinitialiser la migration resetMigration(); } } else { // Réinitialiser la migration resetMigration(); } } else { // Initialiser une nouvelle migration resetMigration(); } if (favoritesList.length === 0) { addLogEntry('Aucun favori à migrer. Veuillez d\'abord télécharger votre fichier JSON.', 'error'); return; } // Démarrer la migration isProcessing = true; isPaused = false; startMigration.classList.add('d-none'); pauseMigration.classList.remove('d-none'); // Initialiser le temps de démarrage si c'est une nouvelle migration if (migration.status === 'not_started' || migration.startTime === null) { migration.startTime = Date.now(); } // Mettre à jour le statut de la migration updateMigrationData('in_progress'); addLogEntry('Démarrage de la migration...', 'info'); // Lancer le processus de migration processBatch(); }); } // Gérer la pause de la migration if (pauseMigration) { pauseMigration.addEventListener('click', function() { if (!isProcessing) return; isPaused = !isPaused; if (isPaused) { pauseMigration.textContent = 'Reprendre'; addLogEntry('Migration en pause.', 'warning'); currentProgress.classList.remove('active'); // Mettre à jour le statut de la migration updateMigrationData('paused'); } else { pauseMigration.textContent = 'Pause'; addLogEntry('Reprise de la migration...', 'info'); currentProgress.classList.add('active'); // Mettre à jour le statut de la migration updateMigrationData('in_progress'); processBatch(); } }); } // Gérer l'annulation de la migration if (cancelMigration) { cancelMigration.addEventListener('click', function() { if (!isProcessing && currentIndex === 0) { // Retour à l'étape 1 si rien n'a commencé step3.classList.add('d-none'); step1.classList.remove('d-none'); return; } const confirmCancel = confirm('Êtes-vous sûr de vouloir annuler la migration en cours ?'); if (confirmCancel) { isProcessing = false; isPaused = false; addLogEntry('Migration annulée.', 'error'); // Réinitialiser l'interface startMigration.classList.remove('d-none'); pauseMigration.classList.add('d-none'); pauseMigration.textContent = 'Pause'; currentProgress.classList.remove('active'); // Réinitialiser les données de migration resetMigration(); } }); } /** * Traite un lot de favoris */ function processBatch() { if (!isProcessing || isPaused || currentIndex >= totalItems) { if (currentIndex >= totalItems) { // Migration terminée isProcessing = false; const summary = `Migration terminée avec succès ! ${successCount} publications ajoutées aux favoris, ${errorCount} erreurs, ${skippedCount} déjà présentes.`; addLogEntry(summary, 'success'); startMigration.classList.remove('d-none'); startMigration.textContent = 'Terminer'; startMigration.addEventListener('click', function() { // Nettoyer localStorage et retourner à l'étape 1 localStorage.removeItem('favmastokey_favorites'); localStorage.removeItem('favmastokey_migration'); step3.classList.add('d-none'); step1.classList.remove('d-none'); }); pauseMigration.classList.add('d-none'); // Mettre à jour la progression à 100% updateProgress(100); // Mettre à jour le statut de la migration updateMigrationData('completed'); } return; } // Nombre d'éléments à traiter dans ce lot const batchSize = 2; // Réduit de 5 à 2 pour limiter les risques de timeout const endIndex = Math.min(currentIndex + batchSize, totalItems); // Préparer les éléments du lot const batch = favoritesList.slice(currentIndex, endIndex); // Mettre à jour la progression actuelle currentProgress.classList.add('active'); updateProgress(); // Ajouter une entrée dans le journal addLogEntry(`Traitement du lot ${currentIndex + 1} à ${endIndex}...`, 'info'); // Compteur de tentatives pour cette requête let retryAttempt = 0; const maxRetries = 3; // Fonction pour envoyer la requête avec retry automatique function sendRequest() { fetch('process.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ batch: batch, currentIndex: currentIndex, totalItems: totalItems }), // Augmenter le timeout pour éviter les erreurs de limite de temps timeout: 60000 }) .then(response => { // Vérifier si la réponse est au format JSON const contentType = response.headers.get('content-type'); if (!contentType || !contentType.includes('application/json')) { // Si ce n'est pas du JSON, récupérer le texte pour déboguer return response.text().then(text => { throw new Error(`Réponse non-JSON reçue: ${text.substring(0, 100)}${text.length > 100 ? '...' : ''}`); }); } return response.json(); }) .then(data => { if (data.success) { // Traiter les résultats if (data.results && data.results.length) { data.results.forEach(result => { addLogEntry(result.message, result.status); // Mettre à jour les compteurs if (result.status === 'success') { successCount++; } else if (result.status === 'error') { errorCount++; } else if (result.status === 'info') { skippedCount++; } }); } // Mettre à jour l'index currentIndex = endIndex; // Mettre à jour la progression updateProgress(data.progress.percentage); // Mettre à jour les données de migration updateMigrationData('in_progress', data.progress); // Traiter le lot suivant après un court délai setTimeout(processBatch, 1000); } else { // Gérer l'erreur addLogEntry('Erreur: ' + data.message, 'error'); // Pause en cas d'erreur isPaused = true; pauseMigration.textContent = 'Reprendre'; currentProgress.classList.remove('active'); // Mettre à jour le statut de la migration updateMigrationData('error'); } }) .catch(error => { // Afficher l'erreur détaillée dans la console pour le débogage console.error('Erreur complète:', error); // Ajouter l'erreur au journal addLogEntry('Erreur de connexion: ' + error.message, 'error'); // Retenter si nous n'avons pas atteint le nombre maximum de tentatives if (retryAttempt < maxRetries) { retryAttempt++; const waitTime = Math.pow(2, retryAttempt) * 1000; // Backoff exponentiel addLogEntry(`Nouvelle tentative (${retryAttempt}/${maxRetries}) dans ${waitTime/1000} secondes...`, 'warning'); setTimeout(sendRequest, waitTime); } else { // Pause après plusieurs échecs isPaused = true; pauseMigration.textContent = 'Reprendre'; currentProgress.classList.remove('active'); // Mettre à jour le statut de la migration updateMigrationData('error'); addLogEntry(`Échec après ${maxRetries} tentatives. Veuillez reprendre manuellement.`, 'error'); } }); } // Lancer la requête sendRequest(); } /** * Met à jour la barre de progression */ function updateProgress(forcedValue = null) { const progress = forcedValue !== null ? forcedValue : (currentIndex / totalItems) * 100; globalProgress.style.width = progress + '%'; globalProgress.textContent = Math.round(progress) + '%'; globalProgress.setAttribute('aria-valuenow', progress); if (forcedValue === null) { // Mettre à jour la progression actuelle const batchProgress = ((currentIndex % 5) / 5) * 100; currentProgress.style.width = batchProgress + '%'; currentProgress.setAttribute('aria-valuenow', batchProgress); } else { currentProgress.style.width = '100%'; currentProgress.setAttribute('aria-valuenow', 100); } } /** * Ajoute une entrée dans le journal des opérations */ function addLogEntry(message, status = 'info') { const entry = document.createElement('div'); entry.className = `log-entry ${status}`; const timestamp = new Date().toLocaleTimeString(); entry.textContent = `[${timestamp}] ${message}`; operationLog.appendChild(entry); // Scroll vers le bas pour voir la dernière entrée const logContainer = document.getElementById('log-container'); logContainer.scrollTop = logContainer.scrollHeight; } });