@wordpress/editor 14.7.0 → 14.7.1-next.5368f64a9.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.
- package/README.md +4 -0
- package/build/bindings/post-meta.js +2 -1
- package/build/bindings/post-meta.js.map +1 -1
- package/build/components/editor/index.js +2 -2
- package/build/components/editor/index.js.map +1 -1
- package/build/components/global-styles-provider/index.js +3 -4
- package/build/components/global-styles-provider/index.js.map +1 -1
- package/build/components/header/index.js +4 -4
- package/build/components/header/index.js.map +1 -1
- package/build/components/inserter-sidebar/index.js +2 -5
- package/build/components/inserter-sidebar/index.js.map +1 -1
- package/build/components/post-publish-panel/maybe-upload-media.js +151 -32
- package/build/components/post-publish-panel/maybe-upload-media.js.map +1 -1
- package/build/components/post-publish-panel/media-util.js +79 -0
- package/build/components/post-publish-panel/media-util.js.map +1 -0
- package/build/components/post-trash/check.js +2 -1
- package/build/components/post-trash/check.js.map +1 -1
- package/build/components/post-trash/index.js +22 -8
- package/build/components/post-trash/index.js.map +1 -1
- package/build/components/preferences-modal/index.js +28 -1
- package/build/components/preferences-modal/index.js.map +1 -1
- package/build/components/provider/index.js +34 -8
- package/build/components/provider/index.js.map +1 -1
- package/build/components/provider/use-block-editor-settings.js +9 -4
- package/build/components/provider/use-block-editor-settings.js.map +1 -1
- package/build/components/sidebar/post-summary.js +3 -0
- package/build/components/sidebar/post-summary.js.map +1 -1
- package/build/components/start-page-options/index.js +16 -19
- package/build/components/start-page-options/index.js.map +1 -1
- package/build/components/start-template-options/index.js +2 -4
- package/build/components/start-template-options/index.js.map +1 -1
- package/build/components/table-of-contents/index.js +2 -4
- package/build/components/table-of-contents/index.js.map +1 -1
- package/build/components/text-editor/index.js +2 -4
- package/build/components/text-editor/index.js.map +1 -1
- package/build/components/visual-editor/edit-template-blocks-notification.js +1 -1
- package/build/components/visual-editor/edit-template-blocks-notification.js.map +1 -1
- package/build/dataviews/actions/reset-post.js +4 -8
- package/build/dataviews/actions/reset-post.js.map +1 -1
- package/build/dataviews/actions/trash-post.js +4 -8
- package/build/dataviews/actions/trash-post.js.map +1 -1
- package/build/store/selectors.js +7 -3
- package/build/store/selectors.js.map +1 -1
- package/build-module/bindings/post-meta.js +2 -1
- package/build-module/bindings/post-meta.js.map +1 -1
- package/build-module/components/editor/index.js +2 -2
- package/build-module/components/editor/index.js.map +1 -1
- package/build-module/components/global-styles-provider/index.js +3 -4
- package/build-module/components/global-styles-provider/index.js.map +1 -1
- package/build-module/components/header/index.js +4 -4
- package/build-module/components/header/index.js.map +1 -1
- package/build-module/components/inserter-sidebar/index.js +2 -5
- package/build-module/components/inserter-sidebar/index.js.map +1 -1
- package/build-module/components/post-publish-panel/maybe-upload-media.js +150 -31
- package/build-module/components/post-publish-panel/maybe-upload-media.js.map +1 -1
- package/build-module/components/post-publish-panel/media-util.js +72 -0
- package/build-module/components/post-publish-panel/media-util.js.map +1 -0
- package/build-module/components/post-trash/check.js +2 -1
- package/build-module/components/post-trash/check.js.map +1 -1
- package/build-module/components/post-trash/index.js +23 -11
- package/build-module/components/post-trash/index.js.map +1 -1
- package/build-module/components/preferences-modal/index.js +28 -1
- package/build-module/components/preferences-modal/index.js.map +1 -1
- package/build-module/components/provider/index.js +35 -9
- package/build-module/components/provider/index.js.map +1 -1
- package/build-module/components/provider/use-block-editor-settings.js +9 -4
- package/build-module/components/provider/use-block-editor-settings.js.map +1 -1
- package/build-module/components/sidebar/post-summary.js +3 -0
- package/build-module/components/sidebar/post-summary.js.map +1 -1
- package/build-module/components/start-page-options/index.js +17 -21
- package/build-module/components/start-page-options/index.js.map +1 -1
- package/build-module/components/start-template-options/index.js +2 -4
- package/build-module/components/start-template-options/index.js.map +1 -1
- package/build-module/components/table-of-contents/index.js +2 -4
- package/build-module/components/table-of-contents/index.js.map +1 -1
- package/build-module/components/text-editor/index.js +2 -4
- package/build-module/components/text-editor/index.js.map +1 -1
- package/build-module/components/visual-editor/edit-template-blocks-notification.js +1 -1
- package/build-module/components/visual-editor/edit-template-blocks-notification.js.map +1 -1
- package/build-module/dataviews/actions/reset-post.js +4 -8
- package/build-module/dataviews/actions/reset-post.js.map +1 -1
- package/build-module/dataviews/actions/trash-post.js +4 -8
- package/build-module/dataviews/actions/trash-post.js.map +1 -1
- package/build-module/store/selectors.js +6 -2
- package/build-module/store/selectors.js.map +1 -1
- package/build-style/style-rtl.css +2 -2
- package/build-style/style.css +2 -2
- package/build-types/components/global-styles-provider/index.d.ts.map +1 -1
- package/build-types/components/header/back-button.d.ts.map +1 -1
- package/build-types/components/inserter-sidebar/index.d.ts.map +1 -1
- package/build-types/components/more-menu/tools-more-menu-group.d.ts.map +1 -1
- package/build-types/components/more-menu/view-more-menu-group.d.ts.map +1 -1
- package/build-types/components/plugin-document-setting-panel/index.d.ts.map +1 -1
- package/build-types/components/plugin-post-publish-panel/index.d.ts.map +1 -1
- package/build-types/components/plugin-post-status-info/index.d.ts.map +1 -1
- package/build-types/components/plugin-pre-publish-panel/index.d.ts.map +1 -1
- package/build-types/components/post-excerpt/plugin.d.ts.map +1 -1
- package/build-types/components/post-publish-panel/maybe-upload-media.d.ts +1 -1
- package/build-types/components/post-publish-panel/maybe-upload-media.d.ts.map +1 -1
- package/build-types/components/post-publish-panel/media-util.d.ts +20 -0
- package/build-types/components/post-publish-panel/media-util.d.ts.map +1 -0
- package/build-types/components/post-trash/check.d.ts.map +1 -1
- package/build-types/components/post-trash/index.d.ts +4 -1
- package/build-types/components/post-trash/index.d.ts.map +1 -1
- package/build-types/components/preferences-modal/enable-plugin-document-setting-panel.d.ts.map +1 -1
- package/build-types/components/preferences-modal/index.d.ts.map +1 -1
- package/build-types/components/provider/index.d.ts.map +1 -1
- package/build-types/components/provider/use-block-editor-settings.d.ts.map +1 -1
- package/build-types/components/save-publish-panels/index.d.ts.map +1 -1
- package/build-types/components/sidebar/post-summary.d.ts.map +1 -1
- package/build-types/components/start-page-options/index.d.ts +1 -0
- package/build-types/components/start-page-options/index.d.ts.map +1 -1
- package/build-types/components/start-template-options/index.d.ts.map +1 -1
- package/build-types/components/text-editor/index.d.ts.map +1 -1
- package/build-types/components/visual-editor/edit-template-blocks-notification.d.ts.map +1 -1
- package/build-types/dataviews/actions/reset-post.d.ts.map +1 -1
- package/build-types/dataviews/actions/trash-post.d.ts.map +1 -1
- package/build-types/store/selectors.d.ts +8 -8
- package/build-types/store/selectors.d.ts.map +1 -1
- package/package.json +36 -36
- package/src/bindings/post-meta.js +1 -1
- package/src/components/editor/index.js +1 -1
- package/src/components/global-styles-provider/index.js +11 -7
- package/src/components/header/index.js +3 -3
- package/src/components/header/style.scss +1 -1
- package/src/components/inserter-sidebar/index.js +2 -5
- package/src/components/post-publish-panel/maybe-upload-media.js +149 -48
- package/src/components/post-publish-panel/media-util.js +87 -0
- package/src/components/post-publish-panel/test/media-util.js +118 -0
- package/src/components/post-trash/check.js +5 -2
- package/src/components/post-trash/index.js +23 -12
- package/src/components/preferences-modal/index.js +227 -172
- package/src/components/provider/index.js +42 -10
- package/src/components/provider/use-block-editor-settings.js +11 -6
- package/src/components/sidebar/post-summary.js +4 -0
- package/src/components/start-page-options/index.js +28 -26
- package/src/components/start-template-options/index.js +1 -2
- package/src/components/table-of-contents/index.js +1 -2
- package/src/components/text-editor/index.js +1 -2
- package/src/components/text-editor/style.scss +1 -1
- package/src/components/visual-editor/edit-template-blocks-notification.js +4 -1
- package/src/dataviews/actions/reset-post.tsx +2 -4
- package/src/dataviews/actions/trash-post.tsx +2 -4
- package/src/store/selectors.js +9 -3
- package/tsconfig.json +1 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -14,6 +14,11 @@ import { store as blockEditorStore } from '@wordpress/block-editor';
|
|
|
14
14
|
import { useState } from '@wordpress/element';
|
|
15
15
|
import { isBlobURL } from '@wordpress/blob';
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Internal dependencies
|
|
19
|
+
*/
|
|
20
|
+
import { fetchMedia } from './media-util';
|
|
21
|
+
|
|
17
22
|
function flattenBlocks( blocks ) {
|
|
18
23
|
const result = [];
|
|
19
24
|
|
|
@@ -25,7 +30,53 @@ function flattenBlocks( blocks ) {
|
|
|
25
30
|
return result;
|
|
26
31
|
}
|
|
27
32
|
|
|
28
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Determine whether a block has external media.
|
|
35
|
+
*
|
|
36
|
+
* Different blocks use different attribute names (and potentially
|
|
37
|
+
* different logic as well) in determining whether the media is
|
|
38
|
+
* present, and whether it's external.
|
|
39
|
+
*
|
|
40
|
+
* @param {{name: string, attributes: Object}} block The block.
|
|
41
|
+
* @return {boolean?} Whether the block has external media
|
|
42
|
+
*/
|
|
43
|
+
function hasExternalMedia( block ) {
|
|
44
|
+
if ( block.name === 'core/image' || block.name === 'core/cover' ) {
|
|
45
|
+
return block.attributes.url && ! block.attributes.id;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if ( block.name === 'core/media-text' ) {
|
|
49
|
+
return block.attributes.mediaUrl && ! block.attributes.mediaId;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Retrieve media info from a block.
|
|
57
|
+
*
|
|
58
|
+
* Different blocks use different attribute names, so we need this
|
|
59
|
+
* function to normalize things into a consistent naming scheme.
|
|
60
|
+
*
|
|
61
|
+
* @param {{name: string, attributes: Object}} block The block.
|
|
62
|
+
* @return {{url: ?string, alt: ?string, id: ?number}} The media info for the block.
|
|
63
|
+
*/
|
|
64
|
+
function getMediaInfo( block ) {
|
|
65
|
+
if ( block.name === 'core/image' || block.name === 'core/cover' ) {
|
|
66
|
+
const { url, alt, id } = block.attributes;
|
|
67
|
+
return { url, alt, id };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if ( block.name === 'core/media-text' ) {
|
|
71
|
+
const { mediaUrl: url, mediaAlt: alt, mediaId: id } = block.attributes;
|
|
72
|
+
return { url, alt, id };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Image component to represent a single image in the upload dialog.
|
|
79
|
+
function Image( { clientId, alt, url } ) {
|
|
29
80
|
const { selectBlock } = useDispatch( blockEditorStore );
|
|
30
81
|
return (
|
|
31
82
|
<motion.img
|
|
@@ -33,17 +84,17 @@ function Image( block ) {
|
|
|
33
84
|
role="button"
|
|
34
85
|
aria-label={ __( 'Select image block.' ) }
|
|
35
86
|
onClick={ () => {
|
|
36
|
-
selectBlock(
|
|
87
|
+
selectBlock( clientId );
|
|
37
88
|
} }
|
|
38
89
|
onKeyDown={ ( event ) => {
|
|
39
90
|
if ( event.key === 'Enter' || event.key === ' ' ) {
|
|
40
|
-
selectBlock(
|
|
91
|
+
selectBlock( clientId );
|
|
41
92
|
event.preventDefault();
|
|
42
93
|
}
|
|
43
94
|
} }
|
|
44
|
-
key={
|
|
45
|
-
alt={
|
|
46
|
-
src={
|
|
95
|
+
key={ clientId }
|
|
96
|
+
alt={ alt }
|
|
97
|
+
src={ url }
|
|
47
98
|
animate={ { opacity: 1 } }
|
|
48
99
|
exit={ { opacity: 0, scale: 0 } }
|
|
49
100
|
style={ {
|
|
@@ -58,7 +109,7 @@ function Image( block ) {
|
|
|
58
109
|
);
|
|
59
110
|
}
|
|
60
111
|
|
|
61
|
-
export default function
|
|
112
|
+
export default function MaybeUploadMediaPanel() {
|
|
62
113
|
const [ isUploading, setIsUploading ] = useState( false );
|
|
63
114
|
const [ isAnimating, setIsAnimating ] = useState( false );
|
|
64
115
|
const [ hadUploadError, setHadUploadError ] = useState( false );
|
|
@@ -69,15 +120,14 @@ export default function PostFormatPanel() {
|
|
|
69
120
|
} ),
|
|
70
121
|
[]
|
|
71
122
|
);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
! block.attributes.id
|
|
123
|
+
|
|
124
|
+
// Get a list of blocks with external media.
|
|
125
|
+
const blocksWithExternalMedia = flattenBlocks( editorBlocks ).filter(
|
|
126
|
+
( block ) => hasExternalMedia( block )
|
|
77
127
|
);
|
|
78
128
|
const { updateBlockAttributes } = useDispatch( blockEditorStore );
|
|
79
129
|
|
|
80
|
-
if ( ! mediaUpload || !
|
|
130
|
+
if ( ! mediaUpload || ! blocksWithExternalMedia.length ) {
|
|
81
131
|
return null;
|
|
82
132
|
}
|
|
83
133
|
|
|
@@ -88,43 +138,86 @@ export default function PostFormatPanel() {
|
|
|
88
138
|
</span>,
|
|
89
139
|
];
|
|
90
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Update an individual block to point to newly-added library media.
|
|
143
|
+
*
|
|
144
|
+
* Different blocks use different attribute names, so we need this
|
|
145
|
+
* function to ensure we modify the correct attributes for each type.
|
|
146
|
+
*
|
|
147
|
+
* @param {{name: string, attributes: Object}} block The block.
|
|
148
|
+
* @param {{id: number, url: string}} media Media library file info.
|
|
149
|
+
*/
|
|
150
|
+
function updateBlockWithUploadedMedia( block, media ) {
|
|
151
|
+
if ( block.name === 'core/image' || block.name === 'core/cover' ) {
|
|
152
|
+
updateBlockAttributes( block.clientId, {
|
|
153
|
+
id: media.id,
|
|
154
|
+
url: media.url,
|
|
155
|
+
} );
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if ( block.name === 'core/media-text' ) {
|
|
159
|
+
updateBlockAttributes( block.clientId, {
|
|
160
|
+
mediaId: media.id,
|
|
161
|
+
mediaUrl: media.url,
|
|
162
|
+
} );
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Handle fetching and uploading all external media in the post.
|
|
91
167
|
function uploadImages() {
|
|
92
168
|
setIsUploading( true );
|
|
93
169
|
setHadUploadError( false );
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
170
|
+
|
|
171
|
+
// Multiple blocks can be using the same URL, so we
|
|
172
|
+
// should ensure we only fetch and upload each of them once.
|
|
173
|
+
const mediaUrls = new Set(
|
|
174
|
+
blocksWithExternalMedia.map( ( block ) => {
|
|
175
|
+
const { url } = getMediaInfo( block );
|
|
176
|
+
return url;
|
|
177
|
+
} )
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
// Create an upload promise for each URL, that we can wait for in all
|
|
181
|
+
// blocks that make use of that media.
|
|
182
|
+
const uploadPromises = Object.fromEntries(
|
|
183
|
+
Object.entries( fetchMedia( [ ...mediaUrls ] ) ).map(
|
|
184
|
+
( [ url, filePromise ] ) => {
|
|
185
|
+
const uploadPromise = filePromise.then(
|
|
186
|
+
( blob ) =>
|
|
187
|
+
new Promise( ( resolve, reject ) => {
|
|
188
|
+
mediaUpload( {
|
|
189
|
+
filesList: [ blob ],
|
|
190
|
+
onFileChange: ( [ media ] ) => {
|
|
191
|
+
if ( isBlobURL( media.url ) ) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
resolve( media );
|
|
196
|
+
},
|
|
197
|
+
onError() {
|
|
198
|
+
reject();
|
|
199
|
+
},
|
|
200
|
+
} );
|
|
201
|
+
} )
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
return [ url, uploadPromise ];
|
|
205
|
+
}
|
|
127
206
|
)
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
// Wait for all blocks to be updated with library media.
|
|
210
|
+
Promise.allSettled(
|
|
211
|
+
blocksWithExternalMedia.map( ( block ) => {
|
|
212
|
+
const { url } = getMediaInfo( block );
|
|
213
|
+
|
|
214
|
+
return uploadPromises[ url ]
|
|
215
|
+
.then( ( media ) =>
|
|
216
|
+
updateBlockWithUploadedMedia( block, media )
|
|
217
|
+
)
|
|
218
|
+
.then( () => setIsAnimating( true ) )
|
|
219
|
+
.catch( () => setHadUploadError( true ) );
|
|
220
|
+
} )
|
|
128
221
|
).finally( () => {
|
|
129
222
|
setIsUploading( false );
|
|
130
223
|
} );
|
|
@@ -147,8 +240,16 @@ export default function PostFormatPanel() {
|
|
|
147
240
|
<AnimatePresence
|
|
148
241
|
onExitComplete={ () => setIsAnimating( false ) }
|
|
149
242
|
>
|
|
150
|
-
{
|
|
151
|
-
|
|
243
|
+
{ blocksWithExternalMedia.map( ( block ) => {
|
|
244
|
+
const { url, alt } = getMediaInfo( block );
|
|
245
|
+
return (
|
|
246
|
+
<Image
|
|
247
|
+
key={ block.clientId }
|
|
248
|
+
clientId={ block.clientId }
|
|
249
|
+
url={ url }
|
|
250
|
+
alt={ alt }
|
|
251
|
+
/>
|
|
252
|
+
);
|
|
152
253
|
} ) }
|
|
153
254
|
</AnimatePresence>
|
|
154
255
|
{ isUploading || isAnimating ? (
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { v4 as uuid } from 'uuid';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* WordPress dependencies
|
|
8
|
+
*/
|
|
9
|
+
import { getFilename } from '@wordpress/url';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Generate a list of unique basenames given a list of URLs.
|
|
13
|
+
*
|
|
14
|
+
* We want all basenames to be unique, since sometimes the extension
|
|
15
|
+
* doesn't reflect the mime type, and may end up getting changed by
|
|
16
|
+
* the server, on upload.
|
|
17
|
+
*
|
|
18
|
+
* @param {string[]} urls The list of URLs
|
|
19
|
+
* @return {Record< string, string >} A URL => basename record.
|
|
20
|
+
*/
|
|
21
|
+
export function generateUniqueBasenames( urls ) {
|
|
22
|
+
const basenames = new Set();
|
|
23
|
+
|
|
24
|
+
return Object.fromEntries(
|
|
25
|
+
urls.map( ( url ) => {
|
|
26
|
+
// We prefer to match the remote filename, if possible.
|
|
27
|
+
const filename = getFilename( url );
|
|
28
|
+
let basename = '';
|
|
29
|
+
|
|
30
|
+
if ( filename ) {
|
|
31
|
+
const parts = filename.split( '.' );
|
|
32
|
+
if ( parts.length > 1 ) {
|
|
33
|
+
// Assume the last part is the extension.
|
|
34
|
+
parts.pop();
|
|
35
|
+
}
|
|
36
|
+
basename = parts.join( '.' );
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if ( ! basename ) {
|
|
40
|
+
// It looks like we don't have a basename, so let's use a UUID.
|
|
41
|
+
basename = uuid();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if ( basenames.has( basename ) ) {
|
|
45
|
+
// Append a UUID to deduplicate the basename.
|
|
46
|
+
// The server will try to deduplicate on its own if we don't do this,
|
|
47
|
+
// but it may run into a race condition
|
|
48
|
+
// (see https://github.com/WordPress/gutenberg/issues/64899).
|
|
49
|
+
// Deduplicating the filenames before uploading is safer.
|
|
50
|
+
basename = `${ basename }-${ uuid() }`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
basenames.add( basename );
|
|
54
|
+
|
|
55
|
+
return [ url, basename ];
|
|
56
|
+
} )
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Fetch a list of URLs, turning those into promises for files with
|
|
62
|
+
* unique filenames.
|
|
63
|
+
*
|
|
64
|
+
* @param {string[]} urls The list of URLs
|
|
65
|
+
* @return {Record< string, Promise< File > >} A URL => File promise record.
|
|
66
|
+
*/
|
|
67
|
+
export function fetchMedia( urls ) {
|
|
68
|
+
return Object.fromEntries(
|
|
69
|
+
Object.entries( generateUniqueBasenames( urls ) ).map(
|
|
70
|
+
( [ url, basename ] ) => {
|
|
71
|
+
const filePromise = window
|
|
72
|
+
.fetch( url.includes( '?' ) ? url : url + '?' )
|
|
73
|
+
.then( ( response ) => response.blob() )
|
|
74
|
+
.then( ( blob ) => {
|
|
75
|
+
// The server will reject the upload if it doesn't have an extension,
|
|
76
|
+
// even though it'll rewrite the file name to match the mime type.
|
|
77
|
+
// Here we provide it with a safe extension to get it past that check.
|
|
78
|
+
return new File( [ blob ], `${ basename }.png`, {
|
|
79
|
+
type: blob.type,
|
|
80
|
+
} );
|
|
81
|
+
} );
|
|
82
|
+
|
|
83
|
+
return [ url, filePromise ];
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
);
|
|
87
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { generateUniqueBasenames } from '../media-util';
|
|
5
|
+
|
|
6
|
+
describe( 'generateUniqueBasenames', () => {
|
|
7
|
+
it( 'should prefer the original basenames', () => {
|
|
8
|
+
const urls = [
|
|
9
|
+
'https://example.com/images/image1.jpg',
|
|
10
|
+
'https://example.com/images/image2.jpg',
|
|
11
|
+
'https://example.com/images/image3.jpg',
|
|
12
|
+
'https://example.com/images/image4.jpg',
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
expect( generateUniqueBasenames( urls ) ).toEqual( {
|
|
16
|
+
'https://example.com/images/image1.jpg': 'image1',
|
|
17
|
+
'https://example.com/images/image2.jpg': 'image2',
|
|
18
|
+
'https://example.com/images/image3.jpg': 'image3',
|
|
19
|
+
'https://example.com/images/image4.jpg': 'image4',
|
|
20
|
+
} );
|
|
21
|
+
} );
|
|
22
|
+
|
|
23
|
+
it( 'should handle filenames with no extensions', () => {
|
|
24
|
+
const urls = [
|
|
25
|
+
'https://example.com/images/image1',
|
|
26
|
+
'https://example.com/images/image2',
|
|
27
|
+
'https://example.com/images/image3',
|
|
28
|
+
'https://example.com/images/image4',
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
expect( generateUniqueBasenames( urls ) ).toEqual( {
|
|
32
|
+
'https://example.com/images/image1': 'image1',
|
|
33
|
+
'https://example.com/images/image2': 'image2',
|
|
34
|
+
'https://example.com/images/image3': 'image3',
|
|
35
|
+
'https://example.com/images/image4': 'image4',
|
|
36
|
+
} );
|
|
37
|
+
} );
|
|
38
|
+
|
|
39
|
+
it( 'should handle query parameters correctly', () => {
|
|
40
|
+
const urls = [
|
|
41
|
+
'https://example.com/images/image1.jpg?a=notafile.npg',
|
|
42
|
+
'https://example.com/images/image2.jpg?a=notafile.npg',
|
|
43
|
+
'https://example.com/images/image3.jpg?a=notafile.npg',
|
|
44
|
+
'https://example.com/images/image4.jpg?a=notafile.npg',
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
expect( generateUniqueBasenames( urls ) ).toEqual( {
|
|
48
|
+
'https://example.com/images/image1.jpg?a=notafile.npg': 'image1',
|
|
49
|
+
'https://example.com/images/image2.jpg?a=notafile.npg': 'image2',
|
|
50
|
+
'https://example.com/images/image3.jpg?a=notafile.npg': 'image3',
|
|
51
|
+
'https://example.com/images/image4.jpg?a=notafile.npg': 'image4',
|
|
52
|
+
} );
|
|
53
|
+
} );
|
|
54
|
+
|
|
55
|
+
it( 'should deduplicate identical filenames', () => {
|
|
56
|
+
const urls = [
|
|
57
|
+
'https://example.com/image1/image.jpg',
|
|
58
|
+
'https://example.com/image2/image.jpg',
|
|
59
|
+
'https://example.com/image3/image.jpg',
|
|
60
|
+
'https://example.com/image4/image.jpg',
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
const results = generateUniqueBasenames( urls );
|
|
64
|
+
const resultLength = Object.entries( results ).length;
|
|
65
|
+
expect( resultLength ).toBe( urls.length );
|
|
66
|
+
|
|
67
|
+
const basenames = new Set( Object.values( results ) );
|
|
68
|
+
expect( basenames.size ).toBe( resultLength );
|
|
69
|
+
} );
|
|
70
|
+
|
|
71
|
+
it( 'should deduplicate identical basenames', () => {
|
|
72
|
+
const urls = [
|
|
73
|
+
'https://example.com/images/image.jpg',
|
|
74
|
+
'https://example.com/images/image.png',
|
|
75
|
+
'https://example.com/images/image.webp',
|
|
76
|
+
'https://example.com/images/image.avif',
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
const results = generateUniqueBasenames( urls );
|
|
80
|
+
const resultLength = Object.entries( results ).length;
|
|
81
|
+
expect( resultLength ).toBe( urls.length );
|
|
82
|
+
|
|
83
|
+
const basenames = new Set( Object.values( results ) );
|
|
84
|
+
expect( basenames.size ).toBe( resultLength );
|
|
85
|
+
} );
|
|
86
|
+
|
|
87
|
+
it( 'should deduplicate filenames without extensions', () => {
|
|
88
|
+
const urls = [
|
|
89
|
+
'https://example.com/image1/image',
|
|
90
|
+
'https://example.com/image2/image',
|
|
91
|
+
'https://example.com/image3/image',
|
|
92
|
+
'https://example.com/image4/image',
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
const results = generateUniqueBasenames( urls );
|
|
96
|
+
const resultLength = Object.entries( results ).length;
|
|
97
|
+
expect( resultLength ).toBe( urls.length );
|
|
98
|
+
|
|
99
|
+
const basenames = new Set( Object.values( results ) );
|
|
100
|
+
expect( basenames.size ).toBe( resultLength );
|
|
101
|
+
} );
|
|
102
|
+
|
|
103
|
+
it( 'should deduplicate paths with no filename', () => {
|
|
104
|
+
const urls = [
|
|
105
|
+
'https://example.com/image1/dir/',
|
|
106
|
+
'https://example.com/image2/dir/',
|
|
107
|
+
'https://example.com/image3/dir/',
|
|
108
|
+
'https://example.com/image4/dir/',
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
const results = generateUniqueBasenames( urls );
|
|
112
|
+
const resultLength = Object.entries( results ).length;
|
|
113
|
+
expect( resultLength ).toBe( urls.length );
|
|
114
|
+
|
|
115
|
+
const basenames = new Set( Object.values( results ) );
|
|
116
|
+
expect( basenames.size ).toBe( resultLength );
|
|
117
|
+
} );
|
|
118
|
+
} );
|
|
@@ -8,6 +8,7 @@ import { store as coreStore } from '@wordpress/core-data';
|
|
|
8
8
|
* Internal dependencies
|
|
9
9
|
*/
|
|
10
10
|
import { store as editorStore } from '../../store';
|
|
11
|
+
import { GLOBAL_POST_TYPES } from '../../store/constants';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Wrapper component that renders its children only if the post can trashed.
|
|
@@ -34,10 +35,12 @@ export default function PostTrashCheck( { children } ) {
|
|
|
34
35
|
: false;
|
|
35
36
|
|
|
36
37
|
return {
|
|
37
|
-
canTrashPost:
|
|
38
|
+
canTrashPost:
|
|
39
|
+
( ! isNew || postId ) &&
|
|
40
|
+
canUserDelete &&
|
|
41
|
+
! GLOBAL_POST_TYPES.includes( postType ),
|
|
38
42
|
};
|
|
39
43
|
}, [] );
|
|
40
|
-
|
|
41
44
|
if ( ! canTrashPost ) {
|
|
42
45
|
return null;
|
|
43
46
|
}
|
|
@@ -1,31 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WordPress dependencies
|
|
3
3
|
*/
|
|
4
|
-
import { __ } from '@wordpress/i18n';
|
|
4
|
+
import { __, sprintf } from '@wordpress/i18n';
|
|
5
5
|
import {
|
|
6
6
|
Button,
|
|
7
7
|
__experimentalConfirmDialog as ConfirmDialog,
|
|
8
8
|
} from '@wordpress/components';
|
|
9
|
-
import { useSelect, useDispatch } from '@wordpress/data';
|
|
9
|
+
import { useSelect, useDispatch, useRegistry } from '@wordpress/data';
|
|
10
10
|
import { useState } from '@wordpress/element';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Internal dependencies
|
|
14
14
|
*/
|
|
15
15
|
import { store as editorStore } from '../../store';
|
|
16
|
+
import PostTrashCheck from './check';
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Displays the Post Trash Button and Confirm Dialog in the Editor.
|
|
19
20
|
*
|
|
21
|
+
* @param {?{onActionPerformed: Object}} An object containing the onActionPerformed function.
|
|
20
22
|
* @return {JSX.Element|null} The rendered PostTrash component.
|
|
21
23
|
*/
|
|
22
|
-
export default function PostTrash() {
|
|
23
|
-
const
|
|
24
|
+
export default function PostTrash( { onActionPerformed } ) {
|
|
25
|
+
const registry = useRegistry();
|
|
26
|
+
const { isNew, isDeleting, postId, title } = useSelect( ( select ) => {
|
|
24
27
|
const store = select( editorStore );
|
|
25
28
|
return {
|
|
26
29
|
isNew: store.isEditedPostNew(),
|
|
27
30
|
isDeleting: store.isDeletingPost(),
|
|
28
31
|
postId: store.getCurrentPostId(),
|
|
32
|
+
title: store.getCurrentPostAttribute( 'title' ),
|
|
29
33
|
};
|
|
30
34
|
}, [] );
|
|
31
35
|
const { trashPost } = useDispatch( editorStore );
|
|
@@ -35,13 +39,18 @@ export default function PostTrash() {
|
|
|
35
39
|
return null;
|
|
36
40
|
}
|
|
37
41
|
|
|
38
|
-
const handleConfirm = () => {
|
|
42
|
+
const handleConfirm = async () => {
|
|
39
43
|
setShowConfirmDialog( false );
|
|
40
|
-
trashPost();
|
|
44
|
+
await trashPost();
|
|
45
|
+
const item = await registry
|
|
46
|
+
.resolveSelect( editorStore )
|
|
47
|
+
.getCurrentPost();
|
|
48
|
+
// After the post is trashed, we want to trigger the onActionPerformed callback, so the user is redirect
|
|
49
|
+
// to the post view depending on if the user is on post editor or site editor.
|
|
50
|
+
onActionPerformed?.( 'move-to-trash', [ item ] );
|
|
41
51
|
};
|
|
42
|
-
|
|
43
52
|
return (
|
|
44
|
-
|
|
53
|
+
<PostTrashCheck>
|
|
45
54
|
<Button
|
|
46
55
|
__next40pxDefaultSize
|
|
47
56
|
className="editor-post-trash"
|
|
@@ -60,12 +69,14 @@ export default function PostTrash() {
|
|
|
60
69
|
onConfirm={ handleConfirm }
|
|
61
70
|
onCancel={ () => setShowConfirmDialog( false ) }
|
|
62
71
|
confirmButtonText={ __( 'Move to trash' ) }
|
|
63
|
-
size="
|
|
72
|
+
size="small"
|
|
64
73
|
>
|
|
65
|
-
{
|
|
66
|
-
|
|
74
|
+
{ sprintf(
|
|
75
|
+
// translators: %s: The item's title.
|
|
76
|
+
__( 'Are you sure you want to move "%s" to the trash?' ),
|
|
77
|
+
title
|
|
67
78
|
) }
|
|
68
79
|
</ConfirmDialog>
|
|
69
|
-
|
|
80
|
+
</PostTrashCheck>
|
|
70
81
|
);
|
|
71
82
|
}
|