ObsiViewer/docs/NIMBUS_INLINE_EDITING_MODE.md
Bruno Charest ee3085ce38 feat: add Nimbus Editor with Unsplash integration
- Integrated Unsplash API for image search functionality with environment configuration
- Added new Nimbus Editor page component with navigation from sidebar and mobile drawer
- Enhanced TOC with highlight animation for editor heading navigation
- Improved CDK overlay z-index hierarchy for proper menu layering
- Removed obsolete logging validation script
2025-11-11 11:38:27 -05:00

9.2 KiB
Raw Blame History

Mode d'édition inline Nimbus - Documentation technique

📋 Vue d'ensemble

Le mode d'édition Nimbus suit le concept WYSIWYG par blocs, inspiré de Notion, avec une toolbar inline intégrée dans chaque bloc plutôt qu'une barre fixe.

🎯 Concepts clés

1. Toolbar inline par bloc

Chaque bloc affiche sa propre toolbar au survol ou au focus:

  • Position: Intégrée directement dans la ligne du bloc
  • Visibilité: Apparaît au hover ou focus
  • Drag handle: ⋮⋮ à gauche pour déplacer/ouvrir menu contextuel

2. Déclenchement du menu contextuel

Le menu "Add Block" s'ouvre de 3 façons:

  1. Caractère "/" - Frappe au début ou après espace
  2. Icône "⬇️" - Clic sur bouton "More items"
  3. Drag handle - Clic sur ⋮⋮ à gauche du bloc

3. États visuels

┌─────────────────────────────────────────────────────────┐
│ État par défaut (non focus, non hover)                  │
│   - Placeholder gris visible                            │
│   - Icônes cachées (opacity: 0)                         │
│   - Drag handle caché                                   │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│ État hover (souris au dessus)                           │
│   - Background subtil (bg-neutral-800/30)               │
│   - Icônes semi-visibles (opacity: 70%)                 │
│   - Drag handle visible                                 │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│ État focus (édition active)                             │
│   - Placeholder masqué                                  │
│   - Icônes complètement visibles (opacity: 100%)        │
│   - Drag handle visible                                 │
│   - Curseur visible                                     │
└─────────────────────────────────────────────────────────┘

🏗️ Architecture des composants

BlockInlineToolbarComponent

Fichier: src/app/editor/components/block/block-inline-toolbar.component.ts

Responsabilités:

  • Afficher le drag handle (⋮⋮) avec tooltip
  • Afficher les icônes rapides (AI, checkbox, lists, table, etc.)
  • Gérer les états hover/focus
  • Émettre les actions vers le bloc parent

Structure:

<div class="group/block">
  <!-- Drag handle (absolute left) -->
  <div class="absolute -left-8">⋮⋮</div>
  
  <!-- Input wrapper -->
  <div class="flex-1 px-3 py-2">
    <ng-content /> <!-- Contenu éditable -->
    
    <!-- Quick icons (conditional opacity) -->
    <div class="flex gap-0.5">
      <button>AI</button>
      <button></button>
      <button></button>
      <!-- ... -->
      <button>⬇️ More</button>
    </div>
  </div>
</div>

Inputs:

  • isFocused: Signal<boolean> - État focus du bloc
  • isHovered: Signal<boolean> - État hover du bloc
  • placeholder: string - Texte du placeholder

Outputs:

  • action: EventEmitter<string> - Action déclenchée (use-ai, table, more, etc.)

ParagraphBlockComponent (mis à jour)

Fichier: src/app/editor/components/block/blocks/paragraph-block.component.ts

Nouvelles fonctionnalités:

  1. Intégration de BlockInlineToolbarComponent
  2. Gestion des états isFocused et isHovered via signals
  3. Détection du "/" pour ouvrir le menu
  4. Gestion des actions de toolbar

Template structure:

<div (mouseenter)="isHovered.set(true)" (mouseleave)="isHovered.set(false)">
  <app-block-inline-toolbar
    [isFocused]="isFocused"
    [isHovered]="isHovered"
    (action)="onToolbarAction($event)"
  >
    <div
      contenteditable="true"
      (focus)="isFocused.set(true)"
      (blur)="isFocused.set(false)"
    ></div>
  </app-block-inline-toolbar>
</div>

BlockMenuComponent (optimisé)

Fichier: src/app/editor/components/palette/block-menu.component.ts

Changements:

  • Taille réduite: 420px × 500px (vs 680px × 600px)
  • Position contextuelle: S'ouvre près du bloc actif/curseur
  • Design compact: Spacing réduit, textes plus petits
  • Sticky headers: Restent visibles au scroll

