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;