1122 content planner create an endpoints for inline banner#23105
Conversation
66b1b1d to
efbdd00
Compare
efbdd00 to
b5060fa
Compare
| echo new Meta_Fields_Presenter( $this->get_metabox_post(), 'social' ); | ||
| } | ||
|
|
||
| $is_block_editor = WP_Screen::get()->is_block_editor(); |
There was a problem hiding this comment.
I see $is_block_editor being redeclared 3 times: is there a specific reason for it not to be a class attribute? 🤔
There was a problem hiding this comment.
Good point, fixing it
- Root cause: WP_Screen::get() was called in __construct(), which runs during plugins_loaded. WP_Screen isn't loaded until later in the WordPress boot sequence. - Fix: Removed the direct call from the constructor and instead hooked set_is_block_editor() to current_screen, which fires after WP_Screen is available. The $is_block_editor property defaults to false and gets set before admin_enqueue_scripts fires (where it's used).
0798ab9 to
b9bfaf1
Compare
we can't detect in save data if it's coming from block editor, so we check only for post type
|
I'm actually rethinking my choices in this PR, following my work on #23117, Which means,
|
…ementation The inline banner state was tracked via hidden input fields, a Redux store with persistence middleware, and post meta saved through the metabox. All of this is replaced by a WordPress block approach where the block's presence in post_content is the state. This commit removes the old mechanism: - Delete Redux store (banner.js, store/index.js) and persistence middleware - Delete hidden field helpers (fields.js) and their tests - Delete HOC container (containers/inline-banner.js) - Delete ContentPlannerEditorPlugin component and its tests - Remove content_planner meta field definitions from class-wpseo-meta.php - Remove content_planner hidden field rendering and saving from class-metabox.php - Revert is_block_editor from class property back to local variable (trunk pattern) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the HOC filter approach with a proper WordPress block (yoast/content-planner-banner). The block is auto-inserted after the first paragraph on new posts and removed from post_content when dismissed. - Add block.json with inserter:false, multiple:false, reusable:false - Add block.js with Edit component wrapping InlineBanner and save:null - Add Content_Planner_Block PHP integration for server-side registration - Add yoast-ai-blocks category to block-categories.php - Rewrite initialize.js with auto-insertion logic via editor plugin Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the HOC filter approach with a proper WordPress block (yoast/content-planner-banner). The block is auto-inserted after the first paragraph on new posts and removed from post_content when dismissed. - Add block.json with inserter:false, multiple:false, reusable:false - Add block.js with Edit component wrapping InlineBanner and save:null - Add Content_Planner_Block PHP integration for server-side registration - Add yoast-ai-blocks category to block-categories.php - Rewrite initialize.js with auto-insertion logic via editor plugin Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ner' of https://github.com/Yoast/wordpress-seo into 1122-content-planner-create-an-endpoints-for-inline-banner # Conflicts: # src/integrations/blocks/content-planner-block.php
Done 👍 |
There was a problem hiding this comment.
Pull request overview
This PR introduces a new “Content Planner Banner” Gutenberg block and supporting editor logic to automatically place it after the first paragraph on new posts (with a dedicated “Yoast AI Blocks” category).
Changes:
- Add a PHP integration to register the content planner banner block type and a new block category.
- Refactor the editor behavior to insert a dedicated banner block (and inject Tailwind styles via the block edit component), removing the previous custom store/HOC approach.
- Improve SVG icon accessibility via
useSvgAriaand remove now-obsolete JS tests/store modules.
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/integrations/blocks/content-planner-block.php | Registers the banner block type on init behind AI_Conditional. |
| src/integrations/blocks/block-categories.php | Adds the yoast-ai-blocks block category. |
| packages/js/src/ai-content-planner/initialize.js | Implements the editor plugin that auto-inserts the banner block on new posts. |
| packages/js/src/ai-content-planner/block.js | Registers the banner block and provides the editor-only Edit UI (with Tailwind CSS injection). |
| packages/js/src/ai-content-planner/block.json | Defines the banner block metadata and support flags. |
| packages/js/src/ai-content-planner/components/inline-banner.js | Adds useSvgAria for icons in the inline banner UI. |
| packages/js/src/ai-content-planner/components/one-spark-note.js | Adds useSvgAria for the spark note icon. |
| packages/js/src/ai-content-planner/store/index.js | Removes the previous custom WP data store. |
| packages/js/src/ai-content-planner/store/banner.js | Removes the previous banner slice/selectors/actions. |
| packages/js/src/ai-content-planner/content-planner-editor-plugin.js | Removes the previous editor plugin implementation tied to the custom store. |
| packages/js/src/ai-content-planner/containers/inline-banner.js | Removes the previous container wiring (store-based dismiss/premium selection). |
| packages/js/tests/ai-content-planner/content-planner-editor-plugin.test.js | Removes the previous unit tests for auto-insertion behavior. |
| inc/class-wpseo-meta.php | Minor formatting/alignment change in meta field definitions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| * @return void | ||
| */ | ||
| public function register_block() { | ||
| \register_block_type( \WPSEO_PATH . 'blocks/ai-content-planner/block.json' ); |
There was a problem hiding this comment.
register_block_type() points at WPSEO_PATH . 'blocks/ai-content-planner/block.json', but there is no blocks/ directory in the repo and this PR only adds packages/js/src/ai-content-planner/block.json. If the build step doesn’t generate/copy the block.json into blocks/ai-content-planner/, WordPress will fail to register the block at runtime when AI is enabled. Please ensure the build/test setup produces that file at the expected path (and update the test bootstrap required-files list if needed).
| \register_block_type( \WPSEO_PATH . 'blocks/ai-content-planner/block.json' ); | |
| \register_block_type( \WPSEO_PATH . 'packages/js/src/ai-content-planner/block.json' ); |
There was a problem hiding this comment.
The blocks directory is created at build time, and block files are copied into it. I just needed to add a stub of the block to let integration tests run.
| function insertBannerAfterFirstParagraph( blocks, insertBlock ) { | ||
| const hasBanner = blocks.some( b => b.name === "yoast/content-planner-banner" ); | ||
| if ( hasBanner ) { | ||
| return true; | ||
| } | ||
|
|
||
| const { isBannerDismissed, shouldShowBanner } = useSelect( select => ( { | ||
| isBannerDismissed: select( STORE_NAME )?.getIsBannerDismissed?.(), | ||
| shouldShowBanner: select( STORE_NAME )?.getShouldShowBanner?.(), | ||
| } ), [] ); | ||
| // If canvas is empty, insert a paragraph first; the effect will re-run. | ||
| if ( blocks.length === 0 ) { | ||
| // eslint-disable-next-line no-undefined | ||
| insertBlock( createBlock( "core/paragraph" ), 0, undefined, false ); | ||
| return false; | ||
| } | ||
|
|
||
| const isNewPost = useSelect( select => select( "core/editor" ).isEditedPostNew(), [] ); | ||
| const firstParagraphIndex = blocks.findIndex( b => b.name === "core/paragraph" ); | ||
| if ( firstParagraphIndex === -1 ) { | ||
| return false; | ||
| } | ||
|
|
||
| const { showBanner } = useDispatch( STORE_NAME ); | ||
| // eslint-disable-next-line no-undefined | ||
| insertBlock( createBlock( "yoast/content-planner-banner" ), firstParagraphIndex + 1, undefined, false ); | ||
| return true; |
There was a problem hiding this comment.
The auto-insertion behavior for the content planner banner block is no longer covered by unit tests: content-planner-editor-plugin.test.js was removed, but there are no new Jest tests verifying banner insertion (new post + post type conditions, inserting paragraph when empty, avoiding duplicate insertions). Please add/port tests for insertBannerAfterFirstParagraph/ContentPlannerEditorPlugin to prevent regressions.
|
LGTM ✅ |


Context
Summary
This PR can be summarized in the following changelog entry:
Relevant technical choices:
Test instructions
Test instructions for the acceptance test before the PR gets merged
This PR can be acceptance tested by following these steps:
Setup
Banner appears on a new post
Dismiss persists across reloads
4. Click the X button on the banner — confirm the banner disappears.
5. Save or publish the post, then reload the page.
6. Confirm the banner does not reappear after the reload.
Rendered state persists across reloads
7. Create another new post (Posts → Add New) and let the banner render without dismissing it.
8. Save the post as a draft, then reload the page.
9. Confirm the banner is still visible after the reload.
Existing post — no banner
10. Open an existing post that has never had the banner rendered (i.e. was created before this branch was active, or was never opened with the banner).
11. Confirm the banner does not appear.
Non-post post types
12. Go to Pages → Add New to open the block editor for a new page.
13. Confirm the banner does not appear.
Relevant test scenarios
Test instructions for QA when the code is in the RC
QA can test this PR by following these steps:
Impact check
This PR affects the following parts of the plugin, which may require extra testing:
Other environments
[shopify-seo], added test instructions for Shopify and attached theShopifylabel to this PR.[yoast-doc-extension], added test instructions for Yoast SEO for Google Docs and attached theGoogle Docs Add-onlabel to this PR.Documentation
Quality assurance
grunt build:imagesand commited the results, if my PR introduces new images or SVGs.Innovation
innovationlabel.Fixes #