Positionnement:

menuPosition = computed(() => {
  const activeBlock = document.querySelector('[contenteditable]:focus');
  if (activeBlock) {
    const rect = activeBlock.getBoundingClientRect();
    return {
      top: rect.top + 30,  // 30px sous le curseur
      left: rect.left      // Aligné à gauche
    };
  }
  return { top: 100, left: 50 }; // Fallback
});

🎨 Design tokens

Toolbar inline

/* Drag handle */
-left-8                    /* Position absolue gauche */
opacity-0                  /* Caché par défaut */
group-hover/block:opacity-100  /* Visible au hover */

/* Container */
px-3 py-2                  /* Padding interne */
hover:bg-neutral-800/30    /* Background au hover */
rounded-lg                 /* Coins arrondis */

/* Icônes */
w-4 h-4                    /* Taille icônes */
text-gray-400              /* Couleur par défaut */
hover:text-gray-200        /* Couleur au hover */

Menu contextuel

/* Panel */
bg-neutral-800/98          /* Background semi-transparent */
backdrop-blur-md           /* Effet flou */
w-[420px]                  /* Largeur fixe */
max-h-[500px]              /* Hauteur max */
rounded-lg                 /* Coins arrondis */
border-neutral-700         /* Bordure */

/* Section header (sticky) */
sticky top-0               /* Reste en haut au scroll */
bg-neutral-800/95          /* Background avec transparence */
backdrop-blur-md           /* Flou de fond */
text-[10px]                /* Texte très petit */
uppercase tracking-wider   /* Majuscules espacées */

/* Item */
px-2 py-1.5                /* Padding compact */
hover:bg-neutral-700/80    /* Background hover */
text-sm                    /* Texte petit */

🔧 Intégration dans d'autres blocs

Pour ajouter la toolbar inline à un autre type de bloc:

1. Importer le composant

import { BlockInlineToolbarComponent } from '../block-inline-toolbar.component';
import { signal } from '@angular/core';

@Component({
  imports: [BlockInlineToolbarComponent],
  // ...
})

2. Ajouter les signals

isFocused = signal(false);
isHovered = signal(false);

3. Wrapper le contenu

<div 
  (mouseenter)="isHovered.set(true)"
  (mouseleave)="isHovered.set(false)"
>
  <app-block-inline-toolbar
    [isFocused]="isFocused"
    [isHovered]="isHovered"
    (action)="onToolbarAction($event)"
  >
    <!-- Votre contenu éditable ici -->
  </app-block-inline-toolbar>
</div>

4. Gérer les événements

onToolbarAction(action: string): void {
  if (action === 'more' || action === 'menu') {
    this.paletteService.open();
  } else {
    // Logique spécifique
  }
}

📱 Responsive

Desktop

  • Drag handle à -left-8 (32px à gauche)
  • Toutes les icônes visibles
  • Menu 420px de large

Tablet

  • Drag handle visible au tap
  • Menu 90% de la largeur viewport
  • Icônes réduites

Mobile

  • Drag handle toujours visible
  • Menu plein écran
  • Toolbar simplifiée (icônes essentielles seulement)

⌨️ Raccourcis clavier

Dans un bloc

Touche Action
/ Ouvrir le menu contextuel
@ Mention (futur)
Enter Nouveau bloc paragraphe
Backspace (bloc vide) Supprimer le bloc
/ Naviguer entre blocs

Dans le menu

Touche Action
/ Naviguer dans les items
Enter Sélectionner l'item
Esc Fermer le menu
Lettres Rechercher

🚀 Améliorations futures

  1. Drag & drop - Utiliser le drag handle pour réordonner
  2. Menu bloc contextuel - Options spécifiques (dupliquer, supprimer, transformer)
  3. Formatage texte - Bold, italic, couleur via toolbar flottante sur sélection
  4. Slash commands avancés - /table 3x3, /heading 2, etc.
  5. Templates inline - Insertion rapide de structures prédéfinies
  6. Collaboration - Curseurs multiples et édition temps réel

📐 Schéma de flux

Utilisateur clique dans un bloc
  ↓
isFocused.set(true)
  ↓
Toolbar inline devient visible (opacity: 100%)
  ↓
Utilisateur tape "/" 
  ↓
PaletteService.open()
  ↓
BlockMenuComponent s'affiche près du curseur
  ↓
Utilisateur sélectionne un item
  ↓
Nouveau bloc inséré après le bloc actuel
  ↓
Focus sur le nouveau bloc

Version: 2.0
Date: 7 novembre 2025
Auteur: Nimbus Team