From b688d2eb10cb63ee4c7ece18be85509ea8529f33 Mon Sep 17 00:00:00 2001 From: Matthew Riley MacPherson Date: Fri, 7 Sep 2018 22:24:44 +0100 Subject: [PATCH 1/6] feat: Focus title if title is empty (#9608) * feat: Focus title when empty Fix #3828. --- docs/data/data-core-editor.md | 5 +- packages/block-library/src/list/index.js | 10 +-- .../editor/src/components/post-title/index.js | 22 +++++- packages/editor/src/store/selectors.js | 3 - test/e2e/specs/hello.test.js | 31 -------- test/e2e/specs/new-post.test.js | 75 +++++++++++++++++++ 6 files changed, 101 insertions(+), 45 deletions(-) delete mode 100644 test/e2e/specs/hello.test.js create mode 100644 test/e2e/specs/new-post.test.js diff --git a/docs/data/data-core-editor.md b/docs/data/data-core-editor.md index df8e8f85be10f7..08e61c6fcdda06 100644 --- a/docs/data/data-core-editor.md +++ b/docs/data/data-core-editor.md @@ -54,9 +54,6 @@ Whether unsaved values exist. Returns true if there are no unsaved values for the current edit session and if the currently edited post is new (has never been saved before). -Note: This selector is not currently used by the editor package, but is made -available as an assumed-useful selector for external integrations. - *Parameters* * state: Global application state. @@ -1414,4 +1411,4 @@ Returns an action object used in signalling that the editor settings have been u *Parameters* - * settings: Updated settings \ No newline at end of file + * settings: Updated settings diff --git a/packages/block-library/src/list/index.js b/packages/block-library/src/list/index.js index b3880662721f89..503fcdd1f028c2 100644 --- a/packages/block-library/src/list/index.js +++ b/packages/block-library/src/list/index.js @@ -244,12 +244,12 @@ export const settings = { } ); } ); - // this checks for languages that do not typically have square brackets on their keyboards + // Check for languages that do not have square brackets on their keyboards. const lang = window.navigator.browserLanguage || window.navigator.language; - const keyboardHasSqBracket = ! /^(?:fr|nl|sv|ru|de|es|it)/.test( lang ); + const keyboardHasSquareBracket = ! /^(?:fr|nl|sv|ru|de|es|it)/.test( lang ); - if ( keyboardHasSqBracket ) { - // keycode 219 = '[' and keycode 221 = ']' + if ( keyboardHasSquareBracket ) { + // `[` is keycode 219; `]` is keycode 221. editor.shortcuts.add( 'meta+219', 'Decrease indent', 'Outdent' ); editor.shortcuts.add( 'meta+221', 'Increase indent', 'Indent' ); } else { @@ -265,7 +265,7 @@ export const settings = { const { setAttributes } = this.props; const { internalListType } = this.state; if ( internalListType ) { - // only change list types, don't toggle off internal lists + // Only change list types, don't toggle off internal lists. if ( internalListType !== type && this.editor ) { this.editor.execCommand( command ); } diff --git a/packages/editor/src/components/post-title/index.js b/packages/editor/src/components/post-title/index.js index 8393c2d4d67aee..718784b7bd9f92 100644 --- a/packages/editor/src/components/post-title/index.js +++ b/packages/editor/src/components/post-title/index.js @@ -88,7 +88,15 @@ class PostTitle extends Component { } render() { - const { title, placeholder, instanceId, isPostTypeViewable, isFocusMode, hasFixedToolbar } = this.props; + const { + hasFixedToolbar, + isCleanNewPost, + isFocusMode, + isPostTypeViewable, + instanceId, + placeholder, + title, + } = this.props; const { isSelected } = this.state; const className = classnames( 'editor-post-title__block', { 'is-selected': isSelected, @@ -119,6 +127,15 @@ class PostTitle extends Component { onFocus={ this.onSelect } onKeyDown={ this.onKeyDown } onKeyPress={ this.onUnselect } + /* + Only autofocus the title when the post is entirely empty. + This should only happen for a new post, which means we + focus the title on new post so the author can start typing + right away, without needing to click anything. + */ + /* eslint-disable jsx-a11y/no-autofocus */ + autoFocus={ isCleanNewPost } + /* eslint-enable jsx-a11y/no-autofocus */ /> { isSelected && isPostTypeViewable && } @@ -130,12 +147,13 @@ class PostTitle extends Component { } const applyWithSelect = withSelect( ( select ) => { - const { getEditedPostAttribute, getEditorSettings } = select( 'core/editor' ); + const { getEditedPostAttribute, getEditorSettings, isCleanNewPost } = select( 'core/editor' ); const { getPostType } = select( 'core' ); const postType = getPostType( getEditedPostAttribute( 'type' ) ); const { titlePlaceholder, focusMode, hasFixedToolbar } = getEditorSettings(); return { + isCleanNewPost: isCleanNewPost(), title: getEditedPostAttribute( 'title' ), isPostTypeViewable: get( postType, [ 'viewable' ], false ), placeholder: titlePlaceholder, diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index c16b3dabd1632e..823fbe522f3638 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -102,9 +102,6 @@ export function isEditedPostDirty( state ) { * Returns true if there are no unsaved values for the current edit session and * if the currently edited post is new (has never been saved before). * - * Note: This selector is not currently used by the editor package, but is made - * available as an assumed-useful selector for external integrations. - * * @param {Object} state Global application state. * * @return {boolean} Whether new post and unsaved values exist. diff --git a/test/e2e/specs/hello.test.js b/test/e2e/specs/hello.test.js deleted file mode 100644 index c43dd7ab313e28..00000000000000 --- a/test/e2e/specs/hello.test.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Internal dependencies - */ -import { newPost } from '../support/utils'; - -describe( 'hello', () => { - beforeAll( async () => { - await newPost(); - } ); - - it( 'Should show the New Post Page in Gutenberg', async () => { - expect( page.url() ).toEqual( expect.stringContaining( 'post-new.php' ) ); - // Should display the title. - const title = await page.$( '[placeholder="Add title"]' ); - expect( title ).not.toBeNull(); - // Should display the Preview button. - const postPreviewButton = await page.$( '.editor-post-preview.components-button' ); - expect( postPreviewButton ).not.toBeNull(); - // Should display the Post Formats UI. - const postFormatsUi = await page.$( '.editor-post-format' ); - expect( postFormatsUi ).not.toBeNull(); - } ); - - it( 'Should have no history', async () => { - const undoButton = await page.$( '.editor-history__undo:not( :disabled )' ); - const redoButton = await page.$( '.editor-history__redo:not( :disabled )' ); - - expect( undoButton ).toBeNull(); - expect( redoButton ).toBeNull(); - } ); -} ); diff --git a/test/e2e/specs/new-post.test.js b/test/e2e/specs/new-post.test.js new file mode 100644 index 00000000000000..61970a83f95361 --- /dev/null +++ b/test/e2e/specs/new-post.test.js @@ -0,0 +1,75 @@ +/** + * Internal dependencies + */ +import { newPost } from '../support/utils'; + +describe( 'new editor state', () => { + beforeAll( async () => { + await newPost(); + } ); + + it( 'should show the New Post page in Gutenberg', async () => { + expect( page.url() ).toEqual( expect.stringContaining( 'post-new.php' ) ); + // Should display the title. + const title = await page.$( '[placeholder="Add title"]' ); + expect( title ).not.toBeNull(); + // Should display the Preview button. + const postPreviewButton = await page.$( '.editor-post-preview.components-button' ); + expect( postPreviewButton ).not.toBeNull(); + // Should display the Post Formats UI. + const postFormatsUi = await page.$( '.editor-post-format' ); + expect( postFormatsUi ).not.toBeNull(); + } ); + + it( 'should have no history', async () => { + const undoButton = await page.$( '.editor-history__undo:not( :disabled )' ); + const redoButton = await page.$( '.editor-history__redo:not( :disabled )' ); + + expect( undoButton ).toBeNull(); + expect( redoButton ).toBeNull(); + } ); + + it( 'should focus the title if the title is empty', async () => { + // We need to remove the tips to make sure they aren't clicked/removed + // during our check of the title `textarea`'s focus. + await page.evaluate( () => { + return wp.data.dispatch( 'core/nux' ).disableTips(); + } ); + + // And then reload the page to ensure we get a new page that should + // autofocus the title, without any NUX tips. + await page.reload(); + + const activeElementClasses = await page.evaluate( () => { + return Object.values( document.activeElement.classList ); + } ); + const activeElementTagName = await page.evaluate( () => { + return document.activeElement.tagName.toLowerCase(); + } ); + + expect( activeElementClasses ).toContain( 'editor-post-title__input' ); + expect( activeElementTagName ).toEqual( 'textarea' ); + } ); + + it( 'should not focus the title if the title exists', async () => { + // Enter a title for this post. + await page.type( '.editor-post-title__input', 'Here is the title' ); + // Save the post as a draft. + await page.click( '.editor-post-save-draft' ); + await page.waitForSelector( '.editor-post-saved-state.is-saved' ); + // Reload the browser so a post is loaded with a title. + await page.reload(); + + const activeElementClasses = await page.evaluate( () => { + return Object.values( document.activeElement.classList ); + } ); + const activeElementTagName = await page.evaluate( () => { + return document.activeElement.tagName.toLowerCase(); + } ); + + expect( activeElementClasses ).not.toContain( 'editor-post-title__input' ); + // The document `body` should be the `activeElement`, because nothing is + // focused by default when a post already has a title. + expect( activeElementTagName ).toEqual( 'body' ); + } ); +} ); From 410f78a705a8aca2ca32a225383dc48cf47e0fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Est=C3=AAv=C3=A3o?= Date: Sat, 8 Sep 2018 10:05:33 +0100 Subject: [PATCH 2/6] React Native - Heading toolbar (#9570) * Update use of content size change event. * Show toolbar heading specific buttons. * Implement subscript in button. * Make viewport implementation empty in native. * Add entry point for React Native in api-fetch package * Share createLevelControl between React Web and React Native * Allow headings to have styles (strong, em, del). * Implement heading toolbar. --- packages/api-fetch/package.json | 1 + packages/block-library/src/heading/edit.js | 23 ++++--------- .../block-library/src/heading/edit.native.js | 17 ++++++++-- .../src/heading/heading-toolbar.js | 33 +++++++++++++++++++ .../components/src/button/index.native.js | 9 +++-- packages/viewport/src/index.native.js | 0 6 files changed, 61 insertions(+), 22 deletions(-) create mode 100644 packages/block-library/src/heading/heading-toolbar.js create mode 100644 packages/viewport/src/index.native.js diff --git a/packages/api-fetch/package.json b/packages/api-fetch/package.json index bf7f74e5c5f503..7296838893d7df 100644 --- a/packages/api-fetch/package.json +++ b/packages/api-fetch/package.json @@ -19,6 +19,7 @@ }, "main": "build/index.js", "module": "build-module/index.js", + "react-native": "src/index", "dependencies": { "@babel/runtime": "^7.0.0", "@wordpress/hooks": "file:../hooks", diff --git a/packages/block-library/src/heading/edit.js b/packages/block-library/src/heading/edit.js index 7612dcc8a8ace2..f0f02afb2579b1 100644 --- a/packages/block-library/src/heading/edit.js +++ b/packages/block-library/src/heading/edit.js @@ -1,14 +1,14 @@ /** - * External dependencies + * Internal dependencies */ -import { range } from 'lodash'; +import HeadingToolbar from './heading-toolbar'; /** * WordPress dependencies */ -import { __, sprintf } from '@wordpress/i18n'; +import { __ } from '@wordpress/i18n'; import { Fragment } from '@wordpress/element'; -import { PanelBody, Toolbar } from '@wordpress/components'; +import { PanelBody } from '@wordpress/components'; import { createBlock } from '@wordpress/blocks'; import { RichText, BlockControls, InspectorControls, AlignmentToolbar } from '@wordpress/editor'; @@ -23,26 +23,15 @@ export default function HeadingEdit( { const { align, content, level, placeholder } = attributes; const tagName = 'h' + level; - function createLevelControl( targetLevel ) { - return { - icon: 'heading', - // translators: %s: heading level e.g: "1", "2", "3" - title: sprintf( __( 'Heading %d' ), targetLevel ), - isActive: targetLevel === level, - onClick: () => setAttributes( { level: targetLevel } ), - subscript: String( targetLevel ), - }; - } - return ( - + setAttributes( { level: newLevel } ) } />

{ __( 'Level' ) }

- + setAttributes( { level: newLevel } ) } />

{ __( 'Text Alignment' ) }

+ setAttributes( { level: newLevel } ) } /> { - setAttributes( value ); + onChange={ ( event ) => { + // Create a React Tree from the new HTML + const newParaBlock = parse( `<${ tagName }>${ event.content }` )[ 0 ]; + setAttributes( { + ...this.props.attributes, + content: newParaBlock.attributes.content, + eventCount: event.eventCount, + } ); } } onContentSizeChange={ ( event ) => { setAttributes( { aztecHeight: event.aztecHeight } ); diff --git a/packages/block-library/src/heading/heading-toolbar.js b/packages/block-library/src/heading/heading-toolbar.js new file mode 100644 index 00000000000000..b01ff2c1c202a4 --- /dev/null +++ b/packages/block-library/src/heading/heading-toolbar.js @@ -0,0 +1,33 @@ +/** + * External dependencies + */ +import { range } from 'lodash'; + +/** + * WordPress dependencies + */ +import { __, sprintf } from '@wordpress/i18n'; +import { Component } from '@wordpress/element'; +import { Toolbar } from '@wordpress/components'; + +class HeadingToolbar extends Component { + createLevelControl( targetLevel, selectedLevel, onChange ) { + return { + icon: 'heading', + // translators: %s: heading level e.g: "1", "2", "3" + title: sprintf( __( 'Heading %d' ), targetLevel ), + isActive: targetLevel === selectedLevel, + onClick: () => onChange( targetLevel ), + subscript: String( targetLevel ), + }; + } + + render() { + const { minLevel, maxLevel, selectedLevel, onChange } = this.props; + return ( + this.createLevelControl( index, selectedLevel, onChange ) ) } /> + ); + } +} + +export default HeadingToolbar; diff --git a/packages/components/src/button/index.native.js b/packages/components/src/button/index.native.js index 1aaedd241c87ad..f4d2f2b9542a46 100644 --- a/packages/components/src/button/index.native.js +++ b/packages/components/src/button/index.native.js @@ -1,10 +1,10 @@ /** * External dependencies */ -import { TouchableOpacity } from 'react-native'; +import { TouchableOpacity, Text, View } from 'react-native'; export default function Button( props ) { - const { children, onClick, 'aria-label': ariaLabel, 'aria-pressed': ariaPressed } = props; + const { children, onClick, 'aria-label': ariaLabel, 'aria-pressed': ariaPressed, 'data-subscript': subscript } = props; return ( - { children } + + { children } + { subscript && ( { subscript } ) } + ); } diff --git a/packages/viewport/src/index.native.js b/packages/viewport/src/index.native.js new file mode 100644 index 00000000000000..e69de29bb2d1d6 From cbf29bf96069ea3857eb41a3b13cad7d035cf895 Mon Sep 17 00:00:00 2001 From: Jorge Date: Wed, 20 Jun 2018 16:25:17 +0100 Subject: [PATCH 3/6] . (+1 squashed commit) Squashed commits: [3dbd4cee8] Squashed commits: [c9daa7092] Improved toolbar order (+6 squashed commits) Squashed commits: [f202072bf] #9634 cherry picked tempararly; This commit should be deleted as soon as the other PR is merged [a09172386] Only use align wide & full [1bf29ba83] Corrected media labels [173a298a1] Revisions to align style; Show resizer only when media is set. [6a94b169d] Change sizing mechanism to apply width directly to the media elements (video/img) [12ede9625] Half image Block --- block-library/index.js | 2 + packages/block-library/src/editor.scss | 1 + packages/block-library/src/index.js | 2 + .../src/layout-half-media/edit.js | 154 ++++++++++++++++++ .../src/layout-half-media/editor.scss | 42 +++++ .../src/layout-half-media/index.js | 121 ++++++++++++++ .../src/layout-half-media/style.scss | 27 +++ packages/block-library/src/style.scss | 1 + packages/editor/src/components/index.js | 1 + .../src/components/media-container/index.js | 128 +++++++++++++++ 10 files changed, 479 insertions(+) create mode 100644 packages/block-library/src/layout-half-media/edit.js create mode 100644 packages/block-library/src/layout-half-media/editor.scss create mode 100644 packages/block-library/src/layout-half-media/index.js create mode 100644 packages/block-library/src/layout-half-media/style.scss create mode 100644 packages/editor/src/components/media-container/index.js diff --git a/block-library/index.js b/block-library/index.js index b76b3ce0bee70d..ddb7658ac92208 100644 --- a/block-library/index.js +++ b/block-library/index.js @@ -28,6 +28,7 @@ import * as column from '../packages/block-library/src/columns/column'; import * as coverImage from '../packages/block-library/src/cover-image'; import * as embed from '../packages/block-library/src/embed'; import * as file from '../packages/block-library/src/file'; +import * as halfMedia from '../packages/block-library/src/layout-half-media'; import * as latestComments from '../packages/block-library/src/latest-comments'; import * as latestPosts from '../packages/block-library/src/latest-posts'; import * as list from '../packages/block-library/src/list'; @@ -77,6 +78,7 @@ export const registerCoreBlocks = () => { ...embed.others, file, freeform, + halfMedia, html, latestComments, latestPosts, diff --git a/packages/block-library/src/editor.scss b/packages/block-library/src/editor.scss index c994183e6fcf03..8e12f1215d4333 100644 --- a/packages/block-library/src/editor.scss +++ b/packages/block-library/src/editor.scss @@ -12,6 +12,7 @@ @import "./image/editor.scss"; @import "./latest-comments/editor.scss"; @import "./latest-posts/editor.scss"; +@import "./layout-half-media/editor.scss"; @import "./list/editor.scss"; @import "./more/editor.scss"; @import "./nextpage/editor.scss"; diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index 1ed6b33539d4ca..bf661c224db1db 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -25,6 +25,7 @@ import * as column from './columns/column'; import * as coverImage from './cover-image'; import * as embed from './embed'; import * as file from './file'; +import * as halfMedia from './layout-half-media'; import * as latestComments from './latest-comments'; import * as latestPosts from './latest-posts'; import * as list from './list'; @@ -67,6 +68,7 @@ export const registerCoreBlocks = () => { ...embed.common, ...embed.others, file, + halfMedia, latestComments, latestPosts, more, diff --git a/packages/block-library/src/layout-half-media/edit.js b/packages/block-library/src/layout-half-media/edit.js new file mode 100644 index 00000000000000..6c2dc37cad1942 --- /dev/null +++ b/packages/block-library/src/layout-half-media/edit.js @@ -0,0 +1,154 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; +import { get } from 'lodash'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + BlockControls, + InnerBlocks, + InspectorControls, + PanelColorSettings, + withColors, + MediaContainer, +} from '@wordpress/editor'; +import { Component, Fragment } from '@wordpress/element'; +import { Toolbar } from '@wordpress/components'; + +/** + * Constants + */ +const ALLOWED_BLOCKS = [ 'core/button', 'core/paragraph', 'core/heading', 'core/list' ]; +const TEMPLATE = [ + [ 'core/paragraph', { fontSize: 'large', placeholder: 'Content…' } ], +]; +const MAX_MEDIA_WIDTH = 900; + +class ImageEdit extends Component { + constructor() { + super( ...arguments ); + + this.onSelectMedia = this.onSelectMedia.bind( this ); + this.onWidthChange = this.onWidthChange.bind( this ); + } + + onSelectMedia( media ) { + const { setAttributes } = this.props; + let newMediaWidth; + if ( media.width ) { + newMediaWidth = parseInt( media.width ); + } else { + const fullSizeWidth = get( media, [ 'sizes', 'full', 'width' ] ); + if ( fullSizeWidth ) { + newMediaWidth = parseInt( fullSizeWidth ); + } + } + + const mediaWidthProp = Number.isFinite( newMediaWidth ) ? + { mediaWidth: Math.min( newMediaWidth, MAX_MEDIA_WIDTH ) } : + {}; + + let mediaType; + // for media selections originated from a file upload. + if ( media.media_type ) { + if ( media.media_type === 'image' ) { + mediaType = 'image'; + } else { + // only images and videos are accepted so if the media_type is not an image we can assume it is a video. + // video contain the media type of 'file' in the object returned from the rest api. + mediaType = 'video'; + } + } else { // for media selections originated from existing files in the media library. + mediaType = media.type; + } + + setAttributes( { + mediaAlt: media.alt, + mediaId: media.id, + mediaType, + mediaUrl: media.url, + mediaWidth: newMediaWidth, + ...mediaWidthProp, + } ); + } + + onWidthChange( width ) { + const { setAttributes } = this.props; + + setAttributes( { + mediaWidth: width, + } ); + } + + renderMediaArea() { + const { attributes } = this.props; + const { mediaAlt, mediaId, mediaPosition, mediaType, mediaUrl, mediaWidth } = attributes; + + return ( + + ); + } + + render() { + const { attributes, backgroundColor, setAttributes, setBackgroundColor } = this.props; + const { mediaPosition } = attributes; + const className = classnames( 'wp-block-half-media', { + 'has-media-on-the-right': 'right' === mediaPosition, + [ backgroundColor.class ]: backgroundColor.class, + } ); + const style = { + backgroundColor: backgroundColor.value, + }; + const colorSettings = [ { + value: backgroundColor.value, + onChange: setBackgroundColor, + label: __( 'Background Color' ), + } ]; + const toolbarControls = [ { + icon: 'align-left', + title: __( 'Show media on left' ), + isActive: mediaPosition === 'left', + onClick: () => setAttributes( { mediaPosition: 'left' } ), + }, { + icon: 'align-left', + title: __( 'Show media on right' ), + isActive: mediaPosition === 'right', + onClick: () => setAttributes( { mediaPosition: 'right' } ), + } ]; + return ( + + + + + + + +
+ { this.renderMediaArea() } + +
+
+ ); + } +} + +export default withColors( 'backgroundColor' )( ImageEdit ); diff --git a/packages/block-library/src/layout-half-media/editor.scss b/packages/block-library/src/layout-half-media/editor.scss new file mode 100644 index 00000000000000..aa6b894769f018 --- /dev/null +++ b/packages/block-library/src/layout-half-media/editor.scss @@ -0,0 +1,42 @@ + +.block-library-half-media__resizer { + grid-area: half-media-media; + align-self: center; +} + +.wp-block-half-media .editor-inner-blocks { + word-break: break-word; + grid-area: half-media-content; + text-align: initial; +} + +.block-library-half-media__resize-handler { + display: none; + border-radius: 50%; + border: 2px solid $white; + width: 16px !important; + height: 16px !important; + position: absolute; + background: theme(primary); + top: calc(50% - 9px) !important; +} + +.editor-block-list__block.is-selected .block-library-half-media__resize-handler { + display: block; +} + +.wp-block-half-media > .editor-inner-blocks > .editor-block-list__layout > .editor-block-list__block { + max-width: unset; +} + +figure.block-library-half-media__media-container { + margin: 0; + height: 100%; + width: 100%; +} + +.block-library-half-media__media-container img, +.block-library-half-media__media-container video { + margin-bottom: -10px; + width: 100%; +} diff --git a/packages/block-library/src/layout-half-media/index.js b/packages/block-library/src/layout-half-media/index.js new file mode 100644 index 00000000000000..0f40840f54ab30 --- /dev/null +++ b/packages/block-library/src/layout-half-media/index.js @@ -0,0 +1,121 @@ +/** + * External dependencies + */ +import { noop } from 'lodash'; +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + InnerBlocks, + getColorClass, +} from '@wordpress/editor'; + +/** + * Internal dependencies + */ +import edit from './edit'; + +export const name = 'core/half-media'; + +export const settings = { + title: __( 'Half Media' ), + + icon: , + + category: 'layout', + + attributes: { + align: { + type: 'string', + default: 'wide', + }, + backgroundColor: { + type: 'string', + }, + customBackgroundColor: { + type: 'string', + }, + mediaAlt: { + type: 'string', + source: 'attribute', + selector: 'figure img', + attribute: 'alt', + default: '', + }, + mediaPosition: { + type: 'string', + default: 'left', + }, + mediaId: { + type: 'number', + }, + mediaUrl: { + type: 'string', + source: 'attribute', + selector: 'figure video,figure img', + attribute: 'src', + }, + mediaType: { + type: 'string', + }, + mediaWidth: { + type: 'number', + source: 'attribute', + selector: 'figure video,figure img', + attribute: 'width', + }, + }, + + supports: { + align: [ 'wide', 'full' ], + }, + + edit, + + save( { attributes } ) { + const { + backgroundColor, + customBackgroundColor, + mediaAlt, + mediaPosition, + mediaType, + mediaUrl, + mediaWidth, + } = attributes; + const mediaTypeRenders = { + image: () => { + return ( + { + ); + }, + video: () => { + return ( +