export function getSaveElement( blockType, attributes, innerBlocks = [] ) { let { save } = blockType; // Component classes are unsupported for save since serialization must // occur synchronously. For improved interoperability with higher-order // components which often return component class, emulate basic support. if ( save.prototype instanceof Component ) { const instance = new save( { attributes } ); save = instance.render.bind( instance ); } let element = save( { attributes, innerBlocks } ); if ( isObject( element ) && hasFilter( 'blocks.getSaveContent.extraProps' ) ) { /** * Filters the props applied to the block save result element. * * @param {Object} props Props applied to save element. * @param {WPBlockType} blockType Block type definition. * @param {Object} attributes Block attributes. */ const props = applyFilters( 'blocks.getSaveContent.extraProps', { ...element.props }, blockType, attributes ); if ( ! isShallowEqual( props, element.props ) ) { element = cloneElement( element, props ); } } /** * Filters the save result of a block during serialization. * * @param {WPElement} element Block save result. * @param {WPBlockType} blockType Block type definition. * @param {Object} attributes Block attributes. */ element = applyFilters( 'blocks.getSaveElement', element, blockType, attributes ); return ( <BlockContentProvider innerBlocks={ innerBlocks }> { element } </BlockContentProvider> ); }
export function getBlockMenuDefaultClassName( blockName ) { // Generated HTML classes for blocks follow the `editor-block-list-item-{name}` nomenclature. // Blocks provided by WordPress drop the prefixes 'core/' or 'core-' (used in 'core-embed/'). const className = 'editor-block-list-item-' + blockName.replace( /\//, '-' ).replace( /^core-/, '' ); return applyFilters( 'blocks.getBlockMenuDefaultClassName', className, blockName ); }
it( 'provides default completers if none are provided', () => { const result = applyFilters( 'editor.Autocomplete.completers', null, BLOCK_NAME ); /* * Assert structural equality because defaults are provided as a * list of cloned completers (and not referentially equal). */ expect( result ).toEqual( defaultAutocompleters ); } );
it( 'provides copies of defaults so they may be directly modified', () => { const result = applyFilters( 'editor.Autocomplete.completers', null, BLOCK_NAME ); result.forEach( ( completer, i ) => { const defaultCompleter = defaultAutocompleters[ i ]; expect( completer ).not.toBe( defaultCompleter ); expect( completer ).toEqual( defaultCompleter ); } ); } );
/** @inheritdoc */ constructor( props ) { super( props ); this.onHooksUpdated = this.onHooksUpdated.bind( this ); this.Component = applyFilters( hookName, OriginalComponent ); this.namespace = uniqueId( 'core/with-filters/component-' ); this.throttledForceUpdate = debounce( () => { this.Component = applyFilters( hookName, OriginalComponent ); this.forceUpdate(); }, ANIMATION_FRAME_PERIOD ); addAction( 'hookRemoved', this.namespace, this.onHooksUpdated ); addAction( 'hookAdded', this.namespace, this.onHooksUpdated ); }
export function isUnmodifiedDefaultBlock( block ) { const defaultBlockName = getDefaultBlockName(); if ( block.name !== defaultBlockName ) { return false; } const newDefaultBlock = createBlock( defaultBlockName ); const attributeKeys = applyFilters( 'blocks.isUnmodifiedDefaultBlock.attributes', [ ...keys( newDefaultBlock.attributes ), ...keys( block.attributes ), ] ); return every( attributeKeys, ( key ) => isEqual( newDefaultBlock.attributes[ key ], block.attributes[ key ] ) ); }
return transformationResults.map( ( result, index ) => { const transformedBlock = { ...result, // The first transformed block whose type matches the "destination" // type gets to keep the existing client ID of the first block. clientId: index === firstSwitchedBlock ? firstBlock.clientId : result.clientId, }; /** * Filters an individual transform result from block transformation. * All of the original blocks are passed, since transformations are * many-to-many, not one-to-one. * * @param {Object} transformedBlock The transformed block. * @param {Object[]} blocks Original blocks transformed. */ return applyFilters( 'blocks.switchToBlockType.transformedBlock', transformedBlock, blocks ); } );
export function registerPlugin( name, settings ) { if ( typeof settings !== 'object' ) { console.error( 'No settings object provided!' ); return null; } if ( typeof name !== 'string' ) { console.error( 'Plugin names must be strings.' ); return null; } if ( ! /^[a-z][a-z0-9-]*$/.test( name ) ) { console.error( 'Plugin names must include only lowercase alphanumeric characters or dashes, and start with a letter. Example: "my-plugin".' ); return null; } if ( plugins[ name ] ) { console.error( `Plugin "${ name }" is already registered.` ); } if ( ! isFunction( settings.render ) ) { console.error( 'The "render" property must be specified and must be a valid function.' ); return null; } settings.name = name; settings = applyFilters( 'plugins.registerPlugin', settings, name ); plugins[ settings.name ] = settings; doAction( 'plugins.pluginRegistered', settings, name ); return settings; }
it( 'does not provide default completers for a populated completer list', () => { const populatedList = [ {}, {} ]; const result = applyFilters( 'editor.Autocomplete.completers', populatedList, BLOCK_NAME ); // Assert referential equality because the list should be unchanged. expect( result ).toBe( populatedList ); } );
function PostFeaturedImage( { currentPostId, featuredImageId, onUpdateImage, onRemoveImage, media, postType } ) { const postLabel = get( postType, [ 'labels' ], {} ); const instructions = <p>{ __( 'To edit the featured image, you need permission to upload media.' ) }</p>; let mediaWidth, mediaHeight, mediaSourceUrl; if ( media ) { const mediaSize = applyFilters( 'editor.PostFeaturedImage.imageSize', 'post-thumbnail', media.id, currentPostId ); if ( has( media, [ 'media_details', 'sizes', mediaSize ] ) ) { mediaWidth = media.media_details.sizes[ mediaSize ].width; mediaHeight = media.media_details.sizes[ mediaSize ].height; mediaSourceUrl = media.media_details.sizes[ mediaSize ].source_url; } else { mediaWidth = media.media_details.width; mediaHeight = media.media_details.height; mediaSourceUrl = media.source_url; } } return ( <PostFeaturedImageCheck> <div className="editor-post-featured-image"> { !! featuredImageId && <MediaUploadCheck fallback={ instructions }> <MediaUpload title={ postLabel.featured_image || DEFAULT_FEATURE_IMAGE_LABEL } onSelect={ onUpdateImage } allowedTypes={ ALLOWED_MEDIA_TYPES } modalClass="editor-post-featured-image__media-modal" render={ ( { open } ) => ( <Button className="editor-post-featured-image__preview" onClick={ open } aria-label={ __( 'Edit or update the image' ) }> { media && <ResponsiveWrapper naturalWidth={ mediaWidth } naturalHeight={ mediaHeight } > <img src={ mediaSourceUrl } alt="" /> </ResponsiveWrapper> } { ! media && <Spinner /> } </Button> ) } value={ featuredImageId } /> </MediaUploadCheck> } { !! featuredImageId && media && ! media.isLoading && <MediaUploadCheck> <MediaUpload title={ postLabel.featured_image || DEFAULT_FEATURE_IMAGE_LABEL } onSelect={ onUpdateImage } allowedTypes={ ALLOWED_MEDIA_TYPES } modalClass="editor-post-featured-image__media-modal" render={ ( { open } ) => ( <Button onClick={ open } isDefault isLarge> { __( 'Replace image' ) } </Button> ) } /> </MediaUploadCheck> } { ! featuredImageId && <div> <MediaUploadCheck fallback={ instructions }> <MediaUpload title={ postLabel.featured_image || DEFAULT_FEATURE_IMAGE_LABEL } onSelect={ onUpdateImage } allowedTypes={ ALLOWED_MEDIA_TYPES } modalClass="editor-post-featured-image__media-modal" render={ ( { open } ) => ( <Button className="editor-post-featured-image__toggle" onClick={ open }> { postLabel.set_featured_image || DEFAULT_SET_FEATURE_IMAGE_LABEL } </Button> ) } /> </MediaUploadCheck> </div> } { !! featuredImageId && <MediaUploadCheck> <Button onClick={ onRemoveImage } isLink isDestructive> { postLabel.remove_featured_image || DEFAULT_REMOVE_FEATURE_IMAGE_LABEL } </Button> </MediaUploadCheck> } </div> </PostFeaturedImageCheck> ); }
this.throttledForceUpdate = debounce( () => { this.Component = applyFilters( hookName, OriginalComponent ); this.forceUpdate(); }, ANIMATION_FRAME_PERIOD );
export function registerBlockType( name, settings ) { settings = { name, ...get( window._wpBlocks, name ), ...settings, }; if ( typeof name !== 'string' ) { console.error( 'Block names must be strings.' ); return; } if ( ! /^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/.test( name ) ) { console.error( 'Block names must contain a namespace prefix, include only lowercase alphanumeric characters or dashes, and start with a letter. Example: my-plugin/my-custom-block' ); return; } if ( blocks[ name ] ) { console.error( 'Block "' + name + '" is already registered.' ); return; } settings = applyFilters( 'blocks.registerBlockType', settings, name ); if ( ! settings || ! isFunction( settings.save ) ) { console.error( 'The "save" property must be specified and must be a valid function.' ); return; } if ( 'edit' in settings && ! isFunction( settings.edit ) ) { console.error( 'The "edit" property must be a valid function.' ); return; } if ( 'keywords' in settings && settings.keywords.length > 3 ) { console.error( 'The block "' + name + '" can have a maximum of 3 keywords.' ); return; } if ( ! ( 'category' in settings ) ) { console.error( 'The block "' + name + '" must have a category.' ); return; } if ( 'category' in settings && ! some( categories, { slug: settings.category } ) ) { console.error( 'The block "' + name + '" must have a registered category.' ); return; } if ( ! ( 'title' in settings ) || settings.title === '' ) { console.error( 'The block "' + name + '" must have a title.' ); return; } if ( typeof settings.title !== 'string' ) { console.error( 'Block titles must be strings.' ); return; } if ( ! settings.icon ) { settings.icon = 'block-default'; } return blocks[ name ] = settings; }