Bruno Charest b1f142c4f7 feat: enhance Excalidraw editor UI and autosave functionality
- Redesigned editor toolbar with improved layout and accessibility attributes
- Added automatic save after 7 seconds of inactivity for better data protection
- Replaced emoji icons with SVG icons for consistent styling and accessibility
- Added success toasts for PNG/SVG exports with destination path
- Updated button styles and layout for better visual hierarchy
- Added aria-labels and improved keyboard navigation support
- Made action buttons optionally hideable via
2025-10-28 22:25:40 -04:00
2025-09-27 18:13:02 -04:00
2025-09-27 18:13:02 -04:00
2025-09-27 18:13:02 -04:00
2025-09-27 18:13:02 -04:00
2025-09-27 18:13:02 -04:00
2025-10-03 14:24:16 -04:00
2025-10-03 14:24:16 -04:00
2025-09-27 18:13:02 -04:00
2025-10-03 14:24:16 -04:00
2025-09-27 18:13:02 -04:00

🌌 ObsiViewer — Explorateur de voûte Obsidian

📋 Description

ObsiViewer est une application web Angular 20 moderne et performante qui permet d'explorer et visualiser une voûte Obsidian en lecture seule. Conçue comme une alternative légère pour consulter vos notes Markdown depuis n'importe quel navigateur, elle offre une expérience riche avec navigation fluide, rendu Markdown avancé, visualisation interactive du graphe de connaissances et recherche compatible Obsidian.

Cas d'usage principaux

  • 📖 Consultation de votre vault Obsidian depuis un navigateur
  • 🌐 Partage de vos notes en lecture seule (hébergement web)
  • 📱 Accès mobile optimisé à votre base de connaissances
  • 🔍 Exploration visuelle des liens entre vos notes
  • 📊 Analyse du vault via graphe et statistiques
  • 🎨 Thèmes personnalisables
  • 📚 Support des notes Excalidraw
  • 📝 Support des notes Markdown
  • 📝 Mode Lecture et Édition
  • 📝 Mode Dark et Light
  • 📝 Mode Mobile et Desktop

📝 Mode démo par défaut : l'interface fonctionne avec des données générées par VaultService. Connectez-la à vos fichiers Markdown réels via le serveur Express pour une utilisation complète.


Fonctionnalités Principales

🗂️ Navigation et Organisation

  • Explorateur de fichiers : Arborescence complète avec dossiers pliables/dépliables
  • Recherche avancée : Moteur de recherche compatible Obsidian avec opérateurs (path:, tag:, line:, etc.)
  • Vue Tags : Visualisation et filtrage par tags avec compteurs
  • Calendrier : Navigation temporelle par dates de création/modification
  • Favoris (Bookmarks) : Gestion complète compatible avec .obsidian/bookmarks.json
  • Breadcrumbs : Navigation contextuelle avec fil d'Ariane

📝 Rendu Markdown

  • Markdown enrichi : Support complet de la syntaxe Obsidian
  • Wikilinks : [[liens internes]] avec preview au survol et navigation
  • Callouts : Blocs d'information stylisés (note, warning, info, etc.)
  • Syntax highlighting : Coloration syntaxique pour 100+ langages via highlight.js
  • Diagrammes Mermaid : Rendu des diagrammes et flowcharts
  • Mathématiques : Support LaTeX/KaTeX pour formules mathématiques
  • Tables avancées : Multi-markdown tables avec fusion de cellules
  • Task lists : Listes de tâches interactives
  • Footnotes : Notes de bas de page
  • Embeddings : Images et attachements avec résolution intelligente

🕸️ Graphe de Connaissances

  • Visualisation interactive : Graphe de liens basé sur d3-force
  • Physique réaliste : Simulation de forces pour placement optimal
  • Drag & Drop : Déplacement et fixation des nœuds
  • Filtres avancés : Tags, attachments, orphelins, recherche
  • Color Groups : Coloration personnalisée par requêtes
  • Paramètres complets : 14+ options (forces, apparence, affichage)
  • Persistance : Sauvegarde dans .obsidian/graph.json
  • Mode focus : Centré sur une note avec profondeur configurable

