Chargement du document…
+ } + + @if (erreur()) { +{{ erreur() }}
+ } + + @if (!chargement() && !erreur() && contenuHtml()) { +diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e9f701b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,40 @@ +# .dockerignore RACINE — utilisé par le build backend dont le contexte est la racine +# du dépôt (afin d'embarquer .kiro/specs). On exclut tout ce qui n'est pas nécessaire +# au build de l'API pour limiter la taille du contexte envoyé au démon Docker. + +# --- Frontend (non requis pour l'image backend) --- +frontend/node_modules +frontend/dist +frontend/.angular + +# --- Artefacts de build .NET (reconstruits dans l'image) --- +**/bin +**/obj + +# --- Projet de tests (non nécessaire au déploiement de l'API) --- +backend/SchotzIadKmTracker.Tests + +# --- Bases SQLite locales (la base vit sur le volume /data) --- +**/*.db +**/*.db-shm +**/*.db-wal + +# --- VCS / IDE / outils --- +.git +.gitignore +.vs +.vscode +.idea +*.user + +# --- Fichiers Docker --- +**/Dockerfile +**/.dockerignore +docker-compose.yml + +# --- Divers --- +.DS_Store +Thumbs.db +.env + +# IMPORTANT : NE PAS ignorer .kiro/specs (embarqué dans l'image backend). diff --git a/.kiro/specs/schotziad-km-tracker/design.md b/.kiro/specs/schotziad-km-tracker/design.md index e28aac9..f3f4453 100644 --- a/.kiro/specs/schotziad-km-tracker/design.md +++ b/.kiro/specs/schotziad-km-tracker/design.md @@ -33,6 +33,7 @@ | 21 | Authentification | Compte unique (e-mail + mot de passe), connexion par JWT (12 h par défaut), réinitialisation de mot de passe par e-mail, garde de session serveur | | 22 | Trajet aller/retour | Case à cocher A/R doublant la distance comptée (Distance_Effective) sans ligne supplémentaire ; badge A/R en liste et mention « (A/R) » sur la carte | | 23 | Barème multi-tranches & recopie | Tranches paramétrables par puissance fiscale (dernière tranche illimitée), coefficient sur 3 décimales, bouton « Recopier le dernier barème connu » | +| 24 | Consultation des specs | Lien « Specs » vers une page dédiée (menu latéral Exigences / Conception / Tâches) affichant les documents Markdown rendus (dont les diagrammes Mermaid), en lecture seule et derrière l'authentification | ### Contraintes techniques @@ -144,7 +145,8 @@ src/ │ │ │ ├── adresse-cache.service.ts # Autocomplétion adresses │ │ │ ├── adresse-nommee.service.ts # CRUD carnet d'adresses nommées │ │ │ ├── profil.service.ts # CRUD profil -│ │ │ └── auth.service.ts # Inscription, connexion, session, reset mot de passe +│ │ │ ├── auth.service.ts # Inscription, connexion, session, reset mot de passe +│ │ │ └── spec.service.ts # Lecture des documents de spécification (specs) │ │ ├── models/ │ │ │ ├── trajet.model.ts │ │ │ ├── resume-mensuel.model.ts @@ -201,6 +203,9 @@ src/ │ │ │ ├── register.component.ts # Création de l'unique compte (étape 1 de l'initialisation) │ │ │ ├── forgot-password.component.ts # Demande de réinitialisation par email │ │ │ └── reset-password.component.ts # Définition d'un nouveau mot de passe via jeton +│ │ ├── specs/ +│ │ │ ├── specs.component.ts # Rendu Markdown des documents de spécification (lazy-loadé) +│ │ │ └── specs.component.html │ ├── core/ │ │ └── guards/ │ │ ├── auth.guard.ts # Valide la session serveur (GET /api/auth/moi) avant les vues protégées @@ -333,6 +338,14 @@ src/ > **Décision — stockage du JWT dans `localStorage`** : retenu pour sa simplicité, malgré le risque XSS, acceptable pour une application personnelle mono-utilisateur. Le cookie HttpOnly a été rejeté pour éviter la complexité de protection CSRF. +**`SpecsComponent`** (documents de spécification — Exigence 19) +- La barre de navigation principale expose un simple lien « Specs » vers `/specs` (redirigé vers le premier document) ; le choix du document se fait dans le **menu latéral** de la page, pas dans la barre. +- Route paramétrée `/specs/:cle` ; **lazy-loadée** (`loadComponent`) pour sortir la librairie Markdown du bundle initial +- Colonne gauche : menu listant les documents (`GET /api/specs`), le document indisponible étant signalé ; colonne droite : contenu rendu +- Rendu : le Markdown (`GET /api/specs/{cle}`) est converti en HTML par **`marked`** (GFM : titres, listes, tableaux, code), puis **assaini par `DOMPurify`** avant injection (`[innerHTML]`) — Exigence 19.7 +- **Diagrammes Mermaid** : un renderer `marked` transforme les blocs ` ```mermaid ` en `
` ; après injection du HTML (déclenchée via `setTimeout` pour laisser Angular résoudre le `@ViewChild`), **Mermaid.js** (importé dynamiquement) rend ces blocs en SVG (`securityLevel: 'loose'` pour accepter les libellés HTML type `
`). En cas d'échec de rendu d'un diagramme, le bloc de code brut reste visible. +- Accès derrière `authGuard` (mêmes règles que les autres vues protégées) + **`ProfilConfigureGuard`** (garde de route Angular — `CanActivate` / `CanActivateFn`) - Au chargement de l'application, appelle `ProfilService.getEtat()` (`GET /api/profil/etat`) - Si `EstConfigure` est `false`, redirige vers la route d'onboarding (`/onboarding`) et bloque l'accès à la saisie / consultation des trajets tant que le profil n'est pas configuré @@ -1167,6 +1180,22 @@ public record ResetPasswordDto( string Token, // jeton en clair reçu par email string MotDePasse // nouveau mot de passe, >= 8 caractères ); + +// --- Documents de spécification (Exigence 19) --- + +// Métadonnées d'un document (élément du sous-menu) +public record SpecDocumentDto( + string Cle, // whitelist : "requirements" | "design" | "tasks" + string Titre, // libellé lisible (ex. « Exigences ») + bool Disponible // fichier présent et lisible +); + +// Contenu Markdown d'un document +public record SpecContenuDto( + string Cle, + string Titre, + string Markdown // contenu brut, rendu en HTML côté frontend +); ``` --- @@ -1274,6 +1303,15 @@ public record ResetPasswordDto( | `PUT` | `/api/montants-regles` | Enregistrer/mettre à jour un montant réglé | `MontantRegleDto` | `200 OK` + `MontantRegleDto` | | `GET` | `/api/regularisation/{annee}?vehiculeId={id}` | Synthèse annuelle de régularisation : `RegularisationAnnuelleDto` avec les 12 `LigneSyntheseMoisDto`, le coefficient et la base de la tranche annuelle, le total réel (barème plein) et la régularisation (réel − payé) | — | `200 OK` + `RegularisationAnnuelleDto` | +#### Documents de spécification (Exigence 19) + +> Endpoints **protégés** (authentification requise). L'accès est restreint à une **whitelist** de clés (`requirements`, `design`, `tasks`) : aucun chemin de fichier n'est construit à partir d'une entrée libre (pas de path traversal). + +| Méthode | Route | Description | Réponse succès | +|---------|-------|-------------|----------------| +| `GET` | `/api/specs` | Lister les documents et leur disponibilité (sous-menu) | `200 OK` + `SpecDocumentDto[]` | +| `GET` | `/api/specs/{cle}` | Contenu Markdown d'un document (clé whitelistée) | `200 OK` + `SpecContenuDto` ; `404` si clé inconnue / fichier absent | + #### Codes d'erreur standardisés | Code HTTP | Cas | @@ -1665,6 +1703,8 @@ volumes: Les **migrations EF Core sont appliquées automatiquement au démarrage du backend** (`await dbContext.Database.MigrateAsync()` dans `Program.cs` avant `app.Run()`). Ainsi, au premier lancement, la base `/data/trajets.db` est créée sur le volume monté ; aux lancements suivants, elle est mise à jour avec les migrations en attente sans intervention manuelle. +> **Documents de spécification embarqués** : le `Dockerfile` backend a pour contexte la racine du dépôt et copie `.kiro/specs/schotziad-km-tracker/{requirements,design,tasks}.md` dans `./specs/` de l'image. Le `SpecDocumentService` lit en priorité ce dossier embarqué (`ContentRootPath/specs`), avec repli sur `.kiro/specs/...` en développement. L'application est ainsi autonome, sans montage du dépôt à l'exécution. + > **Migrations notables** : `VehiculeDenomination` (dénomination du véhicule), `TrajetGeometrieJson` (géométrie d'itinéraire portée par le trajet), `TrancheBaremeKmMaxNullable` / bornes exclusives / coefficient numérique (barème multi-tranches à dernière tranche illimitée), `AjoutAuthentification` (comptes + jetons de réinitialisation), `TrajetAllerRetour` (indicateur aller/retour). ## Error Handling diff --git a/.kiro/specs/schotziad-km-tracker/requirements.md b/.kiro/specs/schotziad-km-tracker/requirements.md index 83e1e10..160b716 100644 --- a/.kiro/specs/schotziad-km-tracker/requirements.md +++ b/.kiro/specs/schotziad-km-tracker/requirements.md @@ -62,6 +62,7 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#. - **Jeton_Reinitialisation** : Un jeton à usage unique et à durée de vie limitée, envoyé par e-mail, permettant de définir un nouveau Mot_De_Passe sans connaître l'ancien. - **Aller_Retour** : Un indicateur booléen d'un Trajet signifiant que le déplacement est effectué dans les deux sens. Lorsqu'il est vrai, la Distance comptée du Trajet (Distance_Effective) est le double de la distance simple mesurée entre le départ et l'arrivée. - **Distance_Effective** : La distance comptée d'un Trajet dans tous les agrégats (liste, totaux, carte, PDF, barème) : égale à la distance simple, ou à son double lorsque le Trajet est un Aller_Retour. +- **Document_Specification** : Un document de conception du projet consultable en lecture seule dans l'Application : les Exigences (requirements), la Conception technique (design) et le Plan de tâches (tasks), rendus depuis leur source Markdown. --- @@ -432,3 +433,20 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#. 5. WHEN l'utilisateur consulte l'infobulle d'un Trajet Aller_Retour sur la Carte, THE Frontend SHALL faire suivre la distance affichée de la mention « (A/R) ». 6. THE Backend SHALL persister l'indicateur Aller_Retour de chaque Trajet et le conserver lors des modifications ne portant pas sur ce champ. +--- + +### Requirement 19: Consultation des documents de spécification + +**User Story:** En tant qu'utilisateur, je veux consulter les documents de spécification du projet (exigences, conception technique, plan de tâches) directement dans l'application, afin de disposer d'une référence lisible sans quitter l'outil. + +#### Acceptance Criteria + +1. THE Frontend SHALL afficher, dans la barre de navigation principale, un lien « Specs » menant à une page dédiée ; cette page présente un menu latéral listant chaque Document_Specification disponible : « Exigences », « Conception technique » et « Plan de tâches ». +2. WHEN l'utilisateur sélectionne un Document_Specification dans le menu latéral, THE Frontend SHALL afficher le contenu du document rendu de façon lisible (titres, listes, tableaux, blocs de code) à partir de sa source Markdown, ET SHALL rendre les blocs de diagramme Mermaid (ex. `graph TB`) sous forme de schémas graphiques. +3. THE Backend SHALL exposer les Documents_Specification en lecture seule via une API, en restreignant les documents accessibles à une liste fermée (exigences, conception, tâches) afin d'empêcher tout accès à un fichier arbitraire. +4. THE Backend SHALL exiger un utilisateur authentifié pour accéder aux Documents_Specification (mêmes règles d'accès que les autres données de l'Application) ; en l'absence de jeton valide, THE Backend SHALL retourner une erreur HTTP 401. +5. IF l'utilisateur demande un Document_Specification inconnu ou absent, THEN THE Backend SHALL retourner une erreur HTTP 404 et THE Frontend SHALL afficher un message indiquant que le document est introuvable. +6. WHERE le fichier d'un Document_Specification n'est pas disponible (ex. plan de tâches incomplet ou absent), THE Frontend SHALL signaler ce document comme indisponible dans le menu latéral tout en affichant les autres documents. +7. THE Frontend SHALL assainir le contenu HTML issu du rendu Markdown avant affichage, afin d'éviter l'injection de contenu actif. +8. IF un diagramme Mermaid ne peut pas être rendu (syntaxe non reconnue), THEN THE Frontend SHALL conserver l'affichage du bloc de code brut correspondant sans empêcher le rendu des autres diagrammes ni du reste du document. + diff --git a/backend/Dockerfile b/backend/Dockerfile index 63b2c73..fb5280e 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,10 +1,13 @@ # ---- Étape build (SDK) ---- +# NOTE : le contexte de build est la RACINE du dépôt (voir docker-compose.yml), +# afin de pouvoir embarquer les documents de spécification (.kiro/specs). Les chemins +# sont donc relatifs à la racine (préfixe backend/). FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src -COPY *.sln ./ -COPY SchotzIadKmTracker.Api/*.csproj SchotzIadKmTracker.Api/ +COPY backend/*.sln ./ +COPY backend/SchotzIadKmTracker.Api/*.csproj SchotzIadKmTracker.Api/ RUN dotnet restore SchotzIadKmTracker.Api/SchotzIadKmTracker.Api.csproj -COPY . . +COPY backend/ . RUN dotnet publish SchotzIadKmTracker.Api/SchotzIadKmTracker.Api.csproj -c Release -o /app/publish # ---- Étape runtime (ASP.NET) ---- @@ -18,6 +21,13 @@ RUN apt-get update \ COPY --from=build /app/publish . +# Documents de spécification embarqués (lus par SpecsController) : l'application est +# ainsi autonome, sans dépendre d'un montage du dépôt à l'exécution. +COPY .kiro/specs/schotziad-km-tracker/requirements.md \ + .kiro/specs/schotziad-km-tracker/design.md \ + .kiro/specs/schotziad-km-tracker/tasks.md \ + ./specs/ + # Répertoire du volume pour la base SQLite persistée RUN mkdir -p /data VOLUME ["/data"] diff --git a/backend/SchotzIadKmTracker.Api/Controllers/SpecsController.cs b/backend/SchotzIadKmTracker.Api/Controllers/SpecsController.cs new file mode 100644 index 0000000..32a5e9a --- /dev/null +++ b/backend/SchotzIadKmTracker.Api/Controllers/SpecsController.cs @@ -0,0 +1,50 @@ +using Microsoft.AspNetCore.Mvc; +using SchotzIadKmTracker.Api.Models.DTOs; +using SchotzIadKmTracker.Api.Services; + +namespace SchotzIadKmTracker.Api.Controllers; + +///+/// Expose en lecture seule les documents de spécification du projet (exigences, +/// conception technique, plan de tâches) pour affichage dans le frontend. Comme tous +/// les controllers hors AuthController, ces endpoints exigent un utilisateur +/// authentifié (autorisation globale) : les specs ne sont pas publiques. +/// +[ApiController] +[Route("api/specs")] +public class SpecsController : ControllerBase +{ + private readonly ISpecDocumentService _specDocumentService; + + public SpecsController(ISpecDocumentService specDocumentService) + { + _specDocumentService = specDocumentService; + } + + ///Liste les documents de spécification connus et leur disponibilité (pour le sous-menu). + [HttpGet] + [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)] + public ActionResult > Lister() + { + return Ok(_specDocumentService.Lister()); + } + + /// + /// Retourne le contenu Markdown d'un document identifié par sa clé (whitelist : + /// "requirements", "design", "tasks"). 404 si la clé est inconnue ou le fichier absent. + /// + [HttpGet("{cle}")] + [ProducesResponseType(typeof(SpecContenuDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> Obtenir(string cle, CancellationToken cancellationToken) + { + var contenu = await _specDocumentService.ObtenirAsync(cle, cancellationToken); + if (contenu is null) + { + return Problem( + detail: $"Le document de spécification '{cle}' est introuvable.", + statusCode: StatusCodes.Status404NotFound); + } + return Ok(contenu); + } +} diff --git a/backend/SchotzIadKmTracker.Api/Models/DTOs/SpecDtos.cs b/backend/SchotzIadKmTracker.Api/Models/DTOs/SpecDtos.cs new file mode 100644 index 0000000..63df758 --- /dev/null +++ b/backend/SchotzIadKmTracker.Api/Models/DTOs/SpecDtos.cs @@ -0,0 +1,21 @@ +namespace SchotzIadKmTracker.Api.Models.DTOs; + +/// Métadonnées d'un document de spécification exposé au frontend. +/// Clé stable et sûre (whitelist) : "requirements", "design" ou "tasks". +/// Libellé lisible pour le sous-menu (ex. « Exigences »). +/// Vrai si le fichier Markdown correspondant est présent et lisible. +public record SpecDocumentDto( + string Cle, + string Titre, + bool Disponible +); + +///Contenu Markdown d'un document de spécification. +/// Clé du document ("requirements", "design", "tasks"). +/// Libellé lisible du document. +/// Contenu brut Markdown du document, rendu côté frontend. +public record SpecContenuDto( + string Cle, + string Titre, + string Markdown +); diff --git a/backend/SchotzIadKmTracker.Api/Program.cs b/backend/SchotzIadKmTracker.Api/Program.cs index be2fbdd..686ba02 100644 --- a/backend/SchotzIadKmTracker.Api/Program.cs +++ b/backend/SchotzIadKmTracker.Api/Program.cs @@ -79,6 +79,7 @@ builder.Services.AddScoped(); // --- Cache des adresses géocodées --- builder.Services.AddScoped (); builder.Services.AddScoped (); +builder.Services.AddScoped (); // --- Services de base --- // Autorisation GLOBALE par défaut : toutes les routes MVC exigent un utilisateur authentifié. diff --git a/backend/SchotzIadKmTracker.Api/Services/ISpecDocumentService.cs b/backend/SchotzIadKmTracker.Api/Services/ISpecDocumentService.cs new file mode 100644 index 0000000..f3c7676 --- /dev/null +++ b/backend/SchotzIadKmTracker.Api/Services/ISpecDocumentService.cs @@ -0,0 +1,19 @@ +using SchotzIadKmTracker.Api.Models.DTOs; + +namespace SchotzIadKmTracker.Api.Services; + +/// +/// Fournit l'accès en lecture seule aux documents de spécification du projet +/// (requirements, design, tasks) pour affichage dans le frontend. +/// +public interface ISpecDocumentService +{ + ///Liste les documents de spécification connus et leur disponibilité. + IReadOnlyListLister(); + + /// + /// Retourne le contenu Markdown du document identifié par + Task, + /// ou null si la clé est inconnue (hors whitelist) ou le fichier absent. + ///ObtenirAsync(string cle, CancellationToken cancellationToken = default); +} diff --git a/backend/SchotzIadKmTracker.Api/Services/SpecDocumentService.cs b/backend/SchotzIadKmTracker.Api/Services/SpecDocumentService.cs new file mode 100644 index 0000000..5ccc5a3 --- /dev/null +++ b/backend/SchotzIadKmTracker.Api/Services/SpecDocumentService.cs @@ -0,0 +1,107 @@ +using SchotzIadKmTracker.Api.Models.DTOs; + +namespace SchotzIadKmTracker.Api.Services; + +/// +public class SpecDocumentService : ISpecDocumentService +{ + /// + /// Whitelist des documents exposés : clé sûre -> (nom de fichier, libellé lisible). + /// La clé provient du client ; seules ces valeurs sont acceptées, ce qui interdit + /// tout parcours de chemin (path traversal) : aucun chemin n'est construit à partir + /// d'une entrée utilisateur libre. + /// + private static readonly IReadOnlyList<(string Cle, string Fichier, string Titre)> Documents = new[] + { + ("requirements", "requirements.md", "Exigences"), + ("design", "design.md", "Conception technique"), + ("tasks", "tasks.md", "Plan de tâches") + }; + + private readonly IWebHostEnvironment _environment; + private readonly ILogger_logger; + + public SpecDocumentService(IWebHostEnvironment environment, ILogger logger) + { + _environment = environment; + _logger = logger; + } + + /// + public IReadOnlyList Lister() + { + return Documents + .Select(d => new SpecDocumentDto(d.Cle, d.Titre, File.Exists(ResoudreChemin(d.Fichier)))) + .ToList(); + } + + /// + public async Task ObtenirAsync(string cle, CancellationToken cancellationToken = default) + { + var doc = Documents.FirstOrDefault(d => d.Cle == cle); + if (doc.Cle is null) + { + // Clé hors whitelist : document inconnu. + return null; + } + + var chemin = ResoudreChemin(doc.Fichier); + if (!File.Exists(chemin)) + { + _logger.LogWarning("Document de spécification introuvable : {Chemin}", chemin); + return null; + } + + var markdown = await File.ReadAllTextAsync(chemin, cancellationToken); + return new SpecContenuDto(doc.Cle, doc.Titre, markdown); + } + + /// + /// Résout le chemin absolu d'un fichier de spécification. En priorité le dossier + /// + private string ResoudreChemin(string fichier) + { + // 1) Dossier embarqué à côté de l'application publiée (Docker). + var embarque = Path.Combine(_environment.ContentRootPath, "specs", fichier); + if (File.Exists(embarque)) + { + return embarque; + } + + // 2) Repli développement : remonter jusqu'au dépôt et lire .kiro/specs/specs/ embarqué à la racine de contenu de l'application (copié dans l'image + /// Docker au build) ; à défaut, le dossier.kiro/specs/<feature>/ du dépôt + /// (utile en développement, où l'API tourne depuis l'arborescence du projet). + ////. + var racineDepot = TrouverRacineDepot(_environment.ContentRootPath); + if (racineDepot is not null) + { + var devPath = Path.Combine( + racineDepot, ".kiro", "specs", "schotziad-km-tracker", fichier); + if (File.Exists(devPath)) + { + return devPath; + } + } + + // Chemin embarqué par défaut (inexistant) : File.Exists renverra false en amont. + return embarque; + } + + /// + /// Remonte l'arborescence depuis + private static string? TrouverRacineDepot(string depart) + { + var dir = new DirectoryInfo(depart); + while (dir is not null) + { + if (Directory.Exists(Path.Combine(dir.FullName, ".kiro"))) + { + return dir.FullName; + } + dir = dir.Parent; + } + return null; + } +} diff --git a/docker-compose.yml b/docker-compose.yml index 9baec33..f416137 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,9 @@ services: backend: build: - context: ./backend - dockerfile: Dockerfile + # Contexte = racine du dépôt pour pouvoir embarquer .kiro/specs dans l'image. + context: . + dockerfile: backend/Dockerfile environment: - ASPNETCORE_URLS=http://+:8080 - ConnectionStrings__Default=Data Source=/data/trajets.db diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 59cb1da..14180a3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -18,7 +18,10 @@ "@angular/platform-browser-dynamic": "^22.1.6", "@angular/router": "^22.1.6", "@types/leaflet": "^1.9.22", + "dompurify": "^3.4.15", "leaflet": "^1.9.4", + "marked": "^18.0.13", + "mermaid": "^12.0.0", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" @@ -590,6 +593,19 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, + "node_modules/@antfu/install-pkg": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-2.1.0.tgz", + "integrity": "sha512-sdg9NxU3zR4Mnawfbc/x6GB5Wf17WYud5qOuEuxXjaKpYpMkISSJEjItGebXJ2bQ4DIcly4NYH23mtkGJjvKUw==", + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.8.0", + "tinyexec": "^1.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/@babel/code-frame": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-8.0.0.tgz", @@ -2161,6 +2177,51 @@ "node": "^22.18.0 || >=24.11.0" } }, + "node_modules/@braintree/sanitize-url": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", + "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==", + "license": "MIT" + }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.2.tgz", + "integrity": "sha512-XTsjvDVB5nDZBQB8o0o/0ozNelQtn2KrUVteIHSlPd2VAV2utEb6JzyCJaJ8tGxACR4RiBNWy5uYUHX2eji88Q==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/gast": "11.1.2", + "@chevrotain/types": "11.1.2", + "lodash-es": "4.17.23" + } + }, + "node_modules/@chevrotain/gast": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.1.2.tgz", + "integrity": "sha512-Z9zfXR5jNZb1Hlsd/p+4XWeUFugrHirq36bKzPWDSIacV+GPSVXdk+ahVWZTwjhNwofAWg/sZg58fyucKSQx5g==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/types": "11.1.2", + "lodash-es": "4.17.23" + } + }, + "node_modules/@chevrotain/regexp-to-ast": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.1.2.tgz", + "integrity": "sha512-nMU3Uj8naWer7xpZTYJdxbAs6RIv/dxYzkYU8GSwgUtcAAlzjcPfX1w+RKRcYG8POlzMeayOQ/znfwxEGo5ulw==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/types": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz", + "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/utils": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.1.2.tgz", + "integrity": "sha512-4mudFAQ6H+MqBTfqLmU7G1ZwRzCLfJEooL/fsF6rCX5eePMbGhoy5n4g+G4vlh2muDcsCTJtL+uKbOzWxs5LHA==", + "license": "Apache-2.0" + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -2678,6 +2739,23 @@ "hono": "^4" } }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.7.tgz", + "integrity": "sha512-JZHlwdID+dy+lTgbYC8NEC4zeugqeYsc6jewvzb4c58kHauJn+X7rNwQjxz5p2qSjqaEeQoLkCIQ9v/H4PK0/w==", + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^2.0.1", + "@iconify/types": "^2.0.0", + "import-meta-resolve": "^4.2.0" + } + }, "node_modules/@inquirer/ansi": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.8.tgz", @@ -3889,6 +3967,18 @@ "win32" ] }, + "node_modules/@mermaid-js/parser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-2.0.0.tgz", + "integrity": "sha512-K8BeapFUfrfxbRAUQAG5oBOCwo1+bNWzaVnPxTCutkfrTBn6T/j91FIhqxWJ32SUeQ9T3iy1zcmPZ5ROZEvDrg==", + "license": "MIT", + "dependencies": { + "@chevrotain/types": "~11.1.2" + }, + "engines": { + "node": ">=22.12.0" + } + }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz", @@ -5595,6 +5685,259 @@ "@types/node": "*" } }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-65Emv9fQiQQqphLlRkuQ5ypPsOmWPhtBGCMv61JDPEPMvsx+gzhGf74yw1a78xFKPj6zw4AgQICJoQv0vK9M2w==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.4.tgz", + "integrity": "sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.12.tgz", + "integrity": "sha512-Qe/KWYhEiIIxGs7HrAAjMfShxKldx19SJtr5zu53f3afPsdZNz7HHtdTLXo/kqeiWNXVycI24kSnfzBYkTzpgw==", + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-kVd74ta9eof3eJOvbNd1vGKS/XERRyQbT26Og63hIsvDO84cjD5gEOhsXf26w3FSoNlPVz84DOFcKv/oou+fMw==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", @@ -5786,6 +6129,13 @@ "@types/node": "*" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/ws": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", @@ -5796,6 +6146,16 @@ "@types/node": "*" } }, + "node_modules/@upsetjs/venn.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz", + "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==", + "license": "MIT", + "optionalDependencies": { + "d3-selection": "^3.0.0", + "d3-transition": "^3.0.1" + } + }, "node_modules/@vitejs/plugin-basic-ssl": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.3.0.tgz", @@ -6620,6 +6980,20 @@ "dev": true, "license": "MIT" }, + "node_modules/chevrotain": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.2.tgz", + "integrity": "sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/cst-dts-gen": "11.1.2", + "@chevrotain/gast": "11.1.2", + "@chevrotain/regexp-to-ast": "11.1.2", + "@chevrotain/types": "11.1.2", + "@chevrotain/utils": "11.1.2", + "lodash-es": "4.17.23" + } + }, "node_modules/chokidar": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", @@ -7101,6 +7475,15 @@ "url": "https://opencollective.com/express" } }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "license": "MIT", + "dependencies": { + "layout-base": "^1.0.0" + } + }, "node_modules/cosmiconfig": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", @@ -7229,6 +7612,526 @@ "dev": true, "license": "MIT" }, + "node_modules/cytoscape": { + "version": "3.34.3", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.34.3.tgz", + "integrity": "sha512-yfYGhRcGAntq6YBD583j4n0Eg3jIxvWmZtz/5uz9UYkeIStSlMxuUja+ec5j3iBD8nv1rwaOAYMW09tBdkSeaQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "license": "MIT", + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-dsv/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz", + "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==", + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" + } + }, "node_modules/date-format": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", @@ -7239,6 +8142,12 @@ "node": ">=4.0" } }, + "node_modules/dayjs": { + "version": "1.11.23", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.23.tgz", + "integrity": "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -7300,6 +8209,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -7415,6 +8333,15 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dompurify": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.15.tgz", + "integrity": "sha512-EUBjM+B+lkDE41iE82DDSCfkoPGfXx8IxFxPMjNzm/Uk4xDet77rTN9wqlxlVg71kK7XGuUMv6wUxJUwwv+Xyw==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/domutils": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", @@ -7459,6 +8386,12 @@ "dev": true, "license": "ISC" }, + "node_modules/elkjs": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.3.tgz", + "integrity": "sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==", + "license": "EPL-2.0" + }, "node_modules/emoji-regex": { "version": "10.6.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", @@ -7704,6 +8637,18 @@ "node": ">= 0.4" } }, + "node_modules/es-toolkit": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.52.0.tgz", + "integrity": "sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks", + "tests/types", + "tests/browser-compat" + ] + }, "node_modules/esbuild": { "version": "0.28.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", @@ -8351,6 +9296,12 @@ "dev": true, "license": "ISC" }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "license": "MIT" + }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -8676,7 +9627,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", - "dev": true, "license": "MIT", "funding": { "type": "github", @@ -8702,6 +9652,15 @@ "dev": true, "license": "ISC" }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/ip-address": { "version": "10.7.0", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.0.tgz", @@ -10251,6 +11210,36 @@ "node": ">=10" } }, + "node_modules/katex": { + "version": "0.16.47", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", + "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -10272,6 +11261,12 @@ "shell-quote": "^1.8.4" } }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "license": "MIT" + }, "node_modules/leaflet": { "version": "1.9.4", "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", @@ -10736,6 +11731,12 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash-es": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "license": "MIT" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -10842,6 +11843,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/marked": { + "version": "18.0.13", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.13.tgz", + "integrity": "sha512-xTxVzZsBFwunP6HDmtBkabUQEYArnP7/rMDGmPj9SlrKlQ4i8MdYVow+nJL0eOqwpUqhzBoTBRADGN6uYwPyOw==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -10913,6 +11926,65 @@ "dev": true, "license": "MIT" }, + "node_modules/mermaid": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-12.0.0.tgz", + "integrity": "sha512-/wQXC9iBxoGV8p3erbvaXs9h77VyLDBH6GdayVjj3hEcSQhFU4N1WUhUppotCEqlIxI2pRMwjwBSwTB1MfZBgQ==", + "license": "MIT", + "dependencies": { + "@braintree/sanitize-url": "^7.1.2", + "@iconify/utils": "^3.0.2", + "@mermaid-js/parser": "^2.0.0", + "@types/d3": "^7.4.3", + "@upsetjs/venn.js": "^2.0.0", + "chevrotain": "~11.1.2", + "cytoscape": "^3.34.0", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.14", + "dayjs": "^1.11.21", + "dompurify": "^3.4.12", + "elkjs": "^0.9.3", + "es-toolkit": "^1.45.1", + "katex": "^0.16.47", + "khroma": "^2.1.0", + "marked": "^16.3.0", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", + "ts-dedent": "^2.2.0", + "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/mermaid/node_modules/marked": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/mermaid/node_modules/uuid": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.2.tgz", + "integrity": "sha512-xZe/16rV4aa+HGSOCiY2YeLT1OybRLrrkL/Rqaq7p7GMVXjFh+6wN4oMYgjFmnSnhY8t6Xpdl2l9qmnHYuMHwQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -11629,6 +12701,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-manager-detector": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.8.0.tgz", + "integrity": "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==", + "license": "MIT" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -11778,6 +12856,12 @@ "node": ">= 0.8" } }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -11880,6 +12964,22 @@ "node": ">=16.0.0" } }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "license": "MIT" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "license": "MIT", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, "node_modules/postcss": { "version": "8.5.25", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", @@ -12511,6 +13611,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", + "license": "Unlicense" + }, "node_modules/rolldown": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.0.tgz", @@ -12555,6 +13661,18 @@ "url": "https://github.com/sponsors/Boshen" } }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "license": "MIT", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -12585,6 +13703,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, "node_modules/rxjs": { "version": "7.8.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", @@ -12637,7 +13761,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, "license": "MIT" }, "node_modules/sass": { @@ -13424,6 +14547,12 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/stylis": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz", + "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -13494,6 +14623,15 @@ "dev": true, "license": "MIT" }, + "node_modules/tinyexec": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", @@ -13561,6 +14699,15 @@ "tslib": "2" } }, + "node_modules/ts-dedent": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.3.0.tgz", + "integrity": "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==", + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 847b8dd..62442b0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -20,7 +20,10 @@ "@angular/platform-browser-dynamic": "^22.1.6", "@angular/router": "^22.1.6", "@types/leaflet": "^1.9.22", + "dompurify": "^3.4.15", "leaflet": "^1.9.4", + "marked": "^18.0.13", + "mermaid": "^12.0.0", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index 389eaab..abe28a4 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -24,6 +24,10 @@ Résumé annuel + + + Specs +jusqu'à trouver un dossier + /// contenant .kiro (racine du dépôt), ounull si non trouvé. + ///diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index a0bb969..42c58c3 100644 --- a/frontend/src/app/app.routes.ts +++ b/frontend/src/app/app.routes.ts @@ -48,6 +48,16 @@ export const routes: Routes = [ { path: 'profil', component: ProfilComponent, canActivate: [authGuard] }, { path: 'vehicules', component: VehiculesComponent, canActivate: [authGuard] }, + // Documents de spécification (lecture seule) — protégés par authGuard. + { path: 'specs', redirectTo: 'specs/requirements', pathMatch: 'full' }, + { + path: 'specs/:cle', + // Lazy-load : la librairie Markdown (marked/DOMPurify) n'est chargée qu'à l'ouverture des specs. + loadComponent: () => + import('./features/specs/specs.component').then((m) => m.SpecsComponent), + canActivate: [authGuard] + }, + // Routes de données PROTEGEES : authGuard d'abord, puis profilConfigureGuard { path: 'consultation', component: ConsultationJourComponent, canActivate: [authGuard, profilConfigureGuard] }, { path: 'carnet-adresses', component: CarnetAdressesComponent, canActivate: [authGuard, profilConfigureGuard] }, diff --git a/frontend/src/app/core/models/index.ts b/frontend/src/app/core/models/index.ts index 70d7341..fa4a3e5 100644 --- a/frontend/src/app/core/models/index.ts +++ b/frontend/src/app/core/models/index.ts @@ -8,3 +8,4 @@ export * from './profil.model'; export * from './bareme.model'; export * from './adresse.model'; export * from './auth.model'; +export * from './spec.model'; diff --git a/frontend/src/app/core/models/spec.model.ts b/frontend/src/app/core/models/spec.model.ts new file mode 100644 index 0000000..3fcf294 --- /dev/null +++ b/frontend/src/app/core/models/spec.model.ts @@ -0,0 +1,19 @@ +// Modèles des documents de spécification (miroir des DTOs backend SpecDtos.cs). + +/** Métadonnées d'un document de spécification (élément du sous-menu). */ +export interface SpecDocument { + /** Clé stable et sûre : 'requirements' | 'design' | 'tasks'. */ + cle: string; + /** Libellé lisible pour le sous-menu (ex. « Exigences »). */ + titre: string; + /** Vrai si le fichier Markdown est présent et lisible côté backend. */ + disponible: boolean; +} + +/** Contenu Markdown d'un document de spécification. */ +export interface SpecContenu { + cle: string; + titre: string; + /** Contenu brut Markdown, rendu en HTML côté composant. */ + markdown: string; +} diff --git a/frontend/src/app/core/services/index.ts b/frontend/src/app/core/services/index.ts index a15ddf2..11f82a9 100644 --- a/frontend/src/app/core/services/index.ts +++ b/frontend/src/app/core/services/index.ts @@ -8,3 +8,4 @@ export * from './adresse-nommee.service'; export * from './profil.service'; export * from './bareme.service'; export * from './remboursement.service'; +export * from './spec.service'; diff --git a/frontend/src/app/core/services/spec.service.ts b/frontend/src/app/core/services/spec.service.ts new file mode 100644 index 0000000..8b213bd --- /dev/null +++ b/frontend/src/app/core/services/spec.service.ts @@ -0,0 +1,27 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable, inject } from '@angular/core'; +import { Observable } from 'rxjs'; + +import { environment } from '../../../environments/environment'; +import { SpecContenu, SpecDocument } from '../models'; + +/** + * Service HTTP d'accès aux documents de spécification du projet (exigences, + * conception technique, plan de tâches) pour affichage en lecture seule. + * Les endpoints sont protégés par l'authentification (comme les autres appels). + */ +@Injectable({ providedIn: 'root' }) +export class SpecService { + private readonly http = inject(HttpClient); + private readonly baseUrl = `${environment.apiUrl}/specs`; + + /** Liste les documents de spécification connus et leur disponibilité (GET /api/specs). */ + lister(): Observable { + return this.http.get (this.baseUrl); + } + + /** Récupère le contenu Markdown d'un document par sa clé (GET /api/specs/{cle}). */ + obtenir(cle: string): Observable { + return this.http.get (`${this.baseUrl}/${cle}`); + } +} diff --git a/frontend/src/app/features/specs/specs.component.html b/frontend/src/app/features/specs/specs.component.html new file mode 100644 index 0000000..9f212c9 --- /dev/null +++ b/frontend/src/app/features/specs/specs.component.html @@ -0,0 +1,38 @@ + + + + + + diff --git a/frontend/src/app/features/specs/specs.component.scss b/frontend/src/app/features/specs/specs.component.scss new file mode 100644 index 0000000..e67e491 --- /dev/null +++ b/frontend/src/app/features/specs/specs.component.scss @@ -0,0 +1,169 @@ +.specs { + display: flex; + gap: 1.5rem; + max-width: 1100px; + margin: 0 auto; + padding: 1rem; + align-items: flex-start; + + &__menu { + flex: 0 0 220px; + position: sticky; + top: 1rem; + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 8px; + padding: 0.75rem; + } + + &__menu-titre { + margin: 0 0 0.5rem; + font-size: 0.85rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.03em; + color: #64748b; + } + + &__liens { + display: flex; + flex-direction: column; + gap: 0.25rem; + } + + &__lien { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.5rem; + padding: 0.5rem 0.6rem; + border-radius: 6px; + color: #1f2937; + text-decoration: none; + font-size: 0.9rem; + + &:hover { + background: #eef2f7; + } + + &--actif { + background: #2563eb; + color: #fff; + font-weight: 600; + } + + &--indisponible { + color: #94a3b8; + } + } + + &__badge { + font-size: 0.65rem; + font-style: italic; + color: #b45309; + } + + &__contenu { + flex: 1 1 auto; + min-width: 0; + } + + &__titre { + margin-top: 0; + } + + &__info, + &__erreur { + padding: 0.75rem; + } + + &__erreur { + color: #b91c1c; + } +} + +/* Rendu lisible du Markdown converti (titres, texte, tableaux, code, citations). */ +.markdown-body { + line-height: 1.6; + color: #1f2937; + overflow-wrap: break-word; + word-break: break-word; + + h1, h2, h3, h4 { + line-height: 1.25; + margin: 1.4em 0 0.6em; + } + h1 { font-size: 1.7rem; border-bottom: 1px solid #e2e8f0; padding-bottom: 0.3em; } + h2 { font-size: 1.4rem; border-bottom: 1px solid #eef2f7; padding-bottom: 0.2em; } + h3 { font-size: 1.15rem; } + + p { margin: 0.6em 0; } + + a { color: #2563eb; } + + ul, ol { padding-left: 1.5rem; margin: 0.5em 0; } + li { margin: 0.2em 0; } + + code { + background: #f1f5f9; + padding: 0.1em 0.35em; + border-radius: 4px; + font-size: 0.88em; + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + } + + pre { + background: #0f172a; + color: #e2e8f0; + padding: 1rem; + border-radius: 8px; + overflow-x: auto; + + code { + background: transparent; + color: inherit; + padding: 0; + } + } + + blockquote { + margin: 0.8em 0; + padding: 0.4em 1em; + border-left: 4px solid #cbd5e1; + background: #f8fafc; + color: #475569; + } + + table { + border-collapse: collapse; + width: 100%; + margin: 1em 0; + display: block; + overflow-x: auto; + } + th, td { + border: 1px solid #e2e8f0; + padding: 0.4em 0.6em; + text-align: left; + vertical-align: top; + } + th { background: #f1f5f9; font-weight: 600; } + + hr { + border: none; + border-top: 1px solid #e2e8f0; + margin: 1.5em 0; + } + + img { max-width: 100%; } + + /* Diagrammes Mermaid rendus (SVG) : centrés, sans le fond sombre des blocs de code. */ + pre.mermaid { + background: transparent; + padding: 0; + text-align: center; + overflow-x: auto; + + svg { max-width: 100%; height: auto; } + } +} diff --git a/frontend/src/app/features/specs/specs.component.ts b/frontend/src/app/features/specs/specs.component.ts new file mode 100644 index 0000000..5d15b85 --- /dev/null +++ b/frontend/src/app/features/specs/specs.component.ts @@ -0,0 +1,195 @@ +import { CommonModule } from '@angular/common'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + OnDestroy, + OnInit, + ViewChild, + inject, + signal +} from '@angular/core'; +import { ActivatedRoute, RouterLink, RouterLinkActive } from '@angular/router'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; +import { Subscription } from 'rxjs'; +import { marked } from 'marked'; +import DOMPurify from 'dompurify'; + +import { SpecDocument } from '../../core/models'; +import { SpecService } from '../../core/services'; + +/** + * `SpecsComponent` — consultation en lecture seule des documents de spécification + * (exigences, conception technique, plan de tâches) rendus depuis le Markdown. + * + * Le menu (colonne gauche) liste les documents disponibles ; le document courant est + * déterminé par le paramètre de route `:cle`. Le Markdown est converti en HTML par + * `marked`, puis assaini par `DOMPurify` avant affichage (le contenu provient du + * backend mais on assainit par principe défensif). + * + * Les blocs de code ` ```mermaid ` (ex. `graph TB`, `flowchart LR`) sont rendus en + * diagrammes SVG via **Mermaid.js**, importé dynamiquement au premier rendu afin de + * ne pas alourdir le chargement initial de la page. + */ +@Component({ + selector: 'app-specs', + standalone: true, + imports: [CommonModule, RouterLink, RouterLinkActive], + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './specs.component.html', + styleUrl: './specs.component.scss' +}) +export class SpecsComponent implements OnInit, OnDestroy { + private readonly specService = inject(SpecService); + private readonly route = inject(ActivatedRoute); + private readonly sanitizer = inject(DomSanitizer); + private readonly cdr = inject(ChangeDetectorRef); + + /** Conteneur du HTML rendu, utilisé pour lancer le rendu Mermaid après injection. */ + @ViewChild('markdownHost') private markdownHost?: ElementRef+ @if (chargement()) { + +Chargement du document…
+ } + + @if (erreur()) { +{{ erreur() }}
+ } + + @if (!chargement() && !erreur() && contenuHtml()) { +{{ titreCourant() }}
+ + + } +; + + /** Documents disponibles pour le menu latéral. */ + readonly documents = signal ([]); + + /** Clé du document actuellement affiché. */ + readonly cleCourante = signal (null); + + /** Titre du document courant (pour l'en-tête). */ + readonly titreCourant = signal (''); + + /** HTML rendu (assaini) du document courant. */ + readonly contenuHtml = signal (null); + + /** Chargement en cours du contenu. */ + readonly chargement = signal(false); + + /** Message d'erreur de chargement (contenu ou liste). */ + readonly erreur = signal (null); + + private listeSub?: Subscription; + private contenuSub?: Subscription; + private paramSub?: Subscription; + + /** Renderer marked partagé : les blocs `mermaid` deviennent ` `. */ + private readonly markedInstance = this.creerMarked(); + + ngOnInit(): void { + // Charge la liste du menu (indépendante du document courant). + this.listeSub = this.specService.lister().subscribe({ + next: (docs) => { + this.documents.set(docs); + this.cdr.markForCheck(); + }, + error: () => { + this.erreur.set('Impossible de charger la liste des documents.'); + this.cdr.markForCheck(); + } + }); + + // Réagit au paramètre de route `:cle` pour charger le document demandé. + this.paramSub = this.route.paramMap.subscribe((params) => { + const cle = params.get('cle'); + if (cle) { + this.chargerDocument(cle); + } + }); + } + + ngOnDestroy(): void { + this.listeSub?.unsubscribe(); + this.contenuSub?.unsubscribe(); + this.paramSub?.unsubscribe(); + } + + /** Charge et rend le document Markdown identifié par `cle`. */ + private chargerDocument(cle: string): void { + this.contenuSub?.unsubscribe(); + this.cleCourante.set(cle); + this.chargement.set(true); + this.erreur.set(null); + this.contenuHtml.set(null); + this.cdr.markForCheck(); + + this.contenuSub = this.specService.obtenir(cle).subscribe({ + next: (doc) => { + this.titreCourant.set(doc.titre); + // marked (GFM) → HTML (les blocs mermaid →), + // puis assainissement DOMPurify avant injection. + const html = this.markedInstance.parse(doc.markdown, { async: false }) as string; + const propre = DOMPurify.sanitize(html, { + // Conserve la classe (indispensable pour cibler .mermaid) ; le SVG généré + // par Mermaid est inséré APRÈS sanitisation, donc pas besoin de l'autoriser ici. + ADD_ATTR: ['class'] + }); + this.contenuHtml.set(this.sanitizer.bypassSecurityTrustHtml(propre)); + this.chargement.set(false); + this.cdr.markForCheck(); + // Rendu des diagrammes Mermaid une fois le HTML injecté dans le DOM et le + // @ViewChild résolu (setTimeout = macrotask, après le cycle de détection). + setTimeout(() => void this.rendreMermaid(), 0); + }, + error: () => { + this.chargement.set(false); + this.erreur.set('Ce document est introuvable ou n\'a pas pu être chargé.'); + this.cdr.markForCheck(); + } + }); + } + + /** + * Rend en SVG les blocs `` présents dans le contenu injecté. + * Mermaid est importé dynamiquement (chunk séparé) et n'est chargé qu'ici. + */ + private async rendreMermaid(): Promise{ + const hote = this.markdownHost?.nativeElement; + if (!hote) { + return; + } + const blocs = hote.querySelectorAll ('pre.mermaid'); + if (blocs.length === 0) { + return; + } + + try { + const mermaid = (await import('mermaid')).default; + mermaid.initialize({ startOnLoad: false, theme: 'default', securityLevel: 'loose' }); + // `run` remplace le contenu des éléments ciblés par le SVG rendu. + await mermaid.run({ nodes: Array.from(blocs) }); + } catch { + // En cas d'échec de rendu d'un diagramme, le bloc de code brut reste visible (repli). + } + } + + /** + * Construit une instance `marked` dont le renderer de bloc de code émet + * ` …` pour le langage `mermaid`, et le rendu standard + * (avec échappement) pour les autres langages. + */ + private creerMarked(): typeof marked { + marked.use({ + renderer: { + code(this: unknown, { text, lang }: { text: string; lang?: string }): string { + if ((lang ?? '').trim().toLowerCase() === 'mermaid') { + // Le texte Mermaid est inséré tel quel dans; + // il est traité comme définition de diagramme, jamais comme HTML. + return `${SpecsComponent.echapper(text)}`; + } + const langue = (lang ?? '').trim(); + const classe = langue ? ` class="language-${SpecsComponent.echapper(langue)}"` : ''; + return ``; + } + } + }); + return marked; + } + + /** Échappe les caractères HTML sensibles. */ + private static echapper(valeur: string): string { + return valeur + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); + } +}${SpecsComponent.echapper(text)}