From 6ef52d6873125362d90e46874a81a46b96d3e050 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Mon, 4 Sep 2023 19:56:45 +0400 Subject: [PATCH 01/15] Table of Contents: Try storing in post-meta --- lib/blocks.php | 1 + .../src/table-of-contents/block.json | 1 + .../src/table-of-contents/edit.js | 15 +- .../src/table-of-contents/hooks.js | 47 +++-- .../src/table-of-contents/index.php | 169 ++++++++++++++++++ 5 files changed, 218 insertions(+), 15 deletions(-) create mode 100644 packages/block-library/src/table-of-contents/index.php diff --git a/lib/blocks.php b/lib/blocks.php index 34ef63f2e87ad9..15f8652c41eeae 100644 --- a/lib/blocks.php +++ b/lib/blocks.php @@ -110,6 +110,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', diff --git a/packages/block-library/src/table-of-contents/block.json b/packages/block-library/src/table-of-contents/block.json index 5fa7e37e6acbdf..b30f877bb3fec9 100644 --- a/packages/block-library/src/table-of-contents/block.json +++ b/packages/block-library/src/table-of-contents/block.json @@ -8,6 +8,7 @@ "description": "Summarize your post with a list of headings. Add HTML anchors to Heading blocks to link them here.", "keywords": [ "document outline", "summary" ], "textdomain": "default", + "usesContext": [ "postId", "postType" ], "attributes": { "headings": { "type": "array", diff --git a/packages/block-library/src/table-of-contents/edit.js b/packages/block-library/src/table-of-contents/edit.js index 915375606b10c3..b78ca3fb7a88a4 100644 --- a/packages/block-library/src/table-of-contents/edit.js +++ b/packages/block-library/src/table-of-contents/edit.js @@ -17,6 +17,7 @@ import { ToolbarGroup, } from '@wordpress/components'; import { useDispatch, useSelect } from '@wordpress/data'; +import { useEntityProp } from '@wordpress/core-data'; import { renderToString } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; @@ -35,19 +36,22 @@ import { useObserveHeadings } from './hooks'; * * @param {Object} props The props. * @param {Object} props.attributes The block attributes. - * @param {HeadingData[]} props.attributes.headings A list of data for each heading in the post. * @param {boolean} props.attributes.onlyIncludeCurrentPage Whether to only include headings from the current page (if the post is paginated). * @param {string} props.clientId * @param {(attributes: Object) => void} props.setAttributes + * @param {Object} props.context + * @param {string} props.context.postType + * @param {number} props.context.postId * * @return {WPComponent} The component. */ export default function TableOfContentsEdit( { - attributes: { headings = [], onlyIncludeCurrentPage }, + attributes: { onlyIncludeCurrentPage }, + context: { postType, postId }, clientId, setAttributes, } ) { - useObserveHeadings( clientId ); + useObserveHeadings( { clientId, postType, postId } ); const blockProps = useBlockProps(); @@ -64,6 +68,11 @@ export default function TableOfContentsEdit( { const { replaceBlocks } = useDispatch( blockEditorStore ); + const [ meta ] = useEntityProp( 'postType', postType, 'meta', postId ); + const headings = meta?.table_of_contents + ? JSON.parse( meta.table_of_contents ) + : []; + const headingTree = linearToNestedHeadingList( headings ); const toolbarControls = canInsertList && ( diff --git a/packages/block-library/src/table-of-contents/hooks.js b/packages/block-library/src/table-of-contents/hooks.js index af7b66568123f8..f294afd59be318 100644 --- a/packages/block-library/src/table-of-contents/hooks.js +++ b/packages/block-library/src/table-of-contents/hooks.js @@ -7,6 +7,7 @@ import fastDeepEqual from 'fast-deep-equal/es6'; * WordPress dependencies */ import { useRegistry } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; import { __unstableStripHTML as stripHTML } from '@wordpress/dom'; import { useEffect } from '@wordpress/element'; import { addQueryArgs, removeQueryArgs } from '@wordpress/url'; @@ -122,35 +123,57 @@ function getLatestHeadings( select, clientId ) { return latestHeadings; } -function observeCallback( select, dispatch, clientId ) { - const { getBlockAttributes } = select( blockEditorStore ); - const { updateBlockAttributes, __unstableMarkNextChangeAsNotPersistent } = - dispatch( blockEditorStore ); +function observeCallback( select, dispatch, context ) { + const { clientId, postType, postId } = context; + const { getBlock } = select( blockEditorStore ); /** * If the block no longer exists in the store, skip the update. * The "undo" action recreates the block and provides a new `clientId`. * The hook still might be observing the changes while the old block unmounts. */ - const attributes = getBlockAttributes( clientId ); - if ( attributes === null ) { + if ( getBlock( clientId ) === null ) { return; } + const { getEditedEntityRecord } = select( coreStore ); + const { editEntityRecord } = dispatch( coreStore ); + + const meta = getEditedEntityRecord( 'postType', postType, postId ).meta; + const storedHeadings = meta?.table_of_contents + ? JSON.parse( meta.table_of_contents ) + : []; + const headings = getLatestHeadings( select, clientId ); - if ( ! fastDeepEqual( headings, attributes.headings ) ) { - __unstableMarkNextChangeAsNotPersistent(); - updateBlockAttributes( clientId, { headings } ); + if ( ! fastDeepEqual( headings, storedHeadings ) ) { + editEntityRecord( + 'postType', + postType, + postId, + { + meta: { + ...meta, + table_of_contents: JSON.stringify( headings ), + }, + }, + { + undoIgnore: true, + } + ); } } -export function useObserveHeadings( clientId ) { +export function useObserveHeadings( { clientId, postType, postId } ) { const registry = useRegistry(); useEffect( () => { // Todo: Limit subscription to block editor store when data no longer depends on `getPermalink`. // See: https://github.com/WordPress/gutenberg/pull/45513 return registry.subscribe( () => - observeCallback( registry.select, registry.dispatch, clientId ) + observeCallback( registry.select, registry.dispatch, { + clientId, + postType, + postId, + } ) ); - }, [ registry, clientId ] ); + }, [ registry, clientId, postType, postId ] ); } diff --git a/packages/block-library/src/table-of-contents/index.php b/packages/block-library/src/table-of-contents/index.php new file mode 100644 index 00000000000000..abc7d48e2a4c07 --- /dev/null +++ b/packages/block-library/src/table-of-contents/index.php @@ -0,0 +1,169 @@ + true, + 'single' => true, + 'type' => 'string', + ) + ); + } + + 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' ); + +/** + * Takes a flat list of heading parameters and nests them based on each header's + * immediate parent's level. + * + * @since 6.4.0 + * + * @param array $headings A flat list of headings to nest. + * @return array $tree An array of nested headings. + */ +function block_core_table_of_contents_build_headings_tree( $headings ) { + if ( empty( $headings ) ) { + return $headings; + } + + $tree = array(); + $firstHeading = $headings[0]; + $total = count( $headings ); + + foreach ( $headings as $key => $heading ) { + if ( empty( $heading['content'] ) ) { + continue; + } + + // Make sure we are only working with the same level as the first iteration in our set. + if ( $heading['level'] !== $firstHeading['level'] ) { + continue; + } + + /** + * Check that the next iteration will return a value. + * If it does and the next level is greater than the current level, + * the next iteration becomes a child of the current iteration. + */ + $offset = $key + 1; + if ( isset( $headings[ $offset ]['level'] ) && (int) $headings[ $offset ]['level'] > (int) $heading['level'] ) { + /** + * Calculate the last index before the next iteration with the same level (siblings). + * Then, use this index to slice the array for use in recursion. + * This prevents duplicate nodes. + */ + $end = $total; + for ( $i = $offset; $i < $total; $i++ ) { + if ( $headings[ $i ]['level'] === $heading['level'] ) { + $end = $i; + break; + } + } + + $tree[] = array( + 'heading' => $heading, + 'children' => block_core_table_of_contents_build_headings_tree( array_slice( $headings, $offset, $end - 1 ) ), + ); + + } else { + // No child heading levels. + $tree[] = array( + 'heading' => $heading, + ); + } + } + + return $tree; +} + +/** + * Build a list of Table of Content items. + * + * @since 6.4.0 + * + * @param array $headings An array of nested headings. + * @return string $list A list of Table of Content items. + */ +function block_core_table_of_contents_build_list( $tree ) { + $list = ''; + + foreach ( $tree as $item ) { + $heading = $item['heading']; + $children = isset( $item['children'] ) ? '
    ' . block_core_table_of_contents_build_list( $item['children'] ) . '
' : ''; + + if ( ! empty( $heading['link'] )) { + $content = '' . esc_html( $heading['content'] ) . ''; + } else { + $content = '' . esc_html( $heading['content'] ) . ''; + } + + $list .= '
  • ' . $content . $children . '
  • '; + } + + return $list; +} + +/** + * Renders the `core/table-of-contents` block on the server. + * + * @since 6.4.0 + * + * @param array $attributes Block attributes. + * @param string $content Block default content. + * @param WP_Block $block Block instance. + * + * @return string Returns the HTML representing the table of contents. + */ +function render_block_core_table_of_contents( $attributes, $content, $block ) { + // @todo: Maybe return saved block content if it exists, for backward compatibility. + + // Bail out early if the post ID is not set for some reason. + if ( empty( $block->context['postId'] ) ) { + return ''; + } + + if ( post_password_required( $block->context['postId'] ) ) { + return ''; + } + + $headings = get_post_meta( $block->context['postId'], 'table_of_contents', true ); + + if ( ! $headings ) { + return ''; + } + + $headings = json_decode( $headings, true ); + + if ( ! is_array( $headings ) || count( $headings ) === 0 ) { + return ''; + } + + $tree = block_core_table_of_contents_build_headings_tree( $headings ); + + return sprintf( + '', + get_block_wrapper_attributes(), + block_core_table_of_contents_build_list( $tree ) + ); +} From 95ca9536e41ff1d6da5bda34da6d59cebc9f4ba4 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 6 Sep 2023 17:08:55 +0400 Subject: [PATCH 02/15] Handle deprecation --- docs/reference-guides/core-blocks.md | 2 +- .../src/table-of-contents/block.json | 7 -- .../src/table-of-contents/deprecated.js | 71 +++++++++++++++++++ .../src/table-of-contents/index.js | 4 +- .../src/table-of-contents/index.php | 5 +- .../src/table-of-contents/save.js | 25 ------- .../blocks/core__table-of-contents.html | 4 +- .../blocks/core__table-of-contents.json | 12 ---- .../core__table-of-contents.parsed.json | 21 +----- .../core__table-of-contents.serialized.html | 4 +- ...ore__table-of-contents__deprecated-v1.html | 3 + ...ore__table-of-contents__deprecated-v1.json | 22 ++++++ ...ble-of-contents__deprecated-v1.parsed.json | 24 +++++++ ...f-contents__deprecated-v1.serialized.html} | 0 .../core__table-of-contents__empty.json | 11 --- ...core__table-of-contents__empty.parsed.json | 9 --- ...__table-of-contents__empty.serialized.html | 1 - 17 files changed, 132 insertions(+), 93 deletions(-) create mode 100644 packages/block-library/src/table-of-contents/deprecated.js delete mode 100644 packages/block-library/src/table-of-contents/save.js create mode 100644 test/integration/fixtures/blocks/core__table-of-contents__deprecated-v1.html create mode 100644 test/integration/fixtures/blocks/core__table-of-contents__deprecated-v1.json create mode 100644 test/integration/fixtures/blocks/core__table-of-contents__deprecated-v1.parsed.json rename test/integration/fixtures/blocks/{core__table-of-contents__empty.html => core__table-of-contents__deprecated-v1.serialized.html} (100%) delete mode 100644 test/integration/fixtures/blocks/core__table-of-contents__empty.json delete mode 100644 test/integration/fixtures/blocks/core__table-of-contents__empty.parsed.json delete mode 100644 test/integration/fixtures/blocks/core__table-of-contents__empty.serialized.html diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index e15f321b7b4204..075612a9f1f0b5 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -852,7 +852,7 @@ Summarize your post with a list of headings. Add HTML anchors to Heading blocks - **Experimental:** true - **Category:** layout - **Supports:** color (background, gradients, link, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ -- **Attributes:** headings, onlyIncludeCurrentPage +- **Attributes:** onlyIncludeCurrentPage ## Tag Cloud diff --git a/packages/block-library/src/table-of-contents/block.json b/packages/block-library/src/table-of-contents/block.json index b30f877bb3fec9..a7ada3d01fb2eb 100644 --- a/packages/block-library/src/table-of-contents/block.json +++ b/packages/block-library/src/table-of-contents/block.json @@ -10,13 +10,6 @@ "textdomain": "default", "usesContext": [ "postId", "postType" ], "attributes": { - "headings": { - "type": "array", - "items": { - "type": "object" - }, - "default": [] - }, "onlyIncludeCurrentPage": { "type": "boolean", "default": false diff --git a/packages/block-library/src/table-of-contents/deprecated.js b/packages/block-library/src/table-of-contents/deprecated.js new file mode 100644 index 00000000000000..0ef37f132a9cae --- /dev/null +++ b/packages/block-library/src/table-of-contents/deprecated.js @@ -0,0 +1,71 @@ +/** + * WordPress dependencies + */ +import { useBlockProps } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import TableOfContentsList from './list'; +import { linearToNestedHeadingList } from './utils'; + +const v1 = { + attributes: { + headings: { + type: 'array', + items: { + type: 'object', + }, + default: [], + }, + onlyIncludeCurrentPage: { + type: 'boolean', + default: false, + }, + }, + supports: { + html: false, + color: { + text: true, + background: true, + gradients: true, + link: true, + }, + spacing: { + margin: true, + padding: true, + }, + typography: { + fontSize: true, + lineHeight: true, + __experimentalFontFamily: true, + __experimentalFontWeight: true, + __experimentalFontStyle: true, + __experimentalTextTransform: true, + __experimentalTextDecoration: true, + __experimentalLetterSpacing: true, + __experimentalDefaultControls: { + fontSize: true, + }, + }, + }, + save( { attributes: { headings = [] } } ) { + if ( headings.length === 0 ) { + return null; + } + + return ( + + ); + }, +}; + +export default [ v1 ]; diff --git a/packages/block-library/src/table-of-contents/index.js b/packages/block-library/src/table-of-contents/index.js index 8952f0ff381bb4..4b1ee6eff3c35f 100644 --- a/packages/block-library/src/table-of-contents/index.js +++ b/packages/block-library/src/table-of-contents/index.js @@ -5,7 +5,7 @@ import initBlock from '../utils/init-block'; import metadata from './block.json'; import edit from './edit'; import icon from './icon'; -import save from './save'; +import deprecated from './deprecated'; const { name } = metadata; @@ -14,7 +14,7 @@ export { metadata, name }; export const settings = { icon, edit, - save, + deprecated, }; export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/table-of-contents/index.php b/packages/block-library/src/table-of-contents/index.php index abc7d48e2a4c07..cda5eabeee004d 100644 --- a/packages/block-library/src/table-of-contents/index.php +++ b/packages/block-library/src/table-of-contents/index.php @@ -136,7 +136,10 @@ function block_core_table_of_contents_build_list( $tree ) { * @return string Returns the HTML representing the table of contents. */ function render_block_core_table_of_contents( $attributes, $content, $block ) { - // @todo: Maybe return saved block content if it exists, for backward compatibility. + // Fallback to static content when it exists. The post hasn't been updated to use headings via post-meta. + if ( trim( $content ) !== '' ) { + return $content; + } // Bail out early if the post ID is not set for some reason. if ( empty( $block->context['postId'] ) ) { diff --git a/packages/block-library/src/table-of-contents/save.js b/packages/block-library/src/table-of-contents/save.js deleted file mode 100644 index 7b9556aca33ffd..00000000000000 --- a/packages/block-library/src/table-of-contents/save.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * WordPress dependencies - */ -import { useBlockProps } from '@wordpress/block-editor'; - -/** - * Internal dependencies - */ -import TableOfContentsList from './list'; -import { linearToNestedHeadingList } from './utils'; - -export default function save( { attributes: { headings = [] } } ) { - if ( headings.length === 0 ) { - return null; - } - return ( - - ); -} diff --git a/test/integration/fixtures/blocks/core__table-of-contents.html b/test/integration/fixtures/blocks/core__table-of-contents.html index 6c8ef7aee27d75..cd71582269d837 100644 --- a/test/integration/fixtures/blocks/core__table-of-contents.html +++ b/test/integration/fixtures/blocks/core__table-of-contents.html @@ -1,3 +1 @@ - - - + diff --git a/test/integration/fixtures/blocks/core__table-of-contents.json b/test/integration/fixtures/blocks/core__table-of-contents.json index 4cb3510b6b3862..396280b2892af8 100644 --- a/test/integration/fixtures/blocks/core__table-of-contents.json +++ b/test/integration/fixtures/blocks/core__table-of-contents.json @@ -3,18 +3,6 @@ "name": "core/table-of-contents", "isValid": true, "attributes": { - "headings": [ - { - "content": "Heading text", - "level": 2, - "link": "#heading-id-1" - }, - { - "content": "A sub-heading", - "level": 3, - "link": "#heading-id-2" - } - ], "onlyIncludeCurrentPage": false }, "innerBlocks": [] diff --git a/test/integration/fixtures/blocks/core__table-of-contents.parsed.json b/test/integration/fixtures/blocks/core__table-of-contents.parsed.json index cb65acd4c18494..b7f38c61dfe062 100644 --- a/test/integration/fixtures/blocks/core__table-of-contents.parsed.json +++ b/test/integration/fixtures/blocks/core__table-of-contents.parsed.json @@ -1,24 +1,9 @@ [ { "blockName": "core/table-of-contents", - "attrs": { - "headings": [ - { - "content": "Heading text", - "level": 2, - "link": "#heading-id-1" - }, - { - "content": "A sub-heading", - "level": 3, - "link": "#heading-id-2" - } - ] - }, + "attrs": {}, "innerBlocks": [], - "innerHTML": "\n\n", - "innerContent": [ - "\n\n" - ] + "innerHTML": "", + "innerContent": [] } ] diff --git a/test/integration/fixtures/blocks/core__table-of-contents.serialized.html b/test/integration/fixtures/blocks/core__table-of-contents.serialized.html index 6c8ef7aee27d75..cd71582269d837 100644 --- a/test/integration/fixtures/blocks/core__table-of-contents.serialized.html +++ b/test/integration/fixtures/blocks/core__table-of-contents.serialized.html @@ -1,3 +1 @@ - - - + diff --git a/test/integration/fixtures/blocks/core__table-of-contents__deprecated-v1.html b/test/integration/fixtures/blocks/core__table-of-contents__deprecated-v1.html new file mode 100644 index 00000000000000..6c8ef7aee27d75 --- /dev/null +++ b/test/integration/fixtures/blocks/core__table-of-contents__deprecated-v1.html @@ -0,0 +1,3 @@ + + + diff --git a/test/integration/fixtures/blocks/core__table-of-contents__deprecated-v1.json b/test/integration/fixtures/blocks/core__table-of-contents__deprecated-v1.json new file mode 100644 index 00000000000000..4cb3510b6b3862 --- /dev/null +++ b/test/integration/fixtures/blocks/core__table-of-contents__deprecated-v1.json @@ -0,0 +1,22 @@ +[ + { + "name": "core/table-of-contents", + "isValid": true, + "attributes": { + "headings": [ + { + "content": "Heading text", + "level": 2, + "link": "#heading-id-1" + }, + { + "content": "A sub-heading", + "level": 3, + "link": "#heading-id-2" + } + ], + "onlyIncludeCurrentPage": false + }, + "innerBlocks": [] + } +] diff --git a/test/integration/fixtures/blocks/core__table-of-contents__deprecated-v1.parsed.json b/test/integration/fixtures/blocks/core__table-of-contents__deprecated-v1.parsed.json new file mode 100644 index 00000000000000..cb65acd4c18494 --- /dev/null +++ b/test/integration/fixtures/blocks/core__table-of-contents__deprecated-v1.parsed.json @@ -0,0 +1,24 @@ +[ + { + "blockName": "core/table-of-contents", + "attrs": { + "headings": [ + { + "content": "Heading text", + "level": 2, + "link": "#heading-id-1" + }, + { + "content": "A sub-heading", + "level": 3, + "link": "#heading-id-2" + } + ] + }, + "innerBlocks": [], + "innerHTML": "\n\n", + "innerContent": [ + "\n\n" + ] + } +] diff --git a/test/integration/fixtures/blocks/core__table-of-contents__empty.html b/test/integration/fixtures/blocks/core__table-of-contents__deprecated-v1.serialized.html similarity index 100% rename from test/integration/fixtures/blocks/core__table-of-contents__empty.html rename to test/integration/fixtures/blocks/core__table-of-contents__deprecated-v1.serialized.html diff --git a/test/integration/fixtures/blocks/core__table-of-contents__empty.json b/test/integration/fixtures/blocks/core__table-of-contents__empty.json deleted file mode 100644 index 62e5d60f0ddaed..00000000000000 --- a/test/integration/fixtures/blocks/core__table-of-contents__empty.json +++ /dev/null @@ -1,11 +0,0 @@ -[ - { - "name": "core/table-of-contents", - "isValid": true, - "attributes": { - "headings": [], - "onlyIncludeCurrentPage": false - }, - "innerBlocks": [] - } -] diff --git a/test/integration/fixtures/blocks/core__table-of-contents__empty.parsed.json b/test/integration/fixtures/blocks/core__table-of-contents__empty.parsed.json deleted file mode 100644 index b7f38c61dfe062..00000000000000 --- a/test/integration/fixtures/blocks/core__table-of-contents__empty.parsed.json +++ /dev/null @@ -1,9 +0,0 @@ -[ - { - "blockName": "core/table-of-contents", - "attrs": {}, - "innerBlocks": [], - "innerHTML": "", - "innerContent": [] - } -] diff --git a/test/integration/fixtures/blocks/core__table-of-contents__empty.serialized.html b/test/integration/fixtures/blocks/core__table-of-contents__empty.serialized.html deleted file mode 100644 index cd71582269d837..00000000000000 --- a/test/integration/fixtures/blocks/core__table-of-contents__empty.serialized.html +++ /dev/null @@ -1 +0,0 @@ - From c3afe9ae970bfca5355fefe2a978f2f093e16fa7 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 6 Sep 2023 21:58:33 +0400 Subject: [PATCH 03/15] Fix PHPCS errors --- .../block-library/src/table-of-contents/index.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/block-library/src/table-of-contents/index.php b/packages/block-library/src/table-of-contents/index.php index cda5eabeee004d..2d4ce8da233b64 100644 --- a/packages/block-library/src/table-of-contents/index.php +++ b/packages/block-library/src/table-of-contents/index.php @@ -47,9 +47,9 @@ function block_core_table_of_contents_build_headings_tree( $headings ) { return $headings; } - $tree = array(); - $firstHeading = $headings[0]; - $total = count( $headings ); + $tree = array(); + $first_heading = $headings[0]; + $total = count( $headings ); foreach ( $headings as $key => $heading ) { if ( empty( $heading['content'] ) ) { @@ -57,7 +57,7 @@ function block_core_table_of_contents_build_headings_tree( $headings ) { } // Make sure we are only working with the same level as the first iteration in our set. - if ( $heading['level'] !== $firstHeading['level'] ) { + if ( $heading['level'] !== $first_heading['level'] ) { continue; } @@ -102,7 +102,7 @@ function block_core_table_of_contents_build_headings_tree( $headings ) { * * @since 6.4.0 * - * @param array $headings An array of nested headings. + * @param array $tree An array of nested headings. * @return string $list A list of Table of Content items. */ function block_core_table_of_contents_build_list( $tree ) { @@ -112,7 +112,7 @@ function block_core_table_of_contents_build_list( $tree ) { $heading = $item['heading']; $children = isset( $item['children'] ) ? '
      ' . block_core_table_of_contents_build_list( $item['children'] ) . '
    ' : ''; - if ( ! empty( $heading['link'] )) { + if ( ! empty( $heading['link'] ) ) { $content = '' . esc_html( $heading['content'] ) . ''; } else { $content = '' . esc_html( $heading['content'] ) . ''; From 705bdf627401bb6da611a8b5a369bd20e191f241 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 6 Sep 2023 23:31:09 +0400 Subject: [PATCH 04/15] Remove ToC from static block list --- lib/blocks.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/blocks.php b/lib/blocks.php index 15f8652c41eeae..0e5a851876d595 100644 --- a/lib/blocks.php +++ b/lib/blocks.php @@ -38,7 +38,6 @@ function gutenberg_reregister_core_block_types() { 'social-links', 'spacer', 'table', - 'table-of-contents', 'text-columns', 'verse', 'video', From 21f711f13c56a9dc491c8c9e46ef36fe59bea00e Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Sat, 9 Sep 2023 13:31:46 +0400 Subject: [PATCH 05/15] Add PHPUnit tests --- .../render-block-table-of-contents-test.php | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 phpunit/blocks/render-block-table-of-contents-test.php diff --git a/phpunit/blocks/render-block-table-of-contents-test.php b/phpunit/blocks/render-block-table-of-contents-test.php new file mode 100644 index 00000000000000..34ea7ff88e04fe --- /dev/null +++ b/phpunit/blocks/render-block-table-of-contents-test.php @@ -0,0 +1,112 @@ +post->create_and_get( + array( + 'post_type' => 'page', + 'post_status' => 'publish', + ) + ); + + add_post_meta( + self::$page->ID, + 'table_of_contents', + '[{"content":"Heading text","level":2,"link":"#heading-text"},{"content":"A sub-heading","level":3,"link":"#a-sub-heading"}]', + true + ); + } + + public function set_up() { + parent::set_up(); + $this->original_block_supports = WP_Block_Supports::$block_to_render; + WP_Block_Supports::$block_to_render = array( + 'attrs' => array(), + 'blockName' => 'core/table-of-contents', + ); + } + + public function tear_down() { + WP_Block_Supports::$block_to_render = $this->original_block_supports; + parent::tear_down(); + } + + public static function wpTearDownAfterClass() { + wp_delete_post( self::$page->ID, true ); + } + + /** + * @covers ::render_block_core_table_of_contents + */ + public function test_render_deprecated_content() { + $content = ''; + + $parsed_blocks = parse_blocks( + ' + + ' + ); + $block = new WP_Block( + $parsed_blocks[0], + array( + 'postId' => self::$page->ID, + 'postType' => self::$page->post_type, + ) + ); + + $new_content = gutenberg_render_block_core_table_of_contents( array(), $content, $block ); + $this->assertStringContainsString( + 'Heading text', + $new_content, + 'Failed to render a heading element' + ); + $this->assertStringContainsString( + 'A sub-heading', + $new_content, + 'Failed to render a sub-heading element' + ); + } + + /** + * @covers ::render_block_core_table_of_contents + */ + public function test_render_table_of_contents_from_meta() { + $parsed_blocks = parse_blocks( '' ); + $block = new WP_Block( + $parsed_blocks[0], + array( + 'postId' => self::$page->ID, + 'postType' => self::$page->post_type, + ) + ); + + $new_content = gutenberg_render_block_core_table_of_contents( array(), '', $block ); + $this->assertStringContainsString( + 'Heading text', + $new_content, + 'Failed to render a heading element from meta' + ); + $this->assertStringContainsString( + 'A sub-heading', + $new_content, + 'Failed to render a sub-heading element from meta' + ); + } +} From 325212616693f6dfe65949b85f7579aa7cfe305f Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Sat, 9 Sep 2023 15:06:40 +0400 Subject: [PATCH 06/15] Don't store permalinks --- .../src/table-of-contents/hooks.js | 38 +++---------------- .../src/table-of-contents/index.php | 6 ++- .../render-block-table-of-contents-test.php | 7 +++- 3 files changed, 14 insertions(+), 37 deletions(-) diff --git a/packages/block-library/src/table-of-contents/hooks.js b/packages/block-library/src/table-of-contents/hooks.js index f294afd59be318..0b22e515c993b3 100644 --- a/packages/block-library/src/table-of-contents/hooks.js +++ b/packages/block-library/src/table-of-contents/hooks.js @@ -10,7 +10,6 @@ import { useRegistry } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; import { __unstableStripHTML as stripHTML } from '@wordpress/dom'; import { useEffect } from '@wordpress/element'; -import { addQueryArgs, removeQueryArgs } from '@wordpress/url'; import { store as blockEditorStore } from '@wordpress/block-editor'; function getLatestHeadings( select, clientId ) { @@ -21,16 +20,8 @@ function getLatestHeadings( select, clientId ) { __experimentalGetGlobalBlocksByName: getGlobalBlocksByName, } = select( blockEditorStore ); - // FIXME: @wordpress/block-library should not depend on @wordpress/editor. - // Blocks can be loaded into a *non-post* block editor, so to avoid - // declaring @wordpress/editor as a dependency, we must access its - // store by string. When the store is not available, editorSelectors - // will be null, and the block's saved markup will lack permalinks. - // eslint-disable-next-line @wordpress/data-no-store-string-literals - const permalink = select( 'core/editor' ).getPermalink() ?? null; - const isPaginated = getGlobalBlocksByName( 'core/nextpage' ).length !== 0; - const { onlyIncludeCurrentPage } = getBlockAttributes( clientId ) ?? {}; + const { onlyIncludeCurrentPage } = getBlockAttributes( clientId ); // Get the client ids of all blocks in the editor. const allBlockClientIds = getClientIdsWithDescendants(); @@ -58,20 +49,10 @@ function getLatestHeadings( select, clientId ) { } } - const latestHeadings = []; - - /** The page (of a paginated post) a heading will be part of. */ + // The page (of a paginated post) a heading will be part of. let headingPage = 1; - let headingPageLink = null; - - // If the core/editor store is available, we can add permalinks to the - // generated table of contents. - if ( typeof permalink === 'string' ) { - headingPageLink = isPaginated - ? addQueryArgs( permalink, { page: headingPage } ) - : permalink; - } + const latestHeadings = []; for ( const blockClientId of allBlockClientIds ) { const blockName = getBlockName( blockClientId ); if ( blockName === 'core/nextpage' ) { @@ -83,13 +64,6 @@ function getLatestHeadings( select, clientId ) { if ( onlyIncludeCurrentPage && headingPage > tocPage ) { break; } - - if ( typeof permalink === 'string' ) { - headingPageLink = addQueryArgs( - removeQueryArgs( permalink, [ 'page' ] ), - { page: headingPage } - ); - } } // If we're including all headings or we've reached headings on // the same page as the Table of Contents block, add them to the @@ -99,7 +73,6 @@ function getLatestHeadings( select, clientId ) { const headingAttributes = getBlockAttributes( blockClientId ); const canBeLinked = - typeof headingPageLink === 'string' && typeof headingAttributes.anchor === 'string' && headingAttributes.anchor !== ''; @@ -112,9 +85,8 @@ function getLatestHeadings( select, clientId ) { ) ), level: headingAttributes.level, - link: canBeLinked - ? `${ headingPageLink }#${ headingAttributes.anchor }` - : null, + link: canBeLinked ? `#${ headingAttributes.anchor }` : null, + page: isPaginated ? headingPage : null, } ); } } diff --git a/packages/block-library/src/table-of-contents/index.php b/packages/block-library/src/table-of-contents/index.php index 2d4ce8da233b64..3da7fbd93a0869 100644 --- a/packages/block-library/src/table-of-contents/index.php +++ b/packages/block-library/src/table-of-contents/index.php @@ -106,14 +106,16 @@ function block_core_table_of_contents_build_headings_tree( $headings ) { * @return string $list A list of Table of Content items. */ function block_core_table_of_contents_build_list( $tree ) { - $list = ''; + $list = ''; + $permalink = get_permalink(); foreach ( $tree as $item ) { $heading = $item['heading']; $children = isset( $item['children'] ) ? '
      ' . block_core_table_of_contents_build_list( $item['children'] ) . '
    ' : ''; if ( ! empty( $heading['link'] ) ) { - $content = '' . esc_html( $heading['content'] ) . ''; + $pagelink = ! empty( $heading['page'] ) ? add_query_arg( 'page', $heading['page'], $permalink ) : $permalink; + $content = '' . esc_html( $heading['content'] ) . ''; } else { $content = '' . esc_html( $heading['content'] ) . ''; } diff --git a/phpunit/blocks/render-block-table-of-contents-test.php b/phpunit/blocks/render-block-table-of-contents-test.php index 34ea7ff88e04fe..7c5ef9c3488c11 100644 --- a/phpunit/blocks/render-block-table-of-contents-test.php +++ b/phpunit/blocks/render-block-table-of-contents-test.php @@ -88,6 +88,9 @@ public function test_render_deprecated_content() { * @covers ::render_block_core_table_of_contents */ public function test_render_table_of_contents_from_meta() { + $GLOBALS['post'] = self::$page; + + $permalink = get_permalink( self::$page->ID ); $parsed_blocks = parse_blocks( '' ); $block = new WP_Block( $parsed_blocks[0], @@ -99,12 +102,12 @@ public function test_render_table_of_contents_from_meta() { $new_content = gutenberg_render_block_core_table_of_contents( array(), '', $block ); $this->assertStringContainsString( - 'Heading text', + 'Heading text', $new_content, 'Failed to render a heading element from meta' ); $this->assertStringContainsString( - 'A sub-heading', + 'A sub-heading', $new_content, 'Failed to render a sub-heading element from meta' ); From 5885c67d84ef144609fe74c7b86ec4ba30423f81 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Sat, 9 Sep 2023 15:13:19 +0400 Subject: [PATCH 07/15] Only subscribe to block editor store --- .../block-library/src/table-of-contents/hooks.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/block-library/src/table-of-contents/hooks.js b/packages/block-library/src/table-of-contents/hooks.js index 0b22e515c993b3..75bb2cdf2125d8 100644 --- a/packages/block-library/src/table-of-contents/hooks.js +++ b/packages/block-library/src/table-of-contents/hooks.js @@ -138,14 +138,14 @@ function observeCallback( select, dispatch, context ) { export function useObserveHeadings( { clientId, postType, postId } ) { const registry = useRegistry(); useEffect( () => { - // Todo: Limit subscription to block editor store when data no longer depends on `getPermalink`. - // See: https://github.com/WordPress/gutenberg/pull/45513 - return registry.subscribe( () => - observeCallback( registry.select, registry.dispatch, { - clientId, - postType, - postId, - } ) + return registry.subscribe( + () => + observeCallback( registry.select, registry.dispatch, { + clientId, + postType, + postId, + } ), + blockEditorStore ); }, [ registry, clientId, postType, postId ] ); } From 2e549dc88378f5887868ac711e4510de460a856c Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Tue, 12 Sep 2023 17:24:07 +0400 Subject: [PATCH 08/15] Increase post-type support --- .../block-library/src/table-of-contents/index.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/table-of-contents/index.php b/packages/block-library/src/table-of-contents/index.php index 3da7fbd93a0869..4cb7659626eb06 100644 --- a/packages/block-library/src/table-of-contents/index.php +++ b/packages/block-library/src/table-of-contents/index.php @@ -11,8 +11,18 @@ * @since 6.4.0 */ function register_block_core_table_of_contents() { - // @todo: Register meta for all post types that use Block Editor. - foreach ( array( 'post', 'page' ) as $post_type ) { + $supported_post_types = get_post_types( + array( + 'public' => true, + 'show_in_rest' => true, + ) + ); + + foreach ( $supported_post_types as $post_type ) { + if ( ! post_type_supports( $post_type, 'editor' ) ) { + continue; + } + register_post_meta( $post_type, 'table_of_contents', From 96cfeb5d2e166cb0c578ff31500fc300f648390c Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Tue, 12 Sep 2023 18:27:19 +0400 Subject: [PATCH 09/15] Prefix meta key and add helper method --- .../block-library/src/table-of-contents/edit.js | 7 ++----- .../block-library/src/table-of-contents/hooks.js | 16 ++++++++++++---- .../src/table-of-contents/index.php | 4 ++-- .../render-block-table-of-contents-test.php | 2 +- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/packages/block-library/src/table-of-contents/edit.js b/packages/block-library/src/table-of-contents/edit.js index b78ca3fb7a88a4..583f4cc750cd50 100644 --- a/packages/block-library/src/table-of-contents/edit.js +++ b/packages/block-library/src/table-of-contents/edit.js @@ -27,7 +27,7 @@ import { __ } from '@wordpress/i18n'; import icon from './icon'; import TableOfContentsList from './list'; import { linearToNestedHeadingList } from './utils'; -import { useObserveHeadings } from './hooks'; +import { getHeadingsFromMeta, useObserveHeadings } from './hooks'; /** @typedef {import('./utils').HeadingData} HeadingData */ @@ -69,10 +69,7 @@ export default function TableOfContentsEdit( { const { replaceBlocks } = useDispatch( blockEditorStore ); const [ meta ] = useEntityProp( 'postType', postType, 'meta', postId ); - const headings = meta?.table_of_contents - ? JSON.parse( meta.table_of_contents ) - : []; - + const headings = getHeadingsFromMeta( meta ); const headingTree = linearToNestedHeadingList( headings ); const toolbarControls = canInsertList && ( diff --git a/packages/block-library/src/table-of-contents/hooks.js b/packages/block-library/src/table-of-contents/hooks.js index 75bb2cdf2125d8..e339fc3fe15489 100644 --- a/packages/block-library/src/table-of-contents/hooks.js +++ b/packages/block-library/src/table-of-contents/hooks.js @@ -12,6 +12,16 @@ import { __unstableStripHTML as stripHTML } from '@wordpress/dom'; import { useEffect } from '@wordpress/element'; import { store as blockEditorStore } from '@wordpress/block-editor'; +export const META_KEY = 'core_table_of_contents'; + +export function getHeadingsFromMeta( meta ) { + if ( ! meta?.[ META_KEY ] ) { + return []; + } + + return JSON.parse( meta[ META_KEY ] ); +} + function getLatestHeadings( select, clientId ) { const { getBlockAttributes, @@ -112,9 +122,7 @@ function observeCallback( select, dispatch, context ) { const { editEntityRecord } = dispatch( coreStore ); const meta = getEditedEntityRecord( 'postType', postType, postId ).meta; - const storedHeadings = meta?.table_of_contents - ? JSON.parse( meta.table_of_contents ) - : []; + const storedHeadings = getHeadingsFromMeta( meta ); const headings = getLatestHeadings( select, clientId ); if ( ! fastDeepEqual( headings, storedHeadings ) ) { @@ -125,7 +133,7 @@ function observeCallback( select, dispatch, context ) { { meta: { ...meta, - table_of_contents: JSON.stringify( headings ), + [ META_KEY ]: JSON.stringify( headings ), }, }, { diff --git a/packages/block-library/src/table-of-contents/index.php b/packages/block-library/src/table-of-contents/index.php index 4cb7659626eb06..8a466e1d5cdaf3 100644 --- a/packages/block-library/src/table-of-contents/index.php +++ b/packages/block-library/src/table-of-contents/index.php @@ -25,7 +25,7 @@ function register_block_core_table_of_contents() { register_post_meta( $post_type, - 'table_of_contents', + 'core_table_of_contents', array( 'show_in_rest' => true, 'single' => true, @@ -162,7 +162,7 @@ function render_block_core_table_of_contents( $attributes, $content, $block ) { return ''; } - $headings = get_post_meta( $block->context['postId'], 'table_of_contents', true ); + $headings = get_post_meta( $block->context['postId'], 'core_table_of_contents', true ); if ( ! $headings ) { return ''; diff --git a/phpunit/blocks/render-block-table-of-contents-test.php b/phpunit/blocks/render-block-table-of-contents-test.php index 7c5ef9c3488c11..e29c2181813c8e 100644 --- a/phpunit/blocks/render-block-table-of-contents-test.php +++ b/phpunit/blocks/render-block-table-of-contents-test.php @@ -28,7 +28,7 @@ public static function wpSetUpBeforeClass() { add_post_meta( self::$page->ID, - 'table_of_contents', + 'core_table_of_contents', '[{"content":"Heading text","level":2,"link":"#heading-text"},{"content":"A sub-heading","level":3,"link":"#a-sub-heading"}]', true ); From a3350d89235ec2f8a6880bfb047af98198c5737f Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Tue, 12 Sep 2023 18:34:22 +0400 Subject: [PATCH 10/15] Pass the ID to the get_permalink --- packages/block-library/src/table-of-contents/index.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/block-library/src/table-of-contents/index.php b/packages/block-library/src/table-of-contents/index.php index 8a466e1d5cdaf3..744829d3ee05db 100644 --- a/packages/block-library/src/table-of-contents/index.php +++ b/packages/block-library/src/table-of-contents/index.php @@ -113,15 +113,17 @@ function block_core_table_of_contents_build_headings_tree( $headings ) { * @since 6.4.0 * * @param array $tree An array of nested headings. + * @param array $args Additional arguments used for building the list. + * * @return string $list A list of Table of Content items. */ -function block_core_table_of_contents_build_list( $tree ) { +function block_core_table_of_contents_build_list( $tree, $args ) { $list = ''; - $permalink = get_permalink(); + $permalink = get_permalink( $args['postId'] ); foreach ( $tree as $item ) { $heading = $item['heading']; - $children = isset( $item['children'] ) ? '
      ' . block_core_table_of_contents_build_list( $item['children'] ) . '
    ' : ''; + $children = isset( $item['children'] ) ? '
      ' . block_core_table_of_contents_build_list( $item['children'], $args ) . '
    ' : ''; if ( ! empty( $heading['link'] ) ) { $pagelink = ! empty( $heading['page'] ) ? add_query_arg( 'page', $heading['page'], $permalink ) : $permalink; @@ -179,6 +181,6 @@ function render_block_core_table_of_contents( $attributes, $content, $block ) { return sprintf( '', get_block_wrapper_attributes(), - block_core_table_of_contents_build_list( $tree ) + block_core_table_of_contents_build_list( $tree, array( 'postId' => $block->context['postId'] ) ) ); } From c79c8c39f217dd7e24cef5764a2956ef8e7dd4b1 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Tue, 12 Sep 2023 18:41:30 +0400 Subject: [PATCH 11/15] Add a test case for missing link property Co-authored-by: Marin Atanasov <8436925+tyxla@users.noreply.github.com> --- packages/block-library/src/table-of-contents/index.php | 2 +- phpunit/blocks/render-block-table-of-contents-test.php | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/table-of-contents/index.php b/packages/block-library/src/table-of-contents/index.php index 744829d3ee05db..3a6ce2e5f2a4e3 100644 --- a/packages/block-library/src/table-of-contents/index.php +++ b/packages/block-library/src/table-of-contents/index.php @@ -129,7 +129,7 @@ function block_core_table_of_contents_build_list( $tree, $args ) { $pagelink = ! empty( $heading['page'] ) ? add_query_arg( 'page', $heading['page'], $permalink ) : $permalink; $content = '' . esc_html( $heading['content'] ) . ''; } else { - $content = '' . esc_html( $heading['content'] ) . ''; + $content = '' . esc_html( $heading['content'] ) . ''; } $list .= '
  • ' . $content . $children . '
  • '; diff --git a/phpunit/blocks/render-block-table-of-contents-test.php b/phpunit/blocks/render-block-table-of-contents-test.php index e29c2181813c8e..66932e6f328b90 100644 --- a/phpunit/blocks/render-block-table-of-contents-test.php +++ b/phpunit/blocks/render-block-table-of-contents-test.php @@ -29,7 +29,7 @@ public static function wpSetUpBeforeClass() { add_post_meta( self::$page->ID, 'core_table_of_contents', - '[{"content":"Heading text","level":2,"link":"#heading-text"},{"content":"A sub-heading","level":3,"link":"#a-sub-heading"}]', + '[{"content":"Heading text","level":2,"link":"#heading-text","page":null},{"content":"A sub-heading","level":3,"link":"#a-sub-heading","page":null},{"content":"Missing anchor","level":2,"link":null,"page":null}]', true ); } @@ -111,5 +111,10 @@ public function test_render_table_of_contents_from_meta() { $new_content, 'Failed to render a sub-heading element from meta' ); + $this->assertStringContainsString( + 'Missing anchor', + $new_content, + 'Failed to render a heading element with a missing anchor' + ); } } From b1e172236c18f2cf90833a18a1562f8818080909 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Tue, 12 Sep 2023 19:21:48 +0400 Subject: [PATCH 12/15] Render full list in helper methods/components --- packages/block-library/src/table-of-contents/edit.js | 4 +--- .../block-library/src/table-of-contents/index.php | 6 +++--- .../block-library/src/table-of-contents/list.tsx | 12 +++++------- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/packages/block-library/src/table-of-contents/edit.js b/packages/block-library/src/table-of-contents/edit.js index 583f4cc750cd50..541d8da8a3e164 100644 --- a/packages/block-library/src/table-of-contents/edit.js +++ b/packages/block-library/src/table-of-contents/edit.js @@ -143,9 +143,7 @@ export default function TableOfContentsEdit( { return ( <> { toolbarControls } { inspectorControls } diff --git a/packages/block-library/src/table-of-contents/index.php b/packages/block-library/src/table-of-contents/index.php index 3a6ce2e5f2a4e3..7f4b0223d7b29a 100644 --- a/packages/block-library/src/table-of-contents/index.php +++ b/packages/block-library/src/table-of-contents/index.php @@ -123,7 +123,7 @@ function block_core_table_of_contents_build_list( $tree, $args ) { foreach ( $tree as $item ) { $heading = $item['heading']; - $children = isset( $item['children'] ) ? '
      ' . block_core_table_of_contents_build_list( $item['children'], $args ) . '
    ' : ''; + $children = isset( $item['children'] ) ? block_core_table_of_contents_build_list( $item['children'], $args ) : ''; if ( ! empty( $heading['link'] ) ) { $pagelink = ! empty( $heading['page'] ) ? add_query_arg( 'page', $heading['page'], $permalink ) : $permalink; @@ -135,7 +135,7 @@ function block_core_table_of_contents_build_list( $tree, $args ) { $list .= '
  • ' . $content . $children . '
  • '; } - return $list; + return '
      ' . $list . '
    '; } /** @@ -179,7 +179,7 @@ function render_block_core_table_of_contents( $attributes, $content, $block ) { $tree = block_core_table_of_contents_build_headings_tree( $headings ); return sprintf( - '', + '', get_block_wrapper_attributes(), block_core_table_of_contents_build_list( $tree, array( 'postId' => $block->context['postId'] ) ) ); diff --git a/packages/block-library/src/table-of-contents/list.tsx b/packages/block-library/src/table-of-contents/list.tsx index e327f8dfe2e86b..e855f7a006ebf4 100644 --- a/packages/block-library/src/table-of-contents/list.tsx +++ b/packages/block-library/src/table-of-contents/list.tsx @@ -16,7 +16,7 @@ export default function TableOfContentsList( { nestedHeadingList: NestedHeadingData[]; } ): WPElement { return ( - <> +
      { nestedHeadingList.map( ( node, index ) => { const { content, link } = node.heading; @@ -32,15 +32,13 @@ export default function TableOfContentsList( {
    1. { entry } { node.children ? ( -
        - -
      + ) : null }
    2. ); } ) } - +
    ); } From 819cafa2258b993815899f16642f52174836a2bc Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Tue, 12 Sep 2023 22:02:57 +0400 Subject: [PATCH 13/15] Fix deprecation error --- .../block-library/src/table-of-contents/deprecated.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/block-library/src/table-of-contents/deprecated.js b/packages/block-library/src/table-of-contents/deprecated.js index 0ef37f132a9cae..539b9d7648ec51 100644 --- a/packages/block-library/src/table-of-contents/deprecated.js +++ b/packages/block-library/src/table-of-contents/deprecated.js @@ -56,13 +56,9 @@ const v1 = { return ( ); }, From 6eb573d3abf73c5e8e7174c34b46516aad369698 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 13 Sep 2023 11:50:06 +0400 Subject: [PATCH 14/15] Protect the meta key --- .../block-library/src/table-of-contents/hooks.js | 2 +- .../block-library/src/table-of-contents/index.php | 12 +++++++----- .../blocks/render-block-table-of-contents-test.php | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/block-library/src/table-of-contents/hooks.js b/packages/block-library/src/table-of-contents/hooks.js index e339fc3fe15489..546d52502ed48e 100644 --- a/packages/block-library/src/table-of-contents/hooks.js +++ b/packages/block-library/src/table-of-contents/hooks.js @@ -12,7 +12,7 @@ import { __unstableStripHTML as stripHTML } from '@wordpress/dom'; import { useEffect } from '@wordpress/element'; import { store as blockEditorStore } from '@wordpress/block-editor'; -export const META_KEY = 'core_table_of_contents'; +export const META_KEY = '_core_table_of_contents'; export function getHeadingsFromMeta( meta ) { if ( ! meta?.[ META_KEY ] ) { diff --git a/packages/block-library/src/table-of-contents/index.php b/packages/block-library/src/table-of-contents/index.php index 7f4b0223d7b29a..d9860e776d5f07 100644 --- a/packages/block-library/src/table-of-contents/index.php +++ b/packages/block-library/src/table-of-contents/index.php @@ -23,13 +23,15 @@ function register_block_core_table_of_contents() { continue; } + // The post meta is only "protected" to prevent exposing serialized content in Custom Fields. register_post_meta( $post_type, - 'core_table_of_contents', + '_core_table_of_contents', array( - 'show_in_rest' => true, - 'single' => true, - 'type' => 'string', + 'show_in_rest' => true, + 'single' => true, + 'type' => 'string', + 'auth_callback' => '__return_true' ) ); } @@ -164,7 +166,7 @@ function render_block_core_table_of_contents( $attributes, $content, $block ) { return ''; } - $headings = get_post_meta( $block->context['postId'], 'core_table_of_contents', true ); + $headings = get_post_meta( $block->context['postId'], '_core_table_of_contents', true ); if ( ! $headings ) { return ''; diff --git a/phpunit/blocks/render-block-table-of-contents-test.php b/phpunit/blocks/render-block-table-of-contents-test.php index 66932e6f328b90..0a49cafb06c950 100644 --- a/phpunit/blocks/render-block-table-of-contents-test.php +++ b/phpunit/blocks/render-block-table-of-contents-test.php @@ -28,7 +28,7 @@ public static function wpSetUpBeforeClass() { add_post_meta( self::$page->ID, - 'core_table_of_contents', + '_core_table_of_contents', '[{"content":"Heading text","level":2,"link":"#heading-text","page":null},{"content":"A sub-heading","level":3,"link":"#a-sub-heading","page":null},{"content":"Missing anchor","level":2,"link":null,"page":null}]', true ); From b0935ab917c672e046eb82b2e116202c8efe8eaf Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 13 Sep 2023 11:59:26 +0400 Subject: [PATCH 15/15] Add aria-label --- .../block-library/src/table-of-contents/index.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/block-library/src/table-of-contents/index.php b/packages/block-library/src/table-of-contents/index.php index d9860e776d5f07..8f1b8cf19ae6e8 100644 --- a/packages/block-library/src/table-of-contents/index.php +++ b/packages/block-library/src/table-of-contents/index.php @@ -31,7 +31,7 @@ function register_block_core_table_of_contents() { 'show_in_rest' => true, 'single' => true, 'type' => 'string', - 'auth_callback' => '__return_true' + 'auth_callback' => '__return_true', ) ); } @@ -178,11 +178,16 @@ function render_block_core_table_of_contents( $attributes, $content, $block ) { return ''; } - $tree = block_core_table_of_contents_build_headings_tree( $headings ); + $tree = block_core_table_of_contents_build_headings_tree( $headings ); + $wrapper_attributes = get_block_wrapper_attributes( + array( + 'aria-label' => __( 'Table of Contents' ), + ) + ); return sprintf( '', - get_block_wrapper_attributes(), + $wrapper_attributes, block_core_table_of_contents_build_list( $tree, array( 'postId' => $block->context['postId'] ) ) ); }