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