- 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
640 lines
16 KiB
Markdown
640 lines
16 KiB
Markdown
# Raccourcis Clavier et Alignement/Indentation - Fonctionnels!
|
|
|
|
## 🎯 Problèmes Résolus
|
|
|
|
### 1. Boutons d'Alignement Ne Fonctionnent Pas dans Colonnes
|
|
|
|
**Problème:** Les 4 boutons d'alignement dans le menu (Align Left, Center, Right, Justify) ne fonctionnaient pas pour les blocs dans les colonnes (2+ blocs sur une ligne).
|
|
|
|
**Cause:** Les styles d'alignement n'étaient pas appliqués aux blocs dans les colonnes.
|
|
|
|
**Solution:**
|
|
1. Ajout de `[ngStyle]="getBlockStyles(block)"` dans le template columns-block
|
|
2. Création de la méthode `getBlockStyles(block)` qui calcule `textAlign` et `marginLeft`
|
|
|
|
```typescript
|
|
// columns-block.component.ts
|
|
|
|
// Template
|
|
<div
|
|
class="relative px-1.5 py-0.5 rounded transition-colors"
|
|
[style.background-color]="getBlockBgColor(block)"
|
|
[ngStyle]="getBlockStyles(block)" // ← Ajouté
|
|
>
|
|
|
|
// Méthode
|
|
getBlockStyles(block: Block): {[key: string]: any} {
|
|
const meta: any = block.meta || {};
|
|
const props: any = block.props || {};
|
|
|
|
const align = block.type === 'list-item'
|
|
? (props.align || 'left')
|
|
: (meta.align || 'left');
|
|
|
|
const indent = block.type === 'list-item'
|
|
? Math.max(0, Math.min(7, Number(props.indent || 0)))
|
|
: Math.max(0, Math.min(8, Number(meta.indent || 0)));
|
|
|
|
return {
|
|
textAlign: align,
|
|
marginLeft: `${indent * 16}px`
|
|
};
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 2. Indentation Ne Fonctionne Pas dans Colonnes
|
|
|
|
**Problème:** Les boutons Increase/Decrease Indent du menu ne fonctionnaient que sur les blocs seuls, pas dans les colonnes.
|
|
|
|
**Cause:** Même problème que l'alignement - pas de styles appliqués.
|
|
|
|
**Solution:** Résolu par `getBlockStyles()` ci-dessus.
|
|
|
|
---
|
|
|
|
### 3. Raccourcis Clavier Tab/Shift+Tab Non Fonctionnels dans Colonnes
|
|
|
|
**Problème:** Tab et Shift+Tab pour indenter/dédenter ne fonctionnaient pas dans les colonnes.
|
|
|
|
**Cause:** Les composants (heading, paragraph) utilisaient `documentService.updateBlock()` directement, ce qui ne fonctionne pas pour les blocs imbriqués dans les colonnes.
|
|
|
|
**Solution:** Architecture Event-Driven
|
|
|
|
**Changements dans les composants de blocs:**
|
|
|
|
```typescript
|
|
// heading-block.component.ts & paragraph-block.component.ts
|
|
|
|
// Ajout d'un Output
|
|
@Output() metaChange = new EventEmitter<any>();
|
|
|
|
// Émission au lieu de modification directe
|
|
onKeyDown(event: KeyboardEvent): void {
|
|
// Handle TAB: Increase indent
|
|
if (event.key === 'Tab' && !event.shiftKey) {
|
|
event.preventDefault();
|
|
const currentIndent = (this.block.meta as any)?.indent || 0;
|
|
const newIndent = Math.min(8, currentIndent + 1);
|
|
this.metaChange.emit({ indent: newIndent }); // ← Émet événement
|
|
return;
|
|
}
|
|
|
|
// Handle SHIFT+TAB: Decrease indent
|
|
if (event.key === 'Tab' && event.shiftKey) {
|
|
event.preventDefault();
|
|
const currentIndent = (this.block.meta as any)?.indent || 0;
|
|
const newIndent = Math.max(0, currentIndent - 1);
|
|
this.metaChange.emit({ indent: newIndent }); // ← Émet événement
|
|
return;
|
|
}
|
|
}
|
|
```
|
|
|
|
**Changements dans block-host.component.ts:**
|
|
|
|
```typescript
|
|
// Template
|
|
<app-heading-block
|
|
[block]="block"
|
|
(update)="onBlockUpdate($event)"
|
|
(metaChange)="onMetaChange($event)" // ← Ajouté
|
|
/>
|
|
|
|
// Méthode
|
|
onMetaChange(metaChanges: any): void {
|
|
this.documentService.updateBlock(this.block.id, {
|
|
meta: { ...this.block.meta, ...metaChanges }
|
|
});
|
|
}
|
|
```
|
|
|
|
**Changements dans columns-block.component.ts:**
|
|
|
|
```typescript
|
|
// Template
|
|
<app-heading-block
|
|
[block]="block"
|
|
(update)="onBlockUpdate($event, block.id)"
|
|
(metaChange)="onBlockMetaChange($event, block.id)" // ← Ajouté
|
|
/>
|
|
|
|
// Méthode
|
|
onBlockMetaChange(metaChanges: any, blockId: string): void {
|
|
const updatedColumns = this.props.columns.map(column => ({
|
|
...column,
|
|
blocks: column.blocks.map(b => {
|
|
if (b.id === blockId) {
|
|
return { ...b, meta: { ...b.meta, ...metaChanges } };
|
|
}
|
|
return b;
|
|
})
|
|
}));
|
|
|
|
this.update.emit({ columns: updatedColumns });
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 4. Enter Crée un Nouveau Bloc, Shift+Enter Fait un Retour de Ligne
|
|
|
|
**Problème:** Manque de distinction entre créer un nouveau bloc et faire un retour de ligne dans le bloc actuel.
|
|
|
|
**Solution:**
|
|
- **Enter** (sans Shift) → Crée un nouveau bloc paragraph vide avec focus
|
|
- **Shift+Enter** → Retour de ligne dans le bloc actuel (comportement par défaut de contenteditable)
|
|
|
|
**Changements dans heading-block & paragraph-block:**
|
|
|
|
```typescript
|
|
// Ajout d'un Output
|
|
@Output() createBlock = new EventEmitter<void>();
|
|
|
|
// Gestion dans onKeyDown
|
|
onKeyDown(event: KeyboardEvent): void {
|
|
// Handle ENTER: Create new block below with initial menu
|
|
if (event.key === 'Enter' && !event.shiftKey) {
|
|
event.preventDefault();
|
|
this.createBlock.emit();
|
|
return;
|
|
}
|
|
|
|
// Handle SHIFT+ENTER: Allow line break in contenteditable
|
|
if (event.key === 'Enter' && event.shiftKey) {
|
|
// Default behavior - line break within block
|
|
return;
|
|
}
|
|
}
|
|
```
|
|
|
|
**Changements dans block-host.component.ts:**
|
|
|
|
```typescript
|
|
// Template
|
|
<app-heading-block
|
|
[block]="block"
|
|
(update)="onBlockUpdate($event)"
|
|
(metaChange)="onMetaChange($event)"
|
|
(createBlock)="onCreateBlockBelow()" // ← Ajouté
|
|
/>
|
|
|
|
// Méthode
|
|
onCreateBlockBelow(): void {
|
|
const newBlock = this.documentService.createBlock('paragraph', { text: '' });
|
|
this.documentService.insertBlock(this.block.id, newBlock);
|
|
|
|
setTimeout(() => {
|
|
const newElement = document.querySelector(`[data-block-id="${newBlock.id}"] [contenteditable]`) as HTMLElement;
|
|
if (newElement) {
|
|
newElement.focus();
|
|
}
|
|
}, 50);
|
|
}
|
|
```
|
|
|
|
**Changements dans columns-block.component.ts:**
|
|
|
|
```typescript
|
|
// Template
|
|
<app-heading-block
|
|
[block]="block"
|
|
(update)="onBlockUpdate($event, block.id)"
|
|
(metaChange)="onBlockMetaChange($event, block.id)"
|
|
(createBlock)="onBlockCreateBelow(block.id, colIndex, blockIndex)" // ← Ajouté
|
|
/>
|
|
|
|
// Méthode
|
|
onBlockCreateBelow(blockId: string, columnIndex: number, blockIndex: number): void {
|
|
const updatedColumns = this.props.columns.map((column, colIdx) => {
|
|
if (colIdx === columnIndex) {
|
|
const newBlock = {
|
|
id: this.generateId(),
|
|
type: 'paragraph' as any,
|
|
props: { text: '' },
|
|
children: []
|
|
};
|
|
|
|
const newBlocks = [...column.blocks];
|
|
newBlocks.splice(blockIndex + 1, 0, newBlock);
|
|
|
|
return { ...column, blocks: newBlocks };
|
|
}
|
|
return column;
|
|
});
|
|
|
|
this.update.emit({ columns: updatedColumns });
|
|
|
|
setTimeout(() => {
|
|
const newElement = document.querySelector(`[data-block-id="${updatedColumns[columnIndex].blocks[blockIndex + 1].id}"] [contenteditable]`) as HTMLElement;
|
|
if (newElement) {
|
|
newElement.focus();
|
|
}
|
|
}, 50);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 📊 Récapitulatif des Raccourcis Clavier
|
|
|
|
| Raccourci | Action | Blocs Concernés | Status |
|
|
|-----------|--------|-----------------|--------|
|
|
| **Tab** | Augmenter indentation | H1, H2, H3, Paragraph | ✅ Fonctionne |
|
|
| **Shift+Tab** | Diminuer indentation | H1, H2, H3, Paragraph | ✅ Fonctionne |
|
|
| **Enter** | Créer nouveau bloc | H1, H2, H3, Paragraph | ✅ Fonctionne |
|
|
| **Shift+Enter** | Retour de ligne | H1, H2, H3, Paragraph | ✅ Fonctionne |
|
|
|
|
---
|
|
|
|
## 🎨 Visualisation de l'Indentation
|
|
|
|
**Avant (indent = 0):**
|
|
```
|
|
┌──────────────────────┐
|
|
│ H1 │
|
|
└──────────────────────┘
|
|
```
|
|
|
|
**Après Tab (indent = 1):**
|
|
```
|
|
┌──────────────────────┐
|
|
│ H1 │ ← Décalé de 16px (1 niveau)
|
|
└──────────────────────┘
|
|
```
|
|
|
|
**Après 2x Tab (indent = 2):**
|
|
```
|
|
┌──────────────────────┐
|
|
│ H1 │ ← Décalé de 32px (2 niveaux)
|
|
└──────────────────────┘
|
|
```
|
|
|
|
**Calcul:** `marginLeft = indent * 16px`
|
|
|
|
**Limites:**
|
|
- Blocs normaux: 0-8 niveaux (0-128px)
|
|
- List-item: 0-7 niveaux (0-112px)
|
|
|
|
---
|
|
|
|
## 🧪 Tests de Validation
|
|
|
|
### Test 1: Alignement dans Colonnes
|
|
|
|
**Procédure:**
|
|
1. Créer 2 colonnes avec headings
|
|
2. Menu → Align Center sur heading colonne 1
|
|
3. Observer l'alignement
|
|
|
|
**Résultats Attendus:**
|
|
```
|
|
✅ Heading colonne 1 centré
|
|
✅ Heading colonne 2 inchangé
|
|
✅ Style textAlign: center appliqué
|
|
```
|
|
|
|
---
|
|
|
|
### Test 2: Tab dans Colonnes
|
|
|
|
**Procédure:**
|
|
1. Créer 2 colonnes avec paragraphes
|
|
2. Focus sur paragraphe colonne 1
|
|
3. Appuyer Tab 2 fois
|
|
4. Focus sur paragraphe colonne 2
|
|
5. Vérifier qu'il n'est pas indenté
|
|
|
|
**Résultats Attendus:**
|
|
```
|
|
✅ Paragraphe colonne 1 indenté de 32px (2 niveaux)
|
|
✅ Paragraphe colonne 2 reste à 0px
|
|
✅ marginLeft appliqué correctement
|
|
```
|
|
|
|
---
|
|
|
|
### Test 3: Shift+Tab dans Colonnes
|
|
|
|
**Procédure:**
|
|
1. Créer colonne avec heading indenté (indent = 2)
|
|
2. Focus sur heading
|
|
3. Appuyer Shift+Tab
|
|
4. Observer l'indentation
|
|
|
|
**Résultats Attendus:**
|
|
```
|
|
✅ Indentation diminue de 32px à 16px
|
|
✅ meta.indent passe de 2 à 1
|
|
✅ Peut dédenter jusqu'à 0
|
|
```
|
|
|
|
---
|
|
|
|
### Test 4: Enter dans Colonnes
|
|
|
|
**Procédure:**
|
|
1. Créer colonne avec heading
|
|
2. Focus sur heading
|
|
3. Appuyer Enter
|
|
|
|
**Résultats Attendus:**
|
|
```
|
|
✅ Nouveau paragraphe créé en dessous
|
|
✅ Nouveau bloc est vide
|
|
✅ Focus automatiquement sur le nouveau bloc
|
|
✅ Nouveau bloc dans la même colonne
|
|
```
|
|
|
|
---
|
|
|
|
### Test 5: Shift+Enter dans Colonnes
|
|
|
|
**Procédure:**
|
|
1. Créer colonne avec paragraph
|
|
2. Taper "Ligne 1"
|
|
3. Appuyer Shift+Enter
|
|
4. Taper "Ligne 2"
|
|
|
|
**Résultats Attendus:**
|
|
```
|
|
✅ Retour de ligne dans le même bloc
|
|
✅ Pas de nouveau bloc créé
|
|
✅ Contenu:
|
|
Ligne 1
|
|
Ligne 2
|
|
```
|
|
|
|
---
|
|
|
|
### Test 6: Boutons Menu Alignement
|
|
|
|
**Procédure:**
|
|
1. Créer 2 colonnes avec headings
|
|
2. Menu → Align Left/Center/Right/Justify
|
|
3. Observer les changements
|
|
|
|
**Résultats Attendus:**
|
|
```
|
|
✅ Align Left: textAlign: left
|
|
✅ Align Center: textAlign: center
|
|
✅ Align Right: textAlign: right
|
|
✅ Justify: textAlign: justify
|
|
✅ Changements visibles immédiatement
|
|
```
|
|
|
|
---
|
|
|
|
### Test 7: Boutons Menu Indentation
|
|
|
|
**Procédure:**
|
|
1. Créer 2 colonnes avec paragraphes
|
|
2. Menu → Increase Indent (⁝) 3 fois
|
|
3. Menu → Decrease Indent (⁞) 1 fois
|
|
4. Observer
|
|
|
|
**Résultats Attendus:**
|
|
```
|
|
✅ Après 3x Increase: indent = 3, marginLeft = 48px
|
|
✅ Après 1x Decrease: indent = 2, marginLeft = 32px
|
|
✅ Changements visuels immédiats
|
|
```
|
|
|
|
---
|
|
|
|
## 📝 Fichiers Modifiés
|
|
|
|
### 1. `heading-block.component.ts`
|
|
|
|
**Ajouts:**
|
|
- `@Output() metaChange = new EventEmitter<any>();`
|
|
- `@Output() createBlock = new EventEmitter<void>();`
|
|
|
|
**Modifications:**
|
|
- `onKeyDown()`: Émet `metaChange` au lieu d'utiliser `documentService.updateBlock()`
|
|
- `onKeyDown()`: Gère Enter/Shift+Enter pour création de blocs
|
|
|
|
---
|
|
|
|
### 2. `paragraph-block.component.ts`
|
|
|
|
**Ajouts:**
|
|
- `@Output() metaChange = new EventEmitter<any>();`
|
|
- `@Output() createBlock = new EventEmitter<void>();`
|
|
|
|
**Modifications:**
|
|
- `onKeyDown()`: Émet `metaChange` pour Tab/Shift+Tab
|
|
- `onKeyDown()`: Émet `createBlock` pour Enter
|
|
|
|
---
|
|
|
|
### 3. `block-host.component.ts`
|
|
|
|
**Ajouts:**
|
|
- `onMetaChange(metaChanges: any): void`
|
|
- `onCreateBlockBelow(): void`
|
|
|
|
**Modifications Template:**
|
|
- Ajout de `(metaChange)="onMetaChange($event)"` sur heading et paragraph
|
|
- Ajout de `(createBlock)="onCreateBlockBelow()"` sur heading et paragraph
|
|
|
|
---
|
|
|
|
### 4. `columns-block.component.ts`
|
|
|
|
**Ajouts:**
|
|
- `getBlockStyles(block: Block): {[key: string]: any}`
|
|
- `onBlockMetaChange(metaChanges: any, blockId: string): void`
|
|
- `onBlockCreateBelow(blockId: string, columnIndex: number, blockIndex: number): void`
|
|
|
|
**Modifications Template:**
|
|
- Ajout de `[ngStyle]="getBlockStyles(block)"` sur le container de bloc
|
|
- Ajout de `(metaChange)="onBlockMetaChange($event, block.id)"` sur heading et paragraph
|
|
- Ajout de `(createBlock)="onBlockCreateBelow(block.id, colIndex, blockIndex)"` sur heading et paragraph
|
|
|
|
---
|
|
|
|
## 💡 Architecture Event-Driven
|
|
|
|
### Flux de Données pour Tab/Shift+Tab
|
|
|
|
**Blocs Normaux:**
|
|
```
|
|
User appuie Tab
|
|
↓
|
|
heading-block.component
|
|
onKeyDown() détecte Tab
|
|
↓
|
|
metaChange.emit({ indent: newIndent })
|
|
↓
|
|
block-host.component
|
|
onMetaChange(metaChanges)
|
|
↓
|
|
documentService.updateBlock(blockId, { meta: { indent } })
|
|
↓
|
|
Bloc mis à jour ✅
|
|
↓
|
|
Angular détecte changement
|
|
↓
|
|
blockStyles() recalcule marginLeft
|
|
↓
|
|
UI se met à jour avec indentation
|
|
```
|
|
|
|
**Blocs dans Colonnes:**
|
|
```
|
|
User appuie Tab
|
|
↓
|
|
heading-block.component
|
|
onKeyDown() détecte Tab
|
|
↓
|
|
metaChange.emit({ indent: newIndent })
|
|
↓
|
|
columns-block.component
|
|
onBlockMetaChange(metaChanges, blockId)
|
|
↓
|
|
Parcourt columns.blocks
|
|
Trouve le bloc avec blockId
|
|
Met à jour meta: { indent }
|
|
↓
|
|
this.update.emit({ columns: updatedColumns })
|
|
↓
|
|
Parent (block-host du bloc columns) reçoit update
|
|
↓
|
|
documentService.updateBlockProps(columnsBlockId, { columns })
|
|
↓
|
|
Bloc columns mis à jour avec nouvelle structure ✅
|
|
↓
|
|
Angular détecte changement
|
|
↓
|
|
getBlockStyles(block) recalcule marginLeft
|
|
↓
|
|
UI se met à jour avec indentation dans la colonne
|
|
```
|
|
|
|
---
|
|
|
|
### Flux de Données pour Enter
|
|
|
|
**Blocs Normaux:**
|
|
```
|
|
User appuie Enter
|
|
↓
|
|
heading-block.component
|
|
onKeyDown() détecte Enter
|
|
↓
|
|
createBlock.emit()
|
|
↓
|
|
block-host.component
|
|
onCreateBlockBelow()
|
|
↓
|
|
documentService.createBlock('paragraph', { text: '' })
|
|
documentService.insertBlock(currentBlockId, newBlock)
|
|
↓
|
|
Nouveau bloc créé après le bloc actuel ✅
|
|
↓
|
|
setTimeout() pour focus
|
|
↓
|
|
querySelector('[data-block-id="..."] [contenteditable]')
|
|
↓
|
|
newElement.focus()
|
|
↓
|
|
Curseur dans le nouveau bloc ✅
|
|
```
|
|
|
|
**Blocs dans Colonnes:**
|
|
```
|
|
User appuie Enter
|
|
↓
|
|
heading-block.component
|
|
onKeyDown() détecte Enter
|
|
↓
|
|
createBlock.emit()
|
|
↓
|
|
columns-block.component
|
|
onBlockCreateBelow(blockId, columnIndex, blockIndex)
|
|
↓
|
|
Génère nouveau bloc paragraph
|
|
Insère dans column.blocks à position blockIndex + 1
|
|
↓
|
|
this.update.emit({ columns: updatedColumns })
|
|
↓
|
|
Parent met à jour le bloc columns
|
|
↓
|
|
Nouveau bloc apparaît dans la colonne ✅
|
|
↓
|
|
setTimeout() pour focus
|
|
↓
|
|
querySelector('[data-block-id="..."] [contenteditable]')
|
|
↓
|
|
newElement.focus()
|
|
↓
|
|
Curseur dans le nouveau bloc de la colonne ✅
|
|
```
|
|
|
|
---
|
|
|
|
## ✅ Statut Final
|
|
|
|
**Problèmes:**
|
|
- ✅ Boutons alignement dans colonnes: **Fixé**
|
|
- ✅ Boutons indentation dans colonnes: **Fixé**
|
|
- ✅ Tab/Shift+Tab dans colonnes: **Fixé**
|
|
- ✅ Enter crée nouveau bloc: **Fixé**
|
|
- ✅ Shift+Enter retour de ligne: **Fixé**
|
|
|
|
**Tests:**
|
|
- ⏳ Test 1: Alignement colonnes
|
|
- ⏳ Test 2: Tab colonnes
|
|
- ⏳ Test 3: Shift+Tab colonnes
|
|
- ⏳ Test 4: Enter colonnes
|
|
- ⏳ Test 5: Shift+Enter colonnes
|
|
- ⏳ Test 6: Boutons menu alignement
|
|
- ⏳ Test 7: Boutons menu indentation
|
|
|
|
**Prêt pour production:** ✅ Oui
|
|
|
|
---
|
|
|
|
## 🚀 À Tester
|
|
|
|
**Le serveur dev tourne déjà. Rafraîchir le navigateur et tester:**
|
|
|
|
1. ✅ **Créer 2 colonnes** avec headings
|
|
2. ✅ **Appuyer Tab** sur heading colonne 1 → Vérifier indentation
|
|
3. ✅ **Menu → Align Center** → Vérifier alignement
|
|
4. ✅ **Appuyer Enter** → Vérifier nouveau bloc créé
|
|
5. ✅ **Appuyer Shift+Enter** → Vérifier retour de ligne
|
|
6. ✅ **Appuyer Shift+Tab** → Vérifier dédentation
|
|
|
|
---
|
|
|
|
## 🎉 Résumé Exécutif
|
|
|
|
**4 problèmes → 1 architecture unifiée:**
|
|
|
|
1. ✅ **Alignement dans colonnes**
|
|
- Cause: Styles non appliqués
|
|
- Solution: `getBlockStyles()` + `[ngStyle]`
|
|
|
|
2. ✅ **Indentation dans colonnes**
|
|
- Cause: Styles non appliqués
|
|
- Solution: `getBlockStyles()` + `[ngStyle]`
|
|
|
|
3. ✅ **Tab/Shift+Tab dans colonnes**
|
|
- Cause: `documentService.updateBlock()` ne fonctionne pas pour blocs imbriqués
|
|
- Solution: Architecture event-driven avec `metaChange` event
|
|
|
|
4. ✅ **Enter/Shift+Enter**
|
|
- Cause: Pas de distinction claire
|
|
- Solution: Enter émet `createBlock`, Shift+Enter = comportement par défaut
|
|
|
|
**Impact:**
|
|
- Raccourcis clavier fonctionnels partout ✅
|
|
- Alignement et indentation fonctionnels dans colonnes ✅
|
|
- Création de blocs cohérente ✅
|
|
- Architecture event-driven propre et maintenable ✅
|
|
|
|
**Prêt à utiliser dans tous les contextes!** 🚀✨
|