Created
April 19, 2011 18:57
-
-
Save winhamwr/929270 to your computer and use it in GitHub Desktop.
WYMeditor plugin to provide spellchecking
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
var spellcheck_type = 'google'; | |
var correction_list = null; | |
// No constants for Node.* IE | |
var ELEMENT_NODE = 1; | |
var TEXT_NODE = 3; | |
WYMeditor.editor.prototype.spellcheck = function() { | |
if (!$.browser.msie) { return }; | |
var wym = this; | |
var misspelled_style = '<style type="text/css">.misspelled { border-bottom: 1px dashed #FF0000 }</style>'; | |
var button_html = '<li class="wym_tools_spellcheck"><a title="Check Spelling" name="Spellcheck" href="#" style="background-image: url(' + SITE_MEDIA_URL + 'lib/wymeditor/wymeditor/plugins/spellcheck/spellcheck.gif)">Check Spelling</a></li>'; | |
var loading_html = '<a title="Loading" href="javascript:void(0)" style="display: none; width: 20px; height: 20px; background-image: url(' + SITE_MEDIA_URL + 'images/tiny_loading.gif); background-position: center center;"> </a>'; | |
// add spellcheck button | |
$(wym._box) | |
.find(wym._options.toolsSelector + wym._options.toolsListSelector) | |
.append(button_html); | |
$('.wym_tools_spellcheck').append(loading_html); | |
$('.wym_tools_spellcheck').click(do_spellcheck); | |
// add div for spellcheck corrections | |
$('div.wym_iframe').prepend('<div class="suggestion_container" />'); | |
// init after wym loads | |
$(wym._doc).ready(function() { | |
$(wym._doc).find('head').append(misspelled_style); | |
remove_all_highlighting(wym); | |
// remove highlighting when you type over a misspelled word to correct it | |
$(wym._doc).bind('keyup', function() { | |
var span = get_cursor_container() ; | |
if ($(span).is('span.misspelled')) { | |
remove_highlight(span); | |
} | |
}); | |
}); | |
function get_cursor_container() { | |
if (document.selection) { // IE | |
return document.selection.createRange().parentElement(); | |
} else { // Firefox | |
var sel = wym._iframe.contentWindow.getSelection(); | |
return sel.getRangeAt(0).startContainer.parentNode; | |
} | |
} | |
function do_spellcheck() { | |
remove_all_highlighting(wym); | |
// normalize all text nodes before submitting to spellcheck | |
$(wym._doc.body) | |
.find('*') | |
.andSelf() | |
.each( function() { normalize_shallow(this); }); | |
// get all text nodes in the body | |
var text_nodes = $(wym._doc.body) | |
.find('*') | |
.andSelf() | |
.contents() | |
.filter(function() { | |
return this.nodeType != 1 && $.trim(this.nodeValue) != ''; | |
}); | |
// submit each block of text to spellcheck server by ajax | |
correction_list = {}; | |
var text_array = []; | |
text_nodes.each(function() { | |
text_array.push(this.nodeValue); | |
}); | |
// show loading graphic | |
var $link = $(this).children('a:first'); | |
var $loading = $(this).children('a:last'); | |
$('.wym_tools_spellcheck').unbind('click', do_spellcheck); | |
$link.hide(); | |
$loading.show(); | |
$.post( | |
'/spellcheck/' + spellcheck_type + '/', | |
{q: text_array.join('\t')}, | |
function(json) { | |
correction_list = json; | |
apply_highlighting(wym, correction_list, text_nodes); | |
// hide loading graphic | |
$loading.hide(); | |
$link.show(); | |
$('.wym_tools_spellcheck').click(do_spellcheck); | |
}, | |
'json' | |
); | |
return false; // cancel normal click | |
} | |
}; | |
function wrap_node(node, wrap) { | |
// use raw DOM instead of jQuery wrap(), to work around an IE bug | |
node.parentNode.insertBefore(wrap, node); | |
wrap.appendChild(node); | |
} | |
function normalize_shallow(node) { | |
for (i=0; i<node.childNodes.length; i++) { | |
var child = node.childNodes[i]; | |
if (child.nodeType != TEXT_NODE) { continue; } | |
if (child.nodeValue == '') { | |
node.removeChild(child); | |
i -= 1; | |
continue; | |
} | |
var next = child.nextSibling; | |
if (next == null || next.nodeType != TEXT_NODE) { continue; } | |
var combined_text = child.nodeValue + next.nodeValue; | |
new_node = node.ownerDocument.createTextNode(combined_text); | |
node.insertBefore(new_node, child); | |
node.removeChild(child); | |
node.removeChild(next); | |
i -= 1; | |
} | |
} | |
function pluralize(word, num, singular, plural) { | |
if (typeof(singular) == 'undefined') { singular = ''; } | |
if (typeof(plural) == 'undefined') { plural = 's'; } | |
if (num == 1) { | |
return word + singular; | |
} else { | |
return word + plural; | |
} | |
} | |
function apply_highlighting(wym, correction_list, text_nodes) { | |
var num_corrections = 0; | |
var text_nodes = text_nodes.get(); | |
$.each(correction_list, function(node_index) { | |
var current_node = text_nodes[node_index]; | |
var corrections = this; | |
// sort corrections in descending order. this is so the offsets don't | |
// need to be updated for multiple corrections | |
corrections.sort(function(a, b) { return b.offset - a.offset; }); | |
$.each(corrections, function(span_index) { | |
// highlight the correction, by splitting the text node in three | |
// and wrapping the middle portion | |
var left = current_node; | |
var middle = left.splitText(this.offset); | |
var right = middle.splitText(this.length); | |
var highlight_span = wym._doc.createElement('span'); | |
$(highlight_span) | |
.addClass('misspelled') | |
.addClass('correction_' + node_index + '_' + span_index); | |
wrap_node(middle, highlight_span); | |
num_corrections += 1; | |
}); | |
}); | |
$(wym._doc.body).find('.misspelled').each(function() { | |
add_suggestions_qtip(this); | |
}); | |
alert(num_corrections + pluralize(' word', num_corrections) + ' misspelled'); | |
} | |
function remove_all_highlighting(wym) { | |
var highlights = $(wym._doc.body).find('.misspelled'); | |
highlights.each(function() { | |
remove_highlight(this); | |
}); | |
wym.update(); | |
} | |
function remove_highlight(span) { | |
var span_parent = span.parentNode; | |
span = $(span); | |
span.contents().insertBefore(span); // move the contents out of the span | |
// delete the empty span and merge adjacent text nodes | |
span.remove(); | |
normalize_shallow(span_parent); | |
} | |
function strip_spellcheck_highlight(wym) { | |
// remove <span> highlighting from text area | |
var html = wym._element.val(); | |
var re_span = RegExp('<span class="misspelled.+?">(.*?)</span>', 'g'); | |
html = html.replace(re_span, '$1'); | |
$(wym._element).val(html); | |
} | |
function add_suggestions_qtip(span) { | |
wym = $.wymeditors(0); | |
// get suggestions | |
var re = /correction_(\d+)_(\d+)/; | |
var match = re.exec(span.className); | |
var node_index = parseInt(match[1]); | |
var span_index = parseInt(match[2]); | |
var suggestions = correction_list[node_index][span_index].suggestions; | |
// pop up a div showing suggestions | |
var suggestion_links = []; | |
$.each(suggestions, function(i, val) { | |
suggestion_links.push('<li>'+val+'</li>'); | |
}); | |
var content = '<ul class="suggestions">'+suggestion_links.join('\n')+'<ul>'; | |
$(span).qtip({ | |
content: { text: content }, | |
show: { delay: 0, solo: true, when: { event: 'click' }, effect: { length: 0 } }, | |
hide: { when: { event: 'unfocus' } }, | |
position: { | |
type: 'static', | |
container: $('div.suggestion_container') | |
}, | |
api: { | |
onRender: function() { | |
var qtip = this; | |
// Apply the suggestion and hide the tooltip when we click. | |
$(qtip.elements.content).find('ul.suggestions li').click(function() { | |
$(span).text($(this).text()); | |
remove_highlight(span); | |
qtip.hide(); | |
}); | |
} | |
} | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment