Skip to content

Instantly share code, notes, and snippets.

@winhamwr
Created February 27, 2012 15:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save winhamwr/1924585 to your computer and use it in GitHub Desktop.
Save winhamwr/1924585 to your computer and use it in GitHub Desktop.
Ghetto WYMeditor plugin to do inline styling
//Extend WYMeditor
WYMeditor.editor.prototype.inlineStyler = function(options) {
if(jQuery.browser.msie == true){
//No support
}else if (jQuery.browser.opera == true){
//No support
}else if (jQuery.browser.safari == true && jQuery.browser.version < 1.9){
//No support
}else{
var inlineStyler = new InlineStyler(options, this);
this._inlineStyler = inlineStyler;
return(inlineStyler);
}
};
//InlineStyler constructor
function InlineStyler(options, wym) {
options = jQuery.extend({
sButtonHtml: "<li class='wym_tools_underline'>"
+ "<a name='Underline' href='#'"
+ " style='background-image:"
+ " url(" + wym._options.basePath + "plugins/inlinestyler/underline.gif)'>"
+ "Underline Selection!!"
+ "</a></li>",
sButtonSelector: "li.wym_tools_underline a"
}, options);
this._options = options;
this._wym = wym;
this.init();
};
InlineStyler.prototype.init = function() {
var wym = this._wym;
var styler = this;
jQuery(wym._box).find(
wym._options.toolsSelector + wym._options.toolsListSelector)
.append(styler._options.sButtonHtml);
//handle click event
jQuery(wym._box).find(styler._options.sButtonSelector).click(function() {
var sel = wym._iframe.contentWindow.getSelection();
styler.underlineSelection(sel);
return false;
});
};
InlineStyler.prototype.underlineSelection = function(sel) {
var wym = this._wym;
var styler = this;
var range = sel.getRangeAt(0); //Todo use rangeCount to loop through all ranges
var $start_node = jQuery(range.startContainer);
var $end_node = jQuery(range.endContainer);
//Get our start node's siblings up to and not including the end node
var passed_end = false;
var found_start = false;
var siblings = jQuery.map($start_node.parent().contents(), (function (value){
if(passed_end){
//console.log('discarding sibbling:');
//console.log(value);
return null;
}else if(found_start){
if(value == $end_node.get(0)){
passed_end = true;
//console.log('discarding sibbling:');
//console.log(value);
}else{
//console.log('adding sibbling:');
//console.log(value);
return value;
}
}else{
//Haven't found our start element yet
if(value == $start_node.get(0)){
found_start = true;
}
}
}));
var same_node = $start_node.get(0) == $end_node.get(0);
//console.log('start offset:' + range.startOffset);
//console.log('end offset:' + range.endOffset);
//Go through each node and wrap it in a span
//console.log("start node:");
//console.log($start_node);
if(same_node){
styler.wymStyleNode($start_node, range.startOffset, 'underline', range.endOffset, same_node);
}else{
styler.wymStyleNode($start_node, range.startOffset, 'underline', null, same_node);
}
jQuery.each(siblings, function (i) {
var $node = jQuery(this);
//console.log("sibling node:");
//console.log($node);
styler.wymStyleNode($node, 0, 'underline', null, same_node);
});
if(!same_node){
styler.wymStyleNode($end_node, 0, 'underline', range.endOffset, same_node);
//console.log("end node:");
//console.log($end_node);
}
// Deleted redundant nested spans with the same class
iframe = jQuery(wym._iframe).contents();
redundants = iframe.find('span.underline span.underline');
redundants.removeClass('underline');
// If any spans don't have ids or classes, remove them
var $spans = iframe.find('span.wym_style');
WYM_STYLE = 'wym_style';
var len_class = WYM_STYLE.length;
$spans.each(function(i) {
span = jQuery(this);
classes = span.attr('class');
if(!classes || classes.length <= len_class){//No classes or just .wym_style
span.replaceWith(span.html());
}
});
}
//Wrap the given node (taking in to account any offset) in a wym_style span
//Used for applying classes to a selection within a block element
InlineStyler.prototype.wymStyleNode = function($node, start_pos, toggle_class, end_pos, same_node) {
var wym = this._wym;
var styler = this;
var unsel_start_text = '';
var sel_text = '';
var unsel_end_text = '';
var unsel_start_node = null;
var $sel_node = null;
var unsel_end_node = null;
var $wrap = styler.getWymStyleWrap(toggle_class);
var text = '';
//console.log("wymStyleNode on:");
//console.log($node);
if($node.get(0).nodeType == Node.TEXT_NODE){
text = $node.get(0).nodeValue;
}else{
text = $node.text();
}
if(start_pos + 1 == text.length || start_pos == end_pos){
//console.log("start and end positions the same or starting at the end");
return;
}
if(start_pos == 0 && (end_pos == null || end_pos + 1 == text.length)){
//Wrapping the whole node
//console.log("Wrapping the whole node");
if($node.parent().is('span') && $node.parent().hasClass('wym_style')){
//Don't mess with nodes that are already in complete spans
//Do the toggle in the span since contents() is recursive
//console.log("Ignoring text node ");
}else if($node.is('span') && $node.hasClass('wym_style')){
$node.toggleClass(toggle_class);
//console.log("toggled node");
}else{
$node.wrap($wrap);
//console.log("wrapped node");
}
return;
}else if(start_pos != 0 && end_pos == null){
//Wrapping last section of a node
//console.log('Wrapping last section');
unsel_start_text += text.substring(0, start_pos);
sel_text = text.substring(start_pos);
}else if(start_pos != 0 && end_pos != null){//Wrapping the middle of a node
//console.log('Wrapping middle section');
unsel_start_text = text.substring(0, start_pos);
sel_text = text.substring(start_pos, end_pos);
unsel_end_text = text.substring(end_pos);
}else if(start_pos == 0 && end_pos != null){//Wrapping start section of a node
//console.log('Wrapping start section');
sel_text = text.substring(0, end_pos);
unsel_end_text = text.substring(end_pos);
}
//debugging
//console.log('all text:' + text);
//console.log('selected text:' + sel_text);
//console.log('unselected start text:' + unsel_start_text);
//console.log('unselected end text:' + unsel_end_text);
if(sel_text.length > 0){//Only change things if we actually selected something
//TODO: Account for multiple text node siblings within a span
//Surround all of the text nodes with a span with the same classes as the parent span
//TODO: If we're partly already in a span, shrink the parent span to not
//contain our selected text node while continuing to contain the unselected nodes
if($node.parent().is('span') && $node.parent().hasClass('wym_style')){
var node_index = $node.parent().contents().index($node);
var $left_side = $node.parent().contents().find(':lt('+node_index+')');
var $right_side = $node.parent().contents().find(':gt('+node_index+')');
var classes = $node.parent().attr('class');
$node.parent().replaceWith($node.parent().html());
$left_wrap = this.getWymStyleWrap('');
$left_wrap.attr('class', classes);
$left_side.wrap($left_wrap);
$right_wrap = this.getWymStyleWrap('');
$right_wrap.attr('class', classes);
$right_side.wrap($right_wrap);
//TODO: add unsel_start_node and unsel_end_node to left and right sides
}
//Build the start, selected and end nodes based on the text
unsel_start_node = document.createTextNode(unsel_start_text);
unsel_end_node = document.createTextNode(unsel_end_text);
$sel_node = $wrap;
$sel_node.text(sel_text);
//Insert the start, selected and end nodes
$node.replaceWith($sel_node);
$sel_node.after(unsel_end_node);
$sel_node.before(unsel_start_node);
}else{
//console.log("empty selection");
}
}
InlineStyler.prototype.getWymStyleWrap = function(style_class) {
var new_span = document.createElement('span');
var $new_span = jQuery(new_span);
$new_span.addClass('wym_style');
$new_span.addClass(style_class);
return $new_span;
}
//keydown handler, mainly used for keyboard shortcuts
InlineStyler.prototype.keydown = function(evt) {
//'this' is the doc
var wym = this._wym;
var styler = this;
if(evt.ctrlKey){
if(evt.keyCode == 85){
//CTRL+u => UNDERLINE
var sel = wym._iframe.contentWindow.getSelection();
styler.underlineSelection(sel);
return false;
}
}
};
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!--
* WYMeditor : what you see is What You Mean web-based editor
* Copyright (c) 2008 Jean-Francois Hovinne, http://www.wymeditor.org/
* Dual licensed under the MIT (MIT-license.txt)
* and GPL (GPL-license.txt) licenses.
*
* For further information visit:
* http://www.wymeditor.org/
*
* File Name:
* test.html
* WYMeditor unit tests.
* See the documentation for more info.
*
* File Authors:
* Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg)
* Wes Winham
-->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Inline-Styler Test Suite</title>
<link rel="stylesheet" href="testsuite.css" type="text/css" media="screen" />
<script type="text/javascript" src="../../../../../jquery/jquery.js"></script>
<script type="text/javascript" src="../../../../../wymeditor/jquery.wymeditor.js"></script>
<script type="text/javascript" src="../../../../../wymeditor/jquery.wymeditor.explorer.js"></script>
<script type="text/javascript" src="../../../../../wymeditor/jquery.wymeditor.mozilla.js"></script>
<script type="text/javascript" src="../../../../../wymeditor/jquery.wymeditor.opera.js"></script>
<script type="text/javascript" src="../../../../../wymeditor/jquery.wymeditor.safari.js"></script>
<script type="text/javascript" src="../../jquery.wymeditor.inlinestyler.js"></script>
<script type="text/javascript" src="testrunner.js"></script>
<script type="text/javascript">
module("Core");
test("Instantiate", function() {
expect(2);
jQuery('.wymeditor').wymeditor({
stylesheet: 'styles.css',
postInit: function(wym) { runPostInitTests(wym) }
});
equals( WYMeditor.INSTANCES.length, 1, "WYMeditor.INSTANCES length" );
equals( typeof(jQuery.wymeditors(0)), 'object', "Type of first WYMeditor instance, using jQuery.wymeditors(0)" );
});
function runPostInitTests(wym) {
module("Post Init");
module("Inline-Styler");
wym.inlineStyler();
test("Underline button was succesfully added to the panel", function() {
button = jQuery(wym._box).find(wym._options.toolsSelector).find('li.wym_tools_underline');
equals( button.length, 1);
});
function resetEditor(){
text = "<p>Some text</p><p>Second Line of text</p><p>Third</p>"
ed = jQuery.wymeditors(0); //The wymeditor instance
ed.html(text);
ed.xhtml();
selObj = window.getSelection();
selObj.removeAllRanges();
range = document.createRange();
var iframe = ed._iframe;
$iframe = jQuery(iframe);
ps = $iframe.contents().find('p');
}
function getSelection(){
var userSelection;
if (window.getSelection) {
userSelection = window.getSelection();
}
else if (document.selection) { // should come last; Opera!
userSelection = document.selection.createRange();
}
return userSelection;
}
function testUnderlineToggle(startIndex, startOffset, endIndex, endOffset, expectedText){
var text = "<p>Some text</p><p>Second Line of text</p><p>Third</p>"
var ed = jQuery.wymeditors(0); //The wymeditor instance
ed.html(text);
ed.xhtml();
var selObj = getSelection();
selObj.removeAllRanges();
var range = document.createRange();
var iframe = ed._iframe;
var $iframe = jQuery(iframe);
var ps = $iframe.contents().find('p');
range.setStart(ps.get(startIndex).firstChild, startOffset);
range.setEnd(ps.get(endIndex).firstChild, endOffset);
selObj.addRange(range);
// Underline them
ed._inlineStyler.underlineSelection(selObj);
equals( jQuery.trim( ed.xhtml() ), expectedText);
// Undo the underline
var spans = $iframe.contents().find('span.wym_style');
first_span = spans.get(0);
last_span = spans.get(spans.length-1);
range.setStart(first_span.firstChild, 0);
range.setEnd(last_span.firstChild, last_span.firstChild.length);
selObj.removeAllRanges();
selObj.addRange(range);
ed._inlineStyler.underlineSelection(selObj);
equals( jQuery.trim( ed.xhtml() ), text);
}
test("Should add and remove 'underline' class to the first and second paragraphs", function() {
var expected = '<p><span class="wym_style underline">Some text</span></p><p><span class="wym_style underline">Second Line of text</span></p><p>Third</p>';
testUnderlineToggle(0, 0, 2, 0, expected);
});
test("Should add and remove 'underline' class to the first paragraph", function() {
var expected = '<p><span class="wym_style underline">Some text</span></p><p>Second Line of text</p><p>Third</p>';
testUnderlineToggle(0, 0, 1, 0, expected);
});
test("Should underline 'Some' and then remove the underlining", function() {
var expected = '<p><span class="wym_style underline">Some</span> text</p><p>Second Line of text</p><p>Third</p>';
testUnderlineToggle(0, 0, 0, 4, expected);
});
test("Should underline ' text' and then remove the underlining", function() {
var expected = '<p>Some<span class="wym_style underline"> text</span></p><p>Second Line of text</p><p>Third</p>';
testUnderlineToggle(0, 4, 0, 9, expected);
});
test("Should underline 'ome tex' and then remove the underlining", function() {
var expected = '<p>S<span class="wym_style underline">ome tex</span>t</p><p>Second Line of text</p><p>Third</p>';
testUnderlineToggle(0, 1, 0, 8, expected);
});
};
</script>
</head>
<body>
<h1>WYMeditor Test Suite</h1>
<h2 id="banner"></h2>
<h2 id="userAgent"></h2>
<form method="post" action="">{% csrf_token %}
<textarea class="wymeditor"></textarea>
<input type="submit" class="wymupdate" />
<input type="button" class="wymclear" name="Clear" value="Clear" />
</form>
<ol id="tests"></ol>
<div id="main"></div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment