- Added UrlStateService to sync app state with URL parameters for note selection, tags, folders, and search - Implemented URL state effects in AppComponent to handle navigation from URL parameters - Updated sidebar and layout components to reflect URL state changes in UI - Added URL state updates when navigating via note selection, tag clicks, and search - Modified note sharing to use URL parameters instead of route paths - Added auto-opening of relevant
381 lines
11 KiB
Markdown
381 lines
11 KiB
Markdown
# UrlStateService - Synchronisation d'État via URL
|
|
|
|
## 🎯 Objectif
|
|
|
|
Le `UrlStateService` permet de synchroniser l'état de l'interface ObsiViewer avec l'URL, offrant:
|
|
|
|
- ✅ **Deep-linking**: Ouvrir une note directement via URL
|
|
- ✅ **Partage de liens**: Générer des URLs partageables
|
|
- ✅ **Restauration d'état**: Retrouver l'état après rechargement
|
|
- ✅ **Filtrage persistant**: Tags, dossiers, quick links via URL
|
|
- ✅ **Recherche persistante**: Termes de recherche dans l'URL
|
|
|
|
## 📦 Fichiers Livrés
|
|
|
|
### Service Principal
|
|
- **`src/app/services/url-state.service.ts`** (350+ lignes)
|
|
- Service complet avec gestion d'état via Angular Signals
|
|
- Synchronisation bidirectionnelle avec l'URL
|
|
- Validation des données
|
|
- Gestion des erreurs
|
|
|
|
### Documentation
|
|
- **`docs/URL_STATE_SERVICE_INTEGRATION.md`** (500+ lignes)
|
|
- Guide complet d'intégration
|
|
- Exemples d'URL
|
|
- Gestion des erreurs
|
|
- Cas d'usage avancés
|
|
- API complète
|
|
|
|
### Exemples d'Intégration
|
|
- **`src/app/components/url-state-integration-examples.ts`** (600+ lignes)
|
|
- 7 exemples complets de composants
|
|
- NotesListComponent avec filtres
|
|
- NoteViewComponent avec chargement
|
|
- TagsComponent, FoldersComponent
|
|
- SearchComponent
|
|
- Partage de lien
|
|
- Historique de navigation
|
|
|
|
### Tests Unitaires
|
|
- **`src/app/services/url-state.service.spec.ts`** (400+ lignes)
|
|
- 40+ tests unitaires
|
|
- Couverture complète du service
|
|
- Tests d'intégration
|
|
- Tests des cas limites
|
|
|
|
## 🚀 Démarrage Rapide
|
|
|
|
### 1. Injection dans AppComponent
|
|
|
|
```typescript
|
|
import { Component, inject } from '@angular/core';
|
|
import { UrlStateService } from './services/url-state.service';
|
|
|
|
@Component({
|
|
selector: 'app-root',
|
|
standalone: true,
|
|
template: `...`
|
|
})
|
|
export class AppComponent {
|
|
private urlStateService = inject(UrlStateService);
|
|
// Le service s'initialise automatiquement
|
|
}
|
|
```
|
|
|
|
### 2. Utilisation dans les composants
|
|
|
|
```typescript
|
|
// Injecter le service
|
|
urlState = inject(UrlStateService);
|
|
|
|
// Utiliser les signaux
|
|
activeTag = this.urlState.activeTag;
|
|
currentNote = this.urlState.currentNote;
|
|
|
|
// Mettre à jour l'URL
|
|
await this.urlState.openNote('Docs/Architecture.md');
|
|
await this.urlState.filterByTag('Ideas');
|
|
```
|
|
|
|
### 3. Exemples d'URL
|
|
|
|
```
|
|
# Ouvrir une note
|
|
/viewer?note=Docs/Architecture.md
|
|
|
|
# Filtrer par tag
|
|
/viewer?tag=Ideas
|
|
|
|
# Filtrer par dossier
|
|
/viewer?folder=Notes/Meetings
|
|
|
|
# Afficher un quick link
|
|
/viewer?quick=Favoris
|
|
|
|
# Rechercher
|
|
/viewer?search=performance
|
|
|
|
# Combinaisons
|
|
/viewer?note=Docs/Architecture.md&search=performance
|
|
```
|
|
|
|
## 📋 API Principale
|
|
|
|
### Signaux (Computed)
|
|
|
|
```typescript
|
|
// État actuel de l'URL
|
|
currentState: Signal<UrlState>
|
|
|
|
// Note actuellement ouverte
|
|
currentNote: Signal<Note | null>
|
|
|
|
// Tag actif
|
|
activeTag: Signal<string | null>
|
|
|
|
// Dossier actif
|
|
activeFolder: Signal<string | null>
|
|
|
|
// Quick link actif
|
|
activeQuickLink: Signal<string | null>
|
|
|
|
// Terme de recherche actif
|
|
activeSearch: Signal<string | null>
|
|
```
|
|
|
|
### Méthodes de Navigation
|
|
|
|
```typescript
|
|
// Ouvrir une note
|
|
async openNote(notePath: string): Promise<void>
|
|
|
|
// Filtrer par tag
|
|
async filterByTag(tag: string): Promise<void>
|
|
|
|
// Filtrer par dossier
|
|
async filterByFolder(folder: string): Promise<void>
|
|
|
|
// Filtrer par quick link
|
|
async filterByQuickLink(quickLink: string): Promise<void>
|
|
|
|
// Mettre à jour la recherche
|
|
async updateSearch(searchTerm: string): Promise<void>
|
|
|
|
// Réinitialiser l'état
|
|
async resetState(): Promise<void>
|
|
```
|
|
|
|
### Méthodes de Vérification
|
|
|
|
```typescript
|
|
// Vérifier si une note est ouverte
|
|
isNoteOpen(notePath: string): boolean
|
|
|
|
// Vérifier si un tag est actif
|
|
isTagActive(tag: string): boolean
|
|
|
|
// Vérifier si un dossier est actif
|
|
isFolderActive(folder: string): boolean
|
|
|
|
// Vérifier si un quick link est actif
|
|
isQuickLinkActive(quickLink: string): boolean
|
|
```
|
|
|
|
### Partage et État
|
|
|
|
```typescript
|
|
// Générer une URL partageble
|
|
generateShareUrl(state?: Partial<UrlState>): string
|
|
|
|
// Copier l'URL actuelle
|
|
async copyCurrentUrlToClipboard(): Promise<void>
|
|
|
|
// Obtenir l'état actuel
|
|
getState(): UrlState
|
|
|
|
// Obtenir l'état précédent
|
|
getPreviousState(): UrlState
|
|
```
|
|
|
|
## 🔄 Flux de Données
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ URL (query params) │
|
|
│ ?note=...&tag=...&folder=...&quick=...&search=... │
|
|
└─────────────────────────────────────────────────────────┘
|
|
↓
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ UrlStateService │
|
|
│ - Parsing des paramètres │
|
|
│ - Validation des données │
|
|
│ - Gestion d'état via Signals │
|
|
└─────────────────────────────────────────────────────────┘
|
|
↓
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ Angular Signals │
|
|
│ - currentState, activeTag, activeFolder, etc. │
|
|
│ - Computed signals pour dérivation d'état │
|
|
└─────────────────────────────────────────────────────────┘
|
|
↓
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ Composants │
|
|
│ - NotesListComponent (filtres) │
|
|
│ - NoteViewComponent (note ouverte) │
|
|
│ - TagsComponent, FoldersComponent │
|
|
│ - SearchComponent │
|
|
└─────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## 💡 Cas d'Usage
|
|
|
|
### 1. Deep-linking
|
|
L'utilisateur reçoit un lien direct vers une note:
|
|
```
|
|
https://app.example.com/viewer?note=Docs/Architecture.md
|
|
```
|
|
La note s'ouvre automatiquement.
|
|
|
|
### 2. Partage de contexte
|
|
L'utilisateur partage un lien avec un filtre appliqué:
|
|
```
|
|
https://app.example.com/viewer?folder=Notes/Meetings&tag=Important
|
|
```
|
|
Le destinataire voit les notes du dossier avec le tag.
|
|
|
|
### 3. Restauration après rechargement
|
|
L'utilisateur recharge la page:
|
|
```
|
|
L'état est restauré depuis l'URL
|
|
```
|
|
|
|
### 4. Historique de navigation
|
|
L'utilisateur peut revenir à l'état précédent:
|
|
```typescript
|
|
const previousState = this.urlState.getPreviousState();
|
|
// Restaurer l'état précédent
|
|
```
|
|
|
|
### 5. Recherche persistante
|
|
L'utilisateur effectue une recherche:
|
|
```
|
|
/viewer?search=performance
|
|
```
|
|
La recherche reste active même après navigation.
|
|
|
|
## 🧪 Tests
|
|
|
|
### Exécuter les tests
|
|
|
|
```bash
|
|
# Tests unitaires
|
|
ng test --include='**/url-state.service.spec.ts'
|
|
|
|
# Tests avec couverture
|
|
ng test --include='**/url-state.service.spec.ts' --code-coverage
|
|
```
|
|
|
|
### Couverture
|
|
|
|
- ✅ Initialization (5 tests)
|
|
- ✅ Computed Signals (7 tests)
|
|
- ✅ Navigation Methods (8 tests)
|
|
- ✅ State Checking (4 tests)
|
|
- ✅ Share URL Methods (3 tests)
|
|
- ✅ State Getters (2 tests)
|
|
- ✅ State Change Events (3 tests)
|
|
- ✅ State Transitions (3 tests)
|
|
- ✅ Edge Cases (4 tests)
|
|
- ✅ Lifecycle (1 test)
|
|
|
|
**Total: 40+ tests**
|
|
|
|
## 🔒 Sécurité
|
|
|
|
- ✅ Validation des chemins de notes
|
|
- ✅ Validation des tags existants
|
|
- ✅ Validation des dossiers existants
|
|
- ✅ Encodage URI pour caractères spéciaux
|
|
- ✅ Pas d'exécution de code depuis l'URL
|
|
- ✅ Gestion des erreurs robuste
|
|
|
|
## ⚡ Performance
|
|
|
|
- ✅ Utilise Angular Signals (réactivité optimisée)
|
|
- ✅ Pas de polling, écoute les changements d'URL natifs
|
|
- ✅ Décodage/encodage URI optimisé
|
|
- ✅ Gestion automatique du cycle de vie
|
|
- ✅ Pas de fuites mémoire
|
|
|
|
## 📚 Documentation Complète
|
|
|
|
Pour une documentation détaillée, consultez:
|
|
- **`docs/URL_STATE_SERVICE_INTEGRATION.md`** - Guide complet d'intégration
|
|
- **`src/app/components/url-state-integration-examples.ts`** - Exemples de code
|
|
- **`src/app/services/url-state.service.spec.ts`** - Tests unitaires
|
|
|
|
## 🎓 Exemples d'Intégration
|
|
|
|
Le fichier `src/app/components/url-state-integration-examples.ts` contient 7 exemples complets:
|
|
|
|
1. **NotesListComponent** - Synchronisation des filtres
|
|
2. **NoteViewComponent** - Chargement depuis l'URL
|
|
3. **TagsComponent** - Synchronisation des tags
|
|
4. **FoldersComponent** - Synchronisation des dossiers
|
|
5. **SearchComponent** - Synchronisation de la recherche
|
|
6. **ShareButton** - Partage de lien
|
|
7. **NavigationHistory** - Historique de navigation
|
|
|
|
## ✅ Checklist d'Intégration
|
|
|
|
- [ ] Service créé et injecté dans AppComponent
|
|
- [ ] NotesListComponent synchronise les filtres
|
|
- [ ] NoteViewComponent ouvre les notes via URL
|
|
- [ ] FoldersSidebarComponent synchronise la sélection
|
|
- [ ] TagsComponent synchronise les tags
|
|
- [ ] SearchComponent synchronise la recherche
|
|
- [ ] Partage de lien implémenté
|
|
- [ ] Historique de navigation implémenté
|
|
- [ ] Gestion des erreurs testée
|
|
- [ ] Tests unitaires passent
|
|
- [ ] Documentation mise à jour
|
|
- [ ] Déploiement en production
|
|
|
|
## 🐛 Troubleshooting
|
|
|
|
### L'URL ne change pas
|
|
Vérifiez que vous appelez les méthodes du service:
|
|
```typescript
|
|
// ❌ Mauvais
|
|
this.currentTag = 'Ideas';
|
|
|
|
// ✅ Correct
|
|
await this.urlState.filterByTag('Ideas');
|
|
```
|
|
|
|
### La note n'est pas trouvée
|
|
Vérifiez le chemin exact:
|
|
```typescript
|
|
// Afficher tous les chemins
|
|
console.log(this.vault.allNotes().map(n => n.filePath));
|
|
```
|
|
|
|
### L'état n'est pas restauré
|
|
Assurez-vous que le service est injecté dans AppComponent:
|
|
```typescript
|
|
export class AppComponent {
|
|
private urlStateService = inject(UrlStateService);
|
|
}
|
|
```
|
|
|
|
## 📞 Support
|
|
|
|
Pour des questions ou des problèmes:
|
|
1. Consultez la documentation complète
|
|
2. Vérifiez les exemples d'intégration
|
|
3. Exécutez les tests unitaires
|
|
4. Vérifiez la console du navigateur
|
|
|
|
## 📝 Notes
|
|
|
|
- Le service utilise Angular Signals pour la réactivité
|
|
- Compatible avec Angular 20+
|
|
- Fonctionne avec le Router d'Angular
|
|
- Supporte les caractères spéciaux via encodage URI
|
|
- Gestion automatique du cycle de vie
|
|
|
|
## 🎉 Résumé
|
|
|
|
Le `UrlStateService` offre une solution complète pour synchroniser l'état de l'interface avec l'URL, permettant:
|
|
|
|
- ✅ Deep-linking vers des notes spécifiques
|
|
- ✅ Partage de liens avec contexte
|
|
- ✅ Restauration d'état après rechargement
|
|
- ✅ Filtrage persistant (tags, dossiers, quick links)
|
|
- ✅ Recherche persistante
|
|
- ✅ Historique de navigation
|
|
|
|
Avec une API simple, une documentation complète et des exemples d'intégration, le service est prêt pour une utilisation en production.
|
|
|