From cc6ae5d6065fb51e93f6e51c2073755d85e81b6b Mon Sep 17 00:00:00 2001 From: Bertrand Benjamin Date: Sat, 6 Sep 2025 16:58:17 +0200 Subject: [PATCH] feat: improve preview style --- script.js | 1822 +++++++++++++++++++++++++++-------------------------- style.css | 8 +- 2 files changed, 947 insertions(+), 883 deletions(-) diff --git a/script.js b/script.js index 0e7f957..1e9c4c1 100644 --- a/script.js +++ b/script.js @@ -1,359 +1,373 @@ // Configuration globale let config = { - baseUrl: '', - currentPath: '' + baseUrl: "", + currentPath: "", }; // Initialisation de l'application -document.addEventListener('DOMContentLoaded', function() { - initializeApp(); - setupEventListeners(); +document.addEventListener("DOMContentLoaded", function () { + initializeApp(); + setupEventListeners(); }); function initializeApp() { - // Tentative de configuration automatique depuis l'URL courante - autoConfigureFromURL(); - - // Affichage du formulaire de développement - showDevForm(); - + // Tentative de configuration automatique depuis l'URL courante + autoConfigureFromURL(); + + // Affichage du formulaire de développement + showDevForm(); } function setupEventListeners() { - // Formulaire de développement - const urlForm = document.getElementById('url-form'); - if (urlForm) { - urlForm.addEventListener('submit', handleUrlFormSubmit); - } - - // Gestion de la navigation arrière/avant du navigateur - window.addEventListener('popstate', handlePopState); - - // Gestionnaires pour la prévisualisation - const closePreview = document.getElementById('close-preview'); - if (closePreview) { - closePreview.addEventListener('click', hidePreview); - } - - const downloadFile = document.getElementById('download-file'); - if (downloadFile) { - downloadFile.addEventListener('click', downloadCurrentFile); - } + // Formulaire de développement + const urlForm = document.getElementById("url-form"); + if (urlForm) { + urlForm.addEventListener("submit", handleUrlFormSubmit); + } + + // Gestion de la navigation arrière/avant du navigateur + window.addEventListener("popstate", handlePopState); + + // Gestionnaires pour la prévisualisation + const closePreview = document.getElementById("close-preview"); + if (closePreview) { + closePreview.addEventListener("click", hidePreview); + } + + const downloadFile = document.getElementById("download-file"); + if (downloadFile) { + downloadFile.addEventListener("click", downloadCurrentFile); + } } function handlePopState(event) { - console.log('Navigation arrière/avant détectée:', event.state); - - if (event.state && event.state.baseUrl) { - // Restaurer la configuration - config.baseUrl = event.state.baseUrl; - config.currentPath = event.state.path || ''; - - updateConfigDisplay(); - - // Charger le contenu sans ajouter à l'historique (pour éviter les boucles) - loadBucketContents(config.currentPath, false); - } + console.log("Navigation arrière/avant détectée:", event.state); + + if (event.state && event.state.baseUrl) { + // Restaurer la configuration + config.baseUrl = event.state.baseUrl; + config.currentPath = event.state.path || ""; + + updateConfigDisplay(); + + // Charger le contenu sans ajouter à l'historique (pour éviter les boucles) + loadBucketContents(config.currentPath, false); + } } function autoConfigureFromURL() { - const currentUrl = window.location.href; - const url = new URL(currentUrl); - - // Configuration simplifiée : on prend juste le base URL sans le bucket - config.baseUrl = `${url.protocol}//${url.host}${url.pathname.replace('/index.html', '')}`; - - // Vérifier s'il y a un fragment d'URL pour définir le chemin initial - if (url.hash) { - const hashPath = url.hash.replace('#/', ''); - config.currentPath = hashPath; - } - - updateConfigDisplay(); - console.log('Configuration automatique:', config); - - // Charger automatiquement le contenu si on a une config valide - if (config.baseUrl) { - loadBucketContents(config.currentPath, false); - } + const currentUrl = window.location.href; + const url = new URL(currentUrl); + + // Configuration simplifiée : on prend juste le base URL sans le bucket + config.baseUrl = `${url.protocol}//${url.host}${url.pathname.replace("/index.html", "")}`; + + // Vérifier s'il y a un fragment d'URL pour définir le chemin initial + if (url.hash) { + const hashPath = url.hash.replace("#/", ""); + config.currentPath = hashPath; + } + + updateConfigDisplay(); + console.log("Configuration automatique:", config); + + // Charger automatiquement le contenu si on a une config valide + if (config.baseUrl) { + loadBucketContents(config.currentPath, false); + } } function handleUrlFormSubmit(event) { - event.preventDefault(); - - const urlInput = document.getElementById('minio-url'); - if (!urlInput) return; - - const minioUrl = urlInput.value.trim(); - - if (!minioUrl) { - showError('Veuillez saisir une URL MinIO'); - return; - } - - parseMinioURL(minioUrl); + event.preventDefault(); + + const urlInput = document.getElementById("minio-url"); + if (!urlInput) return; + + const minioUrl = urlInput.value.trim(); + + if (!minioUrl) { + showError("Veuillez saisir une URL MinIO"); + return; + } + + parseMinioURL(minioUrl); } function parseMinioURL(url) { - try { - const urlObj = new URL(url); - - // Configuration simplifiée : on prend l'URL complète comme base - config.baseUrl = url.replace(/\/$/, ''); // Supprimer le slash final si présent - config.currentPath = ''; - - updateConfigDisplay(); - - // Ajouter l'état initial à l'historique - const initialState = { path: '', baseUrl: config.baseUrl }; - history.replaceState(initialState, '', window.location.href); - - loadBucketContents(); - - } catch (error) { - showError('URL invalide: ' + error.message); - console.error('Erreur de parsing URL:', error); - } + try { + const urlObj = new URL(url); + + // Configuration simplifiée : on prend l'URL complète comme base + config.baseUrl = url.replace(/\/$/, ""); // Supprimer le slash final si présent + config.currentPath = ""; + + updateConfigDisplay(); + + // Ajouter l'état initial à l'historique + const initialState = { path: "", baseUrl: config.baseUrl }; + history.replaceState(initialState, "", window.location.href); + + loadBucketContents(); + } catch (error) { + showError("URL invalide: " + error.message); + console.error("Erreur de parsing URL:", error); + } } function updateConfigDisplay() { - const configInfo = document.getElementById('config-info'); - if (configInfo) { - configInfo.innerHTML = ` + const configInfo = document.getElementById("config-info"); + if (configInfo) { + configInfo.innerHTML = ` Configuration actuelle:
URL de base: ${config.baseUrl}
Chemin actuel: /${config.currentPath} `; - } + } } function showDevForm() { - const devForm = document.getElementById('dev-form'); - if (devForm) { - devForm.style.display = 'block'; - } + const devForm = document.getElementById("dev-form"); + if (devForm) { + devForm.style.display = "block"; + } } - function showError(message) { - const errorSection = document.getElementById('error-section'); - const errorMessage = document.getElementById('error-message'); - - if (errorMessage) { - errorMessage.textContent = message; - } - - if (errorSection) { - errorSection.style.display = 'block'; - - // Masquer l'erreur après 5 secondes - setTimeout(() => { - errorSection.style.display = 'none'; - }, 5000); - } + const errorSection = document.getElementById("error-section"); + const errorMessage = document.getElementById("error-message"); + + if (errorMessage) { + errorMessage.textContent = message; + } + + if (errorSection) { + errorSection.style.display = "block"; + + // Masquer l'erreur après 5 secondes + setTimeout(() => { + errorSection.style.display = "none"; + }, 5000); + } } function showLoading(show = true) { - const loading = document.getElementById('loading'); - const table = document.getElementById('files-table'); - - if (loading) { - loading.style.display = show ? 'block' : 'none'; - } - - if (table) { - table.style.display = show ? 'none' : 'table'; - } + const loading = document.getElementById("loading"); + const table = document.getElementById("files-table"); + + if (loading) { + loading.style.display = show ? "block" : "none"; + } + + if (table) { + table.style.display = show ? "none" : "table"; + } } function updateCurrentPathDisplay() { - const pathDisplay = document.getElementById('current-path'); - if (pathDisplay) { - pathDisplay.textContent = '/' + config.currentPath; - } + const pathDisplay = document.getElementById("current-path"); + if (pathDisplay) { + pathDisplay.textContent = "/" + config.currentPath; + } } -async function loadBucketContents(path = '', pushToHistory = true) { - console.log('Debug loadBucketContents - path received:', JSON.stringify(path)); - - if (!config.baseUrl) { - showError('Configuration URL manquante'); - return; +async function loadBucketContents(path = "", pushToHistory = true) { + console.log( + "Debug loadBucketContents - path received:", + JSON.stringify(path), + ); + + if (!config.baseUrl) { + showError("Configuration URL manquante"); + return; + } + + showLoading(true); + config.currentPath = path; + updateCurrentPathDisplay(); + + // Ajouter à l'historique du navigateur si demandé + if (pushToHistory) { + const state = { path: path, baseUrl: config.baseUrl }; + const url = path ? `#/${path}` : "#/"; + history.pushState(state, "", url); + } + + try { + // Construction de l'URL API avec delimiter + let apiUrl = `${config.baseUrl}?list-type=2&delimiter=/`; + + // Construire le prefix pour l'API MinIO + if (path) { + // Pour un sous-dossier + const cleanPath = path.startsWith("/") ? path.substring(1) : path; + const fullPrefix = cleanPath + "/"; + apiUrl += `&prefix=${encodeURIComponent(fullPrefix)}`; } - - showLoading(true); - config.currentPath = path; - updateCurrentPathDisplay(); - - // Ajouter à l'historique du navigateur si demandé - if (pushToHistory) { - const state = { path: path, baseUrl: config.baseUrl }; - const url = path ? `#/${path}` : '#/'; - history.pushState(state, '', url); - } - - try { - // Construction de l'URL API avec delimiter - let apiUrl = `${config.baseUrl}?list-type=2&delimiter=/`; - - // Construire le prefix pour l'API MinIO - if (path) { - // Pour un sous-dossier - const cleanPath = path.startsWith('/') ? path.substring(1) : path; - const fullPrefix = cleanPath + '/'; - apiUrl += `&prefix=${encodeURIComponent(fullPrefix)}`; - } - - console.log('Appel API MinIO:', apiUrl); - - const response = await fetch(apiUrl); - - if (!response.ok) { - throw new Error(`Erreur HTTP: ${response.status}`); - } - - const xmlText = await response.text(); - console.log('Réponse XML reçue:', xmlText); - - const contents = parseMinioXML(xmlText); - displayContents(contents); - - } catch (error) { - console.error('Erreur lors du chargement:', error); - showError('Erreur de chargement: ' + error.message); - } finally { - showLoading(false); + + console.log("Appel API MinIO:", apiUrl); + + const response = await fetch(apiUrl); + + if (!response.ok) { + throw new Error(`Erreur HTTP: ${response.status}`); } + + const xmlText = await response.text(); + console.log("Réponse XML reçue:", xmlText); + + const contents = parseMinioXML(xmlText); + displayContents(contents); + } catch (error) { + console.error("Erreur lors du chargement:", error); + showError("Erreur de chargement: " + error.message); + } finally { + showLoading(false); + } } function parseMinioXML(xmlText) { - const parser = new DOMParser(); - const xmlDoc = parser.parseFromString(xmlText, 'text/xml'); - - const contents = { - folders: [], - files: [] - }; - - // Déterminer le préfixe attendu basé sur la navigation actuelle - let expectedPrefix = ''; - if (config.currentPath) { - expectedPrefix = config.currentPath + '/'; - } - - // Parse CommonPrefixes (dossiers) - const prefixes = xmlDoc.getElementsByTagName('CommonPrefixes'); - for (let prefix of prefixes) { - const prefixElement = prefix.getElementsByTagName('Prefix')[0]; - if (prefixElement) { - const fullPrefix = prefixElement.textContent; - - // Extraire le nom du dossier (dernière partie avant le /) - const folderName = fullPrefix.replace(/\/$/, '').split('/').pop(); - - if (folderName) { - // Construire le chemin pour la navigation - let folderPath; - if (config.currentPath) { - folderPath = config.currentPath + '/' + folderName; - } else { - folderPath = folderName; - } - - console.log('Debug parsing - fullPrefix:', fullPrefix, 'folderName:', folderName, 'currentPath:', config.currentPath, 'folderPath:', folderPath); - - contents.folders.push({ - name: folderName, - path: folderPath - }); - } + const parser = new DOMParser(); + const xmlDoc = parser.parseFromString(xmlText, "text/xml"); + + const contents = { + folders: [], + files: [], + }; + + // Déterminer le préfixe attendu basé sur la navigation actuelle + let expectedPrefix = ""; + if (config.currentPath) { + expectedPrefix = config.currentPath + "/"; + } + + // Parse CommonPrefixes (dossiers) + const prefixes = xmlDoc.getElementsByTagName("CommonPrefixes"); + for (let prefix of prefixes) { + const prefixElement = prefix.getElementsByTagName("Prefix")[0]; + if (prefixElement) { + const fullPrefix = prefixElement.textContent; + + // Extraire le nom du dossier (dernière partie avant le /) + const folderName = fullPrefix.replace(/\/$/, "").split("/").pop(); + + if (folderName) { + // Construire le chemin pour la navigation + let folderPath; + if (config.currentPath) { + folderPath = config.currentPath + "/" + folderName; + } else { + folderPath = folderName; } + + console.log( + "Debug parsing - fullPrefix:", + fullPrefix, + "folderName:", + folderName, + "currentPath:", + config.currentPath, + "folderPath:", + folderPath, + ); + + contents.folders.push({ + name: folderName, + path: folderPath, + }); + } } - - // Parse Contents (fichiers) - const objects = xmlDoc.getElementsByTagName('Contents'); - for (let obj of objects) { - const keyElement = obj.getElementsByTagName('Key')[0]; - const sizeElement = obj.getElementsByTagName('Size')[0]; - const modifiedElement = obj.getElementsByTagName('LastModified')[0]; - - if (keyElement) { - const fullKey = keyElement.textContent; - const fileName = fullKey.split('/').pop(); - - // Vérifier que le fichier correspond au niveau de navigation actuel - let shouldInclude = false; - - if (config.currentPath) { - // Dans un sous-dossier : vérifier que la clé commence par le bon préfixe - const currentPrefix = config.currentPath + '/'; - if (fullKey.startsWith(currentPrefix)) { - // S'assurer qu'on n'affiche que les fichiers directs (pas dans des sous-sous-dossiers) - const relativePath = fullKey.replace(currentPrefix, ''); - if (!relativePath.includes('/')) { - shouldInclude = true; - } - } - } else { - // À la racine : afficher tous les fichiers qui ne sont pas dans des dossiers - if (!fullKey.includes('/')) { - shouldInclude = true; - } - } - - // Exclure les fichiers de l'application - if (shouldInclude && fileName && !['index.html', 'style.css', 'script.js'].includes(fileName)) { - contents.files.push({ - name: fileName, - size: sizeElement ? parseInt(sizeElement.textContent) : 0, - modified: modifiedElement ? new Date(modifiedElement.textContent) : new Date(), - fullKey: fullKey - }); - } + } + + // Parse Contents (fichiers) + const objects = xmlDoc.getElementsByTagName("Contents"); + for (let obj of objects) { + const keyElement = obj.getElementsByTagName("Key")[0]; + const sizeElement = obj.getElementsByTagName("Size")[0]; + const modifiedElement = obj.getElementsByTagName("LastModified")[0]; + + if (keyElement) { + const fullKey = keyElement.textContent; + const fileName = fullKey.split("/").pop(); + + // Vérifier que le fichier correspond au niveau de navigation actuel + let shouldInclude = false; + + if (config.currentPath) { + // Dans un sous-dossier : vérifier que la clé commence par le bon préfixe + const currentPrefix = config.currentPath + "/"; + if (fullKey.startsWith(currentPrefix)) { + // S'assurer qu'on n'affiche que les fichiers directs (pas dans des sous-sous-dossiers) + const relativePath = fullKey.replace(currentPrefix, ""); + if (!relativePath.includes("/")) { + shouldInclude = true; + } } + } else { + // À la racine : afficher tous les fichiers qui ne sont pas dans des dossiers + if (!fullKey.includes("/")) { + shouldInclude = true; + } + } + + // Exclure les fichiers de l'application + if ( + shouldInclude && + fileName && + !["index.html", "style.css", "script.js"].includes(fileName) + ) { + contents.files.push({ + name: fileName, + size: sizeElement ? parseInt(sizeElement.textContent) : 0, + modified: modifiedElement + ? new Date(modifiedElement.textContent) + : new Date(), + fullKey: fullKey, + }); + } } - - return contents; + } + + return contents; } function displayContents(contents) { - const tbody = document.getElementById('files-tbody'); - if (!tbody) return; - - tbody.innerHTML = ''; - - // Bouton parent directory si on n'est pas à la racine - if (config.currentPath) { - const parentPath = config.currentPath.split('/').slice(0, -1).join('/'); - const parentRow = createParentRow(parentPath); - tbody.appendChild(parentRow); - } - - // Afficher les dossiers - contents.folders.forEach(folder => { - const row = createFolderRow(folder); - tbody.appendChild(row); - }); - - // Afficher les fichiers - contents.files.forEach(file => { - const row = createFileRow(file); - tbody.appendChild(row); - }); - - // Afficher le tableau - const filesTable = document.getElementById('files-table'); - if (filesTable) { - filesTable.style.display = 'table'; - } - - // Chercher et ouvrir automatiquement index.rst s'il existe - autoOpenIndexRst(contents.files); + const tbody = document.getElementById("files-tbody"); + if (!tbody) return; + + tbody.innerHTML = ""; + + // Bouton parent directory si on n'est pas à la racine + if (config.currentPath) { + const parentPath = config.currentPath.split("/").slice(0, -1).join("/"); + const parentRow = createParentRow(parentPath); + tbody.appendChild(parentRow); + } + + // Afficher les dossiers + contents.folders.forEach((folder) => { + const row = createFolderRow(folder); + tbody.appendChild(row); + }); + + // Afficher les fichiers + contents.files.forEach((file) => { + const row = createFileRow(file); + tbody.appendChild(row); + }); + + // Afficher le tableau + const filesTable = document.getElementById("files-table"); + if (filesTable) { + filesTable.style.display = "table"; + } + + // Chercher et ouvrir automatiquement index.rst s'il existe + autoOpenIndexRst(contents.files); } function createParentRow(parentPath) { - const row = document.createElement('tr'); - row.innerHTML = ` + const row = document.createElement("tr"); + row.innerHTML = ` .. @@ -362,19 +376,19 @@ function createParentRow(parentPath) { - - `; - - const link = row.querySelector('.folder-link'); - link.addEventListener('click', (e) => { - e.preventDefault(); - loadBucketContents(parentPath, true); - }); - - return row; + + const link = row.querySelector(".folder-link"); + link.addEventListener("click", (e) => { + e.preventDefault(); + loadBucketContents(parentPath, true); + }); + + return row; } function createFolderRow(folder) { - const row = document.createElement('tr'); - row.innerHTML = ` + const row = document.createElement("tr"); + row.innerHTML = ` ${folder.name}/ @@ -383,21 +397,21 @@ function createFolderRow(folder) { - - `; - - const link = row.querySelector('.folder-link'); - link.addEventListener('click', (e) => { - e.preventDefault(); - loadBucketContents(folder.path, true); - }); - - return row; + + const link = row.querySelector(".folder-link"); + link.addEventListener("click", (e) => { + e.preventDefault(); + loadBucketContents(folder.path, true); + }); + + return row; } function createFileRow(file) { - const row = document.createElement('tr'); - const fileUrl = `${config.baseUrl}/${file.fullKey}`; - - row.innerHTML = ` + const row = document.createElement("tr"); + const fileUrl = `${config.baseUrl}/${file.fullKey}`; + + row.innerHTML = ` ${file.name} @@ -408,310 +422,333 @@ function createFileRow(file) { `; - - const fileLink = row.querySelector('.file-link'); - fileLink.addEventListener('click', (e) => { - e.preventDefault(); - showPreview(fileUrl, file.name); - }); - - const copyBtn = row.querySelector('.copy-btn'); - copyBtn.addEventListener('click', () => copyToClipboard(fileUrl)); - - return row; + + const fileLink = row.querySelector(".file-link"); + fileLink.addEventListener("click", (e) => { + e.preventDefault(); + showPreview(fileUrl, file.name); + }); + + const copyBtn = row.querySelector(".copy-btn"); + copyBtn.addEventListener("click", () => copyToClipboard(fileUrl)); + + return row; } function formatFileSize(bytes) { - if (bytes === 0) return '0 B'; - - const k = 1024; - const sizes = ['B', 'KB', 'MB', 'GB']; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - - return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; + if (bytes === 0) return "0 B"; + + const k = 1024; + const sizes = ["B", "KB", "MB", "GB"]; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i]; } function formatDate(date) { - return date.toLocaleString('fr-FR', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit' - }); + return date.toLocaleString("fr-FR", { + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + }); } async function copyToClipboard(text) { - try { - await navigator.clipboard.writeText(text); - console.log('Lien copié:', text); - - // Feedback visuel temporaire - const btn = event.target; - const originalText = btn.textContent; - btn.textContent = 'Copié !'; - setTimeout(() => { - btn.textContent = originalText; - }, 1000); - - } catch (error) { - console.error('Erreur copie clipboard:', error); - - // Fallback pour les navigateurs plus anciens - const textArea = document.createElement('textarea'); - textArea.value = text; - document.body.appendChild(textArea); - textArea.select(); - document.execCommand('copy'); - document.body.removeChild(textArea); - } + try { + await navigator.clipboard.writeText(text); + console.log("Lien copié:", text); + + // Feedback visuel temporaire + const btn = event.target; + const originalText = btn.textContent; + btn.textContent = "Copié !"; + setTimeout(() => { + btn.textContent = originalText; + }, 1000); + } catch (error) { + console.error("Erreur copie clipboard:", error); + + // Fallback pour les navigateurs plus anciens + const textArea = document.createElement("textarea"); + textArea.value = text; + document.body.appendChild(textArea); + textArea.select(); + document.execCommand("copy"); + document.body.removeChild(textArea); + } } // Variables globales pour la prévisualisation let currentPreviewFile = { - url: '', - name: '' + url: "", + name: "", }; function detectFileType(fileName) { - const extension = fileName.toLowerCase().split('.').pop(); - - const fileTypes = { - images: ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp'], - text: ['txt', 'json', 'xml', 'csv', 'log'], - code: ['js', 'css', 'html', 'htm', 'py', 'java', 'cpp', 'c', 'php', 'rb', 'go', 'rs', 'ts', 'jsx', 'tsx', 'vue', 'svelte', 'tex', 'latex'], - markdown: ['md', 'markdown'], - rst: ['rst', 'rest'], - pdf: ['pdf'], - video: ['mp4', 'avi', 'mov', 'wmv', 'flv', 'webm', 'mkv'], - audio: ['mp3', 'wav', 'ogg', 'aac', 'flac', 'm4a'], - archive: ['zip', 'rar', '7z', 'tar', 'gz', 'bz2'] - }; - - for (const [type, extensions] of Object.entries(fileTypes)) { - if (extensions.includes(extension)) { - return type; - } + const extension = fileName.toLowerCase().split(".").pop(); + + const fileTypes = { + images: ["jpg", "jpeg", "png", "gif", "bmp", "svg", "webp"], + text: ["txt", "json", "xml", "csv", "log"], + code: [ + "js", + "css", + "html", + "htm", + "py", + "java", + "cpp", + "c", + "php", + "rb", + "go", + "rs", + "ts", + "jsx", + "tsx", + "vue", + "svelte", + "tex", + "latex", + ], + markdown: ["md", "markdown"], + rst: ["rst", "rest"], + pdf: ["pdf"], + video: ["mp4", "avi", "mov", "wmv", "flv", "webm", "mkv"], + audio: ["mp3", "wav", "ogg", "aac", "flac", "m4a"], + archive: ["zip", "rar", "7z", "tar", "gz", "bz2"], + }; + + for (const [type, extensions] of Object.entries(fileTypes)) { + if (extensions.includes(extension)) { + return type; } - - return 'unknown'; + } + + return "unknown"; } function showPreview(fileUrl, fileName) { - currentPreviewFile = { url: fileUrl, name: fileName }; - - const previewSection = document.getElementById('preview-section'); - const previewTitle = document.getElementById('preview-title'); - const previewContent = document.getElementById('preview-content'); - - if (!previewSection || !previewTitle || !previewContent) { - console.error('Éléments de prévisualisation non trouvés'); - return; - } - - previewTitle.textContent = `Prévisualisation: ${fileName}`; - previewContent.innerHTML = '
Chargement...
'; - - previewSection.style.display = 'flex'; - - const fileType = detectFileType(fileName); - - switch (fileType) { - case 'images': - showImagePreview(fileUrl, previewContent); - break; - case 'text': - showTextPreview(fileUrl, previewContent); - break; - case 'code': - showCodePreview(fileUrl, fileName, previewContent); - break; - case 'markdown': - showMarkdownPreview(fileUrl, previewContent); - break; - case 'rst': - showRstPreview(fileUrl, previewContent); - break; - case 'pdf': - showPdfPreview(fileUrl, previewContent); - break; - case 'video': - showVideoPreview(fileUrl, previewContent); - break; - case 'audio': - showAudioPreview(fileUrl, previewContent); - break; - default: - showUnsupportedPreview(fileName, previewContent); - } + currentPreviewFile = { url: fileUrl, name: fileName }; + + const previewSection = document.getElementById("preview-section"); + const previewTitle = document.getElementById("preview-title"); + const previewContent = document.getElementById("preview-content"); + + if (!previewSection || !previewTitle || !previewContent) { + console.error("Éléments de prévisualisation non trouvés"); + return; + } + + previewTitle.textContent = `Prévisualisation: ${fileName}`; + previewContent.innerHTML = + '
Chargement...
'; + + previewSection.style.display = "flex"; + + const fileType = detectFileType(fileName); + + switch (fileType) { + case "images": + showImagePreview(fileUrl, previewContent); + break; + case "text": + showTextPreview(fileUrl, previewContent); + break; + case "code": + showCodePreview(fileUrl, fileName, previewContent); + break; + case "markdown": + showMarkdownPreview(fileUrl, previewContent); + break; + case "rst": + showRstPreview(fileUrl, previewContent); + break; + case "pdf": + showPdfPreview(fileUrl, previewContent); + break; + case "video": + showVideoPreview(fileUrl, previewContent); + break; + case "audio": + showAudioPreview(fileUrl, previewContent); + break; + default: + showUnsupportedPreview(fileName, previewContent); + } } function showImagePreview(fileUrl, container) { - const img = document.createElement('img'); - img.src = fileUrl; - img.alt = 'Prévisualisation image'; - - img.onload = () => { - container.innerHTML = ''; - container.appendChild(img); - }; - - img.onerror = () => { - container.innerHTML = '
Erreur: Impossible de charger l\'image
'; - }; + const img = document.createElement("img"); + img.src = fileUrl; + img.alt = "Prévisualisation image"; + + img.onload = () => { + container.innerHTML = ""; + container.appendChild(img); + }; + + img.onerror = () => { + container.innerHTML = + '
Erreur: Impossible de charger l\'image
'; + }; } function showTextPreview(fileUrl, container) { - fetch(fileUrl) - .then(response => { - if (!response.ok) { - throw new Error(`Erreur HTTP: ${response.status}`); - } - return response.text(); - }) - .then(text => { - const pre = document.createElement('pre'); - pre.textContent = text; - container.innerHTML = ''; - container.appendChild(pre); - }) - .catch(error => { - console.error('Erreur chargement texte:', error); - container.innerHTML = '
Erreur: Impossible de charger le fichier texte
'; - }); + fetch(fileUrl) + .then((response) => { + if (!response.ok) { + throw new Error(`Erreur HTTP: ${response.status}`); + } + return response.text(); + }) + .then((text) => { + const pre = document.createElement("pre"); + pre.textContent = text; + container.innerHTML = ""; + container.appendChild(pre); + }) + .catch((error) => { + console.error("Erreur chargement texte:", error); + container.innerHTML = + '
Erreur: Impossible de charger le fichier texte
'; + }); } function detectLanguageFromExtension(fileName) { - const extension = fileName.toLowerCase().split('.').pop(); - - const languageMap = { - 'js': 'javascript', - 'jsx': 'jsx', - 'ts': 'typescript', - 'tsx': 'tsx', - 'py': 'python', - 'java': 'java', - 'c': 'c', - 'cpp': 'cpp', - 'cxx': 'cpp', - 'cc': 'cpp', - 'php': 'php', - 'rb': 'ruby', - 'go': 'go', - 'rs': 'rust', - 'html': 'html', - 'htm': 'html', - 'css': 'css', - 'scss': 'scss', - 'sass': 'sass', - 'less': 'less', - 'json': 'json', - 'xml': 'xml', - 'vue': 'vue', - 'svelte': 'svelte', - 'sh': 'bash', - 'bash': 'bash', - 'zsh': 'bash', - 'fish': 'bash', - 'ps1': 'powershell', - 'sql': 'sql', - 'r': 'r', - 'matlab': 'matlab', - 'm': 'matlab', - 'swift': 'swift', - 'kt': 'kotlin', - 'scala': 'scala', - 'clj': 'clojure', - 'hs': 'haskell', - 'lua': 'lua', - 'pl': 'perl', - 'tex': 'latex', - 'latex': 'latex', - 'md': 'markdown', - 'markdown': 'markdown', - 'yml': 'yaml', - 'yaml': 'yaml', - 'toml': 'toml', - 'ini': 'ini', - 'conf': 'bash', - 'config': 'bash' - }; - - return languageMap[extension] || 'text'; + const extension = fileName.toLowerCase().split(".").pop(); + + const languageMap = { + js: "javascript", + jsx: "jsx", + ts: "typescript", + tsx: "tsx", + py: "python", + java: "java", + c: "c", + cpp: "cpp", + cxx: "cpp", + cc: "cpp", + php: "php", + rb: "ruby", + go: "go", + rs: "rust", + html: "html", + htm: "html", + css: "css", + scss: "scss", + sass: "sass", + less: "less", + json: "json", + xml: "xml", + vue: "vue", + svelte: "svelte", + sh: "bash", + bash: "bash", + zsh: "bash", + fish: "bash", + ps1: "powershell", + sql: "sql", + r: "r", + matlab: "matlab", + m: "matlab", + swift: "swift", + kt: "kotlin", + scala: "scala", + clj: "clojure", + hs: "haskell", + lua: "lua", + pl: "perl", + tex: "latex", + latex: "latex", + md: "markdown", + markdown: "markdown", + yml: "yaml", + yaml: "yaml", + toml: "toml", + ini: "ini", + conf: "bash", + config: "bash", + }; + + return languageMap[extension] || "text"; } function showCodePreview(fileUrl, fileName, container) { - fetch(fileUrl) - .then(response => { - if (!response.ok) { - throw new Error(`Erreur HTTP: ${response.status}`); - } - return response.text(); - }) - .then(text => { - const language = detectLanguageFromExtension(fileName); - - const pre = document.createElement('pre'); - const code = document.createElement('code'); - - if (language !== 'text') { - code.className = `language-${language}`; - } - - code.textContent = text; - pre.appendChild(code); - - container.innerHTML = ''; - container.appendChild(pre); - - // Appliquer la coloration syntaxique si Prism est disponible - if (window.Prism && language !== 'text') { - try { - Prism.highlightElement(code); - } catch (error) { - console.warn('Erreur coloration syntaxique:', error); - } - } - }) - .catch(error => { - console.error('Erreur chargement code:', error); - container.innerHTML = '
Erreur: Impossible de charger le fichier
'; - }); + fetch(fileUrl) + .then((response) => { + if (!response.ok) { + throw new Error(`Erreur HTTP: ${response.status}`); + } + return response.text(); + }) + .then((text) => { + const language = detectLanguageFromExtension(fileName); + + const pre = document.createElement("pre"); + const code = document.createElement("code"); + + if (language !== "text") { + code.className = `language-${language}`; + } + + code.textContent = text; + pre.appendChild(code); + + container.innerHTML = ""; + container.appendChild(pre); + + // Appliquer la coloration syntaxique si Prism est disponible + if (window.Prism && language !== "text") { + try { + Prism.highlightElement(code); + } catch (error) { + console.warn("Erreur coloration syntaxique:", error); + } + } + }) + .catch((error) => { + console.error("Erreur chargement code:", error); + container.innerHTML = + '
Erreur: Impossible de charger le fichier
'; + }); } function showPdfPreview(fileUrl, container) { - const iframe = document.createElement('iframe'); - iframe.src = fileUrl; - iframe.style.width = '100%'; - iframe.style.height = '500px'; - - container.innerHTML = ''; - container.appendChild(iframe); + const iframe = document.createElement("iframe"); + iframe.src = fileUrl; + iframe.style.width = "100%"; + iframe.style.height = "100%"; + + container.innerHTML = ""; + container.appendChild(iframe); } function showVideoPreview(fileUrl, container) { - const video = document.createElement('video'); - video.src = fileUrl; - video.controls = true; - video.style.maxWidth = '100%'; - - container.innerHTML = ''; - container.appendChild(video); + const video = document.createElement("video"); + video.src = fileUrl; + video.controls = true; + video.style.maxWidth = "100%"; + + container.innerHTML = ""; + container.appendChild(video); } function showAudioPreview(fileUrl, container) { - const audio = document.createElement('audio'); - audio.src = fileUrl; - audio.controls = true; - audio.style.width = '100%'; - - container.innerHTML = ''; - container.appendChild(audio); + const audio = document.createElement("audio"); + audio.src = fileUrl; + audio.controls = true; + audio.style.width = "100%"; + + container.innerHTML = ""; + container.appendChild(audio); } function showUnsupportedPreview(fileName, container) { - const extension = fileName.toLowerCase().split('.').pop(); - container.innerHTML = ` + const extension = fileName.toLowerCase().split(".").pop(); + container.innerHTML = `

