Created
July 23, 2013 14:04
-
-
Save winhamwr/6062607 to your computer and use it in GitHub Desktop.
Semi-ghetto WYMeditor spellcheck plugin
This file contains 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 incorrect_spelling_style = { | |
'border-bottom': '1px dashed #FF0000' | |
}; | |
var incorrect_spelling_class = 'incorrect'; | |
// 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 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() { | |
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.' + incorrect_spelling_class)) { | |
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(e) { | |
e.preventDefault(); | |
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 | |
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(); | |
var params = { | |
q: text_array | |
}; | |
var traditional = true; | |
$.post( | |
'/spellcheck/', | |
$.param(params, traditional), | |
function(json) { | |
apply_highlighting(wym, json, text_nodes); | |
// hide loading graphic | |
$loading.hide(); | |
$link.show(); | |
$('.wym_tools_spellcheck').click(do_spellcheck); | |
}, | |
'json' | |
); | |
} | |
}; | |
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, corrections, text_nodes) { | |
var num_corrections = 0; | |
var text_nodes_list = text_nodes.get(); | |
var corrections_by_node_index = {}; | |
$.each(corrections, function(index, correction) { | |
if (!(correction.node_index in corrections_by_node_index)) { | |
corrections_by_node_index[correction.node_index] = []; | |
} | |
corrections_by_node_index[correction.node_index].push(correction); | |
}); | |
$.each(corrections_by_node_index, function(index, corrections) { | |
// Sort the corrections in descending order. We do this to process the | |
// text in reverse order. If we didn't, then all of the offsets would | |
// be incorrect after the first iteration since we modify the DOM | |
corrections.sort(function(a, b) { return b.offset - a.offset; }); | |
$.each(corrections, function(index, correction) { | |
var current_node = text_nodes_list[correction.node_index]; | |
// highlight the correction, by splitting the text node in three | |
// and wrapping the middle portion | |
var left = current_node; | |
var middle = left.splitText(correction.offset); | |
var right = middle.splitText(correction.length); | |
var highlight_span = wym._doc.createElement('span'); | |
$(highlight_span).addClass(incorrect_spelling_class).css(incorrect_spelling_style); | |
wrap_node(middle, highlight_span); | |
add_suggestions_qtip(wym, highlight_span, correction.suggestions); | |
num_corrections += 1; | |
}); | |
}); | |
alert(num_corrections + pluralize(' word', num_corrections) + ' spelled incorrectly'); | |
} | |
function remove_all_highlighting(wym) { | |
var highlights = $(wym._doc.body).find('.' + incorrect_spelling_class); | |
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="'+incorrect_spelling_class+'.+?">(.*?)</span>', 'g'); | |
html = html.replace(re_span, '$1'); | |
$(wym._element).val(html); | |
} | |
function add_suggestions_qtip(wym, span, 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(); | |
}); | |
} | |
} | |
}); | |
} |
This file contains 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
from django.conf.urls.defaults import patterns, url | |
urlpatterns = patterns('spellcheck.views', | |
url(r'^$', 'spellcheck', name='spellcheck'), | |
) |
This file contains 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
import enchant | |
from enchant.tokenize import get_tokenizer | |
from django.views.decorators.csrf import csrf_exempt | |
from pstat.core.utils import JSONResponseView | |
class SpellCheckViewJSON(JSONResponseView): | |
def get_context_data(self, *args, **kwargs): | |
text_list = self.request.POST.getlist('q') | |
# Create a dictionary that uses an in-memory PWL (personal word list) | |
# It's important to use `DictWithPWL` instead of `Dict` because if we | |
# use `Dict`, then a global file is created at | |
# $HOME/.config/enchant/en_US.dic that contains all of the words that | |
# we add here | |
d = enchant.DictWithPWL('en-US') | |
for word in self.request.tenant.settings.get_spellcheck_additions(): | |
d.add(word) | |
# if the word is lowercase, also accept the first-letter | |
# capitalized version as a correct spelling. | |
if word == word.lower(): | |
d.add(word.capitalize()) | |
tokenizer = get_tokenizer('en_US') | |
return [ | |
{ | |
'length': len(word), | |
'suggestions': d.suggest(word), | |
'offset': offset, | |
'node_index': index, | |
} | |
for index, block in enumerate(text_list) | |
for word, offset in tokenizer(block) | |
if not d.check(word) | |
] | |
spellcheck = csrf_exempt(SpellCheckViewJSON.as_view()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment