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
2 changes: 1 addition & 1 deletion docs/reference-guides/core-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -958,7 +958,7 @@ Summarize your post with a list of headings. Add HTML anchors to Heading blocks
- **Experimental:** true
- **Category:** design
- **Supports:** color (background, gradients, link, text), interactivity (clientNavigation), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~
- **Attributes:** headings, maxLevel, onlyIncludeCurrentPage
- **Attributes:** headings, maxLevel, onlyIncludeCurrentPage, ordered

## Tag Cloud

Expand Down
4 changes: 4 additions & 0 deletions packages/block-library/src/table-of-contents/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
},
"maxLevel": {
"type": "number"
},
"ordered": {
"type": "boolean",
"default": true
}
},
"supports": {
Expand Down
79 changes: 58 additions & 21 deletions packages/block-library/src/table-of-contents/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,16 @@ import {
} from '@wordpress/components';
import { useDispatch, useSelect } from '@wordpress/data';
import { renderToString } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { __, isRTL } from '@wordpress/i18n';
import { useInstanceId } from '@wordpress/compose';
import { store as noticeStore } from '@wordpress/notices';
import { tableOfContents as icon } from '@wordpress/icons';
import {
tableOfContents as icon,
formatListBullets,
formatListBulletsRTL,
formatListNumbered,
formatListNumberedRTL,
} from '@wordpress/icons';

/**
* Internal dependencies
Expand All @@ -43,13 +49,19 @@ import { useToolsPanelDropdownMenuProps } from '../utils/hooks';
* @param {HeadingData[]} props.attributes.headings The 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 {number|undefined} props.attributes.maxLevel The maximum heading level to include, or null to include all levels.
* @param {boolean} props.attributes.ordered Whether to display as an ordered list (true) or unordered list (false).
* @param {string} props.clientId The client id.
* @param {(attributes: Object) => void} props.setAttributes The set attributes function.
*
* @return {Component} The component.
*/
export default function TableOfContentsEdit( {
attributes: { headings = [], onlyIncludeCurrentPage, maxLevel },
attributes: {
headings = [],
onlyIncludeCurrentPage,
maxLevel,
ordered = true,
},
clientId,
setAttributes,
} ) {
Expand Down Expand Up @@ -86,27 +98,48 @@ export default function TableOfContentsEdit( {
const dropdownMenuProps = useToolsPanelDropdownMenuProps();
const headingTree = linearToNestedHeadingList( headings );

const toolbarControls = canInsertList && (
const toolbarControls = (
<BlockControls>
<ToolbarGroup>
<ToolbarButton
onClick={ () =>
replaceBlocks(
clientId,
createBlock( 'core/list', {
ordered: true,
values: renderToString(
<TableOfContentsList
nestedHeadingList={ headingTree }
/>
),
} )
)
icon={ isRTL() ? formatListBulletsRTL : formatListBullets }
title={ __( 'Unordered' ) }
description={ __( 'Convert to unordered list' ) }
onClick={ () => setAttributes( { ordered: false } ) }
isActive={ ordered === false }
/>
<ToolbarButton
icon={
isRTL() ? formatListNumberedRTL : formatListNumbered
}
>
{ __( 'Convert to static list' ) }
</ToolbarButton>
title={ __( 'Ordered' ) }
description={ __( 'Convert to ordered list' ) }
onClick={ () => setAttributes( { ordered: true } ) }
isActive={ ordered === true }
/>
</ToolbarGroup>
{ canInsertList && (
<ToolbarGroup>
<ToolbarButton
onClick={ () =>
replaceBlocks(
clientId,
createBlock( 'core/list', {
ordered,
values: renderToString(
<TableOfContentsList
nestedHeadingList={ headingTree }
ordered={ ordered }
/>
),
} )
)
}
>
{ __( 'Convert to static list' ) }
</ToolbarButton>
</ToolbarGroup>
) }
</BlockControls>
);

Expand All @@ -118,6 +151,7 @@ export default function TableOfContentsEdit( {
setAttributes( {
onlyIncludeCurrentPage: false,
maxLevel: undefined,
ordered: true,
} );
} }
dropdownMenuProps={ dropdownMenuProps }
Expand Down Expand Up @@ -210,16 +244,19 @@ export default function TableOfContentsEdit( {
);
}

const ListTag = ordered ? 'ol' : 'ul';

return (
<>
<nav { ...blockProps }>
<ol>
<ListTag>
<TableOfContentsList
nestedHeadingList={ headingTree }
disableLinkActivation
onClick={ showRedirectionPreventedNotice }
ordered={ ordered }
/>
</ol>
</ListTag>
</nav>
{ toolbarControls }
{ inspectorControls }
Expand Down
9 changes: 7 additions & 2 deletions packages/block-library/src/table-of-contents/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ export default function TableOfContentsList( {
nestedHeadingList,
disableLinkActivation,
onClick,
ordered = true,
}: {
nestedHeadingList: NestedHeadingData[];
disableLinkActivation?: boolean;
onClick?: ( event: MouseEvent< HTMLAnchorElement > ) => void;
ordered?: boolean;
} ): ReactElement {
return (
<>
Expand All @@ -42,11 +44,13 @@ export default function TableOfContentsList( {
<span className={ ENTRY_CLASS_NAME }>{ content }</span>
);

const NestedListTag = ordered ? 'ol' : 'ul';

return (
<li key={ index }>
{ entry }
{ node.children ? (
<ol>
<NestedListTag>
<TableOfContentsList
nestedHeadingList={ node.children }
disableLinkActivation={
Expand All @@ -58,8 +62,9 @@ export default function TableOfContentsList( {
? onClick
: undefined
}
ordered={ ordered }
/>
</ol>
</NestedListTag>
) : null }
</li>
);
Expand Down
10 changes: 7 additions & 3 deletions packages/block-library/src/table-of-contents/save.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,21 @@ import { useBlockProps } from '@wordpress/block-editor';
import TableOfContentsList from './list';
import { linearToNestedHeadingList } from './utils';

export default function save( { attributes: { headings = [] } } ) {
export default function save( {
attributes: { headings = [], ordered = true },
} ) {
if ( headings.length === 0 ) {
return null;
}
const ListTag = ordered ? 'ol' : 'ul';
return (
<nav { ...useBlockProps.save() }>
<ol>
<ListTag>
<TableOfContentsList
nestedHeadingList={ linearToNestedHeadingList( headings ) }
ordered={ ordered }
/>
</ol>
</ListTag>
</nav>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"link": "#heading-id-2"
}
],
"onlyIncludeCurrentPage": false
"onlyIncludeCurrentPage": false,
"ordered": true
},
"innerBlocks": []
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"isValid": true,
"attributes": {
"headings": [],
"onlyIncludeCurrentPage": false
"onlyIncludeCurrentPage": false,
"ordered": true
},
"innerBlocks": []
}
Expand Down
Loading