Ticket #28595: 28595.2.patch
File 28595.2.patch, 19.3 KB (added by , 10 years ago) |
---|
-
src/wp-includes/js/mce-view.js
39 39 getHtml: function() {}, 40 40 render: function() { 41 41 this.setContent( 42 '<div class="toolbar">' + 43 ( _.isFunction( views[ this.type ].edit ) ? '<div class="dashicons dashicons-edit edit"></div>' : '' ) + 44 '<div class="dashicons dashicons-no-alt remove"></div>' + 42 '<p class="wpview-selection-before">\u00a0</p>' + 43 '<div class="wpview-body" contenteditable="false">' + 44 '<div class="toolbar">' + 45 ( _.isFunction( views[ this.type ].edit ) ? '<div class="dashicons dashicons-edit edit"></div>' : '' ) + 46 '<div class="dashicons dashicons-no-alt remove"></div>' + 47 '</div>' + 48 '<div class="wpview-content wpview-type-' + this.type + '">' + 49 this.getHtml() + 50 '</div>' + 51 ( this.overlay ? '<div class="wpview-overlay"></div>' : '' ) + 52 // The <ins> is used to mark the end of the wrapper div (has to be the last child node). 53 // Needed when comparing the content as string for preventing extra undo levels. 54 '<ins data-wpview-end="1"></ins>' + 45 55 '</div>' + 46 '<div class="wpview-content">' + 47 this.getHtml() + 48 '</div>' + 49 ( this.overlay ? '<div class="wpview-overlay"></div>' : '' ) + 50 // The <ins> is used to mark the end of the wrapper div (has to be the last child node). 51 // Needed when comparing the content as string for preventing extra undo levels. 52 '<ins data-wpview-end="1"></ins>', 56 '<p class="wpview-selection-after">\u00a0</p>', 53 57 function( self, editor, node ) { 54 58 $( self ).trigger( 'ready', [ editor, node ] ); 55 } 59 }, 60 'wrap' 56 61 ); 57 62 }, 58 63 unbind: function() {}, 59 setContent: function( html, callback, replace) {64 setContent: function( html, callback, option ) { 60 65 _.each( tinymce.editors, function( editor ) { 61 66 var self = this; 62 67 if ( editor.plugins.wpview ) { 63 68 $( editor.getBody() ) 64 69 .find( '[data-wpview-text="' + this.encodedText + '"]' ) 65 70 .each( function ( i, element ) { 66 var contentWrap = $( element ). children( '.wpview-content' ),71 var contentWrap = $( element ).find( '.wpview-content' ), 67 72 wrap = element; 68 73 69 if ( contentWrap.length ) {74 if ( contentWrap.length && option !== 'wrap' ) { 70 75 element = contentWrap = contentWrap[0]; 71 76 } 72 77 73 78 if ( _.isString( html ) ) { 74 if ( replace) {79 if ( option === 'replace' ) { 75 80 element = editor.dom.replace( editor.dom.createFragment( html ), wrap ); 76 81 } else { 77 82 editor.dom.setHTML( element, html ); 78 83 } 79 84 } else { 80 if ( replace) {85 if ( option === 'replace' ) { 81 86 element = editor.dom.replace( html, wrap ); 82 87 } else { 83 88 element.appendChild( html ); … … 85 90 } 86 91 87 92 if ( _.isFunction( callback ) ) { 88 callback( self, editor, $( element ). children( '.wpview-content' )[0] );93 callback( self, editor, $( element ).find( '.wpview-content' )[0] ); 89 94 } 90 95 } ); 91 96 } … … 267 272 tag: 'div', 268 273 269 274 attrs: { 270 'class': 'wpview-wrap wpview-type-' + viewType,275 'class': 'wpview-wrap', 271 276 'data-wpview-text': encodedText, 272 'data-wpview-type': viewType, 273 'contenteditable': 'false' 277 'data-wpview-type': viewType 274 278 }, 275 279 276 280 content: '\u00a0' … … 742 746 743 747 self.setError( response.message, 'admin-media' ); 744 748 } else { 745 self.setContent( '<p>' + self.original + '</p>', null, true);749 self.setContent( '<p>' + self.original + '</p>', null, 'replace' ); 746 750 } 747 751 } else if ( response && response.statusText ) { 748 752 self.setError( response.statusText, 'admin-media' ); -
src/wp-includes/js/tinymce/plugins/wpview/plugin.js
4 4 */ 5 5 tinymce.PluginManager.add( 'wpview', function( editor ) { 6 6 var selected, 7 Env = tinymce.Env, 7 8 VK = tinymce.util.VK, 8 9 TreeWalker = tinymce.dom.TreeWalker, 9 toRemove = false; 10 11 function getParentView( node ) { 12 while ( node && node.nodeName !== 'BODY' ) { 13 if ( isView( node ) ) { 14 return node; 15 } 16 17 node = node.parentNode; 18 } 19 } 10 toRemove = false, 11 cursorInterval, lastKeyDownNode; 20 12 21 13 function isView( node ) { 22 return node && /\bwpview-wrap\b/.test( node.className ); 14 return editor.dom.getParent( node, function( node ) { 15 return editor.dom.hasClass( node, 'wpview-wrap' ); 16 } ); 23 17 } 24 18 25 19 function createPadNode() { 26 20 return editor.dom.create( 'p', { 'data-wpview-pad': 1 }, 27 ( tinymce.Env.ie && tinymce.Env.ie < 11 ) ? '' : '<br data-mce-bogus="1" />' );21 ( Env.ie && Env.ie < 11 ) ? '' : '<br data-mce-bogus="1" />' ); 28 22 } 29 23 30 24 /** 31 25 * Get the text/shortcode string for a view. 32 26 * 33 * @param view The view wrapper's HTML id ornode27 * @param view The view wrapper's node 34 28 * @returns string The text/shoercode string of the view 35 29 */ 36 30 function getViewText( view ) { 37 view = getParentView( typeof view === 'string' ? editor.dom.get( view ) :view );31 view = isView( view ); 38 32 39 33 if ( view ) { 40 34 return window.decodeURIComponent( editor.dom.getAttrib( view, 'data-wpview-text' ) || '' ); … … 49 43 * @param text The text string to be set 50 44 */ 51 45 function setViewText( view, text ) { 52 view = getParentView( typeof view === 'string' ? editor.dom.get( view ) :view );46 view = isView( view ); 53 47 54 48 if ( view ) { 55 49 editor.dom.setAttrib( view, 'data-wpview-text', window.encodeURIComponent( text || '' ) ); … … 62 56 event.stopPropagation(); 63 57 } 64 58 59 function setViewCursor( before, view, noNodeChanged ) { 60 var location = before ? 'before' : 'after', 61 offset = before ? 0 : 1; 62 editor.selection.setCursorLocation( editor.dom.select( '.wpview-selection-' + location, view )[0], offset ); 63 ( ! noNodeChanged ) && editor.nodeChanged(); 64 } 65 66 function handleEnter( view, before ) { 67 var dom = editor.dom, 68 padNode; 69 70 if ( ! before && view.nextSibling && dom.isEmpty( view.nextSibling ) && view.nextSibling.nodeName === 'P' ) { 71 padNode = view.nextSibling; 72 } else if ( before && view.previousSibling && dom.isEmpty( view.previousSibling ) && view.previousSibling.nodeName === 'P' ) { 73 padNode = view.previousSibling; 74 } else { 75 padNode = dom.create( 'p' ); 76 77 if ( ! ( Env.ie && Env.ie < 11 ) ) { 78 padNode.innerHTML = '<br data-mce-bogus="1">'; 79 } 80 81 if ( before ) { 82 view.parentNode.insertBefore( padNode, view ); 83 } else { 84 dom.insertAfter( padNode, view ); 85 } 86 } 87 88 deselect(); 89 editor.selection.setCursorLocation( padNode, 0 ); 90 editor.nodeChanged(); 91 } 92 65 93 function select( viewNode ) { 66 94 var clipboard, 67 95 dom = editor.dom; … … 116 144 selected = null; 117 145 } 118 146 119 function selectSiblingView( node, direction ) {120 var body = editor.getBody(),121 sibling = direction === 'previous' ? 'previousSibling' : 'nextSibling';122 123 while ( node && node.parentNode !== body ) {124 if ( node[sibling] ) {125 // The caret will be in another element126 return false;127 }128 129 node = node.parentNode;130 }131 132 if ( isView( node[sibling] ) ) {133 select( node[sibling] );134 return true;135 }136 137 return false;138 }139 140 147 // Check if the `wp.mce` API exists. 141 148 if ( typeof wp === 'undefined' || ! wp.mce ) { 142 149 return; … … 244 251 // or inserted is added to a text node (instead of the view). 245 252 editor.on( 'BeforeSetContent', function() { 246 253 var walker, target, 247 view = getParentView( selection.getNode() );254 view = isView( selection.getNode() ); 248 255 249 256 // If the selection is not within a view, bail. 250 257 if ( ! view ) { … … 286 293 }); 287 294 288 295 editor.dom.bind( editor.getBody().parentNode, 'mousedown mouseup click', function( event ) { 289 var view = getParentView( event.target ),296 var view = isView( event.target ), 290 297 deselectEventType; 291 298 292 299 // Contain clicks inside the view wrapper … … 294 301 event.stopPropagation(); 295 302 296 303 // Hack to try and keep the block resize handles from appearing. They will show on mousedown and then be removed on mouseup. 297 if ( tinymce.Env.ie <= 10 ) {304 if ( Env.ie <= 10 ) { 298 305 deselect(); 299 306 } 300 307 … … 314 321 315 322 // Fix issue with deselecting a view in IE8. Without this hack, clicking content above the view wouldn't actually deselect it 316 323 // and the caret wouldn't be placed at the mouse location 317 if ( tinymce.Env.ie && tinymce.Env.ie <= 8 ) {324 if ( Env.ie && Env.ie <= 8 ) { 318 325 deselectEventType = 'mouseup'; 319 326 } else { 320 327 deselectEventType = 'mousedown'; … … 361 368 } 362 369 }); 363 370 371 372 // Handle key presses for selected views. 364 373 editor.on( 'keydown', function( event ) { 365 var keyCode = event.keyCode, 366 body = editor.getBody(), 367 view, padNode; 374 var dom = editor.dom, 375 keyCode = event.keyCode, 376 selection = editor.selection, 377 view; 368 378 369 379 // If a view isn't selected, let the event go on its merry way. 370 380 if ( ! selected ) { 371 381 return; 372 382 } 373 383 374 // Let key presses that involve the command or control keys through.384 // Let key presses that involve the command or control keys through. 375 385 // Also, let any of the F# keys through. 376 386 if ( event.metaKey || event.ctrlKey || ( keyCode >= 112 && keyCode <= 123 ) ) { 377 if ( ( event.metaKey || event.ctrlKey ) && keyCode === 88 ) { 378 toRemove = selected; 387 // But remove the view when cmd/ctrl + x/backspace are pressed. 388 if ( ( event.metaKey || event.ctrlKey ) && ( keyCode === 88 || keyCode === VK.BACKSPACE ) ) { 389 // We'll remove a cut view on keyup, otherwise the browser can't copy the content. 390 if ( keyCode === 88 ) { 391 toRemove = selected; 392 } else { 393 editor.dom.remove( selected ); 394 } 379 395 } 380 396 return; 381 397 } 382 398 383 view = getParentView( editor.selection.getNode() );399 view = isView( selection.getNode() ); 384 400 385 // If the caret is not within the selected view, deselect the 386 // view and bail. 401 // If the caret is not within the selected view, deselect the view and bail. 387 402 if ( view !== selected ) { 388 403 deselect(); 389 404 return; 390 405 } 391 406 392 // Deselect views with the arrow keys393 407 if ( keyCode === VK.LEFT || keyCode === VK.UP ) { 408 setViewCursor( true, view ); 394 409 deselect(); 395 // Handle case where two views are stacked on top of one another396 if ( isView( view.previousSibling ) ) {397 select( view.previousSibling );398 // Handle case where view is the first node399 } else if ( ! view.previousSibling ) {400 padNode = createPadNode();401 body.insertBefore( padNode, body.firstChild );402 editor.selection.setCursorLocation( body.firstChild, 0 );403 // Handle default case404 } else {405 editor.selection.select( view.previousSibling, true );406 editor.selection.collapse();407 }408 410 } else if ( keyCode === VK.RIGHT || keyCode === VK.DOWN ) { 411 setViewCursor( false, view ); 409 412 deselect(); 410 // Handle case where the next node is another wpview 411 if ( isView( view.nextSibling ) ) { 412 select( view.nextSibling ); 413 // Handle case were the view is that last node 414 } else if ( ! view.nextSibling ) { 415 padNode = createPadNode(); 416 body.appendChild( padNode ); 417 editor.selection.setCursorLocation( body.lastChild, 0 ); 418 // Handle default case where the next node is a non-wpview 419 } else { 420 editor.selection.setCursorLocation( view.nextSibling, 0 ); 421 } 413 } else if ( keyCode === VK.ENTER ) { 414 handleEnter( view ); 422 415 } else if ( keyCode === VK.DELETE || keyCode === VK.BACKSPACE ) { 423 // If delete or backspace is pressed, delete the view. 424 editor.dom.remove( selected ); 416 dom.remove( selected ); 425 417 } 426 418 427 419 event.preventDefault(); 428 420 }); 429 421 430 // Select views when arrow keys are used to navigate the content of the editor.422 // (De)select views when arrow keys are used to navigate the content of the editor. 431 423 editor.on( 'keydown', function( event ) { 432 424 var keyCode = event.keyCode, 433 425 dom = editor.dom, 434 range = editor.selection.getRng(), 435 startNode = range.startContainer, 436 body = editor.getBody(), 437 node, container; 426 selection = editor.selection, 427 node = selection.getNode(), 428 cursorBefore = ( node.className === 'wpview-selection-before' ), 429 cursorAfter = ( node.className === 'wpview-selection-after' ), 430 view; 438 431 439 if ( ! startNode || startNode === body || event.metaKey || event.ctrlKey ) { 432 lastKeyDownNode = node; 433 434 if ( ! cursorBefore && ! cursorAfter ) { 440 435 return; 441 436 } 442 437 443 if ( keyCode === VK.UP || keyCode === VK.LEFT ) { 444 if ( keyCode === VK.LEFT && ( ! range.collapsed || range.startOffset !== 0 ) ) { 445 // Not at the beginning of the current range 446 return; 447 } 438 if ( event.metaKey || event.ctrlKey || ( keyCode >= 112 && keyCode <= 123 ) ) { 439 return; 440 } 448 441 449 if ( ! ( node = dom.getParent( startNode, dom.isBlock )) ) {450 451 442 if ( ( cursorBefore && keyCode === VK.LEFT ) || ( cursorAfter && keyCode === VK.RIGHT ) ) { 443 return; 444 } 452 445 453 if ( selectSiblingView( node, 'previous' ) ) { 454 event.preventDefault(); 446 view = isView( node ); 447 448 if ( ( cursorAfter && keyCode === VK.UP ) || ( cursorBefore && keyCode === VK.BACKSPACE ) ) { 449 if ( view.previousSibling ) { 450 if ( isView( view.previousSibling ) ) { 451 setViewCursor( false, view.previousSibling ); 452 } else { 453 if ( dom.isEmpty( view.previousSibling ) ) { 454 dom.remove( view.previousSibling ); 455 } else { 456 selection.select( view.previousSibling, true ); 457 selection.collapse(); 458 } 459 } 460 } else { 461 handleEnter( view, true ); 455 462 } 456 } else if ( keyCode === VK.DOWN || keyCode === VK.RIGHT ) { 457 if ( ! ( node = dom.getParent( startNode, dom.isBlock ) ) ) { 458 return; 463 event.preventDefault(); 464 } else if ( cursorAfter && keyCode === VK.DOWN ) { 465 if ( view.nextSibling ) { 466 if ( isView( view.nextSibling ) ) { 467 setViewCursor( false, view.nextSibling ); 468 } else { 469 return; 470 } 471 } else { 472 handleEnter( view ); 459 473 } 460 461 if ( keyCode === VK.RIGHT ) { 462 container = range.endContainer; 463 464 if ( ! range.collapsed || ( range.startOffset === 0 && container.length ) || 465 container.nextSibling || 466 ( container.nodeType === 3 && range.startOffset !== container.length ) ) { // Not at the end of the current range 467 474 event.preventDefault(); 475 } else if ( cursorBefore && keyCode === VK.UP ) { 476 if ( view.previousSibling ) { 477 if ( isView( view.previousSibling ) ) { 478 setViewCursor( true, view.previousSibling ); 479 } else { 468 480 return; 469 481 } 470 471 // In a child element 472 while ( container && container !== node && container !== body ) { 473 if ( container.nextSibling ) { 474 return; 475 } 476 container = container.parentNode; 482 } else { 483 handleEnter( view, true ); 484 } 485 event.preventDefault(); 486 } else if ( cursorBefore && keyCode === VK.DOWN ) { 487 if ( view.previousSibling ) { 488 if ( isView( view.nextSibling ) ) { 489 setViewCursor( true, view.nextSibling ); 490 } else { 491 selection.setCursorLocation( view.nextSibling, 0 ); 477 492 } 493 } else { 494 handleEnter( view ); 478 495 } 496 event.preventDefault(); 497 } else if ( ( cursorAfter && keyCode === VK.LEFT ) || ( cursorBefore && keyCode === VK.RIGHT ) ) { 498 select( view ); 499 event.preventDefault(); 500 } else if ( cursorAfter && keyCode === VK.BACKSPACE ) { 501 dom.remove( view ); 502 event.preventDefault(); 503 } else if ( cursorAfter ) { 504 handleEnter( view ); 505 } else if ( cursorBefore ) { 506 handleEnter( view , true); 507 } 508 509 if ( keyCode === VK.ENTER ) { 510 event.preventDefault(); 511 } 512 }); 513 514 // Make sure we don't eat any content. 515 editor.on( 'keydown', function( event ) { 516 var selection = editor.selection, 517 range, view; 479 518 480 if ( selectSiblingView( node, 'next' ) ) { 519 if ( event.keyCode === VK.BACKSPACE ) { 520 if ( ( range = selection.getRng() ) && 521 range.startOffset === 0 && 522 range.endOffset === 0 && 523 selection.isCollapsed() && 524 ( view = isView( selection.getNode().previousSibling ) ) && 525 ! editor.dom.isEmpty( selection.getNode() ) ) { 526 setViewCursor( false, view ); 481 527 event.preventDefault(); 482 528 } 483 529 } … … 516 562 } 517 563 }); 518 564 565 editor.on( 'nodechange', function( event ) { 566 var dom = editor.dom, 567 views = editor.dom.select( '.wpview-wrap' ), 568 className = event.element.className, 569 view = isView( event.element ), 570 lKDN = lastKeyDownNode; 571 572 lastKeyDownNode = false; 573 574 clearInterval( cursorInterval ); 575 576 dom.removeClass( views, 'wpview-selection-before' ); 577 dom.removeClass( views, 'wpview-selection-after' ); 578 dom.removeClass( views, 'wpview-cursor-hide' ); 579 580 if ( view ) { 581 if ( ! selected && className === 'wpview-selection-before' || className === 'wpview-selection-after' ) { 582 // Make sure the cursor arrived in the right node. 583 // This is necessary for Firefox. 584 if ( lKDN === view.previousSibling ) { 585 setViewCursor( true, view ); 586 return; 587 } else if ( lKDN === view.nextSibling ) { 588 setViewCursor( false, view ); 589 return; 590 } 591 592 dom.addClass( view, className ); 593 594 cursorInterval = setInterval( function() { 595 if ( dom.hasClass( view, 'wpview-cursor-hide' ) ) { 596 dom.removeClass( view, 'wpview-cursor-hide' ); 597 } else { 598 dom.addClass( view, 'wpview-cursor-hide' ); 599 } 600 }, 500 ); 601 // If the cursor happens to be anywhere around the view, then set the cursor properly. 602 } else { 603 setViewCursor( true, view, true ); 604 } 605 } 606 } ); 607 519 608 return { 520 609 getViewText: getViewText, 521 610 setViewText: setViewText -
src/wp-includes/js/tinymce/skins/wordpress/wp-content.css
210 210 } 211 211 212 212 /* hide the shortcode content, but allow the content to still be selected */ 213 .wpview-wrap .wpview-clipboard { 213 .wpview-wrap .wpview-clipboard, 214 .wpview-wrap > p { 214 215 position: absolute; 215 216 top: 0; 216 217 left: 0; … … 226 227 } 227 228 228 229 .wpview-wrap .wpview-clipboard, 229 .wpview-wrap .wpview-clipboard * { 230 .wpview-wrap .wpview-clipboard *, 231 .wpview-wrap > p { 230 232 -moz-user-select: text; 231 233 -webkit-user-select: text; 232 234 -ms-user-select: text; 233 235 user-select: text; 234 236 } 235 237 238 .wpview-wrap.wpview-selection-before:before, 239 .wpview-wrap.wpview-selection-after:before { 240 content: ''; 241 margin: 0; 242 padding: 0; 243 position: absolute; 244 top: -2px; 245 left: -3px; 246 bottom: -2px; 247 width: 1px; 248 background-color: black; 249 -webkit-transition: opacity 0.15s ease-out; 250 -moz-transition: opacity 0.15s ease-out; 251 transition: opacity 0.15s ease-out; 252 opacity: 1; 253 } 254 255 .wpview-wrap.wpview-selection-after:before { 256 left: auto; 257 right: -3px; 258 } 259 260 .wpview-wrap.wpview-cursor-hide:before { 261 opacity: 0; 262 } 263 236 264 /** 237 265 * Media previews 238 266 */ … … 348 376 } 349 377 350 378 /* Audio player is short; therefore let's put the toolbar above */ 351 .wpview- type-audio.toolbar {379 .wpview-wrap[data-wpview-type="audio"] .toolbar { 352 380 top: auto; 353 381 bottom: -34px; 354 382 } 355 383 356 .wpview- type-audio.toolbar div {384 .wpview-wrap[data-wpview-type="audio"] .toolbar div { 357 385 margin-top: 0; 358 386 } 359 387 360 .wpview- type-audio.toolbar div:first-child {388 .wpview-wrap[data-wpview-type="audio"] .toolbar div:first-child { 361 389 margin-left: 0; 362 390 } 363 391