feat(ui): add MediaPicker integration to BlockEditor image block
- import MediaPicker from features/media to avoid circular dependency with @zen/core - add "Choisir un média" button in ImageUrlForm alongside existing URL input - insert image block with `/zen/api/media/file/<slug>` src on media selection - update README to document dual-source form (external URL + media library) and revise known limitations
This commit is contained in:
@@ -287,6 +287,15 @@ descendants. Les styles CSS inline (`font-weight`, `font-style`,
|
|||||||
|
|
||||||
## Bloc image
|
## Bloc image
|
||||||
|
|
||||||
|
Le formulaire d'insertion accepte deux sources :
|
||||||
|
|
||||||
|
- **URL externe** : champ libre + bouton « Insérer ».
|
||||||
|
- **Bibliothèque media** : bouton « Choisir un média » qui ouvre le `MediaPicker`
|
||||||
|
de `@zen/core/features/media`. À la sélection, le bloc est inséré avec
|
||||||
|
`block.src = "/zen/api/media/file/<slug>"`. Les nouveaux uploads passent par
|
||||||
|
ce picker en visibilité `public` (les blocs image vivent dans du contenu
|
||||||
|
publié — un média privé serait illisible côté frontal).
|
||||||
|
|
||||||
Une fois l'URL insérée, l'image affiche au survol une **toolbar flottante**
|
Une fois l'URL insérée, l'image affiche au survol une **toolbar flottante**
|
||||||
(coin haut-droit) reprenant le style `BOX_CLASS` partagé :
|
(coin haut-droit) reprenant le style `BOX_CLASS` partagé :
|
||||||
|
|
||||||
@@ -316,6 +325,7 @@ quand l'`<img>` y est imbriqué pour récupérer `href` / `target`.
|
|||||||
## Limitations connues
|
## Limitations connues
|
||||||
|
|
||||||
- Pas d'imbrication de listes.
|
- Pas d'imbrication de listes.
|
||||||
- Image : URL uniquement, pas d'upload de fichier. La caption est une
|
- Image : insertion par URL externe ou via le `MediaPicker` du module
|
||||||
string plate (pas de formatage inline pour l'instant).
|
`media` (les uploads y sont gérés). La caption est une string plate
|
||||||
|
(pas de formatage inline pour l'instant).
|
||||||
- Tables : Phase 3.
|
- Tables : Phase 3.
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ import {
|
|||||||
SEPARATOR_VERTICAL_CLASS,
|
SEPARATOR_VERTICAL_CLASS,
|
||||||
} from '../inline/menuStyles.js';
|
} from '../inline/menuStyles.js';
|
||||||
import { newBlockId } from '../utils/ids.js';
|
import { newBlockId } from '../utils/ids.js';
|
||||||
|
// Import relatif pour éviter une dépendance circulaire avec @zen/core (le bloc
|
||||||
|
// image vit dans `shared` et tire MediaPicker depuis `features/media`).
|
||||||
|
import MediaPicker from '../../../../features/media/components/MediaPicker.client.js';
|
||||||
|
|
||||||
// Bloc image. État vide = formulaire d'insertion d'URL. État rempli = image
|
// Bloc image. État vide = formulaire d'insertion d'URL. État rempli = image
|
||||||
// rendue + toolbar flottante (alignement, lien, remplacer, supprimer) +
|
// rendue + toolbar flottante (alignement, lien, remplacer, supprimer) +
|
||||||
@@ -44,6 +47,7 @@ function getAlign(block) {
|
|||||||
|
|
||||||
function ImageUrlForm({ initialUrl, onSubmit, onCancel, disabled }) {
|
function ImageUrlForm({ initialUrl, onSubmit, onCancel, disabled }) {
|
||||||
const [url, setUrl] = useState(initialUrl ?? '');
|
const [url, setUrl] = useState(initialUrl ?? '');
|
||||||
|
const [pickerOpen, setPickerOpen] = useState(false);
|
||||||
const inputRef = useRef(null);
|
const inputRef = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -67,6 +71,7 @@ function ImageUrlForm({ initialUrl, onSubmit, onCancel, disabled }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<div className="flex items-center gap-2 rounded-lg border border-dashed border-neutral-300 dark:border-neutral-700 bg-neutral-50 dark:bg-neutral-800/40 px-3 py-3">
|
<div className="flex items-center gap-2 rounded-lg border border-dashed border-neutral-300 dark:border-neutral-700 bg-neutral-50 dark:bg-neutral-800/40 px-3 py-3">
|
||||||
<Image01Icon width={18} height={18} className="text-neutral-500" />
|
<Image01Icon width={18} height={18} className="text-neutral-500" />
|
||||||
<input
|
<input
|
||||||
@@ -87,6 +92,14 @@ function ImageUrlForm({ initialUrl, onSubmit, onCancel, disabled }) {
|
|||||||
>
|
>
|
||||||
Insérer
|
Insérer
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setPickerOpen(true)}
|
||||||
|
disabled={disabled}
|
||||||
|
className="px-3 py-1 text-sm rounded border border-neutral-300 dark:border-neutral-600 text-neutral-700 dark:text-neutral-200 hover:bg-neutral-100 dark:hover:bg-neutral-700/60 disabled:opacity-50"
|
||||||
|
>
|
||||||
|
Choisir un média
|
||||||
|
</button>
|
||||||
{onCancel && (
|
{onCancel && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -97,6 +110,18 @@ function ImageUrlForm({ initialUrl, onSubmit, onCancel, disabled }) {
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<MediaPicker
|
||||||
|
isOpen={pickerOpen}
|
||||||
|
onClose={() => setPickerOpen(false)}
|
||||||
|
onSelect={(media) => {
|
||||||
|
if (media?.slug) onSubmit?.(`/zen/api/media/file/${media.slug}`);
|
||||||
|
}}
|
||||||
|
accept="image/*"
|
||||||
|
visibility="any"
|
||||||
|
uploadVisibility="public"
|
||||||
|
title="Insérer une image depuis la bibliothèque"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user