- 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.
 | |
| 
 |