🔍 Recherche et Filtrage

  • Moteur de recherche Obsidian-compatible avec tous les opérateurs
  • Backend Meilisearch : Recherche ultra-rapide (15-50ms) sans gel UI
  • Recherche en temps réel : Live search avec debounce intelligent (300ms)
  • Assistant de requêtes : Autocomplétion intelligente avec suggestions
  • Historique : Mémorisation des 10 dernières recherches par contexte
  • Highlighting : Mise en évidence des résultats
  • Tri multiple : Pertinence, nom, date de modification
  • Preview : Extraits contextuels dans les résultats

🎨 Interface Utilisateur

  • Design moderne : Interface soignée inspirée d'Obsidian
  • Dark/Light mode : Thèmes clair et sombre avec transition fluide
  • Responsive : Optimisé desktop, tablette et mobile
  • Sidebar redimensionnable : Ajustement de la largeur des panneaux
  • Keyboard shortcuts : Raccourcis clavier (Alt+R, Alt+D)
  • Animations fluides : 60fps avec optimisations performance

🧭 Migration UI — Tags (nouveau gestionnaire)

Depuis cette version, la gestion des tags a été refondue pour une UX claire et performante.

  • Lecture: un bouton icône Tag situé à gauche des chips ouvre/ferme l'éditeur. Cliquer un chip applique un filtre sur NotesList.
  • Édition: l'éditeur est un overlay moderne avec dé-dupe forte, suggestions et raccourcis (Enter/Tab/Backspace). Aucune fermeture par clic extérieur ou touche ESC.

Intégration

  • Composant lecture/commande: src/app/shared/tags/tag-manager/tag-manager.component.ts
  • Overlay édition: src/app/shared/tags/tag-editor-overlay/
  • Store de filtre: src/app/core/stores/tag-filter.store.ts
  • Utilitaires: src/app/shared/tags/tag-utils.ts

API composants

  • TagManagerComponent
    • @Input() tags: string[]
    • @Input() noteId: string
    • @Output() tagSelected(tag: string) (lecture)
    • @Output() editingChanged(isEditing: boolean)
    • @Output() saved(tags: string[]) (après Enregistrer)

Comportements clés

  • Lédition ne se déclenche QUE via licône Tag.
  • En lecture, cliquer un chip: met à jour TagFilterStore et filtre la liste des notes. Un badge “Filtre: #tag” apparaît avec action “Effacer le filtre”.
  • Sauvegarde des tags via VaultService.updateNoteTags(noteId, tags) qui réécrit proprement le frontmatter tags:.

Tests

  • tag-utils.spec.ts couvre normalizeTag et uniqueTags.
  • tag-manager.component.spec.ts vérifie lémission de tagSelected.

✏️ Dessins Excalidraw

  • Éditeur intégré : Ouvrez et modifiez des fichiers .excalidraw directement dans l'app
  • Création rapide : Bouton "Nouveau dessin" dans l'en-tête (icône +)
  • Autosave : Sauvegarde automatique après 800ms d'inactivité
  • Exports : Boutons PNG et SVG pour générer des images (sidecars .png/.svg)
  • Compatibilité Obsidian : Support des formats JSON et Markdown avec compressed-json
  • Thème synchronisé : Mode clair/sombre suit les préférences de l'app

🧰 Prérequis

  • Node.js 20+ et npm (node --version)
  • Angular CLI (facultatif mais pratique) : npm install -g @angular/cli
  • Docker (facultatif) pour utiliser les scripts situés dans docker/ et docker-compose/

⚙️ Installation & Configuration

Installation des dépendances

npm install

Configuration des variables d'environnement

Copiez .env.example vers .env et ajustez les valeurs:

cp .env.example .env

Variables principales:

# Chemin vers votre vault Obsidian (absolu ou relatif)
VAULT_PATH=./vault

# Configuration Meilisearch (recherche backend)
MEILI_MASTER_KEY=devMeiliKey123
MEILI_HOST=http://127.0.0.1:7700

# Port du serveur backend
PORT=4000

Scripts de développement

# Frontend Angular seul (mode démo avec données générées)
npm run dev              # http://localhost:3000

# Backend Express + API
node server/index.mjs    # http://localhost:4000

# Avec variables d'environnement
VAULT_PATH=/path/to/vault MEILI_MASTER_KEY=devMeiliKey123 node server/index.mjs

# Build production
npm run build            # Compile dans dist/
npm run preview          # Sert la build de prod

Démarrage complet en mode DEV

Pour un environnement de développement complet avec recherche Meilisearch:

