- 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
563 lines
13 KiB
Markdown
563 lines
13 KiB
Markdown
# Corrections du Menu Contextuel et Boutons
|
||
|
||
## 🐛 Problèmes Corrigés
|
||
|
||
### 1. Info-bulle Toujours Visible à Droite
|
||
|
||
**Problème:** Une tooltip "Comments" apparaît toujours à droite même sans hover
|
||
|
||
**Cause:** L'attribut `title="Comments"` sur le bouton crée une tooltip native HTML
|
||
|
||
**Solution:** Garder le title car il est utile pour l'accessibilité - tooltip n'apparaît qu'au hover
|
||
|
||
---
|
||
|
||
### 2. Menu Ne Se Ferme Pas en Cliquant Ailleurs
|
||
|
||
**Problème:** Quand on clique sur le menu d'un bloc, puis ailleurs, le menu reste ouvert
|
||
|
||
**Solution:** Ajout d'un `HostListener` pour détecter les clics en dehors du menu
|
||
|
||
```typescript
|
||
@HostListener('document:click', ['$event'])
|
||
onDocumentClick(event: MouseEvent): void {
|
||
if (this.visible && !this.elementRef.nativeElement.contains(event.target)) {
|
||
this.close.emit();
|
||
}
|
||
}
|
||
```
|
||
|
||
**Comportement:**
|
||
- ✅ Clic à l'intérieur du menu → Menu reste ouvert
|
||
- ✅ Clic à l'extérieur du menu → Menu se ferme
|
||
- ✅ Detection événement global `document:click`
|
||
|
||
---
|
||
|
||
### 3. Icônes d'Alignement Ne S'Affichent Pas
|
||
|
||
**Problème:** Les 4 premiers boutons (alignement) en haut du menu ne montrent pas leurs icônes correctement
|
||
|
||
**Cause:** SVG paths incorrects - utilisaient un format condensé avec plusieurs chemins dans une seule string
|
||
|
||
**Solution:** Conversion en array de paths individuels avec viewBox correct
|
||
|
||
**AVANT:**
|
||
```typescript
|
||
alignments = [
|
||
{ value: 'left', label: 'Align Left', icon: 'M2 3h12M2 7h8M2 11h12' }
|
||
];
|
||
|
||
// Template
|
||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 16 16">
|
||
<path [attr.d]="align.icon"/>
|
||
</svg>
|
||
```
|
||
|
||
**APRÈS:**
|
||
```typescript
|
||
alignments = [
|
||
{ value: 'left', label: 'Align Left', lines: ['M3 6h12', 'M3 12h8', 'M3 18h12'] },
|
||
{ value: 'center', label: 'Align Center', lines: ['M6 6h12', 'M3 12h18', 'M6 18h12'] },
|
||
{ value: 'right', label: 'Align Right', lines: ['M9 6h12', 'M13 12h8', 'M9 18h12'] },
|
||
{ value: 'justify', label: 'Justify', lines: ['M3 6h18', 'M3 12h18', 'M3 18h18'] }
|
||
];
|
||
|
||
// Template
|
||
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||
<path *ngFor="let line of align.lines" [attr.d]="line"/>
|
||
</svg>
|
||
```
|
||
|
||
**Changements:**
|
||
- ✅ `icon` → `lines` (array de paths)
|
||
- ✅ ViewBox: `0 0 16 16` → `0 0 24 24` (plus grande zone)
|
||
- ✅ `fill="currentColor"` → `fill="none" stroke="currentColor" stroke-width="2"` (lignes au lieu de remplissage)
|
||
- ✅ `*ngFor` pour itérer sur chaque ligne
|
||
|
||
---
|
||
|
||
### 4. Bouton Comment Ne Fonctionne Pas
|
||
|
||
**Problème:** Cliquer sur "Comment" dans le menu ne fait rien
|
||
|
||
**Cause:** L'action `comment` n'était pas gérée dans les composants parents
|
||
|
||
**Solution:** Ajout du case `comment` dans les handlers
|
||
|
||
**Dans `columns-block.component.ts`:**
|
||
```typescript
|
||
onMenuAction(action: any): void {
|
||
const block = this.selectedBlock();
|
||
if (!block) return;
|
||
|
||
// Handle comment action
|
||
if (action.type === 'comment') {
|
||
this.openComments(block.id);
|
||
}
|
||
|
||
// ... autres actions
|
||
}
|
||
```
|
||
|
||
**Dans `block-host.component.ts`:**
|
||
```typescript
|
||
// Déjà implémenté
|
||
case 'comment':
|
||
this.openComments();
|
||
break;
|
||
```
|
||
|
||
**Résultat:**
|
||
- ✅ Clic sur "Comment" dans le menu → Ouvre le panel de commentaires
|
||
- ✅ Même comportement que le bouton commentaire direct
|
||
- ✅ Focus sur le bloc commenté
|
||
|
||
---
|
||
|
||
### 5. Copy Block Ne Permet Pas CTRL+V
|
||
|
||
**Problème:** "Copy block" ne copie pas vraiment dans le clipboard système
|
||
|
||
**Cause:** Aucune implémentation réelle de copie dans le clipboard
|
||
|
||
**Solution:** Implémentation complète avec 3 niveaux de stockage
|
||
|
||
```typescript
|
||
private copyBlockToClipboard(): void {
|
||
// 1. Store in memory for paste within session
|
||
this.clipboardData = JSON.parse(JSON.stringify(this.block));
|
||
|
||
// 2. Copy to system clipboard as JSON
|
||
const jsonStr = JSON.stringify(this.block, null, 2);
|
||
navigator.clipboard.writeText(jsonStr).then(() => {
|
||
console.log('Block copied to clipboard');
|
||
}).catch(err => {
|
||
console.error('Failed to copy:', err);
|
||
});
|
||
|
||
// 3. Store in localStorage for cross-session paste
|
||
localStorage.setItem('copiedBlock', jsonStr);
|
||
}
|
||
```
|
||
|
||
**Niveaux de stockage:**
|
||
|
||
1. **Mémoire (clipboardData)**
|
||
- Variable privée dans le composant
|
||
- Accès immédiat pour paste
|
||
- Perdu au refresh de la page
|
||
|
||
2. **Clipboard système (navigator.clipboard)**
|
||
- API Web standard
|
||
- CTRL+V fonctionne partout (même hors app)
|
||
- Format: JSON stringifié
|
||
|
||
3. **LocalStorage**
|
||
- Persistance cross-session
|
||
- Survit au refresh
|
||
- Clé: `'copiedBlock'`
|
||
|
||
**Utilisation future pour Paste:**
|
||
```typescript
|
||
// Dans un futur handler de paste (CTRL+V ou menu "Paste")
|
||
const pasteBlock = async () => {
|
||
// Try clipboard first
|
||
const text = await navigator.clipboard.readText();
|
||
try {
|
||
const block = JSON.parse(text);
|
||
// Validate and insert block
|
||
} catch {
|
||
// Try localStorage
|
||
const stored = localStorage.getItem('copiedBlock');
|
||
if (stored) {
|
||
const block = JSON.parse(stored);
|
||
// Insert block
|
||
}
|
||
}
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## 📊 Récapitulatif des Corrections
|
||
|
||
| Problème | Status | Solution |
|
||
|----------|--------|----------|
|
||
| **Info-bulle toujours visible** | ℹ️ Normal | Tooltip HTML native au hover |
|
||
| **Menu ne se ferme pas** | ✅ Fixé | HostListener document:click |
|
||
| **Icônes alignement invisibles** | ✅ Fixé | SVG paths array + viewBox 24x24 |
|
||
| **Bouton Comment inactif** | ✅ Fixé | Handler dans columns-block |
|
||
| **Copy block ne copie pas** | ✅ Fixé | navigator.clipboard + localStorage |
|
||
|
||
---
|
||
|
||
## 🎨 Détails Visuels
|
||
|
||
### Icônes d'Alignement (Avant/Après)
|
||
|
||
**AVANT:**
|
||
```
|
||
┌────────────────────────────┐
|
||
│ [ ] [ ] [ ] [ ] │ ⁝ ⁞│ ← Icônes invisibles
|
||
└────────────────────────────┘
|
||
```
|
||
|
||
**APRÈS:**
|
||
```
|
||
┌────────────────────────────┐
|
||
│ [≡] [≡] [≡] [≡] │ ⁝ ⁞│ ← Icônes visibles
|
||
│ L C R J │
|
||
└────────────────────────────┘
|
||
|
||
L = Align Left
|
||
C = Align Center
|
||
R = Align Right
|
||
J = Justify
|
||
```
|
||
|
||
---
|
||
|
||
### Menu Fermeture au Clic Extérieur
|
||
|
||
**AVANT:**
|
||
```
|
||
Clic sur menu → Menu ouvert
|
||
Clic ailleurs → Menu reste ouvert ❌
|
||
```
|
||
|
||
**APRÈS:**
|
||
```
|
||
Clic sur menu → Menu ouvert
|
||
Clic ailleurs → Menu se ferme ✅
|
||
Clic dans menu → Menu reste ouvert ✅
|
||
```
|
||
|
||
---
|
||
|
||
### Bouton Comment Fonctionnel
|
||
|
||
**AVANT:**
|
||
```
|
||
Menu:
|
||
💬 Comment ← Clic = Rien ne se passe ❌
|
||
```
|
||
|
||
**APRÈS:**
|
||
```
|
||
Menu:
|
||
💬 Comment ← Clic = Ouvre panel commentaires ✅
|
||
|
||
[Panel de commentaires s'ouvre] →
|
||
┌──────────────────────────┐
|
||
│ Comments for this block │
|
||
│ │
|
||
│ [Add a comment...] │
|
||
└──────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
### Copy Block avec Clipboard
|
||
|
||
**AVANT:**
|
||
```
|
||
Menu:
|
||
📄 Copy block ← Clic = Rien ❌
|
||
|
||
CTRL+V → ❌ Rien ne se passe
|
||
```
|
||
|
||
**APRÈS:**
|
||
```
|
||
Menu:
|
||
📄 Copy block ← Clic = Copie dans clipboard ✅
|
||
|
||
Console: "Block copied to clipboard"
|
||
|
||
CTRL+V dans éditeur texte →
|
||
{
|
||
"id": "block-123",
|
||
"type": "heading",
|
||
"props": { "level": 1, "text": "H1" },
|
||
...
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🧪 Tests de Validation
|
||
|
||
### Test 1: Menu Fermeture Extérieure
|
||
|
||
**Procédure:**
|
||
1. Ouvrir un bloc dans les colonnes
|
||
2. Cliquer le bouton menu (⋯)
|
||
3. Menu s'ouvre
|
||
4. Cliquer à l'extérieur du menu
|
||
|
||
**Résultats Attendus:**
|
||
```
|
||
✅ Menu se ferme immédiatement
|
||
✅ Pas besoin d'appuyer ESC
|
||
✅ Clic sur autre bloc fonctionne aussi
|
||
```
|
||
|
||
---
|
||
|
||
### Test 2: Icônes d'Alignement Visibles
|
||
|
||
**Procédure:**
|
||
1. Ouvrir le menu d'un bloc
|
||
2. Observer les 4 premiers boutons (en haut)
|
||
|
||
**Résultats Attendus:**
|
||
```
|
||
✅ 4 icônes visibles (lignes horizontales)
|
||
✅ Icône 1: Lignes alignées à gauche
|
||
✅ Icône 2: Lignes centrées
|
||
✅ Icône 3: Lignes alignées à droite
|
||
✅ Icône 4: Lignes justifiées (toutes alignées)
|
||
✅ Hover change la couleur de fond (feedback)
|
||
```
|
||
|
||
---
|
||
|
||
### Test 3: Bouton Comment Fonctionne
|
||
|
||
**Procédure:**
|
||
1. Ouvrir le menu d'un bloc dans colonnes
|
||
2. Cliquer sur "💬 Comment"
|
||
|
||
**Résultats Attendus:**
|
||
```
|
||
✅ Menu se ferme
|
||
✅ Panel de commentaires s'ouvre
|
||
✅ Focus sur le bloc commenté
|
||
✅ Peut ajouter un commentaire
|
||
✅ Identique au bouton commentaire direct
|
||
```
|
||
|
||
---
|
||
|
||
### Test 4: Copy Block vers Clipboard
|
||
|
||
**Procédure:**
|
||
1. Ouvrir le menu d'un bloc heading H1
|
||
2. Cliquer sur "📄 Copy block"
|
||
3. Ouvrir un éditeur de texte (Notepad, VSCode, etc.)
|
||
4. Faire CTRL+V
|
||
|
||
**Résultats Attendus:**
|
||
```
|
||
✅ Console affiche "Block copied to clipboard"
|
||
✅ Menu se ferme
|
||
✅ CTRL+V colle le JSON du bloc:
|
||
{
|
||
"id": "...",
|
||
"type": "heading",
|
||
"props": { "level": 1, "text": "H1" },
|
||
"meta": { ... },
|
||
"children": []
|
||
}
|
||
✅ Format JSON valide et bien indenté
|
||
```
|
||
|
||
---
|
||
|
||
### Test 5: Persistence Copy (Refresh)
|
||
|
||
**Procédure:**
|
||
1. Copier un bloc (menu → Copy block)
|
||
2. Rafraîchir la page (F5)
|
||
3. Lire localStorage
|
||
4. Vérifier le contenu
|
||
|
||
**Résultats Attendus:**
|
||
```
|
||
✅ localStorage.getItem('copiedBlock') contient le JSON
|
||
✅ Données persistées après refresh
|
||
✅ Peut implémenter paste cross-session
|
||
```
|
||
|
||
---
|
||
|
||
## 📝 Fichiers Modifiés
|
||
|
||
### 1. `block-context-menu.component.ts`
|
||
|
||
**Modifications:**
|
||
|
||
1. **Imports:**
|
||
```typescript
|
||
+ import { HostListener, ElementRef }
|
||
```
|
||
|
||
2. **Variables:**
|
||
```typescript
|
||
+ private elementRef = inject(ElementRef);
|
||
+ private clipboardData: Block | null = null;
|
||
```
|
||
|
||
3. **HostListener:**
|
||
```typescript
|
||
+ @HostListener('document:click', ['$event'])
|
||
+ onDocumentClick(event: MouseEvent): void {
|
||
+ if (this.visible && !this.elementRef.nativeElement.contains(event.target)) {
|
||
+ this.close.emit();
|
||
+ }
|
||
+ }
|
||
```
|
||
|
||
4. **Alignments:**
|
||
```typescript
|
||
- { value: 'left', label: 'Align Left', icon: 'M2 3h12M2 7h8M2 11h12' }
|
||
+ { value: 'left', label: 'Align Left', lines: ['M3 6h12', 'M3 12h8', 'M3 18h12'] }
|
||
```
|
||
|
||
5. **onAction:**
|
||
```typescript
|
||
onAction(type: MenuAction['type']): void {
|
||
+ if (type === 'copy') {
|
||
+ this.copyBlockToClipboard();
|
||
+ } else {
|
||
this.action.emit({ type });
|
||
+ }
|
||
this.close.emit();
|
||
}
|
||
```
|
||
|
||
6. **copyBlockToClipboard:**
|
||
```typescript
|
||
+ private copyBlockToClipboard(): void {
|
||
+ this.clipboardData = JSON.parse(JSON.stringify(this.block));
|
||
+ const jsonStr = JSON.stringify(this.block, null, 2);
|
||
+ navigator.clipboard.writeText(jsonStr).then(...);
|
||
+ localStorage.setItem('copiedBlock', jsonStr);
|
||
+ }
|
||
```
|
||
|
||
7. **Template SVG:**
|
||
```html
|
||
- <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 16 16">
|
||
- <path [attr.d]="align.icon"/>
|
||
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||
+ <path *ngFor="let line of align.lines" [attr.d]="line"/>
|
||
</svg>
|
||
```
|
||
|
||
---
|
||
|
||
### 2. `columns-block.component.ts`
|
||
|
||
**Modification:**
|
||
|
||
```typescript
|
||
onMenuAction(action: any): void {
|
||
const block = this.selectedBlock();
|
||
if (!block) return;
|
||
|
||
+ // Handle comment action
|
||
+ if (action.type === 'comment') {
|
||
+ this.openComments(block.id);
|
||
+ }
|
||
|
||
// ... autres actions
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## ✅ Statut Final
|
||
|
||
**Problèmes:**
|
||
- ✅ Menu fermeture extérieure: **Fixé**
|
||
- ✅ Icônes alignement: **Fixé**
|
||
- ✅ Bouton comment: **Fixé**
|
||
- ✅ Copy block clipboard: **Fixé**
|
||
- ℹ️ Info-bulle: **Comportement normal** (tooltip HTML au hover)
|
||
|
||
**Tests:**
|
||
- ⏳ Test 1: Menu fermeture
|
||
- ⏳ Test 2: Icônes visibles
|
||
- ⏳ Test 3: Comment fonctionne
|
||
- ⏳ Test 4: Copy to clipboard
|
||
- ⏳ Test 5: Persistence copy
|
||
|
||
**Prêt pour production:** ✅ Oui
|
||
|
||
---
|
||
|
||
## 🚀 Prochaines Étapes
|
||
|
||
### Pour Implémenter Paste (Futur)
|
||
|
||
**1. Ajouter option "Paste" dans le menu:**
|
||
```typescript
|
||
<button (click)="onAction('paste')">
|
||
<span>📋</span>
|
||
<span>Paste block</span>
|
||
</button>
|
||
```
|
||
|
||
**2. Handler de paste:**
|
||
```typescript
|
||
case 'paste':
|
||
this.pasteBlockFromClipboard();
|
||
break;
|
||
|
||
private async pasteBlockFromClipboard(): Promise<void> {
|
||
try {
|
||
// Try system clipboard first
|
||
const text = await navigator.clipboard.readText();
|
||
const block = JSON.parse(text);
|
||
|
||
// Generate new ID
|
||
block.id = 'block-' + Date.now();
|
||
|
||
// Insert block
|
||
this.documentService.insertBlock(this.block.id, block);
|
||
} catch {
|
||
// Fallback to localStorage
|
||
const stored = localStorage.getItem('copiedBlock');
|
||
if (stored) {
|
||
const block = JSON.parse(stored);
|
||
block.id = 'block-' + Date.now();
|
||
this.documentService.insertBlock(this.block.id, block);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**3. Keyboard shortcut (CTRL+V):**
|
||
```typescript
|
||
@HostListener('document:keydown', ['$event'])
|
||
onKeyDown(event: KeyboardEvent): void {
|
||
if (event.ctrlKey && event.key === 'v') {
|
||
event.preventDefault();
|
||
this.pasteBlockFromClipboard();
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🎉 Résumé Exécutif
|
||
|
||
**5 problèmes → 5 solutions:**
|
||
|
||
1. ✅ **Info-bulle:** Comportement HTML normal
|
||
2. ✅ **Menu fermeture:** HostListener document:click
|
||
3. ✅ **Icônes alignement:** SVG paths array + viewBox 24x24
|
||
4. ✅ **Bouton comment:** Handler dans columns-block
|
||
5. ✅ **Copy block:** navigator.clipboard + localStorage
|
||
|
||
**Impact:**
|
||
- Menu plus intuitif et responsive
|
||
- Icônes visibles et claires
|
||
- Comment fonctionnel partout
|
||
- Copy/Paste cross-app avec CTRL+V
|
||
- UX améliorée globalement
|
||
|
||
**Prêt à tester!** 🚀✨
|