Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add word and block count to table of contents #2684

Merged
merged 13 commits into from
Sep 25, 2017
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,19 @@ import { filter } from 'lodash';
/**
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { PanelBody } from '@wordpress/components';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import './style.scss';
import TableOfContentsItem from './item';
import { getBlocks, isEditorSidebarPanelOpened } from '../../selectors';
import { selectBlock, toggleSidebarPanel } from '../../actions';
import DocumentOutlineItem from './item';
import { getBlocks } from '../selectors';
import { selectBlock } from '../actions';

/**
* Module constants
*/
const PANEL_NAME = 'table-of-contents';
const emptyHeadingContent = <em>{ __( '(Empty heading)' ) }</em>;
const incorrectLevelContent = [
<br key="incorrect-break" />,
Expand Down Expand Up @@ -53,7 +51,7 @@ const getHeadingLevel = heading => {

const isEmptyHeading = heading => ! heading.attributes.content || heading.attributes.content.length === 0;

const TableOfContents = ( { blocks, onSelect, isOpened, onTogglePanel } ) => {
const DocumentOutline = ( { blocks, onSelect } ) => {
const headings = filter( blocks, ( block ) => block.name === 'core/heading' );

if ( headings.length <= 1 ) {
Expand All @@ -66,7 +64,7 @@ const TableOfContents = ( { blocks, onSelect, isOpened, onTogglePanel } ) => {
// when clicking on a heading item from the list.
const onSelectHeading = ( uid ) => onSelect( uid );

const tocItems = headings.map( ( heading, index ) => {
const items = headings.map( ( heading, index ) => {
const headingLevel = getHeadingLevel( heading );
const isEmpty = isEmptyHeading( heading );

Expand All @@ -83,41 +81,34 @@ const TableOfContents = ( { blocks, onSelect, isOpened, onTogglePanel } ) => {
prevHeadingLevel = headingLevel;

return (
<TableOfContentsItem
<DocumentOutlineItem
key={ index }
level={ headingLevel }
isValid={ isValid }
onClick={ () => onSelectHeading( heading.uid ) }
>
{ isEmpty ? emptyHeadingContent : heading.attributes.content }
{ isIncorrectLevel && incorrectLevelContent }
</TableOfContentsItem>
</DocumentOutlineItem>
);
} );

return (
<PanelBody title={ __( 'Table of Contents' ) } opened={ isOpened } onToggle={ onTogglePanel }>
<div className="table-of-contents__items">
<p><strong>{ sprintf( '%d Headings', headings.length ) }</strong></p>
<ul>{ tocItems }</ul>
</div>
</PanelBody>
<div className="document-outline">
<ul>{ items }</ul>
</div>
);
};

export default connect(
( state ) => {
return {
blocks: getBlocks( state ),
isOpened: isEditorSidebarPanelOpened( state, PANEL_NAME ),
};
},
{
onSelect( uid ) {
return selectBlock( uid );
},
onTogglePanel() {
return toggleSidebarPanel( PANEL_NAME );
},
}
)( TableOfContents );
)( DocumentOutline );
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,25 @@ const TableOfContentsItem = ( {
} ) => (
<li
className={ classnames(
'table-of-contents-item',
'document-outline__item',
`is-h${ level }`,
{
'is-invalid': ! isValid,
}
) }
>
<button
className="table-of-contents__button"
className="document-outline__button"
onClick={ onClick }
aria-label={ __( 'Focus heading block' ) }
>
<span className="table-of-contents-item__emdash" aria-hidden="true"></span>
<strong className="table-of-contents-item__level">
<span className="document-outline__emdash" aria-hidden="true"></span>
<strong className="document-outline__level">
H{ level }
</strong>
<span className="table-of-contents-item__content">
<span className="document-outline__item-content">
{ children }
</span>
<span className="screen-reader-text">{ __( '(Click to focus this heading)' ) }</span>
</button>
</li>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,48 +1,53 @@
.table-of-contents__items {
.document-outline {
margin: 20px 0;
}

.table-of-contents-item {
.document-outline__item {
display: flex;
margin: 4px 0;

.table-of-contents-item__emdash::before {
.document-outline__emdash::before {
color: $light-gray-500;
margin-right: 4px;
}

&.is-h2 .table-of-contents-item__emdash::before {
&.is-h2 .document-outline__emdash::before {
content: '—';
}

&.is-h3 .table-of-contents-item__emdash::before {
&.is-h3 .document-outline__emdash::before {
content: '——';
}

&.is-h4 .table-of-contents-item__emdash::before {
&.is-h4 .document-outline__emdash::before {
content: '———';
}

&.is-h5 .table-of-contents-item__emdash::before {
&.is-h5 .document-outline__emdash::before {
content: '————';
}

&.is-h6 .table-of-contents-item__emdash::before {
&.is-h6 .document-outline__emdash::before {
content: '—————';
}
}

.table-of-contents__button {
.document-outline__button {
cursor: pointer;
background: none;
border: none;
display: flex;
align-items: flex-start;
color: $dark-gray-800;
text-align: left;

&:focus {
box-shadow: $button-focus-style;
outline: none;
}
}

.table-of-contents-item__level {
.document-outline__level {
background: $light-gray-500;
color: $dark-gray-800;
border-radius: 3px;
Expand Down
2 changes: 2 additions & 0 deletions editor/modes/visual-editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import './style.scss';
import VisualEditorBlockList from './block-list';
import PostTitle from '../../post-title';
import WritingFlow from '../../writing-flow';
import TableOfContents from '../../table-of-contents';
import { getBlockUids, getMultiSelectedBlockUids } from '../../selectors';
import { clearSelectedBlock, multiSelect, redo, undo, removeBlocks } from '../../actions';

Expand Down Expand Up @@ -103,6 +104,7 @@ class VisualEditor extends Component {
<PostTitle />
<VisualEditorBlockList ref={ this.bindBlocksContainer } />
</WritingFlow>
<TableOfContents />
</div>
);
/* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */
Expand Down
51 changes: 51 additions & 0 deletions editor/sidebar/document-outline-panel/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* External dependencies
*/
import { connect } from 'react-redux';
import { filter } from 'lodash';

/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { PanelBody } from '@wordpress/components';

/**
* Internal dependencies
*/
import DocumentOutline from '../../document-outline';
import { getBlocks, isEditorSidebarPanelOpened } from '../../selectors';
import { toggleSidebarPanel } from '../../actions';

/**
* Module constants
*/
const PANEL_NAME = 'table-of-contents';

const DocumentOutlinePanel = ( { blocks, isOpened, onTogglePanel } ) => {
const headings = filter( blocks, ( block ) => block.name === 'core/heading' );

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

return (
<PanelBody title={ __( 'Document Outline' ) } opened={ isOpened } onToggle={ onTogglePanel }>
<DocumentOutline />
</PanelBody>
);
};

export default connect(
( state ) => {
return {
blocks: getBlocks( state ),
isOpened: isEditorSidebarPanelOpened( state, PANEL_NAME ),
};
},
{
onTogglePanel() {
return toggleSidebarPanel( PANEL_NAME );
},
}
)( DocumentOutlinePanel );
4 changes: 2 additions & 2 deletions editor/sidebar/post-settings/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import PostTaxonomies from '../post-taxonomies';
import FeaturedImage from '../featured-image';
import DiscussionPanel from '../discussion-panel';
import LastRevision from '../last-revision';
import TableOfContents from '../table-of-contents';
import PageAttributes from '../page-attributes';
import DocumentOutlinePanel from '../document-outline-panel';

const panel = (
<Panel>
Expand All @@ -25,7 +25,7 @@ const panel = (
<PostExcerpt />
<DiscussionPanel />
<PageAttributes />
<TableOfContents />
<DocumentOutlinePanel />
</Panel>
);

Expand Down
87 changes: 87 additions & 0 deletions editor/table-of-contents/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* External dependencies
*/
import { connect } from 'react-redux';
import { filter } from 'lodash';

/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { Dashicon, Popover } from '@wordpress/components';
import { Component } from '@wordpress/element';

/**
* Internal dependencies
*/
import './style.scss';
import DocumentOutline from '../document-outline';
import WordCount from '../word-count';
import { getBlocks } from '../selectors';
import { selectBlock } from '../actions';

class TableOfContents extends Component {
constructor() {
super( ...arguments );
this.state = {
showPopover: false,
};
}

render() {
const { blocks } = this.props;
const headings = filter( blocks, ( block ) => block.name === 'core/heading' );

return (
<div className="table-of-contents">
<button
className="table-of-contents__toggle"
onClick={ () => this.setState( { showPopover: ! this.state.showPopover } ) }
>
<Dashicon icon="info" />
</button>
<Popover
isOpen={ this.state.showPopover }
position="bottom"
className="table-of-contents__popover"
onClose={ () => this.setState( { showPopover: false } ) }
>
<div className="table-of-contents__counts">
<div className="table-of-contents__count">
<WordCount />
{ __( 'Word Count' ) }
</div>
<div className="table-of-contents__count">
<span className="table-of-contents__number">{ blocks.length }</span>
{ __( 'Blocks' ) }
</div>
<div className="table-of-contents__count">
<span className="table-of-contents__number">{ headings.length }</span>
{ __( 'Headings' ) }
</div>
</div>
{ headings.length > 0 &&
<div>
<hr />
<span className="table-of-contents__title">{ __( 'Table of Contents' ) }</span>
<DocumentOutline />
</div>
}
</Popover>
</div>
);
}
}

export default connect(
( state ) => {
return {
blocks: getBlocks( state ),
};
},
{
onSelect( uid ) {
return selectBlock( uid );
},
}
)( TableOfContents );
Loading