From 1899cc9c4158190da883d446f4241688b7d46abc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Helen=20Hou-Sand=C3=AD?= Date: Thu, 10 Jul 2014 06:37:16 +0000 Subject: [PATCH] Media grid attachment modal: * Keyboard navigation. * History and routes for single items and search results. props adamsilverstein. see #24716. Built from https://develop.svn.wordpress.org/trunk@29057 git-svn-id: http://core.svn.wordpress.org/trunk@28845 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-admin/upload.php | 1 + wp-includes/js/media-grid.js | 139 +++++++++++++++++++++++++++++-- wp-includes/js/media-grid.min.js | 2 +- 3 files changed, 136 insertions(+), 6 deletions(-) diff --git a/wp-admin/upload.php b/wp-admin/upload.php index 9903ae6a91..cdb49ef516 100644 --- a/wp-admin/upload.php +++ b/wp-admin/upload.php @@ -24,6 +24,7 @@ if ( 'grid' === $mode ) { wp_enqueue_media(); wp_enqueue_script( 'media-grid' ); wp_enqueue_script( 'media' ); + wp_localize_script( 'media-grid', 'mediaGridSettings', array( 'adminUrl' => parse_url( self_admin_url(), PHP_URL_PATH ) ) ); require_once( ABSPATH . 'wp-admin/admin-header.php' ); include( ABSPATH . 'wp-admin/admin-footer.php' ); diff --git a/wp-includes/js/media-grid.js b/wp-includes/js/media-grid.js index 9e69a4d1ba..b0541a6986 100644 --- a/wp-includes/js/media-grid.js +++ b/wp-includes/js/media-grid.js @@ -1,4 +1,4 @@ -/* global _wpMediaViewsL10n, setUserSetting, deleteUserSetting, MediaElementPlayer */ +/* global _wpMediaViewsL10n, setUserSetting, deleteUserSetting, MediaElementPlayer, mediaGridSettings*/ (function($, _, Backbone, wp) { var media = wp.media, l10n; @@ -91,6 +91,7 @@ * @global wp.Uploader */ initialize: function() { + var self = this; _.defaults( this.options, { title: l10n.mediaLibraryTitle, modal: false, @@ -140,6 +141,24 @@ this.createStates(); this.bindHandlers(); this.render(); + + // Set up the Backbone router after a brief delay + _.delay( function(){ + wp.media.mediarouter = new media.view.Frame.Router( self ); + // Verify pushState support and activate + if ( window.history && window.history.pushState ) { + Backbone.history.start({ + root: mediaGridSettings.adminUrl, + pushState: true + }); + } + }, 250); + + // Update the URL when entering search string (at most once per second) + $( '#media-search-input' ).on( 'input', _.debounce( function() { + var $val = $( this ).val(); + wp.media.mediarouter.navigate( wp.media.mediarouter.baseUrl( ( '' === $val ) ? '' : ( '?search=' + $val ) ) ); + }, 1000 ) ); }, createSelection: function() { @@ -218,10 +237,11 @@ * Open the Edit Attachment modal. */ editAttachment: function( model ) { - var library = this.state().get('library'); + var self = this, + library = this.state().get('library'); // Create a new EditAttachment frame, passing along the library and the attachment model. - this.editAttachmentFrame = new media.view.Frame.EditAttachment({ + this.editAttachmentFrame = new media.view.Frame.EditAttachments({ library: library, model: model }); @@ -229,6 +249,10 @@ // Listen to events on the edit attachment frame for triggering pagination callback handlers. this.listenTo( this.editAttachmentFrame, 'edit:attachment:next', this.editNextAttachment ); this.listenTo( this.editAttachmentFrame, 'edit:attachment:previous', this.editPreviousAttachment ); + // Listen to keyboard events on the modal + $( 'body' ).on( 'keydown.media-modal', function( e ) { + self.editAttachmentFrame.keyEvent( e ); + } ); }, /** @@ -299,6 +323,59 @@ } }); + /** + * A router for handling the browser history and application state + */ + media.view.Frame.Router = Backbone.Router.extend({ + + mediaFrame: '', + + initialize: function( mediaFrame ){ + this.mediaFrame = mediaFrame; + }, + + routes: { + 'upload.php?item=:slug': 'showitem', + 'upload.php?search=:query': 'search', + ':default': 'defaultRoute' + }, + + // Map routes against the page URL + baseUrl: function( url ) { + return 'upload.php' + url; + }, + + // Respond to the search route by filling the search field and trigggering the input event + search: function( query ) { + // Ensure modal closed, see back button + this.closeModal(); + $( '#media-search-input' ).val( query ).trigger( 'input' ); + }, + + // Show the modal with a specific item + showitem: function( query ) { + var library = this.mediaFrame.state().get('library'); + + // Remove existing modal if present + this.closeModal(); + // Trigger the media frame to open the correct item + this.mediaFrame.trigger( 'edit:attachment', library.findWhere( { id: parseInt( query, 10 ) } ) ); + }, + + // Close the modal if set up + closeModal: function() { + if ( 'undefined' !== typeof this.mediaFrame.editAttachmentFrame ) { + this.mediaFrame.editAttachmentFrame.modal.close(); + } + }, + + // Default route: make sure the modal and search are reset + defaultRoute: function() { + this.closeModal(); + $( '#media-search-input' ).val( '' ).trigger( 'input' ); + } + }); + /** * A frame for editing the details of a specific media item. * @@ -306,7 +383,7 @@ * * Requires an attachment model to be passed in the options hash under `model`. */ - media.view.Frame.EditAttachment = media.view.Frame.extend({ + media.view.Frame.EditAttachments = media.view.Frame.extend({ className: 'edit-attachment-frame', template: media.template( 'edit-attachment-frame' ), @@ -328,13 +405,20 @@ state: 'edit-attachment' }); + this.library = this.options.library; + if ( this.options.model ) { + this.model = this.options.model; + } else { + this.model = this.library.at( 0 ); + } + this.createStates(); this.on( 'content:render:edit-metadata', this.editMetadataContent, this ); this.on( 'content:render:edit-image', this.editImageContentUgh, this ); // Only need a tab to Edit Image for images. - if ( this.model.get( 'type' ) === 'image' ) { + if ( 'undefined' !== typeof this.model && this.model.get( 'type' ) === 'image' ) { this.on( 'router:create', this.createRouter, this ); this.on( 'router:render', this.browseRouter, this ); } @@ -352,6 +436,7 @@ // Completely destroy the modal DOM element when closing it. this.modal.close = function() { self.modal.remove(); + $( 'body' ).off( 'keydown.media-modal' ); /* remove the keydown event */ }; this.modal.content( this ); @@ -391,6 +476,8 @@ model: this.model }); this.content.set( view ); + // Update browser url when navigating media details + wp.media.mediarouter.navigate( wp.media.mediarouter.baseUrl( '?item=' + this.model.id ) ); }, /** @@ -461,6 +548,48 @@ return; this.modal.close(); this.trigger( 'edit:attachment:next', this.model ); + }, + + getCurrentIndex: function() { + return this.library.indexOf( this.model ); + }, + + hasNext: function() { + return ( this.getCurrentIndex() + 1 ) < this.library.length; + }, + + hasPrevious: function() { + return ( this.getCurrentIndex() - 1 ) > -1; + }, + /** + * Respond to the keyboard events: right arrow, left arrow, escape. + */ + keyEvent: function( event ) { + var $target = $( event.target ); + // Pressing the escape key routes back to main url + if ( event.keyCode === 27 ) { + this.resetRoute(); + return event; + } + //Don't go left/right if we are in a textarea or input field + if ( $target.is( 'input' ) || $target.is( 'textarea' ) ) { + return event; + } + // The right arrow key + if ( event.keyCode === 39 ) { + if ( ! this.hasNext ) { return; } + _.debounce( this.nextMediaItem(), 250 ); + } + // The left arrow key + if ( event.keyCode === 37 ) { + if ( ! this.hasPrevious ) { return; } + _.debounce( this.previousMediaItem(), 250 ); + } + }, + + resetRoute: function() { + wp.media.mediarouter.navigate( wp.media.mediarouter.baseUrl( '' ) ); + return; } }); diff --git a/wp-includes/js/media-grid.min.js b/wp-includes/js/media-grid.min.js index 22b2d32cee..865cdcb714 100644 --- a/wp-includes/js/media-grid.min.js +++ b/wp-includes/js/media-grid.min.js @@ -1 +1 @@ -!function(a,b,c,d){var e,f=d.media;f.view.l10n?e=f.view.l10n:(e=f.view.l10n="undefined"==typeof _wpMediaViewsL10n?{}:_wpMediaViewsL10n,delete e.settings),f.controller.EditImageNoFrame=f.controller._State.extend({defaults:{id:"edit-attachment",title:e.editImage,menu:!1,router:"edit-metadata",content:"edit-metadata",url:""},initialize:function(){f.controller._State.prototype.initialize.apply(this,arguments)},_postActivate:function(){this._content(),this._router()},deactivate:function(){this.stopListening(this.frame)},_router:function(){var a,b=this.frame.router,c=this.get("router");this.frame.$el.toggleClass("hide-router",!c),c&&(this.frame.router.render(c),a=b.get(),a&&a.select&&a.select(this.frame.content.mode()))},_content:function(){var a=this.get("content");a&&this.frame.content.render(a)}}),f.view.MediaFrame.Manage=f.view.MediaFrame.extend({initialize:function(){b.defaults(this.options,{title:e.mediaLibraryTitle,modal:!1,selection:[],library:{},multiple:"add",state:"library",uploader:!0,mode:["grid","edit"]}),a(document).on("click",".add-new-h2",b.bind(this.addNewClickHandler,this)),this.$el.addClass("wp-core-ui media-grid-view"),(d.Uploader.limitExceeded||!d.Uploader.browser.supported)&&(this.options.uploader=!1),this.options.uploader&&(this.uploader=new f.view.UploaderWindow({controller:this,uploader:{dropzone:a("body"),container:a("body")}}).render(),this.uploader.ready(),a("body").append(this.uploader.el),this.options.uploader=!1),f.view.MediaFrame.prototype.initialize.apply(this,arguments),this.$el.appendTo(this.options.container),this.createSelection(),this.createStates(),this.bindHandlers(),this.render()},createSelection:function(){var a=this.options.selection;a instanceof f.model.Selection||(this.options.selection=new f.model.Selection(a,{multiple:this.options.multiple})),this._selection={attachments:new f.model.Attachments,difference:[]}},createStates:function(){var a,b=this.options;this.options.states||(a=new f.controller.Library({library:f.query(b.library),multiple:b.multiple,title:b.title,priority:20,toolbar:!1,router:!1,content:"browse",filterable:"mime-types"}),a._renderTitle=function(a){var b=this.get("title")||"";a.$el.addClass("wrap"),b+='Add New',a.$el.html(b)},this.states.add([a]))},bindHandlers:function(){this.on("content:create:browse",this.browseContent,this),this.on("content:render:edit-image",this.editImageContent,this),this.on("edit:attachment",this.editAttachment,this),this.on("edit:attachment:next",this.editNextAttachment,this),this.on("edit:attachment:previous",this.editPreviousAttachment,this)},addNewClickHandler:function(){this.trigger("show:upload:attachment")},editPreviousAttachment:function(a){var b=this.state().get("library"),c=b.indexOf(a);this.trigger("edit:attachment",b.at(c-1))},editNextAttachment:function(a){var b=this.state().get("library"),c=b.indexOf(a);this.trigger("edit:attachment",b.at(c+1))},editAttachment:function(a){var b=this.state().get("library");this.editAttachmentFrame=new f.view.Frame.EditAttachment({library:b,model:a}),this.listenTo(this.editAttachmentFrame,"edit:attachment:next",this.editNextAttachment),this.listenTo(this.editAttachmentFrame,"edit:attachment:previous",this.editPreviousAttachment)},browseContent:function(a){var b=this.state();a.view=new f.view.AttachmentsBrowser({controller:this,collection:b.get("library"),selection:b.get("selection"),model:b,sortable:b.get("sortable"),search:b.get("searchable"),filters:b.get("filterable"),display:b.get("displaySettings"),dragInfo:b.get("dragInfo"),bulkEdit:!0,sidebar:!1,suggestedWidth:b.get("suggestedWidth"),suggestedHeight:b.get("suggestedHeight"),AttachmentView:b.get("AttachmentView")})},editImageContent:function(){var a=this.state().get("image"),b=new f.view.EditImage({model:a,controller:this}).render();this.content.set(b),b.loadEditor()}}),f.view.Attachment.Details.TwoColumn=f.view.Attachment.Details.extend({template:d.template("attachment-details-two-column"),initialize:function(){this.$el.attr("aria-label",this.model.attributes.title).attr("aria-checked",!1),this.model.on("change:sizes change:uploading",this.render,this),this.model.on("change:title",this._syncTitle,this),this.model.on("change:caption",this._syncCaption,this),this.model.on("change:percent",this.progress,this),this.model.on("add",this.select,this),this.model.on("remove",this.deselect,this)},render:function(){f.view.Attachment.Details.prototype.render.apply(this,arguments),f.mixin.removeAllPlayers(),a("audio, video",this.$el).each(function(a,b){var c=f.view.MediaDetails.prepareSrc(b);new MediaElementPlayer(c,f.mixin.mejsSettings)})}}),f.view.Frame.EditAttachment=f.view.Frame.extend({className:"edit-attachment-frame",template:f.template("edit-attachment-frame"),regions:["router","content"],events:{click:"collapse","click .delete-media-item":"deleteMediaItem","click .left":"previousMediaItem","click .right":"nextMediaItem"},initialize:function(){var a=this;f.view.Frame.prototype.initialize.apply(this,arguments),b.defaults(this.options,{modal:!0,state:"edit-attachment"}),this.createStates(),this.on("content:render:edit-metadata",this.editMetadataContent,this),this.on("content:render:edit-image",this.editImageContentUgh,this),"image"===this.model.get("type")&&(this.on("router:create",this.createRouter,this),this.on("router:render",this.browseRouter,this)),this.options.hasPrevious=this.options.library.indexOf(this.options.model)>0?!0:!1,this.options.hasNext=this.options.library.indexOf(this.options.model)0;)this.controller.state().get("selection").at(0).destroy()},visibility:function(){var a=this.controller.activeModes.where({id:"bulk-edit"}).length;a?this.$el.show():this.$el.hide()}})}(jQuery,_,Backbone,wp); \ No newline at end of file +!function(a,b,c,d){var e,f=d.media;f.view.l10n?e=f.view.l10n:(e=f.view.l10n="undefined"==typeof _wpMediaViewsL10n?{}:_wpMediaViewsL10n,delete e.settings),f.controller.EditImageNoFrame=f.controller._State.extend({defaults:{id:"edit-attachment",title:e.editImage,menu:!1,router:"edit-metadata",content:"edit-metadata",url:""},initialize:function(){f.controller._State.prototype.initialize.apply(this,arguments)},_postActivate:function(){this._content(),this._router()},deactivate:function(){this.stopListening(this.frame)},_router:function(){var a,b=this.frame.router,c=this.get("router");this.frame.$el.toggleClass("hide-router",!c),c&&(this.frame.router.render(c),a=b.get(),a&&a.select&&a.select(this.frame.content.mode()))},_content:function(){var a=this.get("content");a&&this.frame.content.render(a)}}),f.view.MediaFrame.Manage=f.view.MediaFrame.extend({initialize:function(){var g=this;b.defaults(this.options,{title:e.mediaLibraryTitle,modal:!1,selection:[],library:{},multiple:"add",state:"library",uploader:!0,mode:["grid","edit"]}),a(document).on("click",".add-new-h2",b.bind(this.addNewClickHandler,this)),this.$el.addClass("wp-core-ui media-grid-view"),(d.Uploader.limitExceeded||!d.Uploader.browser.supported)&&(this.options.uploader=!1),this.options.uploader&&(this.uploader=new f.view.UploaderWindow({controller:this,uploader:{dropzone:a("body"),container:a("body")}}).render(),this.uploader.ready(),a("body").append(this.uploader.el),this.options.uploader=!1),f.view.MediaFrame.prototype.initialize.apply(this,arguments),this.$el.appendTo(this.options.container),this.createSelection(),this.createStates(),this.bindHandlers(),this.render(),b.delay(function(){d.media.mediarouter=new f.view.Frame.Router(g),window.history&&window.history.pushState&&c.history.start({root:mediaGridSettings.adminUrl,pushState:!0})},250),a("#media-search-input").on("input",b.debounce(function(){var b=a(this).val();d.media.mediarouter.navigate(d.media.mediarouter.baseUrl(""===b?"":"?search="+b))},1e3))},createSelection:function(){var a=this.options.selection;a instanceof f.model.Selection||(this.options.selection=new f.model.Selection(a,{multiple:this.options.multiple})),this._selection={attachments:new f.model.Attachments,difference:[]}},createStates:function(){var a,b=this.options;this.options.states||(a=new f.controller.Library({library:f.query(b.library),multiple:b.multiple,title:b.title,priority:20,toolbar:!1,router:!1,content:"browse",filterable:"mime-types"}),a._renderTitle=function(a){var b=this.get("title")||"";a.$el.addClass("wrap"),b+='Add New',a.$el.html(b)},this.states.add([a]))},bindHandlers:function(){this.on("content:create:browse",this.browseContent,this),this.on("content:render:edit-image",this.editImageContent,this),this.on("edit:attachment",this.editAttachment,this),this.on("edit:attachment:next",this.editNextAttachment,this),this.on("edit:attachment:previous",this.editPreviousAttachment,this)},addNewClickHandler:function(){this.trigger("show:upload:attachment")},editPreviousAttachment:function(a){var b=this.state().get("library"),c=b.indexOf(a);this.trigger("edit:attachment",b.at(c-1))},editNextAttachment:function(a){var b=this.state().get("library"),c=b.indexOf(a);this.trigger("edit:attachment",b.at(c+1))},editAttachment:function(b){var c=this,d=this.state().get("library");this.editAttachmentFrame=new f.view.Frame.EditAttachments({library:d,model:b}),this.listenTo(this.editAttachmentFrame,"edit:attachment:next",this.editNextAttachment),this.listenTo(this.editAttachmentFrame,"edit:attachment:previous",this.editPreviousAttachment),a("body").on("keydown.media-modal",function(a){c.editAttachmentFrame.keyEvent(a)})},browseContent:function(a){var b=this.state();a.view=new f.view.AttachmentsBrowser({controller:this,collection:b.get("library"),selection:b.get("selection"),model:b,sortable:b.get("sortable"),search:b.get("searchable"),filters:b.get("filterable"),display:b.get("displaySettings"),dragInfo:b.get("dragInfo"),bulkEdit:!0,sidebar:!1,suggestedWidth:b.get("suggestedWidth"),suggestedHeight:b.get("suggestedHeight"),AttachmentView:b.get("AttachmentView")})},editImageContent:function(){var a=this.state().get("image"),b=new f.view.EditImage({model:a,controller:this}).render();this.content.set(b),b.loadEditor()}}),f.view.Attachment.Details.TwoColumn=f.view.Attachment.Details.extend({template:d.template("attachment-details-two-column"),initialize:function(){this.$el.attr("aria-label",this.model.attributes.title).attr("aria-checked",!1),this.model.on("change:sizes change:uploading",this.render,this),this.model.on("change:title",this._syncTitle,this),this.model.on("change:caption",this._syncCaption,this),this.model.on("change:percent",this.progress,this),this.model.on("add",this.select,this),this.model.on("remove",this.deselect,this)},render:function(){f.view.Attachment.Details.prototype.render.apply(this,arguments),f.mixin.removeAllPlayers(),a("audio, video",this.$el).each(function(a,b){var c=f.view.MediaDetails.prepareSrc(b);new MediaElementPlayer(c,f.mixin.mejsSettings)})}}),f.view.Frame.Router=c.Router.extend({mediaFrame:"",initialize:function(a){this.mediaFrame=a},routes:{"upload.php?item=:slug":"showitem","upload.php?search=:query":"search",":default":"defaultRoute"},baseUrl:function(a){return"upload.php"+a},search:function(b){this.closeModal(),a("#media-search-input").val(b).trigger("input")},showitem:function(a){var b=this.mediaFrame.state().get("library");this.closeModal(),this.mediaFrame.trigger("edit:attachment",b.findWhere({id:parseInt(a,10)}))},closeModal:function(){"undefined"!=typeof this.mediaFrame.editAttachmentFrame&&this.mediaFrame.editAttachmentFrame.modal.close()},defaultRoute:function(){this.closeModal(),a("#media-search-input").val("").trigger("input")}}),f.view.Frame.EditAttachments=f.view.Frame.extend({className:"edit-attachment-frame",template:f.template("edit-attachment-frame"),regions:["router","content"],events:{click:"collapse","click .delete-media-item":"deleteMediaItem","click .left":"previousMediaItem","click .right":"nextMediaItem"},initialize:function(){var c=this;f.view.Frame.prototype.initialize.apply(this,arguments),b.defaults(this.options,{modal:!0,state:"edit-attachment"}),this.library=this.options.library,this.model=this.options.model?this.options.model:this.library.at(0),this.createStates(),this.on("content:render:edit-metadata",this.editMetadataContent,this),this.on("content:render:edit-image",this.editImageContentUgh,this),"undefined"!=typeof this.model&&"image"===this.model.get("type")&&(this.on("router:create",this.createRouter,this),this.on("router:render",this.browseRouter,this)),this.options.hasPrevious=this.options.library.indexOf(this.options.model)>0?!0:!1,this.options.hasNext=this.options.library.indexOf(this.options.model)-1},keyEvent:function(c){var d=a(c.target);if(27===c.keyCode)return this.resetRoute(),c;if(d.is("input")||d.is("textarea"))return c;if(39===c.keyCode){if(!this.hasNext)return;b.debounce(this.nextMediaItem(),250)}if(37===c.keyCode){if(!this.hasPrevious)return;b.debounce(this.previousMediaItem(),250)}},resetRoute:function(){d.media.mediarouter.navigate(d.media.mediarouter.baseUrl(""))}}),f.view.GridFieldOptions=f.View.extend({className:"media-grid-field-options",template:f.template("media-grid-field-options"),events:{"change input":"toggleFields"},toggleFields:function(b){var c,d,e=a(b.currentTarget);d=e.data("setting"),c=a(".data-"+d,".data-fields"),e.is(":checked")?(c.show(),deleteUserSetting("hidegrid"+d)):(c.hide(),setUserSetting("hidegrid"+d,1)),a(":checked",this.$el).length?c.parent().show():c.parent().hide()}}),f.view.BulkSelectionToggleButton=f.view.Button.extend({initialize:function(){f.view.Button.prototype.initialize.apply(this,arguments),this.listenTo(this.controller,"bulk-edit:activate bulk-edit:deactivate",b.bind(this.toggleBulkEditHandler,this))},click:function(){var a=this.controller.activeModes.where({id:"bulk-edit"}).length;f.view.Button.prototype.click.apply(this,arguments),a?(this.controller.deactivateMode("bulk-edit"),this.controller.activateMode("edit")):(this.controller.deactivateMode("edit"),this.controller.activateMode("bulk-edit"))},toggleBulkEditHandler:function(){var a=this.controller.activeModes.where({id:"bulk-edit"}).length;a?this.$el.addClass("button-primary"):(this.$el.removeClass("button-primary"),this.controller.state().get("selection").reset())}}),f.view.BulkDeleteButton=f.view.Button.extend({initialize:function(){f.view.Button.prototype.initialize.apply(this,arguments),this.$el.hide(),this.listenTo(this.controller,"bulk-edit:activate bulk-edit:deactivate",b.bind(this.visibility,this))},click:function(){for(f.view.Button.prototype.click.apply(this,arguments);this.controller.state().get("selection").length>0;)this.controller.state().get("selection").at(0).destroy()},visibility:function(){var a=this.controller.activeModes.where({id:"bulk-edit"}).length;a?this.$el.show():this.$el.hide()}})}(jQuery,_,Backbone,wp); \ No newline at end of file