- 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
		
			
				
	
	
	
		
			11 KiB
		
	
	
	
	
	
	
	
			
		
		
	
	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
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
// 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)
// É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
// 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
// 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
// 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:
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
# 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égrationsrc/app/components/url-state-integration-examples.ts- Exemples de codesrc/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:
- NotesListComponent - Synchronisation des filtres
 - NoteViewComponent - Chargement depuis l'URL
 - TagsComponent - Synchronisation des tags
 - FoldersComponent - Synchronisation des dossiers
 - SearchComponent - Synchronisation de la recherche
 - ShareButton - Partage de lien
 - 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:
// ❌ Mauvais
this.currentTag = 'Ideas';
// ✅ Correct
await this.urlState.filterByTag('Ideas');
La note n'est pas trouvée
Vérifiez le chemin exact:
// 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:
export class AppComponent {
  private urlStateService = inject(UrlStateService);
}
📞 Support
Pour des questions ou des problèmes:
- Consultez la documentation complète
 - Vérifiez les exemples d'intégration
 - Exécutez les tests unitaires
 - 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.