- sauvegarde automatique de l'avancement du livre

This commit is contained in:
Tykayn 2025-03-04 15:47:44 +01:00 committed by tykayn
parent de532abbb7
commit 375fbb3a7a
1814 changed files with 334236 additions and 0 deletions

132
static/css/style.css Normal file
View file

@ -0,0 +1,132 @@
:root {
--bg-color: #ffffff;
--text-color: #212529;
--border-color: #dee2e6;
--sidebar-bg: #f8f9fa;
--editor-bg: #ffffff;
--preview-bg: #ffffff;
--code-bg: #f8f9fa;
--blockquote-color: #6c757d;
}
[data-theme="dark"] {
--bg-color: #212529;
--text-color: #f8f9fa;
--border-color: #495057;
--sidebar-bg: #343a40;
--editor-bg: #2b3035;
--preview-bg: #2b3035;
--code-bg: #343a40;
--blockquote-color: #adb5bd;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
}
.sidebar {
height: 100vh;
background-color: var(--sidebar-bg);
padding: 20px;
border-right: 1px solid var(--border-color);
}
.editor {
height: 100vh;
padding: 20px;
}
#editor-content {
width: 100%;
height: calc(100vh - 100px);
font-family: monospace;
padding: 15px;
border: 1px solid var(--border-color);
border-radius: 4px;
resize: none;
background-color: var(--editor-bg);
color: var(--text-color);
}
.word-count {
background-color: var(--code-bg);
padding: 15px;
border-radius: 4px;
margin-bottom: 20px;
}
.preview-panel {
height: 100vh;
padding: 20px;
background-color: var(--bg-color);
border-left: 1px solid var(--border-color);
overflow-y: auto;
}
.preview-content {
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: var(--preview-bg);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.preview-content h1 {
font-size: 2em;
margin-bottom: 1em;
}
.preview-content h2 {
font-size: 1.5em;
margin: 1em 0;
}
.preview-content h3 {
font-size: 1.2em;
margin: 0.8em 0;
}
.preview-content p {
margin-bottom: 1em;
line-height: 1.6;
}
.preview-content ul,
.preview-content ol {
margin-bottom: 1em;
padding-left: 2em;
}
.preview-content li {
margin-bottom: 0.5em;
}
.preview-content blockquote {
border-left: 4px solid var(--border-color);
padding-left: 1em;
margin: 1em 0;
color: var(--blockquote-color);
}
.preview-content code {
background-color: var(--code-bg);
padding: 0.2em 0.4em;
border-radius: 3px;
font-family: monospace;
}
.preview-content pre {
background-color: var(--code-bg);
padding: 1em;
border-radius: 4px;
overflow-x: auto;
margin: 1em 0;
}
.theme-switch {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
}

159
static/js/main.js Normal file
View file

@ -0,0 +1,159 @@
let enable_auto_update = false;
let previewTimeout = null;
let isScrolling = false;
// Gestion du thème
const themeSwitch = document.getElementById('theme-switch');
const htmlElement = document.documentElement;
// Charger le thème sauvegardé
const savedTheme = localStorage.getItem('theme') || 'light';
htmlElement.setAttribute('data-theme', savedTheme);
themeSwitch.checked = savedTheme === 'dark';
// Écouteur d'événements pour le switch de thème
themeSwitch.addEventListener('change', (e) => {
const theme = e.target.checked ? 'dark' : 'light';
htmlElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
});
// Fonction pour convertir le texte org en HTML
function orgToHtml(text) {
// Conversion des titres
text = text.replace(/^\*+ (.*)$/gm, (match, content) => {
const level = match.match(/^\*+/)[0].length;
return `<h${level} class="org-heading">${content}</h${level}>`;
});
// Conversion des listes
text = text.replace(/^- (.*)$/gm, '<li>$1</li>');
text = text.replace(/(<li>.*<\/li>\n?)+/g, '<ul>$&</ul>');
// Conversion des citations
text = text.replace(/^#\+BEGIN_QUOTE\n(.*?)\n#\+END_QUOTE$/gs, '<blockquote>$1</blockquote>');
// Conversion du code
text = text.replace(/^#\+BEGIN_SRC.*\n(.*?)\n#\+END_SRC$/gs, '<pre><code>$1</code></pre>');
text = text.replace(/`([^`]+)`/g, '<code>$1</code>');
// Conversion des paragraphes
text = text.split('\n\n').map(para => {
if (!para.trim()) return '';
if (!para.match(/^<[hul]|^<blockquote|^<pre/)) {
return `<p>${para}</p>`;
}
return para;
}).join('\n');
return text;
}
// Fonction pour mettre à jour la prévisualisation
function updatePreview() {
const content = document.getElementById('editor-content').value;
const previewContent = document.getElementById('preview-content');
previewContent.innerHTML = orgToHtml(content);
}
// Synchronisation du défilement
function syncScroll(source, target) {
if (isScrolling) return;
isScrolling = true;
const sourceScrollPercent = source.scrollTop / (source.scrollHeight - source.clientHeight);
const targetScrollTop = sourceScrollPercent * (target.scrollHeight - target.clientHeight);
target.scrollTop = targetScrollTop;
setTimeout(() => {
isScrolling = false;
}, 100);
}
// Écouteurs d'événements pour la synchronisation du défilement
const editor = document.getElementById('editor-content');
const preview = document.getElementById('preview-content');
editor.addEventListener('scroll', () => {
syncScroll(editor, preview);
});
preview.addEventListener('scroll', () => {
syncScroll(preview, editor);
});
// Écouteur d'événements pour la mise à jour automatique de la prévisualisation
editor.addEventListener('input', () => {
if (document.getElementById('auto-preview').checked) {
clearTimeout(previewTimeout);
previewTimeout = setTimeout(updatePreview, 500);
}
});
// Écouteur d'événements pour le switch de mise à jour automatique
document.getElementById('auto-preview').addEventListener('change', (e) => {
if (e.target.checked) {
updatePreview();
}
});
// Sauvegarde automatique si activée
if (enable_auto_update) {
setInterval(async () => {
const content = document.getElementById('editor-content').value;
try {
const response = await fetch('/update', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `content=${encodeURIComponent(content)}`
});
const data = await response.json();
if (response.ok) {
document.getElementById('words-today').textContent = data.words_today;
console.log('Sauvegarde automatique effectuée');
}
} catch (error) {
console.error('Erreur lors de la sauvegarde automatique');
}
}, 20000); // 20 secondes
}
// Mise à jour du contenu
document.getElementById('update-btn').addEventListener('click', async () => {
const content = document.getElementById('editor-content').value;
try {
const response = await fetch('/update', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `content=${encodeURIComponent(content)}`
});
const data = await response.json();
if (response.ok) {
document.getElementById('words-today').textContent = data.words_today;
alert('Contenu mis à jour avec succès !');
}
} catch (error) {
alert('Erreur lors de la mise à jour');
}
});
// Mise à jour automatique du compteur de mots
async function updateWordCount() {
try {
const response = await fetch('/words_today');
const data = await response.json();
document.getElementById('words-today').textContent = data.words;
} catch (error) {
console.error('Erreur lors de la mise à jour du compteur de mots');
}
}
// Mise à jour toutes les 30 secondes
setInterval(updateWordCount, 30000);
// Initialisation de la prévisualisation
updatePreview();