@wordpress/editor 14.48.0 → 14.48.1

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 (205) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/build/components/block-removal-warnings/index.cjs +0 -3
  3. package/build/components/block-removal-warnings/index.cjs.map +2 -2
  4. package/build/components/collab-sidebar/note-indicator-toolbar.cjs +49 -43
  5. package/build/components/collab-sidebar/note-indicator-toolbar.cjs.map +3 -3
  6. package/build/components/collaborators-overlay/use-block-highlighting.cjs +1 -8
  7. package/build/components/collaborators-overlay/use-block-highlighting.cjs.map +3 -3
  8. package/build/components/collaborators-overlay/use-render-cursors.cjs +1 -7
  9. package/build/components/collaborators-overlay/use-render-cursors.cjs.map +3 -3
  10. package/build/components/more-menu/view-more-menu-group.cjs +1 -2
  11. package/build/components/more-menu/view-more-menu-group.cjs.map +2 -2
  12. package/build/components/page-attributes/parent.cjs +1 -0
  13. package/build/components/page-attributes/parent.cjs.map +2 -2
  14. package/build/components/post-publish-button/index.cjs +114 -157
  15. package/build/components/post-publish-button/index.cjs.map +3 -3
  16. package/build/components/post-revisions-preview/block-diff.cjs +21 -9
  17. package/build/components/post-revisions-preview/block-diff.cjs.map +2 -2
  18. package/build/components/post-revisions-preview/preserve-client-ids.cjs +2 -2
  19. package/build/components/post-revisions-preview/preserve-client-ids.cjs.map +2 -2
  20. package/build/components/provider/index.cjs +2 -0
  21. package/build/components/provider/index.cjs.map +3 -3
  22. package/build/components/provider/use-network-reconnect.cjs +51 -0
  23. package/build/components/provider/use-network-reconnect.cjs.map +7 -0
  24. package/build/components/revision-fields-diff/index.cjs +2 -2
  25. package/build/components/revision-fields-diff/index.cjs.map +2 -2
  26. package/build/components/sidebar/index.cjs +1 -4
  27. package/build/components/sidebar/index.cjs.map +2 -2
  28. package/build/components/template-actions-panel/block-theme-content.cjs +7 -1
  29. package/build/components/template-actions-panel/block-theme-content.cjs.map +2 -2
  30. package/build/components/upload-progress-snackbar/index.cjs +161 -0
  31. package/build/components/upload-progress-snackbar/index.cjs.map +7 -0
  32. package/build/components/upload-progress-snackbar/tracker.cjs +90 -0
  33. package/build/components/upload-progress-snackbar/tracker.cjs.map +7 -0
  34. package/build/private-apis.cjs +2 -0
  35. package/build/private-apis.cjs.map +3 -3
  36. package/build/store/selectors.cjs +1 -2
  37. package/build/store/selectors.cjs.map +2 -2
  38. package/build/utils/media-upload/index.cjs +16 -0
  39. package/build/utils/media-upload/index.cjs.map +3 -3
  40. package/build-module/components/block-removal-warnings/index.mjs +0 -3
  41. package/build-module/components/block-removal-warnings/index.mjs.map +2 -2
  42. package/build-module/components/collab-sidebar/note-indicator-toolbar.mjs +53 -44
  43. package/build-module/components/collab-sidebar/note-indicator-toolbar.mjs.map +2 -2
  44. package/build-module/components/collaborators-overlay/use-block-highlighting.mjs +1 -8
  45. package/build-module/components/collaborators-overlay/use-block-highlighting.mjs.map +2 -2
  46. package/build-module/components/collaborators-overlay/use-render-cursors.mjs +1 -7
  47. package/build-module/components/collaborators-overlay/use-render-cursors.mjs.map +2 -2
  48. package/build-module/components/more-menu/view-more-menu-group.mjs +1 -2
  49. package/build-module/components/more-menu/view-more-menu-group.mjs.map +2 -2
  50. package/build-module/components/page-attributes/parent.mjs +1 -0
  51. package/build-module/components/page-attributes/parent.mjs.map +2 -2
  52. package/build-module/components/post-publish-button/index.mjs +116 -159
  53. package/build-module/components/post-publish-button/index.mjs.map +2 -2
  54. package/build-module/components/post-revisions-preview/block-diff.mjs +20 -8
  55. package/build-module/components/post-revisions-preview/block-diff.mjs.map +2 -2
  56. package/build-module/components/post-revisions-preview/preserve-client-ids.mjs +1 -1
  57. package/build-module/components/post-revisions-preview/preserve-client-ids.mjs.map +1 -1
  58. package/build-module/components/provider/index.mjs +2 -0
  59. package/build-module/components/provider/index.mjs.map +2 -2
  60. package/build-module/components/provider/use-network-reconnect.mjs +30 -0
  61. package/build-module/components/provider/use-network-reconnect.mjs.map +7 -0
  62. package/build-module/components/revision-fields-diff/index.mjs +2 -2
  63. package/build-module/components/revision-fields-diff/index.mjs.map +2 -2
  64. package/build-module/components/sidebar/index.mjs +2 -11
  65. package/build-module/components/sidebar/index.mjs.map +2 -2
  66. package/build-module/components/template-actions-panel/block-theme-content.mjs +7 -1
  67. package/build-module/components/template-actions-panel/block-theme-content.mjs.map +2 -2
  68. package/build-module/components/upload-progress-snackbar/index.mjs +135 -0
  69. package/build-module/components/upload-progress-snackbar/index.mjs.map +7 -0
  70. package/build-module/components/upload-progress-snackbar/tracker.mjs +61 -0
  71. package/build-module/components/upload-progress-snackbar/tracker.mjs.map +7 -0
  72. package/build-module/private-apis.mjs +2 -0
  73. package/build-module/private-apis.mjs.map +2 -2
  74. package/build-module/store/selectors.mjs +1 -2
  75. package/build-module/store/selectors.mjs.map +2 -2
  76. package/build-module/utils/media-upload/index.mjs +19 -0
  77. package/build-module/utils/media-upload/index.mjs.map +2 -2
  78. package/build-style/style-rtl.css +454 -81
  79. package/build-style/style.css +454 -81
  80. package/build-types/components/block-removal-warnings/index.d.ts.map +1 -1
  81. package/build-types/components/collab-sidebar/add-comment.d.ts +6 -0
  82. package/build-types/components/collab-sidebar/add-comment.d.ts.map +1 -0
  83. package/build-types/components/collab-sidebar/comment-author-info.d.ts +8 -0
  84. package/build-types/components/collab-sidebar/comment-author-info.d.ts.map +1 -0
  85. package/build-types/components/collab-sidebar/comment-form.d.ts +9 -0
  86. package/build-types/components/collab-sidebar/comment-form.d.ts.map +1 -0
  87. package/build-types/components/collab-sidebar/comment-indicator-toolbar.d.ts +6 -0
  88. package/build-types/components/collab-sidebar/comment-indicator-toolbar.d.ts.map +1 -0
  89. package/build-types/components/collab-sidebar/comment-menu-item.d.ts +6 -0
  90. package/build-types/components/collab-sidebar/comment-menu-item.d.ts.map +1 -0
  91. package/build-types/components/collab-sidebar/comments.d.ts +10 -0
  92. package/build-types/components/collab-sidebar/comments.d.ts.map +1 -0
  93. package/build-types/components/collab-sidebar/note-indicator-toolbar.d.ts.map +1 -1
  94. package/build-types/components/collaborators-overlay/use-block-highlighting.d.ts +0 -3
  95. package/build-types/components/collaborators-overlay/use-block-highlighting.d.ts.map +1 -1
  96. package/build-types/components/collaborators-overlay/use-render-cursors.d.ts.map +1 -1
  97. package/build-types/components/document-bar/index.d.ts +2 -2
  98. package/build-types/components/document-bar/index.d.ts.map +1 -1
  99. package/build-types/components/global-styles-provider/index.d.ts +16 -0
  100. package/build-types/components/global-styles-provider/index.d.ts.map +1 -0
  101. package/build-types/components/media/index.d.ts +3 -0
  102. package/build-types/components/media/index.d.ts.map +1 -0
  103. package/build-types/components/media/metadata-panel.d.ts +12 -0
  104. package/build-types/components/media/metadata-panel.d.ts.map +1 -0
  105. package/build-types/components/media/preview.d.ts +9 -0
  106. package/build-types/components/media/preview.d.ts.map +1 -0
  107. package/build-types/components/more-menu/view-more-menu-group.d.ts.map +1 -1
  108. package/build-types/components/page-attributes/parent.d.ts.map +1 -1
  109. package/build-types/components/post-publish-button/index.d.ts +9 -9
  110. package/build-types/components/post-publish-button/index.d.ts.map +1 -1
  111. package/build-types/components/post-revisions-preview/block-diff.d.ts +3 -0
  112. package/build-types/components/post-revisions-preview/block-diff.d.ts.map +1 -1
  113. package/build-types/components/post-text-editor/index.d.ts +1 -1
  114. package/build-types/components/post-text-editor/index.d.ts.map +1 -1
  115. package/build-types/components/post-text-editor/utils.d.ts +29 -0
  116. package/build-types/components/post-text-editor/utils.d.ts.map +1 -0
  117. package/build-types/components/provider/index.d.ts.map +1 -1
  118. package/build-types/components/provider/use-network-reconnect.d.ts +8 -0
  119. package/build-types/components/provider/use-network-reconnect.d.ts.map +1 -0
  120. package/build-types/components/revision-fields-diff/index.d.ts +3 -0
  121. package/build-types/components/revision-fields-diff/index.d.ts.map +1 -1
  122. package/build-types/components/sidebar/index.d.ts.map +1 -1
  123. package/build-types/components/template-actions-panel/block-theme-content.d.ts.map +1 -1
  124. package/build-types/components/upload-progress-snackbar/index.d.ts +19 -0
  125. package/build-types/components/upload-progress-snackbar/index.d.ts.map +1 -0
  126. package/build-types/components/upload-progress-snackbar/stories/index.story.d.ts +28 -0
  127. package/build-types/components/upload-progress-snackbar/stories/index.story.d.ts.map +1 -0
  128. package/build-types/components/upload-progress-snackbar/tracker.d.ts +41 -0
  129. package/build-types/components/upload-progress-snackbar/tracker.d.ts.map +1 -0
  130. package/build-types/private-apis.d.ts.map +1 -1
  131. package/build-types/store/selectors.d.ts.map +1 -1
  132. package/build-types/utils/get-template-part-icon.d.ts.map +1 -1
  133. package/build-types/utils/media-upload/index.d.ts.map +1 -1
  134. package/package.json +53 -50
  135. package/src/components/README.md +1 -1
  136. package/src/components/block-removal-warnings/index.js +0 -7
  137. package/src/components/collab-sidebar/note-indicator-toolbar.js +73 -60
  138. package/src/components/collaborators-overlay/use-block-highlighting.ts +0 -9
  139. package/src/components/collaborators-overlay/use-render-cursors.ts +0 -8
  140. package/src/components/collaborators-presence/avatar/test/index.tsx +8 -3
  141. package/src/components/more-menu/view-more-menu-group.js +1 -2
  142. package/src/components/page-attributes/parent.js +1 -0
  143. package/src/components/post-publish-button/index.js +143 -192
  144. package/src/components/post-publish-button/test/index.js +137 -114
  145. package/src/components/post-revisions-preview/block-diff.js +63 -19
  146. package/src/components/post-revisions-preview/preserve-client-ids.js +1 -1
  147. package/src/components/post-revisions-preview/test/block-diff.js +109 -6
  148. package/src/components/provider/index.js +4 -0
  149. package/src/components/provider/test/use-network-reconnect.js +137 -0
  150. package/src/components/provider/use-network-reconnect.js +44 -0
  151. package/src/components/revision-fields-diff/index.js +7 -2
  152. package/src/components/sidebar/index.js +2 -11
  153. package/src/components/template-actions-panel/block-theme-content.js +10 -1
  154. package/src/components/upload-progress-snackbar/README.md +26 -0
  155. package/src/components/upload-progress-snackbar/index.js +216 -0
  156. package/src/components/upload-progress-snackbar/stories/index.story.tsx +85 -0
  157. package/src/components/upload-progress-snackbar/style.scss +30 -0
  158. package/src/components/upload-progress-snackbar/test/index.js +199 -0
  159. package/src/components/upload-progress-snackbar/tracker.js +105 -0
  160. package/src/private-apis.js +2 -0
  161. package/src/store/selectors.js +1 -3
  162. package/src/style.scss +1 -0
  163. package/src/utils/media-upload/index.js +27 -0
  164. package/src/components/commands/index.native.js +0 -2
  165. package/src/components/deprecated.native.js +0 -47
  166. package/src/components/editor-help/add-blocks.native.js +0 -40
  167. package/src/components/editor-help/customize-blocks.native.js +0 -40
  168. package/src/components/editor-help/help-detail-navigation-screen.native.js +0 -67
  169. package/src/components/editor-help/help-get-support-button.native.js +0 -38
  170. package/src/components/editor-help/help-section-title.native.js +0 -29
  171. package/src/components/editor-help/help-topic-row.native.js +0 -33
  172. package/src/components/editor-help/icon-move-blocks.native.js +0 -10
  173. package/src/components/editor-help/index.native.js +0 -208
  174. package/src/components/editor-help/intro-to-blocks.native.js +0 -91
  175. package/src/components/editor-help/move-blocks.native.js +0 -55
  176. package/src/components/editor-help/remove-blocks.native.js +0 -35
  177. package/src/components/editor-help/style.android.scss +0 -6
  178. package/src/components/editor-help/style.ios.scss +0 -6
  179. package/src/components/editor-help/test/index.native.js +0 -81
  180. package/src/components/editor-help/view-sections.native.js +0 -79
  181. package/src/components/error-boundary/index.native.js +0 -192
  182. package/src/components/error-boundary/style.native.scss +0 -116
  183. package/src/components/index.native.js +0 -15
  184. package/src/components/offline-status/index.native.js +0 -99
  185. package/src/components/offline-status/style.native.scss +0 -28
  186. package/src/components/offline-status/test/index.native.js +0 -108
  187. package/src/components/post-title/index.native.js +0 -282
  188. package/src/components/post-title/style.native.scss +0 -13
  189. package/src/components/post-title/test/__snapshots__/index.native.js.snap +0 -25
  190. package/src/components/post-title/test/index.native.js +0 -78
  191. package/src/components/provider/index.native.js +0 -497
  192. package/src/components/provider/use-block-editor-settings.native.js +0 -48
  193. package/src/components/template-part-menu-items/index.native.js +0 -3
  194. package/src/hooks/index.native.js +0 -0
  195. package/src/index.native.js +0 -16
  196. package/src/private-apis.native.js +0 -33
  197. package/src/store/actions.native.js +0 -27
  198. package/src/store/reducer.native.js +0 -94
  199. package/src/store/selectors.native.js +0 -57
  200. package/src/store/test/actions.native.js +0 -16
  201. package/src/store/test/reducer.native.js +0 -36
  202. package/src/store/test/selectors.native.js +0 -28
  203. package/src/utils/index.native.js +0 -6
  204. package/src/utils/media-sideload/index.native.js +0 -1
  205. package/src/utils/media-upload/index.native.js +0 -1
