Ticket #28458: 28458.diff
File 28458.diff, 18.0 KB (added by , 10 years ago) |
---|
-
src/wp-includes/js/mce-view.js
22 22 * 23 23 * A Backbone-like View constructor intended for use when rendering a TinyMCE View. The main difference is 24 24 * that the TinyMCE View is not tied to a particular DOM node. 25 * 26 * @param {Object} [options={}] 25 27 */ 26 28 wp.mce.View = function( options ) { 27 options || (options = {});28 _.extend( this, _.pick(options, viewOptions));29 this.initialize.apply( this, arguments);29 options = options || {}; 30 _.extend( this, _.pick( options, viewOptions ) ); 31 this.initialize.apply( this, arguments ); 30 32 }; 31 33 32 34 _.extend( wp.mce.View.prototype, { … … 36 38 var html = this.getHtml(); 37 39 // Search all tinymce editor instances and update the placeholders 38 40 _.each( tinymce.editors, function( editor ) { 39 var doc,self = this;41 var self = this; 40 42 if ( editor.plugins.wpview ) { 41 doc = editor.getDoc(); 42 $( doc ).find( '[data-wpview-text="' + this.encodedText + '"]' ).each(function (i, elem) { 43 var node = $( elem ); 43 $( editor.getDoc() ).find( '[data-wpview-text="' + this.encodedText + '"]' ).each( function ( i, element ) { 44 var node = $( element ); 44 45 // The <ins> is used to mark the end of the wrapper div. Needed when comparing 45 46 // the content as string for preventing extra undo levels. 46 47 node.html( html ).append( '<ins data-wpview-end="1"></ins>' ); 47 $( self ).trigger( 'ready', elem );48 $( self ).trigger( 'ready', element ); 48 49 }); 49 50 } 50 51 }, this ); … … 74 75 * 75 76 */ 76 77 register: function( type, constructor ) { 78 var defaultConstructor = { 79 type: type, 80 View: {}, 81 toView: function( content ) { 82 var match = wp.shortcode.next( this.type, content ); 83 84 if ( ! match ) { 85 return; 86 } 87 88 return { 89 index: match.index, 90 content: match.content, 91 options: { 92 shortcode: match.shortcode 93 } 94 }; 95 } 96 }; 97 98 constructor = _.defaults( constructor, defaultConstructor ); 99 constructor.View = wp.mce.View.extend( constructor.View ); 100 77 101 views[ type ] = constructor; 78 102 }, 79 103 … … 81 105 * wp.mce.views.get( id ) 82 106 * 83 107 * Returns a TinyMCE view constructor. 108 * 109 * @param type 84 110 */ 85 111 get: function( type ) { 86 112 return views[ type ]; … … 90 116 * wp.mce.views.unregister( type ) 91 117 * 92 118 * Unregisters a TinyMCE view. 119 * 120 * @param type 93 121 */ 94 122 unregister: function( type ) { 95 123 delete views[ type ]; … … 112 140 * matches with wrapper elements, and creates a new instance for 113 141 * every match, which triggers the related data to be fetched. 114 142 * 143 * @param content 115 144 */ 116 145 toViews: function( content ) { 117 146 var pieces = [ { content: content } ], … … 252 281 } 253 282 }; 254 283 255 wp.mce.gallery = { 256 shortcode: 'gallery', 257 toView: function( content ) { 258 var match = wp.shortcode.next( this.shortcode, content ); 284 wp.mce.views.register( 'gallery', { 285 View: { 286 template: media.template( 'editor-gallery' ), 259 287 260 if ( ! match ) {261 return;262 }263 264 return {265 index: match.index,266 content: match.content,267 options: {268 shortcode: match.shortcode269 }270 };271 },272 View: wp.mce.View.extend({273 className: 'editor-gallery',274 template: media.template('editor-gallery'),275 276 288 // The fallback post ID to use as a parent for galleries that don't 277 289 // specify the `ids` or `include` parameters. 278 290 // … … 321 333 return this.template( options ); 322 334 323 335 } 324 } ),336 }, 325 337 326 338 edit: function( node ) { 327 339 var gallery = wp.media.gallery, … … 338 350 frame.detach(); 339 351 }); 340 352 } 353 } ); 341 354 342 };343 wp.mce.views.register( 'gallery', wp.mce.gallery );344 345 355 /** 346 * T iny MCE Views for Audio / Video356 * These are base methods that are shared by the audio and video shortcode's MCE controller. 347 357 * 348 */349 350 /**351 * These are base methods that are shared by each shortcode's MCE controller352 *353 358 * @mixin 354 359 */ 355 wp.mce. media= {360 wp.mce.av = { 356 361 loaded: false, 357 toView: wp.mce.gallery.toView,358 362 363 View: _.extend( {}, wp.media.mixin, { 364 initialize: function( options ) { 365 this.players = []; 366 this.shortcode = options.shortcode; 367 _.bindAll( this, 'setPlayer', 'pausePlayers' ); 368 $( this ).on( 'ready', this.setPlayer ); 369 $( 'body' ).on( 'click', '.wp-switch-editor', this.pausePlayers ); 370 $( document ).on( 'media:edit', this.pausePlayers ); 371 }, 372 373 /** 374 * Creates the player instance for the current node 375 * 376 * @global MediaElementPlayer 377 * 378 * @param {Event} e 379 * @param {HTMLElement} node 380 */ 381 setPlayer: function(e, node) { 382 // if the ready event fires on an empty node 383 if ( ! node ) { 384 return; 385 } 386 387 var self = this, 388 media, 389 firefox = this.ua.is( 'ff' ), 390 className = '.wp-' + this.shortcode.tag + '-shortcode'; 391 392 media = $( node ).find( className ); 393 394 if ( ! this.isCompatible( media ) ) { 395 media.closest( '.wpview-wrap' ).addClass( 'wont-play' ); 396 if ( ! media.parent().hasClass( 'wpview-wrap' ) ) { 397 media.parent().replaceWith( media ); 398 } 399 media.replaceWith( '<p>' + media.find( 'source' ).eq(0).prop( 'src' ) + '</p>' ); 400 return; 401 } else { 402 media.closest( '.wpview-wrap' ).removeClass( 'wont-play' ); 403 if ( firefox ) { 404 media.prop( 'preload', 'metadata' ); 405 } else { 406 media.prop( 'preload', 'none' ); 407 } 408 } 409 410 media = wp.media.view.MediaDetails.prepareSrc( media.get(0) ); 411 412 setTimeout( function() { 413 wp.mce.av.loaded = true; 414 self.players.push( new MediaElementPlayer( media, self.mejsSettings ) ); 415 }, wp.mce.av.loaded ? 10 : 500 ); 416 }, 417 418 /** 419 * Pass data to the View's Underscore template and return the compiled output 420 * 421 * @returns {string} 422 */ 423 getHtml: function() { 424 var attrs = this.shortcode.attrs.named; 425 attrs.content = this.shortcode.content; 426 427 return this.template({ model: _.defaults( 428 attrs, 429 wp.media[ this.shortcode.tag ].defaults ) 430 }); 431 }, 432 433 unbind: function() { 434 this.unsetPlayers(); 435 } 436 } ), 437 359 438 /** 360 439 * Called when a TinyMCE view is clicked for editing. 361 440 * - Parses the shortcode out of the element's data attribute … … 397 476 }; 398 477 399 478 /** 400 * Base View class for audio and video shortcodes401 *402 * @constructor403 * @augments wp.mce.View404 * @mixes wp.media.mixin405 */406 wp.mce.media.View = wp.mce.View.extend({407 initialize: function( options ) {408 this.players = [];409 this.shortcode = options.shortcode;410 _.bindAll( this, 'setPlayer', 'pausePlayers' );411 $( this ).on( 'ready', this.setPlayer );412 $( 'body' ).on( 'click', '.wp-switch-editor', this.pausePlayers );413 $( document ).on( 'media:edit', this.pausePlayers );414 },415 416 /**417 * Creates the player instance for the current node418 *419 * @global MediaElementPlayer420 * @global _wpmejsSettings421 *422 * @param {Event} e423 * @param {HTMLElement} node424 */425 setPlayer: function(e, node) {426 // if the ready event fires on an empty node427 if ( ! node ) {428 return;429 }430 431 var self = this,432 media,433 firefox = this.ua.is( 'ff' ),434 className = '.wp-' + this.shortcode.tag + '-shortcode';435 436 media = $( node ).find( className );437 438 if ( ! this.isCompatible( media ) ) {439 media.closest( '.wpview-wrap' ).addClass( 'wont-play' );440 if ( ! media.parent().hasClass( 'wpview-wrap' ) ) {441 media.parent().replaceWith( media );442 }443 media.replaceWith( '<p>' + media.find( 'source' ).eq(0).prop( 'src' ) + '</p>' );444 return;445 } else {446 media.closest( '.wpview-wrap' ).removeClass( 'wont-play' );447 if ( firefox ) {448 media.prop( 'preload', 'metadata' );449 } else {450 media.prop( 'preload', 'none' );451 }452 }453 454 media = wp.media.view.MediaDetails.prepareSrc( media.get(0) );455 456 setTimeout( function() {457 wp.mce.media.loaded = true;458 self.players.push( new MediaElementPlayer( media, self.mejsSettings ) );459 }, wp.mce.media.loaded ? 10 : 500 );460 },461 462 /**463 * Pass data to the View's Underscore template and return the compiled output464 *465 * @returns {string}466 */467 getHtml: function() {468 var attrs = this.shortcode.attrs.named;469 attrs.content = this.shortcode.content;470 471 return this.template({ model: _.defaults(472 attrs,473 wp.media[ this.shortcode.tag ].defaults )474 });475 },476 477 unbind: function() {478 this.unsetPlayers();479 }480 });481 _.extend( wp.mce.media.View.prototype, wp.media.mixin );482 483 /**484 479 * TinyMCE handler for the video shortcode 485 480 * 486 * @mixes wp.mce. media481 * @mixes wp.mce.av 487 482 */ 488 wp.mce.vi deo = _.extend( {}, wp.mce.media, {483 wp.mce.views.register( 'video', _.extend( {}, wp.mce.av, { 489 484 shortcode: 'video', 490 485 state: 'video-details', 491 View: wp.mce.media.View.extend({ 492 className: 'editor-video', 493 template: media.template('editor-video') 494 }) 495 } ); 496 wp.mce.views.register( 'video', wp.mce.video ); 486 View: _.extend( {}, wp.mce.av.View, { 487 template: media.template( 'editor-video' ) 488 } ) 489 } ) ); 497 490 498 491 /** 499 492 * TinyMCE handler for the audio shortcode 500 493 * 501 * @mixes wp.mce. media494 * @mixes wp.mce.av 502 495 */ 503 wp.mce. audio = _.extend( {}, wp.mce.media, {496 wp.mce.views.register( 'audio', _.extend( {}, wp.mce.av, { 504 497 shortcode: 'audio', 505 498 state: 'audio-details', 506 View: wp.mce.media.View.extend({ 507 className: 'editor-audio', 508 template: media.template('editor-audio') 509 }) 510 } ); 511 wp.mce.views.register( 'audio', wp.mce.audio ); 499 View: _.extend( {}, wp.mce.av.View, { 500 template: media.template( 'editor-audio' ) 501 } ) 502 } ) ); 512 503 513 504 /** 514 * Base View class for playlist shortcodes505 * TinyMCE handler for the playlist shortcode 515 506 * 516 * @constructor 517 * @augments wp.mce.View 518 * @mixes wp.media.mixin 507 * @mixes wp.mce.av 519 508 */ 520 wp.mce.media.PlaylistView = wp.mce.View.extend({ 521 className: 'editor-playlist', 522 template: media.template('editor-playlist'), 509 wp.mce.views.register( 'playlist', _.extend( {}, wp.mce.av, { 510 shortcode: 'playlist', 511 state: ['playlist-edit', 'video-playlist-edit'], 512 View: _.extend( {}, wp.media.mixin, { 513 template: media.template( 'editor-playlist' ), 523 514 524 initialize: function( options ) {525 this.players = [];526 this.data = {};527 this.attachments = [];528 this.shortcode = options.shortcode;515 initialize: function( options ) { 516 this.players = []; 517 this.data = {}; 518 this.attachments = []; 519 this.shortcode = options.shortcode; 529 520 530 $( 'body' ).on( 'click', '.wp-switch-editor', this.pausePlayers );531 $( document ).on( 'media:edit', this.pausePlayers );521 $( 'body' ).on( 'click', '.wp-switch-editor', this.pausePlayers ); 522 $( document ).on( 'media:edit', this.pausePlayers ); 532 523 533 this.fetch(); 534 }, 524 this.fetch(); 535 525 536 /** 537 * Asynchronously fetch the shortcode's attachments 538 */ 539 fetch: function() { 540 this.attachments = wp.media.playlist.attachments( this.shortcode ); 541 this.dfd = this.attachments.more().done( _.bind( this.render, this ) ); 542 }, 526 $( this ).on( 'ready', this.setPlaylist ); 527 }, 543 528 544 /** 545 * Get the HTML for the view (which also set's the data), replace the 546 * current HTML, and then invoke the WPPlaylistView instance to render 547 * the playlist in the editor 548 * 549 * @global WPPlaylistView 550 * @global tinymce.editors 551 */ 552 render: function() { 553 var html = this.getHtml(), self = this; 529 /** 530 * Asynchronously fetch the shortcode's attachments 531 */ 532 fetch: function() { 533 this.attachments = wp.media.playlist.attachments( this.shortcode ); 534 this.dfd = this.attachments.more().done( _.bind( this.render, this ) ); 535 }, 554 536 555 _.each( tinymce.editors, function( editor ) { 556 var doc; 557 if ( editor.plugins.wpview ) { 558 doc = editor.getDoc(); 559 $( doc ).find( '[data-wpview-text="' + this.encodedText + '"]' ).each(function (i, elem) { 560 var node = $( elem ); 537 setPlaylist: function( event, element ) { 538 if ( ! this.data.tracks ) { 539 return; 540 } 561 541 562 // The <ins> is used to mark the end of the wrapper div. Needed when comparing 563 // the content as string for preventing extra undo levels. 564 node.html( html ).append( '<ins data-wpview-end="1"></ins>' ); 542 this.players.push( new WPPlaylistView( { 543 el: $( element ).find( '.wp-playlist' ).get( 0 ), 544 metadata: this.data 545 } ).player ); 546 }, 565 547 566 if ( ! self.data.tracks ) { 567 return; 568 } 548 /** 549 * Set the data that will be used to compile the Underscore template, 550 * compile the template, and then return it. 551 * 552 * @returns {string} 553 */ 554 getHtml: function() { 555 var data = this.shortcode.attrs.named, 556 model = wp.media.playlist, 557 options, 558 attachments, 559 tracks = []; 569 560 570 self.players.push( new WPPlaylistView({ 571 el: $( elem ).find( '.wp-playlist' ).get(0), 572 metadata: self.data 573 }).player ); 574 }); 561 // Don't render errors while still fetching attachments 562 if ( this.dfd && 'pending' === this.dfd.state() && ! this.attachments.length ) { 563 return; 575 564 } 576 }, this );577 },578 565 579 /** 580 * Set the data that will be used to compile the Underscore template, 581 * compile the template, and then return it. 582 * 583 * @returns {string} 584 */ 585 getHtml: function() { 586 var data = this.shortcode.attrs.named, 587 model = wp.media.playlist, 588 options, 589 attachments, 590 tracks = []; 566 _.each( model.defaults, function( value, key ) { 567 data[ key ] = model.coerce( data, key ); 568 }); 591 569 592 // Don't render errors while still fetching attachments 593 if ( this.dfd && 'pending' === this.dfd.state() && ! this.attachments.length ) { 594 return; 595 } 570 options = { 571 type: data.type, 572 style: data.style, 573 tracklist: data.tracklist, 574 tracknumbers: data.tracknumbers, 575 images: data.images, 576 artists: data.artists 577 }; 596 578 597 _.each( model.defaults, function( value, key) {598 data[ key ] = model.coerce( data, key);599 });579 if ( ! this.attachments.length ) { 580 return this.template( options ); 581 } 600 582 601 options = { 602 type: data.type, 603 style: data.style, 604 tracklist: data.tracklist, 605 tracknumbers: data.tracknumbers, 606 images: data.images, 607 artists: data.artists 608 }; 583 attachments = this.attachments.toJSON(); 609 584 610 if ( ! this.attachments.length ) { 611 return this.template( options ); 612 } 585 _.each( attachments, function( attachment ) { 586 var size = {}, resize = {}, track = { 587 src : attachment.url, 588 type : attachment.mime, 589 title : attachment.title, 590 caption : attachment.caption, 591 description : attachment.description, 592 meta : attachment.meta 593 }; 613 594 614 attachments = this.attachments.toJSON(); 615 616 _.each( attachments, function( attachment ) { 617 var size = {}, resize = {}, track = { 618 src : attachment.url, 619 type : attachment.mime, 620 title : attachment.title, 621 caption : attachment.caption, 622 description : attachment.description, 623 meta : attachment.meta 624 }; 625 626 if ( 'video' === data.type ) { 627 size.width = attachment.width; 628 size.height = attachment.height; 629 if ( media.view.settings.contentWidth ) { 630 resize.width = media.view.settings.contentWidth - 22; 631 resize.height = Math.ceil( ( size.height * resize.width ) / size.width ); 632 if ( ! options.width ) { 633 options.width = resize.width; 634 options.height = resize.height; 595 if ( 'video' === data.type ) { 596 size.width = attachment.width; 597 size.height = attachment.height; 598 if ( media.view.settings.contentWidth ) { 599 resize.width = media.view.settings.contentWidth - 22; 600 resize.height = Math.ceil( ( size.height * resize.width ) / size.width ); 601 if ( ! options.width ) { 602 options.width = resize.width; 603 options.height = resize.height; 604 } 605 } else { 606 if ( ! options.width ) { 607 options.width = attachment.width; 608 options.height = attachment.height; 609 } 635 610 } 611 track.dimensions = { 612 original : size, 613 resized : _.isEmpty( resize ) ? size : resize 614 }; 636 615 } else { 637 if ( ! options.width ) { 638 options.width = attachment.width; 639 options.height = attachment.height; 640 } 616 options.width = 400; 641 617 } 642 track.dimensions = {643 original : size,644 resized : _.isEmpty( resize ) ? size : resize645 };646 } else {647 options.width = 400;648 }649 618 650 track.image = attachment.image;651 track.thumb = attachment.thumb;619 track.image = attachment.image; 620 track.thumb = attachment.thumb; 652 621 653 tracks.push( track );654 } );622 tracks.push( track ); 623 } ); 655 624 656 options.tracks = tracks;657 this.data = options;625 options.tracks = tracks; 626 this.data = options; 658 627 659 return this.template( options );660 },628 return this.template( options ); 629 }, 661 630 662 unbind: function() {663 this.unsetPlayers();664 }665 });666 _.extend( wp.mce.media.PlaylistView.prototype, wp.media.mixin);631 unbind: function() { 632 this.unsetPlayers(); 633 } 634 } ) 635 } ) ); 667 636 668 637 /** 669 * TinyMCE handler for the playlist shortcode670 *671 * @mixes wp.mce.media672 */673 wp.mce.playlist = _.extend( {}, wp.mce.media, {674 shortcode: 'playlist',675 state: ['playlist-edit', 'video-playlist-edit'],676 View: wp.mce.media.PlaylistView677 } );678 wp.mce.views.register( 'playlist', wp.mce.playlist );679 680 /**681 638 * TinyMCE handler for the embed shortcode 682 639 */ 683 wp.mce. embed ={640 wp.mce.views.register( 'embed', { 684 641 shortcode: 'embed', 685 toView: wp.mce.gallery.toView, 686 View: wp.mce.View.extend( { 687 className: 'editor-embed', 642 View: _.extend( {}, wp.media.mixin, { 688 643 template: media.template( 'editor-embed' ), 689 644 initialize: function( options ) { 690 645 this.players = []; … … 737 692 } 738 693 } ), 739 694 edit: function() {} 740 } ;695 } ); 741 696 742 _.extend( wp.mce.embed.View.prototype, wp.media.mixin );743 744 wp.mce.views.register( 'embed', wp.mce.embed );745 746 697 }(jQuery));