transform: ( { value, citation, ...attrs } ) => { // If there is no quote content, use the citation as the // content of the resulting heading. A nonexistent citation // will result in an empty heading. if ( value === '<p></p>' ) { return createBlock( 'core/heading', { content: citation, } ); } const pieces = split( create( { html: value, multilineTag: 'p' } ), '\u2028' ); const quotePieces = pieces.slice( 1 ); return [ createBlock( 'core/heading', { content: toHTMLString( { value: pieces[ 0 ] } ), } ), createBlock( 'core/quote', { ...attrs, citation, value: toHTMLString( { value: quotePieces.length ? join( pieces.slice( 1 ), '\u2028' ) : create(), multilineTag: 'p', } ), } ), ]; },
transform: ( { value, citation } ) => { const paragraphs = []; if ( value && value !== '<p></p>' ) { paragraphs.push( ...split( create( { html: value, multilineTag: 'p' } ), '\u2028' ) .map( ( piece ) => createBlock( 'core/paragraph', { content: toHTMLString( { value: piece } ), } ) ) ); } if ( citation && citation !== '<p></p>' ) { paragraphs.push( createBlock( 'core/paragraph', { content: citation, } ) ); } if ( paragraphs.length === 0 ) { return createBlock( 'core/paragraph', { content: '', } ); } return paragraphs; },
transform: ( attributes ) => { const validImages = filter( attributes, ( { id, url } ) => id && url ); if ( validImages.length > 0 ) { return createBlock( 'core/gallery', { images: validImages.map( ( { id, url, alt, caption } ) => ( { id, url, alt, caption } ) ), } ); } return createBlock( 'core/gallery' ); },
transform: attributes => { const validImages = filter( attributes.images, ( { id, url } ) => id && url ); if ( validImages.length > 0 ) { return createBlock( `jetpack/${ name }`, { images: validImages.map( ( { id, url, alt } ) => ( { id, url, alt, } ) ), } ); } return createBlock( `jetpack/${ name }` ); },
( before, after, ...blocks ) => { if ( ! blocks.length ) { blocks.push( createBlock( 'core/paragraph' ) ); } if ( after.length ) { blocks.push( createBlock( 'core/list', { nodeName, values: after, } ) ); } setAttributes( { values: before } ); insertBlocksAfter( blocks ); } :
( obj ) => { if ( this.unmounting ) { return; } // Some plugins only return HTML with no type info, so default this to 'rich'. let { type = 'rich' } = obj; // If we got a provider name from the API, use it for the slug, otherwise we use the title, // because not all embed code gives us a provider name. const { html, provider_name: providerName } = obj; const providerNameSlug = kebabCase( toLower( '' !== providerName ? providerName : title ) ); // This indicates it's a WordPress embed, there aren't a set of URL patterns we can use to match WordPress URLs. if ( includes( html, 'class="wp-embedded-content" data-secret' ) ) { type = 'wp-embed'; // If this is not the WordPress embed block, transform it into one. if ( this.props.name !== 'core-embed/wordpress' ) { this.props.onReplace( createBlock( 'core-embed/wordpress', { url } ) ); return; } } if ( html ) { this.setState( { html, type, providerNameSlug } ); setAttributes( { type, providerNameSlug } ); } else if ( 'photo' === type ) { this.setState( { html: this.getPhotoHtml( obj ), type, providerNameSlug } ); setAttributes( { type, providerNameSlug } ); } else { // No html, no custom type that we support, so show the error state. this.setState( { error: true } ); } this.setState( { fetching: false } ); },
transform: ( { mediaAlt, mediaId, mediaUrl } ) => { return createBlock( 'core/image', { alt: mediaAlt, id: mediaId, url: mediaUrl, } ); },
transform: ( { src, id } ) => ( createBlock( 'core/media-text', { mediaId: id, mediaUrl: src, mediaType: 'video', } ) ),
/** * Split handler for RichText value, namely when content is pasted or the * user presses the Enter key. * * @param {?Array} before Optional before value, to be used as content * in place of what exists currently for the * block. If undefined, the block is deleted. * @param {?Array} after Optional after value, to be appended in a new * paragraph block to the set of blocks passed * as spread. * @param {...WPBlock} blocks Optional blocks inserted between the before * and after value blocks. */ splitBlock( before, after, ...blocks ) { const { attributes, insertBlocksAfter, setAttributes, onReplace, } = this.props; if ( after ) { // Append "After" content as a new paragraph block to the end of // any other blocks being inserted after the current paragraph. blocks.push( createBlock( name, { content: after } ) ); } if ( blocks.length && insertBlocksAfter ) { insertBlocksAfter( blocks ); } const { content } = attributes; if ( ! before ) { // If before content is omitted, treat as intent to delete block. onReplace( [] ); } else if ( content !== before ) { // Only update content if it has in-fact changed. In case that user // has created a new paragraph at end of an existing one, the value // of before will be strictly equal to the current content. setAttributes( { content: before } ); } }
onKeyDown( event ) { const { keyCode } = event; const { insertBlocksAfter } = this.props; if ( keyCode === ENTER ) { insertBlocksAfter( [ createBlock( getDefaultBlockName() ) ] ); } }
/** * Interprets keydown event intent to remove or insert after block if key * event occurs on wrapper node. This can occur when the block has no text * fields of its own, particularly after initial insertion, to allow for * easy deletion and continuous writing flow to add additional content. * * @param {KeyboardEvent} event Keydown event. */ deleteOrInsertAfterWrapper( event ) { const { keyCode, target } = event; if ( target !== this.wrapperNode || this.props.isLocked ) { return; } switch ( keyCode ) { case ENTER: // Insert default block after current block if enter and event // not already handled by descendant. this.props.onInsertBlocks( [ createBlock( 'core/paragraph' ), ], this.props.order + 1 ); event.preventDefault(); break; case BACKSPACE: case DELETE: // Remove block on backspace. const { uid, onRemove, previousBlockUid, onSelect } = this.props; onRemove( uid ); if ( previousBlockUid ) { onSelect( previousBlockUid, -1 ); } event.preventDefault(); break; } }
( before, after, ...blocks ) => { setAttributes( { content: before } ); insertBlocksAfter( [ ...blocks, createBlock( 'core/paragraph', { content: after } ), ] ); } :
beforeAll( () => { registerBlockType( 'core/heading', { category: 'common', title: 'Heading', edit: () => { }, save: () => { }, attributes: { level: { type: 'number', default: 2, }, content: { type: 'string', }, }, } ); registerBlockType( 'core/paragraph', { category: 'common', title: 'Paragraph', edit: () => { }, save: () => {}, } ); registerBlockType( 'core/columns', { category: 'common', title: 'Paragraph', edit: () => { }, save: () => {}, } ); paragraph = createBlock( 'core/paragraph' ); headingH1 = createBlock( 'core/heading', { content: 'Heading 1', level: 1, } ); headingParent = createBlock( 'core/heading', { content: 'Heading parent', level: 2, } ); headingChild = createBlock( 'core/heading', { content: 'Heading child', level: 3, } ); nestedHeading = createBlock( 'core/columns', undefined, [ headingChild ] ); } );
transform: ( { alt, url, id } ) => ( createBlock( 'core/media-text', { mediaAlt: alt, mediaId: id, mediaUrl: url, mediaType: 'image', } ) ),
transform: ( blockAttributes ) => { const items = blockAttributes.map( ( { content } ) => content ); const hasItems = ! items.every( isEmpty ); return createBlock( 'core/list', { nodeName: 'UL', values: hasItems ? items.map( ( content, index ) => <li key={ index }>{ content }</li> ) : [], } ); },
transform: ( { images } ) => { if ( images.length > 0 ) { return images.map( ( { id, url, alt } ) => createBlock( 'core/image', { id, url, alt } ) ); } return createBlock( 'core/image' ); },
export function insertDefaultBlock( attributes, rootUID, index ) { const block = createBlock( getDefaultBlockName(), attributes ); return { ...insertBlock( block, index, rootUID ), isProvisional: true, }; }
transform: ( { values } ) => { return createBlock( 'core/quote', { value: compact( ( values.length === 1 ? values : initial( values ) ) .map( ( value ) => get( value, [ 'props', 'children' ], null ) ) ) .map( ( children ) => ( { children: <p>{ children }</p> } ) ), citation: ( values.length === 1 ? undefined : [ get( last( values ), [ 'props', 'children' ] ) ] ), } ); },
onConfirmClick: () => { const { clientId } = ownProps; const { getBlockCount } = select( 'core/editor' ); const { insertBlock } = wpDispatch( 'core/editor' ); const nextChildPosition = getBlockCount( clientId ); const block = createBlock( 'tribe/tickets-item', {} ); insertBlock( block, nextChildPosition, clientId ); },
export const createUpgradedEmbedBlock = ( props, attributesFromPreview ) => { const { preview, name } = props; const { url } = props.attributes; if ( ! url ) { return; } const matchingBlock = findBlock( url ); // WordPress blocks can work on multiple sites, and so don't have patterns, // so if we're in a WordPress block, assume the user has chosen it for a WordPress URL. if ( WORDPRESS_EMBED_BLOCK !== name && DEFAULT_EMBED_BLOCK !== matchingBlock ) { // At this point, we have discovered a more suitable block for this url, so transform it. if ( name !== matchingBlock ) { return createBlock( matchingBlock, { url } ); } } if ( preview ) { const { html } = preview; // We can't match the URL for WordPress embeds, we have to check the HTML instead. if ( isFromWordPress( html ) ) { // If this is not the WordPress embed block, transform it into one. if ( WORDPRESS_EMBED_BLOCK !== name ) { return createBlock( WORDPRESS_EMBED_BLOCK, { url, // By now we have the preview, but when the new block first renders, it // won't have had all the attributes set, and so won't get the correct // type and it won't render correctly. So, we pass through the current attributes // here so that the initial render works when we switch to the WordPress // block. This only affects the WordPress block because it can't be // rendered in the usual Sandbox (it has a sandbox of its own) and it // relies on the preview to set the correct render type. ...attributesFromPreview, } ); } } } };
onInsertBlock: ( item ) => { const { insertionPoint, selectedBlock } = ownProps; const { index, rootUID, layout } = insertionPoint; const { name, initialAttributes } = item; const insertedBlock = createBlock( name, { ...initialAttributes, layout } ); if ( selectedBlock && isUnmodifiedDefaultBlock( selectedBlock ) ) { return dispatch( 'core/editor' ).replaceBlocks( selectedBlock.uid, insertedBlock ); } return dispatch( 'core/editor' ).insertBlock( insertedBlock, index, rootUID ); },
transform: ( attributes ) => { return createBlock( 'core/quote', { value: toHTMLString( { value: join( attributes.map( ( { content } ) => create( { html: content } ) ), '\u2028' ), multilineTag: 'p', } ), } ); },
...[ 2, 3, 4, 5, 6 ].map( ( level ) => ( { type: 'prefix', prefix: Array( level + 1 ).join( '#' ), transform( content ) { return createBlock( 'core/heading', { level, content, } ); }, } ) ),
transform: ( { value, citation } ) => { const items = value.map( ( p ) => get( p, [ 'children', 'props', 'children' ] ) ); if ( ! isEmpty( citation ) ) { items.push( citation ); } const hasItems = ! items.every( isEmpty ); return createBlock( 'core/list', { nodeName: 'UL', values: hasItems ? items.map( ( content, index ) => <li key={ index }>{ content }</li> ) : [], } ); },
( dispatch, ownProps ) => { return { ignoreInvalid() { const { block } = ownProps; const { name, attributes } = block; const nextBlock = createBlock( name, attributes ); dispatch( replaceBlock( block.uid, nextBlock ) ); }, switchToBlockType( blockType ) { const { block } = ownProps; if ( blockType && block ) { const nextBlock = createBlock( blockType.name, { content: block.originalContent, } ); dispatch( replaceBlock( block.uid, nextBlock ) ); } }, }; }
it( 'should move the nested block down', () => { const movedBlock = createBlock( 'core/test-block' ); const siblingBlock = createBlock( 'core/test-block' ); const wrapperBlock = createBlock( 'core/test-block', {}, [ movedBlock, siblingBlock ] ); const original = editor( undefined, { type: 'RESET_BLOCKS', blocks: [ wrapperBlock ], } ); const state = editor( original, { type: 'MOVE_BLOCKS_DOWN', uids: [ movedBlock.uid ], rootUID: wrapperBlock.uid, } ); expect( state.present.blockOrder ).toEqual( { '': [ wrapperBlock.uid ], [ wrapperBlock.uid ]: [ siblingBlock.uid, movedBlock.uid ], [ movedBlock.uid ]: [], [ siblingBlock.uid ]: [], } ); } );
it( 'should replace the nested block', () => { const nestedBlock = createBlock( 'core/test-block' ); const wrapperBlock = createBlock( 'core/test-block', {}, [ nestedBlock ] ); const replacementBlock = createBlock( 'core/test-block' ); const original = editor( undefined, { type: 'RESET_BLOCKS', blocks: [ wrapperBlock ], } ); const state = editor( original, { type: 'REPLACE_BLOCKS', uids: [ nestedBlock.uid ], blocks: [ replacementBlock ], } ); expect( state.present.blockOrder ).toEqual( { '': [ wrapperBlock.uid ], [ wrapperBlock.uid ]: [ replacementBlock.uid ], [ replacementBlock.uid ]: [], } ); } );
it( 'should cascade remove to include inner blocks', () => { const block = createBlock( 'core/test-block', {}, [ createBlock( 'core/test-block', {}, [ createBlock( 'core/test-block' ), ] ), ] ); const original = editor( undefined, { type: 'RESET_BLOCKS', blocks: [ block ], } ); const state = editor( original, { type: 'REMOVE_BLOCKS', uids: [ block.uid ], } ); expect( state.present.blocksByUid ).toEqual( {} ); expect( state.present.blockOrder ).toEqual( { '': [], } ); } );
withDispatch( ( dispatch, ownProps ) => { const { uid, rootUID, layout } = ownProps; return { onInsert( { name, initialAttributes } ) { const block = createBlock( name, { ...initialAttributes, layout } ); if ( uid ) { dispatch( 'core/editor' ).replaceBlocks( uid, block ); } else { dispatch( 'core/editor' ).insertBlock( block, undefined, rootUID ); } }, }; } ),
export default withDispatch( ( dispatch, { block } ) => { const { replaceBlock } = dispatch( 'core/editor' ); return { convertToHTML() { replaceBlock( block.uid, createBlock( 'core/html', { content: block.originalContent, } ) ); }, convertToBlocks() { replaceBlock( block.uid, rawHandler( { HTML: block.originalContent, mode: 'BLOCKS', } ) ); }, }; } )( InvalidBlockWarning );