@wordpress/editor 12.11.0 → 12.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/components/document-outline/index.js +7 -9
  3. package/build/components/document-outline/index.js.map +1 -1
  4. package/build/components/error-boundary/index.js +3 -0
  5. package/build/components/error-boundary/index.js.map +1 -1
  6. package/build/components/index.js +49 -8
  7. package/build/components/index.js.map +1 -1
  8. package/build/components/page-attributes/order.js +3 -7
  9. package/build/components/page-attributes/order.js.map +1 -1
  10. package/build/components/page-attributes/parent.js +1 -1
  11. package/build/components/page-attributes/parent.js.map +1 -1
  12. package/build/components/post-format/index.js +5 -8
  13. package/build/components/post-format/index.js.map +1 -1
  14. package/build/components/post-schedule/label.js +3 -3
  15. package/build/components/post-schedule/label.js.map +1 -1
  16. package/build/components/post-slug/index.js +8 -13
  17. package/build/components/post-slug/index.js.map +1 -1
  18. package/build/components/post-taxonomies/flat-term-selector.js +1 -0
  19. package/build/components/post-taxonomies/flat-term-selector.js.map +1 -1
  20. package/build/components/post-taxonomies/hierarchical-term-selector.js +1 -0
  21. package/build/components/post-taxonomies/hierarchical-term-selector.js.map +1 -1
  22. package/build/components/post-taxonomies/index.js +3 -1
  23. package/build/components/post-taxonomies/index.js.map +1 -1
  24. package/build/components/post-template/index.js +1 -2
  25. package/build/components/post-template/index.js.map +1 -1
  26. package/build/components/post-url/check.js +54 -0
  27. package/build/components/post-url/check.js.map +1 -0
  28. package/build/components/post-url/index.js +115 -0
  29. package/build/components/post-url/index.js.map +1 -0
  30. package/build/components/post-url/label.js +30 -0
  31. package/build/components/post-url/label.js.map +1 -0
  32. package/build/components/post-visibility/label.js +5 -0
  33. package/build/components/post-visibility/label.js.map +1 -1
  34. package/build/components/table-of-contents/panel.js +5 -1
  35. package/build/components/table-of-contents/panel.js.map +1 -1
  36. package/build/components/time-to-read/index.js +60 -0
  37. package/build/components/time-to-read/index.js.map +1 -0
  38. package/build/store/selectors.js +2 -2
  39. package/build/store/selectors.js.map +1 -1
  40. package/build-module/components/document-outline/index.js +7 -8
  41. package/build-module/components/document-outline/index.js.map +1 -1
  42. package/build-module/components/error-boundary/index.js +2 -0
  43. package/build-module/components/error-boundary/index.js.map +1 -1
  44. package/build-module/components/index.js +6 -3
  45. package/build-module/components/index.js.map +1 -1
  46. package/build-module/components/page-attributes/order.js +3 -6
  47. package/build-module/components/page-attributes/order.js.map +1 -1
  48. package/build-module/components/page-attributes/parent.js +2 -2
  49. package/build-module/components/page-attributes/parent.js.map +1 -1
  50. package/build-module/components/post-format/index.js +5 -8
  51. package/build-module/components/post-format/index.js.map +1 -1
  52. package/build-module/components/post-schedule/label.js +3 -3
  53. package/build-module/components/post-schedule/label.js.map +1 -1
  54. package/build-module/components/post-slug/index.js +8 -14
  55. package/build-module/components/post-slug/index.js.map +1 -1
  56. package/build-module/components/post-taxonomies/flat-term-selector.js +1 -2
  57. package/build-module/components/post-taxonomies/flat-term-selector.js.map +1 -1
  58. package/build-module/components/post-taxonomies/hierarchical-term-selector.js +1 -2
  59. package/build-module/components/post-taxonomies/hierarchical-term-selector.js.map +1 -1
  60. package/build-module/components/post-taxonomies/index.js +4 -1
  61. package/build-module/components/post-taxonomies/index.js.map +1 -1
  62. package/build-module/components/post-template/index.js +1 -2
  63. package/build-module/components/post-template/index.js.map +1 -1
  64. package/build-module/components/post-url/check.js +44 -0
  65. package/build-module/components/post-url/check.js.map +1 -0
  66. package/build-module/components/post-url/index.js +102 -0
  67. package/build-module/components/post-url/index.js.map +1 -0
  68. package/build-module/components/post-url/label.js +18 -0
  69. package/build-module/components/post-url/label.js.map +1 -0
  70. package/build-module/components/post-visibility/label.js +3 -0
  71. package/build-module/components/post-visibility/label.js.map +1 -1
  72. package/build-module/components/table-of-contents/panel.js +4 -1
  73. package/build-module/components/table-of-contents/panel.js.map +1 -1
  74. package/build-module/components/time-to-read/index.js +50 -0
  75. package/build-module/components/time-to-read/index.js.map +1 -0
  76. package/build-module/store/selectors.js +3 -3
  77. package/build-module/store/selectors.js.map +1 -1
  78. package/build-style/style-rtl.css +15 -17
  79. package/build-style/style.css +19 -17
  80. package/package.json +28 -28
  81. package/src/components/document-outline/index.js +9 -8
  82. package/src/components/error-boundary/index.js +3 -0
  83. package/src/components/index.js +9 -3
  84. package/src/components/page-attributes/order.js +1 -9
  85. package/src/components/page-attributes/parent.js +1 -2
  86. package/src/components/post-format/index.js +13 -19
  87. package/src/components/post-format/style.scss +2 -17
  88. package/src/components/post-schedule/label.js +1 -1
  89. package/src/components/post-slug/index.js +7 -13
  90. package/src/components/post-slug/test/index.js +7 -6
  91. package/src/components/post-taxonomies/flat-term-selector.js +1 -1
  92. package/src/components/post-taxonomies/hierarchical-term-selector.js +1 -1
  93. package/src/components/post-taxonomies/index.js +3 -1
  94. package/src/components/post-template/index.js +1 -1
  95. package/src/components/post-url/check.js +38 -0
  96. package/src/components/post-url/index.js +122 -0
  97. package/src/components/post-url/label.js +22 -0
  98. package/src/components/post-url/style.scss +16 -0
  99. package/src/components/post-visibility/label.js +4 -0
  100. package/src/components/table-of-contents/panel.js +7 -2
  101. package/src/components/time-to-read/index.js +59 -0
  102. package/src/store/selectors.js +4 -5
  103. package/src/store/test/selectors.js +1 -1
  104. package/src/style.scss +1 -0
