<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[22210] trunk/wp-includes/js: MCE Views: First pass at keystroke and insertion handling.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg > ul, #logmsg > ol { margin-left: 0; margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://core.trac.wordpress.org/changeset/22210">22210</a></dd>
<dt>Author</dt> <dd>koopersmith</dd>
<dt>Date</dt> <dd>2012-10-12 03:28:22 +0000 (Fri, 12 Oct 2012)</dd>
</dl>

<h3>Log Message</h3>
<pre>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 <a href="http://core.trac.wordpress.org/ticket/21390">#21390</a>, <a href="http://core.trac.wordpress.org/ticket/21812">#21812</a>, <a href="http://core.trac.wordpress.org/ticket/21813">#21813</a>, <a href="http://core.trac.wordpress.org/ticket/21815">#21815</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkwpincludesjsmceviewjs">trunk/wp-includes/js/mce-view.js</a></li>
<li><a href="#trunkwpincludesjstinymcepluginswpvieweditor_plugin_srcjs">trunk/wp-includes/js/tinymce/plugins/wpview/editor_plugin_src.js</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkwpincludesjsmceviewjs"></a>
<div class="modfile"><h4>Modified: trunk/wp-includes/js/mce-view.js (22209 => 22210)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-includes/js/mce-view.js        2012-10-12 00:19:09 UTC (rev 22209)
+++ trunk/wp-includes/js/mce-view.js        2012-10-12 03:28:22 UTC (rev 22210)
</span><span class="lines">@@ -311,8 +311,7 @@
</span><span class="cx">                 render: function( scope ) {
</span><span class="cx">                         $( '.wp-view-wrap', scope ).each( function() {
</span><span class="cx">                                 var wrapper = $(this),
</span><del>-                                        id = wrapper.data('wp-view'),
-                                        view = instances[ id ];
</del><ins>+                                        view = wp.mce.view.instance( this );
</ins><span class="cx"> 
</span><span class="cx">                                 if ( ! view )
</span><span class="cx">                                         return;
</span><span class="lines">@@ -359,6 +358,17 @@
</span><span class="cx">                         return wp.mce.view.removeInternalAttrs( wp.html.attrs( content ) );
</span><span class="cx">                 },
</span><span class="cx"> 
</span><ins>+                // ### 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 ];
+                },
+
</ins><span class="cx">                 // ### Select a view.
</span><span class="cx">                 //
</span><span class="cx">                 // Accepts a MCE view wrapper `node` (i.e. a node with the
</span></span></pre></div>
<a id="trunkwpincludesjstinymcepluginswpvieweditor_plugin_srcjs"></a>
<div class="modfile"><h4>Modified: trunk/wp-includes/js/tinymce/plugins/wpview/editor_plugin_src.js (22209 => 22210)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-includes/js/tinymce/plugins/wpview/editor_plugin_src.js        2012-10-12 00:19:09 UTC (rev 22209)
+++ trunk/wp-includes/js/tinymce/plugins/wpview/editor_plugin_src.js        2012-10-12 03:28:22 UTC (rev 22210)
</span><span class="lines">@@ -3,12 +3,13 @@
</span><span class="cx">  */
</span><span class="cx"> 
</span><span class="cx"> (function() {
</span><ins>+        var VK = tinymce.VK,
+                TreeWalker = tinymce.dom.TreeWalker,
+                selected;
</ins><span class="cx"> 
</span><del>-
</del><span class="cx">         tinymce.create('tinymce.plugins.wpView', {
</span><span class="cx">                 init : function( editor, url ) {
</span><del>-                        var wpView = this,
-                                selected;
</del><ins>+                        var wpView = this;
</ins><span class="cx"> 
</span><span class="cx">                         // Check if the `wp.mce` API exists.
</span><span class="cx">                         if ( typeof wp === 'undefined' || ! wp.mce )
</span><span class="lines">@@ -38,6 +39,33 @@
</span><span class="cx"> 
</span><span class="cx">                         editor.onInit.add( function( editor ) {
</span><span class="cx"> 
</span><ins>+                                // 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 );
+                                });
+
</ins><span class="cx">                                 // When the selection's content changes, scan any new content
</span><span class="cx">                                 // for matching views and immediately render them.
</span><span class="cx">                                 //
</span><span class="lines">@@ -66,35 +94,84 @@
</span><span class="cx">                         });
</span><span class="cx"> 
</span><span class="cx">                         // Triggers when the selection is changed.
</span><del>-                        editor.onNodeChange.add( function( editor, controlManager, node, collapsed, o ) {
</del><ins>+                        // Add the event handler to the top of the stack.
+                        editor.onNodeChange.addToTop( function( editor, controlManager, node, collapsed, o ) {
</ins><span class="cx">                                 var view = wpView.getParentView( node );
</span><span class="cx"> 
</span><ins>+                                // Update the selected view.
+                                if ( view ) {
+                                        wpView.select( view );
+
+                                        // Prevent the selection from propagating to other plugins.
+                                        return false;
+
</ins><span class="cx">                                 // If we've clicked off of the selected view, deselect it.
</span><del>-                                if ( selected &amp;&amp; selected !== view )
-                                        wp.mce.view.deselect( selected );
</del><ins>+                                } else {
+                                        wpView.deselect();
+                                }
+                        });
</ins><span class="cx"> 
</span><del>-                                // Bail if we're not selecting another view.
-                                if ( ! view )
</del><ins>+                        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 )
</ins><span class="cx">                                         return;
</span><span class="cx"> 
</span><del>-                                // Update the selected view.
-                                selected = view;
-                                wp.mce.view.select( selected );
</del><ins>+                                // 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;
+                                }
</ins><span class="cx"> 
</span><del>-                                // Prevent the selection from propagating to other plugins.
-                                return false;
</del><ins>+                                // 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 &gt;= 112 &amp;&amp; keyCode &lt;= 123 ) )
+                                        return;
+
+                                event.preventDefault();
</ins><span class="cx">                         });
</span><span class="cx">                 },
</span><span class="cx"> 
</span><span class="cx">                 getParentView : function( node ) {
</span><span class="cx">                         while ( node ) {
</span><del>-                                if ( /(?:^|\s)wp-view-wrap(?:\s|$)/.test( node.className ) )
</del><ins>+                                if ( this.isView( node ) )
</ins><span class="cx">                                         return node;
</span><span class="cx"> 
</span><span class="cx">                                 node = node.parentNode;
</span><span class="cx">                         }
</span><span class="cx">                 },
</span><span class="cx"> 
</span><ins>+                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;
+                },
+
</ins><span class="cx">                 getInfo : function() {
</span><span class="cx">                         return {
</span><span class="cx">                                 longname  : 'WordPress Views',
</span></span></pre>
</div>
</div>

</body>
</html>