You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

1111 lines
29 KiB

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