WordPress.org

Make WordPress Core

Ticket #24716: 24716.12.diff

File 24716.12.diff, 28.8 KB (added by ericlewis, 4 years ago)
  • src/wp-admin/upload.php

    diff --git a/src/wp-admin/upload.php b/src/wp-admin/upload.php
    index 11175f9..de099c7 100644
    a b if ( 'grid' === $mode ) { 
    2424        wp_enqueue_media();
    2525        wp_enqueue_script( 'media' );
    2626        require_once( ABSPATH . 'wp-admin/admin-header.php' );
    27         ?><div class="view-switch media-grid-view-switch">
    28                 <a href="<?php echo esc_url( add_query_arg( 'mode', 'list', $_SERVER['REQUEST_URI'] ) ) ?>" class="view-list">
    29                         <img id="view-switch-list" src="<?php echo includes_url( 'images/blank.gif' ) ?>" width="20" height="20" title="List View" alt="List View"/>
    30                 </a>
    31                 <a href="<?php echo esc_url( add_query_arg( 'mode', 'grid', $_SERVER['REQUEST_URI'] ) ) ?>" class="view-grid current">
    32                         <img id="view-switch-excerpt" src="<?php echo includes_url( 'images/blank.gif' ) ?>" width="20" height="20" title="Grid View" alt="Grid View"/>
    33                 </a>
    34         </div><?php
    3527        include( ABSPATH . 'wp-admin/admin-footer.php' );
    3628        exit;
    3729}
  • src/wp-includes/css/media-views.css

    diff --git a/src/wp-includes/css/media-views.css b/src/wp-includes/css/media-views.css
    index c768479..d1c023d 100644
    a b  
    909909        border-radius: 0;
    910910}
    911911
     912.attachment .data-field {
     913        white-space: nowrap;
     914        text-overflow: ellipsis;
     915        overflow: hidden;
     916        display: block;
     917        line-height: 19px;
     918        height: 19px;
     919        text-align: left;
     920}
     921
    912922/**
    913923 * Attachments Browser
    914924 */
     
    924934        height: 50px;
    925935}
    926936
     937.attachments-browser.hide-sidebar .media-toolbar {
     938        right: 0;
     939}
     940
    927941.attachments-browser .media-toolbar-primary > .media-button,
    928942.attachments-browser .media-toolbar-primary > .media-button-group,
    929943.attachments-browser .media-toolbar-secondary > .media-button,
     
    942956        outline: none;
    943957}
    944958
     959/**
     960 * Copied styles from the theme browser view.
     961 *
     962 * This should be OOCSS'd so both use a shared selector.
     963 */
     964.attachment .edit-media {
     965        -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
     966        opacity: 0;
     967        position: absolute;
     968        top: 25%;
     969        right: 25%;
     970        left: 25%;
     971        background: #222;
     972        background: rgba(0,0,0,0.7);
     973        color: #fff;
     974        font-size: 15px;
     975        text-shadow: 0 1px 0 rgba(0,0,0,0.6);
     976        -webkit-font-smoothing: antialiased;
     977        font-weight: 600;
     978        padding: 10px 0;
     979        text-align: center;
     980        -webkit-border-radius: 3px;
     981        border-radius: 3px;
     982        -webkit-transition: opacity 0.1s ease-in-out;
     983        transition: opacity 0.1s ease-in-out;
     984}
     985
     986.attachment:hover .edit-media {
     987        -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
     988        opacity: 1;
     989}
     990
     991.attachments-browser.hide-sidebar .attachments {
     992        right: 0;
     993}
     994
    945995.attachments-browser .instructions {
    946996        display: inline-block;
    947997        margin-top: 16px;
     
    23882438        line-height: 29px;
    23892439}
    23902440
    2391 .media-grid-view-switch {
    2392         position: fixed;
    2393         right: 10px;
    2394         top: 44px;
    2395         z-index: 300;
     2441.media-grid-view .view-switch {
     2442        display: inline-block;
     2443        float: none;
     2444        margin-top: 13px;
     2445        vertical-align: middle;
    23962446}
    23972447
    23982448/**
     
    24272477        display: none;
    24282478}
    24292479
     2480/**
     2481 * Copied styles from the Add theme toolbar.
     2482 *
     2483 * This should be OOCSS'd so both use a shared selector.
     2484 */
     2485.media-grid-view .media-toolbar {
     2486        background: #fff;
     2487        -webkit-box-shadow: 0 1px 1px 0 rgba(0,0,0,.1);
     2488        box-shadow: 0 1px 1px 0 rgba(0,0,0,.1);
     2489        -webkit-box-sizing: border-box;
     2490        -moz-box-sizing: border-box;
     2491        box-sizing: border-box;
     2492        color: #555;
     2493        display: inline-block;
     2494        font-size: 13px;
     2495        padding: 0 20px;
     2496        position: relative;
     2497        width: 100%;
     2498}
     2499
     2500/**
     2501 * The left and right buttons are copied from the expanded theme details modal.
     2502 *
     2503 * This should be OOCSS'd so both use a shared selector.
     2504 */
     2505.edit-attachment-frame .edit-media-header .left,
     2506.edit-attachment-frame .edit-media-header .right {
     2507        cursor: pointer;
     2508        color: #777;
     2509        background-color: transparent;
     2510        height: 48px;
     2511        width: 54px;
     2512        float: left;
     2513        text-align: center;
     2514        border: 0;
     2515        border-right: 1px solid #ddd;
     2516}
     2517
     2518.edit-attachment-frame .edit-media-header .right:before,
     2519.edit-attachment-frame .edit-media-header .left:before {
     2520        font: normal 20px/50px 'dashicons' !important;
     2521        display: inline;
     2522        font-weight: 300;
     2523}
     2524
     2525
     2526.edit-attachment-frame .edit-media-header .left:before {
     2527        content: '\f340';
     2528}
     2529
     2530.edit-attachment-frame .edit-media-header .right:before {
     2531        content: '\f344';
     2532}
     2533
     2534.edit-attachment-frame .edit-media-header .left.disabled,
     2535.edit-attachment-frame .edit-media-header .right.disabled,
     2536.edit-attachment-frame .edit-media-header .left.disabled:hover,
     2537.edit-attachment-frame .edit-media-header .right.disabled:hover {
     2538        color: #ccc;
     2539        background: inherit;
     2540        cursor: inherit;
     2541}
     2542
     2543.edit-attachment-frame .edit-media-header .close:hover,
     2544.edit-attachment-frame .edit-media-header .right:hover,
     2545.edit-attachment-frame .edit-media-header .left:hover,
     2546.edit-attachment-frame .edit-media-header .close:focus,
     2547.edit-attachment-frame .edit-media-header .right:focus,
     2548.edit-attachment-frame .edit-media-header .left:focus {
     2549        background: #0074a2;
     2550        color: #fff;
     2551}
     2552
     2553.edit-attachment-frame .media-frame-content,
     2554.edit-attachment-frame .media-frame-router {
     2555        left: 0;
     2556}
     2557
     2558/* Hiding this for the moment instead of removing it from the template. */
     2559.edit-attachment-frame h3 {
     2560        display: none;
     2561}
     2562
     2563.edit-attachment-frame .attachment-details {
     2564        position: absolute;
     2565        overflow: auto;
     2566        top: 0;
     2567        bottom: 0;
     2568        right: 0;
     2569        left: 0;
     2570}
     2571
     2572.edit-attachment-frame .attachment-info {
     2573        border-bottom: 0;
     2574        border-right: 1px solid #ddd;
     2575        bottom: 0;
     2576        position: absolute;
     2577        top: 0;
     2578        left: 0;
     2579        margin-bottom: 0;
     2580        padding: 2% 4%;
     2581        right: 50%;
     2582}
     2583
     2584.edit-attachment-frame .attachment-info .thumbnail {
     2585        float: none;
     2586        max-width: none;
     2587        max-height: none;
     2588}
     2589
     2590.edit-attachment-frame .attachment-info .thumbnail-image:after {
     2591        -webkit-box-shadow: none;
     2592                box-shadow: none;
     2593}
     2594
     2595.edit-attachment-frame .attachment-info .thumbnail img {
     2596        max-width: none;
     2597        max-height: 300px;
     2598}
     2599
     2600.edit-attachment-frame .attachment-info .details {
     2601        float: none;
     2602}
     2603
     2604.edit-attachment-frame .attachment-fields {
     2605        bottom: 0;
     2606        padding: 2% 4%;
     2607        position: absolute;
     2608        top: 0;
     2609        left: 50%;
     2610        right: 0;
     2611}
     2612
     2613.edit-attachment-frame .attachment-fields .setting {
     2614        display: block;
     2615        float: left;
     2616        width: 100%;
     2617        margin: 1px 0;
     2618}
     2619
     2620.edit-attachment-frame .attachment-fields .setting label {
     2621        display: block;
     2622}
     2623
     2624.edit-attachment-frame .attachment-fields .setting .link-to-custom {
     2625        margin: 3px 0;
     2626}
     2627
     2628.edit-attachment-frame .attachment-fields .setting .name {
     2629        min-width: 30%;
     2630        margin-right: 4%;
     2631        font-size: 12px;
     2632        text-align: right;
     2633}
     2634
     2635.edit-attachment-frame .attachment-fields .setting select {
     2636        max-width: 65%;
     2637}
     2638
     2639.edit-attachment-frame .attachment-fields .setting input[type="checkbox"],
     2640.edit-attachment-frame .attachment-fields .field input[type="checkbox"] {
     2641        width: 16px;
     2642        float: none;
     2643        margin: 8px 3px 0;
     2644        padding: 0;
     2645}
     2646
     2647.edit-attachment-frame .attachment-fields .setting span {
     2648        float: left;
     2649        min-height: 22px;
     2650        padding-top: 8px;
     2651        line-height: 16px;
     2652        font-weight: normal;
     2653        color: #666;
     2654}
     2655
     2656.edit-attachment-frame .attachment-fields .setting input,
     2657.edit-attachment-frame .attachment-fields .setting textarea,
     2658.edit-attachment-frame .attachment-fields .setting .value {
     2659        margin: 1px;
     2660        width: 65%;
     2661        float: right;
     2662}
     2663
     2664.edit-attachment-frame .attachment-fields .setting textarea {
     2665        height: 62px;
     2666        resize: vertical;
     2667}
     2668
     2669.edit-attachment-frame .attachment-fields select {
     2670        margin-top: 3px;
     2671}
     2672
     2673.media-grid-view.hide-router .media-frame-title {
     2674        box-shadow: none;
     2675}
     2676
    24302677.media-grid-view .media-frame-content {
     2678        background-color: transparent;
    24312679        bottom: 40px;
    24322680}
    24332681@media screen and (max-width: 782px) {
  • src/wp-includes/js/media-views.js

    diff --git a/src/wp-includes/js/media-views.js b/src/wp-includes/js/media-views.js
    index c1407d3..01e850c 100644
    a b  
    337337        });
    338338
    339339        /**
     340         * A more abstracted state, because media.controller.State expects
     341         * specific regions (menu, title, etc.) to exist on the frame, which do not
     342         * exist in media.view.Frame.EditAttachment.
     343         */
     344        media.controller._State = Backbone.Model.extend({
     345                constructor: function() {
     346                        this.on( 'activate', this._preActivate, this );
     347                        this.on( 'activate', this.activate, this );
     348                        this.on( 'activate', this._postActivate, this );
     349                        this.on( 'deactivate', this._deactivate, this );
     350                        this.on( 'deactivate', this.deactivate, this );
     351                        this.on( 'reset', this.reset, this );
     352                        this.on( 'ready', this.ready, this );
     353                        /**
     354                         * Call parent constructor with passed arguments
     355                         */
     356                        Backbone.Model.apply( this, arguments );
     357                },
     358
     359                /**
     360                 * @abstract
     361                 */
     362                ready: function() {},
     363                /**
     364                 * @abstract
     365                 */
     366                activate: function() {},
     367                /**
     368                 * @abstract
     369                 */
     370                deactivate: function() {},
     371                /**
     372                 * @abstract
     373                 */
     374                reset: function() {},
     375                /**
     376                 * @access private
     377                 */
     378                _preActivate: function() {
     379                        this.active = true;
     380                },
     381                /**
     382                 * @access private
     383                 */
     384                _postActivate: function() {},
     385                /**
     386                 * @access private
     387                 */
     388                _deactivate: function() {
     389                        this.active = false;
     390                }
     391        });
     392
     393        /**
    340394         * wp.media.controller.State
    341395         *
    342396         * A state is a step in a workflow that when set will trigger the controllers
     
    13501404        });
    13511405
    13521406        /**
     1407         * A state for editing (cropping, etc.) an image.
     1408         *
     1409         * @constructor
     1410         * @augments wp.media.controller.State
     1411         * @augments Backbone.Model
     1412         */
     1413        media.controller.EditImageNoFrame = media.controller._State.extend({
     1414                defaults: {
     1415                        id:      'edit-attachment',
     1416                        title:   l10n.editImage,
     1417                        // Region mode defaults.
     1418                        menu:    false,
     1419                        router:  'edit-metadata',
     1420                        content: 'edit-metadata',
     1421                        toolbar: 'toolbar',
     1422
     1423                        url:     ''
     1424                },
     1425
     1426                initialize: function() {
     1427                        media.controller._State.prototype.initialize.apply( this, arguments );
     1428                },
     1429
     1430                activate: function() {
     1431                        this.listenTo( this.frame, 'toolbar:render:edit-image', this.toolbar );
     1432                },
     1433
     1434                _postActivate: function() {
     1435                        this._content();
     1436                        this._router();
     1437                },
     1438
     1439                deactivate: function() {
     1440                        this.stopListening( this.frame );
     1441                },
     1442
     1443                toolbar: function() {
     1444                        var frame = this.frame,
     1445                                lastState = frame.lastState(),
     1446                                previous = lastState && lastState.id;
     1447
     1448                        frame.toolbar.set( new media.view.Toolbar({
     1449                                controller: frame,
     1450                                items: {
     1451                                        back: {
     1452                                                style: 'primary',
     1453                                                text:     l10n.back,
     1454                                                priority: 20,
     1455                                                click:    function() {
     1456                                                        if ( previous ) {
     1457                                                                frame.setState( previous );
     1458                                                        } else {
     1459                                                                frame.close();
     1460                                                        }
     1461                                                }
     1462                                        }
     1463                                }
     1464                        }) );
     1465                },
     1466
     1467                /**
     1468                 * @access private
     1469                 */
     1470                _router: function() {
     1471                        var router = this.frame.router,
     1472                                mode = this.get('router'),
     1473                                view;
     1474
     1475                        this.frame.$el.toggleClass( 'hide-router', ! mode );
     1476                        if ( ! mode ) {
     1477                                return;
     1478                        }
     1479
     1480                        this.frame.router.render( mode );
     1481
     1482                        view = router.get();
     1483                        if ( view && view.select ) {
     1484                                view.select( this.frame.content.mode() );
     1485                        }
     1486                },
     1487
     1488                _content: function() {
     1489                        var mode = this.get( 'content' );
     1490                        if ( mode ) {
     1491                                this.frame[ 'content' ].render( mode );
     1492                        }
     1493                }
     1494        });
     1495
     1496        /**
    13531497         * wp.media.controller.MediaLibrary
    13541498         *
    13551499         * @constructor
     
    17581902                        _.defaults( this.options, {
    17591903                                title:    '',
    17601904                                modal:    true,
    1761                                 uploader: true
     1905                                uploader: true,
     1906                                mode:     ['select']
    17621907                        });
    17631908
    17641909                        // Ensure core UI is enabled.
     
    19812126                                library:   {},
    19822127                                multiple:  false,
    19832128                                state:     'library',
    1984                                 uploader:  true
     2129                                uploader:  true,
     2130                                mode:      [ 'grid', 'edit' ]
    19852131                        });
    19862132
    19872133                        // Ensure core and media grid view UI is enabled.
     
    20562202                                        router:     false,
    20572203                                        content:    'browse',
    20582204                                        filterable: 'mime-types'
    2059                                 }),
    2060 
    2061                                 new media.controller.EditImage( { model: options.editImage } )
     2205                                })
    20622206                        ]);
    20632207                },
    20642208
    20652209                bindHandlers: function() {
    20662210                        this.on( 'content:create:browse', this.browseContent, this );
    20672211                        this.on( 'content:render:edit-image', this.editImageContent, this );
     2212
     2213                        // Handle a frame-level event for editing an attachment.
     2214                        this.on( 'edit:attachment', this.editAttachment, this );
     2215                        this.on( 'edit:attachment:next', this.editNextAttachment, this );
     2216                        this.on( 'edit:attachment:previous', this.editPreviousAttachment, this );
     2217                },
     2218
     2219                editPreviousAttachment: function( currentModel ) {
     2220                        var library = this.state().get('library'),
     2221                            currentModelIndex = library.indexOf( currentModel );
     2222                        this.trigger( 'edit:attachment', library.at( currentModelIndex - 1 ) );
     2223                },
     2224
     2225                editNextAttachment: function( currentModel ) {
     2226                        var library = this.state().get('library'),
     2227                            currentModelIndex = library.indexOf( currentModel );
     2228                        this.trigger( 'edit:attachment', library.at( currentModelIndex + 1 ) );
     2229                },
     2230
     2231                /**
     2232                 * Open the Edit Attachment modal.
     2233                 */
     2234                editAttachment: function( model ) {
     2235                        var library = this.state().get('library'), hasPrevious, hasNext;
     2236                        if ( library.indexOf( model ) > 0 ) {
     2237                                hasPrevious = true;
     2238                        }
     2239                        else {
     2240                                hasPrevious = false;
     2241                        }
     2242                        if ( library.indexOf( model ) < library.length - 1 ) {
     2243                                hasNext = true;
     2244                        }
     2245                        else {
     2246                                hasNext = false;
     2247                        }
     2248
     2249                        new media.view.Frame.EditAttachment({
     2250                                hasPrevious:    hasPrevious,
     2251                                hasNext:        hasNext,
     2252                                model:          model,
     2253                                gridController: this
     2254                        });
    20682255                },
    20692256
    20702257                /**
     
    20882275                                display:    state.get('displaySettings'),
    20892276                                dragInfo:   state.get('dragInfo'),
    20902277                                bulkEdit:   true,
     2278                                sidebar:    false,
    20912279
    20922280                                suggestedWidth:  state.get('suggestedWidth'),
    20932281                                suggestedHeight: state.get('suggestedHeight'),
     
    46654853                        var selection = this.options.selection;
    46664854
    46674855                        this.$el.attr('aria-label', this.model.attributes.title).attr('aria-checked', false);
    4668                         this.model.on( 'change:sizes change:uploading', this.render, this );
     4856                        this.model.on( 'change', this.render, this );
    46694857                        this.model.on( 'change:title', this._syncTitle, this );
    46704858                        this.model.on( 'change:caption', this._syncCaption, this );
    46714859                        this.model.on( 'change:percent', this.progress, this );
     
    47184906                                        compat:        false,
    47194907                                        alt:           '',
    47204908                                        description:   ''
    4721                                 });
     4909                                }, this.options );
    47224910
    47234911                        options.buttons  = this.buttons;
    47244912                        options.describe = this.controller.state().get('describe');
     
    47684956                 */
    47694957                toggleSelectionHandler: function( event ) {
    47704958                        var method;
    4771 
    47724959                        // Catch enter and space events
    47734960                        if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
    47744961                                return;
    47754962                        }
     4963
     4964                        // In the grid view, bubble up an edit:attachment event to the controller.
     4965                        if ( _.contains( this.controller.options.mode, 'grid' ) ) {
     4966                                this.controller.trigger( 'edit:attachment', this.model );
     4967                                return;
     4968                        }
     4969
    47764970                        if ( event.shiftKey ) {
    47774971                                method = 'between';
    47784972                        } else if ( event.ctrlKey || event.metaKey ) {
     
    53035497                 */
    53045498                createAttachmentView: function( attachment ) {
    53055499                        var view = new this.options.AttachmentView({
    5306                                 controller: this.controller,
    5307                                 model:      attachment,
    5308                                 collection: this.collection,
    5309                                 selection:  this.options.selection
     5500                                controller:           this.controller,
     5501                                model:                attachment,
     5502                                collection:           this.collection,
     5503                                selection:            this.options.selection,
     5504                                showAttachmentFields: this.options.showAttachmentFields
    53105505                        });
    53115506
    53125507                        return this._viewsByCid[ attachment.cid ] = view;
     
    56035798                }
    56045799        });
    56055800
    5606 
    56075801        /**
    56085802         * wp.media.view.AttachmentsBrowser
    56095803         *
     
    56215815                                filters: false,
    56225816                                search:  true,
    56235817                                display: false,
    5624 
     5818                                sidebar: true,
     5819                                showAttachmentFields: getUserSetting( 'showAttachmentFields', [ 'title', 'uploadedTo', 'dateFormatted', 'mime' ] ),
    56255820                                AttachmentView: media.view.Attachment.Library
    56265821                        });
    56275822
    56285823                        this.createToolbar();
    56295824                        this.updateContent();
    5630                         this.createSidebar();
     5825                        if ( this.options.sidebar ) {
     5826                                this.createSidebar();
     5827                        } else {
     5828                                this.$el.addClass( 'hide-sidebar' );
     5829                        }
    56315830
    56325831                        this.collection.on( 'add remove reset', this.updateContent, this );
    56335832                },
     
    56525851
    56535852                        this.views.add( this.toolbar );
    56545853
     5854                        // Feels odd to bring the global media library switcher into the Attachment
     5855                        // browser view. Is this a use case for doAction( 'add:toolbar-items:attachments-browser', this.toolbar );
     5856                        // which the controller can tap into and add this view?
     5857                        if ( _.contains( this.controller.options.mode, 'grid' ) ) {
     5858                                var libraryViewSwitcherConstructor = media.View.extend({
     5859                                        className: 'view-switch media-grid-view-switch',
     5860                                        template: media.template( 'media-library-view-switcher')
     5861                                });
     5862                                this.toolbar.set( 'libraryViewSwitcher', new libraryViewSwitcherConstructor({
     5863                                        controller: this.controller,
     5864                                        priority: -90
     5865                                }).render() );
     5866                        }
     5867
    56555868                        filters = this.options.filters;
    56565869                        if ( 'uploaded' === filters ) {
    56575870                                FiltersConstructor = media.view.AttachmentFilters.Uploaded;
     
    57465959                        this.removeContent();
    57475960
    57485961                        this.attachments = new media.view.Attachments({
    5749                                 controller: this.controller,
    5750                                 collection: this.collection,
    5751                                 selection:  this.options.selection,
    5752                                 model:      this.model,
    5753                                 sortable:   this.options.sortable,
     5962                                controller:           this.controller,
     5963                                collection:           this.collection,
     5964                                selection:            this.options.selection,
     5965                                model:                this.model,
     5966                                sortable:             this.options.sortable,
     5967                                showAttachmentFields: this.options.showAttachmentFields,
    57545968
    57555969                                // The single `Attachment` view to be used in the `Attachments` view.
    57565970                                AttachmentView: this.options.AttachmentView
     
    68147028                }
    68157029        });
    68167030
     7031        media.view.Attachment.Details.TwoColumn = media.view.Attachment.Details.extend({
     7032                template: wp.template( 'attachment-details-two-column' ),
     7033
     7034                initialize: function() {
     7035                        var selection = this.options.selection;
     7036
     7037                        this.$el.attr('aria-label', this.model.attributes.title).attr('aria-checked', false);
     7038                        this.model.on( 'change:sizes change:uploading', this.render, this );
     7039                        this.model.on( 'change:title', this._syncTitle, this );
     7040                        this.model.on( 'change:caption', this._syncCaption, this );
     7041                        this.model.on( 'change:percent', this.progress, this );
     7042
     7043                        // Update the selection.
     7044                        this.model.on( 'add', this.select, this );
     7045                        this.model.on( 'remove', this.deselect, this );
     7046                },
     7047        });
     7048
     7049        /**
     7050         * A frame for editing the details of a specific media item.
     7051         *
     7052         * Opens in a modal by default.
     7053         *
     7054         * Requires an attachment model to be passed in the options hash under `model`.
     7055         */
     7056        media.view.Frame.EditAttachment = media.view.Frame.extend({
     7057
     7058                className: 'edit-attachment-frame',
     7059                template: media.template( 'edit-attachment-frame' ),
     7060                regions:   [ 'router', 'content' ],
     7061
     7062                events: {
     7063                        'click':                    'collapse',
     7064                        'click .delete-media-item': 'deleteMediaItem',
     7065                        'click .left':              'previousMediaItem',
     7066                        'click .right':             'nextMediaItem'
     7067                },
     7068
     7069                initialize: function( options ) {
     7070                        var self = this;
     7071                        media.view.Frame.prototype.initialize.apply( this, arguments );
     7072
     7073                        _.defaults( this.options, {
     7074                                modal:    true,
     7075                                state: 'edit-attachment'
     7076                        });
     7077
     7078                        this.createStates();
     7079
     7080                        this.on( 'content:render:edit-metadata', this.editMetadataContent, this );
     7081                        this.on( 'content:render:edit-image', this.editImageContentUgh, this );
     7082
     7083                        // Only need a tab to Edit Image for images.
     7084                        if ( this.model.get( 'type' ) === 'image' ) {
     7085                                this.on( 'router:create', this.createRouter, this );
     7086                                this.on( 'router:render', this.browseRouter, this );
     7087                        }
     7088
     7089                        // Initialize modal container view.
     7090                        if ( this.options.modal ) {
     7091                                this.modal = new media.view.Modal({
     7092                                        controller: this,
     7093                                        title:      this.options.title
     7094                                });
     7095
     7096                                // Completely destroy the modal DOM element when closing it.
     7097                                this.modal.close = function() {
     7098                                        self.modal.remove();
     7099                                };
     7100
     7101                                this.modal.content( this );
     7102                                this.modal.open();
     7103                        }
     7104                },
     7105
     7106                /**
     7107                 * Add the default states to the frame.
     7108                 */
     7109                createStates: function() {
     7110                        this.states.add([
     7111                                new media.controller.EditImageNoFrame( { model: this.model } )
     7112                        ]);
     7113                },
     7114
     7115                /**
     7116                 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
     7117                 */
     7118                render: function() {
     7119                        // Activate the default state if no active state exists.
     7120                        if ( ! this.state() && this.options.state ) {
     7121                                this.setState( this.options.state );
     7122                        }
     7123                        /**
     7124                         * call 'render' directly on the parent class
     7125                         */
     7126                        return media.view.Frame.prototype.render.apply( this, arguments );
     7127                },
     7128
     7129                /**
     7130                 * Content region rendering callback for the `edit-metadata` mode.
     7131                 */
     7132                editMetadataContent: function() {
     7133                        var view = new media.view.Attachment.Details.TwoColumn({
     7134                                controller: this,
     7135                                model:      this.model
     7136                        });
     7137                        this.content.set( view );
     7138                },
     7139
     7140                /**
     7141                 * For some reason the view doesn't exist in the DOM yet, don't have the
     7142                 * patience to track this down right now.
     7143                 */
     7144                editImageContentUgh: function() {
     7145                        _.defer( _.bind( this.editImageContent, this ) );
     7146                },
     7147
     7148                /**
     7149                 * Render the EditImage view into the frame's content region.
     7150                 */
     7151                editImageContent: function() {
     7152                        var view = new media.view.EditImage( { model: this.model, controller: this } ).render();
     7153
     7154                        this.content.set( view );
     7155
     7156                        // after creating the wrapper view, load the actual editor via an ajax call
     7157                        view.loadEditor();
     7158                },
     7159
     7160                /**
     7161                 * Create the router view.
     7162                 *
     7163                 * @param {Object} router
     7164                 * @this wp.media.controller.Region
     7165                 */
     7166                createRouter: function( router ) {
     7167                        router.view = new media.view.Router({
     7168                                controller: this
     7169                        });
     7170                },
     7171
     7172                /**
     7173                 * Router rendering callback.
     7174                 *
     7175                 * @param  media.view.Router view Instantiated in this.createRouter()
     7176                 */
     7177                browseRouter: function( view ) {
     7178                        view.set({
     7179                                'edit-metadata': {
     7180                                        text:     'Edit Metadata',
     7181                                        priority: 20
     7182                                },
     7183                                'edit-image': {
     7184                                        text:     'Edit Image',
     7185                                        priority: 40
     7186                                }
     7187                        });
     7188                },
     7189
     7190                /**
     7191                 * Click handler to switch to the previous media item.
     7192                 */
     7193                previousMediaItem: function() {
     7194                        if ( ! this.options.hasPrevious )
     7195                                return;
     7196                        this.modal.close();
     7197                        this.options.gridController.trigger( 'edit:attachment:previous', this.model );
     7198                },
     7199
     7200                /**
     7201                 * Click handler to switch to the next media item.
     7202                 */
     7203                nextMediaItem: function() {
     7204                        if ( ! this.options.hasNext )
     7205                                return;
     7206                        this.modal.close();
     7207                        this.options.gridController.trigger( 'edit:attachment:next', this.model );
     7208                }
     7209
     7210        });
     7211
    68177212        media.view.EditImage = media.View.extend({
    68187213
    68197214                className: 'image-editor',
  • src/wp-includes/media-template.php

    diff --git a/src/wp-includes/media-template.php b/src/wp-includes/media-template.php
    index 2c0ff80..9a1f5d8 100644
    a b function wp_print_media_templates() { 
    220220                </div>
    221221        </script>
    222222
     223        <script type="text/html" id="tmpl-media-library-view-switcher">
     224                <a href="<?php echo esc_url( add_query_arg( 'mode', 'list', $_SERVER['REQUEST_URI'] ) ) ?>" class="view-list">
     225                        <img id="view-switch-list" src="<?php echo includes_url( 'images/blank.gif' ) ?>" width="20" height="20" title="List View" alt="List View"/>
     226                </a>
     227                <a href="<?php echo esc_url( add_query_arg( 'mode', 'grid', $_SERVER['REQUEST_URI'] ) ) ?>" class="view-grid current">
     228                        <img id="view-switch-excerpt" src="<?php echo includes_url( 'images/blank.gif' ) ?>" width="20" height="20" title="Grid View" alt="Grid View"/>
     229                </a>
     230        </script>
     231
    223232        <script type="text/html" id="tmpl-uploader-status">
    224233                <h3><?php _e( 'Uploading' ); ?></h3>
    225234                <a class="upload-dismiss-errors" href="#"><?php _e('Dismiss Errors'); ?></a>
    function wp_print_media_templates() { 
    241250                <span class="upload-error-message">{{ data.message }}</span>
    242251        </script>
    243252
     253        <script type="text/html" id="tmpl-edit-attachment-frame">
     254                <div class="edit-media-header">
     255                        <button class="left dashicons dashicons-no<# if ( ! data.hasPrevious ) { #> disabled <# } #>"><span class="screen-reader-text"><?php _e( 'Edit previous media item' ); ?></span></button>
     256                        <button class="right dashicons dashicons-no<# if ( ! data.hasNext ) { #> disabled <# } #>"><span class="screen-reader-text"><?php _e( 'Edit next media item' ); ?></span></button>
     257                </div>
     258                <div class="media-frame-router"></div>
     259                <div class="media-frame-content"></div>
     260                <div class="media-frame-toolbar"></div>
     261        </script>
     262
     263        <script type="text/html" id="tmpl-attachment-details-two-column">
     264                <h3>
     265                        <?php _e('Attachment Details'); ?>
     266
     267                        <span class="settings-save-status">
     268                                <span class="spinner"></span>
     269                                <span class="saved"><?php esc_html_e('Saved.'); ?></span>
     270                        </span>
     271                </h3>
     272                <div class="attachment-info">
     273                        <div class="thumbnail thumbnail-{{ data.type }}">
     274                                <# if ( data.uploading ) { #>
     275                                        <div class="media-progress-bar"><div></div></div>
     276                                <# } else if ( 'image' === data.type ) { #>
     277                                        <img src="{{ data.sizes.full.url }}" draggable="false" />
     278                                <# } else { #>
     279                                        <img src="{{ data.icon }}" class="icon" draggable="false" />
     280                                <# } #>
     281                        </div>
     282                        <div class="details">
     283                                <div class="filename">{{ data.filename }}</div>
     284                                <div class="uploaded">{{ data.dateFormatted }}</div>
     285
     286                                <div class="file-size">{{ data.filesizeHumanReadable }}</div>
     287                                <# if ( 'image' === data.type && ! data.uploading ) { #>
     288                                        <# if ( data.width && data.height ) { #>
     289                                                <div class="dimensions">{{ data.width }} &times; {{ data.height }}</div>
     290                                        <# } #>
     291
     292                                        <# if ( data.can.save ) { #>
     293                                                <a class="edit-attachment" href="{{ data.editLink }}&amp;image-editor" target="_blank"><?php _e( 'Edit Image' ); ?></a>
     294                                                <a class="refresh-attachment" href="#"><?php _e( 'Refresh' ); ?></a>
     295                                        <# } #>
     296                                <# } #>
     297
     298                                <# if ( data.fileLength ) { #>
     299                                        <div class="file-length"><?php _e( 'Length:' ); ?> {{ data.fileLength }}</div>
     300                                <# } #>
     301
     302                                <# if ( ! data.uploading && data.can.remove ) { #>
     303                                        <?php if ( MEDIA_TRASH ): ?>
     304                                                <a class="trash-attachment" href="#"><?php _e( 'Trash' ); ?></a>
     305                                        <?php else: ?>
     306                                                <a class="delete-attachment" href="#"><?php _e( 'Delete Permanently' ); ?></a>
     307                                        <?php endif; ?>
     308                                <# } #>
     309
     310                                <div class="compat-meta">
     311                                        <# if ( data.compat && data.compat.meta ) { #>
     312                                                {{{ data.compat.meta }}}
     313                                        <# } #>
     314                                </div>
     315                        </div>
     316                </div>
     317                <div class="attachment-fields">
     318                        <label class="setting" data-setting="url">
     319                                <span class="name"><?php _e('URL'); ?></span>
     320                                <input type="text" value="{{ data.url }}" readonly />
     321                        </label>
     322                        <# var maybeReadOnly = data.can.save || data.allowLocalEdits ? '' : 'readonly'; #>
     323                        <label class="setting" data-setting="title">
     324                                <span class="name"><?php _e('Title'); ?></span>
     325                                <input type="text" value="{{ data.title }}" {{ maybeReadOnly }} />
     326                        </label>
     327                        <label class="setting" data-setting="caption">
     328                                <span class="name"><?php _e('Caption'); ?></span>
     329                                <textarea {{ maybeReadOnly }}>{{ data.caption }}</textarea>
     330                        </label>
     331                        <# if ( 'image' === data.type ) { #>
     332                                <label class="setting" data-setting="alt">
     333                                        <span class="name"><?php _e('Alt Text'); ?></span>
     334                                        <input type="text" value="{{ data.alt }}" {{ maybeReadOnly }} />
     335                                </label>
     336                        <# } #>
     337                        <label class="setting" data-setting="description">
     338                                <span class="name"><?php _e('Description'); ?></span>
     339                                <textarea {{ maybeReadOnly }}>{{ data.description }}</textarea>
     340                        </label>
     341                        <label class="setting">
     342                                        <span class="name"><?php _e( 'Uploaded By' ); ?></span>
     343                                        <span class="value">{{ data.authorName }}</span>
     344                                </label>
     345                        <# if ( data.uploadedTo ) { #>
     346                                <label class="setting">
     347                                        <span class="name"><?php _e('Uploaded To'); ?></span>
     348                                        <span class="value"><a href="{{ data.uploadedToLink }}">{{ data.uploadedToTitle }}</a></span>
     349                                </label>
     350                        <# } #>
     351                </div>
     352        </script>
     353
    244354        <script type="text/html" id="tmpl-attachment">
    245355                <div class="attachment-preview type-{{ data.type }} subtype-{{ data.subtype }} {{ data.orientation }}">
    246356                        <# if ( data.uploading ) { #>
    function wp_print_media_templates() { 
    257367                                        <div>{{ data.filename }}</div>
    258368                                </div>
    259369                        <# } #>
    260 
     370                        <# if ( _.contains( data.controller.options.mode, 'grid' ) ) { #>
     371                                <span class="edit-media">Edit Media</span>
     372                        <# } #>
    261373                        <# if ( data.buttons.close ) { #>
    262374                                <a class="close media-modal-icon" href="#" title="<?php esc_attr_e('Remove'); ?>"></a>
    263375                        <# } #>
    function wp_print_media_templates() { 
    283395                                        <# } #> {{ maybeReadOnly }} />
    284396                        <# } #>
    285397                <# } #>
     398                <# if ( _.contains( data.controller.options.mode, 'grid' ) ) { #>
     399                        <# _.each( data.showAttachmentFields, function( field ) { #>
     400                                <div class="data-field data-{{ field }}">
     401                                        <# if ( 'uploadedTo' === field ) { #>
     402
     403                                                <# if ( data[field] ) { #>
     404                                                <?php _e( 'Uploaded To:' ) ?>
     405                                                <# } else { #>
     406                                                <?php _e( 'Unattached' ) ?>
     407                                                <# } #>
     408
     409                                        <# } #>
     410                                <# if ( data[field] ) { #>
     411                                        &nbsp;{{ data[field] }}
     412                                <# } #>
     413                                </div>
     414                        <# }); #>
     415                <# } #>
     416
    286417        </script>
    287418
    288419        <script type="text/html" id="tmpl-attachment-details">