Media Grid, support `MEDIA_TRASH`:
* Add a setting to `_wpMediaViewsL10n.settings`: `mediaTrash` * In the attachment edit modal, properly toggle between Trash/Untrash * In `media.view.Attachment`, add a method for `untrashAttachment` * When creating the grid toolbar, switch the setting order of subviews so that `media.view.DeleteSelectedButton` can listen to the instance of `media.view.AttachmentFilters.All` to update the text in its UI. * Add a new filter to `media.view.AttachmentFilters.All`, `trash`, when `settings.mediaTrash` is true * Allow the cached queries in `Query.get()` to be flushed when race conditions exist and collections need to be refreshed. This is currently only being used when `MEDIA_TRASH` is set, to refresh the filtered/mirrored collections related to `all`, `trash`, and any already queried filter. * Cleanup the bootstrapping of `media.view.MediaFrame.Manage` * Allow `wp_ajax_query_attachments()` to return items from the trash when `MEDIA_TRASH` is `true` * Allow `wp_ajax_save_attachment()` to set `post_status` when `MEDIA_TRASH` is `true`. It allows `wp_delete_post()` to be called, which will trash the attachment instead of deleting when the flag is set. Props koop for the knowledge sharing and thought partnership. See #29145. git-svn-id: https://develop.svn.wordpress.org/trunk@29490 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
parent
1ab750d0d2
commit
8661fcf0d9
|
@ -2161,7 +2161,14 @@ function wp_ajax_query_attachments() {
|
|||
) ) );
|
||||
|
||||
$query['post_type'] = 'attachment';
|
||||
$query['post_status'] = 'inherit';
|
||||
if ( MEDIA_TRASH
|
||||
&& ! empty( $_REQUEST['query']['post_status'] )
|
||||
&& 'trash' === $_REQUEST['query']['post_status'] ) {
|
||||
$query['post_status'] = 'trash';
|
||||
} else {
|
||||
$query['post_status'] = 'inherit';
|
||||
}
|
||||
|
||||
if ( current_user_can( get_post_type_object( 'attachment' )->cap->read_private_posts ) )
|
||||
$query['post_status'] .= ',private';
|
||||
|
||||
|
@ -2216,6 +2223,9 @@ function wp_ajax_save_attachment() {
|
|||
if ( isset( $changes['description'] ) )
|
||||
$post['post_content'] = $changes['description'];
|
||||
|
||||
if ( MEDIA_TRASH && isset( $changes['status'] ) )
|
||||
$post['post_status'] = $changes['status'];
|
||||
|
||||
if ( isset( $changes['alt'] ) ) {
|
||||
$alt = wp_unslash( $changes['alt'] );
|
||||
if ( $alt != get_post_meta( $id, '_wp_attachment_image_alt', true ) ) {
|
||||
|
@ -2243,7 +2253,12 @@ function wp_ajax_save_attachment() {
|
|||
}
|
||||
}
|
||||
|
||||
wp_update_post( $post );
|
||||
if ( MEDIA_TRASH && isset( $changes['status'] ) && 'trash' === $changes['status'] ) {
|
||||
wp_delete_post( $id );
|
||||
} else {
|
||||
wp_update_post( $post );
|
||||
}
|
||||
|
||||
wp_send_json_success();
|
||||
}
|
||||
|
||||
|
|
|
@ -1603,7 +1603,8 @@
|
|||
.attachment-info .edit-attachment,
|
||||
.attachment-info .refresh-attachment,
|
||||
.attachment-info .delete-attachment,
|
||||
.attachment-info .trash-attachment {
|
||||
.attachment-info .trash-attachment,
|
||||
.attachment-info .untrash-attachment {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
|
@ -1620,12 +1621,14 @@
|
|||
}
|
||||
|
||||
.media-modal .delete-attachment,
|
||||
.media-modal .trash-attachment {
|
||||
.media-modal .trash-attachment,
|
||||
.media-modal .untrash-attachment {
|
||||
color: #bc0b0b;
|
||||
}
|
||||
|
||||
.media-modal .delete-attachment:hover,
|
||||
.media-modal .trash-attachment:hover {
|
||||
.media-modal .trash-attachment:hover,
|
||||
.media-modal .untrash-attachment:hover {
|
||||
color: red;
|
||||
}
|
||||
|
||||
|
@ -2743,7 +2746,9 @@
|
|||
max-height: calc( 100% - 42px ); /* leave space for actions underneath */
|
||||
}
|
||||
|
||||
.edit-attachment-frame .delete-attachment {
|
||||
.edit-attachment-frame .delete-attachment,
|
||||
.edit-attachment-frame .trash-attachment,
|
||||
.edit-attachment-frame .untrash-attachment {
|
||||
float: right;
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
|
|
@ -183,7 +183,7 @@
|
|||
// Create a new EditAttachment frame, passing along the library and the attachment model.
|
||||
wp.media( {
|
||||
frame: 'edit-attachments',
|
||||
gridRouter: this.gridRouter,
|
||||
controller: this,
|
||||
library: this.state().get('library'),
|
||||
model: model
|
||||
} );
|
||||
|
@ -230,6 +230,9 @@
|
|||
},
|
||||
|
||||
bindDeferred: function() {
|
||||
if ( ! this.browserView.dfd ) {
|
||||
return;
|
||||
}
|
||||
this.browserView.dfd.done( _.bind( this.startHistory, this ) );
|
||||
},
|
||||
|
||||
|
@ -352,15 +355,11 @@
|
|||
regions: [ 'title', 'content' ],
|
||||
|
||||
events: {
|
||||
'click': 'collapse',
|
||||
'click .delete-media-item': 'deleteMediaItem',
|
||||
'click .left': 'previousMediaItem',
|
||||
'click .right': 'nextMediaItem'
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
var self = this;
|
||||
|
||||
media.view.Frame.prototype.initialize.apply( this, arguments );
|
||||
|
||||
_.defaults( this.options, {
|
||||
|
@ -368,32 +367,42 @@
|
|||
state: 'edit-attachment'
|
||||
});
|
||||
|
||||
this.gridRouter = this.options.gridRouter;
|
||||
|
||||
this.controller = this.options.controller;
|
||||
this.gridRouter = this.controller.gridRouter;
|
||||
this.library = this.options.library;
|
||||
|
||||
if ( this.options.model ) {
|
||||
this.model = this.options.model;
|
||||
} else {
|
||||
// this is a hack
|
||||
this.model = this.library.at( 0 );
|
||||
}
|
||||
|
||||
// Close the modal if the attachment is deleted.
|
||||
this.listenTo( this.model, 'destroy', this.close, this );
|
||||
|
||||
this.bindHandlers();
|
||||
this.createStates();
|
||||
this.createModal();
|
||||
|
||||
this.title.mode( 'default' );
|
||||
|
||||
this.options.hasPrevious = this.hasPrevious();
|
||||
this.options.hasNext = this.hasNext();
|
||||
},
|
||||
|
||||
bindHandlers: function() {
|
||||
// Bind default title creation.
|
||||
this.on( 'title:create:default', this.createTitle, this );
|
||||
|
||||
// Close the modal if the attachment is deleted.
|
||||
this.listenTo( this.model, 'change:status destroy', this.close, this );
|
||||
|
||||
this.on( 'content:create:edit-metadata', this.editMetadataMode, this );
|
||||
this.on( 'content:create:edit-image', this.editImageMode, this );
|
||||
this.on( 'content:render:edit-image', this.editImageModeRender, this );
|
||||
this.on( 'close', this.detach );
|
||||
},
|
||||
|
||||
// Bind default title creation.
|
||||
this.on( 'title:create:default', this.createTitle, this );
|
||||
this.title.mode( 'default' );
|
||||
|
||||
this.options.hasPrevious = this.hasPrevious();
|
||||
this.options.hasNext = this.hasNext();
|
||||
createModal: function() {
|
||||
var self = this;
|
||||
|
||||
// Initialize modal container view.
|
||||
if ( this.options.modal ) {
|
||||
|
@ -609,16 +618,33 @@
|
|||
media.view.DeleteSelectedButton = media.view.Button.extend({
|
||||
initialize: function() {
|
||||
media.view.Button.prototype.initialize.apply( this, arguments );
|
||||
if ( this.options.filters ) {
|
||||
this.listenTo( this.options.filters.model, 'change', this.filterChange );
|
||||
}
|
||||
this.listenTo( this.controller, 'selection:toggle', this.toggleDisabled );
|
||||
},
|
||||
|
||||
filterChange: function( model ) {
|
||||
if ( 'trash' === model.get( 'status' ) ) {
|
||||
this.model.set( 'text', l10n.untrashSelected );
|
||||
} else if ( media.view.settings.mediaTrash ) {
|
||||
this.model.set( 'text', l10n.trashSelected );
|
||||
} else {
|
||||
this.model.set( 'text', l10n.deleteSelected );
|
||||
}
|
||||
},
|
||||
|
||||
toggleDisabled: function() {
|
||||
this.$el.attr( 'disabled', ! this.controller.state().get( 'selection' ).length );
|
||||
this.model.set( 'disabled', ! this.controller.state().get( 'selection' ).length );
|
||||
},
|
||||
|
||||
render: function() {
|
||||
media.view.Button.prototype.render.apply( this, arguments );
|
||||
this.$el.addClass( 'delete-selected-button hidden' );
|
||||
if ( this.controller.isModeActive( 'select' ) ) {
|
||||
this.$el.addClass( 'delete-selected-button' );
|
||||
} else {
|
||||
this.$el.addClass( 'delete-selected-button hidden' );
|
||||
}
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -824,9 +824,12 @@ window.wp = window.wp || {};
|
|||
/**
|
||||
* @access private
|
||||
*/
|
||||
_requery: function() {
|
||||
_requery: function( cache ) {
|
||||
var props;
|
||||
if ( this.props.get('query') ) {
|
||||
this.mirror( Query.get( this.props.toJSON() ) );
|
||||
props = this.props.toJSON();
|
||||
props.cache = ( true !== cache );
|
||||
this.mirror( Query.get( props ) );
|
||||
}
|
||||
},
|
||||
/**
|
||||
|
@ -947,6 +950,22 @@ window.wp = window.wp || {};
|
|||
}
|
||||
|
||||
return uploadedTo === attachment.get('uploadedTo');
|
||||
},
|
||||
/**
|
||||
* @static
|
||||
* @param {wp.media.model.Attachment} attachment
|
||||
*
|
||||
* @this wp.media.model.Attachments
|
||||
*
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
status: function( attachment ) {
|
||||
var status = this.props.get('status');
|
||||
if ( _.isUndefined( status ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return status === attachment.get('status');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1144,7 +1163,8 @@ window.wp = window.wp || {};
|
|||
'type': 'post_mime_type',
|
||||
'perPage': 'posts_per_page',
|
||||
'menuOrder': 'menu_order',
|
||||
'uploadedTo': 'post_parent'
|
||||
'uploadedTo': 'post_parent',
|
||||
'status': 'post_status'
|
||||
},
|
||||
/**
|
||||
* @static
|
||||
|
@ -1169,11 +1189,13 @@ window.wp = window.wp || {};
|
|||
var args = {},
|
||||
orderby = Query.orderby,
|
||||
defaults = Query.defaultProps,
|
||||
query;
|
||||
query,
|
||||
cache = !! props.cache;
|
||||
|
||||
// Remove the `query` property. This isn't linked to a query,
|
||||
// this *is* the query.
|
||||
delete props.query;
|
||||
delete props.cache;
|
||||
|
||||
// Fill default args.
|
||||
_.defaults( props, defaults );
|
||||
|
@ -1207,9 +1229,13 @@ window.wp = window.wp || {};
|
|||
args.orderby = orderby.valuemap[ props.orderby ] || props.orderby;
|
||||
|
||||
// Search the query cache for matches.
|
||||
query = _.find( queries, function( query ) {
|
||||
return _.isEqual( query.args, args );
|
||||
});
|
||||
if ( cache ) {
|
||||
query = _.find( queries, function( query ) {
|
||||
return _.isEqual( query.args, args );
|
||||
});
|
||||
} else {
|
||||
queries = [];
|
||||
}
|
||||
|
||||
// Otherwise, create a new query and add it to the cache.
|
||||
if ( ! query ) {
|
||||
|
|
|
@ -5671,6 +5671,7 @@
|
|||
filters[ key ] = {
|
||||
text: text,
|
||||
props: {
|
||||
status: null,
|
||||
type: key,
|
||||
uploadedTo: null,
|
||||
orderby: 'date',
|
||||
|
@ -5682,6 +5683,7 @@
|
|||
filters.all = {
|
||||
text: l10n.allMediaItems,
|
||||
props: {
|
||||
status: null,
|
||||
type: null,
|
||||
uploadedTo: null,
|
||||
orderby: 'date',
|
||||
|
@ -5694,6 +5696,7 @@
|
|||
filters.uploaded = {
|
||||
text: l10n.uploadedToThisPost,
|
||||
props: {
|
||||
status: null,
|
||||
type: null,
|
||||
uploadedTo: media.view.settings.post.id,
|
||||
orderby: 'menuOrder',
|
||||
|
@ -5706,6 +5709,7 @@
|
|||
filters.unattached = {
|
||||
text: l10n.unattached,
|
||||
props: {
|
||||
status: null,
|
||||
uploadedTo: 0,
|
||||
type: null,
|
||||
orderby: 'menuOrder',
|
||||
|
@ -5714,6 +5718,20 @@
|
|||
priority: 50
|
||||
};
|
||||
|
||||
if ( media.view.settings.mediaTrash ) {
|
||||
filters.trash = {
|
||||
text: l10n.trash,
|
||||
props: {
|
||||
uploadedTo: null,
|
||||
status: 'trash',
|
||||
type: null,
|
||||
orderby: 'date',
|
||||
order: 'DESC'
|
||||
},
|
||||
priority: 50
|
||||
};
|
||||
}
|
||||
|
||||
this.filters = filters;
|
||||
}
|
||||
});
|
||||
|
@ -5765,9 +5783,7 @@
|
|||
},
|
||||
|
||||
createToolbar: function() {
|
||||
var filters,
|
||||
LibraryViewSwitcher,
|
||||
FiltersConstructor;
|
||||
var LibraryViewSwitcher, Filters;
|
||||
|
||||
/**
|
||||
* @member {wp.media.view.Toolbar}
|
||||
|
@ -5778,6 +5794,38 @@
|
|||
|
||||
this.views.add( this.toolbar );
|
||||
|
||||
this.toolbar.set( 'spinner', new media.view.Spinner({
|
||||
priority: -60
|
||||
}) );
|
||||
|
||||
if ( -1 !== $.inArray( this.options.filters, [ 'uploaded', 'all' ] ) ) {
|
||||
// "Filters" will return a <select>, need to render
|
||||
// screen reader text before
|
||||
this.toolbar.set( 'filtersLabel', new media.view.Label({
|
||||
value: l10n.filterByType,
|
||||
attributes: {
|
||||
'for': 'media-attachment-filters'
|
||||
},
|
||||
priority: -80
|
||||
}).render() );
|
||||
|
||||
if ( 'uploaded' === this.options.filters ) {
|
||||
this.toolbar.set( 'filters', new media.view.AttachmentFilters.Uploaded({
|
||||
controller: this.controller,
|
||||
model: this.collection.props,
|
||||
priority: -80
|
||||
}).render() );
|
||||
} else {
|
||||
Filters = new media.view.AttachmentFilters.All({
|
||||
controller: this.controller,
|
||||
model: this.collection.props,
|
||||
priority: -80
|
||||
});
|
||||
|
||||
this.toolbar.set( 'filters', Filters.render() );
|
||||
}
|
||||
}
|
||||
|
||||
// Feels odd to bring the global media library switcher into the Attachment
|
||||
// browser view. Is this a use case for doAction( 'add:toolbar-items:attachments-browser', this.toolbar );
|
||||
// which the controller can tap into and add this view?
|
||||
|
@ -5814,47 +5862,41 @@
|
|||
}).render() );
|
||||
|
||||
this.toolbar.set( 'deleteSelectedButton', new media.view.DeleteSelectedButton({
|
||||
filters: Filters,
|
||||
style: 'primary',
|
||||
disabled: true,
|
||||
text: l10n.deleteSelected,
|
||||
text: media.view.settings.mediaTrash ? l10n.trashSelected : l10n.deleteSelected,
|
||||
controller: this.controller,
|
||||
priority: -60,
|
||||
click: function() {
|
||||
while ( this.controller.state().get( 'selection' ).length > 0 ) {
|
||||
this.controller.state().get( 'selection' ).at( 0 ).destroy();
|
||||
var model, changed = [],
|
||||
selection = this.controller.state().get( 'selection' ),
|
||||
library = this.controller.state().get( 'library' );
|
||||
|
||||
while ( selection.length > 0 ) {
|
||||
model = selection.at( 0 );
|
||||
if ( media.view.settings.mediaTrash && 'trash' === model.get( 'status' ) ) {
|
||||
model.set( 'status', 'inherit' );
|
||||
changed.push( model.save() );
|
||||
selection.remove( model );
|
||||
} else if ( media.view.settings.mediaTrash ) {
|
||||
model.set( 'status', 'trash' );
|
||||
changed.push( model.save() );
|
||||
selection.remove( model );
|
||||
} else {
|
||||
model.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
if ( changed.length ) {
|
||||
$.when( changed ).then( function() {
|
||||
library._requery( true );
|
||||
} );
|
||||
}
|
||||
}
|
||||
}).render() );
|
||||
}
|
||||
|
||||
this.toolbar.set( 'spinner', new media.view.Spinner({
|
||||
priority: -60
|
||||
}) );
|
||||
|
||||
filters = this.options.filters;
|
||||
if ( 'uploaded' === filters ) {
|
||||
FiltersConstructor = media.view.AttachmentFilters.Uploaded;
|
||||
} else if ( 'all' === filters ) {
|
||||
FiltersConstructor = media.view.AttachmentFilters.All;
|
||||
}
|
||||
|
||||
if ( FiltersConstructor ) {
|
||||
// "FiltersConstructor" will return a <select>, need to render
|
||||
// screen reader text before
|
||||
this.toolbar.set( 'filtersLabel', new media.view.Label({
|
||||
value: l10n.filterByType,
|
||||
attributes: {
|
||||
'for': 'media-attachment-filters'
|
||||
},
|
||||
priority: -80
|
||||
}).render() );
|
||||
this.toolbar.set( 'filters', new FiltersConstructor({
|
||||
controller: this.controller,
|
||||
model: this.collection.props,
|
||||
priority: -80
|
||||
}).render() );
|
||||
}
|
||||
|
||||
if ( this.options.search ) {
|
||||
// Search is an input, screen reader text needs to be rendered before
|
||||
this.toolbar.set( 'searchLabel', new media.view.Label({
|
||||
|
@ -6420,6 +6462,7 @@
|
|||
'change [data-setting] textarea': 'updateSetting',
|
||||
'click .delete-attachment': 'deleteAttachment',
|
||||
'click .trash-attachment': 'trashAttachment',
|
||||
'click .untrash-attachment': 'untrashAttachment',
|
||||
'click .edit-attachment': 'editAttachment',
|
||||
'click .refresh-attachment': 'refreshAttachment',
|
||||
'keydown': 'toggleSelectionHandler'
|
||||
|
@ -6453,9 +6496,29 @@
|
|||
* @param {Object} event
|
||||
*/
|
||||
trashAttachment: function( event ) {
|
||||
var library = this.controller.library;
|
||||
event.preventDefault();
|
||||
|
||||
this.model.destroy();
|
||||
if ( media.view.settings.mediaTrash ) {
|
||||
this.model.set( 'status', 'trash' );
|
||||
this.model.save().done( function() {
|
||||
library._requery( true );
|
||||
} );
|
||||
} else {
|
||||
this.model.destroy();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {Object} event
|
||||
*/
|
||||
untrashAttachment: function( event ) {
|
||||
var library = this.controller.library;
|
||||
event.preventDefault();
|
||||
|
||||
this.model.set( 'status', 'inherit' );
|
||||
this.model.save().done( function() {
|
||||
library._requery( true );
|
||||
} );
|
||||
},
|
||||
/**
|
||||
* @param {Object} event
|
||||
|
|
|
@ -316,7 +316,11 @@ function wp_print_media_templates() {
|
|||
|
||||
<# if ( ! data.uploading && data.can.remove ) { #>
|
||||
<?php if ( MEDIA_TRASH ): ?>
|
||||
<# if ( 'trash' === data.status ) { #>
|
||||
<a class="untrash-attachment" href="#"><?php _e( 'Untrash' ); ?></a>
|
||||
<# } else { #>
|
||||
<a class="trash-attachment" href="#"><?php _e( 'Trash' ); ?></a>
|
||||
<# } #>
|
||||
<?php else: ?>
|
||||
<a class="delete-attachment" href="#"><?php _e( 'Delete Permanently' ); ?></a>
|
||||
<?php endif; ?>
|
||||
|
|
|
@ -2869,6 +2869,7 @@ function wp_enqueue_media( $args = array() ) {
|
|||
'embedMimes' => $ext_mimes,
|
||||
'contentWidth' => $content_width,
|
||||
'months' => $months,
|
||||
'mediaTrash' => MEDIA_TRASH ? 1 : 0
|
||||
);
|
||||
|
||||
$post = null;
|
||||
|
@ -2931,11 +2932,14 @@ function wp_enqueue_media( $args = array() ) {
|
|||
'noItemsFound' => __( 'No items found.' ),
|
||||
'insertIntoPost' => $hier ? __( 'Insert into page' ) : __( 'Insert into post' ),
|
||||
'unattached' => __( 'Unattached' ),
|
||||
'trash' => __( 'Trash' ),
|
||||
'uploadedToThisPost' => $hier ? __( 'Uploaded to this page' ) : __( 'Uploaded to this post' ),
|
||||
'warnDelete' => __( "You are about to permanently delete this item.\n 'Cancel' to stop, 'OK' to delete." ),
|
||||
'warnBulkDelete' => __( "You are about to permanently delete these items.\n 'Cancel' to stop, 'OK' to delete." ),
|
||||
'bulkSelect' => __( 'Bulk Select' ),
|
||||
'cancelSelection' => __( 'Cancel Selection' ),
|
||||
'trashSelected' => __( 'Trash Selected' ),
|
||||
'untrashSelected' => __( 'Untrash Selected' ),
|
||||
'deleteSelected' => __( 'Delete Selected' ),
|
||||
'deletePermanently' => __( 'Delete Permanently' ),
|
||||
'apply' => __( 'Apply' ),
|
||||
|
|
Loading…
Reference in New Issue