home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress / wp-includes / js / media-editor.js < prev    next >
Encoding:
JavaScript  |  2017-09-08  |  28.2 KB  |  1,059 lines

  1. /* global getUserSetting, tinymce, QTags */
  2.  
  3. // WordPress, TinyMCE, and Media
  4. // -----------------------------
  5. (function($, _){
  6.     /**
  7.      * Stores the editors' `wp.media.controller.Frame` instances.
  8.      *
  9.      * @static
  10.      */
  11.     var workflows = {};
  12.  
  13.     /**
  14.      * A helper mixin function to avoid truthy and falsey values being
  15.      *   passed as an input that expects booleans. If key is undefined in the map,
  16.      *   but has a default value, set it.
  17.      *
  18.      * @param {object} attrs Map of props from a shortcode or settings.
  19.      * @param {string} key The key within the passed map to check for a value.
  20.      * @returns {mixed|undefined} The original or coerced value of key within attrs
  21.      */
  22.     wp.media.coerce = function ( attrs, key ) {
  23.         if ( _.isUndefined( attrs[ key ] ) && ! _.isUndefined( this.defaults[ key ] ) ) {
  24.             attrs[ key ] = this.defaults[ key ];
  25.         } else if ( 'true' === attrs[ key ] ) {
  26.             attrs[ key ] = true;
  27.         } else if ( 'false' === attrs[ key ] ) {
  28.             attrs[ key ] = false;
  29.         }
  30.         return attrs[ key ];
  31.     };
  32.  
  33.     /** @namespace wp.media.string */
  34.     wp.media.string = {
  35.         /**
  36.          * Joins the `props` and `attachment` objects,
  37.          * outputting the proper object format based on the
  38.          * attachment's type.
  39.          *
  40.          * @param {Object} [props={}] Attachment details (align, link, size, etc).
  41.          * @param {Object} attachment The attachment object, media version of Post.
  42.          * @returns {Object} Joined props
  43.          */
  44.         props: function( props, attachment ) {
  45.             var link, linkUrl, size, sizes,
  46.                 defaultProps = wp.media.view.settings.defaultProps;
  47.  
  48.             props = props ? _.clone( props ) : {};
  49.  
  50.             if ( attachment && attachment.type ) {
  51.                 props.type = attachment.type;
  52.             }
  53.  
  54.             if ( 'image' === props.type ) {
  55.                 props = _.defaults( props || {}, {
  56.                     align:   defaultProps.align || getUserSetting( 'align', 'none' ),
  57.                     size:    defaultProps.size  || getUserSetting( 'imgsize', 'medium' ),
  58.                     url:     '',
  59.                     classes: []
  60.                 });
  61.             }
  62.  
  63.             // All attachment-specific settings follow.
  64.             if ( ! attachment ) {
  65.                 return props;
  66.             }
  67.  
  68.             props.title = props.title || attachment.title;
  69.  
  70.             link = props.link || defaultProps.link || getUserSetting( 'urlbutton', 'file' );
  71.             if ( 'file' === link || 'embed' === link ) {
  72.                 linkUrl = attachment.url;
  73.             } else if ( 'post' === link ) {
  74.                 linkUrl = attachment.link;
  75.             } else if ( 'custom' === link ) {
  76.                 linkUrl = props.linkUrl;
  77.             }
  78.             props.linkUrl = linkUrl || '';
  79.  
  80.             // Format properties for images.
  81.             if ( 'image' === attachment.type ) {
  82.                 props.classes.push( 'wp-image-' + attachment.id );
  83.  
  84.                 sizes = attachment.sizes;
  85.                 size = sizes && sizes[ props.size ] ? sizes[ props.size ] : attachment;
  86.  
  87.                 _.extend( props, _.pick( attachment, 'align', 'caption', 'alt' ), {
  88.                     width:     size.width,
  89.                     height:    size.height,
  90.                     src:       size.url,
  91.                     captionId: 'attachment_' + attachment.id
  92.                 });
  93.             } else if ( 'video' === attachment.type || 'audio' === attachment.type ) {
  94.                 _.extend( props, _.pick( attachment, 'title', 'type', 'icon', 'mime' ) );
  95.             // Format properties for non-images.
  96.             } else {
  97.                 props.title = props.title || attachment.filename;
  98.                 props.rel = props.rel || 'attachment wp-att-' + attachment.id;
  99.             }
  100.  
  101.             return props;
  102.         },
  103.         /**
  104.          * Create link markup that is suitable for passing to the editor
  105.          *
  106.          * @param {Object} props Attachment details (align, link, size, etc).
  107.          * @param {Object} attachment The attachment object, media version of Post.
  108.          * @returns {string} The link markup
  109.          */
  110.         link: function( props, attachment ) {
  111.             var options;
  112.  
  113.             props = wp.media.string.props( props, attachment );
  114.  
  115.             options = {
  116.                 tag:     'a',
  117.                 content: props.title,
  118.                 attrs:   {
  119.                     href: props.linkUrl
  120.                 }
  121.             };
  122.  
  123.             if ( props.rel ) {
  124.                 options.attrs.rel = props.rel;
  125.             }
  126.  
  127.             return wp.html.string( options );
  128.         },
  129.         /**
  130.          * Create an Audio shortcode string that is suitable for passing to the editor
  131.          *
  132.          * @param {Object} props Attachment details (align, link, size, etc).
  133.          * @param {Object} attachment The attachment object, media version of Post.
  134.          * @returns {string} The audio shortcode
  135.          */
  136.         audio: function( props, attachment ) {
  137.             return wp.media.string._audioVideo( 'audio', props, attachment );
  138.         },
  139.         /**
  140.          * Create a Video shortcode string that is suitable for passing to the editor
  141.          *
  142.          * @param {Object} props Attachment details (align, link, size, etc).
  143.          * @param {Object} attachment The attachment object, media version of Post.
  144.          * @returns {string} The video shortcode
  145.          */
  146.         video: function( props, attachment ) {
  147.             return wp.media.string._audioVideo( 'video', props, attachment );
  148.         },
  149.         /**
  150.          * Helper function to create a media shortcode string
  151.          *
  152.          * @access private
  153.          *
  154.          * @param {string} type The shortcode tag name: 'audio' or 'video'.
  155.          * @param {Object} props Attachment details (align, link, size, etc).
  156.          * @param {Object} attachment The attachment object, media version of Post.
  157.          * @returns {string} The media shortcode
  158.          */
  159.         _audioVideo: function( type, props, attachment ) {
  160.             var shortcode, html, extension;
  161.  
  162.             props = wp.media.string.props( props, attachment );
  163.             if ( props.link !== 'embed' )
  164.                 return wp.media.string.link( props );
  165.  
  166.             shortcode = {};
  167.  
  168.             if ( 'video' === type ) {
  169.                 if ( attachment.image && -1 === attachment.image.src.indexOf( attachment.icon ) ) {
  170.                     shortcode.poster = attachment.image.src;
  171.                 }
  172.  
  173.                 if ( attachment.width ) {
  174.                     shortcode.width = attachment.width;
  175.                 }
  176.  
  177.                 if ( attachment.height ) {
  178.                     shortcode.height = attachment.height;
  179.                 }
  180.             }
  181.  
  182.             extension = attachment.filename.split('.').pop();
  183.  
  184.             if ( _.contains( wp.media.view.settings.embedExts, extension ) ) {
  185.                 shortcode[extension] = attachment.url;
  186.             } else {
  187.                 // Render unsupported audio and video files as links.
  188.                 return wp.media.string.link( props );
  189.             }
  190.  
  191.             html = wp.shortcode.string({
  192.                 tag:     type,
  193.                 attrs:   shortcode
  194.             });
  195.  
  196.             return html;
  197.         },
  198.         /**
  199.          * Create image markup, optionally with a link and/or wrapped in a caption shortcode,
  200.          *  that is suitable for passing to the editor
  201.          *
  202.          * @param {Object} props Attachment details (align, link, size, etc).
  203.          * @param {Object} attachment The attachment object, media version of Post.
  204.          * @returns {string}
  205.          */
  206.         image: function( props, attachment ) {
  207.             var img = {},
  208.                 options, classes, shortcode, html;
  209.  
  210.             props.type = 'image';
  211.             props = wp.media.string.props( props, attachment );
  212.             classes = props.classes || [];
  213.  
  214.             img.src = ! _.isUndefined( attachment ) ? attachment.url : props.url;
  215.             _.extend( img, _.pick( props, 'width', 'height', 'alt' ) );
  216.  
  217.             // Only assign the align class to the image if we're not printing
  218.             // a caption, since the alignment is sent to the shortcode.
  219.             if ( props.align && ! props.caption ) {
  220.                 classes.push( 'align' + props.align );
  221.             }
  222.  
  223.             if ( props.size ) {
  224.                 classes.push( 'size-' + props.size );
  225.             }
  226.  
  227.             img['class'] = _.compact( classes ).join(' ');
  228.  
  229.             // Generate `img` tag options.
  230.             options = {
  231.                 tag:    'img',
  232.                 attrs:  img,
  233.                 single: true
  234.             };
  235.  
  236.             // Generate the `a` element options, if they exist.
  237.             if ( props.linkUrl ) {
  238.                 options = {
  239.                     tag:   'a',
  240.                     attrs: {
  241.                         href: props.linkUrl
  242.                     },
  243.                     content: options
  244.                 };
  245.             }
  246.  
  247.             html = wp.html.string( options );
  248.  
  249.             // Generate the caption shortcode.
  250.             if ( props.caption ) {
  251.                 shortcode = {};
  252.  
  253.                 if ( img.width ) {
  254.                     shortcode.width = img.width;
  255.                 }
  256.  
  257.                 if ( props.captionId ) {
  258.                     shortcode.id = props.captionId;
  259.                 }
  260.  
  261.                 if ( props.align ) {
  262.                     shortcode.align = 'align' + props.align;
  263.                 }
  264.  
  265.                 html = wp.shortcode.string({
  266.                     tag:     'caption',
  267.                     attrs:   shortcode,
  268.                     content: html + ' ' + props.caption
  269.                 });
  270.             }
  271.  
  272.             return html;
  273.         }
  274.     };
  275.  
  276.     wp.media.embed = {
  277.         coerce : wp.media.coerce,
  278.  
  279.         defaults : {
  280.             url : '',
  281.             width: '',
  282.             height: ''
  283.         },
  284.  
  285.         edit : function( data, isURL ) {
  286.             var frame, props = {}, shortcode;
  287.  
  288.             if ( isURL ) {
  289.                 props.url = data.replace(/<[^>]+>/g, '');
  290.             } else {
  291.                 shortcode = wp.shortcode.next( 'embed', data ).shortcode;
  292.  
  293.                 props = _.defaults( shortcode.attrs.named, this.defaults );
  294.                 if ( shortcode.content ) {
  295.                     props.url = shortcode.content;
  296.                 }
  297.             }
  298.  
  299.             frame = wp.media({
  300.                 frame: 'post',
  301.                 state: 'embed',
  302.                 metadata: props
  303.             });
  304.  
  305.             return frame;
  306.         },
  307.  
  308.         shortcode : function( model ) {
  309.             var self = this, content;
  310.  
  311.             _.each( this.defaults, function( value, key ) {
  312.                 model[ key ] = self.coerce( model, key );
  313.  
  314.                 if ( value === model[ key ] ) {
  315.                     delete model[ key ];
  316.                 }
  317.             });
  318.  
  319.             content = model.url;
  320.             delete model.url;
  321.  
  322.             return new wp.shortcode({
  323.                 tag: 'embed',
  324.                 attrs: model,
  325.                 content: content
  326.             });
  327.         }
  328.     };
  329.  
  330.     /**
  331.      * @class wp.media.collection
  332.      *
  333.      * @param {Object} attributes
  334.      */
  335.     wp.media.collection = function(attributes) {
  336.         var collections = {};
  337.  
  338.         return _.extend(/** @lends wp.media.collection.prototype */{
  339.             coerce : wp.media.coerce,
  340.             /**
  341.              * Retrieve attachments based on the properties of the passed shortcode
  342.              *
  343.              * @param {wp.shortcode} shortcode An instance of wp.shortcode().
  344.              * @returns {wp.media.model.Attachments} A Backbone.Collection containing
  345.              *      the media items belonging to a collection.
  346.              *      The query[ this.tag ] property is a Backbone.Model
  347.              *          containing the 'props' for the collection.
  348.              */
  349.             attachments: function( shortcode ) {
  350.                 var shortcodeString = shortcode.string(),
  351.                     result = collections[ shortcodeString ],
  352.                     attrs, args, query, others, self = this;
  353.  
  354.                 delete collections[ shortcodeString ];
  355.                 if ( result ) {
  356.                     return result;
  357.                 }
  358.                 // Fill the default shortcode attributes.
  359.                 attrs = _.defaults( shortcode.attrs.named, this.defaults );
  360.                 args  = _.pick( attrs, 'orderby', 'order' );
  361.  
  362.                 args.type    = this.type;
  363.                 args.perPage = -1;
  364.  
  365.                 // Mark the `orderby` override attribute.
  366.                 if ( undefined !== attrs.orderby ) {
  367.                     attrs._orderByField = attrs.orderby;
  368.                 }
  369.  
  370.                 if ( 'rand' === attrs.orderby ) {
  371.                     attrs._orderbyRandom = true;
  372.                 }
  373.  
  374.                 // Map the `orderby` attribute to the corresponding model property.
  375.                 if ( ! attrs.orderby || /^menu_order(?: ID)?$/i.test( attrs.orderby ) ) {
  376.                     args.orderby = 'menuOrder';
  377.                 }
  378.  
  379.                 // Map the `ids` param to the correct query args.
  380.                 if ( attrs.ids ) {
  381.                     args.post__in = attrs.ids.split(',');
  382.                     args.orderby  = 'post__in';
  383.                 } else if ( attrs.include ) {
  384.                     args.post__in = attrs.include.split(',');
  385.                 }
  386.  
  387.                 if ( attrs.exclude ) {
  388.                     args.post__not_in = attrs.exclude.split(',');
  389.                 }
  390.  
  391.                 if ( ! args.post__in ) {
  392.                     args.uploadedTo = attrs.id;
  393.                 }
  394.  
  395.                 // Collect the attributes that were not included in `args`.
  396.                 others = _.omit( attrs, 'id', 'ids', 'include', 'exclude', 'orderby', 'order' );
  397.  
  398.                 _.each( this.defaults, function( value, key ) {
  399.                     others[ key ] = self.coerce( others, key );
  400.                 });
  401.  
  402.                 query = wp.media.query( args );
  403.                 query[ this.tag ] = new Backbone.Model( others );
  404.                 return query;
  405.             },
  406.             /**
  407.              * Triggered when clicking 'Insert {label}' or 'Update {label}'
  408.              *
  409.              * @param {wp.media.model.Attachments} attachments A Backbone.Collection containing
  410.              *      the media items belonging to a collection.
  411.              *      The query[ this.tag ] property is a Backbone.Model
  412.              *          containing the 'props' for the collection.
  413.              * @returns {wp.shortcode}
  414.              */
  415.             shortcode: function( attachments ) {
  416.                 var props = attachments.props.toJSON(),
  417.                     attrs = _.pick( props, 'orderby', 'order' ),
  418.                     shortcode, clone;
  419.  
  420.                 if ( attachments.type ) {
  421.                     attrs.type = attachments.type;
  422.                     delete attachments.type;
  423.                 }
  424.  
  425.                 if ( attachments[this.tag] ) {
  426.                     _.extend( attrs, attachments[this.tag].toJSON() );
  427.                 }
  428.  
  429.                 // Convert all gallery shortcodes to use the `ids` property.
  430.                 // Ignore `post__in` and `post__not_in`; the attachments in
  431.                 // the collection will already reflect those properties.
  432.                 attrs.ids = attachments.pluck('id');
  433.  
  434.                 // Copy the `uploadedTo` post ID.
  435.                 if ( props.uploadedTo ) {
  436.                     attrs.id = props.uploadedTo;
  437.                 }
  438.                 // Check if the gallery is randomly ordered.
  439.                 delete attrs.orderby;
  440.  
  441.                 if ( attrs._orderbyRandom ) {
  442.                     attrs.orderby = 'rand';
  443.                 } else if ( attrs._orderByField && attrs._orderByField != 'rand' ) {
  444.                     attrs.orderby = attrs._orderByField;
  445.                 }
  446.  
  447.                 delete attrs._orderbyRandom;
  448.                 delete attrs._orderByField;
  449.  
  450.                 // If the `ids` attribute is set and `orderby` attribute
  451.                 // is the default value, clear it for cleaner output.
  452.                 if ( attrs.ids && 'post__in' === attrs.orderby ) {
  453.                     delete attrs.orderby;
  454.                 }
  455.  
  456.                 attrs = this.setDefaults( attrs );
  457.  
  458.                 shortcode = new wp.shortcode({
  459.                     tag:    this.tag,
  460.                     attrs:  attrs,
  461.                     type:   'single'
  462.                 });
  463.  
  464.                 // Use a cloned version of the gallery.
  465.                 clone = new wp.media.model.Attachments( attachments.models, {
  466.                     props: props
  467.                 });
  468.                 clone[ this.tag ] = attachments[ this.tag ];
  469.                 collections[ shortcode.string() ] = clone;
  470.  
  471.                 return shortcode;
  472.             },
  473.             /**
  474.              * Triggered when double-clicking a collection shortcode placeholder
  475.              *   in the editor
  476.              *
  477.              * @param {string} content Content that is searched for possible
  478.              *    shortcode markup matching the passed tag name,
  479.              *
  480.              * @this wp.media.{prop}
  481.              *
  482.              * @returns {wp.media.view.MediaFrame.Select} A media workflow.
  483.              */
  484.             edit: function( content ) {
  485.                 var shortcode = wp.shortcode.next( this.tag, content ),
  486.                     defaultPostId = this.defaults.id,
  487.                     attachments, selection, state;
  488.  
  489.                 // Bail if we didn't match the shortcode or all of the content.
  490.                 if ( ! shortcode || shortcode.content !== content ) {
  491.                     return;
  492.                 }
  493.  
  494.                 // Ignore the rest of the match object.
  495.                 shortcode = shortcode.shortcode;
  496.  
  497.                 if ( _.isUndefined( shortcode.get('id') ) && ! _.isUndefined( defaultPostId ) ) {
  498.                     shortcode.set( 'id', defaultPostId );
  499.                 }
  500.  
  501.                 attachments = this.attachments( shortcode );
  502.  
  503.                 selection = new wp.media.model.Selection( attachments.models, {
  504.                     props:    attachments.props.toJSON(),
  505.                     multiple: true
  506.                 });
  507.  
  508.                 selection[ this.tag ] = attachments[ this.tag ];
  509.  
  510.                 // Fetch the query's attachments, and then break ties from the
  511.                 // query to allow for sorting.
  512.                 selection.more().done( function() {
  513.                     // Break ties with the query.
  514.                     selection.props.set({ query: false });
  515.                     selection.unmirror();
  516.                     selection.props.unset('orderby');
  517.                 });
  518.  
  519.                 // Destroy the previous gallery frame.
  520.                 if ( this.frame ) {
  521.                     this.frame.dispose();
  522.                 }
  523.  
  524.                 if ( shortcode.attrs.named.type && 'video' === shortcode.attrs.named.type ) {
  525.                     state = 'video-' + this.tag + '-edit';
  526.                 } else {
  527.                     state = this.tag + '-edit';
  528.                 }
  529.  
  530.                 // Store the current frame.
  531.                 this.frame = wp.media({
  532.                     frame:     'post',
  533.                     state:     state,
  534.                     title:     this.editTitle,
  535.                     editing:   true,
  536.                     multiple:  true,
  537.                     selection: selection
  538.                 }).open();
  539.  
  540.                 return this.frame;
  541.             },
  542.  
  543.             setDefaults: function( attrs ) {
  544.                 var self = this;
  545.                 // Remove default attributes from the shortcode.
  546.                 _.each( this.defaults, function( value, key ) {
  547.                     attrs[ key ] = self.coerce( attrs, key );
  548.                     if ( value === attrs[ key ] ) {
  549.                         delete attrs[ key ];
  550.                     }
  551.                 });
  552.  
  553.                 return attrs;
  554.             }
  555.         }, attributes );
  556.     };
  557.  
  558.     wp.media._galleryDefaults = {
  559.         itemtag: 'dl',
  560.         icontag: 'dt',
  561.         captiontag: 'dd',
  562.         columns: '3',
  563.         link: 'post',
  564.         size: 'thumbnail',
  565.         order: 'ASC',
  566.         id: wp.media.view.settings.post && wp.media.view.settings.post.id,
  567.         orderby : 'menu_order ID'
  568.     };
  569.  
  570.     if ( wp.media.view.settings.galleryDefaults ) {
  571.         wp.media.galleryDefaults = _.extend( {}, wp.media._galleryDefaults, wp.media.view.settings.galleryDefaults );
  572.     } else {
  573.         wp.media.galleryDefaults = wp.media._galleryDefaults;
  574.     }
  575.  
  576.     wp.media.gallery = new wp.media.collection({
  577.         tag: 'gallery',
  578.         type : 'image',
  579.         editTitle : wp.media.view.l10n.editGalleryTitle,
  580.         defaults : wp.media.galleryDefaults,
  581.  
  582.         setDefaults: function( attrs ) {
  583.             var self = this, changed = ! _.isEqual( wp.media.galleryDefaults, wp.media._galleryDefaults );
  584.             _.each( this.defaults, function( value, key ) {
  585.                 attrs[ key ] = self.coerce( attrs, key );
  586.                 if ( value === attrs[ key ] && ( ! changed || value === wp.media._galleryDefaults[ key ] ) ) {
  587.                     delete attrs[ key ];
  588.                 }
  589.             } );
  590.             return attrs;
  591.         }
  592.     });
  593.  
  594.     /**
  595.      * @namespace wp.media.featuredImage
  596.      * @memberOf wp.media
  597.      */
  598.     wp.media.featuredImage = {
  599.         /**
  600.          * Get the featured image post ID
  601.          *
  602.          * @returns {wp.media.view.settings.post.featuredImageId|number}
  603.          */
  604.         get: function() {
  605.             return wp.media.view.settings.post.featuredImageId;
  606.         },
  607.         /**
  608.          * Set the featured image id, save the post thumbnail data and
  609.          * set the HTML in the post meta box to the new featured image.
  610.          *
  611.          * @param {number} id The post ID of the featured image, or -1 to unset it.
  612.          */
  613.         set: function( id ) {
  614.             var settings = wp.media.view.settings;
  615.  
  616.             settings.post.featuredImageId = id;
  617.  
  618.             wp.media.post( 'get-post-thumbnail-html', {
  619.                 post_id:      settings.post.id,
  620.                 thumbnail_id: settings.post.featuredImageId,
  621.                 _wpnonce:     settings.post.nonce
  622.             }).done( function( html ) {
  623.                 if ( html == '0' ) {
  624.                     window.alert( window.setPostThumbnailL10n.error );
  625.                     return;
  626.                 }
  627.                 $( '.inside', '#postimagediv' ).html( html );
  628.             });
  629.         },
  630.         /**
  631.          * Remove the featured image id, save the post thumbnail data and
  632.          * set the HTML in the post meta box to no featured image.
  633.          */
  634.         remove: function() {
  635.             wp.media.featuredImage.set( -1 );
  636.         },
  637.         /**
  638.          * The Featured Image workflow
  639.          *
  640.          * @this wp.media.featuredImage
  641.          *
  642.          * @returns {wp.media.view.MediaFrame.Select} A media workflow.
  643.          */
  644.         frame: function() {
  645.             if ( this._frame ) {
  646.                 wp.media.frame = this._frame;
  647.                 return this._frame;
  648.             }
  649.  
  650.             this._frame = wp.media({
  651.                 state: 'featured-image',
  652.                 states: [ new wp.media.controller.FeaturedImage() , new wp.media.controller.EditImage() ]
  653.             });
  654.  
  655.             this._frame.on( 'toolbar:create:featured-image', function( toolbar ) {
  656.                 /**
  657.                  * @this wp.media.view.MediaFrame.Select
  658.                  */
  659.                 this.createSelectToolbar( toolbar, {
  660.                     text: wp.media.view.l10n.setFeaturedImage
  661.                 });
  662.             }, this._frame );
  663.  
  664.             this._frame.on( 'content:render:edit-image', function() {
  665.                 var selection = this.state('featured-image').get('selection'),
  666.                     view = new wp.media.view.EditImage( { model: selection.single(), controller: this } ).render();
  667.  
  668.                 this.content.set( view );
  669.  
  670.                 // after bringing in the frame, load the actual editor via an ajax call
  671.                 view.loadEditor();
  672.  
  673.             }, this._frame );
  674.  
  675.             this._frame.state('featured-image').on( 'select', this.select );
  676.             return this._frame;
  677.         },
  678.         /**
  679.          * 'select' callback for Featured Image workflow, triggered when
  680.          *  the 'Set Featured Image' button is clicked in the media modal.
  681.          *
  682.          * @this wp.media.controller.FeaturedImage
  683.          */
  684.         select: function() {
  685.             var selection = this.get('selection').single();
  686.  
  687.             if ( ! wp.media.view.settings.post.featuredImageId ) {
  688.                 return;
  689.             }
  690.  
  691.             wp.media.featuredImage.set( selection ? selection.id : -1 );
  692.         },
  693.         /**
  694.          * Open the content media manager to the 'featured image' tab when
  695.          * the post thumbnail is clicked.
  696.          *
  697.          * Update the featured image id when the 'remove' link is clicked.
  698.          */
  699.         init: function() {
  700.             $('#postimagediv').on( 'click', '#set-post-thumbnail', function( event ) {
  701.                 event.preventDefault();
  702.                 // Stop propagation to prevent thickbox from activating.
  703.                 event.stopPropagation();
  704.  
  705.                 wp.media.featuredImage.frame().open();
  706.             }).on( 'click', '#remove-post-thumbnail', function() {
  707.                 wp.media.featuredImage.remove();
  708.                 return false;
  709.             });
  710.         }
  711.     };
  712.  
  713.     $( wp.media.featuredImage.init );
  714.  
  715.     /** @namespace wp.media.editor */
  716.     wp.media.editor = {
  717.         /**
  718.          * Send content to the editor
  719.          *
  720.          * @param {string} html Content to send to the editor
  721.          */
  722.         insert: function( html ) {
  723.             var editor, wpActiveEditor,
  724.                 hasTinymce = ! _.isUndefined( window.tinymce ),
  725.                 hasQuicktags = ! _.isUndefined( window.QTags );
  726.  
  727.             if ( this.activeEditor ) {
  728.                 wpActiveEditor = window.wpActiveEditor = this.activeEditor;
  729.             } else {
  730.                 wpActiveEditor = window.wpActiveEditor;
  731.             }
  732.  
  733.             // Delegate to the global `send_to_editor` if it exists.
  734.             // This attempts to play nice with any themes/plugins that have
  735.             // overridden the insert functionality.
  736.             if ( window.send_to_editor ) {
  737.                 return window.send_to_editor.apply( this, arguments );
  738.             }
  739.  
  740.             if ( ! wpActiveEditor ) {
  741.                 if ( hasTinymce && tinymce.activeEditor ) {
  742.                     editor = tinymce.activeEditor;
  743.                     wpActiveEditor = window.wpActiveEditor = editor.id;
  744.                 } else if ( ! hasQuicktags ) {
  745.                     return false;
  746.                 }
  747.             } else if ( hasTinymce ) {
  748.                 editor = tinymce.get( wpActiveEditor );
  749.             }
  750.  
  751.             if ( editor && ! editor.isHidden() ) {
  752.                 editor.execCommand( 'mceInsertContent', false, html );
  753.             } else if ( hasQuicktags ) {
  754.                 QTags.insertContent( html );
  755.             } else {
  756.                 document.getElementById( wpActiveEditor ).value += html;
  757.             }
  758.  
  759.             // If the old thickbox remove function exists, call it in case
  760.             // a theme/plugin overloaded it.
  761.             if ( window.tb_remove ) {
  762.                 try { window.tb_remove(); } catch( e ) {}
  763.             }
  764.         },
  765.  
  766.         /**
  767.          * Setup 'workflow' and add to the 'workflows' cache. 'open' can
  768.          *  subsequently be called upon it.
  769.          *
  770.          * @param {string} id A slug used to identify the workflow.
  771.          * @param {Object} [options={}]
  772.          *
  773.          * @this wp.media.editor
  774.          *
  775.          * @returns {wp.media.view.MediaFrame.Select} A media workflow.
  776.          */
  777.         add: function( id, options ) {
  778.             var workflow = this.get( id );
  779.  
  780.             // only add once: if exists return existing
  781.             if ( workflow ) {
  782.                 return workflow;
  783.             }
  784.  
  785.             workflow = workflows[ id ] = wp.media( _.defaults( options || {}, {
  786.                 frame:    'post',
  787.                 state:    'insert',
  788.                 title:    wp.media.view.l10n.addMedia,
  789.                 multiple: true
  790.             } ) );
  791.  
  792.             workflow.on( 'insert', function( selection ) {
  793.                 var state = workflow.state();
  794.  
  795.                 selection = selection || state.get('selection');
  796.  
  797.                 if ( ! selection )
  798.                     return;
  799.  
  800.                 $.when.apply( $, selection.map( function( attachment ) {
  801.                     var display = state.display( attachment ).toJSON();
  802.                     /**
  803.                      * @this wp.media.editor
  804.                      */
  805.                     return this.send.attachment( display, attachment.toJSON() );
  806.                 }, this ) ).done( function() {
  807.                     wp.media.editor.insert( _.toArray( arguments ).join('\n\n') );
  808.                 });
  809.             }, this );
  810.  
  811.             workflow.state('gallery-edit').on( 'update', function( selection ) {
  812.                 /**
  813.                  * @this wp.media.editor
  814.                  */
  815.                 this.insert( wp.media.gallery.shortcode( selection ).string() );
  816.             }, this );
  817.  
  818.             workflow.state('playlist-edit').on( 'update', function( selection ) {
  819.                 /**
  820.                  * @this wp.media.editor
  821.                  */
  822.                 this.insert( wp.media.playlist.shortcode( selection ).string() );
  823.             }, this );
  824.  
  825.             workflow.state('video-playlist-edit').on( 'update', function( selection ) {
  826.                 /**
  827.                  * @this wp.media.editor
  828.                  */
  829.                 this.insert( wp.media.playlist.shortcode( selection ).string() );
  830.             }, this );
  831.  
  832.             workflow.state('embed').on( 'select', function() {
  833.                 /**
  834.                  * @this wp.media.editor
  835.                  */
  836.                 var state = workflow.state(),
  837.                     type = state.get('type'),
  838.                     embed = state.props.toJSON();
  839.  
  840.                 embed.url = embed.url || '';
  841.  
  842.                 if ( 'link' === type ) {
  843.                     _.defaults( embed, {
  844.                         linkText: embed.url,
  845.                         linkUrl: embed.url
  846.                     });
  847.  
  848.                     this.send.link( embed ).done( function( resp ) {
  849.                         wp.media.editor.insert( resp );
  850.                     });
  851.  
  852.                 } else if ( 'image' === type ) {
  853.                     _.defaults( embed, {
  854.                         title:   embed.url,
  855.                         linkUrl: '',
  856.                         align:   'none',
  857.                         link:    'none'
  858.                     });
  859.  
  860.                     if ( 'none' === embed.link ) {
  861.                         embed.linkUrl = '';
  862.                     } else if ( 'file' === embed.link ) {
  863.                         embed.linkUrl = embed.url;
  864.                     }
  865.  
  866.                     this.insert( wp.media.string.image( embed ) );
  867.                 }
  868.             }, this );
  869.  
  870.             workflow.state('featured-image').on( 'select', wp.media.featuredImage.select );
  871.             workflow.setState( workflow.options.state );
  872.             return workflow;
  873.         },
  874.         /**
  875.          * Determines the proper current workflow id
  876.          *
  877.          * @param {string} [id=''] A slug used to identify the workflow.
  878.          *
  879.          * @returns {wpActiveEditor|string|tinymce.activeEditor.id}
  880.          */
  881.         id: function( id ) {
  882.             if ( id ) {
  883.                 return id;
  884.             }
  885.  
  886.             // If an empty `id` is provided, default to `wpActiveEditor`.
  887.             id = window.wpActiveEditor;
  888.  
  889.             // If that doesn't work, fall back to `tinymce.activeEditor.id`.
  890.             if ( ! id && ! _.isUndefined( window.tinymce ) && tinymce.activeEditor ) {
  891.                 id = tinymce.activeEditor.id;
  892.             }
  893.  
  894.             // Last but not least, fall back to the empty string.
  895.             id = id || '';
  896.             return id;
  897.         },
  898.         /**
  899.          * Return the workflow specified by id
  900.          *
  901.          * @param {string} id A slug used to identify the workflow.
  902.          *
  903.          * @this wp.media.editor
  904.          *
  905.          * @returns {wp.media.view.MediaFrame} A media workflow.
  906.          */
  907.         get: function( id ) {
  908.             id = this.id( id );
  909.             return workflows[ id ];
  910.         },
  911.         /**
  912.          * Remove the workflow represented by id from the workflow cache
  913.          *
  914.          * @param {string} id A slug used to identify the workflow.
  915.          *
  916.          * @this wp.media.editor
  917.          */
  918.         remove: function( id ) {
  919.             id = this.id( id );
  920.             delete workflows[ id ];
  921.         },
  922.         /** @namespace wp.media.editor.send */
  923.         send: {
  924.             /**
  925.              * Called when sending an attachment to the editor
  926.              *   from the medial modal.
  927.              *
  928.              * @param {Object} props Attachment details (align, link, size, etc).
  929.              * @param {Object} attachment The attachment object, media version of Post.
  930.              * @returns {Promise}
  931.              */
  932.             attachment: function( props, attachment ) {
  933.                 var caption = attachment.caption,
  934.                     options, html;
  935.  
  936.                 // If captions are disabled, clear the caption.
  937.                 if ( ! wp.media.view.settings.captions ) {
  938.                     delete attachment.caption;
  939.                 }
  940.  
  941.                 props = wp.media.string.props( props, attachment );
  942.  
  943.                 options = {
  944.                     id:           attachment.id,
  945.                     post_content: attachment.description,
  946.                     post_excerpt: caption
  947.                 };
  948.  
  949.                 if ( props.linkUrl ) {
  950.                     options.url = props.linkUrl;
  951.                 }
  952.  
  953.                 if ( 'image' === attachment.type ) {
  954.                     html = wp.media.string.image( props );
  955.  
  956.                     _.each({
  957.                         align: 'align',
  958.                         size:  'image-size',
  959.                         alt:   'image_alt'
  960.                     }, function( option, prop ) {
  961.                         if ( props[ prop ] )
  962.                             options[ option ] = props[ prop ];
  963.                     });
  964.                 } else if ( 'video' === attachment.type ) {
  965.                     html = wp.media.string.video( props, attachment );
  966.                 } else if ( 'audio' === attachment.type ) {
  967.                     html = wp.media.string.audio( props, attachment );
  968.                 } else {
  969.                     html = wp.media.string.link( props );
  970.                     options.post_title = props.title;
  971.                 }
  972.  
  973.                 return wp.media.post( 'send-attachment-to-editor', {
  974.                     nonce:      wp.media.view.settings.nonce.sendToEditor,
  975.                     attachment: options,
  976.                     html:       html,
  977.                     post_id:    wp.media.view.settings.post.id
  978.                 });
  979.             },
  980.             /**
  981.              * Called when 'Insert From URL' source is not an image. Example: YouTube url.
  982.              *
  983.              * @param {Object} embed
  984.              * @returns {Promise}
  985.              */
  986.             link: function( embed ) {
  987.                 return wp.media.post( 'send-link-to-editor', {
  988.                     nonce:     wp.media.view.settings.nonce.sendToEditor,
  989.                     src:       embed.linkUrl,
  990.                     link_text: embed.linkText,
  991.                     html:      wp.media.string.link( embed ),
  992.                     post_id:   wp.media.view.settings.post.id
  993.                 });
  994.             }
  995.         },
  996.         /**
  997.          * Open a workflow
  998.          *
  999.          * @param {string} [id=undefined] Optional. A slug used to identify the workflow.
  1000.          * @param {Object} [options={}]
  1001.          *
  1002.          * @this wp.media.editor
  1003.          *
  1004.          * @returns {wp.media.view.MediaFrame}
  1005.          */
  1006.         open: function( id, options ) {
  1007.             var workflow;
  1008.  
  1009.             options = options || {};
  1010.  
  1011.             id = this.id( id );
  1012.             this.activeEditor = id;
  1013.  
  1014.             workflow = this.get( id );
  1015.  
  1016.             // Redo workflow if state has changed
  1017.             if ( ! workflow || ( workflow.options && options.state !== workflow.options.state ) ) {
  1018.                 workflow = this.add( id, options );
  1019.             }
  1020.  
  1021.             wp.media.frame = workflow;
  1022.  
  1023.             return workflow.open();
  1024.         },
  1025.  
  1026.         /**
  1027.          * Bind click event for .insert-media using event delegation
  1028.          */
  1029.         init: function() {
  1030.             $(document.body)
  1031.                 .on( 'click.add-media-button', '.insert-media', function( event ) {
  1032.                     var elem = $( event.currentTarget ),
  1033.                         editor = elem.data('editor'),
  1034.                         options = {
  1035.                             frame:    'post',
  1036.                             state:    'insert',
  1037.                             title:    wp.media.view.l10n.addMedia,
  1038.                             multiple: true
  1039.                         };
  1040.  
  1041.                     event.preventDefault();
  1042.  
  1043.                     if ( elem.hasClass( 'gallery' ) ) {
  1044.                         options.state = 'gallery';
  1045.                         options.title = wp.media.view.l10n.createGalleryTitle;
  1046.                     }
  1047.  
  1048.                     wp.media.editor.open( editor, options );
  1049.                 });
  1050.  
  1051.             // Initialize and render the Editor drag-and-drop uploader.
  1052.             new wp.media.view.EditorUploader().render();
  1053.         }
  1054.     };
  1055.  
  1056.     _.bindAll( wp.media.editor, 'open' );
  1057.     $( wp.media.editor.init );
  1058. }(jQuery, _));
  1059.