Skip to content
Open
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
1 change: 1 addition & 0 deletions dynamic-table-of-contents/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
= 0.2.0 - Add toggles to preselect heading tags for the table of contents.
2 changes: 1 addition & 1 deletion dynamic-table-of-contents/dynamic-table-of-contents.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Description: Creates a table of contents that's dynamically (PHP) rendered.
* Requires at least: 6.1
* Requires PHP: 8.0
* Version: 0.1.9
* Version: 0.2.0
* Author: WordPress Special Projects Team
* Author URI: https://wpspecialprojects.wordpress.com/
* Update URI: https://opsoasis.wpspecialprojects.com/dynamic-table-of-contents/
Expand Down
37,230 changes: 18,615 additions & 18,615 deletions dynamic-table-of-contents/package-lock.json

Large diffs are not rendered by default.

38 changes: 19 additions & 19 deletions dynamic-table-of-contents/package.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
{
"name": "dynamic-table-of-contents",
"version": "0.1.9",
"description": "Creates a table of contents that's dynamically (PHP) rendered.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
"main": "build/index.js",
"scripts": {
"build": "wp-scripts build src/index.js src/view.js --webpack-copy-php",
"format": "wp-scripts format",
"lint:css": "wp-scripts lint-style",
"lint:js": "wp-scripts lint-js",
"packages-update": "wp-scripts packages-update",
"plugin-zip": "wp-scripts plugin-zip",
"start": "wp-scripts start src/index.js src/view.js --webpack-copy-php"
},
"devDependencies": {
"@wordpress/scripts": "^26.18.0"
}
}
"name": "dynamic-table-of-contents",
"version": "0.1.9",
"description": "Creates a table of contents that's dynamically (PHP) rendered.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
"main": "build/index.js",
"scripts": {
"build": "wp-scripts build src/index.js src/view.js --webpack-copy-php",
"format": "wp-scripts format",
"lint:css": "wp-scripts lint-style",
"lint:js": "wp-scripts lint-js",
"packages-update": "wp-scripts packages-update",
"plugin-zip": "wp-scripts plugin-zip",
"start": "wp-scripts start src/index.js src/view.js --webpack-copy-php"
},
"devDependencies": {
"@wordpress/scripts": "^26.18.0"
}
}
4 changes: 2 additions & 2 deletions dynamic-table-of-contents/readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Contributors: The WordPress Contributors
Tags: block
Tested up to: 6.1
Stable tag: 0.1.0
Stable tag: 0.2.0
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Expand Down Expand Up @@ -45,7 +45,7 @@ directory take precedence. For example, `/assets/screenshot-1.png` would win ove

== Changelog ==

= 0.1.0 =
= 0.2.0 - Add toggles to preselect heading tags for the table of contents.
* Release

== Arbitrary section ==
Expand Down
28 changes: 25 additions & 3 deletions dynamic-table-of-contents/src/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,35 @@
"color": true
}
},
"usesContext": [
"postId"
],
"usesContext": [ "postId" ],
"attributes": {
"title": {
"type": "string",
"default": "Table of Contents"
},
"includeH1": {
"type": "boolean",
"default": true
},
"includeH2": {
"type": "boolean",
"default": true
},
"includeH3": {
"type": "boolean",
"default": true
},
"includeH4": {
"type": "boolean",
"default": true
},
"includeH5": {
"type": "boolean",
"default": true
},
"includeH6": {
"type": "boolean",
"default": true
}
},
"textdomain": "dynamic-table-of-contents",
Expand Down
144 changes: 122 additions & 22 deletions dynamic-table-of-contents/src/edit.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,135 @@
import { __ } from '@wordpress/i18n';
import { useBlockProps, RichText } from '@wordpress/block-editor';
import {
useBlockProps,
RichText,
InspectorControls,
} from '@wordpress/block-editor';
import { PanelBody, ToggleControl } from '@wordpress/components';

