MCE Views: First pass at keystroke and insertion handling.

* When a MCE view is selected, normal keystrokes (those without command/control) are blocked.
* Backspace/delete will successfully remove the view.
* Arrow handling will be added at a later point.
* When a MCE view is selected and content is pasted or injected (e.g. when an attachment is added), the content is added to the first text node after the view. If the view is at the end of the document or the next element is another view, a text node is injected into the DOM immediately after the selected view.

see #21390, #21812, #21813, #21815.


git-svn-id: http://core.svn.wordpress.org/trunk@22210 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Daryl Koopersmith 2012-10-12 03:28:22 +00:00
parent 030e825c54
commit 62e5ae4e56
2 changed files with 104 additions and 17 deletions

View File

@ -311,8 +311,7 @@ window.wp = window.wp || {};
render: function( scope ) {
$( '.wp-view-wrap', scope ).each( function() {
var wrapper = $(this),
id = wrapper.data('wp-view'),
view = instances[ id ];
view = wp.mce.view.instance( this );
if ( ! view )
return;
@ -359,6 +358,17 @@ window.wp = window.wp || {};
return wp.mce.view.removeInternalAttrs( wp.html.attrs( content ) );
},
// ### instance( scope )
//
// Accepts a MCE view wrapper `node` (i.e. a node with the
// `wp-view-wrap` class).
instance: function( node ) {
var id = $( node ).data('wp-view');
if ( id )
return instances[ id ];
},
// ### Select a view.
//
// Accepts a MCE view wrapper `node` (i.e. a node with the

View File

@ -3,12 +3,13 @@
*/
(function() {
var VK = tinymce.VK,
TreeWalker = tinymce.dom.TreeWalker,
selected;
tinymce.create('tinymce.plugins.wpView', {
init : function( editor, url ) {
var wpView = this,
selected;
var wpView = this;
// Check if the `wp.mce` API exists.
if ( typeof wp === 'undefined' || ! wp.mce )
@ -38,6 +39,33 @@
editor.onInit.add( function( editor ) {
// When a view is selected, ensure content that is being pasted
// or inserted is added to a text node (instead of the view).
editor.selection.onBeforeSetContent.add( function( selection, o ) {
var view = wpView.getParentView( selection.getNode() ),
walker, target;
// If the selection is not within a view, bail.
if ( ! view )
return;
// If there are no additional nodes or the next node is a
// view, create a text node after the current view.
if ( ! view.nextSibling || wpView.isView( view.nextSibling ) ) {
target = editor.getDoc().createTextNode('');
editor.dom.insertAfter( target, view );
// Otherwise, find the next text node.
} else {
walker = new TreeWalker( view.nextSibling, view.nextSibling );
target = walker.next();
}
// Select the `target` text node.
selection.select( target );
selection.collapse( true );
});
// When the selection's content changes, scan any new content
// for matching views and immediately render them.
//
@ -66,35 +94,84 @@
});
// Triggers when the selection is changed.
editor.onNodeChange.add( function( editor, controlManager, node, collapsed, o ) {
// Add the event handler to the top of the stack.
editor.onNodeChange.addToTop( function( editor, controlManager, node, collapsed, o ) {
var view = wpView.getParentView( node );
// If we've clicked off of the selected view, deselect it.
if ( selected && selected !== view )
wp.mce.view.deselect( selected );
// Update the selected view.
if ( view ) {
wpView.select( view );
// Bail if we're not selecting another view.
if ( ! view )
// Prevent the selection from propagating to other plugins.
return false;
// If we've clicked off of the selected view, deselect it.
} else {
wpView.deselect();
}
});
editor.onKeyDown.addToTop( function( editor, event ) {
var keyCode = event.keyCode,
view, instance;
// If a view isn't selected, let the event go on its merry way.
if ( ! selected )
return;
// Update the selected view.
selected = view;
wp.mce.view.select( selected );
// If the caret is not within the selected view, deselect the
// view and bail.
view = wpView.getParentView( editor.selection.getNode() );
if ( view !== selected ) {
wpView.deselect();
return;
}
// Prevent the selection from propagating to other plugins.
return false;
// If delete or backspace is pressed, delete the view.
if ( keyCode === VK.DELETE || keyCode === VK.BACKSPACE ) {
if ( (instance = wp.mce.view.instance( selected )) ) {
instance.remove();
wpView.deselect();
}
}
// Let keypresses that involve the command or control keys through.
// Also, let any of the F# keys through.
if ( event.metaKey || event.ctrlKey || ( keyCode >= 112 && keyCode <= 123 ) )
return;
event.preventDefault();
});
},
getParentView : function( node ) {
while ( node ) {
if ( /(?:^|\s)wp-view-wrap(?:\s|$)/.test( node.className ) )
if ( this.isView( node ) )
return node;
node = node.parentNode;
}
},
isView : function( node ) {
return (/(?:^|\s)wp-view-wrap(?:\s|$)/).test( node.className );
},
select : function( view ) {
if ( view === selected )
return;
this.deselect();
selected = view;
wp.mce.view.select( selected );
},
deselect : function() {
if ( selected )
wp.mce.view.deselect( selected );
selected = null;
},
getInfo : function() {
return {
longname : 'WordPress Views',