Changeset 37446
- Timestamp:
- 05/17/2016 06:48:54 PM (9 years ago)
- Location:
- trunk/src/wp-includes/js
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/js/mce-view.js
r35341 r37446 328 328 329 329 if ( content ) { 330 this.setContent( content, function( editor, node , contentNode) {330 this.setContent( content, function( editor, node ) { 331 331 $( node ).data( 'rendered', true ); 332 this.bindNode.call( this, editor, node , contentNode);332 this.bindNode.call( this, editor, node ); 333 333 }, force ? null : false ); 334 334 } else { … … 352 352 */ 353 353 unbind: function() { 354 this.getNodes( function( editor, node , contentNode) {355 this.unbindNode.call( this, editor, node , contentNode);354 this.getNodes( function( editor, node ) { 355 this.unbindNode.call( this, editor, node ); 356 356 $( node ).trigger( 'wp-mce-view-unbind' ); 357 357 }, true ); … … 395 395 } ) 396 396 .each( function() { 397 callback.call( self, editor, this , $( this ).find( '.wpview-content' ).get( 0 ));397 callback.call( self, editor, this ); 398 398 } ); 399 399 } ); … … 422 422 replaceMarkers: function() { 423 423 this.getMarkers( function( editor, node ) { 424 var selected = node === editor.selection.getNode(), 425 $viewNode; 424 var $viewNode; 426 425 427 426 if ( ! this.loader && $( node ).text() !== this.text ) { … … 431 430 432 431 $viewNode = editor.$( 433 '<div class="wpview-wrap" data-wpview-text="' + this.encodedText + '" data-wpview-type="' + this.type + '">' + 434 '<p class="wpview-selection-before">\u00a0</p>' + 435 '<div class="wpview-body" contenteditable="false">' + 436 '<div class="wpview-content wpview-type-' + this.type + '"></div>' + 437 '</div>' + 438 '<p class="wpview-selection-after">\u00a0</p>' + 439 '</div>' 432 '<div class="wpview" data-wpview-text="' + this.encodedText + '" data-wpview-type="' + this.type + '" contenteditable="false"></div>' 440 433 ); 441 434 442 435 editor.$( node ).replaceWith( $viewNode ); 443 444 if ( selected ) {445 editor.wp.setViewCursor( false, $viewNode[0] );446 }447 436 } ); 448 437 }, … … 470 459 this.setIframes( '', content, callback, rendered ); 471 460 } else { 472 this.getNodes( function( editor, node , contentNode) {461 this.getNodes( function( editor, node ) { 473 462 content = content.body || content; 474 463 475 464 if ( content.indexOf( '<iframe' ) !== -1 ) { 476 content += '<div class="wpview-overlay"></div>'; 477 } 478 479 contentNode.innerHTML = ''; 480 contentNode.appendChild( _.isString( content ) ? editor.dom.createFragment( content ) : content ); 481 482 callback && callback.call( this, editor, node, contentNode ); 465 content += '<span class="mce-shim"></span>'; 466 } 467 468 editor.undoManager.transact( function() { 469 node.innerHTML = ''; 470 node.appendChild( _.isString( content ) ? editor.dom.createFragment( content ) : content ); 471 editor.dom.add( node, 'span', { 'class': 'wpview-end' } ); 472 } ); 473 474 callback && callback.call( this, editor, node ); 483 475 }, rendered ); 484 476 } … … 497 489 self = this; 498 490 499 this.getNodes( function( editor, node , contentNode) {491 this.getNodes( function( editor, node ) { 500 492 var dom = editor.dom, 501 493 styles = '', … … 512 504 513 505 if ( self.iframeHeight ) { 514 dom.add( contentNode, 'div', { style: { 515 width: '100%', 516 height: self.iframeHeight 517 } } ); 506 dom.add( node, 'span', { 507 'data-mce-bogus': 1, 508 style: { 509 display: 'block', 510 width: '100%', 511 height: self.iframeHeight 512 } 513 }, '\u200B' ); 518 514 } 519 515 … … 523 519 var iframe, iframeDoc, observer, i, block; 524 520 525 contentNode.innerHTML = ''; 526 527 iframe = dom.add( contentNode, 'iframe', { 528 /* jshint scripturl: true */ 529 src: tinymce.Env.ie ? 'javascript:""' : '', 530 frameBorder: '0', 531 allowTransparency: 'true', 532 scrolling: 'no', 533 'class': 'wpview-sandbox', 534 style: { 535 width: '100%', 536 display: 'block' 537 }, 538 height: self.iframeHeight 521 editor.undoManager.transact( function() { 522 node.innerHTML = ''; 523 524 iframe = dom.add( node, 'iframe', { 525 /* jshint scripturl: true */ 526 src: tinymce.Env.ie ? 'javascript:""' : '', 527 frameBorder: '0', 528 allowTransparency: 'true', 529 scrolling: 'no', 530 'class': 'wpview-sandbox', 531 style: { 532 width: '100%', 533 display: 'block' 534 }, 535 height: self.iframeHeight 536 } ); 537 538 dom.add( node, 'span', { 'class': 'mce-shim' } ); 539 dom.add( node, 'span', { 'class': 'wpview-end' } ); 539 540 } ); 540 541 dom.add( contentNode, 'div', { 'class': 'wpview-overlay' } );542 541 543 542 iframeDoc = iframe.contentWindow.document; … … 636 635 } ); 637 636 638 callback && callback.call( self, editor, node , contentNode);637 callback && callback.call( self, editor, node ); 639 638 }, 50 ); 640 639 }, rendered ); … … 719 718 */ 720 719 remove: function( editor, node ) { 721 this.unbindNode.call( this, editor, node , $( node ).find( '.wpview-content' ).get( 0 ));720 this.unbindNode.call( this, editor, node ); 722 721 $( node ).trigger( 'wp-mce-view-unbind' ); 723 722 editor.dom.remove( node ); -
trunk/src/wp-includes/js/tinymce/plugins/wpview/plugin.js
r35602 r37446 1 /* global tinymce */2 3 1 /** 4 2 * WordPress View plugin. 5 3 */ 6 tinymce.PluginManager.add( 'wpview', function( editor ) { 7 var $ = editor.$, 8 selected, 9 Env = tinymce.Env, 10 VK = tinymce.util.VK, 11 TreeWalker = tinymce.dom.TreeWalker, 12 toRemove = false, 13 firstFocus = true, 14 _noop = function() { return false; }, 15 isios = /iPad|iPod|iPhone/.test( navigator.userAgent ), 16 cursorInterval, 17 lastKeyDownNode, 18 setViewCursorTries, 19 focus, 20 execCommandView, 21 execCommandBefore, 22 toolbar; 4 ( function( tinymce, wp ) { 5 tinymce.PluginManager.add( 'wpview', function( editor ) { 6 function noop () {} 23 7 24 function getView( node ) { 25 return getParent( node, 'wpview-wrap' ); 26 } 8 if ( ! wp || ! wp.mce ) { 9 return { 10 getView: noop 11 }; 12 } 27 13 28 /** 29 * Returns the node or a parent of the node that has the passed className. 30 * Doing this directly is about 40% faster 31 */ 32 function getParent( node, className ) { 33 while ( node && node.parentNode ) { 34 if ( node.className && ( ' ' + node.className + ' ' ).indexOf( ' ' + className + ' ' ) !== -1 ) { 35 return node; 14 // Check if a node is a view or not. 15 function isView( node ) { 16 return editor.dom.hasClass( node, 'wpview' ); 17 } 18 19 // Replace view tags with their text. 20 function resetViews( content ) { 21 function callback( match, $1 ) { 22 return '<p>' + window.decodeURIComponent( $1 ) + '</p>'; 36 23 } 37 24 38 node = node.parentNode; 25 if ( ! content ) { 26 return content; 27 } 28 29 return content 30 .replace( /<div[^>]+data-wpview-text="([^"]+)"[^>]*>(?:\.|[\s\S]+?wpview-end[^>]+>\s*<\/span>\s*)?<\/div>/g, callback ) 31 .replace( /<p[^>]+data-wpview-marker="([^"]+)"[^>]*>[\s\S]*?<\/p>/g, callback ); 39 32 } 40 33 41 return false; 42 } 34 // Scan new content for matching view patterns and replace them with markers. 35 editor.on( 'beforesetcontent', function( event ) { 36 var node; 43 37 44 function _stop( event ) { 45 event.stopPropagation(); 46 } 47 48 function setViewCursor( before, view ) { 49 var location = before ? 'before' : 'after', 50 offset = before ? 0 : 1; 51 deselect(); 52 editor.selection.setCursorLocation( editor.dom.select( '.wpview-selection-' + location, view )[0], offset ); 53 editor.nodeChanged(); 54 } 55 56 function handleEnter( view, before, key ) { 57 var dom = editor.dom, 58 padNode = dom.create( 'p' ); 59 60 if ( ! ( Env.ie && Env.ie < 11 ) ) { 61 padNode.innerHTML = '<br data-mce-bogus="1">'; 62 } 63 64 if ( before ) { 65 view.parentNode.insertBefore( padNode, view ); 66 } else { 67 dom.insertAfter( padNode, view ); 68 } 69 70 deselect(); 71 72 if ( before && key === VK.ENTER ) { 73 setViewCursor( before, view ); 74 } else { 75 editor.selection.setCursorLocation( padNode, 0 ); 76 } 77 78 editor.nodeChanged(); 79 } 80 81 function removeView( view ) { 82 editor.undoManager.transact( function() { 83 handleEnter( view ); 84 wp.mce.views.remove( editor, view ); 85 }); 86 } 87 88 function select( viewNode ) { 89 var clipboard, 90 dom = editor.dom; 91 92 if ( ! viewNode ) { 93 return; 94 } 95 96 if ( viewNode !== selected ) { 97 // Make sure that the editor is focused. 98 // It is possible that the editor is not focused when the mouse event fires 99 // without focus, the selection will not work properly. 100 editor.getBody().focus(); 101 102 deselect(); 103 selected = viewNode; 104 dom.setAttrib( viewNode, 'data-mce-selected', 1 ); 105 106 clipboard = dom.create( 'div', { 107 'class': 'wpview-clipboard', 108 'contenteditable': 'true' 109 }, wp.mce.views.getText( viewNode ) ); 110 111 editor.dom.select( '.wpview-body', viewNode )[0].appendChild( clipboard ); 112 113 // Both of the following are necessary to prevent manipulating the selection/focus 114 dom.bind( clipboard, 'beforedeactivate focusin focusout', _stop ); 115 dom.bind( selected, 'beforedeactivate focusin focusout', _stop ); 116 117 // select the hidden div 118 if ( isios ) { 119 editor.selection.select( clipboard ); 120 } else { 121 editor.selection.select( clipboard, true ); 122 } 123 } 124 125 editor.nodeChanged(); 126 editor.fire( 'wpview-selected', viewNode ); 127 } 128 129 /** 130 * Deselect a selected view and remove clipboard 131 */ 132 function deselect() { 133 var clipboard, 134 dom = editor.dom; 135 136 if ( selected ) { 137 clipboard = editor.dom.select( '.wpview-clipboard', selected )[0]; 138 dom.unbind( clipboard ); 139 dom.remove( clipboard ); 140 141 dom.unbind( selected, 'beforedeactivate focusin focusout click mouseup', _stop ); 142 dom.setAttrib( selected, 'data-mce-selected', null ); 143 } 144 145 selected = null; 146 } 147 148 // Check if the `wp.mce` API exists. 149 if ( typeof wp === 'undefined' || ! wp.mce ) { 150 return { 151 getView: _noop 152 }; 153 } 154 155 function resetViewsCallback( match, viewText ) { 156 return '<p>' + window.decodeURIComponent( viewText ) + '</p>'; 157 } 158 159 // Replace the view tags with the view string 160 function resetViews( content ) { 161 return content.replace( /<div[^>]+data-wpview-text="([^"]+)"[^>]*>(?:[\s\S]+?wpview-selection-after[^>]+>[^<>]*<\/p>\s*|\.)<\/div>/g, resetViewsCallback ) 162 .replace( /<p [^>]*?data-wpview-marker="([^"]+)"[^>]*>[\s\S]*?<\/p>/g, resetViewsCallback ); 163 } 164 165 // Prevent adding undo levels on changes inside a view wrapper 166 editor.on( 'BeforeAddUndo', function( event ) { 167 if ( event.level.content ) { 168 event.level.content = resetViews( event.level.content ); 169 } 170 }); 171 172 // When the editor's content changes, scan the new content for 173 // matching view patterns, and transform the matches into 174 // view wrappers. 175 editor.on( 'BeforeSetContent', function( event ) { 176 var node; 177 178 if ( ! event.selection ) { 179 wp.mce.views.unbind(); 180 } 181 182 if ( ! event.content ) { 183 return; 184 } 185 186 if ( ! event.load ) { 187 if ( selected ) { 188 removeView( selected ); 38 if ( ! event.selection ) { 39 wp.mce.views.unbind(); 189 40 } 190 41 191 node = editor.selection.getNode(); 192 193 if ( node && node !== editor.getBody() && /^\s*https?:\/\/\S+\s*$/i.test( event.content ) ) { 194 // When a url is pasted or inserted, only try to embed it when it is in an empty paragrapgh. 195 node = editor.dom.getParent( node, 'p' ); 196 197 if ( node && /^[\s\uFEFF\u00A0]*$/.test( $( node ).text() || '' ) ) { 198 // Make sure there are no empty inline elements in the <p> 199 node.innerHTML = ''; 200 } else { 201 return; 202 } 203 } 204 } 205 206 event.content = wp.mce.views.setMarkers( event.content ); 207 }); 208 209 // When pasting strip all tags and check if the string is an URL. 210 // Then replace the pasted content with the cleaned URL. 211 editor.on( 'pastePreProcess', function( event ) { 212 var pastedStr = event.content; 213 214 if ( pastedStr ) { 215 pastedStr = tinymce.trim( pastedStr.replace( /<[^>]+>/g, '' ) ); 216 217 if ( /^https?:\/\/\S+$/i.test( pastedStr ) ) { 218 event.content = pastedStr; 219 } 220 } 221 }); 222 223 // When the editor's content has been updated and the DOM has been 224 // processed, render the views in the document. 225 editor.on( 'SetContent', function() { 226 wp.mce.views.render(); 227 }); 228 229 // Set the cursor before or after a view when clicking next to it. 230 editor.on( 'click', function( event ) { 231 var x = event.clientX, 232 y = event.clientY, 233 body = editor.getBody(), 234 bodyRect = body.getBoundingClientRect(), 235 first = body.firstChild, 236 last = body.lastChild, 237 firstRect, lastRect, view; 238 239 if ( ! first || ! last ) { 240 return; 241 } 242 243 firstRect = first.getBoundingClientRect(); 244 lastRect = last.getBoundingClientRect(); 245 246 if ( y < firstRect.top && ( view = getView( first ) ) ) { 247 setViewCursor( true, view ); 248 event.preventDefault(); 249 } else if ( y > lastRect.bottom && ( view = getView( last ) ) ) { 250 setViewCursor( false, view ); 251 event.preventDefault(); 252 } else if ( x < bodyRect.left || x > bodyRect.right ) { 253 tinymce.each( editor.dom.select( '.wpview-wrap' ), function( view ) { 254 var rect = view.getBoundingClientRect(); 255 256 if ( y < rect.top ) { 257 return false; 258 } 259 260 if ( y >= rect.top && y <= rect.bottom ) { 261 if ( x < bodyRect.left ) { 262 setViewCursor( true, view ); 263 event.preventDefault(); 264 } else if ( x > bodyRect.right ) { 265 setViewCursor( false, view ); 266 event.preventDefault(); 267 } 268 269 return false; 270 } 271 }); 272 } 273 }); 274 275 editor.on( 'init', function() { 276 var scrolled = false, 277 selection = editor.selection, 278 MutationObserver = window.MutationObserver || window.WebKitMutationObserver; 279 280 // When a view is selected, ensure content that is being pasted 281 // or inserted is added to a text node (instead of the view). 282 editor.on( 'BeforeSetContent', function() { 283 var walker, target, 284 view = getView( selection.getNode() ); 285 286 // If the selection is not within a view, bail. 287 if ( ! view ) { 42 if ( ! event.content ) { 288 43 return; 289 44 } 290 45 291 if ( ! view.nextSibling || getView( view.nextSibling ) ) { 292 // If there are no additional nodes or the next node is a 293 // view, create a text node after the current view. 294 target = editor.getDoc().createTextNode(''); 295 editor.dom.insertAfter( target, view ); 296 } else { 297 // Otherwise, find the next text node. 298 walker = new TreeWalker( view.nextSibling, view.nextSibling ); 299 target = walker.next(); 300 } 46 if ( ! event.load ) { 47 node = editor.selection.getNode(); 301 48 302 // Select the `target` text node. 303 selection.select( target ); 304 selection.collapse( true ); 305 }); 49 if ( node && node !== editor.getBody() && /^\s*https?:\/\/\S+\s*$/i.test( event.content ) ) { 50 // When a url is pasted or inserted, only try to embed it when it is in an empty paragrapgh. 51 node = editor.dom.getParent( node, 'p' ); 306 52 307 editor.dom.bind( editor.getDoc(), 'touchmove', function() { 308 scrolled = true; 309 }); 310 311 editor.on( 'mousedown mouseup click touchend', function( event ) { 312 var view = getView( event.target ); 313 314 firstFocus = false; 315 316 // Contain clicks inside the view wrapper 317 if ( view ) { 318 event.stopImmediatePropagation(); 319 event.preventDefault(); 320 321 if ( event.type === 'touchend' && scrolled ) { 322 scrolled = false; 323 } else { 324 select( view ); 325 } 326 327 // Returning false stops the ugly bars from appearing in IE11 and stops the view being selected as a range in FF. 328 // Unfortunately, it also inhibits the dragging of views to a new location. 329 return false; 330 } else { 331 if ( event.type === 'touchend' || event.type === 'mousedown' ) { 332 deselect(); 53 if ( node && /^[\s\uFEFF\u00A0]*$/.test( editor.$( node ).text() || '' ) ) { 54 // Make sure there are no empty inline elements in the <p> 55 node.innerHTML = ''; 56 } else { 57 return; 58 } 333 59 } 334 60 } 335 61 336 if ( event.type === 'touchend' && scrolled ) { 337 scrolled = false; 338 } 62 event.content = wp.mce.views.setMarkers( event.content ); 63 } ); 64 65 // Replace any new markers nodes with views. 66 editor.on( 'setcontent', function() { 67 wp.mce.views.render(); 68 } ); 69 70 // Empty view nodes for easier processing. 71 editor.on( 'preprocess', function( event ) { 72 editor.$( 'div[data-wpview-text], p[data-wpview-marker]', event.node ).each( function( i, node ) { 73 node.innerHTML = '.'; 74 } ); 339 75 }, true ); 340 76 341 if ( MutationObserver ) { 342 new MutationObserver( function() { 343 editor.fire( 'wp-body-class-change' ); 344 } ) 345 .observe( editor.getBody(), { 346 attributes: true, 347 attributeFilter: ['class'] 348 } ); 349 } 77 // Replace views with their text. 78 editor.on( 'postprocess', function( event ) { 79 event.content = resetViews( event.content ); 80 } ); 350 81 351 if ( tinymce.Env.ie ) { 352 // Prevent resize handles in newer IE 353 editor.dom.bind( editor.getBody(), 'controlselect mscontrolselect', function( event ) { 354 if ( getView( event.target ) ) { 355 event.preventDefault(); 356 } 357 }); 358 } 359 }); 82 // Replace views with their text inside undo levels. 83 // This also prevents that new levels are added when there are changes inside the views. 84 editor.on( 'beforeaddundo', function( event ) { 85 event.level.content = resetViews( event.level.content ); 86 } ); 360 87 361 // Empty the wpview wrap and marker nodes 362 function emptyViewNodes( rootNode ) { 363 $( 'div[data-wpview-text], p[data-wpview-marker]', rootNode ).each( function( i, node ) { 364 node.innerHTML = '.'; 365 }); 366 } 88 // Make sure views are copied as their text. 89 editor.on( 'drop objectselected', function( event ) { 90 if ( isView( event.targetClone ) ) { 91 event.targetClone = editor.getDoc().createTextNode( 92 window.decodeURIComponent( editor.dom.getAttrib( event.targetClone, 'data-wpview-text' ) ) 93 ); 94 } 95 } ); 367 96 368 // Run that before the DOM cleanup 369 editor.on( 'PreProcess', function( event ) { 370 emptyViewNodes( event.node ); 371 }, true ); 97 // Clean up URLs for easier processing. 98 editor.on( 'pastepreprocess', function( event ) { 99 var content = event.content; 372 100 373 editor.on( 'hide', function() { 374 wp.mce.views.unbind(); 375 deselect(); 376 emptyViewNodes(); 377 }); 101 if ( content ) { 102 content = tinymce.trim( content.replace( /<[^>]+>/g, '' ) ); 378 103 379 editor.on( 'PostProcess', function( event ) { 380 if ( event.content ) { 381 event.content = event.content.replace( /<div [^>]*?data-wpview-text="([^"]+)"[^>]*>[\s\S]*?<\/div>/g, resetViewsCallback ) 382 .replace( /<p [^>]*?data-wpview-marker="([^"]+)"[^>]*>[\s\S]*?<\/p>/g, resetViewsCallback ); 383 } 384 }); 385 386 // Excludes arrow keys, delete, backspace, enter, space bar. 387 // Ref: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.keyCode 388 function isSpecialKey( key ) { 389 return ( ( key <= 47 && key !== VK.SPACEBAR && key !== VK.ENTER && key !== VK.DELETE && key !== VK.BACKSPACE && ( key < 37 || key > 40 ) ) || 390 key >= 224 || // OEM or non-printable 391 ( key >= 144 && key <= 150 ) || // Num Lock, Scroll Lock, OEM 392 ( key >= 91 && key <= 93 ) || // Windows keys 393 ( key >= 112 && key <= 135 ) ); // F keys 394 } 395 396 // (De)select views when arrow keys are used to navigate the content of the editor. 397 editor.on( 'keydown', function( event ) { 398 var key = event.keyCode, 399 dom = editor.dom, 400 selection = editor.selection, 401 node, view, cursorBefore, cursorAfter, 402 range, clonedRange, tempRange; 403 404 if ( selected ) { 405 // Ignore key presses that involve the command or control key, but continue when in combination with backspace or v. 406 // Also ignore the F# keys. 407 if ( ( ( event.metaKey || event.ctrlKey ) && key !== VK.BACKSPACE && key !== 86 ) || ( key >= 112 && key <= 123 ) ) { 408 // Remove the view when pressing cmd/ctrl+x on keyup, otherwise the browser can't copy the content. 409 if ( ( event.metaKey || event.ctrlKey ) && key === 88 ) { 410 toRemove = selected; 411 } 412 return; 413 } 414 415 view = getView( selection.getNode() ); 416 417 // If the caret is not within the selected view, deselect the view and bail. 418 if ( view !== selected ) { 419 deselect(); 420 return; 421 } 422 423 if ( key === VK.LEFT ) { 424 setViewCursor( true, view ); 425 event.preventDefault(); 426 } else if ( key === VK.UP ) { 427 if ( view.previousSibling ) { 428 if ( getView( view.previousSibling ) ) { 429 setViewCursor( true, view.previousSibling ); 430 } else { 431 deselect(); 432 selection.select( view.previousSibling, true ); 433 selection.collapse(); 434 } 435 } else { 436 setViewCursor( true, view ); 437 } 438 event.preventDefault(); 439 } else if ( key === VK.RIGHT ) { 440 setViewCursor( false, view ); 441 event.preventDefault(); 442 } else if ( key === VK.DOWN ) { 443 if ( view.nextSibling ) { 444 if ( getView( view.nextSibling ) ) { 445 setViewCursor( false, view.nextSibling ); 446 } else { 447 deselect(); 448 selection.setCursorLocation( view.nextSibling, 0 ); 449 } 450 } else { 451 setViewCursor( false, view ); 452 } 453 454 event.preventDefault(); 455 // Ignore keys that don't insert anything. 456 } else if ( ! isSpecialKey( key ) ) { 457 removeView( selected ); 458 459 if ( key === VK.ENTER || key === VK.DELETE || key === VK.BACKSPACE ) { 460 event.preventDefault(); 104 if ( /^https?:\/\/\S+$/i.test( content ) ) { 105 event.content = content; 461 106 } 462 107 } 463 } else { 464 if ( event.metaKey || event.ctrlKey || ( key >= 112 && key <= 123 ) ) { 465 return; 108 } ); 109 110 // Show the view type in the element path. 111 editor.on( 'resolvename', function( event ) { 112 if ( isView( event.target ) ) { 113 event.name = editor.dom.getAttrib( event.target, 'data-wpview-type' ) || 'object'; 466 114 } 115 } ); 467 116 468 node = selection.getNode();469 lastKeyDownNode = node;470 v iew = getView( node);117 // See `media` plugin. 118 editor.on( 'click keyup', function() { 119 var node = editor.selection.getNode(); 471 120 472 // Make sure we don't delete part of a view. 473 // If the range ends or starts with the view, we'll need to trim it. 474 if ( ! selection.isCollapsed() ) { 475 range = selection.getRng(); 476 477 if ( view = getView( range.endContainer ) ) { 478 clonedRange = range.cloneRange(); 479 selection.select( view.previousSibling, true ); 480 selection.collapse(); 481 tempRange = selection.getRng(); 482 clonedRange.setEnd( tempRange.endContainer, tempRange.endOffset ); 483 selection.setRng( clonedRange ); 484 } else if ( view = getView( range.startContainer ) ) { 485 clonedRange = range.cloneRange(); 486 clonedRange.setStart( view.nextSibling, 0 ); 487 selection.setRng( clonedRange ); 121 if ( isView( node ) ) { 122 if ( editor.dom.getAttrib( node, 'data-mce-selected' ) ) { 123 node.setAttribute( 'data-mce-selected', '2' ); 488 124 } 489 125 } 126 } ); 490 127 491 if ( ! view ) { 492 // Make sure we don't eat any content. 493 if ( event.keyCode === VK.BACKSPACE ) { 494 if ( editor.dom.isEmpty( node ) ) { 495 if ( view = getView( node.previousSibling ) ) { 496 setViewCursor( false, view ); 497 editor.dom.remove( node ); 498 event.preventDefault(); 499 } 500 } else if ( ( range = selection.getRng() ) && 501 range.startOffset === 0 && 502 range.endOffset === 0 && 503 ( view = getView( node.previousSibling ) ) ) { 504 setViewCursor( false, view ); 505 event.preventDefault(); 128 editor.addButton( 'wp_view_edit', { 129 tooltip: 'Edit ', // trailing space is needed, used for context 130 icon: 'dashicon dashicons-edit', 131 onclick: function() { 132 var node = editor.selection.getNode(); 133 134 if ( isView( node ) ) { 135 wp.mce.views.edit( editor, node ); 136 } 137 } 138 } ); 139 140 editor.addButton( 'wp_view_remove', { 141 tooltip: 'Remove', 142 icon: 'dashicon dashicons-no', 143 onclick: function() { 144 editor.fire( 'cut' ); 145 } 146 } ); 147 148 editor.once( 'preinit', function() { 149 var toolbar; 150 151 if ( editor.wp && editor.wp._createToolbar ) { 152 toolbar = editor.wp._createToolbar( [ 153 'wp_view_edit', 154 'wp_view_remove' 155 ] ); 156 157 editor.on( 'wptoolbar', function( event ) { 158 if ( isView( event.element ) ) { 159 event.toolbar = toolbar; 506 160 } 507 } 508 return; 161 } ); 509 162 } 163 } ); 510 164 511 if ( ! ( ( cursorBefore = dom.hasClass( view, 'wpview-selection-before' ) ) || 512 ( cursorAfter = dom.hasClass( view, 'wpview-selection-after' ) ) ) ) { 513 return; 514 } 165 editor.wp = editor.wp || {}; 166 editor.wp.getView = noop; 167 editor.wp.setViewCursor = noop; 515 168 516 if ( isSpecialKey( key ) ) { 517 // ignore 518 return; 519 } 520 521 if ( ( cursorAfter && key === VK.UP ) || ( cursorBefore && key === VK.BACKSPACE ) ) { 522 if ( view.previousSibling ) { 523 if ( getView( view.previousSibling ) ) { 524 setViewCursor( false, view.previousSibling ); 525 } else { 526 if ( dom.isEmpty( view.previousSibling ) && key === VK.BACKSPACE ) { 527 dom.remove( view.previousSibling ); 528 } else { 529 selection.select( view.previousSibling, true ); 530 selection.collapse(); 531 } 532 } 533 } else { 534 setViewCursor( true, view ); 535 } 536 event.preventDefault(); 537 } else if ( cursorAfter && ( key === VK.DOWN || key === VK.RIGHT ) ) { 538 if ( view.nextSibling ) { 539 if ( getView( view.nextSibling ) ) { 540 setViewCursor( key === VK.RIGHT, view.nextSibling ); 541 } else { 542 selection.setCursorLocation( view.nextSibling, 0 ); 543 } 544 } 545 event.preventDefault(); 546 } else if ( cursorBefore && ( key === VK.UP || key === VK.LEFT ) ) { 547 if ( view.previousSibling ) { 548 if ( getView( view.previousSibling ) ) { 549 setViewCursor( key === VK.UP, view.previousSibling ); 550 } else { 551 selection.select( view.previousSibling, true ); 552 selection.collapse(); 553 } 554 } 555 event.preventDefault(); 556 } else if ( cursorBefore && key === VK.DOWN ) { 557 if ( view.nextSibling ) { 558 if ( getView( view.nextSibling ) ) { 559 setViewCursor( true, view.nextSibling ); 560 } else { 561 selection.setCursorLocation( view.nextSibling, 0 ); 562 } 563 } else { 564 setViewCursor( false, view ); 565 } 566 event.preventDefault(); 567 } else if ( ( cursorAfter && key === VK.LEFT ) || ( cursorBefore && key === VK.RIGHT ) ) { 568 select( view ); 569 event.preventDefault(); 570 } else if ( cursorAfter && key === VK.BACKSPACE ) { 571 removeView( view ); 572 event.preventDefault(); 573 } else if ( cursorAfter ) { 574 handleEnter( view ); 575 } else if ( cursorBefore ) { 576 handleEnter( view , true, key ); 577 } 578 579 if ( key === VK.ENTER ) { 580 event.preventDefault(); 581 } 582 } 583 }); 584 585 editor.on( 'keyup', function() { 586 if ( toRemove ) { 587 removeView( toRemove ); 588 toRemove = false; 589 } 590 }); 591 592 editor.on( 'focus', function() { 593 var view; 594 595 focus = true; 596 editor.dom.addClass( editor.getBody(), 'has-focus' ); 597 598 // Edge case: show the fake caret when the editor is focused for the first time 599 // and the first element is a view. 600 if ( firstFocus && ( view = getView( editor.getBody().firstChild ) ) ) { 601 setViewCursor( true, view ); 602 } 603 604 firstFocus = false; 169 return { 170 getView: noop 171 }; 605 172 } ); 606 607 editor.on( 'blur', function() { 608 focus = false; 609 editor.dom.removeClass( editor.getBody(), 'has-focus' ); 610 } ); 611 612 editor.on( 'NodeChange', function( event ) { 613 var dom = editor.dom, 614 views = editor.dom.select( '.wpview-wrap' ), 615 className = event.element.className, 616 view = getView( event.element ), 617 lKDN = lastKeyDownNode; 618 619 lastKeyDownNode = false; 620 621 clearInterval( cursorInterval ); 622 623 // This runs a lot and is faster than replacing each class separately 624 tinymce.each( views, function ( view ) { 625 if ( view.className ) { 626 view.className = view.className.replace( / ?\bwpview-(?:selection-before|selection-after|cursor-hide)\b/g, '' ); 627 } 628 }); 629 630 if ( focus && view ) { 631 if ( ( className === 'wpview-selection-before' || className === 'wpview-selection-after' ) && 632 editor.selection.isCollapsed() ) { 633 634 setViewCursorTries = 0; 635 636 deselect(); 637 638 // Make sure the cursor arrived in the right node. 639 // This is necessary for Firefox. 640 if ( lKDN === view.previousSibling ) { 641 setViewCursor( true, view ); 642 return; 643 } else if ( lKDN === view.nextSibling ) { 644 setViewCursor( false, view ); 645 return; 646 } 647 648 dom.addClass( view, className ); 649 650 cursorInterval = setInterval( function() { 651 if ( dom.hasClass( view, 'wpview-cursor-hide' ) ) { 652 dom.removeClass( view, 'wpview-cursor-hide' ); 653 } else { 654 dom.addClass( view, 'wpview-cursor-hide' ); 655 } 656 }, 500 ); 657 // If the cursor lands anywhere else in the view, set the cursor before it. 658 // Only try this once to prevent a loop. (You never know.) 659 } else if ( ! getParent( event.element, 'wpview-clipboard' ) && ! setViewCursorTries ) { 660 deselect(); 661 setViewCursorTries++; 662 setViewCursor( true, view ); 663 } 664 } 665 }); 666 667 editor.on( 'BeforeExecCommand', function() { 668 var node = editor.selection.getNode(), 669 view; 670 671 if ( node && ( ( execCommandBefore = node.className === 'wpview-selection-before' ) || node.className === 'wpview-selection-after' ) && ( view = getView( node ) ) ) { 672 handleEnter( view, execCommandBefore ); 673 execCommandView = view; 674 } 675 }); 676 677 editor.on( 'ExecCommand', function() { 678 var toSelect, node; 679 680 if ( selected ) { 681 toSelect = selected; 682 deselect(); 683 select( toSelect ); 684 } 685 686 if ( execCommandView ) { 687 node = execCommandView[ execCommandBefore ? 'previousSibling' : 'nextSibling' ]; 688 689 if ( node && node.nodeName === 'P' && editor.dom.isEmpty( node ) ) { 690 editor.dom.remove( node ); 691 setViewCursor( execCommandBefore, execCommandView ); 692 } 693 694 execCommandView = false; 695 } 696 }); 697 698 editor.on( 'ResolveName', function( event ) { 699 if ( editor.dom.hasClass( event.target, 'wpview-wrap' ) ) { 700 event.name = editor.dom.getAttrib( event.target, 'data-wpview-type' ) || 'wpview'; 701 event.stopPropagation(); 702 } else if ( getView( event.target ) ) { 703 event.preventDefault(); 704 event.stopPropagation(); 705 } 706 }); 707 708 editor.addButton( 'wp_view_edit', { 709 tooltip: 'Edit ', // trailing space is needed, used for context 710 icon: 'dashicon dashicons-edit', 711 onclick: function() { 712 selected && wp.mce.views.edit( editor, selected ); 713 } 714 } ); 715 716 editor.addButton( 'wp_view_remove', { 717 tooltip: 'Remove', 718 icon: 'dashicon dashicons-no', 719 onclick: function() { 720 selected && removeView( selected ); 721 } 722 } ); 723 724 editor.once( 'preinit', function() { 725 if ( editor.wp && editor.wp._createToolbar ) { 726 toolbar = editor.wp._createToolbar( [ 727 'wp_view_edit', 728 'wp_view_remove' 729 ] ); 730 } 731 } ); 732 733 editor.on( 'wptoolbar', function( event ) { 734 if ( selected ) { 735 event.element = selected; 736 event.toolbar = toolbar; 737 } 738 } ); 739 740 // Add to editor.wp 741 editor.wp = editor.wp || {}; 742 editor.wp.getView = getView; 743 editor.wp.setViewCursor = setViewCursor; 744 745 // Keep for back-compat. 746 return { 747 getView: getView 748 }; 749 }); 173 } )( window.tinymce, window.wp ); -
trunk/src/wp-includes/js/tinymce/skins/wordpress/wp-content.css
r37361 r37446 210 210 */ 211 211 212 .wpview -wrap{212 .wpview { 213 213 width: 99.99%; /* All IE need hasLayout, incl. 11 (ugh, not again!!) */ 214 214 position: relative; 215 215 clear: both; 216 } 217 218 /* delegate the handling of the selection to the wpview tinymce plugin */ 219 .wpview-wrap, 220 .wpview-wrap * { 221 -moz-user-select: none; 222 -webkit-user-select: none; 223 -ms-user-select: none; 224 user-select: none; 225 } 226 227 /* hide the shortcode content, but allow the content to still be selected */ 228 .wpview-wrap .wpview-clipboard, 229 .wpview-wrap > p { 230 position: absolute; 231 top: 0; 232 left: 0; 233 z-index: -1; 234 clip: rect(1px 1px 1px 1px); /* IE7 */ 235 clip: rect(1px, 1px, 1px, 1px); 236 overflow: hidden; 237 outline: 0; 238 padding: 0; 239 border: 0; 240 width: 1px; 241 height: 1px; 242 } 243 244 /* An ugly box will appear when this is focussed in IE, so we'll move it outside the window. */ 245 .wpview-wrap.wpview-selection-before > p, 246 .wpview-wrap.wpview-selection-after > p { 247 left: -10000px; 248 } 249 250 .wpview-wrap .wpview-clipboard, 251 .wpview-wrap .wpview-clipboard *, 252 .wpview-wrap > p { 253 -moz-user-select: text; 254 -webkit-user-select: text; 255 -ms-user-select: text; 256 user-select: text; 257 } 258 259 .has-focus .wpview-wrap.wpview-selection-before:before, 260 .has-focus .wpview-wrap.wpview-selection-after:before { 261 content: ''; 262 margin: 0; 263 padding: 0; 264 position: absolute; 265 top: -2px; 266 left: -3px; 267 bottom: -2px; 268 width: 1px; 269 background-color: black; 270 background-color: currentcolor; 271 opacity: 1; 272 } 273 274 .has-focus .wpview-wrap.wpview-selection-after:before { 275 left: auto; 276 right: -3px; 277 } 278 279 .has-focus .wpview-wrap.wpview-cursor-hide:before { 280 opacity: 0; 281 } 282 283 /** 284 * Media previews 285 */ 286 .wpview-wrap { 287 position: relative; 288 margin-bottom: 16px; 216 margin-bottom: 16px; 289 217 border: 1px solid transparent; 290 218 } 291 219 292 .wpview-wrap[data-mce-selected] { 293 background-color: rgba(0,0,0,0.1); 294 border-color: rgba(0,0,0,0.3); 295 } 296 297 .ie8 .wpview-wrap[data-mce-selected], 298 .ie7 .wpview-wrap[data-mce-selected] { 299 background-color: #e5e5e5; 300 border-color: #72777c; 301 } 302 303 .wpview-overlay { 220 .mce-shim { 304 221 position: absolute; 305 222 top: 0; … … 309 226 } 310 227 311 .wpview -wrap[data-mce-selected] .wpview-overlay{228 .wpview[data-mce-selected="2"] .mce-shim { 312 229 display: none; 313 230 } 314 231 315 .wpview -wrap.loading-placeholder {232 .wpview .loading-placeholder { 316 233 border: 1px dashed #ccc; 317 234 padding: 10px; 318 235 } 319 236 320 .wpview -wrap[data-mce-selected] .loading-placeholder {237 .wpview[data-mce-selected] .loading-placeholder { 321 238 border-color: transparent; 322 239 } 323 240 324 241 /* A little "loading" animation, not showing in IE < 10 */ 325 .wpview -wrap.wpview-loading {242 .wpview .wpview-loading { 326 243 width: 60px; 327 244 height: 5px; … … 331 248 } 332 249 333 .wpview -wrap.wpview-loading ins {250 .wpview .wpview-loading ins { 334 251 background-color: #333; 335 252 margin: 0 0 0 -60px; … … 359 276 } 360 277 361 .wpview -wrap.wpview-content > iframe {278 .wpview .wpview-content > iframe { 362 279 max-width: 100%; 363 280 background: transparent; … … 371 288 } 372 289 373 .wpview -wrap[data-mce-selected] .wpview-error {290 .wpview[data-mce-selected] .wpview-error { 374 291 border-color: transparent; 375 292 }
Note: See TracChangeset
for help on using the changeset viewer.