diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index a7570bf590a39..8e38632113868 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -277,6 +277,45 @@ Display footnotes added to the page. ([Source](https://github.com/WordPress/gute - **Supports:** color (background, link, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~, ~~multiple~~, ~~reusable~~ - **Attributes:** +## Form + +A form. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/form)) + +- **Name:** core/form +- **Category:** common +- **Supports:** anchor, color (background, gradients, link, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~className~~ +- **Attributes:** action, email, method, submissionMethod + +## Input field + +The basic building block for forms. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/form-input)) + +- **Name:** core/form-input +- **Category:** common +- **Parent:** core/form +- **Supports:** anchor, spacing (margin), ~~reusable~~ +- **Attributes:** inlineLabel, label, name, placeholder, required, type, value, visibilityPermissions + +## Form Submission Notification + +Provide a notification message after the form has been submitted. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/form-submission-notification)) + +- **Name:** core/form-submission-notification +- **Category:** common +- **Parent:** core/form +- **Supports:** +- **Attributes:** type + +## Form submit button + +A submission button for forms. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/form-submit-button)) + +- **Name:** core/form-submit-button +- **Category:** common +- **Parent:** core/form +- **Supports:** +- **Attributes:** + ## Classic Use the classic WordPress editor. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/freeform)) diff --git a/lib/blocks.php b/lib/blocks.php index 537fa9ce4b45e..1794762b010db 100644 --- a/lib/blocks.php +++ b/lib/blocks.php @@ -22,6 +22,8 @@ function gutenberg_reregister_core_block_types() { 'column', 'columns', 'details', + 'form-input', + 'form-submit-button', 'group', 'html', 'list', @@ -66,6 +68,9 @@ function gutenberg_reregister_core_block_types() { 'comments.php' => 'core/comments', 'footnotes.php' => 'core/footnotes', 'file.php' => 'core/file', + 'form.php' => 'core/form', + 'form-input.php' => 'core/form-input', + 'form-submission-notification.php' => 'core/form-submission-notification', 'home-link.php' => 'core/home-link', 'image.php' => 'core/image', 'gallery.php' => 'core/gallery', diff --git a/lib/experimental/editor-settings.php b/lib/experimental/editor-settings.php index c09b5cde0f16b..00c929a3312a4 100644 --- a/lib/experimental/editor-settings.php +++ b/lib/experimental/editor-settings.php @@ -33,3 +33,15 @@ function gutenberg_enable_experiments() { } add_action( 'admin_init', 'gutenberg_enable_experiments' ); + +/** + * Sets a global JS variable used to trigger the availability of form & input blocks. + */ +function gutenberg_enable_form_input_blocks() { + $gutenberg_experiments = get_option( 'gutenberg-experiments' ); + if ( $gutenberg_experiments && array_key_exists( 'gutenberg-form-blocks', $gutenberg_experiments ) ) { + wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEnableFormBlocks = true', 'before' ); + } +} + +add_action( 'admin_init', 'gutenberg_enable_form_input_blocks' ); diff --git a/lib/experimental/kses-allowed-html.php b/lib/experimental/kses-allowed-html.php new file mode 100644 index 0000000000000..122faef7b4ca2 --- /dev/null +++ b/lib/experimental/kses-allowed-html.php @@ -0,0 +1,43 @@ + array(), + 'name' => array(), + 'value' => array(), + 'checked' => array(), + 'required' => array(), + 'aria-required' => array(), + 'class' => array(), + ); + + $allowedtags['label'] = array( + 'for' => array(), + 'class' => array(), + ); + + $allowedtags['textarea'] = array( + 'name' => array(), + 'required' => array(), + 'aria-required' => array(), + 'class' => array(), + ); + return $allowedtags; +} +add_filter( 'wp_kses_allowed_html', 'gutenberg_kses_allowed_html', 10, 2 ); diff --git a/lib/experiments-page.php b/lib/experiments-page.php index 133d968ba2cb7..90a88fd959288 100644 --- a/lib/experiments-page.php +++ b/lib/experiments-page.php @@ -78,6 +78,17 @@ function gutenberg_initialize_experiments_settings() { 'id' => 'gutenberg-color-randomizer', ) ); + add_settings_field( + 'gutenberg-form-blocks', + __( 'Form and input blocks ', 'gutenberg' ), + 'gutenberg_display_experiment_field', + 'gutenberg-experiments', + 'gutenberg_experiments_section', + array( + 'label' => __( 'Test new blocks to allow building forms (Warning: The new feature is not ready. You may experience UX issues that are being addressed)', 'gutenberg' ), + 'id' => 'gutenberg-form-blocks', + ) + ); add_settings_field( 'gutenberg-group-grid-variation', diff --git a/lib/load.php b/lib/load.php index d87c53081903e..a3a61407764b5 100644 --- a/lib/load.php +++ b/lib/load.php @@ -66,6 +66,8 @@ function gutenberg_is_experiment_enabled( $name ) { } require_once __DIR__ . '/experimental/class-gutenberg-rest-template-revision-count.php'; require_once __DIR__ . '/experimental/rest-api.php'; + + require_once __DIR__ . '/experimental/kses-allowed-html.php'; } require __DIR__ . '/experimental/editor-settings.php'; diff --git a/packages/block-library/src/editor.scss b/packages/block-library/src/editor.scss index 07c58599c5098..5f3d962ae7afa 100644 --- a/packages/block-library/src/editor.scss +++ b/packages/block-library/src/editor.scss @@ -16,6 +16,8 @@ @import "./details/editor.scss"; @import "./embed/editor.scss"; @import "./file/editor.scss"; +@import "./form-input/editor.scss"; +@import "./form-submission-notification/editor.scss"; @import "./freeform/editor.scss"; @import "./gallery/editor.scss"; @import "./group/editor.scss"; diff --git a/packages/block-library/src/form-input/block.json b/packages/block-library/src/form-input/block.json new file mode 100644 index 0000000000000..dbe182f03b499 --- /dev/null +++ b/packages/block-library/src/form-input/block.json @@ -0,0 +1,72 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "core/form-input", + "title": "Input field", + "category": "common", + "parent": [ "core/form" ], + "description": "The basic building block for forms.", + "keywords": [ "input", "form" ], + "textdomain": "default", + "icon": "forms", + "attributes": { + "type": { + "type": "string", + "default": "text" + }, + "name": { + "type": "string" + }, + "label": { + "type": "string", + "default": "Label", + "selector": ".wp-block-form-input__label-content", + "source": "html", + "__experimentalRole": "content" + }, + "inlineLabel": { + "type": "boolean", + "default": false + }, + "required": { + "type": "boolean", + "default": false, + "selector": ".wp-block-form-input__input", + "source": "attribute", + "attribute": "required" + }, + "placeholder": { + "type": "string", + "selector": ".wp-block-form-input__input", + "source": "attribute", + "attribute": "placeholder", + "__experimentalRole": "content" + }, + "value": { + "type": "string", + "default": "", + "selector": "input", + "source": "attribute", + "attribute": "value" + }, + "visibilityPermissions": { + "type": "string", + "default": "all" + } + }, + "supports": { + "anchor": true, + "reusable": false, + "spacing": { + "margin": [ "top", "bottom" ] + }, + "__experimentalBorder": { + "radius": true, + "__experimentalSkipSerialization": true, + "__experimentalDefaultControls": { + "radius": true + } + } + }, + "style": [ "wp-block-form-input" ] +} diff --git a/packages/block-library/src/form-input/edit.js b/packages/block-library/src/form-input/edit.js new file mode 100644 index 0000000000000..0742c22c22f42 --- /dev/null +++ b/packages/block-library/src/form-input/edit.js @@ -0,0 +1,151 @@ +/** + * External dependencies + */ +import classNames from 'classnames'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + InspectorControls, + RichText, + useBlockProps, + __experimentalUseBorderProps as useBorderProps, + __experimentalUseColorProps as useColorProps, +} from '@wordpress/block-editor'; +import { PanelBody, TextControl, CheckboxControl } from '@wordpress/components'; + +import { useRef } from '@wordpress/element'; + +function InputFieldBlock( { attributes, setAttributes, className } ) { + const { type, name, label, inlineLabel, required, placeholder, value } = + attributes; + const blockProps = useBlockProps(); + const ref = useRef(); + const TagName = type === 'textarea' ? 'textarea' : 'input'; + + const borderProps = useBorderProps( attributes ); + const colorProps = useColorProps( attributes ); + if ( ref.current ) { + ref.current.focus(); + } + + const controls = ( + <> + { 'hidden' !== type && ( + + + { 'checkbox' !== type && ( + { + setAttributes( { + inlineLabel: newVal, + } ); + } } + /> + ) } + { + setAttributes( { + required: newVal, + } ); + } } + /> + + + ) } + + { + setAttributes( { + name: newVal, + } ); + } } + help={ __( + 'Affects the "name" atribute of the input element, and is used as a name for the form submission results.' + ) } + /> + + + ); + + if ( 'hidden' === type ) { + return ( + <> + { controls } + + setAttributes( { value: event.target.value } ) + } + /> + + ); + } + + return ( +
+ { controls } + + + setAttributes( { label: newLabel } ) + } + aria-label={ label ? __( 'Label' ) : __( 'Empty label' ) } + data-empty={ label ? false : true } + placeholder={ __( 'Type the label for this input' ) } + /> + + setAttributes( { placeholder: event.target.value } ) + } + aria-required={ required } + style={ { + ...borderProps.style, + ...colorProps.style, + } } + /> + +
+ ); +} + +export default InputFieldBlock; diff --git a/packages/block-library/src/form-input/editor.scss b/packages/block-library/src/form-input/editor.scss new file mode 100644 index 0000000000000..2ac67e6615ed4 --- /dev/null +++ b/packages/block-library/src/form-input/editor.scss @@ -0,0 +1,24 @@ +.wp-block-form-input { + .is-input-hidden { + font-size: 0.85em; + opacity: 0.3; + border: 1px dashed; + padding: 0.5em; + box-sizing: border-box; + background: repeating-linear-gradient(45deg, transparent, transparent 5px, currentColor 5px, currentColor 6px); + + input[type="text"] { + background: transparent; + } + } + &.is-selected { + .is-input-hidden { + opacity: 1; + background: none; + + input[type="text"] { + background: unset; + } + } + } +} diff --git a/packages/block-library/src/form-input/index.js b/packages/block-library/src/form-input/index.js new file mode 100644 index 0000000000000..b700e0ade6ca7 --- /dev/null +++ b/packages/block-library/src/form-input/index.js @@ -0,0 +1,20 @@ +/** + * Internal dependencies + */ +import initBlock from '../utils/init-block'; +import edit from './edit'; +import metadata from './block.json'; +import save from './save'; +import variations from './variations'; + +const { name } = metadata; + +export { metadata, name }; + +export const settings = { + edit, + save, + variations, +}; + +export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/form-input/index.php b/packages/block-library/src/form-input/index.php new file mode 100644 index 0000000000000..f905c2bc6e19f --- /dev/null +++ b/packages/block-library/src/form-input/index.php @@ -0,0 +1,45 @@ + 'render_block_core_form_input', + ) + ); +} +add_action( 'init', 'register_block_core_form_input' ); diff --git a/packages/block-library/src/form-input/init.js b/packages/block-library/src/form-input/init.js new file mode 100644 index 0000000000000..79f0492c2cb2f --- /dev/null +++ b/packages/block-library/src/form-input/init.js @@ -0,0 +1,6 @@ +/** + * Internal dependencies + */ +import { init } from './'; + +export default init(); diff --git a/packages/block-library/src/form-input/save.js b/packages/block-library/src/form-input/save.js new file mode 100644 index 0000000000000..0cca31ca423ee --- /dev/null +++ b/packages/block-library/src/form-input/save.js @@ -0,0 +1,83 @@ +/** + * External dependencies + */ +import classNames from 'classnames'; +import removeAccents from 'remove-accents'; + +/** + * WordPress dependencies + */ +import { + RichText, + __experimentalGetBorderClassesAndStyles as getBorderClassesAndStyles, + __experimentalGetColorClassesAndStyles as getColorClassesAndStyles, +} from '@wordpress/block-editor'; + +/** + * Get the name attribute from a content string. + * + * @param {string} content The block content. + * + * @return {string} Returns the slug. + */ +const getNameFromLabel = ( content ) => { + const dummyElement = document.createElement( 'div' ); + dummyElement.innerHTML = content; + // Get the slug. + return ( + removeAccents( dummyElement.innerText ) + // Convert anything that's not a letter or number to a hyphen. + .replace( /[^\p{L}\p{N}]+/gu, '-' ) + // Convert to lowercase + .toLowerCase() + // Remove any remaining leading or trailing hyphens. + .replace( /(^-+)|(-+$)/g, '' ) + ); +}; + +export default function save( { attributes } ) { + const { type, name, label, inlineLabel, required, placeholder, value } = + attributes; + + const borderProps = getBorderClassesAndStyles( attributes ); + const colorProps = getColorClassesAndStyles( attributes ); + + const inputStyle = { + ...borderProps.style, + ...colorProps.style, + }; + + const inputClasses = classNames( + 'wp-block-form-input__input', + colorProps.className, + borderProps.className + ); + const TagName = type === 'textarea' ? 'textarea' : 'input'; + + if ( 'hidden' === type ) { + return ; + } + + /* eslint-disable jsx-a11y/label-has-associated-control */ + return ( + + ); + /* eslint-enable jsx-a11y/label-has-associated-control */ +} diff --git a/packages/block-library/src/form-input/style.scss b/packages/block-library/src/form-input/style.scss new file mode 100644 index 0000000000000..d45fc8d7f1f72 --- /dev/null +++ b/packages/block-library/src/form-input/style.scss @@ -0,0 +1,61 @@ +.wp-block-form-input__label { + width: 100%; + display: flex; + flex-direction: column; + gap: 0.25em; + margin-bottom: 0.5em; + + &.is-label-inline { + flex-direction: row; + gap: 0.5em; + align-items: center; + + .wp-block-form-input__label-content { + margin-bottom: 0.5em; + } + } + + /* + Small tweak to left-align the checkbox. + Even though `:has` is not currently supported in Firefox, this is a small tweak + and does not affect the functionality of the block or the user's experience. + There will be a minor inconsistency between browsers. However, it's more important to provide + a better experience for 80+% of users, until Firefox catches up and supports `:has`. + */ + &:has(input[type="checkbox"]) { + width: fit-content; + flex-direction: row-reverse; + } +} + +.wp-block-form-input__label-content { + width: fit-content; +} + +.wp-block-form-input__input { + padding: 0 0.5em; + font-size: 1em; + margin-bottom: 0.5em; + + &[type="text"], + &[type="password"], + &[type="date"], + &[type="datetime"], + &[type="datetime-local"], + &[type="email"], + &[type="month"], + &[type="number"], + &[type="search"], + &[type="tel"], + &[type="time"], + &[type="url"], + &[type="week"] { + min-height: 2em; + line-height: 2; + border: 1px solid; + } +} + +textarea.wp-block-form-input__input { + min-height: 10em; +} diff --git a/packages/block-library/src/form-input/variations.js b/packages/block-library/src/form-input/variations.js new file mode 100644 index 0000000000000..cc205feb89501 --- /dev/null +++ b/packages/block-library/src/form-input/variations.js @@ -0,0 +1,82 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +const variations = [ + { + name: 'text', + title: __( 'Text input' ), + icon: 'edit-page', + description: __( 'A generic text input.' ), + attributes: { type: 'text' }, + isDefault: true, + scope: [ 'inserter', 'transform' ], + isActive: ( blockAttributes ) => + ! blockAttributes?.type || blockAttributes?.type === 'text', + }, + { + name: 'textarea', + title: __( 'Textarea input' ), + icon: 'testimonial', + description: __( + 'A textarea input to allow entering multiple lines of text.' + ), + attributes: { type: 'textarea' }, + isDefault: true, + scope: [ 'inserter', 'transform' ], + isActive: ( blockAttributes ) => blockAttributes?.type === 'textarea', + }, + { + name: 'checkbox', + title: __( 'Checkbox input' ), + description: __( 'A simple checkbox input.' ), + icon: 'forms', + attributes: { type: 'checkbox', inlineLabel: true }, + isDefault: true, + scope: [ 'inserter', 'transform' ], + isActive: ( blockAttributes ) => blockAttributes?.type === 'checkbox', + }, + { + name: 'email', + title: __( 'Email input' ), + icon: 'email', + description: __( 'Used for email addresses.' ), + attributes: { type: 'email' }, + isDefault: true, + scope: [ 'inserter', 'transform' ], + isActive: ( blockAttributes ) => blockAttributes?.type === 'email', + }, + { + name: 'url', + title: __( 'URL input' ), + icon: 'admin-site', + description: __( 'Used for URLs.' ), + attributes: { type: 'url' }, + isDefault: true, + scope: [ 'inserter', 'transform' ], + isActive: ( blockAttributes ) => blockAttributes?.type === 'url', + }, + { + name: 'tel', + title: __( 'Telephone input' ), + icon: 'phone', + description: __( 'Used for phone numbers.' ), + attributes: { type: 'tel' }, + isDefault: true, + scope: [ 'inserter', 'transform' ], + isActive: ( blockAttributes ) => blockAttributes?.type === 'tel', + }, + { + name: 'number', + title: __( 'Number input' ), + icon: 'edit-page', + description: __( 'A numeric input.' ), + attributes: { type: 'number' }, + isDefault: true, + scope: [ 'inserter', 'transform' ], + isActive: ( blockAttributes ) => blockAttributes?.type === 'number', + }, +]; + +export default variations; diff --git a/packages/block-library/src/form-submission-notification/block.json b/packages/block-library/src/form-submission-notification/block.json new file mode 100644 index 0000000000000..62284d35ab4dd --- /dev/null +++ b/packages/block-library/src/form-submission-notification/block.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "core/form-submission-notification", + "title": "Form Submission Notification", + "category": "common", + "parent": [ "core/form" ], + "description": "Provide a notification message after the form has been submitted.", + "keywords": [ "form", "feedback", "notification", "message" ], + "textdomain": "default", + "icon": "feedback", + "attributes": { + "type": { + "type": "string", + "default": "success" + } + } +} diff --git a/packages/block-library/src/form-submission-notification/edit.js b/packages/block-library/src/form-submission-notification/edit.js new file mode 100644 index 0000000000000..4425a4d9147df --- /dev/null +++ b/packages/block-library/src/form-submission-notification/edit.js @@ -0,0 +1,63 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + InnerBlocks, + useBlockProps, + useInnerBlocksProps, + store as blockEditorStore, +} from '@wordpress/block-editor'; +import { useSelect } from '@wordpress/data'; + +/** + * External dependencies + */ +import classnames from 'classnames'; + +const TEMPLATE = [ + [ + 'core/paragraph', + { + content: __( + "Enter the message you wish displayed for form submission error/success, and select the type of the message (success/error) from the block's options." + ), + }, + ], +]; + +const Edit = ( { attributes, clientId } ) => { + const { type } = attributes; + const blockProps = useBlockProps( { + className: classnames( 'wp-block-form-submission-notification', { + [ `form-notification-type-${ type }` ]: type, + } ), + } ); + + const { hasInnerBlocks } = useSelect( + ( select ) => { + const { getBlock } = select( blockEditorStore ); + const block = getBlock( clientId ); + return { + hasInnerBlocks: !! ( block && block.innerBlocks.length ), + }; + }, + [ clientId ] + ); + + const innerBlocksProps = useInnerBlocksProps( blockProps, { + template: TEMPLATE, + renderAppender: hasInnerBlocks + ? undefined + : InnerBlocks.ButtonBlockAppender, + } ); + + return ( +
+ ); +}; +export default Edit; diff --git a/packages/block-library/src/form-submission-notification/editor.scss b/packages/block-library/src/form-submission-notification/editor.scss new file mode 100644 index 0000000000000..a8d3f4e3d9263 --- /dev/null +++ b/packages/block-library/src/form-submission-notification/editor.scss @@ -0,0 +1,45 @@ +.wp-block-form-submission-notification { + > * { + opacity: 0.25; + border: 1px dashed; + box-sizing: border-box; + background: repeating-linear-gradient(45deg, transparent, transparent 5px, currentColor 5px, currentColor 6px); + } + + &.is-selected, + &:has(.is-selected) { + > * { + opacity: 1; + background: none; + } + + &::after { + display: none !important; + } + } + + &::after { + display: flex; + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + justify-content: center; + align-items: center; + font-size: 1.1em; + // font-weight: bold; + } + + &.form-notification-type-success { + &::after { + content: attr(data-message-success); + } + } + + &.form-notification-type-error { + &::after { + content: attr(data-message-error); + } + } +} diff --git a/packages/block-library/src/form-submission-notification/index.js b/packages/block-library/src/form-submission-notification/index.js new file mode 100644 index 0000000000000..67c359374eec1 --- /dev/null +++ b/packages/block-library/src/form-submission-notification/index.js @@ -0,0 +1,26 @@ +/** + * WordPress dependencies + */ +import { group as icon } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import initBlock from '../utils/init-block'; +import edit from './edit'; +import metadata from './block.json'; +import save from './save'; +import variations from './variations'; + +const { name } = metadata; + +export { metadata, name }; + +export const settings = { + icon, + edit, + save, + variations, +}; + +export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/form-submission-notification/index.php b/packages/block-library/src/form-submission-notification/index.php new file mode 100644 index 0000000000000..0a57866f37edf --- /dev/null +++ b/packages/block-library/src/form-submission-notification/index.php @@ -0,0 +1,48 @@ + 'render_block_core_form_submission_notification', + ) + ); +} +add_action( 'init', 'register_block_core_form_submission_notification' ); diff --git a/packages/block-library/src/form-submission-notification/init.js b/packages/block-library/src/form-submission-notification/init.js new file mode 100644 index 0000000000000..a7f22ef02d640 --- /dev/null +++ b/packages/block-library/src/form-submission-notification/init.js @@ -0,0 +1,6 @@ +/** + * Internal dependencies + */ +import { init } from '.'; + +export default init(); diff --git a/packages/block-library/src/form-submission-notification/save.js b/packages/block-library/src/form-submission-notification/save.js new file mode 100644 index 0000000000000..7b3c6c895c192 --- /dev/null +++ b/packages/block-library/src/form-submission-notification/save.js @@ -0,0 +1,28 @@ +/** + * WordPress dependencies + */ +import { useInnerBlocksProps, useBlockProps } from '@wordpress/block-editor'; + +/** + * External dependencies + */ +import classnames from 'classnames'; + +export default function save( { attributes } ) { + const { type } = attributes; + + return ( +
+ ); +} diff --git a/packages/block-library/src/form-submission-notification/variations.js b/packages/block-library/src/form-submission-notification/variations.js new file mode 100644 index 0000000000000..b154a26e5e6a4 --- /dev/null +++ b/packages/block-library/src/form-submission-notification/variations.js @@ -0,0 +1,59 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +const variations = [ + { + name: 'form-submission-success', + title: __( 'Form submission success' ), + description: __( 'Success message for form submissions' ), + attributes: { + type: 'success', + }, + isDefault: true, + innerBlocks: [ + [ + 'core/paragraph', + { + content: __( 'Your form has been submitted successfully.' ), + backgroundColor: '#00D084', + textColor: '#000000', + style: { + elements: { link: { color: { text: '#000000' } } }, + }, + }, + ], + ], + scope: [ 'inserter', 'transform' ], + isActive: ( blockAttributes ) => + ! blockAttributes?.type || blockAttributes?.type === 'success', + }, + { + name: 'form-submission-error', + title: __( 'Form submission error' ), + description: __( 'Error/failure message for form submissions' ), + attributes: { + type: 'error', + }, + isDefault: false, + innerBlocks: [ + [ + 'core/paragraph', + { + content: __( 'There was an error submitting your form.' ), + backgroundColor: '#CF2E2E', + textColor: '#FFFFFF', + style: { + elements: { link: { color: { text: '#FFFFFF' } } }, + }, + }, + ], + ], + scope: [ 'inserter', 'transform' ], + isActive: ( blockAttributes ) => + ! blockAttributes?.type || blockAttributes?.type === 'error', + }, +]; + +export default variations; diff --git a/packages/block-library/src/form-submit-button/block.json b/packages/block-library/src/form-submit-button/block.json new file mode 100644 index 0000000000000..faa938e9bbc24 --- /dev/null +++ b/packages/block-library/src/form-submit-button/block.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "core/form-submit-button", + "title": "Form submit button", + "category": "common", + "icon": "button", + "parent": [ "core/form" ], + "description": "A submission button for forms.", + "keywords": [ "submit", "button", "form" ], + "textdomain": "default", + "style": [ "wp-block-form-submit-button" ] +} diff --git a/packages/block-library/src/form-submit-button/edit.js b/packages/block-library/src/form-submit-button/edit.js new file mode 100644 index 0000000000000..f8d7a65c6877a --- /dev/null +++ b/packages/block-library/src/form-submit-button/edit.js @@ -0,0 +1,33 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor'; + +const TEMPLATE = [ + [ + 'core/buttons', + {}, + [ + [ + 'core/button', + { + text: __( 'Submit' ), + tagName: 'button', + }, + ], + ], + ], +]; +const Edit = () => { + const blockProps = useBlockProps(); + const innerBlocksProps = useInnerBlocksProps( blockProps, { + allowedBlocks: TEMPLATE, + template: TEMPLATE, + templateLock: 'all', + } ); + return ( +
+ ); +}; +export default Edit; diff --git a/packages/block-library/src/form-submit-button/index.js b/packages/block-library/src/form-submit-button/index.js new file mode 100644 index 0000000000000..4c60b5f5c2063 --- /dev/null +++ b/packages/block-library/src/form-submit-button/index.js @@ -0,0 +1,18 @@ +/** + * Internal dependencies + */ +import initBlock from '../utils/init-block'; +import edit from './edit'; +import metadata from './block.json'; +import save from './save'; + +const { name } = metadata; + +export { metadata, name }; + +export const settings = { + edit, + save, +}; + +export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/form-submit-button/init.js b/packages/block-library/src/form-submit-button/init.js new file mode 100644 index 0000000000000..79f0492c2cb2f --- /dev/null +++ b/packages/block-library/src/form-submit-button/init.js @@ -0,0 +1,6 @@ +/** + * Internal dependencies + */ +import { init } from './'; + +export default init(); diff --git a/packages/block-library/src/form-submit-button/save.js b/packages/block-library/src/form-submit-button/save.js new file mode 100644 index 0000000000000..ba361ebe9db20 --- /dev/null +++ b/packages/block-library/src/form-submit-button/save.js @@ -0,0 +1,14 @@ +/** + * WordPress dependencies + */ +import { useBlockProps, InnerBlocks } from '@wordpress/block-editor'; + +const Save = () => { + const blockProps = useBlockProps.save(); + return ( +
+ +
+ ); +}; +export default Save; diff --git a/packages/block-library/src/form-submit-button/style.scss b/packages/block-library/src/form-submit-button/style.scss new file mode 100644 index 0000000000000..400016b1618d4 --- /dev/null +++ b/packages/block-library/src/form-submit-button/style.scss @@ -0,0 +1,3 @@ +.wp-block-form-submit-wrapper { + margin-bottom: 0.5em; +} diff --git a/packages/block-library/src/form/block.json b/packages/block-library/src/form/block.json new file mode 100644 index 0000000000000..951d1dce4224e --- /dev/null +++ b/packages/block-library/src/form/block.json @@ -0,0 +1,59 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "core/form", + "title": "Form", + "category": "common", + "description": "A form.", + "keywords": [ "container", "wrapper", "row", "section" ], + "textdomain": "default", + "icon": "feedback", + "attributes": { + "submissionMethod": { + "type": "string", + "default": "email" + }, + "method": { + "type": "string", + "default": "post" + }, + "action": { + "type": "string" + }, + "email": { + "type": "string" + } + }, + "supports": { + "anchor": true, + "className": false, + "color": { + "gradients": true, + "link": true, + "__experimentalDefaultControls": { + "background": true, + "text": true, + "link": true + } + }, + "spacing": { + "margin": true, + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true, + "__experimentalFontFamily": true, + "__experimentalTextDecoration": true, + "__experimentalFontStyle": true, + "__experimentalFontWeight": true, + "__experimentalLetterSpacing": true, + "__experimentalTextTransform": true, + "__experimentalDefaultControls": { + "fontSize": true + } + }, + "__experimentalSelector": "form" + }, + "viewScript": "file:./view.min.js" +} diff --git a/packages/block-library/src/form/edit.js b/packages/block-library/src/form/edit.js new file mode 100644 index 0000000000000..d8ae9ea5e7553 --- /dev/null +++ b/packages/block-library/src/form/edit.js @@ -0,0 +1,179 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + InnerBlocks, + useBlockProps, + useInnerBlocksProps, + InspectorControls, + store as blockEditorStore, +} from '@wordpress/block-editor'; +import { TextControl, SelectControl, PanelBody } from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { + formSubmissionNotificationSuccess, + formSubmissionNotificationError, +} from './utils.js'; + +const ALLOWED_BLOCKS = [ + 'core/paragraph', + 'core/heading', + 'core/form-input', + 'core/form-submit-button', + 'core/form-submission-notification', +]; + +const TEMPLATE = [ + formSubmissionNotificationSuccess, + formSubmissionNotificationError, + [ + 'core/form-input', + { + type: 'text', + label: __( 'Name' ), + required: true, + }, + ], + [ + 'core/form-input', + { + type: 'email', + label: __( 'Email' ), + required: true, + }, + ], + [ + 'core/form-input', + { + type: 'textarea', + label: __( 'Comment' ), + required: true, + }, + ], + [ 'core/form-submit-button', {} ], +]; + +const Edit = ( { attributes, setAttributes, clientId } ) => { + const { action, method, email, submissionMethod } = attributes; + const blockProps = useBlockProps(); + + const { hasInnerBlocks } = useSelect( + ( select ) => { + const { getBlock } = select( blockEditorStore ); + const block = getBlock( clientId ); + return { + hasInnerBlocks: !! ( block && block.innerBlocks.length ), + }; + }, + [ clientId ] + ); + + const innerBlocksProps = useInnerBlocksProps( blockProps, { + allowedBlocks: ALLOWED_BLOCKS, + template: TEMPLATE, + renderAppender: hasInnerBlocks + ? undefined + : InnerBlocks.ButtonBlockAppender, + } ); + + return ( + <> + + + + setAttributes( { submissionMethod: value } ) + } + help={ + submissionMethod === 'custom' + ? __( + 'Select the method to use for form submissions. Additional options for the "custom" mode can be found in the "Andvanced" section.' + ) + : __( + 'Select the method to use for form submissions.' + ) + } + /> + { submissionMethod === 'email' && ( + { + setAttributes( { email: value } ); + setAttributes( { + action: `mailto:${ value }`, + } ); + setAttributes( { method: 'post' } ); + } } + help={ __( + 'The email address where form submissions will be sent. Separate multiple email addresses with a comma.' + ) } + /> + ) } + + + { submissionMethod !== 'email' && ( + + + setAttributes( { method: value } ) + } + help={ __( + 'Select the method to use for form submissions.' + ) } + /> + { + setAttributes( { + action: newVal, + } ); + } } + help={ __( + 'The URL where the form should be submitted.' + ) } + /> + + ) } +
+ + ); +}; +export default Edit; diff --git a/packages/block-library/src/form/index.js b/packages/block-library/src/form/index.js new file mode 100644 index 0000000000000..b700e0ade6ca7 --- /dev/null +++ b/packages/block-library/src/form/index.js @@ -0,0 +1,20 @@ +/** + * Internal dependencies + */ +import initBlock from '../utils/init-block'; +import edit from './edit'; +import metadata from './block.json'; +import save from './save'; +import variations from './variations'; + +const { name } = metadata; + +export { metadata, name }; + +export const settings = { + edit, + save, + variations, +}; + +export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/form/index.php b/packages/block-library/src/form/index.php new file mode 100644 index 0000000000000..0dbbaf6838740 --- /dev/null +++ b/packages/block-library/src/form/index.php @@ -0,0 +1,218 @@ +next_tag( 'form' ); + + // Get the action for this form. + $action = ''; + if ( isset( $attributes['action'] ) ) { + $action = str_replace( + array( '{SITE_URL}', '{ADMIN_URL}' ), + array( site_url(), admin_url() ), + $attributes['action'] + ); + } + $processed_content->set_attribute( 'action', esc_attr( $action ) ); + + // Add the method attribute. If it is not set, default to `post`. + $method = empty( $attributes['method'] ) ? 'post' : $attributes['method']; + $processed_content->set_attribute( 'method', $method ); + + $extra_fields = apply_filters( 'render_block_core_form_extra_fields', '', $attributes ); + + return str_replace( + '
', + $extra_fields . '', + $processed_content->get_updated_html() + ); +} + +/** + * Additional data to add to the view.js script for this block. + */ +function block_core_form_view_script() { + if ( ! gutenberg_is_experiment_enabled( 'gutenberg-form-blocks' ) ) { + return; + } + + wp_localize_script( + 'wp-block-form-view', + 'wpBlockFormSettings', + array( + 'nonce' => wp_create_nonce( 'wp-block-form' ), + 'ajaxUrl' => admin_url( 'admin-ajax.php' ), + 'action' => 'wp_block_form_email_submit', + ) + ); +} +add_action( 'wp_enqueue_scripts', 'block_core_form_view_script' ); + +/** + * Adds extra fields to the form. + * + * If the form is a comment form, adds the post ID as a hidden field, + * to allow the comment to be associated with the post. + * + * @param string $extra_fields The extra fields. + * @param array $attributes The block attributes. + * + * @return string The extra fields. + */ +function block_core_form_extra_fields_comment_form( $extra_fields, $attributes ) { + if ( ! empty( $attributes['action'] ) && str_ends_with( $attributes['action'], '/wp-comments-post.php' ) ) { + $extra_fields .= ''; + } + return $extra_fields; +} +add_filter( 'render_block_core_form_extra_fields', 'block_core_form_extra_fields_comment_form', 10, 2 ); + +/** + * Sends an email if the form is a contact form. + * + * @return void + */ +function block_core_form_send_email() { + check_ajax_referer( 'wp-block-form' ); + + // Get the POST data. + $params = wp_unslash( $_POST ); + // Start building the email content. + $content = sprintf( + /* translators: %s: The request URI. */ + __( 'Form submission from %1$s', 'gutenberg' ) . '
', + '' . get_bloginfo( 'name' ) . '' + ); + + $skip_fields = array( 'formAction', '_ajax_nonce', 'action' ); + foreach ( $params as $key => $value ) { + if ( in_array( $key, $skip_fields, true ) ) { + continue; + } + $content .= sanitize_key( $key ) . ': ' . wp_kses_post( $value ) . '
'; + } + + // Filter the email content. + $content = apply_filters( 'render_block_core_form_email_content', $content, $params ); + + // Send the email. + $result = wp_mail( + str_replace( 'mailto:', '', $params['wp-email-address'] ), + __( 'Form submission', 'gutenberg' ), + $content + ); + + if ( ! $result ) { + wp_send_json_error( $result ); + } + wp_send_json_success( $result ); +} +add_action( 'wp_ajax_wp_block_form_email_submit', 'block_core_form_send_email' ); +add_action( 'wp_ajax_nopriv_wp_block_form_email_submit', 'block_core_form_send_email' ); + +/** + * Send the data export/remove request if the form is a privacy-request form. + * + * @return void + */ +function block_core_form_privacy_form() { + // Get the POST data. + $params = wp_unslash( $_POST ); + + // Bail early if not a form submission, or if the nonce is not valid. + if ( empty( $params['wp-action'] ) + || 'wp_privacy_send_request' !== $params['wp-action'] + || empty( $params['wp-privacy-request'] ) + || '1' !== $params['wp-privacy-request'] + || empty( $params['email'] ) + ) { + return; + } + + // Get the request types. + $request_types = _wp_privacy_action_request_types(); + $requests_found = array(); + foreach ( $request_types as $request_type ) { + if ( ! empty( $params[ $request_type ] ) ) { + $requests_found[] = $request_type; + } + } + + // Bail early if no requests were found. + if ( empty( $requests_found ) ) { + return; + } + + // Process the requests. + $actions_errored = array(); + $actions_performed = array(); + foreach ( $requests_found as $action_name ) { + // Get the request ID. + $request_id = wp_create_user_request( $params['email'], $action_name ); + + // Bail early if the request ID is invalid. + if ( is_wp_error( $request_id ) ) { + $actions_errored[] = $action_name; + continue; + } + + // Send the request email. + wp_send_user_request( $request_id ); + $actions_performed[] = $action_name; + } + + /** + * Determine whether the core/form-submission-notification block should be shown. + * + * @param bool $show Whether to show the core/form-submission-notification block. + * @param array $attributes The block attributes. + * + * @return bool Whether to show the core/form-submission-notification block. + */ + $show_notification = static function ( $show, $attributes ) use ( $actions_performed, $actions_errored ) { + switch ( $attributes['type'] ) { + case 'success': + return ! empty( $actions_performed ) && empty( $actions_errored ); + + case 'error': + return ! empty( $actions_errored ); + + default: + return $show; + } + }; + + // Add filter to show the core/form-submission-notification block. + add_filter( 'show_form_submission_notification_block', $show_notification, 10, 2 ); +} +add_action( 'wp', 'block_core_form_privacy_form' ); + +/** + * Registers the `core/form` block on server. + */ +function register_block_core_form() { + if ( ! gutenberg_is_experiment_enabled( 'gutenberg-form-blocks' ) ) { + return; + } + register_block_type_from_metadata( + __DIR__ . '/form', + array( + 'render_callback' => 'render_block_core_form', + ) + ); +} +add_action( 'init', 'register_block_core_form' ); diff --git a/packages/block-library/src/form/init.js b/packages/block-library/src/form/init.js new file mode 100644 index 0000000000000..79f0492c2cb2f --- /dev/null +++ b/packages/block-library/src/form/init.js @@ -0,0 +1,6 @@ +/** + * Internal dependencies + */ +import { init } from './'; + +export default init(); diff --git a/packages/block-library/src/form/save.js b/packages/block-library/src/form/save.js new file mode 100644 index 0000000000000..a824fc076d2ac --- /dev/null +++ b/packages/block-library/src/form/save.js @@ -0,0 +1,20 @@ +/** + * WordPress dependencies + */ +import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; + +const Save = ( { attributes } ) => { + const blockProps = useBlockProps.save(); + const { submissionMethod } = attributes; + + return ( +
+ + + ); +}; +export default Save; diff --git a/packages/block-library/src/form/utils.js b/packages/block-library/src/form/utils.js new file mode 100644 index 0000000000000..e541f34bbc887 --- /dev/null +++ b/packages/block-library/src/form/utils.js @@ -0,0 +1,39 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +export const formSubmissionNotificationSuccess = [ + 'core/form-submission-notification', + { + type: 'success', + }, + [ + [ + 'core/paragraph', + { + content: + '' + + __( 'Your form has been submitted successfully' ) + + '', + }, + ], + ], +]; +export const formSubmissionNotificationError = [ + 'core/form-submission-notification', + { + type: 'error', + }, + [ + [ + 'core/paragraph', + { + content: + '' + + __( 'There was an error submitting your form.' ) + + '', + }, + ], + ], +]; diff --git a/packages/block-library/src/form/variations.js b/packages/block-library/src/form/variations.js new file mode 100644 index 0000000000000..da3fcbbf03942 --- /dev/null +++ b/packages/block-library/src/form/variations.js @@ -0,0 +1,139 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +/** + * Internal dependencies + */ +import { + formSubmissionNotificationSuccess, + formSubmissionNotificationError, +} from './utils.js'; + +const variations = [ + { + name: 'comment-form', + title: __( 'Experimental Comment form' ), + description: __( 'A comment form for posts and pages.' ), + attributes: { + submissionMethod: 'custom', + action: '{SITE_URL}/wp-comments-post.php', + method: 'post', + anchor: 'comment-form', + }, + isDefault: false, + innerBlocks: [ + [ + 'core/form-input', + { + type: 'text', + name: 'author', + label: __( 'Name' ), + required: true, + visibilityPermissions: 'logged-out', + }, + ], + [ + 'core/form-input', + { + type: 'email', + name: 'email', + label: __( 'Email' ), + required: true, + visibilityPermissions: 'logged-out', + }, + ], + [ + 'core/form-input', + { + type: 'textarea', + name: 'comment', + label: __( 'Comment' ), + required: true, + visibilityPermissions: 'all', + }, + ], + [ 'core/form-submit-button', {} ], + ], + scope: [ 'inserter', 'transform' ], + isActive: ( blockAttributes ) => + ! blockAttributes?.type || blockAttributes?.type === 'text', + }, + { + name: 'wp-privacy-form', + title: __( 'Experimental privacy request form' ), + keywords: [ 'GDPR' ], + description: __( 'A form torequest data exports and/or deletion.' ), + attributes: { + submissionMethod: 'custom', + action: '', + method: 'post', + anchor: 'gdpr-form', + }, + isDefault: false, + innerBlocks: [ + formSubmissionNotificationSuccess, + formSubmissionNotificationError, + [ + 'core/paragraph', + { + content: __( + 'To request an export or deletion of your personal data on this site, please fill-in the form below. You can define the type of request you wish to perform, and your email address. Once the form is submitted, you will receive a confirmation email with instructions on the next steps.' + ), + }, + ], + [ + 'core/form-input', + { + type: 'email', + name: 'email', + label: __( 'Enter your email address.' ), + required: true, + visibilityPermissions: 'all', + }, + ], + [ + 'core/form-input', + { + type: 'checkbox', + name: 'export_personal_data', + label: __( 'Request data export' ), + required: false, + visibilityPermissions: 'all', + }, + ], + [ + 'core/form-input', + { + type: 'checkbox', + name: 'remove_personal_data', + label: __( 'Request data deletion' ), + required: false, + visibilityPermissions: 'all', + }, + ], + [ 'core/form-submit-button', {} ], + [ + 'core/form-input', + { + type: 'hidden', + name: 'wp-action', + value: 'wp_privacy_send_request', + }, + ], + [ + 'core/form-input', + { + type: 'hidden', + name: 'wp-privacy-request', + value: '1', + }, + ], + ], + scope: [ 'inserter', 'transform' ], + isActive: ( blockAttributes ) => + ! blockAttributes?.type || blockAttributes?.type === 'text', + }, +]; + +export default variations; diff --git a/packages/block-library/src/form/view.js b/packages/block-library/src/form/view.js new file mode 100644 index 0000000000000..05efe95da545c --- /dev/null +++ b/packages/block-library/src/form/view.js @@ -0,0 +1,41 @@ +// eslint-disable-next-line eslint-comments/disable-enable-pair +/* eslint-disable no-undef */ +document.querySelectorAll( 'form.wp-block-form' ).forEach( function ( form ) { + // Bail If the form is not using the mailto: action. + if ( ! form.action || ! form.action.startsWith( 'mailto:' ) ) { + return; + } + + const redirectNotification = ( status ) => { + const urlParams = new URLSearchParams( window.location.search ); + urlParams.append( 'wp-form-result', status ); + window.location.search = urlParams.toString(); + }; + + // Add an event listener for the form submission. + form.addEventListener( 'submit', async function ( event ) { + event.preventDefault(); + // Get the form data and merge it with the form action and nonce. + const formData = Object.fromEntries( new FormData( form ).entries() ); + formData.formAction = form.action; + formData._ajax_nonce = wpBlockFormSettings.nonce; + formData.action = wpBlockFormSettings.action; + + try { + const response = await fetch( wpBlockFormSettings.ajaxUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams( formData ).toString(), + } ); + if ( response.ok ) { + redirectNotification( 'success' ); + } else { + redirectNotification( 'error' ); + } + } catch ( error ) { + redirectNotification( 'error' ); + } + } ); +} ); diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index 736b552bf4259..e2e0fd9e414ef 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -48,6 +48,10 @@ import * as cover from './cover'; import * as details from './details'; import * as embed from './embed'; import * as file from './file'; +import * as form from './form'; +import * as formInput from './form-input'; +import * as formSubmitButton from './form-submit-button'; +import * as formSubmissionNotification from './form-submission-notification'; import * as gallery from './gallery'; import * as group from './group'; import * as heading from './heading'; @@ -228,6 +232,12 @@ const getAllBlocks = () => { queryTitle, postAuthorBiography, ]; + if ( window?.__experimentalEnableFormBlocks ) { + blocks.push( form ); + blocks.push( formInput ); + blocks.push( formSubmitButton ); + blocks.push( formSubmissionNotification ); + } // When in a WordPress context, conditionally // add the classic block and TinyMCE editor diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss index c4b0a37e6354d..790e09535f4b6 100644 --- a/packages/block-library/src/style.scss +++ b/packages/block-library/src/style.scss @@ -14,6 +14,7 @@ @import "./details/style.scss"; @import "./embed/style.scss"; @import "./file/style.scss"; +@import "./form-input/style.scss"; @import "./gallery/style.scss"; @import "./group/style.scss"; @import "./heading/style.scss"; diff --git a/phpunit/bootstrap.php b/phpunit/bootstrap.php index 7084df68443ba..acc7cfde89dbd 100644 --- a/phpunit/bootstrap.php +++ b/phpunit/bootstrap.php @@ -94,6 +94,7 @@ function fail_if_died( $message ) { 'gutenberg-experiments' => array( 'gutenberg-widget-experiments' => '1', 'gutenberg-full-site-editing' => 1, + 'gutenberg-form-blocks' => 1, ), ); diff --git a/test/integration/fixtures/blocks/core__form-input.html b/test/integration/fixtures/blocks/core__form-input.html new file mode 100644 index 0000000000000..718c592641bc3 --- /dev/null +++ b/test/integration/fixtures/blocks/core__form-input.html @@ -0,0 +1,3 @@ + + + diff --git a/test/integration/fixtures/blocks/core__form-input.json b/test/integration/fixtures/blocks/core__form-input.json new file mode 100644 index 0000000000000..33802bbcc2088 --- /dev/null +++ b/test/integration/fixtures/blocks/core__form-input.json @@ -0,0 +1,12 @@ +[ + { + "name": "core/missing", + "isValid": true, + "attributes": { + "originalName": "core/form-input", + "originalUndelimitedContent": "", + "originalContent": "\n\n" + }, + "innerBlocks": [] + } +] diff --git a/test/integration/fixtures/blocks/core__form-input.parsed.json b/test/integration/fixtures/blocks/core__form-input.parsed.json new file mode 100644 index 0000000000000..73058fc2e17f0 --- /dev/null +++ b/test/integration/fixtures/blocks/core__form-input.parsed.json @@ -0,0 +1,14 @@ +[ + { + "blockName": "core/form-input", + "attrs": { + "label": "Name", + "required": true + }, + "innerBlocks": [], + "innerHTML": "\n\n", + "innerContent": [ + "\n\n" + ] + } +] diff --git a/test/integration/fixtures/blocks/core__form-input.serialized.html b/test/integration/fixtures/blocks/core__form-input.serialized.html new file mode 100644 index 0000000000000..4e1f6b77998de --- /dev/null +++ b/test/integration/fixtures/blocks/core__form-input.serialized.html @@ -0,0 +1,3 @@ + + + diff --git a/test/integration/fixtures/blocks/core__form.html b/test/integration/fixtures/blocks/core__form.html new file mode 100644 index 0000000000000..ab18e0e11c81a --- /dev/null +++ b/test/integration/fixtures/blocks/core__form.html @@ -0,0 +1,21 @@ + +
+ + + + + + + + + + + + + + + + +
+
+ diff --git a/test/integration/fixtures/blocks/core__form.json b/test/integration/fixtures/blocks/core__form.json new file mode 100644 index 0000000000000..ba07b17e4d00c --- /dev/null +++ b/test/integration/fixtures/blocks/core__form.json @@ -0,0 +1,63 @@ +[ + { + "name": "core/missing", + "isValid": true, + "attributes": { + "originalName": "core/form", + "originalUndelimitedContent": "
\n\n\n\n\n
\n
", + "originalContent": "\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n\n
\n" + }, + "innerBlocks": [ + { + "name": "core/missing", + "isValid": true, + "attributes": { + "originalName": "core/form-input", + "originalUndelimitedContent": "", + "originalContent": "\n\n" + }, + "innerBlocks": [] + }, + { + "name": "core/missing", + "isValid": true, + "attributes": { + "originalName": "core/form-input", + "originalUndelimitedContent": "", + "originalContent": "\n\n" + }, + "innerBlocks": [] + }, + { + "name": "core/missing", + "isValid": true, + "attributes": { + "originalName": "core/form-input", + "originalUndelimitedContent": "", + "originalContent": "\n\n" + }, + "innerBlocks": [] + }, + { + "name": "core/missing", + "isValid": true, + "attributes": { + "originalName": "core/form-input", + "originalUndelimitedContent": "", + "originalContent": "\n\n" + }, + "innerBlocks": [] + }, + { + "name": "core/missing", + "isValid": true, + "attributes": { + "originalName": "core/form-input", + "originalUndelimitedContent": "
", + "originalContent": "\n
\n" + }, + "innerBlocks": [] + } + ] + } +] diff --git a/test/integration/fixtures/blocks/core__form.parsed.json b/test/integration/fixtures/blocks/core__form.parsed.json new file mode 100644 index 0000000000000..379bee84c84e1 --- /dev/null +++ b/test/integration/fixtures/blocks/core__form.parsed.json @@ -0,0 +1,84 @@ +[ + { + "blockName": "core/form", + "attrs": {}, + "innerBlocks": [ + { + "blockName": "core/form-input", + "attrs": { + "label": "Name", + "required": true + }, + "innerBlocks": [], + "innerHTML": "\n\n", + "innerContent": [ + "\n\n" + ] + }, + { + "blockName": "core/form-input", + "attrs": { + "type": "email", + "label": "Email", + "required": true + }, + "innerBlocks": [], + "innerHTML": "\n\n", + "innerContent": [ + "\n\n" + ] + }, + { + "blockName": "core/form-input", + "attrs": { + "type": "url", + "label": "Website" + }, + "innerBlocks": [], + "innerHTML": "\n\n", + "innerContent": [ + "\n\n" + ] + }, + { + "blockName": "core/form-input", + "attrs": { + "type": "textarea", + "label": "Comment", + "required": true + }, + "innerBlocks": [], + "innerHTML": "\n\n", + "innerContent": [ + "\n\n" + ] + }, + { + "blockName": "core/form-input", + "attrs": { + "type": "submit", + "label": "Submit" + }, + "innerBlocks": [], + "innerHTML": "\n
\n", + "innerContent": [ + "\n
\n" + ] + } + ], + "innerHTML": "\n
\n\n\n\n\n\n\n\n
\n", + "innerContent": [ + "\n
", + null, + "\n\n", + null, + "\n\n", + null, + "\n\n", + null, + "\n\n", + null, + "
\n" + ] + } +] diff --git a/test/integration/fixtures/blocks/core__form.serialized.html b/test/integration/fixtures/blocks/core__form.serialized.html new file mode 100644 index 0000000000000..58a2a49967eb5 --- /dev/null +++ b/test/integration/fixtures/blocks/core__form.serialized.html @@ -0,0 +1,19 @@ + +
+ + + + + + + + + + + + + +
+ +
+