Prévisualisation non disponible pour ce type de fichier (.${extension})

Utilisez le bouton "Télécharger" ci-dessous pour obtenir le fichier.

@@ -720,323 +757,354 @@ function showUnsupportedPreview(fileName, container) { } function hidePreview() { - const previewSection = document.getElementById('preview-section'); - if (previewSection) { - previewSection.style.display = 'none'; - } - - currentPreviewFile = { url: '', name: '' }; + const previewSection = document.getElementById("preview-section"); + if (previewSection) { + previewSection.style.display = "none"; + } + + currentPreviewFile = { url: "", name: "" }; } function downloadCurrentFile() { - if (currentPreviewFile.url) { - const link = document.createElement('a'); - link.href = currentPreviewFile.url; - link.download = currentPreviewFile.name; - link.target = '_blank'; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - } + if (currentPreviewFile.url) { + const link = document.createElement("a"); + link.href = currentPreviewFile.url; + link.download = currentPreviewFile.name; + link.target = "_blank"; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } } // Fonction pour ouvrir automatiquement index.rst s'il existe function autoOpenIndexRst(files) { - // Chercher un fichier index.rst dans la liste - const indexRstFile = files.find(file => - file.name.toLowerCase() === 'index.rst' - ); - - if (indexRstFile) { - // Construire l'URL complète du fichier - const fileUrl = `${config.baseUrl}/${indexRstFile.fullKey}`; - - // Ouvrir automatiquement la prévisualisation - console.log('Auto-ouverture de index.rst trouvé:', indexRstFile.name); - showPreview(fileUrl, indexRstFile.name); - } else { - // Pas d'index.rst trouvé, fermer le preview s'il est ouvert - console.log('Aucun index.rst trouvé, fermeture du preview'); - hidePreview(); - } + // Chercher un fichier index.rst dans la liste + const indexRstFile = files.find( + (file) => file.name.toLowerCase() === "index.rst", + ); + + if (indexRstFile) { + // Construire l'URL complète du fichier + const fileUrl = `${config.baseUrl}/${indexRstFile.fullKey}`; + + // Ouvrir automatiquement la prévisualisation + console.log("Auto-ouverture de index.rst trouvé:", indexRstFile.name); + showPreview(fileUrl, indexRstFile.name); + } else { + // Pas d'index.rst trouvé, fermer le preview s'il est ouvert + console.log("Aucun index.rst trouvé, fermeture du preview"); + hidePreview(); + } } // Fonction simple de parsing Markdown vers HTML function parseMarkdown(text) { - let html = text; - - // Headers - html = html.replace(/^### (.*$)/gim, '

