⚡ - sauvegarde automatique de l'avancement du livre
This commit is contained in:
parent
de532abbb7
commit
375fbb3a7a
1814 changed files with 334236 additions and 0 deletions
132
static/css/style.css
Normal file
132
static/css/style.css
Normal 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
159
static/js/main.js
Normal 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();
|
Loading…
Add table
Add a link
Reference in a new issue