Exemple #1
0
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>
	);
}
Exemple #2
0
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 );
		} );
	} );
Exemple #5
0
			/** @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 );
			}
Exemple #6
0
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 ] )
	);
}
Exemple #7
0
	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 );
	} );
Exemple #8
0
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 );
	} );
Exemple #10
0
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>
	);
}
Exemple #11
0
				this.throttledForceUpdate = debounce( () => {
					this.Component = applyFilters( hookName, OriginalComponent );
					this.forceUpdate();
				}, ANIMATION_FRAME_PERIOD );
Exemple #12
0
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;
}