# 1. Configurer les variables
cp .env.example .env
# Éditer .env et définir VAULT_PATH vers votre vault

# 2. Lancer Meilisearch
npm run meili:up

# 3. Indexer le vault
npm run meili:reindex

# 4. Lancer le backend (dans un terminal)
VAULT_PATH=/path/to/vault MEILI_MASTER_KEY=devMeiliKey123 node server/index.mjs

# 5. Lancer le frontend (dans un autre terminal)
npm run dev

Accès:


🏗️ Architecture

Stack Technique

Technologie Version Usage
Angular 20.3.x Framework (Signals, Standalone Components)
TypeScript 5.8.x Langage (mode strict)
TailwindCSS 3.4.x Styling et design system
Angular CDK 20.2.x Overlay, drag-drop
RxJS 7.8.x Programmation réactive
Express 5.1.x API REST backend
Chokidar 4.0.x File watching
Meilisearch 0.44.x Moteur de recherche (backend)
Markdown-it 14.1.x Parsing/rendu Markdown
highlight.js 11.10.x Syntax highlighting
mermaid 11.12.x Diagrammes
d3-force 3.0.x Physique du graphe
date-fns 4.1.x Dates

Patterns

  • Signals pour la réactivité fine-grained
  • Standalone Components (sans NgModules)
  • Repository Pattern (FileSystem / Server / Mémoire)
  • Services pour la logique métier
  • Change Detection OnPush
  • Bootstrap sans Zone.js

Structure du Projet

ObsiViewer/
├── src/
│   ├── app/
│   │   ├── core/                 # Services core (download, theme)
│   │   └── graph/                # Graph settings & runtime
│   ├── components/               # UI (bookmarks, graph, search, calendar, etc.)
│   ├── core/                     # bookmarks/, graph/, search/, services/
│   ├── services/                 # vault.service, markdown.service, etc.
│   ├── shared/                   # Overlays, composants communs
│   ├── styles/                   # CSS globaux, tokens
│   ├── types/                    # Types TS
│   └── app.component.ts          # Composant racine
├── server/index.mjs              # API Express + fichiers statiques
├── docker*/                      # Docker & Compose
├── docs/                         # Documentation détaillée
├── vault/                        # Voûte Obsidian (créée au démarrage)
├── index.tsx                     # Bootstrap Angular
├── angular.json, tsconfig.json   # Configs
└── package.json                  # Dépendances

🔌 Configurer lAPI locale (optionnel)

npm run build            # génère dist/
node server/index.mjs    # lance l'API + serveur statique sur http://localhost:4000

Assurez-vous que vos notes Markdown se trouvent dans vault/. L'API expose :

Endpoints Principaux

Méthode Endpoint Description
GET /api/health État du serveur
GET /api/vault Liste complète des notes avec contenu
GET /api/vault/events Server-Sent Events pour live reload
GET /api/files/metadata Métadonnées de tous les fichiers
GET /api/files/by-date Fichiers créés/modifiés à une date
GET /api/files/by-date-range Fichiers dans un intervalle
GET /api/vault/bookmarks Favoris .obsidian/bookmarks.json
PUT /api/vault/bookmarks Sauvegarde favoris (avec If-Match)
GET /api/vault/graph Configuration graphe .obsidian/graph.json
PUT /api/vault/graph Sauvegarde configuration graphe
GET /api/attachments/resolve Résolution intelligente d'attachements

Fonctionnalités Serveur

  • Live Reload : Détection des changements de fichiers via Chokidar
  • Server-Sent Events : Notifications en temps réel des modifications
  • Résolution d'attachements : Recherche intelligente dans l'arborescence
  • Atomic Writes : Écritures sécurisées avec fichiers temporaires
  • Conflict Detection : Gestion des conflits avec hash de révision
  • CORS Safe : Configuration proxy pour développement

🔍 Recherche Meilisearch (Backend)

ObsiViewer intègre Meilisearch pour une recherche côté serveur ultra-rapide avec typo-tolerance, highlights et facettes. Cette fonctionnalité remplace la recherche O(N) frontend par un backend optimisé ciblant P95 < 150ms sur 1000+ notes.

Avantages

  • Performance : Recherche indexée avec P95 < 150ms même sur de grandes voûtes
  • Typo-tolerance : Trouve "obsiviewer" même si vous tapez "obsever" (1-2 typos)
  • Highlights serveur : Extraits avec <mark> déjà calculés côté backend
  • Facettes : Distribution par tags, dossiers, année, mois
  • Opérateurs Obsidian : Support de tag:, path:, file: combinables

🚀 Démarrage Rapide

1. Lancer Meilisearch avec Docker

npm run meili:up          # Lance Meilisearch sur port 7700
npm run meili:reindex     # Indexe toutes les notes du vault

2. Configuration

Variables d'environnement (fichier .env à la racine):

VAULT_PATH=./vault
MEILI_HOST=http://127.0.0.1:7700
MEILI_MASTER_KEY=devMeiliKey123

Important: Le backend Express et l'indexeur Meilisearch utilisent tous deux VAULT_PATH pour garantir la cohérence.

Angular : Activer dans src/core/logging/environment.ts:

export const environment = {
  USE_MEILI: true,  // Active la recherche Meilisearch
  // ...
};

3. Utilisation

API REST :

# Recherche simple
curl "http://localhost:4000/api/search?q=obsidian"

# Avec opérateurs Obsidian
curl "http://localhost:4000/api/search?q=tag:work path:Projects"

# Reindexation manuelle
curl -X POST http://localhost:4000/api/reindex

Angular :

// Le SearchOrchestratorService délègue automatiquement à Meilisearch
searchOrchestrator.execute('tag:dev file:readme');

📊 Endpoints Meilisearch

Méthode Endpoint Description
GET /api/search Recherche avec query Obsidian (q, limit, offset, sort, highlight)
POST /api/reindex Réindexation complète du vault

🔧 Scripts NPM

npm run meili:up         # Lance Meilisearch (Docker)
npm run meili:down       # Arrête Meilisearch
npm run meili:reindex    # Réindexe tous les fichiers .md
npm run meili:rebuild    # up + reindex (tout-en-un)
npm run bench:search     # Benchmark avec autocannon (P95, avg, throughput)

🎯 Opérateurs Supportés

Opérateur Exemple Description
tag: tag:work Filtre par tag exact
path: path:Projects/Angular Filtre par dossier parent
file: file:readme Recherche dans le nom de fichier
Texte libre obsidian search Recherche plein texte avec typo-tolerance

Combinaisons : tag:dev path:Projects file:plan architecture

🏗️ Architecture

┌─────────────┐      ┌─────────────┐      ┌─────────────┐
│   Angular   │─────▶│   Express   │─────▶│ Meilisearch │
│   (UI)      │      │   (API)     │      │   (Index)   │
└─────────────┘      └─────────────┘      └─────────────┘
                            │
                            ▼
                     ┌─────────────┐
                     │  Chokidar   │
                     │  (Watch)    │
                     └─────────────┘

Indexation :

  • Initiale : npm run meili:reindex (batch de 750 docs)
  • Incrémentale : Chokidar détecte add/change/unlink et met à jour Meilisearch automatiquement

Documents indexés :

{
  "id": "Projects/Angular/App.md",
  "title": "Mon Application",
  "content": "Texte sans markdown...",
  "file": "App.md",
  "path": "Projects/Angular/App.md",
  "tags": ["angular", "dev"],
  "properties": { "author": "John" },
  "headings": ["Introduction", "Setup"],
  "parentDirs": ["Projects", "Projects/Angular"],
  "year": 2025,
  "month": 10,
  "excerpt": "Premiers 500 caractères..."
}

🧪 Tests & Performance

Benchmark :

npm run bench:search
# Teste 5 requêtes avec autocannon (20 connexions, 10s)
# Affiche P95, moyenne, throughput

Exemples de requêtes :

q=*                                   # Toutes les notes
q=tag:work notes                      # Tag + texte libre
q=path:Projects/Angular tag:dev       # Dossier + tag
q=file:readme                         # Nom de fichier
q=obsiviewer searh                    # Typo volontaire (→ "search")

Objectif P0 : P95 < 150ms sur 1000 notes (machine dev locale)

🔒 Sécurité

  • Clé Meili en variable d'env (MEILI_API_KEY)
  • Jamais de secrets hardcodés dans le repo
  • Docker Compose expose port 7700 (changez si prod)

📦 Dépendances Serveur

Ajoutées automatiquement dans package.json :

  • meilisearch : Client officiel
  • gray-matter : Parse frontmatter YAML
  • remove-markdown : Nettoyage du texte
  • fast-glob : Recherche rapide de fichiers
  • pathe : Chemins cross-platform

Gestion des favoris (Bookmarks)

ObsiViewer implémente une gestion complète des favoris 100% compatible avec Obsidian, utilisant <vault>/.obsidian/bookmarks.json comme source unique de vérité.

Fonctionnalités

  • Synchronisation bidirectionnelle : Les modifications dans ObsiViewer apparaissent dans Obsidian et vice-versa
  • Deux modes d'accès :
    • File System Access API (préféré) : Sélectionnez votre dossier vault directement depuis le navigateur
    • Serveur Bridge : L'API Express lit/écrit le fichier .obsidian/bookmarks.json
  • Opérations complètes : Créer, modifier, supprimer, réorganiser, grouper
  • Import/Export : Importer ou exporter vos favoris au format JSON Obsidian
  • Détection de conflits : Alerte si le fichier a été modifié en externe avec options de résolution
  • Sauvegarde automatique : Debouncing de 800ms pour éviter les écritures excessives
  • Interface responsive : Optimisée pour desktop et mobile avec thèmes clair/sombre

Comment utiliser

Mode Navigateur (File System Access API)

  1. Cliquez sur "Connect Vault" dans le panneau Favoris
  2. Sélectionnez le dossier racine de votre vault Obsidian
  3. Accordez les permissions de lecture/écriture
  4. ObsiViewer accède directement à .obsidian/bookmarks.json

⚠️ Nécessite Chrome 86+, Edge 86+, ou Opera 72+ (pas de support Firefox/Safari)

Mode Serveur

node server/index.mjs

Le serveur lit/écrit automatiquement vault/.obsidian/bookmarks.json.

Structure des données

Format JSON compatible Obsidian :

{
  "items": [
    {
      "type": "group",
      "ctime": 1704067200000,
      "title": "Mes Notes",
      "items": [
        {
          "type": "file",
          "ctime": 1704067201000,
          "path": "Notes/important.md",
          "title": "Note Importante"
        }
      ]
    }
  ]
}

Types supportés : group, file (dossiers, recherches, headings, blocks parsés mais non affichés).

Architecture technique

src/core/bookmarks/
├── types.ts                    # Types TypeScript
├── bookmarks.utils.ts          # Opérations arbre + validation
├── bookmarks.repository.ts     # Adapters de persistance
└── bookmarks.service.ts        # Service Angular avec Signals

src/components/
├── bookmarks-panel/            # Composant principal
└── bookmark-item/              # Item d'arborescence

Le service utilise les Signals Angular pour la réactivité et implémente un système de sauvegarde automatique avec debounce.


🐳 Exécution avec Docker (facultatif)

  • docker/build-img.ps1 / docker/deploy-img.ps1 pour construire et pousser une image locale
  • docker/Dockerfile.origi pour une build manuelle (docker build ...)
  • docker-compose/docker-compose.yml pour orchestrer un conteneur préconfiguré

💡 Pensez à personnaliser les fichiers .env (.env.local, docker-compose/.env) avant toute utilisation réelle.


🧪 Tests

Tests unitaires

Scripts disponibles:

npm run test:markdown  # src/services/markdown.service.spec.ts
# Ou exécuter un test spécifique (exemples):
node --loader ts-node/esm --test src/services/wikilink-parser.service.spec.ts
node --loader ts-node/esm --test src/core/bookmarks/bookmarks.service.spec.ts
node --loader ts-node/esm --test src/core/bookmarks/bookmarks.utils.spec.ts
node --loader ts-node/esm --test src/core/search/search-parser.spec.ts

Tests manuels recommandés

  • Wikilinks: [[note]], [[note|Alias]], [[note#Section]], [[note#^block]] → navigation + preview au survol.
  • Graph View: drag & drop, options (forces, affichage), groupes colorés, filtres (tags/attachments/orphans).
  • Bookmarks: connexion vault, création/édition/suppression, drag & drop, import/export, détection de conflits.
  • Recherche: opérateurs path:, file:, tag:, line:, section:, [property], OR/NOT/()``, exact phrases, wildcard, regex.

🛠️ Développement

Standards de code

  • TypeScript strict, architecture Angular 20 (Standalone + Signals)
  • ChangeDetection OnPush, composantisation claire
  • TailwindCSS (dark mode via classe dark/[data-theme="dark"])

Conventions

  • Composants: kebab-case.component.ts, Services: kebab-case.service.ts
  • Types/Interfaces: PascalCase, constantes: SCREAMING_SNAKE_CASE

Variables denvironnement

Variable Description Défaut
PORT Port du serveur Express 4000
NODE_ENV Environnement development
VAULT_PATH Chemin du vault (optionnel) ./vault

Contribution

  1. Fork → branche feature/x
  2. Commits conventionnels (feat:, fix:, docs:...)
  3. PR avec description claire + captures si UI

🔐 Sécurité

  • Lecture seule des notes (écriture uniquement sur .obsidian/bookmarks.json et .obsidian/graph.json)
  • Pas dauthentification intégrée (prévoir un reverse proxy/SSO en production)
  • Résolution dattachements sécurisée (vérifications dexistence)
  • Échappement du HTML pour réduire les risques XSS via Markdown
  • Ne pas committer de secrets .env

📈 Performance

  • Wikilinks: ~30ms/100 liens (voir IMPLEMENTATION_SUMMARY.md)
  • Graph 1000 nœuds: ~400ms initial, 60fps animation
  • Recherche complexe 2000+ notes: <200ms (voir SEARCH_COMPLETE.md)
  • Optimisations: Signals, OnPush, LRU cache (previews), debounce (250800ms), rAF, zoneless bootstrap

🐛 Problèmes connus

  • File System Access API non supportée sur Firefox/Safari → utiliser le mode serveur Express
  • Zoom/Pan du graphe non implémenté (prévu via d3-zoom)
  • bookmark-item: édition inline TODO (voir commentaire dans le code)
  • Contexte menu z-index (UI) à ajuster dans SCSS
  • Très grands vaults (>5000 notes): envisager filtrage/virtualisation

⚠️ Points dattention

  • Les scripts Docker hérités supposent la présence dun dossier server/ et dun schéma db/schema.sql (non inclus ici).
  • Les secrets fournis en exemple dans les fichiers .env doivent être remplacés avant toute utilisation en production.
  • Le rendu Markdown peut nécessiter des adaptations pour des voûtes Obsidian complexes.

📝 Changelog

Voir les fichiers de documentation pour historiques détaillés :

  • IMPLEMENTATION_SUMMARY.md - Wikilinks & Graph View
  • SEARCH_COMPLETE.md - Système de recherche
  • docs/BOOKMARKS_CHANGELOG.md - Évolution des favoris
  • GRAPH_VIEW_SEARCH_IMPLEMENTATION.md - Graph & Search Assistant

Version Actuelle : 0.0.0 (Développement)

Fonctionnalités majeures :

  • Navigation complète vault Obsidian
  • Rendu Markdown enrichi avec wikilinks
  • Graph View interactif avec parité Obsidian
  • Système de recherche avancée
  • Gestion bookmarks synchronisée
  • Live reload avec SSE
  • Dark/Light mode
  • Responsive mobile

📄 Licence

Pas de licence spécifiée - Usage libre pour développement et consultation.

⚠️ Note : Ajoutez une licence appropriée (MIT, Apache 2.0, GPL, etc.) avant publication ou distribution.

👥 Auteurs et Contributeurs

Développement principal : Projet développé avec assistance AI (Claude/Anthropic)

Technologies et Inspirations :

🔗 Liens Utiles

Documentation Projet

Resources Externes

API Documentation


🎯 Quick Start Complet

# 1. Installation
git clone <repo-url>
cd ObsiViewer
npm install

# 2. Démarrage développement (mode demo)
npm run dev
# → Ouvrir http://localhost:3000

# 3. Avec votre vault (mode serveur)
mkdir -p vault
# Copier vos notes .md dans vault/
npm run build
node server/index.mjs
# → Ouvrir http://localhost:4000

# 4. Production
npm run build
# Déployer le contenu de dist/ sur votre hébergement

Bonne exploration dans ObsiViewer !

💡 Besoin d'aide ? Consultez la documentation dans /docs ou ouvrez une issue.

🚀 Prêt pour la production ? Voir ROADMAP.md pour les prochaines étapes.

Description
ObsiViewer
Readme 46 MiB
Languages
TypeScript 69.5%
JavaScript 12.5%
HTML 9.3%
CSS 5.9%
PowerShell 1.2%
Other 1.5%