/* ============================ MarkitDown — Script Frontend ============================ */ let allResults = []; // Liste des catégories et extensions associées const supportedFormats = { "Documents Office": [".docx", ".pptx", ".xlsx"], "PDF": [".pdf"], "Images": [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"], "Web": [".html", ".htm"], "Données": [".csv", ".json", ".xml"], "Archives": [".zip"], "Texte": [".txt", ".md", ".rst"], "Audio": [".mp3", ".wav"] }; // Éléments DOM const uploadZone = document.getElementById("upload-zone"); const fileInput = document.getElementById("file-input"); const uploadStatus = document.getElementById("upload-status"); const resultSection = document.getElementById("result-section"); const resultCount = document.getElementById("result-count"); const resultList = document.getElementById("result-list"); const btnDownloadZip = document.getElementById("btn-download-zip"); const btnCopyAll = document.getElementById("btn-copy-all"); const historyList = document.getElementById("history-list"); const historyPanel = document.getElementById("history-panel"); const toggleHistory = document.getElementById("toggle-history"); const btnClearHistory = document.getElementById("btn-clear-history"); const filterCheckboxesContainer = document.getElementById("filter-checkboxes"); const btnFilterSelectAll = document.getElementById("filter-select-all"); const btnFilterDeselectAll = document.getElementById("filter-deselect-all"); // ============================ // Initialisation des filtres // ============================ function initFilters() { Object.entries(supportedFormats).forEach(([category, extensions]) => { const label = document.createElement("label"); label.className = "filter-label"; const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.className = "filter-checkbox"; checkbox.checked = true; // Tout coché par défaut checkbox.dataset.extensions = extensions.join(","); label.appendChild(checkbox); label.appendChild(document.createTextNode(`${category} (${extensions.join(", ")})`)); filterCheckboxesContainer.appendChild(label); }); btnFilterSelectAll.addEventListener("click", () => { document.querySelectorAll(".filter-checkbox").forEach(cb => cb.checked = true); }); btnFilterDeselectAll.addEventListener("click", () => { document.querySelectorAll(".filter-checkbox").forEach(cb => cb.checked = false); }); } function getSelectedExtensions() { const extensions = new Set(); document.querySelectorAll(".filter-checkbox:checked").forEach(cb => { cb.dataset.extensions.split(",").forEach(ext => extensions.add(ext)); }); return extensions; } initFilters(); // ============================ // Upload — Drag & Drop + Click // ============================ uploadZone.addEventListener("click", () => fileInput.click()); uploadZone.addEventListener("dragover", (e) => { e.preventDefault(); uploadZone.classList.add("drag-over"); }); uploadZone.addEventListener("dragleave", () => { uploadZone.classList.remove("drag-over"); }); uploadZone.addEventListener("drop", async (e) => { e.preventDefault(); uploadZone.classList.remove("drag-over"); console.log("--- DEBUG: Drop event triggered ---"); const items = e.dataTransfer.items; // Détection de l'incompatibilité (Firefox) pour le glisser-déposer de dossiers if (items && items.length > 0 && typeof items[0].webkitGetAsEntry === "undefined") { let hasDirectory = false; // Sur Firefox, un dossier déposé n'a pas de type et une taille de 0 for (let i = 0; i < e.dataTransfer.files.length; i++) { if (e.dataTransfer.files[i].size === 0 && e.dataTransfer.files[i].type === "") { hasDirectory = true; break; } } if (hasDirectory) { uploadStatus.textContent = "Le glisser-déposer de dossiers n'est pas supporté sur ce navigateur. Veuillez cliquer pour sélectionner un dossier."; uploadStatus.className = "upload-status error"; console.log("DEBUG: Folder drop on incompatible browser (like Firefox) detected. Aborting."); return; } } if (items && items.length > 0) { console.log(`DEBUG: ${items.length} items detected in drop event.`); const files = []; for (let i = 0; i < items.length; i++) { if (items[i].kind === 'file') { const entry = items[i].webkitGetAsEntry(); if (entry) { console.log(`DEBUG: Processing item #${i+1}: ${entry.name}`); await traverseFileTree(entry, "", files); } } } console.log(`DEBUG: Finished traversing all items. Total files found: ${files.length}`); if (files.length > 0) { uploadFiles(files); } else { console.log("DEBUG: No files found after traversal."); } } else { console.log("DEBUG: Drop event with no items, trying e.dataTransfer.files."); const files = e.dataTransfer.files; if (files.length > 0) { uploadFiles(Array.from(files)); } } }); async function traverseFileTree(item, path, filesArray) { path = path || ""; if (item.isFile) { console.log(`DEBUG: Found file: ${path}${item.name}`); await new Promise((resolve, reject) => { item.file(file => { if (!file.name.startsWith('.')) { Object.defineProperty(file, 'fullPath', { value: path + item.name, writable: false }); filesArray.push(file); } resolve(); }, reject); }); } else if (item.isDirectory) { console.log(`DEBUG: Entering directory: ${path}${item.name}`); const dirReader = item.createReader(); const readAllDirectoryEntries = async (directoryReader) => { const entries = []; let readEntries = await new Promise((resolve, reject) => { directoryReader.readEntries(resolve, reject); }); while (readEntries.length > 0) { entries.push(...readEntries); readEntries = await new Promise((resolve, reject) => { directoryReader.readEntries(resolve, reject); }); } return entries; }; try { const allEntries = await readAllDirectoryEntries(dirReader); console.log(`DEBUG: Found ${allEntries.length} entries in directory ${item.name}`); for (const entry of allEntries) { await traverseFileTree(entry, path + item.name + "/", filesArray); } } catch (error) { console.error("DEBUG: Error reading directory:", error); } } } fileInput.addEventListener("change", () => { if (fileInput.files.length > 0) { console.log("--- DEBUG: File input change event triggered ---"); uploadFiles(Array.from(fileInput.files)); fileInput.value = ""; } }); // ============================ // Upload & Conversion // ============================ async function uploadFiles(files) { console.log("--- DEBUG: uploadFiles function called ---"); const selectedExtensions = getSelectedExtensions(); if (selectedExtensions.size === 0) { uploadStatus.textContent = "Erreur : Veuillez sélectionner au moins un type de fichier à traiter."; uploadStatus.className = "upload-status error"; return; } const filteredFiles = files.filter(file => { const fileName = file.fullPath || file.webkitRelativePath || file.name; const match = fileName.match(/\.[0-9a-z]+$/i); if (!match) return false; return selectedExtensions.has(match[0].toLowerCase()); }); if (filteredFiles.length === 0) { uploadStatus.textContent = "Aucun fichier correspondant aux filtres sélectionnés n'a été trouvé."; uploadStatus.className = "upload-status error"; console.log("DEBUG: No files left after filtering by extension."); return; } console.log(`DEBUG: ${filteredFiles.length} files left after filtering.`); uploadStatus.textContent = `Conversion de ${filteredFiles.length} fichier(s) en cours…`; uploadStatus.className = "upload-status"; const formData = new FormData(); console.log("DEBUG: Building FormData to send to server..."); for (const file of filteredFiles) { let fileName = file.fullPath || file.webkitRelativePath || file.name; formData.append("files", file); formData.append("paths", fileName); console.log(`DEBUG: Appending to FormData: file=${file.name}, path=${fileName}`); } try { console.log("DEBUG: Sending request to /upload"); const response = await fetch("/upload", { method: "POST", body: formData, }); console.log(`DEBUG: Received response from server with status: ${response.status}`); if (!response.ok) { if (response.status === 413) { uploadStatus.textContent = "Erreur : Le dossier ou les fichiers sont trop volumineux (Limite dépassée)."; uploadStatus.className = "upload-status error"; return; } } const data = await response.json(); console.log("DEBUG: Server response JSON:", data); if (!data.success) { uploadStatus.textContent = data.error || "Erreur lors de la conversion."; uploadStatus.className = "upload-status error"; return; } if (data.errors && data.errors.length > 0) { const errorMsgs = data.errors.map((e) => `${e.filename}: ${e.error}`).join("; "); uploadStatus.textContent = `Erreurs: ${errorMsgs}`; uploadStatus.className = "upload-status error"; } else { uploadStatus.textContent = `${data.results.length} fichier(s) converti(s) avec succès.`; uploadStatus.className = "upload-status success"; } if (data.results && data.results.length > 0) { displayResults(data.results); } loadHistory(); } catch (err) { console.error("DEBUG: Network or fetch error:", err); uploadStatus.textContent = "Erreur réseau. Vérifiez que le serveur est lancé et que le fichier/dossier n'est pas trop lourd (413)."; uploadStatus.className = "upload-status error"; } } // ============================ // Affichage des résultats (multi-fichier) // ============================ function displayResults(results) { allResults = results; resultCount.textContent = `${results.length} fichier(s) converti(s)`; resultList.innerHTML = ""; results.forEach((result, index) => { const item = document.createElement("div"); item.className = "result-item"; item.dataset.id = result.id; const uid = result.id || `result-${index}`; item.innerHTML = `
${escapeHtml(result.markdown)}
Aucune conversion pour le moment.
'; return; } historyList.innerHTML = entries .map( (entry, index) => `$2")
.replace(/`([^`]+)`/g, "$1")
.replace(/^###### (.+)$/gm, "")
.replace(/\n/g, "
");
return "
" + html + "
"; } // ============================ // Utilitaires // ============================ function escapeHtml(str) { const div = document.createElement("div"); div.textContent = str; return div.innerHTML; } function formatSize(bytes) { if (bytes < 1024) return `${bytes} o`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} Ko`; return `${(bytes / (1024 * 1024)).toFixed(1)} Mo`; } function showToast(message) { let toast = document.querySelector(".toast"); if (!toast) { toast = document.createElement("div"); toast.className = "toast"; document.body.appendChild(toast); } toast.textContent = message; toast.classList.add("visible"); clearTimeout(toast._timeout); toast._timeout = setTimeout(() => { toast.classList.remove("visible"); }, 2000); } loadHistory();