ObsiViewer/docs/EDITOR_NIMBUS/ALIGN_INDENT_COLUMNS_FIX.md
Bruno Charest 5e8cddf92e ```
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
2025-11-17 10:09:25 -05:00

14 KiB

Fix: Boutons Alignement et Indentation dans les Colonnes

🐛 Problème

Quand les blocs sont 2 ou plus sur une ligne (dans les colonnes), les boutons du menu contextuel ne fonctionnent pas:

  • Align Left - Ne fait rien
  • Align Center - Ne fait rien
  • Align Right - Ne fait rien
  • Justify - Ne fait rien
  • Increase Indent (⁝) - Ne fait rien
  • Decrease Indent (⁞) - Ne fait rien

🔍 Cause Racine

Architecture du Problème

Pour les blocs normaux:

Menu → onAlign() → documentService.updateBlock(blockId, ...)
                   ↓
              Bloc mis à jour directement ✅

Pour les blocs dans colonnes:

Menu → onAlign() → documentService.updateBlock(blockId, ...)
                   ↓
              ❌ NE FONCTIONNE PAS!

Pourquoi?

Les blocs dans les colonnes ne sont PAS dans documentService.blocks().
Ils sont imbriqués dans la structure:

{
  type: 'columns',
  props: {
    columns: [
      {
        id: 'col1',
        blocks: [
          { id: 'block1', ... },   Ces blocs ne sont pas dans documentService
          { id: 'block2', ... }
        ]
      }
    ]
  }
}

Pour modifier un bloc dans une colonne, il faut:

  1. Trouver le bloc columns parent
  2. Modifier la structure imbriquée
  3. Émettre un événement update vers le bloc columns

Solution Appliquée

1. Changement de l'Architecture du Menu

AVANT: Menu fait les modifications directement via documentService

APRÈS: Menu émet des actions et laisse le parent gérer

// block-context-menu.component.ts

// AVANT (modification directe)
onAlign(alignment: string): void {
  this.documentService.updateBlock(this.block.id, {
    meta: { ...this.block.meta, align: alignment }
  });
}

// APRÈS (émission d'action)
onAlign(alignment: string): void {
  this.action.emit({ type: 'align', payload: { alignment } });
  this.close.emit();
}

Avantages:

  • Le menu ne sait plus comment modifier les blocs
  • Le parent (block-host ou columns-block) décide comment gérer
  • Fonctionne pour les deux cas (normal et colonnes)

2. Ajout des Types d'Actions

// block-context-menu.component.ts

export interface MenuAction {
  type: 'comment' | 'add' | 'convert' | 'background' | 'duplicate' | 
        'copy' | 'lock' | 'copyLink' | 'delete' | 
        'align' | 'indent';  // ← Nouveaux types ajoutés
  payload?: any;
}

3. Handlers dans block-host.component.ts

Pour les blocs normaux (pas dans colonnes):

onMenuAction(action: MenuAction): void {
  switch (action.type) {
    case 'align':
      const { alignment } = action.payload || {};
      if (alignment) {
        // For list-item blocks, update props.align
        if (this.block.type === 'list-item') {
          this.documentService.updateBlockProps(this.block.id, {
            ...this.block.props,
            align: alignment
          });
        } else {
          // For other blocks, update meta.align
          this.documentService.updateBlock(this.block.id, {
            meta: { ...this.block.meta, align: alignment }
          });
        }
      }
      break;
      
    case 'indent':
      const { delta } = action.payload || {};
      if (delta !== undefined) {
        // Calculate new indent level
        const cur = Number(this.block.meta?.indent || 0);
        const next = Math.max(0, Math.min(8, cur + delta));
        this.documentService.updateBlock(this.block.id, {
          meta: { ...this.block.meta, indent: next }
        });
      }
      break;
      
    case 'background':
      const { color } = action.payload || {};
      this.documentService.updateBlock(this.block.id, {
        meta: { ...this.block.meta, bgColor: color === 'transparent' ? undefined : color }
      });
      break;
      
    // ... autres cas
  }
}

4. Handlers dans columns-block.component.ts

Pour les blocs dans colonnes:

onMenuAction(action: any): void {
  const block = this.selectedBlock();
  if (!block) return;
  
  // Handle align action
  if (action.type === 'align') {
    const { alignment } = action.payload || {};
    if (alignment) {
      this.alignBlockInColumns(block.id, alignment);
    }
  }
  
  // Handle indent action
  if (action.type === 'indent') {
    const { delta } = action.payload || {};
    if (delta !== undefined) {
      this.indentBlockInColumns(block.id, delta);
    }
  }
  
  // Handle background action
  if (action.type === 'background') {
    const { color } = action.payload || {};
    this.backgroundColorBlockInColumns(block.id, color);
  }
  
  // ... autres actions
}

5. Méthodes de Modification dans Colonnes

alignBlockInColumns()

private alignBlockInColumns(blockId: string, alignment: string): void {
  const updatedColumns = this.props.columns.map(column => ({
    ...column,
    blocks: column.blocks.map(b => {
      if (b.id === blockId) {
        // For list-item blocks, update props.align
        if (b.type === 'list-item') {
          return { ...b, props: { ...b.props, align: alignment as any } };
        } else {
          // For other blocks, update meta.align
          const current = b.meta || {};
          return { ...b, meta: { ...current, align: alignment as any } };
        }
      }
      return b;
    })
  }));
  
  this.update.emit({ columns: updatedColumns });
}

Fonctionnement:

  1. Parcourt toutes les colonnes
  2. Trouve le bloc avec l'ID correspondant
  3. Met à jour props.align (list-item) ou meta.align (autres)
  4. Émet l'événement update avec la structure modifiée

indentBlockInColumns()

private indentBlockInColumns(blockId: string, delta: number): void {
  const updatedColumns = this.props.columns.map(column => ({
    ...column,
    blocks: column.blocks.map(b => {
      if (b.id === blockId) {
        // For list-item blocks, update props.indent
        if (b.type === 'list-item') {
          const cur = Number((b.props as any).indent || 0);
          const next = Math.max(0, Math.min(7, cur + delta));
          return { ...b, props: { ...b.props, indent: next } };
        } else {
          // For other blocks, update meta.indent
          const current = (b.meta as any) || {};
          const cur = Number(current.indent || 0);
          const next = Math.max(0, Math.min(8, cur + delta));
          return { ...b, meta: { ...current, indent: next } };
        }
      }
      return b;
    })
  }));
  
  this.update.emit({ columns: updatedColumns });
}

Fonctionnement:

  1. Calcule le nouvel indent: current + delta
  2. Limite entre 0 et 7 (list-item) ou 0 et 8 (autres)
  3. Met à jour props.indent ou meta.indent
  4. Émet l'événement update

backgroundColorBlockInColumns()

private backgroundColorBlockInColumns(blockId: string, color: string): void {
  const updatedColumns = this.props.columns.map(column => ({
    ...column,
    blocks: column.blocks.map(b => {
      if (b.id === blockId) {
        return { 
          ...b, 
          meta: { 
            ...b.meta, 
            bgColor: color === 'transparent' ? undefined : color 
          } 
        };
      }
      return b;
    })
  }));
  
  this.update.emit({ columns: updatedColumns });
}

Fonctionnement:

  1. Trouve le bloc
  2. Met à jour meta.bgColor (undefined si transparent)
  3. Émet l'événement update

📊 Flux de Données Complet

Pour Blocs Normaux

User clique "Align Left" dans le menu
         ↓
block-context-menu.component
  onAlign('left')
         ↓
  action.emit({ type: 'align', payload: { alignment: 'left' } })
         ↓
block-host.component
  onMenuAction(action)
         ↓
  case 'align':
    documentService.updateBlock(blockId, { meta: { align: 'left' } })
         ↓
  Bloc mis à jour dans documentService ✅
         ↓
  Angular détecte le changement (signals)
         ↓
  UI se met à jour avec le texte aligné à gauche

Pour Blocs dans Colonnes

User clique "Align Left" dans le menu
         ↓
block-context-menu.component
  onAlign('left')
         ↓
  action.emit({ type: 'align', payload: { alignment: 'left' } })
         ↓
columns-block.component
  onMenuAction(action)
         ↓
  case 'align':
    alignBlockInColumns(blockId, 'left')
         ↓
  Parcourt columns.blocks
    Trouve le bloc avec blockId
    Met à jour meta.align = 'left'
         ↓
  this.update.emit({ columns: updatedColumns })
         ↓
  Parent reçoit l'événement update
         ↓
  documentService.updateBlockProps(columnsBlockId, { columns: updatedColumns })
         ↓
  Bloc columns mis à jour avec nouvelle structure ✅
         ↓
  Angular détecte le changement (signals)
         ↓
  UI se met à jour avec le bloc aligné à gauche dans la colonne

🧪 Tests de Validation

Test 1: Align Left dans Colonnes

Procédure:

  1. Créer 2 colonnes avec un heading H1 dans chaque
  2. Ouvrir menu du heading dans colonne 1
  3. Cliquer "Align Left"

Résultats Attendus:

✅ Menu se ferme
✅ Texte du heading dans colonne 1 aligné à gauche
✅ Heading dans colonne 2 reste inchangé
✅ meta.align = 'left' sur le bloc

Test 2: Align Center dans Colonnes

Procédure:

  1. Créer 3 colonnes avec paragraphes
  2. Menu du paragraphe dans colonne 2
  3. Cliquer "Align Center"

Résultats Attendus:

✅ Texte du paragraphe dans colonne 2 centré
✅ Paragraphes dans colonnes 1 et 3 inchangés
✅ meta.align = 'center' sur le bloc

Test 3: Increase Indent dans Colonnes

Procédure:

  1. Créer 2 colonnes avec list-items
  2. Menu du list-item dans colonne 1
  3. Cliquer "Increase Indent" (⁝)

Résultats Attendus:

✅ List-item dans colonne 1 indenté (décalé à droite)
✅ props.indent = 1 sur le list-item
✅ List-item dans colonne 2 inchangé
✅ Peut indenter jusqu'à 7 niveaux

Test 4: Decrease Indent dans Colonnes

Procédure:

  1. List-item avec indent = 2
  2. Menu du list-item
  3. Cliquer "Decrease Indent" deux fois

Résultats Attendus:

✅ Premier clic: indent = 1
✅ Deuxième clic: indent = 0
✅ Troisième clic: reste à 0 (minimum)

Test 5: Background Color dans Colonnes

Procédure:

  1. Créer colonnes avec heading
  2. Menu du heading
  3. Cliquer "Background color" → Sélectionner bleu

Résultats Attendus:

✅ Heading dans la colonne a fond bleu
✅ meta.bgColor = '#2563eb' (blue-600)
✅ Autres blocs inchangés

Test 6: Align sur Bloc Normal

Procédure:

  1. Créer un heading plein largeur (pas dans colonne)
  2. Menu du heading
  3. Cliquer "Align Right"

Résultats Attendus:

✅ Heading aligné à droite
✅ meta.align = 'right'
✅ Fonctionne comme avant (pas de régression)

📋 Récapitulatif des Modifications

Fichier Modification Description
block-context-menu.component.ts MenuAction interface Ajout types 'align', 'indent'
onAlign() Émet action au lieu de modifier directement
onIndent() Émet action au lieu de modifier directement
onBackgroundColor() Émet action au lieu de modifier directement
block-host.component.ts onMenuAction() Ajout cases 'align', 'indent', 'background'
Gère les modifications pour blocs normaux
columns-block.component.ts onMenuAction() Ajout handlers pour align, indent, background
alignBlockInColumns() Nouvelle méthode pour aligner dans colonnes
indentBlockInColumns() Nouvelle méthode pour indenter dans colonnes
backgroundColorBlockInColumns() Nouvelle méthode pour background dans colonnes

🎯 Principes de Design Appliqués

1. Separation of Concerns

Menu: Responsable de l'UI et de l'émission d'actions
Parent: Responsable de la logique de modification

Avantage: Le menu ne connaît pas la structure des données


2. Event-Driven Architecture

Menu émet des événements → Parents réagissent

Avantage: Flexibilité pour gérer différents cas (normal vs colonnes)


3. Single Responsibility

Chaque composant a une seule responsabilité:

  • Menu: Afficher options et émettre actions
  • Block-host: Gérer blocs normaux
  • Columns-block: Gérer blocs dans colonnes

Statut Final

Problèmes:

  • Align Left/Center/Right/Justify: Fixé
  • Increase/Decrease Indent: Fixé
  • Background Color: Bonus fixé

Tests:

  • Test 1: Align Left colonnes
  • Test 2: Align Center colonnes
  • Test 3: Increase Indent colonnes
  • Test 4: Decrease Indent colonnes
  • Test 5: Background colonnes
  • Test 6: Align bloc normal (régression)

Prêt pour production: Oui


🚀 À Tester

Le serveur dev tourne déjà. Rafraîchir le navigateur et tester:

  1. Créer 2 colonnes avec blocs
  2. Menu → Align Left sur bloc dans colonne
  3. Vérifier l'alignement change
  4. Menu → Increase Indent
  5. Vérifier l'indentation augmente
  6. Menu → Background Color → Bleu
  7. Vérifier le fond devient bleu

🎉 Résumé Exécutif

Problème: Boutons alignement et indentation ne fonctionnaient pas dans les colonnes

Cause: Menu modifiait directement via documentService, qui ne gère pas les blocs imbriqués

Solution:

  • Menu émet des actions au lieu de modifier directement
  • block-host gère les blocs normaux
  • columns-block gère les blocs dans colonnes
  • 3 nouvelles méthodes: alignBlockInColumns(), indentBlockInColumns(), backgroundColorBlockInColumns()

Résultat:

  • Align fonctionne dans colonnes
  • Indent fonctionne dans colonnes
  • Background fonctionne dans colonnes
  • Pas de régression sur blocs normaux
  • Architecture event-driven propre

Impact: Fonctionnalité complète du menu dans toutes les situations! 🎊