Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions includes/Editor/Meta.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ final class Meta {
*/
public const BEEHIIV_POST_ID = '_beehiiv_post_id';

/**
* Set when the post was first synced with Group blocks converted to beehiiv sections.
*
* Posts synced before section support lack it and keep omitting Group blocks on re-sync.
*
* @since x.x.x
*/
public const BEEHIIV_GROUP_SECTIONS = '_beehiiv_group_sections';

/**
* UTC ISO 8601 datetime last synced to beehiiv `scheduled_at` for a linked post.
*
Expand Down
73 changes: 68 additions & 5 deletions includes/Newsletter/BlockConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Beehiiv\API\Resources\AdvertisementOpportunities;
use Beehiiv\Editor\Meta;
use Beehiiv\Newsletter\Converters\ColumnsBlockConverter;
use Beehiiv\Newsletter\Converters\GroupBlockConverter;
use Beehiiv\Newsletter\Converters\MediaTextBlockConverter;
use Beehiiv\Newsletter\Converters\TableBlockConverter;
use Beehiiv\Newsletter\Converters\ListBlockConverter;
Expand Down Expand Up @@ -38,7 +39,12 @@
* - `core/more` — snippet newsletters only; beehiiv Read More `button` via `convert_more_block()`
* - `beehiiv/advertisement` — beehiiv `advertisement` block via `convert_advertisement_block()`
*
* Layout blocks (`core/group`, `core/columns`, etc.) are skipped at the top level.
* - `core/group` — `convert_group_block()` (beehiiv `section` block holding the converted inner
* blocks; only for posts whose first sync used sections, see `PostSettingsBuilder`; a Group
* inside a Group is omitted because beehiiv sections cannot hold sections)
*
* Other layout blocks (`core/columns`, etc.) are skipped at the top level, and so is
* `core/group` for posts first synced before section support.
* `convert_columns_block()` exists for internal reuse (e.g. `core/media-text`) and is
* not wired into the main block walk yet. Snippet mode stops at `core/more`.
*
Expand All @@ -49,11 +55,13 @@ final class BlockConverter {
/**
* Convert a WordPress post's blocks to beehiiv blocks.
*
* @param WP_Post $post_object Post object.
* @param WP_Post $post_object Post object.
* @param bool $convert_groups Whether `core/group` blocks become beehiiv sections. When false,
* Group blocks are omitted.
* @return array<int, array<string, mixed>>
* @since 1.0.0
*/
public static function convert_supported_blocks( WP_Post $post_object ): array {
public static function convert_supported_blocks( WP_Post $post_object, bool $convert_groups = false ): array {
$beehiiv_blocks = [];
$wp_blocks = parse_blocks( $post_object->post_content );

Expand All @@ -73,7 +81,8 @@ public static function convert_supported_blocks( WP_Post $post_object ): array {
$converted_blocks = self::convert_blocks(
$wp_blocks,
$post_object,
$is_send_newsletter_snippet
$is_send_newsletter_snippet,
$convert_groups
);

return array_values(
Expand All @@ -89,13 +98,15 @@ public static function convert_supported_blocks( WP_Post $post_object ): array {
* @param array<int, array<string, mixed>> $wp_blocks Parsed blocks.
* @param WP_Post $post_object Post object.
* @param bool $is_send_newsletter_snippet Whether snippet mode is enabled.
* @param bool $convert_groups Whether Group blocks become sections.
* @return array<int, array<string, mixed>>
* @since 1.0.0
*/
private static function convert_blocks(
array $wp_blocks,
WP_Post $post_object,
bool $is_send_newsletter_snippet
bool $is_send_newsletter_snippet,
bool $convert_groups = false
): array {
$beehiiv_blocks = [];

Expand All @@ -111,6 +122,16 @@ private static function convert_blocks(
break;
}

if ( 'core/group' === $block_name ) {
$section_block = $convert_groups ? self::convert_group_block( $wp_block, $post_object ) : [];

if ( ! empty( $section_block ) ) {
$beehiiv_blocks[] = $section_block;
}

continue;
}

if ( ! SupportedBlocks::is_supported( $block_name, $is_send_newsletter_snippet ) ) {
continue;
}
Expand All @@ -137,6 +158,48 @@ private static function convert_blocks(
return $beehiiv_blocks;
}

/**
* Convert a core/group block to a beehiiv section block.
*
* The Group's inner blocks are converted with the same rules as top-level blocks and
* placed in the section in their original order. A Group nested inside a Group is
* omitted with its inner blocks, because a beehiiv section cannot hold another section
* and sending one makes beehiiv reject the whole post. The Group's background colour, text
* colour, padding, margin and border go in the section's `visual_settings`; every other setting is
* left at beehiiv defaults. Returns an empty array when no inner block converts, so no empty section
* is sent. Snippet-only blocks are omitted inside a section.
*
* @param array<string, mixed> $wp_block Parsed core/group block.
* @param WP_Post $post_object Post object.
* @return array<string, mixed>
* @since x.x.x
*/
private static function convert_group_block( array $wp_block, WP_Post $post_object ): array {
$inner_blocks = self::convert_blocks(
$wp_block['innerBlocks'] ?? [],
$post_object,
false,
false
);

if ( empty( $inner_blocks ) ) {
return [];
}

$section = [
'type' => 'section',
'blocks' => $inner_blocks,
];

$visual_settings = GroupBlockConverter::build_visual_settings( $wp_block );

if ( ! empty( $visual_settings ) ) {
$section['visual_settings'] = $visual_settings;
}

return $section;
}

/**
* Convert a single supported WordPress block.
*
Expand Down
Loading
Loading