From 223eb63b6f0f6c9f0abb96d6cb427f4310c76bdb Mon Sep 17 00:00:00 2001 From: Daryl Koopersmith Date: Wed, 14 Nov 2012 07:17:22 +0000 Subject: [PATCH] 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: https://develop.svn.wordpress.org/trunk@22567 602fd350-edb4-49c9-b593-d223f7449a82 --- wp-admin/js/media-upload.js | 215 +++++++++++++++- wp-includes/class-wp-editor.php | 4 +- wp-includes/js/mce-view.js | 442 +------------------------------- wp-includes/js/shortcode.js | 68 +++++ wp-includes/script-loader.php | 6 +- 5 files changed, 273 insertions(+), 462 deletions(-) diff --git a/wp-admin/js/media-upload.js b/wp-admin/js/media-upload.js index ed933cc699..c686088dbd 100644 --- a/wp-admin/js/media-upload.js +++ b/wp-admin/js/media-upload.js @@ -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)); \ No newline at end of file diff --git a/wp-includes/class-wp-editor.php b/wp-includes/class-wp-editor.php index f7c4cd118d..723e5fd8bc 100644 --- a/wp-includes/class-wp-editor.php +++ b/wp-includes/class-wp-editor.php @@ -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'. diff --git a/wp-includes/js/mce-view.js b/wp-includes/js/mce-view.js index e23bfd393d..912c4c7ce4 100644 --- a/wp-includes/js/mce-view.js +++ b/wp-includes/js/mce-view.js @@ -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 + ''; - } - }); -}()); - (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( '(?:]*)>)?]*class=(?:"[^"]*|\'[^\']*)\\bwp-image-(\\d+)[^>]*)>(?:)?' ), - - 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)); \ No newline at end of file diff --git a/wp-includes/js/shortcode.js b/wp-includes/js/shortcode.js index 9bc74ed289..0f83a445bc 100644 --- a/wp-includes/js/shortcode.js +++ b/wp-includes/js/shortcode.js @@ -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 + ''; + } + }); +}()); \ No newline at end of file diff --git a/wp-includes/script-loader.php b/wp-includes/script-loader.php index 0b09ea53ed..246cc0335e 100644 --- a/wp-includes/script-loader.php +++ b/wp-includes/script-loader.php @@ -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' ) );