From 60a44a086554db5b38610f5a1a09899ed00cb9b2 Mon Sep 17 00:00:00 2001 From: Daryl Koopersmith Date: Fri, 12 Oct 2012 03:28:22 +0000 Subject: [PATCH] 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: https://develop.svn.wordpress.org/trunk@22210 602fd350-edb4-49c9-b593-d223f7449a82 --- wp-includes/js/mce-view.js | 14 ++- .../plugins/wpview/editor_plugin_src.js | 107 +++++++++++++++--- 2 files changed, 104 insertions(+), 17 deletions(-) diff --git a/wp-includes/js/mce-view.js b/wp-includes/js/mce-view.js index ba89e976a9..8242336eaf 100644 --- a/wp-includes/js/mce-view.js +++ b/wp-includes/js/mce-view.js @@ -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 diff --git a/wp-includes/js/tinymce/plugins/wpview/editor_plugin_src.js b/wp-includes/js/tinymce/plugins/wpview/editor_plugin_src.js index ed12deb21a..f7044c461a 100644 --- a/wp-includes/js/tinymce/plugins/wpview/editor_plugin_src.js +++ b/wp-includes/js/tinymce/plugins/wpview/editor_plugin_src.js @@ -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',