Ticket #21390: 21390.7.diff

File 21390.7.diff, 64.4 KB (added by koopersmith, 6 months ago)
Line 
1Index: wp-admin/js/custom-background.js
2===================================================================
3--- wp-admin/js/custom-background.js    (revision 23000)
4+++ wp-admin/js/custom-background.js    (working copy)
5@@ -37,8 +37,8 @@
6                                }
7                        });
8 
9-                       frame.toolbar.on( 'activate:select', function() {
10-                               frame.toolbar.view().set({
11+                       frame.on( 'toolbar:render:select', function( view ) {
12+                               view.set({
13                                        select: {
14                                                style: 'primary',
15                                                text:  $el.data('update'),
16Index: wp-admin/js/custom-header.js
17===================================================================
18--- wp-admin/js/custom-header.js        (revision 23000)
19+++ wp-admin/js/custom-header.js        (working copy)
20@@ -24,8 +24,8 @@
21                                }
22                        });
23 
24-                       frame.toolbar.on( 'activate:select', function() {
25-                               frame.toolbar.view().set({
26+                       frame.on( 'toolbar:render:select', function( view ) {
27+                               view.set({
28                                        select: {
29                                                style: 'primary',
30                                                text:  $el.data('update'),
31Index: wp-includes/css/media-views-rtl.css
32===================================================================
33--- wp-includes/css/media-views-rtl.css (revision 23000)
34+++ wp-includes/css/media-views-rtl.css (working copy)
35@@ -1,25 +1,14 @@
36 /**
37  * Modal
38  */
39-.media-modal-title {
40-       left: auto;
41-       right: 0;
42-}
43-
44 .media-modal-close {
45        right: auto;
46-       left: 0;
47+       left: 7px;
48 }
49 
50 /**
51  * Toolbar
52  */
53-.media-frame-toolbar > .media-toolbar {
54-       left: 0;
55-       right: 200px;
56-}
57-
58-
59 .media-toolbar-primary {
60        float: left;
61 }
62@@ -93,21 +82,51 @@
63  * Menu
64  */
65 .media-menu {
66-       left: auto;
67-       right: 0;
68        border-right: 0;
69        border-left: 1px solid #d9d9d9;
70        box-shadow: inset 6px 0 6px -6px rgba( 0, 0, 0, 0.2 )
71 }
72 
73 /**
74+ * Router
75+ */
76+.media-router > a {
77+       float: right;
78+       border-right: 0;
79+       border-left: 1px solid #dfdfdf;
80+}
81+.media-router > a:last-child {
82+       border-left: 0;
83+}
84+
85+/**
86  * Frame
87  */
88-.media-frame .region-content {
89+.media-frame-menu {
90+       left: auto;
91+       right: 0;
92+}
93+
94+.media-frame-title,
95+.media-frame-router,
96+.media-frame-content,
97+.media-frame-toolbar {
98        left: 0;
99        right: 200px;
100 }
101 
102+.media-frame.hide-menu .media-frame-title,
103+.media-frame.hide-menu .media-frame-router,
104+.media-frame.hide-menu .media-frame-toolbar,
105+.media-frame.hide-menu .media-frame-content {
106+       right: 0;
107+}
108+
109+.media-frame.hide-menu .media-frame-menu {
110+       left: auto;
111+       right: -200px;
112+}
113+
114 /**
115  * Attachment Browser Filters
116  */
117@@ -226,30 +245,6 @@
118 }
119 
120 /**
121- * Selection Preview
122- */
123-.selected-img {
124-       float: right;
125-       margin-right: 0;
126-       margin-left: 14px;
127-}
128-
129-.selection-preview img {
130-       float: right;
131-       margin-left: 0;
132-       margin-right: 1px;
133-}
134-
135-.selection-preview .count {
136-       right: auto;
137-       left: 0;
138-}
139-
140-.selection-preview .clear-selection {
141-       float: right;
142-}
143-
144-/**
145  * Attachment Details
146  */
147 .attachment-info .thumbnail {
148@@ -289,8 +284,10 @@
149  * Responsive layout
150  */
151 @media only screen and (max-width: 900px) {
152-       .media-frame .region-content,
153-       .media-frame-toolbar > .media-toolbar {
154+       .media-frame-title,
155+       .media-frame-router,
156+       .media-frame-content,
157+       .media-frame-toolbar {
158                left: 0;
159                right: 140px;
160        }
161Index: wp-includes/css/media-views.css
162===================================================================
163--- wp-includes/css/media-views.css     (revision 23000)
164+++ wp-includes/css/media-views.css     (working copy)
165@@ -80,10 +80,10 @@
166  */
167 .media-modal {
168        position: fixed;
169-       top: 60px;
170-       left: 40px;
171-       right: 40px;
172-       bottom: 40px;
173+       top: 30px;
174+       left: 30px;
175+       right: 30px;
176+       bottom: 30px;
177        z-index: 160000;
178 }
179 
180@@ -94,42 +94,24 @@
181        right: 0;
182        bottom: 0;
183        background: #000;
184-       opacity: 0.8;
185+       opacity: 0.7;
186        z-index: 159900;
187 }
188 
189-.media-modal-backdrop div,
190-.uploader-window-content {
191+.media-modal-close {
192        position: absolute;
193-       top: 10px;
194-       left: 10px;
195-       right: 10px;
196-       bottom: 10px;
197-       border: 1px dashed rgba( 255, 255, 255, 0.5 );
198+       top: 7px;
199+       right: 7px;
200+       width: 30px;
201+       height: 30px;
202+       z-index: 1000;
203 }
204-
205-.media-modal-title {
206-       position: absolute;
207-       top: -40px;
208-       left: 0;
209-       height: 40px;
210-       padding: 0;
211-       margin: 0;
212-
213-       line-height: 40px;
214-       color: #fff;
215-       font-size: 16px;
216-       font-weight: 200;
217-       text-shadow: 0 0 16px rgba( 0, 0, 0, 0.6 );
218-}
219-
220-.media-modal-close {
221-       position: absolute;
222-       top: -27px;
223-       right: 0;
224+.media-modal-close span {
225+       display: block;
226+       margin: 8px auto 0;
227+       width: 15px;
228        height: 15px;
229-       width: 15px;
230-       background-position: -80px 0;
231+       background-position: -100px 0;
232 }
233 
234 .media-modal-close:active {
235@@ -165,18 +147,6 @@
236        border: 0 solid #dfdfdf;
237 }
238 
239-.media-frame-toolbar > .media-toolbar {
240-       top: auto;
241-       left: 200px;
242-       bottom: 0;
243-       border-width: 1px 0 0 0;
244-       box-shadow: 0 -4px 4px -4px rgba( 0, 0, 0, 0.1 );
245-}
246-
247-.hide-toolbar .media-frame-toolbar > .media-toolbar {
248-       bottom: -61px;
249-}
250-
251 .media-toolbar-primary {
252        float: right;
253 }
254@@ -238,11 +208,6 @@
255        width: 100%;
256 }
257 
258-.media-sidebar .selection-preview {
259-       display: block;
260-       padding-top: 5px;
261-}
262-
263 .media-sidebar h3 {
264        position: relative;
265        font-weight: bold;
266@@ -361,11 +326,10 @@
267        position: absolute;
268        top: 0;
269        left: 0;
270+       right: 0;
271        bottom: 0;
272-       width: 199px;
273        margin: 0;
274        padding: 16px 0;
275-       z-index: 200;
276        border-right: 1px solid #d9d9d9;
277        box-shadow: inset -6px 0 6px -6px rgba( 0, 0, 0, 0.2 );
278        -webkit-user-select: none;
279@@ -374,7 +338,8 @@
280        user-select: none;
281 }
282 
283-.media-menu li {
284+.media-menu > a {
285+       display: block;
286        position: relative;
287        padding: 4px 20px;
288        margin: 0;
289@@ -382,14 +347,16 @@
290        font-size: 14px;
291        color: #21759B;
292        text-shadow: 0 1px 0 #fff;
293+       text-decoration: none;
294 }
295 
296-.media-menu-item {
297-       cursor: pointer;
298+.media-menu > a:hover {
299+       color: #21759B;
300+       background: rgba( 0, 0, 0, 0.04 );
301 }
302 
303-.media-menu li:hover {
304-       background: rgba( 0, 0, 0, 0.04 );
305+.media-menu > a:active {
306+       outline: none;
307 }
308 
309 .media-menu .active,
310@@ -407,6 +374,63 @@
311 }
312 
313 /**
314+ * Menu
315+ */
316+.media-router {
317+       position: relative;
318+       padding: 0 6px;
319+       margin: 0;
320+       clear: both;
321+       -webkit-user-select: none;
322+       -moz-user-select: none;
323+       -ms-user-select: none;
324+       user-select: none;
325+}
326+
327+.media-router > a {
328+       position: relative;
329+       float: left;
330+       padding: 2px 10px;
331+       margin: 0;
332+       height: 18px;
333+       line-height: 18px;
334+       font-size: 14px;
335+       border-right: 1px solid #dfdfdf;
336+       text-shadow: 0 1px 0 #fff;
337+       text-decoration: none;
338+}
339+
340+.media-router > a:last-child {
341+       border-right: 0;
342+}
343+
344+.media-router > a:active {
345+       outline: none;
346+}
347+
348+.media-router .active,
349+.media-router .active:hover {
350+       color: #333;
351+}
352+
353+.media-router .active:after {
354+       content: '';
355+       display: block;
356+       margin: -100px auto 0;
357+       width: 7px;
358+       height: 7px;
359+       background: #fff;
360+       box-shadow: 1px 1px 1px rgba( 0, 0, 0, 0.2 );
361+       z-index: 300;
362+
363+       -webkit-transform: rotate( 45deg ) translate( 75px, 75px );
364+       -moz-transform:    rotate( 45deg ) translate( 75px, 75px );
365+       -ms-transform:     rotate( 45deg ) translate( 75px, 75px );
366+       -o-transform:      rotate( 45deg ) translate( 75px, 75px );
367+       transform:         rotate( 45deg ) translate( 75px, 75px );
368+}
369+
370+/**
371  * Frame
372  */
373 .media-frame {
374@@ -418,11 +442,40 @@
375        bottom: 0;
376 }
377 
378-.media-frame .region-content {
379+.media-frame-menu {
380        position: absolute;
381        top: 0;
382+       left: 0;
383+       bottom: 0;
384+       width: 199px;
385+       z-index: 150;
386+}
387+
388+.media-frame-title {
389+       position: absolute;
390+       top: 0;
391        left: 200px;
392        right: 0;
393+       height: 45px;
394+       z-index: 200;
395+}
396+
397+.media-frame-router {
398+       position: absolute;
399+       top: 45px;
400+       left: 200px;
401+       right: 0;
402+       height: 30px;
403+       z-index: 200;
404+       border-bottom: 1px solid #dfdfdf;
405+       box-shadow: 0 4px 4px -4px rgba( 0, 0, 0, 0.1 );
406+}
407+
408+.media-frame-content {
409+       position: absolute;
410+       top: 75px;
411+       left: 200px;
412+       right: 0;
413        bottom: 61px;
414        height: auto;
415        width: auto;
416@@ -430,14 +483,62 @@
417        overflow: auto;
418 }
419 
420-.media-frame.hide-toolbar .region-content {
421+.media-frame-toolbar {
422+       position: absolute;
423+       left: 200px;
424+       right: 0;
425        bottom: 0;
426+       height: 60px;
427+       z-index: 100;
428+       border: 0 solid #dfdfdf;
429+       border-width: 1px 0 0 0;
430+       box-shadow: 0 -4px 4px -4px rgba( 0, 0, 0, 0.1 );
431 }
432 
433+.media-frame.hide-menu .media-frame-title,
434+.media-frame.hide-menu .media-frame-router,
435+.media-frame.hide-menu .media-frame-toolbar,
436+.media-frame.hide-menu .media-frame-content {
437+       left: 0;
438+}
439+
440+.media-frame.hide-menu .media-frame-menu {
441+       left: -200px;
442+}
443+
444+.media-frame.hide-toolbar .media-frame-content {
445+       bottom: 0;
446+}
447+
448+.media-frame.hide-toolbar .media-frame-toolbar {
449+       bottom: -61px;
450+}
451+
452+.media-frame.hide-router .media-frame-content {
453+       top: 45px;
454+}
455+
456+.media-frame.hide-router .media-frame-router {
457+       display: none;
458+}
459+
460+.media-frame.hide-router .media-frame-title {
461+       border-bottom: 1px solid #dfdfdf;
462+       box-shadow: 0 4px 4px -4px rgba( 0, 0, 0, 0.1 );
463+}
464+
465 .media-frame .media-toolbar .add-to-gallery {
466        display: none;
467 }
468 
469+.media-frame-title h1 {
470+       padding: 0 16px;
471+       font-size: 22px;
472+       font-weight: 200;
473+       line-height: 45px;
474+       margin: 0;
475+}
476+
477 /**
478  * Iframes
479  */
480@@ -721,6 +822,9 @@
481  * Attachments Browser
482  */
483 .media-frame .attachments-browser {
484+       position: relative;
485+       width: 100%;
486+       height: 100%;
487        overflow: hidden;
488 }
489 
490@@ -903,7 +1007,12 @@
491 }
492 
493 .uploader-window-content {
494-       border-color: #fff;
495+       position: absolute;
496+       top: 10px;
497+       left: 10px;
498+       right: 10px;
499+       bottom: 10px;
500+       border: 1px dashed #fff;
501 }
502 
503 .uploader-window h3 {
504@@ -956,6 +1065,10 @@
505        margin: 4em 0;
506 }
507 
508+.uploader-inline .has-upload-message .upload-ui {
509+       margin: 0 0 4em;
510+}
511+
512 .uploader-inline h3 {
513        font-size: 20px;
514        line-height: 28px;
515@@ -963,6 +1076,12 @@
516        margin-bottom: 1.6em;
517 }
518 
519+.uploader-inline .has-upload-message .upload-instructions {
520+       font-size: 14px;
521+       color: #464646;
522+       font-weight: normal;
523+}
524+
525 .uploader-inline .drop-instructions {
526        display: none;
527 }
528@@ -1058,7 +1177,7 @@
529        vertical-align: top;
530 }
531 
532-.media-selection .attachment img {
533+.media-selection .attachment .icon {
534        width: 50%;
535 }
536 
537@@ -1098,69 +1217,6 @@
538 }
539 
540 /**
541- * Selection Preview
542- */
543-.selection-preview {
544-       position: relative;
545-       height: 60px;
546-       overflow: hidden;
547-}
548-
549-.selected-img {
550-       float: left;
551-       position: relative;
552-       margin-right: 14px;
553-}
554-
555-.selection-preview img {
556-       max-width: 40px;
557-       max-height: 40px;
558-       float: left;
559-       margin-top: 6px;
560-       margin-left: 1px;
561-       border: 2px solid white;
562-       box-shadow:
563-           0 0 0 1px #ccc,
564-           3px 3px 0 0 #fff,
565-           3px 3px 0 1px #ccc,
566-           6px 6px 0 0 #fff,
567-           6px 6px 0 1px #ccc;
568-}
569-
570-.selection-preview .selected-count-1 img {
571-       margin-top: 8px;
572-       box-shadow: 0 0 0 1px #ccc;
573-}
574-
575-.selection-preview .selected-count-2 img {
576-       margin-top: 7px;
577-       box-shadow:
578-           0 0 0 1px #ccc,
579-           3px 3px 0 0 #fff,
580-           3px 3px 0 1px #ccc;
581-}
582-
583-.selection-preview .count {
584-       position: absolute;
585-       bottom: 0;
586-       right: 0;
587-       height: 16px;
588-       min-width: 8px;
589-       padding: 0 4px;
590-       font-size: 12px;
591-       text-align: center;
592-       font-weight: bold;
593-       color: #999;
594-       background: #fff;
595-       box-shadow: -1px -1px 2px -1px rgba( 0, 0, 0, 0.2 );
596-}
597-
598-.selection-preview .clear-selection {
599-       float: left;
600-       line-height: 60px;
601-}
602-
603-/**
604  * Spinner
605  */
606 .media-sidebar .settings-save-status {
607@@ -1297,21 +1353,17 @@
608 .embed-url {
609        display: block;
610        position: relative;
611-       height: 75px;
612-       padding: 16px 16px;
613+       height: 40px;
614+       padding: 0 16px 16px;
615        margin: 0;
616-       z-index: 50;
617+       z-index: 250;
618+       background: #fff;
619        border-bottom: 1px solid #dfdfdf;
620        box-shadow: 0 4px 4px -4px rgba( 0, 0, 0, 0.1 );
621        font-size: 18px;
622        font-weight: 200;
623 }
624 
625-.embed-url span {
626-       display: block;
627-       padding: 4px 0 6px 2px;
628-}
629-
630 .media-frame .embed-url input {
631        font-size: 18px;
632        padding: 12px 14px;
633@@ -1324,7 +1376,7 @@
634 .embed-image-settings {
635        position: absolute;
636        background: #f5f5f5;
637-       top: 108px;
638+       top: 57px;
639        left: 0;
640        right: 0;
641        bottom: 0;
642@@ -1388,32 +1440,7 @@
643  * Responsive layout
644  */
645 @media only screen and (max-width: 900px) {
646-       .media-modal {
647-               bottom: 20px;
648-               left: 20px;
649-               right: 20px;
650-               top: 40px;
651-       }
652-
653-       .media-modal-title {
654-               height: 30px;
655-               line-height: 30px;
656-               top: -30px;
657-       }
658-
659-       .media-modal-close {
660-               top: -23px;
661-       }
662-
663-       .media-modal-backdrop div,
664-       .uploader-window-content {
665-               top: 5px;
666-               left: 5px;
667-               right: 5px;
668-               bottom: 5px;
669-       }
670-
671-       .media-menu {
672+       .media-frame-menu {
673                width: 139px;
674        }
675 
676@@ -1421,8 +1448,10 @@
677                padding: 4px 10px;
678        }
679 
680-       .media-frame .region-content,
681-       .media-frame-toolbar > .media-toolbar {
682+       .media-frame-title,
683+       .media-frame-router,
684+       .media-frame-content,
685+       .media-frame-toolbar {
686                left: 140px;
687        }
688 
689Index: wp-includes/js/media-editor.js
690===================================================================
691--- wp-includes/js/media-editor.js      (revision 23000)
692+++ wp-includes/js/media-editor.js      (working copy)
693@@ -384,7 +384,7 @@
694 
695                        workflow = workflows[ id ] = wp.media( _.defaults( options || {}, {
696                                frame:    'post',
697-                               state:    'upload',
698+                               state:    'insert',
699                                title:    wp.media.view.l10n.addMedia,
700                                multiple: true
701                        } ) );
702@@ -408,11 +408,13 @@
703                        }, this );
704 
705                        workflow.state('embed').on( 'select', function() {
706-                               var embed = workflow.state().toJSON();
707+                               var state = workflow.state(),
708+                                       type = state.get('type'),
709+                                       embed = state.props.toJSON();
710 
711                                embed.url = embed.url || '';
712 
713-                               if ( 'link' === embed.type ) {
714+                               if ( 'link' === type ) {
715                                        _.defaults( embed, {
716                                                title:   embed.url,
717                                                linkUrl: embed.url
718@@ -420,7 +422,7 @@
719 
720                                        this.send.link( embed );
721 
722-                               } else if ( 'image' === embed.type ) {
723+                               } else if ( 'image' === type ) {
724                                        _.defaults( embed, {
725                                                title:   embed.url,
726                                                linkUrl: '',
727Index: wp-includes/js/media-views.js
728===================================================================
729--- wp-includes/js/media-views.js       (revision 23000)
730+++ wp-includes/js/media-views.js       (working copy)
731@@ -68,65 +68,71 @@
732         * wp.media.controller.Region
733         */
734        media.controller.Region = function( options ) {
735-               _.extend( this, _.pick( options || {}, 'id', 'controller', 'selector' ) );
736-
737-               this.on( 'activate:empty', this.empty, this );
738-               this.mode('empty');
739+               _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) );
740        };
741 
742        // Use Backbone's self-propagating `extend` inheritance method.
743        media.controller.Region.extend = Backbone.Model.extend;
744 
745-       _.extend( media.controller.Region.prototype, Backbone.Events, {
746-               trigger: (function() {
747-                       var eventSplitter = /\s+/,
748-                               trigger = Backbone.Events.trigger;
749+       _.extend( media.controller.Region.prototype, {
750+               mode: function( mode ) {
751+                       if ( ! mode )
752+                               return this._mode;
753 
754-                       return function( events ) {
755-                               var mode = ':' + this._mode,
756-                                       modeEvents = events.split( eventSplitter ).join( mode ) + mode;
757-
758-                               trigger.apply( this, arguments );
759-                               trigger.apply( this, [ modeEvents ].concat( _.rest( arguments ) ) );
760+                       // Bail if we're trying to change to the current mode.
761+                       if ( mode === this._mode )
762                                return this;
763-                       };
764-               }()),
765 
766-               mode: function( mode ) {
767-                       if ( mode ) {
768-                               this.trigger( 'deactivate', this );
769-                               this._mode = mode;
770-                               return this.trigger( 'activate', this );
771-                       }
772-                       return this._mode;
773+                       this.trigger('deactivate');
774+                       this._mode = mode;
775+                       this.render( mode );
776+                       this.trigger('activate');
777+                       return this;
778                },
779 
780-               view: function( view ) {
781-                       var previous = this._view,
782-                               mode = this._mode,
783-                               id = this.id;
784+               render: function( mode ) {
785+                       // If no mode is provided, just re-render the current mode.
786+                       // If the provided mode isn't active, perform a full switch.
787+                       if ( mode && mode !== this._mode )
788+                               return this.mode( mode );
789 
790-                       // If no argument is provided, return the current view.
791-                       if ( ! view )
792-                               return previous;
793+                       var set = { view: null },
794+                               view;
795 
796-                       // If we're attempting to switch to the current view, bail.
797-                       if ( view === previous )
798+                       this.trigger( 'create', set );
799+                       view = set.view;
800+                       this.trigger( 'render', view );
801+                       if ( view )
802+                               this.set( view );
803+                       return this;
804+               },
805+
806+               get: function() {
807+                       return this.view.views.first( this.selector );
808+               },
809+
810+               set: function( views, options ) {
811+                       if ( options )
812+                               options.add = false;
813+                       return this.view.views.set( this.selector, views, options );
814+               },
815+
816+               trigger: function( event ) {
817+                       var base;
818+                       if ( ! this._mode )
819                                return;
820 
821-                       // Add classes to the new view.
822-                       if ( id )
823-                               view.$el.addClass( 'region-' + id );
824+                       var args = _.toArray( arguments );
825+                       base = this.id + ':' + event;
826 
827-                       if ( mode )
828-                               view.$el.addClass( 'mode-' + mode );
829+                       // Trigger `region:action:mode` event.
830+                       args[0] = base + ':' + this._mode;
831+                       this.view.trigger.apply( this.view, args );
832 
833-                       this.controller.views.set( this.selector, view );
834-                       this._view = view;
835-               },
836-
837-               empty: function() {
838-                       this.view( new media.View() );
839+                       // Trigger `region:action` event.
840+                       args[0] = base;
841+                       this.view.trigger.apply( this.view, args );
842+                       return this;
843                }
844        });
845 
846@@ -208,31 +214,86 @@
847        // wp.media.controller.State
848        // ---------------------------
849        media.controller.State = Backbone.Model.extend({
850-               initialize: function() {
851-                       this.on( 'activate', this._activate, this );
852+               constructor: function() {
853+                       this.on( 'activate', this._preActivate, this );
854                        this.on( 'activate', this.activate, this );
855+                       this.on( 'activate', this._postActivate, this );
856                        this.on( 'deactivate', this._deactivate, this );
857                        this.on( 'deactivate', this.deactivate, this );
858                        this.on( 'reset', this.reset, this );
859+                       this.on( 'ready', this._ready, this );
860+                       this.on( 'ready', this.ready, this );
861+
862+                       this.on( 'change:menu', this._updateMenu, this );
863+
864+                       Backbone.Model.apply( this, arguments );
865                },
866 
867+               ready: function() {},
868                activate: function() {},
869-               _activate: function() {
870+               deactivate: function() {},
871+               reset: function() {},
872+
873+               _ready: function() {
874+                       this._updateMenu();
875+               },
876+
877+               _preActivate: function() {
878                        this.active = true;
879+               },
880 
881-                       this.menu();
882-                       this.toolbar();
883-                       this.content();
884+               _postActivate: function() {
885+                       this.on( 'change:menu', this._menu, this );
886+                       this.on( 'change:titleMode', this._title, this );
887+                       this.on( 'change:content', this._content, this );
888+                       this.on( 'change:toolbar', this._toolbar, this );
889+
890+                       this.frame.on( 'title:render:default', this._renderTitle, this );
891+
892+                       this._title();
893+                       this._menu();
894+                       this._toolbar();
895+                       this._content();
896+                       this._router();
897                },
898 
899-               deactivate: function() {},
900+
901                _deactivate: function() {
902                        this.active = false;
903+
904+                       this.frame.off( 'title:render:default', this._renderTitle, this );
905+
906+                       this.off( 'change:menu', this._menu, this );
907+                       this.off( 'change:titleMode', this._title, this );
908+                       this.off( 'change:content', this._content, this );
909+                       this.off( 'change:toolbar', this._toolbar, this );
910                },
911 
912-               reset: function() {},
913+               _title: function() {
914+                       this.frame.title.render( this.get('titleMode') || 'default' );
915+               },
916 
917-               menu: function() {
918+               _renderTitle: function( view ) {
919+                       view.$el.text( this.get('title') || '' );
920+               },
921+
922+               _router: function() {
923+                       var router = this.frame.router,
924+                               mode = this.get('router'),
925+                               view;
926+
927+                       this.frame.$el.toggleClass( 'hide-router', ! mode );
928+                       if ( ! mode )
929+                               return;
930+
931+                       this.frame.router.render( mode );
932+
933+                       view = router.get();
934+                       if ( view.select )
935+                               view.select( this.frame.content.mode() );
936+               },
937+
938+               _menu: function() {
939                        var menu = this.frame.menu,
940                                mode = this.get('menu'),
941                                view;
942@@ -240,20 +301,48 @@
943                        if ( ! mode )
944                                return;
945 
946-                       if ( menu.mode() !== mode )
947-                               menu.mode( mode );
948+                       menu.mode( mode );
949 
950-                       view = menu.view();
951+                       view = menu.get();
952                        if ( view.select )
953                                view.select( this.id );
954+               },
955+
956+               _updateMenu: function() {
957+                       var previous = this.previous('menu'),
958+                               menu = this.get('menu');
959+
960+                       if ( previous )
961+                               this.frame.off( 'menu:render:' + previous, this._renderMenu, this );
962+
963+                       if ( menu )
964+                               this.frame.on( 'menu:render:' + menu, this._renderMenu, this );
965+               },
966+
967+               _renderMenu: function( view ) {
968+                       var menuItem = this.get('menuItem'),
969+                               title = this.get('title'),
970+                               priority = this.get('priority');
971+
972+                       if ( ! menuItem && title ) {
973+                               menuItem = { text: title };
974+
975+                               if ( priority )
976+                                       menuItem.priority = priority;
977+                       }
978+
979+                       if ( ! menuItem )
980+                               return;
981+
982+                       view.set( this.id, menuItem );
983                }
984        });
985 
986        _.each(['toolbar','content'], function( region ) {
987-               media.controller.State.prototype[ region ] = function() {
988+               media.controller.State.prototype[ '_' + region ] = function() {
989                        var mode = this.get( region );
990                        if ( mode )
991-                               this.frame[ region ].mode( mode );
992+                               this.frame[ region ].render( mode );
993                };
994        });
995 
996@@ -262,15 +351,22 @@
997        media.controller.Library = media.controller.State.extend({
998                defaults: {
999                        id:         'library',
1000-                       multiple:   false,
1001+                       multiple:   false, // false, 'add', 'reset'
1002                        describe:   false,
1003-                       toolbar:    'main-attachments',
1004+                       toolbar:    'select',
1005                        sidebar:    'settings',
1006-                       content:    'browse',
1007+                       content:    'upload',
1008+                       router:     'browse',
1009                        searchable: true,
1010                        filterable: false,
1011-                       uploads:    true,
1012-                       sortable:   true
1013+                       sortable:   true,
1014+                       title:      l10n.mediaLibraryTitle,
1015+
1016+                       // Uses a user setting to override the content mode.
1017+                       contentUserSetting: true,
1018+
1019+                       // Sync the selection from the last state when 'multiple' matches.
1020+                       syncLastSelection: true
1021                },
1022 
1023                initialize: function() {
1024@@ -290,31 +386,37 @@
1025                                this.set( 'gutter', 8 );
1026 
1027                        this.resetDisplays();
1028-
1029-                       media.controller.State.prototype.initialize.apply( this, arguments );
1030                },
1031 
1032                activate: function() {
1033                        var library = this.get('library'),
1034-                               selection = this.get('selection');
1035+                               selection = this.get('selection'),
1036+                               mode;
1037 
1038+                       if ( this.get('syncLastSelection') ) {
1039+                               this.getLastSelection();
1040+                       }
1041+
1042                        this._excludeStateLibrary();
1043                        this.buildComposite();
1044                        this.on( 'change:library change:exclude', this.buildComposite, this );
1045                        this.on( 'change:excludeState', this._excludeState, this );
1046 
1047-                       // If we're in a workflow that supports multiple attachments,
1048-                       // automatically select any uploading attachments.
1049-                       if ( this.get('multiple') )
1050-                               wp.Uploader.queue.on( 'add', this.selectUpload, this );
1051+                       wp.Uploader.queue.on( 'add', this.uploading, this );
1052 
1053                        selection.on( 'add remove reset', this.refreshSelection, this );
1054 
1055-                       this.refresh();
1056                        this.on( 'insert', this._insertDisplaySettings, this );
1057+
1058+                       if ( this.get('contentUserSetting') ) {
1059+                               this.frame.on( 'content:activate', this.saveContentMode, this );
1060+                               this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) );
1061+                       }
1062                },
1063 
1064                deactivate: function() {
1065+                       this.frame.off( 'content:activate', this.saveContentMode, this );
1066+
1067                        // Unbind all event handlers that use this state as the context
1068                        // from the selection.
1069                        this.get('selection').off( null, null, this );
1070@@ -330,10 +432,6 @@
1071                reset: function() {
1072                        this.get('selection').reset();
1073                        this.resetDisplays();
1074-               },
1075-
1076-               refresh: function() {
1077-                       this.content();
1078                        this.refreshSelection();
1079                },
1080 
1081@@ -371,21 +469,63 @@
1082                        setUserSetting( 'urlbutton', display.link );
1083                },
1084 
1085+               getLastSelection: function() {
1086+                       var selection = this.get('selection'),
1087+                               lastState = this.frame.lastState(),
1088+                               lastSelection = lastState && lastState.get('selection'),
1089+                               lastMultiple, thisMultiple;
1090+
1091+                       if ( ! lastSelection )
1092+                               return;
1093+
1094+                       // We don't care about the method of multiple selection the
1095+                       // selections use, just that they both support (or don't support)
1096+                       // multiple selection.
1097+                       lastMultiple = !! lastSelection.multiple;
1098+                       thisMultiple = !! selection.multiple;
1099+
1100+                       if ( lastMultiple !== thisMultiple )
1101+                               return;
1102+
1103+                       selection.reset( lastSelection.toArray() ).single( lastSelection.single() );
1104+               },
1105+
1106                refreshSelection: function() {
1107                        var selection = this.get('selection'),
1108                                mode = this.frame.content.mode();
1109 
1110-                       this.frame.toolbar.view().refresh();
1111+                       this.frame.toolbar.get().refresh();
1112                        this.trigger( 'refresh:selection', this, selection );
1113 
1114                        if ( ! selection.length && 'browse' !== mode && 'upload' !== mode )
1115-                               this.content();
1116+                               this.frame.content.render( this.get('content') );
1117                },
1118 
1119-               selectUpload: function( attachment ) {
1120-                       this.get('selection').add( attachment );
1121+               uploading: function( attachment ) {
1122+                       var content = this.frame.content;
1123+
1124+                       // If the uploader was selected, navigate to the browser.
1125+                       if ( 'upload' === content.mode() )
1126+                               this.frame.content.mode('browse');
1127+
1128+                       // If we're in a workflow that supports multiple attachments,
1129+                       // automatically select any uploading attachments.
1130+                       if ( this.get('multiple') )
1131+                               this.get('selection').add( attachment );
1132                },
1133 
1134+               saveContentMode: function() {
1135+                       // Only track the browse router on library states.
1136+                       if ( 'browse' !== this.get('router') )
1137+                               return;
1138+
1139+                       var mode = this.frame.content.mode(),
1140+                               view = this.frame.router.get();
1141+
1142+                       if ( view && view.get( mode ) )
1143+                               setUserSetting( 'libraryContent', mode );
1144+               },
1145+
1146                buildComposite: function() {
1147                        var original = this.get('_library'),
1148                                exclude = this.get('exclude'),
1149@@ -448,45 +588,9 @@
1150                }
1151        });
1152 
1153-
1154-       // wp.media.controller.Upload
1155-       // ---------------------------
1156-       media.controller.Upload = media.controller.State.extend({
1157-               defaults: _.defaults({
1158-                       id:      'upload',
1159-                       content: 'upload',
1160-                       toolbar: 'empty',
1161-                       uploads: true,
1162-
1163-                       // The state to navigate to when files are uploading.
1164-                       libraryState: 'library'
1165-               }, media.controller.State.prototype.defaults ),
1166-
1167-               initialize: function() {
1168-                       media.controller.State.prototype.initialize.apply( this, arguments );
1169-               },
1170-
1171-               activate: function() {
1172-                       wp.Uploader.queue.on( 'add', this.uploading, this );
1173-                       media.controller.State.prototype.activate.apply( this, arguments );
1174-               },
1175-
1176-               deactivate: function() {
1177-                       wp.Uploader.queue.off( null, null, this );
1178-                       media.controller.State.prototype.deactivate.apply( this, arguments );
1179-               },
1180-
1181-               uploading: function( attachment ) {
1182-                       var library = this.get('libraryState');
1183-
1184-                       this.frame.state( library ).get('selection').add( attachment );
1185-                       this.frame.setState( library );
1186-               }
1187-       });
1188-
1189-       // wp.media.controller.Gallery
1190-       // ---------------------------
1191-       media.controller.Gallery = media.controller.Library.extend({
1192+       // wp.media.controller.GalleryEdit
1193+       // -------------------------------
1194+       media.controller.GalleryEdit = media.controller.Library.extend({
1195                defaults: {
1196                        id:         'gallery-edit',
1197                        multiple:   false,
1198@@ -496,7 +600,9 @@
1199                        sortable:   true,
1200                        searchable: false,
1201                        toolbar:    'gallery-edit',
1202-                       content:    'browse'
1203+                       content:    'browse',
1204+                       title:      l10n.editGalleryTitle,
1205+                       priority:   60
1206                },
1207 
1208                initialize: function() {
1209@@ -519,7 +625,7 @@
1210                        // Watch for uploaded attachments.
1211                        this.get('library').observe( wp.Uploader.queue );
1212 
1213-                       this.frame.content.on( 'activate:browse', this.gallerySettings, this );
1214+                       this.frame.on( 'content:render:browse', this.gallerySettings, this );
1215 
1216                        media.controller.Library.prototype.activate.apply( this, arguments );
1217                },
1218@@ -528,21 +634,19 @@
1219                        // Stop watching for uploaded attachments.
1220                        this.get('library').unobserve( wp.Uploader.queue );
1221 
1222-                       this.frame.content.off( null, null, this );
1223+                       this.frame.off( 'content:render:browse', this.gallerySettings, this );
1224+
1225                        media.controller.Library.prototype.deactivate.apply( this, arguments );
1226                },
1227 
1228-               gallerySettings: function() {
1229-                       var library = this.get('library'),
1230-                               browser;
1231+               gallerySettings: function( browser ) {
1232+                       var library = this.get('library');
1233 
1234-                       if ( ! library )
1235+                       if ( ! library || ! browser )
1236                                return;
1237 
1238                        library.gallery = library.gallery || new Backbone.Model();
1239 
1240-                       browser = this.frame.content.view();
1241-
1242                        browser.sidebar.set({
1243                                gallery: new media.view.Settings.Gallery({
1244                                        controller: this,
1245@@ -570,7 +674,9 @@
1246                        filterable: 'uploaded',
1247                        multiple:   false,
1248                        menu:       'main',
1249-                       toolbar:    'featured-image'
1250+                       toolbar:    'featured-image',
1251+                       title:      l10n.featuredImageTitle,
1252+                       priority:   60
1253                }, media.controller.Library.prototype.defaults ),
1254 
1255                initialize: function() {
1256@@ -629,7 +735,10 @@
1257                        menu:    'main',
1258                        content: 'embed',
1259                        toolbar: 'main-embed',
1260-                       type:    'link'
1261+                       type:    'link',
1262+
1263+                       title:    l10n.fromUrlTitle,
1264+                       priority: 120
1265                },
1266 
1267                // The amount of time used when debouncing the scan.
1268@@ -637,9 +746,9 @@
1269 
1270                initialize: function() {
1271                        this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity );
1272-                       this.on( 'change:url', this.debouncedScan, this );
1273+                       this.props = new Backbone.Model({ url: '' });
1274+                       this.props.on( 'change:url', this.debouncedScan, this );
1275                        this.on( 'scan', this.scanImage, this );
1276-                       media.controller.State.prototype.initialize.apply( this, arguments );
1277                },
1278 
1279                scan: function() {
1280@@ -652,11 +761,11 @@
1281                scanImage: function( attributes ) {
1282                        var frame = this.frame,
1283                                state = this,
1284-                               url = this.get('url'),
1285+                               url = this.props.get('url'),
1286                                image = new Image();
1287 
1288                        image.onload = function() {
1289-                               if ( state !== frame.state() || url !== state.get('url') )
1290+                               if ( state !== frame.state() || url !== state.props.get('url') )
1291                                        return;
1292 
1293                                state.set({
1294@@ -670,14 +779,10 @@
1295                },
1296 
1297                reset: function() {
1298-                       _.each( _.difference( _.keys( this.attributes ), _.keys( this.defaults ) ), function( key ) {
1299-                               this.unset( key );
1300-                       }, this );
1301+                       this.props = new Backbone.Model({ url: '' });
1302 
1303-                       this.set( 'url', '' );
1304-
1305                        if ( this.id === this.frame.state().id )
1306-                               this.frame.toolbar.view().refresh();
1307+                               this.frame.toolbar.get().refresh();
1308                }
1309        });
1310 
1311@@ -1022,9 +1127,13 @@
1312                // The constructor for the `Views` manager.
1313                Views: media.Views,
1314 
1315-               constructor: function() {
1316+               constructor: function( options ) {
1317                        this.views = new this.Views( this, this.views );
1318                        this.on( 'ready', this.ready, this );
1319+
1320+                       if ( options && options.controller )
1321+                               this.controller = options.controller;
1322+
1323                        Backbone.View.apply( this, arguments );
1324                },
1325 
1326@@ -1097,9 +1206,9 @@
1327                        // Initialize regions.
1328                        _.each( this.regions, function( region ) {
1329                                this[ region ] = new media.controller.Region({
1330-                                       controller: this,
1331-                                       id:         region,
1332-                                       selector:   '.media-frame-' + region
1333+                                       view:     this,
1334+                                       id:       region,
1335+                                       selector: '.media-frame-' + region
1336                                });
1337                        }, this );
1338                },
1339@@ -1113,6 +1222,7 @@
1340                        // Ensure states have a reference to the frame.
1341                        this.states.on( 'add', function( model ) {
1342                                model.frame = this;
1343+                               model.trigger('ready');
1344                        }, this );
1345                },
1346 
1347@@ -1131,7 +1241,7 @@
1348        media.view.MediaFrame = media.view.Frame.extend({
1349                className: 'media-frame',
1350                template:  media.template('media-frame'),
1351-               regions:   ['menu','content','toolbar'],
1352+               regions:   ['menu','title','content','toolbar','router'],
1353 
1354                initialize: function() {
1355                        media.view.Frame.prototype.initialize.apply( this, arguments );
1356@@ -1173,6 +1283,10 @@
1357                        }
1358 
1359                        this.on( 'attach', _.bind( this.views.ready, this.views ), this );
1360+
1361+                       // Bind default title creation.
1362+                       this.on( 'title:create:default', this.createTitle, this );
1363+                       this.title.mode('default');
1364                },
1365 
1366                render: function() {
1367@@ -1183,6 +1297,31 @@
1368                        return media.view.Frame.prototype.render.apply( this, arguments );
1369                },
1370 
1371+               createTitle: function( title ) {
1372+                       title.view = new media.View({
1373+                               controller: this,
1374+                               tagName: 'h1'
1375+                       });
1376+               },
1377+
1378+               createMenu: function( menu ) {
1379+                       menu.view = new media.view.Menu({
1380+                               controller: this
1381+                       });
1382+               },
1383+
1384+               createToolbar: function( toolbar ) {
1385+                       menu.view = new media.view.Toolbar({
1386+                               controller: this
1387+                       });
1388+               },
1389+
1390+               createRouter: function( router ) {
1391+                       router.view = new media.view.Router({
1392+                               controller: this
1393+                       });
1394+               },
1395+
1396                createIframeStates: function( options ) {
1397                        var settings = media.view.settings,
1398                                tabs = settings.tabs,
1399@@ -1208,22 +1347,25 @@
1400                                }, options ) );
1401                        }, this );
1402 
1403-                       this.content.on( 'activate:iframe', this.iframeContent, this );
1404-                       this.menu.on( 'activate:main', this.iframeMenu, this );
1405+                       this.on( 'content:create:iframe', this.iframeContent, this );
1406+                       this.on( 'menu:render:main', this.iframeMenu, this );
1407                        this.on( 'open', this.hijackThickbox, this );
1408                        this.on( 'close', this.restoreThickbox, this );
1409                },
1410 
1411-               iframeContent: function() {
1412+               iframeContent: function( content ) {
1413                        this.$el.addClass('hide-toolbar');
1414-                       this.content.view( new media.view.Iframe({
1415+                       content.view = new media.view.Iframe({
1416                                controller: this
1417-                       }).render() );
1418+                       });
1419                },
1420 
1421-               iframeMenu: function() {
1422+               iframeMenu: function( view ) {
1423                        var views = {};
1424 
1425+                       if ( ! view )
1426+                               return;
1427+
1428                        _.each( media.view.settings.tabs, function( title, id ) {
1429                                views[ 'iframe:' + id ] = {
1430                                        text: this.state( 'iframe:' + id ).get('title'),
1431@@ -1231,7 +1373,7 @@
1432                                };
1433                        }, this );
1434 
1435-                       this.menu.view().set( views );
1436+                       view.set( views );
1437                },
1438 
1439                hijackThickbox: function() {
1440@@ -1305,83 +1447,76 @@
1441                                new media.controller.Library({
1442                                        selection: options.selection,
1443                                        library:   media.query( options.library ),
1444-                                       multiple:  this.options.multiple,
1445+                                       multiple:  options.multiple,
1446                                        menu:      'main',
1447-                                       toolbar:   'select'
1448-                               }),
1449-
1450-                               new media.controller.Upload({
1451-                                       menu: 'main'
1452+                                       title:     options.title,
1453+                                       priority:  20
1454                                })
1455                        ]);
1456                },
1457 
1458                bindHandlers: function() {
1459-                       this.menu.on( 'activate:main', this.mainMenu, this );
1460-                       this.content.on( 'activate:browse', this.browseContent, this );
1461-                       this.content.on( 'activate:upload', this.uploadContent, this );
1462-                       this.toolbar.on( 'activate:select', this.selectToolbar, this );
1463+                       this.on( 'menu:create:main', this.createMenu, this );
1464+                       this.on( 'router:create:browse', this.createRouter, this );
1465+                       this.on( 'router:render:browse', this.browseRouter, this );
1466+                       this.on( 'content:create:browse', this.browseContent, this );
1467+                       this.on( 'content:render:upload', this.uploadContent, this );
1468+                       this.on( 'toolbar:create:select', this.createSelectToolbar, this );
1469 
1470                        this.on( 'refresh:selection', this.refreshSelectToolbar, this );
1471                },
1472 
1473-               mainMenu: function( options ) {
1474-                       this.menu.view( new media.view.Menu({
1475-                               controller: this,
1476-                               silent:     options && options.silent,
1477-
1478-                               views: {
1479-                                       upload: {
1480-                                               text: l10n.uploadFilesTitle,
1481-                                               priority: 20
1482-                                       },
1483-                                       library: {
1484-                                               text: l10n.mediaLibraryTitle,
1485-                                               priority: 40
1486-                                       }
1487+               // Routers
1488+               browseRouter: function( view ) {
1489+                       view.set({
1490+                               upload: {
1491+                                       text:     l10n.uploadFilesTitle,
1492+                                       priority: 20
1493+                               },
1494+                               browse: {
1495+                                       text:     l10n.mediaLibraryTitle,
1496+                                       priority: 40
1497                                }
1498-                       }) );
1499+                       });
1500                },
1501 
1502                // Content
1503-               browseContent: function() {
1504+               browseContent: function( content ) {
1505                        var state = this.state();
1506 
1507                        this.$el.removeClass('hide-toolbar');
1508 
1509                        // Browse our library of attachments.
1510-                       this.content.view( new media.view.AttachmentsBrowser({
1511+                       content.view = new media.view.AttachmentsBrowser({
1512                                controller: this,
1513                                collection: state.get('library'),
1514                                selection:  state.get('selection'),
1515                                model:      state,
1516                                sortable:   state.get('sortable'),
1517                                search:     state.get('searchable'),
1518-                               uploads:    state.get('uploads'),
1519                                filters:    state.get('filterable'),
1520                                display:    state.get('displaySettings'),
1521 
1522                                AttachmentView: state.get('AttachmentView')
1523-                       }) );
1524+                       });
1525                },
1526 
1527                uploadContent: function() {
1528-                       this.$el.addClass('hide-toolbar');
1529-
1530-                       this.content.view( new media.view.UploaderInline({
1531+                       this.$el.removeClass('hide-toolbar');
1532+                       this.content.set( new media.view.UploaderInline({
1533                                controller: this
1534                        }) );
1535                },
1536 
1537                // Toolbars
1538-               selectToolbar: function( options ) {
1539+               createSelectToolbar: function( toolbar, options ) {
1540                        options = _.defaults( options || {}, {
1541                                event:  'select',
1542                                silent: false,
1543                                state:  false
1544                        });
1545 
1546-                       this.toolbar.view( new media.view.Toolbar({
1547+                       toolbar.view = new media.view.Toolbar({
1548                                controller: this,
1549                                silent:     options.silent,
1550 
1551@@ -1402,7 +1537,7 @@
1552                                                }
1553                                        }
1554                                }
1555-                       }) );
1556+                       });
1557                },
1558 
1559                refreshSelectToolbar: function() {
1560@@ -1411,7 +1546,7 @@
1561                        if ( ! selection || 'select' !== this.toolbar.mode() )
1562                                return;
1563 
1564-                       this.toolbar.view().get('select').model.set( 'disabled', ! selection.length );
1565+                       this.toolbar.get().get('select').model.set( 'disabled', ! selection.length );
1566                }
1567        });
1568 
1569@@ -1430,35 +1565,55 @@
1570                },
1571 
1572                createStates: function() {
1573-                       var options = this.options;
1574+                       var options = this.options,
1575+                               selection = options.selection,
1576+                               library = {
1577+                                       editable: true,
1578+                                       menu:     'main',
1579 
1580-                       // Add the default states.
1581-                       this.states.add([
1582-                               // Main states.
1583-                               new media.controller.Library({
1584-                                       selection:  options.selection,
1585-                                       library:    media.query( options.library ),
1586-                                       editable:   true,
1587-                                       filterable: 'all',
1588-                                       multiple:   this.options.multiple,
1589-                                       menu:       'main',
1590-
1591                                        // Show the attachment display settings.
1592                                        displaySettings: true,
1593                                        // Update user settings when users adjust the
1594                                        // attachment display settings.
1595                                        displayUserSettings: true
1596-                               }),
1597+                               };
1598 
1599-                               new media.controller.Upload({
1600-                                       menu: 'main'
1601-                               }),
1602+                       // Add the default states.
1603+                       this.states.add([
1604+                               // Main states.
1605+                               new media.controller.Library( _.defaults({
1606+                                       id:         'insert',
1607+                                       title:      l10n.insertMediaTitle,
1608+                                       priority:   20,
1609+                                       toolbar:    'main-insert',
1610+                                       filterable: 'all',
1611+                                       library:    media.query( options.library ),
1612+                                       selection:  selection,
1613+                                       multiple:   options.multiple ? 'reset' : false
1614+                               }, library ) ),
1615 
1616+                               new media.controller.Library( _.defaults({
1617+                                       id:         'gallery',
1618+                                       title:      l10n.createGalleryTitle,
1619+                                       priority:   40,
1620+                                       toolbar:    'main-gallery',
1621+                                       filterable: 'uploaded',
1622+                                       multiple:   'add',
1623+
1624+                                       library:  media.query( _.defaults({
1625+                                               type: 'image'
1626+                                       }, options.library ) ),
1627+
1628+                                       selection: new media.model.Selection( selection.models, {
1629+                                               multiple: 'add'
1630+                                       })
1631+                               }, library ) ),
1632+
1633                                // Embed states.
1634                                new media.controller.Embed(),
1635 
1636                                // Gallery states.
1637-                               new media.controller.Gallery({
1638+                               new media.controller.GalleryEdit({
1639                                        library: options.selection,
1640                                        editing: options.editing,
1641                                        menu:    'gallery'
1642@@ -1468,16 +1623,12 @@
1643                                        id:           'gallery-library',
1644                                        library:      media.query({ type: 'image' }),
1645                                        filterable:   'uploaded',
1646-                                       multiple:     true,
1647+                                       multiple:     'add',
1648                                        menu:         'gallery',
1649                                        toolbar:      'gallery-add',
1650-                                       excludeState: 'gallery-edit'
1651-                               }),
1652-
1653-                               new media.controller.Upload({
1654-                                       id:           'gallery-upload',
1655-                                       menu:         'gallery',
1656-                                       libraryState: 'gallery-edit'
1657+                                       excludeState: 'gallery-edit',
1658+                                       title:        l10n.addToGalleryTitle,
1659+                                       priority:     100
1660                                })
1661                        ]);
1662 
1663@@ -1492,9 +1643,13 @@
1664 
1665                bindHandlers: function() {
1666                        media.view.MediaFrame.Select.prototype.bindHandlers.apply( this, arguments );
1667+                       this.on( 'menu:create:gallery', this.createMenu, this );
1668+                       this.on( 'toolbar:create:main-insert', this.createSelectionToolbar, this );
1669+                       this.on( 'toolbar:create:main-gallery', this.createSelectionToolbar, this );
1670 
1671                        var handlers = {
1672                                        menu: {
1673+                                               'main':    'mainMenu',
1674                                                'gallery': 'galleryMenu'
1675                                        },
1676 
1677@@ -1504,7 +1659,8 @@
1678                                        },
1679 
1680                                        toolbar: {
1681-                                               'main-attachments': 'mainAttachmentsToolbar',
1682+                                               'main-insert':      'mainInsertToolbar',
1683+                                               'main-gallery':     'mainGalleryToolbar',
1684                                                'main-embed':       'mainEmbedToolbar',
1685                                                'featured-image':   'featuredImageToolbar',
1686                                                'gallery-edit':     'galleryEditToolbar',
1687@@ -1514,70 +1670,42 @@
1688 
1689                        _.each( handlers, function( regionHandlers, region ) {
1690                                _.each( regionHandlers, function( callback, handler ) {
1691-                                       this[ region ].on( 'activate:' + handler, this[ callback ], this );
1692+                                       this.on( region + ':render:' + handler, this[ callback ], this );
1693                                }, this );
1694                        }, this );
1695                },
1696 
1697                // Menus
1698-               mainMenu: function() {
1699-                       media.view.MediaFrame.Select.prototype.mainMenu.call( this, { silent: true });
1700-
1701-                       this.menu.view().set({
1702+               mainMenu: function( view ) {
1703+                       view.set({
1704                                'library-separator': new media.View({
1705                                        className: 'separator',
1706-                                       priority: 60
1707-                               }),
1708-                               'embed': {
1709-                                       text: l10n.fromUrlTitle,
1710-                                       priority: 80
1711-                               }
1712+                                       priority: 100
1713+                               })
1714                        });
1715-
1716-                       if ( media.view.settings.post.featuredImageId ) {
1717-                               this.menu.view().set( 'featured-image', {
1718-                                       text: l10n.featuredImageTitle,
1719-                                       priority: 100
1720-                               });
1721-                       }
1722                },
1723 
1724-               galleryMenu: function() {
1725+               galleryMenu: function( view ) {
1726                        var lastState = this.lastState(),
1727                                previous = lastState && lastState.id,
1728                                frame = this;
1729 
1730-                       this.menu.view( new media.view.Menu({
1731-                               controller: this,
1732-                               views: {
1733-                                       cancel: {
1734-                                               text:     l10n.cancelGalleryTitle,
1735-                                               priority: 20,
1736-                                               click:    function() {
1737-                                                       if ( previous )
1738-                                                               frame.setState( previous );
1739-                                                       else
1740-                                                               frame.close();
1741-                                               }
1742-                                       },
1743-                                       separateCancel: new media.View({
1744-                                               className: 'separator',
1745-                                               priority: 40
1746-                                       }),
1747-                                       'gallery-edit': {
1748-                                               text: l10n.editGalleryTitle,
1749-                                               priority: 60
1750-                                       },
1751-                                       'gallery-upload': {
1752-                                               text: l10n.uploadImagesTitle,
1753-                                               priority: 80
1754-                                       },
1755-                                       'gallery-library': {
1756-                                               text: l10n.mediaLibraryTitle,
1757-                                               priority: 100
1758+                       view.set({
1759+                               cancel: {
1760+                                       text:     l10n.cancelGalleryTitle,
1761+                                       priority: 20,
1762+                                       click:    function() {
1763+                                               if ( previous )
1764+                                                       frame.setState( previous );
1765+                                               else
1766+                                                       frame.close();
1767                                        }
1768-                               }
1769-                       }) );
1770+                               },
1771+                               separateCancel: new media.View({
1772+                                       className: 'separator',
1773+                                       priority: 40
1774+                               })
1775+                       });
1776                },
1777 
1778                // Content
1779@@ -1587,7 +1715,7 @@
1780                                model:      this.state()
1781                        }).render();
1782 
1783-                       this.content.view( view );
1784+                       this.content.set( view );
1785                        view.url.focus();
1786                },
1787 
1788@@ -1617,37 +1745,62 @@
1789                        });
1790 
1791                        // Browse our library of attachments.
1792-                       this.content.view( view );
1793+                       this.content.set( view );
1794                },
1795 
1796-               // Sidebars
1797-               onSidebarGallerySettings: function( options ) {
1798-                       var library = this.state().get('library');
1799+               // Toolbars
1800+               createSelectionToolbar: function( toolbar ) {
1801+                       toolbar.view = new media.view.Toolbar.Selection({
1802+                               controller: this,
1803+                               editable:   this.state().get('editable')
1804+                       });
1805+               },
1806 
1807-                       if ( ! library )
1808-                               return;
1809+               mainInsertToolbar: function( view ) {
1810+                       var controller = this;
1811 
1812-                       library.gallery = library.gallery || new Backbone.Model();
1813+                       view.button = 'insert';
1814+                       view.set( 'insert', {
1815+                               style:    'primary',
1816+                               priority: 80,
1817+                               text:     l10n.insertIntoPost,
1818 
1819-                       this.sidebar.view().set({
1820-                               gallery: new media.view.Settings.Gallery({
1821-                                       controller: this,
1822-                                       model:      library.gallery,
1823-                                       priority:   40
1824-                               }).render()
1825-                       }, options );
1826+                               click: function() {
1827+                                       var state = controller.state(),
1828+                                               selection = state.get('selection');
1829+
1830+                                       controller.close();
1831+                                       state.trigger( 'insert', selection ).reset();
1832+                               }
1833+                       });
1834                },
1835 
1836-               // Toolbars
1837-               mainAttachmentsToolbar: function() {
1838-                       this.toolbar.view( new media.view.Toolbar.Insert({
1839-                               controller: this,
1840-                               editable:   this.state().get('editable')
1841-                       }) );
1842+               mainGalleryToolbar: function( view ) {
1843+                       var controller = this;
1844+
1845+                       view.button = 'gallery';
1846+                       view.set( 'gallery', {
1847+                               style:    'primary',
1848+                               text:     l10n.createNewGallery,
1849+                               priority: 60,
1850+
1851+                               click: function() {
1852+                                       var selection = controller.state().get('selection'),
1853+                                               edit = controller.state('gallery-edit'),
1854+                                               models = selection.where({ type: 'image' });
1855+
1856+                                       edit.set( 'library', new media.model.Selection( models, {
1857+                                               props:    selection.props.toJSON(),
1858+                                               multiple: true
1859+                                       }) );
1860+
1861+                                       this.controller.setState('gallery-edit');
1862+                               }
1863+                       });
1864                },
1865 
1866                featuredImageToolbar: function() {
1867-                       this.toolbar.view( new media.view.Toolbar.Select({
1868+                       this.toolbar.set( new media.view.Toolbar.Select({
1869                                controller: this,
1870                                text:       l10n.setFeaturedImage,
1871                                state:      this.options.state || 'upload'
1872@@ -1655,7 +1808,7 @@
1873                },
1874 
1875                mainEmbedToolbar: function() {
1876-                       this.toolbar.view( new media.view.Toolbar.Embed({
1877+                       this.toolbar.set( new media.view.Toolbar.Embed({
1878                                controller: this
1879                        }) );
1880 
1881@@ -1664,7 +1817,7 @@
1882 
1883                galleryEditToolbar: function() {
1884                        var editing = this.state().get('editing');
1885-                       this.toolbar.view( new media.view.Toolbar({
1886+                       this.toolbar.set( new media.view.Toolbar({
1887                                controller: this,
1888                                items: {
1889                                        insert: {
1890@@ -1689,7 +1842,7 @@
1891                },
1892 
1893                galleryAddToolbar: function() {
1894-                       this.toolbar.view( new media.view.Toolbar({
1895+                       this.toolbar.set( new media.view.Toolbar({
1896                                controller: this,
1897                                items: {
1898                                        insert: {
1899@@ -1704,7 +1857,7 @@
1900 
1901                                                        edit.get('library').add( state.get('selection').models );
1902                                                        state.trigger('reset');
1903-                                                       controller.state('gallery-edit');
1904+                                                       controller.setState('gallery-edit');
1905                                                }
1906                                        }
1907                                }
1908@@ -1729,8 +1882,6 @@
1909                },
1910 
1911                initialize: function() {
1912-                       this.controller = this.options.controller;
1913-
1914                        _.defaults( this.options, {
1915                                container: document.body,
1916                                title:     '',
1917@@ -1838,8 +1989,6 @@
1918                initialize: function() {
1919                        var uploader;
1920 
1921-                       this.controller = this.options.controller;
1922-
1923                        this.$browser = $('<a href="#" class="browser" />').hide().appendTo('body');
1924 
1925                        uploader = this.options.uploader = _.defaults( this.options.uploader || {}, {
1926@@ -1906,7 +2055,10 @@
1927                template:  media.template('uploader-inline'),
1928 
1929                initialize: function() {
1930-                       this.controller = this.options.controller;
1931+                       _.defaults( this.options, {
1932+                               message: '',
1933+                               status:  true
1934+                       });
1935 
1936                        if ( ! this.options.$browser && this.controller.uploader )
1937                                this.options.$browser = this.controller.uploader.$browser;
1938@@ -1914,9 +2066,11 @@
1939                        if ( _.isUndefined( this.options.postId ) )
1940                                this.options.postId = media.view.settings.post.id;
1941 
1942-                       this.views.set( '.upload-inline-status', new media.view.UploaderStatus({
1943-                               controller: this.controller
1944-                       }) );
1945+                       if ( this.options.status ) {
1946+                               this.views.set( '.upload-inline-status', new media.view.UploaderStatus({
1947+                                       controller: this.controller
1948+                               }) );
1949+                       }
1950                },
1951 
1952                ready: function() {
1953@@ -1951,8 +2105,6 @@
1954                },
1955 
1956                initialize: function() {
1957-                       this.controller = this.options.controller;
1958-
1959                        this.queue = wp.Uploader.queue;
1960                        this.queue.on( 'add remove reset', this.visibility, this );
1961                        this.queue.on( 'add remove reset change:percent', this.progress, this );
1962@@ -2060,8 +2212,6 @@
1963                className: 'media-toolbar',
1964 
1965                initialize: function() {
1966-                       this.controller = this.options.controller;
1967-
1968                        this._views     = {};
1969                        this.$primary   = $('<div class="media-toolbar-primary" />').prependTo( this.$el );
1970                        this.$secondary = $('<div class="media-toolbar-secondary" />').prependTo( this.$el );
1971@@ -2199,50 +2349,32 @@
1972        // ---------------------------
1973        media.view.Toolbar.Embed = media.view.Toolbar.Select.extend({
1974                initialize: function() {
1975-                       var controller = this.options.controller;
1976-
1977                        _.defaults( this.options, {
1978                                text: l10n.insertIntoPost
1979                        });
1980 
1981                        media.view.Toolbar.Select.prototype.initialize.apply( this, arguments );
1982-                       controller.on( 'change:url', this.refresh, this );
1983+                       this.controller.state().props.on( 'change:url', this.refresh, this );
1984                },
1985 
1986                refresh: function() {
1987-                       var url = this.controller.state().get('url');
1988+                       var url = this.controller.state().props.get('url');
1989                        this.get('select').model.set( 'disabled', ! url || /^https?:\/\/$/.test(url) );
1990                }
1991        });
1992 
1993-       // wp.media.view.Toolbar.Insert
1994-       // ----------------------------
1995-       media.view.Toolbar.Insert = media.view.Toolbar.extend({
1996+       // wp.media.view.Toolbar.Selection
1997+       // -------------------------------
1998+       media.view.Toolbar.Selection = media.view.Toolbar.extend({
1999+               button: 'insert',
2000+
2001                initialize: function() {
2002-                       var controller = this.options.controller,
2003-                               selection = controller.state().get('selection'),
2004-                               selectionToLibrary;
2005+                       var controller = this.controller;
2006 
2007-                       selectionToLibrary = function( state, filter ) {
2008-                               return function() {
2009-                                       var controller = this.controller,
2010-                                               selection = controller.state().get('selection'),
2011-                                               edit = controller.state( state ),
2012-                                               models = filter ? filter( selection ) : selection.models;
2013-
2014-                                       edit.set( 'library', new media.model.Selection( models, {
2015-                                               props:    selection.props.toJSON(),
2016-                                               multiple: true
2017-                                       }) );
2018-
2019-                                       this.controller.setState( state );
2020-                               };
2021-                       };
2022-
2023                        this.options.items = _.defaults( this.options.items || {}, {
2024                                selection: new media.view.Selection({
2025                                        controller: controller,
2026-                                       collection: selection,
2027+                                       collection: controller.state().get('selection'),
2028                                        priority:   -40,
2029 
2030                                        // If the selection is editable, pass the callback to
2031@@ -2250,26 +2382,7 @@
2032                                        editable: this.options.editable && function() {
2033                                                this.controller.content.mode('edit-selection');
2034                                        }
2035-                               }).render(),
2036-
2037-                               insert: {
2038-                                       style:    'primary',
2039-                                       priority: 80,
2040-                                       text:     l10n.insertIntoPost,
2041-
2042-                                       click: function() {
2043-                                               controller.close();
2044-                                               controller.state().trigger( 'insert', selection ).reset();
2045-                                       }
2046-                               },
2047-
2048-                               gallery: {
2049-                                       text:     l10n.createNewGallery,
2050-                                       priority: 40,
2051-                                       click:    selectionToLibrary('gallery-edit', function( selection ) {
2052-                                               return selection.where({ type: 'image' });
2053-                                       })
2054-                               }
2055+                               }).render()
2056                        });
2057 
2058                        media.view.Toolbar.prototype.initialize.apply( this, arguments );
2059@@ -2277,14 +2390,12 @@
2060 
2061                refresh: function() {
2062                        var selection = this.controller.state().get('selection'),
2063-                               count = selection.length;
2064+                               button = this.get( this.button );
2065 
2066-                       this.get('insert').model.set( 'disabled', ! selection.length );
2067+                       if ( ! button )
2068+                               return;
2069 
2070-                       // Check if any attachment in the selection is an image.
2071-                       this.get('gallery').$el.toggle( count > 1 && selection.any( function( attachment ) {
2072-                               return 'image' === attachment.get('type');
2073-                       }) );
2074+                       button.model.set( 'disabled', ! selection.length );
2075                }
2076        });
2077 
2078@@ -2388,8 +2499,7 @@
2079                tagName:   'div',
2080 
2081                initialize: function() {
2082-                       this.controller = this.options.controller;
2083-                       this._views     = {};
2084+                       this._views = {};
2085 
2086                        this.set( _.extend( {}, this._views, this.options.views ), { silent: true });
2087                        delete this.options.views;
2088@@ -2457,23 +2567,95 @@
2089                }
2090        });
2091 
2092+       /**
2093+        * wp.media.view.MenuItem
2094+        */
2095+       media.view.MenuItem = media.View.extend({
2096+               tagName:   'a',
2097+               className: 'media-menu-item',
2098 
2099+               attributes: {
2100+                       href: '#'
2101+               },
2102+
2103+               events: {
2104+                       'click': '_click'
2105+               },
2106+
2107+               _click: function( event ) {
2108+                       var clickOverride = this.options.click;
2109+
2110+                       if ( event )
2111+                               event.preventDefault();
2112+
2113+                       if ( clickOverride )
2114+                               clickOverride.call( this );
2115+                       else
2116+                               this.click();
2117+               },
2118+
2119+               click: function() {
2120+                       var state = this.options.state;
2121+                       if ( state )
2122+                               this.controller.setState( state );
2123+               },
2124+
2125+               render: function() {
2126+                       var options = this.options;
2127+
2128+                       if ( options.text )
2129+                               this.$el.text( options.text );
2130+                       else if ( options.html )
2131+                               this.$el.html( options.html );
2132+
2133+                       return this;
2134+               }
2135+       });
2136+
2137        /**
2138         * wp.media.view.Menu
2139         */
2140        media.view.Menu = media.view.PriorityList.extend({
2141-               tagName:   'ul',
2142+               tagName:   'div',
2143                className: 'media-menu',
2144+               property:  'state',
2145+               ItemView:  media.view.MenuItem,
2146+               region:    'menu',
2147 
2148-               toView: function( options, state ) {
2149+               toView: function( options, id ) {
2150                        options = options || {};
2151-                       options.state = options.state || state;
2152-                       return new media.view.MenuItem( options ).render();
2153+                       options[ this.property ] = options[ this.property ] || id;
2154+                       return new this.ItemView( options ).render();
2155                },
2156 
2157-               select: function( state ) {
2158-                       var view = this.get( state );
2159+               ready: function() {
2160+                       media.view.PriorityList.prototype.ready.apply( this, arguments );
2161+                       this.visibility();
2162+               },
2163 
2164+               set: function() {
2165+                       media.view.PriorityList.prototype.set.apply( this, arguments );
2166+                       this.visibility();
2167+               },
2168+
2169+               unset: function() {
2170+                       media.view.PriorityList.prototype.unset.apply( this, arguments );
2171+                       this.visibility();
2172+               },
2173+
2174+               visibility: function() {
2175+                       var region = this.region,
2176+                               view = this.controller[ region ].get(),
2177+                               views = this.views.get(),
2178+                               hide = ! views || views.length < 2;
2179+
2180+                       if ( this === view )
2181+                               this.controller.$el.toggleClass( 'hide-' + region, hide );
2182+               },
2183+
2184+               select: function( id ) {
2185+                       var view = this.get( id );
2186+
2187                        if ( ! view )
2188                                return;
2189 
2190@@ -2486,35 +2668,40 @@
2191                }
2192        });
2193 
2194-       media.view.MenuItem = media.View.extend({
2195-               tagName:   'li',
2196-               className: 'media-menu-item',
2197+       /**
2198+        * wp.media.view.RouterItem
2199+        */
2200+       media.view.RouterItem = media.view.MenuItem.extend({
2201+               click: function() {
2202+                       var contentMode = this.options.contentMode;
2203+                       if ( contentMode )
2204+                               this.controller.content.mode( contentMode );
2205+               }
2206+       });
2207 
2208-               events: {
2209-                       'click': 'click'
2210-               },
2211+       /**
2212+        * wp.media.view.Router
2213+        */
2214+       media.view.Router = media.view.Menu.extend({
2215+               tagName:   'div',
2216+               className: 'media-router',
2217+               property:  'contentMode',
2218+               ItemView:  media.view.RouterItem,
2219+               region:    'router',
2220 
2221-               click: function() {
2222-                       var options = this.options;
2223-
2224-                       if ( options.click )
2225-                               options.click.call( this );
2226-                       else if ( options.state )
2227-                               this.controller.setState( options.state );
2228+               initialize: function() {
2229+                       this.controller.on( 'content:render', this.update, this );
2230+                       media.view.Menu.prototype.initialize.apply( this, arguments );
2231                },
2232 
2233-               render: function() {
2234-                       var options = this.options;
2235-
2236-                       if ( options.text )
2237-                               this.$el.text( options.text );
2238-                       else if ( options.html )
2239-                               this.$el.html( options.html );
2240-
2241-                       return this;
2242+               update: function() {
2243+                       var mode = this.controller.content.mode();
2244+                       if ( mode )
2245+                               this.select( mode );
2246                }
2247        });
2248 
2249+
2250        /**
2251         * wp.media.view.Sidebar
2252         */
2253@@ -2531,7 +2718,7 @@
2254                template:  media.template('attachment'),
2255 
2256                events: {
2257-                       'click .attachment-preview':      'toggleSelection',
2258+                       'click .attachment-preview':      'toggleSelectionHandler',
2259                        'change [data-setting]':          'updateSetting',
2260                        'change [data-setting] input':    'updateSetting',
2261                        'change [data-setting] select':   'updateSetting',
2262@@ -2546,8 +2733,6 @@
2263                initialize: function() {
2264                        var selection = this.options.selection;
2265 
2266-                       this.controller = this.options.controller;
2267-
2268                        this.model.on( 'change:sizes change:uploading change:caption change:title', this.render, this );
2269                        this.model.on( 'change:percent', this.progress, this );
2270 
2271@@ -2623,20 +2808,70 @@
2272                                this.$bar.width( this.model.get('percent') + '%' );
2273                },
2274 
2275-               toggleSelection: function( event ) {
2276-                       var selection = this.options.selection,
2277-                               model = this.model;
2278+               toggleSelectionHandler: function( event ) {
2279+                       var method;
2280 
2281+                       if ( event.shiftKey )
2282+                               method = 'between';
2283+                       else if ( event.ctrlKey || event.metaKey )
2284+                               method = 'toggle';
2285+
2286+                       this.toggleSelection({
2287+                               method: method
2288+                       });
2289+               },
2290+
2291+               toggleSelection: function( options ) {
2292+                       var collection = this.collection,
2293+                               selection = this.options.selection,
2294+                               model = this.model,
2295+                               method = options && options.method,
2296+                               single, between, models, singleIndex, modelIndex;
2297+
2298                        if ( ! selection )
2299                                return;
2300 
2301+                       single = selection.single();
2302+                       method = _.isUndefined( method ) ? selection.multiple : method;
2303+
2304+                       // If the `method` is set to `between`, select all models that
2305+                       // exist between the current and the selected model.
2306+                       if ( 'between' === method && single && selection.multiple ) {
2307+                               // If the models are the same, short-circuit.
2308+                               if ( single === model )
2309+                                       return;
2310+
2311+                               singleIndex = collection.indexOf( single );
2312+                               modelIndex  = collection.indexOf( this.model );
2313+
2314+                               if ( singleIndex < modelIndex )
2315+                                       models = collection.models.slice( singleIndex, modelIndex + 1 );
2316+                               else
2317+                                       models = collection.models.slice( modelIndex, singleIndex + 1 );
2318+
2319+                               selection.add( models ).single( model );
2320+                               return;
2321+
2322+                       // If the `method` is set to `toggle`, just flip the selection
2323+                       // status, regardless of whether the model is the single model.
2324+                       } else if ( 'toggle' === method ) {
2325+                               selection[ this.selected() ? 'remove' : 'add' ]( model ).single( model );
2326+                               return;
2327+                       }
2328+
2329+                       if ( method !== 'add' )
2330+                               method = 'reset';
2331+
2332                        if ( this.selected() ) {
2333                                // If the model is the single model, remove it.
2334                                // If it is not the same as the single model,
2335                                // it now becomes the single model.
2336-                               selection[ selection.single() === model ? 'remove' : 'single' ]( model );
2337+                               selection[ single === model ? 'remove' : 'single' ]( model );
2338                        } else {
2339-                               selection.add( model ).single( model );
2340+                               // If the model is not selected, run the `method` on the
2341+                               // selection. By default, we `reset` the selection, but the
2342+                               // `method` can be set to `add` the model to the selection.
2343+                               selection[ method ]( model ).single( model );
2344                        }
2345                },
2346 
2347@@ -2838,7 +3073,6 @@
2348                },
2349 
2350                initialize: function() {
2351-                       this.controller = this.options.controller;
2352                        this.el.id = _.uniqueId('__attachments-view-');
2353 
2354                        _.defaults( this.options, {
2355@@ -2950,13 +3184,16 @@
2356                                        collection.remove( model, {
2357                                                silent: true
2358                                        }).add( model, {
2359-                                               at:     ui.item.index(),
2360-                                               silent: true
2361+                                               silent: true,
2362+                                               at:     ui.item.index()
2363                                        });
2364 
2365                                        // Restore the comparator.
2366                                        collection.comparator = comparator;
2367 
2368+                                       // Fire the `reset` event to ensure other collections sync.
2369+                                       collection.trigger( 'reset', collection );
2370+
2371                                        // If the collection is sorted by menu order,
2372                                        // update the menu order.
2373                                        collection.saveMenuOrder();
2374@@ -3200,12 +3437,9 @@
2375                className: 'attachments-browser',
2376 
2377                initialize: function() {
2378-                       this.controller = this.options.controller;
2379-
2380                        _.defaults( this.options, {
2381                                filters: false,
2382                                search:  true,
2383-                               uploads: false,
2384                                display: false,
2385 
2386                                AttachmentView: media.view.Attachment.Library
2387@@ -3290,7 +3524,9 @@
2388                        this.removeContent();
2389 
2390                        this.uploader = new media.view.UploaderInline({
2391-                               controller: this.controller
2392+                               controller: this.controller,
2393+                               status:     false,
2394+                               message:    l10n.noItemsFound
2395                        });
2396 
2397                        this.views.add( this.uploader );
2398@@ -3322,7 +3558,7 @@
2399 
2400                        this.views.add( sidebar );
2401 
2402-                       if ( options.uploads && this.controller.uploader ) {
2403+                       if ( this.controller.uploader ) {
2404                                sidebar.set( 'uploads', new media.view.UploaderStatus({
2405                                        controller: this.controller,
2406                                        priority:   40
2407@@ -3373,57 +3609,6 @@
2408        });
2409 
2410        /**
2411-        * wp.media.view.SelectionPreview
2412-        */
2413-       media.view.SelectionPreview = media.View.extend({
2414-               tagName:   'div',
2415-               className: 'selection-preview',
2416-               template:  media.template('media-selection-preview'),
2417-
2418-               events: {
2419-                       'click .clear-selection': 'clear'
2420-               },
2421-
2422-               initialize: function() {
2423-                       _.defaults( this.options, {
2424-                               clearable: true
2425-                       });
2426-
2427-                       this.controller = this.options.controller;
2428-                       this.collection.on( 'add change:url remove', this.render, this );
2429-                       this.render();
2430-               },
2431-
2432-               render: function() {
2433-                       var options = _.clone( this.options ),
2434-                               last, sizes, amount;
2435-
2436-                       // If nothing is selected, display nothing.
2437-                       if ( ! this.collection.length ) {
2438-                               this.$el.empty();
2439-                               return this;
2440-                       }
2441-
2442-                       options.count = this.collection.length;
2443-                       last  = this.collection.last();
2444-                       sizes = last.get('sizes');
2445-
2446-                       if ( 'image' === last.get('type') )
2447-                               options.thumbnail = ( sizes && sizes.thumbnail ) ? sizes.thumbnail.url : last.get('url');
2448-                       else
2449-                               options.thumbnail =  last.get('icon');
2450-
2451-                       this.$el.html( this.template( options ) );
2452-                       return this;
2453-               },
2454-
2455-               clear: function( event ) {
2456-                       event.preventDefault();
2457-                       this.collection.reset();
2458-               }
2459-       });
2460-
2461-       /**
2462         * wp.media.view.Selection
2463         */
2464        media.view.Selection = media.View.extend({
2465@@ -3442,7 +3627,6 @@
2466                                clearable: true
2467                        });
2468 
2469-                       this.controller = this.options.controller;
2470                        this.attachments = new media.view.Attachments({
2471                                controller: this.controller,
2472                                collection: this.collection,
2473@@ -3768,12 +3952,10 @@
2474        media.view.Iframe = media.View.extend({
2475                className: 'media-iframe',
2476 
2477-               initialize: function() {
2478-                       this.controller = this.options.controller;
2479-               },
2480-
2481                render: function() {
2482+                       this.views.detach();
2483                        this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' );
2484+                       this.views.render();
2485                        return this;
2486                }
2487        });
2488@@ -3785,11 +3967,9 @@
2489                className: 'media-embed',
2490 
2491                initialize: function() {
2492-                       this.controller = this.options.controller;
2493-
2494                        this.url = new media.view.EmbedUrl({
2495                                controller: this.controller,
2496-                               model:      this.model
2497+                               model:      this.model.props
2498                        }).render();
2499 
2500                        this._settings = new media.View();
2501@@ -3826,7 +4006,7 @@
2502 
2503                        this.settings( new constructor({
2504                                controller: this.controller,
2505-                               model:      this.model,
2506+                               model:      this.model.props,
2507                                priority:   40
2508                        }) );
2509                }
2510@@ -3846,15 +4026,13 @@
2511                },
2512 
2513                initialize: function() {
2514-                       this.label = this.make( 'span', null, this.options.label || l10n.url );
2515                        this.input = this.make( 'input', {
2516                                type:  'text',
2517                                value: this.model.get('url') || ''
2518                        });
2519 
2520-                       this.$label = $( this.label );
2521                        this.$input = $( this.input );
2522-                       this.$el.append([ this.label, this.input ]);
2523+                       this.$el.append( this.input );
2524 
2525                        this.model.on( 'change:url', this.render, this );
2526                },
2527Index: wp-includes/media.php
2528===================================================================
2529--- wp-includes/media.php       (revision 23000)
2530+++ wp-includes/media.php       (working copy)
2531@@ -1467,9 +1467,11 @@
2532 
2533                // Library
2534                'mediaLibraryTitle'  => __( 'Media Library' ),
2535+               'insertMediaTitle'   => __( 'Insert Media' ),
2536                'createNewGallery'   => __( 'Create a new gallery' ),
2537                'returnToLibrary'    => __( '&#8592; Return to library' ),
2538                'allMediaItems'      => __( 'All media items' ),
2539+               'noItemsFound'       => __( 'No items found.' ),
2540                'insertIntoPost'     => $hier ? __( 'Insert into page' ) : __( 'Insert into post' ),
2541                'uploadedToThisPost' => $hier ? __( 'Uploaded to this page' ) : __( 'Uploaded to this post' ),
2542                'warnDelete' =>      __( "You are about to permanently delete this item.\n  'Cancel' to stop, 'OK' to delete." ),
2543@@ -1489,6 +1491,7 @@
2544                'updateGallery'      => __( 'Update gallery' ),
2545                'continueEditing'    => __( 'Continue editing' ),
2546                'addToGallery'       => __( 'Add to gallery' ),
2547+               'addToGalleryTitle'  => __( 'Add to Gallery' ),
2548                'reverseOrder'       => __( 'Reverse order' ),
2549        );
2550 
2551@@ -1517,6 +1520,8 @@
2552        ?>
2553        <script type="text/html" id="tmpl-media-frame">
2554                <div class="media-frame-menu"></div>
2555+               <div class="media-frame-title"></div>
2556+               <div class="media-frame-router"></div>
2557                <div class="media-frame-content"></div>
2558                <div class="media-frame-toolbar"></div>
2559                <div class="media-frame-uploader"></div>
2560@@ -1524,13 +1529,10 @@
2561 
2562        <script type="text/html" id="tmpl-media-modal">
2563                <div class="media-modal wp-core-ui">
2564-                       <h3 class="media-modal-title">{{ data.title }}</h3>
2565-                       <a class="media-modal-close media-modal-icon" href="#" title="<?php esc_attr_e('Close'); ?>"></a>
2566+                       <a class="media-modal-close" href="#" title="<?php esc_attr_e('Close'); ?>"><span class="media-modal-icon"></span></a>
2567                        <div class="media-modal-content"></div>
2568                </div>
2569-               <div class="media-modal-backdrop">
2570-                       <div></div>
2571-               </div>
2572+               <div class="media-modal-backdrop"></div>
2573        </script>
2574 
2575        <script type="text/html" id="tmpl-uploader-window">
2576@@ -1540,16 +1542,20 @@
2577        </script>
2578 
2579        <script type="text/html" id="tmpl-uploader-inline">
2580-               <div class="uploader-inline-content">
2581+               <# var messageClass = data.message ? 'has-upload-message' : 'no-upload-message'; #>
2582+               <div class="uploader-inline-content {{ messageClass }}">
2583+               <# if ( data.message ) { #>
2584+                       <h3 class="upload-message">{{ data.message }}</h3>
2585+               <# } #>
2586                <?php if ( ! _device_can_upload() ) : ?>
2587-                       <h3><?php _e('The web browser on your device cannot be used to upload files. You may be able to use the <a href="http://wordpress.org/extend/mobile/">native app for your device</a> instead.'); ?></h3>
2588+                       <h3 class="upload-instructions"><?php _e('The web browser on your device cannot be used to upload files. You may be able to use the <a href="http://wordpress.org/extend/mobile/">native app for your device</a> instead.'); ?></h3>
2589                <?php elseif ( is_multisite() && ! is_upload_space_available() ) : ?>
2590-                       <h3><?php _e( 'Upload Limit Exceeded' ); ?></h3>
2591+                       <h3 class="upload-instructions"><?php _e( 'Upload Limit Exceeded' ); ?></h3>
2592                        <?php do_action( 'upload_ui_over_quota' ); ?>
2593 
2594                <?php else : ?>
2595                        <div class="upload-ui">
2596-                               <h3 class="drop-instructions"><?php _e( 'Drop files anywhere to upload' ); ?></h3>
2597+                               <h3 class="upload-instructions drop-instructions"><?php _e( 'Drop files anywhere to upload' ); ?></h3>
2598                                <a href="#" class="browser button button-hero"><?php _e( 'Select Files' ); ?></a>
2599                        </div>
2600 
2601@@ -1744,19 +1750,6 @@
2602                <div class="selection-view"></div>
2603        </script>
2604 
2605-       <script type="text/html" id="tmpl-media-selection-preview">
2606-               <div class="selected-img selected-count-{{ data.count }}">
2607-                       <# if ( data.thumbnail ) { #>
2608-                               <img src="{{ data.thumbnail }}" draggable="false" />
2609-                       <# } #>
2610-
2611-                       <span class="count">{{ data.count }}</span>
2612-               </div>
2613-               <# if ( data.clearable ) { #>
2614-                       <a class="clear-selection" href="#"><?php _e('Clear selection'); ?></a>
2615-               <# } #>
2616-       </script>
2617-
2618        <script type="text/html" id="tmpl-attachment-display-settings">
2619                <h3><?php _e('Attachment Display Settings'); ?></h3>
2620