Skip to content
Merged
Prev Previous commit
Next Next commit
Try server-side rendering the block
# Conflicts:
#	packages/block-library/src/table-of-contents/save.js
  • Loading branch information
mikachan committed Sep 24, 2025
commit 94942db456455b71fe949d258946934dfd3b38f7
1 change: 1 addition & 0 deletions lib/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ function gutenberg_reregister_core_block_types() {
'site-logo.php' => 'core/site-logo',
'site-tagline.php' => 'core/site-tagline',
'site-title.php' => 'core/site-title',
'table-of-contents.php' => 'core/table-of-contents',
'tag-cloud.php' => 'core/tag-cloud',
'template-part.php' => 'core/template-part',
'term-description.php' => 'core/term-description',
Expand Down
2 changes: 0 additions & 2 deletions packages/block-library/src/table-of-contents/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { tableOfContents as icon } from '@wordpress/icons';
import initBlock from '../utils/init-block';
import metadata from './block.json';
import edit from './edit';
import save from './save';

const { name } = metadata;

Expand All @@ -19,7 +18,6 @@ export { metadata, name };
export const settings = {
icon,
edit,
save,
example: {
innerBlocks: [
{
Expand Down
135 changes: 135 additions & 0 deletions packages/block-library/src/table-of-contents/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<?php
/**
* Server-side rendering of the `core/table-of-contents` block.
*
* @package WordPress
*/

/**
* Renders the `core/table-of-contents` block on the server.
*
* @param array $attributes Block attributes.
* @return string Returns the table of contents markup.
*/
function render_block_core_table_of_contents( $attributes ) {
$headings = isset( $attributes['headings'] ) ? $attributes['headings'] : array();

if ( empty( $headings ) ) {
return '';
}

// Get the aria-label from block attributes, or fallback to localized default.
$aria_label = '';
if ( isset( $attributes['ariaLabel'] ) && ! empty( $attributes['ariaLabel'] ) ) {
$aria_label = $attributes['ariaLabel'];
} else {
$aria_label = __( 'Table of Contents', 'gutenberg' );
}

$wrapper_attributes = get_block_wrapper_attributes(
array(
'aria-label' => $aria_label,
)
);

$nested_headings = convert_linear_to_nested_headings( $headings );
$list_items = build_nested_list_items( $nested_headings );

$html = sprintf(
'<nav %s><ol>%s</ol></nav>',
$wrapper_attributes,
$list_items
);

$processor = new WP_HTML_Tag_Processor( $html );
if ( $processor->next_tag( 'nav' ) ) {
$processor->set_attribute( 'aria-label', $aria_label );
}

return $processor->get_updated_html();
}

/**
* Converts linear heading list to nested structure.
*
* @param array $headings Linear array of headings.
* @return array Nested array of headings.
*/
function convert_linear_to_nested_headings( $headings ) {

Check failure on line 58 in packages/block-library/src/table-of-contents/index.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

The function name 'convert_linear_to_nested_headings()' is invalid. In this file, PHP function names must either match one of the allowed prefixes exactly or begin with one of them, followed by an underscore. The allowed prefixes are: 'block_core_table_of_contents', 'render_block_core_table_of_contents', 'register_block_core_table_of_contents'.
$nested_heading_list = array();

foreach ( $headings as $key => $heading ) {
if ( empty( $heading['content'] ) ) {
continue;
}

if ( $heading['level'] === $headings[0]['level'] ) {
if ( isset( $headings[ $key + 1 ] ) && $headings[ $key + 1 ]['level'] > $heading['level'] ) {
$end_of_slice = count( $headings );
for ( $i = $key + 1; $i < count( $headings ); $i++ ) {
if ( $headings[ $i ]['level'] === $heading['level'] ) {
$end_of_slice = $i;
break;
}
}

$nested_heading_list[] = array(
'heading' => $heading,

Check warning on line 77 in packages/block-library/src/table-of-contents/index.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Array double arrow not aligned correctly; expected 2 space(s) between "'heading'" and double arrow, but found 1.
'children' => convert_linear_to_nested_headings(
array_slice( $headings, $key + 1, $end_of_slice - $key - 1 )
),
);
} else {
$nested_heading_list[] = array(
'heading' => $heading,

Check warning on line 84 in packages/block-library/src/table-of-contents/index.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Array double arrow not aligned correctly; expected 2 space(s) between "'heading'" and double arrow, but found 1.
'children' => array(),
);
}
}
}

return $nested_heading_list;
}

/**
* Builds nested list items HTML recursively.
*
* @param array $nested_headings Nested array of headings.
* @return string HTML for nested list items.
*/
function build_nested_list_items( $nested_headings ) {

Check failure on line 100 in packages/block-library/src/table-of-contents/index.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

The function name 'build_nested_list_items()' is invalid. In this file, PHP function names must either match one of the allowed prefixes exactly or begin with one of them, followed by an underscore. The allowed prefixes are: 'block_core_table_of_contents', 'render_block_core_table_of_contents', 'register_block_core_table_of_contents'.
$html = '';

foreach ( $nested_headings as $item ) {
$heading = $item['heading'];
$link = ! empty( $heading['link'] ) ? $heading['link'] : '';

Check warning on line 105 in packages/block-library/src/table-of-contents/index.php

View workflow job for this annotation

GitHub Actions / PHP coding standards

Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

$html .= '<li>';
$html .= sprintf(
'<a class="wp-block-table-of-contents__entry" href="%s">%s</a>',
esc_url( $link ),
esc_html( $heading['content'] )
);

if ( ! empty( $item['children'] ) ) {
$html .= '<ol>' . build_nested_list_items( $item['children'] ) . '</ol>';
}

$html .= '</li>';
}

return $html;
}

/**
* Registers the `core/table-of-contents` block on the server.
*/
function register_block_core_table_of_contents() {
register_block_type_from_metadata(
__DIR__ . '/table-of-contents',
array(
'render_callback' => 'render_block_core_table_of_contents',
)
);
}
add_action( 'init', 'register_block_core_table_of_contents' );
29 changes: 0 additions & 29 deletions packages/block-library/src/table-of-contents/save.js

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
<!-- wp:table-of-contents {"headings":[{"content":"Heading text","level":2,"link":"#heading-id-1"},{"content":"A sub-heading","level":3,"link":"#heading-id-2"}]} -->
<nav class="wp-block-table-of-contents" aria-label="Table of Contents"><ol><li><a class="wp-block-table-of-contents__entry" href="#heading-id-1">Heading text</a><ol><li><a class="wp-block-table-of-contents__entry" href="#heading-id-2">A sub-heading</a></li></ol></li></ol></nav>
<!-- /wp:table-of-contents -->
<!-- wp:table-of-contents {"headings":[{"content":"Heading text","level":2,"link":"#heading-id-1"},{"content":"A sub-heading","level":3,"link":"#heading-id-2"}]} /-->
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@
]
},
"innerBlocks": [],
"innerHTML": "\n<nav class=\"wp-block-table-of-contents\" aria-label=\"Table of Contents\"><ol><li><a class=\"wp-block-table-of-contents__entry\" href=\"#heading-id-1\">Heading text</a><ol><li><a class=\"wp-block-table-of-contents__entry\" href=\"#heading-id-2\">A sub-heading</a></li></ol></li></ol></nav>\n",
"innerContent": [
"\n<nav class=\"wp-block-table-of-contents\" aria-label=\"Table of Contents\"><ol><li><a class=\"wp-block-table-of-contents__entry\" href=\"#heading-id-1\">Heading text</a><ol><li><a class=\"wp-block-table-of-contents__entry\" href=\"#heading-id-2\">A sub-heading</a></li></ol></li></ol></nav>\n"
]
"innerHTML": "",
"innerContent": []
}
]
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
<!-- wp:table-of-contents {"headings":[{"content":"Heading text","level":2,"link":"#heading-id-1"},{"content":"A sub-heading","level":3,"link":"#heading-id-2"}]} -->
<nav class="wp-block-table-of-contents" aria-label="Table of Contents"><ol><li><a class="wp-block-table-of-contents__entry" href="#heading-id-1">Heading text</a><ol><li><a class="wp-block-table-of-contents__entry" href="#heading-id-2">A sub-heading</a></li></ol></li></ol></nav>
<!-- /wp:table-of-contents -->
<!-- wp:table-of-contents {"headings":[{"content":"Heading text","level":2,"link":"#heading-id-1"},{"content":"A sub-heading","level":3,"link":"#heading-id-2"}]} /-->
Loading