Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
705860a5ec | |||
5912c7a0be | |||
7dfd600936 | |||
a82699c84e | |||
e8a46f15e3 | |||
1d965ba2d6 | |||
4eb4844dab |
186
README.md
186
README.md
@ -1,179 +1,59 @@
|
||||
# Plateforme de Publication de Romans en Ligne
|
||||
# Lectures d'Esenjin - Plateforme de Publication de Romans
|
||||
|
||||
Une plateforme web simple et élégante pour la publication et la lecture de romans en ligne, développée en PHP avec stockage JSON.
|
||||
Une plateforme web élégante pour la publication et la lecture de romans, développée en PHP avec stockage JSON.
|
||||
|
||||
## 🚀 Fonctionnalités
|
||||

|
||||
|
||||
## Fonctionnalités principales
|
||||
|
||||
### Zone Administrative
|
||||
- Interface sécurisée pour la gestion des contenus
|
||||
- Création et édition de romans avec éditeur WYSIWYG
|
||||
- Gestion flexible des chapitres avec réorganisation par glisser-déposer
|
||||
- Prévisualisation avant publication
|
||||
- Stockage JSON pour une maintenance simplifiée
|
||||
- Système d'upload d'images avec redimensionnement automatique
|
||||
- Gestion des métadonnées (date de création, mise à jour, etc.)
|
||||
- Import/Export des romans
|
||||
- Gestion du profil et des options du site
|
||||
|
||||

|
||||

|
||||

|
||||
- Création et édition de romans avec éditeur WYSIWYG (Quill.js)
|
||||
- Gestion des chapitres avec réorganisation par glisser-déposer
|
||||
- Mode brouillon pour les chapitres en cours de rédaction
|
||||
- Système d'upload et de gestion d'images
|
||||
- Gestion des accès utilisateurs (administrateurs, éditeurs)
|
||||
- Import/Export des romans au format ZIP
|
||||
- Personnalisation des options du site (logo, bannière, informations)
|
||||
|
||||
### Zone Publique
|
||||
- Interface de lecture épurée et confortable
|
||||
- Interface de lecture épurée aux tons bruns/ocre
|
||||
- Navigation intuitive entre les chapitres
|
||||
- Design responsive optimisé pour la lecture
|
||||
- Thème personnalisé aux tons bruns/ocre
|
||||
- Affichage adaptatif des images et du contenu
|
||||
- Design responsive optimisé pour tous les appareils
|
||||
- Section "À propos" personnalisable avec statistiques automatiques
|
||||
- Indicateurs pour nouveaux chapitres et contenus en cours de rédaction
|
||||
|
||||

|
||||

