@wordpress/editor 13.14.0 → 13.16.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 (125) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/build/components/entities-saved-states/entity-record-item.js +3 -37
  3. package/build/components/entities-saved-states/entity-record-item.js.map +1 -1
  4. package/build/components/entities-saved-states/entity-type-list.js +2 -4
  5. package/build/components/entities-saved-states/entity-type-list.js.map +1 -1
  6. package/build/components/entities-saved-states/index.js +0 -1
  7. package/build/components/entities-saved-states/index.js.map +1 -1
  8. package/build/components/global-keyboard-shortcuts/{save-shortcut.js → index.js} +13 -25
  9. package/build/components/global-keyboard-shortcuts/index.js.map +1 -0
  10. package/build/components/index.js +32 -19
  11. package/build/components/index.js.map +1 -1
  12. package/build/components/post-comments/index.js +12 -15
  13. package/build/components/post-comments/index.js.map +1 -1
  14. package/build/components/post-excerpt/index.js +9 -20
  15. package/build/components/post-excerpt/index.js.map +1 -1
  16. package/build/components/post-pingbacks/index.js +12 -15
  17. package/build/components/post-pingbacks/index.js.map +1 -1
  18. package/build/components/post-preview-button/index.js +62 -157
  19. package/build/components/post-preview-button/index.js.map +1 -1
  20. package/build/components/post-publish-button/index.js +4 -9
  21. package/build/components/post-publish-button/index.js.map +1 -1
  22. package/build/components/post-publish-button/label.js +2 -4
  23. package/build/components/post-publish-button/label.js.map +1 -1
  24. package/build/components/post-publish-panel/index.js +1 -3
  25. package/build/components/post-publish-panel/index.js.map +1 -1
  26. package/build/components/post-saved-state/index.js +2 -5
  27. package/build/components/post-saved-state/index.js.map +1 -1
  28. package/build/components/post-schedule/label.js +4 -4
  29. package/build/components/post-schedule/label.js.map +1 -1
  30. package/build/components/post-sync-status/index.js +84 -5
  31. package/build/components/post-sync-status/index.js.map +1 -1
  32. package/build/components/post-text-editor/index.js +51 -58
  33. package/build/components/post-text-editor/index.js.map +1 -1
  34. package/build/components/post-type-support-check/index.js +10 -14
  35. package/build/components/post-type-support-check/index.js.map +1 -1
  36. package/build/hooks/custom-sources-backwards-compatibility.js +1 -24
  37. package/build/hooks/custom-sources-backwards-compatibility.js.map +1 -1
  38. package/build/store/actions.js +37 -3
  39. package/build/store/actions.js.map +1 -1
  40. package/build/store/selectors.js +55 -63
  41. package/build/store/selectors.js.map +1 -1
  42. package/build-module/components/entities-saved-states/entity-record-item.js +6 -40
  43. package/build-module/components/entities-saved-states/entity-record-item.js.map +1 -1
  44. package/build-module/components/entities-saved-states/entity-type-list.js +2 -4
  45. package/build-module/components/entities-saved-states/entity-type-list.js.map +1 -1
  46. package/build-module/components/entities-saved-states/index.js +0 -1
  47. package/build-module/components/entities-saved-states/index.js.map +1 -1
  48. package/build-module/components/global-keyboard-shortcuts/{save-shortcut.js → index.js} +12 -23
  49. package/build-module/components/global-keyboard-shortcuts/index.js.map +1 -0
  50. package/build-module/components/index.js +9 -4
  51. package/build-module/components/index.js.map +1 -1
  52. package/build-module/components/post-comments/index.js +13 -14
  53. package/build-module/components/post-comments/index.js.map +1 -1
  54. package/build-module/components/post-excerpt/index.js +10 -19
  55. package/build-module/components/post-excerpt/index.js.map +1 -1
  56. package/build-module/components/post-pingbacks/index.js +13 -14
  57. package/build-module/components/post-pingbacks/index.js.map +1 -1
  58. package/build-module/components/post-preview-button/index.js +63 -149
  59. package/build-module/components/post-preview-button/index.js.map +1 -1
  60. package/build-module/components/post-publish-button/index.js +4 -9
  61. package/build-module/components/post-publish-button/index.js.map +1 -1
  62. package/build-module/components/post-publish-button/label.js +2 -4
  63. package/build-module/components/post-publish-button/label.js.map +1 -1
  64. package/build-module/components/post-publish-panel/index.js +1 -3
  65. package/build-module/components/post-publish-panel/index.js.map +1 -1
  66. package/build-module/components/post-saved-state/index.js +2 -5
  67. package/build-module/components/post-saved-state/index.js.map +1 -1
  68. package/build-module/components/post-schedule/label.js +4 -4
  69. package/build-module/components/post-schedule/label.js.map +1 -1
  70. package/build-module/components/post-sync-status/index.js +84 -8
  71. package/build-module/components/post-sync-status/index.js.map +1 -1
  72. package/build-module/components/post-text-editor/index.js +48 -56
  73. package/build-module/components/post-text-editor/index.js.map +1 -1
  74. package/build-module/components/post-type-support-check/index.js +11 -14
  75. package/build-module/components/post-type-support-check/index.js.map +1 -1
  76. package/build-module/hooks/custom-sources-backwards-compatibility.js +2 -24
  77. package/build-module/hooks/custom-sources-backwards-compatibility.js.map +1 -1
  78. package/build-module/store/actions.js +31 -1
  79. package/build-module/store/actions.js.map +1 -1
  80. package/build-module/store/selectors.js +48 -57
  81. package/build-module/store/selectors.js.map +1 -1
  82. package/build-style/style-rtl.css +0 -18
  83. package/build-style/style.css +0 -18
  84. package/package.json +30 -30
  85. package/src/components/entities-saved-states/entity-record-item.js +3 -61
  86. package/src/components/entities-saved-states/entity-type-list.js +0 -2
  87. package/src/components/entities-saved-states/index.js +0 -1
  88. package/src/components/entities-saved-states/style.scss +0 -15
  89. package/src/components/global-keyboard-shortcuts/index.js +49 -0
  90. package/src/components/index.js +12 -3
  91. package/src/components/post-author/test/check.js +18 -12
  92. package/src/components/post-comments/index.js +11 -17
  93. package/src/components/post-excerpt/index.js +10 -16
  94. package/src/components/post-pingbacks/index.js +11 -15
  95. package/src/components/post-preview-button/index.js +73 -156
  96. package/src/components/post-preview-button/test/index.js +94 -158
  97. package/src/components/post-publish-button/index.js +2 -7
  98. package/src/components/post-publish-button/label.js +2 -2
  99. package/src/components/post-publish-button/test/index.js +0 -10
  100. package/src/components/post-publish-panel/index.js +1 -3
  101. package/src/components/post-saved-state/index.js +2 -5
  102. package/src/components/post-schedule/label.js +4 -4
  103. package/src/components/post-sync-status/index.js +100 -7
  104. package/src/components/post-text-editor/index.js +34 -57
  105. package/src/components/post-title/style.native.scss +5 -5
  106. package/src/components/post-type-support-check/index.js +8 -10
  107. package/src/components/post-type-support-check/test/index.js +35 -19
  108. package/src/hooks/custom-sources-backwards-compatibility.js +1 -25
  109. package/src/store/actions.js +34 -2
  110. package/src/store/selectors.js +47 -43
  111. package/src/store/test/selectors.js +49 -38
  112. package/build/components/global-keyboard-shortcuts/save-shortcut.js.map +0 -1
  113. package/build/components/global-keyboard-shortcuts/text-editor-shortcuts.js +0 -22
  114. package/build/components/global-keyboard-shortcuts/text-editor-shortcuts.js.map +0 -1
  115. package/build/components/global-keyboard-shortcuts/visual-editor-shortcuts.js +0 -45
  116. package/build/components/global-keyboard-shortcuts/visual-editor-shortcuts.js.map +0 -1
  117. package/build-module/components/global-keyboard-shortcuts/save-shortcut.js.map +0 -1
  118. package/build-module/components/global-keyboard-shortcuts/text-editor-shortcuts.js +0 -12
  119. package/build-module/components/global-keyboard-shortcuts/text-editor-shortcuts.js.map +0 -1
  120. package/build-module/components/global-keyboard-shortcuts/visual-editor-shortcuts.js +0 -32
  121. package/build-module/components/global-keyboard-shortcuts/visual-editor-shortcuts.js.map +0 -1
  122. package/src/components/global-keyboard-shortcuts/save-shortcut.js +0 -55
  123. package/src/components/global-keyboard-shortcuts/text-editor-shortcuts.js +0 -8
  124. package/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js +0 -29
  125. package/src/components/post-text-editor/test/index.js +0 -156
@@ -4,10 +4,39 @@
4
4
  import { render, screen, within } from '@testing-library/react';
5
5
  import userEvent from '@testing-library/user-event';
6
6
 
7
+ /**
8
+ * WordPress dependencies
9
+ */
10
+ import { useSelect, useDispatch } from '@wordpress/data';
11
+
7
12
  /**
8
13
  * Internal dependencies
9
14
  */
10
- import { PostPreviewButton } from '../';
15
+ import PostPreviewButton from '..';
16
+
17
+ jest.useRealTimers();
18
+
19
+ jest.mock( '@wordpress/data/src/components/use-select', () => jest.fn() );
20
+ jest.mock( '@wordpress/data/src/components/use-dispatch/use-dispatch', () =>
21
+ jest.fn()
22
+ );
23
+
24
+ function mockUseSelect( overrides ) {
25
+ useSelect.mockImplementation( ( map ) =>
26
+ map( () => ( {
27
+ getPostType: () => ( { viewable: true } ),
28
+ getCurrentPostId: () => 123,
29
+ getCurrentPostType: () => 'post',
30
+ getCurrentPostAttribute: () => undefined,
31
+ getEditedPostPreviewLink: () => undefined,
32
+ isEditedPostSaveable: () => false,
33
+ ...overrides,
34
+ } ) )
35
+ );
36
+ useDispatch.mockImplementation( () => ( {
37
+ __unstableSaveForPreview: () => Promise.resolve(),
38
+ } ) );
39
+ }
11
40
 
