From c25d13619f22ef924082e2d972c9a1b492c74828 Mon Sep 17 00:00:00 2001 From: mricoul Date: Mon, 26 Jan 2026 14:56:26 +0100 Subject: [PATCH 1/7] feat: FAQ block to use InnerBlocks Migrates the FAQ block to utilize InnerBlocks for improved content structure and editing experience. This change replaces the previous attribute-based approach with dedicated `faq-item`, `faq-question`, and `faq-answer` blocks, offering better control and flexibility. Also includes schema generation and support for PHP versions up to 8.4. --- README.md | 57 ++++++++- block.json | 19 ++- blockparty-faq.php | 8 ++ composer.json | 2 +- includes/schema/faq_schema.php | 209 +++++++++++++++++++++++++++++---- package.json | 3 +- src/FaqList.js | 151 ------------------------ src/edit.js | 24 ---- src/faq-answer/block.json | 22 ++++ src/faq-answer/edit.js | 35 ++++++ src/faq-answer/editor.scss | 1 + src/faq-answer/index.js | 27 +++++ src/faq-answer/save.js | 22 ++++ src/faq-answer/style.scss | 1 + src/faq-item/block.json | 17 +++ src/faq-item/edit.js | 76 ++++++++++++ src/faq-item/editor.scss | 1 + src/faq-item/index.js | 26 ++++ src/faq-item/save.js | 17 +++ src/faq-item/style.scss | 1 + src/faq-question/block.json | 26 ++++ src/faq-question/edit.js | 53 +++++++++ src/faq-question/editor.scss | 1 + src/faq-question/index.js | 26 ++++ src/faq-question/save.js | 29 +++++ src/faq-question/style.scss | 1 + src/faq/edit.js | 84 +++++++++++++ src/{ => faq}/editor.scss | 0 src/faq/index.js | 30 +++++ src/faq/save.js | 17 +++ src/{ => faq}/style.scss | 0 src/index.js | 26 ++-- src/save.js | 31 ----- 33 files changed, 783 insertions(+), 260 deletions(-) delete mode 100644 src/FaqList.js delete mode 100644 src/edit.js create mode 100644 src/faq-answer/block.json create mode 100644 src/faq-answer/edit.js create mode 100644 src/faq-answer/editor.scss create mode 100644 src/faq-answer/index.js create mode 100644 src/faq-answer/save.js create mode 100644 src/faq-answer/style.scss create mode 100644 src/faq-item/block.json create mode 100644 src/faq-item/edit.js create mode 100644 src/faq-item/editor.scss create mode 100644 src/faq-item/index.js create mode 100644 src/faq-item/save.js create mode 100644 src/faq-item/style.scss create mode 100644 src/faq-question/block.json create mode 100644 src/faq-question/edit.js create mode 100644 src/faq-question/editor.scss create mode 100644 src/faq-question/index.js create mode 100644 src/faq-question/save.js create mode 100644 src/faq-question/style.scss create mode 100644 src/faq/edit.js rename src/{ => faq}/editor.scss (100%) create mode 100644 src/faq/index.js create mode 100644 src/faq/save.js rename src/{ => faq}/style.scss (100%) delete mode 100644 src/save.js diff --git a/README.md b/README.md index c89942c..7232ea6 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,66 @@ # Blockparty FAQ +A Gutenberg block for SEO friendly FAQ in an accessible accordion. + +## Development Setup + +### Prerequisites + +- Node.js 20.12.0 (managed by Volta) +- Docker (for wp-env) + +### Installation + +1. Clone the repository +2. Install dependencies: + + ```bash + npm install + ``` + +3. Build the blocks: + + ```bash + npm run build + ``` + +4. Start the WordPress environment and install Yoast SEO: + + ```bash + npm run setup:env + ``` + + **Note:** On Windows, you may need to run the commands separately: + + ```bash + npm run start:env + # Wait for WordPress to be ready (about 10-15 seconds) + npm run setup + ``` + +### Available Scripts + +- `npm run build` - Build the blocks for production +- `npm run start` - Start the development server with hot reload +- `npm run start:env` - Start the WordPress environment (wp-env) +- `npm run stop:env` - Stop the WordPress environment +- `npm run install:yoast` - Install and activate Yoast SEO plugin (required for schema generation) +- `npm run setup:env` - Start wp-env and install Yoast SEO in one command + +### Note + +Yoast SEO is required for the FAQ schema (JSON-LD) generation. It is installed automatically via `npm run setup:env` but is not versioned in the repository. + ## Changelog + ### 1.0.0 - 2024-04-02 + * initial commit. ### 1.0.1 - 2024-04-03 + * fix css variable names ### 1.0.2 - 2024-06-06 -* Add support for PHP 8.2 \ No newline at end of file + +* Add support for PHP 8.2 diff --git a/block.json b/block.json index a511d4c..8b5e65c 100644 --- a/block.json +++ b/block.json @@ -1,22 +1,17 @@ { - "apiVersion": 2, + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, "name": "blockparty/faq", "version": "1.0.2", - "title": "Faq", + "title": "FAQ", "category": "design", - "icon": "format-chat", "description": "SEO friendly FAQ module in an accessible accordion", "supports": { - "html": false - }, - "attributes": { - "questions": { - "type": "array", - "default": [] - } + "html": false, + "innerBlocks": true }, "textdomain": "blockparty-faq", - "editorScript": "file:build/index.js", - "editorStyle": "file:build/index.css", + "editorScript": "file:./build/index.js", + "editorStyle": "file:./build/index.css", "style": "file:./build/style-index.css" } diff --git a/blockparty-faq.php b/blockparty-faq.php index 265d534..b8f1b48 100644 --- a/blockparty-faq.php +++ b/blockparty-faq.php @@ -51,7 +51,15 @@ require_once BLOCKPARTY_FAQ_DIR . 'includes/schema/faq_schema.php'; function blockparty_faq_init(): void { + // Register main block (from root block.json) register_block_type( __DIR__ ); + + // Register child blocks + // These blocks are also registered via JavaScript in src/index.js, + // but we need to register them in PHP so WordPress knows about their block.json metadata + register_block_type( __DIR__ . '/src/faq-item' ); + register_block_type( __DIR__ . '/src/faq-question' ); + register_block_type( __DIR__ . '/src/faq-answer' ); } add_action( 'init', __NAMESPACE__ . '\\blockparty_faq_init' ); diff --git a/composer.json b/composer.json index 264a27f..894b89e 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ } }, "require": { - "php": "^8.1 | ^8.2", + "php": "^8.1 | ^8.2 | ^8.3 | ^8.4", "ext-json": "*", "composer/installers": "^1.0 || ^2.0" }, diff --git a/includes/schema/faq_schema.php b/includes/schema/faq_schema.php index 7d6f68e..8a15faa 100644 --- a/includes/schema/faq_schema.php +++ b/includes/schema/faq_schema.php @@ -47,17 +47,45 @@ public function is_needed(): bool { private function generate_ids(): array { $ids = []; foreach ( $this->context->blocks['blockparty/faq'] as $block ) { - if ( empty( $block['attrs']['questions'] ) ) { + if ( empty( $block['innerBlocks'] ) ) { continue; } - $all_faqs_id = wp_list_pluck( $block['attrs']['questions'], 'id' ); - if ( empty( $all_faqs_id ) ) { - continue; - } + // Process faq-item blocks + foreach ( $block['innerBlocks'] as $item_block ) { + if ( 'blockparty/faq-item' !== $item_block['blockName'] ) { + continue; + } + + if ( empty( $item_block['innerBlocks'] ) ) { + continue; + } + + // Find question block in faq-item + $question_block = null; + foreach ( $item_block['innerBlocks'] as $inner_block ) { + if ( 'blockparty/faq-question' === $inner_block['blockName'] ) { + $question_block = $inner_block; + break; + } + } + + if ( ! $question_block ) { + continue; + } - foreach ( $all_faqs_id as $question_id ) { - $ids[] = [ '@id' => $this->context->canonical . '#' . \esc_attr( $question_id ) ]; + // Get question text from attributes or InnerBlocks + $question_text = $question_block['attrs']['question'] ?? ''; + + // If question is empty, try to get it from InnerBlocks (when isAccordion is false) + if ( empty( $question_text ) && ! empty( $question_block['innerBlocks'] ) ) { + $question_text = $this->get_question_content( $question_block ); + } + + $question_id = ! empty( $question_text ) + ? md5( $question_text ) + : 'faq-' . uniqid(); + $ids[] = [ '@id' => $this->context->canonical . '#' . \esc_attr( $question_id ) ]; } } @@ -70,47 +98,137 @@ private function generate_ids(): array { * @return array Our Schema graph. */ public function generate(): array { - $graph = []; - $questions = []; + $graph = []; foreach ( $this->context->blocks['blockparty/faq'] as $block ) { - - if ( empty( $block['attrs']['questions'] ) ) { + if ( empty( $block['innerBlocks'] ) ) { continue; } - $questions = array_merge( $questions, $block['attrs']['questions'] ); - } + $position = 1; - foreach ( $questions as $index => $question ) { - if ( empty( $question['answer'] ) ) { - continue; + // Process faq-item blocks + foreach ( $block['innerBlocks'] as $item_block ) { + if ( 'blockparty/faq-item' !== $item_block['blockName'] ) { + continue; + } + + if ( empty( $item_block['innerBlocks'] ) ) { + continue; + } + + // Find question and answer blocks in faq-item + $question_block = null; + $answer_block = null; + + foreach ( $item_block['innerBlocks'] as $inner_block ) { + if ( 'blockparty/faq-question' === $inner_block['blockName'] ) { + $question_block = $inner_block; + } elseif ( 'blockparty/faq-answer' === $inner_block['blockName'] ) { + $answer_block = $inner_block; + } + } + + // Skip if no question block + if ( ! $question_block ) { + continue; + } + + // Get question text from attributes or InnerBlocks + $question_text = $question_block['attrs']['question'] ?? ''; + + // If question is empty, try to get it from InnerBlocks (when isAccordion is false) + if ( empty( $question_text ) && ! empty( $question_block['innerBlocks'] ) ) { + $question_text = $this->get_question_content( $question_block ); + } + + if ( empty( $question_text ) ) { + continue; + } + + // Get answer content from InnerBlocks + $answer_content = ''; + if ( $answer_block ) { + $answer_content = $this->get_answer_content( $answer_block ); + } + + if ( empty( $answer_content ) ) { + continue; + } + + $question_id = md5( $question_text ); + $graph[] = $this->generate_question_block( + $question_text, + $answer_content, + $question_id, + $position + ); + ++$position; } - $graph[] = $this->generate_question_block( $question, ( $index + 1 ) ); } return $graph; } + /** + * Get question content from InnerBlocks. + * + * @param array $question_block The question block with InnerBlocks. + * @return string The question content as HTML. + */ + protected function get_question_content( array $question_block ): string { + if ( empty( $question_block['innerBlocks'] ) ) { + return ''; + } + + $content = ''; + foreach ( $question_block['innerBlocks'] as $inner_block ) { + $content .= render_block( $inner_block ); + } + + return wp_strip_all_tags( $content ); + } + + /** + * Get answer content from InnerBlocks. + * + * @param array $answer_block The answer block with InnerBlocks. + * @return string The answer content as HTML. + */ + protected function get_answer_content( array $answer_block ): string { + if ( empty( $answer_block['innerBlocks'] ) ) { + return ''; + } + + $content = ''; + foreach ( $answer_block['innerBlocks'] as $inner_block ) { + $content .= render_block( $inner_block ); + } + + return $content; + } + /** * Generate a Question piece. * - * @param array $question The question data to generate schema for. + * @param string $question_text The question text. + * @param string $answer_content The answer content as HTML. + * @param string $question_id The question ID. * @param int $position The position of the question. * * @return array Schema.org Question piece. */ - protected function generate_question_block( array $question, int $position ): array { - $url = $this->context->canonical . '#' . \esc_attr( $question['id'] ); + protected function generate_question_block( string $question_text, string $answer_content, string $question_id, int $position ): array { + $url = $this->context->canonical . '#' . \esc_attr( $question_id ); $data = [ '@type' => 'Question', '@id' => $url, 'position' => $position, 'url' => $url, - 'name' => esc_html( $question['question'] ), + 'name' => esc_html( $question_text ), 'answerCount' => 1, - 'acceptedAnswer' => $this->add_accepted_answer_property( $question['answer'] ), + 'acceptedAnswer' => $this->add_accepted_answer_property( $answer_content ), ]; $data['inLanguage'] = get_bloginfo( 'language' ); @@ -126,9 +244,54 @@ protected function generate_question_block( array $question, int $position ): ar * @return array Schema.org Question piece. */ protected function add_accepted_answer_property( string $answer ): array { + // Allowed HTML elements and attributes for Schema.org FAQPage acceptedAnswer text property. + // Supports: headings, paragraphs, lists, formatting, links, quotes, images, and structure. + $allowed_html = [ + 'h1' => [], + 'h2' => [], + 'h3' => [], + 'h4' => [], + 'h5' => [], + 'h6' => [], + 'p' => [], + 'br' => [], + 'ol' => [], + 'ul' => [], + 'li' => [], + 'a' => [ + 'href' => [], + 'title' => [], + ], + 'b' => [], + 'strong' => [], + 'i' => [], + 'em' => [], + 'blockquote' => [ + 'cite' => [], + ], + 'cite' => [], + 'q' => [ + 'cite' => [], + ], + 'img' => [ + 'src' => [], + 'alt' => [], + 'title' => [], + 'width' => [], + 'height' => [], + ], + 'div' => [ + 'class' => [], + ], + 'span' => [ + 'class' => [], + ], + 'hr' => [], + ]; + $data = [ '@type' => 'Answer', - 'text' => wp_strip_all_tags( wp_unslash( $answer ), '


