-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Copy changes from pull request #15426 (#15426). Adds Table of Contents block to the editor. Code contributions in this commit entirely made by ashwin-pc, originally based on the "Guidepost" block by sorta brilliant (https://sortabrilliant.com/guidepost/). Apply polish suggestions from code review. Improve variable names. Add comment Get rid of autosync (users should now convert to list if they want to edit the contents) Add ability to transform into list; remove unused ListLevel props Update table-of-contents block test configuration Simplify expression Remove unused function Remove unused styles. Rename TOCEdit to TableOfContentsEdit Apply suggestions from code review Remove non-existent import Make imports explicit Remove unused function Change unsubscribe function to class property Change JSON.stringify comparison to Lodash's isEqual Turns out refresh() is required Remove unnecessary state setting Don't change state on save Change behaviour to only add links if there are anchors specified by the user Newline Replace anchor with explicit key in map since anchor can now sometimes be empty Update test data Update packages/block-library/src/table-of-contents/block.json Rename ListLevel to ListItem for clarity and polish. Co-authored-by: ashwin-pc <[email protected]> Co-authored-by: Daniel Richards <[email protected]> Co-authored-by: Zebulan Stanphill <[email protected]>
- Loading branch information
1 parent
a15864f
commit 76b1a21
Showing
13 changed files
with
370 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
export default function ListItem( { children, noWrapList = false } ) { | ||
if ( children ) { | ||
const childNodes = children.map( function( childNode, index ) { | ||
const { content, anchor, level } = childNode.block; | ||
|
||
const entry = anchor ? ( | ||
<a | ||
className="blocks-table-of-contents-entry" | ||
href={ anchor } | ||
data-level={ level } | ||
> | ||
{ content } | ||
</a> | ||
) : ( | ||
<span | ||
className="blocks-table-of-contents-entry" | ||
data-level={ level } | ||
> | ||
{ content } | ||
</span> | ||
); | ||
|
||
return ( | ||
<li key={ index }> | ||
{ entry } | ||
{ childNode.children ? ( | ||
<ListItem>{ childNode.children }</ListItem> | ||
) : null } | ||
</li> | ||
); | ||
} ); | ||
|
||
// Don't wrap the list elements in <ul> if converting to a core/list. | ||
return noWrapList ? childNodes : <ul>{ childNodes }</ul>; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ | ||
"name": "core/table-of-contents", | ||
"category": "common", | ||
"attributes": { | ||
"headings": { | ||
"type": "array", | ||
"source": "query", | ||
"selector": ".blocks-table-of-contents-entry", | ||
"default": [], | ||
"query": { | ||
"content": { "source": "text" }, | ||
"anchor": { "source": "attribute", "attribute": "href" }, | ||
"level": { "source": "attribute", "attribute": "data-level" } | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
|
||
const { isEqual } = require( 'lodash' ); | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { getHeadingsList, linearToNestedHeadingList } from './utils'; | ||
import ListItem from './ListItem'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { Component } from '@wordpress/element'; | ||
import { subscribe } from '@wordpress/data'; | ||
import { __ } from '@wordpress/i18n'; | ||
|
||
class TableOfContentsEdit extends Component { | ||
componentDidMount() { | ||
const { attributes, setAttributes } = this.props; | ||
let { headings } = attributes; | ||
|
||
// Update the table of contents when changes are made to other blocks. | ||
this.unsubscribe = subscribe( () => { | ||
this.setState( { headings: getHeadingsList() } ); | ||
} ); | ||
|
||
if ( ! headings ) { | ||
headings = getHeadingsList(); | ||
} | ||
|
||
setAttributes( { headings } ); | ||
} | ||
|
||
componentWillUnmount() { | ||
this.unsubscribe(); | ||
} | ||
|
||
componentDidUpdate( prevProps, prevState ) { | ||
const { setAttributes } = this.props; | ||
const { headings } = this.state; | ||
|
||
if ( prevState && ! isEqual( headings, prevState.headings ) ) { | ||
setAttributes( { headings } ); | ||
} | ||
} | ||
|
||
render() { | ||
const { attributes } = this.props; | ||
const { headings = [] } = attributes; | ||
|
||
if ( headings.length === 0 ) { | ||
return ( | ||
<p> | ||
{ __( | ||
'Start adding heading blocks to see a Table of Contents here' | ||
) } | ||
</p> | ||
); | ||
} | ||
|
||
return ( | ||
<div className={ this.props.className }> | ||
<ListItem>{ linearToNestedHeadingList( headings ) }</ListItem> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
export default TableOfContentsEdit; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { __ } from '@wordpress/i18n'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import edit from './edit'; | ||
import metadata from './block.json'; | ||
import save from './save'; | ||
import transforms from './transforms'; | ||
|
||
const { name } = metadata; | ||
|
||
export { metadata, name }; | ||
|
||
export const settings = { | ||
title: __( 'Table of Contents' ), | ||
description: __( | ||
'Add a list of internal links allowing your readers to quickly navigate around.' | ||
), | ||
icon: 'list-view', | ||
category: 'layout', | ||
transforms, | ||
edit, | ||
save, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import { linearToNestedHeadingList } from './utils'; | ||
import ListItem from './ListItem'; | ||
|
||
export default function save( props ) { | ||
const { attributes } = props; | ||
const { headings } = attributes; | ||
|
||
if ( headings.length === 0 ) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<nav className={ props.className }> | ||
<ListItem>{ linearToNestedHeadingList( headings ) }</ListItem> | ||
</nav> | ||
); | ||
} |
31 changes: 31 additions & 0 deletions
31
packages/block-library/src/table-of-contents/transforms.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { createBlock } from '@wordpress/blocks'; | ||
import { renderToString } from '@wordpress/element'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { linearToNestedHeadingList } from './utils'; | ||
import ListItem from './ListItem'; | ||
|
||
const transforms = { | ||
to: [ | ||
{ | ||
type: 'block', | ||
blocks: [ 'core/list' ], | ||
transform: ( { headings } ) => { | ||
return createBlock( 'core/list', { | ||
values: renderToString( | ||
<ListItem noWrapList> | ||
{ linearToNestedHeadingList( headings ) } | ||
</ListItem> | ||
), | ||
} ); | ||
}, | ||
}, | ||
], | ||
}; | ||
|
||
export default transforms; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { select } from '@wordpress/data'; | ||
import { create } from '@wordpress/rich-text'; | ||
|
||
/** | ||
* Takes a flat list of heading parameters and nests them based on each header's | ||
* immediate parent's level. | ||
* | ||
* @param {Array} headingsList The flat list of headings to nest. | ||
* @param {number} index The current list index. | ||
* @return {Array} The nested list of headings. | ||
*/ | ||
export function linearToNestedHeadingList( headingsList, index = 0 ) { | ||
const nestedHeadingsList = []; | ||
|
||
headingsList.forEach( function( heading, key ) { | ||
if ( heading.content === undefined ) { | ||
return; | ||
} | ||
|
||
// Make sure we are only working with the same level as the first iteration in our set. | ||
if ( heading.level === headingsList[ 0 ].level ) { | ||
// 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 interation. | ||
if ( | ||
headingsList[ key + 1 ] !== undefined && | ||
headingsList[ key + 1 ].level > heading.level | ||
) { | ||
// We need to calculate the last index before the next iteration that has the same level (siblings). | ||
// We then use this last index to slice the array for use in recursion. | ||
// This prevents duplicate nodes. | ||
let endOfSlice = headingsList.length; | ||
for ( let i = key + 1; i < headingsList.length; i++ ) { | ||
if ( headingsList[ i ].level === heading.level ) { | ||
endOfSlice = i; | ||
break; | ||
} | ||
} | ||
|
||
// We found a child node: Push a new node onto the return array with children. | ||
nestedHeadingsList.push( { | ||
block: heading, | ||
index: index + key, | ||
children: linearToNestedHeadingList( | ||
headingsList.slice( key + 1, endOfSlice ), | ||
index + key + 1 | ||
), | ||
} ); | ||
} else { | ||
// No child node: Push a new node onto the return array. | ||
nestedHeadingsList.push( { | ||
block: heading, | ||
index: index + key, | ||
children: null, | ||
} ); | ||
} | ||
} | ||
} ); | ||
|
||
return nestedHeadingsList; | ||
} | ||
|
||
/** | ||
* Gets a list of heading texts, anchors and levels in the current document. | ||
* | ||
* @return {Array} The list of headings. | ||
*/ | ||
export function getHeadingsList() { | ||
return convertBlocksToTableOfContents( getHeadingBlocks() ); | ||
} | ||
|
||
/** | ||
* Gets a list of heading blocks in the current document. | ||
* | ||
* @return {Array} The list of heading blocks. | ||
*/ | ||
export function getHeadingBlocks() { | ||
const editor = select( 'core/block-editor' ); | ||
return editor | ||
.getBlocks() | ||
.filter( ( block ) => block.name === 'core/heading' ); | ||
} | ||
|
||
/** | ||
* Extracts text, anchor and level from a list of heading blocks. | ||
* | ||
* @param {Array} headingBlocks The list of heading blocks. | ||
* @return {Array} The list of heading parameters. | ||
*/ | ||
export function convertBlocksToTableOfContents( headingBlocks ) { | ||
return headingBlocks.map( function( heading ) { | ||
// This is a string so that it can be stored/sourced as an attribute in the table of contents | ||
// block using a data attribute. | ||
const level = heading.attributes.level.toString(); | ||
|
||
const headingContent = heading.attributes.content; | ||
const anchorContent = heading.attributes.anchor; | ||
|
||
// Strip html from heading to use as the table of contents entry. | ||
const content = headingContent | ||
? create( { html: headingContent } ).text | ||
: ''; | ||
|
||
const anchor = anchorContent ? '#' + anchorContent : ''; | ||
|
||
return { content, anchor, level }; | ||
} ); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3 changes: 3 additions & 0 deletions
3
packages/e2e-tests/fixtures/blocks/core__table-of-contents.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
<!-- wp:core/table-of-contents --> | ||
<nav class="wp-block-table-of-contents"><ul><li><a class="blocks-table-of-contents-entry" href="#0-First-Heading" data-level="2">First Heading</a><ul><li><a class="blocks-table-of-contents-entry" href="#1-Sub-Heading" data-level="3">Sub Heading</a></li><li><a class="blocks-table-of-contents-entry" href="#2-Another-Sub-Heading" data-level="3">Another Sub Heading</a></li><li><span class="blocks-table-of-contents-entry" data-level="3">A Sub Heading Without Link</span></li></ul></li></ul></nav> | ||
<!-- /wp:core/table-of-contents --> |
32 changes: 32 additions & 0 deletions
32
packages/e2e-tests/fixtures/blocks/core__table-of-contents.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
[ | ||
{ | ||
"clientId": "_clientId_0", | ||
"name": "core/table-of-contents", | ||
"isValid": true, | ||
"attributes": { | ||
"headings": [ | ||
{ | ||
"content": "First Heading", | ||
"anchor": "#0-First-Heading", | ||
"level": "2" | ||
}, | ||
{ | ||
"content": "Sub Heading", | ||
"anchor": "#1-Sub-Heading", | ||
"level": "3" | ||
}, | ||
{ | ||
"content": "Another Sub Heading", | ||
"anchor": "#2-Another-Sub-Heading", | ||
"level": "3" | ||
}, | ||
{ | ||
"content": "A Sub Heading Without Link", | ||
"level": "3" | ||
} | ||
] | ||
}, | ||
"innerBlocks": [], | ||
"originalContent": "<nav class=\"wp-block-table-of-contents\"><ul><li><a class=\"blocks-table-of-contents-entry\" href=\"#0-First-Heading\" data-level=\"2\">First Heading</a><ul><li><a class=\"blocks-table-of-contents-entry\" href=\"#1-Sub-Heading\" data-level=\"3\">Sub Heading</a></li><li><a class=\"blocks-table-of-contents-entry\" href=\"#2-Another-Sub-Heading\" data-level=\"3\">Another Sub Heading</a></li><li><span class=\"blocks-table-of-contents-entry\" data-level=\"3\">A Sub Heading Without Link</span></li></ul></li></ul></nav>" | ||
} | ||
] |
11 changes: 11 additions & 0 deletions
11
packages/e2e-tests/fixtures/blocks/core__table-of-contents.parsed.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
[ | ||
{ | ||
"blockName": "core/table-of-contents", | ||
"attrs": {}, | ||
"innerBlocks": [], | ||
"innerHTML": "\n<nav class=\"wp-block-table-of-contents\"><ul><li><a class=\"blocks-table-of-contents-entry\" href=\"#0-First-Heading\" data-level=\"2\">First Heading</a><ul><li><a class=\"blocks-table-of-contents-entry\" href=\"#1-Sub-Heading\" data-level=\"3\">Sub Heading</a></li><li><a class=\"blocks-table-of-contents-entry\" href=\"#2-Another-Sub-Heading\" data-level=\"3\">Another Sub Heading</a></li><li><span class=\"blocks-table-of-contents-entry\" data-level=\"3\">A Sub Heading Without Link</span></li></ul></li></ul></nav>\n", | ||
"innerContent": [ | ||
"\n<nav class=\"wp-block-table-of-contents\"><ul><li><a class=\"blocks-table-of-contents-entry\" href=\"#0-First-Heading\" data-level=\"2\">First Heading</a><ul><li><a class=\"blocks-table-of-contents-entry\" href=\"#1-Sub-Heading\" data-level=\"3\">Sub Heading</a></li><li><a class=\"blocks-table-of-contents-entry\" href=\"#2-Another-Sub-Heading\" data-level=\"3\">Another Sub Heading</a></li><li><span class=\"blocks-table-of-contents-entry\" data-level=\"3\">A Sub Heading Without Link</span></li></ul></li></ul></nav>\n" | ||
] | ||
} | ||
] |
Oops, something went wrong.