|
||||
|
||||
## 📋 Prérequis
|
||||
## Prérequis techniques
|
||||
|
||||
- PHP 8.0 ou supérieur
|
||||
- Serveur web (Apache/Nginx) avec mod_rewrite activé
|
||||
- Extensions PHP : GD ou Imagick pour le traitement des images
|
||||
- Permissions d'écriture sur les dossiers `stories/`, `assets/images/` et `admin/`
|
||||
- Extensions PHP : GD pour le traitement des images
|
||||
- Permissions d'écriture sur les dossiers `stories/` et `assets/images/`
|
||||
|
||||
## 🛠️ Installation
|
||||
## Installation rapide
|
||||
|
||||
1. Clonez le dépôt :
|
||||
```bash
|
||||
git clone https://github.com/votre-username/nom-du-projet.git
|
||||
cd nom-du-projet
|
||||
```
|
||||
1. Clonez le dépôt
|
||||
2. Copiez et modifiez `config.json` avec vos paramètres
|
||||
3. Définissez les permissions appropriées sur les dossiers
|
||||
4. Accédez à `/admin` pour commencer à gérer vos romans
|
||||
|
||||
2. Configurez votre serveur web pour pointer vers le dossier racine du projet
|
||||
|
||||
3. Copiez et modifiez le fichier de configuration :
|
||||
```bash
|
||||
cp config.example.json config.json
|
||||
```
|
||||
|
||||
4. Modifiez `config.json` avec vos paramètres :
|
||||
```json
|
||||
{
|
||||
"site": {
|
||||
"name": "Votre Site",
|
||||
"url": "https://votre-domaine.com",
|
||||
"description": "Description de votre site",
|
||||
"logo": "assets/images/logo.png"
|
||||
},
|
||||
"users": [
|
||||
{
|
||||
"id": "admin",
|
||||
"password": "votre_mot_de_passe_hashé"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
5. Définissez les permissions appropriées :
|
||||
```bash
|
||||
chmod 755 .
|
||||
chmod 644 config.json
|
||||
chmod -R 755 stories/
|
||||
chmod -R 755 assets/images/
|
||||
```
|
||||
|
||||
## 📁 Structure du Projet
|
||||
## Structure du projet
|
||||
|
||||
```
|
||||
/
|
||||
├── admin/ # Zone administrative sécurisée
|
||||
│ ├── api/ # Endpoints API pour les opérations CRUD
|
||||
│ ├── index.php # Dashboard administratif
|
||||
│ └── login.php # Page de connexion
|
||||
├── assets/ # Ressources statiques
|
||||
│ ├── css/ # Styles CSS
|
||||
│ ├── js/ # Scripts JavaScript
|
||||
│ └── images/ # Images uploadées
|
||||
│ ├── covers/ # Couvertures des romans
|
||||
│ └── chapters/ # Images des chapitres
|
||||
├── admin/ # Zone administrative
|
||||
├── assets/ # Ressources statiques (CSS, JS, images)
|
||||
├── includes/ # Fichiers PHP réutilisables
|
||||
├── stories/ # Dossier contenant les récits (JSON)
|
||||
├── stories/ # Romans au format JSON
|
||||
├── config.json # Configuration du site
|
||||
└── index.php # Page d'accueil publique
|
||||
└── index.php # Page d'accueil
|
||||
```
|
||||
|
||||
## 🔒 Sécurité
|
||||
## Sécurité
|
||||
|
||||
- Authentification sécurisée avec hashage des mots de passe
|
||||
- Protection contre les attaques XSS et CSRF
|
||||
- Validation des données entrantes
|
||||
- Sanitization des sorties HTML
|
||||
- Restrictions sur les types de fichiers uploadés
|
||||
- Redimensionnement automatique des images
|
||||
- Sessions sécurisées avec paramètres renforcés
|
||||
- Protection contre les injections et les attaques XSS
|
||||
- Validation des données et restrictions sur les uploads
|
||||
|
||||
## 🌐 Utilisation
|
||||
|
||||
### Interface Administrative
|
||||
1. Accédez à `/admin` et connectez-vous
|
||||
2. Utilisez le menu pour gérer vos romans et chapitres
|
||||
3. L'éditeur WYSIWYG permet une mise en forme riche du texte
|
||||
4. Uploadez des images directement dans l'éditeur
|
||||
5. Réorganisez les chapitres par glisser-déposer
|
||||
6. Prévisualisez vos modifications avant publication
|
||||
|
||||
### Interface Publique
|
||||
- La page d'accueil liste tous les romans disponibles
|
||||
- Chaque roman a sa page dédiée avec description et chapitres
|
||||
- Navigation fluide entre les chapitres
|
||||
- Interface adaptative pour une lecture confortable sur tous les appareils
|
||||
- Optimisation des images selon la taille d'écran
|
||||
|
||||
## 💾 Structure des Données
|
||||
|
||||
### Configuration (config.json)
|
||||
```json
|
||||
{
|
||||
"site": {
|
||||
"name": "Nom du Site",
|
||||
"description": "Description du site",
|
||||
"logo": "path/to/logo.png"
|
||||
},
|
||||
"users": [
|
||||
{
|
||||
"id": "admin",
|
||||
"password": "hashed_password"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Romans (stories/roman-id.json)
|
||||
```json
|
||||
{
|
||||
"id": "roman-id",
|
||||
"title": "Titre du Roman",
|
||||
"description": "Description complète",
|
||||
"cover": "assets/images/covers/cover.jpg",
|
||||
"created": "2025-02-14",
|
||||
"updated": "2025-02-14",
|
||||
"chapters": [
|
||||
{
|
||||
"id": "chapitre-1",
|
||||
"title": "Titre du Chapitre",
|
||||
"content": "Contenu au format Delta/HTML",
|
||||
"created": "2025-02-14",
|
||||
"updated": "2025-02-14"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 🤝 Contribution
|
||||
|
||||
Les contributions sont les bienvenues ! Pour contribuer :
|
||||
|
||||
1. Forkez le projet
|
||||
2. Créez une branche pour votre fonctionnalité (`git checkout -b feature/AmazingFeature`)
|
||||
3. Committez vos changements (`git commit -m 'Add some AmazingFeature'`)
|
||||
4. Poussez vers la branche (`git push origin feature/AmazingFeature`)
|
||||
5. Ouvrez une Pull Request
|
||||
|
||||
## 📝 License
|
||||
## License
|
||||
|
||||
Ce projet est sous licence MIT - voir le fichier [LICENSE.md](LICENSE.md) pour plus de détails.
|
13
about.php
13
about.php
@ -34,6 +34,11 @@ $siteStats = $stats->getStats();
|
||||
<div class="novel-header-background" style="background-image: url('<?= htmlspecialchars($about['background']) ?>');"></div>
|
||||
<?php endif; ?>
|
||||
<h1><?= htmlspecialchars($about['title']) ?></h1>
|
||||
<div class="header-actions">
|
||||
<a href="index.php" class="about-button">
|
||||
<i class="fas fa-home"></i> Accueil
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Contenu principal -->
|
||||
@ -131,11 +136,9 @@ $siteStats = $stats->getStats();
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<div class="back-to-home">
|
||||
<a href="index.php">← Retour à l'accueil</a>
|
||||
</div>
|
||||
|
||||
<button class="scroll-top" aria-label="Retour en haut de page">↑</button>
|
||||
<button class="scroll-top" aria-label="Retour en haut de page">
|
||||
<i class="fas fa-arrow-up"></i>
|
||||
</button>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
@ -29,10 +29,19 @@ try {
|
||||
$chapterUpdated = false;
|
||||
foreach ($story['chapters'] as &$chapter) {
|
||||
if ($chapter['id'] === $chapterId) {
|
||||
$wasInDraft = $chapter['draft'] ?? false;
|
||||
$chapter['title'] = $title;
|
||||
$chapter['content'] = $content;
|
||||
$chapter['draft'] = $draft;
|
||||
$chapter['updated'] = date('Y-m-d');
|
||||
|
||||
// Si le chapitre passe de brouillon à publié, ajouter la date de publication
|
||||
if ($wasInDraft && !$draft) {
|
||||
$chapter['published'] = date('Y-m-d H:i:s');
|
||||
// Marquer le roman comme ayant du nouveau contenu
|
||||
$story['publicUpdated'] = date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
$chapterUpdated = true;
|
||||
break;
|
||||
}
|
||||
|
@ -49,7 +49,9 @@ $stories = Stories::getAll();
|
||||
<span>Administration</span>
|
||||
</div>
|
||||
<div class="nav-menu">
|
||||
<a href="index.php" class="button">Retour</a>
|
||||
<a href="index.php" class="button tooltip" data-tooltip="Retour">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@ -80,8 +82,14 @@ $stories = Stories::getAll();
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="button" class="button select-all">Tout sélectionner</button>
|
||||
<button type="submit" class="button">Exporter la sélection</button>
|
||||
<button type="button" class="button tooltip select-all" data-tooltip="Tout sélectionner">
|
||||
<i class="fas fa-check-square"></i>
|
||||
<span class="tooltip-text">Tout sélectionner</span>
|
||||
</button>
|
||||
<button type="submit" class="button tooltip" data-tooltip="Exporter">
|
||||
<i class="fas fa-upload"></i>
|
||||
<span class="tooltip-text">Exporter</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
@ -102,7 +110,10 @@ $stories = Stories::getAll();
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="button">Importer</button>
|
||||
<button type="submit" class="button tooltip" data-tooltip="Importer">
|
||||
<i class="fas fa-download"></i>
|
||||
<span class="tooltip-text">Importer</span>
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
</main>
|
||||
@ -114,10 +125,17 @@ $stories = Stories::getAll();
|
||||
const checkboxes = document.querySelectorAll('input[name="stories[]"]');
|
||||
|
||||
selectAllBtn.addEventListener('click', function() {
|
||||
const allChecked = Array.from(checkboxes).every(cb => cb.checked);
|
||||
checkboxes.forEach(cb => cb.checked = !allChecked);
|
||||
this.textContent = allChecked ? 'Tout sélectionner' : 'Tout désélectionner';
|
||||
});
|
||||
const allChecked = Array.from(checkboxes).every(cb => cb.checked);
|
||||
checkboxes.forEach(cb => cb.checked = !allChecked);
|
||||
|
||||
// Mise à jour du texte de la tooltip et non du contenu du bouton
|
||||
const tooltipText = this.querySelector('.tooltip-text');
|
||||
if (tooltipText) {
|
||||
tooltipText.textContent = allChecked ? 'Tout sélectionner' : 'Tout désélectionner';
|
||||
}
|
||||
// Mise à jour de l'attribut data-tooltip pour que le survol affiche le bon texte
|
||||
this.setAttribute('data-tooltip', allChecked ? 'Tout sélectionner' : 'Tout désélectionner');
|
||||
});
|
||||
|
||||
// Validation du formulaire d'export
|
||||
document.getElementById('exportForm').addEventListener('submit', function(e) {
|
||||
|
@ -58,18 +58,31 @@ $users = Auth::getAllUsers(false);
|
||||
<?php endif; ?>
|
||||
<span>Administration</span>
|
||||
</div>
|
||||
<!-- Le bouton hamburger sera inséré par JS -->
|
||||
<div class="nav-menu">
|
||||
<a href="../index.php" target="_blank" class="button">Visiter le site</a>
|
||||
<a href="profile.php" class="button">Profil</a>
|
||||
<a href="../index.php" target="_blank" class="button tooltip" data-tooltip="Visiter le site">
|
||||
<i class="fa-solid fa-house"></i>
|
||||
</a>
|
||||
<a href="profile.php" class="button tooltip" data-tooltip="Profil">
|
||||
<i class="fas fa-user"></i>
|
||||
</a>
|
||||
<?php if (Auth::isAdmin() || Auth::hasAdminRole()): ?>
|
||||
<a href="users.php" class="button">Utilisateurs</a>
|
||||
<a href="users.php" class="button tooltip" data-tooltip="Utilisateurs">
|
||||
<i class="fas fa-users"></i>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<a href="story-edit.php" class="button">Nouveau roman</a>
|
||||
<a href="options.php" class="button">Options</a>
|
||||
<a href="export-import.php" class="button">Import/Export</a>
|
||||
<a href="story-edit.php" class="button tooltip" data-tooltip="Nouveau roman">
|
||||
<i class="fa-solid fa-book"></i>
|
||||
</a>
|
||||
<a href="options.php" class="button tooltip" data-tooltip="Options">
|
||||
<i class="fas fa-cog"></i>
|
||||
</a>
|
||||
<a href="export-import.php" class="button tooltip" data-tooltip="Import/Export">
|
||||
<i class="fa-solid fa-upload"></i>
|
||||
</a>
|
||||
<form method="POST" action="logout.php" class="logout-form">
|
||||
<button type="submit">Déconnexion</button>
|
||||
<button type="submit" class="tooltip" data-tooltip="Déconnexion">
|
||||
<i class="fas fa-sign-out-alt"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</nav>
|
||||
@ -92,11 +105,17 @@ $users = Auth::getAllUsers(false);
|
||||
</p>
|
||||
</div>
|
||||
<div class="story-actions">
|
||||
<a href="story-edit.php?id=<?= htmlspecialchars($story['id']) ?>" class="button">Modifier</a>
|
||||
<a href="story-edit.php?id=<?= htmlspecialchars($story['id']) ?>" class="button tooltip" data-tooltip="Modifier">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<?php if (Auth::isAdmin() || Auth::hasAdminRole()): ?>
|
||||
<button type="button" class="button manage-access" data-id="<?= htmlspecialchars($story['id']) ?>">Accès</button>
|
||||
<button type="button" class="button tooltip manage-access" data-tooltip="Accès" data-id="<?= htmlspecialchars($story['id']) ?>">
|
||||
<i class="fas fa-users-cog"></i>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<button type="button" class="button delete-story" data-id="<?= htmlspecialchars($story['id']) ?>">Supprimer</button>
|
||||
<button type="button" class="button tooltip delete-story" data-tooltip="Supprimer" data-id="<?= htmlspecialchars($story['id']) ?>">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
@ -131,8 +150,14 @@ $users = Auth::getAllUsers(false);
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="button dark" id="cancelAccess">Annuler</button>
|
||||
<button type="button" class="button" id="saveAccess">Enregistrer</button>
|
||||
<button type="button" class="button dark" id="cancelAccess">
|
||||
<i class="fas fa-times"></i>
|
||||
<span class="button-text">Annuler</span>
|
||||
</button>
|
||||
<button type="button" class="button" id="saveAccess">
|
||||
<i class="fas fa-save"></i>
|
||||
<span class="button-text">Enregistrer</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -90,7 +90,9 @@ $config = Config::load();
|
||||
<span>Administration</span>
|
||||
</div>
|
||||
<div class="nav-menu">
|
||||
<a href="index.php" class="button">Retour</a>
|
||||
<a href="index.php" class="button tooltip" data-tooltip="Retour">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@ -204,20 +206,30 @@ $config = Config::load();
|
||||
Ouvrir dans un nouvel onglet
|
||||
</label>
|
||||
</div>
|
||||
<button type="button" class="button delete-story">Supprimer ce lien</button>
|
||||
<button type="button" class="button dark tooltip remove-link" data-tooltip="Supprimer ce lien">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<button type="button" id="addLink" class="button">Ajouter un lien</button>
|
||||
<button type="button" id="addLink" class="button tooltip" data-tooltip="Ajouter un lien">
|
||||
<i class="fas fa-plus"></i>
|
||||
</button>
|
||||
<br />
|
||||
<button type="submit" class="button submit-button">Enregistrer les modifications</button>
|
||||
<button type="submit" class="button tooltip submit-button" data-tooltip="Enregistrer">
|
||||
<i class="fas fa-save"></i>
|
||||
<span class="tooltip-text">Enregistrer les modifications</span>
|
||||
</button>
|
||||
<!-- Section Nettoyage des médias -->
|
||||
<h2>Maintenance</h2>
|
||||
<div class="maintenance-actions">
|
||||
<button type="button" id="cleanMedia" class="button">Nettoyer les médias inutilisés</button>
|
||||
<button type="button" id="cleanMedia" class="button tooltip" data-tooltip="Nettoyer">
|
||||
<i class="fas fa-broom"></i>
|
||||
<span class="tooltip-text">Nettoyer les médias inutilisés</span>
|
||||
</button>
|
||||
<small>Supprime les images qui ne sont plus utilisées dans les romans et chapitres.</small>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -96,7 +96,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
<span>Administration</span>
|
||||
</div>
|
||||
<div class="nav-menu">
|
||||
<a href="index.php" class="button">Retour</a>
|
||||
<a href="index.php" class="button tooltip" data-tooltip="Retour">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@ -135,7 +137,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
<input type="password" id="confirm_password" name="confirm_password">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="button">Enregistrer les modifications</button>
|
||||
<button type="submit" class="button tooltip" data-tooltip="Enregistrer">
|
||||
<i class="fas fa-save"></i>
|
||||
<span class="tooltip-text">Enregistrer</span>
|
||||
</button>
|
||||
</form>
|
||||
</main>
|
||||
<link rel="stylesheet" href="../assets/css/dialog.css">
|
||||
|
@ -102,7 +102,9 @@ function generateSlug($title) {
|
||||
<span>Administration</span>
|
||||
</div>
|
||||
<div class="nav-menu">
|
||||
<a href="index.php" class="button">Retour</a>
|
||||
<a href="index.php" class="button tooltip" data-tooltip="Retour">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@ -143,13 +145,19 @@ function generateSlug($title) {
|
||||
<input type="file" id="cover" name="cover" accept="image/*">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="button">Enregistrer</button>
|
||||
<button type="submit" class="button tooltip" data-tooltip="Enregistrer">
|
||||
<i class="fas fa-save"></i>
|
||||
<span class="tooltip-text">Enregistrer</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<?php if ($story): ?>
|
||||
<section class="chapters-section">
|
||||
<h2>Chapitres</h2>
|
||||
<button type="button" id="addChapter" class="button">Ajouter un chapitre</button>
|
||||
<button type="button" id="addChapter" class="button">
|
||||
<i class="fas fa-plus"></i>
|
||||
<span class="button-text">Ajouter un chapitre</span>
|
||||
</button>
|
||||
|
||||
<div id="chaptersList" class="chapters-list">
|
||||
<?php foreach ($story['chapters'] ?? [] as $index => $chapter): ?>
|
||||
@ -157,9 +165,18 @@ function generateSlug($title) {
|
||||
<span class="chapter-number"><?= $index + 1 ?></span>
|
||||
<h3 class="chapter-title"><?= htmlspecialchars($chapter['title']) ?></h3>
|
||||
<div class="chapter-actions">
|
||||
<button type="button" class="button edit-chapter">Éditer</button>
|
||||
<button type="button" class="button edit-cover">Couverture</button>
|
||||
<button type="button" class="button delete-chapter">Supprimer</button>
|
||||
<button type="button" class="edit-chapter">
|
||||
<i class="fas fa-edit"></i>
|
||||
<span class="button-text">Éditer</span>
|
||||
</button>
|
||||
<button type="button" class="edit-cover">
|
||||
<i class="fas fa-image"></i>
|
||||
<span class="button-text">Couverture</span>
|
||||
</button>
|
||||
<button type="button" class="delete-chapter">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
<span class="button-text">Supprimer</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
@ -182,8 +199,14 @@ function generateSlug($title) {
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" id="cancelEdit" class="button dark">Annuler</button>
|
||||
<button type="button" id="saveChapter" class="button">Enregistrer</button>
|
||||
<button type="button" id="cancelEdit" class="button dark">
|
||||
<i class="fas fa-times"></i>
|
||||
<span class="button-text">Annuler</span>
|
||||
</button>
|
||||
<button type="button" id="saveChapter" class="button">
|
||||
<i class="fas fa-save"></i>
|
||||
<span class="button-text">Enregistrer</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -207,9 +230,18 @@ function generateSlug($title) {
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" id="cancelCoverEdit" class="button dark">Annuler</button>
|
||||
<button type="button" id="deleteCover" class="button delete-story" style="display: none;">Supprimer</button>
|
||||
<button type="button" id="saveCover" class="button">Enregistrer</button>
|
||||
<button type="button" id="cancelCoverEdit" class="button dark">
|
||||
<i class="fas fa-times"></i>
|
||||
<span class="button-text">Annuler</span>
|
||||
</button>
|
||||
<button type="button" id="deleteCover" class="button delete-story" style="display: none;">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
<span class="button-text">Supprimer</span>
|
||||
</button>
|
||||
<button type="button" id="saveCover" class="button">
|
||||
<i class="fas fa-save"></i>
|
||||
<span class="button-text">Enregistrer</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -175,7 +175,9 @@ $config = Config::load();
|
||||
<span>Administration</span>
|
||||
</div>
|
||||
<div class="nav-menu">
|
||||
<a href="index.php" class="button">Retour</a>
|
||||
<a href="index.php" class="button tooltip" data-tooltip="Retour">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@ -206,16 +208,23 @@ $config = Config::load();
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="user-actions">
|
||||
<button type="button" class="button edit-user" data-user-id="<?= htmlspecialchars($user['id']) ?>" data-user-comment="<?= htmlspecialchars($user['comment'] ?? '') ?>" data-user-role="<?= htmlspecialchars($user['role'] ?? 'editor') ?>">Modifier</button>
|
||||
<button type="button" class="button tooltip edit-user" data-tooltip="Modifier" data-user-id="<?= htmlspecialchars($user['id']) ?>" data-user-comment="<?= htmlspecialchars($user['comment'] ?? '') ?>" data-user-role="<?= htmlspecialchars($user['role'] ?? 'editor') ?>">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<?php if ($index !== 0): ?>
|
||||
<button type="button" class="button delete-user" data-user-id="<?= htmlspecialchars($user['id']) ?>">Supprimer</button>
|
||||
<button type="button" class="button tooltip delete-user" data-tooltip="Supprimer" data-user-id="<?= htmlspecialchars($user['id']) ?>">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<button type="button" id="addUserBtn" class="button">Ajouter un utilisateur</button>
|
||||
<button type="button" id="addUserBtn" class="button tooltip" data-tooltip="Ajouter un utilisateur">
|
||||
<i class="fas fa-user-plus"></i>
|
||||
<span class="tooltip-text">Ajouter un utilisateur</span>
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<!-- Formulaire d'ajout d'utilisateur (modal) -->
|
||||
@ -297,8 +306,14 @@ $config = Config::load();
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="button dark cancel-btn">Annuler</button>
|
||||
<button type="submit" class="button">Enregistrer</button>
|
||||
<button type="button" class="button dark cancel-btn">
|
||||
<i class="fas fa-times"></i>
|
||||
<span class="button-text">Annuler</span>
|
||||
</button>
|
||||
<button type="submit" class="button">
|
||||
<i class="fas fa-save"></i>
|
||||
<span class="button-text">Enregistrer</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
377
assets/css/buttons.css
Normal file
377
assets/css/buttons.css
Normal file
@ -0,0 +1,377 @@
|
||||
@import "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css";
|
||||
|
||||
/* buttons.css - Styles pour les boutons, tooltips et menu */
|
||||
|
||||
/* Styles pour les boutons avec icônes */
|
||||
.button.tooltip,
|
||||
.tooltip {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
padding: 0;
|
||||
border-radius: var(--radius-sm);
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
|
||||
.button.tooltip i,
|
||||
.tooltip i {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
/* Style pour le bouton de déconnexion */
|
||||
.logout-form button {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 0;
|
||||
border: 1px solid var(--border-color);
|
||||
background-color: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.logout-form button:hover {
|
||||
background-color: var(--error-color);
|
||||
}
|
||||
|
||||
.logout-form button i {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
/* Tooltip styles */
|
||||
.tooltip::after {
|
||||
content: attr(data-tooltip);
|
||||
position: absolute;
|
||||
bottom: -35px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background-color: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
padding: 5px 10px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 0.8rem;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.3s, visibility 0.3s;
|
||||
z-index: 100;
|
||||
pointer-events: none;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.tooltip:hover::after {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* Styles pour le texte à côté des icônes (mobile) */
|
||||
.tooltip-text {
|
||||
display: none;
|
||||
margin-left: var(--spacing-md);
|
||||
}
|
||||
|
||||
/* Menu toggle button */
|
||||
.menu-toggle {
|
||||
display: none;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-primary);
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
padding: var(--spacing-sm);
|
||||
}
|
||||
|
||||
/* Styles pour la barre de navigation */
|
||||
.admin-nav {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
display: flex;
|
||||
gap: var(--spacing-md);
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/* Styles pour les boutons d'action des romans */
|
||||
.story-actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
/* Style spécifique pour le bouton d'édition */
|
||||
.story-actions a.button {
|
||||
background-color: var(--accent-primary);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.story-actions a.button:hover {
|
||||
background-color: var(--accent-secondary);
|
||||
}
|
||||
|
||||
/* Style spécifique pour le bouton d'accès */
|
||||
.story-actions .manage-access {
|
||||
background-color: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.story-actions .manage-access:hover {
|
||||
background-color: var(--accent-primary);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
/* Style spécifique pour le bouton de suppression */
|
||||
.story-actions .delete-story {
|
||||
background-color: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.story-actions .delete-story:hover {
|
||||
background-color: var(--error-color);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
/* Styles pour les boutons modaux */
|
||||
.modal-footer .button {
|
||||
width: auto;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.modal-footer .button i,
|
||||
.modal-footer .button .button-text {
|
||||
pointer-events: none; /* Solution au problème de clic */
|
||||
}
|
||||
|
||||
/* Styles pour afficher du texte dans les boutons */
|
||||
.button-text {
|
||||
margin-left: var(--spacing-sm);
|
||||
}
|
||||
|
||||
/* Styles pour les boutons avec texte intégré */
|
||||
.button.with-text {
|
||||
width: auto;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.button.with-text i {
|
||||
margin-right: var(--spacing-sm);
|
||||
}
|
||||
|
||||
/* Styles pour les boutons de formulaire */
|
||||
.submit-button,
|
||||
#cleanMedia,
|
||||
#addUserBtn {
|
||||
width: auto;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
/* Solution globale pour les problèmes de clic sur les boutons */
|
||||
button i,
|
||||
button span,
|
||||
.button i,
|
||||
.button span {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Styles pour les boutons d'action de chapitre */
|
||||
.chapter-actions .button {
|
||||
padding: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.chapter-actions .edit-chapter {
|
||||
background-color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.chapter-actions .edit-cover {
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.chapter-actions .delete-chapter {
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.chapter-actions .delete-chapter:hover {
|
||||
background-color: var(--error-color);
|
||||
}
|
||||
|
||||
/* Styles pour les boutons de formulaire */
|
||||
.form-actions .button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.form-actions .button i {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
/* Bouton d'ajout */
|
||||
.button.add-button {
|
||||
background-color: var(--accent-primary);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.button.add-button:hover {
|
||||
background-color: var(--accent-secondary);
|
||||
}
|
||||
|
||||
.chapter-actions .edit-chapter,
|
||||
.chapter-actions .edit-cover,
|
||||
.chapter-actions .delete-chapter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
padding: 0;
|
||||
border-radius: var(--radius-sm);
|
||||
transition: var(--transition-fast);
|
||||
position: relative;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chapter-actions .edit-chapter i,
|
||||
.chapter-actions .edit-cover i,
|
||||
.chapter-actions .delete-chapter i {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.chapter-actions .button-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chapter-actions .edit-chapter {
|
||||
background-color: var(--accent-primary);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.chapter-actions .edit-cover,
|
||||
.chapter-actions .delete-chapter {
|
||||
background-color: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.chapter-actions .delete-chapter:hover {
|
||||
background-color: var(--error-color);
|
||||
}
|
||||
|
||||
.chapter-actions .edit-chapter i,
|
||||
.chapter-actions .edit-cover i,
|
||||
.chapter-actions .delete-chapter i,
|
||||
.chapter-actions .button-text {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Media queries pour le menu mobile */
|
||||
@media (max-width: 768px) {
|
||||
.menu-toggle {
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: var(--spacing-md);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
z-index: 101;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
background-color: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: var(--spacing-md);
|
||||
z-index: 100;
|
||||
width: 200px;
|
||||
gap: var(--spacing-sm);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
||||
|
||||
/* État caché par défaut */
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: translateY(-10px);
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.nav-menu.active {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Style des boutons en mode mobile */
|
||||
.nav-menu.active .button,
|
||||
.nav-menu.active .logout-form button {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
/* Afficher le texte des tooltips en mode mobile */
|
||||
.nav-menu.active .tooltip-text {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Cacher les tooltips standards sur mobile */
|
||||
.tooltip::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.logout-form {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Afficher le texte des tooltips en mode mobile pour les actions des romans */
|
||||
.story-actions.active .tooltip-text {
|
||||
display: inline-block;
|
||||
margin-left: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.story-actions.active .button {
|
||||
width: auto;
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
}
|
||||
|
||||
.chapter-actions.active .edit-chapter,
|
||||
.chapter-actions.active .edit-cover,
|
||||
.chapter-actions.active .delete-chapter {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
}
|
||||
|
||||
.chapter-actions.active .button-text {
|
||||
display: inline-block;
|
||||
margin-left: var(--spacing-sm);
|
||||
}
|
||||
}
|
||||
|
||||
/* Pour les très petits écrans */
|
||||
@media (max-width: 480px) {
|
||||
.story-actions {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.story-actions .button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
@ -5,9 +5,10 @@
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
gap: var(--spacing-lg);
|
||||
transition: transform var(--transition-fast), box-shadow var(--transition-fast);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.story-item:hover {
|
||||
@ -16,7 +17,7 @@
|
||||
}
|
||||
|
||||
.story-cover {
|
||||
width: 200px;
|
||||
width: 180px;
|
||||
height: 120px;
|
||||
object-fit: cover;
|
||||
border-radius: var(--radius-sm);
|
||||
@ -35,6 +36,10 @@
|
||||
|
||||
.story-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.story-info p {
|
||||
@ -88,7 +93,7 @@
|
||||
|
||||
.chapter-number {
|
||||
background: var(--accent-primary);
|
||||
color: var(--text-primary);
|
||||
color: var(--text-tertiary);
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
|
182
assets/css/fixes.css
Normal file
182
assets/css/fixes.css
Normal file
@ -0,0 +1,182 @@
|
||||
/* Correctifs pour les problèmes d'affichage des champs de texte */
|
||||
|
||||
/* 1. Règles globales pour tous les conteneurs de formulaire */
|
||||
.form-group {
|
||||
margin-bottom: var(--spacing-lg);
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* 2. Correction pour les champs de texte */
|
||||
.form-group input[type="text"],
|
||||
.form-group input[type="password"],
|
||||
.form-group textarea,
|
||||
.form-group input[type="file"],
|
||||
.form-group select {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
max-width: 100%;
|
||||
padding: var(--spacing-sm);
|
||||
background-color: var(--input-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text-primary);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* 3. Correction pour les modales et leurs contenus */
|
||||
.modal-content {
|
||||
max-width: 90vw;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
padding: var(--spacing-md);
|
||||
width: auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.modal-header,
|
||||
.modal-body,
|
||||
.modal-footer {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
|
||||
/* 4. Correction pour l'éditeur Quill */
|
||||
.ql-container {
|
||||
overflow: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.ql-editor {
|
||||
min-height: 200px;
|
||||
max-width: 100%;
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* 5. Gestion spécifique pour les grilles et les flex containers */
|
||||
.story-item,
|
||||
.chapter-item,
|
||||
.user-item {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-md);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.story-info,
|
||||
.chapter-info {
|
||||
flex: 1;
|
||||
min-width: 0; /* Empêche le dépassement */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 6. Meilleures media queries pour la responsivité */
|
||||
@media (max-width: 768px) {
|
||||
.story-item,
|
||||
.chapter-item,
|
||||
.user-item {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.story-actions,
|
||||
.chapter-actions,
|
||||
.user-actions {
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.form-group input[type="text"],
|
||||
.form-group input[type="password"],
|
||||
.form-group textarea {
|
||||
font-size: 16px; /* Pour éviter le zoom sur mobile */
|
||||
}
|
||||
|
||||
#chapterTitle {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 7. Correction pour les boutons qui peuvent déborder */
|
||||
.button,
|
||||
button {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* 8. Meilleure gestion des tables et des listes */
|
||||
table {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
td, th {
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
padding: var(--spacing-sm);
|
||||
}
|
||||
|
||||
/* 9. Amélioration du contenu affichable */
|
||||
.novel-description,
|
||||
.chapter-content,
|
||||
.about-description {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* 10. Flexibilité des images dans l'éditeur et le contenu */
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* 11. Correction pour les éléments de navigation */
|
||||
.chapters-list a {
|
||||
display: block;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 12. Correction pour la page options.php */
|
||||
.options-section {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#aboutEditor {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.cover-preview-container {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.link-item {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 13. Empêcher les textes de déborder */
|
||||
h1, h2, h3, h4, h5, h6, p {
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
max-width: 100%;
|
||||
}
|
@ -204,6 +204,69 @@
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* Style pour les sélecteurs (dropdowns) */
|
||||
select {
|
||||
width: 100%;
|
||||
padding: var(--spacing-sm);
|
||||
background-color: var(--input-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text-primary);
|
||||
font-size: 1rem;
|
||||
transition: border-color var(--transition-fast);
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='6'%3E%3Cpath d='M0 0h12L6 6 0 0z' fill='%23d2a679'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right var(--spacing-sm) center;
|
||||
background-size: 12px;
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
select:focus {
|
||||
border-color: var(--accent-primary);
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px rgba(210, 166, 121, 0.2);
|
||||
}
|
||||
|
||||
select option {
|
||||
background-color: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
padding: var(--spacing-sm);
|
||||
}
|
||||
|
||||
/* Style pour les sélecteurs dans les formulaires modaux */
|
||||
.modal-content select {
|
||||
max-width: 100%;
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
/* Style pour le sélecteur désactivé (pour le premier utilisateur admin) */
|
||||
select:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
/* Styles spécifiques pour les sélecteurs dans users.php */
|
||||
#role, #edit_role {
|
||||
width: auto;
|
||||
min-width: 180px;
|
||||
background-color: var(--input-bg);
|
||||
}
|
||||
|
||||
/* Style visuel différent pour les options */
|
||||
#role option[value="admin"],
|
||||
#edit_role option[value="admin"] {
|
||||
font-weight: bold;
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
|
||||
#role option[value="editor"],
|
||||
#edit_role option[value="editor"] {
|
||||
font-weight: normal;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.options-section {
|
||||
@ -213,4 +276,9 @@
|
||||
.link-item {
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
|
||||
select {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
}
|
||||
}
|
@ -3,4 +3,6 @@
|
||||
@import 'layout.css';
|
||||
@import 'components.css';
|
||||
@import 'forms.css';
|
||||
@import 'editor.css';
|
||||
@import 'editor.css';
|
||||
@import 'buttons.css';
|
||||
@import 'fixes.css';
|
@ -4,6 +4,9 @@
|
||||
/* Import Google Fonts */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Parisienne&display=swap');
|
||||
|
||||
/* Import Font Awesome */
|
||||
@import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css');
|
||||
|
||||
/* Variables globales */
|
||||
:root {
|
||||
/* Couleurs de fond */
|
||||
@ -759,4 +762,207 @@ body {
|
||||
.stats-value {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Styles pour les boutons avec icônes */
|
||||
.about-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
background-color: var(--accent-primary);
|
||||
color: var(--text-tertiary);
|
||||
text-decoration: none;
|
||||
border-radius: var(--radius-sm);
|
||||
transition: var(--transition);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.about-button i {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.about-button:hover {
|
||||
background-color: var(--accent-secondary);
|
||||
}
|
||||
|
||||
/* Navigation entre chapitres */
|
||||
.chapter-nav {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
color: var(--text-secondary);
|
||||
text-decoration: none;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
border-radius: var(--radius-sm);
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.chapter-nav:hover {
|
||||
background-color: var(--bg-secondary);
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
|
||||
/* Bouton retour en haut */
|
||||
.scroll-top {
|
||||
position: fixed;
|
||||
bottom: 2rem;
|
||||
right: 2rem;
|
||||
background-color: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-decoration: none;
|
||||
border: 1px solid var(--border-color);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.scroll-top i {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.scroll-top.visible {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.scroll-top:hover {
|
||||
background-color: var(--accent-primary);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
/* Lien de retour */
|
||||
.back-to-home a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
color: var(--text-secondary);
|
||||
text-decoration: none;
|
||||
transition: var(--transition);
|
||||
padding: var(--spacing-sm) 0;
|
||||
}
|
||||
|
||||
.back-to-home a:hover {
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
|
||||
/* Icônes dans la liste des chapitres */
|
||||
.chapters-list a {
|
||||
position: relative;
|
||||
padding-left: calc(var(--spacing-md) + 1.2rem);
|
||||
}
|
||||
|
||||
.chapters-list a::before {
|
||||
content: "\f15c"; /* Icône de document */
|
||||
font-family: "Font Awesome 6 Free";
|
||||
font-weight: 900;
|
||||
position: absolute;
|
||||
left: var(--spacing-sm);
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.chapters-list .current-chapter::before {
|
||||
content: "\f02e"; /* Icône de marque-page */
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
/* Pour les petits écrans */
|
||||
@media (max-width: 768px) {
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.about-button {
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Modification des styles pour les chapitres en brouillon */
|
||||
.chapters-list .draft-chapter {
|
||||
opacity: 0.7;
|
||||
border-left: 3px solid var(--accent-primary);
|
||||
padding-left: calc(var(--spacing-sm) + 1.5rem) !important;
|
||||
}
|
||||
|
||||
.chapters-list .draft-chapter::before {
|
||||
content: "\f249";
|
||||
left: calc(var(--spacing-sm) + 0.3rem);
|
||||
}
|
||||
|
||||
.draft-label {
|
||||
font-size: 0.8em;
|
||||
background-color: var(--accent-primary);
|
||||
color: var(--text-tertiary);
|
||||
padding: 2px 6px;
|
||||
border-radius: 10px;
|
||||
margin-left: 8px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* S'assurer que l'étiquette de brouillon ne chevauche pas l'icône */
|
||||
.chapters-list a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
padding: var(--spacing-sm);
|
||||
padding-left: calc(var(--spacing-md) + 1.2rem);
|
||||
gap: var(--spacing-xs);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* Pour conserver l'espace correct en cas de texte long */
|
||||
.chapter-title-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* Modification des styles pour les chapitres en brouillon */
|
||||
.new-chapter {
|
||||
border-left: 3px solid #5a8c54;
|
||||
padding-left: calc(var(--spacing-sm) + 1.5rem) !important;
|
||||
}
|
||||
|
||||
.chapters-list .draft-chapter::before {
|
||||
content: "\f249";
|
||||
left: calc(var(--spacing-sm) + 0.3rem);
|
||||
}
|
||||
|
||||
.new-label {
|
||||
font-size: 0.8em;
|
||||
background-color: #5a8c54;
|
||||
color: var(--text-primary);
|
||||
padding: 2px 6px;
|
||||
border-radius: 10px;
|
||||
margin-left: 8px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.chapters-list a {
|
||||
padding-right: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.draft-label {
|
||||
font-size: 0.7em;
|
||||
padding: 1px 4px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
@ -133,33 +133,126 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
});
|
||||
|
||||
// Gestion du menu mobile
|
||||
const navMenu = document.querySelector('.nav-menu');
|
||||
const navBrand = document.querySelector('.nav-brand');
|
||||
// Gestion du menu mobile
|
||||
const navMenu = document.querySelector('.nav-menu');
|
||||
|
||||
function initializeTooltips() {
|
||||
// Ajouter le texte des tooltips à côté des icônes pour mobile
|
||||
document.querySelectorAll('.tooltip:not(.tooltip-initialized)').forEach(button => {
|
||||
if (!button.querySelector('.tooltip-text')) {
|
||||
const tooltip = button.getAttribute('data-tooltip');
|
||||
if (tooltip) {
|
||||
const tooltipSpan = document.createElement('span');
|
||||
tooltipSpan.className = 'tooltip-text';
|
||||
tooltipSpan.textContent = tooltip;
|
||||
button.appendChild(tooltipSpan);
|
||||
button.classList.add('tooltip-initialized');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Observer les changements du DOM pour initialiser les nouveaux tooltips
|
||||
const tooltipObserver = new MutationObserver(function(mutations) {
|
||||
mutations.forEach(function(mutation) {
|
||||
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
|
||||
initializeTooltips();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Initialiser les tooltips existants et commencer l'observation
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initializeTooltips();
|
||||
tooltipObserver.observe(document.body, { childList: true, subtree: true });
|
||||
|
||||
// Créer le bouton hamburger s'il n'existe pas déjà
|
||||
if (!document.querySelector('.menu-toggle')) {
|
||||
const menuToggle = document.createElement('button');
|
||||
menuToggle.className = 'menu-toggle';
|
||||
menuToggle.innerHTML = '☰';
|
||||
menuToggle.setAttribute('aria-label', 'Menu');
|
||||
// Vérifier le mode mobile au chargement et au redimensionnement
|
||||
function checkMobileMode() {
|
||||
const isMobile = window.innerWidth <= 768;
|
||||
|
||||
// Insérer le bouton avant le menu
|
||||
navMenu.parentNode.insertBefore(menuToggle, navMenu);
|
||||
|
||||
// Gérer les clics sur le bouton
|
||||
menuToggle.addEventListener('click', function() {
|
||||
navMenu.classList.toggle('active');
|
||||
});
|
||||
|
||||
// Fermer le menu au clic en dehors
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!navMenu.contains(e.target) && !menuToggle.contains(e.target)) {
|
||||
navMenu.classList.remove('active');
|
||||
// Gestion des conteneurs d'actions
|
||||
const actionContainers = document.querySelectorAll('.story-actions, .chapter-actions, .user-actions');
|
||||
actionContainers.forEach(container => {
|
||||
if (isMobile) {
|
||||
container.classList.add('active');
|
||||
} else {
|
||||
container.classList.remove('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
checkMobileMode();
|
||||
window.addEventListener('resize', checkMobileMode);
|
||||
});
|
||||
|
||||
// Créer le bouton hamburger s'il n'existe pas déjà
|
||||
if (!document.querySelector('.menu-toggle')) {
|
||||
const menuToggle = document.createElement('button');
|
||||
menuToggle.className = 'menu-toggle';
|
||||
menuToggle.innerHTML = '☰';
|
||||
menuToggle.setAttribute('aria-label', 'Menu');
|
||||
|
||||
// Insérer le bouton avant le menu
|
||||
navMenu.parentNode.insertBefore(menuToggle, navMenu);
|
||||
|
||||
// Ajouter le texte des tooltips à côté des icônes pour mobile
|
||||
document.querySelectorAll('.nav-menu .tooltip').forEach(button => {
|
||||
const tooltip = button.getAttribute('data-tooltip');
|
||||
const tooltipSpan = document.createElement('span');
|
||||
tooltipSpan.className = 'tooltip-text';
|
||||
tooltipSpan.textContent = tooltip;
|
||||
button.appendChild(tooltipSpan);
|
||||
});
|
||||
|
||||
// Gérer les clics sur le bouton
|
||||
menuToggle.addEventListener('click', function() {
|
||||
navMenu.classList.toggle('active');
|
||||
});
|
||||
|
||||
// Fermer le menu au clic en dehors
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!navMenu.contains(e.target) && !menuToggle.contains(e.target)) {
|
||||
navMenu.classList.remove('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Ajouter le texte des tooltips à côté des icônes pour mobile - pour tous les tooltips
|
||||
function addTooltipText() {
|
||||
document.querySelectorAll('.tooltip').forEach(button => {
|
||||
// Vérifier si le tooltip-text n'existe pas déjà
|
||||
if (!button.querySelector('.tooltip-text')) {
|
||||
const tooltip = button.getAttribute('data-tooltip');
|
||||
const tooltipSpan = document.createElement('span');
|
||||
tooltipSpan.className = 'tooltip-text';
|
||||
tooltipSpan.textContent = tooltip;
|
||||
button.appendChild(tooltipSpan);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Appliquer à tous les tooltips du site
|
||||
addTooltipText();
|
||||
|
||||
// Détection de la taille d'écran pour activer le mode mobile
|
||||
function checkMobileMode() {
|
||||
const isMobile = window.innerWidth <= 768;
|
||||
|
||||
// Gestion des actions pour les romans
|
||||
const storyActions = document.querySelectorAll('.story-actions');
|
||||
storyActions.forEach(action => {
|
||||
if (isMobile) {
|
||||
action.classList.add('active');
|
||||
} else {
|
||||
action.classList.remove('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Vérifier au chargement et au redimensionnement
|
||||
checkMobileMode();
|
||||
window.addEventListener('resize', checkMobileMode);
|
||||
|
||||
// Gestion de la suppression des romans
|
||||
const storyList = document.querySelector('.stories-list');
|
||||
if (storyList) {
|
||||
|
@ -480,4 +480,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
// Initialisation
|
||||
detectUnsavedChanges();
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// S'assurer que les clics sur les icônes se propagent correctement
|
||||
document.querySelectorAll('.chapter-actions button i, .chapter-actions button .button-text').forEach(element => {
|
||||
element.style.pointerEvents = 'none';
|
||||
});
|
||||
});
|
79
chapitre.php
79
chapitre.php
@ -79,8 +79,12 @@ $config = Config::load();
|
||||
<div class="novel-header-background" style="background-image: url('<?= htmlspecialchars($currentChapter['cover']) ?>');"></div>
|
||||
<?php endif; ?>
|
||||
<div class="header-actions">
|
||||
<a href="index.php" class="about-button">Accueil</a>
|
||||
<a href="roman.php?id=<?= urlencode($storyId) ?>" class="about-button">Roman</a>
|
||||
<a href="index.php" class="about-button">
|
||||
<i class="fas fa-home"></i> Accueil
|
||||
</a>
|
||||
<a href="roman.php?id=<?= urlencode($storyId) ?>" class="about-button">
|
||||
<i class="fas fa-book"></i> Roman
|
||||
</a>
|
||||
</div>
|
||||
<h1>
|
||||
<?= htmlspecialchars($currentChapter['title']) ?>
|
||||
@ -99,15 +103,15 @@ $config = Config::load();
|
||||
<div class="chapter-navigation">
|
||||
<?php if ($prevChapter): ?>
|
||||
<a href="?story=<?= urlencode($storyId) ?>&chapter=<?= urlencode($prevChapter['id']) ?>"
|
||||
class="chapter-nav prev-chapter">
|
||||
← <?= htmlspecialchars($prevChapter['title']) ?>
|
||||
class="chapter-nav prev-chapter">
|
||||
<i class="fas fa-chevron-left"></i> <?= htmlspecialchars($prevChapter['title']) ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($nextChapter): ?>
|
||||
<a href="?story=<?= urlencode($storyId) ?>&chapter=<?= urlencode($nextChapter['id']) ?>"
|
||||
class="chapter-nav next-chapter">
|
||||
<?= htmlspecialchars($nextChapter['title']) ?> →
|
||||
class="chapter-nav next-chapter">
|
||||
<?= htmlspecialchars($nextChapter['title']) ?> <i class="fas fa-chevron-right"></i>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@ -116,25 +120,56 @@ $config = Config::load();
|
||||
<aside class="chapters-menu">
|
||||
<h2>Chapitres</h2>
|
||||
<ul class="chapters-list">
|
||||
<?php
|
||||
foreach ($visibleChapters as $chapter):
|
||||
$isDraft = $chapter['draft'] ?? false;
|
||||
?>
|
||||
<li>
|
||||
<a href="?story=<?= urlencode($storyId) ?>&chapter=<?= urlencode($chapter['id']) ?>"
|
||||
class="<?= $chapter['id'] === $chapterId ? 'current-chapter' : '' ?> <?= $isDraft ? 'draft-chapter' : '' ?>">
|
||||
<?= htmlspecialchars($chapter['title']) ?>
|
||||
<?php if ($isDraft && $canViewDrafts): ?>
|
||||
<span class="draft-label">(Brouillon)</span>
|
||||
<?php endif; ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php
|
||||
// Vérifier que chapters est un tableau avant de le filtrer
|
||||
$chapters = $story['chapters'] ?? [];
|
||||
|
||||
// Filtrer les chapitres pour n'afficher que les chapitres publiés
|
||||
// ou les brouillons si l'utilisateur a les droits
|
||||
$visibleChapters = [];
|
||||
foreach ($chapters as $chapter) {
|
||||
if (!($chapter['draft'] ?? false) || $canViewDrafts) {
|
||||
$visibleChapters[] = $chapter;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($visibleChapters)): ?>
|
||||
<p>Aucun chapitre publié disponible pour le moment.</p>
|
||||
<?php else:
|
||||
foreach ($visibleChapters as $chapter):
|
||||
$isDraft = $chapter['draft'] ?? false;
|
||||
$isNew = false;
|
||||
|
||||
// Vérifier si le chapitre est nouveau (publié il y a moins d'une semaine)
|
||||
// Utiliser 'created' puisque c'est ce champ qui est présent dans vos données
|
||||
if (isset($chapter['created'])) {
|
||||
$publishedTime = strtotime($chapter['created']);
|
||||
$weekAgo = strtotime('-1 week');
|
||||
$isNew = $publishedTime > $weekAgo && !$isDraft;
|
||||
}
|
||||
?>
|
||||
<li>
|
||||
<a href="chapitre.php?story=<?= urlencode($story['id']) ?>&chapter=<?= urlencode($chapter['id']) ?>"
|
||||
class="<?= $chapter['id'] === $chapterId ? 'current-chapter' : '' ?> <?= $isDraft ? 'draft-chapter' : ($isNew ? 'new-chapter' : '') ?>">
|
||||
<span class="chapter-title"><?= htmlspecialchars($chapter['title']) ?></span>
|
||||
<?php if ($isDraft && $canViewDrafts): ?>
|
||||
<span class="draft-label">(Brouillon)</span>
|
||||
<?php elseif ($isNew): ?>
|
||||
<span class="new-label">(Nouveau)</span>
|
||||
<?php endif; ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php
|
||||
endforeach;
|
||||
endif;
|
||||
?>
|
||||
</ul>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<button class="scroll-top" aria-label="Retour en haut de page">↑</button>
|
||||
<button class="scroll-top" aria-label="Retour en haut de page">
|
||||
<i class="fas fa-arrow-up"></i>
|
||||
</button>
|
||||
|
||||
<style>
|
||||
.draft-header::after {
|
||||
|
@ -18,15 +18,7 @@ class Stories {
|
||||
}
|
||||
return json_decode(file_get_contents($file), true);
|
||||
}
|
||||
|
||||
public static function save($story) {
|
||||
// Ajout de l'heure à la date de mise à jour
|
||||
$story['updated'] = date('Y-m-d H:i:s');
|
||||
|
||||
$file = self::$storiesDir . $story['id'] . '.json';
|
||||
return file_put_contents($file, json_encode($story, JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
|
||||
public static function formatDate($date) {
|
||||
if (strlen($date) > 10) { // Si la date contient aussi l'heure
|
||||
$datetime = DateTime::createFromFormat('Y-m-d H:i:s', $date);
|
||||
@ -36,4 +28,54 @@ class Stories {
|
||||
return $datetime ? $datetime->format('d/m/Y') : '';
|
||||
}
|
||||
}
|
||||
|
||||
public static function save($story) {
|
||||
// Mise à jour de la date standard pour tout changement
|
||||
$story['updated'] = date('Y-m-d H:i:s');
|
||||
|
||||
// Vérification si c'est une création initiale
|
||||
$isNew = !file_exists(self::$storiesDir . $story['id'] . '.json');
|
||||
|
||||
// Vérification si un nouveau chapitre a été ajouté ou un chapitre est passé de draft à publié
|
||||
$existingStory = $isNew ? null : self::get($story['id']);
|
||||
$updatePublicDate = $isNew; // Déjà true si c'est un nouveau roman
|
||||
|
||||
if (!$isNew && isset($existingStory['chapters']) && isset($story['chapters'])) {
|
||||
// Vérifier les nouveaux chapitres ou changements de statut
|
||||
foreach ($story['chapters'] as $chapter) {
|
||||
$found = false;
|
||||
foreach ($existingStory['chapters'] as $oldChapter) {
|
||||
if ($oldChapter['id'] === $chapter['id']) {
|
||||
$found = true;
|
||||
// Chapitre passé de brouillon à publié
|
||||
if (($oldChapter['draft'] ?? false) && !($chapter['draft'] ?? false)) {
|
||||
$updatePublicDate = true;
|
||||
// Ajouter une date de publication au chapitre
|
||||
$chapter['published'] = date('Y-m-d H:i:s');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Nouveau chapitre
|
||||
if (!$found && !($chapter['draft'] ?? false)) {
|
||||
$updatePublicDate = true;
|
||||
// S'assurer que le nouveau chapitre a une date de publication
|
||||
if (!isset($chapter['published'])) {
|
||||
$chapter['published'] = date('Y-m-d H:i:s');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mettre à jour la date publique si nécessaire
|
||||
if ($updatePublicDate) {
|
||||
$story['publicUpdated'] = date('Y-m-d H:i:s');
|
||||
} else if (!isset($story['publicUpdated'])) {
|
||||
// S'assurer que le champ existe
|
||||
$story['publicUpdated'] = $story['created'] ?? date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
$file = self::$storiesDir . $story['id'] . '.json';
|
||||
return file_put_contents($file, json_encode($story, JSON_PRETTY_PRINT));
|
||||
}
|
||||
}
|
@ -11,9 +11,9 @@ usort($stories, function($a, $b) {
|
||||
return strtotime($b['updated']) - strtotime($a['updated']);
|
||||
});
|
||||
|
||||
// Fonction pour vérifier si un roman est une nouvelle parution (moins d'une semaine)
|
||||
// Fonction pour vérifier si un roman est une nouvelle parution
|
||||
function isNewRelease($story) {
|
||||
$updateTime = strtotime($story['updated']);
|
||||
$updateTime = strtotime($story['publicUpdated'] ?? $story['updated']);
|
||||
$weekAgo = strtotime('-1 week');
|
||||
return $updateTime > $weekAgo;
|
||||
}
|
||||
@ -64,7 +64,9 @@ function formatDate($date) {
|
||||
</div>
|
||||
|
||||
<div class="header-actions">
|
||||
<a href="about.php" class="about-button">À propos</a>
|
||||
<a href="about.php" class="about-button">
|
||||
<i class="fas fa-info-circle"></i> À propos
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
|
57
roman.php
57
roman.php
@ -45,7 +45,9 @@ $canViewDrafts = Auth::check() && Auth::canAccessStory($storyId);
|
||||
<div class="novel-header-background" style="background-image: url('<?= htmlspecialchars($story['cover']) ?>');"></div>
|
||||
<?php endif; ?>
|
||||
<div class="header-actions">
|
||||
<a href="index.php" class="about-button">Accueil</a>
|
||||
<a href="index.php" class="about-button">
|
||||
<i class="fas fa-home"></i> Accueil
|
||||
</a>
|
||||
</div>
|
||||
<h1><?= htmlspecialchars($story['title']) ?></h1>
|
||||
</header>
|
||||
@ -61,23 +63,41 @@ $canViewDrafts = Auth::check() && Auth::canAccessStory($storyId);
|
||||
<?php if (!empty($story['chapters'])): ?>
|
||||
<ul class="chapters-list">
|
||||
<?php
|
||||
// Vérifier que chapters est un tableau avant de le filtrer
|
||||
$chapters = $story['chapters'] ?? [];
|
||||
|
||||
// Filtrer les chapitres pour n'afficher que les chapitres publiés
|
||||
// ou les brouillons si l'utilisateur a les droits
|
||||
$visibleChapters = array_filter($story['chapters'], function($chapter) use ($canViewDrafts) {
|
||||
return !($chapter['draft'] ?? false) || $canViewDrafts;
|
||||
});
|
||||
$visibleChapters = [];
|
||||
foreach ($chapters as $chapter) {
|
||||
if (!($chapter['draft'] ?? false) || $canViewDrafts) {
|
||||
$visibleChapters[] = $chapter;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($visibleChapters)): ?>
|
||||
<p>Aucun chapitre publié disponible pour le moment.</p>
|
||||
<?php else:
|
||||
foreach ($visibleChapters as $chapter):
|
||||
$isDraft = $chapter['draft'] ?? false;
|
||||
$isNew = false;
|
||||
|
||||
// Vérifier si le chapitre est nouveau (publié il y a moins d'une semaine)
|
||||
// Utiliser 'created' puisque c'est ce champ qui est présent dans vos données
|
||||
if (isset($chapter['created'])) {
|
||||
$publishedTime = strtotime($chapter['created']);
|
||||
$weekAgo = strtotime('-1 week');
|
||||
$isNew = $publishedTime > $weekAgo && !$isDraft;
|
||||
}
|
||||
?>
|
||||
<li>
|
||||
<a href="chapitre.php?story=<?= urlencode($story['id']) ?>&chapter=<?= urlencode($chapter['id']) ?>"
|
||||
class="<?= ($chapter['draft'] ?? false) ? 'draft-chapter' : '' ?>">
|
||||
<?= htmlspecialchars($chapter['title']) ?>
|
||||
<?php if (($chapter['draft'] ?? false) && $canViewDrafts): ?>
|
||||
class="<?= $isDraft ? 'draft-chapter' : ($isNew ? 'new-chapter' : '') ?>">
|
||||
<span class="chapter-title"><?= htmlspecialchars($chapter['title']) ?></span>
|
||||
<?php if ($isDraft && $canViewDrafts): ?>
|
||||
<span class="draft-label">(Brouillon)</span>
|
||||
<?php elseif ($isNew): ?>
|
||||
<span class="new-label">(Nouveau)</span>
|
||||
<?php endif; ?>
|
||||
</a>
|
||||
</li>
|
||||
@ -92,26 +112,9 @@ $canViewDrafts = Auth::check() && Auth::canAccessStory($storyId);
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<button class="scroll-top" aria-label="Retour en haut de page">↑</button>
|
||||
|
||||
<style>
|
||||
.draft-chapter {
|
||||
opacity: 0.7;
|
||||
border-left: 3px solid var(--accent-primary);
|
||||
padding-left: 8px !important;
|
||||
}
|
||||
|
||||
.draft-label {
|
||||
font-size: 0.8em;
|
||||
background-color: var(--accent-primary);
|
||||
color: var(--text-tertiary);
|
||||
padding: 2px 6px;
|
||||
border-radius: 10px;
|
||||
margin-left: 8px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
<button class="scroll-top" aria-label="Retour en haut de page">
|
||||
<i class="fas fa-arrow-up"></i>
|
||||
</button>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
@ -1 +1 @@
|
||||
1.3.0
|
||||
1.3.3
|
Loading…
x
Reference in New Issue
Block a user