feat: add preview
This commit is contained in:
410
script.js
410
script.js
@@ -25,6 +25,10 @@ function setupEventListeners() {
|
||||
|
||||
// Gestion de la navigation arrière/avant du navigateur
|
||||
window.addEventListener('popstate', handlePopState);
|
||||
|
||||
// Gestionnaires pour la prévisualisation
|
||||
document.getElementById('close-preview').addEventListener('click', hidePreview);
|
||||
document.getElementById('download-file').addEventListener('click', downloadCurrentFile);
|
||||
}
|
||||
|
||||
function handlePopState(event) {
|
||||
@@ -309,6 +313,9 @@ function displayContents(contents) {
|
||||
|
||||
// Afficher le tableau
|
||||
document.getElementById('files-table').style.display = 'table';
|
||||
|
||||
// Chercher et ouvrir automatiquement index.rst s'il existe
|
||||
autoOpenIndexRst(contents.files);
|
||||
}
|
||||
|
||||
function createParentRow(parentPath) {
|
||||
@@ -360,7 +367,7 @@ function createFileRow(file) {
|
||||
row.innerHTML = `
|
||||
<td>
|
||||
<span class="icon-file"></span>
|
||||
<a href="${fileUrl}" class="file-link" target="_blank">${file.name}</a>
|
||||
<a href="#" class="file-link" data-file-url="${fileUrl}" data-file-name="${file.name}">${file.name}</a>
|
||||
</td>
|
||||
<td class="file-size">${formatFileSize(file.size)}</td>
|
||||
<td class="file-date">${formatDate(file.modified)}</td>
|
||||
@@ -369,6 +376,12 @@ function createFileRow(file) {
|
||||
</td>
|
||||
`;
|
||||
|
||||
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));
|
||||
|
||||
@@ -419,4 +432,399 @@ async function copyToClipboard(text) {
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
}
|
||||
|
||||
// Variables globales pour la prévisualisation
|
||||
let currentPreviewFile = {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
|
||||
previewTitle.textContent = `Prévisualisation: ${fileName}`;
|
||||
previewContent.innerHTML = '<div style="text-align: center; padding: 20px;">Chargement...</div>';
|
||||
|
||||
previewSection.style.display = 'flex';
|
||||
|
||||
const fileType = detectFileType(fileName);
|
||||
|
||||
switch (fileType) {
|
||||
case 'images':
|
||||
showImagePreview(fileUrl, previewContent);
|
||||
break;
|
||||
case 'text':
|
||||
case 'code':
|
||||
showTextPreview(fileUrl, 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 = '<div class="preview-error">Erreur: Impossible de charger l\'image</div>';
|
||||
};
|
||||
}
|
||||
|
||||
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 = '<div class="preview-error">Erreur: Impossible de charger le fichier texte</div>';
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
function showVideoPreview(fileUrl, container) {
|
||||
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);
|
||||
}
|
||||
|
||||
function showUnsupportedPreview(fileName, container) {
|
||||
const extension = fileName.toLowerCase().split('.').pop();
|
||||
container.innerHTML = `
|
||||
<div class="preview-error">
|
||||
<p>Prévisualisation non disponible pour ce type de fichier (.${extension})</p>
|
||||
<p>Utilisez le bouton "Télécharger" ci-dessous pour obtenir le fichier.</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function hidePreview() {
|
||||
const previewSection = document.getElementById('preview-section');
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
// Fonction simple de parsing Markdown vers HTML
|
||||
function parseMarkdown(text) {
|
||||
let html = text;
|
||||
|
||||
// Headers
|
||||
html = html.replace(/^### (.*$)/gim, '<h3>$1</h3>');
|
||||
html = html.replace(/^## (.*$)/gim, '<h2>$1</h2>');
|
||||
html = html.replace(/^# (.*$)/gim, '<h1>$1</h1>');
|
||||
|
||||
// Bold
|
||||
html = html.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
|
||||
html = html.replace(/__(.*?)__/g, '<strong>$1</strong>');
|
||||
|
||||
// Italic
|
||||
html = html.replace(/\*(.*?)\*/g, '<em>$1</em>');
|
||||
html = html.replace(/_(.*?)_/g, '<em>$1</em>');
|
||||
|
||||
// Code inline
|
||||
html = html.replace(/`(.*?)`/g, '<code>$1</code>');
|
||||
|
||||
// Code blocks
|
||||
html = html.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>');
|
||||
|
||||
// Links
|
||||
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>');
|
||||
|
||||
// Images
|
||||
html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img alt="$1" src="$2" style="max-width: 100%; height: auto;">');
|
||||
|
||||
// Lists
|
||||
html = html.replace(/^\* (.*$)/gim, '<li>$1</li>');
|
||||
html = html.replace(/^- (.*$)/gim, '<li>$1</li>');
|
||||
html = html.replace(/^\d+\. (.*$)/gim, '<li>$1</li>');
|
||||
|
||||
// Wrap consecutive <li> items in <ul>
|
||||
html = html.replace(/(<li>.*<\/li>)/gs, (match) => {
|
||||
return '<ul>' + match + '</ul>';
|
||||
});
|
||||
|
||||
// Line breaks
|
||||
html = html.replace(/\n\n/g, '</p><p>');
|
||||
html = '<p>' + html + '</p>';
|
||||
|
||||
// Clean up empty paragraphs
|
||||
html = html.replace(/<p><\/p>/g, '');
|
||||
html = html.replace(/<p>(<h[1-6]>)/g, '$1');
|
||||
html = html.replace(/(<\/h[1-6]>)<\/p>/g, '$1');
|
||||
html = html.replace(/<p>(<ul>)/g, '$1');
|
||||
html = html.replace(/(<\/ul>)<\/p>/g, '$1');
|
||||
html = html.replace(/<p>(<pre>)/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, `<a href="${linkUrl}" target="_blank">${linkText}</a>`);
|
||||
return placeholder;
|
||||
};
|
||||
|
||||
// Headers (RST style with underlines) - ordre important pour éviter les conflits
|
||||
html = html.replace(/^(.*)\n#{3,}$/gim, '<h1>$1</h1>');
|
||||
html = html.replace(/^(.*)\n={3,}$/gim, '<h2>$1</h2>');
|
||||
html = html.replace(/^(.*)\n-{3,}$/gim, '<h3>$1</h3>');
|
||||
html = html.replace(/^(.*)\n~{3,}$/gim, '<h4>$1</h4>');
|
||||
html = html.replace(/^(.*)\n\^{3,}$/gim, '<h5>$1</h5>');
|
||||
|
||||
// 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 `<div class="rst-metadata"><span class="metadata-key">${key}:</span> <span class="metadata-value">${processedValue}</span></div>`;
|
||||
});
|
||||
|
||||
// Custom directives (.. directive::)
|
||||
html = html.replace(/^\.\. ([^:]+)::\s*(.*)$/gim, '<div class="rst-directive rst-directive-$1" data-directive="$1" data-content="$2">');
|
||||
|
||||
// Directive options (:option: value)
|
||||
html = html.replace(/^ :([^:]+):\s*(.*)$/gim, (match, key, value) => {
|
||||
// Remplacer les liens par des placeholders dans les options
|
||||
const processedValue = value.replace(/`([^<]+) <([^>]+)>`_/g, (match, text, url) => {
|
||||
return createLinkPlaceholder(text, url);
|
||||
});
|
||||
return `<div class="directive-option" data-option="${key}" data-value="${value}"><span class="option-key">${key}:</span> <span class="option-value">${processedValue}</span></div>`;
|
||||
});
|
||||
|
||||
// Special handling for big_button directive
|
||||
html = html.replace(/<div class="rst-directive rst-directive-big_button"[^>]*>/g, '<div class="rst-big-button">');
|
||||
|
||||
// Process directive content blocks (indented text after directive)
|
||||
html = html.replace(/(<div class="rst-directive[^>]*>)\n((?: [^\n]*\n?)*)/gim, (match, directive, content) => {
|
||||
if (content.trim()) {
|
||||
const cleanContent = content.replace(/^ /gm, '').trim();
|
||||
// Remplacer les liens par des placeholders dans le contenu
|
||||
const processedContent = cleanContent.replace(/`([^<]+) <([^>]+)>`_/g, (match, text, url) => {
|
||||
return createLinkPlaceholder(text, url);
|
||||
});
|
||||
return directive + '<div class="directive-content">' + processedContent + '</div></div>';
|
||||
}
|
||||
return directive + '</div>';
|
||||
});
|
||||
|
||||
// Bold
|
||||
html = html.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
|
||||
|
||||
// Italic
|
||||
html = html.replace(/\*(.*?)\*/g, '<em>$1</em>');
|
||||
|
||||
// Code inline
|
||||
html = html.replace(/``(.*?)``/g, '<code>$1</code>');
|
||||
|
||||
// Code blocks (:: at end of line followed by indented block)
|
||||
html = html.replace(/::\s*\n\n((?: .*\n?)+)/g, '<pre><code>$1</code></pre>');
|
||||
|
||||
// Links restants dans le texte principal
|
||||
html = html.replace(/`([^<]+) <([^>]+)>`_/g, (match, text, url) => {
|
||||
return createLinkPlaceholder(text, url);
|
||||
});
|
||||
|
||||
// Lists
|
||||
html = html.replace(/^\* (.*$)/gim, '<li>$1</li>');
|
||||
html = html.replace(/^- (.*$)/gim, '<li>$1</li>');
|
||||
html = html.replace(/^\d+\. (.*$)/gim, '<li>$1</li>');
|
||||
|
||||
// Wrap consecutive <li> items in <ul>
|
||||
html = html.replace(/(<li>.*<\/li>)/gs, (match) => {
|
||||
return '<ul>' + match + '</ul>';
|
||||
});
|
||||
|
||||
// Line breaks
|
||||
html = html.replace(/\n\n/g, '</p><p>');
|
||||
html = '<p>' + html + '</p>';
|
||||
|
||||
// Clean up empty paragraphs and fix structure
|
||||
html = html.replace(/<p><\/p>/g, '');
|
||||
html = html.replace(/<p>(<h[1-6]>)/g, '$1');
|
||||
html = html.replace(/(<\/h[1-6]>)<\/p>/g, '$1');
|
||||
html = html.replace(/<p>(<div class="rst-)/g, '<div class="rst-');
|
||||
html = html.replace(/(<\/div>)<\/p>/g, '$1');
|
||||
html = html.replace(/<p>(<ul>)/g, '$1');
|
||||
html = html.replace(/(<\/ul>)<\/p>/g, '$1');
|
||||
html = html.replace(/<p>(<pre>)/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 = '<div class="preview-error">Erreur: Impossible de charger le fichier Markdown</div>';
|
||||
});
|
||||
}
|
||||
|
||||
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 = '<div class="preview-error">Erreur: Impossible de charger le fichier reStructuredText</div>';
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user