diff --git a/editor/sidebar/table-of-contents/index.js b/editor/document-outline/index.js
similarity index 68%
rename from editor/sidebar/table-of-contents/index.js
rename to editor/document-outline/index.js
index 2dc54498cfd4b..7673dd28477f1 100644
--- a/editor/sidebar/table-of-contents/index.js
+++ b/editor/document-outline/index.js
@@ -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 = { __( '(Empty heading)' ) };
const incorrectLevelContent = [
,
@@ -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 ) {
@@ -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 );
@@ -83,7 +81,7 @@ const TableOfContents = ( { blocks, onSelect, isOpened, onTogglePanel } ) => {
prevHeadingLevel = headingLevel;
return (
- {
>
{ isEmpty ? emptyHeadingContent : heading.attributes.content }
{ isIncorrectLevel && incorrectLevelContent }
-
+
);
} );
return (
-
-
-
{ sprintf( '%d Headings', headings.length ) }
-
-
-
+
);
};
@@ -109,15 +104,11 @@ 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 );
diff --git a/editor/sidebar/table-of-contents/item.js b/editor/document-outline/item.js
similarity index 58%
rename from editor/sidebar/table-of-contents/item.js
rename to editor/document-outline/item.js
index a63fed0d4add9..dbc7404fa360a 100644
--- a/editor/sidebar/table-of-contents/item.js
+++ b/editor/document-outline/item.js
@@ -16,7 +16,7 @@ const TableOfContentsItem = ( {
} ) => (
);
diff --git a/editor/sidebar/table-of-contents/style.scss b/editor/document-outline/style.scss
similarity index 57%
rename from editor/sidebar/table-of-contents/style.scss
rename to editor/document-outline/style.scss
index b6d54de9313c3..590d8b42fce9b 100644
--- a/editor/sidebar/table-of-contents/style.scss
+++ b/editor/document-outline/style.scss
@@ -1,38 +1,38 @@
-.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;
@@ -40,9 +40,14 @@
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;
diff --git a/editor/modes/visual-editor/index.js b/editor/modes/visual-editor/index.js
index 4733a57817061..e7cc5290f9c1f 100644
--- a/editor/modes/visual-editor/index.js
+++ b/editor/modes/visual-editor/index.js
@@ -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';
@@ -103,6 +104,7 @@ class VisualEditor extends Component {
+
);
/* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */
diff --git a/editor/sidebar/document-outline-panel/index.js b/editor/sidebar/document-outline-panel/index.js
new file mode 100644
index 0000000000000..0c87c66207bfa
--- /dev/null
+++ b/editor/sidebar/document-outline-panel/index.js
@@ -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 (
+
+
+
+ );
+};
+
+export default connect(
+ ( state ) => {
+ return {
+ blocks: getBlocks( state ),
+ isOpened: isEditorSidebarPanelOpened( state, PANEL_NAME ),
+ };
+ },
+ {
+ onTogglePanel() {
+ return toggleSidebarPanel( PANEL_NAME );
+ },
+ }
+)( DocumentOutlinePanel );
diff --git a/editor/sidebar/post-settings/index.js b/editor/sidebar/post-settings/index.js
index 8db77ba4efb53..831e57afe9952 100644
--- a/editor/sidebar/post-settings/index.js
+++ b/editor/sidebar/post-settings/index.js
@@ -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 = (
@@ -25,7 +25,7 @@ const panel = (
-
+
);
diff --git a/editor/table-of-contents/index.js b/editor/table-of-contents/index.js
new file mode 100644
index 0000000000000..7ee23a3055455
--- /dev/null
+++ b/editor/table-of-contents/index.js
@@ -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 (
+
+
+
this.setState( { showPopover: false } ) }
+ >
+
+
+
+ { __( 'Word Count' ) }
+
+
+ { blocks.length }
+ { __( 'Blocks' ) }
+
+
+ { headings.length }
+ { __( 'Headings' ) }
+
+
+ { headings.length > 0 &&
+
+
+ { __( 'Table of Contents' ) }
+
+
+ }
+
+
+ );
+ }
+}
+
+export default connect(
+ ( state ) => {
+ return {
+ blocks: getBlocks( state ),
+ };
+ },
+ {
+ onSelect( uid ) {
+ return selectBlock( uid );
+ },
+ }
+)( TableOfContents );
diff --git a/editor/table-of-contents/style.scss b/editor/table-of-contents/style.scss
new file mode 100644
index 0000000000000..e284781f0ae80
--- /dev/null
+++ b/editor/table-of-contents/style.scss
@@ -0,0 +1,69 @@
+.table-of-contents {
+ display: none;
+ position: fixed;
+ right: 16px;
+ top: 100px;
+
+ .is-sidebar-opened & {
+ right: $sidebar-width + 16px;
+ }
+
+ @include break-small() {
+ display: block;
+ }
+}
+
+.table-of-contents__toggle {
+ background: none;
+ border: none;
+ box-shadow: none;
+ color: $dark-gray-300;
+ cursor: pointer;
+ font-family: $default-font;
+ font-size: 12px;
+ padding: 0 6px 4px;
+
+ .dashicon {
+ width: 20px;
+ height: 20px;
+ position: relative;
+ top: 5px;
+ }
+
+ &:focus {
+ box-shadow: $button-focus-style;
+ outline: none;
+ }
+}
+
+.table-of-contents__popover .components-popover__content {
+ padding: 16px;
+}
+
+.table-of-contents__counts {
+ display: flex;
+ flex-wrap: wrap;
+ margin-bottom: 10px;
+}
+
+.table-of-contents__count {
+ width: 50%;
+ display: flex;
+ flex-direction: column;
+ margin-bottom: 10px;
+}
+
+.table-of-contents__number,
+.table-of-contents__popover .word-count {
+ font-size: 40px;
+ font-weight: 100;
+ line-height: 50px;
+ color: $dark-gray-300;
+}
+
+.table-of-contents__title {
+ display: block;
+ margin-top: 20px;
+ font-size: 15px;
+ font-weight: 600;
+}
diff --git a/editor/word-count/index.js b/editor/word-count/index.js
new file mode 100644
index 0000000000000..958e2dc10ae60
--- /dev/null
+++ b/editor/word-count/index.js
@@ -0,0 +1,25 @@
+/**
+ * External dependencies
+ */
+import { connect } from 'react-redux';
+
+/**
+ * WordPress dependencies
+ */
+import { getBlocks } from '../selectors';
+import { serialize } from 'blocks';
+
+function WordCount( { content } ) {
+ const wordCount = wp.utils.WordCounter.prototype.count( content );
+ return (
+ { wordCount }
+ );
+}
+
+export default connect(
+ ( state ) => {
+ return {
+ content: serialize( getBlocks( state ) ),
+ };
+ }
+)( WordCount );
diff --git a/lib/client-assets.php b/lib/client-assets.php
index 71684f7c628a7..630d2c29725b0 100644
--- a/lib/client-assets.php
+++ b/lib/client-assets.php
@@ -611,7 +611,7 @@ function gutenberg_editor_scripts_and_styles( $hook ) {
wp_enqueue_script(
'wp-editor',
gutenberg_url( 'editor/build/index.js' ),
- array( 'wp-api', 'wp-date', 'wp-i18n', 'wp-blocks', 'wp-element', 'wp-components', 'wp-utils', 'editor' ),
+ array( 'wp-api', 'wp-date', 'wp-i18n', 'wp-blocks', 'wp-element', 'wp-components', 'wp-utils', 'word-count', 'editor' ),
filemtime( gutenberg_dir_path() . 'editor/build/index.js' ),
true // enqueue in the footer.
);
@@ -681,8 +681,6 @@ function gutenberg_editor_scripts_and_styles( $hook ) {
// `wp-utils` doesn't clobbber `word-count`. See WordPress/gutenberg#1569.
$word_count_script = wp_scripts()->query( 'word-count' );
array_push( $word_count_script->deps, 'wp-utils' );
- // Now load the `word-count` script from core.
- wp_enqueue_script( 'word-count' );
// Parse post type from parameters.
$post_type = null;