$1

'); - html = html.replace(/^## (.*$)/gim, '

$1

'); - html = html.replace(/^# (.*$)/gim, '

$1

'); - - // Bold - html = html.replace(/\*\*(.*?)\*\*/g, '$1'); - html = html.replace(/__(.*?)__/g, '$1'); - - // Italic - html = html.replace(/\*(.*?)\*/g, '$1'); - html = html.replace(/_(.*?)_/g, '$1'); - - // Code inline - html = html.replace(/`(.*?)`/g, '$1'); - - // Code blocks - html = html.replace(/```([\s\S]*?)```/g, '
$1
'); - - // Links - html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); - - // Images - html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '$1'); - - // Lists - html = html.replace(/^\* (.*$)/gim, '
  • $1
  • '); - html = html.replace(/^- (.*$)/gim, '
  • $1
  • '); - html = html.replace(/^\d+\. (.*$)/gim, '
  • $1
  • '); - - // Wrap consecutive
  • items in
      - html = html.replace(/(
    • .*<\/li>)/gs, (match) => { - return '
        ' + match + '
      '; - }); - - // Line breaks - html = html.replace(/\n\n/g, '

      '); - html = '

      ' + html + '

      '; - - // Clean up empty paragraphs - html = html.replace(/

      <\/p>/g, ''); - html = html.replace(/

      ()/g, '$1'); - html = html.replace(/(<\/h[1-6]>)<\/p>/g, '$1'); - html = html.replace(/

      (

        )/g, '$1'); - html = html.replace(/(<\/ul>)<\/p>/g, '$1'); - html = html.replace(/

        (

        )/g, '$1');
        -    html = html.replace(/(<\/pre>)<\/p>/g, '$1');
        -    
        -    return html;
        +  let html = text;
        +
        +  // Headers
        +  html = html.replace(/^### (.*$)/gim, "

        $1

        "); + html = html.replace(/^## (.*$)/gim, "

        $1

        "); + html = html.replace(/^# (.*$)/gim, "

        $1

        "); + + // Bold + html = html.replace(/\*\*(.*?)\*\*/g, "$1"); + html = html.replace(/__(.*?)__/g, "$1"); + + // Italic + html = html.replace(/\*(.*?)\*/g, "$1"); + html = html.replace(/_(.*?)_/g, "$1"); + + // Code inline + html = html.replace(/`(.*?)`/g, "$1"); + + // Code blocks + html = html.replace(/```([\s\S]*?)```/g, "
        $1
        "); + + // Links + html = html.replace( + /\[([^\]]+)\]\(([^)]+)\)/g, + '$1', + ); + + // Images + html = html.replace( + /!\[([^\]]*)\]\(([^)]+)\)/g, + '$1', + ); + + // Lists + html = html.replace(/^\* (.*$)/gim, "
      • $1
      • "); + html = html.replace(/^- (.*$)/gim, "
      • $1
      • "); + html = html.replace(/^\d+\. (.*$)/gim, "
      • $1
      • "); + + // Wrap consecutive
      • items in
          + html = html.replace(/(
        • .*<\/li>)/gs, (match) => { + return "
            " + match + "
          "; + }); + + // Line breaks + html = html.replace(/\n\n/g, "

          "); + html = "

          " + html + "

          "; + + // Clean up empty paragraphs + html = html.replace(/

          <\/p>/g, ""); + html = html.replace(/

          ()/g, "$1"); + html = html.replace(/(<\/h[1-6]>)<\/p>/g, "$1"); + html = html.replace(/

          (

            )/g, "$1"); + html = html.replace(/(<\/ul>)<\/p>/g, "$1"); + html = html.replace(/

            (

            )/g, "$1");
            +  html = html.replace(/(<\/pre>)<\/p>/g, "$1");
            +
            +  return html;
             }
             
             // Fonction simple de parsing reStructuredText vers HTML
             function parseRst(text) {
            -    let html = text;
            -    
            -    // Créer un système de placeholders pour éviter les liens imbriqués
            -    const linkPlaceholders = new Map();
            -    let placeholderIndex = 0;
            -    
            -    // Fonction pour créer un placeholder unique
            -    const createLinkPlaceholder = (linkText, linkUrl) => {
            -        const placeholder = `__LINK_PLACEHOLDER_${placeholderIndex++}__`;
            -        linkPlaceholders.set(placeholder, `${linkText}`);
            -        return placeholder;
            -    };
            -    
            -    // Headers (RST style with underlines) - ordre important pour éviter les conflits
            -    html = html.replace(/^(.*)\n#{3,}$/gim, '

            $1

            '); - html = html.replace(/^(.*)\n={3,}$/gim, '

            $1

            '); - html = html.replace(/^(.*)\n-{3,}$/gim, '

            $1

            '); - html = html.replace(/^(.*)\n~{3,}$/gim, '

            $1

            '); - html = html.replace(/^(.*)\n\^{3,}$/gim, '
            $1
            '); - - // Metadata fields - html = html.replace(/^:([^:]+):\s*(.*)$/gim, (match, key, value) => { - // Remplacer les liens par des placeholders dans les métadonnées - const processedValue = value.replace(/`([^<]+) <([^>]+)>`_/g, (match, text, url) => { - return createLinkPlaceholder(text, url); - }); - return ``; - }); - - // Process directives with their options and content - regex corrigée pour s'arrêter aux lignes non indentées - html = html.replace(/\.\. ([^:]+)::([\s\S]*?)(?=\n\S|\.\.|$)/g, (match, directive, block) => { - // Gestion spéciale pour big_button - const isBigButton = directive === 'big_button'; - const className = isBigButton ? 'rst-big-button' : `rst-directive rst-directive-${directive}`; - - let result = `
            `; - - if (block && block.trim()) { - const lines = block.split('\n'); - let options = ''; - let content = ''; - let inContentSection = false; - - // Séparer d'abord les lignes d'options et de contenu - let optionLines = []; - let contentLines = []; - let isInOptions = true; - - for (const line of lines) { - if (!line.trim()) { - // Ligne vide - si on est en section contenu, l'ajouter - if (!isInOptions) { - contentLines.push(line); - } - continue; - } - - // Vérifier si c'est une option (:key: value) - const optionMatch = line.match(/^[ \t]*:([^:]+):\s*(.*)$/); - - if (optionMatch && isInOptions) { - // C'est une option et on est toujours dans la section options - optionLines.push(line); - } else { - // C'est du contenu - marquer qu'on est sorti de la section options - isInOptions = false; - contentLines.push(line); - } - } - - // Traiter les options - for (const line of optionLines) { - const optionMatch = line.match(/^[ \t]*:([^:]+):\s*(.*)$/); - if (optionMatch) { - const [, key, value] = optionMatch; - const processedValue = value.replace(/`([^<]+) <([^>]+)>`_/g, (match, text, url) => { - return createLinkPlaceholder(text, url); - }); - options += `
            ${key}: ${processedValue}
            `; - } - } - - // Traiter le contenu - trouver l'indentation minimale du contenu uniquement - if (contentLines.length > 0) { - // Trouver l'indentation minimale parmi les lignes de contenu non vides - let minContentIndent = null; - for (const line of contentLines) { - if (line.trim()) { - const indentMatch = line.match(/^([ \t]*)/); - if (indentMatch) { - const indent = indentMatch[1]; - if (minContentIndent === null || indent.length < minContentIndent.length) { - minContentIndent = indent; - } - } - } - } - - // Construire le contenu en préservant l'indentation relative - for (const line of contentLines) { - if (!line.trim()) { - content += '\n'; - } else { - let contentLine = line; - if (minContentIndent && line.startsWith(minContentIndent)) { - contentLine = line.substring(minContentIndent.length); - } else { - // Fallback : enlever toute indentation de début - contentLine = line.replace(/^[ \t]+/, ''); - } - content += contentLine + '\n'; - } - } - } - - result += options; - - if (content.trim()) { - const processedContent = content.trim().replace(/`([^<]+) <([^>]+)>`_/g, (match, text, url) => { - return createLinkPlaceholder(text, url); - }); - result += `
            ${processedContent}
            `; - } - } - - result += '
            '; - return result; - }); - - // Bold - html = html.replace(/\*\*(.*?)\*\*/g, '$1'); - - // Italic - html = html.replace(/\*(.*?)\*/g, '$1'); - - // Code inline - html = html.replace(/``(.*?)``/g, '$1'); - - // Code blocks (:: at end of line followed by indented block) - html = html.replace(/::\s*\n\n((?: .*\n?)+)/g, '
            $1
            '); - - // Links restants dans le texte principal - html = html.replace(/`([^<]+) <([^>]+)>`_/g, (match, text, url) => { + let html = text; + + // Créer un système de placeholders pour éviter les liens imbriqués + const linkPlaceholders = new Map(); + let placeholderIndex = 0; + + // Fonction pour créer un placeholder unique + const createLinkPlaceholder = (linkText, linkUrl) => { + const placeholder = `__LINK_PLACEHOLDER_${placeholderIndex++}__`; + linkPlaceholders.set( + placeholder, + `${linkText}`, + ); + return placeholder; + }; + + // Headers (RST style with underlines) - ordre important pour éviter les conflits + html = html.replace(/^(.*)\n#{3,}$/gim, "

            $1

            "); + html = html.replace(/^(.*)\n={3,}$/gim, "

            $1

            "); + html = html.replace(/^(.*)\n-{3,}$/gim, "

            $1

            "); + html = html.replace(/^(.*)\n~{3,}$/gim, "

            $1

            "); + html = html.replace(/^(.*)\n\^{3,}$/gim, "
            $1
            "); + + // Metadata fields + html = html.replace(/^:([^:]+):\s*(.*)$/gim, (match, key, value) => { + // Remplacer les liens par des placeholders dans les métadonnées + const processedValue = value.replace( + /`([^<]+) <([^>]+)>`_/g, + (match, text, url) => { return createLinkPlaceholder(text, url); - }); - - // Lists - html = html.replace(/^\* (.*$)/gim, '
          • $1
          • '); - html = html.replace(/^- (.*$)/gim, '
          • $1
          • '); - html = html.replace(/^\d+\. (.*$)/gim, '
          • $1
          • '); - - // Wrap consecutive
          • items in
              - html = html.replace(/(
            • .*<\/li>)/gs, (match) => { - return '
                ' + match + '
              '; - }); - - // Line breaks - html = html.replace(/\n\n/g, '

              '); - html = '

              ' + html + '

              '; - - // Clean up empty paragraphs and fix structure - html = html.replace(/

              <\/p>/g, ''); - html = html.replace(/

              ()/g, '$1'); - html = html.replace(/(<\/h[1-6]>)<\/p>/g, '$1'); - html = html.replace(/

              (

              )<\/p>/g, '$1'); - html = html.replace(/

              (

                )/g, '$1'); - html = html.replace(/(<\/ul>)<\/p>/g, '$1'); - html = html.replace(/

                (

                )/g, '$1');
                -    html = html.replace(/(<\/pre>)<\/p>/g, '$1');
                -    
                -    // Restaurer tous les liens à partir des placeholders
                -    for (const [placeholder, linkHtml] of linkPlaceholders) {
                -        html = html.replace(new RegExp(placeholder, 'g'), linkHtml);
                -    }
                -    
                -    return html;
                +      },
                +    );
                +    return ``;
                +  });
                +
                +  // Process directives with their options and content - regex corrigée pour s'arrêter aux lignes non indentées
                +  html = html.replace(
                +    /\.\. ([^:]+)::([\s\S]*?)(?=\n\S|\.\.|$)/g,
                +    (match, directive, block) => {
                +      // Gestion spéciale pour big_button
                +      const isBigButton = directive === "big_button";
                +      const className = isBigButton
                +        ? "rst-big-button"
                +        : `rst-directive rst-directive-${directive}`;
                +
                +      let result = `
                `; + + if (block && block.trim()) { + const lines = block.split("\n"); + let options = ""; + let content = ""; + let inContentSection = false; + + // Séparer d'abord les lignes d'options et de contenu + let optionLines = []; + let contentLines = []; + let isInOptions = true; + + for (const line of lines) { + if (!line.trim()) { + // Ligne vide - si on est en section contenu, l'ajouter + if (!isInOptions) { + contentLines.push(line); + } + continue; + } + + // Vérifier si c'est une option (:key: value) + const optionMatch = line.match(/^[ \t]*:([^:]+):\s*(.*)$/); + + if (optionMatch && isInOptions) { + // C'est une option et on est toujours dans la section options + optionLines.push(line); + } else { + // C'est du contenu - marquer qu'on est sorti de la section options + isInOptions = false; + contentLines.push(line); + } + } + + // Traiter les options + for (const line of optionLines) { + const optionMatch = line.match(/^[ \t]*:([^:]+):\s*(.*)$/); + if (optionMatch) { + const [, key, value] = optionMatch; + const processedValue = value.replace( + /`([^<]+) <([^>]+)>`_/g, + (match, text, url) => { + return createLinkPlaceholder(text, url); + }, + ); + options += `
                ${key}: ${processedValue}
                `; + } + } + + // Traiter le contenu - trouver l'indentation minimale du contenu uniquement + if (contentLines.length > 0) { + // Trouver l'indentation minimale parmi les lignes de contenu non vides + let minContentIndent = null; + for (const line of contentLines) { + if (line.trim()) { + const indentMatch = line.match(/^([ \t]*)/); + if (indentMatch) { + const indent = indentMatch[1]; + if ( + minContentIndent === null || + indent.length < minContentIndent.length + ) { + minContentIndent = indent; + } + } + } + } + + // Construire le contenu en préservant l'indentation relative + for (const line of contentLines) { + if (!line.trim()) { + content += "\n"; + } else { + let contentLine = line; + if (minContentIndent && line.startsWith(minContentIndent)) { + contentLine = line.substring(minContentIndent.length); + } else { + // Fallback : enlever toute indentation de début + contentLine = line.replace(/^[ \t]+/, ""); + } + content += contentLine + "\n"; + } + } + } + + result += options; + + if (content.trim()) { + const processedContent = content + .trim() + .replace(/`([^<]+) <([^>]+)>`_/g, (match, text, url) => { + return createLinkPlaceholder(text, url); + }); + result += `
                ${processedContent}
                `; + } + } + + result += "
                "; + return result; + }, + ); + + // Bold + html = html.replace(/\*\*(.*?)\*\*/g, "$1"); + + // Italic + html = html.replace(/\*(.*?)\*/g, "$1"); + + // Code inline + html = html.replace(/``(.*?)``/g, "$1"); + + // Code blocks (:: at end of line followed by indented block) + html = html.replace( + /::\s*\n\n((?: .*\n?)+)/g, + "
                $1
                ", + ); + + // Links restants dans le texte principal + html = html.replace(/`([^<]+) <([^>]+)>`_/g, (match, text, url) => { + return createLinkPlaceholder(text, url); + }); + + // Lists + html = html.replace(/^\* (.*$)/gim, "
              • $1
              • "); + html = html.replace(/^- (.*$)/gim, "
              • $1
              • "); + html = html.replace(/^\d+\. (.*$)/gim, "
              • $1
              • "); + + // Wrap consecutive
              • items in
                  + html = html.replace(/(
                • .*<\/li>)/gs, (match) => { + return "
                    " + match + "
                  "; + }); + + // Line breaks + html = html.replace(/\n\n/g, "

                  "); + html = "

                  " + html + "

                  "; + + // Clean up empty paragraphs and fix structure + html = html.replace(/

                  <\/p>/g, ""); + html = html.replace(/

                  ()/g, "$1"); + html = html.replace(/(<\/h[1-6]>)<\/p>/g, "$1"); + html = html.replace(/

                  (

                  )<\/p>/g, "$1"); + html = html.replace(/

                  (

                    )/g, "$1"); + html = html.replace(/(<\/ul>)<\/p>/g, "$1"); + html = html.replace(/

                    (

                    )/g, "$1");
                    +  html = html.replace(/(<\/pre>)<\/p>/g, "$1");
                    +
                    +  // Restaurer tous les liens à partir des placeholders
                    +  for (const [placeholder, linkHtml] of linkPlaceholders) {
                    +    html = html.replace(new RegExp(placeholder, "g"), linkHtml);
                    +  }
                    +
                    +  return html;
                     }
                     
                     function showMarkdownPreview(fileUrl, container) {
                    -    fetch(fileUrl)
                    -        .then(response => {
                    -            if (!response.ok) {
                    -                throw new Error(`Erreur HTTP: ${response.status}`);
                    -            }
                    -            return response.text();
                    -        })
                    -        .then(text => {
                    -            const htmlContent = parseMarkdown(text);
                    -            const div = document.createElement('div');
                    -            div.className = 'markdown-content';
                    -            div.innerHTML = htmlContent;
                    -            container.innerHTML = '';
                    -            container.appendChild(div);
                    -        })
                    -        .catch(error => {
                    -            console.error('Erreur chargement markdown:', error);
                    -            container.innerHTML = '
                    Erreur: Impossible de charger le fichier Markdown
                    '; - }); + fetch(fileUrl) + .then((response) => { + if (!response.ok) { + throw new Error(`Erreur HTTP: ${response.status}`); + } + return response.text(); + }) + .then((text) => { + const htmlContent = parseMarkdown(text); + const div = document.createElement("div"); + div.className = "markdown-content"; + div.innerHTML = htmlContent; + container.innerHTML = ""; + container.appendChild(div); + }) + .catch((error) => { + console.error("Erreur chargement markdown:", error); + container.innerHTML = + '
                    Erreur: Impossible de charger le fichier Markdown
                    '; + }); } function showRstPreview(fileUrl, container) { - fetch(fileUrl) - .then(response => { - if (!response.ok) { - throw new Error(`Erreur HTTP: ${response.status}`); - } - return response.text(); - }) - .then(text => { - const htmlContent = parseRst(text); - const div = document.createElement('div'); - div.className = 'rst-content'; - div.innerHTML = htmlContent; - container.innerHTML = ''; - container.appendChild(div); - }) - .catch(error => { - console.error('Erreur chargement RST:', error); - container.innerHTML = '
                    Erreur: Impossible de charger le fichier reStructuredText
                    '; - }); -} \ No newline at end of file + fetch(fileUrl) + .then((response) => { + if (!response.ok) { + throw new Error(`Erreur HTTP: ${response.status}`); + } + return response.text(); + }) + .then((text) => { + const htmlContent = parseRst(text); + const div = document.createElement("div"); + div.className = "rst-content"; + div.innerHTML = htmlContent; + container.innerHTML = ""; + container.appendChild(div); + }) + .catch((error) => { + console.error("Erreur chargement RST:", error); + container.innerHTML = + '
                    Erreur: Impossible de charger le fichier reStructuredText
                    '; + }); +} + diff --git a/style.css b/style.css index 6a95bb5..c15eb59 100644 --- a/style.css +++ b/style.css @@ -328,7 +328,6 @@ tr:hover { } .preview-content { - padding: 15px; background-color: white; flex: 1; overflow-y: auto; @@ -361,7 +360,7 @@ tr:hover { .preview-content iframe { width: 100%; - height: 500px; + height: 100%; border: 1px solid #ddd; } @@ -399,6 +398,7 @@ tr:hover { .rst-content { line-height: 1.6; color: #333; + margin: 10px; } .markdown-content h1, @@ -650,16 +650,12 @@ tr:hover { .preview-content pre[class*="language-"], .preview-content code[class*="language-"] { background-color: #f8f8f8; - border: 1px solid #ddd; - border-radius: 4px; font-family: "Consolas", "Monaco", "Lucida Console", monospace; font-size: 13px; line-height: 1.5; } .preview-content pre[class*="language-"] { - padding: 15px; - margin: 10px 0; overflow: auto; max-height: 600px; }