| 1 |
// $Id: spellcheck.js,v 1.2 2005-12-12 10:50:54 thox Exp $
|
| 2 |
|
| 3 |
if (isJsEnabled()) {
|
| 4 |
addLoadEvent(spellcheckerAutoAttach);
|
| 5 |
}
|
| 6 |
|
| 7 |
/**
|
| 8 |
* Attach spellchecking to all textareas on the current page
|
| 9 |
*/
|
| 10 |
function spellcheckerAutoAttach() {
|
| 11 |
var textAreas = document.getElementsByTagName('textarea');
|
| 12 |
for (i = 0; i < textAreas.length; i++) {
|
| 13 |
temp = new spellchecker;
|
| 14 |
temp.setup(textAreas[i]);
|
| 15 |
}
|
| 16 |
|
| 17 |
if (document.onclick) {
|
| 18 |
var oldOnclick = document.onclick;
|
| 19 |
document.onclick = function(e) {
|
| 20 |
spellcheckerHandleClick(e);
|
| 21 |
oldOnclick(e);
|
| 22 |
}
|
| 23 |
} else {
|
| 24 |
document.onclick = spellcheckerHandleClick;
|
| 25 |
}
|
| 26 |
}
|
| 27 |
|
| 28 |
/**
|
| 29 |
* Handles onclick events on the page to hide the suggestions popup
|
| 30 |
*/
|
| 31 |
function spellcheckerHandleClick(e) {
|
| 32 |
var node = window.event ? window.event.srcElement : e.target;
|
| 33 |
switch (node.className) {
|
| 34 |
case 'spellcheck-correction':
|
| 35 |
case 'spellcheck-mistake':
|
| 36 |
break;
|
| 37 |
default:
|
| 38 |
removeNode(document.getElementById('spellcheck-suggestions'));
|
| 39 |
}
|
| 40 |
}
|
| 41 |
|
| 42 |
/**
|
| 43 |
* spellchecker object
|
| 44 |
*/
|
| 45 |
spellchecker = function () {
|
| 46 |
/* Intentionally empty */
|
| 47 |
}
|
| 48 |
|
| 49 |
|
| 50 |
/**
|
| 51 |
* Initialises a spellchecker object
|
| 52 |
*/
|
| 53 |
spellchecker.prototype.setup = function (textArea) {
|
| 54 |
this.highlighted = false;
|
| 55 |
this.textArea = textArea;
|
| 56 |
this.corrections = [];
|
| 57 |
|
| 58 |
var sc = this;
|
| 59 |
this.para = document.createElement('p');
|
| 60 |
this.para.className = 'spellcheck-trigger';
|
| 61 |
this.trigger = document.createElement('a');
|
| 62 |
this.trigger.href = '#';
|
| 63 |
this.trigger.innerHTML = 'Check spelling';
|
| 64 |
this.trigger.onclick = function() { sc.check(); return false; };
|
| 65 |
this.para.appendChild(this.trigger);
|
| 66 |
this.textArea.parentNode.insertBefore(this.para, this.textArea.nextSibling);
|
| 67 |
}
|
| 68 |
|
| 69 |
/**
|
| 70 |
* Performs the actual spelling check, sending results to spellchecker.callback
|
| 71 |
*/
|
| 72 |
spellchecker.prototype.check = function () {
|
| 73 |
if (this.textArea.value.length == 0) {
|
| 74 |
return this.statusMessage('Nothing to check');
|
| 75 |
}
|
| 76 |
this.trigger.innerHTML = 'Checking...';
|
| 77 |
this.trigger.onclick = function() { return false; };
|
| 78 |
var base_url = document.getElementsByTagName('base')[0].href;
|
| 79 |
HTTPPost(base_url + '?q=spellcheck', this.callback, this, this.textArea.value);
|
| 80 |
}
|
| 81 |
|
| 82 |
/**
|
| 83 |
* Converts the spellchecking DIV back to a textarea
|
| 84 |
*/
|
| 85 |
spellchecker.prototype.resume = function () {
|
| 86 |
var sc = this;
|
| 87 |
this.trigger.innerHTML = "Check spelling";
|
| 88 |
this.trigger.onclick = function() { sc.check(); return false; };
|
| 89 |
|
| 90 |
if (this.corrections.length > 0) {
|
| 91 |
var correctedString = '';
|
| 92 |
var offset = 0;
|
| 93 |
var words = this.div.getElementsByTagName('a');
|
| 94 |
for (i = 0; correction = words[i]; i++) {
|
| 95 |
correction = this.corrections[i];
|
| 96 |
correctedString += this.textArea.value.substr(offset, correction.stringStart - offset);
|
| 97 |
correctedString += words[i].innerHTML;
|
| 98 |
offset = correction.stringStart + correction.stringLength;
|
| 99 |
}
|
| 100 |
if (offset < this.textArea.value.length) {
|
| 101 |
correctedString += this.textArea.value.substr(offset);
|
| 102 |
}
|
| 103 |
this.textArea.value = correctedString;
|
| 104 |
removeNode(this.div);
|
| 105 |
this.div = null;
|
| 106 |
this.textArea.style.display = 'block';
|
| 107 |
this.corrections = [];
|
| 108 |
}
|
| 109 |
}
|
| 110 |
|
| 111 |
/**
|
| 112 |
* Handles HTTP errors and sends XML to spellchecker.parseResponse
|
| 113 |
*/
|
| 114 |
spellchecker.prototype.callback = function (html, http, spellcheckerInstance) {
|
| 115 |
if (http.status != 200) {
|
| 116 |
spellcheckerInstance.statusMessage('An HTTP error occured');
|
| 117 |
spellcheckerInstance.resume();
|
| 118 |
return;
|
| 119 |
}
|
| 120 |
spellcheckerInstance.parseResponse(http.responseXML);
|
| 121 |
}
|
| 122 |
|
| 123 |
/**
|
| 124 |
* Displays a status message on the page for 2 seconds
|
| 125 |
*/
|
| 126 |
spellchecker.prototype.statusMessage = function (statusString) {
|
| 127 |
if (!this.statusElement || !this.statusElement.parentNode) {
|
| 128 |
this.statusElement = document.createElement('span');
|
| 129 |
this.statusElement.className = 'spellcheck-status';
|
| 130 |
this.para.appendChild(this.statusElement);
|
| 131 |
}
|
| 132 |
this.statusElement.innerHTML = ' - ' + statusString;
|
| 133 |
var temp = this.statusElement;
|
| 134 |
setTimeout(function() { removeNode(temp); }, 2000);
|
| 135 |
}
|
| 136 |
|
| 137 |
/**
|
| 138 |
* Parses the XML response from Google
|
| 139 |
*/
|
| 140 |
spellchecker.prototype.parseResponse = function (xml) {
|
| 141 |
if (!xml || !xml.documentElement) {
|
| 142 |
return this.statusMessage('An XML error occured');
|
| 143 |
}
|
| 144 |
|
| 145 |
var correctionNodes = xml.documentElement.getElementsByTagName('c');
|
| 146 |
var sc = this;
|
| 147 |
|
| 148 |
if (correctionNodes.length > 0) {
|
| 149 |
this.trigger.innerHTML = "Resume editing";
|
| 150 |
this.trigger.onclick = function() { sc.resume(); return false; };
|
| 151 |
|
| 152 |
var i = 0;
|
| 153 |
for (i = 0; i < correctionNodes.length; i++) {
|
| 154 |
this.corrections[i] = {
|
| 155 |
'stringStart' : parseInt(correctionNodes[i].getAttribute('o')),
|
| 156 |
'stringLength' : parseInt(correctionNodes[i].getAttribute('l')),
|
| 157 |
'suggestions' : correctionNodes[i].hasChildNodes() ? correctionNodes[i].firstChild.nodeValue.split('\t') : []
|
| 158 |
};
|
| 159 |
}
|
| 160 |
this.statusMessage('Found ' + correctionNodes.length + ' possible mistakes');
|
| 161 |
this.displayCorrectionDiv();
|
| 162 |
}
|
| 163 |
else {
|
| 164 |
this.statusMessage('No mistakes found');
|
| 165 |
this.corrections = [];
|
| 166 |
this.resume();
|
| 167 |
}
|
| 168 |
return this.corrections;
|
| 169 |
}
|
| 170 |
|
| 171 |
/**
|
| 172 |
* Converts the current textarea into an interactive spell checking DIV
|
| 173 |
*/
|
| 174 |
spellchecker.prototype.displayCorrectionDiv = function () {
|
| 175 |
this.div = document.createElement('div');
|
| 176 |
this.div.id = 'spellcheck';
|
| 177 |
this.div.style.overflow = 'auto';
|
| 178 |
this.div.style.height = this.textArea.offsetHeight + 'px';
|
| 179 |
this.div.style.width = this.textArea.offsetWidth + 'px';
|
| 180 |
|
| 181 |
var offset = 0;
|
| 182 |
var sc = this;
|
| 183 |
|
| 184 |
for (var i = 0; correction = this.corrections[i]; i++) {
|
| 185 |
span = document.createElement('span');
|
| 186 |
span.innerHTML = this.checkPlain(this.textArea.value.substr(offset, correction.stringStart - offset));
|
| 187 |
this.div.appendChild(span);
|
| 188 |
|
| 189 |
wordLink = document.createElement('a');
|
| 190 |
wordLink.href = '#';
|
| 191 |
wordLink.innerHTML = this.textArea.value.substr(correction.stringStart, correction.stringLength);
|
| 192 |
wordLink.className = 'spellcheck-mistake';
|
| 193 |
wordLink.onclick = function() { sc.clickWord(this); return false; };
|
| 194 |
wordLink.suggestions = correction.suggestions;
|
| 195 |
wordLink.sc = this;
|
| 196 |
this.div.appendChild(wordLink);
|
| 197 |
|
| 198 |
offset = correction.stringStart + correction.stringLength;
|
| 199 |
}
|
| 200 |
if (offset < this.textArea.value.length) {
|
| 201 |
span = document.createElement('span');
|
| 202 |
span.innerHTML = this.checkPlain(this.textArea.value.substr(offset));
|
| 203 |
this.div.appendChild(span);
|
| 204 |
}
|
| 205 |
|
| 206 |
this.textArea.parentNode.insertBefore(this.div, this.textArea);
|
| 207 |
this.textArea.style.display = 'none';
|
| 208 |
}
|
| 209 |
|
| 210 |
/**
|
| 211 |
* Handles the user clicking on a highlighted word
|
| 212 |
*/
|
| 213 |
spellchecker.prototype.clickWord = function (word) {
|
| 214 |
var div = document.getElementById('spellcheck-suggestions');
|
| 215 |
if (!div) {
|
| 216 |
div = document.createElement('div');
|
| 217 |
div.id = 'spellcheck-suggestions';
|
| 218 |
}
|
| 219 |
while (div.hasChildNodes()) {
|
| 220 |
removeNode(div.firstChild);
|
| 221 |
}
|
| 222 |
var ul = document.createElement('ul');
|
| 223 |
var sc = this;
|
| 224 |
|
| 225 |
word.suggestions.push('Edit...');
|
| 226 |
|
| 227 |
for (var i = 0; i < word.suggestions.length; i++) {
|
| 228 |
li = document.createElement('li');
|
| 229 |
li.innerHTML = word.suggestions[i];
|
| 230 |
li.onmousedown = function() { sc.clickSuggestion(this, word); };
|
| 231 |
li.onmouseover = function() { sc.highlight(this, word); };
|
| 232 |
li.onmouseout = function() { sc.unhighlight(this, word); };
|
| 233 |
if (word.suggestions[i] == 'Edit...') {
|
| 234 |
li.className = 'spellcheck-edit';
|
| 235 |
}
|
| 236 |
ul.appendChild(li);
|
| 237 |
}
|
| 238 |
|
| 239 |
word.suggestions.pop();
|
| 240 |
|
| 241 |
var pos = absolutePosition(word);
|
| 242 |
div.style.top = (pos.y + word.offsetHeight) +'px';
|
| 243 |
div.style.left = pos.x +'px';
|
| 244 |
|
| 245 |
div.appendChild(ul);
|
| 246 |
document.getElementsByTagName('body')[0].appendChild(div);
|
| 247 |
}
|
| 248 |
|
| 249 |
/**
|
| 250 |
* Handles highlighting of a word suggestion
|
| 251 |
*/
|
| 252 |
spellchecker.prototype.highlight = function (node) {
|
| 253 |
removeClass(this.highlighted, 'selected');
|
| 254 |
addClass(node, 'selected');
|
| 255 |
this.highlighted = node;
|
| 256 |
}
|
| 257 |
|
| 258 |
/**
|
| 259 |
* Removes highlighting of suggestions
|
| 260 |
*/
|
| 261 |
spellchecker.prototype.unhighlight = function (node) {
|
| 262 |
removeClass(node, 'selected');
|
| 263 |
this.highlighted = false;
|
| 264 |
}
|
| 265 |
|
| 266 |
/**
|
| 267 |
* Handles clicking of spellcheck suggestions
|
| 268 |
*/
|
| 269 |
spellchecker.prototype.clickSuggestion = function(suggestion, word) {
|
| 270 |
if (suggestion.innerHTML == 'Edit...') {
|
| 271 |
var input = document.createElement('input');
|
| 272 |
input.value = word.innerHTML;
|
| 273 |
input.style.width = (word.offsetWidth + 10) + 'px';
|
| 274 |
input.onchange = function () { word.innerHTML = this.value; };
|
| 275 |
word.parentNode.insertBefore(input, word);
|
| 276 |
word.style.display = 'none';
|
| 277 |
}
|
| 278 |
else {
|
| 279 |
word.innerHTML = suggestion.innerHTML;
|
| 280 |
word.className = 'spellcheck-correction';
|
| 281 |
}
|
| 282 |
removeNode(document.getElementById('spellcheck-suggestions'));
|
| 283 |
}
|
| 284 |
|
| 285 |
/**
|
| 286 |
* Converts some characters to HTML
|
| 287 |
*/
|
| 288 |
spellchecker.prototype.checkPlain = function (string) {
|
| 289 |
string = eregReplace("&", "&", string);
|
| 290 |
string = eregReplace(">", ">", string);
|
| 291 |
string = eregReplace("<", "<", string);
|
| 292 |
string = eregReplace("\n", "<br />", string);
|
| 293 |
if (string == ' ') {
|
| 294 |
string = ' ';
|
| 295 |
}
|
| 296 |
return string;
|
| 297 |
}
|