Created
February 27, 2012 15:22
-
-
Save winhamwr/1924585 to your computer and use it in GitHub Desktop.
Ghetto WYMeditor plugin to do inline styling
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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; | |
} | |
} | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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