ObsiViewer/docs/URL_STATE/URL_STATE_SERVICE_README.md
Bruno Charest 96745e9997 feat: add URL state synchronization for navigation
- 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
2025-10-24 23:23:30 -04:00

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é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:

// ❌ 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:

  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.