@@ -0,0 +1,199 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { render, act } from '@testing-library/react';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { useSelect } from '@wordpress/data';
10
+
11
+ /**
12
+ * Internal dependencies
13
+ */
14
+ import UploadProgressSnackbar from '../';
15
+ import { addFiles, advance, reset } from '../tracker';
16
+
17
+ jest.mock( '@wordpress/data/src/components/use-select', () => {
18
+ const mock = jest.fn();
19
+ return mock;
20
+ } );
21
+
22
+ const mockCreateNotice = jest.fn();
23
+ const mockRemoveNotice = jest.fn();
24
+
25
+ jest.mock( '@wordpress/data/src/components/use-dispatch', () => {
26
+ return {
27
+ useDispatch: jest.fn( () => ( {
28
+ createNotice: mockCreateNotice,
29
+ removeNotice: mockRemoveNotice,
30
+ } ) ),
31
+ useDispatchWithMap: jest.fn(),
32
+ };
33
+ } );
34
+
35
+ jest.mock( '@wordpress/a11y', () => ( {
36
+ speak: jest.fn(),
37
+ } ) );
38
+
39
+ function mockQueue( items ) {
40
+ useSelect.mockImplementation( ( mapSelect ) =>
41
+ mapSelect( () => ( {
42
+ getItems: () => items,
43
+ isUploading: () => items.length > 0,
44
+ } ) )
45
+ );
46
+ }
47
+
48
+ function makeItem( id, name, { parentId } = {} ) {
49
+ return {
50
+ id,
51
+ sourceFile: { name },
52
+ status: 'PROCESSING',
53
+ parentId,
54
+ };
55
+ }
56
+
57
+ describe( 'UploadProgressSnackbar', () => {
58
+ beforeEach( () => {
59
+ jest.clearAllMocks();
60
+ reset();
61
+ } );
62
+
63
+ it( 'does not create a notice when both sources are empty', () => {
64
+ mockQueue( [] );
65
+ render( <UploadProgressSnackbar /> );
66
+ expect( mockCreateNotice ).not.toHaveBeenCalled();
67
+ } );
68
+
69
+ it( 'creates a notice with the filename when a single CSM upload is in progress', () => {
70
+ mockQueue( [ makeItem( '1', 'photo.jpg' ) ] );
71
+ render( <UploadProgressSnackbar /> );
72
+ expect( mockCreateNotice ).toHaveBeenCalledWith(
73
+ 'info',
74
+ expect.stringContaining( 'photo.jpg' ),
75
+ expect.objectContaining( {
76
+ id: 'upload-progress',
77
+ type: 'snackbar',
78
+ } )
79
+ );
80
+ expect( mockCreateNotice.mock.calls[ 0 ][ 1 ] ).toBe(
81
+ 'Uploading — photo.jpg'
82
+ );
83
+ } );
84
+
85
+ it( 'shows batch count for CSM uploads', () => {
86
+ mockQueue( [
87
+ makeItem( '1', 'a.jpg' ),
88
+ makeItem( '2', 'b.jpg' ),
89
+ makeItem( '3', 'c.jpg' ),
90
+ ] );
91
+ render( <UploadProgressSnackbar /> );
92
+ expect( mockCreateNotice.mock.calls[ 0 ][ 1 ] ).toMatch( /1 of 3/ );
93
+ } );
94
+
95
+ it( 'excludes subsizes from the CSM count', () => {
96
+ mockQueue( [
97
+ makeItem( '1', 'photo.jpg' ),
98
+ makeItem( '1-thumb', 'photo-150x150.jpg', { parentId: '1' } ),
99
+ makeItem( '1-medium', 'photo-300x300.jpg', { parentId: '1' } ),
100
+ ] );
101
+ render( <UploadProgressSnackbar /> );
102
+ expect( mockCreateNotice.mock.calls[ 0 ][ 1 ] ).toBe(
103
+ 'Uploading — photo.jpg'
104
+ );
105
+ } );
106
+
107
+ it( 'shows non-CSM uploads tracked by the editor mediaUpload wrapper', () => {
108
+ mockQueue( [] );
109
+ act( () => {
110
+ addFiles( [ 'traditional.jpg' ] );
111
+ } );
112
+ render( <UploadProgressSnackbar /> );
113
+ expect( mockCreateNotice.mock.calls[ 0 ][ 1 ] ).toBe(
114
+ 'Uploading — traditional.jpg'
115
+ );
116
+ } );
117
+
118
+ it( 'shows batch count for non-CSM uploads', () => {
119
+ mockQueue( [] );
120
+ act( () => {
121
+ addFiles( [ 'a.jpg', 'b.jpg', 'c.jpg' ] );
122
+ } );
123
+ render( <UploadProgressSnackbar /> );
124
+ expect( mockCreateNotice.mock.calls[ 0 ][ 1 ] ).toMatch( /1 of 3/ );
125
+ expect( mockCreateNotice.mock.calls[ 0 ][ 1 ] ).toMatch( /a\.jpg/ );
126
+ } );
127
+
128
+ it( 'shows a completion notice and then removes it when uploads finish', () => {
129
+ jest.useFakeTimers();
130
+ try {
131
+ mockQueue( [] );
132
+ act( () => {
133
+ addFiles( [ 'a.jpg' ] );
134
+ } );
135
+ render( <UploadProgressSnackbar /> );
136
+ expect( mockCreateNotice ).toHaveBeenCalled();
137
+ mockCreateNotice.mockClear();
138
+
139
+ act( () => {
140
+ advance( 1 );
141
+ } );
142
+
143
+ // Completion notice replaces the progress notice (same ID).
144
+ expect( mockCreateNotice ).toHaveBeenCalledWith(
145
+ 'info',
146
+ 'Upload complete',
147
+ expect.objectContaining( {
148
+ id: 'upload-progress',
149
+ type: 'snackbar',
150
+ icon: expect.anything(),
151
+ } )
152
+ );
153
+ expect( mockRemoveNotice ).not.toHaveBeenCalled();
154
+
155
+ act( () => {
156
+ jest.runAllTimers();
157
+ } );
158
+
159
+ expect( mockRemoveNotice ).toHaveBeenCalledWith(
160
+ 'upload-progress'
161
+ );
162
+ } finally {
163
+ jest.useRealTimers();
164
+ }
165
+ } );
166
+
167
+ it( 'middle-truncates a long filename while keeping the extension', () => {
168
+ const longName =
169
+ 'a-really-long-uuid-like-filename-1234567890-abcdefghij.jpg';
170
+ mockQueue( [ makeItem( '1', longName ) ] );
171
+ render( <UploadProgressSnackbar /> );
172
+ const content = mockCreateNotice.mock.calls[ 0 ][ 1 ];
173
+ // Truncated: shorter than the original, marked with an ellipsis, and the
174
+ // extension is still visible.
175
+ expect( content ).toContain( '…' );
176
+ expect( content ).not.toContain( longName );
177
+ expect( content ).toMatch( /\.jpg$/ );
178
+ } );
179
+
180
+ it( 'does not truncate a short filename', () => {
181
+ mockQueue( [ makeItem( '1', 'photo.jpg' ) ] );
182
+ render( <UploadProgressSnackbar /> );
183
+ expect( mockCreateNotice.mock.calls[ 0 ][ 1 ] ).toBe(
184
+ 'Uploading — photo.jpg'
185
+ );
186
+ } );
187
+
188
+ it( 'includes a spinner icon on the in-progress notice', () => {
189
+ mockQueue( [ makeItem( '1', 'photo.jpg' ) ] );
190
+ render( <UploadProgressSnackbar /> );
191
+ expect( mockCreateNotice ).toHaveBeenCalledWith(
192
+ 'info',
193
+ expect.any( String ),
194
+ expect.objectContaining( {
195
+ icon: expect.anything(),
196
+ } )
197
+ );
198
+ } );
199
+ } );
@@ -0,0 +1,105 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useSyncExternalStore } from '@wordpress/element';
5
+
6
+ /**
7
+ * A minimal in-memory tracker for in-flight media uploads that happen outside
8
+ * the `@wordpress/upload-media` store (i.e. the traditional / non-CSM upload
9
+ * path). The editor's `mediaUpload` wrapper writes to it; the
10
+ * `UploadProgressSnackbar` reads from it.
11
+ *
12
+ * State shape: { total, completed, pending: string[] }
13
+ * - `total`: total files registered in the current session.
14
+ * - `completed`: files that have finished (succeeded OR errored).
15
+ * - `pending`: remaining filenames in submission order.
16
+ *
17
+ * The tracker holds at most one "session" at a time — if a new batch starts
18
+ * while one is in progress, its files are appended to the existing session.
19
+ */
20
+
21
+ let state = null;
22
+ const listeners = new Set();
23
+
24
+ function notify() {
25
+ listeners.forEach( ( listener ) => listener() );
26
+ }
27
+
28
+ /**
29
+ * Registers a new batch of files that have started uploading.
30
+ *
31
+ * @param {string[]} filenames Filenames in submission order.
32
+ */
33
+ export function addFiles( filenames ) {
34
+ if ( ! filenames.length ) {
35
+ return;
36
+ }
37
+ if ( ! state ) {
38
+ state = { total: 0, completed: 0, pending: [] };
39
+ }
40
+ state = {
41
+ total: state.total + filenames.length,
42
+ completed: state.completed,
43
+ pending: [ ...state.pending, ...filenames ],
44
+ };
45
+ notify();
46
+ }
47
+
48
+ /**
49
+ * Advances the tracker by a number of finished files (success or error).
50
+ *
51
+ * @param {number} count Number of files that finished since the last call.
52
+ */
53
+ export function advance( count ) {
54
+ if ( ! state || count <= 0 ) {
55
+ return;
56
+ }
57
+ const completed = Math.min( state.total, state.completed + count );
58
+ const pending = state.pending.slice( count );
59
+ if ( completed >= state.total ) {
60
+ state = null;
61
+ } else {
62
+ state = { total: state.total, completed, pending };
63
+ }
64
+ notify();
65
+ }
66
+
67
+ /**
68
+ * Resets the tracker to its empty state.
69
+ *
70
+ * Test-only helper: `state` is a module-level singleton, so tests call this in
71
+ * `beforeEach` to isolate cases from one another. Not used in production -
72
+ * `advance` clears the state on its own once every file in a batch finishes.
73
+ */
74
+ export function reset() {
75
+ if ( state === null ) {
76
+ return;
77
+ }
78
+ state = null;
79
+ notify();
80
+ }
81
+
82
+ /**
83
+ * Returns the current tracker state, or `null` when idle.
84
+ *
85
+ * @return {?{total: number, completed: number, pending: string[]}} Tracker state.
86
+ */
87
+ export function getState() {
88
+ return state;
89
+ }
90
+
91
+ function subscribe( listener ) {
92
+ listeners.add( listener );
93
+ return () => {
94
+ listeners.delete( listener );
95
+ };
96
+ }
97
+
98
+ /**
99
+ * React hook that subscribes to the tracker.
100
+ *
101
+ * @return {?{total: number, completed: number, pending: string[]}} Tracker state.
102
+ */
103
+ export function useTracker() {
104
+ return useSyncExternalStore( subscribe, getState, getState );
105
+ }
@@ -29,6 +29,7 @@ import GlobalStylesUIWrapper from './components/global-styles';
29
29
  import { StyleBookPreview } from './components/style-book';
