template html & css
This commit is contained in:
97
explorer_md/server.py
Normal file
97
explorer_md/server.py
Normal file
@@ -0,0 +1,97 @@
|
||||
# app.py
|
||||
# pip install flask
|
||||
from flask import Flask, render_template, jsonify, send_from_directory
|
||||
import os
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# Le répertoire que nous voulons explorer (relatif à app.py)
|
||||
FILE_DIR = 'files'
|
||||
|
||||
# --- Configuration des Routes ---
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""Route principale qui sert la page HTML."""
|
||||
return render_template('index.html')
|
||||
|
||||
|
||||
@app.route('/images/<filename>')
|
||||
def get_image(filename):
|
||||
"""Sert une image depuis un dossier."""
|
||||
return send_from_directory('images', filename)
|
||||
|
||||
|
||||
|
||||
@app.route('/api/list_files')
|
||||
def list_files():
|
||||
"""Endpoint API pour lister les fichiers du répertoire."""
|
||||
try:
|
||||
# Assurez-vous que le répertoire existe
|
||||
if not os.path.isdir(FILE_DIR):
|
||||
return jsonify({'error': f"Le répertoire '{FILE_DIR}' n'existe pas."}), 500
|
||||
|
||||
# Liste tous les éléments dans le répertoire
|
||||
all_items = os.listdir(FILE_DIR)
|
||||
|
||||
# Filtre pour ne garder que les fichiers (et non les dossiers)
|
||||
file_list = [item for item in all_items if os.path.isfile(os.path.join(FILE_DIR, item))]
|
||||
|
||||
return jsonify(file_list)
|
||||
except Exception as e:
|
||||
app.logger.error(f"Erreur lors de la récupération de la liste des fichiers : {e}")
|
||||
return jsonify({'error': 'Erreur interne du serveur.'}), 500
|
||||
|
||||
@app.route('/api/get_content/<filename>')
|
||||
def get_content(filename):
|
||||
"""Endpoint API pour obtenir le contenu d'un fichier."""
|
||||
file_path = os.path.join(FILE_DIR, filename)
|
||||
|
||||
# Sécurité: Vérifiez que le chemin est bien dans le répertoire autorisé
|
||||
# Ceci empêche les attaques de 'Directory Traversal'
|
||||
if not os.path.abspath(file_path).startswith(os.path.abspath(FILE_DIR)):
|
||||
return jsonify({'error': 'Accès non autorisé.'}), 403
|
||||
|
||||
try:
|
||||
# Cas des fichiers textes lisibles
|
||||
if filename.endswith(('.txt', '.ini', '.json', '.html', '.css', '.js')):
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
return jsonify({'content': content, 'type': 'text'})
|
||||
elif filename.endswith(('.md')):
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
return jsonify({'content': content, 'type': 'text'})
|
||||
|
||||
# Cas des images ou autres binaires (on simule le contenu)
|
||||
elif filename.endswith(('.jpg', '.png', '.pdf')):
|
||||
return jsonify({
|
||||
'content': f"Fichier binaire ou non textuel. Le contenu de {filename} ne peut pas être affiché directement.",
|
||||
'type': 'binary'
|
||||
})
|
||||
|
||||
# Pour tout autre type de fichier non géré
|
||||
else:
|
||||
return jsonify({'content': f"Type de fichier non supporté pour l'affichage : {filename}", 'type': 'unsupported'})
|
||||
|
||||
except FileNotFoundError:
|
||||
return jsonify({'error': 'Fichier non trouvé.'}), 404
|
||||
except Exception as e:
|
||||
app.logger.error(f"Erreur lors de la lecture du fichier {filename} : {e}")
|
||||
return jsonify({'error': 'Erreur interne du serveur lors de la lecture.'}), 500
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Créez le répertoire des fichiers s'il n'existe pas
|
||||
if not os.path.exists(FILE_DIR):
|
||||
os.makedirs(FILE_DIR)
|
||||
# Créez quelques fichiers de test
|
||||
with open(os.path.join(FILE_DIR, 'Fichier 1.txt'), 'w') as f:
|
||||
f.write("Contenu du premier fichier texte.")
|
||||
with open(os.path.join(FILE_DIR, 'Config.ini'), 'w') as f:
|
||||
f.write("[Settings]\nVersion=1.0\nPath=/data")
|
||||
with open(os.path.join(FILE_DIR, 'Image.jpg'), 'w') as f: # Fichier binaire simulé
|
||||
f.write("Ceci n'est pas une image réelle.")
|
||||
|
||||
# Lancez l'application Flask
|
||||
app.run(debug=True)
|
||||
103
explorer_md/static/script.js
Normal file
103
explorer_md/static/script.js
Normal file
@@ -0,0 +1,103 @@
|
||||
// static/script.js
|
||||
|
||||
const fileListPanel = document.getElementById('file-list-panel');
|
||||
const resizer = document.getElementById('resizer');
|
||||
const container = document.querySelector('.container');
|
||||
const fileListUl = document.getElementById('file-list');
|
||||
const fileContentPre = document.getElementById('file-content');
|
||||
const contentTitle = document.getElementById('content-title');
|
||||
|
||||
let isResizing = false;
|
||||
|
||||
// 1. GESTION DU REDIMENSIONNEMENT (Même logique que précédemment)
|
||||
resizer.addEventListener('mousedown', function(e) {
|
||||
isResizing = true;
|
||||
container.classList.add('resizing');
|
||||
document.body.style.userSelect = 'none';
|
||||
});
|
||||
|
||||
document.addEventListener('mousemove', function(e) {
|
||||
if (!isResizing) return;
|
||||
|
||||
const newWidth = e.clientX - container.getBoundingClientRect().left;
|
||||
const minWidth = 150;
|
||||
const maxWidth = container.clientWidth * 0.5;
|
||||
|
||||
if (newWidth >= minWidth && newWidth <= maxWidth) {
|
||||
fileListPanel.style.width = newWidth + 'px';
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('mouseup', function() {
|
||||
isResizing = false;
|
||||
container.classList.remove('resizing');
|
||||
document.body.style.userSelect = '';
|
||||
});
|
||||
|
||||
// 2. LOGIQUE DU CHARGEMENT DES FICHIERS (Communication avec Flask)
|
||||
|
||||
// A. Récupérer la liste des fichiers depuis le serveur
|
||||
async function fetchFileList() {
|
||||
try {
|
||||
const response = await fetch('/api/list_files');
|
||||
const files = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
fileListUl.innerHTML = ''; // Vider la liste existante
|
||||
if (files.length === 0) {
|
||||
fileListUl.innerHTML = '<li>Aucun fichier trouvé.</li>';
|
||||
fileContentPre.textContent = 'Le répertoire est vide.';
|
||||
} else {
|
||||
files.forEach(filename => {
|
||||
const li = document.createElement('li');
|
||||
li.textContent = filename;
|
||||
li.onclick = () => loadFileContent(filename, li);
|
||||
fileListUl.appendChild(li);
|
||||
});
|
||||
|
||||
// Charger le contenu du premier fichier par défaut
|
||||
if (files.length > 0) {
|
||||
loadFileContent(files[0], fileListUl.querySelector('li'));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Afficher l'erreur retournée par Flask
|
||||
fileContentPre.textContent = `Erreur: ${files.error}`;
|
||||
}
|
||||
} catch (error) {
|
||||
fileContentPre.textContent = `Erreur de connexion au serveur : ${error.message}`;
|
||||
console.error('Erreur lors de la récupération des fichiers:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// B. Charger le contenu du fichier sélectionné
|
||||
async function loadFileContent(filename, element) {
|
||||
// Retirer la classe 'selected' de tous les éléments
|
||||
document.querySelectorAll('#file-list li').forEach(li => {
|
||||
li.classList.remove('selected');
|
||||
});
|
||||
// Ajouter la classe 'selected' à l'élément cliqué
|
||||
if (element) {
|
||||
element.classList.add('selected');
|
||||
}
|
||||
|
||||
contentTitle.textContent = `Contenu de : ${filename}`;
|
||||
fileContentPre.textContent = 'Chargement en cours...';
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/get_content/${filename}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
// Enlève le slice(1, -1) sauf si ton serveur renvoie le contenu entre guillemets
|
||||
fileContentPre.innerHTML = marked.parse(data.content);
|
||||
} else {
|
||||
fileContentPre.innerHTML = `Erreur (${response.status}) : ${data.error}`;
|
||||
}
|
||||
} catch (error) {
|
||||
fileContentPre.textContent = `Erreur réseau lors du chargement : ${error.message}`;
|
||||
console.error('Erreur lors du chargement du contenu:', error);
|
||||
}
|
||||
}
|
||||
// Exécuter au chargement de la page
|
||||
document.addEventListener('DOMContentLoaded', fetchFileList);
|
||||
73
explorer_md/static/style.css
Normal file
73
explorer_md/static/style.css
Normal file
@@ -0,0 +1,73 @@
|
||||
/* static/style.css */
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
/* --- Panneau de la Liste des Fichiers (Gauche) --- */
|
||||
#file-list-panel {
|
||||
width: 300px;
|
||||
min-width: 150px;
|
||||
max-width: 50%;
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
background-color: #f4f4f4;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#file-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#file-list li {
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
#file-list li:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
#file-list li.selected {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* --- Poignée de Redimensionnement --- */
|
||||
#resizer {
|
||||
width: 6px;
|
||||
cursor: col-resize;
|
||||
background-color: #ccc;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
#resizer:hover {
|
||||
background-color: #aaa;
|
||||
}
|
||||
|
||||
/* --- Panneau de Contenu (Droite) --- */
|
||||
#content {
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#file-content {
|
||||
padding: 15px;
|
||||
border: 1px dashed #ddd;
|
||||
background-color: #fff;
|
||||
min-height: 80%;
|
||||
}
|
||||
81
explorer_md/static/style_md.css
Normal file
81
explorer_md/static/style_md.css
Normal file
@@ -0,0 +1,81 @@
|
||||
/* static/style_md.css */
|
||||
|
||||
#file-content {
|
||||
background: white;
|
||||
padding: 30px 40px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
color: #333;
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
border-bottom: 2px solid #eee;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
code {
|
||||
background: #f4f4f4;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #2d2d2d;
|
||||
color: #f8f8f2;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
pre code {
|
||||
background: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 4px solid #ddd;
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0066cc;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
th, td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #f4f4f4;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.loader {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #c00;
|
||||
background: #fee;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
29
explorer_md/templates/index.html
Normal file
29
explorer_md/templates/index.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Explorateur de Fichiers Python/Flask</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style_md.css') }}">
|
||||
<!-- https://github.com/markedjs/marked -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/9.1.6/marked.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div id="file-list-panel">
|
||||
<h3>Liste des Fichiers</h3>
|
||||
<ul id="file-list"></ul>
|
||||
</div>
|
||||
|
||||
<div id="resizer"></div>
|
||||
|
||||
<div id="content">
|
||||
<h3 id="content-title">Contenu du Fichier</h3>
|
||||
<div id="file-content">Chargement de la liste des fichiers...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
84
explorer_md/templates/index_md.html
Normal file
84
explorer_md/templates/index_md.html
Normal file
@@ -0,0 +1,84 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Visualiseur Markdown</title>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/9.1.6/marked.min.js"></script>
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||
line-height: 1.6;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
#content {
|
||||
background: white;
|
||||
padding: 30px 40px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1, h2, h3 { color: #333; margin-top: 1.5em; }
|
||||
h1 { border-bottom: 2px solid #eee; padding-bottom: 10px; }
|
||||
code {
|
||||
background: #f4f4f4;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
}
|
||||
pre {
|
||||
background: #2d2d2d;
|
||||
color: #f8f8f2;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
pre code { background: none; color: inherit; }
|
||||
blockquote {
|
||||
border-left: 4px solid #ddd;
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
color: #666;
|
||||
}
|
||||
a { color: #0066cc; }
|
||||
table { border-collapse: collapse; width: 100%; }
|
||||
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
|
||||
th { background: #f4f4f4; }
|
||||
img { max-width: 100%; }
|
||||
.loader { text-align: center; color: #666; padding: 40px; }
|
||||
.error { color: #c00; background: #fee; padding: 15px; border-radius: 6px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="content">
|
||||
<div class="loader">Chargement...</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// ===== CONFIGURATION =====
|
||||
// Change le nom du fichier ici :
|
||||
const fichierMD = 'install_noMachine_linux.md';
|
||||
// =========================
|
||||
|
||||
async function chargerMarkdown() {
|
||||
const content = document.getElementById('content');
|
||||
try {
|
||||
const response = await fetch(fichierMD);
|
||||
if (!response.ok) throw new Error(`Fichier "${fichierMD}" non trouvé`);
|
||||
const md = await response.text();
|
||||
content.innerHTML = marked.parse(md);
|
||||
} catch (err) {
|
||||
content.innerHTML = `<div class="error">
|
||||
<strong>Erreur :</strong> ${err.message}<br><br>
|
||||
<strong>Solution :</strong> Lance un serveur local avec VS Code (extension Live Server)
|
||||
ou via terminal : <code>npx serve</code> ou <code>python -m http.server</code>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
chargerMarkdown();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user