réorganisation de la page "options" et ajout d'une fonction de lien personnalisé pour la page "à propos"
This commit is contained in:
parent
85d5c28a1b
commit
e14b0a3478
148
about.php
148
about.php
@ -37,77 +37,97 @@ $siteStats = $stats->getStats();
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- Contenu principal -->
|
<!-- Contenu principal -->
|
||||||
<div class="novel-content">
|
<div class="about-content">
|
||||||
<div class="novel-description">
|
<div class="about-description">
|
||||||
<?= $about['content'] ?>
|
<?= $about['content'] // Le contenu est déjà en HTML ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<aside class="stats-menu">
|
<aside class="sidebar">
|
||||||
<h2>Statistiques</h2>
|
<!-- Liens personnalisés -->
|
||||||
<ul class="stats-list">
|
<?php if (!empty($about['links'])): ?>
|
||||||
<!-- Nombre total de romans -->
|
<section class="sidebar-section links-section">
|
||||||
<li class="stats-item">
|
<h2>Liens</h2>
|
||||||
<div class="stats-label">Romans publiés</div>
|
<ul class="custom-links-list">
|
||||||
<div class="stats-value"><?= Stats::formatNumber($siteStats['total_stories']) ?></div>
|
<?php foreach ($about['links'] as $link): ?>
|
||||||
</li>
|
<li>
|
||||||
|
<a href="<?= htmlspecialchars($link['url']) ?>"
|
||||||
|
<?= !empty($link['target']) ? 'target="' . htmlspecialchars($link['target']) . '"' : '' ?>>
|
||||||
|
<?= htmlspecialchars($link['title']) ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<!-- Nombre total de chapitres -->
|
<!-- Statistiques -->
|
||||||
<li class="stats-item">
|
<section class="sidebar-section stats-section">
|
||||||
<div class="stats-label">Chapitres écrits</div>
|
<h2>Statistiques</h2>
|
||||||
<div class="stats-value"><?= Stats::formatNumber($siteStats['total_chapters']) ?></div>
|
<ul class="stats-list">
|
||||||
<div class="stats-detail">
|
<!-- Nombre total de romans -->
|
||||||
Moyenne de <?= $siteStats['avg_chapters_per_story'] ?> chapitres par roman
|
<li class="stats-item">
|
||||||
</div>
|
<div class="stats-label">Romans publiés</div>
|
||||||
</li>
|
<div class="stats-value"><?= Stats::formatNumber($siteStats['total_stories']) ?></div>
|
||||||
|
</li>
|
||||||
|
|
||||||
<!-- Nombre total de mots -->
|
<!-- Nombre total de chapitres -->
|
||||||
<li class="stats-item">
|
<li class="stats-item">
|
||||||
<div class="stats-label">Mots écrits</div>
|
<div class="stats-label">Chapitres écrits</div>
|
||||||
<div class="stats-value"><?= Stats::formatNumber($siteStats['total_words']) ?></div>
|
<div class="stats-value"><?= Stats::formatNumber($siteStats['total_chapters']) ?></div>
|
||||||
<div class="stats-detail">
|
<div class="stats-detail">
|
||||||
Moyenne de <?= Stats::formatNumber($siteStats['avg_words_per_chapter']) ?> mots par chapitre
|
Moyenne de <?= $siteStats['avg_chapters_per_story'] ?> chapitres par roman
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<!-- Roman avec le plus de chapitres -->
|
<!-- Nombre total de mots -->
|
||||||
<?php if ($siteStats['most_chapters']['story']): ?>
|
<li class="stats-item">
|
||||||
<li class="stats-item">
|
<div class="stats-label">Mots écrits</div>
|
||||||
<div class="stats-label">Plus long roman</div>
|
<div class="stats-value"><?= Stats::formatNumber($siteStats['total_words']) ?></div>
|
||||||
<div class="stats-value"><?= Stats::formatNumber($siteStats['most_chapters']['count']) ?> chapitres</div>
|
<div class="stats-detail">
|
||||||
<div class="stats-detail">
|
Moyenne de <?= Stats::formatNumber($siteStats['avg_words_per_chapter']) ?> mots par chapitre
|
||||||
<a href="roman.php?id=<?= htmlspecialchars($siteStats['most_chapters']['story']['id']) ?>">
|
</div>
|
||||||
<?= htmlspecialchars($siteStats['most_chapters']['story']['title']) ?>
|
</li>
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<!-- Plus long chapitre -->
|
<!-- Roman avec le plus de chapitres -->
|
||||||
<?php if ($siteStats['longest_chapter']['story']): ?>
|
<?php if ($siteStats['most_chapters']['story']): ?>
|
||||||
<li class="stats-item">
|
<li class="stats-item">
|
||||||
<div class="stats-label">Plus long chapitre</div>
|
<div class="stats-label">Plus long roman</div>
|
||||||
<div class="stats-value"><?= Stats::formatNumber($siteStats['longest_chapter']['words']) ?> mots</div>
|
<div class="stats-value"><?= Stats::formatNumber($siteStats['most_chapters']['count']) ?> chapitres</div>
|
||||||
<div class="stats-detail">
|
<div class="stats-detail">
|
||||||
<a href="roman.php?id=<?= htmlspecialchars($siteStats['longest_chapter']['story']['id']) ?>">
|
<a href="roman.php?id=<?= htmlspecialchars($siteStats['most_chapters']['story']['id']) ?>">
|
||||||
<?= htmlspecialchars($siteStats['longest_chapter']['story']['title']) ?>
|
<?= htmlspecialchars($siteStats['most_chapters']['story']['title']) ?>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<!-- Dernière mise à jour -->
|
<!-- Plus long chapitre -->
|
||||||
<?php if ($siteStats['latest_update']['story']): ?>
|
<?php if ($siteStats['longest_chapter']['story']): ?>
|
||||||
<li class="stats-item">
|
<li class="stats-item">
|
||||||
<div class="stats-label">Dernière mise à jour</div>
|
<div class="stats-label">Plus long chapitre</div>
|
||||||
<div class="stats-value"><?= Stats::formatDate($siteStats['latest_update']['date']) ?></div>
|
<div class="stats-value"><?= Stats::formatNumber($siteStats['longest_chapter']['words']) ?> mots</div>
|
||||||
<div class="stats-detail">
|
<div class="stats-detail">
|
||||||
<a href="roman.php?id=<?= htmlspecialchars($siteStats['latest_update']['story']['id']) ?>">
|
<a href="roman.php?id=<?= htmlspecialchars($siteStats['longest_chapter']['story']['id']) ?>">
|
||||||
<?= htmlspecialchars($siteStats['latest_update']['story']['title']) ?>
|
<?= htmlspecialchars($siteStats['longest_chapter']['story']['title']) ?>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</ul>
|
|
||||||
|
<!-- Dernière mise à jour -->
|
||||||
|
<?php if ($siteStats['latest_update']['story']): ?>
|
||||||
|
<li class="stats-item">
|
||||||
|
<div class="stats-label">Dernière mise à jour</div>
|
||||||
|
<div class="stats-value"><?= Stats::formatDate($siteStats['latest_update']['date']) ?></div>
|
||||||
|
<div class="stats-detail">
|
||||||
|
<a href="roman.php?id=<?= htmlspecialchars($siteStats['latest_update']['story']['id']) ?>">
|
||||||
|
<?= htmlspecialchars($siteStats['latest_update']['story']['title']) ?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -25,6 +25,20 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$config['about']['title'] = trim($_POST['about_title'] ?? 'À propos');
|
$config['about']['title'] = trim($_POST['about_title'] ?? 'À propos');
|
||||||
$config['about']['content'] = $_POST['about_content'] ?? '';
|
$config['about']['content'] = $_POST['about_content'] ?? '';
|
||||||
|
|
||||||
|
// Traitement des liens personnalisés
|
||||||
|
$config['about']['links'] = [];
|
||||||
|
if (!empty($_POST['links'])) {
|
||||||
|
foreach ($_POST['links'] as $link) {
|
||||||
|
if (!empty($link['title']) && !empty($link['url'])) {
|
||||||
|
$config['about']['links'][] = [
|
||||||
|
'title' => trim($link['title']),
|
||||||
|
'url' => trim($link['url']),
|
||||||
|
'target' => isset($link['target']) ? '_blank' : ''
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Validation
|
// Validation
|
||||||
if (empty($config['site']['name'])) {
|
if (empty($config['site']['name'])) {
|
||||||
throw new Exception('Le nom du site est requis');
|
throw new Exception('Le nom du site est requis');
|
||||||
@ -92,10 +106,10 @@ $config = Config::load();
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="options-container">
|
<div class="options-container">
|
||||||
<!-- Section Options du Site -->
|
|
||||||
<section class="options-section">
|
<section class="options-section">
|
||||||
<h2>Options générales</h2>
|
<h2>Options générales</h2>
|
||||||
<form method="POST" class="options-form" enctype="multipart/form-data">
|
<form method="POST" class="options-form" enctype="multipart/form-data">
|
||||||
|
<!-- Options du site -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="site_name">Nom du site</label>
|
<label for="site_name">Nom du site</label>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
@ -117,8 +131,7 @@ $config = Config::load();
|
|||||||
<?php if (!empty($config['site']['logo'])): ?>
|
<?php if (!empty($config['site']['logo'])): ?>
|
||||||
<div class="current-logo">
|
<div class="current-logo">
|
||||||
<img src="<?= htmlspecialchars('../' . $config['site']['logo']) ?>"
|
<img src="<?= htmlspecialchars('../' . $config['site']['logo']) ?>"
|
||||||
alt="Logo actuel"
|
alt="Logo actuel">
|
||||||
style="max-height: 100px; margin: 10px 0;">
|
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<input type="file"
|
<input type="file"
|
||||||
@ -144,8 +157,7 @@ $config = Config::load();
|
|||||||
<?php if (!empty($config['about']['background'])): ?>
|
<?php if (!empty($config['about']['background'])): ?>
|
||||||
<div class="current-background">
|
<div class="current-background">
|
||||||
<img src="<?= htmlspecialchars('../' . $config['about']['background']) ?>"
|
<img src="<?= htmlspecialchars('../' . $config['about']['background']) ?>"
|
||||||
alt="Image de fond actuelle"
|
alt="Image de fond actuelle">
|
||||||
style="max-height: 100px; margin: 10px 0;">
|
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<input type="file"
|
<input type="file"
|
||||||
@ -158,146 +170,57 @@ $config = Config::load();
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="about_content">Contenu de la page</label>
|
<label for="about_content">Contenu de la page</label>
|
||||||
<input type="hidden" id="about_content" name="about_content">
|
<input type="hidden" id="about_content" name="about_content">
|
||||||
<div id="aboutEditor"></div>
|
<div id="aboutEditor" data-initial-content="<?= htmlspecialchars($config['about']['content'] ?? '') ?>"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Section Liens personnalisés -->
|
||||||
|
<h2>Liens personnalisés</h2>
|
||||||
|
<div id="customLinks" class="custom-links">
|
||||||
|
<?php
|
||||||
|
if (!empty($config['about']['links'])) {
|
||||||
|
foreach ($config['about']['links'] as $index => $link) {
|
||||||
|
?>
|
||||||
|
<div class="link-item">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Titre du lien</label>
|
||||||
|
<input type="text"
|
||||||
|
name="links[<?= $index ?>][title]"
|
||||||
|
value="<?= htmlspecialchars($link['title']) ?>"
|
||||||
|
required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>URL</label>
|
||||||
|
<input type="text"
|
||||||
|
name="links[<?= $index ?>][url]"
|
||||||
|
value="<?= htmlspecialchars($link['url']) ?>"
|
||||||
|
required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox"
|
||||||
|
name="links[<?= $index ?>][target]"
|
||||||
|
value="_blank"
|
||||||
|
<?= (!empty($link['target']) && $link['target'] === '_blank') ? 'checked' : '' ?>>
|
||||||
|
Ouvrir dans un nouvel onglet
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="button delete-story">Supprimer ce lien</button>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
<button type="button" id="addLink" class="button">Ajouter un lien</button>
|
||||||
|
|
||||||
<button type="submit" class="button">Enregistrer les modifications</button>
|
<button type="submit" class="button submit-button">Enregistrer les modifications</button>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script src="https://cdn.quilljs.com/1.3.6/quill.min.js"></script>
|
<script src="https://cdn.quilljs.com/1.3.6/quill.min.js"></script>
|
||||||
<script>
|
<script src="../assets/js/options.js"></script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
// Prévisualisation du logo
|
|
||||||
document.getElementById('site_logo').addEventListener('change', function(e) {
|
|
||||||
const file = e.target.files[0];
|
|
||||||
if (file) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = function(e) {
|
|
||||||
let currentLogo = document.querySelector('.current-logo');
|
|
||||||
if (!currentLogo) {
|
|
||||||
currentLogo = document.createElement('div');
|
|
||||||
currentLogo.className = 'current-logo';
|
|
||||||
e.target.parentElement.insertBefore(currentLogo, e.target.nextSibling);
|
|
||||||
}
|
|
||||||
currentLogo.innerHTML = `
|
|
||||||
<img src="${e.target.result}"
|
|
||||||
alt="Aperçu du logo"
|
|
||||||
style="max-height: 100px; margin: 10px 0;">
|
|
||||||
`;
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Prévisualisation du background
|
|
||||||
document.getElementById('about_background').addEventListener('change', function(e) {
|
|
||||||
const file = e.target.files[0];
|
|
||||||
if (file) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = function(e) {
|
|
||||||
let currentBackground = document.querySelector('.current-background');
|
|
||||||
if (!currentBackground) {
|
|
||||||
currentBackground = document.createElement('div');
|
|
||||||
currentBackground.className = 'current-background';
|
|
||||||
e.target.parentElement.insertBefore(currentBackground, e.target.nextSibling);
|
|
||||||
}
|
|
||||||
currentBackground.innerHTML = `
|
|
||||||
<img src="${e.target.result}"
|
|
||||||
alt="Aperçu du fond"
|
|
||||||
style="max-height: 100px; margin: 10px 0;">
|
|
||||||
`;
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Configuration de l'éditeur Quill
|
|
||||||
const aboutEditor = new Quill('#aboutEditor', {
|
|
||||||
theme: 'snow',
|
|
||||||
modules: {
|
|
||||||
toolbar: {
|
|
||||||
container: [
|
|
||||||
[{ 'header': [1, 2, 3, false] }],
|
|
||||||
['bold', 'italic', 'underline', 'strike'],
|
|
||||||
[{ 'color': [] }, { 'background': [] }],
|
|
||||||
[{ 'font': [] }],
|
|
||||||
[{ 'align': [] }],
|
|
||||||
['blockquote', 'code-block'],
|
|
||||||
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
|
|
||||||
[{ 'script': 'sub'}, { 'script': 'super' }],
|
|
||||||
[{ 'indent': '-1'}, { 'indent': '+1' }],
|
|
||||||
[{ 'direction': 'rtl' }],
|
|
||||||
['link', 'image', 'video'],
|
|
||||||
['clean']
|
|
||||||
],
|
|
||||||
handlers: {
|
|
||||||
image: function() {
|
|
||||||
const input = document.createElement('input');
|
|
||||||
input.setAttribute('type', 'file');
|
|
||||||
input.setAttribute('accept', 'image/*');
|
|
||||||
input.click();
|
|
||||||
|
|
||||||
input.onchange = async () => {
|
|
||||||
const file = input.files[0];
|
|
||||||
if (file) {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('image', file);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('api/upload-image.php', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) throw new Error('Upload failed');
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
if (result.success) {
|
|
||||||
const range = aboutEditor.getSelection(true);
|
|
||||||
aboutEditor.insertEmbed(range.index, 'image', result.url);
|
|
||||||
aboutEditor.setSelection(range.index + 1);
|
|
||||||
} else {
|
|
||||||
showNotification(result.error || 'Erreur lors de l\'upload', 'error');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error:', error);
|
|
||||||
showNotification('Erreur lors de l\'upload de l\'image', 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
placeholder: 'Commencez à écrire le contenu de la page À propos...'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialiser le contenu si existant
|
|
||||||
<?php if (!empty($config['about']['content'])): ?>
|
|
||||||
aboutEditor.root.innerHTML = <?= json_encode($config['about']['content']) ?>;
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
// Mise à jour du champ caché avant la soumission
|
|
||||||
document.querySelector('form').addEventListener('submit', function() {
|
|
||||||
document.querySelector('#about_content').value = aboutEditor.root.innerHTML;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Détection des changements non sauvegardés
|
|
||||||
const form = document.querySelector('form');
|
|
||||||
const initialState = new FormData(form).toString();
|
|
||||||
|
|
||||||
window.addEventListener('beforeunload', (e) => {
|
|
||||||
if (new FormData(form).toString() !== initialState) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.returnValue = '';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="../assets/css/dialog.css">
|
<link rel="stylesheet" href="../assets/css/dialog.css">
|
||||||
<script src="../assets/js/dialog.js"></script>
|
<script src="../assets/js/dialog.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
@ -104,4 +104,98 @@
|
|||||||
height: auto;
|
height: auto;
|
||||||
display: block;
|
display: block;
|
||||||
margin: var(--spacing-md) 0;
|
margin: var(--spacing-md) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section Options */
|
||||||
|
/* Conteneur des options */
|
||||||
|
.options-container {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-section {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
margin-bottom: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-section h2 {
|
||||||
|
margin: var(--spacing-xl) 0 var(--spacing-lg);
|
||||||
|
padding-bottom: var(--spacing-sm);
|
||||||
|
border-bottom: 2px solid var(--accent-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-section h2:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Aperçus des images */
|
||||||
|
.current-logo img,
|
||||||
|
.current-background img {
|
||||||
|
max-height: 100px;
|
||||||
|
margin: var(--spacing-sm) 0;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section des liens personnalisés */
|
||||||
|
.custom-links {
|
||||||
|
margin: var(--spacing-lg) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-item {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
padding: var(--spacing-lg);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-item .form-group {
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-item .form-group:last-of-type {
|
||||||
|
margin-bottom: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-item label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-item label:has(input[type="checkbox"]) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-item input[type="checkbox"] {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bouton d'ajout de lien */
|
||||||
|
#addLink {
|
||||||
|
margin-bottom: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bouton de soumission */
|
||||||
|
.submit-button {
|
||||||
|
margin-top: var(--spacing-xl);
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.options-section {
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-item {
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
}
|
||||||
}
|
}
|
@ -522,6 +522,120 @@ body {
|
|||||||
color: var(--text-tertiary);
|
color: var(--text-tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Styles pour la page À propos */
|
||||||
|
.about-content {
|
||||||
|
max-width: var(--content-width);
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 var(--spacing-md);
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 300px;
|
||||||
|
gap: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-description {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
padding: var(--spacing-lg);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar */
|
||||||
|
.sidebar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-section {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
padding: var(--spacing-lg);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
position: sticky;
|
||||||
|
top: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-section h2 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
color: var(--text-primary);
|
||||||
|
padding-bottom: var(--spacing-sm);
|
||||||
|
border-bottom: 2px solid var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Liens personnalisés */
|
||||||
|
.custom-links-list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-links-list li {
|
||||||
|
margin-bottom: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-links-list a {
|
||||||
|
display: block;
|
||||||
|
padding: var(--spacing-sm);
|
||||||
|
color: var(--text-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-links-list a:hover {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Statistiques */
|
||||||
|
.stats-list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-item {
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
padding-bottom: var(--spacing-md);
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-label {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-value {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-detail {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--accent-primary);
|
||||||
|
margin-top: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-detail a {
|
||||||
|
color: var(--accent-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-detail a:hover {
|
||||||
|
color: var(--accent-secondary);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
/* Responsive */
|
/* Responsive */
|
||||||
@media (max-width: 1200px) {
|
@media (max-width: 1200px) {
|
||||||
:root {
|
:root {
|
||||||
@ -542,6 +656,18 @@ body {
|
|||||||
.novel-header h1 {
|
.novel-header h1 {
|
||||||
font-size: 2.5rem;
|
font-size: 2.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.about-content {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
gap: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-section {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
@ -610,4 +736,17 @@ body {
|
|||||||
.site-header h1 {
|
.site-header h1 {
|
||||||
font-size: 2.4rem;
|
font-size: 2.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.about-content {
|
||||||
|
padding: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-description,
|
||||||
|
.sidebar-section {
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-value {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
}
|
}
|
187
assets/js/options.js
Normal file
187
assets/js/options.js
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Configuration de l'éditeur Quill
|
||||||
|
const aboutEditor = new Quill('#aboutEditor', {
|
||||||
|
theme: 'snow',
|
||||||
|
modules: {
|
||||||
|
toolbar: {
|
||||||
|
container: [
|
||||||
|
[{ 'header': [1, 2, 3, false] }],
|
||||||
|
['bold', 'italic', 'underline', 'strike'],
|
||||||
|
[{ 'color': [] }, { 'background': [] }],
|
||||||
|
[{ 'font': [] }],
|
||||||
|
[{ 'align': [] }],
|
||||||
|
['blockquote', 'code-block'],
|
||||||
|
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
|
||||||
|
[{ 'script': 'sub'}, { 'script': 'super' }],
|
||||||
|
[{ 'indent': '-1'}, { 'indent': '+1' }],
|
||||||
|
[{ 'direction': 'rtl' }],
|
||||||
|
['link', 'image', 'video'],
|
||||||
|
['clean']
|
||||||
|
],
|
||||||
|
handlers: {
|
||||||
|
image: handleImageUpload
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
placeholder: 'Commencez à écrire le contenu de la page À propos...'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Gestion de l'upload d'images
|
||||||
|
function handleImageUpload() {
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.setAttribute('type', 'file');
|
||||||
|
input.setAttribute('accept', 'image/*');
|
||||||
|
input.click();
|
||||||
|
|
||||||
|
input.onchange = async () => {
|
||||||
|
const file = input.files[0];
|
||||||
|
if (file) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('image', file);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('api/upload-image.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error('Upload failed');
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.success) {
|
||||||
|
const range = aboutEditor.getSelection(true);
|
||||||
|
aboutEditor.insertEmbed(range.index, 'image', result.url);
|
||||||
|
aboutEditor.setSelection(range.index + 1);
|
||||||
|
} else {
|
||||||
|
showNotification(result.error || 'Erreur lors de l\'upload', 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
showNotification('Erreur lors de l\'upload de l\'image', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialisation du contenu si existant
|
||||||
|
const editorElement = document.getElementById('aboutEditor');
|
||||||
|
const initialContent = editorElement.getAttribute('data-initial-content');
|
||||||
|
if (initialContent) {
|
||||||
|
aboutEditor.root.innerHTML = initialContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gestion des prévisualisations d'images
|
||||||
|
function handleImagePreview(inputId, previewClass) {
|
||||||
|
const input = document.getElementById(inputId);
|
||||||
|
if (!input) return;
|
||||||
|
|
||||||
|
input.addEventListener('change', function(e) {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (file) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = function(e) {
|
||||||
|
let previewContainer = input.parentElement.querySelector('.' + previewClass);
|
||||||
|
if (!previewContainer) {
|
||||||
|
previewContainer = document.createElement('div');
|
||||||
|
previewContainer.className = previewClass;
|
||||||
|
input.parentElement.insertBefore(previewContainer, input.nextSibling);
|
||||||
|
}
|
||||||
|
previewContainer.innerHTML = `<img src="${e.target.result}" alt="Aperçu">`;
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialisation des prévisualisations
|
||||||
|
handleImagePreview('site_logo', 'current-logo');
|
||||||
|
handleImagePreview('about_background', 'current-background');
|
||||||
|
|
||||||
|
// Gestion des liens personnalisés
|
||||||
|
const customLinks = document.getElementById('customLinks');
|
||||||
|
const addLinkBtn = document.getElementById('addLink');
|
||||||
|
|
||||||
|
// Ajout d'un nouveau lien
|
||||||
|
addLinkBtn.addEventListener('click', function() {
|
||||||
|
const index = customLinks.children.length;
|
||||||
|
const linkItem = document.createElement('div');
|
||||||
|
linkItem.className = 'link-item';
|
||||||
|
linkItem.innerHTML = `
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Titre du lien</label>
|
||||||
|
<input type="text" name="links[${index}][title]" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>URL</label>
|
||||||
|
<input type="text" name="links[${index}][url]" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="links[${index}][target]" value="_blank">
|
||||||
|
Ouvrir dans un nouvel onglet
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="button dark remove-link">Supprimer ce lien</button>
|
||||||
|
`;
|
||||||
|
customLinks.appendChild(linkItem);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Suppression d'un lien
|
||||||
|
customLinks.addEventListener('click', function(e) {
|
||||||
|
if (e.target.matches('.remove-link')) {
|
||||||
|
const linkItem = e.target.closest('.link-item');
|
||||||
|
confirmDialog.show({
|
||||||
|
title: 'Supprimer le lien',
|
||||||
|
message: 'Êtes-vous sûr de vouloir supprimer ce lien ?',
|
||||||
|
confirmText: 'Supprimer',
|
||||||
|
confirmClass: 'danger',
|
||||||
|
onConfirm: () => {
|
||||||
|
linkItem.remove();
|
||||||
|
// Réindexer les champs
|
||||||
|
customLinks.querySelectorAll('.link-item').forEach((item, index) => {
|
||||||
|
item.querySelectorAll('input').forEach(input => {
|
||||||
|
const name = input.getAttribute('name');
|
||||||
|
input.setAttribute('name', name.replace(/\[\d+\]/, `[${index}]`));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mise à jour du champ caché avant la soumission
|
||||||
|
document.querySelector('form').addEventListener('submit', function() {
|
||||||
|
document.querySelector('#about_content').value = aboutEditor.root.innerHTML;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Détection des changements non sauvegardés
|
||||||
|
const form = document.querySelector('form');
|
||||||
|
const initialState = new FormData(form).toString();
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', (e) => {
|
||||||
|
if (new FormData(form).toString() !== initialState) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.returnValue = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fonction utilitaire pour les notifications
|
||||||
|
function showNotification(message, type = 'success') {
|
||||||
|
const notification = document.createElement('div');
|
||||||
|
notification.className = `notification ${type}`;
|
||||||
|
notification.textContent = message;
|
||||||
|
|
||||||
|
document.body.appendChild(notification);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
notification.style.opacity = '1';
|
||||||
|
notification.style.transform = 'translateY(0)';
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
notification.style.opacity = '0';
|
||||||
|
notification.style.transform = 'translateY(-100%)';
|
||||||
|
setTimeout(() => notification.remove(), 300);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
});
|
@ -6,7 +6,14 @@
|
|||||||
"about": {
|
"about": {
|
||||||
"title": "À propos",
|
"title": "À propos",
|
||||||
"content": "",
|
"content": "",
|
||||||
"background": "assets/images/site/about-bg.jpg"
|
"background": "assets/images/site/about-bg.jpg",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"title": "Mon Blog",
|
||||||
|
"url": "https://blog.example.com",
|
||||||
|
"target": "_blank"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"users": [
|
"users": [
|
||||||
{
|
{
|
||||||
|
@ -1 +1 @@
|
|||||||
1.1.4
|
1.1.5
|
Loading…
x
Reference in New Issue
Block a user