feat(BlockEditor): add image alignment, link, and replace/delete controls

- add align (left/center/right/full), href, newTab fields to image block
- render floating toolbar on image hover with alignment buttons and link popover
- add replace and delete actions to image toolbar
- wrap image in <a> in disabled mode and HTML export when href is set
- update htmlToBlocks/blocksToHtml to serialize/parse align, href, newTab
- guard handleContainerMouseDown to prevent multi-block selection on input/textarea focus
- add alignment and link icons to shared icons index
- update README with image block spec and toolbar behaviour
This commit is contained in:
2026-04-26 16:26:41 -04:00
parent 83490de15d
commit d66b107636
5 changed files with 425 additions and 63 deletions
+31 -3
View File
@@ -30,7 +30,7 @@ Chaque bloc a un `id` (UUID) et un `type`. Selon le type :
| `quote` | `content` | citation |
| `code` | `content` | bloc de code (monospace) |
| `divider` | — | séparateur horizontal |
| `image` | `src`, `alt`, `caption` | image (URL uniquement) |
| `image` | `src`, `alt`, `caption`, `align`, `href`, `newTab` | image (URL uniquement) — `align``left\|center\|right\|full`, `href` optionnel |
`content` est un **tableau `InlineNode[]`** depuis Phase 2 — voir ci-dessous.
@@ -285,9 +285,37 @@ page) sont traversés pour atteindre les éléments block-level
descendants. Les styles CSS inline (`font-weight`, `font-style`,
`text-decoration`) sont également lus en plus des tags sémantiques.
## Bloc image
Une fois l'URL insérée, l'image affiche au survol une **toolbar flottante**
(coin haut-droit) reprenant le style `BOX_CLASS` partagé :
- **Alignement** : 4 boutons (gauche / centre / droite / pleine largeur).
Persisté dans `block.align`. Le wrapper applique `justify-content` selon
la valeur ; `'full'` passe l'image à `width: 100%` (l'image étire la
largeur du conteneur de bloc, pas du viewport).
- **Lien** : ouvre un mini-popover (input URL + case « nouvel onglet »).
Persisté dans `block.href` et `block.newTab`. Côté éditeur l'image n'est
jamais wrappée dans `<a>` pour ne pas piéger les clics ; en mode `disabled`
et à l'export HTML (`blocksToHtml`), l'image est wrappée dans `<a>` avec
`target="_blank" rel="noopener noreferrer"` quand `newTab` est vrai.
- **Remplacer** : remet le formulaire URL avec l'URL courante préremplie.
Conserve `alt`, `caption`, `align`, `href`. Échap pour annuler.
- **Supprimer** : vide tous les champs ; le formulaire URL réapparaît.
Les inputs *Légende* et *Texte alternatif* sous l'image sont des `<input>`
HTML standards. Le clic dans ces inputs ne déclenche pas la sélection
multi-blocs (cf. garde dans `handleContainerMouseDown` : tout `input`,
`textarea`, `select` ou `[contenteditable="true"]` reçoit son focus normal
et n'entre pas en mode sélection).
Sérialisation HTML : `<figure data-align="…"><a href><img></a><figcaption></figcaption></figure>`.
Le parser inverse (`htmlToBlocks`) lit `data-align`, et descend dans `<a>`
quand l'`<img>` y est imbriqué pour récupérer `href` / `target`.
## Limitations connues
- Pas d'imbrication de listes.
- Image : URL uniquement, pas d'upload de fichier (Phase 2). La caption est
une string plate (pas de formatage inline pour l'instant).
- Image : URL uniquement, pas d'upload de fichier. La caption est une
string plate (pas de formatage inline pour l'instant).
- Tables : Phase 3.