@ -10,7 +10,9 @@
|
|||||||
"Bash(/home/mathieu/git/project-notes/notes/test-delete-1.md)",
|
"Bash(/home/mathieu/git/project-notes/notes/test-delete-1.md)",
|
||||||
"Bash(/home/mathieu/git/project-notes/notes/test-delete-2.md)",
|
"Bash(/home/mathieu/git/project-notes/notes/test-delete-2.md)",
|
||||||
"Bash(/home/mathieu/git/project-notes/notes/test-delete-folder/test.md)",
|
"Bash(/home/mathieu/git/project-notes/notes/test-delete-folder/test.md)",
|
||||||
"Bash(npm install)"
|
"Bash(npm install)",
|
||||||
|
"Bash(go get:*)",
|
||||||
|
"Bash(ls:*)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
377
API.md
377
API.md
@ -6,6 +6,7 @@ Base URL: `http://localhost:8080/api/v1`
|
|||||||
## Table des matières
|
## Table des matières
|
||||||
|
|
||||||
- [Vue d'ensemble](#vue-densemble)
|
- [Vue d'ensemble](#vue-densemble)
|
||||||
|
- [Commandes CLI](#commandes-cli)
|
||||||
- [Authentification](#authentification)
|
- [Authentification](#authentification)
|
||||||
- [Formats de données](#formats-de-données)
|
- [Formats de données](#formats-de-données)
|
||||||
- [Endpoints](#endpoints)
|
- [Endpoints](#endpoints)
|
||||||
@ -13,6 +14,9 @@ Base URL: `http://localhost:8080/api/v1`
|
|||||||
- [Récupérer une note](#récupérer-une-note)
|
- [Récupérer une note](#récupérer-une-note)
|
||||||
- [Créer/Mettre à jour une note](#créermettre-à-jour-une-note)
|
- [Créer/Mettre à jour une note](#créermettre-à-jour-une-note)
|
||||||
- [Supprimer une note](#supprimer-une-note)
|
- [Supprimer une note](#supprimer-une-note)
|
||||||
|
- [Lister les notes publiques](#lister-les-notes-publiques)
|
||||||
|
- [Basculer le statut public d'une note](#basculer-le-statut-public-dune-note)
|
||||||
|
- [Notes publiques](#notes-publiques)
|
||||||
- [Codes de statut HTTP](#codes-de-statut-http)
|
- [Codes de statut HTTP](#codes-de-statut-http)
|
||||||
- [Exemples d'utilisation](#exemples-dutilisation)
|
- [Exemples d'utilisation](#exemples-dutilisation)
|
||||||
|
|
||||||
@ -38,6 +42,69 @@ L'API REST de PersoNotes permet de gérer vos notes Markdown via HTTP. Elle supp
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Commandes CLI
|
||||||
|
|
||||||
|
Le serveur inclut des commandes CLI intégrées pour gérer les notes publiques sans avoir à lancer le serveur HTTP.
|
||||||
|
|
||||||
|
### Lister les notes publiques
|
||||||
|
|
||||||
|
Affiche toutes les notes qui ont été exportées en HTML public.
|
||||||
|
|
||||||
|
**Commande** :
|
||||||
|
```bash
|
||||||
|
./server list-public [notes-dir]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Arguments** :
|
||||||
|
- `notes-dir` (optionnel) : Chemin vers le répertoire des notes (défaut: `./notes`)
|
||||||
|
|
||||||
|
**Exemple de sortie** :
|
||||||
|
```
|
||||||
|
📚 Notes publiques (4):
|
||||||
|
|
||||||
|
• 2025 Learning Goals
|
||||||
|
Source: personal/learning-goals.md
|
||||||
|
Public: public/learning-goals.html
|
||||||
|
Date: 2025-11-13 20:06:21
|
||||||
|
|
||||||
|
• AI Writing Assistant
|
||||||
|
Source: archive/ai-assistant.md
|
||||||
|
Public: public/ai-assistant.html
|
||||||
|
Date: 2025-11-13 19:43:28
|
||||||
|
|
||||||
|
• API Endpoints Reference
|
||||||
|
Source: documentation/api/endpoints.md
|
||||||
|
Public: public/endpoints.html
|
||||||
|
Date: 2025-11-13 19:36:57
|
||||||
|
|
||||||
|
• Product Backlog
|
||||||
|
Source: tasks/backlog.md
|
||||||
|
Public: public/backlog.html
|
||||||
|
Date: 2025-11-13 20:13:05
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cas particuliers** :
|
||||||
|
- Si aucune note n'est publique : affiche "Aucune note publique."
|
||||||
|
- Si le fichier `.public.json` n'existe pas : affiche "Aucune note publique trouvée."
|
||||||
|
- Erreur si le répertoire n'existe pas
|
||||||
|
|
||||||
|
**Utilisation typique** :
|
||||||
|
```bash
|
||||||
|
# Lister les notes publiques
|
||||||
|
./server list-public
|
||||||
|
|
||||||
|
# Avec un répertoire personnalisé
|
||||||
|
./server list-public /path/to/notes
|
||||||
|
|
||||||
|
# Compter les notes publiques (Linux/macOS)
|
||||||
|
./server list-public | grep -c "^•"
|
||||||
|
|
||||||
|
# Exporter la liste dans un fichier
|
||||||
|
./server list-public > public-notes-list.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Authentification
|
## Authentification
|
||||||
|
|
||||||
**Version actuelle : Aucune authentification requise**
|
**Version actuelle : Aucune authentification requise**
|
||||||
@ -364,6 +431,313 @@ curl -X DELETE http://localhost:8080/api/v1/notes/projet/old-note.md
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### Lister les notes publiques
|
||||||
|
|
||||||
|
Récupère la liste des notes qui ont été publiées dans l'espace public.
|
||||||
|
|
||||||
|
**Endpoint** : `GET /api/public/list`
|
||||||
|
|
||||||
|
**Paramètres** : Aucun
|
||||||
|
|
||||||
|
**Réponse** : `200 OK`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"notes": [
|
||||||
|
{
|
||||||
|
"path": "projet/backend.md",
|
||||||
|
"title": "Backend API",
|
||||||
|
"published_at": "2025-11-13T14:30:00Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "personal/guide.md",
|
||||||
|
"title": "Guide d'utilisation",
|
||||||
|
"published_at": "2025-11-13T10:15:00Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Champs retournés** :
|
||||||
|
- `path` : Chemin relatif de la note source
|
||||||
|
- `title` : Titre de la note
|
||||||
|
- `published_at` : Date/heure de publication (format ISO 8601)
|
||||||
|
|
||||||
|
**Notes** :
|
||||||
|
- Les notes sont triées par date de publication (plus récentes d'abord)
|
||||||
|
- Les fichiers HTML générés se trouvent dans `public/{nom-de-la-note}.html`
|
||||||
|
- Liste vide si aucune note n'est publique
|
||||||
|
|
||||||
|
**Exemple curl** :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Lister toutes les notes publiques
|
||||||
|
curl http://localhost:8080/api/public/list
|
||||||
|
|
||||||
|
# Avec formatage jq
|
||||||
|
curl -s http://localhost:8080/api/public/list | jq '.notes[] | "\(.title) -> public/\(.path | split("/")[-1] | sub(".md$"; ".html"))"'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Exemple de sortie formatée** :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ curl -s http://localhost:8080/api/public/list | jq '.notes[] | .title'
|
||||||
|
"Backend API"
|
||||||
|
"Guide d'utilisation"
|
||||||
|
"Documentation projet"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Compter les notes publiques** :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s http://localhost:8080/api/public/list | jq '.notes | length'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Basculer le statut public d'une note
|
||||||
|
|
||||||
|
Publie ou retire une note de l'espace public. Génère automatiquement un fichier HTML statique exportable.
|
||||||
|
|
||||||
|
**Endpoint** : `POST /api/public/toggle`
|
||||||
|
|
||||||
|
**Content-Type** : `application/x-www-form-urlencoded`
|
||||||
|
|
||||||
|
**Paramètres** :
|
||||||
|
- `path` (form) : Chemin relatif de la note (ex: `projet/backend.md`)
|
||||||
|
|
||||||
|
**Réponse** : `200 OK`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "public",
|
||||||
|
"path": "projet/backend.md"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Valeurs possibles pour `status` :
|
||||||
|
- `"public"` : La note est maintenant publique (HTML généré)
|
||||||
|
- `"private"` : La note est maintenant privée (HTML supprimé)
|
||||||
|
|
||||||
|
**Comportement** :
|
||||||
|
- ✅ **Génère du HTML statique** dans `public/nom-de-la-note.html`
|
||||||
|
- ✅ **Copie les CSS** nécessaires dans `public/static/`
|
||||||
|
- ✅ **Met à jour l'index** dans `public/index.html`
|
||||||
|
- ✅ **Structure plate** : Tous les fichiers dans `public/`, pas de sous-dossiers
|
||||||
|
- ✅ **Portable** : Les fichiers HTML peuvent être copiés sur n'importe quel serveur web
|
||||||
|
|
||||||
|
**Fichiers générés** :
|
||||||
|
```
|
||||||
|
public/
|
||||||
|
├── index.html # Liste de toutes les notes publiques
|
||||||
|
├── backend.md.html # Note convertie en HTML standalone
|
||||||
|
├── autre-note.html
|
||||||
|
└── static/
|
||||||
|
├── theme.css # Styles copiés
|
||||||
|
└── themes.css
|
||||||
|
```
|
||||||
|
|
||||||
|
**Erreurs** :
|
||||||
|
- `400 Bad Request` : Chemin manquant ou invalide
|
||||||
|
- `404 Not Found` : Note inexistante
|
||||||
|
- `405 Method Not Allowed` : Méthode autre que POST
|
||||||
|
- `500 Internal Server Error` : Erreur de génération HTML
|
||||||
|
|
||||||
|
**Exemple curl** :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Publier une note
|
||||||
|
curl -X POST http://localhost:8080/api/public/toggle \
|
||||||
|
-d "path=projet/backend.md"
|
||||||
|
|
||||||
|
# La note est maintenant accessible à :
|
||||||
|
# http://localhost:8080/public/backend.html
|
||||||
|
```
|
||||||
|
|
||||||
|
**Exemple JavaScript** :
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Publier une note
|
||||||
|
const response = await fetch('/api/public/toggle', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
body: new URLSearchParams({ path: 'projet/backend.md' }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log(data.status); // "public" ou "private"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Scripts utiles** :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# Lister les notes publiques avec leurs URLs
|
||||||
|
|
||||||
|
echo "📚 Notes publiques:"
|
||||||
|
curl -s http://localhost:8080/api/public/list | jq -r '.notes[] | "• \(.title)\n → http://localhost:8080/public/\(.path | split("/")[-1] | sub(".md$"; ".html"))\n"'
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# Exporter toutes les notes en HTML public
|
||||||
|
|
||||||
|
echo "Exporting all notes to public HTML..."
|
||||||
|
curl -s http://localhost:8080/api/v1/notes | jq -r '.notes[].path' | while read path; do
|
||||||
|
curl -X POST http://localhost:8080/api/public/toggle -d "path=$path" > /dev/null 2>&1
|
||||||
|
echo "✓ Published: $path"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ Export terminé!"
|
||||||
|
echo "📊 Total: $(curl -s http://localhost:8080/api/public/list | jq '.notes | length') notes publiques"
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# Retirer toutes les notes du public
|
||||||
|
|
||||||
|
echo "Unpublishing all public notes..."
|
||||||
|
curl -s http://localhost:8080/api/public/list | jq -r '.notes[].path' | while read path; do
|
||||||
|
curl -X POST http://localhost:8080/api/public/toggle -d "path=$path" > /dev/null 2>&1
|
||||||
|
echo "✓ Unpublished: $path"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "✅ Toutes les notes sont maintenant privées"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes publiques
|
||||||
|
|
||||||
|
Le système de notes publiques génère des fichiers HTML statiques exportables. Cette section explique comment utiliser ces fichiers.
|
||||||
|
|
||||||
|
### Accès aux notes publiques
|
||||||
|
|
||||||
|
**Sur le serveur Personotes** :
|
||||||
|
```
|
||||||
|
http://localhost:8080/public/
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fichiers générés** :
|
||||||
|
- `public/index.html` : Liste de toutes les notes publiques
|
||||||
|
- `public/*.html` : Notes converties en HTML standalone
|
||||||
|
- `public/static/` : CSS et assets
|
||||||
|
|
||||||
|
### Déploiement
|
||||||
|
|
||||||
|
Les fichiers HTML sont complètement autonomes et peuvent être déployés sur :
|
||||||
|
|
||||||
|
#### 1. Serveur web classique
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copier sur un serveur Apache/Nginx
|
||||||
|
scp -r public/ user@server.com:/var/www/html/notes/
|
||||||
|
|
||||||
|
# Ou avec rsync
|
||||||
|
rsync -av public/ user@server.com:/var/www/html/notes/
|
||||||
|
```
|
||||||
|
|
||||||
|
**Configuration Nginx** :
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name notes.example.com;
|
||||||
|
root /var/www/html/notes;
|
||||||
|
index index.html;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Configuration Apache** :
|
||||||
|
```apache
|
||||||
|
<VirtualHost *:80>
|
||||||
|
ServerName notes.example.com
|
||||||
|
DocumentRoot /var/www/html/notes
|
||||||
|
</VirtualHost>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. GitHub Pages (gratuit)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd public/
|
||||||
|
git init
|
||||||
|
git add .
|
||||||
|
git commit -m "Public notes"
|
||||||
|
git remote add origin https://github.com/username/notes-public.git
|
||||||
|
git push -u origin main
|
||||||
|
|
||||||
|
# Activer GitHub Pages dans Settings → Pages → main branch
|
||||||
|
# Vos notes seront accessibles à :
|
||||||
|
# https://username.github.io/notes-public/
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Netlify Drop
|
||||||
|
|
||||||
|
1. Allez sur https://app.netlify.com/drop
|
||||||
|
2. Glissez-déposez le dossier `public/`
|
||||||
|
3. Netlify génère automatiquement une URL
|
||||||
|
|
||||||
|
#### 4. Vercel
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd public/
|
||||||
|
npx vercel
|
||||||
|
```
|
||||||
|
|
||||||
|
### Automatisation de l'export
|
||||||
|
|
||||||
|
**Script de synchronisation** :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# sync-public.sh - Synchroniser les notes publiques vers un serveur
|
||||||
|
|
||||||
|
REMOTE_USER="user"
|
||||||
|
REMOTE_HOST="server.com"
|
||||||
|
REMOTE_PATH="/var/www/html/notes"
|
||||||
|
|
||||||
|
rsync -av --delete public/ ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}
|
||||||
|
|
||||||
|
echo "✅ Notes publiques synchronisées !"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Git Hook automatique** :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .git/hooks/post-commit
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Si le dossier public/ a changé, synchroniser
|
||||||
|
if git diff --name-only HEAD~1 | grep -q "^public/"; then
|
||||||
|
./sync-public.sh
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
### Avantages
|
||||||
|
|
||||||
|
- ✅ **Performance** : HTML pré-généré = chargement instantané
|
||||||
|
- ✅ **Sécurité** : Fichiers statiques = surface d'attaque minimale
|
||||||
|
- ✅ **Portabilité** : Fonctionne sur n'importe quel serveur web
|
||||||
|
- ✅ **Gratuit** : Hébergement possible sur GitHub Pages, Netlify
|
||||||
|
- ✅ **SEO** : HTML pré-rendu = indexation optimale par Google
|
||||||
|
- ✅ **Pas de backend** : Pas besoin de Go sur le serveur de destination
|
||||||
|
|
||||||
|
### Limitations
|
||||||
|
|
||||||
|
- ⚠️ **Noms uniques** : Si deux notes dans différents dossiers ont le même nom (ex: `personal/test.md` et `work/test.md`), elles s'écraseront car la structure est plate
|
||||||
|
- ⚠️ **Republication manuelle** : Si vous modifiez une note déjà publique, vous devez la republier pour régénérer le HTML
|
||||||
|
- ⚠️ **Pas de recherche** : Les fichiers HTML n'incluent pas de fonction de recherche (uniquement consultables)
|
||||||
|
|
||||||
|
### Documentation complète
|
||||||
|
|
||||||
|
Pour plus d'informations sur l'export des notes publiques :
|
||||||
|
- **QUICK_START_PUBLIC.md** : Guide de démarrage rapide
|
||||||
|
- **EXPORT_GUIDE.md** : Guide complet de déploiement
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Codes de statut HTTP
|
## Codes de statut HTTP
|
||||||
|
|
||||||
| Code | Signification | Description |
|
| Code | Signification | Description |
|
||||||
@ -556,13 +930,14 @@ echo "$STATS" | jq -r '.notes[].tags[]' | sort | uniq -c | sort -rn | head -5
|
|||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
### v1 (2025-11-10)
|
### v1 (2025-11-13)
|
||||||
- ✨ Première version de l'API REST
|
- ✨ Première version de l'API REST
|
||||||
- ✅ Endpoints: LIST, GET, PUT, DELETE
|
- ✅ Endpoints: LIST, GET, PUT, DELETE
|
||||||
- ✅ Content negotiation JSON/Markdown
|
- ✅ Content negotiation JSON/Markdown
|
||||||
- ✅ Support sous-dossiers
|
- ✅ Support sous-dossiers
|
||||||
- ✅ Gestion automatique du front matter
|
- ✅ Gestion automatique du front matter
|
||||||
- ✅ Ré-indexation automatique
|
- ✅ Ré-indexation automatique
|
||||||
|
- ✅ Export de notes publiques en HTML statique (POST /api/public/toggle)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
155
CLAUDE.md
155
CLAUDE.md
@ -9,11 +9,13 @@ A lightweight web-based Markdown note-taking application with a Go backend and m
|
|||||||
**Key Features**:
|
**Key Features**:
|
||||||
- **Daily Notes**: Quick daily journaling with interactive calendar, keyboard shortcuts (Ctrl/Cmd+D), and structured templates
|
- **Daily Notes**: Quick daily journaling with interactive calendar, keyboard shortcuts (Ctrl/Cmd+D), and structured templates
|
||||||
- **Favorites System**: Star important notes and folders for quick access from the sidebar
|
- **Favorites System**: Star important notes and folders for quick access from the sidebar
|
||||||
|
- **Public Notes**: Share selected notes publicly without authentication via server-rendered HTML pages
|
||||||
- **Note Linking**: Create links between notes with `/link` command and fuzzy search modal
|
- **Note Linking**: Create links between notes with `/link` command and fuzzy search modal
|
||||||
- **Vim Mode**: Full Vim keybindings support (hjkl navigation, modes, commands) for power users
|
- **Vim Mode**: Full Vim keybindings support (hjkl navigation, modes, commands) for power users
|
||||||
- **Multiple Themes**: 8 dark themes (Material Dark, Monokai, Dracula, One Dark, Solarized, Nord, Catppuccin, Everforest)
|
- **Multiple Themes**: 8 dark themes (Material Dark, Monokai, Dracula, One Dark, Solarized, Nord, Catppuccin, Everforest)
|
||||||
- **Font Customization**: 8 fonts (JetBrains Mono, Fira Code, Inter, etc.) with 4 size options
|
- **Font Customization**: 8 fonts (JetBrains Mono, Fira Code, Inter, etc.) with 4 size options
|
||||||
- **Keyboard Shortcuts**: 10+ global shortcuts for navigation, editing, and productivity
|
- **Keyboard Shortcuts**: 10+ global shortcuts for navigation, editing, and productivity
|
||||||
|
- **Internationalization**: Full i18n support with English and French translations, automatic language detection
|
||||||
|
|
||||||
**Recent Modernization**: The project has been migrated from a simple textarea editor to CodeMirror 6, with a Vite build system for frontend modules. The backend remains unchanged, maintaining the same Go architecture with htmx for dynamic interactions.
|
**Recent Modernization**: The project has been migrated from a simple textarea editor to CodeMirror 6, with a Vite build system for frontend modules. The backend remains unchanged, maintaining the same Go architecture with htmx for dynamic interactions.
|
||||||
|
|
||||||
@ -21,31 +23,41 @@ A lightweight web-based Markdown note-taking application with a Go backend and m
|
|||||||
|
|
||||||
### Backend (Go)
|
### Backend (Go)
|
||||||
|
|
||||||
Four main packages under `internal/`:
|
Five main packages under `internal/`:
|
||||||
- **indexer**: Maintains an in-memory index mapping tags to note files. Parses YAML front matter from `.md` files to build the index. Thread-safe with RWMutex.
|
- **indexer**: Maintains an in-memory index mapping tags to note files. Parses YAML front matter from `.md` files to build the index. Thread-safe with RWMutex.
|
||||||
- **watcher**: Uses `fsnotify` to monitor filesystem changes and trigger re-indexing with 200ms debounce. Recursively watches all subdirectories.
|
- **watcher**: Uses `fsnotify` to monitor filesystem changes and trigger re-indexing with 200ms debounce. Recursively watches all subdirectories.
|
||||||
|
- **i18n**: Internationalization support with JSON-based translations. Loads translations at startup and provides `T()` function for translation lookups with variable interpolation.
|
||||||
- **api**: HTTP handlers that serve templates and handle CRUD operations on notes. Updates front matter automatically on save.
|
- **api**: HTTP handlers that serve templates and handle CRUD operations on notes. Updates front matter automatically on save.
|
||||||
- `handler.go` - Main HTML endpoints for the web interface
|
- `handler.go` - Main HTML endpoints for the web interface
|
||||||
- `rest_handler.go` - REST API endpoints (v1)
|
- `rest_handler.go` - REST API endpoints (v1)
|
||||||
- `daily_notes.go` - Daily note creation and calendar functionality
|
- `daily_notes.go` - Daily note creation and calendar functionality
|
||||||
- `favorites.go` - Favorites management (star/unstar notes and folders)
|
- `favorites.go` - Favorites management (star/unstar notes and folders)
|
||||||
|
- `public.go` - Public notes sharing with server-side HTML rendering
|
||||||
|
|
||||||
The server (`cmd/server/main.go`) coordinates these components:
|
The server (`cmd/server/main.go`) coordinates these components:
|
||||||
1. Loads initial index from notes directory
|
1. Loads initial index from notes directory
|
||||||
2. Starts filesystem watcher for automatic re-indexing
|
2. Loads translations from `locales/` directory (JSON files for each language)
|
||||||
3. Pre-parses HTML templates from `templates/`
|
3. Starts filesystem watcher for automatic re-indexing
|
||||||
4. Serves routes:
|
4. Pre-parses HTML templates from `templates/`
|
||||||
|
5. Serves routes:
|
||||||
- `/` (main page)
|
- `/` (main page)
|
||||||
- `/api/v1/notes` and `/api/v1/notes/*` (REST API - JSON responses)
|
- `/api/v1/notes` and `/api/v1/notes/*` (REST API - JSON responses)
|
||||||
|
- `/api/i18n/{lang}` (Translation JSON endpoint)
|
||||||
- `/api/search` (HTML search results)
|
- `/api/search` (HTML search results)
|
||||||
- `/api/notes/*` (HTML editor for notes)
|
- `/api/notes/*` (HTML editor for notes)
|
||||||
- `/api/tree` (HTML file tree)
|
- `/api/tree` (HTML file tree)
|
||||||
- `/api/folders/create` (Folder management)
|
- `/api/folders/create` (Folder management)
|
||||||
- `/api/files/move` (File/folder moving)
|
- `/api/files/move` (File/folder moving)
|
||||||
- `/api/home` (Home page)
|
- `/api/home` (Home page)
|
||||||
- `/api/daily-notes/*` (Daily note creation and calendar)
|
- `/api/about` (About page)
|
||||||
- `/api/favorites/*` (Favorites management)
|
- `/api/daily` and `/api/daily/*` (Daily note creation and calendar)
|
||||||
5. Handles static files from `static/` directory
|
- `/api/favorites` (Favorites management)
|
||||||
|
- `/api/folder/*` (Folder view)
|
||||||
|
- `/public` (Public notes list - no auth required)
|
||||||
|
- `/public/view/*` (View public note - no auth required)
|
||||||
|
- `/api/public/toggle` (Toggle public status - requires auth in production)
|
||||||
|
- `/api/public/list` (List public notes JSON)
|
||||||
|
6. Handles static files from `static/` directory and `public/` directory (for public HTML exports)
|
||||||
|
|
||||||
### Frontend
|
### Frontend
|
||||||
|
|
||||||
@ -69,9 +81,14 @@ frontend/src/
|
|||||||
├── file-tree.js # Drag-and-drop file organization
|
├── file-tree.js # Drag-and-drop file organization
|
||||||
├── favorites.js # Favorites system (star/unstar functionality)
|
├── favorites.js # Favorites system (star/unstar functionality)
|
||||||
├── daily-notes.js # Daily notes creation and calendar widget
|
├── daily-notes.js # Daily notes creation and calendar widget
|
||||||
|
├── public-toggle.js # Public/private status toggle for notes
|
||||||
├── keyboard-shortcuts.js # Global keyboard shortcuts management
|
├── keyboard-shortcuts.js # Global keyboard shortcuts management
|
||||||
├── theme-manager.js # Theme switching and persistence
|
├── theme-manager.js # Theme switching and persistence
|
||||||
├── font-manager.js # Font selection and size management
|
├── font-manager.js # Font selection and size management
|
||||||
|
├── i18n.js # i18n client with translation function and language detection
|
||||||
|
├── language-manager.js # Language selector UI and page translation
|
||||||
|
├── sidebar-sections.js # Sidebar sections management (Recent/Favorites/All notes)
|
||||||
|
├── debug.js # Debug utilities
|
||||||
└── ui.js # Sidebar toggle functionality
|
└── ui.js # Sidebar toggle functionality
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -289,6 +306,33 @@ The favorites system allows quick access to frequently used notes and folders:
|
|||||||
|
|
||||||
Favorites are loaded on server startup and updated in real-time via htmx.
|
Favorites are loaded on server startup and updated in real-time via htmx.
|
||||||
|
|
||||||
|
### Public Notes
|
||||||
|
|
||||||
|
**Implementation**: `internal/api/public.go` and `frontend/src/public-toggle.js`
|
||||||
|
|
||||||
|
The public notes feature allows sharing selected notes publicly without requiring authentication:
|
||||||
|
|
||||||
|
**Features**:
|
||||||
|
- **Toggle Button**: Click 🔒 Privé/🌐 Public button in the editor to publish/unpublish notes
|
||||||
|
- **Server-side Rendering**: Notes converted to HTML using `goldmark` (Go Markdown library)
|
||||||
|
- **Public Routes**: `/public` (list) and `/public/view/{path}` (individual note) - no auth required
|
||||||
|
- **Persistence**: Public notes list stored in `notes/.public.json`
|
||||||
|
- **GitHub Flavored Markdown**: Supports tables, strikethrough, task lists, syntax highlighting
|
||||||
|
- **SEO-friendly**: Pre-rendered HTML with proper meta tags
|
||||||
|
- **Internationalized**: Full i18n support for public pages
|
||||||
|
|
||||||
|
**Security Model**:
|
||||||
|
- Public routes (`/public*`) are accessible without authentication
|
||||||
|
- Admin routes (`/api/public/toggle`) should be protected by reverse proxy auth (Authelia, Authentik, Basic Auth)
|
||||||
|
- In production, use nginx/Caddy with auth to protect all routes except `/public*`
|
||||||
|
|
||||||
|
**Command-line tool**:
|
||||||
|
```bash
|
||||||
|
go run ./cmd/server list-public # List all published notes
|
||||||
|
```
|
||||||
|
|
||||||
|
**Documentation**: See `PUBLIC_NOTES.md` for complete security recommendations and usage guide.
|
||||||
|
|
||||||
### Note Format
|
### Note Format
|
||||||
|
|
||||||
Notes have YAML front matter with these fields:
|
Notes have YAML front matter with these fields:
|
||||||
@ -372,6 +416,7 @@ go mod tidy
|
|||||||
Key backend dependencies:
|
Key backend dependencies:
|
||||||
- `github.com/fsnotify/fsnotify` - Filesystem watcher
|
- `github.com/fsnotify/fsnotify` - Filesystem watcher
|
||||||
- `gopkg.in/yaml.v3` - YAML parsing for front matter
|
- `gopkg.in/yaml.v3` - YAML parsing for front matter
|
||||||
|
- `github.com/yuin/goldmark` - Markdown to HTML conversion for public notes (with GFM extensions)
|
||||||
|
|
||||||
### Vite Build System
|
### Vite Build System
|
||||||
|
|
||||||
@ -510,6 +555,65 @@ Themes are applied via CSS custom properties and persist in localStorage.
|
|||||||
|
|
||||||
Font settings apply to both the editor and preview pane.
|
Font settings apply to both the editor and preview pane.
|
||||||
|
|
||||||
|
### Internationalization (i18n)
|
||||||
|
|
||||||
|
**Implementation**: `internal/i18n/i18n.go`, `frontend/src/i18n.js`, `frontend/src/language-manager.js`
|
||||||
|
|
||||||
|
The application provides full internationalization support:
|
||||||
|
|
||||||
|
#### Backend (Go)
|
||||||
|
- **Package**: `internal/i18n/i18n.go` - Translation engine with JSON-based translation files
|
||||||
|
- **Storage**: Translation files in `locales/` directory (e.g., `en.json`, `fr.json`)
|
||||||
|
- **Loading**: Translations loaded once at server startup from `locales/` directory
|
||||||
|
- **Translation Function**: `T(lang, key, vars...)` - Looks up translation keys with optional variable interpolation
|
||||||
|
- **API Endpoint**: `/api/i18n/{lang}` - Serves translation JSON for frontend consumption
|
||||||
|
- **Helper Functions**: `getLanguage(r)` and `t(r, key, vars...)` in handler.go for request-scoped translations
|
||||||
|
|
||||||
|
#### Frontend (JavaScript)
|
||||||
|
- **Module**: `frontend/src/i18n.js` - Client-side translation with automatic language detection
|
||||||
|
- **Detection Order**:
|
||||||
|
1. localStorage (`language` key)
|
||||||
|
2. Browser language (navigator.language)
|
||||||
|
3. Default to English
|
||||||
|
- **Translation Function**: `t(key, vars)` - Looks up translations with variable interpolation
|
||||||
|
- **Page Translation**: `translatePage()` - Automatically translates elements with `data-i18n` attributes
|
||||||
|
- **Language Manager**: `frontend/src/language-manager.js` - UI for language selection in Settings modal
|
||||||
|
- **Persistence**: Language preference stored in localStorage
|
||||||
|
- **Reload**: Automatic page translation on language change
|
||||||
|
|
||||||
|
#### Available Languages
|
||||||
|
- **English** (en) - Default language
|
||||||
|
- **French** (fr) - Full translation
|
||||||
|
|
||||||
|
#### Adding New Languages
|
||||||
|
1. Create new JSON file in `locales/` (e.g., `locales/es.json`)
|
||||||
|
2. Copy structure from `en.json` with 200+ translation keys
|
||||||
|
3. Translate all keys maintaining the nested structure
|
||||||
|
4. Add language option to Settings modal in templates
|
||||||
|
5. Restart server to load new translations
|
||||||
|
|
||||||
|
#### Translation Key Structure
|
||||||
|
```
|
||||||
|
app.name → "Personotes"
|
||||||
|
menu.home → "Home" / "Accueil"
|
||||||
|
editor.save → "Save" / "Sauvegarder"
|
||||||
|
editor.confirmDelete → "Are you sure...?" (supports {{filename}} interpolation)
|
||||||
|
errors.methodNotAllowed → "Method not allowed" / "Méthode non supportée"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Variable Interpolation
|
||||||
|
```javascript
|
||||||
|
// JavaScript
|
||||||
|
t('editor.confirmDelete', { filename: 'test.md' })
|
||||||
|
// → "Are you sure you want to delete this note (test.md)?"
|
||||||
|
|
||||||
|
// Go
|
||||||
|
h.t(r, "editor.confirmDelete", map[string]string{"filename": "test.md"})
|
||||||
|
// → "Êtes-vous sûr de vouloir supprimer cette note (test.md) ?"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Documentation**: See `I18N_IMPLEMENTATION.md` for complete implementation details.
|
||||||
|
|
||||||
### Vim Mode
|
### Vim Mode
|
||||||
|
|
||||||
**Implementation**: `frontend/src/vim-mode-manager.js` using `@replit/codemirror-vim`
|
**Implementation**: `frontend/src/vim-mode-manager.js` using `@replit/codemirror-vim`
|
||||||
@ -565,6 +669,13 @@ File path validation in `handler.go` and `rest_handler.go`:
|
|||||||
- CORS not configured (same-origin only)
|
- CORS not configured (same-origin only)
|
||||||
- No rate limiting (add middleware if needed)
|
- No rate limiting (add middleware if needed)
|
||||||
|
|
||||||
|
**Public Notes Security**:
|
||||||
|
- Routes `/public` and `/public/view/*` are intentionally public (no auth required)
|
||||||
|
- Route `/api/public/toggle` MUST be protected in production (requires auth to publish/unpublish)
|
||||||
|
- Use reverse proxy (nginx with Authelia/Authentik, or Basic Auth) to protect admin routes
|
||||||
|
- Public HTML is server-rendered with goldmark (prevents XSS, safe rendering)
|
||||||
|
- See `PUBLIC_NOTES.md` for complete security setup guide
|
||||||
|
|
||||||
### Template System
|
### Template System
|
||||||
|
|
||||||
Templates are pre-parsed at startup. The API handler returns HTML fragments that htmx inserts into the page. Out-of-band swaps update the file tree sidebar without full page reload.
|
Templates are pre-parsed at startup. The API handler returns HTML fragments that htmx inserts into the page. Out-of-band swaps update the file tree sidebar without full page reload.
|
||||||
@ -752,10 +863,14 @@ personotes/
|
|||||||
│ │ ├── handler.go # HTTP handlers for CRUD operations
|
│ │ ├── handler.go # HTTP handlers for CRUD operations
|
||||||
│ │ ├── rest_handler.go # REST API v1 endpoints
|
│ │ ├── rest_handler.go # REST API v1 endpoints
|
||||||
│ │ ├── daily_notes.go # Daily notes functionality
|
│ │ ├── daily_notes.go # Daily notes functionality
|
||||||
│ │ └── favorites.go # Favorites management
|
│ │ ├── favorites.go # Favorites management
|
||||||
|
│ │ └── public.go # Public notes sharing
|
||||||
│ ├── indexer/
|
│ ├── indexer/
|
||||||
│ │ ├── indexer.go # Note indexing and search
|
│ │ ├── indexer.go # Note indexing and search
|
||||||
│ │ └── indexer_test.go # Indexer tests
|
│ │ └── indexer_test.go # Indexer tests
|
||||||
|
│ ├── i18n/
|
||||||
|
│ │ ├── i18n.go # Internationalization engine
|
||||||
|
│ │ └── i18n_test.go # i18n tests
|
||||||
│ └── watcher/
|
│ └── watcher/
|
||||||
│ └── watcher.go # Filesystem watcher with fsnotify
|
│ └── watcher.go # Filesystem watcher with fsnotify
|
||||||
├── frontend/ # Frontend build system
|
├── frontend/ # Frontend build system
|
||||||
@ -767,9 +882,15 @@ personotes/
|
|||||||
│ │ ├── file-tree.js # Drag-and-drop file tree
|
│ │ ├── file-tree.js # Drag-and-drop file tree
|
||||||
│ │ ├── favorites.js # Favorites system
|
│ │ ├── favorites.js # Favorites system
|
||||||
│ │ ├── daily-notes.js # Daily notes and calendar widget
|
│ │ ├── daily-notes.js # Daily notes and calendar widget
|
||||||
|
│ │ ├── public-toggle.js # Public/private status toggle
|
||||||
│ │ ├── keyboard-shortcuts.js # Global keyboard shortcuts
|
│ │ ├── keyboard-shortcuts.js # Global keyboard shortcuts
|
||||||
│ │ ├── theme-manager.js # Theme switching
|
│ │ ├── theme-manager.js # Theme switching
|
||||||
│ │ ├── font-manager.js # Font customization
|
│ │ ├── font-manager.js # Font customization
|
||||||
|
│ │ ├── i18n.js # i18n client and translation
|
||||||
|
│ │ ├── language-manager.js # Language selector UI
|
||||||
|
│ │ ├── link-inserter.js # Note linking modal
|
||||||
|
│ │ ├── sidebar-sections.js # Sidebar sections management
|
||||||
|
│ │ ├── debug.js # Debug utilities
|
||||||
│ │ └── ui.js # Sidebar toggle
|
│ │ └── ui.js # Sidebar toggle
|
||||||
│ ├── package.json # NPM dependencies
|
│ ├── package.json # NPM dependencies
|
||||||
│ ├── package-lock.json
|
│ ├── package-lock.json
|
||||||
@ -785,11 +906,20 @@ personotes/
|
|||||||
│ ├── file-tree.html # File tree sidebar
|
│ ├── file-tree.html # File tree sidebar
|
||||||
│ ├── search-results.html # Search results
|
│ ├── search-results.html # Search results
|
||||||
│ └── new-note-prompt.html # New note modal
|
│ └── new-note-prompt.html # New note modal
|
||||||
|
├── locales/ # i18n translation files
|
||||||
|
│ ├── en.json # English translations (200+ keys)
|
||||||
|
│ ├── fr.json # French translations (200+ keys)
|
||||||
|
│ └── README.md # Translation guide
|
||||||
├── notes/ # Note storage directory
|
├── notes/ # Note storage directory
|
||||||
│ ├── *.md # Markdown files with YAML front matter
|
│ ├── *.md # Markdown files with YAML front matter
|
||||||
│ ├── daily/ # Daily notes (YYYY-MM-DD.md)
|
│ ├── daily/ # Daily notes (YYYY-MM-DD.md)
|
||||||
│ ├── .favorites.json # Favorites list (auto-generated)
|
│ ├── .favorites.json # Favorites list (auto-generated)
|
||||||
|
│ ├── .public.json # Public notes list (auto-generated)
|
||||||
│ └── daily-note-template.md # Optional daily note template
|
│ └── daily-note-template.md # Optional daily note template
|
||||||
|
├── public/ # Public HTML exports (auto-generated)
|
||||||
|
│ ├── index.html # Public notes list page
|
||||||
|
│ ├── *.html # Individual public notes (HTML)
|
||||||
|
│ └── static/ # Static assets for public pages
|
||||||
├── docs/ # Documentation
|
├── docs/ # Documentation
|
||||||
│ ├── KEYBOARD_SHORTCUTS.md # Keyboard shortcuts reference
|
│ ├── KEYBOARD_SHORTCUTS.md # Keyboard shortcuts reference
|
||||||
│ ├── DAILY_NOTES.md # Daily notes guide
|
│ ├── DAILY_NOTES.md # Daily notes guide
|
||||||
@ -798,6 +928,8 @@ personotes/
|
|||||||
├── go.mod # Go dependencies
|
├── go.mod # Go dependencies
|
||||||
├── go.sum
|
├── go.sum
|
||||||
├── API.md # REST API documentation
|
├── API.md # REST API documentation
|
||||||
|
├── I18N_IMPLEMENTATION.md # i18n implementation guide
|
||||||
|
├── PUBLIC_NOTES.md # Public notes documentation and security guide
|
||||||
└── CLAUDE.md # This file
|
└── CLAUDE.md # This file
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -809,8 +941,10 @@ personotes/
|
|||||||
- `internal/api/rest_handler.go` - REST API v1 endpoints
|
- `internal/api/rest_handler.go` - REST API v1 endpoints
|
||||||
- `internal/api/daily_notes.go` - Daily notes and calendar functionality
|
- `internal/api/daily_notes.go` - Daily notes and calendar functionality
|
||||||
- `internal/api/favorites.go` - Favorites management
|
- `internal/api/favorites.go` - Favorites management
|
||||||
|
- `internal/api/public.go` - Public notes sharing and HTML export
|
||||||
- `internal/indexer/indexer.go` - Search and indexing logic
|
- `internal/indexer/indexer.go` - Search and indexing logic
|
||||||
- `internal/watcher/watcher.go` - Filesystem monitoring
|
- `internal/watcher/watcher.go` - Filesystem monitoring
|
||||||
|
- `internal/i18n/i18n.go` - Internationalization engine
|
||||||
|
|
||||||
**Frontend Development**:
|
**Frontend Development**:
|
||||||
- `frontend/src/editor.js` - CodeMirror editor, preview, slash commands
|
- `frontend/src/editor.js` - CodeMirror editor, preview, slash commands
|
||||||
@ -820,12 +954,17 @@ personotes/
|
|||||||
- `frontend/src/file-tree.js` - File tree interactions and drag-and-drop
|
- `frontend/src/file-tree.js` - File tree interactions and drag-and-drop
|
||||||
- `frontend/src/favorites.js` - Favorites system
|
- `frontend/src/favorites.js` - Favorites system
|
||||||
- `frontend/src/daily-notes.js` - Daily notes creation and calendar widget
|
- `frontend/src/daily-notes.js` - Daily notes creation and calendar widget
|
||||||
|
- `frontend/src/public-toggle.js` - Public/private status toggle
|
||||||
- `frontend/src/keyboard-shortcuts.js` - Global keyboard shortcuts
|
- `frontend/src/keyboard-shortcuts.js` - Global keyboard shortcuts
|
||||||
- `frontend/src/theme-manager.js` - Theme switching logic
|
- `frontend/src/theme-manager.js` - Theme switching logic
|
||||||
- `frontend/src/font-manager.js` - Font customization logic
|
- `frontend/src/font-manager.js` - Font customization logic
|
||||||
|
- `frontend/src/i18n.js` - i18n client and translation
|
||||||
|
- `frontend/src/language-manager.js` - Language selector UI
|
||||||
|
- `frontend/src/sidebar-sections.js` - Sidebar sections management
|
||||||
- `frontend/src/ui.js` - UI utilities (sidebar toggle)
|
- `frontend/src/ui.js` - UI utilities (sidebar toggle)
|
||||||
- `static/theme.css` - Styling and theming (8 themes)
|
- `static/theme.css` - Styling and theming (8 themes)
|
||||||
- `templates/*.html` - HTML templates (Go template syntax)
|
- `templates/*.html` - HTML templates (Go template syntax)
|
||||||
|
- `locales/*.json` - Translation files (en.json, fr.json)
|
||||||
|
|
||||||
**Configuration**:
|
**Configuration**:
|
||||||
- `frontend/vite.config.js` - Frontend build configuration
|
- `frontend/vite.config.js` - Frontend build configuration
|
||||||
|
|||||||
360
EXPORT_GUIDE.md
Normal file
360
EXPORT_GUIDE.md
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
# 📤 Export de Notes Publiques - Guide Complet
|
||||||
|
|
||||||
|
## 🎯 Concept
|
||||||
|
|
||||||
|
Les notes publiques sont **générées en fichiers HTML statiques** directement sur le serveur. Cela signifie que vous pouvez copier le dossier `public/` et le déployer sur n'importe quel serveur web (Apache, Nginx, GitHub Pages, Netlify, etc.) **sans avoir besoin de Go**.
|
||||||
|
|
||||||
|
## 🔍 Lister les notes publiques
|
||||||
|
|
||||||
|
Vous pouvez lister toutes les notes publiques avec la commande CLI intégrée :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./server list-public
|
||||||
|
```
|
||||||
|
|
||||||
|
**Sortie** :
|
||||||
|
```
|
||||||
|
📚 Notes publiques (4):
|
||||||
|
|
||||||
|
• Mon Guide
|
||||||
|
Source: personal/guide.md
|
||||||
|
Public: public/guide.html
|
||||||
|
Date: 2025-11-13 20:06:21
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette commande lit le fichier `notes/.public.json` et affiche :
|
||||||
|
- Le titre de chaque note
|
||||||
|
- Son chemin source
|
||||||
|
- Son chemin public
|
||||||
|
- La date de publication
|
||||||
|
|
||||||
|
## 📁 Structure générée
|
||||||
|
|
||||||
|
Quand vous publiez une note, le système génère automatiquement :
|
||||||
|
|
||||||
|
```
|
||||||
|
public/
|
||||||
|
├── index.html # Liste de toutes les notes publiques
|
||||||
|
├── ma-note.html # Notes converties en HTML (structure plate)
|
||||||
|
├── autre.html
|
||||||
|
└── static/
|
||||||
|
├── theme.css # Styles CSS copiés
|
||||||
|
└── themes.css
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note** : La structure est plate - toutes les notes publiques sont directement dans `public/`, peu importe leur emplacement d'origine dans vos dossiers personnels.
|
||||||
|
|
||||||
|
## 🔄 Génération automatique
|
||||||
|
|
||||||
|
### Quand une note est publiée
|
||||||
|
|
||||||
|
1. **L'utilisateur clique sur "🔒 Privé"** dans l'éditeur
|
||||||
|
2. Le système :
|
||||||
|
- Lit le fichier Markdown
|
||||||
|
- Extrait le front matter (titre, tags, date)
|
||||||
|
- Convertit le Markdown en HTML avec goldmark
|
||||||
|
- Génère un fichier HTML standalone complet
|
||||||
|
- Copie les CSS nécessaires
|
||||||
|
- Régénère `index.html` avec la nouvelle note
|
||||||
|
3. **Le bouton devient "🌐 Public"**
|
||||||
|
|
||||||
|
### Quand une note est retirée
|
||||||
|
|
||||||
|
1. **L'utilisateur clique sur "🌐 Public"**
|
||||||
|
2. Le système :
|
||||||
|
- Supprime le fichier HTML correspondant
|
||||||
|
- Régénère `index.html` sans cette note
|
||||||
|
3. **Le bouton redevient "🔒 Privé"**
|
||||||
|
|
||||||
|
## 📋 Emplacement des fichiers
|
||||||
|
|
||||||
|
- **Source** : `notes/` (vos fichiers Markdown originaux)
|
||||||
|
- **Export** : `public/` (fichiers HTML générés)
|
||||||
|
- **Index** : `.public.json` (liste des notes publiques)
|
||||||
|
|
||||||
|
## 🚀 Déploiement
|
||||||
|
|
||||||
|
### Option 1 : Copie manuelle sur serveur web
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copier le dossier public/ sur votre serveur
|
||||||
|
scp -r public/ user@server.com:/var/www/html/notes/
|
||||||
|
|
||||||
|
# Ou avec rsync
|
||||||
|
rsync -av public/ user@server.com:/var/www/html/notes/
|
||||||
|
```
|
||||||
|
|
||||||
|
**Configuration Nginx** :
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name notes.example.com;
|
||||||
|
|
||||||
|
root /var/www/html/notes;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ =404;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Configuration Apache** :
|
||||||
|
```apache
|
||||||
|
<VirtualHost *:80>
|
||||||
|
ServerName notes.example.com
|
||||||
|
DocumentRoot /var/www/html/notes
|
||||||
|
|
||||||
|
<Directory /var/www/html/notes>
|
||||||
|
Options Indexes FollowSymLinks
|
||||||
|
AllowOverride All
|
||||||
|
Require all granted
|
||||||
|
</Directory>
|
||||||
|
</VirtualHost>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2 : GitHub Pages (gratuit)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Créer un repo GitHub
|
||||||
|
git init public/
|
||||||
|
cd public/
|
||||||
|
git add .
|
||||||
|
git commit -m "Initial public notes"
|
||||||
|
|
||||||
|
# 2. Pousser vers GitHub
|
||||||
|
git remote add origin https://github.com/username/notes-public.git
|
||||||
|
git push -u origin main
|
||||||
|
|
||||||
|
# 3. Activer GitHub Pages
|
||||||
|
# Settings → Pages → Source: main branch → Save
|
||||||
|
```
|
||||||
|
|
||||||
|
Vos notes seront disponibles à : `https://username.github.io/notes-public/`
|
||||||
|
|
||||||
|
### Option 3 : Netlify Drop (ultra simple)
|
||||||
|
|
||||||
|
1. Allez sur https://app.netlify.com/drop
|
||||||
|
2. Glissez-déposez le dossier `public/`
|
||||||
|
3. Netlify génère automatiquement une URL
|
||||||
|
|
||||||
|
### Option 4 : Vercel
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd public/
|
||||||
|
npx vercel
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 Automatisation avec script
|
||||||
|
|
||||||
|
Créez un script pour synchroniser automatiquement :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# sync-public.sh
|
||||||
|
|
||||||
|
# Serveur de destination
|
||||||
|
REMOTE_USER="user"
|
||||||
|
REMOTE_HOST="server.com"
|
||||||
|
REMOTE_PATH="/var/www/html/notes"
|
||||||
|
|
||||||
|
# Synchroniser
|
||||||
|
rsync -av --delete public/ ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}
|
||||||
|
|
||||||
|
echo "✅ Notes publiques synchronisées !"
|
||||||
|
```
|
||||||
|
|
||||||
|
Utilisation :
|
||||||
|
```bash
|
||||||
|
chmod +x sync-public.sh
|
||||||
|
./sync-public.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 Automatisation avec Git Hook
|
||||||
|
|
||||||
|
Synchroniser automatiquement après chaque publication :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .git/hooks/post-commit
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Si le dossier public/ a changé, synchroniser
|
||||||
|
if git diff --name-only HEAD~1 | grep -q "^public/"; then
|
||||||
|
./sync-public.sh
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 Avantages de cette approche
|
||||||
|
|
||||||
|
### ✅ Performance
|
||||||
|
- HTML pré-généré = temps de chargement instantané
|
||||||
|
- Pas de backend requis = moins de latence
|
||||||
|
- Cacheable à 100% par CDN
|
||||||
|
|
||||||
|
### ✅ Sécurité
|
||||||
|
- Fichiers statiques = surface d'attaque minimale
|
||||||
|
- Pas de code serveur exécuté côté public
|
||||||
|
- Isolation complète du système d'édition
|
||||||
|
|
||||||
|
### ✅ Portabilité
|
||||||
|
- Fonctionne sur **n'importe quel serveur web**
|
||||||
|
- Hébergement gratuit possible (GitHub Pages, Netlify)
|
||||||
|
- Peut être mis sur un CDN (Cloudflare, etc.)
|
||||||
|
|
||||||
|
### ✅ Simplicité
|
||||||
|
- Pas besoin de Go sur le serveur de destination
|
||||||
|
- Pas de base de données
|
||||||
|
- Juste des fichiers HTML à copier
|
||||||
|
|
||||||
|
### ✅ SEO
|
||||||
|
- HTML pré-rendu = indexation optimale par Google
|
||||||
|
- Pas de JavaScript requis pour afficher le contenu
|
||||||
|
- Meta tags facilement ajoutables
|
||||||
|
|
||||||
|
## 🎨 Personnalisation
|
||||||
|
|
||||||
|
### Modifier les styles
|
||||||
|
|
||||||
|
Les fichiers CSS sont dans `public/static/`. Vous pouvez les modifier directement :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Éditer le CSS
|
||||||
|
nano public/static/theme.css
|
||||||
|
|
||||||
|
# Ou copier vos propres CSS
|
||||||
|
cp my-custom.css public/static/custom.css
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ajouter des meta tags
|
||||||
|
|
||||||
|
Modifiez `internal/api/public.go` dans la fonction `generateStandaloneHTML()` :
|
||||||
|
|
||||||
|
```go
|
||||||
|
<head>
|
||||||
|
<!-- Meta tags SEO -->
|
||||||
|
<meta name="description" content="%s">
|
||||||
|
<meta property="og:title" content="%s">
|
||||||
|
<meta property="og:type" content="article">
|
||||||
|
|
||||||
|
<!-- Votre titre existant -->
|
||||||
|
<title>%s - PersoNotes</title>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ajouter Google Analytics
|
||||||
|
|
||||||
|
Ajoutez dans `generateStandaloneHTML()` avant `</head>` :
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Google Analytics -->
|
||||||
|
<script async src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"></script>
|
||||||
|
<script>
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
function gtag(){dataLayer.push(arguments);}
|
||||||
|
gtag('js', new Date());
|
||||||
|
gtag('config', 'GA_MEASUREMENT_ID');
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🐛 Dépannage
|
||||||
|
|
||||||
|
### Les notes ne s'affichent pas
|
||||||
|
|
||||||
|
**Problème** : `/public/` retourne 404
|
||||||
|
|
||||||
|
**Solutions** :
|
||||||
|
1. Vérifier que le dossier `public/` existe : `ls -la public/`
|
||||||
|
2. Publier au moins une note pour générer le dossier
|
||||||
|
3. Redémarrer le serveur Go
|
||||||
|
|
||||||
|
### Les styles ne s'appliquent pas
|
||||||
|
|
||||||
|
**Problème** : La page s'affiche sans CSS
|
||||||
|
|
||||||
|
**Solutions** :
|
||||||
|
1. Vérifier que `public/static/theme.css` existe
|
||||||
|
2. Les chemins CSS sont relatifs : `../static/theme.css`
|
||||||
|
3. Si vous copiez ailleurs, ajustez les chemins
|
||||||
|
|
||||||
|
### Le HTML contient du Markdown brut
|
||||||
|
|
||||||
|
**Problème** : Le Markdown n'est pas converti
|
||||||
|
|
||||||
|
**Solutions** :
|
||||||
|
1. Vérifier que goldmark est installé : `go mod tidy`
|
||||||
|
2. Republier la note (toggle Private → Public)
|
||||||
|
3. Vérifier les logs du serveur
|
||||||
|
|
||||||
|
## 📝 Workflow recommandé
|
||||||
|
|
||||||
|
### Workflow quotidien
|
||||||
|
|
||||||
|
1. **Écrire** vos notes en Markdown dans l'éditeur
|
||||||
|
2. **Publier** les notes que vous voulez partager (bouton Public)
|
||||||
|
3. **Synchroniser** le dossier `public/` vers votre serveur (manuel ou automatique)
|
||||||
|
|
||||||
|
### Workflow avec Git
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Publier des notes via l'interface web
|
||||||
|
# 2. Commiter les changements
|
||||||
|
git add public/
|
||||||
|
git commit -m "Publish new notes"
|
||||||
|
git push
|
||||||
|
|
||||||
|
# 3. Sur le serveur de production
|
||||||
|
git pull
|
||||||
|
# Les nouvelles notes sont disponibles !
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔐 Sécurité
|
||||||
|
|
||||||
|
### Routes publiques (pas d'auth)
|
||||||
|
- ✅ `/public/*` - Fichiers HTML statiques accessibles à tous
|
||||||
|
|
||||||
|
### Routes privées (nécessitent auth)
|
||||||
|
- 🔒 `/` - Interface d'édition
|
||||||
|
- 🔒 `/api/*` - APIs de modification
|
||||||
|
- 🔒 `/api/public/toggle` - **Protéger cet endpoint !**
|
||||||
|
|
||||||
|
### Protection recommandée
|
||||||
|
|
||||||
|
Utilisez un reverse proxy avec authentification pour protéger l'édition :
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
# Nginx
|
||||||
|
location /public {
|
||||||
|
# Pas d'auth - accessible à tous
|
||||||
|
proxy_pass http://localhost:8080;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
# Auth requise pour édition
|
||||||
|
auth_basic "Personotes Admin";
|
||||||
|
auth_basic_user_file /etc/nginx/.htpasswd;
|
||||||
|
proxy_pass http://localhost:8080;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📚 Ressources
|
||||||
|
|
||||||
|
- **Goldmark** : https://github.com/yuin/goldmark
|
||||||
|
- **GitHub Pages** : https://pages.github.com
|
||||||
|
- **Netlify** : https://www.netlify.com
|
||||||
|
- **Vercel** : https://vercel.com
|
||||||
|
|
||||||
|
## ❓ FAQ
|
||||||
|
|
||||||
|
**Q: Puis-je personnaliser le design des pages publiques ?**
|
||||||
|
R: Oui ! Modifiez `public/static/theme.css` ou éditez la fonction `generateStandaloneHTML()` dans `internal/api/public.go`.
|
||||||
|
|
||||||
|
**Q: Les images dans mes notes fonctionnent-elles ?**
|
||||||
|
R: Oui, si vous utilisez des URLs absolues ou si vous copiez les images dans `public/static/images/`.
|
||||||
|
|
||||||
|
**Q: Puis-je exporter vers PDF ?**
|
||||||
|
R: Les fichiers HTML peuvent être convertis en PDF avec wkhtmltopdf ou un navigateur (Imprimer → PDF).
|
||||||
|
|
||||||
|
**Q: Comment supprimer toutes les notes publiques d'un coup ?**
|
||||||
|
R: Supprimez le dossier `public/` et le fichier `.public.json`, puis relancez le serveur.
|
||||||
|
|
||||||
|
**Q: Les modifications des notes sont-elles automatiquement republiées ?**
|
||||||
|
R: Non. Si vous modifiez une note Markdown qui est déjà publique, vous devez la republier (toggle Private puis Public) pour régénérer le HTML.
|
||||||
188
PUBLIC_NOTES.md
Normal file
188
PUBLIC_NOTES.md
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
# 📚 Public Notes - Documentation
|
||||||
|
|
||||||
|
## Vue d'ensemble
|
||||||
|
|
||||||
|
La fonctionnalité **Public Notes** permet de partager certaines notes sélectionnées avec le public, sans authentification requise. Ces notes sont converties en HTML côté serveur avec `goldmark` et sont accessibles via des URLs dédiées.
|
||||||
|
|
||||||
|
## 🔒 Architecture de sécurité
|
||||||
|
|
||||||
|
### Routes publiques (sans authentification)
|
||||||
|
|
||||||
|
Les routes suivantes sont **publiques** et **accessibles à tous** :
|
||||||
|
|
||||||
|
- `GET /public` - Liste de toutes les notes publiques
|
||||||
|
- `GET /public/view/{path}` - Affichage d'une note publique en HTML
|
||||||
|
|
||||||
|
### Routes protégées (nécessitent authentification en production)
|
||||||
|
|
||||||
|
Toutes les autres routes doivent être protégées par authentification :
|
||||||
|
|
||||||
|
- `/` - Interface principale de l'application
|
||||||
|
- `/api/*` - Toutes les API (édition, création, suppression)
|
||||||
|
- `/api/public/toggle` - Toggle du statut public (création/retrait)
|
||||||
|
|
||||||
|
**IMPORTANT** : L'endpoint `/api/public/toggle` doit être protégé car il permet de publier/dépublier des notes.
|
||||||
|
|
||||||
|
## 🛡️ Recommandations de sécurité
|
||||||
|
|
||||||
|
### Option 1 : Authelia / Authentik (Recommandé)
|
||||||
|
|
||||||
|
Utilisez un reverse proxy avec authentification devant Personotes :
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
# Nginx avec Authelia
|
||||||
|
location /public {
|
||||||
|
proxy_pass http://localhost:8080;
|
||||||
|
# Pas d'auth pour /public
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost:8080;
|
||||||
|
# Rediriger vers Authelia pour authentification
|
||||||
|
auth_request /authelia;
|
||||||
|
error_page 401 =302 https://auth.example.com;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2 : Basic Auth Nginx
|
||||||
|
|
||||||
|
Configuration minimale pour protéger l'édition :
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
location /public {
|
||||||
|
proxy_pass http://localhost:8080;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost:8080;
|
||||||
|
auth_basic "Personotes Admin";
|
||||||
|
auth_basic_user_file /etc/nginx/.htpasswd;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 3 : Cloudflare Access / Zero Trust
|
||||||
|
|
||||||
|
Utilisez Cloudflare Access pour protéger toutes les routes sauf `/public`.
|
||||||
|
|
||||||
|
## 📝 Utilisation
|
||||||
|
|
||||||
|
### 1. Publier une note
|
||||||
|
|
||||||
|
1. Ouvrez une note dans l'éditeur
|
||||||
|
2. Cliquez sur le bouton **🔒 Privé**
|
||||||
|
3. Le bouton devient **🌐 Public** (vert)
|
||||||
|
4. La note est maintenant visible sur `/public`
|
||||||
|
|
||||||
|
**Note** : Le titre et les tags sont automatiquement extraits du front matter.
|
||||||
|
|
||||||
|
### 2. Retirer une note du public
|
||||||
|
|
||||||
|
1. Ouvrez la note publiée
|
||||||
|
2. Cliquez sur le bouton **🌐 Public**
|
||||||
|
3. Le bouton redevient **🔒 Privé**
|
||||||
|
4. La note n'est plus visible sur `/public`
|
||||||
|
|
||||||
|
### 3. Voir les notes publiques
|
||||||
|
|
||||||
|
Accédez à : `http://votre-domaine.com/public`
|
||||||
|
|
||||||
|
## 🌍 Internationalisation
|
||||||
|
|
||||||
|
La fonctionnalité est entièrement internationalisée :
|
||||||
|
|
||||||
|
- **Anglais** : Public Notes, Published on, etc.
|
||||||
|
- **Français** : Notes Publiques, Publié le, etc.
|
||||||
|
|
||||||
|
La langue est détectée automatiquement depuis :
|
||||||
|
1. localStorage (`language` key)
|
||||||
|
2. Navigateur (navigator.language)
|
||||||
|
3. Par défaut : Anglais
|
||||||
|
|
||||||
|
## 📂 Fichiers techniques
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
|
||||||
|
- `internal/api/public.go` - Handlers pour les notes publiques
|
||||||
|
- `notes/.public.json` - Liste des notes publiques (auto-généré)
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
|
||||||
|
- `frontend/src/public-toggle.js` - Gestion du bouton Public/Privé
|
||||||
|
- `templates/public-list.html` - Liste des notes publiques
|
||||||
|
- `templates/public-view.html` - Vue d'une note publique
|
||||||
|
|
||||||
|
### Traductions
|
||||||
|
|
||||||
|
- `locales/en.json` - Traductions anglaises (section `public`)
|
||||||
|
- `locales/fr.json` - Traductions françaises (section `public`)
|
||||||
|
|
||||||
|
## 🎨 Rendu HTML
|
||||||
|
|
||||||
|
Les notes publiques sont rendues avec **goldmark** (bibliothèque Go) :
|
||||||
|
|
||||||
|
- ✅ GitHub Flavored Markdown (GFM)
|
||||||
|
- ✅ Tables, strikethrough, task lists
|
||||||
|
- ✅ Syntax highlighting (highlight.js)
|
||||||
|
- ✅ Typographie intelligente (smart quotes, dashes)
|
||||||
|
- ✅ Auto-génération des IDs pour les titres
|
||||||
|
|
||||||
|
**Avantages** :
|
||||||
|
- SEO-friendly (HTML pré-rendu)
|
||||||
|
- Performance optimale (pas de JS requis)
|
||||||
|
- Sécurité renforcée (rendu côté serveur)
|
||||||
|
- Cache facile (HTML statique)
|
||||||
|
|
||||||
|
## 🔗 URLs et partage
|
||||||
|
|
||||||
|
### Structure des URLs
|
||||||
|
|
||||||
|
- Liste : `https://votre-domaine.com/public`
|
||||||
|
- Note : `https://votre-domaine.com/public/view/path/to/note.md`
|
||||||
|
|
||||||
|
### Partage sur réseaux sociaux
|
||||||
|
|
||||||
|
Pour améliorer le partage, vous pouvez ajouter des meta tags Open Graph dans `templates/public-view.html` :
|
||||||
|
|
||||||
|
```html
|
||||||
|
<meta property="og:title" content="{{.Title}}">
|
||||||
|
<meta property="og:type" content="article">
|
||||||
|
<meta property="og:url" content="https://votre-domaine.com/public/view/{{.Path}}">
|
||||||
|
<meta property="og:description" content="Note publique partagée">
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Évolutions futures possibles
|
||||||
|
|
||||||
|
- [ ] Export PDF des notes publiques
|
||||||
|
- [ ] RSS feed des notes publiques
|
||||||
|
- [ ] Sitemap XML pour SEO
|
||||||
|
- [ ] Commentaires sur les notes publiques
|
||||||
|
- [ ] Analytics (pages vues, etc.)
|
||||||
|
- [ ] Bouton "Partager" avec liens directs
|
||||||
|
|
||||||
|
## ❓ FAQ
|
||||||
|
|
||||||
|
### Q: Les notes publiques peuvent-elles contenir des liens vers des notes privées ?
|
||||||
|
|
||||||
|
**R:** Oui, mais les liens vers des notes privées ne fonctionneront que pour les utilisateurs authentifiés. Pour les visiteurs publics, ils verront une erreur 403.
|
||||||
|
|
||||||
|
### Q: Puis-je changer le thème des pages publiques ?
|
||||||
|
|
||||||
|
**R:** Oui, modifiez les templates `public-list.html` et `public-view.html`. Ils utilisent les mêmes CSS que l'application principale (`static/theme.css`).
|
||||||
|
|
||||||
|
### Q: Comment savoir quelles notes sont publiques ?
|
||||||
|
|
||||||
|
**R:** Consultez le fichier `notes/.public.json` ou accédez à `/public` pour voir la liste complète.
|
||||||
|
|
||||||
|
### Q: Les images dans les notes publiques fonctionnent-elles ?
|
||||||
|
|
||||||
|
**R:** Oui, si les images sont référencées avec des chemins absolus ou des URLs. Les chemins relatifs ne fonctionneront que si les images sont dans `static/`.
|
||||||
|
|
||||||
|
### Q: Peut-on avoir plusieurs niveaux d'accès (public, privé, semi-privé) ?
|
||||||
|
|
||||||
|
**R:** Actuellement non. Il y a uniquement 2 niveaux : **privé** (nécessite auth) et **public** (accessible à tous). Pour plus de granularité, utilisez un système d'authentification avec rôles (Authelia, Authentik).
|
||||||
|
|
||||||
|
## 📞 Support
|
||||||
|
|
||||||
|
Pour toute question ou problème :
|
||||||
|
- GitHub Issues : https://github.com/mathieu/personotes/issues
|
||||||
|
- Documentation complète : `/docs/USAGE_GUIDE.md`
|
||||||
119
QUICK_START_PUBLIC.md
Normal file
119
QUICK_START_PUBLIC.md
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
# 🚀 Quick Start - Notes Publiques
|
||||||
|
|
||||||
|
## Comment ça marche ?
|
||||||
|
|
||||||
|
Les notes publiques sont **générées en fichiers HTML statiques**. Vous pouvez les copier et les héberger n'importe où !
|
||||||
|
|
||||||
|
## 📝 Publier une note
|
||||||
|
|
||||||
|
1. Ouvrez une note dans l'éditeur
|
||||||
|
2. Cliquez sur le bouton **🔒 Privé**
|
||||||
|
3. Le bouton devient **🌐 Public** ✅
|
||||||
|
|
||||||
|
**Que se passe-t-il ?**
|
||||||
|
- Un fichier HTML est généré dans `public/notes/`
|
||||||
|
- Le fichier `public/index.html` est mis à jour
|
||||||
|
- Les CSS sont copiés dans `public/static/`
|
||||||
|
|
||||||
|
## 🌐 Accéder aux notes publiques
|
||||||
|
|
||||||
|
### Lister les notes publiques (CLI)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./server list-public
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette commande affiche toutes les notes exportées avec leurs chemins source et public.
|
||||||
|
|
||||||
|
### Sur le serveur Personotes
|
||||||
|
|
||||||
|
```
|
||||||
|
http://localhost:8080/public/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Structure générée
|
||||||
|
|
||||||
|
```
|
||||||
|
public/
|
||||||
|
├── index.html # Liste de toutes vos notes publiques
|
||||||
|
├── ma-note.html # Vos notes en HTML (structure plate)
|
||||||
|
├── autre.html
|
||||||
|
└── static/
|
||||||
|
├── theme.css # Styles
|
||||||
|
└── themes.css
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📤 Exporter vers un autre serveur
|
||||||
|
|
||||||
|
### Méthode 1 : Copie simple
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copier tout le dossier public/
|
||||||
|
cp -r public/ /var/www/html/notes/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Méthode 2 : SCP (serveur distant)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scp -r public/ user@server.com:/var/www/html/notes/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Méthode 3 : GitHub Pages (gratuit)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd public/
|
||||||
|
git init
|
||||||
|
git add .
|
||||||
|
git commit -m "Public notes"
|
||||||
|
git remote add origin https://github.com/username/notes.git
|
||||||
|
git push -u origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
Activez GitHub Pages dans les settings du repo → vos notes sont en ligne !
|
||||||
|
|
||||||
|
### Méthode 4 : Netlify Drop
|
||||||
|
|
||||||
|
1. Allez sur https://app.netlify.com/drop
|
||||||
|
2. Glissez-déposez le dossier `public/`
|
||||||
|
3. C'est en ligne !
|
||||||
|
|
||||||
|
## 🔧 Configuration serveur web
|
||||||
|
|
||||||
|
### Apache
|
||||||
|
|
||||||
|
```apache
|
||||||
|
<VirtualHost *:80>
|
||||||
|
ServerName notes.example.com
|
||||||
|
DocumentRoot /var/www/html/notes
|
||||||
|
</VirtualHost>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nginx
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name notes.example.com;
|
||||||
|
root /var/www/html/notes;
|
||||||
|
index index.html;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## ✨ Avantages
|
||||||
|
|
||||||
|
- ✅ **Portable** : Fonctionne sur n'importe quel serveur web
|
||||||
|
- ✅ **Rapide** : HTML pré-généré = chargement instantané
|
||||||
|
- ✅ **Gratuit** : Hébergez sur GitHub Pages, Netlify, etc.
|
||||||
|
- ✅ **SEO** : Google peut indexer vos notes
|
||||||
|
- ✅ **Sécurisé** : Pas de code serveur, juste du HTML
|
||||||
|
|
||||||
|
## 🔄 Workflow typique
|
||||||
|
|
||||||
|
1. **Écrire** dans Personotes (Markdown)
|
||||||
|
2. **Publier** (bouton Public)
|
||||||
|
3. **Copier** le dossier `public/` vers votre serveur
|
||||||
|
4. **Profit !** Vos notes sont en ligne
|
||||||
|
|
||||||
|
## 📖 Plus d'infos
|
||||||
|
|
||||||
|
Voir `EXPORT_GUIDE.md` pour le guide complet.
|
||||||
@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -10,6 +11,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -20,21 +22,82 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// Vérifier si une sous-commande est demandée
|
||||||
|
if len(os.Args) > 1 && os.Args[1] == "list-public" {
|
||||||
|
cmdListPublic()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
addr := flag.String("addr", ":8080", "Adresse d ecoute HTTP")
|
addr := flag.String("addr", ":8080", "Adresse d ecoute HTTP")
|
||||||
notesDir := flag.String("notes-dir", "./notes", "Repertoire contenant les notes Markdown")
|
notesDir := flag.String("notes-dir", "./notes", "Repertoire contenant les notes Markdown")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
logger := log.New(os.Stdout, "[server] ", log.LstdFlags)
|
logger := log.New(os.Stdout, "[server] ", log.LstdFlags)
|
||||||
|
|
||||||
|
runServer(*addr, *notesDir, logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cmdListPublic liste les notes publiques
|
||||||
|
func cmdListPublic() {
|
||||||
|
notesDir := "./notes"
|
||||||
|
if len(os.Args) > 2 && os.Args[2] != "" {
|
||||||
|
notesDir = os.Args[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
publicFile := filepath.Join(notesDir, ".public.json")
|
||||||
|
|
||||||
|
// Lire le fichier .public.json
|
||||||
|
data, err := os.ReadFile(publicFile)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
fmt.Println("Aucune note publique trouvée.")
|
||||||
|
fmt.Printf("Le fichier %s n'existe pas.\n", publicFile)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "Erreur de lecture: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var publicNotes struct {
|
||||||
|
Notes []struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
PublishedAt time.Time `json:"published_at"`
|
||||||
|
} `json:"notes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(data, &publicNotes); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Erreur de parsing JSON: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(publicNotes.Notes) == 0 {
|
||||||
|
fmt.Println("Aucune note publique.")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\n📚 Notes publiques (%d):\n\n", len(publicNotes.Notes))
|
||||||
|
for _, note := range publicNotes.Notes {
|
||||||
|
filename := filepath.Base(note.Path)
|
||||||
|
htmlFile := filename[:len(filename)-3] + ".html"
|
||||||
|
|
||||||
|
fmt.Printf("• %s\n", note.Title)
|
||||||
|
fmt.Printf(" Source: %s\n", note.Path)
|
||||||
|
fmt.Printf(" Public: public/%s\n", htmlFile)
|
||||||
|
fmt.Printf(" Date: %s\n\n", note.PublishedAt.Format("2006-01-02 15:04:05"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runServer(addr, notesDir string, logger *log.Logger) {
|
||||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||||
defer stop()
|
defer stop()
|
||||||
|
|
||||||
if err := ensureDir(*notesDir); err != nil {
|
if err := ensureDir(notesDir); err != nil {
|
||||||
logger.Fatalf("repertoire notes invalide: %v", err)
|
logger.Fatalf("repertoire notes invalide: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
idx := indexer.New()
|
idx := indexer.New()
|
||||||
if err := idx.Load(*notesDir); err != nil {
|
if err := idx.Load(notesDir); err != nil {
|
||||||
logger.Fatalf("echec de l indexation initiale: %v", err)
|
logger.Fatalf("echec de l indexation initiale: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +108,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
logger.Printf("traductions chargees: %v", translator.GetAvailableLanguages())
|
logger.Printf("traductions chargees: %v", translator.GetAvailableLanguages())
|
||||||
|
|
||||||
w, err := watcher.Start(ctx, *notesDir, idx, logger)
|
w, err := watcher.Start(ctx, notesDir, idx, logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatalf("echec du watcher: %v", err)
|
logger.Fatalf("echec du watcher: %v", err)
|
||||||
}
|
}
|
||||||
@ -63,6 +126,12 @@ func main() {
|
|||||||
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
|
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
|
||||||
mux.Handle("/frontend/", http.StripPrefix("/frontend/", http.FileServer(http.Dir("./frontend"))))
|
mux.Handle("/frontend/", http.StripPrefix("/frontend/", http.FileServer(http.Dir("./frontend"))))
|
||||||
|
|
||||||
|
// Servir les fichiers HTML publics (notes exportées)
|
||||||
|
publicDir := filepath.Join(notesDir, "..", "public")
|
||||||
|
if _, err := os.Stat(publicDir); err == nil {
|
||||||
|
mux.Handle("/public/", http.StripPrefix("/public/", http.FileServer(http.Dir(publicDir))))
|
||||||
|
}
|
||||||
|
|
||||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path != "/" {
|
if r.URL.Path != "/" {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
@ -77,7 +146,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
apiHandler := api.NewHandler(*notesDir, idx, templates, logger, translator)
|
apiHandler := api.NewHandler(notesDir, idx, templates, logger, translator)
|
||||||
mux.Handle("/api/i18n/", apiHandler) // I18n translations
|
mux.Handle("/api/i18n/", apiHandler) // I18n translations
|
||||||
mux.Handle("/api/v1/notes", apiHandler) // REST API v1
|
mux.Handle("/api/v1/notes", apiHandler) // REST API v1
|
||||||
mux.Handle("/api/v1/notes/", apiHandler) // REST API v1
|
mux.Handle("/api/v1/notes/", apiHandler) // REST API v1
|
||||||
@ -93,16 +162,18 @@ func main() {
|
|||||||
mux.Handle("/api/folder/", apiHandler) // Folder view
|
mux.Handle("/api/folder/", apiHandler) // Folder view
|
||||||
mux.Handle("/api/notes/", apiHandler)
|
mux.Handle("/api/notes/", apiHandler)
|
||||||
mux.Handle("/api/tree", apiHandler)
|
mux.Handle("/api/tree", apiHandler)
|
||||||
|
mux.Handle("/api/public/list", apiHandler) // List public notes
|
||||||
|
mux.Handle("/api/public/toggle", apiHandler) // Toggle public status (génère HTML statique)
|
||||||
|
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: *addr,
|
Addr: addr,
|
||||||
Handler: mux,
|
Handler: mux,
|
||||||
ReadTimeout: 15 * time.Second,
|
ReadTimeout: 15 * time.Second,
|
||||||
WriteTimeout: 15 * time.Second,
|
WriteTimeout: 15 * time.Second,
|
||||||
IdleTimeout: 60 * time.Second,
|
IdleTimeout: 60 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Printf("demarrage du serveur sur %s", *addr)
|
logger.Printf("demarrage du serveur sur %s", addr)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
|
|||||||
360
docs/EXPORT_GUIDE.md
Normal file
360
docs/EXPORT_GUIDE.md
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
# 📤 Export de Notes Publiques - Guide Complet
|
||||||
|
|
||||||
|
## 🎯 Concept
|
||||||
|
|
||||||
|
Les notes publiques sont **générées en fichiers HTML statiques** directement sur le serveur. Cela signifie que vous pouvez copier le dossier `public/` et le déployer sur n'importe quel serveur web (Apache, Nginx, GitHub Pages, Netlify, etc.) **sans avoir besoin de Go**.
|
||||||
|
|
||||||
|
## 🔍 Lister les notes publiques
|
||||||
|
|
||||||
|
Vous pouvez lister toutes les notes publiques avec la commande CLI intégrée :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./server list-public
|
||||||
|
```
|
||||||
|
|
||||||
|
**Sortie** :
|
||||||
|
```
|
||||||
|
📚 Notes publiques (4):
|
||||||
|
|
||||||
|
• Mon Guide
|
||||||
|
Source: personal/guide.md
|
||||||
|
Public: public/guide.html
|
||||||
|
Date: 2025-11-13 20:06:21
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette commande lit le fichier `notes/.public.json` et affiche :
|
||||||
|
- Le titre de chaque note
|
||||||
|
- Son chemin source
|
||||||
|
- Son chemin public
|
||||||
|
- La date de publication
|
||||||
|
|
||||||
|
## 📁 Structure générée
|
||||||
|
|
||||||
|
Quand vous publiez une note, le système génère automatiquement :
|
||||||
|
|
||||||
|
```
|
||||||
|
public/
|
||||||
|
├── index.html # Liste de toutes les notes publiques
|
||||||
|
├── ma-note.html # Notes converties en HTML (structure plate)
|
||||||
|
├── autre.html
|
||||||
|
└── static/
|
||||||
|
├── theme.css # Styles CSS copiés
|
||||||
|
└── themes.css
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note** : La structure est plate - toutes les notes publiques sont directement dans `public/`, peu importe leur emplacement d'origine dans vos dossiers personnels.
|
||||||
|
|
||||||
|
## 🔄 Génération automatique
|
||||||
|
|
||||||
|
### Quand une note est publiée
|
||||||
|
|
||||||
|
1. **L'utilisateur clique sur "🔒 Privé"** dans l'éditeur
|
||||||
|
2. Le système :
|
||||||
|
- Lit le fichier Markdown
|
||||||
|
- Extrait le front matter (titre, tags, date)
|
||||||
|
- Convertit le Markdown en HTML avec goldmark
|
||||||
|
- Génère un fichier HTML standalone complet
|
||||||
|
- Copie les CSS nécessaires
|
||||||
|
- Régénère `index.html` avec la nouvelle note
|
||||||
|
3. **Le bouton devient "🌐 Public"**
|
||||||
|
|
||||||
|
### Quand une note est retirée
|
||||||
|
|
||||||
|
1. **L'utilisateur clique sur "🌐 Public"**
|
||||||
|
2. Le système :
|
||||||
|
- Supprime le fichier HTML correspondant
|
||||||
|
- Régénère `index.html` sans cette note
|
||||||
|
3. **Le bouton redevient "🔒 Privé"**
|
||||||
|
|
||||||
|
## 📋 Emplacement des fichiers
|
||||||
|
|
||||||
|
- **Source** : `notes/` (vos fichiers Markdown originaux)
|
||||||
|
- **Export** : `public/` (fichiers HTML générés)
|
||||||
|
- **Index** : `.public.json` (liste des notes publiques)
|
||||||
|
|
||||||
|
## 🚀 Déploiement
|
||||||
|
|
||||||
|
### Option 1 : Copie manuelle sur serveur web
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copier le dossier public/ sur votre serveur
|
||||||
|
scp -r public/ user@server.com:/var/www/html/notes/
|
||||||
|
|
||||||
|
# Ou avec rsync
|
||||||
|
rsync -av public/ user@server.com:/var/www/html/notes/
|
||||||
|
```
|
||||||
|
|
||||||
|
**Configuration Nginx** :
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name notes.example.com;
|
||||||
|
|
||||||
|
root /var/www/html/notes;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ =404;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Configuration Apache** :
|
||||||
|
```apache
|
||||||
|
<VirtualHost *:80>
|
||||||
|
ServerName notes.example.com
|
||||||
|
DocumentRoot /var/www/html/notes
|
||||||
|
|
||||||
|
<Directory /var/www/html/notes>
|
||||||
|
Options Indexes FollowSymLinks
|
||||||
|
AllowOverride All
|
||||||
|
Require all granted
|
||||||
|
</Directory>
|
||||||
|
</VirtualHost>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2 : GitHub Pages (gratuit)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Créer un repo GitHub
|
||||||
|
git init public/
|
||||||
|
cd public/
|
||||||
|
git add .
|
||||||
|
git commit -m "Initial public notes"
|
||||||
|
|
||||||
|
# 2. Pousser vers GitHub
|
||||||
|
git remote add origin https://github.com/username/notes-public.git
|
||||||
|
git push -u origin main
|
||||||
|
|
||||||
|
# 3. Activer GitHub Pages
|
||||||
|
# Settings → Pages → Source: main branch → Save
|
||||||
|
```
|
||||||
|
|
||||||
|
Vos notes seront disponibles à : `https://username.github.io/notes-public/`
|
||||||
|
|
||||||
|
### Option 3 : Netlify Drop (ultra simple)
|
||||||
|
|
||||||
|
1. Allez sur https://app.netlify.com/drop
|
||||||
|
2. Glissez-déposez le dossier `public/`
|
||||||
|
3. Netlify génère automatiquement une URL
|
||||||
|
|
||||||
|
### Option 4 : Vercel
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd public/
|
||||||
|
npx vercel
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 Automatisation avec script
|
||||||
|
|
||||||
|
Créez un script pour synchroniser automatiquement :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# sync-public.sh
|
||||||
|
|
||||||
|
# Serveur de destination
|
||||||
|
REMOTE_USER="user"
|
||||||
|
REMOTE_HOST="server.com"
|
||||||
|
REMOTE_PATH="/var/www/html/notes"
|
||||||
|
|
||||||
|
# Synchroniser
|
||||||
|
rsync -av --delete public/ ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}
|
||||||
|
|
||||||
|
echo "✅ Notes publiques synchronisées !"
|
||||||
|
```
|
||||||
|
|
||||||
|
Utilisation :
|
||||||
|
```bash
|
||||||
|
chmod +x sync-public.sh
|
||||||
|
./sync-public.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 Automatisation avec Git Hook
|
||||||
|
|
||||||
|
Synchroniser automatiquement après chaque publication :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .git/hooks/post-commit
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Si le dossier public/ a changé, synchroniser
|
||||||
|
if git diff --name-only HEAD~1 | grep -q "^public/"; then
|
||||||
|
./sync-public.sh
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 Avantages de cette approche
|
||||||
|
|
||||||
|
### ✅ Performance
|
||||||
|
- HTML pré-généré = temps de chargement instantané
|
||||||
|
- Pas de backend requis = moins de latence
|
||||||
|
- Cacheable à 100% par CDN
|
||||||
|
|
||||||
|
### ✅ Sécurité
|
||||||
|
- Fichiers statiques = surface d'attaque minimale
|
||||||
|
- Pas de code serveur exécuté côté public
|
||||||
|
- Isolation complète du système d'édition
|
||||||
|
|
||||||
|
### ✅ Portabilité
|
||||||
|
- Fonctionne sur **n'importe quel serveur web**
|
||||||
|
- Hébergement gratuit possible (GitHub Pages, Netlify)
|
||||||
|
- Peut être mis sur un CDN (Cloudflare, etc.)
|
||||||
|
|
||||||
|
### ✅ Simplicité
|
||||||
|
- Pas besoin de Go sur le serveur de destination
|
||||||
|
- Pas de base de données
|
||||||
|
- Juste des fichiers HTML à copier
|
||||||
|
|
||||||
|
### ✅ SEO
|
||||||
|
- HTML pré-rendu = indexation optimale par Google
|
||||||
|
- Pas de JavaScript requis pour afficher le contenu
|
||||||
|
- Meta tags facilement ajoutables
|
||||||
|
|
||||||
|
## 🎨 Personnalisation
|
||||||
|
|
||||||
|
### Modifier les styles
|
||||||
|
|
||||||
|
Les fichiers CSS sont dans `public/static/`. Vous pouvez les modifier directement :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Éditer le CSS
|
||||||
|
nano public/static/theme.css
|
||||||
|
|
||||||
|
# Ou copier vos propres CSS
|
||||||
|
cp my-custom.css public/static/custom.css
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ajouter des meta tags
|
||||||
|
|
||||||
|
Modifiez `internal/api/public.go` dans la fonction `generateStandaloneHTML()` :
|
||||||
|
|
||||||
|
```go
|
||||||
|
<head>
|
||||||
|
<!-- Meta tags SEO -->
|
||||||
|
<meta name="description" content="%s">
|
||||||
|
<meta property="og:title" content="%s">
|
||||||
|
<meta property="og:type" content="article">
|
||||||
|
|
||||||
|
<!-- Votre titre existant -->
|
||||||
|
<title>%s - PersoNotes</title>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ajouter Google Analytics
|
||||||
|
|
||||||
|
Ajoutez dans `generateStandaloneHTML()` avant `</head>` :
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Google Analytics -->
|
||||||
|
<script async src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"></script>
|
||||||
|
<script>
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
function gtag(){dataLayer.push(arguments);}
|
||||||
|
gtag('js', new Date());
|
||||||
|
gtag('config', 'GA_MEASUREMENT_ID');
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🐛 Dépannage
|
||||||
|
|
||||||
|
### Les notes ne s'affichent pas
|
||||||
|
|
||||||
|
**Problème** : `/public/` retourne 404
|
||||||
|
|
||||||
|
**Solutions** :
|
||||||
|
1. Vérifier que le dossier `public/` existe : `ls -la public/`
|
||||||
|
2. Publier au moins une note pour générer le dossier
|
||||||
|
3. Redémarrer le serveur Go
|
||||||
|
|
||||||
|
### Les styles ne s'appliquent pas
|
||||||
|
|
||||||
|
**Problème** : La page s'affiche sans CSS
|
||||||
|
|
||||||
|
**Solutions** :
|
||||||
|
1. Vérifier que `public/static/theme.css` existe
|
||||||
|
2. Les chemins CSS sont relatifs : `../static/theme.css`
|
||||||
|
3. Si vous copiez ailleurs, ajustez les chemins
|
||||||
|
|
||||||
|
### Le HTML contient du Markdown brut
|
||||||
|
|
||||||
|
**Problème** : Le Markdown n'est pas converti
|
||||||
|
|
||||||
|
**Solutions** :
|
||||||
|
1. Vérifier que goldmark est installé : `go mod tidy`
|
||||||
|
2. Republier la note (toggle Private → Public)
|
||||||
|
3. Vérifier les logs du serveur
|
||||||
|
|
||||||
|
## 📝 Workflow recommandé
|
||||||
|
|
||||||
|
### Workflow quotidien
|
||||||
|
|
||||||
|
1. **Écrire** vos notes en Markdown dans l'éditeur
|
||||||
|
2. **Publier** les notes que vous voulez partager (bouton Public)
|
||||||
|
3. **Synchroniser** le dossier `public/` vers votre serveur (manuel ou automatique)
|
||||||
|
|
||||||
|
### Workflow avec Git
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Publier des notes via l'interface web
|
||||||
|
# 2. Commiter les changements
|
||||||
|
git add public/
|
||||||
|
git commit -m "Publish new notes"
|
||||||
|
git push
|
||||||
|
|
||||||
|
# 3. Sur le serveur de production
|
||||||
|
git pull
|
||||||
|
# Les nouvelles notes sont disponibles !
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔐 Sécurité
|
||||||
|
|
||||||
|
### Routes publiques (pas d'auth)
|
||||||
|
- ✅ `/public/*` - Fichiers HTML statiques accessibles à tous
|
||||||
|
|
||||||
|
### Routes privées (nécessitent auth)
|
||||||
|
- 🔒 `/` - Interface d'édition
|
||||||
|
- 🔒 `/api/*` - APIs de modification
|
||||||
|
- 🔒 `/api/public/toggle` - **Protéger cet endpoint !**
|
||||||
|
|
||||||
|
### Protection recommandée
|
||||||
|
|
||||||
|
Utilisez un reverse proxy avec authentification pour protéger l'édition :
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
# Nginx
|
||||||
|
location /public {
|
||||||
|
# Pas d'auth - accessible à tous
|
||||||
|
proxy_pass http://localhost:8080;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
# Auth requise pour édition
|
||||||
|
auth_basic "Personotes Admin";
|
||||||
|
auth_basic_user_file /etc/nginx/.htpasswd;
|
||||||
|
proxy_pass http://localhost:8080;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📚 Ressources
|
||||||
|
|
||||||
|
- **Goldmark** : https://github.com/yuin/goldmark
|
||||||
|
- **GitHub Pages** : https://pages.github.com
|
||||||
|
- **Netlify** : https://www.netlify.com
|
||||||
|
- **Vercel** : https://vercel.com
|
||||||
|
|
||||||
|
## ❓ FAQ
|
||||||
|
|
||||||
|
**Q: Puis-je personnaliser le design des pages publiques ?**
|
||||||
|
R: Oui ! Modifiez `public/static/theme.css` ou éditez la fonction `generateStandaloneHTML()` dans `internal/api/public.go`.
|
||||||
|
|
||||||
|
**Q: Les images dans mes notes fonctionnent-elles ?**
|
||||||
|
R: Oui, si vous utilisez des URLs absolues ou si vous copiez les images dans `public/static/images/`.
|
||||||
|
|
||||||
|
**Q: Puis-je exporter vers PDF ?**
|
||||||
|
R: Les fichiers HTML peuvent être convertis en PDF avec wkhtmltopdf ou un navigateur (Imprimer → PDF).
|
||||||
|
|
||||||
|
**Q: Comment supprimer toutes les notes publiques d'un coup ?**
|
||||||
|
R: Supprimez le dossier `public/` et le fichier `.public.json`, puis relancez le serveur.
|
||||||
|
|
||||||
|
**Q: Les modifications des notes sont-elles automatiquement republiées ?**
|
||||||
|
R: Non. Si vous modifiez une note Markdown qui est déjà publique, vous devez la republier (toggle Private puis Public) pour régénérer le HTML.
|
||||||
@ -133,13 +133,13 @@ class FavoritesManager {
|
|||||||
attachFavoriteButtons() {
|
attachFavoriteButtons() {
|
||||||
debug('attachFavoriteButtons: Début...');
|
debug('attachFavoriteButtons: Début...');
|
||||||
|
|
||||||
// Supprimer tous les boutons favoris existants pour les recréer avec le bon état
|
|
||||||
document.querySelectorAll('.add-to-favorites').forEach(btn => btn.remove());
|
|
||||||
|
|
||||||
// Ajouter des boutons étoile aux éléments du file tree
|
// Ajouter des boutons étoile aux éléments du file tree
|
||||||
this.getFavoritesPaths().then(favoritePaths => {
|
this.getFavoritesPaths().then(favoritePaths => {
|
||||||
debug('Chemins favoris:', favoritePaths);
|
debug('Chemins favoris:', favoritePaths);
|
||||||
|
|
||||||
|
// Supprimer tous les boutons favoris existants APRÈS avoir récupéré la liste
|
||||||
|
document.querySelectorAll('.add-to-favorites').forEach(btn => btn.remove());
|
||||||
|
|
||||||
// Dossiers
|
// Dossiers
|
||||||
const folderHeaders = document.querySelectorAll('.folder-header');
|
const folderHeaders = document.querySelectorAll('.folder-header');
|
||||||
debug('Nombre de folder-header trouvés:', folderHeaders.length);
|
debug('Nombre de folder-header trouvés:', folderHeaders.length);
|
||||||
@ -153,7 +153,7 @@ class FavoritesManager {
|
|||||||
if (path) {
|
if (path) {
|
||||||
const button = document.createElement('button');
|
const button = document.createElement('button');
|
||||||
button.className = 'add-to-favorites';
|
button.className = 'add-to-favorites';
|
||||||
button.innerHTML = '⭐';
|
button.innerHTML = '<i data-lucide="star" class="icon-sm"></i>';
|
||||||
button.title = 'Ajouter aux favoris';
|
button.title = 'Ajouter aux favoris';
|
||||||
|
|
||||||
// Extraire le nom avant d'ajouter le bouton
|
// Extraire le nom avant d'ajouter le bouton
|
||||||
@ -191,11 +191,11 @@ class FavoritesManager {
|
|||||||
if (path) {
|
if (path) {
|
||||||
const button = document.createElement('button');
|
const button = document.createElement('button');
|
||||||
button.className = 'add-to-favorites';
|
button.className = 'add-to-favorites';
|
||||||
button.innerHTML = '⭐';
|
button.innerHTML = '<i data-lucide="star" class="icon-sm"></i>';
|
||||||
button.title = 'Ajouter aux favoris';
|
button.title = 'Ajouter aux favoris';
|
||||||
|
|
||||||
// Extraire le nom avant d'ajouter le bouton
|
// Extraire le nom avant d'ajouter le bouton
|
||||||
const name = fileItem.textContent.trim().replace('📄', '').trim().replace('.md', '');
|
const name = fileItem.textContent.trim().replace('.md', '');
|
||||||
|
|
||||||
button.onclick = (e) => {
|
button.onclick = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -220,6 +220,11 @@ class FavoritesManager {
|
|||||||
});
|
});
|
||||||
|
|
||||||
debug('attachFavoriteButtons: Terminé');
|
debug('attachFavoriteButtons: Terminé');
|
||||||
|
|
||||||
|
// Initialiser les icônes Lucide après création des boutons
|
||||||
|
if (typeof lucide !== 'undefined') {
|
||||||
|
lucide.createIcons();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,9 @@ class FileTree {
|
|||||||
init() {
|
init() {
|
||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
|
|
||||||
|
// Restaurer l'état des dossiers au démarrage
|
||||||
|
setTimeout(() => this.restoreFolderStates(), 500);
|
||||||
|
|
||||||
debug('FileTree initialized with event delegation');
|
debug('FileTree initialized with event delegation');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,17 +70,60 @@ class FileTree {
|
|||||||
const children = folderItem.querySelector('.folder-children');
|
const children = folderItem.querySelector('.folder-children');
|
||||||
const toggle = header.querySelector('.folder-toggle');
|
const toggle = header.querySelector('.folder-toggle');
|
||||||
const icon = header.querySelector('.folder-icon');
|
const icon = header.querySelector('.folder-icon');
|
||||||
|
const folderPath = folderItem.getAttribute('data-path');
|
||||||
|
|
||||||
if (children.style.display === 'none') {
|
if (children.style.display === 'none') {
|
||||||
// Ouvrir le dossier
|
// Ouvrir le dossier
|
||||||
children.style.display = 'block';
|
children.style.display = 'block';
|
||||||
toggle.classList.add('expanded');
|
toggle.classList.add('expanded');
|
||||||
icon.textContent = '📂';
|
icon.innerHTML = '<i data-lucide="folder-open" class="icon-sm"></i>';
|
||||||
|
this.saveFolderState(folderPath, true);
|
||||||
} else {
|
} else {
|
||||||
// Fermer le dossier
|
// Fermer le dossier
|
||||||
children.style.display = 'none';
|
children.style.display = 'none';
|
||||||
toggle.classList.remove('expanded');
|
toggle.classList.remove('expanded');
|
||||||
icon.textContent = '📁';
|
icon.innerHTML = '<i data-lucide="folder" class="icon-sm"></i>';
|
||||||
|
this.saveFolderState(folderPath, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveFolderState(folderPath, isExpanded) {
|
||||||
|
if (!folderPath) return;
|
||||||
|
const expandedFolders = this.getExpandedFolders();
|
||||||
|
if (isExpanded) {
|
||||||
|
expandedFolders.add(folderPath);
|
||||||
|
} else {
|
||||||
|
expandedFolders.delete(folderPath);
|
||||||
|
}
|
||||||
|
localStorage.setItem('expanded-folders', JSON.stringify([...expandedFolders]));
|
||||||
|
}
|
||||||
|
|
||||||
|
getExpandedFolders() {
|
||||||
|
const saved = localStorage.getItem('expanded-folders');
|
||||||
|
return saved ? new Set(JSON.parse(saved)) : new Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreFolderStates() {
|
||||||
|
const expandedFolders = this.getExpandedFolders();
|
||||||
|
document.querySelectorAll('.folder-item').forEach(folderItem => {
|
||||||
|
const folderPath = folderItem.getAttribute('data-path');
|
||||||
|
if (folderPath && expandedFolders.has(folderPath)) {
|
||||||
|
const header = folderItem.querySelector('.folder-header');
|
||||||
|
const children = folderItem.querySelector('.folder-children');
|
||||||
|
const toggle = header?.querySelector('.folder-toggle');
|
||||||
|
const icon = header?.querySelector('.folder-icon');
|
||||||
|
|
||||||
|
if (children && toggle && icon) {
|
||||||
|
children.style.display = 'block';
|
||||||
|
toggle.classList.add('expanded');
|
||||||
|
icon.innerHTML = '<i data-lucide="folder-open" class="icon-sm"></i>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Réinitialiser les icônes Lucide
|
||||||
|
if (typeof lucide !== 'undefined') {
|
||||||
|
lucide.createIcons();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,8 +205,9 @@ class FileTree {
|
|||||||
// Vérifier si le swap concerne le file-tree
|
// Vérifier si le swap concerne le file-tree
|
||||||
const target = event.detail?.target;
|
const target = event.detail?.target;
|
||||||
if (target && (target.id === 'file-tree' || target.closest('#file-tree'))) {
|
if (target && (target.id === 'file-tree' || target.closest('#file-tree'))) {
|
||||||
debug('FileTree: afterSwap detected, updating attributes...');
|
debug('FileTree: afterSwap detected, updating attributes and restoring folder states...');
|
||||||
this.updateDraggableAttributes();
|
this.updateDraggableAttributes();
|
||||||
|
setTimeout(() => this.restoreFolderStates(), 50);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -169,8 +216,9 @@ class FileTree {
|
|||||||
const target = event.detail?.target;
|
const target = event.detail?.target;
|
||||||
// Ignorer les swaps de statut (auto-save-status, save-status)
|
// Ignorer les swaps de statut (auto-save-status, save-status)
|
||||||
if (target && target.id === 'file-tree') {
|
if (target && target.id === 'file-tree') {
|
||||||
debug('FileTree: oobAfterSwap detected, updating attributes...');
|
debug('FileTree: oobAfterSwap detected, updating attributes and restoring folder states...');
|
||||||
this.updateDraggableAttributes();
|
this.updateDraggableAttributes();
|
||||||
|
setTimeout(() => this.restoreFolderStates(), 50);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -181,6 +229,7 @@ class FileTree {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
this.updateDraggableAttributes();
|
this.updateDraggableAttributes();
|
||||||
|
this.restoreFolderStates();
|
||||||
}, 50);
|
}, 50);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -678,7 +727,7 @@ class SelectionManager {
|
|||||||
const checkbox = document.querySelector(`.selection-checkbox[data-path="${path}"]`);
|
const checkbox = document.querySelector(`.selection-checkbox[data-path="${path}"]`);
|
||||||
const isDir = checkbox?.dataset.isDir === 'true';
|
const isDir = checkbox?.dataset.isDir === 'true';
|
||||||
|
|
||||||
li.innerHTML = `${isDir ? '📁' : '📄'} <code>${path}</code>`;
|
li.innerHTML = `${isDir ? '<i data-lucide="folder" class="icon-sm"></i>' : '<i data-lucide="file-text" class="icon-sm"></i>'} <code>${path}</code>`;
|
||||||
ul.appendChild(li);
|
ul.appendChild(li);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -231,8 +231,10 @@ class I18n {
|
|||||||
// Create singleton instance
|
// Create singleton instance
|
||||||
export const i18n = new I18n();
|
export const i18n = new I18n();
|
||||||
|
|
||||||
// Export convenience function
|
// Export convenience functions
|
||||||
export const t = (key, args) => i18n.t(key, args);
|
export const t = (key, args) => i18n.t(key, args);
|
||||||
|
export const loadTranslations = () => i18n.loadTranslations();
|
||||||
|
export const translatePage = () => i18n.translatePage();
|
||||||
|
|
||||||
// Initialize on import
|
// Initialize on import
|
||||||
i18n.init().then(() => {
|
i18n.init().then(() => {
|
||||||
|
|||||||
@ -181,12 +181,12 @@ class LanguageManager {
|
|||||||
// Header buttons
|
// Header buttons
|
||||||
const homeButton = document.querySelector('button[hx-get="/api/home"]');
|
const homeButton = document.querySelector('button[hx-get="/api/home"]');
|
||||||
if (homeButton && !homeButton.hasAttribute('data-i18n')) {
|
if (homeButton && !homeButton.hasAttribute('data-i18n')) {
|
||||||
homeButton.innerHTML = `🏠 ${t('menu.home')}`;
|
homeButton.innerHTML = `<i data-lucide="home" class="icon-sm"></i> ${t('menu.home')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newNoteButton = document.querySelector('header button[onclick="showNewNoteModal()"]');
|
const newNoteButton = document.querySelector('header button[onclick="showNewNoteModal()"]');
|
||||||
if (newNoteButton && !newNoteButton.hasAttribute('data-i18n')) {
|
if (newNoteButton && !newNoteButton.hasAttribute('data-i18n')) {
|
||||||
newNoteButton.innerHTML = `✨ ${t('menu.newNote')}`;
|
newNoteButton.innerHTML = `<i data-lucide="file-plus" class="icon-sm"></i> ${t('menu.newNote')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search placeholder
|
// Search placeholder
|
||||||
@ -199,7 +199,7 @@ class LanguageManager {
|
|||||||
const newNoteModal = document.getElementById('new-note-modal');
|
const newNoteModal = document.getElementById('new-note-modal');
|
||||||
if (newNoteModal) {
|
if (newNoteModal) {
|
||||||
const title = newNoteModal.querySelector('h2');
|
const title = newNoteModal.querySelector('h2');
|
||||||
if (title) title.textContent = `📝 ${t('newNoteModal.title')}`;
|
if (title) title.innerHTML = `<i data-lucide="file-text" class="icon-sm"></i> ${t('newNoteModal.title')}`;
|
||||||
|
|
||||||
const label = newNoteModal.querySelector('label[for="note-name"]');
|
const label = newNoteModal.querySelector('label[for="note-name"]');
|
||||||
if (label) label.textContent = t('newNoteModal.label');
|
if (label) label.textContent = t('newNoteModal.label');
|
||||||
@ -218,7 +218,7 @@ class LanguageManager {
|
|||||||
const newFolderModal = document.getElementById('new-folder-modal');
|
const newFolderModal = document.getElementById('new-folder-modal');
|
||||||
if (newFolderModal) {
|
if (newFolderModal) {
|
||||||
const title = newFolderModal.querySelector('h2');
|
const title = newFolderModal.querySelector('h2');
|
||||||
if (title) title.textContent = `📁 ${t('newFolderModal.title')}`;
|
if (title) title.innerHTML = `<i data-lucide="folder-plus" class="icon-sm"></i> ${t('newFolderModal.title')}`;
|
||||||
|
|
||||||
const label = newFolderModal.querySelector('label[for="folder-name"]');
|
const label = newFolderModal.querySelector('label[for="folder-name"]');
|
||||||
if (label) label.textContent = t('newFolderModal.label');
|
if (label) label.textContent = t('newFolderModal.label');
|
||||||
@ -256,16 +256,16 @@ class LanguageManager {
|
|||||||
// Theme modal
|
// Theme modal
|
||||||
const modalTitle = document.querySelector('.theme-modal-content h2');
|
const modalTitle = document.querySelector('.theme-modal-content h2');
|
||||||
if (modalTitle) {
|
if (modalTitle) {
|
||||||
modalTitle.textContent = `⚙️ ${t('settings.title')}`;
|
modalTitle.innerHTML = `<i data-lucide="settings" class="icon-sm"></i> ${t('settings.title')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translate tabs
|
// Translate tabs
|
||||||
const tabs = document.querySelectorAll('.settings-tab');
|
const tabs = document.querySelectorAll('.settings-tab');
|
||||||
if (tabs.length >= 4) {
|
if (tabs.length >= 4) {
|
||||||
tabs[0].innerHTML = `🎨 ${t('tabs.themes')}`;
|
tabs[0].innerHTML = `<i data-lucide="palette" class="icon-sm"></i> ${t('tabs.themes')}`;
|
||||||
tabs[1].innerHTML = `🔤 ${t('tabs.fonts')}`;
|
tabs[1].innerHTML = `<i data-lucide="type" class="icon-sm"></i> ${t('tabs.fonts')}`;
|
||||||
tabs[2].innerHTML = `⌨️ ${t('tabs.shortcuts')}`;
|
tabs[2].innerHTML = `<i data-lucide="keyboard" class="icon-sm"></i> ${t('tabs.shortcuts')}`;
|
||||||
tabs[3].innerHTML = `⚙️ ${t('tabs.other')}`;
|
tabs[3].innerHTML = `<i data-lucide="settings" class="icon-sm"></i> ${t('tabs.other')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translate close button in settings
|
// Translate close button in settings
|
||||||
@ -281,20 +281,20 @@ class LanguageManager {
|
|||||||
if (langSection) {
|
if (langSection) {
|
||||||
const heading = langSection.querySelector('h3');
|
const heading = langSection.querySelector('h3');
|
||||||
if (heading) {
|
if (heading) {
|
||||||
heading.textContent = `🌍 ${t('languages.title')}`;
|
heading.innerHTML = `<i data-lucide="languages" class="icon-sm"></i> ${t('languages.title')}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sidebar sections
|
// Sidebar sections
|
||||||
const searchSectionTitle = document.querySelector('.sidebar-section-title');
|
const searchSectionTitle = document.querySelector('.sidebar-section-title');
|
||||||
if (searchSectionTitle && searchSectionTitle.textContent.includes('🔍')) {
|
if (searchSectionTitle && (searchSectionTitle.textContent.includes('🔍') || searchSectionTitle.querySelector('[data-lucide="search"]'))) {
|
||||||
searchSectionTitle.textContent = `🔍 ${t('search.title') || 'Recherche'}`;
|
searchSectionTitle.innerHTML = `<i data-lucide="search" class="icon-sm"></i> ${t('search.title') || 'Recherche'}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sidebar "Nouveau dossier" button
|
// Sidebar "Nouveau dossier" button
|
||||||
const newFolderBtn = document.querySelector('.folder-create-btn');
|
const newFolderBtn = document.querySelector('.folder-create-btn');
|
||||||
if (newFolderBtn && !newFolderBtn.hasAttribute('data-i18n')) {
|
if (newFolderBtn && !newFolderBtn.hasAttribute('data-i18n')) {
|
||||||
newFolderBtn.innerHTML = `📁 ${t('fileTree.newFolder')}`;
|
newFolderBtn.innerHTML = `<i data-lucide="folder-plus" class="icon-sm"></i> ${t('fileTree.newFolder')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sidebar "Paramètres" button span
|
// Sidebar "Paramètres" button span
|
||||||
|
|||||||
@ -309,7 +309,7 @@ class LinkInserter {
|
|||||||
this.results = [];
|
this.results = [];
|
||||||
this.resultsContainer.innerHTML = `
|
this.resultsContainer.innerHTML = `
|
||||||
<div class="link-inserter-no-results">
|
<div class="link-inserter-no-results">
|
||||||
<div class="link-inserter-no-results-icon">🔍</div>
|
<div class="link-inserter-no-results-icon"><i data-lucide="search" class="icon-lg"></i></div>
|
||||||
<p>Aucune note trouvée pour « <strong>${this.escapeHtml(query)}</strong> »</p>
|
<p>Aucune note trouvée pour « <strong>${this.escapeHtml(query)}</strong> »</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@ -318,7 +318,7 @@ class LinkInserter {
|
|||||||
showError() {
|
showError() {
|
||||||
this.resultsContainer.innerHTML = `
|
this.resultsContainer.innerHTML = `
|
||||||
<div class="link-inserter-error">
|
<div class="link-inserter-error">
|
||||||
<div class="link-inserter-error-icon">⚠️</div>
|
<div class="link-inserter-error-icon"><i data-lucide="alert-triangle" class="icon-lg"></i></div>
|
||||||
<p>Erreur lors de la recherche</p>
|
<p>Erreur lors de la recherche</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -12,3 +12,7 @@ import './vim-mode-manager.js';
|
|||||||
import './favorites.js';
|
import './favorites.js';
|
||||||
import './sidebar-sections.js';
|
import './sidebar-sections.js';
|
||||||
import './keyboard-shortcuts.js';
|
import './keyboard-shortcuts.js';
|
||||||
|
import { initPublicToggle } from './public-toggle.js';
|
||||||
|
|
||||||
|
// Initialiser le toggle public
|
||||||
|
initPublicToggle();
|
||||||
|
|||||||
145
frontend/src/public-toggle.js
Normal file
145
frontend/src/public-toggle.js
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
// public-toggle.js - Gestion du statut public/privé des notes
|
||||||
|
import { t } from './i18n.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise le gestionnaire de toggle public
|
||||||
|
*/
|
||||||
|
export function initPublicToggle() {
|
||||||
|
// Event delegation sur le body pour gérer les boutons dynamiques
|
||||||
|
document.body.addEventListener('click', (e) => {
|
||||||
|
const btn = e.target.closest('#toggle-public-btn');
|
||||||
|
if (!btn) return;
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
togglePublicStatus(btn);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bascule le statut public d'une note
|
||||||
|
* @param {HTMLElement} btn - Le bouton cliqué
|
||||||
|
*/
|
||||||
|
async function togglePublicStatus(btn) {
|
||||||
|
const path = btn.dataset.path;
|
||||||
|
const isCurrentlyPublic = btn.dataset.isPublic === 'true';
|
||||||
|
|
||||||
|
if (!path) {
|
||||||
|
console.error('Pas de chemin de note trouvé');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Désactiver le bouton pendant la requête
|
||||||
|
btn.disabled = true;
|
||||||
|
const originalText = btn.textContent;
|
||||||
|
btn.textContent = '⏳ ' + t('public.loading');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/public/toggle', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
body: new URLSearchParams({ path }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP Error: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
const isNowPublic = data.status === 'public';
|
||||||
|
|
||||||
|
// Mettre à jour le bouton
|
||||||
|
btn.dataset.isPublic = isNowPublic;
|
||||||
|
|
||||||
|
// Mettre à jour le texte et le titre avec i18n
|
||||||
|
const textKey = isNowPublic ? 'public.buttonPublic' : 'public.buttonPrivate';
|
||||||
|
const titleKey = isNowPublic ? 'public.titlePublic' : 'public.titlePrivate';
|
||||||
|
|
||||||
|
btn.innerHTML = `<span data-i18n="${textKey}">${isNowPublic ? '🌐 ' + t('public.buttonPublic') : '🔒 ' + t('public.buttonPrivate')}</span>`;
|
||||||
|
btn.title = t(titleKey);
|
||||||
|
btn.setAttribute('data-i18n-title', titleKey);
|
||||||
|
|
||||||
|
// Ajouter/retirer la classe CSS
|
||||||
|
if (isNowPublic) {
|
||||||
|
btn.classList.add('public-active');
|
||||||
|
} else {
|
||||||
|
btn.classList.remove('public-active');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Afficher une notification de succès
|
||||||
|
const messageKey = isNowPublic ? 'public.notificationPublished' : 'public.notificationUnpublished';
|
||||||
|
showNotification(t(messageKey));
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error toggling public status:', error);
|
||||||
|
btn.textContent = originalText;
|
||||||
|
showNotification(t('public.notificationError'), 'error');
|
||||||
|
} finally {
|
||||||
|
btn.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Affiche une notification temporaire
|
||||||
|
* @param {string} message - Le message à afficher
|
||||||
|
* @param {string} type - Type de notification (success, error)
|
||||||
|
*/
|
||||||
|
function showNotification(message, type = 'success') {
|
||||||
|
// Créer l'élément de notification
|
||||||
|
const notification = document.createElement('div');
|
||||||
|
notification.className = `public-notification ${type}`;
|
||||||
|
notification.textContent = message;
|
||||||
|
notification.style.cssText = `
|
||||||
|
position: fixed;
|
||||||
|
bottom: 2rem;
|
||||||
|
right: 2rem;
|
||||||
|
background: ${type === 'error' ? 'var(--error-red, #e74c3c)' : 'var(--success-green, #2ecc71)'};
|
||||||
|
color: white;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||||
|
z-index: 10000;
|
||||||
|
animation: slideIn 0.3s ease-out;
|
||||||
|
font-weight: 500;
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Ajouter l'animation
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = `
|
||||||
|
@keyframes slideIn {
|
||||||
|
from {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes slideOut {
|
||||||
|
from {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
if (!document.querySelector('style[data-public-notification]')) {
|
||||||
|
style.setAttribute('data-public-notification', 'true');
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.appendChild(notification);
|
||||||
|
|
||||||
|
// Retirer après 3 secondes
|
||||||
|
setTimeout(() => {
|
||||||
|
notification.style.animation = 'slideOut 0.3s ease-out';
|
||||||
|
setTimeout(() => {
|
||||||
|
notification.remove();
|
||||||
|
}, 300);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
@ -51,7 +51,7 @@ class SearchModal {
|
|||||||
<div class="search-modal-body">
|
<div class="search-modal-body">
|
||||||
<div class="search-modal-results">
|
<div class="search-modal-results">
|
||||||
<div class="search-modal-help">
|
<div class="search-modal-help">
|
||||||
<div class="search-modal-help-title">💡 Recherche avancée</div>
|
<div class="search-modal-help-title"><i data-lucide="lightbulb" class="icon-sm"></i> Recherche avancée</div>
|
||||||
<div class="search-modal-help-items">
|
<div class="search-modal-help-items">
|
||||||
<div class="search-modal-help-item">
|
<div class="search-modal-help-item">
|
||||||
<code>tag:projet</code>
|
<code>tag:projet</code>
|
||||||
@ -289,7 +289,7 @@ class SearchModal {
|
|||||||
this.results = [];
|
this.results = [];
|
||||||
this.resultsContainer.innerHTML = `
|
this.resultsContainer.innerHTML = `
|
||||||
<div class="search-modal-help">
|
<div class="search-modal-help">
|
||||||
<div class="search-modal-help-title">💡 Recherche avancée</div>
|
<div class="search-modal-help-title"><i data-lucide="lightbulb" class="icon-sm"></i> Recherche avancée</div>
|
||||||
<div class="search-modal-help-items">
|
<div class="search-modal-help-items">
|
||||||
<div class="search-modal-help-item">
|
<div class="search-modal-help-item">
|
||||||
<code>tag:projet</code>
|
<code>tag:projet</code>
|
||||||
@ -325,7 +325,7 @@ class SearchModal {
|
|||||||
this.results = [];
|
this.results = [];
|
||||||
this.resultsContainer.innerHTML = `
|
this.resultsContainer.innerHTML = `
|
||||||
<div class="search-modal-no-results">
|
<div class="search-modal-no-results">
|
||||||
<div class="search-modal-no-results-icon">🔍</div>
|
<div class="search-modal-no-results-icon"><i data-lucide="search" class="icon-lg"></i></div>
|
||||||
<p class="search-modal-no-results-text">Aucun résultat pour « <strong>${this.escapeHtml(query)}</strong> »</p>
|
<p class="search-modal-no-results-text">Aucun résultat pour « <strong>${this.escapeHtml(query)}</strong> »</p>
|
||||||
<p class="search-modal-no-results-hint">Essayez d'autres mots-clés ou utilisez les filtres</p>
|
<p class="search-modal-no-results-hint">Essayez d'autres mots-clés ou utilisez les filtres</p>
|
||||||
</div>
|
</div>
|
||||||
@ -335,7 +335,7 @@ class SearchModal {
|
|||||||
showError() {
|
showError() {
|
||||||
this.resultsContainer.innerHTML = `
|
this.resultsContainer.innerHTML = `
|
||||||
<div class="search-modal-error">
|
<div class="search-modal-error">
|
||||||
<div class="search-modal-error-icon">⚠️</div>
|
<div class="search-modal-error-icon"><i data-lucide="alert-triangle" class="icon-lg"></i></div>
|
||||||
<p>Une erreur s'est produite lors de la recherche</p>
|
<p>Une erreur s'est produite lors de la recherche</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -9,49 +9,49 @@ class ThemeManager {
|
|||||||
{
|
{
|
||||||
id: 'material-dark',
|
id: 'material-dark',
|
||||||
name: 'Material Dark',
|
name: 'Material Dark',
|
||||||
icon: '🌙',
|
icon: 'moon',
|
||||||
description: 'Thème professionnel inspiré de Material Design'
|
description: 'Thème professionnel inspiré de Material Design'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'monokai-dark',
|
id: 'monokai-dark',
|
||||||
name: 'Monokai Dark',
|
name: 'Monokai Dark',
|
||||||
icon: '🎨',
|
icon: 'palette',
|
||||||
description: 'Palette Monokai classique pour les développeurs'
|
description: 'Palette Monokai classique pour les développeurs'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'dracula',
|
id: 'dracula',
|
||||||
name: 'Dracula',
|
name: 'Dracula',
|
||||||
icon: '🧛',
|
icon: 'moon-star',
|
||||||
description: 'Thème sombre élégant avec des accents violets et cyan'
|
description: 'Thème sombre élégant avec des accents violets et cyan'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'one-dark',
|
id: 'one-dark',
|
||||||
name: 'One Dark',
|
name: 'One Dark',
|
||||||
icon: '⚡',
|
icon: 'zap',
|
||||||
description: 'Thème populaire d\'Atom avec des couleurs douces'
|
description: 'Thème populaire d\'Atom avec des couleurs douces'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'solarized-dark',
|
id: 'solarized-dark',
|
||||||
name: 'Solarized Dark',
|
name: 'Solarized Dark',
|
||||||
icon: '☀️',
|
icon: 'sun',
|
||||||
description: 'Palette scientifiquement optimisée pour réduire la fatigue oculaire'
|
description: 'Palette scientifiquement optimisée pour réduire la fatigue oculaire'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'nord',
|
id: 'nord',
|
||||||
name: 'Nord',
|
name: 'Nord',
|
||||||
icon: '❄️',
|
icon: 'snowflake',
|
||||||
description: 'Palette arctique apaisante avec des tons bleus froids'
|
description: 'Palette arctique apaisante avec des tons bleus froids'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'catppuccin',
|
id: 'catppuccin',
|
||||||
name: 'Catppuccin',
|
name: 'Catppuccin',
|
||||||
icon: '🌸',
|
icon: 'flower-2',
|
||||||
description: 'Thème pastel doux et chaleureux avec des accents roses et bleus'
|
description: 'Thème pastel doux et chaleureux avec des accents roses et bleus'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'everforest',
|
id: 'everforest',
|
||||||
name: 'Everforest',
|
name: 'Everforest',
|
||||||
icon: '🌲',
|
icon: 'tree-pine',
|
||||||
description: 'Palette naturelle inspirée de la forêt avec des tons verts et beiges'
|
description: 'Palette naturelle inspirée de la forêt avec des tons verts et beiges'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
1
go.mod
1
go.mod
@ -4,6 +4,7 @@ go 1.22
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/fsnotify/fsnotify v1.7.0
|
github.com/fsnotify/fsnotify v1.7.0
|
||||||
|
github.com/yuin/goldmark v1.7.13
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
2
go.sum
2
go.sum
@ -1,5 +1,7 @@
|
|||||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||||
|
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
|
||||||
|
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
|||||||
@ -71,6 +71,16 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Public endpoints
|
||||||
|
if path == "/api/public/list" {
|
||||||
|
h.handlePublicList(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if path == "/api/public/toggle" {
|
||||||
|
h.handlePublicToggle(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Legacy/HTML endpoints
|
// Legacy/HTML endpoints
|
||||||
if strings.HasPrefix(path, "/api/search") {
|
if strings.HasPrefix(path, "/api/search") {
|
||||||
h.handleSearch(w, r)
|
h.handleSearch(w, r)
|
||||||
@ -295,12 +305,14 @@ func (h *Handler) handleHome(w http.ResponseWriter, r *http.Request) {
|
|||||||
Filename string
|
Filename string
|
||||||
Content string
|
Content string
|
||||||
IsHome bool
|
IsHome bool
|
||||||
|
IsPublic bool
|
||||||
Backlinks []BacklinkInfo
|
Backlinks []BacklinkInfo
|
||||||
Breadcrumb template.HTML
|
Breadcrumb template.HTML
|
||||||
}{
|
}{
|
||||||
Filename: "🏠 Accueil - Index",
|
Filename: "🏠 Accueil - Index",
|
||||||
Content: content,
|
Content: content,
|
||||||
IsHome: true,
|
IsHome: true,
|
||||||
|
IsPublic: false,
|
||||||
Backlinks: nil, // Pas de backlinks pour la page d'accueil
|
Backlinks: nil, // Pas de backlinks pour la page d'accueil
|
||||||
Breadcrumb: h.generateBreadcrumb(""),
|
Breadcrumb: h.generateBreadcrumb(""),
|
||||||
}
|
}
|
||||||
@ -351,7 +363,10 @@ func (h *Handler) generateHomeMarkdown(r *http.Request) string {
|
|||||||
// Section des favoris (après les tags)
|
// Section des favoris (après les tags)
|
||||||
h.generateFavoritesSection(&sb, r)
|
h.generateFavoritesSection(&sb, r)
|
||||||
|
|
||||||
// Section des notes récemment modifiées (après les favoris)
|
// Section des notes publiques (après les favoris)
|
||||||
|
h.generatePublicNotesSection(&sb, r)
|
||||||
|
|
||||||
|
// Section des notes récemment modifiées (après les notes publiques)
|
||||||
h.generateRecentNotesSection(&sb, r)
|
h.generateRecentNotesSection(&sb, r)
|
||||||
|
|
||||||
// Section de toutes les notes avec accordéon
|
// Section de toutes les notes avec accordéon
|
||||||
@ -380,7 +395,7 @@ func (h *Handler) generateTagsSection(sb *strings.Builder) {
|
|||||||
|
|
||||||
sb.WriteString("<div class=\"home-section\">\n")
|
sb.WriteString("<div class=\"home-section\">\n")
|
||||||
sb.WriteString(" <div class=\"home-section-header\" onclick=\"toggleFolder('tags')\">\n")
|
sb.WriteString(" <div class=\"home-section-header\" onclick=\"toggleFolder('tags')\">\n")
|
||||||
sb.WriteString(" <h2 class=\"home-section-title\">🏷️ Tags</h2>\n")
|
sb.WriteString(" <h2 class=\"home-section-title\"><i data-lucide=\"tags\" class=\"icon-sm\"></i> Tags</h2>\n")
|
||||||
sb.WriteString(" </div>\n")
|
sb.WriteString(" </div>\n")
|
||||||
sb.WriteString(" <div class=\"home-section-content\" id=\"folder-tags\">\n")
|
sb.WriteString(" <div class=\"home-section-content\" id=\"folder-tags\">\n")
|
||||||
sb.WriteString(" <div class=\"tags-cloud\">\n")
|
sb.WriteString(" <div class=\"tags-cloud\">\n")
|
||||||
@ -408,7 +423,7 @@ func (h *Handler) generateFavoritesSection(sb *strings.Builder, r *http.Request)
|
|||||||
|
|
||||||
sb.WriteString("<div class=\"home-section\">\n")
|
sb.WriteString("<div class=\"home-section\">\n")
|
||||||
sb.WriteString(" <div class=\"home-section-header\" onclick=\"toggleFolder('favorites')\">\n")
|
sb.WriteString(" <div class=\"home-section-header\" onclick=\"toggleFolder('favorites')\">\n")
|
||||||
sb.WriteString(" <h2 class=\"home-section-title\">⭐ " + h.t(r, "favorites.title") + "</h2>\n")
|
sb.WriteString(" <h2 class=\"home-section-title\"><i data-lucide=\"star\" class=\"icon-sm\"></i> " + h.t(r, "favorites.title") + "</h2>\n")
|
||||||
sb.WriteString(" </div>\n")
|
sb.WriteString(" </div>\n")
|
||||||
sb.WriteString(" <div class=\"home-section-content\" id=\"folder-favorites\">\n")
|
sb.WriteString(" <div class=\"home-section-content\" id=\"folder-favorites\">\n")
|
||||||
sb.WriteString(" <div class=\"note-tree favorites-tree\">\n")
|
sb.WriteString(" <div class=\"note-tree favorites-tree\">\n")
|
||||||
@ -420,7 +435,7 @@ func (h *Handler) generateFavoritesSection(sb *strings.Builder, r *http.Request)
|
|||||||
// Dossier - avec accordéon
|
// Dossier - avec accordéon
|
||||||
sb.WriteString(fmt.Sprintf(" <div class=\"folder indent-level-1\">\n"))
|
sb.WriteString(fmt.Sprintf(" <div class=\"folder indent-level-1\">\n"))
|
||||||
sb.WriteString(fmt.Sprintf(" <div class=\"folder-header\" onclick=\"toggleFolder('%s')\">\n", safeID))
|
sb.WriteString(fmt.Sprintf(" <div class=\"folder-header\" onclick=\"toggleFolder('%s')\">\n", safeID))
|
||||||
sb.WriteString(fmt.Sprintf(" <span class=\"folder-icon\" id=\"icon-%s\">📁</span>\n", safeID))
|
sb.WriteString(fmt.Sprintf(" <span class=\"folder-icon\" id=\"icon-%s\"><i data-lucide=\"folder\" class=\"icon-sm\"></i></span>\n", safeID))
|
||||||
sb.WriteString(fmt.Sprintf(" <strong>%s</strong>\n", fav.Title))
|
sb.WriteString(fmt.Sprintf(" <strong>%s</strong>\n", fav.Title))
|
||||||
sb.WriteString(fmt.Sprintf(" </div>\n"))
|
sb.WriteString(fmt.Sprintf(" </div>\n"))
|
||||||
sb.WriteString(fmt.Sprintf(" <div class=\"folder-content\" id=\"folder-%s\">\n", safeID))
|
sb.WriteString(fmt.Sprintf(" <div class=\"folder-content\" id=\"folder-%s\">\n", safeID))
|
||||||
@ -445,6 +460,44 @@ func (h *Handler) generateFavoritesSection(sb *strings.Builder, r *http.Request)
|
|||||||
sb.WriteString("</div>\n\n")
|
sb.WriteString("</div>\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generatePublicNotesSection génère la section des notes publiques
|
||||||
|
func (h *Handler) generatePublicNotesSection(sb *strings.Builder, r *http.Request) {
|
||||||
|
publicNotes, err := h.loadPublicNotes()
|
||||||
|
if err != nil || len(publicNotes.Notes) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteString("<div class=\"home-section\">\n")
|
||||||
|
sb.WriteString(" <div class=\"home-section-header\" onclick=\"toggleFolder('public-notes')\">\n")
|
||||||
|
sb.WriteString(fmt.Sprintf(" <h2 class=\"home-section-title\"><i data-lucide=\"globe\" class=\"icon-sm\"></i> %s (%d)</h2>\n", h.t(r, "publicNotes.title"), len(publicNotes.Notes)))
|
||||||
|
sb.WriteString(" </div>\n")
|
||||||
|
sb.WriteString(" <div class=\"home-section-content\" id=\"folder-public-notes\">\n")
|
||||||
|
sb.WriteString(" <div class=\"public-notes-list\">\n")
|
||||||
|
|
||||||
|
for _, note := range publicNotes.Notes {
|
||||||
|
filename := filepath.Base(note.Path)
|
||||||
|
htmlFile := filename[:len(filename)-3] + ".html"
|
||||||
|
publicURL := fmt.Sprintf("/public/%s", htmlFile)
|
||||||
|
|
||||||
|
sb.WriteString(" <div class=\"public-note-card\">\n")
|
||||||
|
sb.WriteString(fmt.Sprintf(" <div class=\"public-note-header\">\n"))
|
||||||
|
sb.WriteString(fmt.Sprintf(" <a href=\"#\" class=\"public-note-edit\" hx-get=\"/api/notes/%s\" hx-target=\"#editor-container\" hx-swap=\"innerHTML\" hx-push-url=\"true\" title=\"%s\">", note.Path, h.t(r, "publicNotes.editNote")))
|
||||||
|
sb.WriteString(fmt.Sprintf(" <span class=\"public-note-title\">%s</span>", note.Title))
|
||||||
|
sb.WriteString(" </a>\n")
|
||||||
|
sb.WriteString(fmt.Sprintf(" <a href=\"%s\" target=\"_blank\" class=\"public-note-view\" title=\"%s\">🌐</a>\n", publicURL, h.t(r, "publicNotes.viewPublic")))
|
||||||
|
sb.WriteString(" </div>\n")
|
||||||
|
sb.WriteString(fmt.Sprintf(" <div class=\"public-note-meta\">\n"))
|
||||||
|
sb.WriteString(fmt.Sprintf(" <span class=\"public-note-source\">📄 %s</span>\n", note.Path))
|
||||||
|
sb.WriteString(fmt.Sprintf(" <span class=\"public-note-date\"><i data-lucide=\"calendar\" class=\"icon-sm\"></i> %s</span>\n", note.PublishedAt.Format("02/01/2006")))
|
||||||
|
sb.WriteString(" </div>\n")
|
||||||
|
sb.WriteString(" </div>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteString(" </div>\n")
|
||||||
|
sb.WriteString(" </div>\n")
|
||||||
|
sb.WriteString("</div>\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
// generateRecentNotesSection génère la section des notes récemment modifiées
|
// generateRecentNotesSection génère la section des notes récemment modifiées
|
||||||
func (h *Handler) generateRecentNotesSection(sb *strings.Builder, r *http.Request) {
|
func (h *Handler) generateRecentNotesSection(sb *strings.Builder, r *http.Request) {
|
||||||
recentDocs := h.idx.GetRecentDocuments(5)
|
recentDocs := h.idx.GetRecentDocuments(5)
|
||||||
@ -477,7 +530,7 @@ func (h *Handler) generateRecentNotesSection(sb *strings.Builder, r *http.Reques
|
|||||||
sb.WriteString(fmt.Sprintf(" <a href=\"#\" class=\"recent-note-link\" hx-get=\"/api/notes/%s\" hx-target=\"#editor-container\" hx-swap=\"innerHTML\" hx-push-url=\"true\">\n", doc.Path))
|
sb.WriteString(fmt.Sprintf(" <a href=\"#\" class=\"recent-note-link\" hx-get=\"/api/notes/%s\" hx-target=\"#editor-container\" hx-swap=\"innerHTML\" hx-push-url=\"true\">\n", doc.Path))
|
||||||
sb.WriteString(fmt.Sprintf(" <div class=\"recent-note-title\">%s</div>\n", doc.Title))
|
sb.WriteString(fmt.Sprintf(" <div class=\"recent-note-title\">%s</div>\n", doc.Title))
|
||||||
sb.WriteString(fmt.Sprintf(" <div class=\"recent-note-meta\">\n"))
|
sb.WriteString(fmt.Sprintf(" <div class=\"recent-note-meta\">\n"))
|
||||||
sb.WriteString(fmt.Sprintf(" <span class=\"recent-note-date\">📅 %s</span>\n", dateStr))
|
sb.WriteString(fmt.Sprintf(" <span class=\"recent-note-date\"><i data-lucide=\"calendar\" class=\"icon-sm\"></i> %s</span>\n", dateStr))
|
||||||
if len(doc.Tags) > 0 {
|
if len(doc.Tags) > 0 {
|
||||||
sb.WriteString(fmt.Sprintf(" <span class=\"recent-note-tags\">"))
|
sb.WriteString(fmt.Sprintf(" <span class=\"recent-note-tags\">"))
|
||||||
for i, tag := range doc.Tags {
|
for i, tag := range doc.Tags {
|
||||||
@ -523,7 +576,7 @@ func (h *Handler) generateFavoriteFolderContent(sb *strings.Builder, folderPath
|
|||||||
// Sous-dossier
|
// Sous-dossier
|
||||||
sb.WriteString(fmt.Sprintf("%s<div class=\"folder %s\">\n", indent, indentClass))
|
sb.WriteString(fmt.Sprintf("%s<div class=\"folder %s\">\n", indent, indentClass))
|
||||||
sb.WriteString(fmt.Sprintf("%s <div class=\"folder-header\" onclick=\"toggleFolder('%s')\">\n", indent, safeID))
|
sb.WriteString(fmt.Sprintf("%s <div class=\"folder-header\" onclick=\"toggleFolder('%s')\">\n", indent, safeID))
|
||||||
sb.WriteString(fmt.Sprintf("%s <span class=\"folder-icon\" id=\"icon-%s\">📁</span>\n", indent, safeID))
|
sb.WriteString(fmt.Sprintf("%s <span class=\"folder-icon\" id=\"icon-%s\"><i data-lucide=\"folder\" class=\"icon-sm\"></i></span>\n", indent, safeID))
|
||||||
sb.WriteString(fmt.Sprintf("%s <strong>%s</strong>\n", indent, name))
|
sb.WriteString(fmt.Sprintf("%s <strong>%s</strong>\n", indent, name))
|
||||||
sb.WriteString(fmt.Sprintf("%s </div>\n", indent))
|
sb.WriteString(fmt.Sprintf("%s </div>\n", indent))
|
||||||
sb.WriteString(fmt.Sprintf("%s <div class=\"folder-content\" id=\"folder-%s\">\n", indent, safeID))
|
sb.WriteString(fmt.Sprintf("%s <div class=\"folder-content\" id=\"folder-%s\">\n", indent, safeID))
|
||||||
@ -579,7 +632,7 @@ func (h *Handler) generateMarkdownTree(sb *strings.Builder, node *TreeNode, dept
|
|||||||
indentClass := fmt.Sprintf("indent-level-%d", depth)
|
indentClass := fmt.Sprintf("indent-level-%d", depth)
|
||||||
sb.WriteString(fmt.Sprintf("%s<div class=\"folder %s\">\n", indent, indentClass))
|
sb.WriteString(fmt.Sprintf("%s<div class=\"folder %s\">\n", indent, indentClass))
|
||||||
sb.WriteString(fmt.Sprintf("%s <div class=\"folder-header\" onclick=\"toggleFolder('%s')\">\n", indent, safeID))
|
sb.WriteString(fmt.Sprintf("%s <div class=\"folder-header\" onclick=\"toggleFolder('%s')\">\n", indent, safeID))
|
||||||
sb.WriteString(fmt.Sprintf("%s <span class=\"folder-icon\" id=\"icon-%s\">📁</span>\n", indent, safeID))
|
sb.WriteString(fmt.Sprintf("%s <span class=\"folder-icon\" id=\"icon-%s\"><i data-lucide=\"folder\" class=\"icon-sm\"></i></span>\n", indent, safeID))
|
||||||
sb.WriteString(fmt.Sprintf("%s <strong>%s</strong>\n", indent, node.Name))
|
sb.WriteString(fmt.Sprintf("%s <strong>%s</strong>\n", indent, node.Name))
|
||||||
sb.WriteString(fmt.Sprintf("%s </div>\n", indent))
|
sb.WriteString(fmt.Sprintf("%s </div>\n", indent))
|
||||||
sb.WriteString(fmt.Sprintf("%s <div class=\"folder-content\" id=\"folder-%s\">\n", indent, safeID))
|
sb.WriteString(fmt.Sprintf("%s <div class=\"folder-content\" id=\"folder-%s\">\n", indent, safeID))
|
||||||
@ -688,12 +741,14 @@ func (h *Handler) createAndRenderNote(w http.ResponseWriter, r *http.Request, fi
|
|||||||
Filename string
|
Filename string
|
||||||
Content string
|
Content string
|
||||||
IsHome bool
|
IsHome bool
|
||||||
|
IsPublic bool
|
||||||
Backlinks []BacklinkInfo
|
Backlinks []BacklinkInfo
|
||||||
}{
|
}{
|
||||||
Filename: filename,
|
Filename: filename,
|
||||||
Content: initialContent,
|
Content: initialContent,
|
||||||
IsHome: false,
|
IsHome: false,
|
||||||
Backlinks: nil, // Pas de backlinks pour une nouvelle note
|
IsPublic: false, // Nouvelle note, pas publique par défaut
|
||||||
|
Backlinks: nil, // Pas de backlinks pour une nouvelle note
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.templates.ExecuteTemplate(w, "editor.html", data)
|
err = h.templates.ExecuteTemplate(w, "editor.html", data)
|
||||||
@ -832,12 +887,14 @@ func (h *Handler) handleGetNote(w http.ResponseWriter, r *http.Request, filename
|
|||||||
Filename string
|
Filename string
|
||||||
Content string
|
Content string
|
||||||
IsHome bool
|
IsHome bool
|
||||||
|
IsPublic bool
|
||||||
Backlinks []BacklinkInfo
|
Backlinks []BacklinkInfo
|
||||||
Breadcrumb template.HTML
|
Breadcrumb template.HTML
|
||||||
}{
|
}{
|
||||||
Filename: filename,
|
Filename: filename,
|
||||||
Content: string(content),
|
Content: string(content),
|
||||||
IsHome: false,
|
IsHome: false,
|
||||||
|
IsPublic: h.isPublic(filename),
|
||||||
Backlinks: backlinkData,
|
Backlinks: backlinkData,
|
||||||
Breadcrumb: h.generateBreadcrumb(filename),
|
Breadcrumb: h.generateBreadcrumb(filename),
|
||||||
}
|
}
|
||||||
@ -1347,15 +1404,17 @@ func (h *Handler) handleFolderView(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// Utiliser le template editor.html
|
// Utiliser le template editor.html
|
||||||
data := struct {
|
data := struct {
|
||||||
Filename string
|
Filename string
|
||||||
Content string
|
Content string
|
||||||
IsHome bool
|
IsHome bool
|
||||||
Backlinks []BacklinkInfo
|
IsPublic bool
|
||||||
|
Backlinks []BacklinkInfo
|
||||||
Breadcrumb template.HTML
|
Breadcrumb template.HTML
|
||||||
}{
|
}{
|
||||||
Filename: cleanPath,
|
Filename: cleanPath,
|
||||||
Content: content,
|
Content: content,
|
||||||
IsHome: true, // Pas d'édition pour une vue de dossier
|
IsHome: true, // Pas d'édition pour une vue de dossier
|
||||||
|
IsPublic: false,
|
||||||
Backlinks: nil,
|
Backlinks: nil,
|
||||||
Breadcrumb: h.generateBreadcrumb(cleanPath),
|
Breadcrumb: h.generateBreadcrumb(cleanPath),
|
||||||
}
|
}
|
||||||
@ -1370,7 +1429,7 @@ func (h *Handler) handleFolderView(w http.ResponseWriter, r *http.Request) {
|
|||||||
// generateBreadcrumb génère un fil d'Ariane HTML cliquable
|
// generateBreadcrumb génère un fil d'Ariane HTML cliquable
|
||||||
func (h *Handler) generateBreadcrumb(path string) template.HTML {
|
func (h *Handler) generateBreadcrumb(path string) template.HTML {
|
||||||
if path == "" {
|
if path == "" {
|
||||||
return template.HTML(`<strong>📁 Racine</strong>`)
|
return template.HTML(`<strong><i data-lucide="folder" class="icon-sm"></i> Racine</strong>`)
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.Split(filepath.ToSlash(path), "/")
|
parts := strings.Split(filepath.ToSlash(path), "/")
|
||||||
@ -1379,7 +1438,7 @@ func (h *Handler) generateBreadcrumb(path string) template.HTML {
|
|||||||
sb.WriteString(`<span class="breadcrumb">`)
|
sb.WriteString(`<span class="breadcrumb">`)
|
||||||
|
|
||||||
// Lien racine
|
// Lien racine
|
||||||
sb.WriteString(`<a href="#" hx-get="/api/home" hx-target="#editor-container" hx-swap="innerHTML" hx-push-url="true" class="breadcrumb-link">📁 Racine</a>`)
|
sb.WriteString(`<a href="#" hx-get="/api/home" hx-target="#editor-container" hx-swap="innerHTML" hx-push-url="true" class="breadcrumb-link"><i data-lucide="folder" class="icon-sm"></i> Racine</a>`)
|
||||||
|
|
||||||
// Construire les liens pour chaque partie
|
// Construire les liens pour chaque partie
|
||||||
currentPath := ""
|
currentPath := ""
|
||||||
|
|||||||
607
internal/api/public.go
Normal file
607
internal/api/public.go
Normal file
@ -0,0 +1,607 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mathieu/personotes/internal/indexer"
|
||||||
|
"github.com/yuin/goldmark"
|
||||||
|
"github.com/yuin/goldmark/extension"
|
||||||
|
"github.com/yuin/goldmark/parser"
|
||||||
|
"github.com/yuin/goldmark/renderer/html"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PublicNote représente une note partagée publiquement
|
||||||
|
type PublicNote struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
PublishedAt time.Time `json:"published_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublicNotesData contient la liste des notes publiques
|
||||||
|
type PublicNotesData struct {
|
||||||
|
Notes []PublicNote `json:"notes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPublicDirPath retourne le chemin du dossier public HTML
|
||||||
|
func (h *Handler) getPublicDirPath() string {
|
||||||
|
return filepath.Join(h.notesDir, "..", "public")
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPublicNotesFilePath retourne le chemin du fichier .public.json
|
||||||
|
func (h *Handler) getPublicNotesFilePath() string {
|
||||||
|
return filepath.Join(h.notesDir, ".public.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadPublicNotes charge les notes publiques depuis le fichier JSON
|
||||||
|
func (h *Handler) loadPublicNotes() (*PublicNotesData, error) {
|
||||||
|
path := h.getPublicNotesFilePath()
|
||||||
|
|
||||||
|
// Si le fichier n'existe pas, retourner une liste vide
|
||||||
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
return &PublicNotesData{Notes: []PublicNote{}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var publicNotes PublicNotesData
|
||||||
|
if err := json.Unmarshal(data, &publicNotes); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trier par date de publication (plus récent d'abord)
|
||||||
|
sort.Slice(publicNotes.Notes, func(i, j int) bool {
|
||||||
|
return publicNotes.Notes[i].PublishedAt.After(publicNotes.Notes[j].PublishedAt)
|
||||||
|
})
|
||||||
|
|
||||||
|
return &publicNotes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// savePublicNotes sauvegarde les notes publiques dans le fichier JSON
|
||||||
|
func (h *Handler) savePublicNotes(publicNotes *PublicNotesData) error {
|
||||||
|
path := h.getPublicNotesFilePath()
|
||||||
|
|
||||||
|
data, err := json.MarshalIndent(publicNotes, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.WriteFile(path, data, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isPublic vérifie si une note est publique
|
||||||
|
func (h *Handler) isPublic(notePath string) bool {
|
||||||
|
publicNotes, err := h.loadPublicNotes()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, note := range publicNotes.Notes {
|
||||||
|
if note.Path == notePath {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensurePublicDir crée le dossier public s'il n'existe pas
|
||||||
|
func (h *Handler) ensurePublicDir() error {
|
||||||
|
publicDir := h.getPublicDirPath()
|
||||||
|
|
||||||
|
// Créer public/
|
||||||
|
if err := os.MkdirAll(publicDir, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Créer public/static/
|
||||||
|
staticDir := filepath.Join(publicDir, "static")
|
||||||
|
if err := os.MkdirAll(staticDir, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyStaticAssets copie les fichiers CSS/fonts nécessaires
|
||||||
|
func (h *Handler) copyStaticAssets() error {
|
||||||
|
publicStaticDir := filepath.Join(h.getPublicDirPath(), "static")
|
||||||
|
|
||||||
|
// Fichiers à copier
|
||||||
|
filesToCopy := []string{
|
||||||
|
"static/theme.css",
|
||||||
|
"static/themes.css",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range filesToCopy {
|
||||||
|
src := file
|
||||||
|
dst := filepath.Join(publicStaticDir, filepath.Base(file))
|
||||||
|
|
||||||
|
if err := copyFile(src, dst); err != nil {
|
||||||
|
h.logger.Printf("Avertissement: impossible de copier %s: %v", file, err)
|
||||||
|
// Continuer même si la copie échoue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyFile copie un fichier
|
||||||
|
func copyFile(src, dst string) error {
|
||||||
|
sourceFile, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer sourceFile.Close()
|
||||||
|
|
||||||
|
destFile, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer destFile.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(destFile, sourceFile)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateNoteHTML génère le fichier HTML pour une note publique
|
||||||
|
func (h *Handler) generateNoteHTML(notePath string) error {
|
||||||
|
// Lire le fichier Markdown
|
||||||
|
absPath := filepath.Join(h.notesDir, notePath)
|
||||||
|
content, err := os.ReadFile(absPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("lecture fichier: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extraire le front matter et le contenu
|
||||||
|
fm, body, err := indexer.ExtractFrontMatterAndBodyFromReader(bytes.NewReader(content))
|
||||||
|
if err != nil {
|
||||||
|
// Continuer avec le contenu complet si erreur
|
||||||
|
body = string(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Déterminer le titre
|
||||||
|
title := filepath.Base(notePath)
|
||||||
|
if fm.Title != "" {
|
||||||
|
title = fm.Title
|
||||||
|
} else {
|
||||||
|
title = strings.TrimSuffix(title, ".md")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convertir Markdown en HTML avec goldmark
|
||||||
|
md := goldmark.New(
|
||||||
|
goldmark.WithExtensions(
|
||||||
|
extension.GFM,
|
||||||
|
extension.Typographer,
|
||||||
|
),
|
||||||
|
goldmark.WithParserOptions(
|
||||||
|
parser.WithAutoHeadingID(),
|
||||||
|
),
|
||||||
|
goldmark.WithRendererOptions(
|
||||||
|
html.WithHardWraps(),
|
||||||
|
html.WithXHTML(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := md.Convert([]byte(body), &buf); err != nil {
|
||||||
|
return fmt.Errorf("conversion markdown: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Générer le HTML complet standalone
|
||||||
|
htmlContent := h.generateStandaloneHTML(title, buf.String(), fm.Tags, fm.Date)
|
||||||
|
|
||||||
|
// Écrire le fichier HTML - structure plate avec seulement le nom du fichier
|
||||||
|
filename := filepath.Base(notePath)
|
||||||
|
outputPath := filepath.Join(h.getPublicDirPath(), strings.TrimSuffix(filename, ".md")+".html")
|
||||||
|
|
||||||
|
if err := os.WriteFile(outputPath, []byte(htmlContent), 0644); err != nil {
|
||||||
|
return fmt.Errorf("écriture fichier: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Printf("HTML généré: %s", outputPath)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteNoteHTML supprime le fichier HTML d'une note
|
||||||
|
func (h *Handler) deleteNoteHTML(notePath string) error {
|
||||||
|
filename := filepath.Base(notePath)
|
||||||
|
outputPath := filepath.Join(h.getPublicDirPath(), strings.TrimSuffix(filename, ".md")+".html")
|
||||||
|
|
||||||
|
if err := os.Remove(outputPath); err != nil && !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Printf("HTML supprimé: %s", outputPath)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generatePublicIndex génère le fichier index.html avec la liste des notes
|
||||||
|
func (h *Handler) generatePublicIndex() error {
|
||||||
|
publicNotes, err := h.loadPublicNotes()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("chargement notes publiques: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enrichir avec les métadonnées
|
||||||
|
enrichedNotes := []map[string]interface{}{}
|
||||||
|
for _, note := range publicNotes.Notes {
|
||||||
|
filename := filepath.Base(note.Path)
|
||||||
|
item := map[string]interface{}{
|
||||||
|
"Path": strings.TrimSuffix(filename, ".md") + ".html",
|
||||||
|
"Title": note.Title,
|
||||||
|
"PublishedAt": note.PublishedAt.Format("02/01/2006"),
|
||||||
|
"Tags": []string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Essayer de lire le fichier pour obtenir les métadonnées
|
||||||
|
absPath := filepath.Join(h.notesDir, note.Path)
|
||||||
|
if content, err := os.ReadFile(absPath); err == nil {
|
||||||
|
if fm, _, err := indexer.ExtractFrontMatterAndBodyFromReader(bytes.NewReader(content)); err == nil {
|
||||||
|
if fm.Title != "" {
|
||||||
|
item["Title"] = fm.Title
|
||||||
|
}
|
||||||
|
item["Tags"] = fm.Tags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enrichedNotes = append(enrichedNotes, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Générer le HTML de l'index
|
||||||
|
htmlContent := h.generateIndexHTML(enrichedNotes)
|
||||||
|
|
||||||
|
// Écrire index.html
|
||||||
|
indexPath := filepath.Join(h.getPublicDirPath(), "index.html")
|
||||||
|
if err := os.WriteFile(indexPath, []byte(htmlContent), 0644); err != nil {
|
||||||
|
return fmt.Errorf("écriture index.html: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Printf("Index généré: %s", indexPath)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handlePublicList retourne la liste des notes publiques
|
||||||
|
func (h *Handler) handlePublicList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodGet {
|
||||||
|
http.Error(w, "Méthode non autorisée", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
publicNotes, err := h.loadPublicNotes()
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Printf("Erreur chargement notes publiques: %v", err)
|
||||||
|
http.Error(w, "Erreur de chargement", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(publicNotes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handlePublicToggle bascule le statut public d'une note + génère/supprime HTML
|
||||||
|
func (h *Handler) handlePublicToggle(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Printf("handlePublicToggle appelé")
|
||||||
|
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
h.logger.Printf("Méthode non autorisée: %s", r.Method)
|
||||||
|
http.Error(w, "Méthode non autorisée", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
h.logger.Printf("Erreur ParseForm: %v", err)
|
||||||
|
http.Error(w, "Formulaire invalide", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
path := r.FormValue("path")
|
||||||
|
h.logger.Printf("Chemin reçu: %s", path)
|
||||||
|
if path == "" {
|
||||||
|
h.logger.Printf("Chemin vide")
|
||||||
|
http.Error(w, "Chemin requis", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valider que le fichier existe
|
||||||
|
absPath := filepath.Join(h.notesDir, path)
|
||||||
|
if _, err := os.Stat(absPath); os.IsNotExist(err) {
|
||||||
|
http.Error(w, "Fichier introuvable", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
publicNotes, err := h.loadPublicNotes()
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Printf("Erreur chargement notes publiques: %v", err)
|
||||||
|
http.Error(w, "Erreur de chargement", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier si la note est déjà publique
|
||||||
|
isCurrentlyPublic := false
|
||||||
|
newNotes := []PublicNote{}
|
||||||
|
for _, note := range publicNotes.Notes {
|
||||||
|
if note.Path == path {
|
||||||
|
isCurrentlyPublic = true
|
||||||
|
} else {
|
||||||
|
newNotes = append(newNotes, note)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isCurrentlyPublic {
|
||||||
|
// Retirer du public - supprimer le HTML
|
||||||
|
publicNotes.Notes = newNotes
|
||||||
|
|
||||||
|
if err := h.deleteNoteHTML(path); err != nil {
|
||||||
|
h.logger.Printf("Erreur suppression HTML: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Ajouter au public - générer le HTML
|
||||||
|
title := filepath.Base(path)
|
||||||
|
title = strings.TrimSuffix(title, ".md")
|
||||||
|
|
||||||
|
// Essayer de lire le titre depuis le fichier
|
||||||
|
if content, err := os.ReadFile(absPath); err == nil {
|
||||||
|
if fm, _, err := indexer.ExtractFrontMatterAndBodyFromReader(bytes.NewReader(content)); err == nil && fm.Title != "" {
|
||||||
|
title = fm.Title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newNote := PublicNote{
|
||||||
|
Path: path,
|
||||||
|
Title: title,
|
||||||
|
PublishedAt: time.Now(),
|
||||||
|
}
|
||||||
|
publicNotes.Notes = append(publicNotes.Notes, newNote)
|
||||||
|
|
||||||
|
// Créer les dossiers nécessaires
|
||||||
|
if err := h.ensurePublicDir(); err != nil {
|
||||||
|
h.logger.Printf("Erreur création dossiers: %v", err)
|
||||||
|
http.Error(w, "Erreur de création des dossiers", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copier les assets statiques
|
||||||
|
if err := h.copyStaticAssets(); err != nil {
|
||||||
|
h.logger.Printf("Avertissement copie assets: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Générer le HTML de la note
|
||||||
|
if err := h.generateNoteHTML(path); err != nil {
|
||||||
|
h.logger.Printf("Erreur génération HTML: %v", err)
|
||||||
|
http.Error(w, "Erreur de génération HTML", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sauvegarder le fichier .public.json
|
||||||
|
if err := h.savePublicNotes(publicNotes); err != nil {
|
||||||
|
h.logger.Printf("Erreur sauvegarde notes publiques: %v", err)
|
||||||
|
http.Error(w, "Erreur de sauvegarde", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Régénérer l'index
|
||||||
|
if err := h.generatePublicIndex(); err != nil {
|
||||||
|
h.logger.Printf("Erreur génération index: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retourner le nouveau statut
|
||||||
|
status := "private"
|
||||||
|
if !isCurrentlyPublic {
|
||||||
|
status = "public"
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(map[string]string{
|
||||||
|
"status": status,
|
||||||
|
"path": path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateStandaloneHTML génère un fichier HTML standalone complet
|
||||||
|
func (h *Handler) generateStandaloneHTML(title, content string, tags []string, date string) string {
|
||||||
|
tagsHTML := ""
|
||||||
|
if len(tags) > 0 {
|
||||||
|
tagsHTML = `<div class="public-tags">`
|
||||||
|
for _, tag := range tags {
|
||||||
|
tagsHTML += fmt.Sprintf(`<span class="tag">#%s</span>`, template.HTMLEscapeString(tag))
|
||||||
|
}
|
||||||
|
tagsHTML += `</div>`
|
||||||
|
}
|
||||||
|
|
||||||
|
dateHTML := ""
|
||||||
|
if date != "" {
|
||||||
|
dateHTML = fmt.Sprintf(`<span><i data-lucide="calendar" class="icon-sm"></i> %s</span>`, template.HTMLEscapeString(date))
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(`<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>%s - PersoNotes</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="static/theme.css" />
|
||||||
|
<link rel="stylesheet" href="static/themes.css" />
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/styles/atom-one-dark.min.css" />
|
||||||
|
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script>
|
||||||
|
<!-- Lucide Icons -->
|
||||||
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 0; background: var(--bg-primary, #1e1e1e); color: var(--text-primary, #e0e0e0); font-family: 'JetBrains Mono', monospace; }
|
||||||
|
.public-view-container { max-width: 800px; margin: 0 auto; padding: 2rem; }
|
||||||
|
.public-nav { margin-bottom: 2rem; }
|
||||||
|
.public-nav a { color: var(--accent-blue, #42a5f5); text-decoration: none; display: inline-flex; align-items: center; gap: 0.5rem; }
|
||||||
|
.public-nav a:hover { text-decoration: underline; }
|
||||||
|
.public-header { margin-bottom: 2rem; padding-bottom: 1.5rem; border-bottom: 2px solid var(--bg-tertiary, #2d2d2d); }
|
||||||
|
.public-title { font-size: 2.5rem; margin: 0 0 1rem 0; color: var(--text-primary, #e0e0e0); }
|
||||||
|
.public-meta { display: flex; gap: 1.5rem; color: var(--text-secondary, #b0b0b0); font-size: 0.95rem; }
|
||||||
|
.public-tags { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 1rem; }
|
||||||
|
.tag { background: var(--bg-tertiary, #2d2d2d); color: var(--text-secondary, #b0b0b0); padding: 0.25rem 0.75rem; border-radius: 4px; font-size: 0.85rem; }
|
||||||
|
.public-content { line-height: 1.8; color: var(--text-primary, #e0e0e0); font-size: 1.05rem; }
|
||||||
|
.public-content h1, .public-content h2, .public-content h3, .public-content h4, .public-content h5, .public-content h6 { color: var(--text-primary, #e0e0e0); margin-top: 2rem; margin-bottom: 1rem; }
|
||||||
|
.public-content h1 { font-size: 2rem; } .public-content h2 { font-size: 1.75rem; } .public-content h3 { font-size: 1.5rem; } .public-content h4 { font-size: 1.25rem; }
|
||||||
|
.public-content p { margin-bottom: 1rem; }
|
||||||
|
.public-content a { color: var(--accent-blue, #42a5f5); text-decoration: none; }
|
||||||
|
.public-content a:hover { text-decoration: underline; }
|
||||||
|
.public-content pre { background: var(--bg-secondary, #252525); border: 1px solid var(--bg-tertiary, #2d2d2d); border-radius: 6px; padding: 1rem; overflow-x: auto; margin: 1rem 0; }
|
||||||
|
.public-content code { font-family: 'JetBrains Mono', monospace; background: var(--bg-secondary, #252525); padding: 0.2rem 0.4rem; border-radius: 3px; font-size: 0.9em; }
|
||||||
|
.public-content pre code { background: none; padding: 0; }
|
||||||
|
.public-content blockquote { border-left: 4px solid var(--accent-blue, #42a5f5); padding-left: 1rem; margin: 1rem 0; color: var(--text-secondary, #b0b0b0); font-style: italic; }
|
||||||
|
.public-content ul, .public-content ol { margin: 1rem 0; padding-left: 2rem; }
|
||||||
|
.public-content li { margin-bottom: 0.5rem; }
|
||||||
|
.public-content table { width: 100%%; border-collapse: collapse; margin: 1rem 0; }
|
||||||
|
.public-content th, .public-content td { border: 1px solid var(--bg-tertiary, #2d2d2d); padding: 0.75rem; text-align: left; }
|
||||||
|
.public-content th { background: var(--bg-secondary, #252525); font-weight: 600; }
|
||||||
|
.public-content img { max-width: 100%%; height: auto; border-radius: 6px; margin: 1rem 0; }
|
||||||
|
.public-content hr { border: none; border-top: 2px solid var(--bg-tertiary, #2d2d2d); margin: 2rem 0; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="public-view-container">
|
||||||
|
<div class="public-nav">
|
||||||
|
<a href="index.html">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<line x1="19" y1="12" x2="5" y2="12"></line>
|
||||||
|
<polyline points="12 19 5 12 12 5"></polyline>
|
||||||
|
</svg>
|
||||||
|
Back to public notes
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<article>
|
||||||
|
<div class="public-header">
|
||||||
|
<h1 class="public-title">%s</h1>
|
||||||
|
<div class="public-meta">%s</div>
|
||||||
|
%s
|
||||||
|
</div>
|
||||||
|
<div class="public-content">
|
||||||
|
%s
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
document.querySelectorAll('pre code').forEach((block) => {
|
||||||
|
hljs.highlightElement(block);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
// Initialize Lucide icons
|
||||||
|
if (typeof lucide !== 'undefined') {
|
||||||
|
lucide.createIcons();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>`, template.HTMLEscapeString(title), template.HTMLEscapeString(title), dateHTML, tagsHTML, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateIndexHTML génère le HTML de la page d'index
|
||||||
|
func (h *Handler) generateIndexHTML(notes []map[string]interface{}) string {
|
||||||
|
notesHTML := ""
|
||||||
|
|
||||||
|
if len(notes) > 0 {
|
||||||
|
notesHTML = `<ul class="notes-list">`
|
||||||
|
for _, note := range notes {
|
||||||
|
path, _ := note["Path"].(string)
|
||||||
|
title, _ := note["Title"].(string)
|
||||||
|
publishedAt, _ := note["PublishedAt"].(string)
|
||||||
|
tags := []string{}
|
||||||
|
if tagsInterface, ok := note["Tags"].([]string); ok {
|
||||||
|
tags = tagsInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
tagsHTML := ""
|
||||||
|
if len(tags) > 0 {
|
||||||
|
tagsHTML = `<div class="note-tags">`
|
||||||
|
for _, tag := range tags {
|
||||||
|
tagsHTML += fmt.Sprintf(`<span class="tag">#%s</span>`, template.HTMLEscapeString(tag))
|
||||||
|
}
|
||||||
|
tagsHTML += `</div>`
|
||||||
|
}
|
||||||
|
|
||||||
|
notesHTML += fmt.Sprintf(`
|
||||||
|
<li class="note-item">
|
||||||
|
<a href="%s">
|
||||||
|
<h2 class="note-title">%s</h2>
|
||||||
|
<div class="note-meta">
|
||||||
|
<span><i data-lucide="calendar" class="icon-sm"></i> Published on %s</span>
|
||||||
|
</div>
|
||||||
|
%s
|
||||||
|
</a>
|
||||||
|
</li>`, path, template.HTMLEscapeString(title), publishedAt, tagsHTML)
|
||||||
|
}
|
||||||
|
notesHTML += `</ul>`
|
||||||
|
} else {
|
||||||
|
notesHTML = `
|
||||||
|
<div class="empty-state">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
|
||||||
|
<polyline points="14 2 14 8 20 8"></polyline>
|
||||||
|
</svg>
|
||||||
|
<p>No public notes yet</p>
|
||||||
|
</div>`
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(`<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Public Notes - PersoNotes</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="static/theme.css" />
|
||||||
|
<link rel="stylesheet" href="static/themes.css" />
|
||||||
|
<!-- Lucide Icons -->
|
||||||
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 0; background: var(--bg-primary, #1e1e1e); color: var(--text-primary, #e0e0e0); font-family: 'JetBrains Mono', monospace; }
|
||||||
|
.public-container { max-width: 900px; margin: 0 auto; padding: 2rem; }
|
||||||
|
.public-header { text-align: center; margin-bottom: 3rem; padding-bottom: 1.5rem; border-bottom: 2px solid var(--bg-tertiary, #2d2d2d); }
|
||||||
|
.public-header h1 { font-size: 2.5rem; margin: 0 0 0.5rem 0; color: var(--text-primary, #e0e0e0); }
|
||||||
|
.public-header p { color: var(--text-secondary, #b0b0b0); font-size: 1.1rem; }
|
||||||
|
.notes-list { list-style: none; padding: 0; margin: 0; }
|
||||||
|
.note-item { background: var(--bg-secondary, #252525); border: 1px solid var(--bg-tertiary, #2d2d2d); border-radius: 8px; padding: 1.5rem; margin-bottom: 1rem; transition: transform 0.2s, box-shadow 0.2s; }
|
||||||
|
.note-item:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); }
|
||||||
|
.note-item a { text-decoration: none; color: inherit; display: block; }
|
||||||
|
.note-title { font-size: 1.5rem; font-weight: 600; margin: 0 0 0.5rem 0; color: var(--text-primary, #e0e0e0); }
|
||||||
|
.note-meta { display: flex; gap: 1rem; color: var(--text-secondary, #b0b0b0); font-size: 0.9rem; margin-bottom: 0.5rem; }
|
||||||
|
.note-tags { display: flex; gap: 0.5rem; flex-wrap: wrap; }
|
||||||
|
.tag { background: var(--bg-tertiary, #2d2d2d); color: var(--text-secondary, #b0b0b0); padding: 0.25rem 0.75rem; border-radius: 4px; font-size: 0.85rem; }
|
||||||
|
.empty-state { text-align: center; padding: 3rem; color: var(--text-secondary, #b0b0b0); }
|
||||||
|
.empty-state svg { width: 64px; height: 64px; margin-bottom: 1rem; opacity: 0.5; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="public-container">
|
||||||
|
<div class="public-header">
|
||||||
|
<h1><i data-lucide="book-open" class="icon-md"></i> Public Notes</h1>
|
||||||
|
<p>Discover my shared notes</p>
|
||||||
|
</div>
|
||||||
|
%s
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
// Initialize Lucide icons
|
||||||
|
if (typeof lucide !== 'undefined') {
|
||||||
|
lucide.createIcons();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>`, notesHTML)
|
||||||
|
}
|
||||||
@ -260,5 +260,25 @@
|
|||||||
"nextMonth": "Next month",
|
"nextMonth": "Next month",
|
||||||
"noNote": "No note",
|
"noNote": "No note",
|
||||||
"noteOf": "Note of"
|
"noteOf": "Note of"
|
||||||
|
},
|
||||||
|
"public": {
|
||||||
|
"buttonPrivate": "Private",
|
||||||
|
"buttonPublic": "Public",
|
||||||
|
"titlePrivate": "This note is private - Click to make it public",
|
||||||
|
"titlePublic": "This note is public - Click to make it private",
|
||||||
|
"listTitle": "Public Notes",
|
||||||
|
"listSubtitle": "Discover my shared notes",
|
||||||
|
"noPublicNotes": "No public notes yet",
|
||||||
|
"publishedOn": "Published on",
|
||||||
|
"backToList": "Back to public notes",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"notificationPublished": "✅ Note published! It is now visible at /public",
|
||||||
|
"notificationUnpublished": "✅ Note removed from public space",
|
||||||
|
"notificationError": "❌ Error changing status"
|
||||||
|
},
|
||||||
|
"publicNotes": {
|
||||||
|
"title": "Public Notes",
|
||||||
|
"editNote": "Edit note",
|
||||||
|
"viewPublic": "View public page"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -260,5 +260,25 @@
|
|||||||
"nextMonth": "Mois suivant",
|
"nextMonth": "Mois suivant",
|
||||||
"noNote": "Pas de note",
|
"noNote": "Pas de note",
|
||||||
"noteOf": "Note du"
|
"noteOf": "Note du"
|
||||||
|
},
|
||||||
|
"public": {
|
||||||
|
"buttonPrivate": "Privé",
|
||||||
|
"buttonPublic": "Public",
|
||||||
|
"titlePrivate": "Cette note est privée - Cliquer pour la rendre publique",
|
||||||
|
"titlePublic": "Cette note est publique - Cliquer pour la rendre privée",
|
||||||
|
"listTitle": "Notes Publiques",
|
||||||
|
"listSubtitle": "Découvrez mes notes partagées",
|
||||||
|
"noPublicNotes": "Aucune note publique pour le moment",
|
||||||
|
"publishedOn": "Publié le",
|
||||||
|
"backToList": "Retour aux notes publiques",
|
||||||
|
"loading": "Chargement...",
|
||||||
|
"notificationPublished": "✅ Note publiée ! Elle est maintenant visible sur /public",
|
||||||
|
"notificationUnpublished": "✅ Note retirée de l'espace public",
|
||||||
|
"notificationError": "❌ Erreur lors de la modification du statut"
|
||||||
|
},
|
||||||
|
"publicNotes": {
|
||||||
|
"title": "Notes Publiques",
|
||||||
|
"editNote": "Éditer la note",
|
||||||
|
"viewPublic": "Voir la page publique"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,40 +7,68 @@
|
|||||||
"added_at": "2025-11-11T13:55:49.371541279+01:00",
|
"added_at": "2025-11-11T13:55:49.371541279+01:00",
|
||||||
"order": 0
|
"order": 0
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"path": "research/design/ui-inspiration.md",
|
|
||||||
"is_dir": false,
|
|
||||||
"title": "ui-inspiration",
|
|
||||||
"added_at": "2025-11-11T14:20:49.985321698+01:00",
|
|
||||||
"order": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"path": "ideas/client-feedback.md",
|
"path": "ideas/client-feedback.md",
|
||||||
"is_dir": false,
|
"is_dir": false,
|
||||||
"title": "client-feedback",
|
"title": "client-feedback",
|
||||||
"added_at": "2025-11-11T14:22:16.497953232+01:00",
|
"added_at": "2025-11-11T14:22:16.497953232+01:00",
|
||||||
|
"order": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "personal/learning-goals.md",
|
||||||
|
"is_dir": false,
|
||||||
|
"title": "learning-goals",
|
||||||
|
"added_at": "2025-12-24T13:17:21.123080299+01:00",
|
||||||
"order": 2
|
"order": 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "ideas/collaboration.md",
|
"path": "archive",
|
||||||
"is_dir": false,
|
"is_dir": true,
|
||||||
"title": "collaboration",
|
"title": "archive",
|
||||||
"added_at": "2025-11-11T14:22:18.012032002+01:00",
|
"added_at": "2025-12-24T15:48:42.323990909+01:00",
|
||||||
"order": 3
|
"order": 3
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "ideas/mobile-app.md",
|
"path": "archive/ai-assistant.md",
|
||||||
"is_dir": false,
|
"is_dir": false,
|
||||||
"title": "mobile-app",
|
"title": "ai-assistant",
|
||||||
"added_at": "2025-11-11T14:22:19.048311608+01:00",
|
"added_at": "2025-12-24T15:49:08.265811752+01:00",
|
||||||
"order": 4
|
"order": 4
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "documentation/guides",
|
"path": "meetings/2025/sprint-planning.md",
|
||||||
"is_dir": true,
|
"is_dir": false,
|
||||||
"title": "guides",
|
"title": "sprint-planning",
|
||||||
"added_at": "2025-11-12T18:18:20.53353467+01:00",
|
"added_at": "2025-12-24T15:55:04.58786532+01:00",
|
||||||
"order": 5
|
"order": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "meetings",
|
||||||
|
"is_dir": true,
|
||||||
|
"title": "meetings",
|
||||||
|
"added_at": "2025-12-24T15:56:40.332077313+01:00",
|
||||||
|
"order": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "Myfolder.txt",
|
||||||
|
"is_dir": true,
|
||||||
|
"title": "Myfolder.txt",
|
||||||
|
"added_at": "2025-12-24T15:57:09.512148418+01:00",
|
||||||
|
"order": 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "projets",
|
||||||
|
"is_dir": true,
|
||||||
|
"title": "projets",
|
||||||
|
"added_at": "2025-12-24T15:59:24.938636283+01:00",
|
||||||
|
"order": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "documentation/bienvenue.md",
|
||||||
|
"is_dir": false,
|
||||||
|
"title": "bienvenue",
|
||||||
|
"added_at": "2025-12-24T16:30:46.322365652+01:00",
|
||||||
|
"order": 9
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
39
notes/.public.json
Normal file
39
notes/.public.json
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"notes": [
|
||||||
|
{
|
||||||
|
"path": "documentation/bienvenue.md",
|
||||||
|
"title": "Bienvenue dans PersoNotes",
|
||||||
|
"published_at": "2025-12-24T16:28:26.173656053+01:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "documentation/authentication.md",
|
||||||
|
"title": "Authentication Guide",
|
||||||
|
"published_at": "2025-12-24T16:26:46.9731494+01:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "ideas/collaboration.md",
|
||||||
|
"title": "Real-time Collaboration",
|
||||||
|
"published_at": "2025-12-24T12:40:15.778895438+01:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "daily/2025/11/11.md",
|
||||||
|
"title": "Daily Note - 2025-11-11",
|
||||||
|
"published_at": "2025-12-24T12:38:12.77519956+01:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "ideas/mobile-app.md",
|
||||||
|
"title": "Native Mobile App",
|
||||||
|
"published_at": "2025-12-24T12:37:20.649319402+01:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "tasks/backlog.md",
|
||||||
|
"title": "Product Backlog",
|
||||||
|
"published_at": "2025-11-13T20:13:05.36334514+01:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "documentation/api/endpoints.md",
|
||||||
|
"title": "API Endpoints Reference",
|
||||||
|
"published_at": "2025-11-13T19:36:57.522466752+01:00"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
25
notes/daily/2025/12/24.md
Normal file
25
notes/daily/2025/12/24.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
title: Daily Note - 2025-12-24
|
||||||
|
date: 24-12-2025
|
||||||
|
last_modified: 24-12-2025:12:36
|
||||||
|
tags:
|
||||||
|
- daily
|
||||||
|
---
|
||||||
|
|
||||||
|
# 📅 Mercredi 24 Décembre 2025
|
||||||
|
|
||||||
|
## 🎯 Objectifs du jour
|
||||||
|
-
|
||||||
|
|
||||||
|
|
||||||
|
## 📝 Notes
|
||||||
|
-
|
||||||
|
|
||||||
|
## ✅ Accompli
|
||||||
|
-
|
||||||
|
|
||||||
|
## 💭 Réflexions
|
||||||
|
-
|
||||||
|
|
||||||
|
## 🔗 Liens
|
||||||
|
-
|
||||||
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: Bienvenue dans PersoNotes
|
title: Bienvenue dans PersoNotes
|
||||||
date: 08-11-2025
|
date: 08-11-2025
|
||||||
last_modified: 09-11-2025:01:13
|
last_modified: 24-12-2025:16:28
|
||||||
tags:
|
tags:
|
||||||
- aide
|
- aide
|
||||||
- documentation
|
- documentation
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: Client Feedback Session
|
title: Client Feedback Session
|
||||||
date: 10-11-2025
|
date: 10-11-2025
|
||||||
last_modified: 11-11-2025:11:12
|
last_modified: 24-12-2025:16:45
|
||||||
tags:
|
tags:
|
||||||
- meeting
|
- meeting
|
||||||
- client
|
- client
|
||||||
@ -28,3 +28,7 @@ Focus sur l'export PDF pour la v1.1
|
|||||||
|
|
||||||
|
|
||||||
# DERNIER EDIT
|
# DERNIER EDIT
|
||||||
|
|
||||||
|
[Progressive Web App](projets/mobile/pwa.md)
|
||||||
|
|
||||||
|
`This is a `
|
||||||
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: CodeMirror Integration
|
title: CodeMirror Integration
|
||||||
date: 10-11-2025
|
date: 10-11-2025
|
||||||
last_modified: 12-11-2025:09:37
|
last_modified: 24-12-2025:16:46
|
||||||
tags:
|
tags:
|
||||||
- projet
|
- projet
|
||||||
- frontend
|
- frontend
|
||||||
@ -1,8 +1,11 @@
|
|||||||
---
|
---
|
||||||
title: "Vite Build Process"
|
title: Vite Build Process
|
||||||
date: "10-11-2025"
|
date: 10-11-2025
|
||||||
last_modified: "10-11-2025:19:21"
|
last_modified: 24-12-2025:16:41
|
||||||
tags: ["projet", "frontend", "build"]
|
tags:
|
||||||
|
- projet
|
||||||
|
- frontend
|
||||||
|
- build
|
||||||
---
|
---
|
||||||
|
|
||||||
# Vite Build Process
|
# Vite Build Process
|
||||||
|
|||||||
108
public/authentication.html
Normal file
108
public/authentication.html
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Authentication Guide - PersoNotes</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="static/theme.css" />
|
||||||
|
<link rel="stylesheet" href="static/themes.css" />
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/styles/atom-one-dark.min.css" />
|
||||||
|
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script>
|
||||||
|
<!-- Lucide Icons -->
|
||||||
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 0; background: var(--bg-primary, #1e1e1e); color: var(--text-primary, #e0e0e0); font-family: 'JetBrains Mono', monospace; }
|
||||||
|
.public-view-container { max-width: 800px; margin: 0 auto; padding: 2rem; }
|
||||||
|
.public-nav { margin-bottom: 2rem; }
|
||||||
|
.public-nav a { color: var(--accent-blue, #42a5f5); text-decoration: none; display: inline-flex; align-items: center; gap: 0.5rem; }
|
||||||
|
.public-nav a:hover { text-decoration: underline; }
|
||||||
|
.public-header { margin-bottom: 2rem; padding-bottom: 1.5rem; border-bottom: 2px solid var(--bg-tertiary, #2d2d2d); }
|
||||||
|
.public-title { font-size: 2.5rem; margin: 0 0 1rem 0; color: var(--text-primary, #e0e0e0); }
|
||||||
|
.public-meta { display: flex; gap: 1.5rem; color: var(--text-secondary, #b0b0b0); font-size: 0.95rem; }
|
||||||
|
.public-tags { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 1rem; }
|
||||||
|
.tag { background: var(--bg-tertiary, #2d2d2d); color: var(--text-secondary, #b0b0b0); padding: 0.25rem 0.75rem; border-radius: 4px; font-size: 0.85rem; }
|
||||||
|
.public-content { line-height: 1.8; color: var(--text-primary, #e0e0e0); font-size: 1.05rem; }
|
||||||
|
.public-content h1, .public-content h2, .public-content h3, .public-content h4, .public-content h5, .public-content h6 { color: var(--text-primary, #e0e0e0); margin-top: 2rem; margin-bottom: 1rem; }
|
||||||
|
.public-content h1 { font-size: 2rem; } .public-content h2 { font-size: 1.75rem; } .public-content h3 { font-size: 1.5rem; } .public-content h4 { font-size: 1.25rem; }
|
||||||
|
.public-content p { margin-bottom: 1rem; }
|
||||||
|
.public-content a { color: var(--accent-blue, #42a5f5); text-decoration: none; }
|
||||||
|
.public-content a:hover { text-decoration: underline; }
|
||||||
|
.public-content pre { background: var(--bg-secondary, #252525); border: 1px solid var(--bg-tertiary, #2d2d2d); border-radius: 6px; padding: 1rem; overflow-x: auto; margin: 1rem 0; }
|
||||||
|
.public-content code { font-family: 'JetBrains Mono', monospace; background: var(--bg-secondary, #252525); padding: 0.2rem 0.4rem; border-radius: 3px; font-size: 0.9em; }
|
||||||
|
.public-content pre code { background: none; padding: 0; }
|
||||||
|
.public-content blockquote { border-left: 4px solid var(--accent-blue, #42a5f5); padding-left: 1rem; margin: 1rem 0; color: var(--text-secondary, #b0b0b0); font-style: italic; }
|
||||||
|
.public-content ul, .public-content ol { margin: 1rem 0; padding-left: 2rem; }
|
||||||
|
.public-content li { margin-bottom: 0.5rem; }
|
||||||
|
.public-content table { width: 100%; border-collapse: collapse; margin: 1rem 0; }
|
||||||
|
.public-content th, .public-content td { border: 1px solid var(--bg-tertiary, #2d2d2d); padding: 0.75rem; text-align: left; }
|
||||||
|
.public-content th { background: var(--bg-secondary, #252525); font-weight: 600; }
|
||||||
|
.public-content img { max-width: 100%; height: auto; border-radius: 6px; margin: 1rem 0; }
|
||||||
|
.public-content hr { border: none; border-top: 2px solid var(--bg-tertiary, #2d2d2d); margin: 2rem 0; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="public-view-container">
|
||||||
|
<div class="public-nav">
|
||||||
|
<a href="index.html">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<line x1="19" y1="12" x2="5" y2="12"></line>
|
||||||
|
<polyline points="12 19 5 12 12 5"></polyline>
|
||||||
|
</svg>
|
||||||
|
Back to public notes
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<article>
|
||||||
|
<div class="public-header">
|
||||||
|
<h1 class="public-title">Authentication Guide</h1>
|
||||||
|
<div class="public-meta"><span><i data-lucide="calendar" class="icon-sm"></i> 10-11-2025</span></div>
|
||||||
|
<div class="public-tags"><span class="tag">#documentation</span><span class="tag">#api</span><span class="tag">#security</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="public-content">
|
||||||
|
<h1 id="authentication">Authentication</h1>
|
||||||
|
<h2 id="current-status">Current Status</h2>
|
||||||
|
<p>⚠️ No authentication currently implemented.</p>
|
||||||
|
<h2 id="future-implementation">Future Implementation</h2>
|
||||||
|
<h3 id="jwt-tokens">JWT Tokens</h3>
|
||||||
|
<pre><code>POST /api/auth/login
|
||||||
|
{
|
||||||
|
"username": "user",
|
||||||
|
"password": "pass"
|
||||||
|
}
|
||||||
|
|
||||||
|
Response:
|
||||||
|
{
|
||||||
|
"token": "eyJhbGc..."
|
||||||
|
}
|
||||||
|
</code></pre>
|
||||||
|
<h3 id="bearer-token">Bearer Token</h3>
|
||||||
|
<pre><code>Authorization: Bearer eyJhbGc...
|
||||||
|
</code></pre>
|
||||||
|
<h2 id="security">Security</h2>
|
||||||
|
<ul>
|
||||||
|
<li>HTTPS only in production</li>
|
||||||
|
<li>Reverse proxy with nginx</li>
|
||||||
|
<li>Rate limiting</li>
|
||||||
|
</ul>
|
||||||
|
<p><!-- raw HTML omitted -->Test Delete 1<!-- raw HTML omitted --></p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
document.querySelectorAll('pre code').forEach((block) => {
|
||||||
|
hljs.highlightElement(block);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
// Initialize Lucide icons
|
||||||
|
if (typeof lucide !== 'undefined') {
|
||||||
|
lucide.createIcons();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
95
public/backlog.html
Normal file
95
public/backlog.html
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Product Backlog - PersoNotes</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="static/theme.css" />
|
||||||
|
<link rel="stylesheet" href="static/themes.css" />
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/styles/atom-one-dark.min.css" />
|
||||||
|
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 0; background: var(--bg-primary, #1e1e1e); color: var(--text-primary, #e0e0e0); font-family: 'JetBrains Mono', monospace; }
|
||||||
|
.public-view-container { max-width: 800px; margin: 0 auto; padding: 2rem; }
|
||||||
|
.public-nav { margin-bottom: 2rem; }
|
||||||
|
.public-nav a { color: var(--accent-blue, #42a5f5); text-decoration: none; display: inline-flex; align-items: center; gap: 0.5rem; }
|
||||||
|
.public-nav a:hover { text-decoration: underline; }
|
||||||
|
.public-header { margin-bottom: 2rem; padding-bottom: 1.5rem; border-bottom: 2px solid var(--bg-tertiary, #2d2d2d); }
|
||||||
|
.public-title { font-size: 2.5rem; margin: 0 0 1rem 0; color: var(--text-primary, #e0e0e0); }
|
||||||
|
.public-meta { display: flex; gap: 1.5rem; color: var(--text-secondary, #b0b0b0); font-size: 0.95rem; }
|
||||||
|
.public-tags { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 1rem; }
|
||||||
|
.tag { background: var(--bg-tertiary, #2d2d2d); color: var(--text-secondary, #b0b0b0); padding: 0.25rem 0.75rem; border-radius: 4px; font-size: 0.85rem; }
|
||||||
|
.public-content { line-height: 1.8; color: var(--text-primary, #e0e0e0); font-size: 1.05rem; }
|
||||||
|
.public-content h1, .public-content h2, .public-content h3, .public-content h4, .public-content h5, .public-content h6 { color: var(--text-primary, #e0e0e0); margin-top: 2rem; margin-bottom: 1rem; }
|
||||||
|
.public-content h1 { font-size: 2rem; } .public-content h2 { font-size: 1.75rem; } .public-content h3 { font-size: 1.5rem; } .public-content h4 { font-size: 1.25rem; }
|
||||||
|
.public-content p { margin-bottom: 1rem; }
|
||||||
|
.public-content a { color: var(--accent-blue, #42a5f5); text-decoration: none; }
|
||||||
|
.public-content a:hover { text-decoration: underline; }
|
||||||
|
.public-content pre { background: var(--bg-secondary, #252525); border: 1px solid var(--bg-tertiary, #2d2d2d); border-radius: 6px; padding: 1rem; overflow-x: auto; margin: 1rem 0; }
|
||||||
|
.public-content code { font-family: 'JetBrains Mono', monospace; background: var(--bg-secondary, #252525); padding: 0.2rem 0.4rem; border-radius: 3px; font-size: 0.9em; }
|
||||||
|
.public-content pre code { background: none; padding: 0; }
|
||||||
|
.public-content blockquote { border-left: 4px solid var(--accent-blue, #42a5f5); padding-left: 1rem; margin: 1rem 0; color: var(--text-secondary, #b0b0b0); font-style: italic; }
|
||||||
|
.public-content ul, .public-content ol { margin: 1rem 0; padding-left: 2rem; }
|
||||||
|
.public-content li { margin-bottom: 0.5rem; }
|
||||||
|
.public-content table { width: 100%; border-collapse: collapse; margin: 1rem 0; }
|
||||||
|
.public-content th, .public-content td { border: 1px solid var(--bg-tertiary, #2d2d2d); padding: 0.75rem; text-align: left; }
|
||||||
|
.public-content th { background: var(--bg-secondary, #252525); font-weight: 600; }
|
||||||
|
.public-content img { max-width: 100%; height: auto; border-radius: 6px; margin: 1rem 0; }
|
||||||
|
.public-content hr { border: none; border-top: 2px solid var(--bg-tertiary, #2d2d2d); margin: 2rem 0; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="public-view-container">
|
||||||
|
<div class="public-nav">
|
||||||
|
<a href="index.html">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<line x1="19" y1="12" x2="5" y2="12"></line>
|
||||||
|
<polyline points="12 19 5 12 12 5"></polyline>
|
||||||
|
</svg>
|
||||||
|
Back to public notes
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<article>
|
||||||
|
<div class="public-header">
|
||||||
|
<h1 class="public-title">Product Backlog</h1>
|
||||||
|
<div class="public-meta"><span>📅 10-11-2025</span></div>
|
||||||
|
<div class="public-tags"><span class="tag">#task</span><span class="tag">#planning</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="public-content">
|
||||||
|
<h1 id="product-backlog">Product Backlog</h1>
|
||||||
|
<h2 id="high-priority">High Priority</h2>
|
||||||
|
<ul>
|
||||||
|
<li><input disabled="" type="checkbox" /> Export notes to PDF</li>
|
||||||
|
<li><input disabled="" type="checkbox" /> Bulk operations (delete, move)</li>
|
||||||
|
<li><input disabled="" type="checkbox" /> Tags management page</li>
|
||||||
|
<li><input disabled="" type="checkbox" /> Keyboard shortcuts documentation</li>
|
||||||
|
</ul>
|
||||||
|
<h2 id="medium-priority">Medium Priority</h2>
|
||||||
|
<ul>
|
||||||
|
<li><input disabled="" type="checkbox" /> Note templates</li>
|
||||||
|
<li><input disabled="" type="checkbox" /> Trash/Recycle bin</li>
|
||||||
|
<li><input disabled="" type="checkbox" /> Note history/versions</li>
|
||||||
|
<li><input disabled="" type="checkbox" /> Full-text search improvements</li>
|
||||||
|
</ul>
|
||||||
|
<h2 id="low-priority">Low Priority</h2>
|
||||||
|
<ul>
|
||||||
|
<li><input disabled="" type="checkbox" /> Themes customization</li>
|
||||||
|
<li><input disabled="" type="checkbox" /> Plugin system</li>
|
||||||
|
<li><input disabled="" type="checkbox" /> Graph view of notes links</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
document.querySelectorAll('pre code').forEach((block) => {
|
||||||
|
hljs.highlightElement(block);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
312
public/bienvenue.html
Normal file
312
public/bienvenue.html
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Bienvenue dans PersoNotes - PersoNotes</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="static/theme.css" />
|
||||||
|
<link rel="stylesheet" href="static/themes.css" />
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/styles/atom-one-dark.min.css" />
|
||||||
|
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script>
|
||||||
|
<!-- Lucide Icons -->
|
||||||
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 0; background: var(--bg-primary, #1e1e1e); color: var(--text-primary, #e0e0e0); font-family: 'JetBrains Mono', monospace; }
|
||||||
|
.public-view-container { max-width: 800px; margin: 0 auto; padding: 2rem; }
|
||||||
|
.public-nav { margin-bottom: 2rem; }
|
||||||
|
.public-nav a { color: var(--accent-blue, #42a5f5); text-decoration: none; display: inline-flex; align-items: center; gap: 0.5rem; }
|
||||||
|
.public-nav a:hover { text-decoration: underline; }
|
||||||
|
.public-header { margin-bottom: 2rem; padding-bottom: 1.5rem; border-bottom: 2px solid var(--bg-tertiary, #2d2d2d); }
|
||||||
|
.public-title { font-size: 2.5rem; margin: 0 0 1rem 0; color: var(--text-primary, #e0e0e0); }
|
||||||
|
.public-meta { display: flex; gap: 1.5rem; color: var(--text-secondary, #b0b0b0); font-size: 0.95rem; }
|
||||||
|
.public-tags { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 1rem; }
|
||||||
|
.tag { background: var(--bg-tertiary, #2d2d2d); color: var(--text-secondary, #b0b0b0); padding: 0.25rem 0.75rem; border-radius: 4px; font-size: 0.85rem; }
|
||||||
|
.public-content { line-height: 1.8; color: var(--text-primary, #e0e0e0); font-size: 1.05rem; }
|
||||||
|
.public-content h1, .public-content h2, .public-content h3, .public-content h4, .public-content h5, .public-content h6 { color: var(--text-primary, #e0e0e0); margin-top: 2rem; margin-bottom: 1rem; }
|
||||||
|
.public-content h1 { font-size: 2rem; } .public-content h2 { font-size: 1.75rem; } .public-content h3 { font-size: 1.5rem; } .public-content h4 { font-size: 1.25rem; }
|
||||||
|
.public-content p { margin-bottom: 1rem; }
|
||||||
|
.public-content a { color: var(--accent-blue, #42a5f5); text-decoration: none; }
|
||||||
|
.public-content a:hover { text-decoration: underline; }
|
||||||
|
.public-content pre { background: var(--bg-secondary, #252525); border: 1px solid var(--bg-tertiary, #2d2d2d); border-radius: 6px; padding: 1rem; overflow-x: auto; margin: 1rem 0; }
|
||||||
|
.public-content code { font-family: 'JetBrains Mono', monospace; background: var(--bg-secondary, #252525); padding: 0.2rem 0.4rem; border-radius: 3px; font-size: 0.9em; }
|
||||||
|
.public-content pre code { background: none; padding: 0; }
|
||||||
|
.public-content blockquote { border-left: 4px solid var(--accent-blue, #42a5f5); padding-left: 1rem; margin: 1rem 0; color: var(--text-secondary, #b0b0b0); font-style: italic; }
|
||||||
|
.public-content ul, .public-content ol { margin: 1rem 0; padding-left: 2rem; }
|
||||||
|
.public-content li { margin-bottom: 0.5rem; }
|
||||||
|
.public-content table { width: 100%; border-collapse: collapse; margin: 1rem 0; }
|
||||||
|
.public-content th, .public-content td { border: 1px solid var(--bg-tertiary, #2d2d2d); padding: 0.75rem; text-align: left; }
|
||||||
|
.public-content th { background: var(--bg-secondary, #252525); font-weight: 600; }
|
||||||
|
.public-content img { max-width: 100%; height: auto; border-radius: 6px; margin: 1rem 0; }
|
||||||
|
.public-content hr { border: none; border-top: 2px solid var(--bg-tertiary, #2d2d2d); margin: 2rem 0; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="public-view-container">
|
||||||
|
<div class="public-nav">
|
||||||
|
<a href="index.html">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<line x1="19" y1="12" x2="5" y2="12"></line>
|
||||||
|
<polyline points="12 19 5 12 12 5"></polyline>
|
||||||
|
</svg>
|
||||||
|
Back to public notes
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<article>
|
||||||
|
<div class="public-header">
|
||||||
|
<h1 class="public-title">Bienvenue dans PersoNotes</h1>
|
||||||
|
<div class="public-meta"><span><i data-lucide="calendar" class="icon-sm"></i> 08-11-2025</span></div>
|
||||||
|
<div class="public-tags"><span class="tag">#aide</span><span class="tag">#documentation</span><span class="tag">#tutorial</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="public-content">
|
||||||
|
<p>08/11/2025 -</p>
|
||||||
|
<p>C’est mon application de prise de note</p>
|
||||||
|
<h2 id="jespre-quelle-va-bien-marcher">J’espére qu’elle va bien marcher</h2>
|
||||||
|
<h1 id="bienvenue-dans-personotes">Bienvenue dans PersoNotes</h1>
|
||||||
|
<p>Bienvenue dans votre application de prise de notes en Markdown ! Cette page vous explique comment utiliser l’application et le format front matter.</p>
|
||||||
|
<h2 id="quest-ce-que-le-front-matter-">Qu’est-ce que le Front Matter ?</h2>
|
||||||
|
<p>Le <strong>front matter</strong> est un bloc de métadonnées en YAML placé au début de chaque note, entre deux lignes <code>---</code>. Il permet d’ajouter des informations structurées à vos notes.</p>
|
||||||
|
<h3 id="format-du-front-matter">Format du Front Matter</h3>
|
||||||
|
<pre><code class="language-yaml">---
|
||||||
|
title: Titre de votre note
|
||||||
|
date: 08-11-2025
|
||||||
|
last_modified: 08-11-2025:14:10
|
||||||
|
tags: [projet, urgent, backend]
|
||||||
|
---
|
||||||
|
</code></pre>
|
||||||
|
<h3 id="champs-disponibles">Champs disponibles</h3>
|
||||||
|
<ul>
|
||||||
|
<li><strong>title</strong> : Le titre de votre note (généré automatiquement depuis le nom du fichier)</li>
|
||||||
|
<li><strong>date</strong> : Date de création (format: JJ-MM-AAAA)</li>
|
||||||
|
<li><strong>last_modified</strong> : Dernière modification (format: JJ-MM-AAAA:HH:MM) - mis à jour automatiquement</li>
|
||||||
|
<li><strong>tags</strong> : Liste de tags pour organiser et rechercher vos notes</li>
|
||||||
|
</ul>
|
||||||
|
<h3 id="exemples-de-tags">Exemples de tags</h3>
|
||||||
|
<p>Vous pouvez écrire vos tags de deux façons :</p>
|
||||||
|
<pre><code class="language-yaml"># Format inline
|
||||||
|
tags: [projet, urgent, backend, api]
|
||||||
|
|
||||||
|
# Format liste
|
||||||
|
tags:
|
||||||
|
- projet
|
||||||
|
- urgent
|
||||||
|
- backend
|
||||||
|
- api
|
||||||
|
</code></pre>
|
||||||
|
<p>Les tags sont indexés et permettent de rechercher vos notes via la barre de recherche.</p>
|
||||||
|
<h2 id="guide-markdown">Guide Markdown</h2>
|
||||||
|
<h3 id="titres">Titres</h3>
|
||||||
|
<pre><code class="language-markdown"># Titre niveau 1
|
||||||
|
## Titre niveau 2
|
||||||
|
### Titre niveau 3
|
||||||
|
</code></pre>
|
||||||
|
<h3 id="emphase">Emphase</h3>
|
||||||
|
<pre><code class="language-markdown">*italique* ou _italique_
|
||||||
|
**gras** ou __gras__
|
||||||
|
***gras et italique***
|
||||||
|
~~barré~~
|
||||||
|
</code></pre>
|
||||||
|
<p>Rendu : <em>italique</em>, <strong>gras</strong>, <em><strong>gras et italique</strong></em></p>
|
||||||
|
<h3 id="listes">Listes</h3>
|
||||||
|
<h4 id="liste-non-ordonne">Liste non ordonnée</h4>
|
||||||
|
<pre><code class="language-markdown">- Élément 1
|
||||||
|
- Élément 2
|
||||||
|
- Sous-élément 2.1
|
||||||
|
- Sous-élément 2.2
|
||||||
|
- Élément 3
|
||||||
|
</code></pre>
|
||||||
|
<p>Rendu :</p>
|
||||||
|
<ul>
|
||||||
|
<li>Élément 1</li>
|
||||||
|
<li>Élément 2
|
||||||
|
<ul>
|
||||||
|
<li>Sous-élément 2.1</li>
|
||||||
|
<li>Sous-élément 2.2</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>Élément 3</li>
|
||||||
|
</ul>
|
||||||
|
<h4 id="liste-ordonne">Liste ordonnée</h4>
|
||||||
|
<pre><code class="language-markdown">1. Premier élément
|
||||||
|
2. Deuxième élément
|
||||||
|
3. Troisième élément
|
||||||
|
</code></pre>
|
||||||
|
<p>Rendu :</p>
|
||||||
|
<ol>
|
||||||
|
<li>Premier élément</li>
|
||||||
|
<li>Deuxième élément</li>
|
||||||
|
<li>Troisième élément</li>
|
||||||
|
</ol>
|
||||||
|
<h3 id="liens-et-images">Liens et Images</h3>
|
||||||
|
<pre><code class="language-markdown">[Texte du lien](https://example.com)
|
||||||
|

|
||||||
|
</code></pre>
|
||||||
|
<p>Exemple : <a href="https://www.markdownguide.org/">Documentation Markdown</a></p>
|
||||||
|
<h3 id="code">Code</h3>
|
||||||
|
<h4 id="code-inline">Code inline</h4>
|
||||||
|
<p>Utilisez des backticks : <code>code inline</code></p>
|
||||||
|
<h4 id="bloc-de-code">Bloc de code</h4>
|
||||||
|
<pre><code class="language-markdown">```javascript
|
||||||
|
function hello() {
|
||||||
|
console.log("Hello World!");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</code></pre>
|
||||||
|
<p>Rendu :</p>
|
||||||
|
<pre><code class="language-javascript">function hello() {
|
||||||
|
console.log("Hello World!");
|
||||||
|
}
|
||||||
|
</code></pre>
|
||||||
|
<h3 id="citations">Citations</h3>
|
||||||
|
<pre><code class="language-markdown">> Ceci est une citation
|
||||||
|
> sur plusieurs lignes
|
||||||
|
</code></pre>
|
||||||
|
<p>Rendu :</p>
|
||||||
|
<blockquote>
|
||||||
|
<p>Ceci est une citation<br />
|
||||||
|
sur plusieurs lignes</p>
|
||||||
|
</blockquote>
|
||||||
|
<h3 id="tableaux">Tableaux</h3>
|
||||||
|
<pre><code class="language-markdown">| Colonne 1 | Colonne 2 | Colonne 3 |
|
||||||
|
|-----------|-----------|-----------|
|
||||||
|
| Ligne 1 | Données | Données |
|
||||||
|
| Ligne 2 | Données | Données |
|
||||||
|
</code></pre>
|
||||||
|
<p>Rendu :</p>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Colonne 1</th>
|
||||||
|
<th>Colonne 2</th>
|
||||||
|
<th>Colonne 3</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Ligne 1</td>
|
||||||
|
<td>Données</td>
|
||||||
|
<td>Données</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Ligne 2</td>
|
||||||
|
<td>Données</td>
|
||||||
|
<td>Données</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<h3 id="sparateurs">Séparateurs</h3>
|
||||||
|
<pre><code class="language-markdown">---
|
||||||
|
</code></pre>
|
||||||
|
<p>Rendu :</p>
|
||||||
|
<hr />
|
||||||
|
<h2 id="commandes-slash">Commandes Slash</h2>
|
||||||
|
<p>Utilisez le caractère <code>/</code> au début d’une ligne pour accéder aux commandes rapides :</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>/h1</code>, <code>/h2</code>, <code>/h3</code> - Titres</li>
|
||||||
|
<li><code>/list</code> - Liste à puces</li>
|
||||||
|
<li><code>/date</code> - Insérer la date du jour</li>
|
||||||
|
<li><code>/link</code> - Créer un lien</li>
|
||||||
|
<li><code>/bold</code> - Texte en gras</li>
|
||||||
|
<li><code>/italic</code> - Texte en italique</li>
|
||||||
|
<li><code>/code</code> - Code inline</li>
|
||||||
|
<li><code>/codeblock</code> - Bloc de code</li>
|
||||||
|
<li><code>/quote</code> - Citation</li>
|
||||||
|
<li><code>/hr</code> - Ligne de séparation</li>
|
||||||
|
<li><code>/table</code> - Créer un tableau</li>
|
||||||
|
</ul>
|
||||||
|
<p><strong>Navigation</strong> : Utilisez les flèches ↑↓ pour naviguer, Entrée ou Tab pour insérer, Échap pour annuler.</p>
|
||||||
|
<h2 id="raccourcis-et-astuces">Raccourcis et Astuces</h2>
|
||||||
|
<h3 id="crer-une-note">Créer une note</h3>
|
||||||
|
<p>Cliquez sur le bouton <strong>✨ Nouvelle note</strong> dans l’en-tête. Si la note existe déjà, elle sera ouverte, sinon elle sera créée.</p>
|
||||||
|
<h3 id="rechercher-des-notes">Rechercher des notes</h3>
|
||||||
|
<p>Utilisez la barre de recherche en haut pour filtrer vos notes par tags. La recherche est mise à jour en temps réel.</p>
|
||||||
|
<h3 id="sauvegarder">Sauvegarder</h3>
|
||||||
|
<p>Cliquez sur le bouton <strong>💾 Enregistrer</strong> pour sauvegarder vos modifications. Le champ <code>last_modified</code> du front matter sera automatiquement mis à jour.</p>
|
||||||
|
<h3 id="supprimer-une-note">Supprimer une note</h3>
|
||||||
|
<p>Cliquez sur l’icône 🗑️ à côté du nom de la note dans la sidebar.</p>
|
||||||
|
<h2 id="organisation-avec-les-tags">Organisation avec les tags</h2>
|
||||||
|
<p>Les tags sont un excellent moyen d’organiser vos notes. Voici quelques suggestions :</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Par projet</strong> : <code>projet-notes</code>, <code>projet-api</code>, <code>projet-frontend</code></li>
|
||||||
|
<li><strong>Par priorité</strong> : <code>urgent</code>, <code>important</code>, <code>backlog</code></li>
|
||||||
|
<li><strong>Par type</strong> : <code>documentation</code>, <code>tutorial</code>, <code>meeting</code>, <code>todo</code></li>
|
||||||
|
<li><strong>Par technologie</strong> : <code>javascript</code>, <code>go</code>, <code>python</code>, <code>docker</code></li>
|
||||||
|
<li><strong>Par statut</strong> : <code>en-cours</code>, <code>terminé</code>, <code>archive</code></li>
|
||||||
|
</ul>
|
||||||
|
<h2 id="exemple-complet">Exemple complet</h2>
|
||||||
|
<p>Voici un exemple de note complète :</p>
|
||||||
|
<pre><code class="language-markdown">---
|
||||||
|
title: Réunion API Backend
|
||||||
|
date: 08-11-2025
|
||||||
|
last_modified: 08-11-2025:15:30
|
||||||
|
tags: [meeting, backend, api, urgent]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Réunion API Backend
|
||||||
|
|
||||||
|
## Participants
|
||||||
|
|
||||||
|
- Alice (Lead Dev)
|
||||||
|
- Bob (Backend)
|
||||||
|
- Charlie (Frontend)
|
||||||
|
|
||||||
|
## Points discutés
|
||||||
|
|
||||||
|
### 1. Architecture de l'API
|
||||||
|
|
||||||
|
Nous avons décidé d'utiliser une architecture REST avec les endpoints suivants :
|
||||||
|
|
||||||
|
- `GET /api/notes` - Liste toutes les notes
|
||||||
|
- `POST /api/notes` - Créer une note
|
||||||
|
- `PUT /api/notes/:id` - Modifier une note
|
||||||
|
- `DELETE /api/notes/:id` - Supprimer une note
|
||||||
|
|
||||||
|
### 2. Authentification
|
||||||
|
|
||||||
|
> Utilisation de JWT pour l'authentification
|
||||||
|
|
||||||
|
Code d'exemple :
|
||||||
|
|
||||||
|
```go
|
||||||
|
func generateToken(userID string) (string, error) {
|
||||||
|
// Implementation
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Prochaines étapes
|
||||||
|
|
||||||
|
- [ ] Implémenter les endpoints
|
||||||
|
- [ ] Écrire les tests
|
||||||
|
- [ ] Documentation API
|
||||||
|
|
||||||
|
## Actions
|
||||||
|
|
||||||
|
| Qui | Action | Deadline |
|
||||||
|
|---------|---------------------|------------|
|
||||||
|
| Bob | Endpoints API | 15-11-2025 |
|
||||||
|
| Charlie | Interface Frontend | 20-11-2025 |
|
||||||
|
| Alice | Review & Deploy | 25-11-2025 |
|
||||||
|
</code></pre>
|
||||||
|
<hr />
|
||||||
|
<p>Bonne prise de notes ! 📝</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
document.querySelectorAll('pre code').forEach((block) => {
|
||||||
|
hljs.highlightElement(block);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
// Initialize Lucide icons
|
||||||
|
if (typeof lucide !== 'undefined') {
|
||||||
|
lucide.createIcons();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
89
public/collaboration.html
Normal file
89
public/collaboration.html
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Real-time Collaboration - PersoNotes</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="static/theme.css" />
|
||||||
|
<link rel="stylesheet" href="static/themes.css" />
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/styles/atom-one-dark.min.css" />
|
||||||
|
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 0; background: var(--bg-primary, #1e1e1e); color: var(--text-primary, #e0e0e0); font-family: 'JetBrains Mono', monospace; }
|
||||||
|
.public-view-container { max-width: 800px; margin: 0 auto; padding: 2rem; }
|
||||||
|
.public-nav { margin-bottom: 2rem; }
|
||||||
|
.public-nav a { color: var(--accent-blue, #42a5f5); text-decoration: none; display: inline-flex; align-items: center; gap: 0.5rem; }
|
||||||
|
.public-nav a:hover { text-decoration: underline; }
|
||||||
|
.public-header { margin-bottom: 2rem; padding-bottom: 1.5rem; border-bottom: 2px solid var(--bg-tertiary, #2d2d2d); }
|
||||||
|
.public-title { font-size: 2.5rem; margin: 0 0 1rem 0; color: var(--text-primary, #e0e0e0); }
|
||||||
|
.public-meta { display: flex; gap: 1.5rem; color: var(--text-secondary, #b0b0b0); font-size: 0.95rem; }
|
||||||
|
.public-tags { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 1rem; }
|
||||||
|
.tag { background: var(--bg-tertiary, #2d2d2d); color: var(--text-secondary, #b0b0b0); padding: 0.25rem 0.75rem; border-radius: 4px; font-size: 0.85rem; }
|
||||||
|
.public-content { line-height: 1.8; color: var(--text-primary, #e0e0e0); font-size: 1.05rem; }
|
||||||
|
.public-content h1, .public-content h2, .public-content h3, .public-content h4, .public-content h5, .public-content h6 { color: var(--text-primary, #e0e0e0); margin-top: 2rem; margin-bottom: 1rem; }
|
||||||
|
.public-content h1 { font-size: 2rem; } .public-content h2 { font-size: 1.75rem; } .public-content h3 { font-size: 1.5rem; } .public-content h4 { font-size: 1.25rem; }
|
||||||
|
.public-content p { margin-bottom: 1rem; }
|
||||||
|
.public-content a { color: var(--accent-blue, #42a5f5); text-decoration: none; }
|
||||||
|
.public-content a:hover { text-decoration: underline; }
|
||||||
|
.public-content pre { background: var(--bg-secondary, #252525); border: 1px solid var(--bg-tertiary, #2d2d2d); border-radius: 6px; padding: 1rem; overflow-x: auto; margin: 1rem 0; }
|
||||||
|
.public-content code { font-family: 'JetBrains Mono', monospace; background: var(--bg-secondary, #252525); padding: 0.2rem 0.4rem; border-radius: 3px; font-size: 0.9em; }
|
||||||
|
.public-content pre code { background: none; padding: 0; }
|
||||||
|
.public-content blockquote { border-left: 4px solid var(--accent-blue, #42a5f5); padding-left: 1rem; margin: 1rem 0; color: var(--text-secondary, #b0b0b0); font-style: italic; }
|
||||||
|
.public-content ul, .public-content ol { margin: 1rem 0; padding-left: 2rem; }
|
||||||
|
.public-content li { margin-bottom: 0.5rem; }
|
||||||
|
.public-content table { width: 100%; border-collapse: collapse; margin: 1rem 0; }
|
||||||
|
.public-content th, .public-content td { border: 1px solid var(--bg-tertiary, #2d2d2d); padding: 0.75rem; text-align: left; }
|
||||||
|
.public-content th { background: var(--bg-secondary, #252525); font-weight: 600; }
|
||||||
|
.public-content img { max-width: 100%; height: auto; border-radius: 6px; margin: 1rem 0; }
|
||||||
|
.public-content hr { border: none; border-top: 2px solid var(--bg-tertiary, #2d2d2d); margin: 2rem 0; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="public-view-container">
|
||||||
|
<div class="public-nav">
|
||||||
|
<a href="index.html">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<line x1="19" y1="12" x2="5" y2="12"></line>
|
||||||
|
<polyline points="12 19 5 12 12 5"></polyline>
|
||||||
|
</svg>
|
||||||
|
Back to public notes
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<article>
|
||||||
|
<div class="public-header">
|
||||||
|
<h1 class="public-title">Real-time Collaboration</h1>
|
||||||
|
<div class="public-meta"><span>📅 10-11-2025</span></div>
|
||||||
|
<div class="public-tags"><span class="tag">#idea</span><span class="tag">#collaboration</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="public-content">
|
||||||
|
<h1 id="real-time-collaboration">Real-time Collaboration</h1>
|
||||||
|
<h2 id="goal">Goal</h2>
|
||||||
|
<p>Plusieurs utilisateurs éditent la même note simultanément.</p>
|
||||||
|
<h2 id="technology">Technology</h2>
|
||||||
|
<ul>
|
||||||
|
<li>WebSockets</li>
|
||||||
|
<li>Operational Transforms ou CRDT</li>
|
||||||
|
<li>Presence indicators</li>
|
||||||
|
</ul>
|
||||||
|
<h2 id="challenges">Challenges</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Conflict resolution</li>
|
||||||
|
<li>Performance at scale</li>
|
||||||
|
<li>User permissions</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
document.querySelectorAll('pre code').forEach((block) => {
|
||||||
|
hljs.highlightElement(block);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
110
public/index.html
Normal file
110
public/index.html
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Public Notes - PersoNotes</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="static/theme.css" />
|
||||||
|
<link rel="stylesheet" href="static/themes.css" />
|
||||||
|
<!-- Lucide Icons -->
|
||||||
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 0; background: var(--bg-primary, #1e1e1e); color: var(--text-primary, #e0e0e0); font-family: 'JetBrains Mono', monospace; }
|
||||||
|
.public-container { max-width: 900px; margin: 0 auto; padding: 2rem; }
|
||||||
|
.public-header { text-align: center; margin-bottom: 3rem; padding-bottom: 1.5rem; border-bottom: 2px solid var(--bg-tertiary, #2d2d2d); }
|
||||||
|
.public-header h1 { font-size: 2.5rem; margin: 0 0 0.5rem 0; color: var(--text-primary, #e0e0e0); }
|
||||||
|
.public-header p { color: var(--text-secondary, #b0b0b0); font-size: 1.1rem; }
|
||||||
|
.notes-list { list-style: none; padding: 0; margin: 0; }
|
||||||
|
.note-item { background: var(--bg-secondary, #252525); border: 1px solid var(--bg-tertiary, #2d2d2d); border-radius: 8px; padding: 1.5rem; margin-bottom: 1rem; transition: transform 0.2s, box-shadow 0.2s; }
|
||||||
|
.note-item:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); }
|
||||||
|
.note-item a { text-decoration: none; color: inherit; display: block; }
|
||||||
|
.note-title { font-size: 1.5rem; font-weight: 600; margin: 0 0 0.5rem 0; color: var(--text-primary, #e0e0e0); }
|
||||||
|
.note-meta { display: flex; gap: 1rem; color: var(--text-secondary, #b0b0b0); font-size: 0.9rem; margin-bottom: 0.5rem; }
|
||||||
|
.note-tags { display: flex; gap: 0.5rem; flex-wrap: wrap; }
|
||||||
|
.tag { background: var(--bg-tertiary, #2d2d2d); color: var(--text-secondary, #b0b0b0); padding: 0.25rem 0.75rem; border-radius: 4px; font-size: 0.85rem; }
|
||||||
|
.empty-state { text-align: center; padding: 3rem; color: var(--text-secondary, #b0b0b0); }
|
||||||
|
.empty-state svg { width: 64px; height: 64px; margin-bottom: 1rem; opacity: 0.5; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="public-container">
|
||||||
|
<div class="public-header">
|
||||||
|
<h1><i data-lucide="book-open" class="icon-md"></i> Public Notes</h1>
|
||||||
|
<p>Discover my shared notes</p>
|
||||||
|
</div>
|
||||||
|
<ul class="notes-list">
|
||||||
|
<li class="note-item">
|
||||||
|
<a href="bienvenue.html">
|
||||||
|
<h2 class="note-title">Bienvenue dans PersoNotes</h2>
|
||||||
|
<div class="note-meta">
|
||||||
|
<span><i data-lucide="calendar" class="icon-sm"></i> Published on 24/12/2025</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="note-item">
|
||||||
|
<a href="authentication.html">
|
||||||
|
<h2 class="note-title">Authentication Guide</h2>
|
||||||
|
<div class="note-meta">
|
||||||
|
<span><i data-lucide="calendar" class="icon-sm"></i> Published on 24/12/2025</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="note-item">
|
||||||
|
<a href="collaboration.html">
|
||||||
|
<h2 class="note-title">Real-time Collaboration</h2>
|
||||||
|
<div class="note-meta">
|
||||||
|
<span><i data-lucide="calendar" class="icon-sm"></i> Published on 24/12/2025</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="note-item">
|
||||||
|
<a href="11.html">
|
||||||
|
<h2 class="note-title">Daily Note - 2025-11-11</h2>
|
||||||
|
<div class="note-meta">
|
||||||
|
<span><i data-lucide="calendar" class="icon-sm"></i> Published on 24/12/2025</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="note-item">
|
||||||
|
<a href="mobile-app.html">
|
||||||
|
<h2 class="note-title">Native Mobile App</h2>
|
||||||
|
<div class="note-meta">
|
||||||
|
<span><i data-lucide="calendar" class="icon-sm"></i> Published on 24/12/2025</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="note-item">
|
||||||
|
<a href="backlog.html">
|
||||||
|
<h2 class="note-title">Product Backlog</h2>
|
||||||
|
<div class="note-meta">
|
||||||
|
<span><i data-lucide="calendar" class="icon-sm"></i> Published on 13/11/2025</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="note-item">
|
||||||
|
<a href="endpoints.html">
|
||||||
|
<h2 class="note-title">API Endpoints Reference</h2>
|
||||||
|
<div class="note-meta">
|
||||||
|
<span><i data-lucide="calendar" class="icon-sm"></i> Published on 13/11/2025</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</a>
|
||||||
|
</li></ul>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
// Initialize Lucide icons
|
||||||
|
if (typeof lucide !== 'undefined') {
|
||||||
|
lucide.createIcons();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
93
public/mobile-app.html
Normal file
93
public/mobile-app.html
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Native Mobile App - PersoNotes</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="static/theme.css" />
|
||||||
|
<link rel="stylesheet" href="static/themes.css" />
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/styles/atom-one-dark.min.css" />
|
||||||
|
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 0; background: var(--bg-primary, #1e1e1e); color: var(--text-primary, #e0e0e0); font-family: 'JetBrains Mono', monospace; }
|
||||||
|
.public-view-container { max-width: 800px; margin: 0 auto; padding: 2rem; }
|
||||||
|
.public-nav { margin-bottom: 2rem; }
|
||||||
|
.public-nav a { color: var(--accent-blue, #42a5f5); text-decoration: none; display: inline-flex; align-items: center; gap: 0.5rem; }
|
||||||
|
.public-nav a:hover { text-decoration: underline; }
|
||||||
|
.public-header { margin-bottom: 2rem; padding-bottom: 1.5rem; border-bottom: 2px solid var(--bg-tertiary, #2d2d2d); }
|
||||||
|
.public-title { font-size: 2.5rem; margin: 0 0 1rem 0; color: var(--text-primary, #e0e0e0); }
|
||||||
|
.public-meta { display: flex; gap: 1.5rem; color: var(--text-secondary, #b0b0b0); font-size: 0.95rem; }
|
||||||
|
.public-tags { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 1rem; }
|
||||||
|
.tag { background: var(--bg-tertiary, #2d2d2d); color: var(--text-secondary, #b0b0b0); padding: 0.25rem 0.75rem; border-radius: 4px; font-size: 0.85rem; }
|
||||||
|
.public-content { line-height: 1.8; color: var(--text-primary, #e0e0e0); font-size: 1.05rem; }
|
||||||
|
.public-content h1, .public-content h2, .public-content h3, .public-content h4, .public-content h5, .public-content h6 { color: var(--text-primary, #e0e0e0); margin-top: 2rem; margin-bottom: 1rem; }
|
||||||
|
.public-content h1 { font-size: 2rem; } .public-content h2 { font-size: 1.75rem; } .public-content h3 { font-size: 1.5rem; } .public-content h4 { font-size: 1.25rem; }
|
||||||
|
.public-content p { margin-bottom: 1rem; }
|
||||||
|
.public-content a { color: var(--accent-blue, #42a5f5); text-decoration: none; }
|
||||||
|
.public-content a:hover { text-decoration: underline; }
|
||||||
|
.public-content pre { background: var(--bg-secondary, #252525); border: 1px solid var(--bg-tertiary, #2d2d2d); border-radius: 6px; padding: 1rem; overflow-x: auto; margin: 1rem 0; }
|
||||||
|
.public-content code { font-family: 'JetBrains Mono', monospace; background: var(--bg-secondary, #252525); padding: 0.2rem 0.4rem; border-radius: 3px; font-size: 0.9em; }
|
||||||
|
.public-content pre code { background: none; padding: 0; }
|
||||||
|
.public-content blockquote { border-left: 4px solid var(--accent-blue, #42a5f5); padding-left: 1rem; margin: 1rem 0; color: var(--text-secondary, #b0b0b0); font-style: italic; }
|
||||||
|
.public-content ul, .public-content ol { margin: 1rem 0; padding-left: 2rem; }
|
||||||
|
.public-content li { margin-bottom: 0.5rem; }
|
||||||
|
.public-content table { width: 100%; border-collapse: collapse; margin: 1rem 0; }
|
||||||
|
.public-content th, .public-content td { border: 1px solid var(--bg-tertiary, #2d2d2d); padding: 0.75rem; text-align: left; }
|
||||||
|
.public-content th { background: var(--bg-secondary, #252525); font-weight: 600; }
|
||||||
|
.public-content img { max-width: 100%; height: auto; border-radius: 6px; margin: 1rem 0; }
|
||||||
|
.public-content hr { border: none; border-top: 2px solid var(--bg-tertiary, #2d2d2d); margin: 2rem 0; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="public-view-container">
|
||||||
|
<div class="public-nav">
|
||||||
|
<a href="index.html">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<line x1="19" y1="12" x2="5" y2="12"></line>
|
||||||
|
<polyline points="12 19 5 12 12 5"></polyline>
|
||||||
|
</svg>
|
||||||
|
Back to public notes
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<article>
|
||||||
|
<div class="public-header">
|
||||||
|
<h1 class="public-title">Native Mobile App</h1>
|
||||||
|
<div class="public-meta"><span>📅 10-11-2025</span></div>
|
||||||
|
<div class="public-tags"><span class="tag">#idea</span><span class="tag">#mobile</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="public-content">
|
||||||
|
<h1 id="native-mobile-app-idea">Native Mobile App Idea</h1>
|
||||||
|
<h2 id="concept">Concept</h2>
|
||||||
|
<p>Créer une app native iOS/Android pour l’édition de notes.</p>
|
||||||
|
<h2 id="tech-stack">Tech Stack</h2>
|
||||||
|
<ul>
|
||||||
|
<li>React Native ou Flutter</li>
|
||||||
|
<li>Sync avec l’API REST</li>
|
||||||
|
<li>Offline-first architecture</li>
|
||||||
|
</ul>
|
||||||
|
<h2 id="features">Features</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Push notifications</li>
|
||||||
|
<li>Widget home screen</li>
|
||||||
|
<li>Voice notes</li>
|
||||||
|
<li>Photo attachments</li>
|
||||||
|
</ul>
|
||||||
|
<h2 id="timeline">Timeline</h2>
|
||||||
|
<p>Q2 2025 - Prototype<br />
|
||||||
|
Q3 2025 - Beta testing</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
document.querySelectorAll('pre code').forEach((block) => {
|
||||||
|
hljs.highlightElement(block);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
94
public/notes/personal/learning-goals.html
Normal file
94
public/notes/personal/learning-goals.html
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>2025 Learning Goals - PersoNotes</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="../static/theme.css" />
|
||||||
|
<link rel="stylesheet" href="../static/themes.css" />
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/styles/atom-one-dark.min.css" />
|
||||||
|
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 0; background: var(--bg-primary, #1e1e1e); color: var(--text-primary, #e0e0e0); font-family: 'JetBrains Mono', monospace; }
|
||||||
|
.public-view-container { max-width: 800px; margin: 0 auto; padding: 2rem; }
|
||||||
|
.public-nav { margin-bottom: 2rem; }
|
||||||
|
.public-nav a { color: var(--accent-blue, #42a5f5); text-decoration: none; display: inline-flex; align-items: center; gap: 0.5rem; }
|
||||||
|
.public-nav a:hover { text-decoration: underline; }
|
||||||
|
.public-header { margin-bottom: 2rem; padding-bottom: 1.5rem; border-bottom: 2px solid var(--bg-tertiary, #2d2d2d); }
|
||||||
|
.public-title { font-size: 2.5rem; margin: 0 0 1rem 0; color: var(--text-primary, #e0e0e0); }
|
||||||
|
.public-meta { display: flex; gap: 1.5rem; color: var(--text-secondary, #b0b0b0); font-size: 0.95rem; }
|
||||||
|
.public-tags { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 1rem; }
|
||||||
|
.tag { background: var(--bg-tertiary, #2d2d2d); color: var(--text-secondary, #b0b0b0); padding: 0.25rem 0.75rem; border-radius: 4px; font-size: 0.85rem; }
|
||||||
|
.public-content { line-height: 1.8; color: var(--text-primary, #e0e0e0); font-size: 1.05rem; }
|
||||||
|
.public-content h1, .public-content h2, .public-content h3, .public-content h4, .public-content h5, .public-content h6 { color: var(--text-primary, #e0e0e0); margin-top: 2rem; margin-bottom: 1rem; }
|
||||||
|
.public-content h1 { font-size: 2rem; } .public-content h2 { font-size: 1.75rem; } .public-content h3 { font-size: 1.5rem; } .public-content h4 { font-size: 1.25rem; }
|
||||||
|
.public-content p { margin-bottom: 1rem; }
|
||||||
|
.public-content a { color: var(--accent-blue, #42a5f5); text-decoration: none; }
|
||||||
|
.public-content a:hover { text-decoration: underline; }
|
||||||
|
.public-content pre { background: var(--bg-secondary, #252525); border: 1px solid var(--bg-tertiary, #2d2d2d); border-radius: 6px; padding: 1rem; overflow-x: auto; margin: 1rem 0; }
|
||||||
|
.public-content code { font-family: 'JetBrains Mono', monospace; background: var(--bg-secondary, #252525); padding: 0.2rem 0.4rem; border-radius: 3px; font-size: 0.9em; }
|
||||||
|
.public-content pre code { background: none; padding: 0; }
|
||||||
|
.public-content blockquote { border-left: 4px solid var(--accent-blue, #42a5f5); padding-left: 1rem; margin: 1rem 0; color: var(--text-secondary, #b0b0b0); font-style: italic; }
|
||||||
|
.public-content ul, .public-content ol { margin: 1rem 0; padding-left: 2rem; }
|
||||||
|
.public-content li { margin-bottom: 0.5rem; }
|
||||||
|
.public-content table { width: 100%; border-collapse: collapse; margin: 1rem 0; }
|
||||||
|
.public-content th, .public-content td { border: 1px solid var(--bg-tertiary, #2d2d2d); padding: 0.75rem; text-align: left; }
|
||||||
|
.public-content th { background: var(--bg-secondary, #252525); font-weight: 600; }
|
||||||
|
.public-content img { max-width: 100%; height: auto; border-radius: 6px; margin: 1rem 0; }
|
||||||
|
.public-content hr { border: none; border-top: 2px solid var(--bg-tertiary, #2d2d2d); margin: 2rem 0; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="public-view-container">
|
||||||
|
<div class="public-nav">
|
||||||
|
<a href="../index.html">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<line x1="19" y1="12" x2="5" y2="12"></line>
|
||||||
|
<polyline points="12 19 5 12 12 5"></polyline>
|
||||||
|
</svg>
|
||||||
|
Back to public notes
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<article>
|
||||||
|
<div class="public-header">
|
||||||
|
<h1 class="public-title">2025 Learning Goals</h1>
|
||||||
|
<div class="public-meta"><span>📅 10-11-2025</span></div>
|
||||||
|
<div class="public-tags"><span class="tag">#personal</span><span class="tag">#learning</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="public-content">
|
||||||
|
<h1 id="learning-goals-2025">Learning Goals 2025</h1>
|
||||||
|
<h2 id="technical">Technical</h2>
|
||||||
|
<ul>
|
||||||
|
<li><input checked="" disabled="" type="checkbox" /> Master Go concurrency patterns</li>
|
||||||
|
<li><input disabled="" type="checkbox" /> Learn Rust basics</li>
|
||||||
|
<li><input disabled="" type="checkbox" /> Deep dive into databases</li>
|
||||||
|
<li><input disabled="" type="checkbox" /> System design courses</li>
|
||||||
|
</ul>
|
||||||
|
<h2 id="soft-skills">Soft Skills</h2>
|
||||||
|
<ul>
|
||||||
|
<li><input disabled="" type="checkbox" /> Technical writing</li>
|
||||||
|
<li><input disabled="" type="checkbox" /> Public speaking</li>
|
||||||
|
<li><input disabled="" type="checkbox" /> Mentoring</li>
|
||||||
|
</ul>
|
||||||
|
<h2 id="books-to-read">Books to Read</h2>
|
||||||
|
<ol>
|
||||||
|
<li>Designing Data-Intensive Applications</li>
|
||||||
|
<li>The Pragmatic Programmer</li>
|
||||||
|
<li>Clean Architecture</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
document.querySelectorAll('pre code').forEach((block) => {
|
||||||
|
hljs.highlightElement(block);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
3663
public/static/theme.css
Normal file
3663
public/static/theme.css
Normal file
File diff suppressed because it is too large
Load Diff
679
public/static/themes.css
Normal file
679
public/static/themes.css
Normal file
@ -0,0 +1,679 @@
|
|||||||
|
/*
|
||||||
|
* PersoNotes - Multi-Theme System
|
||||||
|
* Supports: Material Dark (default), Monokai Dark, Dracula, One Dark, Solarized Dark, Nord
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
THEME: MATERIAL DARK (défaut)
|
||||||
|
=========================== */
|
||||||
|
:root,
|
||||||
|
[data-theme="material-dark"] {
|
||||||
|
--bg-primary: #1e1e1e;
|
||||||
|
--bg-secondary: #252525;
|
||||||
|
--bg-tertiary: #2d2d2d;
|
||||||
|
--bg-elevated: #323232;
|
||||||
|
|
||||||
|
--border-primary: #3e3e3e;
|
||||||
|
--border-secondary: #2a2a2a;
|
||||||
|
|
||||||
|
--text-primary: #e0e0e0;
|
||||||
|
--text-secondary: #b0b0b0;
|
||||||
|
--text-muted: #6e6e6e;
|
||||||
|
|
||||||
|
--accent-primary: #42a5f5;
|
||||||
|
--accent-hover: #5ab3f7;
|
||||||
|
|
||||||
|
--success: #66bb6a;
|
||||||
|
--warning: #ffa726;
|
||||||
|
--error: #ef5350;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
THEME: MONOKAI DARK
|
||||||
|
=========================== */
|
||||||
|
[data-theme="monokai-dark"] {
|
||||||
|
--bg-primary: #272822;
|
||||||
|
--bg-secondary: #2d2e27;
|
||||||
|
--bg-tertiary: #3e3d32;
|
||||||
|
--bg-elevated: #49483e;
|
||||||
|
|
||||||
|
--border-primary: #49483e;
|
||||||
|
--border-secondary: #3e3d32;
|
||||||
|
|
||||||
|
--text-primary: #f8f8f2;
|
||||||
|
--text-secondary: #cfcfc2;
|
||||||
|
--text-muted: #75715e;
|
||||||
|
|
||||||
|
--accent-primary: #66d9ef;
|
||||||
|
--accent-hover: #7ee5f7;
|
||||||
|
|
||||||
|
--success: #88c070;
|
||||||
|
--warning: #e6db74;
|
||||||
|
--error: #f92672;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
THEME: DRACULA
|
||||||
|
=========================== */
|
||||||
|
[data-theme="dracula"] {
|
||||||
|
--bg-primary: #282a36;
|
||||||
|
--bg-secondary: #2f3241;
|
||||||
|
--bg-tertiary: #373844;
|
||||||
|
--bg-elevated: #44475a;
|
||||||
|
|
||||||
|
--border-primary: #44475a;
|
||||||
|
--border-secondary: #373844;
|
||||||
|
|
||||||
|
--text-primary: #f8f8f2;
|
||||||
|
--text-secondary: #d6d6d6;
|
||||||
|
--text-muted: #6272a4;
|
||||||
|
|
||||||
|
--accent-primary: #8be9fd;
|
||||||
|
--accent-hover: #9ff3ff;
|
||||||
|
|
||||||
|
--success: #50fa7b;
|
||||||
|
--warning: #f1fa8c;
|
||||||
|
--error: #ff5555;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
THEME: ONE DARK
|
||||||
|
=========================== */
|
||||||
|
[data-theme="one-dark"] {
|
||||||
|
--bg-primary: #282c34;
|
||||||
|
--bg-secondary: #2c313a;
|
||||||
|
--bg-tertiary: #333842;
|
||||||
|
--bg-elevated: #3e4451;
|
||||||
|
|
||||||
|
--border-primary: #3e4451;
|
||||||
|
--border-secondary: #333842;
|
||||||
|
|
||||||
|
--text-primary: #abb2bf;
|
||||||
|
--text-secondary: #9ca3af;
|
||||||
|
--text-muted: #5c6370;
|
||||||
|
|
||||||
|
--accent-primary: #61afef;
|
||||||
|
--accent-hover: #75bdf5;
|
||||||
|
|
||||||
|
--success: #98c379;
|
||||||
|
--warning: #e5c07b;
|
||||||
|
--error: #e06c75;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
THEME: SOLARIZED DARK
|
||||||
|
=========================== */
|
||||||
|
[data-theme="solarized-dark"] {
|
||||||
|
--bg-primary: #002b36;
|
||||||
|
--bg-secondary: #073642;
|
||||||
|
--bg-tertiary: #094454;
|
||||||
|
--bg-elevated: #0e5261;
|
||||||
|
|
||||||
|
--border-primary: #0e5261;
|
||||||
|
--border-secondary: #094454;
|
||||||
|
|
||||||
|
--text-primary: #839496;
|
||||||
|
--text-secondary: #93a1a1;
|
||||||
|
--text-muted: #586e75;
|
||||||
|
|
||||||
|
--accent-primary: #268bd2;
|
||||||
|
--accent-hover: #4098d9;
|
||||||
|
|
||||||
|
--success: #859900;
|
||||||
|
--warning: #b58900;
|
||||||
|
--error: #dc322f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
THEME: NORD
|
||||||
|
=========================== */
|
||||||
|
[data-theme="nord"] {
|
||||||
|
--bg-primary: #2e3440;
|
||||||
|
--bg-secondary: #3b4252;
|
||||||
|
--bg-tertiary: #434c5e;
|
||||||
|
--bg-elevated: #4c566a;
|
||||||
|
|
||||||
|
--border-primary: #4c566a;
|
||||||
|
--border-secondary: #434c5e;
|
||||||
|
|
||||||
|
--text-primary: #eceff4;
|
||||||
|
--text-secondary: #d8dee9;
|
||||||
|
--text-muted: #616e88;
|
||||||
|
|
||||||
|
--accent-primary: #88c0d0;
|
||||||
|
--accent-hover: #9dcadb;
|
||||||
|
|
||||||
|
--success: #a3be8c;
|
||||||
|
--warning: #ebcb8b;
|
||||||
|
--error: #bf616a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
THEME: CATPPUCCIN MOCHA
|
||||||
|
=========================== */
|
||||||
|
[data-theme="catppuccin"] {
|
||||||
|
--bg-primary: #1e1e2e;
|
||||||
|
--bg-secondary: #181825;
|
||||||
|
--bg-tertiary: #313244;
|
||||||
|
--bg-elevated: #45475a;
|
||||||
|
|
||||||
|
--border-primary: #45475a;
|
||||||
|
--border-secondary: #313244;
|
||||||
|
|
||||||
|
--text-primary: #cdd6f4;
|
||||||
|
--text-secondary: #bac2de;
|
||||||
|
--text-muted: #6c7086;
|
||||||
|
|
||||||
|
--accent-primary: #89b4fa;
|
||||||
|
--accent-hover: #a6c8ff;
|
||||||
|
|
||||||
|
--success: #a6e3a1;
|
||||||
|
--warning: #f9e2af;
|
||||||
|
--error: #f38ba8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
THEME: EVERFOREST DARK
|
||||||
|
=========================== */
|
||||||
|
[data-theme="everforest"] {
|
||||||
|
--bg-primary: #2d353b;
|
||||||
|
--bg-secondary: #272e33;
|
||||||
|
--bg-tertiary: #343f44;
|
||||||
|
--bg-elevated: #3d484d;
|
||||||
|
|
||||||
|
--border-primary: #3d484d;
|
||||||
|
--border-secondary: #343f44;
|
||||||
|
|
||||||
|
--text-primary: #d3c6aa;
|
||||||
|
--text-secondary: #b4a990;
|
||||||
|
--text-muted: #7a8478;
|
||||||
|
|
||||||
|
--accent-primary: #7fbbb3;
|
||||||
|
--accent-hover: #93c9c1;
|
||||||
|
|
||||||
|
--success: #a7c080;
|
||||||
|
--warning: #dbbc7f;
|
||||||
|
--error: #e67e80;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
BOUTONS D'ACTION DE LA SIDEBAR
|
||||||
|
=========================== */
|
||||||
|
.sidebar-action-btn {
|
||||||
|
width: 100%;
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 1px solid var(--border-primary);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
color: var(--text-primary);
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-top: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-action-btn:hover {
|
||||||
|
background: var(--bg-elevated);
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
color: var(--accent-primary);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style spécifique pour le bouton paramètres (avec animation) */
|
||||||
|
.theme-settings-btn {
|
||||||
|
width: 100%;
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 1px solid var(--border-primary);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
color: var(--text-primary);
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-top: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-settings-btn:hover {
|
||||||
|
background: var(--bg-elevated);
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
color: var(--accent-primary);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-settings-btn svg {
|
||||||
|
animation: rotate 2s linear infinite;
|
||||||
|
animation-play-state: paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-settings-btn:hover svg {
|
||||||
|
animation-play-state: running;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
MODALE DE SÉLECTION DE THÈME
|
||||||
|
=========================== */
|
||||||
|
#theme-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-modal-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-modal-content {
|
||||||
|
position: relative;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--accent-primary);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
max-width: 600px;
|
||||||
|
width: 90%;
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
box-shadow: var(--shadow-lg), var(--shadow-glow);
|
||||||
|
animation: slideUp 0.3s ease;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-modal-content h2 {
|
||||||
|
margin: 0 0 var(--spacing-lg) 0;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 1.4rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-card {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 2px solid var(--border-primary);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-card:hover {
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-card.active {
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
box-shadow: 0 0 0 3px rgba(66, 165, 245, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-card.active::before {
|
||||||
|
content: '✓';
|
||||||
|
position: absolute;
|
||||||
|
top: var(--spacing-xs);
|
||||||
|
right: var(--spacing-xs);
|
||||||
|
background: var(--accent-primary);
|
||||||
|
color: white;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-card-icon {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-card-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-preview {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
|
margin-bottom: var(--spacing-sm);
|
||||||
|
height: 40px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-preview-color {
|
||||||
|
flex: 1;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-card:hover .theme-preview-color {
|
||||||
|
transform: scaleY(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-description {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Couleurs de prévisualisation pour chaque thème */
|
||||||
|
.theme-card[data-theme="material-dark"] .theme-preview-color:nth-child(1) { background: #1e1e1e; }
|
||||||
|
.theme-card[data-theme="material-dark"] .theme-preview-color:nth-child(2) { background: #42a5f5; }
|
||||||
|
.theme-card[data-theme="material-dark"] .theme-preview-color:nth-child(3) { background: #29b6f6; }
|
||||||
|
.theme-card[data-theme="material-dark"] .theme-preview-color:nth-child(4) { background: #e0e0e0; }
|
||||||
|
|
||||||
|
.theme-card[data-theme="monokai-dark"] .theme-preview-color:nth-child(1) { background: #272822; }
|
||||||
|
.theme-card[data-theme="monokai-dark"] .theme-preview-color:nth-child(2) { background: #66d9ef; }
|
||||||
|
.theme-card[data-theme="monokai-dark"] .theme-preview-color:nth-child(3) { background: #88c070; }
|
||||||
|
.theme-card[data-theme="monokai-dark"] .theme-preview-color:nth-child(4) { background: #f8f8f2; }
|
||||||
|
|
||||||
|
.theme-card[data-theme="dracula"] .theme-preview-color:nth-child(1) { background: #282a36; }
|
||||||
|
.theme-card[data-theme="dracula"] .theme-preview-color:nth-child(2) { background: #8be9fd; }
|
||||||
|
.theme-card[data-theme="dracula"] .theme-preview-color:nth-child(3) { background: #bd93f9; }
|
||||||
|
.theme-card[data-theme="dracula"] .theme-preview-color:nth-child(4) { background: #f8f8f2; }
|
||||||
|
|
||||||
|
.theme-card[data-theme="one-dark"] .theme-preview-color:nth-child(1) { background: #282c34; }
|
||||||
|
.theme-card[data-theme="one-dark"] .theme-preview-color:nth-child(2) { background: #61afef; }
|
||||||
|
.theme-card[data-theme="one-dark"] .theme-preview-color:nth-child(3) { background: #c678dd; }
|
||||||
|
.theme-card[data-theme="one-dark"] .theme-preview-color:nth-child(4) { background: #abb2bf; }
|
||||||
|
|
||||||
|
.theme-card[data-theme="solarized-dark"] .theme-preview-color:nth-child(1) { background: #002b36; }
|
||||||
|
.theme-card[data-theme="solarized-dark"] .theme-preview-color:nth-child(2) { background: #268bd2; }
|
||||||
|
.theme-card[data-theme="solarized-dark"] .theme-preview-color:nth-child(3) { background: #2aa198; }
|
||||||
|
.theme-card[data-theme="solarized-dark"] .theme-preview-color:nth-child(4) { background: #839496; }
|
||||||
|
|
||||||
|
.theme-card[data-theme="nord"] .theme-preview-color:nth-child(1) { background: #2e3440; }
|
||||||
|
.theme-card[data-theme="nord"] .theme-preview-color:nth-child(2) { background: #88c0d0; }
|
||||||
|
.theme-card[data-theme="nord"] .theme-preview-color:nth-child(3) { background: #81a1c1; }
|
||||||
|
.theme-card[data-theme="nord"] .theme-preview-color:nth-child(4) { background: #eceff4; }
|
||||||
|
|
||||||
|
.theme-card[data-theme="catppuccin"] .theme-preview-color:nth-child(1) { background: #1e1e2e; }
|
||||||
|
.theme-card[data-theme="catppuccin"] .theme-preview-color:nth-child(2) { background: #89b4fa; }
|
||||||
|
.theme-card[data-theme="catppuccin"] .theme-preview-color:nth-child(3) { background: #f5c2e7; }
|
||||||
|
.theme-card[data-theme="catppuccin"] .theme-preview-color:nth-child(4) { background: #cdd6f4; }
|
||||||
|
|
||||||
|
.theme-card[data-theme="everforest"] .theme-preview-color:nth-child(1) { background: #2d353b; }
|
||||||
|
.theme-card[data-theme="everforest"] .theme-preview-color:nth-child(2) { background: #7fbbb3; }
|
||||||
|
.theme-card[data-theme="everforest"] .theme-preview-color:nth-child(3) { background: #a7c080; }
|
||||||
|
.theme-card[data-theme="everforest"] .theme-preview-color:nth-child(4) { background: #d3c6aa; }
|
||||||
|
|
||||||
|
.theme-modal-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
margin-top: var(--spacing-lg);
|
||||||
|
padding-top: var(--spacing-md);
|
||||||
|
border-top: 1px solid var(--border-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Onglets de paramètres */
|
||||||
|
.settings-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
border-bottom: 2px solid var(--border-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-tab {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
padding: var(--spacing-sm) var(--spacing-md);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
margin-bottom: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-tab:hover {
|
||||||
|
color: var(--text-primary);
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-tab.active {
|
||||||
|
color: var(--accent-primary);
|
||||||
|
border-bottom-color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section {
|
||||||
|
animation: fadeIn 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grille de polices */
|
||||||
|
.font-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-card {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 2px solid var(--border-primary);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-card:hover {
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-card.active {
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
box-shadow: 0 0 0 3px rgba(66, 165, 245, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-card.active::before {
|
||||||
|
content: '✓';
|
||||||
|
position: absolute;
|
||||||
|
top: var(--spacing-xs);
|
||||||
|
right: var(--spacing-xs);
|
||||||
|
background: var(--accent-primary);
|
||||||
|
color: white;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-card-icon {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-card-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-preview {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border: 1px solid var(--border-primary);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
margin-bottom: var(--spacing-sm);
|
||||||
|
font-size: 1.1rem;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-primary);
|
||||||
|
min-height: 50px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-description {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sélecteur de taille de police */
|
||||||
|
.font-size-selector {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-size-option {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 2px solid var(--border-primary);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-size-option:hover {
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-size-option.active {
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
background: rgba(66, 165, 245, 0.1);
|
||||||
|
box-shadow: 0 0 0 3px rgba(66, 165, 245, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-label {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-size-option.active .size-label {
|
||||||
|
color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-preview {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
TOGGLE SWITCH (pour Mode Vim)
|
||||||
|
=========================== */
|
||||||
|
.toggle-switch {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 50px;
|
||||||
|
height: 26px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch input {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-slider {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: var(--bg-tertiary);
|
||||||
|
transition: 0.3s;
|
||||||
|
border-radius: 26px;
|
||||||
|
border: 1px solid var(--border-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-slider:before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
height: 18px;
|
||||||
|
width: 18px;
|
||||||
|
left: 4px;
|
||||||
|
bottom: 3px;
|
||||||
|
background-color: var(--text-muted);
|
||||||
|
transition: 0.3s;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch input:checked + .toggle-slider {
|
||||||
|
background-color: var(--accent-primary);
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch input:checked + .toggle-slider:before {
|
||||||
|
transform: translateX(24px);
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch input:focus + .toggle-slider {
|
||||||
|
box-shadow: 0 0 0 3px rgba(66, 165, 245, 0.2);
|
||||||
|
}
|
||||||
223
static/theme.css
223
static/theme.css
@ -17,35 +17,33 @@
|
|||||||
--text-secondary: #b0b0b0;
|
--text-secondary: #b0b0b0;
|
||||||
--text-muted: #6e6e6e;
|
--text-muted: #6e6e6e;
|
||||||
|
|
||||||
/* Accent colors - Blue focused */
|
/* Accent color - Single blue accent for consistency */
|
||||||
--accent-primary: #42a5f5;
|
--accent-primary: #42a5f5;
|
||||||
--accent-primary-hover: #64b5f6;
|
--accent-hover: #64b5f6;
|
||||||
--accent-secondary: #29b6f6;
|
|
||||||
--accent-secondary-hover: #4fc3f7;
|
|
||||||
|
|
||||||
/* Semantic colors */
|
/* Semantic colors */
|
||||||
--success: #66bb6a;
|
--success: #66bb6a;
|
||||||
--warning: #ffa726;
|
--warning: #ffa726;
|
||||||
--error: #ef5350;
|
--error: #ef5350;
|
||||||
|
|
||||||
/* Spacing */
|
/* Spacing - 8px base unit system */
|
||||||
--spacing-xs: 0.25rem;
|
--spacing-xs: 0.5rem; /* 8px - 1 unit */
|
||||||
--spacing-sm: 0.5rem;
|
--spacing-sm: 1rem; /* 16px - 2 units */
|
||||||
--spacing-md: 1rem;
|
--spacing-md: 1.5rem; /* 24px - 3 units */
|
||||||
--spacing-lg: 1.5rem;
|
--spacing-lg: 2rem; /* 32px - 4 units */
|
||||||
--spacing-xl: 2rem;
|
--spacing-xl: 3rem; /* 48px - 6 units */
|
||||||
|
|
||||||
/* Sidebar compact spacing */
|
|
||||||
--sidebar-item-gap: 0.05rem;
|
|
||||||
--sidebar-padding-v: 0.3rem;
|
|
||||||
--sidebar-padding-h: 0.75rem;
|
|
||||||
--sidebar-indent: 1rem;
|
|
||||||
|
|
||||||
/* Shadows */
|
/* Sidebar spacing - aligned to 8px grid */
|
||||||
--shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.4);
|
--sidebar-item-gap: 0.125rem; /* 2px - minimal spacing between items */
|
||||||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -2px rgba(0, 0, 0, 0.3);
|
--sidebar-padding-v: 0.25rem; /* 4px - compact vertical padding */
|
||||||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -4px rgba(0, 0, 0, 0.3);
|
--sidebar-padding-h: 1rem; /* 16px */
|
||||||
--shadow-glow: 0 0 20px rgba(66, 165, 245, 0.2);
|
--sidebar-indent: 1.5rem; /* 24px */
|
||||||
|
|
||||||
|
/* Shadows - reduced opacity for minimal look */
|
||||||
|
--shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.08);
|
||||||
|
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.12), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
|
||||||
|
--shadow-glow: 0 0 20px rgba(66, 165, 245, 0.1);
|
||||||
|
|
||||||
/* Border radius */
|
/* Border radius */
|
||||||
--radius-sm: 4px;
|
--radius-sm: 4px;
|
||||||
@ -55,8 +53,59 @@
|
|||||||
/* Transitions */
|
/* Transitions */
|
||||||
--transition-fast: 150ms ease;
|
--transition-fast: 150ms ease;
|
||||||
--transition-normal: 250ms ease;
|
--transition-normal: 250ms ease;
|
||||||
|
|
||||||
|
/* Typography scale - consistent font sizes */
|
||||||
|
--text-xs: 0.75rem; /* 12px */
|
||||||
|
--text-sm: 0.875rem; /* 14px */
|
||||||
|
--text-base: 1rem; /* 16px - default */
|
||||||
|
--text-md: 1.125rem; /* 18px */
|
||||||
|
--text-lg: 1.25rem; /* 20px */
|
||||||
|
--text-xl: 1.5rem; /* 24px */
|
||||||
|
--text-2xl: 2rem; /* 32px */
|
||||||
|
--text-3xl: 3rem; /* 48px */
|
||||||
|
|
||||||
|
/* Touch targets - minimum sizes for accessibility */
|
||||||
|
--touch-sm: 2rem; /* 32px - compact */
|
||||||
|
--touch-md: 2.75rem; /* 44px - standard mobile minimum */
|
||||||
|
--touch-lg: 3rem; /* 48px - comfortable */
|
||||||
|
|
||||||
|
/* Lucide Icons - Professional SVG icons */
|
||||||
|
--icon-xs: 14px;
|
||||||
|
--icon-sm: 16px;
|
||||||
|
--icon-md: 20px;
|
||||||
|
--icon-lg: 24px;
|
||||||
|
--icon-xl: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Lucide Icons Styling */
|
||||||
|
.lucide {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
stroke-width: 2;
|
||||||
|
stroke: currentColor;
|
||||||
|
fill: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icon size variants */
|
||||||
|
.icon-xs { width: var(--icon-xs); height: var(--icon-xs); }
|
||||||
|
.icon-sm { width: var(--icon-sm); height: var(--icon-sm); }
|
||||||
|
.icon-md { width: var(--icon-md); height: var(--icon-md); }
|
||||||
|
.icon-lg { width: var(--icon-lg); height: var(--icon-lg); }
|
||||||
|
.icon-xl { width: var(--icon-xl); height: var(--icon-xl); }
|
||||||
|
|
||||||
|
/* Icon with text alignment */
|
||||||
|
.icon-text {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icon color variants */
|
||||||
|
.icon-primary { color: var(--text-primary); }
|
||||||
|
.icon-secondary { color: var(--text-secondary); }
|
||||||
|
.icon-muted { color: var(--text-muted); }
|
||||||
|
.icon-accent { color: var(--accent-primary); }
|
||||||
|
|
||||||
/* Base styles */
|
/* Base styles */
|
||||||
html {
|
html {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
@ -90,7 +139,7 @@ header h1 {
|
|||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
background: var(--accent-primary);
|
||||||
-webkit-background-clip: text;
|
-webkit-background-clip: text;
|
||||||
-webkit-text-fill-color: transparent;
|
-webkit-text-fill-color: transparent;
|
||||||
background-clip: text;
|
background-clip: text;
|
||||||
@ -257,25 +306,7 @@ aside hr {
|
|||||||
margin: var(--spacing-sm) 0;
|
margin: var(--spacing-sm) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* File tree and search results */
|
/* File tree and search results - styles now handled by .file-item class */
|
||||||
#file-tree a,
|
|
||||||
#search-results a {
|
|
||||||
display: block;
|
|
||||||
padding: var(--spacing-sm) var(--spacing-md);
|
|
||||||
color: var(--text-primary);
|
|
||||||
text-decoration: none;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
transition: all var(--transition-fast);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
margin-bottom: var(--spacing-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
#file-tree a:hover,
|
|
||||||
#search-results a:hover {
|
|
||||||
background: var(--bg-tertiary);
|
|
||||||
color: var(--accent-primary);
|
|
||||||
transform: translateX(2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Search results header */
|
/* Search results header */
|
||||||
.search-results-header {
|
.search-results-header {
|
||||||
@ -411,7 +442,7 @@ aside hr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.search-no-results-text strong {
|
.search-no-results-text strong {
|
||||||
color: var(--accent-secondary);
|
color: var(--accent-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-no-results-hint {
|
.search-no-results-hint {
|
||||||
@ -455,7 +486,7 @@ aside hr {
|
|||||||
|
|
||||||
.search-help-example code {
|
.search-help-example code {
|
||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
color: var(--accent-secondary);
|
color: var(--accent-primary);
|
||||||
padding: 0.2rem 0.4rem;
|
padding: 0.2rem 0.4rem;
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
@ -720,7 +751,7 @@ main::-webkit-scrollbar-thumb:hover {
|
|||||||
|
|
||||||
.preview h2 {
|
.preview h2 {
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
color: var(--accent-secondary);
|
color: var(--accent-primary);
|
||||||
margin-bottom: 0.9em;
|
margin-bottom: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -771,7 +802,7 @@ main::-webkit-scrollbar-thumb:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.preview ol > li::marker {
|
.preview ol > li::marker {
|
||||||
color: var(--accent-secondary);
|
color: var(--accent-primary);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -797,7 +828,7 @@ main::-webkit-scrollbar-thumb:hover {
|
|||||||
|
|
||||||
.preview a:hover {
|
.preview a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
color: var(--accent-primary-hover);
|
color: var(--accent-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview strong, .preview b {
|
.preview strong, .preview b {
|
||||||
@ -815,7 +846,7 @@ main::-webkit-scrollbar-thumb:hover {
|
|||||||
padding: 0.2em 0.4em;
|
padding: 0.2em 0.4em;
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
color: var(--accent-secondary);
|
color: var(--accent-primary);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -853,7 +884,7 @@ main::-webkit-scrollbar-thumb:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.preview table thead {
|
.preview table thead {
|
||||||
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
background: var(--accent-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview table thead th {
|
.preview table thead th {
|
||||||
@ -895,7 +926,7 @@ main::-webkit-scrollbar-thumb:hover {
|
|||||||
button,
|
button,
|
||||||
[type="submit"],
|
[type="submit"],
|
||||||
[type="button"] {
|
[type="button"] {
|
||||||
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
background: var(--accent-primary);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
@ -984,7 +1015,7 @@ button.secondary:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#slash-commands-palette li[style*="background-color"] {
|
#slash-commands-palette li[style*="background-color"] {
|
||||||
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary)) !important;
|
background: var(--accent-primary) !important;
|
||||||
color: white !important;
|
color: white !important;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
transform: translateX(2px);
|
transform: translateX(2px);
|
||||||
@ -1016,7 +1047,7 @@ progress::-webkit-progress-bar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
progress::-webkit-progress-value {
|
progress::-webkit-progress-value {
|
||||||
background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
|
background: var(--accent-primary);
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1236,39 +1267,26 @@ body, html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.folder-item.drag-over .folder-header {
|
.folder-item.drag-over .folder-header {
|
||||||
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
background: var(--accent-primary);
|
||||||
color: white;
|
color: white;
|
||||||
box-shadow: var(--shadow-glow);
|
box-shadow: var(--shadow-glow);
|
||||||
border: 2px solid var(--accent-primary);
|
border: 2px solid var(--accent-primary);
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
animation: pulse 1s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes pulse {
|
|
||||||
0%, 100% {
|
|
||||||
transform: scale(1);
|
|
||||||
box-shadow: var(--shadow-glow);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
transform: scale(1.02);
|
|
||||||
box-shadow: 0 0 30px rgba(88, 166, 255, 0.4);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-item.drag-over {
|
.file-item.drag-over {
|
||||||
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
background: var(--accent-primary);
|
||||||
color: white;
|
color: white;
|
||||||
box-shadow: var(--shadow-glow);
|
box-shadow: var(--shadow-glow);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Style pour la racine en drag-over */
|
/* Style pour la racine en drag-over */
|
||||||
.sidebar-section-header.drag-over {
|
.sidebar-section-header.drag-over {
|
||||||
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary)) !important;
|
background: var(--accent-primary) !important;
|
||||||
color: white !important;
|
color: white !important;
|
||||||
box-shadow: var(--shadow-glow);
|
box-shadow: var(--shadow-glow);
|
||||||
border: 2px solid var(--accent-primary);
|
border: 2px solid var(--accent-primary);
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
animation: pulse 1s ease-in-out infinite;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-section-header.drag-over .folder-name,
|
.sidebar-section-header.drag-over .folder-name,
|
||||||
@ -1401,11 +1419,10 @@ body, html {
|
|||||||
|
|
||||||
/* Quand on drag au-dessus de la racine */
|
/* Quand on drag au-dessus de la racine */
|
||||||
.root-drop-zone.drag-over .root-folder-header {
|
.root-drop-zone.drag-over .root-folder-header {
|
||||||
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
background: var(--accent-primary);
|
||||||
color: white;
|
color: white;
|
||||||
border-color: var(--accent-primary);
|
border-color: var(--accent-primary);
|
||||||
box-shadow: var(--shadow-glow);
|
box-shadow: var(--shadow-glow);
|
||||||
animation: pulse 1s ease-in-out infinite;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.root-drop-zone.drag-over .root-folder-header .folder-name,
|
.root-drop-zone.drag-over .root-folder-header .folder-name,
|
||||||
@ -2053,7 +2070,7 @@ body, html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.search-modal-result-item.selected {
|
.search-modal-result-item.selected {
|
||||||
background: linear-gradient(135deg, rgba(130, 170, 255, 0.15), rgba(199, 146, 234, 0.15));
|
background: var(--bg-secondary);
|
||||||
border-color: var(--accent-primary);
|
border-color: var(--accent-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2074,7 +2091,7 @@ body, html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.search-modal-result-title mark {
|
.search-modal-result-title mark {
|
||||||
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
background: var(--accent-primary);
|
||||||
color: white;
|
color: white;
|
||||||
padding: 2px 4px;
|
padding: 2px 4px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
@ -2200,11 +2217,6 @@ body, html {
|
|||||||
border: 3px solid var(--border-primary);
|
border: 3px solid var(--border-primary);
|
||||||
border-top-color: var(--accent-primary);
|
border-top-color: var(--accent-primary);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
to { transform: rotate(360deg); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-modal-loading p {
|
.search-modal-loading p {
|
||||||
@ -2566,12 +2578,12 @@ body, html {
|
|||||||
|
|
||||||
/* Today */
|
/* Today */
|
||||||
.calendar-day-today {
|
.calendar-day-today {
|
||||||
border-color: var(--accent-secondary);
|
border-color: var(--accent-primary);
|
||||||
background: linear-gradient(135deg, rgba(130, 170, 255, 0.1), rgba(199, 146, 234, 0.1));
|
background: var(--bg-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar-day-today .calendar-day-number {
|
.calendar-day-today .calendar-day-number {
|
||||||
color: var(--accent-secondary);
|
color: var(--accent-primary);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2596,7 +2608,7 @@ body, html {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: var(--spacing-sm);
|
margin-top: var(--spacing-sm);
|
||||||
padding: var(--spacing-sm);
|
padding: var(--spacing-sm);
|
||||||
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
background: var(--accent-primary);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
@ -2681,11 +2693,12 @@ body, html {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: var(--sidebar-item-gap) 0;
|
margin: 0; /* No margin on wrapper - use same spacing as folders */
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-item-wrapper .file-item {
|
.file-item-wrapper .file-item {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
margin: 0 !important; /* Force remove margin to avoid double spacing */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bouton de mode sélection */
|
/* Bouton de mode sélection */
|
||||||
@ -2715,8 +2728,8 @@ body, html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.icon-button.active:hover {
|
.icon-button.active:hover {
|
||||||
background: var(--accent-primary-hover);
|
background: var(--accent-hover);
|
||||||
border-color: var(--accent-primary-hover);
|
border-color: var(--accent-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Toolbar de sélection flottante */
|
/* Toolbar de sélection flottante */
|
||||||
@ -2784,7 +2797,7 @@ body, html {
|
|||||||
background: #ff5370;
|
background: #ff5370;
|
||||||
border-color: #ff5370;
|
border-color: #ff5370;
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
box-shadow: 0 4px 12px rgba(240, 113, 120, 0.4);
|
box-shadow: 0 4px 12px rgba(240, 113, 120, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.danger-button:active {
|
.danger-button:active {
|
||||||
@ -2974,30 +2987,38 @@ body, html {
|
|||||||
|
|
||||||
/* Bouton d'ajout aux favoris (dans le file tree) */
|
/* Bouton d'ajout aux favoris (dans le file tree) */
|
||||||
.add-to-favorites {
|
.add-to-favorites {
|
||||||
opacity: 0;
|
opacity: 0.4; /* Always slightly visible */
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.9rem;
|
font-size: 1rem;
|
||||||
padding: 0 0.2rem;
|
padding: 0.25rem;
|
||||||
transition: all var(--transition-fast);
|
transition: all var(--transition-fast);
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.folder-header:hover .add-to-favorites,
|
.folder-header:hover .add-to-favorites,
|
||||||
.file-item:hover .add-to-favorites {
|
.file-item:hover .add-to-favorites {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
background: var(--bg-tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-to-favorites:hover {
|
.add-to-favorites:hover {
|
||||||
color: var(--warning);
|
color: var(--warning);
|
||||||
transform: scale(1.2);
|
transform: scale(1.15);
|
||||||
|
background: rgba(255, 193, 7, 0.1); /* Subtle yellow background */
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-to-favorites.is-favorite {
|
.add-to-favorites.is-favorite {
|
||||||
opacity: 1;
|
opacity: 1 !important; /* Always fully visible when favorited */
|
||||||
color: var(--warning);
|
color: var(--warning);
|
||||||
|
background: rgba(255, 193, 7, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-to-favorites.is-favorite:hover {
|
||||||
|
background: rgba(255, 193, 7, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive - Hauteurs adaptatives pour #favorites-list */
|
/* Responsive - Hauteurs adaptatives pour #favorites-list */
|
||||||
@ -3162,7 +3183,7 @@ body, html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.link-inserter-result-item.selected {
|
.link-inserter-result-item.selected {
|
||||||
background: linear-gradient(135deg, rgba(130, 170, 255, 0.15), rgba(199, 146, 234, 0.15));
|
background: var(--bg-secondary);
|
||||||
border-color: var(--accent-primary);
|
border-color: var(--accent-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3184,7 +3205,7 @@ body, html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.link-inserter-result-title mark {
|
.link-inserter-result-title mark {
|
||||||
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
background: var(--accent-primary);
|
||||||
color: white;
|
color: white;
|
||||||
padding: 2px 4px;
|
padding: 2px 4px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
@ -3241,7 +3262,6 @@ body, html {
|
|||||||
border: 3px solid var(--bg-tertiary);
|
border: 3px solid var(--bg-tertiary);
|
||||||
border-top-color: var(--accent-primary);
|
border-top-color: var(--accent-primary);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
animation: spin 0.8s linear infinite;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-inserter-loading p {
|
.link-inserter-loading p {
|
||||||
@ -3575,7 +3595,7 @@ body, html {
|
|||||||
|
|
||||||
.breadcrumb-link:hover {
|
.breadcrumb-link:hover {
|
||||||
background: var(--bg-tertiary);
|
background: var(--bg-tertiary);
|
||||||
color: var(--accent-secondary);
|
color: var(--accent-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumb-separator {
|
.breadcrumb-separator {
|
||||||
@ -3624,3 +3644,20 @@ body, html {
|
|||||||
color: var(--accent-primary);
|
color: var(--accent-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Public toggle button */
|
||||||
|
#toggle-public-btn {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toggle-public-btn.public-active {
|
||||||
|
background: var(--accent-green, #2ecc71);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--accent-green, #2ecc71);
|
||||||
|
}
|
||||||
|
|
||||||
|
#toggle-public-btn.public-active:hover {
|
||||||
|
background: var(--accent-green-dark, #27ae60);
|
||||||
|
border-color: var(--accent-green-dark, #27ae60);
|
||||||
|
}
|
||||||
|
|||||||
@ -21,9 +21,7 @@
|
|||||||
--text-muted: #6e6e6e;
|
--text-muted: #6e6e6e;
|
||||||
|
|
||||||
--accent-primary: #42a5f5;
|
--accent-primary: #42a5f5;
|
||||||
--accent-primary-hover: #5ab3f7;
|
--accent-hover: #5ab3f7;
|
||||||
--accent-secondary: #29b6f6;
|
|
||||||
--accent-secondary-hover: #4fc3f7;
|
|
||||||
|
|
||||||
--success: #66bb6a;
|
--success: #66bb6a;
|
||||||
--warning: #ffa726;
|
--warning: #ffa726;
|
||||||
@ -47,9 +45,7 @@
|
|||||||
--text-muted: #75715e;
|
--text-muted: #75715e;
|
||||||
|
|
||||||
--accent-primary: #66d9ef;
|
--accent-primary: #66d9ef;
|
||||||
--accent-primary-hover: #7ee5f7;
|
--accent-hover: #7ee5f7;
|
||||||
--accent-secondary: #88c070;
|
|
||||||
--accent-secondary-hover: #9acc84;
|
|
||||||
|
|
||||||
--success: #88c070;
|
--success: #88c070;
|
||||||
--warning: #e6db74;
|
--warning: #e6db74;
|
||||||
@ -73,9 +69,7 @@
|
|||||||
--text-muted: #6272a4;
|
--text-muted: #6272a4;
|
||||||
|
|
||||||
--accent-primary: #8be9fd;
|
--accent-primary: #8be9fd;
|
||||||
--accent-primary-hover: #9ff3ff;
|
--accent-hover: #9ff3ff;
|
||||||
--accent-secondary: #bd93f9;
|
|
||||||
--accent-secondary-hover: #cba6ff;
|
|
||||||
|
|
||||||
--success: #50fa7b;
|
--success: #50fa7b;
|
||||||
--warning: #f1fa8c;
|
--warning: #f1fa8c;
|
||||||
@ -99,9 +93,7 @@
|
|||||||
--text-muted: #5c6370;
|
--text-muted: #5c6370;
|
||||||
|
|
||||||
--accent-primary: #61afef;
|
--accent-primary: #61afef;
|
||||||
--accent-primary-hover: #75bdf5;
|
--accent-hover: #75bdf5;
|
||||||
--accent-secondary: #c678dd;
|
|
||||||
--accent-secondary-hover: #d48ae9;
|
|
||||||
|
|
||||||
--success: #98c379;
|
--success: #98c379;
|
||||||
--warning: #e5c07b;
|
--warning: #e5c07b;
|
||||||
@ -125,9 +117,7 @@
|
|||||||
--text-muted: #586e75;
|
--text-muted: #586e75;
|
||||||
|
|
||||||
--accent-primary: #268bd2;
|
--accent-primary: #268bd2;
|
||||||
--accent-primary-hover: #4098d9;
|
--accent-hover: #4098d9;
|
||||||
--accent-secondary: #2aa198;
|
|
||||||
--accent-secondary-hover: #3eb3a8;
|
|
||||||
|
|
||||||
--success: #859900;
|
--success: #859900;
|
||||||
--warning: #b58900;
|
--warning: #b58900;
|
||||||
@ -151,9 +141,7 @@
|
|||||||
--text-muted: #616e88;
|
--text-muted: #616e88;
|
||||||
|
|
||||||
--accent-primary: #88c0d0;
|
--accent-primary: #88c0d0;
|
||||||
--accent-primary-hover: #9dcadb;
|
--accent-hover: #9dcadb;
|
||||||
--accent-secondary: #81a1c1;
|
|
||||||
--accent-secondary-hover: #94b0cc;
|
|
||||||
|
|
||||||
--success: #a3be8c;
|
--success: #a3be8c;
|
||||||
--warning: #ebcb8b;
|
--warning: #ebcb8b;
|
||||||
@ -177,9 +165,7 @@
|
|||||||
--text-muted: #6c7086;
|
--text-muted: #6c7086;
|
||||||
|
|
||||||
--accent-primary: #89b4fa;
|
--accent-primary: #89b4fa;
|
||||||
--accent-primary-hover: #a6c8ff;
|
--accent-hover: #a6c8ff;
|
||||||
--accent-secondary: #f5c2e7;
|
|
||||||
--accent-secondary-hover: #f9d5ee;
|
|
||||||
|
|
||||||
--success: #a6e3a1;
|
--success: #a6e3a1;
|
||||||
--warning: #f9e2af;
|
--warning: #f9e2af;
|
||||||
@ -203,9 +189,7 @@
|
|||||||
--text-muted: #7a8478;
|
--text-muted: #7a8478;
|
||||||
|
|
||||||
--accent-primary: #7fbbb3;
|
--accent-primary: #7fbbb3;
|
||||||
--accent-primary-hover: #93c9c1;
|
--accent-hover: #93c9c1;
|
||||||
--accent-secondary: #a7c080;
|
|
||||||
--accent-secondary-hover: #b8cc94;
|
|
||||||
|
|
||||||
--success: #a7c080;
|
--success: #a7c080;
|
||||||
--warning: #dbbc7f;
|
--warning: #dbbc7f;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<div id="about-content" style="padding: 3rem; max-width: 900px; margin: 0 auto;">
|
<div id="about-content" style="padding: 3rem; max-width: 900px; margin: 0 auto;">
|
||||||
<div style="text-align: center; margin-bottom: 3rem;">
|
<div style="text-align: center; margin-bottom: 3rem;">
|
||||||
<h1 style="font-size: 2.5rem; color: #c792ea; margin-bottom: 1rem;">
|
<h1 style="font-size: 2.5rem; color: #c792ea; margin-bottom: 1rem;">
|
||||||
📝 About PersoNotes
|
<i data-lucide="file-text" class="icon-md"></i> About PersoNotes
|
||||||
</h1>
|
</h1>
|
||||||
<p style="font-size: 1.2rem; color: var(--text-secondary); margin-bottom: 2rem;">
|
<p style="font-size: 1.2rem; color: var(--text-secondary); margin-bottom: 2rem;">
|
||||||
Un gestionnaire de notes Markdown moderne et puissant
|
Un gestionnaire de notes Markdown moderne et puissant
|
||||||
@ -10,23 +10,23 @@
|
|||||||
|
|
||||||
<div style="margin-top: 3rem;">
|
<div style="margin-top: 3rem;">
|
||||||
<h2 style="color: #82aaff; font-size: 1.5rem; margin-bottom: 1.5rem;">
|
<h2 style="color: #82aaff; font-size: 1.5rem; margin-bottom: 1.5rem;">
|
||||||
🚀 Démarrage rapide
|
<i data-lucide="rocket" class="icon-sm"></i> Démarrage rapide
|
||||||
</h2>
|
</h2>
|
||||||
<div style="display: grid; gap: 1.5rem; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));">
|
<div style="display: grid; gap: 1.5rem; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));">
|
||||||
<div style="background: var(--bg-secondary); border: 1px solid var(--border-primary); border-radius: var(--radius-md); padding: 1.5rem;">
|
<div style="background: var(--bg-secondary); border: 1px solid var(--border-primary); border-radius: var(--radius-md); padding: 1.5rem;">
|
||||||
<h3 style="color: #89ddff; margin-bottom: 0.5rem;">📁 Parcourir</h3>
|
<h3 style="color: #89ddff; margin-bottom: 0.5rem;"><i data-lucide="folder-open" class="icon-sm"></i> Parcourir</h3>
|
||||||
<p style="color: var(--text-secondary); font-size: 0.9rem; margin-bottom: 1rem;">
|
<p style="color: var(--text-secondary); font-size: 0.9rem; margin-bottom: 1rem;">
|
||||||
Explorez vos notes dans l'arborescence à gauche
|
Explorez vos notes dans l'arborescence à gauche
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div style="background: var(--bg-secondary); border: 1px solid var(--border-primary); border-radius: var(--radius-md); padding: 1.5rem;">
|
<div style="background: var(--bg-secondary); border: 1px solid var(--border-primary); border-radius: var(--radius-md); padding: 1.5rem;">
|
||||||
<h3 style="color: #c3e88d; margin-bottom: 0.5rem;">🔍 Rechercher</h3>
|
<h3 style="color: #c3e88d; margin-bottom: 0.5rem;"><i data-lucide="search" class="icon-sm"></i> Rechercher</h3>
|
||||||
<p style="color: var(--text-secondary); font-size: 0.9rem; margin-bottom: 1rem;">
|
<p style="color: var(--text-secondary); font-size: 0.9rem; margin-bottom: 1rem;">
|
||||||
Utilisez la barre de recherche en haut pour trouver vos notes
|
Utilisez la barre de recherche en haut pour trouver vos notes
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div style="background: var(--bg-secondary); border: 1px solid var(--border-primary); border-radius: var(--radius-md); padding: 1.5rem;">
|
<div style="background: var(--bg-secondary); border: 1px solid var(--border-primary); border-radius: var(--radius-md); padding: 1.5rem;">
|
||||||
<h3 style="color: #ffcb6b; margin-bottom: 0.5rem;">⚡ Slash commands</h3>
|
<h3 style="color: #ffcb6b; margin-bottom: 0.5rem;"><i data-lucide="zap" class="icon-sm"></i> Slash commands</h3>
|
||||||
<p style="color: var(--text-secondary); font-size: 0.9rem; margin-bottom: 1rem;">
|
<p style="color: var(--text-secondary); font-size: 0.9rem; margin-bottom: 1rem;">
|
||||||
Tapez <code style="color: #f07178;">/</code> dans l'éditeur pour insérer du Markdown
|
Tapez <code style="color: #f07178;">/</code> dans l'éditeur pour insérer du Markdown
|
||||||
</p>
|
</p>
|
||||||
@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
<div style="margin-top: 3rem;">
|
<div style="margin-top: 3rem;">
|
||||||
<h2 style="color: #82aaff; font-size: 1.5rem; margin-bottom: 1.5rem;">
|
<h2 style="color: #82aaff; font-size: 1.5rem; margin-bottom: 1.5rem;">
|
||||||
✨ Fonctionnalités
|
<i data-lucide="sparkles" class="icon-sm"></i> Fonctionnalités
|
||||||
</h2>
|
</h2>
|
||||||
<ul style="color: var(--text-secondary); line-height: 2; list-style: none; padding: 0;">
|
<ul style="color: var(--text-secondary); line-height: 2; list-style: none; padding: 0;">
|
||||||
<li style="padding: 0.5rem 0; border-bottom: 1px solid var(--border-secondary);">
|
<li style="padding: 0.5rem 0; border-bottom: 1px solid var(--border-secondary);">
|
||||||
@ -62,13 +62,13 @@
|
|||||||
|
|
||||||
<div style="margin-top: 3rem; text-align: center; padding: 2rem; background: var(--bg-secondary); border-radius: var(--radius-md); border: 1px solid var(--border-primary);">
|
<div style="margin-top: 3rem; text-align: center; padding: 2rem; background: var(--bg-secondary); border-radius: var(--radius-md); border: 1px solid var(--border-primary);">
|
||||||
<p style="color: var(--text-muted); font-size: 0.9rem;">
|
<p style="color: var(--text-muted); font-size: 0.9rem;">
|
||||||
💡 Astuce : Cliquez sur une note dans l'arborescence pour commencer à éditer
|
<i data-lucide="lightbulb" class="icon-sm"></i> Astuce : Cliquez sur une note dans l'arborescence pour commencer à éditer
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-top: 3rem;">
|
<div style="margin-top: 3rem;">
|
||||||
<h2 style="color: #82aaff; font-size: 1.5rem; margin-bottom: 1.5rem;">
|
<h2 style="color: #82aaff; font-size: 1.5rem; margin-bottom: 1.5rem;">
|
||||||
⌨️ Raccourcis clavier
|
<i data-lucide="keyboard" class="icon-sm"></i> Raccourcis clavier
|
||||||
</h2>
|
</h2>
|
||||||
<div style="display: grid; gap: 0.75rem; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));">
|
<div style="display: grid; gap: 0.75rem; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));">
|
||||||
<div style="background: var(--bg-secondary); border: 1px solid var(--border-primary); border-radius: var(--radius-sm); padding: 0.75rem; display: flex; justify-content: space-between; align-items: center;">
|
<div style="background: var(--bg-secondary); border: 1px solid var(--border-primary); border-radius: var(--radius-sm); padding: 0.75rem; display: flex; justify-content: space-between; align-items: center;">
|
||||||
@ -113,7 +113,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p style="color: var(--text-muted); font-size: 0.85rem; text-align: center; margin-top: 1.5rem;">
|
<p style="color: var(--text-muted); font-size: 0.85rem; text-align: center; margin-top: 1.5rem;">
|
||||||
💡 Sur Mac, utilisez Cmd au lieu de Ctrl
|
<i data-lucide="lightbulb" class="icon-sm"></i> Sur Mac, utilisez Cmd au lieu de Ctrl
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -57,7 +57,7 @@
|
|||||||
data-i18n="calendar.today"
|
data-i18n="calendar.today"
|
||||||
title="Ouvrir la note du jour (Ctrl/Cmd+D)"
|
title="Ouvrir la note du jour (Ctrl/Cmd+D)"
|
||||||
style="flex: 1; padding: 0.5rem; font-size: 0.85rem;">
|
style="flex: 1; padding: 0.5rem; font-size: 0.85rem;">
|
||||||
📅 Aujourd'hui
|
<i data-lucide="calendar-check" class="icon-sm"></i> Aujourd'hui
|
||||||
</button>
|
</button>
|
||||||
<button class="daily-today-btn"
|
<button class="daily-today-btn"
|
||||||
hx-get="/api/daily/calendar/{{.CurrentMonth}}"
|
hx-get="/api/daily/calendar/{{.CurrentMonth}}"
|
||||||
@ -66,7 +66,7 @@
|
|||||||
data-i18n="calendar.thisMonth"
|
data-i18n="calendar.thisMonth"
|
||||||
title="Revenir au mois actuel"
|
title="Revenir au mois actuel"
|
||||||
style="flex: 1; padding: 0.5rem; font-size: 0.85rem;">
|
style="flex: 1; padding: 0.5rem; font-size: 0.85rem;">
|
||||||
🗓️ Ce mois
|
<i data-lucide="calendar-days" class="icon-sm"></i> Ce mois
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
</label>
|
</label>
|
||||||
{{if .IsHome}}
|
{{if .IsHome}}
|
||||||
<button type="button" class="toggle-preview-btn" hx-get="/api/home" hx-target="#editor-container" hx-swap="innerHTML" hx-push-url="true" data-i18n="editor.refresh" title="Actualiser la page d'accueil">
|
<button type="button" class="toggle-preview-btn" hx-get="/api/home" hx-target="#editor-container" hx-swap="innerHTML" hx-push-url="true" data-i18n="editor.refresh" title="Actualiser la page d'accueil">
|
||||||
🔄 Actualiser
|
<i data-lucide="refresh-cw" class="icon-sm"></i> Actualiser
|
||||||
</button>
|
</button>
|
||||||
{{else}}
|
{{else}}
|
||||||
<button type="button" id="toggle-preview-btn" class="toggle-preview-btn" onclick="togglePreview()" data-i18n-title="editor.togglePreview" title="Mode: Éditeur + Preview (cliquer pour Éditeur seul)">
|
<button type="button" id="toggle-preview-btn" class="toggle-preview-btn" onclick="togglePreview()" data-i18n-title="editor.togglePreview" title="Mode: Éditeur + Preview (cliquer pour Éditeur seul)">
|
||||||
@ -36,12 +36,12 @@
|
|||||||
</div>
|
</div>
|
||||||
{{if .Backlinks}}
|
{{if .Backlinks}}
|
||||||
<div id="backlinks-section" class="backlinks-section">
|
<div id="backlinks-section" class="backlinks-section">
|
||||||
<h3 class="backlinks-title">🔗 Référencé par</h3>
|
<h3 class="backlinks-title"><i data-lucide="link" class="icon-sm"></i> Référencé par</h3>
|
||||||
<ul class="backlinks-list">
|
<ul class="backlinks-list">
|
||||||
{{range .Backlinks}}
|
{{range .Backlinks}}
|
||||||
<li class="backlink-item">
|
<li class="backlink-item">
|
||||||
<a href="#" onclick="return false;" hx-get="/api/notes/{{.Path}}" hx-target="#editor-container" hx-swap="innerHTML" hx-push-url="true" class="backlink-link">
|
<a href="#" onclick="return false;" hx-get="/api/notes/{{.Path}}" hx-target="#editor-container" hx-swap="innerHTML" hx-push-url="true" class="backlink-link">
|
||||||
📄 {{.Title}}
|
<i data-lucide="file-text" class="icon-sm"></i> {{.Title}}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{{end}}
|
{{end}}
|
||||||
@ -54,6 +54,17 @@
|
|||||||
<div class="editor-actions">
|
<div class="editor-actions">
|
||||||
<div class="editor-actions-primary">
|
<div class="editor-actions-primary">
|
||||||
<button type="submit" data-i18n="editor.save">Enregistrer</button>
|
<button type="submit" data-i18n="editor.save">Enregistrer</button>
|
||||||
|
<button
|
||||||
|
id="toggle-public-btn"
|
||||||
|
type="button"
|
||||||
|
class="secondary {{if .IsPublic}}public-active{{end}}"
|
||||||
|
data-path="{{.Filename}}"
|
||||||
|
data-is-public="{{.IsPublic}}"
|
||||||
|
data-i18n-title="{{if .IsPublic}}public.titlePublic{{else}}public.titlePrivate{{end}}"
|
||||||
|
title="{{if .IsPublic}}This note is public - Click to make it private{{else}}This note is private - Click to make it public{{end}}"
|
||||||
|
>
|
||||||
|
{{if .IsPublic}}<span data-i18n="public.buttonPublic"><i data-lucide="globe" class="icon-sm"></i> Public</span>{{else}}<span data-i18n="public.buttonPrivate"><i data-lucide="lock" class="icon-sm"></i> Private</span>{{end}}
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
hx-delete="/api/notes/{{.Filename}}"
|
hx-delete="/api/notes/{{.Filename}}"
|
||||||
hx-confirm="Êtes-vous sûr de vouloir supprimer cette note ({{.Filename}}) ?"
|
hx-confirm="Êtes-vous sûr de vouloir supprimer cette note ({{.Filename}}) ?"
|
||||||
|
|||||||
@ -3,12 +3,12 @@
|
|||||||
<div class="favorite-item" data-path="{{.Path}}">
|
<div class="favorite-item" data-path="{{.Path}}">
|
||||||
{{if .IsDir}}
|
{{if .IsDir}}
|
||||||
<div class="favorite-folder" data-path="{{.Path}}">
|
<div class="favorite-folder" data-path="{{.Path}}">
|
||||||
<span class="favorite-icon">⭐</span>
|
<span class="favorite-icon"><i data-lucide="star" class="icon-sm"></i></span>
|
||||||
<span class="favorite-folder-icon">{{.Icon}}</span>
|
<span class="favorite-folder-icon">{{.Icon}}</span>
|
||||||
<span class="favorite-name">{{.Title}}</span>
|
<span class="favorite-name">{{.Title}}</span>
|
||||||
<button class="favorite-remove"
|
<button class="favorite-remove"
|
||||||
onclick="removeFavorite('{{.Path}}')"
|
onclick="removeFavorite('{{.Path}}')"
|
||||||
title="Retirer des favoris">×</button>
|
title="Retirer des favoris"><i data-lucide="x" class="icon-xs"></i></button>
|
||||||
</div>
|
</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
<a href="#"
|
<a href="#"
|
||||||
@ -18,12 +18,12 @@
|
|||||||
hx-target="#editor-container"
|
hx-target="#editor-container"
|
||||||
hx-swap="innerHTML"
|
hx-swap="innerHTML"
|
||||||
hx-push-url="true">
|
hx-push-url="true">
|
||||||
<span class="favorite-icon">⭐</span>
|
<span class="favorite-icon"><i data-lucide="star" class="icon-sm"></i></span>
|
||||||
<span class="favorite-file-icon">{{.Icon}}</span>
|
<span class="favorite-file-icon">{{.Icon}}</span>
|
||||||
<span class="favorite-name">{{.Title}}</span>
|
<span class="favorite-name">{{.Title}}</span>
|
||||||
<button class="favorite-remove"
|
<button class="favorite-remove"
|
||||||
onclick="event.preventDefault(); event.stopPropagation(); removeFavorite('{{.Path}}')"
|
onclick="event.preventDefault(); event.stopPropagation(); removeFavorite('{{.Path}}')"
|
||||||
title="Retirer des favoris">×</button>
|
title="Retirer des favoris"><i data-lucide="x" class="icon-xs"></i></button>
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
@ -32,7 +32,7 @@
|
|||||||
<p class="favorites-empty">
|
<p class="favorites-empty">
|
||||||
Aucun favori.<br>
|
Aucun favori.<br>
|
||||||
<span style="font-size: 0.75rem; color: var(--text-muted);">
|
<span style="font-size: 0.75rem; color: var(--text-muted);">
|
||||||
Cliquez sur ⭐ à côté d'une note ou d'un dossier pour l'ajouter.
|
Cliquez sur <i data-lucide="star" class="icon-sm" style="display: inline; width: 14px; height: 14px; vertical-align: middle;"></i> à côté d'une note ou d'un dossier pour l'ajouter.
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<!-- Indicateur de racine (maintenant cliquable et rétractable) -->
|
<!-- Indicateur de racine (maintenant cliquable et rétractable) -->
|
||||||
<div class="sidebar-section-header" data-section="notes" data-path="" data-is-dir="true" onclick="toggleSidebarSection('notes', event)" style="cursor: pointer;">
|
<div class="sidebar-section-header" data-section="notes" data-path="" data-is-dir="true" onclick="toggleSidebarSection('notes', event)" style="cursor: pointer;">
|
||||||
<span class="section-toggle expanded">▶</span>
|
<span class="section-toggle expanded">▶</span>
|
||||||
<span class="folder-icon">🏠</span>
|
<span class="folder-icon"><i data-lucide="home" class="icon-sm"></i></span>
|
||||||
<span class="folder-name">Racine</span>
|
<span class="folder-name">Racine</span>
|
||||||
<span class="root-hint">(notes/)</span>
|
<span class="root-hint">(notes/)</span>
|
||||||
</div>
|
</div>
|
||||||
@ -28,7 +28,7 @@
|
|||||||
<div class="folder-header">
|
<div class="folder-header">
|
||||||
<input type="checkbox" class="selection-checkbox folder-checkbox" data-path="{{.Path}}" data-is-dir="true" style="display: none;">
|
<input type="checkbox" class="selection-checkbox folder-checkbox" data-path="{{.Path}}" data-is-dir="true" style="display: none;">
|
||||||
<span class="folder-toggle">▶</span>
|
<span class="folder-toggle">▶</span>
|
||||||
<span class="folder-icon">📁</span>
|
<span class="folder-icon"><i data-lucide="folder" class="icon-sm"></i></span>
|
||||||
<span class="folder-name">{{.Name}}</span>
|
<span class="folder-name">{{.Name}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="folder-children" style="display: none;">
|
<div class="folder-children" style="display: none;">
|
||||||
@ -49,7 +49,7 @@
|
|||||||
hx-swap="innerHTML"
|
hx-swap="innerHTML"
|
||||||
hx-push-url="true"
|
hx-push-url="true"
|
||||||
draggable="true">
|
draggable="true">
|
||||||
📄 {{.Name}}
|
<i data-lucide="file-text" class="icon-sm"></i> {{.Name}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@ -16,13 +16,15 @@
|
|||||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/dompurify/dist/purify.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/dompurify/dist/purify.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script>
|
||||||
|
<!-- Lucide Icons - Professional SVG icons -->
|
||||||
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
<script src="/static/sidebar-resize.js"></script>
|
<script src="/static/sidebar-resize.js"></script>
|
||||||
<script type="module" src="/static/dist/personotes-frontend.es.js"></script>
|
<script type="module" src="/static/dist/personotes-frontend.es.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<button id="toggle-sidebar-btn" title="Afficher/Masquer la barre latérale (Ctrl/Cmd+B)" style="background: none; border: none; padding: 0; margin-right: 1rem; cursor: pointer; color: var(--text-primary); display: flex; align-items: center;">
|
<button id="toggle-sidebar-btn" title="Afficher/Masquer la barre latérale (Ctrl/Cmd+B)" style="background: none; border: none; padding: 0; margin-right: 1rem; cursor: pointer; color: var(--text-primary); display: flex; align-items: center;">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg>
|
<i data-lucide="menu" style="width: 24px; height: 24px;"></i>
|
||||||
</button>
|
</button>
|
||||||
<div style="display: flex; align-items: center; gap: 0.75rem;">
|
<div style="display: flex; align-items: center; gap: 0.75rem;">
|
||||||
<img src="/static/images/logo.svg" alt="Logo" style="width: 40px; height: 40px;">
|
<img src="/static/images/logo.svg" alt="Logo" style="width: 40px; height: 40px;">
|
||||||
@ -46,10 +48,10 @@
|
|||||||
style="white-space: nowrap;"
|
style="white-space: nowrap;"
|
||||||
data-i18n="menu.home"
|
data-i18n="menu.home"
|
||||||
title="Retour à la page d'accueil (Ctrl/Cmd+H)">
|
title="Retour à la page d'accueil (Ctrl/Cmd+H)">
|
||||||
🏠 Accueil
|
<i data-lucide="home" class="icon-sm"></i> Accueil
|
||||||
</button>
|
</button>
|
||||||
<button onclick="showNewNoteModal()" style="white-space: nowrap;" data-i18n="menu.newNote" title="Créer une nouvelle note (Ctrl/Cmd+N)">
|
<button onclick="showNewNoteModal()" style="white-space: nowrap;" data-i18n="menu.newNote" title="Créer une nouvelle note (Ctrl/Cmd+N)">
|
||||||
✨ Nouvelle note
|
<i data-lucide="file-plus" class="icon-sm"></i> Nouvelle note
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@ -57,7 +59,7 @@
|
|||||||
<div id="new-note-modal" style="display: none;">
|
<div id="new-note-modal" style="display: none;">
|
||||||
<div class="modal-overlay" onclick="hideNewNoteModal()"></div>
|
<div class="modal-overlay" onclick="hideNewNoteModal()"></div>
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<h2>📝 Nouvelle note</h2>
|
<h2><i data-lucide="file-text" class="icon-md"></i> Nouvelle note</h2>
|
||||||
<form onsubmit="handleNewNote(event)">
|
<form onsubmit="handleNewNote(event)">
|
||||||
<label for="note-name">Nom de la note</label>
|
<label for="note-name">Nom de la note</label>
|
||||||
<input
|
<input
|
||||||
@ -69,7 +71,7 @@
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<p style="color: var(--text-muted); font-size: 0.85rem; margin-top: 0.5rem;">
|
<p style="color: var(--text-muted); font-size: 0.85rem; margin-top: 0.5rem;">
|
||||||
💡 Si la note existe déjà, elle sera ouverte.
|
<i data-lucide="lightbulb" class="icon-sm"></i> Si la note existe déjà, elle sera ouverte.
|
||||||
</p>
|
</p>
|
||||||
<div style="display: flex; gap: 0.5rem; margin-top: 1.5rem;">
|
<div style="display: flex; gap: 0.5rem; margin-top: 1.5rem;">
|
||||||
<button type="submit">Créer / Ouvrir</button>
|
<button type="submit">Créer / Ouvrir</button>
|
||||||
@ -83,7 +85,7 @@
|
|||||||
<div id="new-folder-modal" style="display: none;">
|
<div id="new-folder-modal" style="display: none;">
|
||||||
<div class="modal-overlay" onclick="hideNewFolderModal()"></div>
|
<div class="modal-overlay" onclick="hideNewFolderModal()"></div>
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<h2>📁 Nouveau dossier</h2>
|
<h2><i data-lucide="folder-plus" class="icon-md"></i> Nouveau dossier</h2>
|
||||||
<form onsubmit="handleNewFolder(event)">
|
<form onsubmit="handleNewFolder(event)">
|
||||||
<label for="folder-name">Nom du dossier</label>
|
<label for="folder-name">Nom du dossier</label>
|
||||||
<input
|
<input
|
||||||
@ -95,7 +97,7 @@
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<p style="color: var(--text-muted); font-size: 0.85rem; margin-top: 0.5rem;">
|
<p style="color: var(--text-muted); font-size: 0.85rem; margin-top: 0.5rem;">
|
||||||
💡 Vous pouvez créer des sous-dossiers avec "/", ex: projets/backend
|
<i data-lucide="lightbulb" class="icon-sm"></i> Vous pouvez créer des sous-dossiers avec "/", ex: projets/backend
|
||||||
</p>
|
</p>
|
||||||
<div style="display: flex; gap: 0.5rem; margin-top: 1.5rem;">
|
<div style="display: flex; gap: 0.5rem; margin-top: 1.5rem;">
|
||||||
<button type="submit">Créer</button>
|
<button type="submit">Créer</button>
|
||||||
@ -114,10 +116,7 @@
|
|||||||
<span id="selection-count" class="selection-count">0 élément(s) sélectionné(s)</span>
|
<span id="selection-count" class="selection-count">0 élément(s) sélectionné(s)</span>
|
||||||
<div class="toolbar-actions">
|
<div class="toolbar-actions">
|
||||||
<button onclick="deleteSelected()" class="danger-button">
|
<button onclick="deleteSelected()" class="danger-button">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<i data-lucide="trash-2" class="icon-sm"></i>
|
||||||
<polyline points="3 6 5 6 21 6"></polyline>
|
|
||||||
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
|
||||||
</svg>
|
|
||||||
Supprimer
|
Supprimer
|
||||||
</button>
|
</button>
|
||||||
<button onclick="cancelSelection()" class="secondary">
|
<button onclick="cancelSelection()" class="secondary">
|
||||||
@ -131,13 +130,13 @@
|
|||||||
<div id="delete-confirmation-modal" style="display: none;">
|
<div id="delete-confirmation-modal" style="display: none;">
|
||||||
<div class="modal-overlay" onclick="hideDeleteConfirmationModal()"></div>
|
<div class="modal-overlay" onclick="hideDeleteConfirmationModal()"></div>
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<h2 style="color: var(--error);">⚠️ Confirmer la suppression</h2>
|
<h2 style="color: var(--error);"><i data-lucide="alert-triangle" class="icon-md"></i> Confirmer la suppression</h2>
|
||||||
<p>Vous êtes sur le point de supprimer <strong id="delete-count">0</strong> élément(s) :</p>
|
<p>Vous êtes sur le point de supprimer <strong id="delete-count">0</strong> élément(s) :</p>
|
||||||
<div id="delete-items-list" style="max-height: 300px; overflow-y: auto; margin: 1rem 0; padding: 0.5rem; background: var(--bg-tertiary); border-radius: var(--radius-md);">
|
<div id="delete-items-list" style="max-height: 300px; overflow-y: auto; margin: 1rem 0; padding: 0.5rem; background: var(--bg-tertiary); border-radius: var(--radius-md);">
|
||||||
<!-- Liste des éléments à supprimer -->
|
<!-- Liste des éléments à supprimer -->
|
||||||
</div>
|
</div>
|
||||||
<p style="color: var(--warning); font-size: 0.9rem;">
|
<p style="color: var(--warning); font-size: 0.9rem;">
|
||||||
⚠️ Cette action est <strong>irréversible</strong>. Les dossiers seront supprimés avec tout leur contenu.
|
<i data-lucide="alert-triangle" class="icon-sm"></i> Cette action est <strong>irréversible</strong>. Les dossiers seront supprimés avec tout leur contenu.
|
||||||
</p>
|
</p>
|
||||||
<div style="display: flex; gap: 0.5rem; margin-top: 1.5rem;">
|
<div style="display: flex; gap: 0.5rem; margin-top: 1.5rem;">
|
||||||
<button onclick="confirmDelete()" class="danger-button">Confirmer la suppression</button>
|
<button onclick="confirmDelete()" class="danger-button">Confirmer la suppression</button>
|
||||||
@ -151,22 +150,22 @@
|
|||||||
<div class="theme-modal-overlay" onclick="closeThemeModal()"></div>
|
<div class="theme-modal-overlay" onclick="closeThemeModal()"></div>
|
||||||
<div class="theme-modal-content">
|
<div class="theme-modal-content">
|
||||||
<h2>
|
<h2>
|
||||||
⚙️ Paramètres d'apparence
|
<i data-lucide="settings" class="icon-md"></i> Paramètres d'apparence
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<!-- Onglets -->
|
<!-- Onglets -->
|
||||||
<div class="settings-tabs">
|
<div class="settings-tabs">
|
||||||
<button class="settings-tab active" onclick="switchSettingsTab('themes')">
|
<button class="settings-tab active" onclick="switchSettingsTab('themes')">
|
||||||
🎨 Thèmes
|
<i data-lucide="palette" class="icon-sm"></i> Thèmes
|
||||||
</button>
|
</button>
|
||||||
<button class="settings-tab" onclick="switchSettingsTab('fonts')">
|
<button class="settings-tab" onclick="switchSettingsTab('fonts')">
|
||||||
🔤 Polices
|
<i data-lucide="type" class="icon-sm"></i> Polices
|
||||||
</button>
|
</button>
|
||||||
<button class="settings-tab" onclick="switchSettingsTab('editor')">
|
<button class="settings-tab" onclick="switchSettingsTab('editor')">
|
||||||
⌨️ Éditeur
|
<i data-lucide="keyboard" class="icon-sm"></i> Éditeur
|
||||||
</button>
|
</button>
|
||||||
<button class="settings-tab" onclick="switchSettingsTab('other')">
|
<button class="settings-tab" onclick="switchSettingsTab('other')">
|
||||||
⚙️ Autre
|
<i data-lucide="settings" class="icon-sm"></i> Autre
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -176,7 +175,7 @@
|
|||||||
<!-- Material Dark -->
|
<!-- Material Dark -->
|
||||||
<div class="theme-card active" data-theme="material-dark" onclick="selectTheme('material-dark')">
|
<div class="theme-card active" data-theme="material-dark" onclick="selectTheme('material-dark')">
|
||||||
<div class="theme-card-header">
|
<div class="theme-card-header">
|
||||||
<span class="theme-card-icon">🌙</span>
|
<span class="theme-card-icon"><i data-lucide="moon" class="icon-sm"></i></span>
|
||||||
<span class="theme-card-name">Material Dark</span>
|
<span class="theme-card-name">Material Dark</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="theme-preview">
|
<div class="theme-preview">
|
||||||
@ -191,7 +190,7 @@
|
|||||||
<!-- Monokai Dark -->
|
<!-- Monokai Dark -->
|
||||||
<div class="theme-card" data-theme="monokai-dark" onclick="selectTheme('monokai-dark')">
|
<div class="theme-card" data-theme="monokai-dark" onclick="selectTheme('monokai-dark')">
|
||||||
<div class="theme-card-header">
|
<div class="theme-card-header">
|
||||||
<span class="theme-card-icon">🎨</span>
|
<span class="theme-card-icon"><i data-lucide="palette" class="icon-sm"></i></span>
|
||||||
<span class="theme-card-name">Monokai Dark</span>
|
<span class="theme-card-name">Monokai Dark</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="theme-preview">
|
<div class="theme-preview">
|
||||||
@ -206,7 +205,7 @@
|
|||||||
<!-- Dracula -->
|
<!-- Dracula -->
|
||||||
<div class="theme-card" data-theme="dracula" onclick="selectTheme('dracula')">
|
<div class="theme-card" data-theme="dracula" onclick="selectTheme('dracula')">
|
||||||
<div class="theme-card-header">
|
<div class="theme-card-header">
|
||||||
<span class="theme-card-icon">🧛</span>
|
<span class="theme-card-icon"><i data-lucide="moon-star" class="icon-sm"></i></span>
|
||||||
<span class="theme-card-name">Dracula</span>
|
<span class="theme-card-name">Dracula</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="theme-preview">
|
<div class="theme-preview">
|
||||||
@ -221,7 +220,7 @@
|
|||||||
<!-- One Dark -->
|
<!-- One Dark -->
|
||||||
<div class="theme-card" data-theme="one-dark" onclick="selectTheme('one-dark')">
|
<div class="theme-card" data-theme="one-dark" onclick="selectTheme('one-dark')">
|
||||||
<div class="theme-card-header">
|
<div class="theme-card-header">
|
||||||
<span class="theme-card-icon">⚡</span>
|
<span class="theme-card-icon"><i data-lucide="zap" class="icon-sm"></i></span>
|
||||||
<span class="theme-card-name">One Dark</span>
|
<span class="theme-card-name">One Dark</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="theme-preview">
|
<div class="theme-preview">
|
||||||
@ -236,7 +235,7 @@
|
|||||||
<!-- Solarized Dark -->
|
<!-- Solarized Dark -->
|
||||||
<div class="theme-card" data-theme="solarized-dark" onclick="selectTheme('solarized-dark')">
|
<div class="theme-card" data-theme="solarized-dark" onclick="selectTheme('solarized-dark')">
|
||||||
<div class="theme-card-header">
|
<div class="theme-card-header">
|
||||||
<span class="theme-card-icon">☀️</span>
|
<span class="theme-card-icon"><i data-lucide="sun" class="icon-sm"></i></span>
|
||||||
<span class="theme-card-name">Solarized Dark</span>
|
<span class="theme-card-name">Solarized Dark</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="theme-preview">
|
<div class="theme-preview">
|
||||||
@ -251,7 +250,7 @@
|
|||||||
<!-- Nord -->
|
<!-- Nord -->
|
||||||
<div class="theme-card" data-theme="nord" onclick="selectTheme('nord')">
|
<div class="theme-card" data-theme="nord" onclick="selectTheme('nord')">
|
||||||
<div class="theme-card-header">
|
<div class="theme-card-header">
|
||||||
<span class="theme-card-icon">❄️</span>
|
<span class="theme-card-icon"><i data-lucide="snowflake" class="icon-sm"></i></span>
|
||||||
<span class="theme-card-name">Nord</span>
|
<span class="theme-card-name">Nord</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="theme-preview">
|
<div class="theme-preview">
|
||||||
@ -266,7 +265,7 @@
|
|||||||
<!-- Catppuccin -->
|
<!-- Catppuccin -->
|
||||||
<div class="theme-card" data-theme="catppuccin" onclick="selectTheme('catppuccin')">
|
<div class="theme-card" data-theme="catppuccin" onclick="selectTheme('catppuccin')">
|
||||||
<div class="theme-card-header">
|
<div class="theme-card-header">
|
||||||
<span class="theme-card-icon">🌸</span>
|
<span class="theme-card-icon"><i data-lucide="flower" class="icon-sm"></i></span>
|
||||||
<span class="theme-card-name">Catppuccin</span>
|
<span class="theme-card-name">Catppuccin</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="theme-preview">
|
<div class="theme-preview">
|
||||||
@ -281,7 +280,7 @@
|
|||||||
<!-- Everforest -->
|
<!-- Everforest -->
|
||||||
<div class="theme-card" data-theme="everforest" onclick="selectTheme('everforest')">
|
<div class="theme-card" data-theme="everforest" onclick="selectTheme('everforest')">
|
||||||
<div class="theme-card-header">
|
<div class="theme-card-header">
|
||||||
<span class="theme-card-icon">🌲</span>
|
<span class="theme-card-icon"><i data-lucide="tree-deciduous" class="icon-sm"></i></span>
|
||||||
<span class="theme-card-name">Everforest</span>
|
<span class="theme-card-name">Everforest</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="theme-preview">
|
<div class="theme-preview">
|
||||||
@ -301,7 +300,7 @@
|
|||||||
<!-- Fira Code -->
|
<!-- Fira Code -->
|
||||||
<div class="font-card" data-font="fira-code" onclick="selectFont('fira-code')">
|
<div class="font-card" data-font="fira-code" onclick="selectFont('fira-code')">
|
||||||
<div class="font-card-header">
|
<div class="font-card-header">
|
||||||
<span class="font-card-icon">💻</span>
|
<span class="font-card-icon"><i data-lucide="code" class="icon-sm"></i></span>
|
||||||
<span class="font-card-name">Fira Code</span>
|
<span class="font-card-name">Fira Code</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="font-preview" style="font-family: 'Fira Code', monospace;">
|
<div class="font-preview" style="font-family: 'Fira Code', monospace;">
|
||||||
@ -313,7 +312,7 @@
|
|||||||
<!-- Sans-serif -->
|
<!-- Sans-serif -->
|
||||||
<div class="font-card" data-font="sans-serif" onclick="selectFont('sans-serif')">
|
<div class="font-card" data-font="sans-serif" onclick="selectFont('sans-serif')">
|
||||||
<div class="font-card-header">
|
<div class="font-card-header">
|
||||||
<span class="font-card-icon">📝</span>
|
<span class="font-card-icon"><i data-lucide="type" class="icon-sm"></i></span>
|
||||||
<span class="font-card-name">Sans-serif</span>
|
<span class="font-card-name">Sans-serif</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="font-preview" style="font-family: -apple-system, sans-serif;">
|
<div class="font-preview" style="font-family: -apple-system, sans-serif;">
|
||||||
@ -325,7 +324,7 @@
|
|||||||
<!-- Inter -->
|
<!-- Inter -->
|
||||||
<div class="font-card" data-font="inter" onclick="selectFont('inter')">
|
<div class="font-card" data-font="inter" onclick="selectFont('inter')">
|
||||||
<div class="font-card-header">
|
<div class="font-card-header">
|
||||||
<span class="font-card-icon">✨</span>
|
<span class="font-card-icon"><i data-lucide="sparkles" class="icon-sm"></i></span>
|
||||||
<span class="font-card-name">Inter</span>
|
<span class="font-card-name">Inter</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="font-preview" style="font-family: 'Inter', sans-serif;">
|
<div class="font-preview" style="font-family: 'Inter', sans-serif;">
|
||||||
@ -337,7 +336,7 @@
|
|||||||
<!-- Poppins -->
|
<!-- Poppins -->
|
||||||
<div class="font-card" data-font="poppins" onclick="selectFont('poppins')">
|
<div class="font-card" data-font="poppins" onclick="selectFont('poppins')">
|
||||||
<div class="font-card-header">
|
<div class="font-card-header">
|
||||||
<span class="font-card-icon">🎯</span>
|
<span class="font-card-icon"><i data-lucide="target" class="icon-sm"></i></span>
|
||||||
<span class="font-card-name">Poppins</span>
|
<span class="font-card-name">Poppins</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="font-preview" style="font-family: 'Poppins', sans-serif;">
|
<div class="font-preview" style="font-family: 'Poppins', sans-serif;">
|
||||||
@ -349,7 +348,7 @@
|
|||||||
<!-- Public Sans -->
|
<!-- Public Sans -->
|
||||||
<div class="font-card" data-font="public-sans" onclick="selectFont('public-sans')">
|
<div class="font-card" data-font="public-sans" onclick="selectFont('public-sans')">
|
||||||
<div class="font-card-header">
|
<div class="font-card-header">
|
||||||
<span class="font-card-icon">🏛️</span>
|
<span class="font-card-icon"><i data-lucide="building" class="icon-sm"></i></span>
|
||||||
<span class="font-card-name">Public Sans</span>
|
<span class="font-card-name">Public Sans</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="font-preview" style="font-family: 'Public Sans', sans-serif;">
|
<div class="font-preview" style="font-family: 'Public Sans', sans-serif;">
|
||||||
@ -361,7 +360,7 @@
|
|||||||
<!-- JetBrains Mono -->
|
<!-- JetBrains Mono -->
|
||||||
<div class="font-card active" data-font="jetbrains-mono" onclick="selectFont('jetbrains-mono')">
|
<div class="font-card active" data-font="jetbrains-mono" onclick="selectFont('jetbrains-mono')">
|
||||||
<div class="font-card-header">
|
<div class="font-card-header">
|
||||||
<span class="font-card-icon">⚡</span>
|
<span class="font-card-icon"><i data-lucide="zap" class="icon-sm"></i></span>
|
||||||
<span class="font-card-name">JetBrains Mono</span>
|
<span class="font-card-name">JetBrains Mono</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="font-preview" style="font-family: 'JetBrains Mono', monospace;">
|
<div class="font-preview" style="font-family: 'JetBrains Mono', monospace;">
|
||||||
@ -373,7 +372,7 @@
|
|||||||
<!-- Cascadia Code -->
|
<!-- Cascadia Code -->
|
||||||
<div class="font-card" data-font="cascadia-code" onclick="selectFont('cascadia-code')">
|
<div class="font-card" data-font="cascadia-code" onclick="selectFont('cascadia-code')">
|
||||||
<div class="font-card-header">
|
<div class="font-card-header">
|
||||||
<span class="font-card-icon">🪟</span>
|
<span class="font-card-icon"><i data-lucide="square" class="icon-sm"></i></span>
|
||||||
<span class="font-card-name">Cascadia Code</span>
|
<span class="font-card-name">Cascadia Code</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="font-preview" style="font-family: 'Cascadia Code', monospace;">
|
<div class="font-preview" style="font-family: 'Cascadia Code', monospace;">
|
||||||
@ -385,7 +384,7 @@
|
|||||||
<!-- Source Code Pro -->
|
<!-- Source Code Pro -->
|
||||||
<div class="font-card" data-font="source-code-pro" onclick="selectFont('source-code-pro')">
|
<div class="font-card" data-font="source-code-pro" onclick="selectFont('source-code-pro')">
|
||||||
<div class="font-card-header">
|
<div class="font-card-header">
|
||||||
<span class="font-card-icon">🔧</span>
|
<span class="font-card-icon"><i data-lucide="wrench" class="icon-sm"></i></span>
|
||||||
<span class="font-card-name">Source Code Pro</span>
|
<span class="font-card-name">Source Code Pro</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="font-preview" style="font-family: 'Source Code Pro', monospace;">
|
<div class="font-preview" style="font-family: 'Source Code Pro', monospace;">
|
||||||
@ -397,7 +396,7 @@
|
|||||||
|
|
||||||
<!-- Sélecteur de taille de police -->
|
<!-- Sélecteur de taille de police -->
|
||||||
<div style="margin-top: var(--spacing-lg); padding-top: var(--spacing-lg); border-top: 1px solid var(--border-primary);">
|
<div style="margin-top: var(--spacing-lg); padding-top: var(--spacing-lg); border-top: 1px solid var(--border-primary);">
|
||||||
<h3 style="font-size: 1rem; color: var(--text-primary); margin-bottom: var(--spacing-md);">📏 Taille de police</h3>
|
<h3 style="font-size: 1rem; color: var(--text-primary); margin-bottom: var(--spacing-md);"><i data-lucide="ruler" class="icon-sm"></i> Taille de police</h3>
|
||||||
<div class="font-size-selector">
|
<div class="font-size-selector">
|
||||||
<button class="font-size-option" data-size="small" onclick="selectFontSize('small')">
|
<button class="font-size-option" data-size="small" onclick="selectFontSize('small')">
|
||||||
<span class="size-label">Petite</span>
|
<span class="size-label">Petite</span>
|
||||||
@ -421,7 +420,7 @@
|
|||||||
|
|
||||||
<!-- Section Éditeur -->
|
<!-- Section Éditeur -->
|
||||||
<div id="editor-section" class="settings-section" style="display: none;">
|
<div id="editor-section" class="settings-section" style="display: none;">
|
||||||
<h3 style="font-size: 1.1rem; color: var(--text-primary); margin-bottom: var(--spacing-lg);">⌨️ Mode d'édition</h3>
|
<h3 style="font-size: 1.1rem; color: var(--text-primary); margin-bottom: var(--spacing-lg);"><i data-lucide="keyboard" class="icon-sm"></i> Mode d'édition</h3>
|
||||||
|
|
||||||
<!-- Toggle Mode Vim -->
|
<!-- Toggle Mode Vim -->
|
||||||
<div style="display: flex; align-items: center; justify-content: space-between; padding: var(--spacing-lg); background: var(--bg-secondary); border-radius: var(--radius-md); border: 1px solid var(--border-primary);">
|
<div style="display: flex; align-items: center; justify-content: space-between; padding: var(--spacing-lg); background: var(--bg-secondary); border-radius: var(--radius-md); border: 1px solid var(--border-primary);">
|
||||||
@ -444,21 +443,21 @@
|
|||||||
|
|
||||||
<div style="margin-top: var(--spacing-md); padding: var(--spacing-md); background: var(--bg-tertiary); border-radius: var(--radius-sm); border-left: 3px solid var(--accent-primary);">
|
<div style="margin-top: var(--spacing-md); padding: var(--spacing-md); background: var(--bg-tertiary); border-radius: var(--radius-sm); border-left: 3px solid var(--accent-primary);">
|
||||||
<p style="font-size: 0.85rem; color: var(--text-muted); margin: 0;">
|
<p style="font-size: 0.85rem; color: var(--text-muted); margin: 0;">
|
||||||
💡 <strong>Astuce :</strong> Le mode Vim sera appliqué immédiatement à l'éditeur actuel. Si vous ouvrez une nouvelle note, le mode restera activé.
|
<i data-lucide="lightbulb" class="icon-sm"></i> <strong>Astuce :</strong> Le mode Vim sera appliqué immédiatement à l'éditeur actuel. Si vous ouvrez une nouvelle note, le mode restera activé.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Section Autre (Langue) -->
|
<!-- Section Autre (Langue) -->
|
||||||
<div id="other-section" class="settings-section" style="display: none;">
|
<div id="other-section" class="settings-section" style="display: none;">
|
||||||
<h3 style="font-size: 1.1rem; color: var(--text-primary); margin-bottom: var(--spacing-lg);">🌍 Langue / Language</h3>
|
<h3 style="font-size: 1.1rem; color: var(--text-primary); margin-bottom: var(--spacing-lg);"><i data-lucide="languages" class="icon-sm"></i> Langue / Language</h3>
|
||||||
|
|
||||||
<div style="display: flex; flex-direction: column; gap: var(--spacing-md);">
|
<div style="display: flex; flex-direction: column; gap: var(--spacing-md);">
|
||||||
<label class="language-option" style="display: flex; align-items: center; padding: var(--spacing-lg); background: var(--bg-secondary); border-radius: var(--radius-md); border: 2px solid var(--border-primary); cursor: pointer; transition: all 0.2s ease;">
|
<label class="language-option" style="display: flex; align-items: center; padding: var(--spacing-lg); background: var(--bg-secondary); border-radius: var(--radius-md); border: 2px solid var(--border-primary); cursor: pointer; transition: all 0.2s ease;">
|
||||||
<input type="radio" name="language" value="en" style="margin-right: var(--spacing-md); width: 20px; height: 20px; cursor: pointer;">
|
<input type="radio" name="language" value="en" style="margin-right: var(--spacing-md); width: 20px; height: 20px; cursor: pointer;">
|
||||||
<div style="flex: 1;">
|
<div style="flex: 1;">
|
||||||
<div style="font-weight: 500; color: var(--text-primary); margin-bottom: 0.25rem; font-size: 1rem;">
|
<div style="font-weight: 500; color: var(--text-primary); margin-bottom: 0.25rem; font-size: 1rem;">
|
||||||
🇬🇧 English
|
English
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size: 0.85rem; color: var(--text-secondary);">
|
<div style="font-size: 0.85rem; color: var(--text-secondary);">
|
||||||
English interface
|
English interface
|
||||||
@ -470,7 +469,7 @@
|
|||||||
<input type="radio" name="language" value="fr" style="margin-right: var(--spacing-md); width: 20px; height: 20px; cursor: pointer;">
|
<input type="radio" name="language" value="fr" style="margin-right: var(--spacing-md); width: 20px; height: 20px; cursor: pointer;">
|
||||||
<div style="flex: 1;">
|
<div style="flex: 1;">
|
||||||
<div style="font-weight: 500; color: var(--text-primary); margin-bottom: 0.25rem; font-size: 1rem;">
|
<div style="font-weight: 500; color: var(--text-primary); margin-bottom: 0.25rem; font-size: 1rem;">
|
||||||
🇫🇷 Français
|
Français
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size: 0.85rem; color: var(--text-secondary);">
|
<div style="font-size: 0.85rem; color: var(--text-secondary);">
|
||||||
Interface en français
|
Interface en français
|
||||||
@ -489,10 +488,10 @@
|
|||||||
<div class="main-layout">
|
<div class="main-layout">
|
||||||
<aside id="sidebar">
|
<aside id="sidebar">
|
||||||
<button class="sidebar-close-btn" onclick="toggleSidebar()" title="Fermer le menu">
|
<button class="sidebar-close-btn" onclick="toggleSidebar()" title="Fermer le menu">
|
||||||
✕
|
<i data-lucide="x" class="icon-md"></i>
|
||||||
</button>
|
</button>
|
||||||
<section>
|
<section>
|
||||||
<h2 class="sidebar-section-title">🔍 Recherche</h2>
|
<h2 class="sidebar-section-title"><i data-lucide="search" class="icon-sm"></i> Recherche</h2>
|
||||||
<div id="search-results">
|
<div id="search-results">
|
||||||
<!-- Les résultats de la recherche apparaîtront ici -->
|
<!-- Les résultats de la recherche apparaîtront ici -->
|
||||||
</div>
|
</div>
|
||||||
@ -503,7 +502,7 @@
|
|||||||
<section>
|
<section>
|
||||||
<div class="sidebar-section-header" data-section="favorites" onclick="toggleSidebarSection('favorites', event)" style="cursor: pointer; display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem 0;">
|
<div class="sidebar-section-header" data-section="favorites" onclick="toggleSidebarSection('favorites', event)" style="cursor: pointer; display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem 0;">
|
||||||
<span class="section-toggle expanded">▶</span>
|
<span class="section-toggle expanded">▶</span>
|
||||||
<h2 class="sidebar-section-title" data-i18n="sidebar.favorites" style="margin: 0; flex: 1;">⭐ Favoris</h2>
|
<h2 class="sidebar-section-title" data-i18n="sidebar.favorites" style="margin: 0; flex: 1;"><i data-lucide="star" class="icon-sm"></i> Favoris</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="sidebar-section-content" id="favorites-content" style="display: block;">
|
<div class="sidebar-section-content" id="favorites-content" style="display: block;">
|
||||||
<div id="favorites-list"
|
<div id="favorites-list"
|
||||||
@ -521,7 +520,7 @@
|
|||||||
<section>
|
<section>
|
||||||
<div class="sidebar-section-header" data-section="daily-notes" onclick="toggleSidebarSection('daily-notes', event)" style="cursor: pointer; display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem 0;">
|
<div class="sidebar-section-header" data-section="daily-notes" onclick="toggleSidebarSection('daily-notes', event)" style="cursor: pointer; display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem 0;">
|
||||||
<span class="section-toggle expanded">▶</span>
|
<span class="section-toggle expanded">▶</span>
|
||||||
<h2 class="sidebar-section-title" data-i18n="sidebar.daily" style="margin: 0; flex: 1;">📅 Daily Notes</h2>
|
<h2 class="sidebar-section-title" data-i18n="sidebar.daily" style="margin: 0; flex: 1;"><i data-lucide="calendar" class="icon-sm"></i> Daily Notes</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="sidebar-section-content" id="daily-notes-content" style="display: block;">
|
<div class="sidebar-section-content" id="daily-notes-content" style="display: block;">
|
||||||
<div id="daily-calendar-container"
|
<div id="daily-calendar-container"
|
||||||
@ -546,12 +545,9 @@
|
|||||||
|
|
||||||
<section>
|
<section>
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-sm);">
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-sm);">
|
||||||
<h2 class="sidebar-section-title" style="margin: 0;">📚 Notes</h2>
|
<h2 class="sidebar-section-title" style="margin: 0;"><i data-lucide="notebook" class="icon-sm"></i> Notes</h2>
|
||||||
<button id="toggle-selection-mode" onclick="toggleSelectionMode()" class="icon-button" title="Mode sélection">
|
<button id="toggle-selection-mode" onclick="toggleSelectionMode()" class="icon-button" title="Mode sélection">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<i data-lucide="check-square" style="width: 18px; height: 18px;"></i>
|
||||||
<polyline points="9 11 12 14 22 4"></polyline>
|
|
||||||
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path>
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="file-tree" hx-get="/api/tree" hx-trigger="load once delay:250ms" hx-swap="innerHTML">
|
<div id="file-tree" hx-get="/api/tree" hx-trigger="load once delay:250ms" hx-swap="innerHTML">
|
||||||
@ -562,33 +558,26 @@
|
|||||||
|
|
||||||
<!-- Bouton Nouveau dossier avant les paramètres -->
|
<!-- Bouton Nouveau dossier avant les paramètres -->
|
||||||
<button onclick="showNewFolderModal()" class="folder-create-btn sidebar-action-btn" data-i18n="fileTree.newFolder" title="Créer un nouveau dossier (Ctrl/Cmd+Shift+F)">
|
<button onclick="showNewFolderModal()" class="folder-create-btn sidebar-action-btn" data-i18n="fileTree.newFolder" title="Créer un nouveau dossier (Ctrl/Cmd+Shift+F)">
|
||||||
📁 Nouveau dossier
|
<i data-lucide="folder-plus" class="icon-sm"></i> Nouveau dossier
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Boutons du bas de la sidebar -->
|
<!-- Boutons du bas de la sidebar -->
|
||||||
<div style="display: flex; gap: 0.5rem; align-items: stretch;">
|
<div style="display: flex; gap: 0.5rem; align-items: stretch;">
|
||||||
<!-- Bouton Paramètres (thèmes) -->
|
<!-- Bouton Paramètres (thèmes) -->
|
||||||
<button id="theme-settings-btn" class="sidebar-action-btn" onclick="openThemeModal()" title="Ouvrir les paramètres (Ctrl/Cmd+,)" style="flex: 1; display: flex; align-items: center; gap: 0.5rem; padding: 0.6rem 0.8rem; font-size: 0.85rem;">
|
<button id="theme-settings-btn" class="sidebar-action-btn" onclick="openThemeModal()" title="Ouvrir les paramètres (Ctrl/Cmd+,)" style="flex: 1; display: flex; align-items: center; gap: 0.5rem; padding: 0.6rem 0.8rem; font-size: 0.85rem;">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<i data-lucide="settings" style="width: 16px; height: 16px;"></i>
|
||||||
<circle cx="12" cy="12" r="3"></circle>
|
|
||||||
<path d="M12 1v6m0 6v6m-6-6h6m6 0h-6m-5.3-5.3l4.2 4.2m4.2 4.2l4.2 4.2m0-12.6l-4.2 4.2m-4.2 4.2L2.7 19.3"></path>
|
|
||||||
</svg>
|
|
||||||
<span data-i18n="settings.title">Paramètres</span>
|
<span data-i18n="settings.title">Paramètres</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Bouton À propos -->
|
<!-- Bouton À propos -->
|
||||||
<button
|
<button
|
||||||
class="sidebar-action-btn"
|
class="sidebar-action-btn"
|
||||||
title="À propos de PersoNotes"
|
title="À propos de PersoNotes"
|
||||||
hx-get="/api/about"
|
hx-get="/api/about"
|
||||||
hx-target="#editor-container"
|
hx-target="#editor-container"
|
||||||
hx-swap="innerHTML"
|
hx-swap="innerHTML"
|
||||||
style="display: flex; align-items: center; justify-content: center; padding: 0.6rem; min-width: auto; opacity: 0.7;">
|
style="display: flex; align-items: center; justify-content: center; padding: 0.6rem; min-width: auto; opacity: 0.7;">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<i data-lucide="info" style="width: 16px; height: 16px;"></i>
|
||||||
<circle cx="12" cy="12" r="10"></circle>
|
|
||||||
<line x1="12" y1="16" x2="12" y2="12"></line>
|
|
||||||
<line x1="12" y1="8" x2="12.01" y2="8"></line>
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
@ -598,9 +587,7 @@
|
|||||||
hx-trigger="load once"
|
hx-trigger="load once"
|
||||||
hx-swap="innerHTML">
|
hx-swap="innerHTML">
|
||||||
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; height: 50vh; text-align: center; color: var(--text-secondary);">
|
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; height: 50vh; text-align: center; color: var(--text-secondary);">
|
||||||
<svg style="width: 64px; height: 64px; margin-bottom: 1rem; opacity: 0.5;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<i data-lucide="file-text" style="width: 64px; height: 64px; margin-bottom: 1rem; opacity: 0.5;"></i>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
|
||||||
</svg>
|
|
||||||
<p style="font-size: 1.1rem; margin: 0;">Chargement...</p>
|
<p style="font-size: 1.1rem; margin: 0;">Chargement...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -695,5 +682,33 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- Lucide Icons Initialization & htmx Integration -->
|
||||||
|
<script>
|
||||||
|
// Initialize Lucide icons on page load
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
if (typeof lucide !== 'undefined') {
|
||||||
|
lucide.createIcons();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reinitialize Lucide icons after htmx swaps
|
||||||
|
document.body.addEventListener('htmx:afterSwap', function(event) {
|
||||||
|
if (typeof lucide !== 'undefined') {
|
||||||
|
lucide.createIcons({
|
||||||
|
nameAttr: 'data-lucide'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reinitialize after out-of-band swaps (file-tree updates)
|
||||||
|
document.body.addEventListener('htmx:oobAfterSwap', function(event) {
|
||||||
|
if (typeof lucide !== 'undefined') {
|
||||||
|
lucide.createIcons({
|
||||||
|
nameAttr: 'data-lucide'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -12,7 +12,7 @@
|
|||||||
hx-target="#editor-container"
|
hx-target="#editor-container"
|
||||||
hx-swap="innerHTML"
|
hx-swap="innerHTML"
|
||||||
hx-push-url="true">
|
hx-push-url="true">
|
||||||
<div class="search-result-icon">📄</div>
|
<div class="search-result-icon"><i data-lucide="file-text" class="icon-md"></i></div>
|
||||||
<div class="search-result-content">
|
<div class="search-result-content">
|
||||||
<div class="search-result-header">
|
<div class="search-result-header">
|
||||||
<span class="search-result-title">{{.Title}}</span>
|
<span class="search-result-title">{{.Title}}</span>
|
||||||
@ -40,14 +40,14 @@
|
|||||||
</ul>
|
</ul>
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="search-no-results">
|
<div class="search-no-results">
|
||||||
<div class="search-no-results-icon">🔍</div>
|
<div class="search-no-results-icon"><i data-lucide="search" class="icon-lg"></i></div>
|
||||||
<p class="search-no-results-text">Aucun résultat pour « <strong>{{.Query}}</strong> »</p>
|
<p class="search-no-results-text">Aucun résultat pour « <strong>{{.Query}}</strong> »</p>
|
||||||
<p class="search-no-results-hint">Essayez d'autres mots-clés ou utilisez les filtres</p>
|
<p class="search-no-results-hint">Essayez d'autres mots-clés ou utilisez les filtres</p>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="search-help">
|
<div class="search-help">
|
||||||
<p class="search-help-title">💡 Recherche avancée</p>
|
<p class="search-help-title"><i data-lucide="lightbulb" class="icon-sm"></i> Recherche avancée</p>
|
||||||
<p class="search-help-text">Saisissez des mots-clés pour rechercher dans vos notes</p>
|
<p class="search-help-text">Saisissez des mots-clés pour rechercher dans vos notes</p>
|
||||||
<div class="search-help-examples">
|
<div class="search-help-examples">
|
||||||
<div class="search-help-example">
|
<div class="search-help-example">
|
||||||
|
|||||||
Reference in New Issue
Block a user