docs(BlockEditor): document mediaSlug media library link and add server helpers
- update image block schema in README table to include `mediaSlug?` and clarify fields - add "Liaison avec la médiathèque" section documenting mediaSlug behavior, read-only alt/caption, and internal `_` fields - document new server helpers (`normalizeImageBlocks`, `enrichBlocksWithMedia`, `syncBlockImageReferences`) with usage examples - add `block_image` field convention to media feature README with cross-references - implement `mediaLink.server.js` with the three server-side helpers - store `mediaSlug` on image block at insertion time in `Image.client.js` - persist `mediaSlug` through clipboard paste/duplicate in `clipboard.js` - export `mediaLink` entry point in `package.json` exports map
This commit is contained in:
@@ -15,6 +15,16 @@ import { inlineFromText, inlineToPlainText, normalize } from './types.js';
|
||||
|
||||
const HEADING_RE = /^heading_([1-6])$/;
|
||||
const BLOCK_TAG_RE = /^(P|H[1-6]|UL|OL|BLOCKQUOTE|PRE|HR|FIGURE|DIV|TABLE)$/;
|
||||
const MEDIA_FILE_URL_RE = /^\/zen\/api\/media\/file\/([^/?#]+)$/;
|
||||
|
||||
// Quand une image collée pointe sur la médiathèque interne, on dérive le
|
||||
// slug pour reconstituer le lien `mediaSlug` (un copier-coller ne traverse
|
||||
// pas les champs cachés). Pour les URL externes, retourne null.
|
||||
function imageSlugFromSrc(src) {
|
||||
if (typeof src !== 'string') return null;
|
||||
const m = MEDIA_FILE_URL_RE.exec(src);
|
||||
return m ? m[1] : null;
|
||||
}
|
||||
|
||||
// Block[] → HTML string. Regroupe les listes consécutives sous un seul
|
||||
// <ul>/<ol>. Les blocs inconnus deviennent un <p> au texte aplati.
|
||||
@@ -98,9 +108,15 @@ function blockToElement(block) {
|
||||
: align === 'left' ? 'text-align:left'
|
||||
: 'text-align:right');
|
||||
}
|
||||
// Quand le bloc est lié à un média (`mediaSlug`), alt/caption sont
|
||||
// résolus côté serveur (`_resolvedAlt` / `_resolvedCaption`) ou
|
||||
// captés à l'insertion (`_mediaAlt` / `_mediaCaption`). Pour les URL
|
||||
// externes, on retombe sur les champs locaux du bloc.
|
||||
const altText = block._resolvedAlt ?? block._mediaAlt ?? block.alt ?? '';
|
||||
const captionText = block._resolvedCaption ?? block._mediaCaption ?? block.caption ?? '';
|
||||
const img = document.createElement('img');
|
||||
img.setAttribute('src', block.src || '');
|
||||
if (block.alt) img.setAttribute('alt', block.alt);
|
||||
if (altText) img.setAttribute('alt', altText);
|
||||
if (align === 'full') img.setAttribute('width', '100%');
|
||||
let imgHost = img;
|
||||
if (block.href) {
|
||||
@@ -114,9 +130,9 @@ function blockToElement(block) {
|
||||
imgHost = a;
|
||||
}
|
||||
fig.appendChild(imgHost);
|
||||
if (block.caption) {
|
||||
if (captionText) {
|
||||
const cap = document.createElement('figcaption');
|
||||
cap.textContent = block.caption;
|
||||
cap.textContent = captionText;
|
||||
fig.appendChild(cap);
|
||||
}
|
||||
return fig;
|
||||
@@ -133,7 +149,11 @@ export function blocksToPlainText(blocks) {
|
||||
return blocks
|
||||
.map(b => {
|
||||
if (b.type === 'divider') return '---';
|
||||
if (b.type === 'image') return b.alt || b.caption || '';
|
||||
if (b.type === 'image') {
|
||||
const alt = b._resolvedAlt ?? b._mediaAlt ?? b.alt ?? '';
|
||||
const cap = b._resolvedCaption ?? b._mediaCaption ?? b.caption ?? '';
|
||||
return alt || cap || '';
|
||||
}
|
||||
return inlineToPlainText(b.content ?? []);
|
||||
})
|
||||
.join('\n');
|
||||
@@ -296,15 +316,21 @@ function parseChildren(node, out) {
|
||||
const align = ['left', 'center', 'right', 'full'].includes(dataAlign) ? dataAlign : 'center';
|
||||
const href = linkEl?.getAttribute('href') || '';
|
||||
const newTab = !!href && linkEl?.getAttribute('target') === '_blank';
|
||||
const src = img.getAttribute('src') || '';
|
||||
const slug = imageSlugFromSrc(src);
|
||||
const altText = img.getAttribute('alt') || '';
|
||||
const captionText = cap?.textContent?.trim() || '';
|
||||
out.push({
|
||||
id: newBlockId(),
|
||||
type: 'image',
|
||||
src: img.getAttribute('src') || '',
|
||||
alt: img.getAttribute('alt') || '',
|
||||
caption: cap?.textContent?.trim() || '',
|
||||
src,
|
||||
mediaSlug: slug || '',
|
||||
alt: slug ? '' : altText,
|
||||
caption: slug ? '' : captionText,
|
||||
align,
|
||||
href,
|
||||
newTab,
|
||||
...(slug ? { _mediaAlt: altText, _mediaCaption: captionText } : null),
|
||||
});
|
||||
}
|
||||
continue;
|
||||
@@ -312,15 +338,20 @@ function parseChildren(node, out) {
|
||||
|
||||
if (tag === 'IMG') {
|
||||
flush();
|
||||
const src = child.getAttribute('src') || '';
|
||||
const slug = imageSlugFromSrc(src);
|
||||
const altText = child.getAttribute('alt') || '';
|
||||
out.push({
|
||||
id: newBlockId(),
|
||||
type: 'image',
|
||||
src: child.getAttribute('src') || '',
|
||||
alt: child.getAttribute('alt') || '',
|
||||
src,
|
||||
mediaSlug: slug || '',
|
||||
alt: slug ? '' : altText,
|
||||
caption: '',
|
||||
align: 'center',
|
||||
href: '',
|
||||
newTab: false,
|
||||
...(slug ? { _mediaAlt: altText } : null),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user