import * as React from 'react' import {__, _x} from '@wordpress/i18n' import cx from 'classnames' import { useBlockProps, MediaPlaceholder, MediaUpload, InspectorControls, BlockControls, // @ts-expect-error } from '@wordpress/block-editor' import { store as coreDataStore, type Attachment, } from '@wordpress/core-data' import { withNotices, BaseControl, TextControl, TextareaControl, SelectControl, ToggleControl, __experimentalHStack as HStack, __experimentalVStack as VStack, ToolbarGroup, ToolbarButton, } from '@wordpress/components' import { compose, } from '@wordpress/compose' import { useSelect, } from '@wordpress/data' import { createBlock, // @ts-expect-error } from '@wordpress/blocks' import { SkaPanelBody, } from '@ska/plugin' import { ButtonGroup, WithTooltip, } from '@ska/components' import { IS_SKA_THEME, } from '@ska/utils' // @ts-ignore import metadata from './block.json' import { useAs, useAttributes, type AsAttributes, type AttributesAttributes, } from '../../supports' import { ImageSizeControl, useMediaSizeOptions, } from '../../components' import { COVER_CLASSES, getPlaceholder, } from '../image' import type { SkaBlocks, } from '../../types' import { getAttachmentUrlBySizeSlug, type BlockModule, type tBlockEditProps, type tBlockSaveProps, } from '@ska/shared' const ALLOWED_MEDIA_TYPES = ['image'] const LINK_TO_OPTIONS = [ { label: __('None', 'ska-blocks'), value: 'none', }, { label: __('Attachment page', 'ska-blocks'), value: 'attachment', }, { label: __('Custom links', 'ska-blocks'), value: 'list', }, ] const ROLES = [ { label: _x('Figure', 'Image block role', 'ska-blocks'), tooltip: _x(`Apply role="figure" to gallery images that have an alt text.`, 'Image block role description', 'ska-blocks'), value: 'figure', }, { label: _x('Presentation', 'Image block role', 'ska-blocks'), tooltip: _x('Hide the gallery images 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(`Gallery images are wrapped in links, the image needs to have an alt text in WordPress media library.`, 'Image block role description', 'ska-blocks'), }), })) const VOID_LINK_CLICK = (e: React.MouseEvent) => (e.preventDefault(), false) export interface GalleryBlockAttributes extends AsAttributes, AttributesAttributes { ids: number[] role?: 'figure' | 'presentation' | 'none' /** Shorthand to add `w-full h-full object-cover rounded-[inherit] aspect-[inherit]` to gallery images. */ cover?: boolean size: string width: number height: number crop: boolean linkTo: string links: string lightbox: boolean /** Add `srcset` to gallery images when possible. */ srcset: boolean /** Lazyload images - @deprecated All images always lazyloaded unless `eager > 0`. */ lazyload: boolean /** Number of images to load eagerly (when lazyload is enabled). */ eager: number /** Disable to render gallery without a wrapper element. */ wrap: boolean } const getRoleProps = ({ role = 'figure', isLink = false, }: { role: GalleryBlockAttributes['role'], isLink?: boolean, }) => { return { ...(role === 'figure' && { role: 'figure', }), ...(role === 'presentation' && { 'aria-hidden': true, }), } } const GalleryInspectorControls: React.FC> = (props) => { const { attributes, setAttributes, } = props const { role = 'figure', cover = false, size = 'full', width = 0, height = 0, crop = true, linkTo = 'none', links = '', lightbox = false, srcset = true, eager = 0, wrap = true, } = attributes const [Element] = useAs(props) const hasLinks = (linkTo !== 'none' || lightbox) && Element !== 'a' const MEDIA_SIZE_OPTIONS = useMediaSizeOptions() return <> setAttributes({role: (value || 'figure') as GalleryBlockAttributes['role']})} useDefaultButtons /> setAttributes({size: slug})} resizable={false} custom /> {size === 'custom' && <> setAttributes({width: Number(value) || 0})} __nextHasNoMarginBottom __next40pxDefaultSize /> setAttributes({height: Number(value) || 0})} __nextHasNoMarginBottom __next40pxDefaultSize /> setAttributes({crop: !crop})} __nextHasNoMarginBottom /> } setAttributes({linkTo: value})} __nextHasNoMarginBottom __next40pxDefaultSize /> {linkTo === 'list' && <> setAttributes({links: value})} __nextHasNoMarginBottom /> } {wrap && <> setAttributes({lightbox: !lightbox})} __nextHasNoMarginBottom /> } setAttributes({cover: !cover})} __nextHasNoMarginBottom /> setAttributes({srcset: !srcset})} __nextHasNoMarginBottom /> setAttributes({eager: Number(value) || 0})} __nextHasNoMarginBottom __next40pxDefaultSize /> setAttributes({wrap: !wrap})} __nextHasNoMarginBottom /> } const GalleryEdit: React.FC> = (props) => { const { // @ts-ignore noticeUI, // @ts-ignore noticeOperations, attributes, setAttributes, } = props const { ids = [], role = 'figure', cover = false, size = 'full', linkTo = 'none', lightbox = false, } = attributes const blockProps = useBlockProps() const [Element, asProps, {isVoidElement}] = useAs(props) const attributesProps = useAttributes(props) const onSelectImages = (images: {id: number}[]) => { setAttributes({ids: images.map(image => image.id)}) } const onUploadError = (message: any) => { noticeOperations.removeAllNotices() noticeOperations.createErrorNotice(message) } const hasImages = !!ids.length const images = useSelect(select => { const { getEntityRecord, } = select(coreDataStore) return ids.map(id => getEntityRecord('postType', 'attachment', id)) }, [ids]) const galleryContent = hasImages ? images.map((image, index) => { const key = `image-${index}` if(typeof image === 'undefined') { return (
) } const src = getAttachmentUrlBySizeSlug(image, size) const Image = ( {image.alt_text} ) const hasLink = (linkTo !== 'none' || lightbox) && Element !== 'a' if(hasLink) { return ( // eslint-disable-next-line jsx-a11y/anchor-is-valid {Image} ) } return (
{Image}
) }) : <> return <> {hasImages && ( ( )} /> setAttributes({ids: []})} /> )} } // @ts-ignore const Edit = compose([withNotices])(GalleryEdit) const GALLERY_CONTENT_PLACEHOLDER = const Save: React.FC> = (props) => { const blockProps = useBlockProps.save() const [Element, asProps, {isVoidElement}] = useAs.save(props) const attributesProps = useAttributes.save(props) return ( ) } export default (skaBlocks: SkaBlocks): BlockModule => { const defaultTailwindClassAttributes = skaBlocks.tailwind.createBlockAttributes( IS_SKA_THEME ? 'grid grid-cols-auto-fit-xs gap-4' : 'grid grid-cols-2 sm:grid-cols-4 gap-4' ) return { metadata, settings: { edit: Edit, save: Save, variations: [ { isDefault: true, attributes: { ...defaultTailwindClassAttributes, skaBlocksSelectors: { '[&>.image]': {}, }, }, }, { name: 'gallery-images', title: __('Gallery images', 'ska-blocks'), description: __('Displays images from the WordPress Media Library without a wrapper element on the front end.', 'ska-blocks'), attributes: { ...defaultTailwindClassAttributes, wrap: false, }, isActive: ({wrap = true}) => !wrap, }, ], transforms: { from: [ { type: 'block', blocks: ['core/gallery'], transform: (attributes, innerBlocks) => { const ids = innerBlocks.map(innerBlock => { if(innerBlock.name !== 'core/image') { return false } const { attributes = {}, } = innerBlock const { id, } = attributes return id }).filter(v => v) return createBlock('ska/gallery', {ids}) }, }, ], }, }, } }