import * as React from 'react' import {__, _x, sprintf} from '@wordpress/i18n' import { useBlockProps, InspectorControls, InspectorAdvancedControls, BlockControls, MediaPlaceholder, MediaReplaceFlow, // @ts-expect-error } from '@wordpress/block-editor' import { useEffect, useMemo, useState, } from '@wordpress/element' import { Button, ToolbarButton, BaseControl, TextControl, TextareaControl, ToggleControl, RangeControl, Notice, __experimentalHStack as HStack, __experimentalVStack as VStack, } from '@wordpress/components' import { select, useSelect, } from '@wordpress/data' import { store as coreDataStore, useEntityProp, type Attachment, } from '@wordpress/core-data' import { createBlock, // @ts-expect-error } from '@wordpress/blocks' import { __unstableStripHTML as stripHTML, } from '@wordpress/dom' import { Player as LottiePlayer, Controls as LottiePlayerControls, } from '@lottiefiles/react-lottie-player' import { ButtonGroup, skaSvgIcon, WithTooltip, } from '@ska/components' import { SkaPanelBody, } from '@ska/plugin' import { IS_DEBUG, sanitizeSVG, usePostMeta, } from '@ska/utils' import { IconPicker, PlaceholderImageControl, SVGEditModal, ImageSizeControl, } from '../../components' import { placeholderImageUrl, } from '../../data' import InlineImageButton from './InlineImageButton' // @ts-ignore import metadata from './block.json' import { useLinkWrapper, LinkAttributes, LinkControls, } from '../../supports/link' import { useAttributes, AttributesAttributes, } from '../../supports' import { usePluginPreference, } from '../../store' import SVGStringRenderer, {SVGStringRendererNoRef} from './SVGStringRenderer' import { html_beautify, // @ts-expect-error } from 'js-beautify' const HTML_BEAUTIFY_SETTINGS = { 'indent_size': '1', 'indent_char': '\t', 'max_preserve_newlines': '-1', 'preserve_newlines': false, 'end_with_newline': false, } import type { SkaBlocks, } from '../../types' import { type BlockModule, type tImage, type tBlockEditProps, type tBlockSaveProps, type tFile, getPlaceholderSvg, getAttachmentUrlBySizeSlug, getAttachmentSizeSlugByUrl, selectImage, } from '@ska/shared' import type { CoreAlignAttributes, } from '..' import './style.scss' const MODES = [ { label: _x('File', 'Image block mode', 'ska-blocks'), value: 'file', }, { label: _x('SVG', 'Image block mode', 'ska-blocks'), value: 'svg', }, { label: _x('Icon', 'Image block mode', 'ska-blocks'), value: 'icon', }, { label: _x('Lottie', 'Image block mode', 'ska-blocks'), value: 'lottie', }, { label: _x('Placeholder', 'Image block mode', 'ska-blocks'), value: 'placeholder', }, ] const ROLES = [ { label: _x('Figure', 'Image block role', 'ska-blocks'), tooltip: _x(`Apply role="figure" to wrapper element, image should have an alt text provided.`, 'Image block role description', 'ska-blocks'), value: 'figure', }, { label: _x('Presentation', 'Image block role', 'ska-blocks'), tooltip: _x('Hide the image from assistive technologies.', 'Image block role description', 'ska-blocks'), value: 'presentation', }, { label: _x('None', 'Image block role', 'ska-blocks'), tooltip: _x(`Don't apply any semantics.`, 'Image block role description', 'ska-blocks'), value: 'none', }, ] const LINK_ROLES = ROLES.map(role => ({ ...role, ...(role.value === 'figure' && { label: _x('Link', 'Image block role', 'ska-blocks'), tooltip: _x(`Image is wrapped in a link, provide an "aria-label" or "title" attribute, or fall back to the image's alt text.`, 'Image block role description', 'ska-blocks'), }), })) const LOADING = [ { label: _x('Lazy', 'Image loading attribute', 'ska-blocks'), tooltip: _x(`Lazyload the image.`, 'Image loading description', 'ska-blocks'), value: 'lazy', }, { label: _x('Eager', 'Image loading attribute', 'ska-blocks'), tooltip: _x(`Don't lazyload the image.`, 'Image loading description', 'ska-blocks'), value: 'eager', }, { label: _x('LCP', 'Image loading attribute', 'ska-blocks'), tooltip: _x(`Enable high fetchpriority and synchronous decoding.`, 'Image loading description', 'ska-blocks'), value: 'lcp', }, { label: _x('Preload', 'Image loading attribute', 'ska-blocks'), tooltip: _x(`Preload the image in document head.`, 'Image loading description', 'ska-blocks'), value: 'preload', }, ] /** These need to match whatever is defined in `DynamicLinks.php` as `manual=true` links. */ const IMAGE_DYNAMIC_LINKS = [ { label: _x('Attachment page', 'Image dynamic link name', 'ska-blocks'), value: '#ska-link--media-attachment', }, { label: _x('Full size image', 'Image dynamic link name', 'ska-blocks'), value: '#ska-link--media-file', }, ] export interface ImageBlockAttributes extends LinkAttributes, AttributesAttributes, CoreAlignAttributes { mode?: 'file' | 'svg' | 'icon' | 'lottie' | 'placeholder' role?: 'figure' | 'presentation' | 'none' /** Use featured image. */ featured?: boolean featuredSize: string featuredCrop: boolean /** Whether to show placeholder on front end when featured image is missing. */ featuredPlaceholder: boolean /** Skips rendering the block when post content contains `ska/image` with `featured` `true`. */ featuredExclusive: boolean /** Meta key that contains image ID. */ metaKey?: string width?: number height?: number svg?: string id?: number src?: string alt?: string /** Icon collection name. */ collection?: string /** Icon name. */ icon?: string /** Image loading mode. */ loading?: 'lazy' | 'eager' | 'lcp' | 'preload' isPlaceholder?: boolean /** Display larger image when clicking on the image. */ lightbox?: boolean /** Wrap image in role="figure". */ wrap?: boolean /** Shorthand to add `w-full h-full object-cover rounded-[inherit] aspect-[inherit]` to image when it has a wrapper. */ cover?: boolean /** Add `srcset` to image when possible. */ srcset?: boolean /** Add low res placeholder background to the image. */ placeholder?: boolean /** Whether to preload low res placeholder in head. */ preloadPlaceholder?: boolean /** Placeholder image index. */ placeholderIndex?: number /** Sanitize svg. */ sanitize?: boolean lottieControls?: boolean lottieAutoplay?: boolean lottieLoop?: boolean lottieHover?: boolean lottieMode?: 'normal' | 'bounce' lottieSpeed?: number lottieDirection?: 1 | -1 } export const COVER_CLASSES = 'w-full h-full object-cover rounded-[inherit] aspect-[inherit]' const FEATURED_IMAGE_PLACEHOLDER = '%SKA_FEATURED_IMAGE%' const PLACEHOLDER_IMAGE_PLACEHOLDER = '%SKA_PLACEHOLDER_IMAGE%' export const getPlaceholder = (args: {width?: string | number, height?: string | number, cover?: boolean, placeholderIndex?: number} = {}) => { const { width = '100%', height = '100%', cover = false, placeholderIndex = 0, } = args if(placeholderIndex > 0 && placeholderImageUrl) { // @ts-expect-error return `` } return getPlaceholderSvg({width, height, className: cover ? COVER_CLASSES : ''}) } const getPlaceholderIcon = () => skaSvgIcon const ACCEPT_LOTTIE = undefined const ALLOWED_TYPES_LOTTIE = ['application/json', 'text/plain'] const LOTTIE_MODES = [ { label: __('Normal', 'ska-blocks'), value: 'normal', }, { label: __('Bounce', 'ska-blocks'), value: 'bounce', }, ] const LottiePlayerElement = 'lottie-player' const getRoleProps = ({ role = 'figure', mode = 'file', isLink = false, wrap = false, }: { role: ImageBlockAttributes['role'], mode: ImageBlockAttributes['mode'], isLink: boolean, /** Are we wrapping or not, this isn't the block attribute `wrap` but the computed `shouldWrap`, e.g. in lightbox/link/icon mode we wrap regardless. */ wrap: boolean, }) => { // Plain svg is aria-hidden by default, but can be overridden with custom attribute. if(mode === 'svg' && !wrap) { return [ {'aria-hidden': true}, {}, ] } // Icon is always wrapped. if(mode === 'icon' && !isLink && role === 'figure') { return [ { role, 'aria-hidden': true, // For back-compat, otherwise would remove }, {}, ] } // Plain images with role=presentation. if(mode === 'file' && !wrap && role === 'presentation') { return [ {'aria-hidden': true}, {}, ] } // Wrapped images with role=presentation. if(mode === 'file' && wrap && !isLink && role === 'presentation') { return [ {'aria-hidden': true}, {}, ] } if(role === 'presentation' && wrap && !isLink) { // Hide the wrapper element, hiding the contained media as well. return [ {'aria-hidden': true}, {}, ] } if(mode === 'placeholder' && wrap && !isLink) { return [ { ...(role === 'figure' && {role}), // Back-compat ...(role === 'presentation' && { 'aria-hidden': true, }), }, {}, ] } return [ { // Apply the role for non-links. ...(wrap && !isLink && role !== 'none' && { role, }), }, { // Props for the media element inside the wrapper. ...wrap && { // Hide media inside ...(role === 'presentation' && isLink && { 'aria-hidden': true, }), }, }, ] } const ImageInspectorControls: React.FC> = props => { const [isOpen, setIsOpen] = useState(false) const { attributes, setAttributes, context, } = props const { postId, postType, } = context const { mode = 'placeholder', role = 'figure', featured = false, featuredSize = 'full', featuredCrop = true, featuredPlaceholder = false, metaKey = '', width, height, id, svg = '', src, alt = '', collection, icon, loading, lightbox = false, wrap = true, cover = false, srcset = true, placeholder = false, preloadPlaceholder = true, placeholderIndex = 0, sanitize = true, lottieControls = false, lottieAutoplay = true, lottieLoop = true, lottieHover = false, lottieMode = 'normal', lottieSpeed = 1, lottieDirection = 1, } = attributes const [_featuredImageId] = useEntityProp('postType', postType, 'featured_media', postId) const postMeta = usePostMeta(postType, postId) const metaValue = featured && metaKey && Object(postMeta).hasOwnProperty(metaKey) && !isNaN(postMeta[metaKey]) ? parseInt(postMeta[metaKey]) : undefined const featuredImageId = metaKey ? metaValue : _featuredImageId const media = useSelect(select => { const mediaId = featured ? featuredImageId : id if(mode !== 'file' || !mediaId) { return undefined } return select(coreDataStore).getEntityRecord('postType', 'attachment', mediaId) }, [mode, id, featured, featuredImageId]) /** Automatically set image width and height when they are missing but available. */ useEffect(() => { if(media?.media_details?.sizes && src && !width && !height) { const found = Object.keys(media.media_details.sizes).find(size => { return media.media_details.sizes[size].source_url === src }) if(found) { const details = media.media_details.sizes[found] const { width: imageWidth = 0, height: imageHeight = 0, } = details if(imageWidth > 0 && imageHeight > 0) { setAttributes({width: imageWidth, height: imageHeight}) } } } }, [media, src, width, height, setAttributes]) const isDataURL = src && src.startsWith('data:image') const supportsResponsive = mode === 'file' && ((id && id > 0) || featured) && !isDataURL const supportsLoading = mode === 'file' && ((src && !isDataURL) || featured) const supportsLightbox = mode === 'file' && !isDataURL const supportsPlaceholder = supportsResponsive && supportsLoading && !!media?.media_details?.sizes?.medium const isLightbox = supportsLightbox && lightbox const supportsSvgEdit = mode === 'svg' || (mode === 'icon' && svg) const [Element] = useLinkWrapper(isLightbox ? 'a' : 'div', props) const isLink = Element === 'a' const shouldWrap = isLightbox || isLink || mode === 'icon' || mode === 'placeholder' ? true : wrap const sanitizedSvg = useMemo(() => sanitize ? sanitizeSVG(svg) : svg, [svg, sanitize]) const svgIsAutoAriaHidden = svg && !shouldWrap && ( !('aria-hidden' in (attributes?.skaBlocksAttributes?.record || {})) && svg.indexOf('aria-hidden="false"') < 1 ) const svgInvalid = svg && !sanitizedSvg return <> { value && setAttributes({ mode: value as ImageBlockAttributes['mode'], /** Reset src when it's a placeholder value. */ ...(src && src.indexOf('%SKA_') === 0 && { src: '', }), /** Reset when switching between file/lottie mode. */ ...(((mode === 'lottie' && value === 'file') || (mode === 'file' && value === 'lottie')) && { id: undefined, src: '', }), }) }} /> {/* Role is not applied to ``-s and unwrapped ``-s are always `aria-hidden=true` (can be overridden with custom attributes though). */} {(mode !== 'svg' || shouldWrap) && <> setAttributes({role: (value || 'figure') as ImageBlockAttributes['role']})} useDefaultButtons /> } {isLink && !isLightbox && ( )} {supportsSvgEdit && <> {isOpen && ( setAttributes({svg: nextSvg})} onRequestClose={() => setIsOpen(false)} /> )} } {mode === 'svg' && <> setAttributes({wrap: !wrap})} __nextHasNoMarginBottom /> {svgIsAutoAriaHidden && ( )} {svgInvalid && ( Sanitize SVG option to allow rendering it.`, 'ska-blocks')} /> )} } {mode === 'icon' && <> { setAttributes({ collection, icon, svg: html_beautify(svg, HTML_BEAUTIFY_SETTINGS), }) }} /> } {mode === 'lottie' && <> setAttributes({lottieControls: !lottieControls})} __nextHasNoMarginBottom /> setAttributes({lottieAutoplay: !lottieAutoplay})} __nextHasNoMarginBottom /> setAttributes({lottieLoop: !lottieLoop})} __nextHasNoMarginBottom /> setAttributes({lottieHover: !lottieHover})} __nextHasNoMarginBottom /> setAttributes({lottieMode: value as ImageBlockAttributes['lottieMode']})} /> setAttributes({lottieSpeed: nextSpeed})} min={0.1} max={5} step={0.1} __nextHasNoMarginBottom __next40pxDefaultSize /> setAttributes({lottieDirection: lottieDirection === 1 ? -1 : 1})} __nextHasNoMarginBottom /> } {mode === 'file' && <> {(id || src || featured) && role !== 'presentation' && ( setAttributes({alt: value})} __nextHasNoMarginBottom /> )} setAttributes({featured: !featured})} __nextHasNoMarginBottom /> {featured && <> setAttributes({featuredPlaceholder: !featuredPlaceholder})} __nextHasNoMarginBottom /> } {supportsLightbox && <> setAttributes({lightbox: !lightbox})} __nextHasNoMarginBottom /> } {!isLightbox && <> setAttributes({wrap: !wrap})} __nextHasNoMarginBottom /> } } {(mode === 'file' || mode === 'placeholder') && <> {shouldWrap && <> setAttributes({cover: !cover})} __nextHasNoMarginBottom /> } } {supportsResponsive && <> setAttributes({srcset: !srcset})} __nextHasNoMarginBottom /> } {supportsPlaceholder && <> setAttributes({placeholder: !placeholder})} __nextHasNoMarginBottom /> {placeholder && <> setAttributes({preloadPlaceholder: !preloadPlaceholder})} __nextHasNoMarginBottom /> } } {supportsLoading && <> setAttributes({loading: value as ImageBlockAttributes['loading']})} useDefaultButtons /> } {mode === 'file' && <> { if(featured) { setAttributes({ ...(slug && { featuredSize: slug, }), ...(width && height && { width, height, }), }) } else { setAttributes({ src, ...(width && height && { width, height, }), }) } }} resizable={!featured} custom={featured} /> } {mode === 'file' && ( setAttributes({width: Number(value) || 0})} __nextHasNoMarginBottom __next40pxDefaultSize /> setAttributes({height: Number(value) || 0})} __nextHasNoMarginBottom __next40pxDefaultSize /> {featured && featuredSize === 'custom' && ( setAttributes({featuredCrop: !featuredCrop})} __nextHasNoMarginBottom /> )} )} {mode === 'placeholder' && <> setAttributes({placeholderIndex})} showPlaceholderImageToggle /> } } const Edit: React.FC> = props => { const { attributes, setAttributes, context, isSelected, } = props const { postId, postType, } = context const { mode = 'placeholder', role = 'figure', featured = false, featuredSize = 'full', featuredExclusive = false, metaKey = '', width, height, id, svg = '', src, alt = '', loading, lightbox = false, wrap = true, cover = false, placeholderIndex = 0, sanitize = true, lottieControls = false, lottieAutoplay = true, lottieLoop = true, lottieHover = false, lottieSpeed = 1, lottieDirection = 1, } = attributes const { children, ...blockProps } = useBlockProps({className: 'image'}) const attributesProps = useAttributes(props) const [_featuredImageId] = useEntityProp('postType', postType, 'featured_media', postId) const postMeta = usePostMeta(postType, postId) const metaValue = featured && metaKey && Object(postMeta).hasOwnProperty(metaKey) && !isNaN(postMeta[metaKey]) ? parseInt(postMeta[metaKey]) : undefined const featuredImageId = metaKey ? metaValue : _featuredImageId const [showPlaceholderImage] = usePluginPreference('showPlaceholderImage') const media = useSelect(select => { const mediaId = featured ? featuredImageId : id if(mode !== 'file' || !mediaId) { return undefined } return select(coreDataStore).getEntityRecord('postType', 'attachment', mediaId) }, [mode, id, featured, featuredImageId]) const isDataURL = src && src.startsWith('data:image') const supportsInlining = mode === 'file' && src && !isDataURL const supportsLoading = mode === 'file' && ((src && !isDataURL) || featured) const supportsLightbox = mode === 'file' && !isDataURL const isLightbox = supportsLightbox && lightbox const [Element, linkProps] = useLinkWrapper(isLightbox ? 'a' : 'div', props) const isLink = Element === 'a' const imgProps = { src, ...(mode === 'file' && featured && { src: '', ...(media && { src: getAttachmentUrlBySizeSlug(media, featuredSize), }), }), ...(!!alt && {alt}), ...(width && width > 0 && {width}), ...(height && height > 0 && {height}), ...(supportsLoading && loading === 'lazy' && {loading}), ...((wrap && cover) && {className: COVER_CLASSES}), } const hasFile = !!imgProps.src const onSelect = (selectedImage: tImage) => { const image = selectImage(selectedImage, getAttachmentSizeSlugByUrl(media, src)) setAttributes({ id: image.id, src: image.url, width: image.width, height: image.height, mode: 'file', ...(image.alt && {alt: image.alt}), }) } const onSelectURL = (url: string) => { setAttributes({ id: undefined, src: url, mode: 'file', }) } const onSelectLottie = (file: tFile) => { setAttributes({ id: file.id, src: file.url, mode: 'lottie', }) } const onSelectLottieURL = (url: string) => { setAttributes({ id: undefined, src: url, mode: 'lottie', }) } const onClear = () => { setAttributes({ id: undefined, src: undefined, width: undefined, height: undefined, }) } const hasLottie = !!src const shouldWrap = isLightbox || isLink || mode === 'icon' || mode === 'placeholder' ? true : wrap const shouldCover = (mode === 'file' || mode === 'placeholder') && shouldWrap && cover const [roleProps, imgRoleProps] = getRoleProps({role, mode, isLink, wrap: shouldWrap}) const rootProps = { ...linkProps, ...attributesProps, ...blockProps, } const sanitizedSvg = useMemo(() => sanitize ? sanitizeSVG(svg) : svg, [svg, sanitize]) return <> {mode === 'file' && featured && <> setAttributes({metaKey: value})} __nextHasNoMarginBottom __next40pxDefaultSize /> setAttributes({featuredExclusive: !featuredExclusive})} __nextHasNoMarginBottom /> } {mode === 'svg' && <> setAttributes({sanitize: !sanitize})} __nextHasNoMarginBottom /> } {supportsInlining && <> { setAttributes({ id: undefined, src: dataURL, }) }} /> } {!isLightbox && ( )} {((mode === 'file' && !featured) || mode === 'placeholder') && ( {(!id && !src) && <> } {(id || src) && <> } )} {mode === 'lottie' && hasLottie && ( )} {mode === 'svg' && <> {shouldWrap && <> } {!shouldWrap && <> } } {mode === 'icon' && <> } {mode === 'file' && hasFile && <> {shouldWrap && ( )} {!shouldWrap && ( )} } {((mode === 'file' && featured && !hasFile) || (mode === 'placeholder' && (!isSelected || showPlaceholderImage))) && ( )} {((mode === 'placeholder' && isSelected && !showPlaceholderImage) || (mode === 'file' && !featured && !hasFile)) && (
)} {mode === 'lottie' && !hasLottie && (
)} {mode === 'lottie' && hasLottie && (
)} } const Save: React.FC> = props => { const { attributes, } = props const { mode = 'placeholder', role = 'figure', featured = false, width, height, svg, src, alt, loading, lightbox = false, wrap = true, cover = false, placeholderIndex = 0, sanitize = true, lottieControls = false, lottieAutoplay = true, lottieLoop = true, lottieHover = false, lottieMode = 'normal', lottieSpeed = 1, lottieDirection = 1, } = attributes const { children, ...blockProps } = useBlockProps.save() const isDataURL = src && src.startsWith('data:image') const supportsLoading = mode === 'file' && ((src && !isDataURL) || featured) const supportsLightbox = mode === 'file' && !isDataURL const isLightbox = supportsLightbox && lightbox const [Element, linkProps] = useLinkWrapper.save(isLightbox ? 'a' : 'div', props) const isLink = Element === 'a' const shouldWrap = lightbox || isLink || mode === 'icon' || mode === 'placeholder' ? true : wrap const [roleProps, imgRoleProps] = getRoleProps({role, mode, isLink, wrap: shouldWrap}) const attributesProps = useAttributes.save(props) if(mode === 'lottie') { if(!src) { return null } return ( {/* @ts-ignore */} ) } if(['placeholder', 'svg', 'icon'].includes(mode) && placeholderIndex === 0) { const sanitizedSvg = sanitize ? sanitizeSVG(svg) : svg if(mode === 'svg' && !shouldWrap) { return ( ) } return ( ) } const imgProps = { src, ...(mode === 'file' && featured && { src: FEATURED_IMAGE_PLACEHOLDER, }), ...(mode === 'placeholder' && placeholderIndex > 0 && { src: PLACEHOLDER_IMAGE_PLACEHOLDER, }), ...(!!alt && {alt}), ...(width && width > 0 && {width}), ...(height && height > 0 && {height}), ...(supportsLoading && loading === 'lazy' && {loading}), ...((wrap && cover) && {className: COVER_CLASSES}), } if(!imgProps.src) { return null } return <> {shouldWrap && ( )} {!shouldWrap && ( )} } const defaultAttributes = {} export default (skaBlocks: SkaBlocks): BlockModule => ({ metadata, settings: { edit: Edit, save: Save, variations: [ { isDefault: true, attributes: defaultAttributes, }, { name: 'icon', title: __('Icon', 'ska-blocks'), description: __('Scalable vector graphics that can be used as visual symbols.', 'ska-blocks'), attributes: { mode: 'icon', role: 'presentation', collection: 'heroicons', ...skaBlocks.tailwind.createBlockAttributes( 'grid place-items-center size-6 text-current', `.grid{display:grid}.place-items-center{place-items:center}.size-6{width:var(--spacing-6);height:var(--spacing-6)}.text-current{color:currentColor}` ), }, isActive: ({mode}) => mode === 'icon', }, { name: 'featured', title: __('Featured image', 'ska-blocks'), attributes: { mode: 'file', role: 'presentation', featured: true, }, // @ts-ignore isActive: ({mode, featured, metaKey}) => mode === 'file' && featured && !metaKey, }, ], transforms: { from: [ { type: 'block', blocks: ['core/image'], transform: ({id, url, alt, caption, width, height, align}) => { return createBlock('ska/image', { ...defaultAttributes, id, src: url, alt: alt || stripHTML(caption), width, height, mode: 'file', align, }) }, }, { type: 'block', blocks: ['core/post-featured-image', 'woocommerce/product-image'], transform: () => { return createBlock('ska/image', { ...defaultAttributes, mode: 'file', featured: true, }) }, }, ], to: [ { type: 'block', blocks: ['core/image'], transform: (attributes) => { const {id, src, alt, width, height, align} = attributes as ImageBlockAttributes return createBlock('core/image', { id, url: src, alt, width, height, align, }) }, }, ], }, __experimentalLabel: (attributes, {context}) => { const customName = attributes?.metadata?.name if(context === 'list-view' && customName) { return customName } const { mode, className, } = attributes if(context === 'list-view') { if(mode === 'file') { const { id, src, featured = false, } = attributes if(src && src.startsWith('data:image')) { return _x('Inlined image', 'Block name in list view - Image block', 'ska-blocks') } /** Notify when ID is specified but not found in media library (debug only). */ if(IS_DEBUG && !featured && id) { const args = ['postType', 'attachment', id] // @ts-expect-error const media = select(coreDataStore).getEntityRecord(...args) // @ts-expect-error const resolutionStatus = select(coreDataStore).getResolutionState('getEntityRecord', args)?.status if(resolutionStatus === 'error') { return `${_x('Image', 'Block name in list view - Image block', 'ska-blocks')} ❗ ${sprintf(_x('Media (%d) not found', 'ID', 'ska-blocks'), id)}` } } } if(mode === 'icon') { const { collection, icon, } = attributes if(collection && icon) { const fullIconName = `${collection}/${icon}` const parts = fullIconName.split('/') const iconName = parts[parts.length - 1] if(iconName) { return sprintf(_x(`Icon (%s)`, 'Block name in list view - Image block (icon name)', 'ska-blocks'), iconName) } } } if(mode === 'svg') { const renderClassName = className ? `.${className.split(' ').join('.')}` : '' const tagName = `svg${renderClassName}` return sprintf(_x(`<%s>`, 'Block name in list view - HTML element', 'ska-blocks'), tagName) } if(mode === 'placeholder') { return _x('Placeholder image', 'Block name in list view - Image block', 'ska-blocks') } if(mode === 'lottie') { return _x('Lottie', 'Block name in list view - Image block', 'ska-blocks') } } }, }, })