@wordpress/editor 12.10.0 → 12.13.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 (154) hide show
  1. package/CHANGELOG.md +6 -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 +61 -9
  7. package/build/components/index.js.map +1 -1
  8. package/build/components/local-autosave-monitor/index.js +24 -19
  9. package/build/components/local-autosave-monitor/index.js.map +1 -1
  10. package/build/components/page-attributes/order.js +3 -7
  11. package/build/components/page-attributes/order.js.map +1 -1
  12. package/build/components/page-attributes/parent.js +8 -4
  13. package/build/components/page-attributes/parent.js.map +1 -1
  14. package/build/components/post-format/index.js +5 -8
  15. package/build/components/post-format/index.js.map +1 -1
  16. package/build/components/post-locked-modal/index.js +1 -1
  17. package/build/components/post-locked-modal/index.js.map +1 -1
  18. package/build/components/post-publish-panel/maybe-category-panel.js +2 -4
  19. package/build/components/post-publish-panel/maybe-category-panel.js.map +1 -1
  20. package/build/components/post-schedule/label.js +93 -13
  21. package/build/components/post-schedule/label.js.map +1 -1
  22. package/build/components/post-slug/index.js +8 -13
  23. package/build/components/post-slug/index.js.map +1 -1
  24. package/build/components/post-taxonomies/flat-term-selector.js +8 -4
  25. package/build/components/post-taxonomies/flat-term-selector.js.map +1 -1
  26. package/build/components/post-taxonomies/hierarchical-term-selector.js +1 -0
  27. package/build/components/post-taxonomies/hierarchical-term-selector.js.map +1 -1
  28. package/build/components/post-taxonomies/index.js +3 -1
  29. package/build/components/post-taxonomies/index.js.map +1 -1
  30. package/build/components/post-template/index.js +1 -2
  31. package/build/components/post-template/index.js.map +1 -1
  32. package/build/components/post-title/index.js +8 -2
  33. package/build/components/post-title/index.js.map +1 -1
  34. package/build/components/post-url/check.js +54 -0
  35. package/build/components/post-url/check.js.map +1 -0
  36. package/build/components/post-url/index.js +115 -0
  37. package/build/components/post-url/index.js.map +1 -0
  38. package/build/components/post-url/label.js +30 -0
  39. package/build/components/post-url/label.js.map +1 -0
  40. package/build/components/post-visibility/index.js +3 -1
  41. package/build/components/post-visibility/index.js.map +1 -1
  42. package/build/components/post-visibility/label.js +5 -0
  43. package/build/components/post-visibility/label.js.map +1 -1
  44. package/build/components/provider/index.native.js +3 -2
  45. package/build/components/provider/index.native.js.map +1 -1
  46. package/build/components/provider/use-block-editor-settings.js +2 -2
  47. package/build/components/provider/use-block-editor-settings.js.map +1 -1
  48. package/build/components/table-of-contents/panel.js +5 -1
  49. package/build/components/table-of-contents/panel.js.map +1 -1
  50. package/build/components/time-to-read/index.js +60 -0
  51. package/build/components/time-to-read/index.js.map +1 -0
  52. package/build/store/actions.js +5 -1
  53. package/build/store/actions.js.map +1 -1
  54. package/build/store/reducer.js +1 -1
  55. package/build/store/reducer.js.map +1 -1
  56. package/build/store/selectors.js +3 -3
  57. package/build/store/selectors.js.map +1 -1
  58. package/build-module/components/document-outline/index.js +7 -8
  59. package/build-module/components/document-outline/index.js.map +1 -1
  60. package/build-module/components/error-boundary/index.js +2 -0
  61. package/build-module/components/error-boundary/index.js.map +1 -1
  62. package/build-module/components/index.js +7 -4
  63. package/build-module/components/index.js.map +1 -1
  64. package/build-module/components/local-autosave-monitor/index.js +24 -18
  65. package/build-module/components/local-autosave-monitor/index.js.map +1 -1
  66. package/build-module/components/page-attributes/order.js +3 -6
  67. package/build-module/components/page-attributes/order.js.map +1 -1
  68. package/build-module/components/page-attributes/parent.js +6 -5
  69. package/build-module/components/page-attributes/parent.js.map +1 -1
  70. package/build-module/components/post-format/index.js +5 -8
  71. package/build-module/components/post-format/index.js.map +1 -1
  72. package/build-module/components/post-locked-modal/index.js +1 -1
  73. package/build-module/components/post-locked-modal/index.js.map +1 -1
  74. package/build-module/components/post-publish-panel/maybe-category-panel.js +2 -4
  75. package/build-module/components/post-publish-panel/maybe-category-panel.js.map +1 -1
  76. package/build-module/components/post-schedule/label.js +90 -13
  77. package/build-module/components/post-schedule/label.js.map +1 -1
  78. package/build-module/components/post-slug/index.js +8 -14
  79. package/build-module/components/post-slug/index.js.map +1 -1
  80. package/build-module/components/post-taxonomies/flat-term-selector.js +8 -6
  81. package/build-module/components/post-taxonomies/flat-term-selector.js.map +1 -1
  82. package/build-module/components/post-taxonomies/hierarchical-term-selector.js +1 -2
  83. package/build-module/components/post-taxonomies/hierarchical-term-selector.js.map +1 -1
  84. package/build-module/components/post-taxonomies/index.js +4 -1
  85. package/build-module/components/post-taxonomies/index.js.map +1 -1
  86. package/build-module/components/post-template/index.js +1 -2
  87. package/build-module/components/post-template/index.js.map +1 -1
  88. package/build-module/components/post-title/index.js +8 -2
  89. package/build-module/components/post-title/index.js.map +1 -1
  90. package/build-module/components/post-url/check.js +44 -0
  91. package/build-module/components/post-url/check.js.map +1 -0
  92. package/build-module/components/post-url/index.js +102 -0
  93. package/build-module/components/post-url/index.js.map +1 -0
  94. package/build-module/components/post-url/label.js +18 -0
  95. package/build-module/components/post-url/label.js.map +1 -0
  96. package/build-module/components/post-visibility/index.js +4 -2
  97. package/build-module/components/post-visibility/index.js.map +1 -1
  98. package/build-module/components/post-visibility/label.js +3 -0
  99. package/build-module/components/post-visibility/label.js.map +1 -1
  100. package/build-module/components/provider/index.native.js +3 -2
  101. package/build-module/components/provider/index.native.js.map +1 -1
  102. package/build-module/components/provider/use-block-editor-settings.js +2 -2
  103. package/build-module/components/provider/use-block-editor-settings.js.map +1 -1
  104. package/build-module/components/table-of-contents/panel.js +4 -1
  105. package/build-module/components/table-of-contents/panel.js.map +1 -1
  106. package/build-module/components/time-to-read/index.js +50 -0
  107. package/build-module/components/time-to-read/index.js.map +1 -0
  108. package/build-module/store/actions.js +5 -1
  109. package/build-module/store/actions.js.map +1 -1
  110. package/build-module/store/reducer.js +2 -2
  111. package/build-module/store/reducer.js.map +1 -1
  112. package/build-module/store/selectors.js +4 -4
  113. package/build-module/store/selectors.js.map +1 -1
  114. package/build-style/style-rtl.css +19 -18
  115. package/build-style/style.css +23 -18
  116. package/package.json +30 -29
  117. package/src/components/README.md +1 -1
  118. package/src/components/document-outline/index.js +9 -8
  119. package/src/components/error-boundary/index.js +3 -0
  120. package/src/components/index.js +13 -4
  121. package/src/components/local-autosave-monitor/index.js +24 -18
  122. package/src/components/page-attributes/order.js +1 -9
  123. package/src/components/page-attributes/parent.js +6 -13
  124. package/src/components/post-format/index.js +13 -19
  125. package/src/components/post-format/style.scss +2 -17
  126. package/src/components/post-locked-modal/index.js +1 -1
  127. package/src/components/post-publish-panel/maybe-category-panel.js +7 -6
  128. package/src/components/post-publish-panel/test/__snapshots__/index.js.snap +1 -1
  129. package/src/components/post-schedule/label.js +111 -17
  130. package/src/components/post-schedule/test/label.js +127 -15
  131. package/src/components/post-slug/index.js +7 -13
  132. package/src/components/post-slug/test/index.js +7 -6
  133. package/src/components/post-taxonomies/flat-term-selector.js +6 -5
  134. package/src/components/post-taxonomies/hierarchical-term-selector.js +1 -1
  135. package/src/components/post-taxonomies/index.js +3 -1
  136. package/src/components/post-template/index.js +1 -1
  137. package/src/components/post-title/index.js +8 -2
  138. package/src/components/post-title/style.scss +1 -1
  139. package/src/components/post-trash/style.scss +3 -0
  140. package/src/components/post-url/check.js +38 -0
  141. package/src/components/post-url/index.js +122 -0
  142. package/src/components/post-url/label.js +22 -0
  143. package/src/components/post-url/style.scss +16 -0
  144. package/src/components/post-visibility/index.js +2 -2
  145. package/src/components/post-visibility/label.js +4 -0
  146. package/src/components/provider/index.native.js +4 -8
  147. package/src/components/provider/use-block-editor-settings.js +4 -1
  148. package/src/components/table-of-contents/panel.js +7 -2
  149. package/src/components/time-to-read/index.js +59 -0
  150. package/src/store/actions.js +3 -1
  151. package/src/store/reducer.js +2 -2
  152. package/src/store/selectors.js +5 -6
  153. package/src/store/test/selectors.js +8 -10
  154. package/src/style.scss +1 -0
@@ -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
  },
@@ -55,11 +55,11 @@ const termNamesToIds = ( names, terms ) => {
55
55
  };
56
56
 
57
57
  // Tries to create a term or fetch it if it already exists.
58
- function findOrCreateTerm( termName, restBase ) {
58
+ function findOrCreateTerm( termName, restBase, namespace ) {
59
59
  const escapedTermName = escapeString( termName );
60
60
 
61
61
  return apiFetch( {
62
- path: `/wp/v2/${ restBase }`,
62
+ path: `/${ namespace }/${ restBase }`,
63
63
  method: 'POST',
64
64
  data: { name: escapedTermName },
65
65
  } )
@@ -68,7 +68,7 @@ function findOrCreateTerm( termName, restBase ) {
68
68
  if ( errorCode === 'term_exists' ) {
69
69
  // If the terms exist, fetch it instead of creating a new one.
70
70
  const addRequest = apiFetch( {
71
- path: addQueryArgs( `/wp/v2/${ restBase }`, {
71
+ path: addQueryArgs( `/${ namespace }/${ restBase }`, {
72
72
  ...DEFAULT_QUERY,
73
73
  search: escapedTermName,
74
74
  } ),
@@ -86,7 +86,7 @@ function findOrCreateTerm( termName, restBase ) {
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 );
@@ -224,9 +224,10 @@ function FlatTermSelector( { slug } ) {
224
224
  return;
225
225
  }
226
226
 
227
+ const namespace = taxonomy?.rest_namespace ?? 'wp/v2';
227
228
  Promise.all(
228
229
  newTermNames.map( ( termName ) =>
229
- findOrCreateTerm( termName, taxonomy.rest_base )
230
+ findOrCreateTerm( termName, taxonomy.rest_base, namespace )
230
231
  )
231
232
  ).then( ( newTerms ) => {
232
233
  const newAvailableTerms = availableTerms.concat( newTerms );
@@ -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 {
@@ -149,9 +149,13 @@ function PostTitle( _, forwardedRef ) {
149
149
  plainText,
150
150
  } );
151
151
 
152
- if ( typeof content !== 'string' && content.length ) {
153
- event.preventDefault();
152
+ event.preventDefault();
153
+
154
+ if ( ! content.length ) {
155
+ return;
156
+ }
154
157
 
158
+ if ( typeof content !== 'string' ) {
155
159
  const [ firstBlock ] = content;
156
160
 
157
161
  if (
@@ -164,6 +168,8 @@ function PostTitle( _, forwardedRef ) {
164
168
  } else {
165
169
  onInsertBlockAfter( content );
166
170
  }
171
+ } else {
172
+ onUpdate( content );
167
173
  }
168
174
  }
169
175
 
@@ -2,7 +2,7 @@
2
2
  position: relative;
3
3
 
4
4
  &.is-focus-mode {
5
- opacity: 0.5;
5
+ opacity: 0.2;
6
6
  transition: opacity 0.1s linear;
7
7
  @include reduce-motion("transition");
8
8
 
@@ -1,3 +1,6 @@
1
1
  .editor-post-trash.components-button {
2
+ display: flex;
3
+ justify-content: center;
2
4
  margin-top: $grid-unit-05;
5
+ width: 100%;
3
6
  }
@@ -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
+ }
@@ -68,7 +68,7 @@ export default function PostVisibility( { onClose } ) {
68
68
  };
69
69
 
70
70
  return (
71
- <>
71
+ <div className="editor-post-visibility">
72
72
  <InspectorPopoverHeader
73
73
  title={ __( 'Visibility' ) }
74
74
  help={ __( 'Control how this post is viewed.' ) }
@@ -128,7 +128,7 @@ export default function PostVisibility( { onClose } ) {
128
128
  >
129
129
  { __( 'Would you like to privately publish this post now?' ) }
130
130
  </ConfirmDialog>
131
- </>
131
+ </div>
132
132
  );
133
133
  }
134
134
 
@@ -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
  );
@@ -91,12 +91,14 @@ class NativeEditorProvider extends Component {
91
91
  }
92
92
 
93
93
  componentDidMount() {
94
- const { capabilities, locale, updateSettings } = this.props;
94
+ const { capabilities, locale, hostAppNamespace, updateSettings } =
95
+ this.props;
95
96
 
96
97
  updateSettings( {
97
98
  ...capabilities,
98
99
  ...this.getThemeColors( this.props ),
99
100
  locale,
101
+ hostAppNamespace,
100
102
  } );
101
103
 
102
104
  this.subscriptionParentGetHtml = subscribeParentGetHtml( () => {
@@ -319,13 +321,7 @@ class NativeEditorProvider extends Component {
319
321
  }
320
322
 
321
323
  render() {
322
- const {
323
- children,
324
- post, // eslint-disable-line no-unused-vars
325
- capabilities,
326
- settings,
327
- ...props
328
- } = this.props;
324
+ const { children, post, capabilities, settings, ...props } = this.props;
329
325
  const editorSettings = this.getEditorSettings( settings, capabilities );
330
326
 
331
327
  return (
@@ -41,7 +41,9 @@ function useBlockEditorSettings( settings, hasTemplate ) {
41
41
  const isWeb = Platform.OS === 'web';
42
42
  const { canUser, getEntityRecord } = select( coreStore );
43
43
 
44
- const siteSettings = getEntityRecord( 'root', 'site' );
44
+ const siteSettings = canUser( 'read', 'settings' )
45
+ ? getEntityRecord( 'root', 'site' )
46
+ : undefined;
45
47
 
46
48
  return {
47
49
  canUseUnfilteredHTML: canUserUseUnfilteredHTML(),
@@ -136,6 +138,7 @@ function useBlockEditorSettings( settings, hasTemplate ) {
136
138
  'generateAnchors',
137
139
  'hasFixedToolbar',
138
140
  'hasReducedUI',
141
+ 'hasInlineToolbar',
139
142
  'imageDefaultSize',
140
143
  'imageDimensions',
141
144
  'imageEditing',
@@ -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
+ }
@@ -250,10 +250,12 @@ export const trashPost =
250
250
  .resolveSelect( coreStore )
251
251
  .getPostType( postTypeSlug );
252
252
  registry.dispatch( noticesStore ).removeNotice( TRASH_POST_NOTICE_ID );
253
+ const { rest_base: restBase, rest_namespace: restNamespace = 'wp/v2' } =
254
+ postType;
253
255
  try {
254
256
  const post = select.getCurrentPost();
255
257
  await apiFetch( {
256
- path: `/wp/v2/${ postType.rest_base }/${ post.id }`,
258
+ path: `/${ restNamespace }/${ restBase }/${ post.id }`,
257
259
  method: 'DELETE',
258
260
  } );
259
261
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { omit, keys, isEqual } from 'lodash';
4
+ import { omit, isEqual } from 'lodash';
5
5
 
6
6
  /**
7
7
  * WordPress dependencies
@@ -39,7 +39,7 @@ export function getPostRawValue( value ) {
39
39
  * @return {boolean} Whether the two objects have the same keys.
40
40
  */
41
41
  export function hasSameKeys( a, b ) {
42
- return isEqual( keys( a ), keys( b ) );
42
+ return isEqual( Object.keys( a ), Object.keys( b ) );
43
43
  }
44
44
 
45
45
  /**
@@ -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
  /**
@@ -534,7 +534,7 @@ export function isEditedPostEmpty( state ) {
534
534
 
535
535
  if ( blocks.length ) {
536
536
  // Pierce the abstraction of the serializer in knowing that blocks are
537
- // joined with with newlines such that even if every individual block
537
+ // joined with newlines such that even if every individual block
538
538
  // produces an empty save result, the serialized content is non-empty.
539
539
  if ( blocks.length > 1 ) {
540
540
  return false;
@@ -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