@@ -51,16 +51,22 @@ export { default as PostSticky } from './post-sticky';
51
51
  export { default as PostStickyCheck } from './post-sticky/check';
52
52
  export { default as PostSwitchToDraftButton } from './post-switch-to-draft-button';
53
53
  export { default as PostTaxonomies } from './post-taxonomies';
54
- export { default as PostTaxonomiesFlatTermSelector } from './post-taxonomies/flat-term-selector';
55
- export { default as PostTaxonomiesHierarchicalTermSelector } from './post-taxonomies/hierarchical-term-selector';
54
+ export { FlatTermSelector as PostTaxonomiesFlatTermSelector } from './post-taxonomies/flat-term-selector';
55
+ export { HierarchicalTermSelector as PostTaxonomiesHierarchicalTermSelector } from './post-taxonomies/hierarchical-term-selector';
56
56
  export { default as PostTaxonomiesCheck } from './post-taxonomies/check';
57
57
  export { default as PostTextEditor } from './post-text-editor';
58
58
  export { default as PostTitle } from './post-title';
59
59
  export { default as PostTrash } from './post-trash';
60
60
  export { default as PostTrashCheck } from './post-trash/check';
61
61
  export { default as PostTypeSupportCheck } from './post-type-support-check';
62
+ export { default as PostURL } from './post-url';
63
+ export { default as PostURLCheck } from './post-url/check';
64
+ export { default as PostURLLabel, usePostURLLabel } from './post-url/label';
62
65
  export { default as PostVisibility } from './post-visibility';
63
- export { default as PostVisibilityLabel } from './post-visibility/label';
66
+ export {
67
+ default as PostVisibilityLabel,
68
+ usePostVisibilityLabel,
69
+ } from './post-visibility/label';
64
70
  export { default as PostVisibilityCheck } from './post-visibility/check';
65
71
  export { default as TableOfContents } from './table-of-contents';
66
72
  export { default as ThemeSupportCheck } from './theme-support-check';
@@ -1,8 +1,3 @@
1
- /**
2
- * External dependencies
3
- */
4
- import { invoke } from 'lodash';
5
-
6
1
  /**
7
2
  * WordPress dependencies
8
3
  */
