├── .gitattributes ├── src ├── styles │ ├── style │ │ ├── _background-parallax.scss │ │ ├── _horizontal-gutter.scss │ │ ├── _shadow.scss │ │ ├── _background-radius.scss │ │ ├── _radius.scss │ │ ├── _filters.scss │ │ ├── _padding.scss │ │ ├── _margin-bottom.scss │ │ ├── _margin-top.scss │ │ ├── _background-overlay.scss │ │ ├── _margin-left.scss │ │ ├── _margin-right.scss │ │ ├── _margin-left-negative.scss │ │ ├── _margin-right-negative.scss │ │ ├── _captions.scss │ │ ├── gutter.scss │ │ ├── _background-classes.scss │ │ ├── _carousel.scss │ │ ├── _bricks.scss │ │ ├── _grid.scss │ │ ├── _flickity.scss │ │ └── _core-themes.scss │ ├── admin.scss │ ├── editor │ │ ├── _gallery-upload.scss │ │ ├── _icons.scss │ │ ├── _remove-gallery-item-button.scss │ │ ├── _is-background-transient.scss │ │ ├── _gallery-item.scss │ │ ├── _flickity-arrows.scss │ │ ├── _radius.scss │ │ ├── core │ │ │ ├── _breakpoints.scss │ │ │ ├── _variables.scss │ │ │ ├── _colors.scss │ │ │ └── _mixins.scss │ │ ├── _inspector.scss │ │ ├── _inspector-tabs.scss │ │ └── _core-themes.scss │ ├── editor.scss │ └── style.scss ├── blocks │ ├── carousel │ │ ├── styles │ │ │ ├── style.scss │ │ │ └── editor.scss │ │ └── components │ │ │ ├── inspector.js │ │ │ └── edit.js │ ├── stacked │ │ ├── styles │ │ │ ├── editor.scss │ │ │ └── style.scss │ │ ├── components │ │ │ ├── inspector.js │ │ │ └── edit.js │ │ └── index.js │ └── masonry │ │ ├── styles │ │ ├── style.scss │ │ └── editor.scss │ │ ├── components │ │ ├── inspector.js │ │ └── edit.js │ │ └── index.js ├── components │ ├── lightbox │ │ ├── options.js │ │ ├── classes.js │ │ ├── attributes.js │ │ ├── transforms.js │ │ ├── index.js │ │ └── control.js │ ├── global │ │ ├── index.js │ │ ├── styles.js │ │ ├── classes.js │ │ ├── transforms.js │ │ ├── attributes.js │ │ └── toolbar.js │ ├── background │ │ ├── index.js │ │ ├── styles.js │ │ ├── transforms.js │ │ ├── attributes.js │ │ ├── classes.js │ │ ├── toolbar.js │ │ └── panel.js │ ├── gallery-placeholder.js │ ├── gallery-dropzone.js │ ├── responsive-tabs-control.js │ ├── gallery-upload.js │ ├── slider-panel.js │ ├── size-control.js │ └── gallery-image.js ├── utils │ ├── caption-options.js │ ├── link-options.js │ ├── block-category.js │ ├── filter-options.js │ ├── helper.js │ └── autoplay-options.js ├── js │ └── block-gallery-masonry.js ├── common.scss └── blocks.js ├── .eslintignore ├── .babelrc ├── .editorconfig ├── .gitignore ├── includes ├── admin │ ├── class-block-gallery-footer-text.php │ ├── class-block-gallery-url-generator.php │ └── class-block-gallery-action-links.php ├── class-block-gallery-body-classes.php └── class-block-gallery-block-assets.php ├── README.md ├── CONTRIBUTING.md ├── package.json ├── .eslintrc.json ├── class-block-gallery.php └── readme.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /src/styles/style/_background-parallax.scss: -------------------------------------------------------------------------------- 1 | .has-parallax { 2 | background-attachment: fixed; 3 | } 4 | -------------------------------------------------------------------------------- /src/styles/style/_horizontal-gutter.scss: -------------------------------------------------------------------------------- 1 | .has-horizontal-gutter { 2 | overflow-x: hidden; 3 | } 4 | -------------------------------------------------------------------------------- /src/styles/admin.scss: -------------------------------------------------------------------------------- 1 | .block-gallery-plugins-gopro { 2 | color: #39b54a; 3 | font-weight: 700; 4 | } 5 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.min.js 2 | **/*.build.js 3 | **/node_modules/** 4 | **/vendor/** 5 | build 6 | coverage 7 | cypress 8 | node_modules 9 | vendor 10 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@wordpress/babel-preset-default"], 3 | "plugins": [ 4 | [ 5 | "@wordpress/babel-plugin-makepot", 6 | { 7 | "output": "languages/block-gallery-js.pot" 8 | } 9 | ] 10 | ] 11 | } -------------------------------------------------------------------------------- /src/blocks/carousel/styles/style.scss: -------------------------------------------------------------------------------- 1 | .wp-block-blockgallery-carousel { 2 | 3 | &, 4 | .blockgallery { 5 | height: 100%; 6 | position: relative; 7 | } 8 | 9 | .blockgallery--figure { 10 | height: 100%; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/styles/style/_shadow.scss: -------------------------------------------------------------------------------- 1 | .has-shadow-sml { 2 | box-shadow: 0 0.5vw 2vw -0.25vw rgba(0, 0, 0, 0.2); 3 | } 4 | 5 | .has-shadow-med { 6 | box-shadow: 0 1vw 3vw -0.5vw rgba(0, 0, 0, 0.2); 7 | } 8 | 9 | .has-shadow-lrg { 10 | box-shadow: 0 1.8vw 3vw -0.7vw rgba(0, 0, 0, 0.2); 11 | } 12 | -------------------------------------------------------------------------------- /src/styles/style/_background-radius.scss: -------------------------------------------------------------------------------- 1 | // Utility class for adding a border-radius to the figure elements. 2 | @for $i from 2 through 20 { 3 | 4 | .has-background-border-radius-#{ $i } { 5 | border-radius: #{ $i }px; 6 | 7 | &::before { 8 | border-radius: #{ $i }px; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/components/lightbox/options.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies 3 | */ 4 | const { __ } = wp.i18n; 5 | 6 | /** 7 | * Style options. 8 | */ 9 | const lightboxStyleOptions = [ 10 | { value: 'light', label: __( 'Light' ) }, 11 | { value: 'dark', label: __( 'Dark' ) }, 12 | ]; 13 | 14 | export default lightboxStyleOptions; -------------------------------------------------------------------------------- /src/components/lightbox/classes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS classes 3 | */ 4 | function LightboxClasses( attributes ) { 5 | return [ 6 | { 'has-lightbox': attributes.lightbox }, 7 | { [ `has-lightbox-style-${ attributes.lightboxStyle }` ] : attributes.lightbox && attributes.lightboxStyle }, 8 | ]; 9 | } 10 | 11 | export default LightboxClasses; -------------------------------------------------------------------------------- /src/components/lightbox/attributes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Set the attributes for this component 3 | * @type {Object} 4 | */ 5 | const LightboxAttributes = { 6 | lightbox: { 7 | type: 'boolean', 8 | default: false, 9 | }, 10 | lightboxStyle: { 11 | type: 'string', 12 | default: 'light', 13 | }, 14 | }; 15 | 16 | export default LightboxAttributes; -------------------------------------------------------------------------------- /src/utils/caption-options.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies 3 | */ 4 | const { __ } = wp.i18n; 5 | 6 | /** 7 | * Link options. 8 | */ 9 | const captionOptions = [ 10 | { value: 'dark', label: __( 'Dark' ) }, 11 | { value: 'light', label: __( 'Light' ) }, 12 | { value: 'none', label: __( 'None' ) }, 13 | ]; 14 | 15 | export default captionOptions; -------------------------------------------------------------------------------- /src/styles/style/_radius.scss: -------------------------------------------------------------------------------- 1 | // Utility class for adding a border-radius to the figure elements. 2 | @for $i from 2 through 20 { 3 | 4 | &.has-border-radius-#{ $i } { 5 | 6 | .blockgallery--item img { 7 | border-radius: #{ $i }px; 8 | } 9 | 10 | .blockgallery--item figcaption { 11 | border-radius: 0 0 #{ $i }px #{ $i }px; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/lightbox/transforms.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Set the attributes for the component transformations 3 | * @type {Object} 4 | */ 5 | function LightboxTransforms( props ) { 6 | 7 | const transforms = { 8 | lightbox: props.lightbox, 9 | lightboxStyle: props.lightboxStyle, 10 | }; 11 | 12 | return transforms; 13 | } 14 | 15 | export default LightboxTransforms; -------------------------------------------------------------------------------- /src/utils/link-options.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies 3 | */ 4 | const { __ } = wp.i18n; 5 | 6 | /** 7 | * Link options. 8 | */ 9 | const linkOptions = [ 10 | { value: 'attachment', label: __( 'Attachment Page' ) }, 11 | { value: 'media', label: __( 'Media File' ) }, 12 | { value: 'none', label: __( 'None' ) }, 13 | ]; 14 | 15 | export default linkOptions; -------------------------------------------------------------------------------- /src/styles/style/_filters.scss: -------------------------------------------------------------------------------- 1 | .has-filter-grayscale img { 2 | filter: grayscale(1); 3 | } 4 | 5 | .has-filter-saturation img { 6 | filter: saturate(1.75); 7 | } 8 | 9 | .has-filter-sepia img { 10 | filter: sepia(0.5); 11 | } 12 | 13 | .has-filter-dim img { 14 | filter: brightness(0.5); 15 | } 16 | 17 | .has-filter-vintage img { 18 | filter: contrast(1.3) saturate(1.5) sepia(0.6); 19 | } 20 | -------------------------------------------------------------------------------- /src/components/lightbox/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal dependencies 3 | */ 4 | import LightboxAttributes from './attributes'; 5 | import LightboxClasses from './classes'; 6 | import LightboxTransforms from './transforms'; 7 | import LightboxControl from './control'; 8 | 9 | /** 10 | * Export 11 | */ 12 | export { 13 | LightboxAttributes, 14 | LightboxClasses, 15 | LightboxTransforms, 16 | LightboxControl, 17 | }; 18 | -------------------------------------------------------------------------------- /src/js/block-gallery-masonry.js: -------------------------------------------------------------------------------- 1 | ( function( $ ) { 2 | "use strict"; 3 | 4 | var container = $( '.wp-block-blockgallery-masonry ul' ); 5 | 6 | $( document ).ready( function () { 7 | 8 | container.imagesLoaded(function(){ 9 | container.masonry( { 10 | itemSelector: '.blockgallery--item', 11 | transitionDuration: '0.2s', 12 | percentPosition: true, 13 | } ); 14 | }); 15 | }); 16 | 17 | } )( jQuery ); 18 | -------------------------------------------------------------------------------- /src/components/global/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal dependencies 3 | */ 4 | import GlobalAttributes from './attributes'; 5 | import GlobalClasses from './classes'; 6 | import GlobalToolbar from './toolbar'; 7 | import GlobalTransforms from './transforms'; 8 | import GlobalStyles from './styles'; 9 | 10 | /** 11 | * Export 12 | */ 13 | export { 14 | GlobalAttributes, 15 | GlobalClasses, 16 | GlobalToolbar, 17 | GlobalTransforms, 18 | GlobalStyles, 19 | }; 20 | -------------------------------------------------------------------------------- /src/styles/editor/_gallery-upload.scss: -------------------------------------------------------------------------------- 1 | .components-blockgallery-gallery-item__upload.components-button { 2 | border-radius: 0; 3 | border: none; 4 | box-shadow: none !important; 5 | display: flex; 6 | flex-direction: column; 7 | justify-content: center; 8 | min-height: 100px; 9 | padding: 0 !important; 10 | 11 | .dashicon { 12 | margin-top: 10px; 13 | } 14 | 15 | &:hover, 16 | &:focus { 17 | border: $border-width solid $dark-gray-500; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/utils/block-category.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies 3 | */ 4 | const { getCategories, setCategories } = wp.blocks; 5 | 6 | /** 7 | * Internal dependencies 8 | */ 9 | import icons from './icons'; 10 | 11 | setCategories( [ 12 | // Add a Block Gallery block category 13 | { 14 | slug: 'block-gallery', 15 | title: 'Block Gallery', 16 | icon: icons.logo, 17 | }, 18 | ...getCategories().filter( ( { slug } ) => slug !== 'block-gallery' ), 19 | ] ); 20 | -------------------------------------------------------------------------------- /src/components/global/styles.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies 3 | */ 4 | const { getColorClassName } = wp.editor; 5 | 6 | /** 7 | * Background Classes 8 | */ 9 | function GlobalStyles( attributes ) { 10 | 11 | const captionColorClass = getColorClassName( 'color', attributes.captionColor ); 12 | 13 | const styles = { 14 | color: captionColorClass ? undefined : attributes.customCaptionColor, 15 | }; 16 | 17 | return styles; 18 | } 19 | 20 | export default GlobalStyles; -------------------------------------------------------------------------------- /src/styles/style/_padding.scss: -------------------------------------------------------------------------------- 1 | // Utility class for adding margin-bottom to the figure elements. 2 | @media (min-width: 700px) { 3 | // For desktop. 4 | @for $i from 1 through 20 { 5 | 6 | &.has-padding-#{ $i * 5 } { 7 | padding: #{ $i * 5 }px !important; 8 | } 9 | } 10 | } 11 | 12 | @media (max-width: 699px) { 13 | // For desktop. 14 | @for $i from 1 through 20 { 15 | 16 | &.has-padding-mobile-#{ $i * 5 } { 17 | padding: #{ $i * 5 }px !important; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | # WordPress Coding Standards 5 | # https://make.wordpress.org/core/handbook/coding-standards/ 6 | 7 | root = true 8 | 9 | [*] 10 | charset = utf-8 11 | end_of_line = lf 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | indent_style = tab 15 | 16 | [*.yml] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /src/utils/filter-options.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies 3 | */ 4 | const { __ } = wp.i18n; 5 | 6 | /** 7 | * Link options. 8 | */ 9 | const filterOptions = [ 10 | { value: 'grayscale', label: __( 'Grayscale' ) }, 11 | { value: 'sepia', label: __( 'Sepia' ) }, 12 | { value: 'saturation', label: __( 'Saturation' ) }, 13 | { value: 'dim', label: __( 'Dark' ) }, 14 | { value: 'vintage', label: __( 'Vintage' ) }, 15 | { value: 'none', label: __( 'Original' ) }, 16 | ]; 17 | 18 | export default filterOptions; -------------------------------------------------------------------------------- /src/styles/style/_margin-bottom.scss: -------------------------------------------------------------------------------- 1 | // Utility class for adding margin-bottom to the figure elements. 2 | @media (min-width: 700px) { 3 | // For desktop. 4 | @for $i from 1 through 10 { 5 | 6 | &.has-margin-bottom-#{ $i * 5 } { 7 | margin-bottom: #{ $i * 5 }px !important; 8 | } 9 | } 10 | } 11 | 12 | @media (max-width: 699px) { 13 | // For desktop. 14 | @for $i from 1 through 10 { 15 | 16 | &.has-margin-bottom-mobile-#{ $i * 5 } { 17 | margin-bottom: #{ $i * 5 }px !important; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/styles/style/_margin-top.scss: -------------------------------------------------------------------------------- 1 | // Utility class for adding margin-top to the figure elements. 2 | @media (min-width: 700px) { 3 | // For desktop. 4 | @for $i from 1 through 10 { 5 | 6 | &.has-margin-top-#{ $i * 5 } { 7 | margin-top: #{ round($i * 5 / 2) }px !important; 8 | } 9 | } 10 | } 11 | 12 | @media (max-width: 699px) { 13 | // For desktop. 14 | @for $i from 1 through 10 { 15 | 16 | &.has-margin-top-mobile-#{ $i * 5 } { 17 | margin-top: #{ round($i * 5 / 2) }px !important; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/styles/style/_background-overlay.scss: -------------------------------------------------------------------------------- 1 | .has-background-overlay { 2 | 3 | position: relative; 4 | 5 | &::before { 6 | background-color: inherit; 7 | bottom: 0; 8 | content: ""; 9 | left: 0; 10 | opacity: 0.5; 11 | position: absolute; 12 | right: 0; 13 | top: 0; 14 | } 15 | } 16 | 17 | @for $i from 1 through 9 { 18 | 19 | .has-background-overlay-#{ $i * 10 }::before { 20 | opacity: $i * 0.1; 21 | } 22 | } 23 | 24 | .has-background-overlay:not(.has-background) { 25 | background-color: #000; 26 | } 27 | -------------------------------------------------------------------------------- /src/styles/style/_margin-left.scss: -------------------------------------------------------------------------------- 1 | // Utility class for adding margin-left to the figure elements. 2 | @media (min-width: 700px) { 3 | // For desktop. 4 | @for $i from 1 through 10 { 5 | 6 | &.has-margin-left-#{ $i * 5 } { 7 | margin-left: #{ round($i * 5 / 2) }px !important; 8 | } 9 | } 10 | } 11 | 12 | @media (max-width: 699px) { 13 | // For desktop. 14 | @for $i from 1 through 10 { 15 | 16 | &.has-margin-left-mobile-#{ $i * 5 } { 17 | margin-left: #{ round($i * 5 / 2) }px !important; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/styles/style/_margin-right.scss: -------------------------------------------------------------------------------- 1 | // Utility class for adding margin-right to the figure elements. 2 | @media (min-width: 700px) { 3 | // For desktop. 4 | @for $i from 1 through 10 { 5 | 6 | &.has-margin-right-#{ $i * 5 } { 7 | margin-right: #{ round($i * 5 / 2) }px !important; 8 | } 9 | } 10 | } 11 | 12 | @media (max-width: 699px) { 13 | // For desktop. 14 | @for $i from 1 through 10 { 15 | 16 | &.has-margin-right-mobile-#{ $i * 5 } { 17 | margin-right: #{ round($i * 5 / 2) }px !important; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/styles/style/_margin-left-negative.scss: -------------------------------------------------------------------------------- 1 | // Utility class for adding margin-left to the figure elements. 2 | @media (min-width: 700px) { 3 | // For desktop. 4 | @for $i from 1 through 10 { 5 | 6 | &.has-negative-margin-left-#{ $i * 5 } { 7 | margin-left: -#{ round($i * 5 / 2) }px !important; 8 | } 9 | } 10 | } 11 | 12 | @media (max-width: 699px) { 13 | // For desktop. 14 | @for $i from 1 through 10 { 15 | 16 | &.has-negative-margin-left-mobile-#{ $i * 5 } { 17 | margin-left: -#{ round($i * 5 / 2) }px !important; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/styles/style/_margin-right-negative.scss: -------------------------------------------------------------------------------- 1 | // Utility class for adding margin-right to the figure elements. 2 | @media (min-width: 700px) { 3 | // For desktop. 4 | @for $i from 1 through 10 { 5 | 6 | &.has-negative-margin-right-#{ $i * 5 } { 7 | margin-right: -#{ round($i * 5 / 2) }px !important; 8 | } 9 | } 10 | } 11 | 12 | @media (max-width: 699px) { 13 | // For desktop. 14 | @for $i from 1 through 10 { 15 | 16 | &.has-negative-margin-right-mobile-#{ $i * 5 } { 17 | margin-right: -#{ round($i * 5 / 2) }px !important; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/background/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal dependencies 3 | */ 4 | import BackgroundAttributes from './attributes'; 5 | import BackgroundClasses from './classes'; 6 | import BackgroundPanel from './panel'; 7 | import BackgroundStyles from './styles'; 8 | import BackgroundToolbar from './toolbar'; 9 | import BackgroundTransforms from './transforms'; 10 | 11 | /** 12 | * Export 13 | */ 14 | export { 15 | BackgroundAttributes, 16 | BackgroundClasses, 17 | BackgroundPanel, 18 | BackgroundStyles, 19 | BackgroundToolbar, 20 | BackgroundTransforms, 21 | }; 22 | -------------------------------------------------------------------------------- /src/common.scss: -------------------------------------------------------------------------------- 1 | // Block Gallery branding color. 2 | $blockgallery: #1e35b9; 3 | 4 | // Colors. 5 | $blue: #0085ba; 6 | $white: #fff; 7 | $black: rgb(41, 41, 41); 8 | $black-000: #000; 9 | 10 | // Output all blocks 11 | @mixin blockGalleryEditorBlocks { 12 | 13 | .wp-block[data-type="blockgallery/offset"], 14 | .wp-block[data-type="blockgallery/masonry"], 15 | .wp-block[data-type="blockgallery/stacked"], 16 | .wp-block[data-type="blockgallery/carousel"], 17 | .wp-block[data-type="blockgallery/thumbnails"], 18 | .wp-block[data-type="blockgallery/auto-height"] { 19 | @content; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/components/background/styles.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies 3 | */ 4 | const { getColorClassName } = wp.editor; 5 | 6 | /** 7 | * Background Classes 8 | */ 9 | function BackgroundStyles( attributes ) { 10 | 11 | const backgroundClass = getColorClassName( 'background-color', attributes.backgroundColor ); 12 | 13 | const styles = { 14 | backgroundImage: attributes.backgroundImg ? `url(${ attributes.backgroundImg })` : undefined, 15 | backgroundColor: backgroundClass ? undefined : attributes.customBackgroundColor, 16 | }; 17 | 18 | return styles; 19 | } 20 | 21 | export default BackgroundStyles; -------------------------------------------------------------------------------- /src/blocks/carousel/styles/editor.scss: -------------------------------------------------------------------------------- 1 | .editor-block-list__block[data-type="blockgallery/carousel"] { 2 | 3 | // Fix fullwidth alignment bug where gutters would not display. 4 | &.editor-block-list__block[data-align="full"] > .editor-block-list__block-edit figure { 5 | width: auto; 6 | } 7 | 8 | // Fullwidth upload image button. 9 | .blockgallery--item-uploader { 10 | 11 | .components-form-file-upload, 12 | .components-blockgallery-gallery-item__upload { 13 | height: 100%; 14 | width: 100%; 15 | } 16 | } 17 | 18 | .flickity-enabled { 19 | position: inherit; 20 | height: 100% !important; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/styles/editor/_icons.scss: -------------------------------------------------------------------------------- 1 | // The icon that displays on the CoBlocks block inserter category. 2 | .components-blockgallery-logo-icon { 3 | position: relative; 4 | 5 | &.components-panel__icon { 6 | margin-left: 8px; 7 | top: 1px; 8 | } 9 | 10 | &--pink { 11 | fill: $blockgallery; 12 | } 13 | } 14 | 15 | // Style our custom colored icons like the default icons. 16 | .components-blockgallery-svg { 17 | .components-panel &, 18 | .components-toolbar &, 19 | .editor-block-navigation__item & { 20 | color: $dark-gray-500; 21 | } 22 | } 23 | 24 | .editor-block-icon.has-colors::after { 25 | color: $dark-gray-500; 26 | } 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.min.js 2 | **/*.build.js 3 | **/node_modules/** 4 | **/vendor/** 5 | **/dist/blocks.build.js 6 | **/dist/blocks.editor.build.css 7 | **/dist/blocks.style.build.css 8 | **/build/** 9 | **/languages/block-gallery.pot 10 | build 11 | coverage 12 | cypress 13 | node_modules 14 | vendor 15 | *.sublime-project 16 | *.sublime-workspace 17 | .DS_Store 18 | .ftppass 19 | *.cache 20 | sftp.json.github/ 21 | src/.DS_Store 22 | dist/blocks.editor.build.css 23 | dist/styles/block-gallery-admin.css 24 | dist/styles/block-gallery-admin.min.css 25 | src/.DS_Store 26 | dist/css/block-gallery-admin.css 27 | dist/css/block-gallery-admin.min.css 28 | -------------------------------------------------------------------------------- /src/styles/editor/_remove-gallery-item-button.scss: -------------------------------------------------------------------------------- 1 | .components-blockgallery-gallery-item__remove { 2 | 3 | &-wrapper { 4 | background-color: $blue; 5 | display: inline-flex; 6 | padding: 2px; 7 | position: absolute; 8 | right: 2px; 9 | top: 2px; 10 | z-index: 20; 11 | transition: padding 0.1s linear; 12 | 13 | svg { 14 | position: relative; 15 | } 16 | } 17 | 18 | &-button { 19 | box-shadow: none !important; 20 | padding: 0; 21 | color: $white; 22 | 23 | &:hover { 24 | background: $blue !important; 25 | border-color: $blue !important; 26 | color: #fff !important; 27 | opacity: 0.75; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/blocks/stacked/styles/editor.scss: -------------------------------------------------------------------------------- 1 | .editor-block-list__block[data-type="blockgallery/stacked"] { 2 | 3 | // Set a min-height for the "Upload an Image" trigger for this gallery. 4 | .components-blockgallery-gallery-item__upload { 5 | min-height: 100px; 6 | width: 100% !important; 7 | } 8 | 9 | // Fix issue where selected images are not wider than the viewport. 10 | .blockgallery--item:not(.blockgallery--item-uploader) .blockgallery--figure { 11 | display: inline-block; 12 | width: auto !important; 13 | } 14 | 15 | .has-fullwidth-images .blockgallery--item:not(.blockgallery--item-uploader) .blockgallery--figure { 16 | width: 100% !important; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/utils/helper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import pick from 'lodash/pick'; 5 | import get from 'lodash/get'; 6 | 7 | export function overlayToClass( ratio ) { 8 | return ( ratio === 0 || ratio === 50 ) ? 9 | null : 10 | 'has-background-overlay-' + ( 10 * Math.round( ratio / 10 ) ); 11 | } 12 | 13 | export const pickRelevantMediaFiles = ( image ) => { 14 | const imageProps = pick( image, [ 'alt', 'id', 'link', 'caption' ] ); 15 | imageProps.url = get( image, [ 'sizes', 'large', 'url' ] ) || get( image, [ 'media_details', 'sizes', 'large', 'source_url' ] ) || image.url; 16 | return imageProps; 17 | }; 18 | 19 | export const ALLOWED_MEDIA_TYPES = [ 'image' ]; 20 | -------------------------------------------------------------------------------- /src/styles/editor/_is-background-transient.scss: -------------------------------------------------------------------------------- 1 | // Animation effect when a background image is uploaded 2 | @keyframes background_loading_fade { 3 | 4 | 0% { 5 | opacity: 0.5; 6 | } 7 | 8 | 50% { 9 | opacity: 0.75; 10 | } 11 | 12 | 100% { 13 | opacity: 0.5; 14 | } 15 | } 16 | 17 | @mixin background_loading_fade { 18 | animation: background_loading_fade 1.6s ease-in-out infinite; 19 | } 20 | 21 | .has-background-image-transient > div { 22 | z-index: 2; 23 | } 24 | 25 | .has-background-image-transient::after { 26 | position: absolute; 27 | top: 0; 28 | bottom: 0; 29 | right: 0; 30 | left: 0; 31 | background: #fff; 32 | content: ""; 33 | z-index: 1; 34 | @include background_loading_fade; 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/autoplay-options.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies 3 | */ 4 | const { __ } = wp.i18n; 5 | 6 | /** 7 | * Link options. 8 | */ 9 | const autoPlayOptions = [ 10 | { value: 1000, label: __( 'One Second' ) }, 11 | { value: 2000, label: __( 'Two Seconds' ) }, 12 | { value: 3000, label: __( 'Three Seconds' ) }, 13 | { value: 4000, label: __( 'Four Seconds' ) }, 14 | { value: 5000, label: __( 'Five Seconds' ) }, 15 | { value: 6000, label: __( 'Six Second' ) }, 16 | { value: 7000, label: __( 'Seven Seconds' ) }, 17 | { value: 8000, label: __( 'Eight Seconds' ) }, 18 | { value: 9000, label: __( 'Nine Seconds' ) }, 19 | { value: 10000, label: __( 'Ten Seconds' ) }, 20 | ]; 21 | 22 | export default autoPlayOptions; -------------------------------------------------------------------------------- /src/styles/style/_captions.scss: -------------------------------------------------------------------------------- 1 | .has-caption-style-light { 2 | 3 | .blockgallery--item .blockgallery--figure figcaption { 4 | background: linear-gradient(0deg, rgba($color: $white, $alpha: 0.93) 6.3%, rgba($color: $white, $alpha: 0.5) 61%, rgba(255, 255, 255, 0)) !important; 5 | opacity: 1 !important; 6 | } 7 | } 8 | 9 | .blockgallery:not(.has-caption-color).has-caption-style-dark .blockgallery--figure figcaption { 10 | color: $white; 11 | } 12 | 13 | .blockgallery:not(.has-caption-color).has-caption-style-light .blockgallery--figure figcaption { 14 | color: $black; 15 | } 16 | 17 | .has-caption-style-none { 18 | 19 | .blockgallery--item .blockgallery--figure figcaption { 20 | background: none !important; 21 | opacity: 1 !important; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/blocks/masonry/styles/style.scss: -------------------------------------------------------------------------------- 1 | .wp-block-blockgallery-masonry { 2 | position: relative; 3 | 4 | ul { 5 | padding: 0 !important; 6 | list-style: none !important; 7 | } 8 | 9 | li { 10 | margin: 0 !important; 11 | } 12 | 13 | figure { 14 | overflow: hidden; 15 | } 16 | 17 | img { 18 | vertical-align: bottom; 19 | } 20 | 21 | figcaption { 22 | position: absolute !important; 23 | bottom: 0; 24 | width: 100%; 25 | max-height: 100% !important; 26 | overflow: auto; 27 | padding: 30px 10px 10px !important; 28 | opacity: 0.9; 29 | text-align: center; 30 | font-size: 13px; 31 | background: linear-gradient(0deg, rgba($color: $black, $alpha: 0.7) 0, rgba($color: $black, $alpha: 0.3) 50%, transparent); 32 | 33 | img { 34 | display: inline; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/blocks/stacked/styles/style.scss: -------------------------------------------------------------------------------- 1 | .wp-block-blockgallery-stacked { 2 | position: relative; 3 | text-align: center; 4 | 5 | &:not(.has-caption-color) { 6 | color: #333 !important; 7 | } 8 | 9 | .blockgallery--item { 10 | margin-left: auto; 11 | margin-right: auto; 12 | 13 | &:last-child { 14 | margin-bottom: 0; 15 | 16 | figure { 17 | margin-bottom: 0 !important; 18 | } 19 | 20 | figcaption { 21 | padding-bottom: 0; 22 | } 23 | } 24 | } 25 | 26 | .blockgallery--caption { 27 | padding-bottom: 1em; 28 | padding-top: 1em; 29 | text-align: center; 30 | 31 | &:not([class*="font-size"]) { 32 | font-size: 13px; 33 | } 34 | 35 | .is-selected &, 36 | .is-typing & { 37 | padding-left: 1em; 38 | padding-right: 1em; 39 | } 40 | } 41 | 42 | .has-fullwidth-images img { 43 | width: 100%; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/styles/editor/_gallery-item.scss: -------------------------------------------------------------------------------- 1 | .blockgallery--item { 2 | margin: 0; 3 | 4 | figure:focus { 5 | outline: none; 6 | } 7 | 8 | img:focus { 9 | outline: none; 10 | box-shadow: inset 0 0 0 4px #0085ba; 11 | } 12 | 13 | .is-selected { 14 | 15 | &::after { 16 | position: absolute; 17 | top: 0; 18 | right: 0; 19 | bottom: 0; 20 | left: 0; 21 | box-shadow: inset 0 0 0 4px #0085ba; 22 | z-index: 1; 23 | content: ""; 24 | } 25 | } 26 | 27 | .is-transient img { 28 | opacity: 0.3; 29 | } 30 | 31 | .editor-rich-text { 32 | z-index: 2; 33 | } 34 | 35 | .components-spinner { 36 | position: absolute; 37 | top: 50%; 38 | left: 50%; 39 | margin-top: -9px; 40 | margin-left: -9px; 41 | } 42 | } 43 | 44 | .has-caption-color .blockgallery--caption { 45 | 46 | a { 47 | color: inherit !important; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/components/background/transforms.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Set the attributes for the Background transformations 3 | * @type {Object} 4 | */ 5 | function BackgroundTransforms( props ) { 6 | 7 | const transforms = { 8 | backgroundColor: props.backgroundColor, 9 | customBackgroundColor: props.customBackgroundColor, 10 | backgroundImg: props.backgroundImg, 11 | backgroundOverlay: props.backgroundOverlay, 12 | backgroundPosition: props.backgroundPosition, 13 | backgroundRepeat: props.backgroundRepeat, 14 | backgroundSize: props.backgroundSize, 15 | backgroundPadding: props.backgroundPadding, 16 | backgroundPaddingMobile: props.backgroundPaddingMobile, 17 | backgroundRadius: props.backgroundRadius, 18 | hasParallax: props.hasParallax, 19 | captionStyle: props.captionStyle, 20 | }; 21 | 22 | return transforms; 23 | } 24 | 25 | export default BackgroundTransforms; -------------------------------------------------------------------------------- /src/styles/editor/_flickity-arrows.scss: -------------------------------------------------------------------------------- 1 | .has-no-arrows { 2 | 3 | .flickity-prev-next-button { 4 | background-color: $blue; 5 | border: none; 6 | width: 38px; 7 | height: 38px; 8 | border-radius: 4px; 9 | opacity: 1; 10 | display: none; 11 | transition: none; 12 | 13 | &:hover { 14 | background-color: #007eb1; 15 | } 16 | 17 | &:active { 18 | background-color: #006a95; 19 | } 20 | 21 | .flickity-button-icon { 22 | fill: $white; 23 | top: 22.5%; 24 | width: 55%; 25 | height: 55%; 26 | } 27 | 28 | &.previous { 29 | left: 15px; 30 | 31 | .flickity-button-icon { 32 | left: 27%; 33 | } 34 | } 35 | 36 | &.next { 37 | right: 15px; 38 | 39 | .flickity-button-icon { 40 | left: 20%; 41 | } 42 | } 43 | } 44 | 45 | &.blockgallery.is-selected .flickity-prev-next-button { 46 | display: block; 47 | } 48 | } 49 | 50 | .has-no-dots .flickity-page-dots { 51 | display: none !important; 52 | } 53 | -------------------------------------------------------------------------------- /src/components/background/attributes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Set the attributes for the Background Panel 3 | * @type {Object} 4 | */ 5 | const BackgroundAttributes = { 6 | backgroundImg: { 7 | type: 'string', 8 | }, 9 | backgroundPosition: { 10 | type: 'string', 11 | default: 'center center', 12 | }, 13 | backgroundRepeat: { 14 | type: 'string', 15 | default: 'no-repeat', 16 | }, 17 | backgroundSize: { 18 | type: 'string', 19 | }, 20 | backgroundRadius: { 21 | type: 'number', 22 | default: 0, 23 | }, 24 | backgroundPadding: { 25 | type: 'number', 26 | default: 0, 27 | }, 28 | backgroundPaddingMobile: { 29 | type: 'number', 30 | default: 0, 31 | }, 32 | hasParallax: { 33 | type: 'boolean', 34 | default: false, 35 | }, 36 | backgroundOverlay: { 37 | type: 'number', 38 | default: 0, 39 | }, 40 | backgroundColor: { 41 | type: 'string', 42 | }, 43 | customBackgroundColor: { 44 | type: 'string', 45 | }, 46 | }; 47 | 48 | export default BackgroundAttributes; -------------------------------------------------------------------------------- /src/styles/editor/_radius.scss: -------------------------------------------------------------------------------- 1 | // Utility class for adding a border-radius to the figure elements. 2 | @for $i from 2 through 20 { 3 | 4 | .has-border-radius-#{ $i } { 5 | 6 | .blockgallery--figure::after { 7 | border-radius: #{ $i }px; 8 | } 9 | 10 | .components-blockgallery-gallery-item__remove-button, 11 | .components-blockgallery-gallery-item__remove-wrapper { 12 | border-top-right-radius: #{ $i }px !important; 13 | border-bottom-left-radius: #{ $i }px !important; 14 | } 15 | 16 | .components-blockgallery-gallery-item__upload.components-button { 17 | border-radius: #{ $i }px; 18 | } 19 | 20 | figcaption { 21 | border-bottom-right-radius: #{ $i - 4}px !important; 22 | border-bottom-left-radius: #{ $i - 4 }px !important; 23 | } 24 | } 25 | } 26 | 27 | @for $i from 13 through 20 { 28 | 29 | .has-border-radius-#{ $i } { 30 | 31 | .components-blockgallery-gallery-item__remove-wrapper { 32 | padding: 6px; 33 | 34 | svg { 35 | right: -1px; 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/global/classes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal dependencies 3 | */ 4 | import { LightboxClasses } from '../../components/lightbox'; 5 | import { BackgroundClasses } from '../../components/background'; 6 | 7 | /** 8 | * WordPress dependencies 9 | */ 10 | const { getColorClassName } = wp.editor; 11 | 12 | /** 13 | * CSS classes 14 | */ 15 | function GlobalClasses( attributes ) { 16 | 17 | const captionColorClass = getColorClassName( 'color', attributes.captionColor ); 18 | 19 | const { 20 | align, 21 | images, 22 | radius, 23 | filter, 24 | captionStyle, 25 | customCaptionColor, 26 | } = attributes; 27 | 28 | return [ 29 | 'blockgallery', 30 | { 'has-no-alignment' : ! align }, 31 | { [ `has-border-radius-${ radius }` ] : radius > 0 }, 32 | { [ `has-filter-${ filter }` ] : filter != 'none' }, 33 | { [ `has-caption-style-${ captionStyle }` ] : captionStyle != undefined }, 34 | { 'has-caption-color': captionColorClass, }, 35 | ...LightboxClasses( attributes ), 36 | ...BackgroundClasses( attributes ), 37 | captionColorClass, 38 | ]; 39 | } 40 | 41 | export default GlobalClasses; 42 | -------------------------------------------------------------------------------- /src/blocks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies 3 | */ 4 | const { registerBlockType } = wp.blocks; 5 | 6 | // Category slug and title 7 | const category = { 8 | slug: 'block-gallery', 9 | title: 'Block Gallery', 10 | }; 11 | 12 | // Custom foreground icon color based on the Block Gallery branding 13 | const iconColor = '#1e35b9'; 14 | 15 | // Register block icons 16 | import icons from './utils/block-category'; 17 | 18 | // Editor and Frontend Styles 19 | import './styles/editor.scss'; 20 | import './styles/style.scss'; 21 | 22 | // Register Blocks 23 | import * as carousel from './blocks/carousel'; 24 | import * as masonry from './blocks/masonry'; 25 | import * as stacked from './blocks/stacked'; 26 | 27 | export function registerBlocks () { 28 | [ 29 | carousel, 30 | masonry, 31 | stacked, 32 | ].forEach( ( block ) => { 33 | 34 | if ( ! block ) { 35 | return; 36 | } 37 | 38 | const { name, icon, settings } = block; 39 | 40 | registerBlockType( `blockgallery/${ name }`, { category: category.slug, icon: { src: icon, foreground: iconColor, }, ...settings } ); 41 | } ); 42 | }; 43 | registerBlocks(); 44 | -------------------------------------------------------------------------------- /src/styles/editor.scss: -------------------------------------------------------------------------------- 1 | // Core Gutenberg modules 2 | @import "editor/core/colors"; 3 | @import "editor/core/variables"; 4 | @import "editor/core/breakpoints"; 5 | @import "editor/core/mixins"; 6 | 7 | // Our modules 8 | @import "editor/icons"; 9 | @import "editor/gallery-item"; 10 | @import "editor/remove-gallery-item-button"; 11 | @import "editor/gallery-upload"; 12 | @import "editor/inspector"; 13 | @import "editor/inspector-tabs"; 14 | @import "editor/radius"; 15 | @import "editor/is-background-transient"; 16 | @import "editor/flickity-arrows"; 17 | @import "editor/core-themes"; 18 | 19 | .components-toolbar { 20 | 21 | .components-dropdown-menu { 22 | padding: 0; 23 | } 24 | } 25 | 26 | .blockgallery { 27 | 28 | .components-resizable-box__handle { 29 | z-index: 9999; 30 | } 31 | } 32 | 33 | .components-blockgallery-inspector__lightbox { 34 | display: none; 35 | } 36 | 37 | .editor-media-placeholder[class*="blockgallery"] .components-placeholder__label .dashicon { 38 | margin-right: 0.7ch; 39 | position: relative; 40 | top: -2px; 41 | } 42 | 43 | .components-blockgallery-inspector__size-control--shadow { 44 | margin-bottom: 2.85em; 45 | } 46 | -------------------------------------------------------------------------------- /src/styles/editor/core/_breakpoints.scss: -------------------------------------------------------------------------------- 1 | // Breakpoints & Media Queries 2 | 3 | // Most used breakpoints 4 | $break-huge: 1440px; 5 | $break-wide: 1280px; 6 | $break-large: 960px; // admin sidebar auto folds 7 | $break-medium: 782px; // adminbar goes big 8 | $break-small: 600px; 9 | $break-mobile: 480px; 10 | 11 | // All media queries currently in WordPress: 12 | // 13 | // min-width: 2000px 14 | // min-width: 1680px 15 | // min-width: 1250px 16 | // max-width: 1120px * 17 | // max-width: 1000px 18 | // min-width: 769px and max-width: 1000px 19 | // max-width: 960px * 20 | // max-width: 900px 21 | // max-width: 850px 22 | // min-width: 800px and max-width: 1499px 23 | // max-width: 800px 24 | // max-width: 799px 25 | // max-width: 782px * 26 | // max-width: 768px 27 | // max-width: 640px * 28 | // max-width: 600px * 29 | // max-width: 520px 30 | // max-width: 500px 31 | // max-width: 480px * 32 | // max-width: 400px * 33 | // max-width: 380px 34 | // max-width: 320px * 35 | // 36 | // Those marked * seem to be more commonly used than the others. 37 | // Let's try and use as few of these as possible, and be mindful about adding new ones, so we don't make the situation worse 38 | -------------------------------------------------------------------------------- /src/styles/style/gutter.scss: -------------------------------------------------------------------------------- 1 | .blockgallery { 2 | 3 | &.has-gutter { 4 | overflow: hidden; 5 | } 6 | 7 | &:not(.has-gutter) { 8 | margin-left: auto !important; 9 | margin-right: auto !important; 10 | } 11 | } 12 | 13 | // For desktop. 14 | @media (min-width: 700px) { 15 | // Add negative margin to the parent ul to offset the gutter. 16 | @for $i from 1 through 10 { 17 | 18 | .has-gutter-#{ $i * 5 } { 19 | margin: -#{ round($i * 5 / 2) }px !important; 20 | max-width: calc(100% + #{ round($i * 5) }px) !important; 21 | } 22 | } 23 | 24 | // Add gutter margin to the figure elements. 25 | @for $i from 1 through 10 { 26 | 27 | .has-gutter-#{ $i * 5 } { 28 | 29 | .blockgallery--figure { 30 | margin: #{ round($i * 5 / 2) }px; 31 | } 32 | } 33 | } 34 | } 35 | 36 | // For desktop. 37 | @media (max-width: 699px) { 38 | // Add gutter margin to the figure elements. 39 | @for $i from 1 through 10 { 40 | 41 | .has-gutter-mobile-#{ $i * 5 } { 42 | margin: -#{ round($i * 5 / 2) }px !important; 43 | max-width: calc(100% + #{ round($i * 5) }px) !important; 44 | 45 | .blockgallery--figure { 46 | margin: #{ round($i * 5 / 2) }px; 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/styles/style/_background-classes.scss: -------------------------------------------------------------------------------- 1 | .has-background { 2 | 3 | // Background repeat 4 | &-repeat { 5 | background-repeat: repeat; 6 | } 7 | 8 | &-no-repeat { 9 | background-repeat: no-repeat; 10 | } 11 | 12 | &-repeat-x { 13 | background-repeat: repeat-x; 14 | } 15 | 16 | &-repeat-y { 17 | background-repeat: repeat-y; 18 | } 19 | 20 | // Background size 21 | &-cover { 22 | background-size: cover; 23 | } 24 | 25 | &-auto { 26 | background-size: auto; 27 | } 28 | 29 | &-contain { 30 | background-size: contain; 31 | } 32 | 33 | // Background position 34 | &-top-left { 35 | background-position: top left; 36 | } 37 | 38 | &-top-center { 39 | background-position: top center; 40 | } 41 | 42 | &-top-right { 43 | background-position: top right; 44 | } 45 | 46 | &-center-left { 47 | background-position: center left; 48 | } 49 | 50 | &-center-center { 51 | background-position: center center; 52 | } 53 | 54 | &-center-right { 55 | background-position: center right; 56 | } 57 | 58 | &-bottom-left { 59 | background-position: bottom left; 60 | } 61 | 62 | &-bottom-center { 63 | background-position: bottom center; 64 | } 65 | 66 | &-bottom-right { 67 | background-position: bottom right; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/components/global/transforms.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal dependencies 3 | */ 4 | import * as helper from './../../utils/helper'; 5 | import { LightboxTransforms } from '../../components/lightbox'; 6 | import { BackgroundTransforms } from '../../components/background'; 7 | 8 | /** 9 | * Set global transforms that every block uses. 10 | * @type {Object} 11 | */ 12 | function GlobalTransforms( props ) { 13 | 14 | const transforms = { 15 | align: props.align, 16 | gutter: props.gutter, 17 | gutterMobile: props.gutterMobile, 18 | gridSize: props.gridSize, 19 | images: props.images.map( ( image ) => helper.pickRelevantMediaFiles( image ) ), 20 | linkTo: props.linkTo, 21 | radius: props.radius, 22 | shadow: props.shadow, 23 | filter: props.filter, 24 | height: props.height, 25 | primaryCaption: props.primaryCaption, 26 | captions: props.captions, 27 | captionColor: props.captionColor, 28 | customCaptionColor: props.customCaptionColor, 29 | pageDots: props.pageDots, 30 | prevNextButtons: props.prevNextButtons, 31 | autoPlay: props.autoPlay, 32 | autoPlaySpeed: props.autoPlaySpeed, 33 | draggable: props.draggable, 34 | fontSize: props.fontSize, 35 | customFontSize: props.customFontSize, 36 | ...LightboxTransforms( props ), 37 | ...BackgroundTransforms( props ), 38 | }; 39 | 40 | return transforms; 41 | } 42 | 43 | export default GlobalTransforms; -------------------------------------------------------------------------------- /src/styles/style/_carousel.scss: -------------------------------------------------------------------------------- 1 | .blockgallery--item { 2 | 3 | // We really don't want margin on the flickity items. 4 | // Set 100% so the ResizableBox functions properly. 5 | .wp-block-blockgallery-carousel &, 6 | .wp-block-blockgallery-thumbnails & { 7 | margin: 0 !important; 8 | height: 100%; 9 | } 10 | 11 | .has-carousel-sml & { 12 | width: 65%; 13 | 14 | @media (min-width: 700px) { 15 | width: 33.333%; 16 | } 17 | 18 | @media (min-width: 1100px) { 19 | width: 25%; 20 | } 21 | 22 | @media (min-width: 1600px) { 23 | width: 20%; 24 | } 25 | } 26 | 27 | .has-carousel-med & { 28 | width: 70%; 29 | 30 | @media (min-width: 700px) { 31 | width: 33.333%; 32 | } 33 | 34 | @media (min-width: 1800px) { 35 | width: 20%; 36 | } 37 | } 38 | 39 | .has-carousel-lrg & { 40 | width: 80%; 41 | 42 | @media (min-width: 600px) { 43 | width: 70%; 44 | } 45 | 46 | @media (min-width: 1300px) { 47 | width: 60%; 48 | } 49 | } 50 | 51 | .has-carousel-xlrg & { 52 | width: 100%; 53 | 54 | @media (min-width: 1200px) { 55 | width: 80%; 56 | } 57 | 58 | @media (min-width: 1800px) { 59 | width: 66.666%; 60 | } 61 | } 62 | 63 | .has-no-alignment .has-carousel-lrg & { 64 | @media (min-width: 1300px) { 65 | width: 70%; 66 | } 67 | } 68 | 69 | .has-no-alignment .has-carousel-xlrg & { 70 | width: 100%; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /includes/admin/class-block-gallery-footer-text.php: -------------------------------------------------------------------------------- 1 | ★★★★★' 47 | ); 48 | 49 | return $footer_text; 50 | } 51 | } 52 | 53 | new Block_Gallery_Footer_Text(); 54 | -------------------------------------------------------------------------------- /includes/admin/class-block-gallery-url-generator.php: -------------------------------------------------------------------------------- 1 | apply_filters( 'blockgallery_affiliate_id', null ) ); 29 | 30 | return $id; 31 | } 32 | 33 | /** 34 | * Returns a URL that points to the Block Architect store. 35 | * 36 | * @since 1.0.0 37 | * @param string|string $path A URL path to append to the store URL. 38 | * @param array|array $params An array of key/value params to add to the query string. 39 | * @return string 40 | */ 41 | public function get_store_url( $path = '', $params = array() ) { 42 | 43 | $id = $this->get_affiliate_id(); 44 | 45 | $params = array_merge( $params, $id ); 46 | 47 | $url = trailingslashit( BLOCKGALLERY_SHOP_URL . $path ) . '?' . http_build_query( $params, '', '&' ); 48 | 49 | return $url; 50 | } 51 | } 52 | 53 | return new Block_Gallery_URL_Generator(); 54 | -------------------------------------------------------------------------------- /src/components/gallery-placeholder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal dependencies 3 | */ 4 | import * as helper from './../utils/helper'; 5 | 6 | /** 7 | * WordPress dependencies 8 | */ 9 | const { __ } = wp.i18n; 10 | const { Component, Fragment } = wp.element; 11 | const { MediaPlaceholder } = wp.editor; 12 | 13 | /** 14 | * Gallery Image Component 15 | */ 16 | class GalleryPlaceholder extends Component { 17 | 18 | constructor() { 19 | super( ...arguments ); 20 | this.onSelectImages = this.onSelectImages.bind( this ); 21 | } 22 | 23 | onSelectImages( images ) { 24 | this.props.setAttributes( { 25 | images: images.map( ( image ) => helper.pickRelevantMediaFiles( image ) ), 26 | } ); 27 | } 28 | 29 | render() { 30 | 31 | const { 32 | attributes, 33 | className, 34 | noticeOperations, 35 | noticeUI, 36 | } = this.props; 37 | 38 | return ( 39 | 40 | 54 | 55 | ); 56 | } 57 | } 58 | 59 | export default GalleryPlaceholder; 60 | 61 | -------------------------------------------------------------------------------- /src/components/gallery-dropzone.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies 3 | */ 4 | const { __ } = wp.i18n; 5 | const { Component, Fragment } = wp.element; 6 | const { mediaUpload } = wp.editor; 7 | const { DropZone } = wp.components; 8 | 9 | /** 10 | * Internal dependencies 11 | */ 12 | import * as helper from './../utils/helper'; 13 | 14 | /** 15 | * Gallery Drop Zone Component 16 | */ 17 | class GalleryDropZone extends Component { 18 | 19 | constructor() { 20 | super( ...arguments ); 21 | 22 | this.addFiles = this.addFiles.bind( this ); 23 | } 24 | 25 | addFiles( files ) { 26 | const currentImages = this.props.attributes.images || []; 27 | const { noticeOperations, setAttributes } = this.props; 28 | mediaUpload( { 29 | allowedTypes: helper.ALLOWED_MEDIA_TYPES, 30 | filesList: files, 31 | onFileChange: ( images ) => { 32 | const imagesNormalized = images.map( ( image ) => helper.pickRelevantMediaFiles( image ) ); 33 | setAttributes( { 34 | images: currentImages.concat( imagesNormalized ), 35 | } ); 36 | }, 37 | onError: noticeOperations.createErrorNotice, 38 | } ); 39 | } 40 | 41 | render() { 42 | 43 | const { 44 | attributes, 45 | className, 46 | noticeOperations, 47 | noticeUI, 48 | label, 49 | } = this.props; 50 | 51 | return ( 52 | 53 | 57 | 58 | ); 59 | } 60 | } 61 | 62 | export default GalleryDropZone; -------------------------------------------------------------------------------- /src/components/lightbox/control.js: -------------------------------------------------------------------------------- 1 | import lightboxStyleOptions from './options'; 2 | 3 | /** 4 | * WordPress dependencies 5 | */ 6 | const { __ } = wp.i18n; 7 | const { Component, Fragment } = wp.element; 8 | const { PanelBody, SelectControl, ToggleControl } = wp.components; 9 | 10 | /** 11 | * Gutter Controls Component 12 | */ 13 | class LightboxControl extends Component { 14 | 15 | constructor() { 16 | super( ...arguments ); 17 | this.setLightboxTo = this.setLightboxTo.bind( this ); 18 | this.setLightboxStyleTo = this.setLightboxStyleTo.bind( this ); 19 | } 20 | 21 | setLightboxTo() { 22 | this.props.setAttributes( { lightbox: ! this.props.attributes.lightbox } ); 23 | } 24 | 25 | setLightboxStyleTo( value ) { 26 | this.props.setAttributes( { lightboxStyle: value } ); 27 | } 28 | 29 | render() { 30 | 31 | const { 32 | attributes, 33 | setAttributes, 34 | } = this.props; 35 | 36 | const { 37 | lightbox, 38 | lightboxStyle, 39 | } = attributes; 40 | 41 | return ( 42 | 43 |
44 | 49 | { lightbox && } 56 |
57 |
58 | ); 59 | } 60 | } 61 | 62 | export default LightboxControl; -------------------------------------------------------------------------------- /src/styles/style/_bricks.scss: -------------------------------------------------------------------------------- 1 | .has-bricks-grid-sml { 2 | 3 | .blockgallery--item img { 4 | 5 | @media screen and (min-width: 800px) { 6 | max-height: 250px !important; 7 | } 8 | 9 | @media screen and (min-width: 1200px) { 10 | max-height: 300px !important; 11 | } 12 | } 13 | } 14 | 15 | .has-bricks-grid-med { 16 | 17 | .blockgallery--item img { 18 | 19 | @media screen and (min-width: 700px) { 20 | max-height: 250px !important; 21 | } 22 | 23 | @media screen and (min-width: 1000px) { 24 | max-height: 300px !important; 25 | } 26 | 27 | @media screen and (min-width: 1400px) { 28 | max-height: 400px !important; 29 | } 30 | } 31 | } 32 | 33 | .has-bricks-grid-lrg { 34 | 35 | .blockgallery--item img { 36 | 37 | @media screen and (min-width: 300px) { 38 | max-height: 180px !important; 39 | } 40 | 41 | @media screen and (min-width: 600px) { 42 | max-height: 300px !important; 43 | } 44 | 45 | @media screen and (min-width: 1000px) { 46 | max-height: 350px !important; 47 | } 48 | 49 | @media screen and (min-width: 1400px) { 50 | max-height: 450px !important; 51 | } 52 | 53 | @media screen and (min-width: 1900px) { 54 | max-height: 550px !important; 55 | } 56 | } 57 | } 58 | 59 | .has-bricks-grid-xlrg { 60 | 61 | .blockgallery--item img { 62 | 63 | @media screen and (min-width: 300px) { 64 | max-height: 200px !important; 65 | } 66 | 67 | @media screen and (min-width: 600px) { 68 | max-height: 350px !important; 69 | } 70 | 71 | @media screen and (min-width: 1000px) { 72 | max-height: 400px !important; 73 | } 74 | 75 | @media screen and (min-width: 1400px) { 76 | max-height: 550px !important; 77 | } 78 | 79 | @media screen and (min-width: 1900px) { 80 | max-height: 650px !important; 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /src/blocks/masonry/styles/editor.scss: -------------------------------------------------------------------------------- 1 | .editor-block-list__block[data-type="blockgallery/masonry"] { 2 | 3 | // Fixes an issue where figures are set to display 100% if alignfull is set. 4 | .blockgallery--figure { 5 | width: auto !important; 6 | 7 | img { 8 | vertical-align: bottom; 9 | } 10 | } 11 | 12 | .components-blockgallery-gallery-item__upload { 13 | min-height: 200px; 14 | width: 100% !important; 15 | 16 | @media (max-width: 700px) { 17 | 18 | span { 19 | display: none; 20 | } 21 | 22 | .dashicon { 23 | margin-top: 0; 24 | } 25 | } 26 | } 27 | 28 | // Captions. 29 | .editor-rich-text { 30 | bottom: 0; 31 | left: 0; 32 | max-height: 100%; 33 | overflow-y: auto; 34 | position: absolute; 35 | right: 0; 36 | width: 100%; 37 | 38 | .editor-rich-text__tinymce { 39 | color: inherit; 40 | 41 | a { 42 | color: inherit; 43 | } 44 | 45 | &:not(.mce-content-body) { 46 | opacity: 0.6; 47 | } 48 | 49 | &:focus a[data-mce-selected] { 50 | opacity: 0.2; 51 | } 52 | } 53 | } 54 | 55 | .editor-rich-text figcaption:not([data-is-placeholder-visible="true"]) { 56 | position: relative !important; 57 | overflow: hidden; 58 | } 59 | 60 | .is-selected .editor-rich-text { 61 | // IE calculates this incorrectly, so leave it to modern browsers. 62 | @supports (position: sticky) { 63 | bottom: 4px; 64 | left: 4px; 65 | right: 4px; 66 | width: calc(100% - 8px); 67 | margin-top: -4px; 68 | } 69 | 70 | figcaption { 71 | padding-bottom: 6px !important; 72 | } 73 | 74 | // Override negative margins so this toolbar isn't hidden by overflow. 75 | // Overflow is needed for long captions. 76 | .editor-rich-text__inline-toolbar { 77 | top: 0; 78 | } 79 | 80 | // Make extra space for the inline toolbar. 81 | .editor-rich-text__tinymce { 82 | padding-top: 48px; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/styles/editor/_inspector.scss: -------------------------------------------------------------------------------- 1 | .components-blockgallery-inspector__size-control { 2 | align-items: center; 3 | display: flex; 4 | justify-content: space-between; 5 | margin-bottom: 1.7em; 6 | 7 | &--label { 8 | display: block; 9 | margin-bottom: 4px; 10 | } 11 | 12 | .components-range-control + &--label { 13 | margin-top: 0 !important; 14 | } 15 | 16 | + .components-blockgallery-inspector__tabs { 17 | margin-top: 2em; 18 | } 19 | 20 | .components-button { 21 | background: none; 22 | border: 1px solid #8d96a0; 23 | box-shadow: 0 0 0 transparent; 24 | position: relative; 25 | text-shadow: none; 26 | transition: box-shadow 0.1s linear; 27 | 28 | &.is-primary { 29 | 30 | &, 31 | &:focus, 32 | &:active { 33 | border-color: #6c7781; 34 | background: #6c7781; 35 | color: #fff; 36 | cursor: default; 37 | } 38 | } 39 | 40 | &.is-button:first-child { 41 | border-radius: 4px 0 0 4px; 42 | } 43 | 44 | &.is-button:last-child:not(.is-small) { 45 | border-radius: 0 4px 4px 0; 46 | } 47 | } 48 | } 49 | 50 | .components-blockgallery-inspector__lightbox-style { 51 | margin-top: -0.75em !important; 52 | } 53 | 54 | .components-blockgallery-inspector__background-overlay-style-control { 55 | margin-bottom: 1.8em !important; 56 | 57 | .components-select-control__input { 58 | margin-top: 0.5em; 59 | } 60 | 61 | label { 62 | display: none; 63 | } 64 | } 65 | 66 | .components-blockgallery-inspector__autoplayspeed-select { 67 | margin-bottom: 1.75em !important; 68 | } 69 | 70 | .components-blockgallery-inspector__background-size { 71 | margin-bottom: 2em !important; 72 | } 73 | 74 | .components-blockgallery-inspector__background-remove-button { 75 | justify-content: center; 76 | line-height: 26px; 77 | width: 100%; 78 | display: none; 79 | } 80 | 81 | .components-blockgallery-inspector__background-image-overlay { 82 | margin-bottom: 0.7em !important; 83 | margin-top: -0.7em !important; 84 | } 85 | -------------------------------------------------------------------------------- /src/styles/style/_grid.scss: -------------------------------------------------------------------------------- 1 | .has-grid-sml .blockgallery--item { 2 | 3 | @media (min-width: 250px) { 4 | width: 1 / 2 * 100%; 5 | } 6 | 7 | @media (min-width: 500px) { 8 | width: 1 / 3 * 100%; 9 | } 10 | 11 | @media (min-width: 800px) { 12 | width: 1 / 4 * 100%; 13 | } 14 | 15 | @media (min-width: 1300px) { 16 | width: 1 / 5 * 100%; 17 | } 18 | 19 | @media (min-width: 1700px) { 20 | width: 1 / 6 * 100%; 21 | } 22 | 23 | @media (min-width: 1900px) { 24 | width: 1 / 7 * 100%; 25 | } 26 | } 27 | 28 | .has-grid-med .blockgallery--item { 29 | 30 | @media (min-width: 350px) { 31 | width: 1 / 2 * 100%; 32 | } 33 | 34 | @media (min-width: 650px) { 35 | width: 1 / 3 * 100%; 36 | } 37 | 38 | @media (min-width: 1100px) { 39 | width: 1 / 4 * 100%; 40 | } 41 | 42 | .alignfull & { 43 | @media (min-width: 1600px) { 44 | width: 1 / 5 * 100%; 45 | } 46 | 47 | @media (min-width: 1900px) { 48 | width: 1 / 6 * 100%; 49 | } 50 | } 51 | } 52 | 53 | .has-grid-lrg .blockgallery--item { 54 | 55 | @media (min-width: 400px) { 56 | width: 1 / 2 * 100%; 57 | } 58 | 59 | @media (min-width: 800px) { 60 | width: 1 / 3 * 100%; 61 | } 62 | 63 | .alignfull & { 64 | @media (min-width: 1600px) { 65 | width: 1 / 4 * 100%; 66 | } 67 | 68 | @media (min-width: 1900px) { 69 | width: 1 / 5 * 100%; 70 | } 71 | } 72 | } 73 | 74 | .has-grid-xlrg .blockgallery--item { 75 | 76 | @media (min-width: 400px) { 77 | width: 1 / 2 * 100%; 78 | } 79 | 80 | .alignfull & { 81 | @media (min-width: 1600px) { 82 | width: 1 / 3 * 100%; 83 | } 84 | 85 | @media (min-width: 1900px) { 86 | width: 1 / 4 * 100%; 87 | } 88 | } 89 | } 90 | 91 | .has-no-alignment .has-grid-lrg .blockgallery--item { 92 | 93 | @media (min-width: 400px) { 94 | width: 1 / 2 * 100%; 95 | } 96 | 97 | @media (min-width: 900px) { 98 | width: 1 / 3 * 100%; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/components/background/classes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal dependencies 3 | */ 4 | import * as ratio from './../../utils/helper'; 5 | 6 | /** 7 | * WordPress dependencies 8 | */ 9 | const { getColorClassName } = wp.editor; 10 | 11 | /** 12 | * Background Classes 13 | */ 14 | function BackgroundClasses( attributes ) { 15 | 16 | const backgroundClass = getColorClassName( 'background-color', attributes.backgroundColor ); 17 | const backgroundSizeDefault = ( typeof options !== 'undefined' && typeof options.backgroundSize !== 'undefined' ) ? options.backgroundSize : 'cover'; 18 | const backgroundSize = attributes.backgroundSize ? attributes.backgroundSize : backgroundSizeDefault; 19 | 20 | return [ 21 | { 'has-background': attributes.backgroundColor || attributes.customBackgroundColor }, 22 | { [ backgroundClass ]: backgroundClass }, 23 | { 'has-padding': attributes.backgroundPadding > 0 }, 24 | { [ `has-background-border-radius-${ attributes.backgroundRadius }` ] : attributes.backgroundRadius > 0 }, 25 | { [ `has-padding-${ attributes.backgroundPadding }` ] : attributes.backgroundPadding > 0 }, 26 | { [ `has-padding-mobile-${ attributes.backgroundPaddingMobile }` ]: attributes.backgroundPaddingMobile > 0 }, 27 | { 'has-parallax': attributes.backgroundImg && attributes.hasParallax }, 28 | { 'has-background-image': attributes.backgroundImg }, 29 | { 'has-background-image-transient': attributes.backgroundImg && 0 === attributes.backgroundImg.indexOf( 'blob:' ) }, 30 | { [ `has-background-${ attributes.backgroundRepeat }` ] : attributes.backgroundImg && attributes.backgroundRepeat }, 31 | { [ `has-background-${ attributes.backgroundPosition.split(' ').join('-') }` ] : attributes.backgroundImg && attributes.backgroundPosition }, 32 | { [ `has-background-${ backgroundSize }` ] : attributes.backgroundImg }, 33 | { [ `has-background-overlay` ] : attributes.backgroundImg && attributes.backgroundOverlay !== 0 }, 34 | { [ ratio.overlayToClass( attributes.backgroundOverlay ) ] : attributes.backgroundImg && attributes.backgroundOverlay !== 0 && attributes.backgroundOverlay !== 50 }, 35 | ]; 36 | } 37 | 38 | export default BackgroundClasses; -------------------------------------------------------------------------------- /src/components/global/attributes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal dependencies 3 | */ 4 | import Lightbox, { LightboxAttributes } from '../../components/lightbox'; 5 | import BackgroundPanel, { BackgroundAttributes } from '../../components/background'; 6 | 7 | /** 8 | * Set global attributes that every block uses. 9 | * @type {Object} 10 | */ 11 | const GlobalAttributes = { 12 | images: { 13 | type: 'array', 14 | default: [], 15 | source: 'query', 16 | selector: '.blockgallery--item', 17 | query: { 18 | url: { 19 | source: 'attribute', 20 | selector: 'img', 21 | attribute: 'src', 22 | }, 23 | link: { 24 | source: 'attribute', 25 | selector: 'img', 26 | attribute: 'data-link', 27 | }, 28 | alt: { 29 | source: 'attribute', 30 | selector: 'img', 31 | attribute: 'alt', 32 | default: '', 33 | }, 34 | id: { 35 | source: 'attribute', 36 | selector: 'img', 37 | attribute: 'data-id', 38 | }, 39 | caption: { 40 | type: 'array', 41 | source: 'children', 42 | selector: 'figcaption', 43 | }, 44 | }, 45 | }, 46 | linkTo: { 47 | type: 'string', 48 | default: 'none', 49 | }, 50 | align: { 51 | type: 'string', 52 | }, 53 | gutter: { 54 | type: 'number', 55 | default: 15, 56 | }, 57 | gutterMobile: { 58 | type: 'number', 59 | default: 15, 60 | }, 61 | radius: { 62 | type: 'number', 63 | default: 0, 64 | }, 65 | shadow: { 66 | type: 'string', 67 | default: 'none', 68 | }, 69 | filter: { 70 | type: 'string', 71 | default: 'none', 72 | }, 73 | captions: { 74 | type: 'boolean', 75 | default: true, 76 | }, 77 | captionStyle: { 78 | type: 'string', 79 | default: 'dark', 80 | }, 81 | captionColor: { 82 | type: 'string', 83 | }, 84 | customCaptionColor: { 85 | type: 'string', 86 | }, 87 | fontSize: { 88 | type: 'string', 89 | }, 90 | customFontSize: { 91 | type: 'number', 92 | }, 93 | primaryCaption: { 94 | type: 'array', 95 | source: 'children', 96 | selector: '.blockgallery--primary-caption', 97 | }, 98 | ...LightboxAttributes, 99 | ...BackgroundAttributes, 100 | }; 101 | 102 | export default GlobalAttributes; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Block Gallery — WordPress Gallery Gutenberg blocks 2 | 3 | #### Notice: Block Gallery in being deprecated in favor of the wonderful gallery blocks within CoBlocks. Each Block Gallery block may be transformed into it's corresponding CoBlocks gallery block. 4 | 5 | --- 6 | 7 | The first of its kind, [Block Gallery](https://wpblockgallery.com/) offers an unrivaled drag and drop gallery building experience in WordPress 5.0. Drop your images in your choice of photo gallery block, customize display settings, hit publish. 8 | 9 | An innovative transform system lets you instantly change your photos galleries into another form. Go from a fullscreen masonry gallery to a casual carousel, with just a single click. You won’t find another Gutenberg gallery plugin with this kind of capability. Guaranteed. 10 | 11 | [![Block Gallery, Winner at the Automattic Design Awards](https://user-images.githubusercontent.com/1813435/49888484-1a0eaf00-fe0d-11e8-9237-cd9d6f7716ae.jpg)](https://richtabor.com/block-gallery-automattic-design-awards/?utm_medium=block-gallery-github&utm_source=readme&utm_campaign=readme&utm_content=design-awards-banner) 12 | 13 | Block Gallery was selected as this year’s winner in the Best Solution category of the [Automattic Design Awards](https://automatticdesignaward.blog/2018/12/08/the-winners/) at WordCamp US 2018. [Continue reading...](https://richtabor.com/block-gallery-automattic-design-awards/) 14 | 15 | ## Installation ## 16 | 1. Install the offical [Gutenberg](https://wordpress.org/plugins/gutenberg/) plugin. Note that Gutenberg is not suggested for use on production sites. 17 | 2. [Download the latest release](https://github.com/godaddy/block-gallery/releases) from the GitHub repository, or.. 18 | 3. Download the plugin from the [WordPress plugin directory](https://wordpress.org/plugins/block-gallery/). 19 | 20 | ## Development ## 21 | 1. Clone the GitHub repository: `https://github.com/godaddy/block-gallery.git` 22 | 2. Browse to the folder in the command line. 23 | 3. Run the `npm install` command to install the plugin's dependencies within a /node_modules/ folder. 24 | 4. Run the `npm start` command for development. 25 | 5. Run the `build` gulp task to process build files and generate a zip. 26 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contribute To Block Gallery 2 | 3 | Community made patches, localisations, bug reports and contributions are always welcome! 4 | 5 | When contributing please ensure you follow the guidelines below so that we can keep on top of things. 6 | 7 | __Please Note:__ GitHub is for bug reports and contributions only - if you have a support question head over to the [support forum on WordPress.org](https://wordpress.org/support/plugin/block-gallery). 8 | 9 | ## Getting Started 10 | 11 | * __Do not report potential security vulnerabilities here. Email them privately to me at [plugins@godaddy.com](mailto:plugins@godaddy.com)__ 12 | * Before submitting a ticket, please be sure to replicate the behavior with no other plugins active and on a base theme like Twenty Seventeen. 13 | * Submit a ticket for your issue, assuming one does not already exist. 14 | * Raise it on our [Issue Tracker](https://github.com/godaddy/block-gallery/issues) 15 | * Clearly describe the issue including steps to reproduce the bug. 16 | * Make sure you fill in the earliest version that you know has the issue as well as the version of WordPress you're using. 17 | 18 | ## Making Changes 19 | 20 | * Fork the repository on GitHub 21 | * Make the changes to your forked repository 22 | * Ensure you stick to the [WordPress Coding Standards](https://codex.wordpress.org/WordPress_Coding_Standards) 23 | * When committing, reference your issue (if present) and include a note about the fix 24 | * If possible, and if applicable, please also add/update unit tests for your changes 25 | * Push the changes to your fork and submit a pull request to the 'master' branch of the Block Gallery repository 26 | 27 | ## Code Documentation 28 | 29 | * We ensure that every Block Gallery function is documented well and follows the standards set by phpDoc 30 | * Finally, please use tabs and not spaces. The tab indent size should be 8. 31 | 32 | At this point you're waiting on us to merge your pull request. We'll review all pull requests, and make suggestions and changes if necessary. 33 | 34 | # Additional Resources 35 | * [General GitHub Documentation](https://help.github.com/) 36 | * [GitHub Pull Request documentation](https://help.github.com/send-pull-requests/) 37 | * [PHPUnit Tests Guide](https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html) -------------------------------------------------------------------------------- /src/styles/editor/_inspector-tabs.scss: -------------------------------------------------------------------------------- 1 | .components-blockgallery-inspector__tabs { 2 | position: relative; 3 | display: flex; 4 | flex-direction: row-reverse; 5 | align-items: center; 6 | 7 | .components-base-control, 8 | .components-base-control__field { 9 | margin-bottom: 0 !important; 10 | } 11 | 12 | .components-tab-panel__tab-content { 13 | width: 100%; 14 | } 15 | 16 | &-item { 17 | appearance: none; 18 | background: $white; 19 | border-radius: 4px; 20 | border: 1px solid #8d96a0; 21 | color: #6c7781; 22 | height: 28px; 23 | line-height: 1; 24 | margin: 0; 25 | padding-left: 7px; 26 | padding-right: 7px; 27 | 28 | @media (max-width: 782px) { 29 | height: 40px; 30 | padding-left: 17px; 31 | padding-right: 17px; 32 | } 33 | 34 | @media (max-width: 514px) { 35 | padding-left: 10px; 36 | padding-right: 10px; 37 | } 38 | 39 | &:focus { 40 | border-color: #00a0d2 !important; 41 | color: #00a0d2; 42 | outline-offset: -2px; 43 | outline: 2px solid transparent; 44 | } 45 | 46 | &:not(.is-active):active:enabled { 47 | background: #eee; 48 | border-color: #999; 49 | box-shadow: inset 0 1px 0 #999; 50 | } 51 | 52 | &:not(.is-active):hover { 53 | background: #fafafa; 54 | border-color: #999; 55 | box-shadow: inset 0 -1px 0 #999; 56 | color: #555d66; 57 | cursor: pointer; 58 | } 59 | 60 | &.is-active { 61 | background: #6c7781; 62 | border-color: #6c7781; 63 | color: $white; 64 | z-index: 1; 65 | 66 | &:focus { 67 | background: #00a0d2; 68 | } 69 | } 70 | 71 | &:first-of-type { 72 | border-top-right-radius: 0; 73 | border-bottom-right-radius: 0; 74 | 75 | &:not(.is-active) { 76 | border-right: none; 77 | } 78 | } 79 | 80 | &:last-of-type { 81 | border-top-left-radius: 0; 82 | border-bottom-left-radius: 0; 83 | 84 | &:not(.is-active) { 85 | border-left: none; 86 | } 87 | } 88 | 89 | svg { 90 | height: 15px; 91 | position: relative; 92 | width: 15px; 93 | top: 0.08em; 94 | } 95 | } 96 | 97 | .components-tab-panel__tabs { 98 | align-items: center; 99 | display: flex; 100 | margin-left: 7px; 101 | margin-right: 1px; 102 | position: relative; 103 | top: 11px; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "block-gallery", 3 | "title": "Block Gallery", 4 | "description": "The most advanced suite of gallery blocks for the Gutenberg block editor. Create stunning masonry, carousel and stacked galleries in seconds, with the brilliantly intuitive interface. Block Gallery is absolutely the best collection of native editor gallery blocks in the world.", 5 | "version": "1.1.6", 6 | "tested_up_to": "5.2", 7 | "author": "GoDaddy", 8 | "author_uri": "https://www.godaddy.com", 9 | "requires": "5.0", 10 | "license": "GPL-2.0", 11 | "scripts": { 12 | "start": "gulp | cgb-scripts start", 13 | "build": "cgb-scripts build | babel src", 14 | "eject": "cgb-scripts eject", 15 | "makepot": "wp i18n make-pot . --merge=languages/block-gallery-js.pot --skip-js", 16 | "makepot:php": "npx pot-to-php languages/block-gallery.pot languages/block-gallery-translations.php block-gallery" 17 | }, 18 | "dependencies": { 19 | "cgb-scripts": "1.11.0", 20 | "classnames": "2.2.5", 21 | "compose": "^0.1.2", 22 | "delete-empty": "^2.0.0", 23 | "gutenberg-sortable": "^1.0.5", 24 | "lodash": "4.17.13", 25 | "memize": "^1.0.5", 26 | "react": "^16.2.0", 27 | "react-flickity-component": "^3.1.0", 28 | "react-masonry-component": "^6.2.1", 29 | "stylelint": "^9.4.0", 30 | "stylelint-config-wordpress": "^13.0.0" 31 | }, 32 | "devDependencies": { 33 | "@wordpress/babel-plugin-makepot": "^2.1.2", 34 | "@wordpress/babel-preset-default": "^1.1.2", 35 | "@wordpress/i18n": "^1.2.3", 36 | "babel-cli": "^6.26.0", 37 | "babel-core": "^6.25.0", 38 | "babel-loader": "^7.1.4", 39 | "del": "^3.0.0", 40 | "gulp": "^4.0.0", 41 | "gulp-autoprefixer": "^5.0.0", 42 | "gulp-cache": "^1.0.2", 43 | "gulp-copy": "^1.0.1", 44 | "gulp-if": "^2.0.2", 45 | "gulp-line-ending-corrector": "^1.0.1", 46 | "gulp-notify": "^3.2.0", 47 | "gulp-open": "^2.0.0", 48 | "gulp-rename": "^1.2.0", 49 | "gulp-replace-task": "^0.11.0", 50 | "gulp-run-command": "0.0.9", 51 | "gulp-sass": "^3.2.1", 52 | "gulp-sftp": "^0.1.5", 53 | "gulp-uglify": "^1.5.3", 54 | "gulp-uglifycss": "^1.0.8", 55 | "gulp-wp-pot": "^2.3.2", 56 | "gulp-zip": "^4.0.0", 57 | "stylelint-config-wordpress": "^13.0.0" 58 | }, 59 | "stylelint": { 60 | "extends": "stylelint-config-wordpress/scss", 61 | "rules": { 62 | "at-rule-empty-line-before": null, 63 | "no-descending-specificity": null 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/styles/editor/core/_variables.scss: -------------------------------------------------------------------------------- 1 | // Often re-used variables 2 | 3 | // Fonts & basics 4 | $default-font: -apple-system, BlinkMacSystemFont,"Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell,"Helvetica Neue", sans-serif; 5 | $default-font-size: 13px; 6 | $default-line-height: 1.4; 7 | $editor-font: "Noto Serif", serif; 8 | $editor-html-font: Menlo, Consolas, monaco, monospace; 9 | $editor-font-size: 16px; 10 | $text-editor-font-size: 14px; 11 | $editor-line-height: 1.8; 12 | $big-font-size: 18px; 13 | $mobile-text-min-font-size: 16px; // Any font size below 16px will cause Mobile Safari to "zoom in" 14 | 15 | // Grid size 16 | $item-spacing: 10px; // Should deprecate this in favor of $grid-size. 17 | $grid-size-small: 4px; 18 | $grid-size: 8px; 19 | $grid-size-large: 16px; 20 | 21 | // Widths, heights & dimensions 22 | $panel-padding: 16px; 23 | $header-height: 56px; 24 | $panel-header-height: 50px; 25 | $admin-bar-height: 32px; 26 | $admin-bar-height-big: 46px; 27 | $admin-sidebar-width: 160px; 28 | $admin-sidebar-width-big: 190px; 29 | $admin-sidebar-width-collapsed: 36px; 30 | $empty-paragraph-height: $text-editor-font-size * 4; 31 | 32 | // Visuals 33 | $shadow-popover: 0 3px 30px rgba($dark-gray-900, 0.1); 34 | $shadow-toolbar: 0 2px 10px rgba($dark-gray-900, 0.1), 0 0 2px rgba($dark-gray-900, 0.1); 35 | $shadow-below-only: 0 5px 10px rgba($dark-gray-900, 0.05), 0 2px 2px rgba($dark-gray-900, 0.05); 36 | $shadow-modal: 0 3px 30px rgba($dark-gray-900, 0.2); 37 | 38 | // Editor Widths 39 | $sidebar-width: 280px; 40 | $content-width: 610px; // For the visual width, subtract 30px (2 * $block-padding + 2px borders). This comes to 580px, which is optimized for 70 characters. 41 | 42 | // Block UI 43 | $border-width: 1px; 44 | $block-controls-height: 36px; 45 | $icon-button-size: 36px; 46 | $icon-button-size-small: 24px; 47 | $inserter-tabs-height: 36px; 48 | $block-toolbar-height: $block-controls-height + $border-width; 49 | 50 | // Blocks 51 | $block-padding: 14px; // padding of nested blocks 52 | $block-spacing: 4px; // vertical space between blocks 53 | 54 | $block-side-ui-width: 28px; // width of the side UI, matches half matches half of a single line of text, so two buttons stacke matches 1 55 | $block-side-ui-clearance: 2px; // space between side UI and block 56 | $block-side-ui-padding: $block-side-ui-width + $block-side-ui-clearance; // total space used to accommodate side UI 57 | 58 | $block-container-side-padding: $block-side-ui-width + $block-padding + 2 * $block-side-ui-clearance; 59 | 60 | // Buttons & UI Widgets 61 | $radius-round-rectangle: 4px; 62 | $radius-round: 50%; 63 | -------------------------------------------------------------------------------- /src/styles/style.scss: -------------------------------------------------------------------------------- 1 | .blockgallery { 2 | list-style: none !important; 3 | margin-left: auto !important; 4 | margin-right: auto !important; 5 | padding: 0; 6 | 7 | &--item { 8 | list-style: none !important; 9 | margin: 0; 10 | padding: 0 !important; 11 | 12 | figure { 13 | margin: 0; 14 | position: relative; 15 | } 16 | 17 | img { 18 | vertical-align: middle; 19 | opacity: 1 !important; 20 | } 21 | } 22 | 23 | & &--item figcaption { 24 | margin: 0 !important; 25 | } 26 | 27 | &:not(.has-padding) { 28 | padding: 0 !important; 29 | } 30 | 31 | &:not(.has-margin) &--item { 32 | margin: auto !important; 33 | } 34 | 35 | &.is-cropped { 36 | 37 | .blockgallery--item, 38 | .blockgallery--item-thumbnail { 39 | 40 | a, 41 | img { 42 | // IE11 doesn't support object-fit, so just make sure images aren't skewed. 43 | // The following rules are for all browsers. 44 | width: 100%; 45 | 46 | // IE11 doesn't read rules inside this query. 47 | // They are applied only to modern browsers. 48 | @supports (position: sticky) { 49 | flex: 1; 50 | height: 100%; 51 | object-fit: cover; 52 | } 53 | } 54 | } 55 | } 56 | } 57 | 58 | .alignfull, 59 | .alignwide { 60 | 61 | ul.blockgallery { 62 | max-width: 100%; 63 | } 64 | } 65 | 66 | .blockgallery--item-thumbnail { 67 | list-style: none !important; 68 | margin: 0; 69 | padding: 0; 70 | 71 | figure { 72 | margin: 0; 73 | position: relative; 74 | } 75 | 76 | img { 77 | vertical-align: middle; 78 | } 79 | } 80 | 81 | figcaption.blockgallery--primary-caption { 82 | font-size: 13px; 83 | margin-bottom: 1em; 84 | margin-top: 1.2em; 85 | text-align: center; 86 | 87 | &:not(.has-caption-color) { 88 | color: #555d66; 89 | } 90 | } 91 | 92 | // Front-end modules 93 | @import "style/grid"; 94 | @import "style/bricks"; 95 | @import "style/gutter"; 96 | @import "style/horizontal-gutter"; 97 | @import "style/carousel"; 98 | @import "style/margin-bottom"; 99 | @import "style/margin-top"; 100 | @import "style/margin-right"; 101 | @import "style/margin-left"; 102 | @import "style/margin-right-negative"; 103 | @import "style/margin-left-negative"; 104 | @import "style/padding"; 105 | @import "style/shadow"; 106 | @import "style/radius"; 107 | @import "style/filters"; 108 | @import "style/captions"; 109 | @import "style/background-classes"; 110 | @import "style/background-overlay"; 111 | @import "style/background-parallax"; 112 | @import "style/background-radius"; 113 | @import "style/flickity"; 114 | @import "style/core-themes"; 115 | -------------------------------------------------------------------------------- /src/components/background/toolbar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal dependencies 3 | */ 4 | import icons from './../../utils/icons'; 5 | 6 | /** 7 | * Internal dependencies 8 | */ 9 | import * as helper from './../../utils/helper'; 10 | 11 | /** 12 | * WordPress dependencies 13 | */ 14 | const { __ } = wp.i18n; 15 | const { Component, Fragment } = wp.element; 16 | const { MediaUpload, MediaUploadCheck } = wp.editor; 17 | const { Toolbar, IconButton } = wp.components; 18 | 19 | /** 20 | * Background Image Toolbar 21 | */ 22 | class BackgroundToolbar extends Component { 23 | 24 | constructor( props ) { 25 | super( ...arguments ); 26 | } 27 | 28 | render() { 29 | 30 | const { 31 | attributes, 32 | setAttributes, 33 | } = this.props; 34 | 35 | const { 36 | backgroundColor, 37 | backgroundImg, 38 | backgroundPadding, 39 | backgroundPaddingMobile, 40 | } = attributes; 41 | 42 | return ( 43 | 44 | 45 | 46 | { ! backgroundImg ? 47 | { 49 | setAttributes( { 50 | backgroundImg: media.url, 51 | } ); 52 | 53 | if ( ! backgroundPadding && ! backgroundPaddingMobile ) { 54 | setAttributes( { 55 | backgroundPadding: 30, 56 | backgroundPaddingMobile: 30, 57 | } ); 58 | } 59 | } } 60 | allowedTypes={ helper.ALLOWED_MEDIA_TYPES } 61 | value={ backgroundImg } 62 | render={ ( { open } ) => ( 63 | 69 | ) } 70 | /> 71 | : 72 | { 77 | setAttributes( { 78 | backgroundImg: '', 79 | backgroundOverlay: 0, 80 | backgroundRepeat: 'no-repeat', 81 | backgroundPosition: '', 82 | backgroundSize: 'cover', 83 | hasParallax: false, 84 | } ); 85 | 86 | if ( ! backgroundColor ) { 87 | setAttributes( { 88 | backgroundPadding: 0, 89 | backgroundPaddingMobile: 0, 90 | } ); 91 | } 92 | } } 93 | /> 94 | } 95 | 96 | 97 | 98 | ); 99 | } 100 | } 101 | 102 | export default BackgroundToolbar; -------------------------------------------------------------------------------- /src/components/responsive-tabs-control.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal dependencies 3 | */ 4 | import icons from './../utils/icons'; 5 | 6 | /** 7 | * WordPress dependencies 8 | */ 9 | const { __, sprintf } = wp.i18n; 10 | const { Component, Fragment } = wp.element; 11 | const { RangeControl, TabPanel } = wp.components; 12 | 13 | /** 14 | * Gutter Controls Component 15 | */ 16 | class ResponsiveTabsControl extends Component { 17 | 18 | constructor() { 19 | super( ...arguments ); 20 | this.setGutterTo = this.setGutterTo.bind( this ); 21 | this.setGutterMobileTo = this.setGutterMobileTo.bind( this ); 22 | } 23 | 24 | setGutterTo( value ) { 25 | this.props.setAttributes( { gutter: value } ); 26 | } 27 | 28 | setGutterMobileTo( value ) { 29 | this.props.setAttributes( { gutterMobile: value } ); 30 | } 31 | 32 | render() { 33 | 34 | const { 35 | attributes, 36 | label = __( 'Gutter' ), 37 | max = 50, 38 | min = 0, 39 | onChange = this.setGutterTo, 40 | onChangeMobile = this.setGutterMobileTo, 41 | setAttributes, 42 | step = 5, 43 | value = this.props.attributes.gutter, 44 | valueMobile = this.props.attributes.gutterMobile, 45 | } = this.props; 46 | 47 | return ( 48 | 49 | 65 | { 66 | ( tab ) => { 67 | if ( 'mobile' === tab.name ) { 68 | return ( 69 | onChangeMobile( valueMobile ) } 74 | min={ min } 75 | max={ max } 76 | step={ step } 77 | /> 78 | ) 79 | } else { 80 | return ( 81 | onChange( value ) } 85 | min={ min } 86 | max={ max } 87 | step={ step } 88 | /> 89 | ) 90 | } 91 | } 92 | } 93 | 94 | 95 | ); 96 | } 97 | } 98 | 99 | export default ResponsiveTabsControl; 100 | -------------------------------------------------------------------------------- /includes/class-block-gallery-body-classes.php: -------------------------------------------------------------------------------- 1 | get( 'TextDomain' ) ); 68 | } 69 | 70 | /** 71 | * Add .is-{theme} class to the frontend for select themes. 72 | * 73 | * @param array $classes Classes for the body element. 74 | * @return array (Maybe) filtered body classes. 75 | * 76 | * @access public 77 | */ 78 | public function body_class( $classes ) { 79 | 80 | if ( $this->is_active_theme( $this->themes() ) ) { 81 | $classes[] = 'is-' . $this->theme_slug(); 82 | } 83 | 84 | return $classes; 85 | } 86 | 87 | /** 88 | * Add .is-{theme} class to the admin editor for select themes. 89 | * 90 | * @param array $classes Classes for the admin editor body element. 91 | * @return array (Maybe) filtered body classes. 92 | * 93 | * @access public 94 | */ 95 | public function admin_body_class( $classes ) { 96 | global $pagenow; 97 | 98 | // Return if not on viewing the editor. 99 | if ( ! in_array( $pagenow, array( 'post.php' ), true ) ) { 100 | return $classes; 101 | } 102 | 103 | if ( $this->is_active_theme( $this->themes() ) ) { 104 | $classes .= 'is-' . $this->theme_slug(); 105 | } 106 | 107 | return $classes; 108 | } 109 | } 110 | 111 | new Block_Gallery_Body_Classes(); 112 | -------------------------------------------------------------------------------- /src/components/gallery-upload.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External Dependencies 3 | */ 4 | import classnames from 'classnames'; 5 | 6 | /** 7 | * Internal dependencies 8 | */ 9 | import * as helper from './../utils/helper'; 10 | 11 | /** 12 | * WordPress dependencies 13 | */ 14 | const { __ } = wp.i18n; 15 | const { Component, Fragment } = wp.element; 16 | const { compose } = wp.compose; 17 | const { withSelect } = wp.data; 18 | const { 19 | IconButton, 20 | DropZone, 21 | FormFileUpload, 22 | PanelBody, 23 | Toolbar, 24 | withNotices, 25 | } = wp.components; 26 | const { 27 | MediaPlaceholder, 28 | mediaUpload, 29 | } = wp.editor; 30 | 31 | /** 32 | * Gallery Image Component 33 | */ 34 | class GalleryUpload extends Component { 35 | 36 | constructor() { 37 | super( ...arguments ); 38 | this.addFiles = this.addFiles.bind( this ); 39 | this.uploadFromFiles = this.uploadFromFiles.bind( this ); 40 | } 41 | 42 | uploadFromFiles( event ) { 43 | this.addFiles( event.target.files ); 44 | } 45 | 46 | addFiles( files ) { 47 | const currentImages = this.props.attributes.images || []; 48 | const { noticeOperations, setAttributes } = this.props; 49 | mediaUpload( { 50 | allowedTypes: helper.ALLOWED_MEDIA_TYPES, 51 | filesList: files, 52 | onFileChange: ( images ) => { 53 | setAttributes( { 54 | images: currentImages.concat( images ), 55 | } ); 56 | }, 57 | onError: noticeOperations.createErrorNotice, 58 | } ); 59 | } 60 | 61 | render() { 62 | 63 | const { 64 | gutter, 65 | gutterMobile, 66 | marginLeft, 67 | marginRight, 68 | marginTop, 69 | marginBottom, 70 | } = this.props; 71 | 72 | const className = classnames( 73 | 'blockgallery--figure', { 74 | [ `has-margin-top-${ gutter }` ] : marginTop && gutter > 0, 75 | [ `has-margin-top-mobile-${ gutterMobile }` ] : marginTop && gutterMobile > 0, 76 | [ `has-margin-right-${ gutter }` ] : marginRight && gutter > 0, 77 | [ `has-margin-right-mobile-${ gutterMobile }` ] : marginRight && gutterMobile > 0, 78 | [ `has-margin-bottom-${ gutter }` ] : marginBottom && gutter > 0, 79 | [ `has-margin-bottom-mobile-${ gutterMobile }` ] : marginBottom && gutterMobile > 0, 80 | [ `has-margin-left-${ gutter }` ] : marginLeft && gutter > 0, 81 | [ `has-margin-left-mobile-${ gutterMobile }` ] : marginLeft && gutterMobile > 0, 82 | } ); 83 | 84 | return ( 85 | 86 |
  • 87 |
    88 | 96 | { __( 'Upload an image' ) } 97 | 98 |
    99 |
  • 100 |
    101 | ) 102 | } 103 | } 104 | 105 | export default GalleryUpload; 106 | 107 | -------------------------------------------------------------------------------- /includes/admin/class-block-gallery-action-links.php: -------------------------------------------------------------------------------- 1 | has_pro() || Block_Gallery()->is_pro() ) { 36 | return $links; 37 | } 38 | 39 | // Generator the upgrade link. 40 | $url_generator = new Block_Gallery_URL_Generator(); 41 | 42 | $url = $url_generator->get_store_url( 43 | 'pricing', 44 | array( 45 | 'utm_medium' => 'block-gallery-lite', 46 | 'utm_source' => 'plugins-page', 47 | 'utm_campaign' => 'plugins-row', 48 | 'utm_content' => 'go-pro', 49 | ) 50 | ); 51 | 52 | $links['go_pro'] = sprintf( '%2$s', esc_url( $url ), esc_html__( 'Go Pro', '@@textdomain' ) ); 53 | 54 | return $links; 55 | } 56 | 57 | /** 58 | * Plugin row meta links 59 | * 60 | * @param array $plugin_meta An array of the plugin's metadata. 61 | * @param string $plugin_file Path to the plugin file. 62 | * @return array $input 63 | */ 64 | public function plugin_row_meta( $plugin_meta, $plugin_file ) { 65 | 66 | // Check if this is defined. 67 | if ( ! defined( 'BLOCKGALLERYPRO_PLUGIN_BASE' ) ) { 68 | define( 'BLOCKGALLERYPRO_PLUGIN_BASE', null ); 69 | } 70 | 71 | $url_generator = new Block_Gallery_URL_Generator(); 72 | 73 | $support_url = $url_generator->get_store_url( 74 | '/', 75 | array( 76 | 'utm_medium' => 'block-gallery-lite', 77 | 'utm_source' => 'plugins-page', 78 | 'utm_campaign' => 'plugins-row', 79 | 'utm_content' => 'help-and-faqs', 80 | ) 81 | ); 82 | 83 | if ( BLOCKGALLERY_PLUGIN_BASE === $plugin_file ) { 84 | $row_meta = array( 85 | 'docs' => '' . __( 'Help & FAQs', '@@textdomain' ) . '', 86 | 'review' => '' . __( 'Leave a Review', '@@textdomain' ) . '', 87 | ); 88 | 89 | $plugin_meta = array_merge( $plugin_meta, $row_meta ); 90 | } 91 | 92 | return $plugin_meta; 93 | } 94 | } 95 | 96 | new Block_Gallery_Action_Links(); 97 | -------------------------------------------------------------------------------- /src/styles/editor/_core-themes.scss: -------------------------------------------------------------------------------- 1 | .is-twentysixteen, 2 | .is-twentyseventeen, 3 | .is-twentythirteen, 4 | .is-twentytwelve { 5 | 6 | .blockgallery--primary-caption.editor-rich-text__tinymce { 7 | text-align: left; 8 | font-style: italic; 9 | margin-bottom: 0; 10 | } 11 | } 12 | 13 | .is-twentynineteen { 14 | 15 | @include blockGalleryEditorBlocks { 16 | margin-bottom: 48px; 17 | margin-top: 50px; 18 | } 19 | 20 | .blockgallery--primary-caption { 21 | margin-bottom: -10px; 22 | } 23 | } 24 | 25 | .is-twentyfifteen { 26 | 27 | @include blockGalleryEditorBlocks { 28 | margin-bottom: 42px; 29 | margin-top: 42px; 30 | } 31 | 32 | .blockgallery--primary-caption.editor-rich-text__tinymce { 33 | color: #707070; 34 | font-family: "Noto Sans", sans-serif; 35 | font-size: 12px; 36 | line-height: 1.5; 37 | margin-bottom: -5px; 38 | 39 | @media screen and (min-width: 46.25em) { 40 | font-size: 14px; 41 | } 42 | 43 | @media screen and (min-width: 55em) { 44 | font-size: 16px; 45 | } 46 | 47 | @media screen and (min-width: 59.6875em) { 48 | font-size: 12px; 49 | } 50 | 51 | @media screen and (min-width: 68.75em) { 52 | font-size: 14px; 53 | } 54 | 55 | @media screen and (min-width: 77.5em) { 56 | font-size: 16px; 57 | } 58 | } 59 | } 60 | 61 | .is-twentyfourteen { 62 | 63 | .blockgallery--primary-caption.editor-rich-text__tinymce { 64 | text-align: left; 65 | font-style: italic; 66 | margin-bottom: -7px; 67 | padding: 0; 68 | margin-top: 8px; 69 | } 70 | } 71 | 72 | .is-twentythirteen { 73 | 74 | @include blockGalleryEditorBlocks { 75 | margin-bottom: 40px; 76 | margin-top: 40px; 77 | } 78 | 79 | .blockgallery--primary-caption.editor-rich-text__tinymce { 80 | padding: 0; 81 | margin-top: 8px; 82 | } 83 | } 84 | 85 | 86 | .is-twentytwelve { 87 | 88 | // Override the theme's border radius behavior for our blocks. 89 | .blockgallery:not([class*="border-radius"]) img { 90 | border-radius: inherit; 91 | } 92 | 93 | .blockgallery--primary-caption.editor-rich-text__tinymce { 94 | padding: 0; 95 | margin-top: 1.1em; 96 | } 97 | 98 | .wp-block[data-type="blockgallery/stacked"] figcaption { 99 | text-align: center !important; 100 | } 101 | } 102 | 103 | .is-twentyeleven { 104 | 105 | // Override the theme's padding/border effect on our blocks. 106 | .blockgallery img { 107 | border: 0; 108 | padding: 0; 109 | max-width: 100%; 110 | } 111 | 112 | .blockgallery--primary-caption.editor-rich-text__tinymce { 113 | padding: 2px 0 0 40px; 114 | margin-bottom: -5px; 115 | text-align: left; 116 | font-family: Georgia, serif; 117 | font-size: 12px; 118 | 119 | &::before { 120 | color: #666; 121 | content: "\2014"; 122 | font-size: 14px; 123 | font-style: normal; 124 | font-weight: 600; 125 | margin-right: 5px; 126 | position: absolute; 127 | left: 10px; 128 | top: 0; 129 | } 130 | } 131 | 132 | .wp-block[data-type="blockgallery/stacked"] figcaption { 133 | text-align: center; 134 | padding-left: 1em; 135 | padding-right: 1em; 136 | 137 | &::before { 138 | display: none; 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/blocks/carousel/components/inspector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal dependencies 3 | */ 4 | import { title } from '../' 5 | import ResponsiveTabsControl from '../../../components/responsive-tabs-control'; 6 | import SizeControl from '../../../components/size-control'; 7 | import { BackgroundPanel } from '../../../components/background'; 8 | import SliderPanel from '../../../components/slider-panel'; 9 | 10 | /** 11 | * WordPress dependencies 12 | */ 13 | const { __, sprintf } = wp.i18n; 14 | const { Component, Fragment } = wp.element; 15 | const { InspectorControls } = wp.editor; 16 | const { PanelBody, RangeControl } = wp.components; 17 | 18 | /** 19 | * Inspector controls 20 | */ 21 | class Inspector extends Component { 22 | 23 | constructor( props ) { 24 | super( ...arguments ); 25 | this.setSizeControl = this.setSizeControl.bind( this ); 26 | this.setRadiusTo = this.setRadiusTo.bind( this ); 27 | this.setHeightTo = this.setHeightTo.bind( this ); 28 | } 29 | 30 | setRadiusTo( value ) { 31 | this.props.setAttributes( { radius: value } ); 32 | } 33 | 34 | setSizeControl( value ) { 35 | this.props.setAttributes( { gridSize: value } ); 36 | } 37 | 38 | setHeightTo( value ) { 39 | this.props.setAttributes( { height: value } ); 40 | } 41 | 42 | render() { 43 | 44 | const { 45 | attributes, 46 | setAttributes, 47 | isSelected, 48 | } = this.props; 49 | 50 | const { 51 | align, 52 | gridSize, 53 | gutter, 54 | height, 55 | images, 56 | radius, 57 | } = attributes; 58 | 59 | return ( 60 | isSelected && ( 61 | 62 | 63 | 69 | 76 | { gridSize != null && ( align == 'wide' || align == 'full' ) && 77 | 81 | } 82 | { gridSize != 'xlrg' && ! align && 83 | 87 | } 88 | 96 | { gutter > 0 && } 104 | 105 | 106 | 109 | 110 | 111 | ) 112 | ) 113 | } 114 | }; 115 | 116 | export default Inspector; 117 | -------------------------------------------------------------------------------- /src/styles/editor/core/_colors.scss: -------------------------------------------------------------------------------- 1 | // Colors 2 | 3 | // Hugo's new WordPress shades of gray, 4 | // from http://codepen.io/hugobaeta/pen/grJjVp 5 | $black: #000; 6 | $dark-gray-900: #191e23; 7 | $dark-gray-800: #23282d; 8 | $dark-gray-700: #32373c; 9 | $dark-gray-600: #40464d; 10 | $dark-gray-500: #555d66; // Use this most of the time for dark items. 11 | $dark-gray-400: #606a73; 12 | $dark-gray-300: #6c7781; // Lightest gray that can be used for AA text contrast. 13 | $dark-gray-200: #7e8993; 14 | $dark-gray-150: #8d96a0; // Lightest gray that can be used for AA non-text contrast. 15 | $dark-gray-100: #8f98a1; 16 | $light-gray-900: #a2aab2; 17 | $light-gray-800: #b5bcc2; 18 | $light-gray-700: #ccd0d4; 19 | $light-gray-600: #d7dade; 20 | $light-gray-500: #e2e4e7; // Good for "grayed" items and borders 21 | $light-gray-400: #e8eaeb; 22 | $light-gray-300: #edeff0; 23 | $light-gray-200: #f3f4f5; 24 | $light-gray-100: #f8f9f9; 25 | $white: #fff; 26 | 27 | // Dark opacities, for use with light themes. 28 | $dark-opacity-900: rgba(#000510, 0.9); 29 | $dark-opacity-800: rgba(#00000a, 0.85); 30 | $dark-opacity-700: rgba(#06060b, 0.8); 31 | $dark-opacity-600: rgba(#000913, 0.75); 32 | $dark-opacity-500: rgba(#0a1829, 0.7); 33 | $dark-opacity-400: rgba(#0a1829, 0.65); 34 | $dark-opacity-300: rgba(#0e1c2e, 0.62); 35 | $dark-opacity-200: rgba(#162435, 0.55); 36 | $dark-opacity-100: rgba(#223443, 0.5); 37 | $dark-opacity-light-900: rgba(#304455, 0.45); 38 | $dark-opacity-light-800: rgba(#425863, 0.4); 39 | $dark-opacity-light-700: rgba(#667886, 0.35); 40 | $dark-opacity-light-600: rgba(#7b86a2, 0.3); 41 | $dark-opacity-light-500: rgba(#9197a2, 0.25); 42 | $dark-opacity-light-400: rgba(#95959c, 0.2); 43 | $dark-opacity-light-300: rgba(#829493, 0.15); 44 | $dark-opacity-light-200: rgba(#8b8b96, 0.1); 45 | $dark-opacity-light-100: rgba(#747474, 0.05); 46 | 47 | // Light opacities, for use with dark themes. 48 | $light-opacity-900: rgba($white, 1); 49 | $light-opacity-800: rgba($white, 0.9); 50 | $light-opacity-700: rgba($white, 0.85); 51 | $light-opacity-600: rgba($white, 0.8); 52 | $light-opacity-500: rgba($white, 0.75); 53 | $light-opacity-400: rgba($white, 0.7); 54 | $light-opacity-300: rgba($white, 0.65); 55 | $light-opacity-200: rgba($white, 0.6); 56 | $light-opacity-100: rgba($white, 0.55); 57 | $light-opacity-light-900: rgba($white, 0.5); 58 | $light-opacity-light-800: rgba($white, 0.45); 59 | $light-opacity-light-700: rgba($white, 0.4); 60 | $light-opacity-light-600: rgba($white, 0.35); 61 | $light-opacity-light-500: rgba($white, 0.3); 62 | $light-opacity-light-400: rgba($white, 0.25); 63 | $light-opacity-light-300: rgba($white, 0.2); 64 | $light-opacity-light-200: rgba($white, 0.15); 65 | $light-opacity-light-100: rgba($white, 0.1); 66 | 67 | // Additional colors 68 | // some from https://make.wordpress.org/design/handbook/foundations/colors/ 69 | $blue-wordpress-700: #00669b; 70 | $blue-dark-900: #0071a1; 71 | 72 | $blue-medium-900: #006589; 73 | $blue-medium-800: #00739c; 74 | $blue-medium-700: #007fac; 75 | $blue-medium-600: #008dbe; 76 | $blue-medium-500: #00a0d2; 77 | $blue-medium-400: #33b3db; 78 | $blue-medium-300: #66c6e4; 79 | $blue-medium-200: #bfe7f3; 80 | $blue-medium-100: #e5f5fa; 81 | $blue-medium-highlight: #b3e7fe; 82 | $blue-medium-focus: #007cba; 83 | 84 | // Alert colors 85 | $alert-yellow: #f0b849; 86 | $alert-red: #d94f4f; 87 | $alert-green: #4ab866; 88 | -------------------------------------------------------------------------------- /src/styles/style/_flickity.scss: -------------------------------------------------------------------------------- 1 | // Flickity v2.1.2 2 | .flickity-enabled { 3 | position: relative; 4 | 5 | &:focus { 6 | outline: none; 7 | } 8 | 9 | &.is-draggable { 10 | user-select: none; 11 | } 12 | } 13 | 14 | .flickity-viewport { 15 | overflow: hidden; 16 | position: relative; 17 | height: 100%; 18 | 19 | .is-cropped & { 20 | height: 100% !important; 21 | } 22 | } 23 | 24 | .flickity-slider { 25 | position: absolute; 26 | width: 100%; 27 | height: 100%; 28 | } 29 | 30 | .flickity-enabled.is-draggable .flickity-viewport { 31 | cursor: move; 32 | cursor: -webkit-grab; 33 | cursor: grab; 34 | } 35 | 36 | .flickity-enabled.is-draggable .flickity-viewport.is-pointer-down { 37 | cursor: -webkit-grabbing; 38 | cursor: grabbing; 39 | } 40 | 41 | .flickity-button { 42 | background: hsla(0, 0%, 100%, 0.75); 43 | border: none; 44 | color: #000; 45 | position: absolute; 46 | padding: 0; 47 | transition: background 100ms, opacity 100ms; 48 | 49 | &:hover { 50 | background: $white; 51 | cursor: pointer; 52 | } 53 | 54 | &:focus { 55 | outline: none; 56 | border: none; 57 | background: $white; 58 | box-shadow: 0 0 0 2px #000; 59 | } 60 | 61 | &:active { 62 | border: none; 63 | opacity: 0.6; 64 | } 65 | 66 | &:disabled { 67 | opacity: 0.25; 68 | cursor: auto; 69 | pointer-events: none; 70 | } 71 | } 72 | 73 | .flickity-button-icon { 74 | fill: #000; 75 | transform: translate3d(0, 0, 0); 76 | } 77 | 78 | .flickity-prev-next-button { 79 | top: 50%; 80 | width: 57px; 81 | height: 72px; 82 | border-radius: 9px; 83 | transform: translateY(-50%); 84 | 85 | .has-top-left-carousel-arrows & { 86 | top: 20px; 87 | transform: none; 88 | width: 42px; 89 | height: 42px; 90 | border-radius: 4px; 91 | 92 | &.previous { 93 | left: 20px; 94 | } 95 | 96 | &.next { 97 | left: calc(25px + 42px); 98 | } 99 | } 100 | } 101 | 102 | .flickity-prev-next-button.previous { 103 | left: 10px; 104 | 105 | @media (min-width: 600px) { 106 | left: 20px; 107 | } 108 | } 109 | 110 | .flickity-prev-next-button.next { 111 | right: 10px; 112 | 113 | @media (min-width: 600px) { 114 | right: 20px; 115 | } 116 | } 117 | 118 | .flickity-rtl .flickity-prev-next-button.previous { 119 | left: auto; 120 | right: 10px; 121 | 122 | @media (min-width: 600px) { 123 | right: 20px; 124 | } 125 | } 126 | 127 | .flickity-rtl .flickity-prev-next-button.next { 128 | right: auto; 129 | left: 10px; 130 | 131 | @media (min-width: 600px) { 132 | left: 20px; 133 | } 134 | } 135 | 136 | .flickity-prev-next-button .flickity-button-icon { 137 | position: absolute; 138 | left: 23%; 139 | top: 25%; 140 | width: 50%; 141 | height: 50%; 142 | } 143 | 144 | .previous.flickity-prev-next-button .flickity-button-icon { 145 | left: 26%; 146 | } 147 | 148 | .flickity-page-dots { 149 | position: absolute; 150 | width: 100%; 151 | bottom: 18px; 152 | padding: 0 !important; 153 | margin: 0 !important; 154 | list-style: none; 155 | text-align: center; 156 | line-height: 1; 157 | } 158 | 159 | .flickity-rtl .flickity-page-dots { 160 | direction: rtl; 161 | } 162 | 163 | .flickity-page-dots .dot { 164 | display: inline-block; 165 | width: 9px; 166 | height: 9px; 167 | margin: 0 6px; 168 | background: hsla(0, 0%, 0, 0.3); 169 | border-radius: 50%; 170 | cursor: pointer; 171 | } 172 | 173 | .flickity-page-dots .dot.is-selected { 174 | background: hsla(0, 0, 100%, 0.75); 175 | } 176 | -------------------------------------------------------------------------------- /src/components/slider-panel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import map from 'lodash/map'; 5 | 6 | /** 7 | * Internal dependencies 8 | */ 9 | import autoPlayOptions from '../utils/autoplay-options'; 10 | 11 | /** 12 | * WordPress dependencies 13 | */ 14 | const { __, sprintf } = wp.i18n; 15 | const { Component, Fragment } = wp.element; 16 | const { PanelBody, ToggleControl, SelectControl } = wp.components; 17 | 18 | class SliderPanel extends Component { 19 | 20 | constructor( props ) { 21 | super( ...arguments ); 22 | this.getAutoPlayHelp = this.getAutoPlayHelp.bind( this ); 23 | } 24 | 25 | getAutoPlayHelp( checked ) { 26 | // Retrieve the height value and divide it to display full seconds. 27 | const speed = this.props.attributes.autoPlaySpeed / 1000; 28 | const time = ( speed > 1 ) ? __( 'seconds' ) : __( 'second' ); 29 | 30 | // translators: 1. Speed of the slider, 2: Time until the slide advances 31 | return checked ? sprintf( __( 'Automatically advancing to the next gallery item after %1$d %2$s.' ), speed, time ) : __( 'Automatically advance to the next gallery item after a set duration.' ); 32 | } 33 | 34 | getDraggableHelp( checked ) { 35 | return checked ? __( 'Dragging and flicking enabled on desktop and mobile devices.' ) : __( 'Toggle to enable drag functionality on desktop and mobile devices.' ); 36 | } 37 | 38 | getArrowNavigationHelp( checked ) { 39 | return checked ? __( 'Showing slide navigation arrows.' ) : __( 'Toggle to show slide navigation arrows.' ); 40 | } 41 | 42 | getDotNavigationHelp( checked ) { 43 | return checked ? __( 'Showing dot navigation arrows.' ) : __( 'Toggle to show dot navigation.' ); 44 | } 45 | 46 | render() { 47 | 48 | const { 49 | attributes, 50 | setAttributes, 51 | } = this.props; 52 | 53 | const { 54 | autoPlay, 55 | autoPlaySpeed, 56 | draggable, 57 | pageDots, 58 | prevNextButtons, 59 | } = attributes; 60 | 61 | return ( 62 | 63 | 64 | setAttributes( { autoPlay: ! autoPlay } ) } 68 | help={ this.getAutoPlayHelp } 69 | /> 70 | { autoPlay && setAttributes( { autoPlaySpeed: value } ) } 75 | options={ autoPlayOptions } 76 | className='components-blockgallery-inspector__autoplayspeed-select' 77 | /> } 78 | setAttributes( { draggable: ! draggable } ) } 82 | help={ this.getDraggableHelp } 83 | /> 84 | setAttributes( { prevNextButtons: ! prevNextButtons } ) } 88 | help={ this.getArrowNavigationHelp } 89 | /> 90 | setAttributes( { pageDots: ! pageDots } ) } 94 | help={ this.getDotNavigationHelp } 95 | /> 96 | 97 | 98 | ); 99 | } 100 | } 101 | 102 | export default SliderPanel; -------------------------------------------------------------------------------- /src/components/global/toolbar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal dependencies 3 | */ 4 | import * as helper from './../../utils/helper'; 5 | import icons from './../../utils/icons'; 6 | import { BackgroundToolbar } from '../../components/background'; 7 | 8 | /** 9 | * WordPress dependencies 10 | */ 11 | const { __ } = wp.i18n; 12 | const { Component, Fragment } = wp.element; 13 | const { 14 | IconButton, 15 | Toolbar, 16 | DropdownMenu, 17 | } = wp.components; 18 | const { 19 | BlockControls, 20 | MediaUpload, 21 | MediaUploadCheck, 22 | AlignmentToolbar, 23 | } = wp.editor; 24 | 25 | /** 26 | * Global Toolbar Component 27 | */ 28 | class GlobalToolbar extends Component { 29 | 30 | constructor( props ) { 31 | super( ...arguments ); 32 | this.onSelectImages = this.onSelectImages.bind( this ); 33 | } 34 | 35 | onSelectImages( images ) { 36 | this.props.setAttributes( { 37 | images: images.map( ( image ) => helper.pickRelevantMediaFiles( image ) ), 38 | } ); 39 | } 40 | 41 | render() { 42 | 43 | const { 44 | attributes, 45 | isSelected, 46 | setAttributes, 47 | hasAlignmentToolbar, 48 | } = this.props; 49 | 50 | const { 51 | images, 52 | contentAlign, 53 | } = attributes; 54 | 55 | return ( 56 | isSelected && ( 57 | 58 | 59 | { hasAlignmentToolbar && ( 60 | { 63 | setAttributes( { contentAlign: nextAlign } ); 64 | } } 65 | /> 66 | ) } 67 | 68 | { !! images.length && ( 69 | 70 | 71 | img.id ) } 77 | render={ ( { open } ) => ( 78 | 84 | ) } 85 | /> 86 | 87 | { setAttributes( { filter: 'grayscale' } ) }, 95 | }, 96 | { 97 | icon: icons.imageFilterSepia, 98 | title: __( 'Sepia' ), 99 | onClick: () => { setAttributes( { filter: 'sepia' } ) }, 100 | }, 101 | { 102 | icon: icons.imageFilterSaturation, 103 | title: __( 'Saturation' ), 104 | onClick: () => { setAttributes( { filter: 'saturation' } ) }, 105 | }, 106 | { 107 | icon: icons.imageFilterDark, 108 | title: __( 'Dark' ), 109 | onClick: () => { setAttributes( { filter: 'dim' } ) }, 110 | }, 111 | { 112 | icon: icons.imageFilterVintage, 113 | title: __( 'Vintage' ), 114 | onClick: () => { setAttributes( { filter: 'vintage' } ) }, 115 | }, 116 | { 117 | icon: icons.image, 118 | title: __( 'Original' ), 119 | onClick: () => { setAttributes( { filter: 'none' } ) }, 120 | }, 121 | ] } 122 | /> 123 | 124 | ) } 125 | 126 | 127 | ) 128 | ) 129 | } 130 | } 131 | 132 | export default GlobalToolbar; 133 | -------------------------------------------------------------------------------- /src/styles/style/_core-themes.scss: -------------------------------------------------------------------------------- 1 | .is-twentynineteen { 2 | 3 | .entry-content div[class*="wp-block-blockgallery"] { 4 | margin-bottom: 46px; 5 | margin-top: 46px; 6 | 7 | .blockgallery--caption { 8 | font-size: 0.71111em; 9 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; 10 | line-height: 1.6; 11 | margin: 0 0 -20px; 12 | padding: 0.5rem; 13 | text-align: center; 14 | } 15 | } 16 | 17 | .wp-block-blockgallery-stacked figcaption:not([class*="font-size"]) { 18 | font-size: 0.71111em; 19 | } 20 | } 21 | 22 | .is-twentyseventeen { 23 | 24 | .entry-content div[class*="wp-block-blockgallery"] { 25 | margin-top: 1.65em; 26 | margin-bottom: 1.75em; 27 | 28 | .blockgallery--primary-caption { 29 | font-style: italic; 30 | margin-bottom: -10px; 31 | margin-top: 1em; 32 | } 33 | 34 | &:not([class*="masonry"]) { 35 | 36 | .blockgallery:not(.has-caption-color) figcaption { 37 | color: #555d66; 38 | } 39 | } 40 | } 41 | 42 | .wp-block-blockgallery-masonry figcaption { 43 | padding-bottom: 6px; 44 | } 45 | } 46 | 47 | .is-twentysixteen { 48 | 49 | .entry-content div[class*="wp-block-blockgallery"] { 50 | margin-top: 2.3em; 51 | margin-bottom: 2.3em; 52 | 53 | .blockgallery--primary-caption { 54 | margin-bottom: -10px; 55 | margin-top: 0.6em; 56 | } 57 | } 58 | 59 | .wp-block-blockgallery-masonry figcaption { 60 | padding-bottom: 6px; 61 | } 62 | } 63 | 64 | .is-twentyfifteen { 65 | 66 | .entry-content div[class*="wp-block-blockgallery"] { 67 | margin-top: 2.2em; 68 | margin-bottom: 2.2em; 69 | 70 | .blockgallery--primary-caption { 71 | margin-top: 0.5em; 72 | padding-bottom: 0; 73 | margin-bottom: -15px; 74 | } 75 | } 76 | 77 | .wp-block-blockgallery-masonry figcaption { 78 | font-size: 13px !important; 79 | } 80 | } 81 | 82 | .is-twentyfourteen { 83 | 84 | .entry-content div[class*="wp-block-blockgallery"] { 85 | margin-top: 30px; 86 | margin-bottom: 30px; 87 | 88 | .blockgallery--primary-caption { 89 | text-align: left; 90 | margin-bottom: -7px; 91 | } 92 | } 93 | } 94 | 95 | .is-twentythirteen { 96 | 97 | .entry-content div[class*="wp-block-blockgallery"] { 98 | margin-top: 34px; 99 | margin-bottom: 30px; 100 | 101 | .blockgallery--primary-caption { 102 | margin-bottom: -10px; 103 | } 104 | } 105 | 106 | .wp-block-blockgallery-masonry figcaption { 107 | font-size: 13px !important; 108 | } 109 | } 110 | 111 | .is-twentytwelve { 112 | 113 | .entry-content div[class*="wp-block-blockgallery"] { 114 | margin-top: 32px; 115 | margin-bottom: 32px; 116 | 117 | // Override the theme's border radius behavior for our blocks. 118 | .blockgallery:not([class*="border-radius"]) img { 119 | border-radius: inherit; 120 | } 121 | 122 | .blockgallery--primary-caption { 123 | margin-bottom: -10px; 124 | } 125 | } 126 | 127 | .wp-block-blockgallery-masonry figcaption { 128 | padding-bottom: 6px; 129 | } 130 | } 131 | 132 | .is-twentyeleven { 133 | 134 | .entry-content div[class*="wp-block-blockgallery"] { 135 | margin-top: 33px; 136 | margin-bottom: 32px; 137 | 138 | // Override the theme's padding/border effect on our blocks. 139 | .blockgallery img { 140 | border: 0; 141 | padding: 0; 142 | max-width: 100%; 143 | } 144 | 145 | .blockgallery--primary-caption { 146 | margin-bottom: -15px; 147 | 148 | &::before { 149 | color: #666; 150 | content: '\2014'; 151 | font-size: 14px; 152 | font-style: normal; 153 | font-weight: 600; 154 | margin-right: 5px; 155 | position: absolute; 156 | left: 10px; 157 | top: 0; 158 | } 159 | } 160 | } 161 | 162 | .wp-block-blockgallery-stacked figcaption { 163 | text-align: center !important; 164 | padding-left: 1em !important; 165 | padding-right: 1em !important; 166 | 167 | &::before { 168 | display: none; 169 | padding-left: 0; 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/blocks/masonry/components/inspector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal dependencies 3 | */ 4 | import { title } from '../' 5 | import ResponsiveTabsControl from '../../../components/responsive-tabs-control'; 6 | import linkOptions from '../../../utils/link-options'; 7 | import captionOptions from '../../../utils/caption-options'; 8 | import SizeControl from '../../../components/size-control'; 9 | import { LightboxControl } from '../../../components/lightbox'; 10 | import { BackgroundPanel } from '../../../components/background'; 11 | 12 | /** 13 | * WordPress dependencies 14 | */ 15 | const { __, sprintf } = wp.i18n; 16 | const { Component, Fragment } = wp.element; 17 | const { InspectorControls } = wp.editor; 18 | const { PanelBody, RangeControl, ToggleControl, SelectControl } = wp.components; 19 | 20 | /** 21 | * Inspector controls 22 | */ 23 | class Inspector extends Component { 24 | 25 | constructor( props ) { 26 | super( ...arguments ); 27 | this.setSizeControl = this.setSizeControl.bind( this ); 28 | this.setLinkTo = this.setLinkTo.bind( this ); 29 | this.setRadiusTo = this.setRadiusTo.bind( this ); 30 | this.setCaptionStyleTo = this.setCaptionStyleTo.bind( this ); 31 | } 32 | 33 | componentDidUpdate( prevProps ) { 34 | if ( this.props.attributes.gutter <= 0 ) { 35 | this.props.setAttributes( { 36 | radius: 0, 37 | } ); 38 | } 39 | } 40 | 41 | setLinkTo( value ) { 42 | this.props.setAttributes( { linkTo: value } ); 43 | } 44 | 45 | setRadiusTo( value ) { 46 | this.props.setAttributes( { radius: value } ); 47 | } 48 | 49 | setSizeControl( value ) { 50 | this.props.setAttributes( { gridSize: value } ); 51 | } 52 | 53 | setCaptionStyleTo( value ) { 54 | this.props.setAttributes( { captionStyle: value } ); 55 | } 56 | 57 | getCaptionsHelp( checked ) { 58 | return checked ? __( 'Showing captions for each media item.' ) : __( 'Toggle to show media captions.' ); 59 | } 60 | 61 | render() { 62 | 63 | const { 64 | attributes, 65 | setAttributes, 66 | isSelected, 67 | } = this.props; 68 | 69 | const { 70 | align, 71 | captionStyle, 72 | gridSize, 73 | gutter, 74 | images, 75 | lightbox, 76 | linkTo, 77 | radius, 78 | captions, 79 | } = attributes; 80 | 81 | return ( 82 | isSelected && ( 83 | 84 | 85 | 86 | 93 | 94 | { gutter > 0 && } 103 | 104 | setAttributes( { captions: ! captions } ) } 108 | help={ this.getCaptionsHelp } 109 | /> 110 | { captions && 111 | 117 | } 118 | 119 | { ! lightbox && 123 | 129 | } 130 | 133 | 134 | 135 | ) 136 | ) 137 | } 138 | }; 139 | 140 | export default Inspector; 141 | -------------------------------------------------------------------------------- /src/blocks/stacked/components/inspector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal dependencies 3 | */ 4 | import { title } from '../' 5 | import ResponsiveTabsControl from '../../../components/responsive-tabs-control'; 6 | import linkOptions from '../../../utils/link-options'; 7 | import SizeControl from '../../../components/size-control'; 8 | import { LightboxControl } from '../../../components/lightbox'; 9 | import { BackgroundPanel } from '../../../components/background'; 10 | 11 | /** 12 | * WordPress dependencies 13 | */ 14 | const { __, sprintf } = wp.i18n; 15 | const { Component, Fragment } = wp.element; 16 | const { compose } = wp.compose; 17 | const { withSelect } = wp.data; 18 | const { InspectorControls, FontSizePicker, withFontSizes } = wp.editor; 19 | const { PanelBody, RangeControl, ToggleControl, SelectControl } = wp.components; 20 | 21 | /** 22 | * Inspector controls 23 | */ 24 | class Inspector extends Component { 25 | 26 | constructor( props ) { 27 | super( ...arguments ); 28 | this.setLinkTo = this.setLinkTo.bind( this ); 29 | this.setRadiusTo = this.setRadiusTo.bind( this ); 30 | this.setFullwidthTo = this.setFullwidthTo.bind( this ); 31 | this.setShadowTo = this.setShadowTo.bind( this ); 32 | } 33 | 34 | setLinkTo( value ) { 35 | this.props.setAttributes( { linkTo: value } ); 36 | } 37 | 38 | setRadiusTo( value ) { 39 | this.props.setAttributes( { radius: value } ); 40 | } 41 | 42 | setShadowTo( value ) { 43 | this.props.setAttributes( { shadow: value } ); 44 | } 45 | 46 | setFullwidthTo() { 47 | this.props.setAttributes( { fullwidth: ! this.props.attributes.fullwidth, shadow: 'none' } ); 48 | } 49 | 50 | getFullwidthImagesHelp( checked ) { 51 | return checked ? __( 'Fullwidth images are enabled.' ) : __( 'Toggle to fill the available gallery area with completely fullwidth images.' ); 52 | } 53 | 54 | getCaptionsHelp( checked ) { 55 | return checked ? __( 'Showing captions for each media item.' ) : __( 'Toggle to show media captions.' ); 56 | } 57 | 58 | render() { 59 | 60 | const { 61 | attributes, 62 | setAttributes, 63 | isSelected, 64 | setFontSize, 65 | fontSize, 66 | wideControlsEnabled = false, 67 | } = this.props; 68 | 69 | const { 70 | align, 71 | images, 72 | linkTo, 73 | gutter, 74 | lightbox, 75 | fullwidth, 76 | radius, 77 | shadow, 78 | captions, 79 | backgroundPadding, 80 | } = attributes; 81 | 82 | return ( 83 | isSelected && ( 84 | 85 | 86 | 87 | { wideControlsEnabled && 88 | 1 ? __( 'Fullwidth Images' ) : __( 'Fullwidth Image' ) } 90 | checked={ !! fullwidth } 91 | help={ this.getFullwidthImagesHelp } 92 | onChange={ this.setFullwidthTo } 93 | /> 94 | } 95 | { images.length > 1 && 96 | 99 | } 100 | { gutter > 0 && } 108 | { ! fullwidth && } 116 | 117 | setAttributes( { captions: ! captions } ) } 121 | help={ this.getCaptionsHelp } 122 | /> 123 | { captions && 124 | 128 | } 129 | 130 | { ! lightbox && 134 | 140 | } 141 | 144 | 145 | 146 | ) 147 | ) 148 | } 149 | }; 150 | 151 | export default compose( [ 152 | withSelect( ( select ) => ( { 153 | wideControlsEnabled: select( 'core/editor' ).getEditorSettings().alignWide, 154 | } ) ), 155 | withFontSizes( 'fontSize' ), 156 | ] )( Inspector ); 157 | -------------------------------------------------------------------------------- /src/components/size-control.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import classnames from 'classnames'; 5 | import map from 'lodash/map'; 6 | 7 | /** 8 | * WordPress dependencies 9 | */ 10 | const { __ } = wp.i18n; 11 | const { compose } = wp.compose; 12 | const { Component, Fragment } = wp.element; 13 | const { ButtonGroup, Button } = wp.components; 14 | const { withSelect } = wp.data; 15 | 16 | class SizeControl extends Component { 17 | 18 | constructor( props ) { 19 | super( ...arguments ); 20 | this.getSizes = this.getSizes.bind( this ); 21 | } 22 | 23 | componentDidUpdate( prevProps ) { 24 | 25 | const { align, columns, gridSize } = this.props.attributes; 26 | 27 | // Prevent small and medium column grid sizes without wide or full alignments. 28 | if ( align == undefined ) { 29 | if ( columns == 'med' || columns == 'sml' ) { 30 | this.props.setAttributes( { 31 | gridSize: 'xlrg', 32 | } ); 33 | } 34 | } 35 | } 36 | 37 | getSizes() { 38 | const { type, wideControlsEnabled } = this.props; 39 | const { align } = this.props.attributes; 40 | 41 | const defaultSizes = [ 42 | { 43 | shortName: 'None', 44 | size: 'none', 45 | }, 46 | { 47 | shortName: 'S', 48 | size: 'sml', 49 | }, 50 | { 51 | shortName: 'M', 52 | size: 'med', 53 | }, 54 | { 55 | shortName: 'L', 56 | size: 'lrg', 57 | }, 58 | ]; 59 | 60 | const standardSizes = [ 61 | { 62 | shortName: wideControlsEnabled == true && 'wide' == align || 'full' == align ? 'L' : __( 'Large' ), 63 | size: 'lrg', 64 | }, 65 | { 66 | shortName: wideControlsEnabled == true && 'wide' == align || 'full' == align ? 'XL' : __( 'Extra Large' ), 67 | size: 'xlrg', 68 | }, 69 | ]; 70 | 71 | const wideSizes = [ 72 | { 73 | shortName: 'M', 74 | size: 'med', 75 | }, 76 | ]; 77 | 78 | const fullSizes = [ 79 | { 80 | shortName: 'S', 81 | size: 'sml', 82 | }, 83 | ]; 84 | 85 | // If this is a standard size settings, not a complex grid sizer. 86 | if ( 'smlx' == type ) { 87 | 88 | const standardSizes = [ 89 | { 90 | shortName: 'S', 91 | size: 'sml', 92 | }, 93 | { 94 | shortName: 'M', 95 | size: 'med', 96 | }, 97 | { 98 | shortName: 'L', 99 | size: 'lrg', 100 | }, 101 | { 102 | shortName: 'XL', 103 | size: 'xlrg', 104 | }, 105 | ]; 106 | 107 | return standardSizes; 108 | } 109 | 110 | // If this is a standard size settings, not a complex grid sizer. 111 | if ( 'reverse-grid' == type ) { 112 | 113 | const standardSizes = [ 114 | { 115 | shortName: wideControlsEnabled == true && 'wide' == align || 'full' == align ? 'S' : __( 'Small' ), 116 | size: 'sml', 117 | }, 118 | { 119 | shortName: wideControlsEnabled == true && 'wide' == align || 'full' == align ? 'M' : __( 'Medium' ), 120 | size: 'med', 121 | }, 122 | ]; 123 | 124 | const wideSizes = [ 125 | { 126 | shortName: 'L', 127 | size: 'lrg', 128 | }, 129 | ]; 130 | 131 | const fullSizes = [ 132 | { 133 | shortName: 'XL', 134 | size: 'xlrg', 135 | }, 136 | ]; 137 | 138 | if ( 'wide' == align ) { 139 | return standardSizes.concat( wideSizes ); 140 | } else if ( 'full' == align ) { 141 | return standardSizes.concat( wideSizes ).concat( fullSizes ); 142 | } else { 143 | return standardSizes; 144 | } 145 | } 146 | 147 | // If this is a standard size settings, not a complex grid sizer. 148 | if ( 'grid' != type ) { 149 | return defaultSizes; 150 | } 151 | 152 | if ( wideControlsEnabled == true && 'wide' == align ) { 153 | return wideSizes.concat( standardSizes ); 154 | } else if ( wideControlsEnabled == true && 'full' == align ) { 155 | return fullSizes.concat( wideSizes ).concat( standardSizes ); 156 | } else { 157 | return standardSizes; 158 | } 159 | } 160 | 161 | render() { 162 | 163 | const { 164 | onChange, 165 | value, 166 | resetValue = undefined, 167 | label, 168 | className, 169 | reset = true, 170 | } = this.props; 171 | 172 | const classes = classnames( 173 | 'components-blockgallery-inspector__size-control', { 174 | [ className ] : className, 175 | } 176 | ); 177 | 178 | return ( 179 | 180 | { label &&

    { label }

    } 181 |
    182 | 183 | { map( this.getSizes(), ( { size, shortName } ) => ( 184 | 193 | ) ) } 194 | 195 | { reset && 196 | 202 | } 203 |
    204 |
    205 | ); 206 | } 207 | } 208 | 209 | export default compose( [ 210 | withSelect( ( select ) => ( { 211 | wideControlsEnabled: select( 'core/editor' ).getEditorSettings().alignWide, 212 | } ) ), 213 | ] )( SizeControl ); -------------------------------------------------------------------------------- /includes/class-block-gallery-block-assets.php: -------------------------------------------------------------------------------- 1 | _version = BLOCKGALLERY_VERSION; 70 | $this->_slug = 'block-gallery'; 71 | $this->_dir = untrailingslashit( plugin_dir_path( '/', __FILE__ ) ); 72 | $this->_url = untrailingslashit( plugins_url( '/', dirname( __FILE__ ) ) ); 73 | 74 | add_action( 'init', array( $this, 'register_blocks' ), 99 ); 75 | add_action( 'init', array( $this, 'block_assets' ) ); 76 | add_action( 'init', array( $this, 'editor_assets' ) ); 77 | add_action( 'enqueue_block_editor_assets', array( $this, 'localization' ) ); 78 | add_action( 'wp_enqueue_scripts', array( $this, 'frontend_scripts' ) ); 79 | add_action( 'the_post', array( $this, 'frontend_scripts' ) ); 80 | } 81 | 82 | /** 83 | * Add actions to enqueue assets. 84 | * 85 | * @access public 86 | */ 87 | public function register_blocks() { 88 | 89 | // Return early if this function does not exist. 90 | if ( ! function_exists( 'register_block_type' ) ) { 91 | return; 92 | } 93 | 94 | // Shortcut for the slug. 95 | $slug = $this->_slug; 96 | 97 | register_block_type( 98 | 'blockgallery/carousel', 99 | array( 100 | 'editor_script' => $slug . '-editor', 101 | 'editor_style' => $slug . '-editor', 102 | 'style' => $slug . '-frontend', 103 | ) 104 | ); 105 | register_block_type( 106 | 'blockgallery/masonry', 107 | array( 108 | 'editor_script' => $slug . '-editor', 109 | 'editor_style' => $slug . '-editor', 110 | 'style' => $slug . '-frontend', 111 | ) 112 | ); 113 | 114 | register_block_type( 115 | 'blockgallery/stacked', 116 | array( 117 | 'editor_script' => $slug . '-editor', 118 | 'editor_style' => $slug . '-editor', 119 | 'style' => $slug . '-frontend', 120 | ) 121 | ); 122 | } 123 | 124 | /** 125 | * Enqueue block assets for use within Gutenberg. 126 | * 127 | * @access public 128 | */ 129 | public function block_assets() { 130 | 131 | // Styles. 132 | wp_register_style( 133 | $this->_slug . '-frontend', 134 | $this->_url . '/dist/blocks.style.build.css', 135 | array(), 136 | $this->_version 137 | ); 138 | } 139 | 140 | /** 141 | * Enqueue block assets for use within Gutenberg. 142 | * 143 | * @access public 144 | */ 145 | public function editor_assets() { 146 | 147 | // Styles. 148 | wp_register_style( 149 | $this->_slug . '-editor', 150 | $this->_url . '/dist/blocks.editor.build.css', 151 | array(), 152 | $this->_version 153 | ); 154 | 155 | // Scripts. 156 | wp_register_script( 157 | $this->_slug . '-editor', 158 | $this->_url . '/dist/blocks.build.js', 159 | array( 'wp-blocks', 'wp-i18n', 'wp-element', 'wp-plugins', 'wp-components', 'wp-edit-post', 'wp-api' ), 160 | $this->_version, 161 | true 162 | ); 163 | } 164 | 165 | /** 166 | * Enqueue front-end assets for blocks. 167 | * 168 | * @access public 169 | */ 170 | public function frontend_scripts() { 171 | 172 | // Define where the asset is loaded from. 173 | $dir = Block_Gallery()->asset_source( 'js' ); 174 | 175 | // Define where the vendor asset is loaded from. 176 | $vendors_dir = Block_Gallery()->asset_source( 'js', 'vendors' ); 177 | 178 | // Masonry block. 179 | if ( has_block( 'blockgallery/masonry' ) ) { 180 | wp_enqueue_script( 181 | $this->_slug . '-masonry', 182 | $dir . $this->_slug . '-masonry' . BLOCKGALLERY_ASSET_SUFFIX . '.js', 183 | array( 'jquery', 'masonry', 'imagesloaded' ), 184 | $this->_version, 185 | true 186 | ); 187 | } 188 | 189 | // Carousel block. 190 | if ( has_block( 'blockgallery/carousel' ) ) { 191 | wp_enqueue_script( 192 | $this->_slug . '-flickity', 193 | $vendors_dir . 'flickity' . BLOCKGALLERY_ASSET_SUFFIX . '.js', 194 | array( 'jquery' ), 195 | $this->_version, 196 | true 197 | ); 198 | } 199 | } 200 | 201 | /** 202 | * Enqueue Jed-formatted localization data. 203 | * 204 | * @access public 205 | */ 206 | public function localization() { 207 | if ( function_exists( 'wp_set_script_translations' ) ) { 208 | wp_set_script_translations( $this->_slug . '-editor', '@@textdomain' ); 209 | } 210 | } 211 | } 212 | 213 | Block_Gallery_Block_Assets::register(); 214 | -------------------------------------------------------------------------------- /src/blocks/masonry/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import classnames from 'classnames'; 5 | import filter from 'lodash/filter'; 6 | 7 | /** 8 | * Internal dependencies 9 | */ 10 | import './styles/style.scss'; 11 | import './styles/editor.scss'; 12 | import icons from './../../utils/icons'; 13 | import Edit from './components/edit'; 14 | import { BackgroundStyles } from '../../components/background/'; 15 | import { GlobalAttributes, GlobalTransforms, GlobalClasses, GlobalStyles } from '../../components/global/'; 16 | 17 | /** 18 | * WordPress dependencies 19 | */ 20 | const { __ } = wp.i18n; 21 | const { createBlock } = wp.blocks; 22 | const { RichText } = wp.editor; 23 | 24 | /** 25 | * Block constants 26 | */ 27 | const name = 'masonry'; 28 | 29 | const title = __( 'Masonry' ); 30 | 31 | const icon = icons.masonry; 32 | 33 | const keywords = [ 34 | __( 'gallery' ), 35 | __( 'images' ), 36 | __( 'photos' ), 37 | ]; 38 | 39 | const blockAttributes = { 40 | ...GlobalAttributes, 41 | 42 | // Block specific attributes. 43 | gridSize: { 44 | type: 'string', 45 | default: 'xlrg', 46 | }, 47 | }; 48 | 49 | const settings = { 50 | 51 | title: title, 52 | 53 | description: __( 'Display multiple images in an organized masonry gallery.' ), 54 | 55 | keywords: keywords, 56 | 57 | attributes: blockAttributes, 58 | 59 | supports: { 60 | align: [ 'wide', 'full' ], 61 | }, 62 | 63 | transforms: { 64 | from: [ 65 | { 66 | type: 'block', 67 | blocks: [ 'blockgallery/stacked' ], 68 | transform: ( attributes ) => ( 69 | createBlock( `blockgallery/${ name }`, { 70 | ...GlobalTransforms( attributes ), 71 | } ) 72 | ), 73 | }, 74 | { 75 | type: 'block', 76 | blocks: [ 'blockgallery/carousel' ], 77 | transform: ( attributes ) => ( 78 | createBlock( `blockgallery/${ name }`, { 79 | ...GlobalTransforms( attributes ), 80 | } ) 81 | ), 82 | }, 83 | { 84 | type: 'block', 85 | blocks: [ 'blockgallery/thumbnails' ], 86 | transform: ( attributes ) => ( 87 | createBlock( `blockgallery/${ name }`, { 88 | ...GlobalTransforms( attributes ), 89 | } ) 90 | ), 91 | }, 92 | { 93 | type: 'block', 94 | blocks: [ 'blockgallery/offset' ], 95 | transform: ( attributes ) => ( 96 | createBlock( `blockgallery/${ name }`, { 97 | ...GlobalTransforms( attributes ), 98 | } ) 99 | ), 100 | }, 101 | { 102 | type: 'block', 103 | blocks: [ 'blockgallery/auto-height' ], 104 | transform: ( attributes ) => ( 105 | createBlock( `blockgallery/${ name }`, { 106 | ...GlobalTransforms( attributes ), 107 | } ) 108 | ), 109 | }, 110 | { 111 | type: 'block', 112 | blocks: [ 'core/gallery' ], 113 | transform: ( attributes ) => ( 114 | createBlock( `blockgallery/${ name }`, { 115 | ...GlobalTransforms( attributes ), 116 | } ) 117 | ), 118 | }, 119 | { 120 | type: 'block', 121 | isMultiBlock: true, 122 | blocks: [ 'core/image' ], 123 | transform: ( attributes ) => { 124 | const validImages = filter( attributes, ( { id, url } ) => id && url ); 125 | if ( validImages.length > 0 ) { 126 | return createBlock( `blockgallery/${ name }`, { 127 | images: validImages.map( ( { id, url, alt, caption } ) => ( { id, url, alt, caption } ) ), 128 | ids: validImages.map( ( { id } ) => id ), 129 | } ); 130 | } 131 | return createBlock( `blockgallery/${ name }` ); 132 | }, 133 | }, 134 | { 135 | type: 'prefix', 136 | prefix: ':masonry', 137 | transform: function( content ) { 138 | return createBlock( `blockgallery/${ name }`, { 139 | content, 140 | } ); 141 | }, 142 | }, 143 | ], 144 | to: [ 145 | { 146 | type: 'block', 147 | blocks: [ 'core/gallery' ], 148 | transform: ( attributes ) => ( 149 | createBlock( 'core/gallery', { 150 | ...GlobalTransforms( attributes ), 151 | } ) 152 | ), 153 | }, 154 | ], 155 | }, 156 | 157 | edit: Edit, 158 | 159 | save( { attributes, className } ) { 160 | 161 | const { 162 | gridSize, 163 | gutter, 164 | gutterMobile, 165 | images, 166 | linkTo, 167 | captions, 168 | } = attributes; 169 | 170 | const wrapperClasses = classnames( 171 | ...GlobalClasses( attributes ), { 172 | [ `has-gutter` ] : gutter > 0, 173 | } 174 | ); 175 | 176 | const wrapperStyles = { 177 | ...BackgroundStyles( attributes ), 178 | }; 179 | 180 | const masonryClasses = classnames( 181 | `has-grid-${ gridSize }`, { 182 | [ `has-gutter-${ gutter }` ] : gutter > 0, 183 | [ `has-gutter-mobile-${ gutterMobile }` ] : gutterMobile > 0, 184 | } 185 | ); 186 | 187 | const masonryStyles = { 188 | ...GlobalStyles( attributes ), 189 | }; 190 | 191 | return ( 192 |
    193 |
    197 | 227 |
    228 |
    229 | ); 230 | }, 231 | }; 232 | 233 | export { name, title, icon, settings }; 234 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "babel-eslint", 4 | "extends": [ 5 | "wordpress", 6 | "plugin:react/recommended", 7 | "plugin:jsx-a11y/recommended", 8 | "plugin:jest/recommended" 9 | ], 10 | "env": { 11 | "browser": false, 12 | "es6": true, 13 | "node": true, 14 | "mocha": true, 15 | "jest/globals": true 16 | }, 17 | "parserOptions": { 18 | "sourceType": "module", 19 | "ecmaFeatures": { 20 | "jsx": true 21 | } 22 | }, 23 | "globals": { 24 | "wp": true, 25 | "wpApiSettings": true, 26 | "window": true, 27 | "document": true 28 | }, 29 | "plugins": ["react", "jsx-a11y", "jest"], 30 | "settings": { 31 | "react": { 32 | "pragma": "wp" 33 | } 34 | }, 35 | "rules": { 36 | "array-bracket-spacing": ["error", "always"], 37 | "brace-style": ["error", "1tbs"], 38 | "camelcase": ["error", { "properties": "never" }], 39 | "comma-dangle": ["error", "always-multiline"], 40 | "comma-spacing": "error", 41 | "comma-style": "error", 42 | "computed-property-spacing": ["error", "always"], 43 | "constructor-super": "error", 44 | "dot-notation": "error", 45 | "eol-last": "error", 46 | "eqeqeq": "error", 47 | "func-call-spacing": "error", 48 | "indent": ["error", "tab", { "SwitchCase": 1 }], 49 | "jsx-a11y/label-has-for": [ 50 | "error", 51 | { 52 | "required": "id" 53 | } 54 | ], 55 | "jsx-a11y/media-has-caption": "off", 56 | "jsx-a11y/no-noninteractive-tabindex": "off", 57 | "jsx-a11y/role-has-required-aria-props": "off", 58 | "jsx-quotes": "error", 59 | "key-spacing": "error", 60 | "keyword-spacing": "error", 61 | "lines-around-comment": "off", 62 | "no-alert": "error", 63 | "no-bitwise": "error", 64 | "no-caller": "error", 65 | "no-console": "error", 66 | "no-const-assign": "error", 67 | "no-debugger": "error", 68 | "no-dupe-args": "error", 69 | "no-dupe-class-members": "error", 70 | "no-dupe-keys": "error", 71 | "no-duplicate-case": "error", 72 | "no-duplicate-imports": "error", 73 | "no-else-return": "error", 74 | "no-eval": "error", 75 | "no-extra-semi": "error", 76 | "no-fallthrough": "error", 77 | "no-lonely-if": "error", 78 | "no-mixed-operators": "error", 79 | "no-mixed-spaces-and-tabs": "error", 80 | "no-multiple-empty-lines": ["error", { "max": 1 }], 81 | "no-multi-spaces": "error", 82 | "no-multi-str": "off", 83 | "no-negated-in-lhs": "error", 84 | "no-nested-ternary": "error", 85 | "no-redeclare": "error", 86 | "no-restricted-syntax": [ 87 | "error", 88 | { 89 | "selector": 90 | "ImportDeclaration[source.value=/^@wordpress\\u002F.+\\u002F/]", 91 | "message": "Path access on WordPress dependencies is not allowed." 92 | }, 93 | { 94 | "selector": "ImportDeclaration[source.value=/^blocks$/]", 95 | "message": "Use @wordpress/blocks as import path instead." 96 | }, 97 | { 98 | "selector": "ImportDeclaration[source.value=/^components$/]", 99 | "message": "Use @wordpress/components as import path instead." 100 | }, 101 | { 102 | "selector": "ImportDeclaration[source.value=/^date$/]", 103 | "message": "Use @wordpress/date as import path instead." 104 | }, 105 | { 106 | "selector": "ImportDeclaration[source.value=/^editor$/]", 107 | "message": "Use @wordpress/editor as import path instead." 108 | }, 109 | { 110 | "selector": "ImportDeclaration[source.value=/^element$/]", 111 | "message": "Use @wordpress/element as import path instead." 112 | }, 113 | { 114 | "selector": "ImportDeclaration[source.value=/^i18n$/]", 115 | "message": "Use @wordpress/i18n as import path instead." 116 | }, 117 | { 118 | "selector": "ImportDeclaration[source.value=/^data$/]", 119 | "message": "Use @wordpress/data as import path instead." 120 | }, 121 | { 122 | "selector": "ImportDeclaration[source.value=/^utils$/]", 123 | "message": "Use @wordpress/utils as import path instead." 124 | }, 125 | { 126 | "selector": 127 | "CallExpression[callee.name=/^__|_n|_x$/]:not([arguments.0.type=/^Literal|BinaryExpression$/])", 128 | "message": "Translate function arguments must be string literals." 129 | }, 130 | { 131 | "selector": 132 | "CallExpression[callee.name=/^_n|_x$/]:not([arguments.1.type=/^Literal|BinaryExpression$/])", 133 | "message": "Translate function arguments must be string literals." 134 | }, 135 | { 136 | "selector": 137 | "CallExpression[callee.name=_nx]:not([arguments.2.type=/^Literal|BinaryExpression$/])", 138 | "message": "Translate function arguments must be string literals." 139 | } 140 | ], 141 | "no-shadow": "error", 142 | "no-undef": "error", 143 | "no-undef-init": "error", 144 | "no-unreachable": "error", 145 | "no-unsafe-negation": "error", 146 | "no-unused-expressions": "error", 147 | "no-unused-vars": "error", 148 | "no-useless-computed-key": "error", 149 | "no-useless-constructor": "error", 150 | "no-useless-return": "error", 151 | "no-var": "error", 152 | "no-whitespace-before-property": "error", 153 | "object-curly-spacing": ["error", "always"], 154 | "padded-blocks": ["error", "never"], 155 | "prefer-const": "error", 156 | "quote-props": ["error", "as-needed"], 157 | "react/display-name": "off", 158 | "react/jsx-curly-spacing": [ 159 | "error", 160 | { 161 | "when": "always", 162 | "children": true 163 | } 164 | ], 165 | "react/jsx-equals-spacing": "error", 166 | "react/jsx-indent": ["error", "tab"], 167 | "react/jsx-indent-props": ["error", "tab"], 168 | "react/jsx-key": "error", 169 | "react/jsx-tag-spacing": "error", 170 | "react/no-children-prop": "off", 171 | "react/no-find-dom-node": "warn", 172 | "react/prop-types": "off", 173 | "semi": "error", 174 | "semi-spacing": "error", 175 | "space-before-blocks": ["error", "always"], 176 | "space-before-function-paren": ["error", "never"], 177 | "space-in-parens": ["error", "always"], 178 | "space-infix-ops": ["error", { "int32Hint": false }], 179 | "space-unary-ops": [ 180 | "error", 181 | { 182 | "overrides": { 183 | "!": true 184 | } 185 | } 186 | ], 187 | "template-curly-spacing": ["error", "always"], 188 | "valid-jsdoc": ["error", { "requireReturn": false }], 189 | "valid-typeof": "error", 190 | "yoda": "off" 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/blocks/stacked/components/edit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import classnames from 'classnames'; 5 | import filter from 'lodash/filter'; 6 | 7 | /** 8 | * Internal dependencies 9 | */ 10 | import GalleryImage from '../../../components/gallery-image'; 11 | import GalleryPlaceholder from '../../../components/gallery-placeholder'; 12 | import GalleryDropZone from '../../../components/gallery-dropzone'; 13 | import GalleryUpload from '../../../components/gallery-upload'; 14 | import Inspector from './inspector'; 15 | import { BackgroundStyles } from '../../../components/background/'; 16 | import { title, icon } from '../' 17 | import { GlobalClasses, GlobalToolbar } from '../../../components/global/'; 18 | 19 | /** 20 | * WordPress dependencies 21 | */ 22 | const { __, sprintf } = wp.i18n; 23 | const { Component, Fragment } = wp.element; 24 | const { compose } = wp.compose; 25 | const { withSelect } = wp.data; 26 | const { withNotices } = wp.components; 27 | const { withColors, withFontSizes } = wp.editor; 28 | 29 | /** 30 | * Block edit function 31 | */ 32 | class Edit extends Component { 33 | constructor() { 34 | super( ...arguments ); 35 | 36 | this.onSelectImage = this.onSelectImage.bind( this ); 37 | this.onRemoveImage = this.onRemoveImage.bind( this ); 38 | this.setImageAttributes = this.setImageAttributes.bind( this ); 39 | 40 | this.state = { 41 | selectedImage: null, 42 | }; 43 | } 44 | 45 | componentDidMount() { 46 | // This block does not support caption style. 47 | this.props.setAttributes( { 48 | captionStyle: undefined, 49 | } ); 50 | } 51 | 52 | componentDidUpdate( prevProps ) { 53 | // Deselect images when deselecting the block 54 | if ( ! this.props.isSelected && prevProps.isSelected ) { 55 | this.setState( { 56 | selectedImage: null, 57 | captionSelected: false, 58 | } ); 59 | } 60 | } 61 | 62 | onSelectImage( index ) { 63 | return () => { 64 | if ( this.state.selectedImage !== index ) { 65 | this.setState( { 66 | selectedImage: index, 67 | } ); 68 | } 69 | }; 70 | } 71 | 72 | onRemoveImage( index ) { 73 | return () => { 74 | const images = filter( this.props.attributes.images, ( img, i ) => index !== i ); 75 | this.setState( { selectedImage: null } ); 76 | this.props.setAttributes( { 77 | images, 78 | } ); 79 | }; 80 | } 81 | 82 | setImageAttributes( index, attributes ) { 83 | const { attributes: { images }, setAttributes } = this.props; 84 | if ( ! images[ index ] ) { 85 | return; 86 | } 87 | setAttributes( { 88 | images: [ 89 | ...images.slice( 0, index ), 90 | { 91 | ...images[ index ], 92 | ...attributes, 93 | }, 94 | ...images.slice( index + 1 ), 95 | ], 96 | } ); 97 | } 98 | 99 | render() { 100 | const { 101 | attributes, 102 | backgroundColor, 103 | captionColor, 104 | className, 105 | isSelected, 106 | noticeOperations, 107 | noticeUI, 108 | setAttributes, 109 | fontSize, 110 | } = this.props; 111 | 112 | const { 113 | align, 114 | fullwidth, 115 | gutter, 116 | gutterMobile, 117 | images, 118 | linkTo, 119 | shadow, 120 | captions, 121 | } = attributes; 122 | 123 | const dropZone = ( 124 | 128 | ); 129 | 130 | const wrapperClasses = classnames( 131 | ...GlobalClasses( attributes ), { 132 | 'has-fullwidth-images': fullwidth, 133 | [ `align${ align }` ] : align, 134 | [ `has-margin` ] : gutter > 0, 135 | [ `has-margin-bottom-${ gutter }` ] : gutter > 0, 136 | [ `has-margin-bottom-mobile-${ gutterMobile }` ] : gutterMobile > 0, 137 | } 138 | ); 139 | 140 | const wrapperStyles = { 141 | ...BackgroundStyles( attributes ), 142 | backgroundColor: backgroundColor.color, 143 | color: captionColor.color, 144 | }; 145 | 146 | if ( images.length === 0 ) { 147 | return ( 148 | 153 | ); 154 | } 155 | 156 | return ( 157 | 158 | 161 | 164 | { noticeUI } 165 |
    166 |
      167 | { dropZone } 168 | { images.map( ( img, index ) => { 169 | // translators: %1$d is the order number of the image, %2$d is the total number of images. 170 | const ariaLabel = __( sprintf( 'image %1$d of %2$d in gallery', ( index + 1 ), images.length ) ); 171 | 172 | return ( 173 |
    • 174 | this.setImageAttributes( index, attrs ) } 186 | caption={ img.caption } 187 | aria-label={ ariaLabel } 188 | captions={ captions } 189 | supportsCaption={ true } 190 | fontSize={ fontSize.size } 191 | /> 192 |
    • 193 | ); 194 | } ) } 195 | { isSelected && ( 196 | 201 | ) } 202 |
    203 |
    204 |
    205 | ); 206 | } 207 | } 208 | 209 | export default compose( [ 210 | withSelect( ( select ) => ( { 211 | editorSidebarOpened: select( 'core/edit-post' ).isEditorSidebarOpened(), 212 | pluginSidebarOpened: select( 'core/edit-post' ).isPluginSidebarOpened(), 213 | publishSidebarOpened: select( 'core/edit-post' ).isPublishSidebarOpened(), 214 | wideControlsEnabled: select( 'core/editor' ).getEditorSettings().alignWide, 215 | } ) ), 216 | withColors( { backgroundColor : 'background-color', captionColor : 'color' } ), 217 | withFontSizes( 'fontSize' ), 218 | withNotices, 219 | ] )( Edit ); 220 | -------------------------------------------------------------------------------- /src/components/gallery-image.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External Dependencies 3 | */ 4 | import classnames from 'classnames'; 5 | 6 | /** 7 | * WordPress dependencies 8 | */ 9 | const { __ } = wp.i18n; 10 | const { Component, Fragment } = wp.element; 11 | const { compose } = wp.compose; 12 | const { IconButton, Spinner } = wp.components; 13 | const { RichText } = wp.editor; 14 | const { withSelect } = wp.data; 15 | const { BACKSPACE, DELETE } = wp.keycodes; 16 | const { isBlobURL } = wp.blob; 17 | 18 | /** 19 | * Gallery Image Component 20 | */ 21 | class GalleryImage extends Component { 22 | constructor() { 23 | super( ...arguments ); 24 | 25 | this.onImageClick = this.onImageClick.bind( this ); 26 | this.onSelectCaption = this.onSelectCaption.bind( this ); 27 | this.onKeyDown = this.onKeyDown.bind( this ); 28 | this.bindContainer = this.bindContainer.bind( this ); 29 | 30 | this.state = { 31 | captionSelected: false, 32 | captionFocused: false, 33 | }; 34 | } 35 | 36 | bindContainer( ref ) { 37 | this.container = ref; 38 | } 39 | 40 | onSelectCaption() { 41 | if ( ! this.state.captionSelected ) { 42 | this.setState( { 43 | captionSelected: true, 44 | } ); 45 | } 46 | 47 | if ( ! this.props.isSelected ) { 48 | this.props.onSelect(); 49 | } 50 | } 51 | 52 | onImageClick() { 53 | if ( ! this.props.isSelected ) { 54 | this.props.onSelect(); 55 | } 56 | 57 | if ( this.state.captionSelected ) { 58 | this.setState( { 59 | captionSelected: false, 60 | captionFocused: false, 61 | } ); 62 | } 63 | } 64 | 65 | onKeyDown( event ) { 66 | if ( 67 | this.container === document.activeElement && 68 | this.props.isSelected && [ BACKSPACE, DELETE ].indexOf( event.keyCode ) !== -1 69 | ) { 70 | event.stopPropagation(); 71 | event.preventDefault(); 72 | this.props.onRemove(); 73 | } 74 | } 75 | 76 | componentDidUpdate( prevProps ) { 77 | const { isSelected, image, url } = this.props; 78 | if ( image && ! url ) { 79 | this.props.setAttributes( { 80 | url: image.source_url, 81 | alt: image.alt_text, 82 | } ); 83 | } 84 | 85 | // unselect the caption so when the user selects other image and comeback 86 | // the caption is not immediately selected 87 | if ( this.state.captionSelected && ! isSelected && prevProps.isSelected ) { 88 | this.setState( { 89 | captionSelected: false, 90 | captionFocused: false, 91 | } ); 92 | } 93 | } 94 | 95 | render() { 96 | 97 | const { 98 | alt, 99 | caption, 100 | fontSize, 101 | gutter, 102 | gutterMobile, 103 | id, 104 | isSelected, 105 | link, 106 | linkTo, 107 | marginBottom, 108 | marginLeft, 109 | marginRight, 110 | marginTop, 111 | onRemove, 112 | setAttributes, 113 | shadow, 114 | supportsCaption, 115 | url, 116 | captions, 117 | 'aria-label': ariaLabel, 118 | } = this.props; 119 | 120 | let href; 121 | 122 | switch ( linkTo ) { 123 | case 'media': 124 | href = url; 125 | break; 126 | case 'attachment': 127 | href = link; 128 | break; 129 | } 130 | 131 | const imgClasses = classnames( { 132 | [ `has-shadow-${ shadow }` ] : shadow != 'none' || shadow != undefined, 133 | } ); 134 | 135 | // Disable reason: Image itself is not meant to be 136 | // interactive, but should direct image selection and unfocus caption fields 137 | // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events 138 | const img = ( 139 | // Disable reason: Image itself is not meant to be interactive, but should 140 | // direct image selection and unfocus caption fields. 141 | /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ 142 | 143 | { 153 | { isBlobURL( url ) && } 154 | 155 | /* eslint-enable jsx-a11y/no-noninteractive-element-interactions */ 156 | ); 157 | 158 | const className = classnames( { 159 | 'is-selected': isSelected, 160 | 'is-transient': url && 0 === url.indexOf( 'blob:' ), 161 | [ `has-margin-top-${ gutter }` ] : marginTop && gutter > 0, 162 | [ `has-margin-top-mobile-${ gutterMobile }` ] : marginTop && gutterMobile > 0, 163 | [ `has-margin-right-${ gutter }` ] : marginRight && gutter > 0, 164 | [ `has-margin-right-mobile-${ gutterMobile }` ] : marginRight && gutterMobile > 0, 165 | [ `has-margin-bottom-${ gutter }` ] : marginBottom && gutter > 0, 166 | [ `has-margin-bottom-mobile-${ gutterMobile }` ] : marginBottom && gutterMobile > 0, 167 | [ `has-margin-left-${ gutter }` ] : marginLeft && gutter > 0, 168 | [ `has-margin-left-mobile-${ gutterMobile }` ] : marginLeft && gutterMobile > 0, 169 | } ); 170 | 171 | const captionStyles = { 172 | fontSize: fontSize ? fontSize + 'px' : undefined, 173 | }; 174 | 175 | // Disable reason: Each block can be selected by clicking on it and we should keep the same saved markup 176 | /* eslint-disable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ 177 | return ( 178 |
    179 | { isSelected && 180 |
    181 | 187 |
    188 | } 189 | { href ? { img } : img } 190 | { ( supportsCaption === true ) && ( ! RichText.isEmpty( caption ) || isSelected ) && captions ? ( 191 | setAttributes( { caption: newCaption } ) } 199 | unstableOnFocus={ this.onSelectCaption } 200 | inlineToolbar 201 | /> 202 | ) : null } 203 |
    204 | ); 205 | /* eslint-enable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ 206 | } 207 | } 208 | 209 | export default compose( [ 210 | withSelect( ( select, ownProps ) => { 211 | const { getMedia } = select( 'core' ); 212 | const { id } = ownProps; 213 | 214 | return { 215 | image: id ? getMedia( id ) : null, 216 | }; 217 | } ), 218 | ] )( GalleryImage ); 219 | -------------------------------------------------------------------------------- /src/blocks/stacked/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import classnames from 'classnames'; 5 | import filter from 'lodash/filter'; 6 | 7 | /** 8 | * Internal dependencies 9 | */ 10 | import './styles/style.scss'; 11 | import './styles/editor.scss'; 12 | import Edit from './components/edit'; 13 | import icons from './../../utils/icons'; 14 | import { BackgroundStyles } from '../../components/background/'; 15 | import { GlobalAttributes, GlobalTransforms, GlobalClasses, GlobalStyles } from '../../components/global/'; 16 | 17 | /** 18 | * WordPress dependencies 19 | */ 20 | const { __ } = wp.i18n; 21 | const { createBlock } = wp.blocks; 22 | const { RichText, getFontSizeClass } = wp.editor; 23 | 24 | /** 25 | * Block constants 26 | */ 27 | const name = 'stacked'; 28 | 29 | const title = __( 'Stacked' ); 30 | 31 | const icon = icons.stacked; 32 | 33 | const keywords = [ 34 | __( 'gallery' ), 35 | __( 'images' ), 36 | __( 'photos' ), 37 | ]; 38 | 39 | const blockAttributes = { 40 | ...GlobalAttributes, 41 | 42 | // Block specific attributes and overrides. 43 | align: { 44 | type: 'string', 45 | default: 'full', 46 | }, 47 | captionStyle: { 48 | type: 'string', 49 | }, 50 | fullwidth: { 51 | type: 'boolean', 52 | default: true, 53 | }, 54 | gutter: { 55 | type: 'number', 56 | default: 0, 57 | }, 58 | gutterMobile: { 59 | type: 'number', 60 | default: 0, 61 | }, 62 | }; 63 | 64 | const settings = { 65 | 66 | title: title, 67 | 68 | description: __( 'Display multiple images in an single column stacked gallery.' ), 69 | 70 | keywords: keywords, 71 | 72 | attributes: blockAttributes, 73 | 74 | supports: { 75 | align: [ 'wide', 'full' ], 76 | }, 77 | 78 | transforms: { 79 | from: [ 80 | { 81 | type: 'block', 82 | blocks: [ 'blockgallery/masonry' ], 83 | transform: ( attributes ) => ( 84 | createBlock( `blockgallery/${ name }`, { 85 | ...GlobalTransforms( attributes ), 86 | } ) 87 | ), 88 | }, 89 | { 90 | type: 'block', 91 | blocks: [ 'blockgallery/carousel' ], 92 | transform: ( attributes ) => ( 93 | createBlock( `blockgallery/${ name }`, { 94 | ...GlobalTransforms( attributes ), 95 | } ) 96 | ), 97 | }, 98 | { 99 | type: 'block', 100 | blocks: [ 'blockgallery/thumbnails' ], 101 | transform: ( attributes ) => ( 102 | createBlock( `blockgallery/${ name }`, { 103 | ...GlobalTransforms( attributes ), 104 | } ) 105 | ), 106 | }, 107 | { 108 | type: 'block', 109 | blocks: [ 'blockgallery/offset' ], 110 | transform: ( attributes ) => ( 111 | createBlock( `blockgallery/${ name }`, { 112 | ...GlobalTransforms( attributes ), 113 | } ) 114 | ), 115 | }, 116 | { 117 | type: 'block', 118 | blocks: [ 'blockgallery/auto-height' ], 119 | transform: ( attributes ) => ( 120 | createBlock( `blockgallery/${ name }`, { 121 | ...GlobalTransforms( attributes ), 122 | } ) 123 | ), 124 | }, 125 | { 126 | type: 'block', 127 | blocks: [ 'core/gallery' ], 128 | transform: ( attributes ) => ( 129 | createBlock( `blockgallery/${ name }`, { 130 | ...GlobalTransforms( attributes ), 131 | } ) 132 | ), 133 | }, 134 | { 135 | type: 'block', 136 | isMultiBlock: true, 137 | blocks: [ 'core/image' ], 138 | transform: ( attributes ) => { 139 | const validImages = filter( attributes, ( { id, url } ) => id && url ); 140 | if ( validImages.length > 0 ) { 141 | return createBlock( `blockgallery/${ name }`, { 142 | images: validImages.map( ( { id, url, alt, caption } ) => ( { id, url, alt, caption } ) ), 143 | ids: validImages.map( ( { id } ) => id ), 144 | } ); 145 | } 146 | return createBlock( `blockgallery/${ name }` ); 147 | }, 148 | }, 149 | { 150 | type: 'prefix', 151 | prefix: ':stacked', 152 | transform: function( content ) { 153 | return createBlock( `blockgallery/${ name }`, { 154 | content, 155 | } ); 156 | }, 157 | }, 158 | ], 159 | to: [ 160 | { 161 | type: 'block', 162 | blocks: [ 'core/gallery' ], 163 | transform: ( attributes ) => ( 164 | createBlock( 'core/gallery', { 165 | ...GlobalTransforms( attributes ), 166 | } ) 167 | ), 168 | }, 169 | ], 170 | }, 171 | 172 | edit: Edit, 173 | 174 | save( { attributes, className } ) { 175 | 176 | const { 177 | captionColor, 178 | captions, 179 | customCaptionColor, 180 | customFontSize, 181 | fontSize, 182 | fullwidth, 183 | gutter, 184 | gutterMobile, 185 | images, 186 | linkTo, 187 | shadow, 188 | } = attributes; 189 | 190 | const wrapperClasses = classnames( 191 | ...GlobalClasses( attributes ), { 192 | 'has-fullwidth-images': fullwidth, 193 | [ `has-margin` ] : gutter > 0, 194 | } 195 | ); 196 | 197 | const wrapperStyles = { 198 | ...GlobalStyles( attributes ), 199 | ...BackgroundStyles( attributes ), 200 | }; 201 | 202 | const fontSizeClass = getFontSizeClass( fontSize ); 203 | 204 | const figureClasses = classnames( 205 | 'blockgallery--figure', { 206 | [ `has-margin-bottom-${ gutter }` ] : gutter > 0, 207 | [ `has-margin-bottom-mobile-${ gutterMobile }` ] : gutterMobile > 0, 208 | [ fontSizeClass ]: fontSizeClass, 209 | } ); 210 | 211 | const captionClasses = classnames( 212 | 'blockgallery--caption', { 213 | [ fontSizeClass ]: fontSizeClass, 214 | } ); 215 | 216 | const captionStyles = { 217 | fontSize: fontSizeClass ? undefined : customFontSize, 218 | }; 219 | 220 | return ( 221 |
    222 |
      223 | { images.map( ( image ) => { 224 | let href; 225 | 226 | switch ( linkTo ) { 227 | case 'media': 228 | href = image.url; 229 | break; 230 | case 'attachment': 231 | href = image.link; 232 | break; 233 | } 234 | 235 | const imgClasses = classnames( 236 | image.id ? [ `wp-image-${ image.id }` ] : null, { 237 | [ `has-shadow-${ shadow }` ] : shadow != 'none' || shadow != undefined , 238 | } ); 239 | 240 | const img = {; 241 | 242 | return ( 243 |
    • 244 |
      245 | { href ? { img } : img } 246 | { captions && image.caption && image.caption.length > 0 && ( 247 | 248 | ) } 249 |
      250 |
    • 251 | ); 252 | } ) } 253 |
    254 |
    255 | ); 256 | }, 257 | }; 258 | 259 | export { name, title, icon, settings }; 260 | -------------------------------------------------------------------------------- /src/blocks/masonry/components/edit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import classnames from 'classnames'; 5 | import filter from 'lodash/filter'; 6 | import Masonry from 'react-masonry-component'; 7 | 8 | /** 9 | * Internal dependencies 10 | */ 11 | import GalleryImage from '../../../components/gallery-image'; 12 | import GalleryPlaceholder from '../../../components/gallery-placeholder'; 13 | import GalleryDropZone from '../../../components/gallery-dropzone'; 14 | import GalleryUpload from '../../../components/gallery-upload'; 15 | import Inspector from './inspector'; 16 | import { BackgroundStyles } from '../../../components/background/'; 17 | import { title, icon } from '../' 18 | import { GlobalClasses, GlobalToolbar, GlobalStyles } from '../../../components/global/'; 19 | 20 | /** 21 | * WordPress dependencies 22 | */ 23 | const { __, sprintf } = wp.i18n; 24 | const { Component, Fragment } = wp.element; 25 | const { compose } = wp.compose; 26 | const { withSelect } = wp.data; 27 | const { withNotices } = wp.components; 28 | const { withColors } = wp.editor; 29 | 30 | /** 31 | * Block consts 32 | */ 33 | const masonryOptions = { 34 | transitionDuration: 0, 35 | percentPosition: true, 36 | }; 37 | 38 | /** 39 | * Block edit function 40 | */ 41 | class Edit extends Component { 42 | constructor() { 43 | super( ...arguments ); 44 | 45 | this.onSelectImage = this.onSelectImage.bind( this ); 46 | this.onRemoveImage = this.onRemoveImage.bind( this ); 47 | this.setImageAttributes = this.setImageAttributes.bind( this ); 48 | 49 | this.state = { 50 | selectedImage: null, 51 | }; 52 | } 53 | 54 | componentDidMount() { 55 | 56 | if ( this.props.wideControlsEnabled == true && ! this.props.attributes.align && this.props.attributes.gridSize == 'xlrg' ) { 57 | this.props.setAttributes( { 58 | align: 'wide', 59 | gridSize: 'lrg' 60 | } ); 61 | } 62 | } 63 | 64 | componentDidUpdate( prevProps ) { 65 | // Deselect images when deselecting the block. 66 | if ( ! this.props.isSelected && prevProps.isSelected ) { 67 | this.setState( { 68 | selectedImage: null, 69 | captionSelected: false, 70 | } ); 71 | } 72 | } 73 | 74 | onSelectImage( index ) { 75 | return () => { 76 | if ( this.state.selectedImage !== index ) { 77 | this.setState( { 78 | selectedImage: index, 79 | } ); 80 | } 81 | }; 82 | } 83 | 84 | onRemoveImage( index ) { 85 | return () => { 86 | const images = filter( this.props.attributes.images, ( img, i ) => index !== i ); 87 | const { gridSize } = this.props.attributes; 88 | this.setState( { selectedImage: null } ); 89 | this.props.setAttributes( { 90 | images, 91 | } ); 92 | }; 93 | } 94 | 95 | setImageAttributes( index, attributes ) { 96 | const { attributes: { images }, setAttributes } = this.props; 97 | if ( ! images[ index ] ) { 98 | return; 99 | } 100 | setAttributes( { 101 | images: [ 102 | ...images.slice( 0, index ), 103 | { 104 | ...images[ index ], 105 | ...attributes, 106 | }, 107 | ...images.slice( index + 1 ), 108 | ], 109 | } ); 110 | } 111 | 112 | render() { 113 | const { 114 | attributes, 115 | backgroundColor, 116 | className, 117 | editorSidebarOpened, 118 | isSelected, 119 | noticeOperations, 120 | noticeUI, 121 | pluginSidebarOpened, 122 | publishSidebarOpened, 123 | setAttributes, 124 | captionColor, 125 | } = this.props; 126 | 127 | const { 128 | align, 129 | customBackgroundColor, 130 | gridSize, 131 | gutter, 132 | gutterMobile, 133 | images, 134 | linkTo, 135 | captions, 136 | } = attributes; 137 | 138 | const dropZone = ( 139 | 144 | ); 145 | 146 | const sidebarIsOpened = editorSidebarOpened || pluginSidebarOpened || publishSidebarOpened; 147 | 148 | const wrapperClasses = classnames( 149 | ...GlobalClasses( attributes ), 150 | sidebarIsOpened, { 151 | [ `align${ align }` ] : align, 152 | [ `has-gutter` ] : gutter > 0, 153 | } 154 | ); 155 | 156 | const wrapperStyles = { 157 | ...BackgroundStyles( attributes ), 158 | backgroundColor: backgroundColor.color, 159 | }; 160 | 161 | const masonryClasses = classnames( 162 | `has-grid-${ gridSize }`, { 163 | [ `has-gutter-${ gutter }` ] : gutter > 0, 164 | [ `has-gutter-mobile-${ gutterMobile }` ] : gutterMobile > 0, 165 | } 166 | ); 167 | 168 | const masonryStyles = { 169 | color: captionColor.color, 170 | }; 171 | 172 | if ( images.length === 0 ) { 173 | return ( 174 | 180 | ); 181 | } 182 | 183 | return ( 184 | 185 | 188 | 191 | { noticeUI } 192 |
    193 |
    197 | { dropZone } 198 | 206 | { images.map( ( img, index ) => { 207 | // translators: %1$d is the order number of the image, %2$d is the total number of images 208 | const ariaLabel = __( sprintf( 'image %1$d of %2$d in gallery', ( index + 1 ), images.length ) ); 209 | 210 | return ( 211 |
  • 212 | this.setImageAttributes( index, attrs ) } 220 | caption={ img.caption } 221 | aria-label={ ariaLabel } 222 | captions={ captions } 223 | supportsCaption={ true } 224 | /> 225 |
  • 226 | ); 227 | } ) } 228 | { isSelected && ( 229 | 232 | ) } 233 |
    234 |
    235 |
    236 |
    237 | ); 238 | } 239 | } 240 | 241 | export default compose( [ 242 | withSelect( ( select ) => ( { 243 | editorSidebarOpened: select( 'core/edit-post' ).isEditorSidebarOpened(), 244 | pluginSidebarOpened: select( 'core/edit-post' ).isPluginSidebarOpened(), 245 | publishSidebarOpened: select( 'core/edit-post' ).isPublishSidebarOpened(), 246 | wideControlsEnabled: select( 'core/editor' ).getEditorSettings().alignWide, 247 | } ) ), 248 | withColors( { backgroundColor : 'background-color', captionColor : 'color' } ), 249 | withNotices, 250 | ] )( Edit ); -------------------------------------------------------------------------------- /src/components/background/panel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import isEmpty from 'lodash/isEmpty'; 5 | 6 | /** 7 | * Internal dependencies 8 | */ 9 | import ResponsiveTabsControl from '../../components/responsive-tabs-control'; 10 | 11 | /** 12 | * WordPress dependencies 13 | */ 14 | const { __ } = wp.i18n; 15 | const { Component, Fragment } = wp.element; 16 | const { compose } = wp.compose; 17 | const { withColors, ColorPalette, PanelColorSettings } = wp.editor; 18 | const { SelectControl, RangeControl, ToggleControl, PanelBody, Button } = wp.components; 19 | 20 | /** 21 | * Gutter Controls Component 22 | */ 23 | class BackgroundPanel extends Component { 24 | 25 | constructor() { 26 | super( ...arguments ); 27 | this.setBackgroundPaddingTo = this.setBackgroundPaddingTo.bind( this ); 28 | this.setBackgroundPaddingMobileTo = this.setBackgroundPaddingMobileTo.bind( this ); 29 | this.getColors = this.getColors.bind( this ); 30 | } 31 | 32 | setBackgroundPaddingTo( value ) { 33 | this.props.setAttributes( { backgroundPadding: value } ); 34 | 35 | if ( this.props.attributes.backgroundPadding <= 0 ) { 36 | this.props.setAttributes( { 37 | backgroundRadius: 0, 38 | } ); 39 | } 40 | 41 | } 42 | 43 | setBackgroundPaddingMobileTo( value ) { 44 | this.props.setAttributes( { backgroundPaddingMobile: value } ); 45 | } 46 | 47 | getColors() { 48 | 49 | const { 50 | backgroundColor, 51 | setBackgroundColor, 52 | captionColor, 53 | setCaptionColor, 54 | hasCaption, 55 | attributes, 56 | } = this.props; 57 | 58 | const { 59 | backgroundImg, 60 | backgroundPadding, 61 | backgroundPaddingMobile, 62 | } = attributes; 63 | 64 | const background = [ 65 | { 66 | value: backgroundColor.color, 67 | onChange: ( nextBackgroundColor ) => { 68 | 69 | setBackgroundColor( nextBackgroundColor ); 70 | 71 | // Add default padding, if they are not yet present. 72 | if ( ! backgroundPadding && ! backgroundPaddingMobile ) { 73 | this.props.setAttributes( { 74 | backgroundPadding: 30, 75 | backgroundPaddingMobile: 30, 76 | } ); 77 | } 78 | 79 | // Reset when cleared. 80 | if ( ! nextBackgroundColor && ! backgroundImg ) { 81 | this.props.setAttributes( { 82 | backgroundPadding: 0, 83 | backgroundPaddingMobile: 0, 84 | } ); 85 | } 86 | }, 87 | label: __( 'Background Color' ), 88 | }, 89 | ]; 90 | 91 | const caption = [ 92 | { 93 | value: captionColor.color, 94 | onChange: setCaptionColor, 95 | label: __( 'Caption Text Color' ), 96 | }, 97 | ]; 98 | 99 | if ( hasCaption ) { 100 | return background.concat( caption ); 101 | } else { 102 | return background; 103 | } 104 | } 105 | 106 | render() { 107 | 108 | const { 109 | attributes, 110 | setAttributes, 111 | backgroundColor, 112 | setBackgroundColor, 113 | captionColor, 114 | setCaptionColor, 115 | } = this.props; 116 | 117 | const { 118 | align, 119 | backgroundPosition, 120 | backgroundRepeat, 121 | backgroundSize, 122 | backgroundOverlay, 123 | backgroundPadding, 124 | backgroundPaddingMobile, 125 | hasParallax, 126 | backgroundImg, 127 | backgroundRadius, 128 | } = attributes; 129 | 130 | const backgroundPositionOptions = [ 131 | { value: 'top left', label: __( 'Top Left' ) }, 132 | { value: 'top center', label: __( 'Top Center' ) }, 133 | { value: 'top right', label: __( 'Top Right' ) }, 134 | { value: 'center left', label: __( 'Center Left' ) }, 135 | { value: 'center center', label: __( 'Center Center' ) }, 136 | { value: 'center right', label: __( 'Center Right' ) }, 137 | { value: 'bottom left', label: __( 'Bottom Left' ) }, 138 | { value: 'bottom center', label: __( 'Bottom Center' ) }, 139 | { value: 'bottom right', label: __( 'Bottom Right' ) }, 140 | ]; 141 | 142 | const backgroundRepeatOptions = [ 143 | { value: 'no-repeat', label: __( 'No Repeat' ) }, 144 | { value: 'repeat', label: __( 'Repeat' ) }, 145 | { value: 'repeat-x', label: __( 'Repeat Horizontally' ) }, 146 | { value: 'repeat-y', label: __( 'Repeat Vertically' ) }, 147 | ]; 148 | 149 | const backgroundSizeOptions = [ 150 | { value: 'auto', label: __( 'Auto' ) }, 151 | { value: 'cover', label: __( 'Cover' ) }, 152 | { value: 'contain', label: __( 'Contain' ) }, 153 | ]; 154 | 155 | const backgroundSizeDefault = ( typeof options !== 'undefined' && typeof options.backgroundSize !== 'undefined' ) ? options.backgroundSize : 'cover'; 156 | 157 | return ( 158 | 159 | { ( ! isEmpty( backgroundImg ) || ! isEmpty( backgroundColor.color ) ) && ( 160 | 164 | 173 | { ( ( ! isEmpty( backgroundImg ) || ! isEmpty( backgroundColor.color ) ) && backgroundPadding > 0 ) && align != 'full' && 174 | setAttributes( { backgroundRadius: nextBackgroundRadius } ) } 178 | min={ 0 } 179 | max={ 20 } 180 | step={ 1 } 181 | /> 182 | } 183 | { backgroundImg && ( 184 | 185 | setAttributes( { backgroundOverlay: nextBackgroundOverlay } ) } 190 | min={ 0 } 191 | max={ 90 } 192 | step={ 10 } 193 | /> 194 | setAttributes( { backgroundPosition: nextbackgroundPosition } ) } 200 | /> 201 | setAttributes( { backgroundRepeat: nextbackgroundRepeat } ) } 207 | /> 208 | setAttributes( { backgroundSize: nextbackgroundSize } ) } 214 | /> 215 | setAttributes( { hasParallax: ! hasParallax } ) } 220 | /> 221 | 228 | 229 | ) } 230 | 231 | ) } 232 | 237 | 238 | 239 | ); 240 | } 241 | } 242 | 243 | export default compose( [ 244 | withColors( { backgroundColor : 'background-color', captionColor : 'color' } ), 245 | ] )( BackgroundPanel ); 246 | -------------------------------------------------------------------------------- /src/styles/editor/core/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Breakpoint mixins 2 | 3 | @mixin break-huge() { 4 | @media (min-width: #{ ($break-huge) }) { 5 | @content; 6 | } 7 | } 8 | 9 | @mixin break-wide() { 10 | @media (min-width: #{ ($break-wide) }) { 11 | @content; 12 | } 13 | } 14 | 15 | @mixin break-large() { 16 | @media (min-width: #{ ($break-large) }) { 17 | @content; 18 | } 19 | } 20 | 21 | @mixin break-medium() { 22 | @media (min-width: #{ ($break-medium) }) { 23 | @content; 24 | } 25 | } 26 | 27 | @mixin break-small() { 28 | @media (min-width: #{ ($break-small) }) { 29 | @content; 30 | } 31 | } 32 | 33 | @mixin break-mobile() { 34 | @media (min-width: #{ ($break-mobile) }) { 35 | @content; 36 | } 37 | } 38 | 39 | 40 | /** 41 | * Long content fade mixin 42 | * 43 | * Creates a fading overlay to signify that the content is longer 44 | * than the space allows. 45 | */ 46 | 47 | @mixin long-content-fade($direction: right, $size: 20%, $color: #fff, $edge: 0, $z-index: false) { 48 | content: ""; 49 | display: block; 50 | position: absolute; 51 | -webkit-touch-callout: none; 52 | -webkit-user-select: none; 53 | -khtml-user-select: none; 54 | -moz-user-select: none; 55 | -ms-user-select: none; 56 | user-select: none; 57 | pointer-events: none; 58 | 59 | @if $z-index { 60 | z-index: $z-index; 61 | } 62 | 63 | @if $direction == "bottom" { 64 | background: linear-gradient(to top, rgba($color, 0), $color 90%); 65 | left: $edge; 66 | right: $edge; 67 | top: $edge; 68 | bottom: calc(100% - $size); 69 | width: auto; 70 | } 71 | 72 | @if $direction == "top" { 73 | background: linear-gradient(to bottom, rgba($color, 0), $color 90%); 74 | top: calc(100% - $size); 75 | left: $edge; 76 | right: $edge; 77 | bottom: $edge; 78 | width: auto; 79 | } 80 | 81 | @if $direction == "left" { 82 | background: linear-gradient(to left, rgba($color, 0), $color 90%); 83 | top: $edge; 84 | left: $edge; 85 | bottom: $edge; 86 | right: auto; 87 | width: $size; 88 | height: auto; 89 | } 90 | 91 | @if $direction == "right" { 92 | background: linear-gradient(to right, rgba($color, 0), $color 90%); 93 | top: $edge; 94 | bottom: $edge; 95 | right: $edge; 96 | left: auto; 97 | width: $size; 98 | height: auto; 99 | } 100 | } 101 | 102 | /** 103 | * Button states and focus styles 104 | */ 105 | 106 | // Buttons with rounded corners. 107 | @mixin button-style__disabled { 108 | opacity: 0.6; 109 | cursor: default; 110 | } 111 | 112 | @mixin button-style__hover { 113 | background-color: $white; 114 | color: $dark-gray-900; 115 | box-shadow: inset 0 0 0 1px $light-gray-500, inset 0 0 0 2px $white, 0 1px 1px rgba($dark-gray-900, 0.2); 116 | } 117 | 118 | @mixin button-style__active() { 119 | outline: none; 120 | background-color: $white; 121 | color: $dark-gray-900; 122 | box-shadow: inset 0 0 0 1px $light-gray-700, inset 0 0 0 2px $white; 123 | } 124 | 125 | @mixin button-style__focus-active() { 126 | background-color: $white; 127 | color: $dark-gray-900; 128 | box-shadow: inset 0 0 0 1px $dark-gray-300, inset 0 0 0 2px $white; 129 | 130 | // Windows High Contrast mode will show this outline, but not the box-shadow. 131 | outline: 2px solid transparent; 132 | outline-offset: -2px; 133 | } 134 | 135 | // Switch. 136 | @mixin switch-style__focus-active() { 137 | box-shadow: 0 0 0 2px $white, 0 0 0 3px $dark-gray-300; 138 | 139 | // Windows High Contrast mode will show this outline, but not the box-shadow. 140 | outline: 2px solid transparent; 141 | outline-offset: 2px; 142 | } 143 | 144 | // Formatting Buttons. 145 | @mixin formatting-button-style__hover { 146 | color: $dark-gray-500; 147 | box-shadow: inset 0 0 0 1px $dark-gray-500, inset 0 0 0 2px $white; 148 | } 149 | 150 | @mixin formatting-button-style__active() { 151 | outline: none; 152 | color: $white; 153 | box-shadow: none; 154 | background: $dark-gray-500; 155 | } 156 | 157 | @mixin formatting-button-style__focus() { 158 | box-shadow: inset 0 0 0 1px $dark-gray-500, inset 0 0 0 2px $white; 159 | 160 | // Windows High Contrast mode will show this outline, but not the box-shadow. 161 | outline: 2px solid transparent; 162 | outline-offset: -2px; 163 | } 164 | 165 | // Tabs, Inputs, Square buttons. 166 | @mixin input-style__neutral() { 167 | box-shadow: 0 0 0 transparent; 168 | transition: box-shadow 0.1s linear; 169 | border-radius: $radius-round-rectangle; 170 | border: $border-width solid $dark-gray-150; 171 | } 172 | 173 | @mixin input-style__focus() { 174 | color: $dark-gray-900; 175 | border-color: $blue-medium-500; 176 | box-shadow: 0 0 0 1px $blue-medium-500; 177 | 178 | // Windows High Contrast mode will show this outline, but not the box-shadow. 179 | outline: 2px solid transparent; 180 | outline-offset: -2px; 181 | } 182 | 183 | // Square buttons. 184 | @mixin square-style__neutral() { 185 | outline-offset: -1px; 186 | } 187 | 188 | @mixin square-style__focus() { 189 | color: $dark-gray-900; 190 | outline: 1px solid $dark-gray-300; 191 | box-shadow: none; 192 | } 193 | 194 | // Menu items. 195 | @mixin menu-style__neutral() { 196 | border: none; 197 | box-shadow: none; 198 | } 199 | 200 | @mixin menu-style__hover() { 201 | color: $dark-gray-900; 202 | border: none; 203 | box-shadow: none; 204 | } 205 | 206 | @mixin menu-style__focus() { 207 | color: $dark-gray-900; 208 | border: none; 209 | box-shadow: none; 210 | outline-offset: -2px; 211 | outline: 1px dotted $dark-gray-500; 212 | } 213 | 214 | // Blocks in the Library. 215 | @mixin block-style__disabled { 216 | opacity: 0.6; 217 | cursor: default; 218 | } 219 | 220 | @mixin block-style__hover { 221 | background: $light-gray-100; 222 | color: $dark-gray-900; 223 | } 224 | 225 | @mixin block-style__focus-active() { 226 | color: $dark-gray-900; 227 | box-shadow: 0 0 0 2px $blue-medium-500; 228 | 229 | // Windows High Contrast mode will show this outline, but not the box-shadow. 230 | outline: 2px solid transparent; 231 | outline-offset: -2px; 232 | } 233 | 234 | /** 235 | * Applies editor left position to the selector passed as argument 236 | */ 237 | 238 | @mixin editor-left($selector) { 239 | #{$selector} { /* Set left position when auto-fold is not on the body element. */ 240 | left: 0; 241 | 242 | @include break-medium() { 243 | left: $admin-sidebar-width; 244 | } 245 | } 246 | 247 | .auto-fold #{$selector} { /* Auto fold is when on smaller breakpoints, nav menu auto colllapses. */ 248 | @include break-medium() { 249 | left: $admin-sidebar-width-collapsed; 250 | } 251 | 252 | @include break-large() { 253 | left: $admin-sidebar-width; 254 | } 255 | } 256 | 257 | /* Sidebar manually collapsed. */ 258 | .folded #{$selector} { 259 | left: 0; 260 | 261 | @include break-medium() { 262 | left: $admin-sidebar-width-collapsed; 263 | } 264 | } 265 | 266 | /* Mobile menu opened. */ 267 | @media (max-width: #{ ($break-medium) }) { 268 | .auto-fold .wp-responsive-open #{$selector} { 269 | left: $admin-sidebar-width-big; 270 | } 271 | } 272 | 273 | /* In small screens with resposive menu expanded there is small white space. */ 274 | @media (max-width: #{ ($break-small) }) { 275 | .auto-fold .wp-responsive-open #{$selector} { 276 | margin-left: -18px; 277 | } 278 | } 279 | 280 | body.is-fullscreen-mode #{$selector} { 281 | left: 0 !important; 282 | } 283 | } 284 | 285 | /** 286 | * Applies editor right position to the selector passed as argument 287 | */ 288 | 289 | @mixin editor-right($selector) { 290 | #{ $selector } { 291 | right: 0; 292 | } 293 | 294 | .edit-post-layout.is-sidebar-opened #{ $selector } { 295 | right: $sidebar-width; 296 | } 297 | } 298 | 299 | 300 | /** 301 | * Styles that are reused verbatim in a few places 302 | */ 303 | 304 | @mixin caption-style() { 305 | margin-top: 0.5em; 306 | margin-bottom: 1em; 307 | color: $dark-gray-500; 308 | text-align: center; 309 | font-size: $default-font-size; 310 | } 311 | 312 | @mixin dropdown-arrow() { 313 | content: ""; 314 | pointer-events: none; 315 | display: block; 316 | width: 0; 317 | height: 0; 318 | border-left: 3px solid transparent; 319 | border-right: 3px solid transparent; 320 | border-top: 5px solid currentColor; 321 | margin-left: $grid-size-small; 322 | 323 | // This gives the icon space on the right side consistent with the material 324 | // icon standards. 325 | margin-right: 2px; 326 | } 327 | -------------------------------------------------------------------------------- /class-block-gallery.php: -------------------------------------------------------------------------------- 1 | Notice: Block Gallery in being deprecated in favor of the wonderful – and fully featured – gallery blocks within the CoBlocks plugin. Each Block Gallery block may be transformed into it's corresponding CoBlocks gallery block via traditional block transforms. 6 | * Author: GoDaddy 7 | * Author URI: https://www.godaddy.com 8 | * Version: 1.1.6 9 | * Text Domain: @@textdomain 10 | * Domain Path: languages 11 | * Tested up to: 5.2 12 | * 13 | * Block Gallery is free software: you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation, either version 2 of the License, or 16 | * any later version. 17 | * 18 | * Block Gallery is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | * 23 | * You should have received a copy of the GNU General Public License 24 | * along with @@pkg.title. If not, see . 25 | * 26 | * @package Block Gallery 27 | */ 28 | 29 | // Exit if accessed directly. 30 | if ( ! defined( 'ABSPATH' ) ) { 31 | exit; 32 | } 33 | 34 | if ( ! class_exists( 'Block_Gallery' ) ) : 35 | /** 36 | * Main Block_Gallery Class. 37 | * 38 | * @since 1.0.0 39 | */ 40 | final class Block_Gallery { 41 | /** 42 | * This plugin's instance. 43 | * 44 | * @var Block_Gallery 45 | * @since 1.0.0 46 | */ 47 | private static $instance; 48 | 49 | /** 50 | * Main Block_Gallery Instance. 51 | * 52 | * Insures that only one instance of Block_Gallery exists in memory at any one 53 | * time. Also prevents needing to define globals all over the place. 54 | * 55 | * @since 1.0.0 56 | * @static 57 | * @uses Block_Gallery::constants() Setup the constants needed. 58 | * @uses Block_Gallery::includes() Include the required files. 59 | * @see WIDGETOPTS() 60 | * @return object|Block_Gallery The one true Block_Gallery 61 | */ 62 | public static function instance() { 63 | if ( ! isset( self::$instance ) && ! ( self::$instance instanceof Block_Gallery ) ) { 64 | self::$instance = new Block_Gallery(); 65 | self::$instance->init(); 66 | self::$instance->constants(); 67 | self::$instance->asset_suffix(); 68 | self::$instance->includes(); 69 | 70 | } 71 | return self::$instance; 72 | } 73 | 74 | /** 75 | * Throw error on object clone. 76 | * 77 | * The whole idea of the singleton design pattern is that there is a single 78 | * object therefore, we don't want the object to be cloned. 79 | * 80 | * @since 1.0.0 81 | * @access protected 82 | * @return void 83 | */ 84 | public function __clone() { 85 | // Cloning instances of the class is forbidden. 86 | _doing_it_wrong( __FUNCTION__, esc_html__( 'Cheating huh?', '@@textdomain' ), '1.0' ); 87 | } 88 | 89 | /** 90 | * Disable unserializing of the class. 91 | * 92 | * @since 1.0.0 93 | * @access protected 94 | * @return void 95 | */ 96 | public function __wakeup() { 97 | // Unserializing instances of the class is forbidden. 98 | _doing_it_wrong( __FUNCTION__, esc_html__( 'Cheating huh?', '@@textdomain' ), '1.0' ); 99 | } 100 | 101 | /** 102 | * Setup plugin constants. 103 | * 104 | * @access private 105 | * @since 1.0.0 106 | * @return void 107 | */ 108 | private function constants() { 109 | $this->define( 'BLOCKGALLERY_DEBUG', true ); 110 | $this->define( 'BLOCKGALLERY_VERSION', '@@pkg.version' ); 111 | $this->define( 'BLOCKGALLERY_HAS_PRO', false ); 112 | $this->define( 'BLOCKGALLERY_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); 113 | $this->define( 'BLOCKGALLERY_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); 114 | $this->define( 'BLOCKGALLERY_PLUGIN_FILE', __FILE__ ); 115 | $this->define( 'BLOCKGALLERY_PLUGIN_BASE', plugin_basename( __FILE__ ) ); 116 | $this->define( 'BLOCKGALLERY_SHOP_URL', 'https://wpblockgallery.com/' ); 117 | $this->define( 'BLOCKGALLERY_REVIEW_URL', 'https://wordpress.org/support/plugin/block-gallery/reviews/' ); 118 | } 119 | 120 | /** 121 | * Define constant if not already set. 122 | * 123 | * @param string|string $name Name of the definition. 124 | * @param string|bool $value Default value. 125 | */ 126 | private function define( $name, $value ) { 127 | if ( ! defined( $name ) ) { 128 | define( $name, $value ); 129 | } 130 | } 131 | 132 | /** 133 | * Include required files. 134 | * 135 | * @access private 136 | * @since 1.0.0 137 | * @return void 138 | */ 139 | private function includes() { 140 | require_once BLOCKGALLERY_PLUGIN_DIR . 'includes/class-block-gallery-block-assets.php'; 141 | require_once BLOCKGALLERY_PLUGIN_DIR . 'includes/class-block-gallery-body-classes.php'; 142 | 143 | if ( is_admin() || ( defined( 'WP_CLI' ) && WP_CLI ) ) { 144 | require_once BLOCKGALLERY_PLUGIN_DIR . 'includes/admin/class-block-gallery-url-generator.php'; 145 | require_once BLOCKGALLERY_PLUGIN_DIR . 'includes/admin/class-block-gallery-action-links.php'; 146 | require_once BLOCKGALLERY_PLUGIN_DIR . 'includes/admin/class-block-gallery-footer-text.php'; 147 | require_once BLOCKGALLERY_PLUGIN_DIR . 'includes/admin/class-block-gallery-feedback.php'; 148 | } 149 | } 150 | 151 | /** 152 | * Load actions 153 | * 154 | * @return void 155 | */ 156 | private function init() { 157 | add_action( 'init', array( $this, 'load_textdomain' ) ); 158 | } 159 | 160 | /** 161 | * Change the plugin's minified or src file name, based on debug mode. 162 | * 163 | * @since 1.0.0 164 | */ 165 | public function asset_suffix() { 166 | if ( true === BLOCKGALLERY_DEBUG ) { 167 | define( 'BLOCKGALLERY_ASSET_SUFFIX', null ); 168 | } else { 169 | define( 'BLOCKGALLERY_ASSET_SUFFIX', '.min' ); 170 | } 171 | } 172 | 173 | /** 174 | * If debug is on, serve unminified source assets. 175 | * 176 | * @since 1.0.0 177 | * @param string|string $type The type of resource. 178 | * @param string|string $directory Any extra directories needed. 179 | */ 180 | public function asset_source( $type = 'js', $directory = null ) { 181 | 182 | if ( 'js' === $type ) { 183 | if ( true === BLOCKGALLERY_DEBUG ) { 184 | return BLOCKGALLERY_PLUGIN_URL . 'src/' . $type . '/' . $directory . '/'; 185 | } else { 186 | return BLOCKGALLERY_PLUGIN_URL . 'dist/' . $type . '/' . $directory . '/'; 187 | } 188 | } else { 189 | return BLOCKGALLERY_PLUGIN_URL . 'dist/css/' . $directory; 190 | } 191 | } 192 | 193 | /** 194 | * Check if pro exists. 195 | * 196 | * @access public 197 | */ 198 | public function has_pro() { 199 | if ( true === BLOCKGALLERY_HAS_PRO ) { 200 | return true; 201 | } else { 202 | return false; 203 | } 204 | } 205 | 206 | /** 207 | * Check if pro is activated. 208 | * 209 | * @access public 210 | */ 211 | public function is_pro() { 212 | 213 | if ( class_exists( 'Block_Gallery_Pro' ) ) { 214 | return true; 215 | } else { 216 | return false; 217 | } 218 | } 219 | 220 | /** 221 | * Loads the plugin language files. 222 | * 223 | * @access public 224 | * @since 1.0.0 225 | * @return void 226 | */ 227 | public function load_textdomain() { 228 | load_plugin_textdomain( '@@textdomain', false, dirname( plugin_basename( BLOCKGALLERY_PLUGIN_DIR ) ) . '/languages/' ); 229 | } 230 | } 231 | endif; 232 | 233 | /** 234 | * The main function for that returns Block_Gallery 235 | * 236 | * The main function responsible for returning the one true Block_Gallery 237 | * Instance to functions everywhere. 238 | * 239 | * Use this function like you would a global variable, except without needing 240 | * to declare the global. 241 | * 242 | * Example: 243 | * 244 | * @since 1.0.0 245 | * @return object|Block_Gallery The one true Block_Gallery Instance. 246 | */ 247 | function block_gallery() { 248 | return Block_Gallery::instance(); 249 | } 250 | 251 | // Get the plugin running. Load on plugins_loaded action to avoid issue on multisite. 252 | if ( function_exists( 'is_multisite' ) && is_multisite() ) { 253 | add_action( 'plugins_loaded', 'block_gallery', 90 ); 254 | } else { 255 | block_gallery(); 256 | } 257 | -------------------------------------------------------------------------------- /src/blocks/carousel/components/edit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import classnames from 'classnames'; 5 | import filter from 'lodash/filter'; 6 | import Flickity from 'react-flickity-component'; 7 | 8 | /** 9 | * Internal dependencies 10 | */ 11 | import GalleryImage from '../../../components/gallery-image'; 12 | import GalleryPlaceholder from '../../../components/gallery-placeholder'; 13 | import GalleryDropZone from '../../../components/gallery-dropzone'; 14 | import GalleryUpload from '../../../components/gallery-upload'; 15 | import Inspector from './inspector'; 16 | import { BackgroundStyles } from '../../../components/background/'; 17 | import { title, icon } from '../' 18 | import { GlobalClasses, GlobalToolbar } from '../../../components/global/'; 19 | 20 | /** 21 | * WordPress dependencies 22 | */ 23 | const { __, sprintf } = wp.i18n; 24 | const { Component, Fragment } = wp.element; 25 | const { compose } = wp.compose; 26 | const { withSelect } = wp.data; 27 | const { withNotices, ResizableBox } = wp.components; 28 | const { withColors, RichText } = wp.editor; 29 | 30 | /** 31 | * Block consts. 32 | */ 33 | const flickityOptions = { 34 | draggable: false, 35 | pageDots: true, 36 | prevNextButtons: true, 37 | wrapAround: true, 38 | autoPlay: false, 39 | arrowShape: { 40 | x0: 10, 41 | x1: 60, y1: 50, 42 | x2: 65, y2: 45, 43 | x3: 20 44 | }, 45 | } 46 | 47 | /** 48 | * Block edit function 49 | */ 50 | class Edit extends Component { 51 | constructor() { 52 | super( ...arguments ); 53 | 54 | this.onSelectImage = this.onSelectImage.bind( this ); 55 | this.onRemoveImage = this.onRemoveImage.bind( this ); 56 | this.setImageAttributes = this.setImageAttributes.bind( this ); 57 | this.onFocusCaption = this.onFocusCaption.bind( this ); 58 | this.onItemClick = this.onItemClick.bind( this ); 59 | 60 | this.state = { 61 | selectedImage: null, 62 | captionFocused: false, 63 | }; 64 | } 65 | 66 | componentDidMount() { 67 | 68 | // This block does not support the following attributes. 69 | this.props.setAttributes( { 70 | lightbox: undefined, 71 | lightboxStyle: undefined, 72 | shadow: undefined, 73 | } ); 74 | } 75 | 76 | componentDidUpdate( prevProps ) { 77 | 78 | // Deselect images when deselecting the block. 79 | if ( ! this.props.isSelected && prevProps.isSelected ) { 80 | this.setState( { 81 | selectedImage: null, 82 | captionSelected: false, 83 | captionFocused: false, 84 | } ); 85 | } 86 | 87 | if ( ! this.props.isSelected && prevProps.isSelected && this.state.captionFocused ) { 88 | this.setState( { 89 | captionFocused: false, 90 | } ); 91 | } 92 | 93 | if ( this.props.attributes.gutter <= 0 ) { 94 | this.props.setAttributes( { 95 | radius: 0, 96 | } ); 97 | } 98 | 99 | if ( this.props.attributes.gridSize == 'xlrg' && prevProps.attributes.align == undefined ) { 100 | this.props.setAttributes( { 101 | gutter: 0, 102 | gutterMobile: 0, 103 | } ); 104 | } 105 | } 106 | 107 | onSelectImage( index ) { 108 | return () => { 109 | if ( this.state.selectedImage !== index ) { 110 | this.setState( { 111 | selectedImage: index, 112 | captionFocused: false, 113 | } ); 114 | } 115 | }; 116 | } 117 | 118 | onRemoveImage( index ) { 119 | return () => { 120 | const images = filter( this.props.attributes.images, ( img, i ) => index !== i ); 121 | const { gridSize } = this.props.attributes; 122 | this.setState( { selectedImage: null } ); 123 | this.props.setAttributes( { 124 | images, 125 | } ); 126 | }; 127 | } 128 | 129 | setImageAttributes( index, attributes ) { 130 | const { attributes: { images }, setAttributes } = this.props; 131 | if ( ! images[ index ] ) { 132 | return; 133 | } 134 | setAttributes( { 135 | images: [ 136 | ...images.slice( 0, index ), 137 | { 138 | ...images[ index ], 139 | ...attributes, 140 | }, 141 | ...images.slice( index + 1 ), 142 | ], 143 | } ); 144 | } 145 | 146 | onFocusCaption() { 147 | if ( ! this.state.captionFocused ) { 148 | this.setState( { 149 | captionFocused: true, 150 | } ); 151 | } 152 | } 153 | 154 | onItemClick() { 155 | if ( ! this.props.isSelected ) { 156 | this.props.onSelect(); 157 | } 158 | 159 | if ( this.state.captionFocused ) { 160 | this.setState( { 161 | captionFocused: false, 162 | } ); 163 | } 164 | } 165 | 166 | render() { 167 | const { 168 | attributes, 169 | backgroundColor, 170 | className, 171 | isSelected, 172 | noticeOperations, 173 | noticeUI, 174 | setAttributes, 175 | toggleSelection, 176 | captionColor, 177 | } = this.props; 178 | 179 | const { 180 | align, 181 | autoPlay, 182 | gridSize, 183 | gutter, 184 | gutterMobile, 185 | height, 186 | images, 187 | pageDots, 188 | prevNextButtons, 189 | primaryCaption, 190 | } = attributes; 191 | 192 | const dropZone = ( 193 | 198 | ); 199 | 200 | const wrapperClasses = classnames( 201 | 'is-cropped', 202 | ...GlobalClasses( attributes ), { 203 | [ `align${ align }` ] : align, 204 | [ `has-horizontal-gutter` ] : gutter > 0, 205 | [ `has-no-dots` ] : ! pageDots, 206 | [ `has-no-arrows` ] : ! prevNextButtons, 207 | 'is-selected': isSelected, 208 | 209 | } 210 | ); 211 | 212 | const wrapperStyles = { 213 | ...BackgroundStyles( attributes ), 214 | backgroundColor: backgroundColor.color, 215 | 'is-selected': isSelected, 216 | }; 217 | 218 | const captionStyles = { 219 | color: captionColor.color, 220 | }; 221 | 222 | const flickityClasses = classnames( 223 | 'has-carousel', 224 | `has-carousel-${ gridSize }`, {} 225 | ); 226 | 227 | if ( images.length === 0 ) { 228 | return ( 229 | 235 | ); 236 | } 237 | 238 | return ( 239 | 240 | 243 | 246 | { noticeUI } 247 | { 267 | setAttributes( { 268 | height: parseInt( height + delta.height, 10 ), 269 | } ); 270 | toggleSelection( true ); 271 | } } 272 | onResizeStart={ () => { 273 | toggleSelection( false ); 274 | } } 275 | > 276 | { dropZone } 277 |
    278 |
    282 | this.flkty = c } 286 | options={ flickityOptions } 287 | reloadOnUpdate={ true } 288 | updateOnEachImageLoad={ true } 289 | > 290 | { images.map( ( img, index ) => { 291 | // translators: %1$d is the order number of the image, %2$d is the total number of images 292 | const ariaLabel = __( sprintf( 'image %1$d of %2$d in gallery', ( index + 1 ), images.length ) ); 293 | 294 | return ( 295 |
    296 | this.setImageAttributes( index, attrs ) } 308 | caption={ img.caption } 309 | aria-label={ ariaLabel } 310 | supportsCaption={ false } 311 | /> 312 |
    313 | ); 314 | } ) } 315 | { isSelected && ( 316 | 322 | ) } 323 |
    324 |
    325 |
    326 |
    327 | { ( ! RichText.isEmpty( primaryCaption ) || isSelected ) && ( 328 | setAttributes( { primaryCaption: value } ) } 336 | isSelected={ this.state.captionFocused } 337 | keepPlaceholderOnFocus 338 | inlineToolbar 339 | /> 340 | ) } 341 |
    342 | ); 343 | } 344 | } 345 | 346 | export default compose( [ 347 | withColors( { backgroundColor : 'background-color', captionColor : 'color' } ), 348 | withNotices, 349 | ] )( Edit ); -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === Block Gallery - Photo Gallery Gutenberg Blocks === 2 | Author URI: https://www.godaddy.com 3 | Plugin URI: https://wpblockgallery.com 4 | Contributors: richtabor 5 | Tags: blocks, gutenberg, gallery, page builder, gutenberg blocks, editor, photo gallery, masonry, block, slider, carousel 6 | Requires at least: 5.0 7 | Tested up to: 5.2 8 | Requires PHP: 5.2.4 9 | Stable tag: @@pkg.version 10 | License: GPL-2.0 11 | License URI: http://www.gnu.org/licenses/gpl-2.0.html 12 | 13 | == Description == 14 | 15 | ## Important notice 16 | **Block Gallery in being deprecated** in favor of the wonderful gallery blocks within [CoBlocks](https://wordpress.org/plugins/coblocks). Each Block Gallery block may be transformed into it's corresponding CoBlocks gallery block via traditional block transforms. 17 | 18 | [Block Gallery](https://wpblockgallery.com?utm_medium=wp.org&utm_source=wordpressorg&utm_campaign=readme&utm_content=block-gallery) is a suite of beautiful gallery Gutenberg blocks for photographers, artists, writers and content marketers. This is the smartest, most powerful photo gallery plugin for WordPress. Block Gallery is absolutely brilliant any way you look at it. 19 | 20 | >Good news! Block Gallery was selected as this year’s winner in the Best Solution category of the [Automattic Design Awards](https://automatticdesignaward.blog/2018/12/08/the-winners/) at WordCamp US 2018. 🔥🔥 21 | 22 | ## A short demo of Block Gallery 23 | [vimeo https://vimeo.com/296746112] 24 | 25 | ## Unrivaled, in every way 26 | The first of its kind, [Block Gallery](https://wpblockgallery.com?utm_medium=wp.org&utm_source=wordpressorg&utm_campaign=readme&utm_content=block-gallery) offers an unrivaled drag and drop gallery building experience in Gutenberg. Drop your images in your choice of photo gallery block, customize display settings, hit publish. 27 | 28 | ## Unparalleled capabilities 29 | An innovative transform system lets you instantly change your photos galleries into another form. Go from a fullscreen masonry gallery to a casual carousel, with just a single click. You won't find another Gutenberg gallery plugin with this kind of capability. Guaranteed. 30 | 31 | ## Highly responsive 32 | Our Gutenberg gallery blocks are second-to-none, featuring fullscale responsive support. And with fine controls for mobile and desktop styles, you can set custom styling for each gallery. 33 | 34 | ## A Super-fast experience 35 | We've built a highly interactive and intuitive experience with a focus on speed and ease of use. Drag. Drop. Transform. Style. 36 | 37 | ## Included Gallery Gutenberg Blocks 38 | 39 | * Masonry Gallery - ([demo](https://richtabor.com/block-gallery-blocks/?utm_medium=wp.org&utm_source=wordpressorg&utm_campaign=readme&utm_content=block-gallery-masonry-demo#masonry)) 40 | * Fullscreen Stacked Gallery - ([demo](https://richtabor.com/block-gallery-blocks/?utm_medium=wp.org&utm_source=wordpressorg&utm_campaign=readme&utm_content=block-gallery-stacked-demo#stacked)) 41 | * Carousel Slider - ([demo](https://richtabor.com/block-gallery-blocks/?utm_medium=wp.org&utm_source=wordpressorg&utm_campaign=readme&utm_content=block-gallery-carousel-demo#carousel)) 42 | 43 | ## Works with CoBlocks 44 | If you enjoy Block Gallery, check out [CoBlocks](https://wordpress.org/plugins/coblocks), a suite of page builder WordPress blocks and tools for the Gutenberg editor — also built by Rich. It's fantastic! 45 | 46 | == Screenshots == 47 | 48 | 1. Masonry Grid block 49 | 2. Carousel Slideshow block 50 | 3. Stacked Fullwidth block 51 | 4. Offset Grid block (Pro Only - coming soon) 52 | 5. Auto Height Slider block (Pro Only - coming soon) 53 | 54 | == Installation == 55 | 56 | 1. Upload the `block-gallery` folder to your `/wp-content/plugins/` directory or alternatively upload the block-gallery.zip file via the plugin page of WordPress by clicking 'Add New' and selecting the zip from your computer. 57 | 2. Install and activate the Gutenberg WordPress plugin (if pre WordPress 5.0). 58 | 3. Activate the Block Gallery WordPress plugin through the 'Plugins' menu in WordPress. 59 | 60 | == Frequently Asked Questions == 61 | 62 | = How do I start using Gutenberg? = 63 | To get the full experience of the next-generation WordPress block editor, you'll need a Gutenberg-ready WordPress theme, like [Tabor](https://themebeans.com/themes/tabor?utm_medium=block-gallery-lite&utm_source=readme&utm_campaign=readme&utm_content=tabor) or [Stash](https://themebeans.com/themes/stash?utm_medium=block-gallery-lite&utm_source=readme&utm_campaign=readme&utm_content=stash). Then install the [Gutenberg](https://wordpress.org/plugins/gutenberg/) WordPress plugin. That's it! 💥 64 | 65 | = What themes work with Block Gallery = 66 | Most WordPress themes that have baked in Gutenberg support will work with Block Gallery. If you’re looking for exceptional themes, check out my theme catalogue at [ThemeBeans](https://themebeans.com?utm_medium=wp.org&utm_source=wordpressorg&utm_campaign=readme&utm_content=block-gallery). 67 | 68 | = Is Block Gallery free? = 69 | Yes! Block Gallery's core features are absolutely free. 70 | 71 | = Where can I ask for help? = 72 | Please reach out via the official [support forum on WordPress.org](https://wordpress.org/support/plugin/block-gallery/). 73 | 74 | == Changelog == 75 | 76 | = 1.1.6 = 77 | * New: Block Gallery now supports WordPress 5.1 and Gutenberg 5.0 78 | * Tweak: Add Block Gallery color to icons within the block inserter 79 | 80 | = 1.1.5 = 81 | * Tweak: Use the MediaUploadCheck component to make sure the current user has upload permissions 82 | * Fix: Resolve lodash/isEmpty issue with the npm start command [thanks @mtekk] 83 | * Fix: Resolve issue where image radius styles were not applied to child captions [thanks @wido] 84 | 85 | = 1.1.4 = 86 | * New: Add toggles for turning image captions on/off for each block 87 | * New: Add new "none" Caption Style option 88 | * New: Add new options for slider autoplay times up to 10 seconds [thanks @batracy] 89 | * Fix: Resolve translatable placeholder issue in the Slider Settings panel [thanks @morganestes] 90 | * Fix: Resolve translatable invalid HTML issue in the Carousel block [thanks @morganestes] 91 | * Fix: Resolve issue where the Carousel autoplay speed was not functioning properly [thanks @morganestes] 92 | * Fix: Resolve PHP 5.4.16 compatibility issue [thanks @ndcadmin] 93 | * Tweak: Adjust Stacked Inspector interface 94 | 95 | = 1.1.3 = 96 | * Fix: Resolve issue where block assets were not loading on the blogroll 97 | 98 | = 1.1.2 = 99 | * New: Add minor style touch-ups for the default Twenty Nineteen WordPress theme 100 | 101 | = 1.1.1 = 102 | * Tweak: Remove Gutenberg check 103 | 104 | = 1.1.0, December 04, 2018 = 105 | * New: Add ability to transform Image blocks to Block Gallery blocks 106 | * New: Add ":" prefix transforms using each blocks' name - i.e. ":masonry", 107 | * New: Load frontend assets only on pages that need them 108 | * New: Add translation strings in /languages/block-gallery.pot 109 | * New: Add support for the WP 5.0 wp_set_script_translations() function 110 | * New: Add styling for the core Twenty Seventeen theme 111 | * New: Add styling for the core Twenty Sixteen theme 112 | * New: Add styling for the core Twenty Fifteen theme 113 | * New: Add styling for the core Twenty Fourteen theme 114 | * New: Add styling for the core Twenty Twelve theme 115 | * New: Add styling for the core Twenty Eleven theme 116 | * New: Add block-gallery-translations.php for referencing PHP translatable strings 117 | * Tweak: Improve grid size responsiveness for the Masonry block 118 | * Tweak: Hide the GalleryUpload component if not selected 119 | * Tweak: Improve Flickity focus styles for better theme compatibility 120 | 121 | = 1.0.9 = 122 | Tweak: Use better specificity for figcaption margins 123 | Tweak: Add inherit color for caption link hovers 124 | 125 | = 1.0.8 = 126 | * Tweak: Remove unnecessary style dependancies 127 | 128 | = 1.0.7 = 129 | * Tweak: Indicate uploading using a spinner 130 | * Tweak: Adjust figcaption margin for better theme compatibility 131 | 132 | = 1.0.6 = 133 | * Tweak: Adjust mobile styles for the block inspector controls UI 134 | * Tweak: Adjust UI of SizeControl controls 135 | * Tweak: Adjust pickRelevantMediaFiles 136 | * Tweak: Hide shadow controls if Stacked block is fullwidth 137 | * Tweak: Improve default caption style for Carousel 138 | * Tweak: Tweak mobile styles for Carousel block arrows 139 | * Tweak: Adjust height of Stacked image uploader 140 | 141 | = 1.0.5 = 142 | * Tweak: Ensure the last figcaption in the Stacked Block is styled appropriately 143 | * Tweak: Update styling of feedback notice 144 | * Tweak: Improve language of the gallery instructions for placeholders 145 | * Tweak: Tweak icon placement in the placeholder label 146 | * Tweak: Remove caption color that was overriding theme styles 147 | * Tweak: Use register_block_type to check if the block editor is live 148 | * Tweak: Improve UI of the ResponsiveTabsControl component 149 | * Tweak: Add tab navigation support for gallery images 150 | * Tweak: Tweak editor styles for captions 151 | 152 | = 1.0.4 = 153 | * Fix: Resolve issue with the Stacked block shadow attribute 154 | 155 | = 1.0.3 = 156 | * New: Add support for adding a primary caption to the Carousel block 157 | * New: Add font size option for the Stacked gallery block 158 | * Tweak: Improve UI of the slider arrows within the editor 159 | * Tweak: Improve help language of the Slider Settings panel for better context and understanding 160 | * Tweak: Improve UI of the ResponsiveTabsControl component 161 | * Tweak: Add slider controls to global transforms 162 | * Fix: Improve display of fullwidth images in the Stacked gallery block 163 | * Fix Improved reliablity of the Stacked block when triggering fullwidth imagery 164 | * Fix: Improve display of carousel arrows 165 | 166 | = 1.0.2 = 167 | * Tweak: Improve figcaption display 168 | * Tweak: Improve block category registration 169 | * Tweak: Add icon to the block category for Gutenberg 4.2+ 170 | * Fix: Color palette colors properly render in the editor 171 | 172 | = 1.0.1 = 173 | * New: Improve block registration 174 | 175 | = 1.0.0 = 176 | * Initial release on WordPress.org. Enjoy! 177 | --------------------------------------------------------------------------------