Ticket #26959: 26959-05.patch
File 26959-05.patch, 35.1 KB (added by , 11 years ago) |
---|
-
src/wp-includes/class-wp-editor.php
diff --git src/wp-includes/class-wp-editor.php src/wp-includes/class-wp-editor.php index 004ec91..6d4c6dc 100644
final class _WP_Editors { 235 235 'fullscreen', 236 236 'wordpress', 237 237 'wpeditimage', 238 'wpgallery',239 238 'wplink', 240 239 'wpdialogs', 240 'wpview' 241 241 ) ) ); 242 242 243 243 if ( ( $key = array_search( 'spellchecker', $plugins ) ) !== false ) { … … final class _WP_Editors { 497 497 if ( self::$has_medialib ) { 498 498 add_thickbox(); 499 499 wp_enqueue_script('media-upload'); 500 501 if ( self::$has_tinymce ) 502 wp_enqueue_script('mce-view'); 500 503 } 504 501 505 } 502 506 503 507 public static function wp_mce_translation() { -
src/wp-includes/js/mce-view.js
diff --git src/wp-includes/js/mce-view.js src/wp-includes/js/mce-view.js index 912c4c7..9448714 100644
1 /* global tinymce */ 2 1 3 // Ensure the global `wp` object exists. 2 4 window.wp = window.wp || {}; 3 5 4 6 (function($){ 5 7 var views = {}, 6 instances = {}; 8 instances = {}, 9 media = wp.media, 10 viewOptions = ['encodedText']; 7 11 8 12 // Create the `wp.mce` object if necessary. 9 13 wp.mce = wp.mce || {}; 10 14 11 // wp.mce.view 12 // ----------- 13 // A set of utilities that simplifies adding custom UI within a TinyMCE editor. 14 // At its core, it serves as a series of converters, transforming text to a 15 // custom UI, and back again. 16 wp.mce.view = { 17 // ### defaults 18 defaults: { 19 // The default properties used for objects with the `pattern` key in 20 // `wp.mce.view.add()`. 21 pattern: { 22 view: Backbone.View, 23 text: function( instance ) { 24 return instance.options.original; 25 }, 26 27 toView: function( content ) { 28 if ( ! this.pattern ) 29 return; 30 31 this.pattern.lastIndex = 0; 32 var match = this.pattern.exec( content ); 33 34 if ( ! match ) 35 return; 36 37 return { 38 index: match.index, 39 content: match[0], 40 options: { 41 original: match[0], 42 results: match 43 } 44 }; 45 } 46 }, 47 48 // The default properties used for objects with the `shortcode` key in 49 // `wp.mce.view.add()`. 50 shortcode: { 51 view: Backbone.View, 52 text: function( instance ) { 53 return instance.options.shortcode.string(); 54 }, 55 56 toView: function( content ) { 57 var match = wp.shortcode.next( this.shortcode, content ); 58 59 if ( ! match ) 60 return; 61 62 return { 63 index: match.index, 64 content: match.content, 65 options: { 66 shortcode: match.shortcode 67 } 68 }; 69 } 70 } 71 }, 72 73 // ### add( id, options ) 74 // Registers a new TinyMCE view. 75 // 76 // Accepts a unique `id` and an `options` object. 77 // 78 // `options` accepts the following properties: 79 // 80 // * `pattern` is the regular expression used to scan the content and 81 // detect matching views. 82 // 83 // * `view` is a `Backbone.View` constructor. If a plain object is 84 // provided, it will automatically extend the parent constructor 85 // (usually `Backbone.View`). Views are instantiated when the `pattern` 86 // is successfully matched. The instance's `options` object is provided 87 // with the `original` matched value, the match `results` including 88 // capture groups, and the `viewType`, which is the constructor's `id`. 89 // 90 // * `extend` an existing view by passing in its `id`. The current 91 // view will inherit all properties from the parent view, and if 92 // `view` is set to a plain object, it will extend the parent `view` 93 // constructor. 94 // 95 // * `text` is a method that accepts an instance of the `view` 96 // constructor and transforms it into a text representation. 97 add: function( id, options ) { 98 var parent, remove, base, properties; 99 100 // Fetch the parent view or the default options. 101 if ( options.extend ) 102 parent = wp.mce.view.get( options.extend ); 103 else if ( options.shortcode ) 104 parent = wp.mce.view.defaults.shortcode; 105 else 106 parent = wp.mce.view.defaults.pattern; 107 108 // Extend the `options` object with the parent's properties. 109 _.defaults( options, parent ); 110 options.id = id; 111 112 // Create properties used to enhance the view for use in TinyMCE. 113 properties = { 114 // Ensure the wrapper element and references to the view are 115 // removed. Otherwise, removed views could randomly restore. 116 remove: function() { 117 delete instances[ this.el.id ]; 118 this.$el.parent().remove(); 119 120 // Trigger the inherited `remove` method. 121 if ( remove ) 122 remove.apply( this, arguments ); 123 124 return this; 125 } 126 }; 127 128 // If the `view` provided was an object, use the parent's 129 // `view` constructor as a base. If a `view` constructor 130 // was provided, treat that as the base. 131 if ( _.isFunction( options.view ) ) { 132 base = options.view; 133 } else { 134 base = parent.view; 135 remove = options.view.remove; 136 _.defaults( properties, options.view ); 137 } 138 139 // If there's a `remove` method on the `base` view that wasn't 140 // created by this method, inherit it. 141 if ( ! remove && ! base._mceview ) 142 remove = base.prototype.remove; 143 144 // Automatically create the new `Backbone.View` constructor. 145 options.view = base.extend( properties, { 146 // Flag that the new view has been created by `wp.mce.view`. 147 _mceview: true 148 }); 15 /** 16 * wp.mce.View 17 * 18 * A Backbone-like View constructor intended for use when rendering a TinyMCE View. The main difference is 19 * that the TinyMCE View is intended to be short-lived. Once the view is rendered, the View is destroyed rather than 20 * being attached to the DOM and listening for either DOM event or Backbone events. 21 */ 22 wp.mce.View = function( options ) { 23 options || (options = {}); 24 _.extend(this, _.pick(options, viewOptions)); 25 this.initialize.apply(this, arguments); 26 }; 149 27 150 views[ id ] = options; 28 _.extend( wp.mce.View.prototype, { 29 initialize: function() {}, 30 render: function() {} 31 } ); 32 33 // take advantage of the Backbone extend method 34 wp.mce.View.extend = Backbone.View.extend; 35 36 /** 37 * wp.mce.views 38 * 39 * A set of utilities that simplifies adding custom UI within a TinyMCE editor. 40 * At its core, it serves as a series of converters, transforming text to a 41 * custom UI, and back again. 42 */ 43 wp.mce.views = { 44 45 /** 46 * wp.mce.views.register( type, view ) 47 * 48 * Registers a new TinyMCE view. 49 * 50 * @param type 51 * @param constructor 52 * 53 */ 54 register: function( type, constructor ) { 55 views[ type ] = constructor; 151 56 }, 152 57 153 // ### get( id ) 154 // Returns a TinyMCE view options object. 155 get: function( id ) { 156 return views[ id ]; 58 /** 59 * wp.mce.views.get( id ) 60 * 61 * Returns a TinyMCE view constructor. 62 */ 63 get: function( type ) { 64 return views[ type ]; 157 65 }, 158 66 159 // ### remove( id ) 160 // Unregisters a TinyMCE view. 161 remove: function( id ) { 162 delete views[ id ]; 67 /** 68 * wp.mce.views.unregister( type ) 69 * 70 * Unregisters a TinyMCE view. 71 */ 72 unregister: function( type ) { 73 delete views[ type ]; 163 74 }, 164 75 165 // ### toViews( content ) 166 // Scans a `content` string for each view's pattern, replacing any 167 // matches with wrapper elements, and creates a new view instance for 168 // every match. 169 // 170 // To render the views, call `wp.mce.view.render( scope )`. 76 /** 77 * toViews( content ) 78 * Scans a `content` string for each view's pattern, replacing any 79 * matches with wrapper elements, and creates a new instance for 80 * every match, which triggers the related data to be fetched. 81 * 82 */ 171 83 toViews: function( content ) { 172 84 var pieces = [ { content: content } ], 173 85 current; … … window.wp = window.wp || {}; 190 102 // and slicing the string as we go. 191 103 while ( remaining && (result = view.toView( remaining )) ) { 192 104 // Any text before the match becomes an unprocessed piece. 193 if ( result.index ) 105 if ( result.index ) { 194 106 pieces.push({ content: remaining.substring( 0, result.index ) }); 107 } 195 108 196 109 // Add the processed piece for the match. 197 110 pieces.push({ 198 content: wp.mce.view.toView( viewType, result.options ),111 content: wp.mce.views.toView( viewType, result.content, result.options ), 199 112 processed: true 200 113 }); 201 114 … … window.wp = window.wp || {}; 205 118 206 119 // There are no additional matches. If any content remains, 207 120 // add it as an unprocessed piece. 208 if ( remaining ) 121 if ( remaining ) { 209 122 pieces.push({ content: remaining }); 123 } 210 124 }); 211 125 }); 212 126 213 127 return _.pluck( pieces, 'content' ).join(''); 214 128 }, 215 129 216 toView: function( viewType, options ) { 217 var view = wp.mce.view.get( viewType ), 218 instance, id; 219 220 if ( ! view ) 221 return ''; 222 223 // Create a new view instance. 224 instance = new view.view( _.extend( options || {}, { 225 viewType: viewType 226 }) ); 227 228 // Use the view's `id` if it already exists. Otherwise, 229 // create a new `id`. 230 id = instance.el.id = instance.el.id || _.uniqueId('__wpmce-'); 231 instances[ id ] = instance; 130 /** 131 * Create a placeholder for a particular view type 132 * 133 * @param viewType 134 * @param text 135 * @param options 136 * 137 */ 138 toView: function( viewType, text, options ) { 139 var view = wp.mce.views.get( viewType ), 140 encodedText = window.encodeURIComponent( text ), 141 instance, viewOptions; 142 143 144 if ( ! view ) { 145 return text; 146 } 232 147 233 // Create a dummy `$wrapper` property to allow `$wrapper` to be 234 // called in the view's `render` method without a conditional. 235 instance.$wrapper = $(); 148 if ( ! wp.mce.views.getInstance( encodedText ) ) { 149 viewOptions = options; 150 viewOptions.encodedText = encodedText; 151 instance = new view.View( viewOptions ); 152 instances[ encodedText ] = instance; 153 } 236 154 237 155 return wp.html.string({ 238 // If the view is a span, wrap it in a span. 239 tag: 'span' === instance.tagName ? 'span' : 'div', 156 tag: 'div', 240 157 241 158 attrs: { 242 'class': 'wp-view-wrap wp-view-type-' + viewType, 243 'data-wp-view': id, 244 'contenteditable': false 245 } 246 }); 247 }, 248 249 // ### render( scope ) 250 // Renders any view instances inside a DOM node `scope`. 251 // 252 // View instances are detected by the presence of wrapper elements. 253 // To generate wrapper elements, pass your content through 254 // `wp.mce.view.toViews( content )`. 255 render: function( scope ) { 256 $( '.wp-view-wrap', scope ).each( function() { 257 var wrapper = $(this), 258 view = wp.mce.view.instance( this ); 259 260 if ( ! view ) 261 return; 159 'class': 'wpview-wrap wpview-type-' + viewType, 160 'data-wpview-text': encodedText, 161 'data-wpview-type': viewType, 162 'contenteditable': 'false' 163 }, 262 164 263 // Link the real wrapper to the view. 264 view.$wrapper = wrapper; 265 // Render the view. 266 view.render(); 267 // Detach the view element to ensure events are not unbound. 268 view.$el.detach(); 269 270 // Empty the wrapper, attach the view element to the wrapper, 271 // and add an ending marker to the wrapper to help regexes 272 // scan the HTML string. 273 wrapper.empty().append( view.el ).append('<span data-wp-view-end class="wp-view-end"></span>'); 165 content: '\u00a0' 274 166 }); 275 167 }, 276 168 277 // ### toText( content ) 278 // Scans an HTML `content` string and replaces any view instances with 279 // their respective text representations. 280 toText: function( content ) { 281 return content.replace( /<(?:div|span)[^>]+data-wp-view="([^"]+)"[^>]*>.*?<span[^>]+data-wp-view-end[^>]*><\/span><\/(?:div|span)>/g, function( match, id ) { 282 var instance = instances[ id ], 283 view; 284 285 if ( instance ) 286 view = wp.mce.view.get( instance.options.viewType ); 287 288 return instance && view ? view.text( instance ) : ''; 289 }); 169 /** 170 * Refresh view after an update is made 171 * 172 * @param view {object} being refreshed 173 * @param text {string} textual representation of the view 174 */ 175 refreshView: function( view, text ) { 176 var encodedText = window.encodeURIComponent( text ), 177 viewOptions, 178 result, instance; 179 180 if ( ! wp.mce.views.getInstance( encodedText ) ) { 181 result = view.toView( text ); 182 viewOptions = result.options; 183 viewOptions.encodedText = encodedText; 184 instance = new view.View( viewOptions ); 185 instances[ encodedText ] = instance; 186 } 290 187 }, 291 188 292 // ### Remove internal TinyMCE attributes. 293 removeInternalAttrs: function( attrs ) { 294 var result = {}; 295 _.each( attrs, function( value, attr ) { 296 if ( -1 === attr.indexOf('data-mce') ) 297 result[ attr ] = value; 298 }); 299 return result; 189 getInstance: function( encodedText ) { 190 return instances[ encodedText ]; 300 191 }, 301 192 302 // ### Parse an attribute string and removes internal TinyMCE attributes. 303 attrs: function( content ) { 304 return wp.mce.view.removeInternalAttrs( wp.html.attrs( content ) ); 193 /** 194 * render( scope ) 195 * 196 * Renders any view instances inside a DOM node `scope`. 197 * 198 * View instances are detected by the presence of wrapper elements. 199 * To generate wrapper elements, pass your content through 200 * `wp.mce.view.toViews( content )`. 201 */ 202 render: function() { 203 _.each( instances, function( instance ) { 204 instance.render(); 205 } ); 305 206 }, 306 207 307 // ### instance( scope ) 308 // 309 // Accepts a MCE view wrapper `node` (i.e. a node with the 310 // `wp-view-wrap` class). 311 instance: function( node ) { 312 var id = $( node ).data('wp-view'); 208 edit: function( node ) { 209 var viewType = $( node ).data('wpview-type'), 210 view = wp.mce.views.get( viewType ); 313 211 314 if ( id ) 315 return instances[ id ]; 316 }, 212 if ( view ) { 213 view.edit( node ); 214 } 215 } 216 }; 317 217 318 // ### Select a view. 319 // 320 // Accepts a MCE view wrapper `node` (i.e. a node with the 321 // `wp-view-wrap` class). 322 select: function( node ) { 323 var $node = $(node); 218 wp.mce.gallery = { 219 shortcode: 'gallery', 220 toView: function( content ) { 221 var match = wp.shortcode.next( this.shortcode, content ); 324 222 325 // Bail if node is already selected. 326 if ( $node.hasClass('selected') ) 223 if ( ! match ) { 327 224 return; 225 } 328 226 329 $node.addClass('selected'); 330 $( node.firstChild ).trigger('select'); 227 return { 228 index: match.index, 229 content: match.content, 230 options: { 231 shortcode: match.shortcode 232 } 233 }; 331 234 }, 235 View: wp.mce.View.extend({ 236 className: 'editor-gallery', 237 template: media.template('editor-gallery'), 238 239 // The fallback post ID to use as a parent for galleries that don't 240 // specify the `ids` or `include` parameters. 241 // 242 // Uses the hidden input on the edit posts page by default. 243 postID: $('#post_ID').val(), 244 245 initialize: function( options ) { 246 this.shortcode = options.shortcode; 247 this.fetch(); 248 }, 332 249 333 // ### Deselect a view. 334 // 335 // Accepts a MCE view wrapper `node` (i.e. a node with the 336 // `wp-view-wrap` class). 337 deselect: function( node ) { 338 var $node = $(node); 250 fetch: function() { 251 this.attachments = wp.media.gallery.attachments( this.shortcode, this.postID ); 252 this.attachments.more().done( _.bind( this.render, this ) ); 253 }, 339 254 340 // Bail if node is already selected. 341 if ( ! $node.hasClass('selected') ) 342 return; 255 render: function() { 256 var attrs = this.shortcode.attrs.named, 257 options, 258 html; 259 260 if ( ! this.attachments.length ) { 261 return; 262 } 263 264 options = { 265 attachments: this.attachments.toJSON(), 266 columns: attrs.columns ? parseInt( attrs.columns, 10 ) : 3 267 }; 268 269 html = this.template( options ); 343 270 344 $node.removeClass('selected'); 345 $( node.firstChild ).trigger('deselect'); 271 // Search all tinymce editor instances and update the placeholders 272 _.each( tinymce.editors, function( editor ) { 273 var doc; 274 if ( editor.plugins.wpview ) { 275 doc = editor.getDoc(); 276 $( doc ).find( '[data-wpview-text="' + this.encodedText + '"]' ).html( html ); 277 } 278 }, this ); 279 } 280 }), 281 282 edit: function( node ) { 283 var gallery = wp.media.gallery, 284 self = this, 285 frame, data; 286 287 data = window.decodeURIComponent( $( node ).data('wpview-text') ); 288 frame = gallery.edit( data ); 289 290 frame.state('gallery-edit').on( 'update', function( selection ) { 291 var shortcode = gallery.shortcode( selection ).string(); 292 $( node ).attr( 'data-wpview-text', window.encodeURIComponent( shortcode ) ); 293 wp.mce.views.refreshView( self, shortcode ); 294 frame.detach(); 295 }); 346 296 } 347 };348 297 349 }(jQuery)); 350 No newline at end of file 298 }; 299 wp.mce.views.register( 'gallery', wp.mce.gallery ); 300 }(jQuery)); -
src/wp-includes/js/tinymce/plugins/wpview/plugin.js
diff --git src/wp-includes/js/tinymce/plugins/wpview/plugin.js src/wp-includes/js/tinymce/plugins/wpview/plugin.js index 0c56ecb..8f4293a 100644
2 2 /** 3 3 * WordPress View plugin. 4 4 */ 5 6 (function() { 7 var VK = tinymce.VK,5 tinymce.PluginManager.add( 'wpview', function( editor ) { 6 var selected, 7 VK = tinymce.util.VK, 8 8 TreeWalker = tinymce.dom.TreeWalker, 9 selected; 10 11 tinymce.create('tinymce.plugins.wpView', { 12 init : function( editor ) { 13 var wpView = this; 9 toRemove = false; 14 10 15 // Check if the `wp.mce` API exists. 16 if ( typeof wp === 'undefined' || ! wp.mce ) { 17 return; 11 function getParentView( node ) { 12 while ( node && node.nodeName !== 'BODY' ) { 13 if ( isView( node ) ) { 14 return node; 18 15 } 19 16 20 editor.on( 'PreInit', function() { 21 // Add elements so we can set `contenteditable` to false. 22 editor.schema.addValidElements('div[*],span[*]'); 23 }); 24 25 // When the editor's content changes, scan the new content for 26 // matching view patterns, and transform the matches into 27 // view wrappers. Since the editor's DOM is outdated at this point, 28 // we'll wait to render the views. 29 editor.on( 'BeforeSetContent', function( e ) { 30 if ( ! e.content ) { 31 return; 32 } 17 node = node.parentNode; 18 } 19 } 20 21 function isView( node ) { 22 return node && /\bwpview-wrap\b/.test( node.className ); 23 } 24 25 function createPadNode() { 26 return editor.dom.create( 'p', { 'data-wpview-pad': 1 }, 27 ( tinymce.Env.ie && tinymce.Env.ie < 11 ) ? '' : '<br data-mce-bogus="1" />' ); 28 } 29 30 // 31 // @arg view can be either the view wrapper's HTML id or node 32 /** 33 * Get the text/shortcode string for a view. 34 * 35 * @param view The view wrapper's HTML id or node 36 * @returns string The text/shoercode string of the view 37 */ 38 function getViewText( view ) { 39 view = getParentView( typeof view === 'string' ? editor.dom.get( view ) : view ); 40 41 if ( view ) { 42 return window.decodeURIComponent( editor.dom.getAttrib( view, 'data-wpview-text' ) || '' ); 43 } 44 return ''; 45 } 46 47 /** 48 * Set the view's original text/shortcode string 49 * 50 * @param view The view wrapper's HTML id or node 51 * @param text The text string to be set 52 */ 53 function setViewText( view, text ) { 54 view = getParentView( typeof view === 'string' ? editor.dom.get( view ) : view ); 55 56 if ( view ) { 57 editor.dom.setAttrib( view, 'data-wpview-text', window.encodeURIComponent( text || '' ) ); 58 return true; 59 } 60 return false; 61 } 33 62 34 e.content = wp.mce.view.toViews( e.content ); 35 }); 36 37 // When the editor's content has been updated and the DOM has been 38 // processed, render the views in the document. 39 editor.on( 'SetContent', function() { 40 wp.mce.view.render( editor.getDoc() ); 41 }); 42 43 editor.on( 'init', function() { 44 var selection = editor.selection; 45 // When a view is selected, ensure content that is being pasted 46 // or inserted is added to a text node (instead of the view). 47 editor.on( 'BeforeSetContent', function() { 48 var walker, target, 49 view = wpView.getParentView( selection.getNode() ); 50 51 // If the selection is not within a view, bail. 52 if ( ! view ) { 53 return; 54 } 63 function _stop( event ) { 64 event.stopPropagation(); 65 } 55 66 56 // If there are no additional nodes or the next node is a 57 // view, create a text node after the current view. 58 if ( ! view.nextSibling || wpView.isView( view.nextSibling ) ) { 59 target = editor.getDoc().createTextNode(''); 60 editor.dom.insertAfter( target, view ); 67 function select( viewNode ) { 68 var clipboard, 69 dom = editor.dom; 61 70 62 // Otherwise, find the next text node. 63 } else { 64 walker = new TreeWalker( view.nextSibling, view.nextSibling ); 65 target = walker.next(); 66 } 71 // Bail if node is already selected. 72 if ( viewNode === selected ) { 73 return; 74 } 67 75 68 // Select the `target` text node. 69 selection.select( target ); 70 selection.collapse( true ); 71 }); 72 73 // When the selection's content changes, scan any new content 74 // for matching views and immediately render them. 75 // 76 // Runs on paste and on inserting nodes/html. 77 editor.on( 'SetContent', function( e ) { 78 if ( ! e.context ) { 79 return; 80 } 76 deselect(); 77 selected = viewNode; 78 dom.addClass( viewNode, 'selected' ); 81 79 82 var node = selection.getNode(); 80 clipboard = dom.create( 'div', { 81 'class': 'wpview-clipboard', 82 'contenteditable': 'true' 83 }, getViewText( viewNode ) ); 83 84 84 if ( ! node.innerHTML ) { 85 return; 86 } 85 viewNode.appendChild( clipboard ); 87 86 88 node.innerHTML = wp.mce.view.toViews( node.innerHTML ); 89 wp.mce.view.render( node ); 90 }); 91 }); 87 // Both of the following are necessary to prevent manipulating the selection/focus 88 editor.dom.bind( clipboard, 'beforedeactivate focusin focusout', _stop ); 89 editor.dom.bind( selected, 'beforedeactivate focusin focusout', _stop ); 92 90 93 // When the editor's contents are being accessed as a string, 94 // transform any views back to their text representations. 95 editor.on( 'PostProcess', function( e ) { 96 if ( ( ! e.get && ! e.save ) || ! e.content ) { 97 return; 98 } 91 // select the hidden div 92 editor.selection.select( clipboard, true ); 93 } 99 94 100 e.content = wp.mce.view.toText( e.content ); 101 }); 95 // ### Deselect a view. 96 function deselect() { 97 var clipboard, 98 dom = editor.dom; 102 99 103 // Triggers when the selection is changed.104 // Add the event handler to the top of the stack.105 editor.on( 'NodeChange', function( e ) {106 var view = wpView.getParentView( e.element);100 if ( selected ) { 101 clipboard = editor.dom.select( '.wpview-clipboard', selected )[0]; 102 dom.unbind( clipboard ); 103 dom.remove( clipboard ); 107 104 108 // Update the selected view. 109 if ( view ) { 110 wpView.select( view ); 105 dom.unbind( selected, 'beforedeactivate focusin focusout click mouseup', _stop ); 106 dom.removeClass( selected, 'selected' ); 111 107 112 // Prevent the selection from propagating to other plugins.113 return false;108 editor.selection.select( selected.nextSibling ); 109 editor.selection.collapse(); 114 110 115 // If we've clicked off of the selected view, deselect it. 116 } else { 117 wpView.deselect(); 118 } 119 }); 111 } 120 112 121 editor.on( 'keydown', function( event ) { 122 var keyCode = event.keyCode, 123 view, instance; 113 selected = null; 114 } 124 115 125 // If a view isn't selected, let the event go on its merry way.126 if ( ! selected) {127 128 116 // Check if the `wp.mce` API exists. 117 if ( typeof wp === 'undefined' || ! wp.mce ) { 118 return; 119 } 129 120 130 // If the caret is not within the selected view, deselect the 131 // view and bail. 132 view = wpView.getParentView( editor.selection.getNode() ); 133 if ( view !== selected ) { 134 wpView.deselect(); 135 return; 136 } 121 editor.on( 'BeforeAddUndo', function( event ) { 122 if ( selected && ! toRemove ) { 123 event.preventDefault(); 124 } 125 }); 137 126 138 // If delete or backspace is pressed, delete the view.139 if ( keyCode === VK.DELETE || keyCode === VK.BACKSPACE ) {140 if ( (instance = wp.mce.view.instance( selected )) ) {141 instance.remove();142 wpView.deselect();143 }144 127 // When the editor's content changes, scan the new content for 128 // matching view patterns, and transform the matches into 129 // view wrappers. 130 editor.on( 'BeforeSetContent', function( e ) { 131 if ( ! e.content ) { 132 return; 133 } 145 134 146 // Let keypresses that involve the command or control keys through. 147 // Also, let any of the F# keys through. 148 if ( event.metaKey || event.ctrlKey || ( keyCode >= 112 && keyCode <= 123 ) ) { 149 return; 150 } 135 e.content = wp.mce.views.toViews( e.content ); 136 }); 151 137 152 event.preventDefault(); 153 }); 154 }, 138 // When the editor's content has been updated and the DOM has been 139 // processed, render the views in the document. 140 editor.on( 'SetContent', function( event ) { 141 var body, padNode; 155 142 156 getParentView : function( node ) { 157 while ( node ) { 158 if ( this.isView( node ) ) { 159 return node; 160 } 143 wp.mce.views.render(); 144 145 // Add padding <p> if the noneditable node is last 146 if ( event.load || ! event.set ) { 147 body = editor.getBody(); 148 149 if ( isView( body.lastChild ) ) { 150 padNode = createPadNode(); 151 body.appendChild( padNode ); 152 editor.selection.setCursorLocation( padNode, 0 ); 153 } 154 } 161 155 162 node = node.parentNode; 156 // refreshEmptyContentNode(); 157 }); 158 159 // Detect mouse down events that are adjacent to a view when a view is the first view or the last view 160 editor.on( 'click', function( event ) { 161 var body = editor.getBody(), 162 doc = editor.getDoc(), 163 scrollTop = doc.documentElement.scrollTop || body.scrollTop || 0, 164 x, y, firstNode, lastNode, padNode; 165 166 if ( event.target.nodeName === 'HTML' && ! event.metaKey && ! event.ctrlKey ) { 167 firstNode = body.firstChild; 168 lastNode = body.lastChild; 169 x = event.clientX; 170 y = event.clientY; 171 172 if ( isView( firstNode ) && ( ( x < firstNode.offsetLeft && y < ( firstNode.offsetHeight - scrollTop ) ) || 173 y < firstNode.offsetTop ) ) { 174 // detect events above or to the left of the first view 175 176 padNode = createPadNode(); 177 body.insertBefore( padNode, firstNode ); 178 } else if ( isView( lastNode ) && ( x > ( lastNode.offsetLeft + lastNode.offsetWidth ) || 179 ( ( scrollTop + y ) - ( lastNode.offsetTop + lastNode.offsetHeight ) ) > 0 ) ) { 180 // detect events to the right and below the last view 181 182 padNode = createPadNode(); 183 body.appendChild( padNode ); 163 184 } 164 },165 185 166 isView : function( node ) { 167 return (/(?:^|\s)wp-view-wrap(?:\s|$)/).test( node.className ); 168 }, 186 if ( padNode ) { 187 editor.selection.setCursorLocation( padNode, 0 ); 188 } 189 } 190 }); 169 191 170 select : function( view ) { 171 if ( view === selected ) { 192 editor.on( 'init', function() { 193 var selection = editor.selection; 194 // When a view is selected, ensure content that is being pasted 195 // or inserted is added to a text node (instead of the view). 196 editor.on( 'BeforeSetContent', function() { 197 var walker, target, 198 view = getParentView( selection.getNode() ); 199 200 // If the selection is not within a view, bail. 201 if ( ! view ) { 172 202 return; 173 203 } 174 204 175 this.deselect(); 176 selected = view; 177 wp.mce.view.select( selected ); 178 }, 205 if ( ! view.nextSibling || isView( view.nextSibling ) ) { 206 // If there are no additional nodes or the next node is a 207 // view, create a text node after the current view. 208 target = editor.getDoc().createTextNode(''); 209 editor.dom.insertAfter( target, view ); 210 } else { 211 // Otherwise, find the next text node. 212 walker = new TreeWalker( view.nextSibling, view.nextSibling ); 213 target = walker.next(); 214 } 179 215 180 deselect : function() { 181 if ( selected ) { 182 wp.mce.view.deselect( selected ); 216 // Select the `target` text node. 217 selection.select( target ); 218 selection.collapse( true ); 219 }); 220 221 // When the selection's content changes, scan any new content 222 // for matching views. 223 // 224 // Runs on paste and on inserting nodes/html. 225 editor.on( 'SetContent', function( e ) { 226 if ( ! e.context ) { 227 return; 228 } 229 230 var node = selection.getNode(); 231 232 if ( ! node.innerHTML ) { 233 return; 234 } 235 236 node.innerHTML = wp.mce.views.toViews( node.innerHTML ); 237 }); 238 239 editor.dom.bind( editor.getBody(), 'mousedown mouseup click', function( event ) { 240 var view = getParentView( event.target ); 241 242 // Contain clicks inside the view wrapper 243 if ( view ) { 244 event.stopPropagation(); 245 246 if ( event.type === 'click' ) { 247 if ( ! event.metaKey && ! event.ctrlKey ) { 248 if ( editor.dom.hasClass( event.target, 'edit' ) ) { 249 wp.mce.views.edit( view ); 250 } else if ( editor.dom.hasClass( event.target, 'remove' ) ) { 251 editor.dom.remove( view ); 252 } else { 253 select( view ); 254 } 255 } 256 } 257 } else { 258 if ( event.type === 'click' ) { 259 deselect(); 260 } 183 261 } 262 }); 263 264 }); 265 266 editor.on( 'PreProcess', function( event ) { 267 var dom = editor.dom; 268 269 // Remove empty padding nodes 270 tinymce.each( dom.select( 'p[data-wpview-pad]', event.node ), function( node ) { 271 if ( dom.isEmpty( node ) ) { 272 dom.remove( node ); 273 } else { 274 dom.setAttrib( node, 'data-wpview-pad', null ); 275 } 276 }); 277 278 // Replace the wpview node with the wpview string/shortcode? 279 tinymce.each( dom.select( 'div[data-wpview-text]', event.node ), function( node ) { 280 // Empty the wrap node 281 /* while ( node.firstChild ) { 282 node.removeChild( node.firstChild ); 283 }*/ 284 285 if ( 'textContent' in node ) { 286 node.textContent = ''; 287 } else { 288 node.innerText = ''; 289 } 290 291 // TODO: that makes all views into block tags (as we use <div>). 292 // Can use 'PostProcess' and toText() instead. 293 dom.replace( dom.create( 'p', null, window.decodeURIComponent( dom.getAttrib( node, 'data-wpview-text' ) ) ), node ); 294 }); 295 }); 296 297 editor.on( 'keydown', function( event ) { 298 var keyCode = event.keyCode, 299 view; 184 300 185 selected = null; 301 // If a view isn't selected, let the event go on its merry way. 302 if ( ! selected ) { 303 return; 304 } 305 306 // Let keypresses that involve the command or control keys through. 307 // Also, let any of the F# keys through. 308 if ( event.metaKey || event.ctrlKey || ( keyCode >= 112 && keyCode <= 123 ) ) { 309 if ( ( event.metaKey || event.ctrlKey ) && keyCode === 88 ) { 310 toRemove = selected; 311 } 312 return; 313 } 314 315 // If the caret is not within the selected view, deselect the 316 // view and bail. 317 view = getParentView( editor.selection.getNode() ); 318 319 if ( view !== selected ) { 320 deselect(); 321 return; 322 } 323 324 // If delete or backspace is pressed, delete the view. 325 if ( keyCode === VK.DELETE || keyCode === VK.BACKSPACE ) { 326 editor.dom.remove( selected ); 327 } 328 329 event.preventDefault(); 330 }); 331 332 editor.on( 'keyup', function( event ) { 333 var padNode, 334 keyCode = event.keyCode, 335 body = editor.getBody(); 336 337 if ( toRemove ) { 338 editor.dom.remove( toRemove ); 339 toRemove = false; 340 } 341 342 // Make sure there is padding if the last element is a view 343 if ( ( keyCode === VK.DELETE || keyCode === VK.BACKSPACE ) && isView( body.lastChild ) ) { 344 padNode = createPadNode(); 345 body.appendChild( padNode ); 346 347 if ( body.childNodes.length === 2 ) { 348 editor.selection.setCursorLocation( padNode, 0 ); 349 } 186 350 } 187 351 }); 188 352 189 // Register plugin 190 tinymce.PluginManager.add( 'wpview', tinymce.plugins.wpView ); 191 })(); 353 return { 354 getViewText: getViewText, 355 setViewText: setViewText 356 }; 357 }); -
src/wp-includes/js/tinymce/skins/wordpress/wp-content.css
diff --git src/wp-includes/js/tinymce/skins/wordpress/wp-content.css src/wp-includes/js/tinymce/skins/wordpress/wp-content.css index 0026e14..2f0309b 100644
img::selection { 195 195 outline: 0; 196 196 } 197 197 198 199 /** 200 * WP Views 201 */ 202 203 /* IE hasLayout. Needed for all IE incl. 11 (ugh, not again!!) */ 204 .wpview-wrap { 205 width: 99.99%; 206 position: relative; 207 } 208 209 /* delegate the handling of the selection to the wpview tinymce plugin */ 210 .wpview-wrap, 211 .wpview-wrap * { 212 -moz-user-select: none; 213 -webkit-user-select: none; 214 -ms-user-select: none; 215 user-select: none; 216 } 217 218 /* hide the shortcode content, but allow the content to still be selected */ 219 .wpview-wrap .wpview-clipboard { 220 position: absolute; 221 top: 0; 222 left: 0; 223 z-index: -1; 224 clip: rect(1px, 1px, 1px, 1px); 225 overflow: hidden; 226 outline: 0; 227 } 228 229 /** 230 * Gallery preview 231 */ 232 .wpview-type-gallery { 233 position: relative; 234 padding: 0 0 12px; 235 margin-bottom: 16px; 236 cursor: pointer; 237 } 238 239 .wpview-type-gallery:after { 240 content: ''; 241 display: block; 242 height: 0; 243 clear: both; 244 visibility: hidden; 245 } 246 247 .wpview-type-gallery.selected { 248 background-color: #efefef; 249 } 250 251 .wpview-type-gallery .toolbar { 252 position: absolute; 253 top: 0; 254 left: 0; 255 background-color: #333; 256 color: white; 257 padding: 4px; 258 display: none; 259 } 260 261 .wpview-type-gallery.selected .toolbar { 262 display: block; 263 } 264 265 .wpview-type-gallery .toolbar span { 266 cursor: pointer; 267 } 268 269 .gallery img[data-mce-selected]:focus { 270 outline: none; 271 } 272 273 .gallery a { 274 cursor: default; 275 } 276 277 .gallery { 278 margin: auto; 279 line-height: 1; 280 } 281 282 .gallery .gallery-item { 283 float: left; 284 margin: 10px 0 0 0; 285 text-align: center; 286 } 287 288 .gallery .gallery-caption, 289 .gallery .gallery-icon { 290 margin: 0; 291 } 292 293 .gallery-columns-1 .gallery-item { 294 width: 99%; 295 } 296 297 .gallery-columns-2 .gallery-item { 298 width: 49.5%; 299 } 300 301 .gallery-columns-3 .gallery-item { 302 width: 33%; 303 } 304 305 .gallery-columns-4 .gallery-item { 306 width: 24.75%; 307 } 308 309 .gallery-columns-5 .gallery-item { 310 width: 19.825%; 311 } 312 313 .gallery-columns-6 .gallery-item { 314 width: 16%; 315 } 316 317 .gallery-columns-7 .gallery-item { 318 width: 14%; 319 } 320 321 .gallery-columns-8 .gallery-item { 322 width: 12%; 323 } 324 325 .gallery-columns-9 .gallery-item { 326 width: 11%; 327 } 328 329 .gallery img { 330 border: 1px solid #cfcfcf; 331 } 332 198 333 img.wp-oembed { 199 334 border: 1px dashed #888; 200 335 background: #f7f5f2 url(images/embedded.png) no-repeat scroll center center; -
src/wp-includes/media-template.php
diff --git src/wp-includes/media-template.php src/wp-includes/media-template.php index 91216ed..a6ee6f7 100644
function wp_print_media_templates() { 644 644 </script> 645 645 <?php 646 646 647 //TODO: do we want to deal with the fact that the elements used for gallery items are filterable and can be overriden via shortcode attributes 648 // do we want to deal with the difference between display and edit context at all? (e.g. wptexturize() being applied to the caption. 649 ?> 650 651 <script type="text/html" id="tmpl-editor-gallery"> 652 <div class="toolbar"> 653 <div class="dashicons dashicons-format-gallery edit"></div> 654 <div class="dashicons dashicons-no-alt remove"></div> 655 </div> 656 <div class="gallery gallery-columns-{{{ data.columns }}}"> 657 <# _.each( data.attachments, function( attachment, index ) { #> 658 <dl class="gallery-item"> 659 <dt class="gallery-icon"> 660 <?php // TODO: need to figure out the best way to make sure that we have thumbnails ?> 661 <img src="{{{ attachment.sizes.thumbnail.url }}}" /> 662 </dt> 663 <dd class="wp-caption-text gallery-caption"> 664 {{ attachment.caption }} 665 </dd> 666 </dl> 667 <?php // this is kind silly, but copied from the gallery shortcode. Maybe it should be removed ?> 668 <# if ( index % data.columns === data.columns - 1 ) { #> 669 <br style="clear: both;"> 670 <# } #> 671 672 <# } ); #> 673 </div> 674 </script> 675 <?php 676 647 677 /** 648 678 * Prints the media manager custom media templates. 649 679 *