12
41
  describe( 'PostPreviewButton', () => {
13
42
  const documentWrite = jest.fn();
@@ -42,33 +71,39 @@ describe( 'PostPreviewButton', () => {
42
71
  } );
43
72
 
44
73
  it( 'should render with `editor-post-preview` class if no `className` is specified.', () => {
74
+ mockUseSelect();
75
+
45
76
  render( <PostPreviewButton /> );
46
77
 
47
- expect( screen.getByRole( 'button' ) ).toHaveClass(
48
- 'editor-post-preview'
49
- );
78
+ const button = screen.getByRole( 'button' );
79
+ expect( button ).toHaveClass( 'editor-post-preview' );
50
80
  } );
51
81
 
52
82
  it( 'should render with a custom class and not `editor-post-preview` if `className` is specified.', () => {
83
+ mockUseSelect();
84
+
53
85
  render( <PostPreviewButton className="foo-bar" /> );
54
86
 
55
87
  const button = screen.getByRole( 'button' );
56
-
57
88
  expect( button ).toHaveClass( 'foo-bar' );
58
89
  expect( button ).not.toHaveClass( 'editor-post-preview' );
59
90
  } );
60
91
 
61
92
  it( 'should render a tertiary button if no classname is specified.', () => {
93
+ mockUseSelect();
94
+
62
95
  render( <PostPreviewButton /> );
63
96
 
64
- expect( screen.getByRole( 'button' ) ).toHaveClass( 'is-tertiary' );
97
+ const button = screen.getByRole( 'button' );
98
+ expect( button ).toHaveClass( 'is-tertiary' );
65
99
  } );
66
100
 
67
101
  it( 'should render the button in its default variant if a custom classname is specified.', () => {
102
+ mockUseSelect();
103
+
68
104
  render( <PostPreviewButton className="foo-bar" /> );
69
105
 
70
106
  const button = screen.getByRole( 'button' );
71
-
72
107
  expect( button ).not.toHaveClass( 'is-primary' );
73
108
  expect( button ).not.toHaveClass( 'is-secondary' );
74
109
  expect( button ).not.toHaveClass( 'is-tertiary' );
@@ -76,12 +111,13 @@ describe( 'PostPreviewButton', () => {
76
111
  } );
77
112
 
78
113
  it( 'should render `textContent` if specified.', () => {
114
+ mockUseSelect();
115
+
79
116
  const textContent = 'Foo bar';
80
117
 
81
118
  render( <PostPreviewButton textContent={ textContent } /> );
82
119
 
83
120
  const button = screen.getByRole( 'button' );
84
-
85
121
  expect( button ).toHaveTextContent( textContent );
86
122
  expect(
87
123
  within( button ).queryByText( 'Preview' )
@@ -92,213 +128,113 @@ describe( 'PostPreviewButton', () => {
92
128
  } );
93
129
 
94
130
  it( 'should render `Preview` with accessibility text if `textContent` not specified.', () => {
131
+ mockUseSelect();
132
+
95
133
  render( <PostPreviewButton /> );
96
134
 
97
135
  const button = screen.getByRole( 'button' );
98
-
99
136
  expect( within( button ).getByText( 'Preview' ) ).toBeVisible();
100
137
  expect(
101
138
  within( button ).getByText( '(opens in a new tab)' )
102
139
  ).toBeInTheDocument();
103
140
  } );
104
141
 
105
- it( 'should be disabled if post is not saveable.', async () => {
106
- render( <PostPreviewButton isSaveable={ false } postId={ 123 } /> );
142
+ it( 'should be disabled if post is not saveable.', () => {
143
+ mockUseSelect( { isEditedPostSaveable: () => false } );
144
+
145
+ render( <PostPreviewButton /> );
107
146
 
108
147
  expect( screen.getByRole( 'button' ) ).toBeDisabled();
109
148
  } );
110
149
 
111
- it( 'should not be disabled if post is saveable.', async () => {
112
- render( <PostPreviewButton isSaveable postId={ 123 } /> );
150
+ it( 'should not be disabled if post is saveable.', () => {
151
+ mockUseSelect( { isEditedPostSaveable: () => true } );
152
+
153
+ render( <PostPreviewButton /> );
113
154
 
114
155
  expect( screen.getByRole( 'button' ) ).toBeEnabled();
115
156
  } );
116
157
 
117
- it( 'should set `href` to `previewLink` if `previewLink` is specified.', async () => {
158
+ it( 'should set `href` to edited post preview link if specified.', () => {
118
159
  const url = 'https://wordpress.org';
160
+ mockUseSelect( {
161
+ getEditedPostPreviewLink: () => url,
162
+ isEditedPostSaveable: () => true,
163
+ } );
119
164
 
120
- render(
121
- <PostPreviewButton isSaveable postId={ 123 } previewLink={ url } />
122
- );
165
+ render( <PostPreviewButton /> );
123
166
 
124
167
  expect( screen.getByRole( 'link' ) ).toHaveAttribute( 'href', url );
125
168
  } );
126
169
 
127
- it( 'should set `href` to `currentPostLink` if `currentPostLink` is specified.', async () => {
170
+ it( 'should set `href` to current post link if specified.', () => {
128
171
  const url = 'https://wordpress.org';
172
+ mockUseSelect( {
173
+ getCurrentPostAttribute: () => url,
174
+ isEditedPostSaveable: () => true,
175
+ } );
129
176
 
130
- render(
131
- <PostPreviewButton
132
- isSaveable
133
- postId={ 123 }
134
- currentPostLink={ url }
135
- />
136
- );
177
+ render( <PostPreviewButton /> );
137
178
 
138
179
  expect( screen.getByRole( 'link' ) ).toHaveAttribute( 'href', url );
139
180
  } );
140
181
 
141
- it( 'should prioritize `previewLink` if both `previewLink` and `currentPostLink` are specified.', async () => {
182
+ it( 'should prioritize preview link if both preview link and link attribute are specified.', () => {
142
183
  const url1 = 'https://wordpress.org';
143
184
  const url2 = 'https://wordpress.com';
185
+ mockUseSelect( {
186
+ getEditedPostPreviewLink: () => url1,
187
+ getCurrentPostAttribute: () => url2,
188
+ isEditedPostSaveable: () => true,
189
+ } );
144
190
 
145
- render(
146
- <PostPreviewButton
147
- isSaveable
148
- postId={ 123 }
149
- previewLink={ url1 }
150
- currentPostLink={ url2 }
151
- />
152
- );
191
+ render( <PostPreviewButton /> );
153
192
 
154
193
  expect( screen.getByRole( 'link' ) ).toHaveAttribute( 'href', url1 );
155
194
  } );
156
195
 
157
- it( 'should properly set target to `wp-preview-${ postId }`.', async () => {
158
- const postId = 123;
159
- const url = 'https://wordpress.org';
196
+ it( 'should properly set link target', () => {
197
+ mockUseSelect( {
198
+ getEditedPostPreviewLink: () => 'https://wordpress.org',
199
+ isEditedPostSaveable: () => true,
200
+ } );
160
201
 
161
- render(
162
- <PostPreviewButton
163
- isSaveable
164
- postId={ postId }
165
- previewLink={ url }
166
- />
167
- );
202
+ render( <PostPreviewButton /> );
168
203
 
169
204
  expect( screen.getByRole( 'link' ) ).toHaveAttribute(
170
205
  'target',
171
- `wp-preview-${ postId }`
172
- );
173
- } );
174
-
175
- it( 'should save post if `isDraft` is `true`', async () => {
176
- const user = userEvent.setup();
177
- const url = 'https://wordpress.org';
178
- const savePost = jest.fn();
179
- const autosave = jest.fn();
180
-
181
- render(
182
- <PostPreviewButton
183
- isAutosaveable
184
- isSaveable
185
- isDraft
186
- postId={ 123 }
187
- previewLink={ url }
188
- savePost={ savePost }
189
- autosave={ autosave }
190
- />
191
- );
192
-
193
- await user.click( screen.getByRole( 'link' ) );
194
-
195
- expect( savePost ).toHaveBeenCalledWith(
196
- expect.objectContaining( { isPreview: true } )
197
- );
198
- expect( autosave ).not.toHaveBeenCalled();
199
- } );
200
-
201
- it( 'should autosave post if `isDraft` is `false`', async () => {
202
- const user = userEvent.setup();
203
- const url = 'https://wordpress.org';
204
- const savePost = jest.fn();
205
- const autosave = jest.fn();
206
-
207
- render(
208
- <PostPreviewButton
209
- isAutosaveable
210
- isSaveable
211
- isDraft={ false }
212
- postId={ 123 }
213
- previewLink={ url }
214
- savePost={ savePost }
215
- autosave={ autosave }
216
- />
217
- );
218
-
219
- await user.click( screen.getByRole( 'link' ) );
220
-
221
- expect( savePost ).not.toHaveBeenCalled();
222
- expect( autosave ).toHaveBeenCalledWith(
223
- expect.objectContaining( { isPreview: true } )
206
+ 'wp-preview-123'
224
207
  );
225
208
  } );
226
209
 
227
210
  it( 'should open a window with the specified target', async () => {
228
211
  const user = userEvent.setup();
229
- const postId = 123;
230
- const url = 'https://wordpress.org';
231
212
 
232
- render(
233
- <PostPreviewButton
234
- isAutosaveable
235
- isSaveable
236
- postId={ postId }
237
- previewLink={ url }
238
- savePost={ jest.fn() }
239
- autosave={ jest.fn() }
240
- />
241
- );
213
+ mockUseSelect( {
214
+ getEditedPostPreviewLink: () => 'https://wordpress.org',
215
+ isEditedPostSaveable: () => true,
216
+ } );
217
+
218
+ render( <PostPreviewButton /> );
242
219
 
243
220
  await user.click( screen.getByRole( 'link' ) );
244
221
 
245
- expect( global.open ).toHaveBeenCalledWith(
246
- '',
247
- `wp-preview-${ postId }`
248
- );
222
+ expect( global.open ).toHaveBeenCalledWith( '', 'wp-preview-123' );
249
223
  } );
250
224
 
251
- it( 'should set the location in the window properly', async () => {
225
+ it( 'should display a `Generating preview` message while waiting for autosaving', async () => {
252
226
  const user = userEvent.setup();
253
- const postId = 123;
254
- const url = 'https://wordpress.org';
255
-
256
- const { rerender } = render(
257
- <PostPreviewButton
258
- isSaveable
259
- postId={ postId }
260
- savePost={ jest.fn() }
261
- autosave={ jest.fn() }
262
- />
263
- );
264
-
265
- await user.click( screen.getByRole( 'button' ) );
266
227
 
267
- expect( setLocation ).toHaveBeenCalledWith( undefined );
228
+ mockUseSelect( {
229
+ getEditedPostPreviewLink: () => 'https://wordpress.org',
230
+ isEditedPostSaveable: () => true,
231
+ } );
268
232
 
269
- rerender(
270
- <PostPreviewButton
271
- isSaveable
272
- postId={ postId }
273
- previewLink={ url }
274
- savePost={ jest.fn() }
275
- autosave={ jest.fn() }
276
- />
277
- );
233
+ render( <PostPreviewButton /> );
278
234
 
279
- expect( setLocation ).toHaveBeenCalledWith( url );
280
- } );
235
+ await user.click( screen.getByRole( 'link' ) );
281
236
 
282
- it( 'should display a `Generating preview` message while waiting for autosaving', async () => {
283
- const user = userEvent.setup();
284
237
  const previewText = 'Generating preview…';
285
- const url = 'https://wordpress.org';
286
- const savePost = jest.fn();
287
- const autosave = jest.fn();
288
-
289
- render(
290
- <PostPreviewButton
291
- isAutosaveable
292
- isSaveable
293
- isDraft={ false }
294
- postId={ 123 }
295
- previewLink={ url }
296
- savePost={ savePost }
297
- autosave={ autosave }
298
- />
299
- );
300
-
301
- await user.click( screen.getByRole( 'link' ) );
302
238
 
303
239
  expect( documentWrite ).toHaveBeenCalledWith(
304
240
  expect.stringContaining( previewText )
@@ -102,7 +102,6 @@ export class PostPublishButton extends Component {
102
102
  render() {
103
103
  const {
104
104
  forceIsDirty,
105
- forceIsSaving,
106
105
  hasPublishAction,
107
106
  isBeingScheduled,
108
107
  isOpen,
@@ -124,7 +123,6 @@ export class PostPublishButton extends Component {
124
123
 
125
124
  const isButtonDisabled =
126
125
  ( isSaving ||
127
- forceIsSaving ||
128
126
  ! isSaveable ||
129
127
  isPostSavingLocked ||
130
128
  ( ! isPublishable && ! forceIsDirty ) ) &&
@@ -133,7 +131,6 @@ export class PostPublishButton extends Component {
133
131
  const isToggleDisabled =
134
132
  ( isPublished ||
135
133
  isSaving ||
136
- forceIsSaving ||
137
134
  ! isSaveable ||
138
135
  ( ! isPublishable && ! forceIsDirty ) ) &&
139
136
  ( ! hasNonPostEntityChanges || isSavingNonPostEntityChanges );
@@ -187,7 +184,6 @@ export class PostPublishButton extends Component {
187
184
  : __( 'Publish' );
188
185
  const buttonChildren = (
189
186
  <PublishButtonLabel
190
- forceIsSaving={ forceIsSaving }
191
187
  hasNonPostEntityChanges={ hasNonPostEntityChanges }
192
188
  />
193
189
  );
@@ -231,10 +227,9 @@ export default compose( [
231
227
  hasNonPostEntityChanges,
232
228
  isSavingNonPostEntityChanges,
233
229
  } = select( editorStore );
234
- const _isAutoSaving = isAutosavingPost();
235
230
  return {
236
- isSaving: isSavingPost() || _isAutoSaving,
237
- isAutoSaving: _isAutoSaving,
231
+ isSaving: isSavingPost(),
232
+ isAutoSaving: isAutosavingPost(),
238
233
  isBeingScheduled: isEditedPostBeingScheduled(),
239
234
  visibility: getEditedPostVisibility(),
240
235
  isSaveable: isEditedPostSaveable(),
@@ -44,7 +44,7 @@ export function PublishButtonLabel( {
44
44
  }
45
45
 
46
46
  export default compose( [
47
- withSelect( ( select, { forceIsSaving } ) => {
47
+ withSelect( ( select ) => {
48
48
  const {
49
49
  isCurrentPostPublished,
50
50
  isEditedPostBeingScheduled,
@@ -57,7 +57,7 @@ export default compose( [
57
57
  return {
58
58
  isPublished: isCurrentPostPublished(),
59
59
  isBeingScheduled: isEditedPostBeingScheduled(),
60
- isSaving: forceIsSaving || isSavingPost(),
60
+ isSaving: isSavingPost(),
61
61
  isPublishing: isPublishingPost(),
62
62
  hasPublishAction:
63
63
  getCurrentPost()._links?.[ 'wp:action-publish' ] ?? false,
@@ -19,16 +19,6 @@ describe( 'PostPublishButton', () => {
19
19
  ).toHaveAttribute( 'aria-disabled', 'true' );
20
20
  } );
21
21
 
22
- it( 'should be true if forceIsSaving is true', () => {
23
- render(
24
- <PostPublishButton isPublishable isSaveable forceIsSaving />
25
- );
26
-
27
- expect(
28
- screen.getByRole( 'button', { name: 'Submit for Review' } )
29
- ).toHaveAttribute( 'aria-disabled', 'true' );
30
- } );
31
-
32
22
  it( 'should be true if post is not publishable and not forceIsDirty', () => {
33
23
  render(
34
24
  <PostPublishButton
@@ -51,7 +51,6 @@ export class PostPublishPanel extends Component {
51
51
  render() {
52
52
  const {
53
53
  forceIsDirty,
54
- forceIsSaving,
55
54
  isBeingScheduled,
56
55
  isPublished,
57
56
  isPublishSidebarEnabled,
@@ -87,10 +86,9 @@ export class PostPublishPanel extends Component {
87
86
  <>
88
87
  <div className="editor-post-publish-panel__header-publish-button">
89
88
  <PostPublishButton
90
- focusOnMount={ true }
89
+ focusOnMount
91
90
  onSubmit={ this.onSubmit }
92
91
  forceIsDirty={ forceIsDirty }
93
- forceIsSaving={ forceIsSaving }
94
92
  />
95
93
  </div>
96
94
  <div className="editor-post-publish-panel__header-cancel-button">
@@ -29,14 +29,11 @@ import { store as editorStore } from '../../store';
29
29
  * @param {Object} props Component props.
30
30
  * @param {?boolean} props.forceIsDirty Whether to force the post to be marked
31
31
  * as dirty.
32
- * @param {?boolean} props.forceIsSaving Whether to force the post to be marked
33
- * as being saved.
34
32
  * @param {?boolean} props.showIconLabels Whether interface buttons show labels instead of icons
35
33
  * @return {import('@wordpress/element').WPComponent} The component.
36
34
  */
37
35
  export default function PostSavedState( {
38
36
  forceIsDirty,
39
- forceIsSaving,
40
37
  showIconLabels = false,
41
38
  } ) {
42
39
  const [ forceSavedMessage, setForceSavedMessage ] = useState( false );
@@ -72,14 +69,14 @@ export default function PostSavedState( {
72
69
  isNew: isEditedPostNew(),
73
70
  isPending: 'pending' === getEditedPostAttribute( 'status' ),
74
71
  isPublished: isCurrentPostPublished(),
75
- isSaving: forceIsSaving || isSavingPost(),
72
+ isSaving: isSavingPost(),
76
73
  isSaveable: isEditedPostSaveable(),
77
74
  isScheduled: isCurrentPostScheduled(),
78
75
  hasPublishAction:
79
76
  getCurrentPost()?._links?.[ 'wp:action-publish' ] ?? false,
80
77
  };
81
78
  },
82
- [ forceIsDirty, forceIsSaving ]
79
+ [ forceIsDirty ]
83
80
  );
84
81
 
85
82
  const { savePost } = useDispatch( editorStore );
@@ -33,7 +33,7 @@ export function getFullPostScheduleLabel( dateAttribute ) {
33
33
 
34
34
  const timezoneAbbreviation = getTimezoneAbbreviation();
35
35
  const formattedDate = dateI18n(
36
- // translators: If using a space between 'g:i' and 'a', use a non-breaking sapce.
36
+ // translators: If using a space between 'g:i' and 'a', use a non-breaking space.
37
37
  _x( 'F j, Y g:i\xa0a', 'post schedule full date format' ),
38
38
  date
39
39
  );
@@ -62,7 +62,7 @@ export function getPostScheduleLabel(
62
62
  return sprintf(
63
63
  // translators: %s: Time of day the post is scheduled for.
64
64
  __( 'Today at %s' ),
65
- // translators: If using a space between 'g:i' and 'a', use a non-breaking sapce.
65
+ // translators: If using a space between 'g:i' and 'a', use a non-breaking space.
66
66
  dateI18n( _x( 'g:i\xa0a', 'post schedule time format' ), date )
67
67
  );
68
68
  }
@@ -74,14 +74,14 @@ export function getPostScheduleLabel(
74
74
  return sprintf(
75
75
  // translators: %s: Time of day the post is scheduled for.
76
76
  __( 'Tomorrow at %s' ),
77
- // translators: If using a space between 'g:i' and 'a', use a non-breaking sapce.
77
+ // translators: If using a space between 'g:i' and 'a', use a non-breaking space.
78
78
  dateI18n( _x( 'g:i\xa0a', 'post schedule time format' ), date )
79
79
  );
80
80
  }
81
81
 
82
82
  if ( date.getFullYear() === now.getFullYear() ) {
83
83
  return dateI18n(
84
- // translators: If using a space between 'g:i' and 'a', use a non-breaking sapce.
84
+ // translators: If using a space between 'g:i' and 'a', use a non-breaking space.
85
85
  _x( 'F j g:i\xa0a', 'post schedule date format without year' ),
86
86
  date
87
87
  );
@@ -1,35 +1,128 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
- import { useSelect } from '@wordpress/data';
4
+ import { useSelect, useDispatch } from '@wordpress/data';
5
5
  import { __ } from '@wordpress/i18n';
6
- import { PanelRow } from '@wordpress/components';
6
+ import {
7
+ PanelRow,
8
+ Modal,
9
+ Button,
10
+ __experimentalHStack as HStack,
11
+ __experimentalVStack as VStack,
12
+ ToggleControl,
13
+ } from '@wordpress/components';
14
+ import { useEffect, useState } from '@wordpress/element';
15
+ import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';
7
16
 
8
17
  /**
9
18
  * Internal dependencies
10
19
  */
11
20
  import { store as editorStore } from '../../store';
21
+ import { unlock } from '../../lock-unlock';
12
22
 
13
23
  export default function PostSyncStatus() {
14
- const { syncStatus, postType } = useSelect( ( select ) => {
24
+ const { syncStatus, postType, meta } = useSelect( ( select ) => {
15
25
  const { getEditedPostAttribute } = select( editorStore );
16
26
  return {
17
27
  syncStatus: getEditedPostAttribute( 'wp_pattern_sync_status' ),
28
+ meta: getEditedPostAttribute( 'meta' ),
18
29
  postType: getEditedPostAttribute( 'type' ),
19
30
  };
20
- }, [] );
31
+ } );
32
+
21
33
  if ( postType !== 'wp_block' ) {
22
34
  return null;
23
35
  }
24
-
25
- const isFullySynced = ! syncStatus;
36
+ // When the post is first created, the top level wp_pattern_sync_status is not set so get meta value instead.
37
+ const currentSyncStatus =
38
+ meta?.wp_pattern_sync_status === 'unsynced' ? 'unsynced' : syncStatus;
26
39
 
27
40
  return (
28
41
  <PanelRow className="edit-post-sync-status">
29
42
  <span>{ __( 'Sync status' ) }</span>
30
43
  <div>
31
- { isFullySynced ? __( 'Fully synced' ) : __( 'Not synced' ) }
44
+ { currentSyncStatus === 'unsynced'
45
+ ? __( 'Not synced' )
46
+ : __( 'Fully synced' ) }
32
47
  </div>
33
48
  </PanelRow>
34
49
  );
35
50
  }
51
+
52
+ export function PostSyncStatusModal() {
53
+ const { editPost } = useDispatch( editorStore );
54
+ const [ isModalOpen, setIsModalOpen ] = useState( false );
55
+ const [ syncType, setSyncType ] = useState( undefined );
56
+
57
+ const { postType, isNewPost } = useSelect( ( select ) => {
58
+ const { getEditedPostAttribute, isCleanNewPost } =
59
+ select( editorStore );
60
+ return {
61
+ postType: getEditedPostAttribute( 'type' ),
62
+ isNewPost: isCleanNewPost(),
63
+ };
64
+ }, [] );
65
+
66
+ useEffect( () => {
67
+ if ( isNewPost && postType === 'wp_block' ) {
68
+ setIsModalOpen( true );
69
+ }
70
+ // We only want the modal to open when the page is first loaded.
71
+ // eslint-disable-next-line react-hooks/exhaustive-deps
72
+ }, [] );
73
+
74
+ const setSyncStatus = () => {
75
+ editPost( {
76
+ meta: {
77
+ wp_pattern_sync_status: syncType,
78
+ },
79
+ } );
80
+ };
81
+
82
+ if ( postType !== 'wp_block' || ! isNewPost ) {
83
+ return null;
84
+ }
85
+ const { ReusableBlocksRenameHint } = unlock( blockEditorPrivateApis );
86
+ return (
87
+ <>
88
+ { isModalOpen && (
89
+ <Modal
90
+ title={ __( 'Set pattern sync status' ) }
91
+ onRequestClose={ () => {
92
+ setIsModalOpen( false );
93
+ } }
94
+ overlayClassName="reusable-blocks-menu-items__convert-modal"
95
+ >
96
+ <form
97
+ onSubmit={ ( event ) => {
98
+ event.preventDefault();
99
+ setIsModalOpen( false );
100
+ setSyncStatus();
101
+ } }
102
+ >
103
+ <VStack spacing="5">
104
+ <ReusableBlocksRenameHint />
105
+ <ToggleControl
106
+ label={ __( 'Synced' ) }
107
+ help={ __(
108
+ 'Editing the pattern will update it anywhere it is used.'
109
+ ) }
110
+ checked={ ! syncType }
111
+ onChange={ () => {
112
+ setSyncType(
113
+ ! syncType ? 'unsynced' : undefined
114
+ );
115
+ } }
116
+ />
117
+ <HStack justify="right">
118
+ <Button variant="primary" type="submit">
119
+ { __( 'Create' ) }
120
+ </Button>
121
+ </HStack>
122
+ </VStack>
123
+ </form>
124
+ </Modal>
125
+ ) }
126
+ </>
127
+ );
128
+ }