diff --git a/image-filters.php b/image-filters.php index 0a26a96..2755f7d 100644 --- a/image-filters.php +++ b/image-filters.php @@ -26,16 +26,39 @@ define( 'IMAGE_FILTERS_URL', plugin_dir_url( __FILE__ ) ); /** * Bootstrap the plugin. * - * Block registration is wired in a follow-up task. This function exists so the - * activation hook can be added without needing a global-side-effect pattern. + * Registers the Filtered Image block from the build directory. The registered + * block type is static (its markup is produced by the JS save function). * * @return void */ function image_filters_bootstrap(): void { - // Block registration will be added in Task 5. + if ( ! function_exists( 'register_block_type' ) ) { + return; + } + + register_block_type( + IMAGE_FILTERS_DIR . 'build', + array( + 'render_callback' => 'image_filters_render_block', + ) + ); } add_action( 'plugins_loaded', 'image_filters_bootstrap' ); +/** + * Render callback for the Filtered Image block. + * + * Static blocks render their own markup via save.js. The render_callback + * is a placeholder for future server-side logic (e.g. dynamic alt text). + * + * @param array $attributes Block attributes. + * @param string $content Saved block content. + * @return string + */ +function image_filters_render_block( array $attributes, string $content = '' ): string { + return $content; +} + /** * Load plugin textdomain for translations. * diff --git a/src/block.json b/src/block.json new file mode 100644 index 0000000..3bd56bf --- /dev/null +++ b/src/block.json @@ -0,0 +1,65 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "ksolo/image-filter", + "version": "0.1.0", + "title": "Filtered Image", + "category": "media", + "icon": "image-filter", + "description": "An image with an Instagram-style filter applied.", + "keywords": [ "filter", "instagram", "image" ], + "textdomain": "image-filters", + "supports": { + "align": [ "left", "center", "right", "wide", "full" ], + "anchor": true, + "html": false, + "spacing": { "margin": true } + }, + "attributes": { + "filter": { + "type": "string", + "default": "normal" + }, + "intensity": { + "type": "number", + "default": 100 + }, + "imageId": { + "type": "number", + "default": 0 + }, + "imageUrl": { + "type": "string", + "default": "", + "source": "attribute", + "selector": "img", + "attribute": "src" + }, + "imageAlt": { + "type": "string", + "default": "", + "source": "attribute", + "selector": "img", + "attribute": "alt" + }, + "width": { + "type": "number" + }, + "height": { + "type": "number" + }, + "linkUrl": { + "type": "string", + "default": "" + }, + "caption": { + "type": "string", + "default": "", + "source": "html", + "selector": "figcaption" + } + }, + "editorScript": "file:./index.js", + "editorStyle": "file:./index.css", + "style": "file:./style-index.css" +} diff --git a/src/edit.js b/src/edit.js new file mode 100644 index 0000000..ce386ae --- /dev/null +++ b/src/edit.js @@ -0,0 +1,80 @@ +/** + * Editor component for the Filtered Image block. + * + * The MediaPlaceholder is the same UX as the core Image block. After an + * image is selected, the block renders the image with the filter class + * and intensity CSS custom property. The Inspector panel is mounted + * separately. + * + * @package ImageFilters + */ + +import { __ } from '@wordpress/i18n'; +import { useBlockProps, MediaPlaceholder, BlockControls } from '@wordpress/block-editor'; +import { Button, ToolbarGroup, ToolbarItem } from '@wordpress/components'; +import { useState } from '@wordpress/element'; + +export default function Edit( { attributes, setAttributes } ) { + const { filter, intensity, imageId, imageUrl, imageAlt, width, height } = attributes; + const blockProps = useBlockProps( { + className: `wp-block-ksolo-image-filter has-filter-${ filter }`, + style: { '--filter-intensity': String( intensity / 100 ) }, + } ); + + const [ isEditing, setIsEditing ] = useState( ! imageUrl ); + + if ( isEditing || ! imageUrl ) { + return ( +