From 31be3a559b0778082b9a1431371d54057d83400c Mon Sep 17 00:00:00 2001 From: Nazmul Hasan Monshi Date: Sat, 10 Oct 2026 22:32:19 +0600 Subject: [PATCH] Media: Prevent drag and drop uploads in media states that don't support uploading. The "Drop files to upload" overlay and drag and drop uploads were active in every media modal state, including ones unrelated to uploading such as cropping or editing an image. Dropping a file there uploaded it to the library behind the current task. Introduce an `uploader` state attribute: when set to false, the uploader window ignores files dragged onto the modal. Set it for the cropper, edit image, image/audio/video details, and embed states. See #47130. --- src/js/media/controllers/audio-details.js | 1 + src/js/media/controllers/cropper.js | 1 + src/js/media/controllers/edit-image.js | 3 +- src/js/media/controllers/embed.js | 1 + src/js/media/controllers/image-details.js | 1 + src/js/media/controllers/video-details.js | 1 + src/js/media/views/uploader/window.js | 39 ++++++++++++++++++++++- 7 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/js/media/controllers/audio-details.js b/src/js/media/controllers/audio-details.js index 07fb99029f9a0..e53d381ebc412 100644 --- a/src/js/media/controllers/audio-details.js +++ b/src/js/media/controllers/audio-details.js @@ -21,6 +21,7 @@ AudioDetails = State.extend(/** @lends wp.media.controller.AudioDetails.prototyp content: 'audio-details', menu: 'audio-details', router: false, + uploader: false, priority: 60 }, diff --git a/src/js/media/controllers/cropper.js b/src/js/media/controllers/cropper.js index 0921839224eb0..cef778336816a 100644 --- a/src/js/media/controllers/cropper.js +++ b/src/js/media/controllers/cropper.js @@ -20,6 +20,7 @@ Cropper = wp.media.controller.State.extend(/** @lends wp.media.controller.Croppe toolbar: 'crop', content: 'crop', router: false, + uploader: false, canSkipCrop: false, // Default doCrop Ajax arguments to allow the Customizer (for example) to inject state. diff --git a/src/js/media/controllers/edit-image.js b/src/js/media/controllers/edit-image.js index 828cec6aaaa22..6e9859b34d3d9 100644 --- a/src/js/media/controllers/edit-image.js +++ b/src/js/media/controllers/edit-image.js @@ -28,7 +28,8 @@ EditImage = wp.media.controller.State.extend(/** @lends wp.media.controller.Edit menu: false, toolbar: 'edit-image', content: 'edit-image', - url: '' + url: '', + uploader: false }, /** diff --git a/src/js/media/controllers/embed.js b/src/js/media/controllers/embed.js index e485260cc5fe3..e2c243b9097e9 100644 --- a/src/js/media/controllers/embed.js +++ b/src/js/media/controllers/embed.js @@ -33,6 +33,7 @@ Embed = wp.media.controller.State.extend(/** @lends wp.media.controller.Embed.pr menu: 'default', toolbar: 'main-embed', priority: 120, + uploader: false, type: 'link', url: '', metadata: {} diff --git a/src/js/media/controllers/image-details.js b/src/js/media/controllers/image-details.js index e2bd41f47da0e..3ab4f7b7337a3 100644 --- a/src/js/media/controllers/image-details.js +++ b/src/js/media/controllers/image-details.js @@ -38,6 +38,7 @@ ImageDetails = State.extend(/** @lends wp.media.controller.ImageDetails.prototyp router: false, toolbar: 'image-details', editing: false, + uploader: false, priority: 60 }, Library.prototype.defaults ), diff --git a/src/js/media/controllers/video-details.js b/src/js/media/controllers/video-details.js index d54fcdbfa829b..5cc9f52121b69 100644 --- a/src/js/media/controllers/video-details.js +++ b/src/js/media/controllers/video-details.js @@ -21,6 +21,7 @@ VideoDetails = State.extend(/** @lends wp.media.controller.VideoDetails.prototyp content: 'video-details', menu: 'video-details', router: false, + uploader: false, priority: 60 }, diff --git a/src/js/media/views/uploader/window.js b/src/js/media/views/uploader/window.js index 0cd97f7c9dd76..24e904bcf1ec2 100644 --- a/src/js/media/views/uploader/window.js +++ b/src/js/media/views/uploader/window.js @@ -55,7 +55,7 @@ UploaderWindow = wp.media.View.extend(/** @lends wp.media.view.UploaderWindow.pr ready: function() { var postId = wp.media.view.settings.post.id, - dropzone; + dropzone, blockDrop; // If the uploader already exists, bail. if ( this.uploader ) { @@ -71,9 +71,46 @@ UploaderWindow = wp.media.View.extend(/** @lends wp.media.view.UploaderWindow.pr dropzone.on( 'dropzone:enter', _.bind( this.show, this ) ); dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) ); + // Ignore files dragged onto states that don't support uploading, e.g. cropping. + // Listen in the capture phase to run before the uploader's own drag and drop handlers. + blockDrop = _.bind( this.blockDrop, this ); + dropzone.each( function() { + var element = this; + + _.each( [ 'dragenter', 'dragover', 'drop' ], function( type ) { + element.addEventListener( type, blockDrop, true ); + } ); + } ); + $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) ); }, + /** + * Prevents dropping files when the current state doesn't support uploading. + * + * States opt out of uploading by setting their `uploader` attribute to false. + * + * @since 7.2.0 + * + * @param {DragEvent} event The drag event. + */ + blockDrop: function( event ) { + var state = this.controller.state(); + + if ( ! state || false !== state.get( 'uploader' ) ) { + return; + } + + // Only block files; other content, such as text, can still be dragged. + if ( ! event.dataTransfer || -1 === _.indexOf( event.dataTransfer.types, 'Files' ) ) { + return; + } + + event.stopPropagation(); + event.preventDefault(); + event.dataTransfer.dropEffect = 'none'; + }, + _ready: function() { this.controller.trigger( 'uploader:ready' ); },