Media: Restore 3.4 editor behavior and remove TinyMCE views.

* Reactivates the `wpgallery` and `wpeditimage` TinyMCE plugins. Deactivates the `wpviews` TinyMCE plugin.
* Moves still-relevant logic from `mce-views.js` to `media-upload.js` and `shortcode.js`.
* No longer include `wp-includes/js/mce-views.js`. This code will not be used in 3.5, and should be considered unstable.
* Currently, this is the real 3.4 experience; as such, editing triggers the old modals. Changing this is the next major step.

When reassessing views, we should look over all of these tickets and anticipate these bugs accordingly.

fixes #21813, #22123, #22155, #22161, #22257, #22266, #22318, #22407, see #21390.


git-svn-id: http://core.svn.wordpress.org/trunk@22567 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Daryl Koopersmith 2012-11-14 07:17:22 +00:00
parent ad53112897
commit b2db6270ee
5 changed files with 273 additions and 462 deletions

View File

@ -90,10 +90,204 @@ var tb_position;
// WordPress, TinyMCE, and Media
// -----------------------------
(function($){
// Stores the editors' `wp.media.controller.Workflow` instances.
var workflows = {};
// Stores the editors' `wp.media.controller.Frame` instances.
var workflows = {},
linkToUrl;
wp.mce.media = {
linkToUrl = function( attachment, props ) {
var link = props.link,
url;
if ( 'file' === link )
url = attachment.get('url');
else if ( 'post' === link )
url = attachment.get('link');
else if ( 'custom' === link )
url = props.linkUrl;
return url || '';
};
wp.media.string = {};
wp.media.string.link = function( attachment, props ) {
var linkTo = getUserSetting( 'urlbutton', 'post' ),
options = {
tag: 'a',
content: attachment.get('title') || attachment.get('filename'),
attrs: {
rel: 'attachment wp-att-' + attachment.id
}
};
options.attrs.href = linkToUrl( attachment, props );
return wp.html.string( options );
};
wp.media.string.image = function( attachment, props ) {
var classes, img, options, size, shortcode, html;
props = _.defaults( props || {}, {
img: {},
align: getUserSetting( 'align', 'none' ),
size: getUserSetting( 'imgsize', 'medium' ),
link: getUserSetting( 'urlbutton', 'post' )
});
props.linkUrl = linkToUrl( attachment, props );
attachment = attachment.toJSON();
img = _.clone( props.img );
classes = img['class'] ? img['class'].split(/\s+/) : [];
size = attachment.sizes ? attachment.sizes[ props.size ] : {};
if ( ! size ) {
delete props.size;
size = attachment;
}
img.width = size.width;
img.height = size.height;
img.src = size.url;
// Only assign the align class to the image if we're not printing
// a caption, since the alignment is sent to the shortcode.
if ( props.align && ! attachment.caption )
classes.push( 'align' + props.align );
if ( props.size )
classes.push( 'size-' + props.size );
classes.push( 'wp-image-' + attachment.id );
img['class'] = _.compact( classes ).join(' ');
// Generate `img` tag options.
options = {
tag: 'img',
attrs: img,
single: true
};
// Generate the `href` based on the `link` property.
if ( props.linkUrl ) {
props.anchor = props.anchor || {};
props.anchor.href = props.linkUrl;
}
// Generate the `a` element options, if they exist.
if ( props.anchor ) {
options = {
tag: 'a',
attrs: props.anchor,
content: options
};
}
html = wp.html.string( options );
// Generate the caption shortcode.
if ( attachment.caption ) {
shortcode = {
id: 'attachment_' + attachment.id,
width: img.width
};
if ( props.align )
shortcode.align = 'align' + props.align;
html = wp.shortcode.string({
tag: 'caption',
attrs: shortcode,
content: html + ' ' + attachment.caption
});
}
return html;
};
wp.media.gallery = (function() {
var galleries = {};
return {
attachments: function( shortcode, parent ) {
var shortcodeString = shortcode.string(),
result = galleries[ shortcodeString ],
attrs, args, query, others;
delete galleries[ shortcodeString ];
if ( result )
return result;
attrs = shortcode.attrs.named;
args = _.pick( attrs, 'orderby', 'order' );
args.type = 'image';
args.perPage = -1;
// Map the `ids` param to the correct query args.
if ( attrs.ids ) {
args.post__in = attrs.ids.split(',');
args.orderby = 'post__in';
} else if ( attrs.include ) {
args.post__in = attrs.include.split(',');
}
if ( attrs.exclude )
args.post__not_in = attrs.exclude.split(',');
if ( ! args.post__in )
args.parent = attrs.id || parent;
// Collect the attributes that were not included in `args`.
others = {};
_.filter( attrs, function( value, key ) {
if ( _.isUndefined( args[ key ] ) )
others[ key ] = value;
});
query = media.query( args );
query.gallery = new Backbone.Model( others );
return query;
},
shortcode: function( attachments ) {
var props = attachments.props.toJSON(),
attrs = _.pick( props, 'include', 'exclude', 'orderby', 'order' ),
shortcode, clone;
if ( attachments.gallery )
_.extend( attrs, attachments.gallery.toJSON() );
attrs.ids = attachments.pluck('id');
// If the `ids` attribute is set and `orderby` attribute
// is the default value, clear it for cleaner output.
if ( attrs.ids && 'post__in' === attrs.orderby )
delete attrs.orderby;
shortcode = new wp.shortcode({
tag: 'gallery',
attrs: attrs,
type: 'single'
});
// Use a cloned version of the gallery.
clone = new wp.media.model.Attachments( attachments.models, {
props: props
});
clone.gallery = attachments.gallery;
galleries[ shortcode.string() ] = clone;
return shortcode;
}
};
}());
wp.media.editor = {
insert: send_to_editor,
add: function( id, options ) {
@ -134,14 +328,7 @@ var tb_position;
}, this );
workflow.get('gallery-edit').on( 'update', function( selection ) {
var view = wp.mce.view.get('gallery'),
shortcode;
if ( ! view )
return;
shortcode = view.gallery.shortcode( selection );
this.insert( shortcode.string() );
this.insert( wp.media.gallery.shortcode( selection ).string() );
}, this );
workflow.get('embed').on( 'select', function() {
@ -211,7 +398,7 @@ var tb_position;
if ( ! editor )
return;
workflow = wp.mce.media.get( editor );
workflow = wp.media.editor.get( editor );
// If the workflow exists, just open it.
if ( workflow ) {
@ -220,10 +407,10 @@ var tb_position;
}
// Initialize the editor's workflow if we haven't yet.
wp.mce.media.add( editor );
wp.media.editor.add( editor );
});
}
};
$( wp.mce.media.init );
$( wp.media.editor.init );
}(jQuery));

View File

@ -191,12 +191,12 @@ final class _WP_Editors {
self::$baseurl = includes_url('js/tinymce');
self::$mce_locale = $mce_locale = ( '' == get_locale() ) ? 'en' : strtolower( substr(get_locale(), 0, 2) ); // only ISO 639-1
$no_captions = (bool) apply_filters( 'disable_captions', '' );
$plugins = array( 'inlinepopups', 'spellchecker', 'tabfocus', 'paste', 'media', 'fullscreen', 'wordpress', 'wplink', 'wpdialogs', 'wpview' );
$plugins = array( 'inlinepopups', 'spellchecker', 'tabfocus', 'paste', 'media', 'fullscreen', 'wordpress', 'wpeditimage', 'wpgallery', 'wplink', 'wpdialogs' );
$first_run = true;
$ext_plugins = '';
if ( $set['teeny'] ) {
self::$plugins = $plugins = apply_filters( 'teeny_mce_plugins', array('inlinepopups', 'fullscreen', 'wordpress', 'wplink', 'wpdialogs', 'wpview'), $editor_id );
self::$plugins = $plugins = apply_filters( 'teeny_mce_plugins', array('inlinepopups', 'fullscreen', 'wordpress', 'wplink', 'wpdialogs' ), $editor_id );
} else {
/*
The following filter takes an associative array of external plugins for TinyMCE in the form 'plugin_name' => 'url'.

View File

@ -1,72 +1,6 @@
// Ensure the global `wp` object exists.
window.wp = window.wp || {};
// HTML utility functions
// ----------------------
(function(){
wp.html = _.extend( wp.html || {}, {
// ### Parse HTML attributes.
//
// Converts `content` to a set of parsed HTML attributes.
// Utilizes `wp.shortcode.attrs( content )`, which is a valid superset of
// the HTML attribute specification. Reformats the attributes into an
// object that contains the `attrs` with `key:value` mapping, and a record
// of the attributes that were entered using `empty` attribute syntax (i.e.
// with no value).
attrs: function( content ) {
var result, attrs;
// If `content` ends in a slash, strip it.
if ( '/' === content[ content.length - 1 ] )
content = content.slice( 0, -1 );
result = wp.shortcode.attrs( content );
attrs = result.named;
_.each( result.numeric, function( key ) {
if ( /\s/.test( key ) )
return;
attrs[ key ] = '';
});
return attrs;
},
// ### Convert an HTML-representation of an object to a string.
string: function( options ) {
var text = '<' + options.tag,
content = options.content || '';
_.each( options.attrs, function( value, attr ) {
text += ' ' + attr;
// Use empty attribute notation where possible.
if ( '' === value )
return;
// Convert boolean values to strings.
if ( _.isBoolean( value ) )
value = value ? 'true' : 'false';
text += '="' + value + '"';
});
// Return the result if it is a self-closing tag.
if ( options.single )
return text + ' />';
// Complete the opening tag.
text += '>';
// If `content` is an object, recursively call this function.
text += _.isObject( content ) ? wp.html.string( content ) : content;
return text + '</' + options.tag + '>';
}
});
}());
(function($){
var views = {},
instances = {};
@ -409,381 +343,7 @@ window.wp = window.wp || {};
$node.removeClass('selected');
$( node.firstChild ).trigger('deselect');
},
// Link any localized strings.
l10n: _.isUndefined( _wpMceViewL10n ) ? {} : _wpMceViewL10n
}
};
}(jQuery));
// Default TinyMCE Views
// ---------------------
(function($){
var mceview = wp.mce.view,
linkToUrl;
linkToUrl = function( attachment, props ) {
var link = props.link,
url;
if ( 'file' === link )
url = attachment.get('url');
else if ( 'post' === link )
url = attachment.get('link');
else if ( 'custom' === link )
url = props.linkUrl;
return url || '';
};
wp.media.string = {};
wp.media.string.link = function( attachment, props ) {
var linkTo = getUserSetting( 'urlbutton', 'post' ),
options = {
tag: 'a',
content: attachment.get('title') || attachment.get('filename'),
attrs: {
rel: 'attachment wp-att-' + attachment.id
}
};
options.attrs.href = linkToUrl( attachment, props );
return wp.html.string( options );
};
wp.media.string.image = function( attachment, props ) {
var classes, img, options, size, shortcode, html;
props = _.defaults( props || {}, {
img: {},
align: getUserSetting( 'align', 'none' ),
size: getUserSetting( 'imgsize', 'medium' ),
link: getUserSetting( 'urlbutton', 'post' )
});
props.linkUrl = linkToUrl( attachment, props );
attachment = attachment.toJSON();
img = _.clone( props.img );
classes = img['class'] ? img['class'].split(/\s+/) : [];
size = attachment.sizes ? attachment.sizes[ props.size ] : {};
if ( ! size ) {
delete props.size;
size = attachment;
}
img.width = size.width;
img.height = size.height;
img.src = size.url;
// Only assign the align class to the image if we're not printing
// a caption, since the alignment is sent to the shortcode.
if ( props.align && ! attachment.caption )
classes.push( 'align' + props.align );
if ( props.size )
classes.push( 'size-' + props.size );
classes.push( 'wp-image-' + attachment.id );
img['class'] = _.compact( classes ).join(' ');
// Generate `img` tag options.
options = {
tag: 'img',
attrs: img,
single: true
};
// Generate the `href` based on the `link` property.
if ( props.linkUrl ) {
props.anchor = props.anchor || {};
props.anchor.href = props.linkUrl;
}
// Generate the `a` element options, if they exist.
if ( props.anchor ) {
options = {
tag: 'a',
attrs: props.anchor,
content: options
};
}
html = wp.html.string( options );
// Generate the caption shortcode.
if ( attachment.caption ) {
shortcode = {
id: 'attachment_' + attachment.id,
width: img.width
};
if ( props.align )
shortcode.align = 'align' + props.align;
html = wp.shortcode.string({
tag: 'caption',
attrs: shortcode,
content: html + ' ' + attachment.caption
});
}
return html;
};
mceview.add( 'attachment', {
pattern: new RegExp( '(?:<a([^>]*)>)?<img([^>]*class=(?:"[^"]*|\'[^\']*)\\bwp-image-(\\d+)[^>]*)>(?:</a>)?' ),
text: function( instance ) {
var props = _.pick( instance, 'align', 'size', 'link', 'img', 'anchor' );
return wp.media.string.image( instance.model, props );
},
view: {
className: 'editor-attachment',
template: media.template('editor-attachment'),
events: {
'click .close': 'remove'
},
initialize: function() {
var view = this,
results = this.options.results,
id = results[3],
className;
this.model = wp.media.model.Attachment.get( id );
if ( results[1] )
this.anchor = mceview.attrs( results[1] );
this.img = mceview.attrs( results[2] );
className = this.img['class'];
// Strip ID class.
className = className.replace( /(?:^|\s)wp-image-\d+/, '' );
// Calculate thumbnail `size` and remove class.
className = className.replace( /(?:^|\s)size-(\S+)/, function( match, size ) {
view.size = size;
return '';
});
// Calculate `align` and remove class.
className = className.replace( /(?:^|\s)align(left|center|right|none)(?:\s|$)/, function( match, align ) {
view.align = align;
return '';
});
this.img['class'] = className;
this.$el.addClass('spinner');
this.model.fetch().done( _.bind( this.render, this ) );
},
render: function() {
var attachment = this.model.toJSON(),
options;
// If we don't have the attachment data, bail.
if ( ! attachment.url )
return;
// Align the wrapper.
if ( this.align )
this.$wrapper.addClass( 'align' + this.align );
// Generate the template options.
options = {
url: 'image' === attachment.type ? attachment.url : attachment.icon,
uploading: attachment.uploading
};
_.extend( options, wp.media.fit({
width: attachment.width,
height: attachment.height,
maxWidth: mceview.l10n.contentWidth
}) );
// Use the specified size if it exists.
if ( this.size && attachment.sizes && attachment.sizes[ this.size ] )
_.extend( options, _.pick( attachment.sizes[ this.size ], 'url', 'width', 'height' ) );
this.$el.html( this.template( options ) );
}
}
});
mceview.add( 'gallery', {
shortcode: 'gallery',
gallery: (function() {
var galleries = {};
return {
attachments: function( shortcode, parent ) {
var shortcodeString = shortcode.string(),
result = galleries[ shortcodeString ],
attrs, args, query, others;
delete galleries[ shortcodeString ];
if ( result )
return result;
attrs = shortcode.attrs.named;
args = _.pick( attrs, 'orderby', 'order' );
args.type = 'image';
args.perPage = -1;
// Map the `ids` param to the correct query args.
if ( attrs.ids ) {
args.post__in = attrs.ids.split(',');
args.orderby = 'post__in';
} else if ( attrs.include ) {
args.post__in = attrs.include.split(',');
}
if ( attrs.exclude )
args.post__not_in = attrs.exclude.split(',');
if ( ! args.post__in )
args.parent = attrs.id || parent;
// Collect the attributes that were not included in `args`.
others = {};
_.filter( attrs, function( value, key ) {
if ( _.isUndefined( args[ key ] ) )
others[ key ] = value;
});
query = media.query( args );
query.gallery = new Backbone.Model( others );
return query;
},
shortcode: function( attachments ) {
var props = attachments.props.toJSON(),
attrs = _.pick( props, 'include', 'exclude', 'orderby', 'order' ),
shortcode, clone;
if ( attachments.gallery )
_.extend( attrs, attachments.gallery.toJSON() );
attrs.ids = attachments.pluck('id');
// If the `ids` attribute is set and `orderby` attribute
// is the default value, clear it for cleaner output.
if ( attrs.ids && 'post__in' === attrs.orderby )
delete attrs.orderby;
shortcode = new wp.shortcode({
tag: 'gallery',
attrs: attrs,
type: 'single'
});
// Use a cloned version of the gallery.
clone = new wp.media.model.Attachments( attachments.models, {
props: props
});
clone.gallery = attachments.gallery;
galleries[ shortcode.string() ] = clone;
return shortcode;
}
};
}()),
view: {
className: 'editor-gallery',
template: media.template('editor-gallery'),
// The fallback post ID to use as a parent for galleries that don't
// specify the `ids` or `include` parameters.
//
// Uses the hidden input on the edit posts page by default.
parent: $('#post_ID').val(),
events: {
'click .close': 'remove',
'click .edit': 'edit'
},
initialize: function() {
this.update();
},
update: function() {
var view = mceview.get('gallery');
this.attachments = view.gallery.attachments( this.options.shortcode, this.parent );
this.attachments.more().done( _.bind( this.render, this ) );
},
render: function() {
var options, thumbnail, size;
if ( ! this.attachments.length )
return;
thumbnail = this.attachments.first().toJSON();
size = thumbnail.sizes && thumbnail.sizes.thumbnail ? thumbnail.sizes.thumbnail : thumbnail;
options = {
url: size.url,
orientation: size.orientation,
count: this.attachments.length
};
this.$el.html( this.template( options ) );
},
edit: function() {
var selection;
if ( ! wp.media.view || this.frame )
return;
selection = new wp.media.model.Selection( this.attachments.models, {
props: this.attachments.props.toJSON(),
multiple: true
});
selection.gallery = this.attachments.gallery;
this.frame = wp.media({
frame: 'post',
state: 'gallery-edit',
title: mceview.l10n.editGallery,
editing: true,
multiple: true,
selection: selection
});
// Create a single-use frame. If the frame is closed,
// then detach it from the DOM and remove the reference.
this.frame.on( 'close', function() {
if ( this.frame )
this.frame.detach();
delete this.frame;
}, this );
// Update the `shortcode` and `attachments`.
this.frame.get('gallery-edit').on( 'update', function( selection ) {
var view = mceview.get('gallery');
this.options.shortcode = view.gallery.shortcode( selection );
this.update();
}, this );
}
}
});
}(jQuery));

View File

@ -272,3 +272,71 @@ window.wp = window.wp || {};
}
});
}());
// HTML utility functions
// ----------------------
//
// Experimental. These functions may change or be removed in the future.
(function(){
wp.html = _.extend( wp.html || {}, {
// ### Parse HTML attributes.
//
// Converts `content` to a set of parsed HTML attributes.
// Utilizes `wp.shortcode.attrs( content )`, which is a valid superset of
// the HTML attribute specification. Reformats the attributes into an
// object that contains the `attrs` with `key:value` mapping, and a record
// of the attributes that were entered using `empty` attribute syntax (i.e.
// with no value).
attrs: function( content ) {
var result, attrs;
// If `content` ends in a slash, strip it.
if ( '/' === content[ content.length - 1 ] )
content = content.slice( 0, -1 );
result = wp.shortcode.attrs( content );
attrs = result.named;
_.each( result.numeric, function( key ) {
if ( /\s/.test( key ) )
return;
attrs[ key ] = '';
});
return attrs;
},
// ### Convert an HTML-representation of an object to a string.
string: function( options ) {
var text = '<' + options.tag,
content = options.content || '';
_.each( options.attrs, function( value, attr ) {
text += ' ' + attr;
// Use empty attribute notation where possible.
if ( '' === value )
return;
// Convert boolean values to strings.
if ( _.isBoolean( value ) )
value = value ? 'true' : 'false';
text += '="' + value + '"';
});
// Return the result if it is a self-closing tag.
if ( options.single )
return text + ' />';
// Complete the opening tag.
text += '>';
// If `content` is an object, recursively call this function.
text += _.isObject( content ) ? wp.html.string( content ) : content;
return text + '</' + options.tag + '>';
}
});
}());

View File

@ -298,7 +298,7 @@ function wp_default_scripts( &$scripts ) {
'type' => 'characters' == _x( 'words', 'word count: words or characters?' ) ? 'c' : 'w',
) );
$scripts->add( 'media-upload', "/wp-admin/js/media-upload$suffix.js", array( 'thickbox', 'mce-view', 'media-views' ), false, 1 );
$scripts->add( 'media-upload', "/wp-admin/js/media-upload$suffix.js", array( 'thickbox', 'shortcode', 'media-views' ), false, 1 );
$scripts->add( 'hoverIntent', "/wp-includes/js/hoverIntent$suffix.js", array('jquery'), 'r6', 1 );
@ -326,10 +326,6 @@ function wp_default_scripts( &$scripts ) {
$scripts->add( 'media-views', "/wp-includes/js/media-views$suffix.js", array( 'media-models', 'wp-plupload' ), false, 1 );
$scripts->add( 'shortcode', "/wp-includes/js/shortcode$suffix.js", array( 'underscore' ), false, 1 );
$scripts->add( 'mce-view', "/wp-includes/js/mce-view$suffix.js", array( 'shortcode', 'media-models' ), false, 1 );
did_action( 'init' ) && $scripts->localize( 'mce-view', '_wpMceViewL10n', array(
'contentWidth' => isset( $GLOBALS['content_width'] ) ? $GLOBALS['content_width'] : 800,
'editGallery' => __( 'Edit Gallery' ),
) );
if ( is_admin() ) {
$scripts->add( 'ajaxcat', "/wp-admin/js/cat$suffix.js", array( 'wp-lists' ) );