feat: add tag editor API endpoint and improve tag editing UI
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
# Refonte complète du système d'édition des tags
|
||||
|
||||
## 🎯 Objectifs atteints
|
||||
|
||||
Cette refonte complète du mécanisme d'édition des tags dans ObsiViewer offre un mode édition fluide, fiable et élégant, avec persistance dans le front-matter YAML et retour utilisateur via toasts.
|
||||
|
||||
## ✨ Fonctionnalités implémentées
|
||||
|
||||
### 1. **Gestion YAML robuste** ✅
|
||||
- Normalisation et déduplique des tags (case-insensitive)
|
||||
- Suppression automatique de la clé `tags:` si liste vide
|
||||
- Création automatique du front-matter `--- ... ---` si absent
|
||||
- Aucune ligne vide dans le front-matter résultant
|
||||
- Préservation de l'ordre et du format des autres propriétés
|
||||
|
||||
### 2. **UI/UX professionnelle** ✅
|
||||
- **Mode lecture** : Chips élégants avec icône tag, clic pour éditer
|
||||
- **Mode édition** : Carte avec backdrop blur, états visuels distincts
|
||||
- Tag ajouté : bordure verte + badge "+"
|
||||
- Tag existant : style normal
|
||||
- Tag supprimé : marqué visuellement (status 'removed')
|
||||
- Bouton "Fermer l'édition" (✕) avec spinner pendant sauvegarde
|
||||
- Clic à l'extérieur de la zone → sauvegarde automatique
|
||||
|
||||
### 3. **Autocomplétion intelligente** ✅
|
||||
- Liste déroulante filtrée à la frappe (max 8 suggestions)
|
||||
- Filtrage case-insensitive
|
||||
- Navigation clavier complète (↑/↓, Enter, Escape)
|
||||
- Désactivation des doublons
|
||||
|
||||
### 4. **Sauvegarde automatique** ✅
|
||||
- Déclenchée lors de la sortie du mode édition
|
||||
- Endpoint API : `PUT /api/notes/:id/tags`
|
||||
- Toasts de feedback :
|
||||
- ✅ Succès : "Tags mis à jour"
|
||||
- ❌ Erreur : Message détaillé avec raison
|
||||
- Reste en mode édition en cas d'erreur (pas de perte de données)
|
||||
|
||||
### 5. **Bouton "Copier le chemin"** ✅
|
||||
- Positionné à gauche du path, juste au-dessus des tags
|
||||
- Toast de confirmation : "📋 Chemin copié"
|
||||
- Style cohérent avec le design system
|
||||
|
||||
### 6. **Accessibilité** ✅
|
||||
- Focus clair sur tous les éléments interactifs
|
||||
- `aria-label` sur les boutons et champs
|
||||
- Navigation clavier complète
|
||||
- Messages de statut pour lecteurs d'écran
|
||||
|
||||
## 📁 Fichiers modifiés/créés
|
||||
|
||||
### Frontend (Angular)
|
||||
- ✅ `src/app/shared/markdown/markdown-frontmatter.util.ts` - Helper YAML refactoré
|
||||
- ✅ `src/app/shared/tags-editor/tags-editor.component.ts` - Composant refactoré
|
||||
- ✅ `src/app/shared/tags-editor/tags-editor.component.html` - Template avec états visuels
|
||||
- ✅ `src/app/features/note/components/note-header/note-header.component.html` - Bouton copier repositionné
|
||||
- ✅ `src/app/features/note/components/note-header/note-header.component.ts` - Outputs simplifiés
|
||||
- ✅ `src/components/tags-view/note-viewer/note-viewer.component.ts` - Intégration simplifiée
|
||||
|
||||
### Backend (Node/Express)
|
||||
- ✅ `server/markdown-frontmatter.mjs` - Helper YAML côté serveur
|
||||
- ✅ `server/markdown-frontmatter.test.mjs` - Tests unitaires (13 tests)
|
||||
- ✅ `server/index.mjs` - Endpoint `PUT /api/notes/:id/tags`
|
||||
|
||||
## 🧪 Tests validés
|
||||
|
||||
Tous les tests unitaires passent (13/13) :
|
||||
- ✅ Création de front-matter si absent
|
||||
- ✅ Pas de front-matter si tags vide
|
||||
- ✅ Suppression de la clé tags si liste vide
|
||||
- ✅ Déduplique les tags (case-insensitive)
|
||||
- ✅ Normalise les espaces
|
||||
- ✅ Préserve les autres propriétés
|
||||
- ✅ Pas de lignes vides dans le front-matter
|
||||
- ✅ Supprime le front-matter si vide
|
||||
- ✅ Gère les tags inline
|
||||
- ✅ Extraction des tags
|
||||
- ✅ Gère les caractères spéciaux
|
||||
- ✅ Normalise BOM et line endings
|
||||
|
||||
## 🎨 Design System
|
||||
|
||||
### Couleurs (Tailwind)
|
||||
- **Chips lecture** : `slate-100/800` avec bordure `slate-200/700`
|
||||
- **Chips ajouté** : `emerald-50/900` avec bordure `emerald-300`
|
||||
- **Carte édition** : `white/slate-900` avec backdrop blur
|
||||
- **Input focus** : Ring `sky-400/500`
|
||||
- **Boutons** : Style cohérent avec bordures arrondies `rounded-xl`
|
||||
|
||||
### Animations
|
||||
- Fade-in pour l'entrée en mode édition
|
||||
- Slide-in pour les suggestions
|
||||
- Transitions fluides sur hover
|
||||
|
||||
## 🔒 Sécurité
|
||||
|
||||
- Validation des entrées côté serveur
|
||||
- Écriture atomique avec backup (.bak)
|
||||
- Gestion des erreurs avec restauration
|
||||
- Pas d'injection possible dans le YAML
|
||||
|
||||
## 📊 Performance
|
||||
|
||||
- Debounce sur les suggestions (évite les calculs inutiles)
|
||||
- Écriture atomique avec fichier temporaire
|
||||
- Reindex Meilisearch après sauvegarde
|
||||
- Pas de polling, événements uniquement
|
||||
|
||||
## 🚀 Utilisation
|
||||
|
||||
### Mode lecture
|
||||
1. Cliquer sur la zone des tags pour entrer en mode édition
|
||||
|
||||
### Mode édition
|
||||
1. Taper pour rechercher/filtrer les tags existants
|
||||
2. Sélectionner avec ↑/↓ et Enter
|
||||
3. Ou créer un nouveau tag en tapant et Enter
|
||||
4. Supprimer avec le bouton ✕ sur chaque chip
|
||||
5. Fermer avec le bouton ✕ ou clic extérieur → sauvegarde automatique
|
||||
6. Escape pour annuler et fermer
|
||||
|
||||
### Raccourcis clavier
|
||||
- **Enter** : Ajouter le tag saisi ou sélectionné
|
||||
- **Backspace** (input vide) : Supprimer le dernier tag
|
||||
- **↑/↓** : Naviguer dans les suggestions
|
||||
- **Escape** : Fermer et sauvegarder
|
||||
|
||||
## 📝 Règles métier YAML
|
||||
|
||||
```yaml
|
||||
# Cas 1 : Création avec tags
|
||||
---
|
||||
tags:
|
||||
- tag1
|
||||
- tag2
|
||||
---
|
||||
|
||||
# Cas 2 : Suppression totale (pas de tags: [])
|
||||
# → Le front-matter est supprimé si vide
|
||||
|
||||
# Cas 3 : Préservation des autres propriétés
|
||||
---
|
||||
title: Ma note
|
||||
author: John
|
||||
tags:
|
||||
- tag1
|
||||
---
|
||||
|
||||
# Cas 4 : Pas de doublons (case-insensitive)
|
||||
# Input: ['Tag', 'tag', 'TAG'] → Output: ['Tag']
|
||||
```
|
||||
|
||||
## ✅ Critères d'acceptation (DoD)
|
||||
|
||||
- [x] Passer de lecture → édition → sortie sauvegarde correctement
|
||||
- [x] YAML final respecte toutes les règles métier
|
||||
- [x] Aucun doublon, normalisation cohérente
|
||||
- [x] Toasts affichés aux bons moments
|
||||
- [x] Bouton "Copier le chemin" positionné correctement
|
||||
- [x] UI/UX professionnelle, cohérente dark/light
|
||||
- [x] Focus visible, navigation clavier OK
|
||||
- [x] Tests unitaires passent (13/13)
|
||||
|
||||
## 🔮 Améliorations futures (optionnelles)
|
||||
|
||||
- [ ] Action "Annuler" dans le toast (restaure l'ancien état)
|
||||
- [ ] Virtualisation pour très longues listes de tags
|
||||
- [ ] Drag & drop pour réorganiser les tags
|
||||
- [ ] Historique des modifications
|
||||
- [ ] Validation XSS stricte pour caractères dangereux
|
||||
|
||||
## 📚 Documentation technique
|
||||
|
||||
### API Endpoint
|
||||
|
||||
```http
|
||||
PUT /api/notes/:id/tags
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"tags": ["tag1", "tag2"]
|
||||
}
|
||||
|
||||
Response:
|
||||
{
|
||||
"ok": true,
|
||||
"tags": ["tag1", "tag2"],
|
||||
"noteId": "note-id"
|
||||
}
|
||||
```
|
||||
|
||||
### Helpers
|
||||
|
||||
```typescript
|
||||
// Frontend
|
||||
rewriteTagsFrontmatter(rawMarkdown: string, tags: string[]): string
|
||||
|
||||
// Backend
|
||||
rewriteTagsFrontmatter(rawMarkdown, tags): string
|
||||
extractTagsFromFrontmatter(rawMarkdown): string[]
|
||||
```
|
||||
|
||||
## 🎉 Résultat
|
||||
|
||||
Un système d'édition de tags moderne, robuste et agréable à utiliser, qui respecte les standards Obsidian et offre une expérience utilisateur de qualité professionnelle.
|
||||
Reference in New Issue
Block a user