/**
* The edit function describes the structure of your block in the context of the
* editor. This represents what the editor will render when the block is used.
*
* @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#edit
*
* @param {Object} root0 The component props.
* @param {Object} root0.attributes The block attributes.
* @param {Function} root0.setAttributes Function to update block attributes.
* @return {Element} Element to render.
*/
export default function Edit({ attributes, setAttributes }) {
export default function Edit( { attributes, setAttributes } ) {
const {
title,
includeH1,
includeH2,
includeH3,
includeH4,
includeH5,
includeH6,
} = attributes;

return (
<div {...useBlockProps()}>
<RichText
tagName="h2"
placeholder={__('Table of Contents Title', 'dynamic-table-of-contents')}
value={attributes.title}
onChange={(title) => setAttributes({ title })}
className="table-of-contents-title"
/>
<ul className="table-of-contents-list">
<li className="table-of-contents-list-item">
<a href="#" className='active'>This is an example.</a>
</li>
<li className="table-of-contents-list-item">
<a href="#">Of the block appearance.</a>
</li>
<li className="table-of-contents-list-item">
<a href="#">When used in your post.</a>
</li>
</ul>
</div>
<>
<InspectorControls>
<PanelBody
title={ __(
'Heading Levels',
'dynamic-table-of-contents'
) }
initialOpen={ true }
>
<ToggleControl
label={ __(
'Include H1 headings',
'dynamic-table-of-contents'
) }
checked={ includeH1 }
onChange={ ( newIncludeH1 ) =>
setAttributes( { includeH1: newIncludeH1 } )
}
/>
<ToggleControl
label={ __(
'Include H2 headings',
'dynamic-table-of-contents'
) }
checked={ includeH2 }
onChange={ ( newIncludeH2 ) =>
setAttributes( { includeH2: newIncludeH2 } )
}
/>
<ToggleControl
label={ __(
'Include H3 headings',
'dynamic-table-of-contents'
) }
checked={ includeH3 }
onChange={ ( newIncludeH3 ) =>
setAttributes( { includeH3: newIncludeH3 } )
}
/>
<ToggleControl
label={ __(
'Include H4 headings',
'dynamic-table-of-contents'
) }
checked={ includeH4 }
onChange={ ( newIncludeH4 ) =>
setAttributes( { includeH4: newIncludeH4 } )
}
/>
<ToggleControl
label={ __(
'Include H5 headings',
'dynamic-table-of-contents'
) }
checked={ includeH5 }
onChange={ ( newIncludeH5 ) =>
setAttributes( { includeH5: newIncludeH5 } )
}
/>
<ToggleControl
label={ __(
'Include H6 headings',
'dynamic-table-of-contents'
) }
checked={ includeH6 }
onChange={ ( newIncludeH6 ) =>
setAttributes( { includeH6: newIncludeH6 } )
}
/>
</PanelBody>
</InspectorControls>
<div { ...useBlockProps() }>
<RichText
tagName="h2"
placeholder={ __(
'Table of Contents Title',
'dynamic-table-of-contents'
) }
value={ title }
onChange={ ( newTitle ) =>
setAttributes( { title: newTitle } )
}
className="table-of-contents-title"
/>
<ul className="table-of-contents-list">
<li className="table-of-contents-list-item">
{ /* eslint-disable-next-line jsx-a11y/anchor-is-valid -- Not a valid link, just preview. */ }
<a href="#" className="active">
This is an example.
</a>
</li>
<li className="table-of-contents-list-item">
{ /* eslint-disable-next-line jsx-a11y/anchor-is-valid -- Not a valid link, just preview. */ }
<a href="#">Of the block appearance.</a>
</li>
<li className="table-of-contents-list-item">
{ /* eslint-disable-next-line jsx-a11y/anchor-is-valid -- Not a valid link, just preview. */ }
<a href="#">When used in your post.</a>
</li>
</ul>
</div>
</>
);
}
4 changes: 2 additions & 2 deletions dynamic-table-of-contents/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ const icon = (
focusable="false"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
fillRule="evenodd"
clipRule="evenodd"
d="M20 9.484h-8.889v-1.5H20v1.5Zm0 7h-4.889v-1.5H20v1.5Zm-14 .032a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm0 1a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"
></path>
<path d="M13 15.516a2 2 0 1 1-4 0 2 2 0 0 1 4 0ZM8 8.484a2 2 0 1 1-4 0 2 2 0 0 1 4 0Z"></path>
Expand Down
21 changes: 20 additions & 1 deletion dynamic-table-of-contents/src/render.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,26 @@
);
?>

<div <?php echo wp_kses_post( get_block_wrapper_attributes() ); ?>>
<?php
// Prepare data attributes for heading levels
$heading_levels = array();
$heading_attributes = array( 'includeH1', 'includeH2', 'includeH3', 'includeH4', 'includeH5', 'includeH6' );

foreach ( $heading_attributes as $heading_attr ) {
if ( isset( $attributes[ $heading_attr ] ) && true === $attributes[ $heading_attr ] ) {
$heading_level = strtolower( str_replace( 'include', '', $heading_attr ) );
$heading_levels[] = $heading_level;
}
}

$data_attributes = array(
'data-heading-levels' => esc_attr( implode( ',', $heading_levels ) ),
);

$wrapper_attributes = get_block_wrapper_attributes( $data_attributes );
Comment on lines +30 to +41
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

All-levels-off edge case currently falls back to “all levels”.

When all toggles are off, $heading_levels becomes empty, resulting in data-heading-levels="". In view.js, an empty string is falsy and defaults to all levels—contrary to the PR objective (“edge case: all levels toggled off (empty list)”). We need to distinguish:

  • Legacy blocks (attributes absent) → default to all levels.
  • User explicitly set all false → no headings.

Set the data attribute only when any include* attribute exists; leave it absent for legacy. Keep it present (possibly empty) for new blocks. Pair with the view.js change below.

Apply:

 // Prepare data attributes for heading levels
 $heading_levels     = array();
 $heading_attributes = array( 'includeH1', 'includeH2', 'includeH3', 'includeH4', 'includeH5', 'includeH6' );
+$has_any_include_attr = false;

-foreach ( $heading_attributes as $heading_attr ) {
-	if ( isset( $attributes[ $heading_attr ] ) && true === $attributes[ $heading_attr ] ) {
-		$heading_level    = strtolower( str_replace( 'include', '', $heading_attr ) );
-		$heading_levels[] = $heading_level;
-	}
-}
+foreach ( $heading_attributes as $heading_attr ) {
+	if ( array_key_exists( $heading_attr, $attributes ) ) {
+		$has_any_include_attr = true;
+		if ( true === $attributes[ $heading_attr ] ) {
+			$heading_level    = strtolower( str_replace( 'include', '', $heading_attr ) );
+			$heading_levels[] = $heading_level;
+		}
+	}
+}

-$data_attributes = array(
-	'data-heading-levels' => esc_attr( implode( ',', $heading_levels ) ),
-);
-
-$wrapper_attributes = get_block_wrapper_attributes( $data_attributes );
+$wrapper_attributes = $has_any_include_attr
+	? get_block_wrapper_attributes(
+		array( 'data-heading-levels' => esc_attr( implode( ',', $heading_levels ) ) )
+	)
+	: get_block_wrapper_attributes();
🤖 Prompt for AI Agents
In dynamic-table-of-contents/src/render.php around lines 30 to 41, the code
currently always sets data-heading-levels based on $heading_levels which becomes
an empty string when all toggles are explicitly false, causing view.js to treat
it as "all levels"; instead, detect whether any include* attribute keys exist in
$attributes (i.e. distinguish legacy blocks where attributes are absent from new
blocks where includes are present even if false). Only build and pass the
'data-heading-levels' entry in $data_attributes when at least one include*
attribute key exists on the block (if present, set its value to the escaped
implode of $heading_levels, which may be empty for all-false); leave the data
attribute entirely absent for legacy blocks.

?>

<div <?php echo wp_kses_post( $wrapper_attributes ); ?>>
<?php
$block_title = $attributes['title'] ?? '';
$block_title = apply_filters( 'wpcomsp_dynamic_table_of_contents_block_title', $block_title, $attributes );
Expand Down
Loading