- 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
313 lines
9.2 KiB
Markdown
313 lines
9.2 KiB
Markdown
# 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**:
|
||
```html
|
||
<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**:
|
||
```html
|
||
<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**:
|
||
```typescript
|
||
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
|
||
|
||
```css
|
||
/* 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
|
||
|
||
```css
|
||
/* 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
|
||
|
||
```typescript
|
||
import { BlockInlineToolbarComponent } from '../block-inline-toolbar.component';
|
||
import { signal } from '@angular/core';
|
||
|
||
@Component({
|
||
imports: [BlockInlineToolbarComponent],
|
||
// ...
|
||
})
|
||
```
|
||
|
||
### 2. Ajouter les signals
|
||
|
||
```typescript
|
||
isFocused = signal(false);
|
||
isHovered = signal(false);
|
||
```
|
||
|
||
### 3. Wrapper le contenu
|
||
|
||
```html
|
||
<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
|
||
|
||
```typescript
|
||
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
|