@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.
- package/CHANGELOG.md +25 -1
- package/build/components/block-removal-warnings/index.cjs +0 -3
- package/build/components/block-removal-warnings/index.cjs.map +2 -2
- package/build/components/collab-sidebar/note-indicator-toolbar.cjs +49 -43
- package/build/components/collab-sidebar/note-indicator-toolbar.cjs.map +3 -3
- package/build/components/collaborators-overlay/use-block-highlighting.cjs +1 -8
- package/build/components/collaborators-overlay/use-block-highlighting.cjs.map +3 -3
- package/build/components/collaborators-overlay/use-render-cursors.cjs +1 -7
- package/build/components/collaborators-overlay/use-render-cursors.cjs.map +3 -3
- package/build/components/more-menu/view-more-menu-group.cjs +1 -2
- package/build/components/more-menu/view-more-menu-group.cjs.map +2 -2
- package/build/components/page-attributes/parent.cjs +1 -0
- package/build/components/page-attributes/parent.cjs.map +2 -2
- package/build/components/post-publish-button/index.cjs +114 -157
- package/build/components/post-publish-button/index.cjs.map +3 -3
- package/build/components/post-revisions-preview/block-diff.cjs +21 -9
- package/build/components/post-revisions-preview/block-diff.cjs.map +2 -2
- package/build/components/post-revisions-preview/preserve-client-ids.cjs +2 -2
- package/build/components/post-revisions-preview/preserve-client-ids.cjs.map +2 -2
- package/build/components/provider/index.cjs +2 -0
- package/build/components/provider/index.cjs.map +3 -3
- package/build/components/provider/use-network-reconnect.cjs +51 -0
- package/build/components/provider/use-network-reconnect.cjs.map +7 -0
- package/build/components/revision-fields-diff/index.cjs +2 -2
- package/build/components/revision-fields-diff/index.cjs.map +2 -2
- package/build/components/sidebar/index.cjs +1 -4
- package/build/components/sidebar/index.cjs.map +2 -2
- package/build/components/template-actions-panel/block-theme-content.cjs +7 -1
- package/build/components/template-actions-panel/block-theme-content.cjs.map +2 -2
- package/build/components/upload-progress-snackbar/index.cjs +161 -0
- package/build/components/upload-progress-snackbar/index.cjs.map +7 -0
- package/build/components/upload-progress-snackbar/tracker.cjs +90 -0
- package/build/components/upload-progress-snackbar/tracker.cjs.map +7 -0
- package/build/private-apis.cjs +2 -0
- package/build/private-apis.cjs.map +3 -3
- package/build/store/selectors.cjs +1 -2
- package/build/store/selectors.cjs.map +2 -2
- package/build/utils/media-upload/index.cjs +16 -0
- package/build/utils/media-upload/index.cjs.map +3 -3
- package/build-module/components/block-removal-warnings/index.mjs +0 -3
- package/build-module/components/block-removal-warnings/index.mjs.map +2 -2
- package/build-module/components/collab-sidebar/note-indicator-toolbar.mjs +53 -44
- package/build-module/components/collab-sidebar/note-indicator-toolbar.mjs.map +2 -2
- package/build-module/components/collaborators-overlay/use-block-highlighting.mjs +1 -8
- package/build-module/components/collaborators-overlay/use-block-highlighting.mjs.map +2 -2
- package/build-module/components/collaborators-overlay/use-render-cursors.mjs +1 -7
- package/build-module/components/collaborators-overlay/use-render-cursors.mjs.map +2 -2
- package/build-module/components/more-menu/view-more-menu-group.mjs +1 -2
- package/build-module/components/more-menu/view-more-menu-group.mjs.map +2 -2
- package/build-module/components/page-attributes/parent.mjs +1 -0
- package/build-module/components/page-attributes/parent.mjs.map +2 -2
- package/build-module/components/post-publish-button/index.mjs +116 -159
- package/build-module/components/post-publish-button/index.mjs.map +2 -2
- package/build-module/components/post-revisions-preview/block-diff.mjs +20 -8
- package/build-module/components/post-revisions-preview/block-diff.mjs.map +2 -2
- package/build-module/components/post-revisions-preview/preserve-client-ids.mjs +1 -1
- package/build-module/components/post-revisions-preview/preserve-client-ids.mjs.map +1 -1
- package/build-module/components/provider/index.mjs +2 -0
- package/build-module/components/provider/index.mjs.map +2 -2
- package/build-module/components/provider/use-network-reconnect.mjs +30 -0
- package/build-module/components/provider/use-network-reconnect.mjs.map +7 -0
- package/build-module/components/revision-fields-diff/index.mjs +2 -2
- package/build-module/components/revision-fields-diff/index.mjs.map +2 -2
- package/build-module/components/sidebar/index.mjs +2 -11
- package/build-module/components/sidebar/index.mjs.map +2 -2
- package/build-module/components/template-actions-panel/block-theme-content.mjs +7 -1
- package/build-module/components/template-actions-panel/block-theme-content.mjs.map +2 -2
- package/build-module/components/upload-progress-snackbar/index.mjs +135 -0
- package/build-module/components/upload-progress-snackbar/index.mjs.map +7 -0
- package/build-module/components/upload-progress-snackbar/tracker.mjs +61 -0
- package/build-module/components/upload-progress-snackbar/tracker.mjs.map +7 -0
- package/build-module/private-apis.mjs +2 -0
- package/build-module/private-apis.mjs.map +2 -2
- package/build-module/store/selectors.mjs +1 -2
- package/build-module/store/selectors.mjs.map +2 -2
- package/build-module/utils/media-upload/index.mjs +19 -0
- package/build-module/utils/media-upload/index.mjs.map +2 -2
- package/build-style/style-rtl.css +454 -81
- package/build-style/style.css +454 -81
- package/build-types/components/block-removal-warnings/index.d.ts.map +1 -1
- package/build-types/components/collab-sidebar/add-comment.d.ts +6 -0
- package/build-types/components/collab-sidebar/add-comment.d.ts.map +1 -0
- package/build-types/components/collab-sidebar/comment-author-info.d.ts +8 -0
- package/build-types/components/collab-sidebar/comment-author-info.d.ts.map +1 -0
- package/build-types/components/collab-sidebar/comment-form.d.ts +9 -0
- package/build-types/components/collab-sidebar/comment-form.d.ts.map +1 -0
- package/build-types/components/collab-sidebar/comment-indicator-toolbar.d.ts +6 -0
- package/build-types/components/collab-sidebar/comment-indicator-toolbar.d.ts.map +1 -0
- package/build-types/components/collab-sidebar/comment-menu-item.d.ts +6 -0
- package/build-types/components/collab-sidebar/comment-menu-item.d.ts.map +1 -0
- package/build-types/components/collab-sidebar/comments.d.ts +10 -0
- package/build-types/components/collab-sidebar/comments.d.ts.map +1 -0
- package/build-types/components/collab-sidebar/note-indicator-toolbar.d.ts.map +1 -1
- package/build-types/components/collaborators-overlay/use-block-highlighting.d.ts +0 -3
- package/build-types/components/collaborators-overlay/use-block-highlighting.d.ts.map +1 -1
- package/build-types/components/collaborators-overlay/use-render-cursors.d.ts.map +1 -1
- package/build-types/components/document-bar/index.d.ts +2 -2
- package/build-types/components/document-bar/index.d.ts.map +1 -1
- package/build-types/components/global-styles-provider/index.d.ts +16 -0
- package/build-types/components/global-styles-provider/index.d.ts.map +1 -0
- package/build-types/components/media/index.d.ts +3 -0
- package/build-types/components/media/index.d.ts.map +1 -0
- package/build-types/components/media/metadata-panel.d.ts +12 -0
- package/build-types/components/media/metadata-panel.d.ts.map +1 -0
- package/build-types/components/media/preview.d.ts +9 -0
- package/build-types/components/media/preview.d.ts.map +1 -0
- package/build-types/components/more-menu/view-more-menu-group.d.ts.map +1 -1
- package/build-types/components/page-attributes/parent.d.ts.map +1 -1
- package/build-types/components/post-publish-button/index.d.ts +9 -9
- package/build-types/components/post-publish-button/index.d.ts.map +1 -1
- package/build-types/components/post-revisions-preview/block-diff.d.ts +3 -0
- package/build-types/components/post-revisions-preview/block-diff.d.ts.map +1 -1
- package/build-types/components/post-text-editor/index.d.ts +1 -1
- package/build-types/components/post-text-editor/index.d.ts.map +1 -1
- package/build-types/components/post-text-editor/utils.d.ts +29 -0
- package/build-types/components/post-text-editor/utils.d.ts.map +1 -0
- package/build-types/components/provider/index.d.ts.map +1 -1
- package/build-types/components/provider/use-network-reconnect.d.ts +8 -0
- package/build-types/components/provider/use-network-reconnect.d.ts.map +1 -0
- package/build-types/components/revision-fields-diff/index.d.ts +3 -0
- package/build-types/components/revision-fields-diff/index.d.ts.map +1 -1
- package/build-types/components/sidebar/index.d.ts.map +1 -1
- package/build-types/components/template-actions-panel/block-theme-content.d.ts.map +1 -1
- package/build-types/components/upload-progress-snackbar/index.d.ts +19 -0
- package/build-types/components/upload-progress-snackbar/index.d.ts.map +1 -0
- package/build-types/components/upload-progress-snackbar/stories/index.story.d.ts +28 -0
- package/build-types/components/upload-progress-snackbar/stories/index.story.d.ts.map +1 -0
- package/build-types/components/upload-progress-snackbar/tracker.d.ts +41 -0
- package/build-types/components/upload-progress-snackbar/tracker.d.ts.map +1 -0
- package/build-types/private-apis.d.ts.map +1 -1
- package/build-types/store/selectors.d.ts.map +1 -1
- package/build-types/utils/get-template-part-icon.d.ts.map +1 -1
- package/build-types/utils/media-upload/index.d.ts.map +1 -1
- package/package.json +53 -50
- package/src/components/README.md +1 -1
- package/src/components/block-removal-warnings/index.js +0 -7
- package/src/components/collab-sidebar/note-indicator-toolbar.js +73 -60
- package/src/components/collaborators-overlay/use-block-highlighting.ts +0 -9
- package/src/components/collaborators-overlay/use-render-cursors.ts +0 -8
- package/src/components/collaborators-presence/avatar/test/index.tsx +8 -3
- package/src/components/more-menu/view-more-menu-group.js +1 -2
- package/src/components/page-attributes/parent.js +1 -0
- package/src/components/post-publish-button/index.js +143 -192
- package/src/components/post-publish-button/test/index.js +137 -114
- package/src/components/post-revisions-preview/block-diff.js +63 -19
- package/src/components/post-revisions-preview/preserve-client-ids.js +1 -1
- package/src/components/post-revisions-preview/test/block-diff.js +109 -6
- package/src/components/provider/index.js +4 -0
- package/src/components/provider/test/use-network-reconnect.js +137 -0
- package/src/components/provider/use-network-reconnect.js +44 -0
- package/src/components/revision-fields-diff/index.js +7 -2
- package/src/components/sidebar/index.js +2 -11
- package/src/components/template-actions-panel/block-theme-content.js +10 -1
- package/src/components/upload-progress-snackbar/README.md +26 -0
- package/src/components/upload-progress-snackbar/index.js +216 -0
- package/src/components/upload-progress-snackbar/stories/index.story.tsx +85 -0
- package/src/components/upload-progress-snackbar/style.scss +30 -0
- package/src/components/upload-progress-snackbar/test/index.js +199 -0
- package/src/components/upload-progress-snackbar/tracker.js +105 -0
- package/src/private-apis.js +2 -0
- package/src/store/selectors.js +1 -3
- package/src/style.scss +1 -0
- package/src/utils/media-upload/index.js +27 -0
- package/src/components/commands/index.native.js +0 -2
- package/src/components/deprecated.native.js +0 -47
- package/src/components/editor-help/add-blocks.native.js +0 -40
- package/src/components/editor-help/customize-blocks.native.js +0 -40
- package/src/components/editor-help/help-detail-navigation-screen.native.js +0 -67
- package/src/components/editor-help/help-get-support-button.native.js +0 -38
- package/src/components/editor-help/help-section-title.native.js +0 -29
- package/src/components/editor-help/help-topic-row.native.js +0 -33
- package/src/components/editor-help/icon-move-blocks.native.js +0 -10
- package/src/components/editor-help/index.native.js +0 -208
- package/src/components/editor-help/intro-to-blocks.native.js +0 -91
- package/src/components/editor-help/move-blocks.native.js +0 -55
- package/src/components/editor-help/remove-blocks.native.js +0 -35
- package/src/components/editor-help/style.android.scss +0 -6
- package/src/components/editor-help/style.ios.scss +0 -6
- package/src/components/editor-help/test/index.native.js +0 -81
- package/src/components/editor-help/view-sections.native.js +0 -79
- package/src/components/error-boundary/index.native.js +0 -192
- package/src/components/error-boundary/style.native.scss +0 -116
- package/src/components/index.native.js +0 -15
- package/src/components/offline-status/index.native.js +0 -99
- package/src/components/offline-status/style.native.scss +0 -28
- package/src/components/offline-status/test/index.native.js +0 -108
- package/src/components/post-title/index.native.js +0 -282
- package/src/components/post-title/style.native.scss +0 -13
- package/src/components/post-title/test/__snapshots__/index.native.js.snap +0 -25
- package/src/components/post-title/test/index.native.js +0 -78
- package/src/components/provider/index.native.js +0 -497
- package/src/components/provider/use-block-editor-settings.native.js +0 -48
- package/src/components/template-part-menu-items/index.native.js +0 -3
- package/src/hooks/index.native.js +0 -0
- package/src/index.native.js +0 -16
- package/src/private-apis.native.js +0 -33
- package/src/store/actions.native.js +0 -27
- package/src/store/reducer.native.js +0 -94
- package/src/store/selectors.native.js +0 -57
- package/src/store/test/actions.native.js +0 -16
- package/src/store/test/reducer.native.js +0 -36
- package/src/store/test/selectors.native.js +0 -28
- package/src/utils/index.native.js +0 -6
- package/src/utils/media-sideload/index.native.js +0 -1
- package/src/utils/media-upload/index.native.js +0 -1
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { renderHook } from '@testing-library/react';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* WordPress dependencies
|
|
8
|
+
*/
|
|
9
|
+
import { useRegistry } from '@wordpress/data';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Internal dependencies
|
|
13
|
+
*/
|
|
14
|
+
import useNetworkReconnect from '../use-network-reconnect';
|
|
15
|
+
|
|
16
|
+
const mockPauseQueue = jest.fn();
|
|
17
|
+
const mockResumeQueue = jest.fn();
|
|
18
|
+
|
|
19
|
+
jest.mock( '@wordpress/data', () => ( {
|
|
20
|
+
useRegistry: jest.fn(),
|
|
21
|
+
} ) );
|
|
22
|
+
|
|
23
|
+
jest.mock( '@wordpress/upload-media', () => ( {
|
|
24
|
+
store: 'core/upload-media',
|
|
25
|
+
} ) );
|
|
26
|
+
|
|
27
|
+
jest.mock( '../../../lock-unlock', () => ( {
|
|
28
|
+
unlock: jest.fn( () => ( {
|
|
29
|
+
pauseQueue: mockPauseQueue,
|
|
30
|
+
resumeQueue: mockResumeQueue,
|
|
31
|
+
} ) ),
|
|
32
|
+
} ) );
|
|
33
|
+
|
|
34
|
+
describe( 'useNetworkReconnect', () => {
|
|
35
|
+
const originalAddEventListener = window.addEventListener;
|
|
36
|
+
const originalRemoveEventListener = window.removeEventListener;
|
|
37
|
+
let listeners;
|
|
38
|
+
|
|
39
|
+
beforeEach( () => {
|
|
40
|
+
mockPauseQueue.mockClear();
|
|
41
|
+
mockResumeQueue.mockClear();
|
|
42
|
+
listeners = {};
|
|
43
|
+
window.addEventListener = jest.fn( ( event, cb ) => {
|
|
44
|
+
listeners[ event ] = cb;
|
|
45
|
+
} );
|
|
46
|
+
window.removeEventListener = jest.fn( ( event ) => {
|
|
47
|
+
delete listeners[ event ];
|
|
48
|
+
} );
|
|
49
|
+
useRegistry.mockReturnValue( {
|
|
50
|
+
dispatch: jest.fn( () => ( {} ) ),
|
|
51
|
+
} );
|
|
52
|
+
} );
|
|
53
|
+
|
|
54
|
+
afterEach( () => {
|
|
55
|
+
window.addEventListener = originalAddEventListener;
|
|
56
|
+
window.removeEventListener = originalRemoveEventListener;
|
|
57
|
+
delete window.__clientSideMediaProcessing;
|
|
58
|
+
} );
|
|
59
|
+
|
|
60
|
+
it( 'does nothing when client-side media processing is disabled', () => {
|
|
61
|
+
window.__clientSideMediaProcessing = false;
|
|
62
|
+
renderHook( () => useNetworkReconnect() );
|
|
63
|
+
|
|
64
|
+
expect( window.addEventListener ).not.toHaveBeenCalled();
|
|
65
|
+
} );
|
|
66
|
+
|
|
67
|
+
it( 'does nothing when the flag is undefined', () => {
|
|
68
|
+
renderHook( () => useNetworkReconnect() );
|
|
69
|
+
|
|
70
|
+
expect( window.addEventListener ).not.toHaveBeenCalled();
|
|
71
|
+
} );
|
|
72
|
+
|
|
73
|
+
it( 'registers offline and online listeners when enabled', () => {
|
|
74
|
+
window.__clientSideMediaProcessing = true;
|
|
75
|
+
renderHook( () => useNetworkReconnect() );
|
|
76
|
+
|
|
77
|
+
expect( window.addEventListener ).toHaveBeenCalledWith(
|
|
78
|
+
'offline',
|
|
79
|
+
expect.any( Function )
|
|
80
|
+
);
|
|
81
|
+
expect( window.addEventListener ).toHaveBeenCalledWith(
|
|
82
|
+
'online',
|
|
83
|
+
expect.any( Function )
|
|
84
|
+
);
|
|
85
|
+
} );
|
|
86
|
+
|
|
87
|
+
it( 'pauses the queue when the offline event fires', () => {
|
|
88
|
+
window.__clientSideMediaProcessing = true;
|
|
89
|
+
renderHook( () => useNetworkReconnect() );
|
|
90
|
+
|
|
91
|
+
listeners.offline();
|
|
92
|
+
|
|
93
|
+
expect( mockPauseQueue ).toHaveBeenCalledTimes( 1 );
|
|
94
|
+
expect( mockResumeQueue ).not.toHaveBeenCalled();
|
|
95
|
+
} );
|
|
96
|
+
|
|
97
|
+
it( 'resumes the queue when the online event fires', () => {
|
|
98
|
+
window.__clientSideMediaProcessing = true;
|
|
99
|
+
renderHook( () => useNetworkReconnect() );
|
|
100
|
+
|
|
101
|
+
listeners.online();
|
|
102
|
+
|
|
103
|
+
expect( mockResumeQueue ).toHaveBeenCalledTimes( 1 );
|
|
104
|
+
expect( mockPauseQueue ).not.toHaveBeenCalled();
|
|
105
|
+
} );
|
|
106
|
+
|
|
107
|
+
it( 'handles multiple offline/online cycles', () => {
|
|
108
|
+
window.__clientSideMediaProcessing = true;
|
|
109
|
+
renderHook( () => useNetworkReconnect() );
|
|
110
|
+
|
|
111
|
+
listeners.offline();
|
|
112
|
+
listeners.online();
|
|
113
|
+
listeners.offline();
|
|
114
|
+
listeners.online();
|
|
115
|
+
|
|
116
|
+
expect( mockPauseQueue ).toHaveBeenCalledTimes( 2 );
|
|
117
|
+
expect( mockResumeQueue ).toHaveBeenCalledTimes( 2 );
|
|
118
|
+
} );
|
|
119
|
+
|
|
120
|
+
it( 'removes the listeners on unmount', () => {
|
|
121
|
+
window.__clientSideMediaProcessing = true;
|
|
122
|
+
const { unmount } = renderHook( () => useNetworkReconnect() );
|
|
123
|
+
|
|
124
|
+
unmount();
|
|
125
|
+
|
|
126
|
+
expect( window.removeEventListener ).toHaveBeenCalledWith(
|
|
127
|
+
'offline',
|
|
128
|
+
expect.any( Function )
|
|
129
|
+
);
|
|
130
|
+
expect( window.removeEventListener ).toHaveBeenCalledWith(
|
|
131
|
+
'online',
|
|
132
|
+
expect.any( Function )
|
|
133
|
+
);
|
|
134
|
+
expect( listeners.offline ).toBeUndefined();
|
|
135
|
+
expect( listeners.online ).toBeUndefined();
|
|
136
|
+
} );
|
|
137
|
+
} );
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { useEffect } from '@wordpress/element';
|
|
5
|
+
import { useRegistry } from '@wordpress/data';
|
|
6
|
+
import { store as uploadStore } from '@wordpress/upload-media';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Internal dependencies
|
|
10
|
+
*/
|
|
11
|
+
import { unlock } from '../../lock-unlock';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A hook that pauses the media upload queue when the browser goes offline
|
|
15
|
+
* and resumes it when connectivity is restored.
|
|
16
|
+
*
|
|
17
|
+
* Only active when client-side media processing is enabled.
|
|
18
|
+
*/
|
|
19
|
+
export default function useNetworkReconnect() {
|
|
20
|
+
const isEnabled = window.__clientSideMediaProcessing;
|
|
21
|
+
const registry = useRegistry();
|
|
22
|
+
|
|
23
|
+
useEffect( () => {
|
|
24
|
+
if ( ! isEnabled ) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const handleOffline = () => {
|
|
29
|
+
unlock( registry.dispatch( uploadStore ) ).pauseQueue();
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const handleOnline = () => {
|
|
33
|
+
unlock( registry.dispatch( uploadStore ) ).resumeQueue();
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
window.addEventListener( 'offline', handleOffline );
|
|
37
|
+
window.addEventListener( 'online', handleOnline );
|
|
38
|
+
|
|
39
|
+
return () => {
|
|
40
|
+
window.removeEventListener( 'offline', handleOffline );
|
|
41
|
+
window.removeEventListener( 'online', handleOnline );
|
|
42
|
+
};
|
|
43
|
+
}, [ isEnabled, registry ] );
|
|
44
|
+
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
|
-
|
|
4
|
+
/*
|
|
5
|
+
* `diffWordsWithSpace` preserves the v4-style per-word output. v6+
|
|
6
|
+
* stopped treating whitespace as a token in `diffWords`, which coalesces
|
|
7
|
+
* adjacent word changes into a single removed/added pair.
|
|
8
|
+
*/
|
|
9
|
+
import { diffWordsWithSpace } from 'diff';
|
|
5
10
|
|
|
6
11
|
/**
|
|
7
12
|
* WordPress dependencies
|
|
@@ -71,7 +76,7 @@ export default function RevisionFieldsDiffPanel() {
|
|
|
71
76
|
continue;
|
|
72
77
|
}
|
|
73
78
|
|
|
74
|
-
result[ key ] =
|
|
79
|
+
result[ key ] = diffWordsWithSpace( prevStr, revStr );
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
if ( Object.keys( result ).length === 0 ) {
|
|
@@ -6,13 +6,7 @@ import {
|
|
|
6
6
|
store as blockEditorStore,
|
|
7
7
|
} from '@wordpress/block-editor';
|
|
8
8
|
import { useSelect, useDispatch } from '@wordpress/data';
|
|
9
|
-
import {
|
|
10
|
-
Platform,
|
|
11
|
-
useCallback,
|
|
12
|
-
useContext,
|
|
13
|
-
useEffect,
|
|
14
|
-
useRef,
|
|
15
|
-
} from '@wordpress/element';
|
|
9
|
+
import { useCallback, useContext, useEffect, useRef } from '@wordpress/element';
|
|
16
10
|
import { isRTL, __, _x } from '@wordpress/i18n';
|
|
17
11
|
import { drawerLeft, drawerRight } from '@wordpress/icons';
|
|
18
12
|
import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts';
|
|
@@ -47,10 +41,7 @@ import {
|
|
|
47
41
|
|
|
48
42
|
const { Tabs } = unlock( componentsPrivateApis );
|
|
49
43
|
|
|
50
|
-
const SIDEBAR_ACTIVE_BY_DEFAULT =
|
|
51
|
-
web: true,
|
|
52
|
-
native: false,
|
|
53
|
-
} );
|
|
44
|
+
const SIDEBAR_ACTIVE_BY_DEFAULT = true;
|
|
54
45
|
|
|
55
46
|
const SidebarContent = ( {
|
|
56
47
|
tabName,
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import { useState } from '@wordpress/element';
|
|
18
18
|
import { __, sprintf } from '@wordpress/i18n';
|
|
19
19
|
import { decodeEntities } from '@wordpress/html-entities';
|
|
20
|
+
import { ENTER, SPACE } from '@wordpress/keycodes';
|
|
20
21
|
import { store as noticesStore } from '@wordpress/notices';
|
|
21
22
|
import { store as preferencesStore } from '@wordpress/preferences';
|
|
22
23
|
// eslint-disable-next-line @wordpress/use-recommended-components -- `Tooltip` is not yet on the recommended `@wordpress/ui` allow-list; landing as a migration step ahead of the wider rollout.
|
|
@@ -127,7 +128,15 @@ export default function TemplateActionsPanelContent() {
|
|
|
127
128
|
tabIndex={ 0 }
|
|
128
129
|
aria-label={ tooltipText }
|
|
129
130
|
onClick={ () => setIsSwapModalOpen( true ) }
|
|
130
|
-
|
|
131
|
+
onKeyDown={ ( event ) => {
|
|
132
|
+
if (
|
|
133
|
+
event.keyCode === ENTER ||
|
|
134
|
+
event.keyCode === SPACE
|
|
135
|
+
) {
|
|
136
|
+
event.preventDefault();
|
|
137
|
+
setIsSwapModalOpen( true );
|
|
138
|
+
}
|
|
139
|
+
} }
|
|
131
140
|
>
|
|
132
141
|
{ previewContent }
|
|
133
142
|
</div>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# UploadProgressSnackbar
|
|
2
|
+
|
|
3
|
+
`UploadProgressSnackbar` manages a snackbar notice that shows media upload progress. It creates and updates a notice via the notices store, so the snackbar positions and stacks with every other snackbar in the editor.
|
|
4
|
+
|
|
5
|
+
The component reads from two sources so it works for both upload paths:
|
|
6
|
+
|
|
7
|
+
- **`@wordpress/upload-media`** — the client-side media processing (CSM) path. Only counts original user-uploaded files, ignoring generated subsizes and thumbnails (items with a `parentId`).
|
|
8
|
+
- **Editor-local tracker** — populated by the editor's `mediaUpload` wrapper for the traditional (non-CSM) upload path (e.g. Safari, or when a filter disables CSM).
|
|
9
|
+
|
|
10
|
+
While uploads are in progress the snackbar shows a spinner to the left of the text. When the last upload finishes, the spinner is replaced with a green checkmark for a brief moment before the snackbar dismisses.
|
|
11
|
+
|
|
12
|
+
The component renders nothing itself — it is a controller that manages a notice.
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```jsx
|
|
17
|
+
import { UploadProgressSnackbar } from '@wordpress/editor';
|
|
18
|
+
|
|
19
|
+
// Mount anywhere in the editor — it doesn't render DOM, just manages a notice.
|
|
20
|
+
<UploadProgressSnackbar />
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Accessibility
|
|
24
|
+
|
|
25
|
+
- The component calls `wp.a11y.speak()` once when uploads start and once when they complete, avoiding per-tick chatter.
|
|
26
|
+
- The snackbar is created with `speak: false` to prevent the notices store from re-announcing on every text update.
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { useSelect, useDispatch } from '@wordpress/data';
|
|
5
|
+
import { useEffect, useMemo, useRef, useState } from '@wordpress/element';
|
|
6
|
+
import { __, sprintf } from '@wordpress/i18n';
|
|
7
|
+
import { speak } from '@wordpress/a11y';
|
|
8
|
+
import { store as uploadStore } from '@wordpress/upload-media';
|
|
9
|
+
import { store as noticesStore } from '@wordpress/notices';
|
|
10
|
+
import { Icon as WCIcon, Spinner } from '@wordpress/components';
|
|
11
|
+
import { check } from '@wordpress/icons';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Internal dependencies
|
|
15
|
+
*/
|
|
16
|
+
import { useTracker } from './tracker';
|
|
17
|
+
|
|
18
|
+
const NOTICE_ID = 'upload-progress';
|
|
19
|
+
|
|
20
|
+
// How long the completion checkmark is shown before the snackbar dismisses.
|
|
21
|
+
const COMPLETION_DISPLAY_MS = 3000;
|
|
22
|
+
|
|
23
|
+
// Longest filename shown before it is middle-truncated. Long names (e.g. the
|
|
24
|
+
// UUID-style names some sources produce) would otherwise stretch the snackbar.
|
|
25
|
+
const MAX_FILENAME_LENGTH = 40;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Middle-truncates a filename that exceeds `MAX_FILENAME_LENGTH`, keeping the
|
|
29
|
+
* start and the end (so the file extension stays visible).
|
|
30
|
+
*
|
|
31
|
+
* @param {string} filename The filename to truncate.
|
|
32
|
+
* @return {string} The original or middle-truncated filename.
|
|
33
|
+
*/
|
|
34
|
+
function truncateFilename( filename ) {
|
|
35
|
+
if ( filename.length <= MAX_FILENAME_LENGTH ) {
|
|
36
|
+
return filename;
|
|
37
|
+
}
|
|
38
|
+
const ellipsis = '…';
|
|
39
|
+
const visible = MAX_FILENAME_LENGTH - ellipsis.length;
|
|
40
|
+
const front = Math.ceil( visible / 2 );
|
|
41
|
+
const back = Math.floor( visible / 2 );
|
|
42
|
+
return (
|
|
43
|
+
filename.slice( 0, front ) +
|
|
44
|
+
ellipsis +
|
|
45
|
+
filename.slice( filename.length - back )
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Exported so the Storybook story can render the exact icon markup the notice
|
|
50
|
+
// uses, keeping the visual review faithful to what ships.
|
|
51
|
+
export const UPLOAD_SPINNER = (
|
|
52
|
+
<span
|
|
53
|
+
className="editor-upload-progress-snackbar__spinner"
|
|
54
|
+
aria-hidden="true"
|
|
55
|
+
>
|
|
56
|
+
<Spinner />
|
|
57
|
+
</span>
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
export const UPLOAD_DONE = (
|
|
61
|
+
<span className="editor-upload-progress-snackbar__check" aria-hidden="true">
|
|
62
|
+
<WCIcon icon={ check } />
|
|
63
|
+
</span>
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Manages a snackbar notice that shows media upload progress while uploads are
|
|
68
|
+
* in progress. It creates/updates a notice via the notices store so that it
|
|
69
|
+
* positions and stacks with every other snackbar in the editor.
|
|
70
|
+
*
|
|
71
|
+
* Reads from two sources to cover both upload paths:
|
|
72
|
+
* - `@wordpress/upload-media` store (client-side media processing path).
|
|
73
|
+
* - An editor-local tracker populated by the traditional `mediaUpload`
|
|
74
|
+
* wrapper (non-CSM path — e.g. Safari, or when a filter disables CSM).
|
|
75
|
+
*
|
|
76
|
+
* Only counts original user-uploaded files (items without a `parentId`),
|
|
77
|
+
* ignoring generated subsizes/thumbnails.
|
|
78
|
+
*
|
|
79
|
+
* @return {null} This component renders nothing — it only manages a notice.
|
|
80
|
+
*/
|
|
81
|
+
export default function UploadProgressSnackbar() {
|
|
82
|
+
const items = useSelect(
|
|
83
|
+
( select ) => select( uploadStore ).getItems(),
|
|
84
|
+
[]
|
|
85
|
+
);
|
|
86
|
+
const tracker = useTracker();
|
|
87
|
+
|
|
88
|
+
// CSM path: originals in the upload-media queue (subsizes excluded). Memoized
|
|
89
|
+
// so its reference is stable across renders where `items` is unchanged, since
|
|
90
|
+
// it's a dependency of the effect below.
|
|
91
|
+
const csmOriginals = useMemo(
|
|
92
|
+
() => items.filter( ( item ) => ! item.parentId ),
|
|
93
|
+
[ items ]
|
|
94
|
+
);
|
|
95
|
+
const csmRemaining = csmOriginals.length;
|
|
96
|
+
|
|
97
|
+
// Non-CSM path: files tracked by the editor's mediaUpload wrapper.
|
|
98
|
+
const trackedRemaining = tracker ? tracker.total - tracker.completed : 0;
|
|
99
|
+
|
|
100
|
+
const remaining = csmRemaining + trackedRemaining;
|
|
101
|
+
|
|
102
|
+
// Track peak total across sources during a session. The CSM queue removes
|
|
103
|
+
// items on completion, and the tracker tops out at its recorded total, so
|
|
104
|
+
// `total` has to be tracked as the high-water mark.
|
|
105
|
+
const [ peak, setPeak ] = useState( 0 );
|
|
106
|
+
const sessionTotal = csmRemaining + ( tracker ? tracker.total : 0 );
|
|
107
|
+
if ( sessionTotal > peak ) {
|
|
108
|
+
setPeak( sessionTotal );
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const { createNotice, removeNotice } = useDispatch( noticesStore );
|
|
112
|
+
|
|
113
|
+
// Track whether the user has dismissed the notice. If so, don't re-create
|
|
114
|
+
// it until the current batch finishes and a new one starts.
|
|
115
|
+
const dismissedRef = useRef( false );
|
|
116
|
+
const wasUploadingRef = useRef( false );
|
|
117
|
+
|
|
118
|
+
// Timeout that removes the completion-state (checkmark) notice after a
|
|
119
|
+
// brief display. Held so a new upload can cancel it.
|
|
120
|
+
const completionTimeoutRef = useRef( null );
|
|
121
|
+
useEffect( () => {
|
|
122
|
+
return () => {
|
|
123
|
+
if ( completionTimeoutRef.current ) {
|
|
124
|
+
clearTimeout( completionTimeoutRef.current );
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}, [] );
|
|
128
|
+
|
|
129
|
+
useEffect( () => {
|
|
130
|
+
const isUploading = remaining > 0;
|
|
131
|
+
|
|
132
|
+
if ( isUploading && ! wasUploadingRef.current ) {
|
|
133
|
+
dismissedRef.current = false;
|
|
134
|
+
speak( __( 'Media upload started' ), 'polite' );
|
|
135
|
+
// A new batch started during the completion display: cancel the
|
|
136
|
+
// pending dismissal so the snackbar transitions straight back
|
|
137
|
+
// into the uploading state, and reset the peak so the new batch
|
|
138
|
+
// counts from `1 of N` rather than resuming the previous total.
|
|
139
|
+
if ( completionTimeoutRef.current ) {
|
|
140
|
+
clearTimeout( completionTimeoutRef.current );
|
|
141
|
+
completionTimeoutRef.current = null;
|
|
142
|
+
setPeak( 0 );
|
|
143
|
+
}
|
|
144
|
+
} else if ( ! isUploading && wasUploadingRef.current ) {
|
|
145
|
+
speak( __( 'Media upload complete' ), 'polite' );
|
|
146
|
+
|
|
147
|
+
if ( ! dismissedRef.current ) {
|
|
148
|
+
createNotice( 'info', __( 'Upload complete' ), {
|
|
149
|
+
id: NOTICE_ID,
|
|
150
|
+
type: 'snackbar',
|
|
151
|
+
isDismissible: false,
|
|
152
|
+
explicitDismiss: false,
|
|
153
|
+
speak: false,
|
|
154
|
+
icon: UPLOAD_DONE,
|
|
155
|
+
onDismiss: () => {
|
|
156
|
+
dismissedRef.current = true;
|
|
157
|
+
},
|
|
158
|
+
} );
|
|
159
|
+
|
|
160
|
+
completionTimeoutRef.current = setTimeout( () => {
|
|
161
|
+
removeNotice( NOTICE_ID );
|
|
162
|
+
completionTimeoutRef.current = null;
|
|
163
|
+
setPeak( 0 );
|
|
164
|
+
}, COMPLETION_DISPLAY_MS );
|
|
165
|
+
} else {
|
|
166
|
+
setPeak( 0 );
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
wasUploadingRef.current = isUploading;
|
|
171
|
+
|
|
172
|
+
if ( ! isUploading || dismissedRef.current ) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const total = peak;
|
|
177
|
+
const current = total - remaining + 1;
|
|
178
|
+
|
|
179
|
+
// Prefer the CSM queue's first original filename, then fall back to
|
|
180
|
+
// the tracker's first pending filename.
|
|
181
|
+
const filename = truncateFilename(
|
|
182
|
+
csmOriginals[ 0 ]?.sourceFile?.name ||
|
|
183
|
+
tracker?.pending[ 0 ] ||
|
|
184
|
+
__( 'Uploading' )
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const content =
|
|
188
|
+
total === 1
|
|
189
|
+
? sprintf(
|
|
190
|
+
/* translators: %s: filename. */
|
|
191
|
+
__( 'Uploading — %s' ),
|
|
192
|
+
filename
|
|
193
|
+
)
|
|
194
|
+
: sprintf(
|
|
195
|
+
/* translators: 1: current upload number, 2: total uploads, 3: filename. */
|
|
196
|
+
__( 'Uploading %1$d of %2$d — %3$s' ),
|
|
197
|
+
current,
|
|
198
|
+
total,
|
|
199
|
+
filename
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
createNotice( 'info', content, {
|
|
203
|
+
id: NOTICE_ID,
|
|
204
|
+
type: 'snackbar',
|
|
205
|
+
isDismissible: false,
|
|
206
|
+
explicitDismiss: true,
|
|
207
|
+
speak: false,
|
|
208
|
+
icon: UPLOAD_SPINNER,
|
|
209
|
+
onDismiss: () => {
|
|
210
|
+
dismissedRef.current = true;
|
|
211
|
+
},
|
|
212
|
+
} );
|
|
213
|
+
}, [ remaining, peak, csmOriginals, tracker, createNotice, removeNotice ] );
|
|
214
|
+
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* WordPress dependencies
|
|
8
|
+
*/
|
|
9
|
+
import { Snackbar } from '@wordpress/components';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Internal dependencies
|
|
13
|
+
*/
|
|
14
|
+
import { UPLOAD_DONE, UPLOAD_SPINNER } from '../index';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The `UploadProgressSnackbar` component itself renders no UI: it manages a
|
|
18
|
+
* snackbar notice via the notices store while media uploads are in progress.
|
|
19
|
+
*
|
|
20
|
+
* These stories render the `Snackbar` with the same icon markup and text the
|
|
21
|
+
* notice produces, so the in-progress and completed states can be reviewed
|
|
22
|
+
* visually without running an actual upload.
|
|
23
|
+
*/
|
|
24
|
+
const meta: Meta< typeof Snackbar > = {
|
|
25
|
+
title: 'Editor/UploadProgressSnackbar',
|
|
26
|
+
component: Snackbar,
|
|
27
|
+
parameters: {
|
|
28
|
+
docs: { canvas: { sourceState: 'shown' } },
|
|
29
|
+
},
|
|
30
|
+
argTypes: {
|
|
31
|
+
icon: { control: false },
|
|
32
|
+
children: { control: false },
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
export default meta;
|
|
36
|
+
|
|
37
|
+
type Story = StoryObj< typeof Snackbar >;
|
|
38
|
+
|
|
39
|
+
export const Uploading: Story = {
|
|
40
|
+
args: {
|
|
41
|
+
icon: UPLOAD_SPINNER,
|
|
42
|
+
children: 'Uploading — sunset-over-the-bay.jpg',
|
|
43
|
+
explicitDismiss: true,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const UploadingMultiple: Story = {
|
|
48
|
+
args: {
|
|
49
|
+
icon: UPLOAD_SPINNER,
|
|
50
|
+
children: 'Uploading 1 of 3 — sunset-over-the-bay.jpg',
|
|
51
|
+
explicitDismiss: true,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const Complete: Story = {
|
|
56
|
+
args: {
|
|
57
|
+
icon: UPLOAD_DONE,
|
|
58
|
+
children: 'Upload complete',
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* All states shown together for quick comparison of the icon alignment and
|
|
64
|
+
* sizing between the spinner and the completion checkmark.
|
|
65
|
+
*/
|
|
66
|
+
export const AllStates: Story = {
|
|
67
|
+
render: () => (
|
|
68
|
+
<div
|
|
69
|
+
style={ {
|
|
70
|
+
display: 'flex',
|
|
71
|
+
flexDirection: 'column',
|
|
72
|
+
alignItems: 'flex-start',
|
|
73
|
+
gap: '12px',
|
|
74
|
+
} }
|
|
75
|
+
>
|
|
76
|
+
<Snackbar icon={ UPLOAD_SPINNER } explicitDismiss>
|
|
77
|
+
Uploading — sunset-over-the-bay.jpg
|
|
78
|
+
</Snackbar>
|
|
79
|
+
<Snackbar icon={ UPLOAD_SPINNER } explicitDismiss>
|
|
80
|
+
Uploading 1 of 3 — sunset-over-the-bay.jpg
|
|
81
|
+
</Snackbar>
|
|
82
|
+
<Snackbar icon={ UPLOAD_DONE }>Upload complete</Snackbar>
|
|
83
|
+
</div>
|
|
84
|
+
),
|
|
85
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
@use "@wordpress/base-styles/variables" as *;
|
|
2
|
+
|
|
3
|
+
.editor-upload-progress-snackbar__spinner,
|
|
4
|
+
.editor-upload-progress-snackbar__check {
|
|
5
|
+
display: inline-flex;
|
|
6
|
+
align-items: center;
|
|
7
|
+
justify-content: center;
|
|
8
|
+
// Size the slot to $icon-size to match the icon dimensions the Snackbar's
|
|
9
|
+
// absolute positioning is tuned for, so the spinner and checkmark sit
|
|
10
|
+
// vertically centered against the notice text.
|
|
11
|
+
width: $icon-size;
|
|
12
|
+
height: $icon-size;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.editor-upload-progress-snackbar__spinner .components-spinner {
|
|
16
|
+
// The Spinner ships with an asymmetric default margin (top, not bottom),
|
|
17
|
+
// which offsets it within the centered icon slot. Reset it so the flex
|
|
18
|
+
// centering above keeps the spinner vertically centered against the text.
|
|
19
|
+
margin: 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.editor-upload-progress-snackbar__check svg {
|
|
23
|
+
// Render at the full icon size: the check glyph only occupies the center of
|
|
24
|
+
// its viewBox, so a smaller size makes the mark look faint next to the text.
|
|
25
|
+
width: $icon-size;
|
|
26
|
+
height: $icon-size;
|
|
27
|
+
// The icon ships without a fill, which renders black and disappears against
|
|
28
|
+
// the dark snackbar. Inherit the snackbar's white text color instead.
|
|
29
|
+
fill: currentColor;
|
|
30
|
+
}
|