docs: remove outdated implementation documentation files - Deleted AI_TOOLS_IMPLEMENTATION.md (296 lines) - outdated AI tools integration guide - Deleted ALIGN_INDENT_COLUMNS_FIX.md (557 lines) - obsolete column alignment fix documentation - Deleted BLOCK_COMMENTS_IMPLEMENTATION.md (400 lines) - superseded block comments implementation notes - Deleted DRAG_DROP_COLUMNS_IMPLEMENTATION.md (500 lines) - outdated drag-and-drop columns guide - Deleted INLINE_TOOLBAR_IMPLEMENTATION.md (350 lines) - obsol
14 KiB
Améliorations du Système de Colonnes
📋 Vue d'Ensemble
Trois améliorations majeures ont été apportées au système de colonnes pour une expérience utilisateur professionnelle et cohérente.
✨ Améliorations Implémentées
1. Redistribution Automatique des Largeurs ✅
Problème: Lorsqu'on supprime un bloc d'une colonne, les colonnes restantes ne s'ajustaient pas automatiquement pour occuper toute la largeur disponible.
Solution:
- Détection automatique des colonnes vides après suppression
- Suppression des colonnes vides
- Redistribution équitable des largeurs entre les colonnes restantes
Exemple:
Avant suppression (4 colonnes):
┌──────┬──────┬──────┬──────┐
│ 25% │ 25% │ 25% │ 25% │
└──────┴──────┴──────┴──────┘
Après suppression d'une colonne:
┌────────┬────────┬────────┐
│ 33% │ 33% │ 33% │
└────────┴────────┴────────┘
Code:
private deleteBlockFromColumns(blockId: string): void {
// Filtrer les blocs
let updatedColumns = this.props.columns.map(column => ({
...column,
blocks: column.blocks.filter(b => b.id !== blockId)
}));
// Supprimer les colonnes vides
updatedColumns = updatedColumns.filter(col => col.blocks.length > 0);
// Redistribuer les largeurs
if (updatedColumns.length > 0) {
const newWidth = 100 / updatedColumns.length;
updatedColumns = updatedColumns.map(col => ({
...col,
width: newWidth
}));
}
this.update.emit({ columns: updatedColumns });
}
2. Drag & Drop Fonctionnel avec le Bouton 6 Points ✅
Problème: Les blocs dans les colonnes n'avaient pas de bouton de drag & drop fonctionnel, rendant impossible la réorganisation des blocs.
Solution:
- Ajout d'un bouton drag handle avec 6 points (⋮⋮)
- Implémentation complète du drag & drop entre colonnes
- Déplacement des blocs au sein d'une même colonne
- Redistribution automatique des largeurs après déplacement
Interface:
┌─────────────────┐
│ ⋮⋮ ⋯ 💬 │ ← 6 points = drag, 3 points = menu
│ H2 Content │
└─────────────────┘
Fonctionnalités:
- ✅ Drag un bloc d'une colonne à une autre
- ✅ Réorganiser les blocs dans une colonne
- ✅ Visual feedback (curseur grabbing)
- ✅ Suppression automatique des colonnes vides
- ✅ Redistribution des largeurs
Code:
onDragStart(block: Block, columnIndex: number, blockIndex: number, event: MouseEvent): void {
this.draggedBlock = { block, columnIndex, blockIndex };
const onMove = (e: MouseEvent) => {
document.body.style.cursor = 'grabbing';
};
const onUp = (e: MouseEvent) => {
const target = document.elementFromPoint(e.clientX, e.clientY);
const blockEl = target.closest('[data-block-id]');
if (blockEl) {
const targetColIndex = parseInt(blockEl.getAttribute('data-column-index'));
const targetBlockIndex = parseInt(blockEl.getAttribute('data-block-index'));
this.moveBlock(
this.draggedBlock.columnIndex,
this.draggedBlock.blockIndex,
targetColIndex,
targetBlockIndex
);
}
};
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
}
3. Comportement Uniforme pour Tous les Types de Blocs ✅
Problème: Les blocs dans les colonnes ne se comportaient pas de la même manière que les blocs en pleine largeur. L'édition était différente, les interactions étaient incohérentes.
Solution:
- Implémentation d'éléments
contenteditabledirects pour les headings et paragraphs - Comportement d'édition identique en colonnes et en pleine largeur
- Même apparence visuelle
- Mêmes raccourcis clavier
Types de Blocs Uniformisés:
Headings (H1, H2, H3):
<h1 contenteditable="true"
class="text-xl font-bold focus:outline-none"
(input)="onContentInput($event, block.id)"
(blur)="onContentBlur($event, block.id)">
{{ getBlockText(block) }}
</h1>
Paragraphs:
<div contenteditable="true"
class="text-sm focus:outline-none"
(input)="onContentInput($event, block.id)"
(blur)="onContentBlur($event, block.id)"
[attr.data-placeholder]="'Start writing...'">
{{ getBlockText(block) }}
</div>
Avantages:
- ✅ Édition en temps réel identique
- ✅ Placeholders cohérents
- ✅ Focus states uniformes
- ✅ Pas de différence UX entre colonnes et pleine largeur
📊 Architecture Technique
Flux de Drag & Drop
User mousedown sur ⋮⋮
↓
onDragStart(block, colIndex, blockIndex)
↓
Store draggedBlock info
↓
User mousemove
↓
Update cursor to 'grabbing'
↓
User mouseup sur target block
↓
Get target column & block index
↓
moveBlock(fromCol, fromBlock, toCol, toBlock)
↓
Remove from source column
↓
Insert into target column
↓
Remove empty columns
↓
Redistribute widths
↓
Emit update event
Gestion de l'État
class ColumnsBlockComponent {
// Drag state
private draggedBlock: {
block: Block;
columnIndex: number;
blockIndex: number;
} | null = null;
private dropIndicator = signal<{
columnIndex: number;
blockIndex: number;
} | null>(null);
}
Méthodes Principales
moveBlock() - Déplace un bloc entre colonnes
private moveBlock(fromCol: number, fromBlock: number,
toCol: number, toBlock: number): void {
// 1. Copier les colonnes
const columns = [...this.props.columns];
// 2. Extraire le bloc à déplacer
const blockToMove = columns[fromCol].blocks[fromBlock];
// 3. Retirer de la source
columns[fromCol].blocks = columns[fromCol].blocks.filter((_, i) => i !== fromBlock);
// 4. Ajuster l'index cible si nécessaire
let actualToBlock = toBlock;
if (fromCol === toCol && fromBlock < toBlock) {
actualToBlock--;
}
// 5. Insérer à la cible
columns[toCol].blocks.splice(actualToBlock, 0, blockToMove);
// 6. Nettoyer et redistribuer
const nonEmpty = columns.filter(col => col.blocks.length > 0);
const newWidth = 100 / nonEmpty.length;
const redistributed = nonEmpty.map(col => ({ ...col, width: newWidth }));
// 7. Émettre l'update
this.update.emit({ columns: redistributed });
}
onContentInput() - Édition en temps réel
onContentInput(event: Event, blockId: string): void {
const target = event.target as HTMLElement;
const text = target.textContent || '';
this.onBlockUpdate({ text }, blockId);
}
deleteBlockFromColumns() - Suppression avec redistribution
private deleteBlockFromColumns(blockId: string): void {
// Filtrer, nettoyer, redistribuer
let updatedColumns = this.props.columns
.map(col => ({ ...col, blocks: col.blocks.filter(b => b.id !== blockId) }))
.filter(col => col.blocks.length > 0);
if (updatedColumns.length > 0) {
const newWidth = 100 / updatedColumns.length;
updatedColumns = updatedColumns.map(col => ({ ...col, width: newWidth }));
}
this.update.emit({ columns: updatedColumns });
}
🎯 Cas d'Usage
Use Case 1: Réorganisation par Drag & Drop
Scénario: Un utilisateur veut déplacer un bloc de la colonne 1 vers la colonne 3.
Actions:
- Hover sur le bloc dans la colonne 1
- Voir apparaître ⋮⋮ (drag) et ⋯ (menu)
- Cliquer et maintenir sur ⋮⋮
- Drag vers la colonne 3
- Relâcher sur la position désirée
Résultat:
Avant:
┌──────┬──────┬──────┐
│ [A] │ B │ C │ ← A à déplacer
│ D │ │ │
└──────┴──────┴──────┘
Après:
┌──────┬──────┬──────┐
│ D │ B │ [A] │ ← A déplacé
│ │ │ C │
└──────┴──────┴──────┘
Use Case 2: Suppression avec Redistribution
Scénario: Un utilisateur supprime tous les blocs d'une colonne.
Actions:
- Cliquer sur ⋯ d'un bloc
- Sélectionner "Delete"
- Répéter pour tous les blocs de la colonne
Résultat:
Avant (3 colonnes):
┌────────┬────────┬────────┐
│ A │ B │ C │
│ D │ │ E │
└────────┴────────┴────────┘
33.33% 33.33% 33.33%
Après suppression colonne 2:
┌────────────┬────────────┐
│ A │ C │
│ D │ E │
└────────────┴────────────┘
50% 50%
Use Case 3: Édition Cohérente
Scénario: Un utilisateur édite un heading dans une colonne.
Actions:
- Cliquer dans le texte du heading
- Taper du nouveau contenu
- Cliquer en dehors pour blur
Comportement:
- ✅ Édition en temps réel (onInput)
- ✅ Sauvegarde au blur
- ✅ Placeholder si vide
- ✅ Identique à l'édition en pleine largeur
🧪 Tests
Test 1: Redistribution des Largeurs
1. Créer 4 colonnes avec 1 bloc chacune
✅ Vérifier: Chaque colonne = 25%
2. Supprimer le bloc de la 2ème colonne
✅ Vérifier: 3 colonnes restantes
✅ Vérifier: Chaque colonne = 33.33%
3. Supprimer le bloc de la 3ème colonne
✅ Vérifier: 2 colonnes restantes
✅ Vérifier: Chaque colonne = 50%
Test 2: Drag & Drop
1. Créer 3 colonnes avec 2 blocs chacune
2. Drag le 1er bloc de col1 → col3
✅ Vérifier: Bloc déplacé vers col3
✅ Vérifier: Col1 a maintenant 1 bloc
3. Drag le dernier bloc de col2 → col1
✅ Vérifier: Bloc déplacé vers col1
✅ Vérifier: Col2 a maintenant 1 bloc
4. Drag tous les blocs vers col1
✅ Vérifier: Col2 et col3 supprimées
✅ Vérifier: Col1 = 100% de largeur
Test 3: Comportement Uniforme
1. Créer un H2 en pleine largeur
2. Créer un H2 dans une colonne
3. Éditer les deux
✅ Vérifier: Même apparence visuelle
✅ Vérifier: Même comportement d'édition
✅ Vérifier: Même placeholder
✅ Vérifier: Même style de focus
📚 API Complète
Props et Inputs
@Input({ required: true }) block!: Block<ColumnsProps>;
@Output() update = new EventEmitter<ColumnsProps>();
Méthodes Publiques
// Drag & Drop
onDragStart(block: Block, columnIndex: number, blockIndex: number, event: MouseEvent): void
// Édition
onContentInput(event: Event, blockId: string): void
onContentBlur(event: Event, blockId: string): void
// Menu
openMenu(block: Block, event: MouseEvent): void
closeMenu(): void
onMenuAction(action: MenuAction): void
// Commentaires
openComments(blockId: string): void
getBlockCommentCount(blockId: string): number
Méthodes Privées
// Manipulation des blocs
private moveBlock(fromCol: number, fromBlock: number, toCol: number, toBlock: number): void
private deleteBlockFromColumns(blockId: string): void
private duplicateBlockInColumns(blockId: string): void
private convertBlockInColumns(blockId: string, newType: string, preset: any): void
// Helpers
private getBlockText(block: Block): string
private getHeadingLevel(block: Block): number
private generateId(): string
private createDummyBlock(): Block
🎨 Interface Utilisateur
Boutons par Bloc
┌─────────────────┐
│ ⋮⋮ ⋯ 💬2│ ← Tous les boutons visibles au hover
│ │
│ H2 Content │ ← Éditable directement
│ │
└─────────────────┘
Légende:
⋮⋮ = Drag handle (6 points)
⋯ = Menu contextuel (3 points)
💬 = Commentaires
États Visuels
Normal:
- Boutons cachés (opacity: 0)
- Bordure subtile
Hover:
- Tous les boutons visibles (opacity: 100)
- Curseur pointeur sur les boutons
Dragging:
- Curseur grabbing
- Bloc source semi-transparent
- Indicateur de drop position
Editing:
- Focus outline
- Placeholder si vide
- Curseur text
✅ Checklist de Validation
Redistribution des Largeurs:
- Suppression d'un bloc vide la colonne
- Colonne vide est supprimée automatiquement
- Largeurs redistribuées équitablement
- Fonctionne avec 2, 3, 4, 5+ colonnes
Drag & Drop:
- Bouton ⋮⋮ visible au hover
- Drag entre colonnes fonctionne
- Drag dans une même colonne fonctionne
- Curseur change en grabbing
- Colonnes vides supprimées après drag
- Largeurs redistribuées après drag
Comportement Uniforme:
- Headings éditables identiquement
- Paragraphs éditables identiquement
- Placeholders cohérents
- Focus states uniformes
- Pas de différence UX visible
🚀 Améliorations Futures Possibles
-
Indicateurs visuels de drop:
- Ligne de drop indicator
- Highlight de la zone cible
- Animation de transition
-
Undo/Redo:
- Annuler un déplacement
- Annuler une suppression
- Historique des changements
-
Raccourcis clavier:
- Ctrl+Arrow pour déplacer entre colonnes
- Shift+Arrow pour réorganiser dans une colonne
- Delete pour supprimer rapidement
-
Multi-sélection:
- Sélectionner plusieurs blocs
- Déplacer en batch
- Supprimer en batch
🎉 Résultat Final
Les trois améliorations sont complètement implémentées et fonctionnelles:
- ✅ Redistribution automatique - Les largeurs s'ajustent intelligemment
- ✅ Drag & Drop complet - Réorganisation fluide et intuitive
- ✅ Comportement uniforme - UX cohérente partout
L'expérience utilisateur est maintenant professionnelle et intuitive, avec un système de colonnes robuste et flexible.
Rafraîchissez le navigateur et testez! 🚀