PluginProbe
Gutenberg / 18.9.0
Gutenberg v18.9.0
24.0.0 23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 All 403 releases
gutenberg / build / media-utils / index.js

index.js in Gutenberg 18.9.0, at build/media-utils/index.js

712 lines 22.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /******/ (() => { // webpackBootstrap
2 /******/ "use strict";
3 /******/ // The require scope
4 /******/ var __webpack_require__ = {};
5 /******/
6 /************************************************************************/
7 /******/ /* webpack/runtime/compat get default export */
8 /******/ (() => {
9 /******/ // getDefaultExport function for compatibility with non-harmony modules
10 /******/ __webpack_require__.n = (module) => {
11 /******/ var getter = module && module.__esModule ?
12 /******/ () => (module['default']) :
13 /******/ () => (module);
14 /******/ __webpack_require__.d(getter, { a: getter });
15 /******/ return getter;
16 /******/ };
17 /******/ })();
18 /******/
19 /******/ /* webpack/runtime/define property getters */
20 /******/ (() => {
21 /******/ // define getter functions for harmony exports
22 /******/ __webpack_require__.d = (exports, definition) => {
23 /******/ for(var key in definition) {
24 /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
25 /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
26 /******/ }
27 /******/ }
28 /******/ };
29 /******/ })();
30 /******/
31 /******/ /* webpack/runtime/hasOwnProperty shorthand */
32 /******/ (() => {
33 /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
34 /******/ })();
35 /******/
36 /******/ /* webpack/runtime/make namespace object */
37 /******/ (() => {
38 /******/ // define __esModule on exports
39 /******/ __webpack_require__.r = (exports) => {
40 /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
41 /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
42 /******/ }
43 /******/ Object.defineProperty(exports, '__esModule', { value: true });
44 /******/ };
45 /******/ })();
46 /******/
47 /************************************************************************/
48 var __webpack_exports__ = {};
49 // ESM COMPAT FLAG
50 __webpack_require__.r(__webpack_exports__);
51
52 // EXPORTS
53 __webpack_require__.d(__webpack_exports__, {
54 MediaUpload: () => (/* reexport */ media_upload),
55 uploadMedia: () => (/* reexport */ uploadMedia)
56 });
57
58 ;// CONCATENATED MODULE: external ["wp","element"]
59 const external_wp_element_namespaceObject = window["wp"]["element"];
60 ;// CONCATENATED MODULE: external ["wp","i18n"]
61 const external_wp_i18n_namespaceObject = window["wp"]["i18n"];
62 ;// CONCATENATED MODULE: ./packages/media-utils/build-module/components/media-upload/index.js
63 /**
64 * WordPress dependencies
65 */
66
67
68 const DEFAULT_EMPTY_GALLERY = [];
69
70 /**
71 * Prepares the Featured Image toolbars and frames.
72 *
73 * @return {window.wp.media.view.MediaFrame.Select} The default media workflow.
74 */
75 const getFeaturedImageMediaFrame = () => {
76 const {
77 wp
78 } = window;
79 return wp.media.view.MediaFrame.Select.extend({
80 /**
81 * Enables the Set Featured Image Button.
82 *
83 * @param {Object} toolbar toolbar for featured image state
84 * @return {void}
85 */
86 featuredImageToolbar(toolbar) {
87 this.createSelectToolbar(toolbar, {
88 text: wp.media.view.l10n.setFeaturedImage,
89 state: this.options.state
90 });
91 },
92 /**
93 * Handle the edit state requirements of selected media item.
94 *
95 * @return {void}
96 */
97 editState() {
98 const selection = this.state('featured-image').get('selection');
99 const view = new wp.media.view.EditImage({
100 model: selection.single(),
101 controller: this
102 }).render();
103
104 // Set the view to the EditImage frame using the selected image.
105 this.content.set(view);
106
107 // After bringing in the frame, load the actual editor via an ajax call.
108 view.loadEditor();
109 },
110 /**
111 * Create the default states.
112 *
113 * @return {void}
114 */
115 createStates: function createStates() {
116 this.on('toolbar:create:featured-image', this.featuredImageToolbar, this);
117 this.on('content:render:edit-image', this.editState, this);
118 this.states.add([new wp.media.controller.FeaturedImage(), new wp.media.controller.EditImage({
119 model: this.options.editImage
120 })]);
121 }
122 });
123 };
124
125 /**
126 * Prepares the Gallery toolbars and frames.
127 *
128 * @return {window.wp.media.view.MediaFrame.Post} The default media workflow.
129 */
130 const getGalleryDetailsMediaFrame = () => {
131 const {
132 wp
133 } = window;
134 /**
135 * Custom gallery details frame.
136 *
137 * @see https://github.com/xwp/wp-core-media-widgets/blob/905edbccfc2a623b73a93dac803c5335519d7837/wp-admin/js/widgets/media-gallery-widget.js
138 * @class GalleryDetailsMediaFrame
139 * @class
140 */
141 return wp.media.view.MediaFrame.Post.extend({
142 /**
143 * Set up gallery toolbar.
144 *
145 * @return {void}
146 */
147 galleryToolbar() {
148 const editing = this.state().get('editing');
149 this.toolbar.set(new wp.media.view.Toolbar({
150 controller: this,
151 items: {
152 insert: {
153 style: 'primary',
154 text: editing ? wp.media.view.l10n.updateGallery : wp.media.view.l10n.insertGallery,
155 priority: 80,
156 requires: {
157 library: true
158 },
159 /**
160 * @fires wp.media.controller.State#update
161 */
162 click() {
163 const controller = this.controller,
164 state = controller.state();
165 controller.close();
166 state.trigger('update', state.get('library'));
167
168 // Restore and reset the default state.
169 controller.setState(controller.options.state);
170 controller.reset();
171 }
172 }
173 }
174 }));
175 },
176 /**
177 * Handle the edit state requirements of selected media item.
178 *
179 * @return {void}
180 */
181 editState() {
182 const selection = this.state('gallery').get('selection');
183 const view = new wp.media.view.EditImage({
184 model: selection.single(),
185 controller: this
186 }).render();
187
188 // Set the view to the EditImage frame using the selected image.
189 this.content.set(view);
190
191 // After bringing in the frame, load the actual editor via an ajax call.
192 view.loadEditor();
193 },
194 /**
195 * Create the default states.
196 *
197 * @return {void}
198 */
199 createStates: function createStates() {
200 this.on('toolbar:create:main-gallery', this.galleryToolbar, this);
201 this.on('content:render:edit-image', this.editState, this);
202 this.states.add([new wp.media.controller.Library({
203 id: 'gallery',
204 title: wp.media.view.l10n.createGalleryTitle,
205 priority: 40,
206 toolbar: 'main-gallery',
207 filterable: 'uploaded',
208 multiple: 'add',
209 editable: false,
210 library: wp.media.query({
211 type: 'image',
212 ...this.options.library
213 })
214 }), new wp.media.controller.EditImage({
215 model: this.options.editImage
216 }), new wp.media.controller.GalleryEdit({
217 library: this.options.selection,
218 editing: this.options.editing,
219 menu: 'gallery',
220 displaySettings: false,
221 multiple: true
222 }), new wp.media.controller.GalleryAdd()]);
223 }
224 });
225 };
226
227 // The media library image object contains numerous attributes
228 // we only need this set to display the image in the library.
229 const slimImageObject = img => {
230 const attrSet = ['sizes', 'mime', 'type', 'subtype', 'id', 'url', 'alt', 'link', 'caption'];
231 return attrSet.reduce((result, key) => {
232 if (img?.hasOwnProperty(key)) {
233 result[key] = img[key];
234 }
235 return result;
236 }, {});
237 };
238 const getAttachmentsCollection = ids => {
239 const {
240 wp
241 } = window;
242 return wp.media.query({
243 order: 'ASC',
244 orderby: 'post__in',
245 post__in: ids,
246 posts_per_page: -1,
247 query: true,
248 type: 'image'
249 });
250 };
251 class MediaUpload extends external_wp_element_namespaceObject.Component {
252 constructor() {
253 super(...arguments);
254 this.openModal = this.openModal.bind(this);
255 this.onOpen = this.onOpen.bind(this);
256 this.onSelect = this.onSelect.bind(this);
257 this.onUpdate = this.onUpdate.bind(this);
258 this.onClose = this.onClose.bind(this);
259 }
260 initializeListeners() {
261 // When an image is selected in the media frame...
262 this.frame.on('select', this.onSelect);
263 this.frame.on('update', this.onUpdate);
264 this.frame.on('open', this.onOpen);
265 this.frame.on('close', this.onClose);
266 }
267
268 /**
269 * Sets the Gallery frame and initializes listeners.
270 *
271 * @return {void}
272 */
273 buildAndSetGalleryFrame() {
274 const {
275 addToGallery = false,
276 allowedTypes,
277 multiple = false,
278 value = DEFAULT_EMPTY_GALLERY
279 } = this.props;
280
281 // If the value did not changed there is no need to rebuild the frame,
282 // we can continue to use the existing one.
283 if (value === this.lastGalleryValue) {
284 return;
285 }
286 const {
287 wp
288 } = window;
289 this.lastGalleryValue = value;
290
291 // If a frame already existed remove it.
292 if (this.frame) {
293 this.frame.remove();
294 }
295 let currentState;
296 if (addToGallery) {
297 currentState = 'gallery-library';
298 } else {
299 currentState = value && value.length ? 'gallery-edit' : 'gallery';
300 }
301 if (!this.GalleryDetailsMediaFrame) {
302 this.GalleryDetailsMediaFrame = getGalleryDetailsMediaFrame();
303 }
304 const attachments = getAttachmentsCollection(value);
305 const selection = new wp.media.model.Selection(attachments.models, {
306 props: attachments.props.toJSON(),
307 multiple
308 });
309 this.frame = new this.GalleryDetailsMediaFrame({
310 mimeType: allowedTypes,
311 state: currentState,
312 multiple,
313 selection,
314 editing: value && value.length ? true : false
315 });
316 wp.media.frame = this.frame;
317 this.initializeListeners();
318 }
319
320 /**
321 * Initializes the Media Library requirements for the featured image flow.
322 *
323 * @return {void}
324 */
325 buildAndSetFeatureImageFrame() {
326 const {
327 wp
328 } = window;
329 const {
330 value: featuredImageId,
331 multiple,
332 allowedTypes
333 } = this.props;
334 const featuredImageFrame = getFeaturedImageMediaFrame();
335 const attachments = getAttachmentsCollection(featuredImageId);
336 const selection = new wp.media.model.Selection(attachments.models, {
337 props: attachments.props.toJSON()
338 });
339 this.frame = new featuredImageFrame({
340 mimeType: allowedTypes,
341 state: 'featured-image',
342 multiple,
343 selection,
344 editing: featuredImageId
345 });
346 wp.media.frame = this.frame;
347 // In order to select the current featured image when opening
348 // the media library we have to set the appropriate settings.
349 // Currently they are set in php for the post editor, but
350 // not for site editor.
351 wp.media.view.settings.post = {
352 ...wp.media.view.settings.post,
353 featuredImageId: featuredImageId || -1
354 };
355 }
356 componentWillUnmount() {
357 this.frame?.remove();
358 }
359 onUpdate(selections) {
360 const {
361 onSelect,
362 multiple = false
363 } = this.props;
364 const state = this.frame.state();
365 const selectedImages = selections || state.get('selection');
366 if (!selectedImages || !selectedImages.models.length) {
367 return;
368 }
369 if (multiple) {
370 onSelect(selectedImages.models.map(model => slimImageObject(model.toJSON())));
371 } else {
372 onSelect(slimImageObject(selectedImages.models[0].toJSON()));
373 }
374 }
375 onSelect() {
376 const {
377 onSelect,
378 multiple = false
379 } = this.props;
380 // Get media attachment details from the frame state.
381 const attachment = this.frame.state().get('selection').toJSON();
382 onSelect(multiple ? attachment : attachment[0]);
383 }
384 onOpen() {
385 const {
386 wp
387 } = window;
388 const {
389 value
390 } = this.props;
391 this.updateCollection();
392
393 //Handle active tab in media model on model open.
394 if (this.props.mode) {
395 this.frame.content.mode(this.props.mode);
396 }
397
398 // Handle both this.props.value being either (number[]) multiple ids
399 // (for galleries) or a (number) singular id (e.g. image block).
400 const hasMedia = Array.isArray(value) ? !!value?.length : !!value;
401 if (!hasMedia) {
402 return;
403 }
404 const isGallery = this.props.gallery;
405 const selection = this.frame.state().get('selection');
406 const valueArray = Array.isArray(value) ? value : [value];
407 if (!isGallery) {
408 valueArray.forEach(id => {
409 selection.add(wp.media.attachment(id));
410 });
411 }
412
413 // Load the images so they are available in the media modal.
414 const attachments = getAttachmentsCollection(valueArray);
415
416 // Once attachments are loaded, set the current selection.
417 attachments.more().done(function () {
418 if (isGallery && attachments?.models?.length) {
419 selection.add(attachments.models);
420 }
421 });
422 }
423 onClose() {
424 const {
425 onClose
426 } = this.props;
427 if (onClose) {
428 onClose();
429 }
430 this.frame.detach();
431 }
432 updateCollection() {
433 const frameContent = this.frame.content.get();
434 if (frameContent && frameContent.collection) {
435 const collection = frameContent.collection;
436
437 // Clean all attachments we have in memory.
438 collection.toArray().forEach(model => model.trigger('destroy', model));
439
440 // Reset has more flag, if library had small amount of items all items may have been loaded before.
441 collection.mirroring._hasMore = true;
442
443 // Request items.
444 collection.more();
445 }
446 }
447 openModal() {
448 const {
449 allowedTypes,
450 gallery = false,
451 unstableFeaturedImageFlow = false,
452 modalClass,
453 multiple = false,
454 title = (0,external_wp_i18n_namespaceObject.__)('Select or Upload Media')
455 } = this.props;
456 const {
457 wp
458 } = window;
459 if (gallery) {
460 this.buildAndSetGalleryFrame();
461 } else {
462 const frameConfig = {
463 title,
464 multiple
465 };
466 if (!!allowedTypes) {
467 frameConfig.library = {
468 type: allowedTypes
469 };
470 }
471 this.frame = wp.media(frameConfig);
472 }
473 if (modalClass) {
474 this.frame.$el.addClass(modalClass);
475 }
476 if (unstableFeaturedImageFlow) {
477 this.buildAndSetFeatureImageFrame();
478 }
479 this.initializeListeners();
480 this.frame.open();
481 }
482 render() {
483 return this.props.render({
484 open: this.openModal
485 });
486 }
487 }
488 /* harmony default export */ const media_upload = (MediaUpload);
489
490 ;// CONCATENATED MODULE: ./packages/media-utils/build-module/components/index.js
491
492
493 ;// CONCATENATED MODULE: external ["wp","apiFetch"]
494 const external_wp_apiFetch_namespaceObject = window["wp"]["apiFetch"];
495 var external_wp_apiFetch_default = /*#__PURE__*/__webpack_require__.n(external_wp_apiFetch_namespaceObject);
496 ;// CONCATENATED MODULE: external ["wp","blob"]
497 const external_wp_blob_namespaceObject = window["wp"]["blob"];
498 ;// CONCATENATED MODULE: ./packages/media-utils/build-module/utils/upload-media.js
499 /**
500 * WordPress dependencies
501 */
502
503
504
505 const noop = () => {};
506
507 /**
508 * Browsers may use unexpected mime types, and they differ from browser to browser.
509 * This function computes a flexible array of mime types from the mime type structured provided by the server.
510 * Converts { jpg|jpeg|jpe: "image/jpeg" } into [ "image/jpeg", "image/jpg", "image/jpeg", "image/jpe" ]
511 * The computation of this array instead of directly using the object,
512 * solves the problem in chrome where mp3 files have audio/mp3 as mime type instead of audio/mpeg.
513 * https://bugs.chromium.org/p/chromium/issues/detail?id=227004
514 *
515 * @param {?Object} wpMimeTypesObject Mime type object received from the server.
516 * Extensions are keys separated by '|' and values are mime types associated with an extension.
517 *
518 * @return {?Array} An array of mime types or the parameter passed if it was "falsy".
519 */
520 function getMimeTypesArray(wpMimeTypesObject) {
521 if (!wpMimeTypesObject) {
522 return wpMimeTypesObject;
523 }
524 return Object.entries(wpMimeTypesObject).map(([extensionsString, mime]) => {
525 const [type] = mime.split('/');
526 const extensions = extensionsString.split('|');
527 return [mime, ...extensions.map(extension => `${type}/${extension}`)];
528 }).flat();
529 }
530
531 /**
532 * Media Upload is used by audio, image, gallery, video, and file blocks to
533 * handle uploading a media file when a file upload button is activated.
534 *
535 * TODO: future enhancement to add an upload indicator.
536 *
537 * @param {Object} $0 Parameters object passed to the function.
538 * @param {?Array} $0.allowedTypes Array with the types of media that can be uploaded, if unset all types are allowed.
539 * @param {?Object} $0.additionalData Additional data to include in the request.
540 * @param {Array} $0.filesList List of files.
541 * @param {?number} $0.maxUploadFileSize Maximum upload size in bytes allowed for the site.
542 * @param {Function} $0.onError Function called when an error happens.
543 * @param {Function} $0.onFileChange Function called each time a file or a temporary representation of the file is available.
544 * @param {?Object} $0.wpAllowedMimeTypes List of allowed mime types and file extensions.
545 */
546 async function uploadMedia({
547 allowedTypes,
548 additionalData = {},
549 filesList,
550 maxUploadFileSize,
551 onError = noop,
552 onFileChange,
553 wpAllowedMimeTypes = null
554 }) {
555 // Cast filesList to array.
556 const files = [...filesList];
557 const filesSet = [];
558 const setAndUpdateFiles = (idx, value) => {
559 (0,external_wp_blob_namespaceObject.revokeBlobURL)(filesSet[idx]?.url);
560 filesSet[idx] = value;
561 onFileChange(filesSet.filter(Boolean));
562 };
563
564 // Allowed type specified by consumer.
565 const isAllowedType = fileType => {
566 if (!allowedTypes) {
567 return true;
568 }
569 return allowedTypes.some(allowedType => {
570 // If a complete mimetype is specified verify if it matches exactly the mime type of the file.
571 if (allowedType.includes('/')) {
572 return allowedType === fileType;
573 }
574 // Otherwise a general mime type is used and we should verify if the file mimetype starts with it.
575 return fileType.startsWith(`${allowedType}/`);
576 });
577 };
578
579 // Allowed types for the current WP_User.
580 const allowedMimeTypesForUser = getMimeTypesArray(wpAllowedMimeTypes);
581 const isAllowedMimeTypeForUser = fileType => {
582 return allowedMimeTypesForUser.includes(fileType);
583 };
584 const validFiles = [];
585 for (const mediaFile of files) {
586 // Verify if user is allowed to upload this mime type.
587 // Defer to the server when type not detected.
588 if (allowedMimeTypesForUser && mediaFile.type && !isAllowedMimeTypeForUser(mediaFile.type)) {
589 onError({
590 code: 'MIME_TYPE_NOT_ALLOWED_FOR_USER',
591 message: (0,external_wp_i18n_namespaceObject.sprintf)(
592 // translators: %s: file name.
593 (0,external_wp_i18n_namespaceObject.__)('%s: Sorry, you are not allowed to upload this file type.'), mediaFile.name),
594 file: mediaFile
595 });
596 continue;
597 }
598
599 // Check if the block supports this mime type.
600 // Defer to the server when type not detected.
601 if (mediaFile.type && !isAllowedType(mediaFile.type)) {
602 onError({
603 code: 'MIME_TYPE_NOT_SUPPORTED',
604 message: (0,external_wp_i18n_namespaceObject.sprintf)(
605 // translators: %s: file name.
606 (0,external_wp_i18n_namespaceObject.__)('%s: Sorry, this file type is not supported here.'), mediaFile.name),
607 file: mediaFile
608 });
609 continue;
610 }
611
612 // Verify if file is greater than the maximum file upload size allowed for the site.
613 if (maxUploadFileSize && mediaFile.size > maxUploadFileSize) {
614 onError({
615 code: 'SIZE_ABOVE_LIMIT',
616 message: (0,external_wp_i18n_namespaceObject.sprintf)(
617 // translators: %s: file name.
618 (0,external_wp_i18n_namespaceObject.__)('%s: This file exceeds the maximum upload size for this site.'), mediaFile.name),
619 file: mediaFile
620 });
621 continue;
622 }
623
624 // Don't allow empty files to be uploaded.
625 if (mediaFile.size <= 0) {
626 onError({
627 code: 'EMPTY_FILE',
628 message: (0,external_wp_i18n_namespaceObject.sprintf)(
629 // translators: %s: file name.
630 (0,external_wp_i18n_namespaceObject.__)('%s: This file is empty.'), mediaFile.name),
631 file: mediaFile
632 });
633 continue;
634 }
635 validFiles.push(mediaFile);
636
637 // Set temporary URL to create placeholder media file, this is replaced
638 // with final file from media gallery when upload is `done` below.
639 filesSet.push({
640 url: (0,external_wp_blob_namespaceObject.createBlobURL)(mediaFile)
641 });
642 onFileChange(filesSet);
643 }
644 for (let idx = 0; idx < validFiles.length; ++idx) {
645 const mediaFile = validFiles[idx];
646 try {
647 var _savedMedia$caption$r;
648 const savedMedia = await createMediaFromFile(mediaFile, additionalData);
649 // eslint-disable-next-line camelcase
650 const {
651 alt_text,
652 source_url,
653 ...savedMediaProps
654 } = savedMedia;
655 const mediaObject = {
656 ...savedMediaProps,
657 alt: savedMedia.alt_text,
658 caption: (_savedMedia$caption$r = savedMedia.caption?.raw) !== null && _savedMedia$caption$r !== void 0 ? _savedMedia$caption$r : '',
659 title: savedMedia.title.raw,
660 url: savedMedia.source_url
661 };
662 setAndUpdateFiles(idx, mediaObject);
663 } catch (error) {
664 // Reset to empty on failure.
665 setAndUpdateFiles(idx, null);
666 let message;
667 if (error.message) {
668 message = error.message;
669 } else {
670 message = (0,external_wp_i18n_namespaceObject.sprintf)(
671 // translators: %s: file name
672 (0,external_wp_i18n_namespaceObject.__)('Error while uploading file %s to the media library.'), mediaFile.name);
673 }
674 onError({
675 code: 'GENERAL',
676 message,
677 file: mediaFile
678 });
679 }
680 }
681 }
682
683 /**
684 * @param {File} file Media File to Save.
685 * @param {?Object} additionalData Additional data to include in the request.
686 *
687 * @return {Promise} Media Object Promise.
688 */
689 function createMediaFromFile(file, additionalData) {
690 // Create upload payload.
691 const data = new window.FormData();
692 data.append('file', file, file.name || file.type.replace('/', '.'));
693 if (additionalData) {
694 Object.entries(additionalData).forEach(([key, value]) => data.append(key, value));
695 }
696 return external_wp_apiFetch_default()({
697 path: '/wp/v2/media',
698 body: data,
699 method: 'POST'
700 });
701 }
702
703 ;// CONCATENATED MODULE: ./packages/media-utils/build-module/utils/index.js
704
705
706 ;// CONCATENATED MODULE: ./packages/media-utils/build-module/index.js
707
708
709
710 (window.wp = window.wp || {}).mediaUtils = __webpack_exports__;
711 /******/ })()
712 ;