diff --git a/blocks/library/gallery/gallery-image.js b/blocks/library/gallery/gallery-image.js new file mode 100644 index 00000000000000..c2aac32ef7b518 --- /dev/null +++ b/blocks/library/gallery/gallery-image.js @@ -0,0 +1,8 @@ + +export default function GalleryImage( props ) { + return ( +
+ { +
+ ); +} diff --git a/blocks/library/gallery/index.js b/blocks/library/gallery/index.js new file mode 100644 index 00000000000000..8f0941260d9ad5 --- /dev/null +++ b/blocks/library/gallery/index.js @@ -0,0 +1,142 @@ +/** + * Internal dependencies + */ +import './style.scss'; +import { registerBlockType, query as hpq } from '../../api'; + +import Placeholder from 'components/placeholder'; +import MediaUploadButton from '../../media-upload-button'; + +import GalleryImage from './gallery-image'; + +const { query, attr } = hpq; + +const editMediaLibrary = ( attributes, setAttributes ) => { + const frameConfig = { + frame: 'post', + title: wp.i18n.__( 'Update Gallery media' ), + button: { + text: wp.i18n.__( 'Select' ), + }, + multiple: true, + state: 'gallery-edit', + selection: new wp.media.model.Selection( attributes.images, { multiple: true } ), + }; + + const editFrame = wp.media( frameConfig ); + function updateFn() { + setAttributes( { + images: this.frame.state().attributes.library.models.map( ( a ) => { + return a.attributes; + } ), + } ); + } + + editFrame.on( 'insert', updateFn ); + editFrame.state( 'gallery-edit' ).on( 'update', updateFn ); + editFrame.open( 'gutenberg-gallery' ); +}; + +/** + * Returns an attribute setter with behavior that if the target value is + * already the assigned attribute value, it will be set to undefined. + * + * @param {string} align Alignment value + * @return {Function} Attribute setter + */ +function toggleAlignment( align ) { + return ( attributes, setAttributes ) => { + const nextAlign = attributes.align === align ? undefined : align; + setAttributes( { align: nextAlign } ); + }; +} + +registerBlockType( 'core/gallery', { + title: wp.i18n.__( 'Gallery' ), + icon: 'format-gallery', + category: 'common', + + attributes: { + images: + query( 'div.blocks-gallery figure.blocks-gallery-image img', { + url: attr( 'src' ), + alt: attr( 'alt' ), + } ), + }, + + controls: [ + { + icon: 'format-image', + title: wp.i18n.__( 'Edit Gallery' ), + onClick: editMediaLibrary, + }, + { + icon: 'align-left', + title: wp.i18n.__( 'Align left' ), + isActive: ( { align } ) => 'left' === align, + onClick: toggleAlignment( 'left' ), + }, + { + icon: 'align-center', + title: wp.i18n.__( 'Align center' ), + isActive: ( { align } ) => ! align || 'center' === align, + onClick: toggleAlignment( 'center' ), + }, + { + icon: 'align-right', + title: wp.i18n.__( 'Align right' ), + isActive: ( { align } ) => 'right' === align, + onClick: toggleAlignment( 'right' ), + }, + { + icon: 'align-full-width', + title: wp.i18n.__( 'Wide width' ), + isActive: ( { align } ) => 'wide' === align, + onClick: toggleAlignment( 'wide' ), + }, + ], + + edit( { attributes, setAttributes } ) { + const { images, align = 'none' } = attributes; + if ( ! images ) { + const setMediaUrl = ( imgs ) => setAttributes( { images: imgs } ); + return ( + + + { wp.i18n.__( 'Insert from Media Library' ) } + + + ); + } + + return ( +
+ { images.map( ( img, i ) => ( + + ) ) } +
+ ); + }, + + save( { attributes } ) { + const { images, align = 'none' } = attributes; + + return ( +
+ { images.map( ( img, i ) => ( + + ) ) } +
+ ); + }, + +} ); diff --git a/blocks/library/gallery/style.scss b/blocks/library/gallery/style.scss new file mode 100644 index 00000000000000..d07d3b801db5c2 --- /dev/null +++ b/blocks/library/gallery/style.scss @@ -0,0 +1,37 @@ + +.blocks-gallery { + + display: flex; + flex-wrap: wrap; + + .blocks-gallery-image { + + margin: 8px; + + img { + max-width: 120px; + } + } +} + +.blocks-gallery.is-placeholder { + margin: -15px; + padding: 6em 0; + border: 2px solid $light-gray-500; + text-align: center; +} + +.blocks-gallery__placeholder-label { + display: flex; + align-items: center; + justify-content: center; + font-weight: bold; + + .dashicon { + margin-right: 1ch; + } +} + +.blocks-gallery__placeholder-instructions { + margin: 1.8em 0; +} diff --git a/blocks/library/index.js b/blocks/library/index.js index 5f1b28bcf894d2..c611bf9984bcea 100644 --- a/blocks/library/index.js +++ b/blocks/library/index.js @@ -11,4 +11,5 @@ import './pullquote'; import './table'; import './preformatted'; import './code'; +import './gallery'; import './latest-posts'; diff --git a/blocks/test/fixtures/core-gallery.html b/blocks/test/fixtures/core-gallery.html new file mode 100644 index 00000000000000..233b319fba5713 --- /dev/null +++ b/blocks/test/fixtures/core-gallery.html @@ -0,0 +1,10 @@ + + + diff --git a/blocks/test/fixtures/core-gallery.json b/blocks/test/fixtures/core-gallery.json new file mode 100644 index 00000000000000..10cd14286ac919 --- /dev/null +++ b/blocks/test/fixtures/core-gallery.json @@ -0,0 +1,12 @@ +[ + { + "uid": "_uid_0", + "name": "core/gallery", + "attributes": { + "images": [ + { "url": "https://cldup.com/uuUqE_dXzy.jpg", "alt": "title" }, + { "url": "https://cldup.com/uuUqE_dXzy.jpg", "alt": "title" } + ] + } + } +] diff --git a/blocks/test/fixtures/core-gallery.serialized.html b/blocks/test/fixtures/core-gallery.serialized.html new file mode 100644 index 00000000000000..d3f126352a25b0 --- /dev/null +++ b/blocks/test/fixtures/core-gallery.serialized.html @@ -0,0 +1,7 @@ + + + + diff --git a/blocks/test/full-content.js b/blocks/test/full-content.js index 0798782fb5e3db..4bdf65b03bac7f 100644 --- a/blocks/test/full-content.js +++ b/blocks/test/full-content.js @@ -53,7 +53,10 @@ function normalizeReactTree( element ) { return element.map( child => normalizeReactTree( child ) ); } - if ( isObject( element ) ) { + // Check if we got an object first, then if it actually has a `type` like a + // React component. Sometimes we get other stuff here, which probably + // indicates a bug. + if ( isObject( element ) && element.type ) { const toReturn = { type: element.type, };