Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p><strong>EDIT : This is not a good solution. It completely breaks down with anything more complex than a single line of simple text. I have worked out and will be posting a better solution soon.</strong></p> <p>Figured it out.</p> <p>I build an array containing each text node in the wysiwyg area, and a list of its parent tags.</p> <p>Then I wrapped the selection in a custom element so it would be easy to remove later, and so as not to conflict with any existing HTML elements, using the x- prefix as recommended.</p> <p>I then rebuilt the contents of the wysiwyg element from that list, removing the tag for the clicked button from all nodes in the selection if they all already had it, which is how most wywiwyg editors handle it.</p> <p><a href="http://jsfiddle.net/x7WRZ/" rel="nofollow">http://jsfiddle.net/x7WRZ/</a></p> <pre><code>&lt;button data-action="B"&gt;&lt;b&gt;b&lt;/b&gt;&lt;/button&gt; &lt;button data-action="I"&gt;&lt;i&gt;i&lt;/i&gt;&lt;/button&gt; &lt;button data-action="U"&gt;&lt;u&gt;u&lt;/u&gt;&lt;/button&gt; &lt;p contenteditable&gt;The quick brown fox jumps over the lazy dog.&lt;/p&gt; &lt;script src="http://code.jquery.com/jquery-1.9.1.min.js"&gt;&lt;/script&gt; &lt;script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js"&gt;&lt;/script&gt; &lt;script&gt; $( function(){ var selectionWrapper = 'X-SELECTION'; function getTextData( element ) { function getTextNodesIn( root ) { var textNodes = []; var parents = []; function getTextNodes( node ) { if( node.nodeType === 3 ){ var text = node.textContent; textNodes.push({ text: text, parents: parents.slice( 0 ) }); } else { if( node !== root ){ parents.push( node.tagName ); } for( var i = 0, len = node.childNodes.length; i &lt; len; ++i ){ getTextNodes( node.childNodes[ i ] ); } parents.pop(); } } getTextNodes( element ); return textNodes; } return getTextNodesIn( element ); } function handleSelection( container, action ) { var textData = getTextData( container ); container.innerHTML = ''; //if every textNode in the selection has action as a parent, we want //to remove it from all of them. var selection = _( textData ).filter( function( data ){ return _( data.parents ).contains( selectionWrapper ); }); var remove = _( selection ).every( function( data ) { return _( data.parents ).contains( action ) || data.text.trim() === ''; }); _( selection ).each( function( data ){ if( remove ) { data.parents = _( data.parents ).without( action ); } else { data.parents.push( action ); } }); //rebuild each text node _( textData ).each( function( data ){ //no need to add empty text nodes if( data.text === '' ) { return; } //remove duplicates of the same parent tag and remove the selection wrapper var parents = _( data.parents ).chain().uniq().without( selectionWrapper ).value(); var target = container; _( parents ).each( function( parent ){ var node = document.createElement( parent ); target.appendChild( node ); target = node; }); var text = document.createTextNode( data.text ); target.appendChild( text ); }); } $( 'button' ).on( 'click', function(){ var action = $( this ).attr( 'data-action' ); var selection = window.getSelection(); for( var i = 0; i &lt; selection.rangeCount; i++ ){ var range = selection.getRangeAt( i ); var node = document.createElement( selectionWrapper ); node.appendChild( range.extractContents() ); range.insertNode( node ); handleSelection( $( 'p' )[ 0 ], action ); } return false; }); }); &lt;/script&gt; </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload