feat(media): extract media details into reusable modal component
- add `MediaDetailsModal.client.js` with support for `media={…}` or `slug="…"` props
- add `GET /zen/api/media/by-slug/:slug` route for slug-based lookup
- refactor `MediaPage.client.js` to use the new modal instead of inline details panel
- export `MediaDetailsModal` as `./features/media/details-modal` in package.json
- update `BlockEditor` image block to open `MediaDetailsModal` for media editing
- update media feature README to document new component and route
This commit is contained in:
@@ -300,14 +300,33 @@ Le formulaire d'insertion accepte deux sources :
|
||||
### Liaison avec la médiathèque (`mediaSlug`)
|
||||
|
||||
Quand un bloc image est lié à un média (`mediaSlug` présent), le **média est
|
||||
la source unique de vérité** pour `alt` et `caption`. Le bloc ne stocke ni l'un
|
||||
ni l'autre — l'éditeur affiche les valeurs du média en lecture seule avec un
|
||||
lien « Modifier dans la médiathèque ». Toute mise à jour de l'alt ou de la
|
||||
légende côté médiathèque est immédiatement répercutée sur tous les blocs qui
|
||||
référencent ce média, sans toucher aux contenus.
|
||||
la source unique de vérité** pour `alt` et `caption`. Le bloc ne stocke ni
|
||||
l'un ni l'autre. Toute mise à jour côté médiathèque est immédiatement
|
||||
répercutée sur tous les blocs qui référencent ce média, sans toucher aux
|
||||
contenus.
|
||||
|
||||
Pour les **URL externes** (pas de `mediaSlug`), les inputs alt/caption locaux
|
||||
restent disponibles sur le bloc — il n'y a pas d'autre source possible.
|
||||
Pour les **URL externes** (pas de `mediaSlug`), `alt` et `caption` vivent
|
||||
sur le bloc — il n'y a pas d'autre source possible.
|
||||
|
||||
### Affichage et édition des métadonnées
|
||||
|
||||
Sous l'image, on n'affiche **que la légende**, et **uniquement si elle
|
||||
existe**. Pas de placeholder « Aucune légende », pas de bandeau gris, pas
|
||||
d'alt visible (l'alt vit sur l'attribut `<img alt>`). L'objectif est que le
|
||||
rendu en édition reflète exactement ce que verra le lecteur final.
|
||||
|
||||
L'édition des métadonnées passe par un **bouton dédié** dans la toolbar
|
||||
flottante de l'image (icône `Settings02Icon`) :
|
||||
|
||||
- Image **liée à la médiathèque** → ouvre `MediaDetailsModal`, le **même
|
||||
composant** que celui de la page admin Médias (visibilité, alt, caption,
|
||||
URL, suppression). Toute édition met à jour `zen_media` directement.
|
||||
- Image **URL externe** → ouvre une modale légère avec deux champs (alt,
|
||||
caption) qui patchent le bloc local.
|
||||
|
||||
`MediaDetailsModal` est exposé en entry public `@zen/core/features/media/details-modal`
|
||||
et accepte soit `media={media}` (objet déjà chargé), soit `slug="…"` (le bloc
|
||||
image l'utilise — résolution via `GET /zen/api/media/by-slug/:slug`).
|
||||
|
||||
Champs internes au composant (préfixe `_`, jamais persistés) :
|
||||
|
||||
@@ -373,18 +392,14 @@ Une fois l'URL insérée, l'image affiche au survol une **toolbar flottante**
|
||||
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.
|
||||
- **Métadonnées** (`Settings02Icon`) : ouvre la modale d'édition alt/caption.
|
||||
Pour une image liée à la médiathèque, c'est `MediaDetailsModal` (édition
|
||||
directe du média en BD). Pour une URL externe, c'est une modale légère qui
|
||||
patche `block.alt` / `block.caption`.
|
||||
- **Remplacer** : remet le formulaire URL avec l'URL courante préremplie.
|
||||
Conserve `alt`, `caption`, `align`, `href`. Échap pour annuler.
|
||||
Conserve `mediaSlug`, `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 (uniquement pour les images sans `mediaSlug` — cf.
|
||||
[Liaison avec la médiathèque](#liaison-avec-la-médiathèque-mediaslug)).
|
||||
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`. Si le `src`
|
||||
|
||||
Reference in New Issue
Block a user