@@ -24,10 +19,7 @@ export const PageAttributesOrder = ( { onUpdateOrder, order = 0 } ) => {
24
19
  const setUpdatedOrder = ( value ) => {
25
20
  setOrderInput( value );
26
21
  const newOrder = Number( value );
27
- if (
28
- Number.isInteger( newOrder ) &&
29
- invoke( value, [ 'trim' ] ) !== ''
30
- ) {
22
+ if ( Number.isInteger( newOrder ) && value.trim?.() !== '' ) {
31
23
  onUpdateOrder( Number( value ) );
32
24
  }
33
25
  };
@@ -5,7 +5,6 @@ import {
5
5
  get,
6
6
  unescape as unescapeString,
7
7
  debounce,
8
- repeat,
9
8
  find,
10
9
  deburr,
11
10
  } from 'lodash';
@@ -98,7 +97,7 @@ export function PageAttributesParent() {
98
97
  {
99
98
  value: treeNode.id,
100
99
  label:
101
- repeat( '— ', level ) + unescapeString( treeNode.name ),
100
+ '— '.repeat( level ) + unescapeString( treeNode.name ),
102
101
  rawName: treeNode.name,
103
102
  },
104
103
  ...getOptionsFromTree( treeNode.children || [], level + 1 ),
@@ -81,24 +81,18 @@ export default function PostFormat() {
81
81
  return (
82
82
  <PostFormatCheck>
83
83
  <div className="editor-post-format">
84
- <div className="editor-post-format__content">
85
- <label htmlFor={ postFormatSelectorId }>
86
- { __( 'Post Format' ) }
87
- </label>
88
- <SelectControl
89
- value={ postFormat }
90
- onChange={ ( format ) => onUpdatePostFormat( format ) }
91
- id={ postFormatSelectorId }
92
- options={ formats.map( ( format ) => ( {
93
- label: format.caption,
94
- value: format.id,
95
- } ) ) }
96
- />
97
- </div>
98
-
84
+ <SelectControl
85
+ label={ __( 'Post Format' ) }
86
+ value={ postFormat }
87
+ onChange={ ( format ) => onUpdatePostFormat( format ) }
88
+ id={ postFormatSelectorId }
89
+ options={ formats.map( ( format ) => ( {
90
+ label: format.caption,
91
+ value: format.id,
92
+ } ) ) }
93
+ />
99
94
  { suggestion && suggestion.id !== postFormat && (
100
- <div className="editor-post-format__suggestion">
101
- { __( 'Suggestion:' ) }{ ' ' }
95
+ <p className="editor-post-format__suggestion">
102
96
  <Button
103
97
  variant="link"
104
98
  onClick={ () =>
@@ -107,11 +101,11 @@ export default function PostFormat() {
107
101
  >
108
102
  { sprintf(
109
103
  /* translators: %s: post format */
110
- __( 'Apply format: %s' ),
104
+ __( 'Apply suggested format: %s' ),
111
105
  suggestion.caption
112
106
  ) }
113
107
  </Button>
114
- </div>
108
+ </p>
115
109
  ) }
116
110
  </div>
117
111
  </PostFormatCheck>
@@ -1,18 +1,3 @@
1
- .editor-post-format {
2
- flex-direction: column;
3
- align-items: stretch;
4
- width: 100%;
5
- }
6
-
7
- .editor-post-format__content {
8
- display: inline-flex;
9
- justify-content: space-between;
10
- align-items: center;
11
- width: 100%;
12
- }
13
-
14
- .editor-post-format__suggestion {
15
- padding: $grid-unit-15 * 0.5;
16
- text-align: right;
17
- font-size: $default-font-size;
1
+ [class].editor-post-format__suggestion {
2
+ margin: $grid-unit-05 0 0 0;
18
3
  }
@@ -14,7 +14,7 @@ export default function PostScheduleLabel( props ) {
14
14
  return usePostScheduleLabel( props );
15
15
  }
16
16
 
17
- export function usePostScheduleLabel( { full } ) {
17
+ export function usePostScheduleLabel( { full = false } = {} ) {
18
18
  const { date, isFloating } = useSelect(
19
19
  ( select ) => ( {
20
20
  date: select( editorStore ).getEditedPostAttribute( 'date' ),
@@ -4,8 +4,9 @@
4
4
  import { withDispatch, withSelect } from '@wordpress/data';
5
5
  import { Component } from '@wordpress/element';
6
6
  import { __ } from '@wordpress/i18n';
7
- import { withInstanceId, compose } from '@wordpress/compose';
7
+ import { compose } from '@wordpress/compose';
8
8
  import { safeDecodeURIComponent, cleanForSlug } from '@wordpress/url';
9
+ import { TextControl } from '@wordpress/components';
9
10
 
10
11
  /**
11
12
  * Internal dependencies
@@ -41,25 +42,19 @@ export class PostSlug extends Component {
41
42
  }
42
43
 
43
44
  render() {
44
- const { instanceId } = this.props;
45
45
  const { editedSlug } = this.state;
46
-
47
- const inputId = 'editor-post-slug-' + instanceId;
48
-
49
46
  return (
50
47
  <PostSlugCheck>
51
- <label htmlFor={ inputId }>{ __( 'Slug' ) }</label>
52
- <input
48
+ <TextControl
49
+ label={ __( 'Slug' ) }
53
50
  autoComplete="off"
54
51
  spellCheck="false"
55
- type="text"
56
- id={ inputId }
57
52
  value={ editedSlug }
58
- onChange={ ( event ) =>
59
- this.setState( { editedSlug: event.target.value } )
53
+ onChange={ ( slug ) =>
54
+ this.setState( { editedSlug: slug } )
60
55
  }
61
56
  onBlur={ this.setSlug }
62
- className="editor-post-slug__input"
57
+ className="editor-post-slug"
63
58
  />
64
59
  </PostSlugCheck>
65
60
  );
@@ -86,5 +81,4 @@ export default compose( [
86
81
  },
87
82
  };
88
83
  } ),
89
- withInstanceId,
90
84
  ] )( PostSlug );
@@ -3,6 +3,11 @@
3
3
  */
4
4
  import { shallow } from 'enzyme';
5
5
 
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { TextControl } from '@wordpress/components';
10
+
6
11
  /**
7
12
  * Internal dependencies
8
13
  */
@@ -13,11 +18,7 @@ describe( 'PostSlug', () => {
13
18
  it( 'should update internal slug', () => {
14
19
  const wrapper = shallow( <PostSlug postSlug="index" /> );
15
20
 
16
- wrapper.find( 'input' ).simulate( 'change', {
17
- target: {
18
- value: 'single',
19
- },
20
- } );
21
+ wrapper.find( TextControl ).prop( 'onChange' )( 'single' );
21
22
 
22
23
  expect( wrapper.state().editedSlug ).toEqual( 'single' );
23
24
  } );
@@ -28,7 +29,7 @@ describe( 'PostSlug', () => {
28
29
  <PostSlug postSlug="index" onUpdateSlug={ onUpdateSlug } />
29
30
  );
30
31
 
31
- wrapper.find( 'input' ).simulate( 'blur', {
32
+ wrapper.find( TextControl ).prop( 'onBlur' )( {
32
33
  target: {
33
34
  value: 'single',
34
35
  },
@@ -86,7 +86,7 @@ function findOrCreateTerm( termName, restBase, namespace ) {
86
86
  .then( unescapeTerm );
87
87
  }
88
88
 
89
- function FlatTermSelector( { slug } ) {
89
+ export function FlatTermSelector( { slug } ) {
90
90
  const [ values, setValues ] = useState( [] );
91
91
  const [ search, setSearch ] = useState( '' );
92
92
  const debouncedSearch = useDebounce( setSearch, 500 );
@@ -152,7 +152,7 @@ export function getFilterMatcher( filterValue ) {
152
152
  * @param {string} props.slug Taxonomy slug.
153
153
  * @return {WPElement} Hierarchical term selector component.
154
154
  */
155
- function HierarchicalTermSelector( { slug } ) {
155
+ export function HierarchicalTermSelector( { slug } ) {
156
156
  const [ adding, setAdding ] = useState( false );
157
157
  const [ formName, setFormName ] = useState( '' );
158
158
  /**
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { filter, identity, includes } from 'lodash';
4
+ import { filter, includes } from 'lodash';
5
5
 
6
6
  /**
7
7
  * WordPress dependencies
@@ -18,6 +18,8 @@ import HierarchicalTermSelector from './hierarchical-term-selector';
18
18
  import FlatTermSelector from './flat-term-selector';
19
19
  import { store as editorStore } from '../../store';
20
20
 
21
+ const identity = ( x ) => x;
22
+
21
23
  export function PostTaxonomies( {
22
24
  postType,
23
25
  taxonomies,
@@ -16,7 +16,7 @@ import { store as coreStore } from '@wordpress/core-data';
16
16
  */
17
17
  import { store as editorStore } from '../../store';
18
18
 
19
- export function PostTemplate( {} ) {
19
+ export function PostTemplate() {
20
20
  const { availableTemplates, selectedTemplate, isViewable } = useSelect(
21
21
  ( select ) => {
22
22
  const {
@@ -0,0 +1,38 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useSelect } from '@wordpress/data';
5
+ import { store as coreStore } from '@wordpress/core-data';
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import { store as editorStore } from '../../store';
11
+
12
+ export default function PostURLCheck( { children } ) {
13
+ const isVisible = useSelect( ( select ) => {
14
+ const postTypeSlug = select( editorStore ).getCurrentPostType();
15
+ const postType = select( coreStore ).getPostType( postTypeSlug );
16
+ if ( ! postType?.viewable ) {
17
+ return false;
18
+ }
19
+
20
+ const post = select( editorStore ).getCurrentPost();
21
+ if ( ! post.link ) {
22
+ return false;
23
+ }
24
+
25
+ const permalinkParts = select( editorStore ).getPermalinkParts();
26
+ if ( ! permalinkParts ) {
27
+ return false;
28
+ }
29
+
30
+ return true;
31
+ }, [] );
32
+
33
+ if ( ! isVisible ) {
34
+ return null;
35
+ }
36
+
37
+ return children;
38
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useSelect, useDispatch } from '@wordpress/data';
5
+ import { safeDecodeURIComponent, cleanForSlug } from '@wordpress/url';
6
+ import { useState } from '@wordpress/element';
7
+ import { __experimentalInspectorPopoverHeader as InspectorPopoverHeader } from '@wordpress/block-editor';
8
+ import { __ } from '@wordpress/i18n';
9
+ import { TextControl, ExternalLink } from '@wordpress/components';
10
+ import { store as coreStore } from '@wordpress/core-data';
11
+
12
+ /**
13
+ * Internal dependencies
14
+ */
15
+ import { store as editorStore } from '../../store';
16
+
17
+ export default function PostURL( { onClose } ) {
18
+ const {
19
+ isEditable,
20
+ postSlug,
21
+ viewPostLabel,
22
+ postLink,
23
+ permalinkPrefix,
24
+ permalinkSuffix,
25
+ } = useSelect( ( select ) => {
26
+ const postTypeSlug = select( editorStore ).getCurrentPostType();
27
+ const postType = select( coreStore ).getPostType( postTypeSlug );
28
+ const permalinkParts = select( editorStore ).getPermalinkParts();
29
+ return {
30
+ isEditable: select( editorStore ).isPermalinkEditable(),
31
+ postSlug: safeDecodeURIComponent(
32
+ select( editorStore ).getEditedPostSlug()
33
+ ),
34
+ viewPostLabel: postType?.labels.view_item,
35
+ postLink: select( editorStore ).getCurrentPost().link,
36
+ permalinkPrefix: permalinkParts?.prefix,
37
+ permalinkSuffix: permalinkParts?.suffix,
38
+ };
39
+ }, [] );
40
+
41
+ const { editPost } = useDispatch( editorStore );
42
+
43
+ const [ forceEmptyField, setForceEmptyField ] = useState( false );
44
+
45
+ return (
46
+ <div className="editor-post-url">
47
+ <InspectorPopoverHeader title={ __( 'URL' ) } onClose={ onClose } />
48
+ { isEditable && (
49
+ <TextControl
50
+ label={ __( 'Permalink' ) }
51
+ value={ forceEmptyField ? '' : postSlug }
52
+ autoComplete="off"
53
+ spellCheck="false"
54
+ help={
55
+ <>
56
+ { __( 'The last part of the URL.' ) }{ ' ' }
57
+ <ExternalLink
58
+ href={ __(
59
+ 'https://wordpress.org/support/article/settings-sidebar/#permalink'
60
+ ) }
61
+ >
62
+ { __( 'Learn more.' ) }
63
+ </ExternalLink>
64
+ </>
65
+ }
66
+ onChange={ ( newValue ) => {
67
+ editPost( { slug: newValue } );
68
+ // When we delete the field the permalink gets
69
+ // reverted to the original value.
70
+ // The forceEmptyField logic allows the user to have
71
+ // the field temporarily empty while typing.
72
+ if ( ! newValue ) {
73
+ if ( ! forceEmptyField ) {
74
+ setForceEmptyField( true );
75
+ }
76
+ return;
77
+ }
78
+ if ( forceEmptyField ) {
79
+ setForceEmptyField( false );
80
+ }
81
+ } }
82
+ onBlur={ ( event ) => {
83
+ editPost( {
84
+ slug: cleanForSlug( event.target.value ),
85
+ } );
86
+ if ( forceEmptyField ) {
87
+ setForceEmptyField( false );
88
+ }
89
+ } }
90
+ />
91
+ ) }
92
+ { isEditable && (
93
+ <h3 className="editor-post-url__link-label">
94
+ { viewPostLabel ?? __( 'View post' ) }
95
+ </h3>
96
+ ) }
97
+ <p>
98
+ <ExternalLink
99
+ className="editor-post-url__link"
100
+ href={ postLink }
101
+ target="_blank"
102
+ >
103
+ { isEditable ? (
104
+ <>
105
+ <span className="editor-post-url__link-prefix">
106
+ { permalinkPrefix }
107
+ </span>
108
+ <span className="editor-post-url__link-slug">
109
+ { postSlug }
110
+ </span>
111
+ <span className="editor-post-url__link-suffix">
112
+ { permalinkSuffix }
113
+ </span>
114
+ </>
115
+ ) : (
116
+ postLink
117
+ ) }
118
+ </ExternalLink>
119
+ </p>
120
+ </div>
121
+ );
122
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useSelect } from '@wordpress/data';
5
+ import { filterURLForDisplay } from '@wordpress/url';
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import { store as editorStore } from '../../store';
11
+
12
+ export default function PostURLLabel() {
13
+ return usePostURLLabel();
14
+ }
15
+
16
+ export function usePostURLLabel() {
17
+ const postLink = useSelect(
18
+ ( select ) => select( editorStore ).getCurrentPost().link,
19
+ []
20
+ );
21
+ return filterURLForDisplay( postLink );
22
+ }
@@ -0,0 +1,16 @@
1
+ .editor-post-url__link-label {
2
+ font-size: $default-font-size;
3
+ font-weight: 400;
4
+ margin: 0;
5
+ }
6
+
7
+ /* rtl:begin:ignore */
8
+ .editor-post-url__link {
9
+ direction: ltr;
10
+ word-break: break-word;
11
+ }
12
+ /* rtl:end:ignore */
13
+
14
+ .editor-post-url__link-slug {
15
+ font-weight: 600;
16
+ }
@@ -10,6 +10,10 @@ import { visibilityOptions } from './utils';
10
10
  import { store as editorStore } from '../../store';
11
11
 
12
12
  export default function PostVisibilityLabel() {
13
+ return usePostVisibilityLabel();
14
+ }
15
+
16
+ export function usePostVisibilityLabel() {
13
17
  const visibility = useSelect( ( select ) =>
14
18
  select( editorStore ).getEditedPostVisibility()
15
19
  );
@@ -9,6 +9,7 @@ import { store as blockEditorStore } from '@wordpress/block-editor';
9
9
  * Internal dependencies
10
10
  */
11
11
  import WordCount from '../word-count';
12
+ import TimeToRead from '../time-to-read';
12
13
  import DocumentOutline from '../document-outline';
13
14
  import CharacterCount from '../character-count';
14
15
 
@@ -38,6 +39,10 @@ function TableOfContentsPanel( { hasOutlineItemsDisabled, onRequestClose } ) {
38
39
  tabIndex="0"
39
40
  >
40
41
  <ul role="list" className="table-of-contents__counts">
42
+ <li className="table-of-contents__count">
43
+ { __( 'Words' ) }
44
+ <WordCount />
45
+ </li>
41
46
  <li className="table-of-contents__count">
42
47
  { __( 'Characters' ) }
43
48
  <span className="table-of-contents__number">
@@ -45,8 +50,8 @@ function TableOfContentsPanel( { hasOutlineItemsDisabled, onRequestClose } ) {
45
50
  </span>
46
51
  </li>
47
52
  <li className="table-of-contents__count">
48
- { __( 'Words' ) }
49
- <WordCount />
53
+ { __( 'Time to read' ) }
54
+ <TimeToRead />
50
55
  </li>
51
56
  <li className="table-of-contents__count">
52
57
  { __( 'Headings' ) }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useSelect } from '@wordpress/data';
5
+ import { _x, _n, __, sprintf } from '@wordpress/i18n';
6
+ import { count as wordCount } from '@wordpress/wordcount';
7
+ import { createInterpolateElement } from '@wordpress/element';
8
+
9
+ /**
10
+ * Internal dependencies
11
+ */
12
+ import { store as editorStore } from '../../store';
13
+
14
+ /**
15
+ * Average reading rate - based on average taken from
16
+ * https://irisreading.com/average-reading-speed-in-various-languages/
17
+ * (Characters/minute used for Chinese rather than words).
18
+ *
19
+ * @type {number} A rough estimate of the average reading rate across multiple languages.
20
+ */
21
+ const AVERAGE_READING_RATE = 189;
22
+
23
+ export default function TimeToRead() {
24
+ const content = useSelect(
25
+ ( select ) => select( editorStore ).getEditedPostAttribute( 'content' ),
26
+ []
27
+ );
28
+
29
+ /*
30
+ * translators: If your word count is based on single characters (e.g. East Asian characters),
31
+ * enter 'characters_excluding_spaces' or 'characters_including_spaces'. Otherwise, enter 'words'.
32
+ * Do not translate into your own language.
33
+ */
34
+ const wordCountType = _x( 'words', 'Word count type. Do not translate!' );
35
+ const minutesToRead = Math.round(
36
+ wordCount( content, wordCountType ) / AVERAGE_READING_RATE
37
+ );
38
+ const minutesToReadString =
39
+ minutesToRead === 0
40
+ ? createInterpolateElement( __( '<span>< 1</span> minute' ), {
41
+ span: <span className="table-of-contents__number" />,
42
+ } )
43
+ : createInterpolateElement(
44
+ sprintf(
45
+ /* translators: %s is the number of minutes the post will take to read. */
46
+ _n(
47
+ '<span>%d</span> minute',
48
+ '<span>%d</span> minutes',
49
+ minutesToRead
50
+ ),
51
+ minutesToRead
52
+ ),
53
+ {
54
+ span: <span className="table-of-contents__number" />,
55
+ }
56
+ );
57
+
58
+ return <span className="time-to-read">{ minutesToReadString }</span>;
59
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { find, get, has, isString, includes, some } from 'lodash';
4
+ import { find, get, has, includes, some } from 'lodash';
5
5
  import createSelector from 'rememo';
6
6
 
7
7
  /**
@@ -1605,10 +1605,9 @@ export function __experimentalGetTemplateInfo( state, template ) {
1605
1605
  const { title: defaultTitle, description: defaultDescription } =
1606
1606
  __experimentalGetDefaultTemplateType( state, slug );
1607
1607
 
1608
- const templateTitle = isString( title ) ? title : title?.rendered;
1609
- const templateDescription = isString( description )
1610
- ? description
1611
- : description?.raw;
1608
+ const templateTitle = typeof title === 'string' ? title : title?.rendered;
1609
+ const templateDescription =
1610
+ typeof description === 'string' ? description : description?.raw;
1612
1611
  const templateIcon =
1613
1612
  __experimentalGetDefaultTemplatePartAreas( state ).find(
1614
1613
  ( item ) => area === item.area
@@ -1599,7 +1599,7 @@ describe( 'selectors', () => {
1599
1599
 
1600
1600
  for ( const variantField of fields ) {
1601
1601
  for ( const constantField of fields.filter(
1602
- ( f ) => ! f === variantField
1602
+ ( f ) => f !== variantField
1603
1603
  ) ) {
1604
1604
  const state = {
1605
1605
  editor: {
package/src/style.scss CHANGED
@@ -14,6 +14,7 @@
14
14
  @import "./components/post-saved-state/style.scss";
15
15
  @import "./components/post-taxonomies/style.scss";
16
16
  @import "./components/post-text-editor/style.scss";
17
+ @import "./components/post-url/style.scss";
17
18
  @import "./components/post-visibility/style.scss";
18
19
  @import "./components/post-title/style.scss";
19
20
  @import "./components/post-trash/style.scss";