-
- -
- - ) ) } - - - ); -} From 0ce8d5279e92802029478d59abea25e6415dad27 Mon Sep 17 00:00:00 2001 From: mricoul Date: Mon, 26 Jan 2026 16:15:16 +0100 Subject: [PATCH 2/7] style(js): lint --- src/faq-answer/block.json | 2 +- src/faq-answer/edit.js | 6 +----- src/faq-question/block.json | 2 +- src/faq-question/edit.js | 10 +++++----- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/faq-answer/block.json b/src/faq-answer/block.json index 5550eb3..1f37fc0 100644 --- a/src/faq-answer/block.json +++ b/src/faq-answer/block.json @@ -1,5 +1,5 @@ { - "$schema": "https://schemas.wp.org/trunk/block.json", + "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "blockparty/faq-answer", "version": "1.0.0", diff --git a/src/faq-answer/edit.js b/src/faq-answer/edit.js index 45b422e..363b551 100644 --- a/src/faq-answer/edit.js +++ b/src/faq-answer/edit.js @@ -5,11 +5,7 @@ import { useBlockProps } from '@wordpress/block-editor'; import { InnerBlocks } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; -const ALLOWED_BLOCKS = [ - 'core/paragraph', - 'core/heading', - 'core/list', -]; +const ALLOWED_BLOCKS = [ 'core/paragraph', 'core/heading', 'core/list' ]; export default function Edit() { const blockProps = useBlockProps( { diff --git a/src/faq-question/block.json b/src/faq-question/block.json index e3f8281..ab7b38d 100644 --- a/src/faq-question/block.json +++ b/src/faq-question/block.json @@ -1,5 +1,5 @@ { - "$schema": "https://schemas.wp.org/trunk/block.json", + "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "blockparty/faq-question", "version": "1.0.0", diff --git a/src/faq-question/edit.js b/src/faq-question/edit.js index fe4fe3f..e2ce249 100644 --- a/src/faq-question/edit.js +++ b/src/faq-question/edit.js @@ -9,10 +9,7 @@ import { RichText, InnerBlocks } from '@wordpress/block-editor'; */ import { __ } from '@wordpress/i18n'; -const ALLOWED_BLOCKS_SIMPLE = [ - 'core/heading', - 'core/paragraph', -]; +const ALLOWED_BLOCKS_SIMPLE = [ 'core/heading', 'core/paragraph' ]; export default function Edit( { attributes, setAttributes } ) { const { question, isAccordion = true } = attributes; @@ -40,7 +37,10 @@ export default function Edit( { attributes, setAttributes } ) { [ 'core/paragraph', { - placeholder: __( 'Question…?', 'blockparty-faq' ), + placeholder: __( + 'Question…?', + 'blockparty-faq' + ), }, ], ] } From ce22493cbeab7b2c0449c2b3a3eef9120adffcf6 Mon Sep 17 00:00:00 2001 From: mricoul Date: Mon, 26 Jan 2026 16:15:49 +0100 Subject: [PATCH 3/7] feat(deprecated): enables inner blocks for FAQ block Migrates the FAQ block to use inner blocks for managing questions and answers, allowing for greater flexibility and content structure. Adds a migration script to convert existing FAQ blocks from the old `questions` array format to the new inner blocks format. Includes deprecated block configuration to handle the migration process smoothly. --- src/faq/deprecated.js | 216 ++++++++++++++++++++++++++++++++++++++++++ src/faq/edit.js | 43 ++++++--- src/faq/index.js | 7 ++ src/faq/save.js | 8 +- 4 files changed, 257 insertions(+), 17 deletions(-) create mode 100644 src/faq/deprecated.js diff --git a/src/faq/deprecated.js b/src/faq/deprecated.js new file mode 100644 index 0000000..fcd60bc --- /dev/null +++ b/src/faq/deprecated.js @@ -0,0 +1,216 @@ +/** + * WordPress dependencies + */ +import { createBlock, parse } from '@wordpress/blocks'; +import { useBlockProps } from '@wordpress/block-editor'; +import { RichText } from '@wordpress/block-editor'; + +/** + * Migration script to convert old FAQ format to new InnerBlocks format. + * + * Old format: questions array in attributes + * New format: InnerBlocks with faq-item blocks + * + * @param {Object} attributes The block attributes. + * @param {Array} innerBlocks The inner blocks. + * @return {Array} Tuple array [attributes, innerBlocks] when migrating to InnerBlocks. + */ +function migrate( attributes, innerBlocks ) { + // If no questions attribute, return existing innerBlocks (new format or empty block) + // According to WordPress documentation, when returning only innerBlocks, + // we should return a tuple: [attributes, innerBlocks] + if ( + ! attributes.questions || + ! Array.isArray( attributes.questions ) || + attributes.questions.length === 0 + ) { + // Return tuple: [attributes, innerBlocks] + return [ + { + isAccordion: + attributes.isAccordion !== undefined + ? attributes.isAccordion + : true, + }, + innerBlocks, + ]; + } + + const isAccordion = + attributes.isAccordion !== undefined ? attributes.isAccordion : true; + + // Convert each question to a faq-item block + const migratedBlocks = attributes.questions.map( ( question ) => { + // Create faq-question block + const questionBlock = createBlock( 'blockparty/faq-question', { + question: question.question || '', + isAccordion: isAccordion, + } ); + + // Create faq-answer block with content + const answerContent = question.answer || ''; + + // Create inner blocks for the answer + let answerInnerBlocks = []; + if ( answerContent ) { + // Check if the answer contains HTML tags + const hasHtmlTags = /<[a-z][\s\S]*>/i.test( answerContent ); + + if ( hasHtmlTags ) { + // Answer contains HTML: try to parse it into blocks + try { + const parsedBlocks = parse( answerContent ); + + // If parsing succeeded and returned blocks, use them + if ( parsedBlocks && parsedBlocks.length > 0 ) { + answerInnerBlocks = parsedBlocks; + } else { + // Parsing didn't return blocks, create a paragraph with the HTML content + answerInnerBlocks = [ + createBlock( 'core/paragraph', { + content: answerContent, + } ), + ]; + } + } catch ( error ) { + // If parsing fails, create a paragraph with the content + answerInnerBlocks = [ + createBlock( 'core/paragraph', { + content: answerContent, + } ), + ]; + } + } else { + // Plain text: directly create a paragraph with the content + answerInnerBlocks = [ + createBlock( 'core/paragraph', { + content: answerContent, + } ), + ]; + } + } else { + // Empty answer, create empty paragraph + answerInnerBlocks = [ createBlock( 'core/paragraph' ) ]; + } + + const answerBlock = createBlock( + 'blockparty/faq-answer', + { + isAccordion: isAccordion, + }, + answerInnerBlocks + ); + + // Create faq-item block containing question and answer + return createBlock( 'blockparty/faq-item', {}, [ + questionBlock, + answerBlock, + ] ); + } ); + + // Return new attributes (without questions) and migrated innerBlocks + // According to WordPress documentation, when migrating to InnerBlocks, + // migrate() must return a tuple array: [attributes, innerBlocks] + return [ + { + isAccordion: isAccordion, + }, + migratedBlocks, + ]; +} + +/** + * Check if a block is eligible for migration. + * + * @param {Object} attributes The block attributes. + * @param {Object} innerBlocks The inner blocks. + * @return {boolean} Whether the block should be migrated. + */ +function isEligible( attributes, innerBlocks ) { + // If questions attribute exists and has content, block needs migration + return ( + attributes.questions && + Array.isArray( attributes.questions ) && + attributes.questions.length > 0 + ); +} + +/** + * Save function for deprecated format. + * Matches the old HTML structure to allow proper block validation. + * The old format had:
... + * + * @param {Object} props Component props. + * @param {Object} props.attributes Block attributes. + * @return {JSX.Element} Saved block markup. + */ +function deprecatedSave( { attributes } ) { + const { questions = [] } = attributes; + const blockProps = useBlockProps.save(); + + if ( ! questions || questions.length === 0 ) { + return ( +
+
+
+ ); + } + + return ( +
+
+ { questions.map( ( item ) => ( +
+

+ +

+
+ +
+
+ ) ) } +
+
+ ); +} + +/** + * Deprecated block configuration for migration from old format. + * + * Old format: questions array in attributes + * New format: InnerBlocks with faq-item blocks + */ +const deprecated = [ + { + attributes: { + questions: { + type: 'array', + default: [], + }, + isAccordion: { + type: 'boolean', + default: true, + }, + }, + supports: { + html: false, + innerBlocks: true, + }, + isEligible, + migrate, + save: deprecatedSave, + }, +]; + +export default deprecated; diff --git a/src/faq/edit.js b/src/faq/edit.js index 1e283d5..3284e42 100644 --- a/src/faq/edit.js +++ b/src/faq/edit.js @@ -1,8 +1,18 @@ /** * WordPress dependencies */ -import { BlockControls, InnerBlocks, useBlockProps, InspectorControls } from '@wordpress/block-editor'; -import { ToolbarGroup, ToolbarButton, PanelBody, ToggleControl } from '@wordpress/components'; +import { + BlockControls, + InnerBlocks, + useBlockProps, + InspectorControls, +} from '@wordpress/block-editor'; +import { + ToolbarGroup, + ToolbarButton, + PanelBody, + ToggleControl, +} from '@wordpress/components'; import { addCard } from '@wordpress/icons'; import { useDispatch, useSelect } from '@wordpress/data'; import { createBlock } from '@wordpress/blocks'; @@ -11,12 +21,14 @@ import { useEffect } from '@wordpress/element'; export default function Edit( { clientId, attributes, setAttributes } ) { const { isAccordion = true } = attributes; - const blockProps = useBlockProps( { - className: 'faq__accordion', - } ); + const blockProps = useBlockProps(); - const { insertBlock, updateBlockAttributes } = useDispatch( 'core/block-editor' ); - const { getBlocks } = useSelect( ( select ) => select( 'core/block-editor' ), [] ); + const { insertBlock, updateBlockAttributes } = + useDispatch( 'core/block-editor' ); + const { getBlocks } = useSelect( + ( select ) => select( 'core/block-editor' ), + [] + ); // Synchronize isAccordion attribute to all child blocks useEffect( () => { @@ -68,16 +80,21 @@ export default function Edit( { clientId, attributes, setAttributes } ) { 'blockparty-faq' ) } checked={ isAccordion } - onChange={ ( value ) => setAttributes( { isAccordion: value } ) } + onChange={ ( value ) => + setAttributes( { isAccordion: value } ) + } + __nextHasNoMarginBottom />
- +
+ +
); diff --git a/src/faq/index.js b/src/faq/index.js index 5faca62..9331f57 100644 --- a/src/faq/index.js +++ b/src/faq/index.js @@ -11,6 +11,7 @@ import './style.scss'; import './editor.scss'; import Edit from './edit'; import save from './save'; +import deprecated from './deprecated'; // Register child blocks first import '../faq-item'; @@ -27,4 +28,10 @@ registerBlockType( 'blockparty/faq', { */ save, icon: help, + + /** + * Migration from old format (questions array) to new format (InnerBlocks) + * @see ./deprecated.js + */ + deprecated, } ); diff --git a/src/faq/save.js b/src/faq/save.js index 32e59f6..f58f43b 100644 --- a/src/faq/save.js +++ b/src/faq/save.js @@ -5,13 +5,13 @@ import { useBlockProps } from '@wordpress/block-editor'; import { InnerBlocks } from '@wordpress/block-editor'; export default function save() { - const blockProps = useBlockProps.save( { - className: 'faq__accordion', - } ); + const blockProps = useBlockProps.save(); return (
- +
+ +
); } From e71ad8b0679d451e54dbde1482a3e057caf971bd Mon Sep 17 00:00:00 2001 From: mricoul Date: Mon, 26 Jan 2026 16:23:49 +0100 Subject: [PATCH 4/7] feat(faq-question): adds inner blocks support to FAQ question Enables inner blocks for FAQ question, allowing richer content within the question. Handles conversion between accordion and non-accordion states: - When switching to non-accordion, moves question text to a heading block. - When switching to accordion, extracts content from the first inner block and sets it as the question text. --- src/faq-question/edit.js | 56 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/src/faq-question/edit.js b/src/faq-question/edit.js index e2ce249..28c684f 100644 --- a/src/faq-question/edit.js +++ b/src/faq-question/edit.js @@ -3,6 +3,9 @@ */ import { useBlockProps } from '@wordpress/block-editor'; import { RichText, InnerBlocks } from '@wordpress/block-editor'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { createBlock } from '@wordpress/blocks'; +import { useEffect } from '@wordpress/element'; /** * Internal dependencies @@ -11,12 +14,60 @@ import { __ } from '@wordpress/i18n'; const ALLOWED_BLOCKS_SIMPLE = [ 'core/heading', 'core/paragraph' ]; -export default function Edit( { attributes, setAttributes } ) { +export default function Edit( { attributes, setAttributes, clientId } ) { const { question, isAccordion = true } = attributes; const blockProps = useBlockProps( { className: 'faq__title', } ); + const innerBlocks = useSelect( + ( select ) => select( 'core/block-editor' ).getBlocks( clientId ), + [ clientId ] + ); + + const { replaceInnerBlocks } = useDispatch( 'core/block-editor' ); + + // When isAccordion changes from true to false and innerBlocks are empty, + // create a core/heading block with the question attribute value + // When isAccordion changes from false to true, remove innerBlocks and + // transfer their content to the question attribute + useEffect( () => { + if ( ! isAccordion && innerBlocks.length === 0 && question ) { + // Switch from accordion to non-accordion: create heading block + const headingBlock = createBlock( 'core/heading', { + level: 3, + content: question, + } ); + replaceInnerBlocks( clientId, [ headingBlock ] ); + } else if ( isAccordion && innerBlocks.length > 0 ) { + // Switch from non-accordion to accordion: extract content from innerBlocks + // Get the text content from the first block (usually a heading or paragraph) + const firstBlock = innerBlocks[ 0 ]; + let extractedContent = ''; + + if ( firstBlock.name === 'core/heading' ) { + extractedContent = firstBlock.attributes?.content || ''; + } else if ( firstBlock.name === 'core/paragraph' ) { + extractedContent = firstBlock.attributes?.content || ''; + } + + // Update the question attribute with the extracted content + if ( extractedContent && extractedContent !== question ) { + setAttributes( { question: extractedContent } ); + } + + // Remove innerBlocks + replaceInnerBlocks( clientId, [] ); + } + }, [ + isAccordion, + innerBlocks, + question, + clientId, + replaceInnerBlocks, + setAttributes, + ] ); + return (

@@ -35,8 +86,9 @@ export default function Edit( { attributes, setAttributes } ) { allowedBlocks={ ALLOWED_BLOCKS_SIMPLE } template={ [ [ - 'core/paragraph', + 'core/heading', { + level: 3, placeholder: __( 'Question…?', 'blockparty-faq' From ae6f2ee49ae37ef2bfab89d1ab47dd7fb869152f Mon Sep 17 00:00:00 2001 From: mricoul Date: Mon, 26 Jan 2026 16:27:57 +0100 Subject: [PATCH 5/7] chore: bump block.json versions --- src/faq-answer/block.json | 2 +- src/faq-item/block.json | 2 +- src/faq-question/block.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/faq-answer/block.json b/src/faq-answer/block.json index 1f37fc0..82ee279 100644 --- a/src/faq-answer/block.json +++ b/src/faq-answer/block.json @@ -2,7 +2,7 @@ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "blockparty/faq-answer", - "version": "1.0.0", + "version": "1.0.2", "title": "FAQ Answer", "category": "design", "parent": [ "blockparty/faq-item" ], diff --git a/src/faq-item/block.json b/src/faq-item/block.json index f57cf69..bbad613 100644 --- a/src/faq-item/block.json +++ b/src/faq-item/block.json @@ -2,7 +2,7 @@ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "blockparty/faq-item", - "version": "1.0.0", + "version": "1.0.2", "title": "FAQ Item", "category": "design", "parent": [ "blockparty/faq" ], diff --git a/src/faq-question/block.json b/src/faq-question/block.json index ab7b38d..62ee4ce 100644 --- a/src/faq-question/block.json +++ b/src/faq-question/block.json @@ -2,7 +2,7 @@ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "blockparty/faq-question", - "version": "1.0.0", + "version": "1.0.2", "title": "FAQ Question", "category": "design", "parent": [ "blockparty/faq-item" ], From f02f8e58953a1c36359edb5a7a7ce592cd90341d Mon Sep 17 00:00:00 2001 From: mricoul Date: Mon, 26 Jan 2026 16:57:13 +0100 Subject: [PATCH 6/7] feat(i18n): add translations files --- block.json | 2 +- blockparty-faq.php | 53 +++++++- ...r_FR-dfbff627e6c248bcb3b61d7d06da9ca9.json | 1 + languages/blockparty-faq-fr_FR.mo | Bin 0 -> 2128 bytes languages/blockparty-faq-fr_FR.po | 117 ++++++++++++++++++ languages/blockparty-faq.pot | 104 ++++++++++++++++ package.json | 4 +- src/faq-answer/block.json | 2 +- src/faq-item/block.json | 2 +- src/faq-question/block.json | 2 +- src/faq/edit.js | 2 +- 11 files changed, 281 insertions(+), 8 deletions(-) create mode 100644 languages/blockparty-faq-fr_FR-dfbff627e6c248bcb3b61d7d06da9ca9.json create mode 100644 languages/blockparty-faq-fr_FR.mo create mode 100644 languages/blockparty-faq-fr_FR.po create mode 100644 languages/blockparty-faq.pot diff --git a/block.json b/block.json index 8b5e65c..7f75bb8 100644 --- a/block.json +++ b/block.json @@ -5,7 +5,7 @@ "version": "1.0.2", "title": "FAQ", "category": "design", - "description": "SEO friendly FAQ module in an accessible accordion", + "description": "A FAQ block for WordPress Editor that provided structured data based on FAQ schema.", "supports": { "html": false, "innerBlocks": true diff --git a/blockparty-faq.php b/blockparty-faq.php index b8f1b48..5e748a8 100644 --- a/blockparty-faq.php +++ b/blockparty-faq.php @@ -1,7 +1,7 @@ 9gih-_8!XwfZ@q^vI0<@ZYIpmX?H+gc z*j#{gh=>%400I&ur2Pp4Ha-y$+(@(ER!?`;S6_A2?-v%n z5_q1&`wHG4@xF@p<^%Y_^D}S{_zUm?@X~`qJPBL{J`P+5J`dahJ_Y3Zy9e&W{;alt z13nG=4OWuDJAAA7XXyGFyzBp=hrSlTx>-L>o-t*6Rk!B3>8+?qp`a!StIBJqXA z-&A@@L+S9>=+cWzCOnm~=p_lfD69^7A`g|ZqL-%S7Ug*=dfFYay?f_-vBuQf-J}60 z6Ri@Nk!Lv-Yk^HJt-l@4h(K_Uy;pkd#O8=t%VEapBB(LFKiJ+nVMRwO%Mg{6_H1*@ z9_*wesYxD_8d)oT^4)CE#^Y3iw34kjlUz@KUCkj*w}H~6?xd)&a+~ybh~6zUax2*4_doBQJ)W0h!tIx zp1ZW%Szd`cOVRQQEvU3IL()nm$AzvoZ7+u1O9v5=V(Ezvhl-;F~{cxk- z+TPq=KS=|;TXi|2!R;JzJbw~Mp{(yvGLhD?fA{)eBU(M#7s8F$M(a8;DT?2v)uHmO z8_|2L*;YJu4Wm`*t#}pdSIrdux^yN-h|Sn&vMJbbhrMd@=fIGM`Ol@E?mSW*P+NX}EeR0#(g7pBXKBwdX?9 ze3s!1_K5y`mK*Ij%!I9y7)ay{g~DLKB@7I!VR3dk;OIC*5x}$TSh1%lfeW-VdfuzU z`D~Fgc}%_@8EH}J;hDRivVZqmIU(*p%Q*0YwP92zRrpwN6WG`O;Dt$8R{7!pM|1)-Ytrj)0@HRGWA b7REqRxv(PUU(^{kwP+f>v77!p?)QHH`oMNB literal 0 HcmV?d00001 diff --git a/languages/blockparty-faq-fr_FR.po b/languages/blockparty-faq-fr_FR.po new file mode 100644 index 0000000..7d38da3 --- /dev/null +++ b/languages/blockparty-faq-fr_FR.po @@ -0,0 +1,117 @@ +# Copyright (C) 2026 Be API Technical team +# This file is distributed under the GPL-2.0-or-later. +msgid "" +msgstr "" +"Project-Id-Version: Blockparty Faq 1.0.2\n" +"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/blockparty-faq\n" +"POT-Creation-Date: 2026-01-26T15:49:49+00:00\n" +"PO-Revision-Date: 2026-01-26 16:50+0100\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: fr_FR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.8\n" +"X-Domain: blockparty-faq\n" + +#. Plugin Name of the plugin +#: blockparty-faq.php +msgid "Blockparty FAQ" +msgstr "Blockparty FAQ" + +#. Plugin URI of the plugin +#. Author URI of the plugin +#: blockparty-faq.php +msgid "https://beapi.fr" +msgstr "https://beapi.fr" + +#. Description of the plugin +#: blockparty-faq.php +msgid "" +"A FAQ block for WordPress Editor that provided structured data based on FAQ " +"schema." +msgstr "" +"Un bloc FAQ pour l’éditeur de WordPress qui ajouter des données structurées " +"basées sur le schéma FAQ." + +#. Author of the plugin +#: blockparty-faq.php +msgid "Be API Technical team" +msgstr "L’équipe technique de Be API" + +#: build/index.js:1 +msgid "Add FAQ item" +msgstr "Ajouter un élément" + +#: build/index.js:1 +msgid "Remove FAQ item" +msgstr "Supprimer l’élément" + +#: build/index.js:1 +msgid "FAQ Settings" +msgstr "Réglages de FAQ" + +#: build/index.js:1 +msgid "Accordion behavior" +msgstr "Comportement d’accordéon" + +#: build/index.js:1 +msgid "" +"If enabled, the HTML structure will be interpreted as an accordion from " +"screen readers." +msgstr "" +"Si c’est activé, la structure HTML du bloc sera interprété en tant " +"qu’accordéon pour les technologies d’assistance." + +#: build/index.js:1 +msgid "Question…?" +msgstr "Question…?" + +#: build/index.js:1 +msgid "Answer…" +msgstr "Réponse…" + +#: block.json +msgctxt "block title" +msgid "FAQ" +msgstr "FAQ" + +#: block.json +msgctxt "block description" +msgid "" +"A FAQ block for WordPress Editor that provided structured data based on FAQ " +"schema." +msgstr "" +"A FAQ block for WordPress Editor that provided structured data based on FAQ " +"schema." + +#: build/faq-answer/block.json +msgctxt "block title" +msgid "FAQ Answer" +msgstr "Réponse FAQ" + +#: build/faq-answer/block.json +msgctxt "block description" +msgid "Content of the FAQ answer." +msgstr "Contenu de la réponse FAQ." + +#: build/faq-item/block.json +msgctxt "block title" +msgid "FAQ Item" +msgstr "Élément de FAQ" + +#: build/faq-item/block.json +msgctxt "block description" +msgid "A FAQ item containing a question and an answer." +msgstr "Un élément de FAQ contient une question et une réponse." + +#: build/faq-question/block.json +msgctxt "block title" +msgid "FAQ Question" +msgstr "Question de FAQ" + +#: build/faq-question/block.json +msgctxt "block description" +msgid "Content of the FAQ question." +msgstr "Contenu de la question FAQ." diff --git a/languages/blockparty-faq.pot b/languages/blockparty-faq.pot new file mode 100644 index 0000000..9d514cd --- /dev/null +++ b/languages/blockparty-faq.pot @@ -0,0 +1,104 @@ +# Copyright (C) 2026 Be API Technical team +# This file is distributed under the GPL-2.0-or-later. +msgid "" +msgstr "" +"Project-Id-Version: Blockparty FAQ 1.0.2\n" +"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/blockparty-faq\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"POT-Creation-Date: 2026-01-26T15:49:49+00:00\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"X-Generator: WP-CLI 2.11.0\n" +"X-Domain: blockparty-faq\n" + +#. Plugin Name of the plugin +#: blockparty-faq.php +msgid "Blockparty FAQ" +msgstr "" + +#. Plugin URI of the plugin +#. Author URI of the plugin +#: blockparty-faq.php +msgid "https://beapi.fr" +msgstr "" + +#. Description of the plugin +#: blockparty-faq.php +msgid "A FAQ block for WordPress Editor that provided structured data based on FAQ schema." +msgstr "" + +#. Author of the plugin +#: blockparty-faq.php +msgid "Be API Technical team" +msgstr "" + +#: build/index.js:1 +msgid "Add FAQ item" +msgstr "" + +#: build/index.js:1 +msgid "Remove FAQ item" +msgstr "" + +#: build/index.js:1 +msgid "FAQ Settings" +msgstr "" + +#: build/index.js:1 +msgid "Accordion behavior" +msgstr "" + +#: build/index.js:1 +msgid "If enabled, the HTML structure will be interpreted as an accordion from screen readers." +msgstr "" + +#: build/index.js:1 +msgid "Question…?" +msgstr "" + +#: build/index.js:1 +msgid "Answer…" +msgstr "" + +#: block.json +msgctxt "block title" +msgid "FAQ" +msgstr "" + +#: block.json +msgctxt "block description" +msgid "A FAQ block for WordPress Editor that provided structured data based on FAQ schema." +msgstr "" + +#: build/faq-answer/block.json +msgctxt "block title" +msgid "FAQ Answer" +msgstr "" + +#: build/faq-answer/block.json +msgctxt "block description" +msgid "Content of the FAQ answer." +msgstr "" + +#: build/faq-item/block.json +msgctxt "block title" +msgid "FAQ Item" +msgstr "" + +#: build/faq-item/block.json +msgctxt "block description" +msgid "A FAQ item containing a question and an answer." +msgstr "" + +#: build/faq-question/block.json +msgctxt "block title" +msgid "FAQ Question" +msgstr "" + +#: build/faq-question/block.json +msgctxt "block description" +msgid "Content of the FAQ question." +msgstr "" diff --git a/package.json b/package.json index 1df8e59..50315a7 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,9 @@ "packages-update": "wp-scripts packages-update", "start:env": "wp-env start --config=./wp-env.json", "stop:env": "wp-env stop --config=./wp-env.json", - "setup:env": "wp-env run cli wp plugin install wordpress-seo --activate --allow-root" + "setup:env": "wp-env run cli wp plugin install wordpress-seo --activate --allow-root", + "make-pot": "wp i18n make-pot . languages/blockparty-faq.pot --exclude=\"src\" --domain=blockparty-faq", + "make-json": "wp i18n make-json languages/blockparty-faq-fr_FR.po languages/ --no-purge" }, "dependencies": { "@wordpress/block-editor": "^12.22.0", diff --git a/src/faq-answer/block.json b/src/faq-answer/block.json index 82ee279..b7a954e 100644 --- a/src/faq-answer/block.json +++ b/src/faq-answer/block.json @@ -6,7 +6,7 @@ "title": "FAQ Answer", "category": "design", "parent": [ "blockparty/faq-item" ], - "description": "An answer in a FAQ list", + "description": "Content of the FAQ answer.", "supports": { "html": false, "inserter": false, diff --git a/src/faq-item/block.json b/src/faq-item/block.json index bbad613..93404aa 100644 --- a/src/faq-item/block.json +++ b/src/faq-item/block.json @@ -6,7 +6,7 @@ "title": "FAQ Item", "category": "design", "parent": [ "blockparty/faq" ], - "description": "A FAQ item containing a question and an answer", + "description": "A FAQ item containing a question and an answer.", "supports": { "html": false, "inserter": false, diff --git a/src/faq-question/block.json b/src/faq-question/block.json index 62ee4ce..515474c 100644 --- a/src/faq-question/block.json +++ b/src/faq-question/block.json @@ -6,7 +6,7 @@ "title": "FAQ Question", "category": "design", "parent": [ "blockparty/faq-item" ], - "description": "A question in a FAQ list", + "description": "Content of the FAQ question.", "supports": { "html": false, "inserter": false, diff --git a/src/faq/edit.js b/src/faq/edit.js index 3284e42..6016e58 100644 --- a/src/faq/edit.js +++ b/src/faq/edit.js @@ -76,7 +76,7 @@ export default function Edit( { clientId, attributes, setAttributes } ) { Date: Mon, 26 Jan 2026 17:18:41 +0100 Subject: [PATCH 7/7] refactor: FAQ block for improved architecture Moves the FAQ block's structure to leverage build artifacts and enhances its functionality. This includes: - Registers block types from build directory for better organization. - Introduces `wp_localize_script` to pass configuration from PHP to the view script. - Implements Javascript translations for block editor. - Adds `@beapi/be-a11y` for accessibility improvements, enabling accordion functionality. --- block.json | 17 ---------- blockparty-faq.php | 78 ++++++++++++++++++---------------------------- package-lock.json | 20 ++++++++++++ package.json | 1 + src/faq/block.json | 18 +++++++++++ src/faq/index.js | 5 ++- src/faq/script.js | 12 +++++++ 7 files changed, 86 insertions(+), 65 deletions(-) delete mode 100644 block.json create mode 100644 src/faq/block.json create mode 100644 src/faq/script.js diff --git a/block.json b/block.json deleted file mode 100644 index 7f75bb8..0000000 --- a/block.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "$schema": "https://schemas.wp.org/trunk/block.json", - "apiVersion": 3, - "name": "blockparty/faq", - "version": "1.0.2", - "title": "FAQ", - "category": "design", - "description": "A FAQ block for WordPress Editor that provided structured data based on FAQ schema.", - "supports": { - "html": false, - "innerBlocks": true - }, - "textdomain": "blockparty-faq", - "editorScript": "file:./build/index.js", - "editorStyle": "file:./build/index.css", - "style": "file:./build/style-index.css" -} diff --git a/blockparty-faq.php b/blockparty-faq.php index 5e748a8..34569ea 100644 --- a/blockparty-faq.php +++ b/blockparty-faq.php @@ -50,48 +50,6 @@ require_once BLOCKPARTY_FAQ_DIR . 'includes/hooks/schema.php'; require_once BLOCKPARTY_FAQ_DIR . 'includes/schema/faq_schema.php'; -/** - * Load plugin text domain for PHP translations (PO/MO files). - * - * @since 1.0.2 - * - * @return void - */ -function blockparty_faq_load_textdomain(): void { - load_plugin_textdomain( - 'blockparty-faq', - false, - dirname( plugin_basename( __FILE__ ) ) . '/languages' - ); -} - -add_action( 'plugins_loaded', __NAMESPACE__ . '\\blockparty_faq_load_textdomain' ); - -/** - * Load JavaScript translations (JSON files) for blocks. - * - * @since 1.0.2 - * - * @return void - */ -function blockparty_faq_set_script_translations(): void { - if ( ! function_exists( 'wp_set_script_translations' ) ) { - return; - } - - // WordPress generates handles for block scripts based on block name and script type - // For blockparty/faq with editorScript, the handle is: blockparty-faq-editor-script - $script_handle = 'blockparty-faq-editor-script'; - - wp_set_script_translations( - $script_handle, - 'blockparty-faq', - BLOCKPARTY_FAQ_DIR . 'languages' - ); -} - -add_action( 'enqueue_block_editor_assets', __NAMESPACE__ . '\\blockparty_faq_set_script_translations', 1 ); - /** * Initialize plugin blocks. * @@ -100,15 +58,41 @@ function blockparty_faq_set_script_translations(): void { * @return void */ function blockparty_faq_init(): void { - // Register main block (from root block.json) - register_block_type( __DIR__ ); + load_plugin_textdomain( 'blockparty-faq', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' ); + + // Register main block (from src/faq/block.json) + register_block_type( __DIR__ . '/build/faq' ); // Register child blocks // These blocks are also registered via JavaScript in src/index.js, // but we need to register them in PHP so WordPress knows about their block.json metadata - register_block_type( __DIR__ . '/src/faq-item' ); - register_block_type( __DIR__ . '/src/faq-question' ); - register_block_type( __DIR__ . '/src/faq-answer' ); + register_block_type( __DIR__ . '/build/faq-item' ); + register_block_type( __DIR__ . '/build/faq-question' ); + register_block_type( __DIR__ . '/build/faq-answer' ); + + // Load translations for JS + wp_set_script_translations( 'blockparty-faq-editor-script', 'blockparty-faq', BLOCKPARTY_FAQ_DIR . 'languages' ); + + // Pass PHP values to main script + $constants = [ + 'accordionConfig' => apply_filters( + 'beapi_faq_block_config', + [ + 'allowMultiple' => true, + 'closedDefault' => true, + 'forceExpand' => false, + 'hasAnimation' => true, + 'openMultiple' => false, + 'panelSelector' => '.faq__panel', + 'prefixId' => 'block-faq', + 'triggerSelector' => '.faq__trigger', + ] + ), + ]; + + wp_localize_script( 'blockparty-faq-view-script', 'beapiFaqBlock', $constants ); + + do_action( 'blockparty_faq_init' ); } add_action( 'init', __NAMESPACE__ . '\\blockparty_faq_init' ); diff --git a/package-lock.json b/package-lock.json index 372493b..b42f093 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.2", "license": "GPL-2.0-or-later", "dependencies": { + "@beapi/be-a11y": "^1.7.2", "@wordpress/block-editor": "^12.22.0", "@wordpress/blocks": "^12.31.0", "@wordpress/components": "^27.2.0", @@ -2032,6 +2033,15 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@beapi/be-a11y": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@beapi/be-a11y/-/be-a11y-1.7.2.tgz", + "integrity": "sha512-4/k5Iul3wFUiQQDn5zGeCJB5MOx+rfkfJnP2EwyJpqFA2MdSw/OSnmIP1tb+8XheHASQxHqMl4iNUvaivAKIPQ==", + "dependencies": { + "body-scroll-lock": "4.0.0-beta.0", + "oneloop.js": "^5.1.4" + } + }, "node_modules/@csstools/selector-specificity": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz", @@ -8484,6 +8494,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/body-scroll-lock": { + "version": "4.0.0-beta.0", + "resolved": "https://registry.npmjs.org/body-scroll-lock/-/body-scroll-lock-4.0.0-beta.0.tgz", + "integrity": "sha512-a7tP5+0Mw3YlUJcGAKUqIBkYYGlYxk2fnCasq/FUph1hadxlTRjF+gAcZksxANnaMnALjxEddmSi/H3OR8ugcQ==" + }, "node_modules/bonjour-service": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", @@ -18560,6 +18575,11 @@ "wrappy": "1" } }, + "node_modules/oneloop.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/oneloop.js/-/oneloop.js-5.2.2.tgz", + "integrity": "sha512-hcZ9yMYdnr2ND6ZwXeUNcfiAKDRspxUPwU9gzVgotm0ZGxkY7Hnc6kKLObOmOMH8IREBHVP/LDKlF3/qj8nH5Q==" + }, "node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", diff --git a/package.json b/package.json index 50315a7..3648855 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "make-json": "wp i18n make-json languages/blockparty-faq-fr_FR.po languages/ --no-purge" }, "dependencies": { + "@beapi/be-a11y": "^1.7.2", "@wordpress/block-editor": "^12.22.0", "@wordpress/blocks": "^12.31.0", "@wordpress/components": "^27.2.0", diff --git a/src/faq/block.json b/src/faq/block.json new file mode 100644 index 0000000..4c2d748 --- /dev/null +++ b/src/faq/block.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "blockparty/faq", + "version": "1.0.2", + "title": "FAQ", + "category": "design", + "description": "A FAQ block for WordPress Editor that provided structured data based on FAQ schema.", + "supports": { + "html": false, + "innerBlocks": true + }, + "textdomain": "blockparty-faq", + "editorScript": "file:./index.js", + "viewScript": "file:./script.js", + "editorStyle": "file:./index.css", + "style": "file:./style-index.css" +} diff --git a/src/faq/index.js b/src/faq/index.js index 9331f57..7689e23 100644 --- a/src/faq/index.js +++ b/src/faq/index.js @@ -12,12 +12,14 @@ import './editor.scss'; import Edit from './edit'; import save from './save'; import deprecated from './deprecated'; +import metadata from './block.json'; // Register child blocks first import '../faq-item'; // Register parent block -registerBlockType( 'blockparty/faq', { +registerBlockType( metadata.name, { + ...metadata, /** * @see ./edit.js */ @@ -27,6 +29,7 @@ registerBlockType( 'blockparty/faq', { * @see ./save.js */ save, + icon: help, /** diff --git a/src/faq/script.js b/src/faq/script.js new file mode 100644 index 0000000..9990707 --- /dev/null +++ b/src/faq/script.js @@ -0,0 +1,12 @@ +import { Accordion } from '@beapi/be-a11y'; + +// eslint-disable-next-line no-undef +const accordionConfig = beapiFaqBlock.accordionConfig; + +// Initialize beapi-accordion +window.addEventListener( 'load', function () { + Accordion.init( + '.wp-block-blockparty-faq:has(button.faq__trigger)', + accordionConfig + ); +} );