Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 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
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/block-library/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"fast-average-color": "4.3.0",
"lodash": "^4.17.11",
"memize": "^1.0.5",
"slugify": "^1.3.4",
"url": "^0.11.0"
},
"publishConfig": {
Expand Down
1 change: 1 addition & 0 deletions packages/block-library/src/editor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
@import "./spacer/editor.scss";
@import "./subhead/editor.scss";
@import "./table/editor.scss";
@import "./table-of-contents/editor.scss";
@import "./tag-cloud/editor.scss";
@import "./text-columns/editor.scss";
@import "./verse/editor.scss";
Expand Down
2 changes: 2 additions & 0 deletions packages/block-library/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import * as shortcode from './shortcode';
import * as spacer from './spacer';
import * as subhead from './subhead';
import * as table from './table';
import * as tableOfContents from './table-of-contents';
import * as template from './template';
import * as textColumns from './text-columns';
import * as verse from './verse';
Expand Down Expand Up @@ -114,6 +115,7 @@ export const registerCoreBlocks = () => {
spacer,
subhead,
table,
tableOfContents,
tagCloud,
template,
textColumns,
Expand Down
69 changes: 69 additions & 0 deletions packages/block-library/src/table-of-contents/ListLevel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* WordPress dependencies
*/
import { RichText } from '@wordpress/editor';

export default function ListLevel( props ) {
const { edit, attributes, setAttributes } = props;
let childnodes = null;

if ( props.children ) {
childnodes = props.children.map( function( childnode ) {
const link = getLinkElement( childnode, props );

return (
<li key={ childnode.block.anchor }>
{ link }
{ childnode.children ? <ListLevel
edit={ edit }
attributes={ attributes }
setAttributes={ setAttributes }
>
{ childnode.children }
</ListLevel> : null }
</li>
);
} );

return (
<ul>
{ childnodes }
</ul>
);
}
}

function getLinkElement( childnode, props ) {
const { edit, attributes, setAttributes } = props;
const { headings, autosync } = attributes;

const updateHeading = ( content ) => {
headings[ childnode.index ].content = content;
setAttributes( { headings } );
};

if ( autosync ) {
return <a href={ childnode.block.anchor } data-level={ childnode.block.level }>{ childnode.block.content }</a>;
}

if ( edit ) {
return (
<RichText
tagName="a"
href={ childnode.block.anchor }
data-level={ childnode.block.level }
onChange={ ( content ) => updateHeading( content ) }
value={ childnode.block.content }
/>
);
}

return (
<RichText.Content
tagName="a"
href={ childnode.block.anchor }
data-level={ childnode.block.level }
value={ childnode.block.content }
/>
);
}
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
@@ -0,0 +1,4 @@
{
"name": "core/table-of-contents",
"category": "common"
}
127 changes: 127 additions & 0 deletions packages/block-library/src/table-of-contents/edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/**
* Internal dependencies
*/
import * as Utils from './utils';
import ListLevel from './ListLevel';

/**
* WordPress dependencies
*/
import { Component } from '@wordpress/element';
import { subscribe } from '@wordpress/data';
import {
IconButton,
Toolbar,
PanelBody,
ToggleControl,
} from '@wordpress/components';
import {
BlockControls,
InspectorControls,
} from '@wordpress/editor';

class TOCEdit extends Component {
constructor() {
super( ...arguments );

this.state = {
wpDataUnsubscribe: null,
};

this.toggleAttribute = this.toggleAttribute.bind( this );
this.refresh = this.refresh.bind( this );
}

toggleAttribute( propName ) {
const value = this.props.attributes[ propName ];
const { setAttributes } = this.props;

setAttributes( { [ propName ]: ! value } );
}

refresh() {
const { setAttributes } = this.props;
const headings = Utils.getPageHeadings();
setAttributes( { headings } );
}

componentDidMount() {
const { attributes, setAttributes } = this.props;
const headings = attributes.headings || [];
const wpDataUnsubscribe = subscribe( () => {
Copy link
Member

Choose a reason for hiding this comment

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

I'd suggest using withSelect to get the page headings instead of doing a manual subscribe here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@swissspidy Thanks for the suggestion. I will definitely look into that.

const pageHeadings = Utils.getPageHeadings();
this.setState( { pageHeadings } );
} );

setAttributes( { headings } );
this.setState( { wpDataUnsubscribe } );
}

componentWillUnmount() {
this.state.wpDataUnsubscribe();
}

componentDidUpdate( prevProps, prevState ) {
const { attributes, setAttributes } = this.props;
const pageHeadings = Utils.getPageHeadings();
if ( JSON.stringify( pageHeadings ) !== JSON.stringify( prevState.pageHeadings ) ) {
this.setState( { pageHeadings } );
if ( attributes.autosync ) {
setAttributes( { headings: pageHeadings } ); // this is displayed on the page
}
}
}

render() {
const { attributes, setAttributes } = this.props;
const { autosync } = attributes;
const headings = attributes.headings || [];
if ( headings.length === 0 ) {
return ( <p>Start adding headings to generate Table of Contents</p> );
Copy link
Member

Choose a reason for hiding this comment

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

Missing translation function

}

Utils.updateHeadingBlockAnchors();

return (
<div className={ this.props.className }>
{ ! autosync &&
<BlockControls>
<Toolbar>
<IconButton
label={ 'Update' }
Copy link
Member

Choose a reason for hiding this comment

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

Missing translation function

aria-pressed={ this.state.isEditing }
onClick={ this.refresh }
icon="update"
/>
</Toolbar>
</BlockControls>
}
{
<InspectorControls>
<PanelBody title={ 'Table of Contents Settings' }>
Copy link
Member

Choose a reason for hiding this comment

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

Missing translation function

<ToggleControl
label={ 'Auto Sync' }
Copy link
Member

Choose a reason for hiding this comment

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

Missing translation function

Plus, this needs a better label IMO. "Auto Sync" is not clear at all for users.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree. What would be a better label though?

Choose a reason for hiding this comment

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

@ashwin-pc perhaps a better label for this could be Track changes?

Choose a reason for hiding this comment

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

Update Items Automatically?

checked={ autosync }
onChange={ () => {
if ( ! autosync ) {
this.refresh();
}
this.toggleAttribute( 'autosync' );
} }
/>
</PanelBody>
</InspectorControls>
}
<ListLevel
edit={ true }
attributes={ attributes }
setAttributes={ setAttributes }
>
{ Utils.linearToNestedList( headings ) }
</ListLevel>
</div>
);
}
}

export default TOCEdit;
21 changes: 21 additions & 0 deletions packages/block-library/src/table-of-contents/editor.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.wp-block-table-of-contents {
.editor-block-list__block & ul {
padding-left: 1.3em;

a {
display: block;

&:focus {
box-shadow: none;
}
}

ul {
margin-bottom: 0;
}
}

p {
opacity: 0.5;
}
}
39 changes: 39 additions & 0 deletions packages/block-library/src/table-of-contents/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import edit from './edit';
import metadata from './block.json';
import save from './save';

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',
attributes: {
headings: {
source: 'query',
selector: 'a',
query: {
content: { source: 'text' },
anchor: { source: 'attribute', attribute: 'href' },
level: { source: 'attribute', attribute: 'data-level' },
},
},
autosync: {
type: 'boolean',
default: true,
},
},
edit,
save,
};
27 changes: 27 additions & 0 deletions packages/block-library/src/table-of-contents/save.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Internal dependencies
*/
import * as Utils from './utils';
import ListLevel from './ListLevel';

export default function save( props ) {
const { attributes, setAttributes } = props;
const headings = attributes.headings;

if ( headings.length === 0 ) {
return null;
}

Utils.updateHeadingBlockAnchors();
return (
<div className={ props.className }>
<ListLevel
edit={ false }
attributes={ attributes }
setAttributes={ setAttributes }
>
{ Utils.linearToNestedList( headings ) }
</ListLevel>
</div>
);
}
Loading