30
30
  import { useGlobalStyles, useStyle } from './components/global-styles/hooks';
31
31
  import { GlobalStylesActionMenu } from './components/global-styles/menu';
32
+ import UploadProgressSnackbar from './components/upload-progress-snackbar';
32
33
 
33
34
  const { store: interfaceStore, ...remainingInterfaceApis } = interfaceApis;
34
35
 
@@ -48,6 +49,7 @@ lock( privateApis, {
48
49
  ToolsMoreMenuGroup,
49
50
  ViewMoreMenuGroup,
50
51
  ResizableEditor,
52
+ UploadProgressSnackbar,
51
53
  registerCoreBlockBindingsSources,
52
54
  getTemplateInfo,
53
55
  // Global Styles
@@ -11,7 +11,6 @@ import { isInTheFuture, getDate } from '@wordpress/date';
11
11
  import { addQueryArgs, cleanForSlug } from '@wordpress/url';
12
12
  import { createSelector, createRegistrySelector } from '@wordpress/data';
13
13
  import deprecated from '@wordpress/deprecated';
14
- import { Platform } from '@wordpress/element';
15
14
  import { store as blockEditorStore } from '@wordpress/block-editor';
16
15
  import { store as coreStore } from '@wordpress/core-data';
17
16
  import { store as preferencesStore } from '@wordpress/preferences';
@@ -524,8 +523,7 @@ export function isEditedPostSaveable( state ) {
524
523
  return (
525
524
  !! getEditedPostAttribute( state, 'title' ) ||
526
525
  !! getEditedPostAttribute( state, 'excerpt' ) ||
527
- ! isEditedPostEmpty( state ) ||
528
- Platform.OS === 'native'
526
+ ! isEditedPostEmpty( state )
529
527
  );
530
528
  }
531
529
 
package/src/style.scss CHANGED
@@ -68,6 +68,7 @@
68
68
  @use "./components/styles-canvas/style.scss" as *;
69
69
  @use "./components/table-of-contents/style.scss" as *;
70
70
  @use "./components/text-editor/style.scss" as *;
71
+ @use "./components/upload-progress-snackbar/style.scss" as *;
71
72
  @use "./components/visual-editor/style.scss" as *;
72
73
  @use "./dataviews/fields/content-preview/style.scss" as *;
73
74
  @use "./hooks/push-changes-to-global-styles/style.scss" as *;
@@ -15,6 +15,10 @@ import { isClientSideMediaSupported } from '@wordpress/upload-media';
15
15
  * Internal dependencies
16
16
  */
17
17
  import { store as editorStore } from '../../store';
18
+ import {
19
+ addFiles as trackStart,
20
+ advance as trackAdvance,
21
+ } from '../../components/upload-progress-snackbar/tracker';
18
22
 
19
23
  const noop = () => {};
20
24
 
@@ -78,6 +82,18 @@ export default function mediaUpload( {
78
82
 
79
83
  const postData = currentPostId ? { post: currentPostId } : {};
80
84
 
85
+ // Track this batch for the upload progress snackbar. Only applies to the
86
+ // non-CSM path — when CSM is enabled, the block-editor provider intercepts
87
+ // mediaUpload and dispatches to the upload-media store, so this wrapper is
88
+ // not called.
89
+ if ( ! isClientSideMediaActive ) {
90
+ const trackingFiles = Array.from( filesList ).map(
91
+ ( f ) => f?.name || ''
92
+ );
93
+ trackStart( trackingFiles );
94
+ }
95
+ let lastCompletedCount = 0;
96
+
81
97
  uploadMedia( {
82
98
  allowedTypes,
83
99
  filesList,
@@ -106,6 +122,15 @@ export default function mediaUpload( {
106
122
  ) {
107
123
  clearSaveLock();
108
124
  }
125
+
126
+ // Advance the snackbar tracker for newly-completed files.
127
+ if ( ! isClientSideMediaActive ) {
128
+ const completedCount = entityFiles.length;
129
+ if ( completedCount > lastCompletedCount ) {
130
+ trackAdvance( completedCount - lastCompletedCount );
131
+ lastCompletedCount = completedCount;
132
+ }
133
+ }
109
134
  },
110
135
  onSuccess,
111
136
  additionalData: {
@@ -116,6 +141,8 @@ export default function mediaUpload( {
116
141
  onError: ( { message } ) => {
117
142
  if ( ! isClientSideMediaActive ) {
118
143
  clearSaveLock();
144
+ // Failed files still count as "done" for the snackbar.
145
+ trackAdvance( 1 );
119
146
  }
120
147
  onError( message );
121
148
  },
@@ -1,2 +0,0 @@
1
- // Commands are disabled in the mobile native version.
2
- export default function useCommands() {}
@@ -1,47 +0,0 @@
1
- // Block Creation Components.
2
- /**
3
- * WordPress dependencies
4
- */
5
- import {
6
- BlockControls,
7
- BlockEdit,
8
- BlockFormatControls,
9
- DefaultBlockAppender,
10
- createCustomColorsHOC,
11
- getColorClassName,
12
- getColorObjectByAttributeValues,
13
- getColorObjectByColorValue,
14
- InspectorControls,
15
- PlainText,
16
- RichText,
17
- RichTextShortcut,
18
- RichTextToolbarButton,
19
- RichTextInserterItem,
20
- __unstableRichTextInputEvent,
21
- MediaPlaceholder,
22
- URLInput,
23
- withColors,
24
- withFontSizes,
25
- } from '@wordpress/block-editor';
26
-
27
- export {
28
- BlockControls,
29
- BlockEdit,
30
- BlockFormatControls,
31
- DefaultBlockAppender,
32
- createCustomColorsHOC,
33
- getColorClassName,
34
- getColorObjectByAttributeValues,
35
- getColorObjectByColorValue,
36
- InspectorControls,
37
- PlainText,
38
- RichText,
39
- RichTextShortcut,
40
- RichTextToolbarButton,
41
- RichTextInserterItem,
42
- __unstableRichTextInputEvent,
43
- MediaPlaceholder,
44
- URLInput,
45
- withColors,
46
- withFontSizes,
47
- };
@@ -1,40 +0,0 @@
1
- /**
2
- * External dependencies
3
- */
4
- import { View } from 'react-native';
5
-
6
- /**
7
- * WordPress dependencies
8
- */
9
- import { __ } from '@wordpress/i18n';
10
-
11
- /**
12
- * Internal dependencies
13
- */
14
- import styles from './style.scss';
15
- import { HelpDetailBodyText, HelpDetailImage } from './view-sections';
16
-
17
- const AddBlocks = () => {
18
- return (
19
- <>
20
- <HelpDetailImage
21
- source={ require( './images/add-light.png' ) }
22
- sourceDarkMode={ require( './images/add-dark.png' ) }
23
- />
24
- <View style={ styles.helpDetailContainer }>
25
- <HelpDetailBodyText
26
- text={ __(
27
- 'Add a new block at any time by tapping on the + icon in the toolbar on the bottom left.'
28
- ) }
29
- />
30
- <HelpDetailBodyText
31
- text={ __(
32
- 'Once you become familiar with the names of different blocks, you can add a block by typing a forward slash followed by the block name — for example, /image or /heading.'
33
- ) }
34
- />
35
- </View>
36
- </>
37
- );
38
- };
39
-
40
- export default AddBlocks;
@@ -1,40 +0,0 @@
1
- /**
2
- * External dependencies
3
- */
4
- import { View } from 'react-native';
5
-
6
- /**
7
- * WordPress dependencies
8
- */
9
- import { __ } from '@wordpress/i18n';
10
-
11
- /**
12
- * Internal dependencies
13
- */
14
- import styles from './style.scss';
15
- import { HelpDetailBodyText, HelpDetailImage } from './view-sections';
16
-
17
- const CustomizeBlocks = () => {
18
- return (
19
- <>
20
- <HelpDetailImage
21
- source={ require( './images/settings-light.png' ) }
22
- sourceDarkMode={ require( './images/settings-dark.png' ) }
23
- />
24
- <View style={ styles.helpDetailContainer }>
25
- <HelpDetailBodyText
26
- text={ __(
27
- 'Each block has its own settings. To find them, tap on a block. Its settings will appear on the toolbar at the bottom of the screen.'
28
- ) }
29
- />
30
- <HelpDetailBodyText
31
- text={ __(
32
- 'Some blocks have additional settings. Tap the settings icon on the bottom right of the block to view more options.'
33
- ) }
34
- />
35
- </View>
36
- </>
37
- );
38
- };
39
-
40
- export default CustomizeBlocks;
@@ -1,67 +0,0 @@
1
- /**
2
- * External dependencies
3
- */
4
- import {
5
- ScrollView,
6
- StyleSheet,
7
- TouchableWithoutFeedback,
8
- View,
9
- } from 'react-native';
10
- import { useNavigation } from '@react-navigation/native';
11
-
12
- /**
13
- * WordPress dependencies
14
- */
15
- import { BottomSheet, BottomSheetContext } from '@wordpress/components';
16
- import { useContext } from '@wordpress/element';
17
-
18
- /**
19
- * Internal dependencies
20
- */
21
- import styles from './style.scss';
22
-
23
- const HelpDetailNavigationScreen = ( { content, label } ) => {
24
- const navigation = useNavigation();
25
-
26
- const { listProps } = useContext( BottomSheetContext );
27
- const contentContainerStyle = StyleSheet.flatten(
28
- listProps.contentContainerStyle
29
- );
30
-
31
- return (
32
- <BottomSheet.NavigationScreen isScrollable fullScreen>
33
- <View style={ styles.container }>
34
- <BottomSheet.NavBar>
35
- <BottomSheet.NavBar.BackButton
36
- onPress={ navigation.goBack }
37
- />
38
- <BottomSheet.NavBar.Heading>
39
- { label }
40
- </BottomSheet.NavBar.Heading>
41
- </BottomSheet.NavBar>
42
- <ScrollView
43
- { ...listProps }
44
- contentContainerStyle={ {
45
- ...contentContainerStyle,
46
- paddingBottom: Math.max(
47
- listProps.safeAreaBottomInset,
48
- contentContainerStyle.paddingBottom
49
- ),
50
- /**
51
- * Remove margin set via `hideHeader`. Combining a header
52
- * and navigation in this bottom sheet is at odds with the
53
- * current `BottomSheet` implementation.
54
- */
55
- marginTop: 0,
56
- } }
57
- >
58
- <TouchableWithoutFeedback accessible={ false }>
59
- <View>{ content }</View>
60
- </TouchableWithoutFeedback>
61
- </ScrollView>
62
- </View>
63
- </BottomSheet.NavigationScreen>
64
- );
65
- };
66
-
67
- export default HelpDetailNavigationScreen;
@@ -1,38 +0,0 @@
1
- /**
2
- * External dependencies
3
- */
4
- import { Pressable, Text } from 'react-native';
5
-
6
- /**
7
- * WordPress dependencies
8
- */
9
- import { usePreferredColorSchemeStyle } from '@wordpress/compose';
10
-
11
- /**
12
- * Internal dependencies
13
- */
14
- import styles from './style.scss';
15
-
16
- const HelpGetSupportButton = ( { onPress, title } ) => {
17
- const buttonStyle = usePreferredColorSchemeStyle(
18
- styles.button,
19
- styles.buttonDark
20
- );
21
-
22
- const textStyle = usePreferredColorSchemeStyle(
23
- styles.buttonText,
24
- styles.buttonTextDark
25
- );
26
-
27
- return (
28
- <Pressable
29
- style={ buttonStyle }
30
- onPress={ onPress }
31
- accessibilityRole="button"
32
- >
33
- <Text style={ textStyle }>{ title }</Text>
34
- </Pressable>
35
- );
36
- };
37
-
38
- export default HelpGetSupportButton;
@@ -1,29 +0,0 @@
1
- /**
2
- * External dependencies
3
- */
4
- import { Text, View } from 'react-native';
5
-
6
- /**
7
- * WordPress dependencies
8
- */
9
- import { usePreferredColorSchemeStyle } from '@wordpress/compose';
10
-
11
- /**
12
- * Internal dependencies
13
- */
14
- import styles from './style.scss';
15
-
16
- const HelpSectionTitle = ( { children } ) => {
17
- const helpSectionTitle = usePreferredColorSchemeStyle(
18
- styles.helpSectionTitle,
19
- styles.helpSectionTitleDark
20
- );
21
-
22
- return (
23
- <View style={ styles.helpSectionTitleContainer }>
24
- <Text style={ helpSectionTitle }>{ children }</Text>
25
- </View>
26
- );
27
- };
28
-
29
- export default HelpSectionTitle;