Skip to content

Instantly share code, notes, and snippets.

@winhamwr
Created April 19, 2011 18:57
Show Gist options
  • Save winhamwr/929270 to your computer and use it in GitHub Desktop.
Save winhamwr/929270 to your computer and use it in GitHub Desktop.
WYMeditor plugin to provide spellchecking
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;">&nbsp;</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