Ticket #26959: 26959-04.patch
File 26959-04.patch, 30.5 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 a829945..16a4bc5 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/css/editor.css
diff --git src/wp-includes/css/editor.css src/wp-includes/css/editor.css index aa21bc6..b9abb77 100644
i.mce-i-wp_page:before { 1132 1132 color: #2ea2cc; 1133 1133 } 1134 1134 */ 1135 1136 1137 1135 1138 /* Distraction Free Writing mode 1136 1139 * =Overlay Styles 1137 1140 -------------------------------------------------------------- */ -
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..0b804ef 100644
window.wp = window.wp || {}; 3 3 4 4 (function($){ 5 5 var views = {}, 6 instances = {}; 6 instances = {}, 7 media = wp.media; 7 8 8 9 // Create the `wp.mce` object if necessary. 9 10 wp.mce = wp.mce || {}; … … window.wp = window.wp || {}; 19 20 // The default properties used for objects with the `pattern` key in 20 21 // `wp.mce.view.add()`. 21 22 pattern: { 22 view: Backbone.View,23 view: wp.Backbone.View, 23 24 text: function( instance ) { 24 25 return instance.options.original; 25 26 }, 26 27 27 28 toView: function( content ) { 28 if ( ! this.pattern ) 29 if ( ! this.pattern ) { 29 30 return; 31 } 30 32 31 33 this.pattern.lastIndex = 0; 32 34 var match = this.pattern.exec( content ); 33 35 34 if ( ! match ) 36 if ( ! match ) { 35 37 return; 38 } 36 39 37 40 return { 38 41 index: match.index, … … window.wp = window.wp || {}; 48 51 // The default properties used for objects with the `shortcode` key in 49 52 // `wp.mce.view.add()`. 50 53 shortcode: { 51 view: Backbone.View,54 view: wp.Backbone.View, 52 55 text: function( instance ) { 53 56 return instance.options.shortcode.string(); 54 57 }, … … window.wp = window.wp || {}; 56 59 toView: function( content ) { 57 60 var match = wp.shortcode.next( this.shortcode, content ); 58 61 59 if ( ! match ) 62 if ( ! match ) { 60 63 return; 64 } 61 65 62 66 return { 63 67 index: match.index, … … window.wp = window.wp || {}; 98 102 var parent, remove, base, properties; 99 103 100 104 // Fetch the parent view or the default options. 101 if ( options.extend ) 105 if ( options.extend ) { 102 106 parent = wp.mce.view.get( options.extend ); 103 else if ( options.shortcode )107 } else if ( options.shortcode ) { 104 108 parent = wp.mce.view.defaults.shortcode; 105 else109 } else { 106 110 parent = wp.mce.view.defaults.pattern; 111 } 107 112 108 113 // Extend the `options` object with the parent's properties. 109 114 _.defaults( options, parent ); … … window.wp = window.wp || {}; 118 123 this.$el.parent().remove(); 119 124 120 125 // Trigger the inherited `remove` method. 121 if ( remove ) 126 if ( remove ) { 122 127 remove.apply( this, arguments ); 128 } 123 129 124 130 return this; 125 131 } … … window.wp = window.wp || {}; 138 144 139 145 // If there's a `remove` method on the `base` view that wasn't 140 146 // created by this method, inherit it. 141 if ( ! remove && ! base._mceview ) 147 if ( ! remove && ! base._mceview ) { 142 148 remove = base.prototype.remove; 149 } 143 150 144 151 // Automatically create the new `Backbone.View` constructor. 145 152 options.view = base.extend( properties, { … … window.wp = window.wp || {}; 168 175 // every match. 169 176 // 170 177 // To render the views, call `wp.mce.view.render( scope )`. 178 // TODO: needs unit tests! 171 179 toViews: function( content ) { 172 180 var pieces = [ { content: content } ], 173 181 current; … … window.wp = window.wp || {}; 190 198 // and slicing the string as we go. 191 199 while ( remaining && (result = view.toView( remaining )) ) { 192 200 // Any text before the match becomes an unprocessed piece. 193 if ( result.index ) 201 if ( result.index ) { 194 202 pieces.push({ content: remaining.substring( 0, result.index ) }); 203 } 195 204 196 205 // Add the processed piece for the match. 197 206 pieces.push({ … … window.wp = window.wp || {}; 205 214 206 215 // There are no additional matches. If any content remains, 207 216 // add it as an unprocessed piece. 208 if ( remaining ) 217 if ( remaining ) { 209 218 pieces.push({ content: remaining }); 219 } 210 220 }); 211 221 }); 212 222 … … window.wp = window.wp || {}; 217 227 var view = wp.mce.view.get( viewType ), 218 228 instance, id; 219 229 220 if ( ! view ) 230 if ( ! view ) { 221 231 return ''; 222 232 } 223 233 // Create a new view instance. 224 234 instance = new view.view( _.extend( options || {}, { 225 235 viewType: viewType … … window.wp = window.wp || {}; 239 249 tag: 'span' === instance.tagName ? 'span' : 'div', 240 250 241 251 attrs: { 242 'class': 252 'class': 'wp-view-wrap wp-view-type-' + viewType, 243 253 'data-wp-view': id, 244 254 'contenteditable': false 245 } 255 }, 256 257 content: '\u00a0' 246 258 }); 247 259 }, 248 260 … … window.wp = window.wp || {}; 257 269 var wrapper = $(this), 258 270 view = wp.mce.view.instance( this ); 259 271 260 if ( ! view ) 272 if ( ! view ) { 261 273 return; 274 } 262 275 263 276 // Link the real wrapper to the view. 264 277 view.$wrapper = wrapper; … … window.wp = window.wp || {}; 268 281 view.$el.detach(); 269 282 270 283 // Empty the wrapper, attach the view element to the wrapper, 284 // add a hidden element with the shortcode, 271 285 // and add an ending marker to the wrapper to help regexes 272 286 // scan the HTML string. 273 wrapper.empty().append( view.el ).append('<span data-wp-view-end class="wp-view-end"></span>'); 287 wrapper.empty().append( view.el ) 288 .append('<span data-wp-view-end class="wp-view-end"></span>'); 274 289 }); 275 290 }, 276 291 … … window.wp = window.wp || {}; 278 293 // Scans an HTML `content` string and replaces any view instances with 279 294 // their respective text representations. 280 295 toText: function( content ) { 281 return content.replace( /<(?:div|span)[^>]+data-wp-view="([^"]+)"[^>]*>.*?<span[^>]+data-wp-view-end[^>]*><\/span><\/(?:div|span)>/g, function( match, id ) { 296 297 return content.replace( /<(?:div|span)[^>]+data-wp-view="([^"]+)"[^>]*>.*?<span[^>]+data-wp-view-end[^>]*><\/span><\/(?:div|span)>/mg, function( match, id ) { 282 298 var instance = instances[ id ], 283 299 view; 284 300 285 if ( instance ) 301 if ( instance ) { 286 302 view = wp.mce.view.get( instance.options.viewType ); 287 303 } 288 304 return instance && view ? view.text( instance ) : ''; 289 305 }); 290 306 }, … … window.wp = window.wp || {}; 293 309 removeInternalAttrs: function( attrs ) { 294 310 var result = {}; 295 311 _.each( attrs, function( value, attr ) { 296 if ( -1 === attr.indexOf('data-mce') ) 312 if ( -1 === attr.indexOf('data-mce') ) { 297 313 result[ attr ] = value; 314 } 298 315 }); 299 316 return result; 300 317 }, … … window.wp = window.wp || {}; 311 328 instance: function( node ) { 312 329 var id = $( node ).data('wp-view'); 313 330 314 if ( id ) 331 if ( id ) { 315 332 return instances[ id ]; 333 } 316 334 }, 317 335 318 336 // ### Select a view. … … window.wp = window.wp || {}; 320 338 // Accepts a MCE view wrapper `node` (i.e. a node with the 321 339 // `wp-view-wrap` class). 322 340 select: function( node ) { 323 var $node = $(node); 341 var $node = $(node), 342 $shortcode, 343 view; 324 344 325 345 // Bail if node is already selected. 326 if ( $node.hasClass('selected') ) 346 if ( $node.hasClass('selected') ) { 327 347 return; 348 } 328 349 329 350 $node.addClass('selected'); 351 352 view = wp.mce.view.instance( node ); 353 354 $shortcode = $( '<div />' ) 355 .addClass( 'wp-view-shortcode' ) 356 .prop( 'contenteditable', 'true' ) 357 .data( 'mce-bogus', '1' ) 358 .text( view.options.shortcode.string() ); 359 360 $node.prepend( $shortcode ); 361 330 362 $( node.firstChild ).trigger('select'); 331 363 }, 332 364 … … window.wp = window.wp || {}; 338 370 var $node = $(node); 339 371 340 372 // Bail if node is already selected. 341 if ( ! $node.hasClass('selected') ) 373 if ( ! $node.hasClass('selected') ) { 342 374 return; 375 } 343 376 344 377 $node.removeClass('selected'); 378 379 $node.find( '.wp-view-shortcode' ).remove(); 345 380 $( node.firstChild ).trigger('deselect'); 346 381 } 347 382 }; 348 383 349 }(jQuery)); 350 No newline at end of file 384 wp.mce.view.add( 'gallery', { 385 shortcode: 'gallery', 386 387 gallery: (function() { 388 var galleries = {}; 389 390 return { 391 attachments: function( shortcode, parent ) { 392 var shortcodeString = shortcode.string(), 393 result = galleries[ shortcodeString ], 394 attrs, args, query, others; 395 396 delete galleries[ shortcodeString ]; 397 398 if ( result ) { 399 return result; 400 } 401 402 attrs = shortcode.attrs.named; 403 args = _.pick( attrs, 'orderby', 'order' ); 404 405 args.type = 'image'; 406 args.perPage = -1; 407 408 // Map the `ids` param to the correct query args. 409 if ( attrs.ids ) { 410 args.post__in = attrs.ids.split(','); 411 args.orderby = 'post__in'; 412 } else if ( attrs.include ) { 413 args.post__in = attrs.include.split(','); 414 } 415 416 if ( attrs.exclude ) { 417 args.post__not_in = attrs.exclude.split(','); 418 } 419 420 if ( ! args.post__in ) { 421 args.parent = attrs.id || parent; 422 } 423 424 // Collect the attributes that were not included in `args`. 425 others = {}; 426 _.filter( attrs, function( value, key ) { 427 if ( _.isUndefined( args[ key ] ) ) { 428 others[ key ] = value; 429 } 430 }); 431 432 query = media.query( args ); 433 query.gallery = new Backbone.Model( others ); 434 return query; 435 }, 436 437 shortcode: function( attachments ) { 438 var props = attachments.props.toJSON(), 439 attrs = _.pick( props, 'include', 'exclude', 'orderby', 'order' ), 440 shortcode, clone; 441 442 if ( attachments.gallery ) { 443 _.extend( attrs, attachments.gallery.toJSON() ); 444 } 445 446 attrs.ids = attachments.pluck('id'); 447 448 // If the `ids` attribute is set and `orderby` attribute 449 // is the default value, clear it for cleaner output. 450 if ( attrs.ids && 'post__in' === attrs.orderby ) { 451 delete attrs.orderby; 452 } 453 454 shortcode = new wp.shortcode({ 455 tag: 'gallery', 456 attrs: attrs, 457 type: 'single' 458 }); 459 460 // Use a cloned version of the gallery. 461 clone = new wp.media.model.Attachments( attachments.models, { 462 props: props 463 }); 464 clone.gallery = attachments.gallery; 465 galleries[ shortcode.string() ] = clone; 466 467 return shortcode; 468 } 469 }; 470 }()), 471 472 view: { 473 className: 'editor-gallery', 474 template: media.template('editor-gallery'), 475 476 // The fallback post ID to use as a parent for galleries that don't 477 // specify the `ids` or `include` parameters. 478 // 479 // Uses the hidden input on the edit posts page by default. 480 parent: $('#post_ID').val(), 481 482 events: { 483 'click .remove': 'remove', 484 'click .edit': 'edit' 485 }, 486 487 initialize: function() { 488 this.update(); 489 }, 490 491 update: function() { 492 var view = wp.mce.view.get('gallery'); 493 494 this.attachments = view.gallery.attachments( this.options.shortcode, this.parent ); 495 this.attachments.more().done( _.bind( this.render, this ) ); 496 }, 497 498 499 render: function() { 500 var attrs = this.options.shortcode.attrs.named, 501 options; 502 503 if ( ! this.attachments.length ) { 504 return; 505 } 506 507 options = { 508 attachments: this.attachments.toJSON(), 509 columns: attrs.columns ? parseInt( attrs.columns, 10 ) : 3 510 }; 511 512 this.$el.html( this.template( options ) ); 513 }, 514 515 edit: function() { 516 var selection; 517 518 if ( ! wp.media.view || this.frame ) { 519 return; 520 } 521 522 selection = new wp.media.model.Selection( this.attachments.models, { 523 props: this.attachments.props.toJSON(), 524 multiple: true 525 }); 526 selection.gallery = this.attachments.gallery; 527 528 this.frame = wp.media({ 529 frame: 'post', 530 state: 'gallery-edit', 531 editing: true, 532 multiple: true, 533 selection: selection 534 }); 535 536 // Create a single-use frame. If the frame is closed, 537 // then detach it from the DOM and remove the reference. 538 this.frame.on( 'close', function() { 539 if ( this.frame ) { 540 this.frame.detach(); 541 } 542 delete this.frame; 543 }, this ); 544 545 // Update the `shortcode` and `attachments`. 546 this.frame.state('gallery-edit').on( 'update', function( selection ) { 547 var view = wp.mce.view.get('gallery'); 548 549 this.options.shortcode = view.gallery.shortcode( selection ); 550 this.update(); 551 }, this ); 552 553 this.frame.open(); 554 } 555 } 556 }); 557 }(jQuery)); -
src/wp-includes/js/tinymce/plugins/wpeditimage/plugin.js
diff --git src/wp-includes/js/tinymce/plugins/wpeditimage/plugin.js src/wp-includes/js/tinymce/plugins/wpeditimage/plugin.js index f625097..797b7f3 100644
tinymce.PluginManager.add( 'wpeditimage', function( editor ) { 379 379 return true; 380 380 } 381 381 382 if ( dom.getParent( node, '.wp-view-wrap' ) ) { 383 return true; 384 } 385 382 386 return false; 383 387 } 384 388 -
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..6610205 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 VK = tinymce.util.VK, 8 7 TreeWalker = tinymce.dom.TreeWalker, 8 toRemove = false, 9 9 selected; 10 10 11 tinymce.create('tinymce.plugins.wpView', { 12 init : function( editor ) { 13 var wpView = this; 11 function getParentView( node ) { 12 while ( node ) { 13 if ( isView( node ) ) { 14 return node; 15 } 16 17 node = node.parentNode; 18 } 19 } 20 21 function isView( node ) { 22 return (/(?:^|\s)wp-view-wrap(?:\s|$)/).test( node.className ); 23 } 24 25 function select( view ) { 26 var elem; 27 if ( view === selected ) { 28 return; 29 } 30 31 deselect(); 32 selected = view; 33 34 wp.mce.view.select( selected ); 35 36 elem = editor.dom.select( '.wp-view-shortcode', view )[0]; 37 38 // the following are both necessary to avoid tinymce from manipulating the selection/focus 39 editor.dom.bind(elem, 'beforedeactivate focusin focusout', function(e) { 40 e.stopPropagation(); 41 }); 42 editor.dom.bind(selected, 'beforedeactivate focusin focusout click mouseup', function(e) { 43 e.stopPropagation(); 44 }); 45 46 // select a the hidden div 47 editor.selection.select( elem, true ); 48 elem.focus(); 49 } 50 51 function deselect() { 52 var elem; 53 54 if ( selected ) { 55 elem = editor.dom.select( '.wp-view-shortcode', selected )[0]; 56 editor.dom.unbind(elem, 'beforedeactivate focusin focusout'); 57 editor.dom.unbind(selected, 'beforedeactivate focusin focusout click mouseup'); 58 59 editor.selection.select( selected.nextSibling ); 60 editor.selection.collapse(); 61 wp.mce.view.deselect( selected ); 62 } 63 64 selected = null; 65 } 66 67 function refreshEmptyContentNode() { 68 var body = editor.getBody(), 69 node, 70 editableNode; 71 72 // Gecko adds an editable node if there are no other editable elements 73 editableNode = editor.dom.select( '[_moz_editor_bogus_node="TRUE"]' ); 74 75 if ( body.childNodes.length === ( 1 + editableNode.length ) ) { 14 76 15 // Check if the `wp.mce` API exists. 16 if ( typeof wp === 'undefined' || ! wp.mce ) { 77 node = body.childNodes[ body.childNodes.length - 1 ]; 78 79 if ( node && isView( node ) ) { 80 editor.dom.add( body, 'p', {}, '<br data-mce-bogus="1">' ); 81 } 82 83 } 84 } 85 86 // Check if the `wp.mce` API exists. 87 if ( typeof wp === 'undefined' || ! wp.mce ) { 88 return; 89 } 90 91 editor.on( 'PreInit', function() { 92 // Add elements so we can set `contenteditable` to false. 93 // TODO: since we are serializing, is this needed? 94 editor.schema.addValidElements('div[*],span[*]'); 95 }); 96 97 editor.on( 'BeforeAddUndo', function( event ) { 98 if ( selected && ! toRemove ) { 99 event.preventDefault(); 100 } 101 }); 102 103 // When the editor's content changes, scan the new content for 104 // matching view patterns, and transform the matches into 105 // view wrappers. Since the editor's DOM is outdated at this point, 106 // we'll wait to render the views. 107 editor.on( 'BeforeSetContent', function( e ) { 108 if ( ! e.content ) { 109 return; 110 } 111 112 e.content = wp.mce.view.toViews( e.content ); 113 }); 114 115 // When the editor's content has been updated and the DOM has been 116 // processed, render the views in the document. 117 editor.on( 'SetContent', function() { 118 wp.mce.view.render( editor.getDoc() ); 119 refreshEmptyContentNode(); 120 }); 121 122 // Provide our own handler for selecting a view that is picked up before TinyMCE 123 // Ideally, TinyMCE would provide a way to relinquish control over a block that is marked contenteditable=false perhaps through some sort of data attribute 124 editor.on( 'mousedown', function( event ) { 125 var view = getParentView( event.target ); 126 127 if ( event.metaKey || event.ctrlKey ) { 128 return; 129 } 130 131 // Update the selected view. 132 if ( view ) { 133 select( view ); 134 135 // maybe we can trigger the mousedown so that a view can listen to it. 136 // Prevent the selection from propagating to other plugins. 137 return false; 138 139 } else { 140 deselect(); 141 } 142 } ); 143 144 // Detect mouse down events that are adjacent to a view when a view is the first view or the last view 145 // 146 editor.on( 'click', function( event ) { 147 var body = editor.getBody(), 148 doc = editor.getDoc(), 149 scrollTop = body.scrollTop || doc.documentElement.scrollTop || 0, 150 x, y, firstView, lastView, emptyNode; 151 152 if ( event.metaKey || event.ctrlKey ) { 153 return; 154 } 155 156 if ( event.target.nodeName === 'HTML' && ( isView( body.firstChild ) || isView( body.lastChild ) ) ) { 157 firstView = body.firstChild; 158 lastView = body.lastChild; 159 160 x = event.clientX; 161 y = event.clientY; 162 163 emptyNode = editor.dom.create( 'p', {}, '<br data-mce-bogus="1">' ); 164 165 // detect events above or to the left of the first view 166 if ( isView( firstView ) && ( ( x < firstView.offsetLeft && y < ( firstView.offsetHeight - scrollTop ) ) || 167 y < firstView.offsetTop ) ) { 168 169 body.insertBefore( emptyNode, firstView ); 170 editor.selection.select( emptyNode.firstChild ); 171 editor.selection.collapse( true ); 172 editor.selection.scrollIntoView( emptyNode ); 173 174 return false; 175 } 176 // detect events to the right and below the last view 177 else if ( isView( lastView ) && ( x > ( lastView.offsetLeft + lastView.offsetWidth ) || 178 ( ( scrollTop + y ) - ( lastView.offsetTop + lastView.offsetHeight ) ) > 0 ) ) { 179 180 body.appendChild( emptyNode ); 181 182 editor.selection.select( body.lastChild ); 183 editor.selection.collapse( true ); 184 editor.selection.scrollIntoView( body.lastChild ); 185 186 return false; 187 } 188 } 189 } ); 190 191 editor.on( 'init', function() { 192 var selection = editor.selection; 193 // When a view is selected, ensure content that is being pasted 194 // or inserted is added to a text node (instead of the view). 195 editor.on( 'BeforeSetContent', function() { 196 var walker, target, 197 view = getParentView( selection.getNode() ); 198 199 // If the selection is not within a view, bail. 200 if ( ! view ) { 17 201 return; 18 202 } 19 203 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 } 33 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 } 55 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 ); 61 62 // Otherwise, find the next text node. 63 } else { 64 walker = new TreeWalker( view.nextSibling, view.nextSibling ); 65 target = walker.next(); 66 } 67 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 } 81 82 var node = selection.getNode(); 83 84 if ( ! node.innerHTML ) { 85 return; 86 } 87 88 node.innerHTML = wp.mce.view.toViews( node.innerHTML ); 89 wp.mce.view.render( node ); 90 }); 91 }); 92 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 } 99 100 e.content = wp.mce.view.toText( e.content ); 101 }); 102 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 ); 107 108 // Update the selected view. 109 if ( view ) { 110 wpView.select( view ); 111 112 // Prevent the selection from propagating to other plugins. 113 return false; 114 115 // If we've clicked off of the selected view, deselect it. 116 } else { 117 wpView.deselect(); 118 } 119 }); 120 121 editor.on( 'keydown', function( event ) { 122 var keyCode = event.keyCode, 123 view, instance; 124 125 // If a view isn't selected, let the event go on its merry way. 126 if ( ! selected ) { 127 return; 128 } 129 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 } 137 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 } 145 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 } 151 152 event.preventDefault(); 153 }); 154 }, 155 156 getParentView : function( node ) { 157 while ( node ) { 158 if ( this.isView( node ) ) { 159 return node; 160 } 161 162 node = node.parentNode; 204 // If there are no additional nodes or the next node is a 205 // view, create a text node after the current view. 206 if ( ! view.nextSibling || isView( view.nextSibling ) ) { 207 target = editor.getDoc().createTextNode(''); 208 editor.dom.insertAfter( target, view ); 209 210 // Otherwise, find the next text node. 211 } else { 212 walker = new TreeWalker( view.nextSibling, view.nextSibling ); 213 target = walker.next(); 214 } 215 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 and immediately render them. 223 // 224 // Runs on paste and on inserting nodes/html. 225 editor.on( 'SetContent', function( e ) { 226 if ( ! e.context ) { 227 return; 163 228 } 164 },165 229 166 isView : function( node ) { 167 return (/(?:^|\s)wp-view-wrap(?:\s|$)/).test( node.className ); 168 }, 230 var node = selection.getNode(); 169 231 170 select : function( view ) { 171 if ( view === selected ) { 232 if ( ! node.innerHTML ) { 172 233 return; 173 234 } 174 235 175 this.deselect(); 176 selected = view; 177 wp.mce.view.select( selected ); 178 }, 236 node.innerHTML = wp.mce.view.toViews( node.innerHTML ); 237 wp.mce.view.render( node ); 238 }); 239 }); 240 241 // When the editor's contents are being accessed as a string, 242 // transform any views back to their text representations. 243 editor.on( 'PostProcess', function( e ) { 244 if ( ( ! e.get && ! e.save ) || ! e.content ) { 245 return; 246 } 247 248 e.content = wp.mce.view.toText( e.content ); 249 }); 250 251 editor.on( 'keydown', function( event ) { 252 var keyCode = event.keyCode, 253 view, instance; 254 255 // If a view isn't selected, let the event go on its merry way. 256 if ( ! selected ) { 257 return; 258 } 179 259 180 deselect : function() { 181 if ( selected ) { 182 wp.mce.view.deselect( selected ); 260 // Let keypresses that involve the command or control keys through. 261 // Also, let any of the F# keys through. 262 if ( event.metaKey || event.ctrlKey || ( keyCode >= 112 && keyCode <= 123 ) ) { 263 if ( ( event.metaKey || event.ctrlKey ) && keyCode === 88 ) { 264 toRemove = selected; 183 265 } 266 return; 267 } 184 268 185 selected = null; 269 // If the caret is not within the selected view, deselect the 270 // view and bail. 271 view = getParentView( editor.selection.getNode() ); 272 if ( view !== selected ) { 273 deselect(); 274 return; 186 275 } 276 277 // If delete or backspace is pressed, delete the view. 278 if ( keyCode === VK.DELETE || keyCode === VK.BACKSPACE ) { 279 if ( (instance = wp.mce.view.instance( selected )) ) { 280 instance.remove(); 281 deselect(); 282 } 283 } 284 285 event.preventDefault(); 187 286 }); 188 287 189 // Register plugin 190 tinymce.PluginManager.add( 'wpview', tinymce.plugins.wpView ); 191 })(); 288 editor.on( 'keyup', function() { 289 var instance; 290 291 if ( toRemove ) { 292 instance = wp.mce.view.instance( toRemove ); 293 instance.remove(); 294 toRemove = false; 295 } 296 297 }); 298 }); -
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 f0b9a73..3d5b71d 100644
img::selection { 183 183 outline: 1px solid #777; 184 184 } 185 185 186 187 /** 188 * WP Views 189 */ 190 191 /* delegate the handling of the selection to the wpview tinymce plugin */ 192 193 .wp-view-wrap, 194 .wp-view-wrap * { 195 -moz-user-select: none; 196 -webkit-user-select: none; 197 -ms-user-select: none; 198 user-select: none; 199 } 200 201 /* hide the shortcode content, but allow the content to still be selected */ 202 .wp-view-wrap .wp-view-shortcode { 203 position: absolute; 204 top: 20px; 205 width: 10px; 206 z-index: 100; 207 overflow: hidden; 208 opacity: 1; 209 left: -9999px; 210 display: block; 211 -moz-user-select: element; 212 -webkit-user-select: element; 213 -ms-user-select: element; 214 user-select: element; 215 216 } 217 218 /** 219 * Gallery preview 220 */ 221 .wp-view-type-gallery { 222 position: relative; 223 padding: 16px 0; 224 margin-bottom: 16px; 225 cursor: pointer; 226 } 227 228 .wp-view-type-gallery:after { 229 content: ''; 230 display: block; 231 height: 0; 232 clear: both; 233 visibility: hidden; 234 } 235 236 .wp-view-type-gallery.selected { 237 background-color: #efefef; 238 } 239 240 .wp-view-type-gallery .toolbar { 241 position: absolute; 242 top: 0; 243 left: 0; 244 background-color: #333; 245 color: white; 246 padding: 4px; 247 display: none; 248 } 249 250 .wp-view-type-gallery.selected .toolbar { 251 display: block; 252 } 253 254 .wp-view-type-gallery .toolbar span { 255 cursor: pointer; 256 } 257 258 .gallery img[data-mce-selected]:focus { 259 outline: none; 260 } 261 262 .gallery a { 263 cursor: default; 264 } 265 266 .gallery { 267 margin: auto; 268 line-height: 1; 269 } 270 271 .gallery .gallery-item { 272 float: left; 273 margin: 10px 0 0 0; 274 text-align: center; 275 } 276 277 .gallery .gallery-caption, 278 .gallery .gallery-icon { 279 margin: 0; 280 } 281 282 .gallery-columns-1 .gallery-item { 283 width: 100%; 284 } 285 286 .gallery-columns-2 .gallery-item { 287 width: 50%; 288 } 289 290 .gallery-columns-3 .gallery-item { 291 width: 33.333%; 292 } 293 294 .gallery-columns-4 .gallery-item { 295 width: 25%; 296 } 297 298 .gallery-columns-5 .gallery-item { 299 width: 20%; 300 } 301 302 .gallery-columns-6 .gallery-item { 303 width: 16%; 304 } 305 306 .gallery-columns-7 .gallery-item { 307 width: 14%; 308 } 309 310 .gallery-columns-8 .gallery-item { 311 width: 12%; 312 } 313 314 .gallery-columns-9 .gallery-item { 315 width: 11%; 316 } 317 318 .gallery img { 319 border: 2px solid #cfcfcf; 320 } 321 186 322 img.wp-oembed { 187 323 border: 1px dashed #888; 188 324 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 28f4a1a..679a831 100644
function wp_print_media_templates() { 599 599 </script> 600 600 <?php 601 601 602 //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 603 // do we want to deal with the difference between display and edit context at all? (e.g. wptexturize() being applied to the caption. 604 ?> 605 606 <script type="text/html" id="tmpl-editor-gallery"> 607 <div class="toolbar"> 608 <div class="dashicons dashicons-format-gallery edit"></div> 609 <div class="dashicons dashicons-no-alt remove"></div> 610 </div> 611 <div class="gallery gallery-columns-{{{ data.columns }}}"> 612 <# _.each( data.attachments, function( attachment, index ) { #> 613 <dl class="gallery-item"> 614 <dt class="gallery-icon"> 615 <?php // TODO: need to figure out the best way to make sure that we have thumbnails ?> 616 <img src="{{{ attachment.sizes.thumbnail.url }}}" /> 617 </dt> 618 <dd class="wp-caption-text gallery-caption"> 619 {{ attachment.caption }} 620 </dd> 621 </dl> 622 <?php // this is kind silly, but copied from the gallery shortcode. Maybe it should be removed ?> 623 <# if ( index % data.columns === data.columns - 1 ) { #> 624 <br style="clear: both;"> 625 <# } #> 626 627 <# } ); #> 628 </div> 629 </script> 630 <?php 631 602 632 /** 603 633 * Prints the media manager custom media templates. 604 634 *