media-editor.js 28 KB

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