/** * DokuWiki Spellcheck AJAX clientside script * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Andreas Gohr */ /** * Licence info: This spellchecker is inspired by code by Garrison Locke available * at http://www.broken-notebook.com/spell_checker/index.php (licensed under the Terms * of an BSD license). The code in this file was nearly completly rewritten for DokuWiki * and is licensed under GPL version 2 (See COPYING for details). * * Original Copyright notice follows: * * Copyright (c) 2005, Garrison Locke * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the http://www.broken-notebook.com nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ /* * Uses some general functions defined elsewhere. Here is a list: * * Defined in script.js: * * findPosX() * findPosY() * addEvent() * * Defined in edit.js: * * createToolButton() */ /** * quotes single quotes * * @author Andreas Gohr */ function qquote(str){ return str.split('\'').join('\\\''); } /** * AJAX Spellchecker Class * * Note to some function use a hardcoded instance named ajax_spell to make * references to object members. Used Object-IDs are hardcoded in the init() * method. * * @author Andreas Gohr * @author Garrison Locke */ function ajax_spell_class(){ this.inited = false; this.utf8ok = 1; this.handler = DOKU_BASE+'lib/exe/spellcheck.php'; // to hold the page objects (initialized with init()) this.textboxObj = null; this.showboxObj = null; this.suggestObj = null; this.editbarObj = null; this.buttonObj = null; this.imageObj = null; // hold translations this.txtStart = 'Check Spelling'; this.txtStop = 'Resume Editing'; this.txtRun = 'Checking...'; this.txtNoErr = 'No Mistakes'; this.txtNoSug = 'No Suggestions'; this.txtChange= 'Change'; /** * Initializes everything * * Call after the page was setup. Hardcoded element IDs here. * * @author Andreas Gohr */ this.init = function(txtStart,txtStop,txtRun,txtNoErr,txtNoSug,txtChange){ // don't run twice if (this.inited) return; this.inited = true; // check for AJAX availability var ajax = new sack(this.handler); if(ajax.failed) return; // get Elements this.textboxObj = document.getElementById('wikitext'); this.editbarObj = document.getElementById('wikieditbar'); this.showboxObj = document.getElementById('spell_result'); this.suggestObj = document.getElementById('spell_suggest'); // set Translation Strings this.txtStart = txtStart; this.txtStop = txtStop; this.txtRun = txtRun; this.txtNoErr = txtNoErr; this.txtNoSug = txtNoSug; this.txtChange= txtChange; // create ToolBar Button with ID and add it to the toolbar with null action var toolbarObj = document.getElementById('toolbar'); this.buttonObj = createToolButton('spellcheck.png',txtStart,'k','spellcheck'); this.buttonObj.onclick = function(){return false;}; toolbarObj.appendChild(this.buttonObj); this.imageObj = document.getElementById('spellcheck_ico'); // start UTF-8 compliance test - send an UTF-8 char and see what comes back ajax.AjaxFailedAlert = ''; ajax.encodeURIString = false; ajax.onCompletion = this.initReady; ajax.runAJAX('call=utf8test&data='+encodeURIComponent('ü')); // second part of initialisation is in initReady() function } /** * Eventhandler for click objects anywhere on the document * * Disables the suggestion box * * @author Andreas Gohr * @author Garrison Locke */ this.docClick = function(e){ // what was clicked? try{ target = window.event.srcElement; }catch(ex){ target = e.target; } if (target.id != ajax_spell.suggestObj.id){ ajax_spell.suggestObj.style.display = "none"; } } /** * Changes the Spellchecker link according to the given mode * * @author Andreas Gohr */ this.setState = function(state){ switch (state){ case 'stop': ajax_spell.buttonObj.onclick = function(){ ajax_spell.resume(); return false; }; ajax_spell.buttonObj.title = ajax_spell.txtStop; ajax_spell.buttonObj.accessKey = ''; ajax_spell.imageObj.src = DOKU_BASE+'lib/images/toolbar/spellstop.png'; break; case 'noerr': ajax_spell.buttonObj.onclick = function(){ajax_spell.setState('start'); return false; }; ajax_spell.buttonObj.title = ajax_spell.txtNoErr; ajax_spell.buttonObj.accessKey = ''; ajax_spell.imageObj.src = DOKU_BASE+'lib/images/toolbar/spellnoerr.png'; break; case 'run': ajax_spell.buttonObj.onclick = function(){return false;}; ajax_spell.buttonObj.title = ajax_spell.txtRun; ajax_spell.buttonObj.accessKey = ''; ajax_spell.imageObj.src = DOKU_BASE+'lib/images/toolbar/spellwait.gif'; break; default: ajax_spell.buttonObj.onclick = function(){ ajax_spell.run(); return false; }; ajax_spell.buttonObj.title = ajax_spell.txtStart+' [ALT-K]'; ajax_spell.buttonObj.accessKey = 'k'; ajax_spell.imageObj.src = DOKU_BASE+'lib/images/toolbar/spellcheck.png'; break; } } /** * Replaces a word identified by id with its correction given in word * * @author Garrison Locke */ this.correct = function (id, word){ var obj = document.getElementById('spell_error'+id); obj.innerHTML = decodeURIComponent(word); obj.style.color = "#005500"; this.suggestObj.style.display = "none"; } /** * Opens a prompt to let the user change the word her self * * @author Andreas Gohr */ this.ask = function(id){ var word = document.getElementById('spell_error'+id).innerHTML; word = prompt(this.txtChange,word); if(word){ this.correct(id,encodeURIComponent(word)); } } /** * Displays the suggestions for a misspelled word * * @author Andreas Gohr * @author Garrison Locke */ this.suggest = function(){ var args = this.suggest.arguments; if(!args[0]) return; var id = args[0]; // set position of the popup this.suggestObj.style.display = "none"; var x = findPosX('spell_error'+id); var y = findPosY('spell_error'+id); // handle scrolling if(is_opera){ var scrollPos = 0; //FIXME how to do this without browser sniffing? }else{ var scrollPos = this.showboxObj.scrollTop; } this.suggestObj.style.left = x+'px'; this.suggestObj.style.top = (y+16-scrollPos)+'px'; // handle suggestions var text = ''; if(args.length == 1){ text += this.txtNoSug+'
'; }else{ for(var i=1; i'; text += args[i]; text += '
'; } } // add option for manual edit text += ''; text += '['+this.txtChange+']'; text += '
'; this.suggestObj.innerHTML = text; this.suggestObj.style.display = "block"; } // --- Callbacks --- /** * Callback. Called after the object was initialized and UTF-8 tested * Inside the callback 'this' is the SACK object!! * * @author Andreas Gohr */ this.initReady = function(){ var data = this.response; //test for UTF-8 compliance (will fail for konqueror) if(data != 'ü'){ ajax_spell.utf8ok = 0; } // register click event addEvent(document,'onclick',ajax_spell.docClick); // register focus event addEvent(ajax_spell.textboxObj,'onfocus',ajax_spell.setState); // get started ajax_spell.setState('start'); } /** * Callback. Called after finishing spellcheck. * Inside the callback 'this' is the SACK object!! * * @author Andreas Gohr */ this.start = function(){ var data = this.response; var error = data.charAt(0); data = data.substring(1); if(error == '1'){ ajax_spell.setState('stop'); // convert numeric entities back to UTF-8 if needed if(!ajax_spell.utf8ok){ data = data.replace(/&#(\d+);/g, function(whole,match1) { return String.fromCharCode(+match1); }); } // replace textbox through div ajax_spell.showboxObj.innerHTML = data; ajax_spell.showboxObj.style.width = ajax_spell.textboxObj.style.width; ajax_spell.showboxObj.style.height = ajax_spell.textboxObj.style.height; ajax_spell.textboxObj.style.display = 'none'; ajax_spell.editbarObj.style.visibility = 'hidden'; ajax_spell.showboxObj.style.display = 'block'; }else{ if(error == '2'){ alert(data); } ajax_spell.textboxObj.disabled = false; ajax_spell.editbarObj.style.visibility = 'visible'; ajax_spell.setState('noerr'); } } /** * Callback. Gets called by resume() - switches back to edit mode * Inside the callback 'this' is the SACK object!! * * @author Andreas Gohr */ this.stop = function(){ var data = this.response; // convert numeric entities back to UTF-8 if needed if(!ajax_spell.utf8ok){ data = data.replace(/&#(\d+);/g, function(whole,match1) { return String.fromCharCode(+match1); }); // now remove & protection data = data.replace(/&/g,'&'); } // replace div with textbox again ajax_spell.textboxObj.value = data; ajax_spell.textboxObj.disabled = false; ajax_spell.showboxObj.style.display = 'none'; ajax_spell.textboxObj.style.display = 'block'; ajax_spell.editbarObj.style.visibility = 'visible'; ajax_spell.showboxObj.innerHTML = ''; ajax_spell.setState('start'); } // --- Callers --- /** * Starts the spellchecking by sending an AJAX request * * @author Andreas Gohr */ this.run = function(){ ajax_spell.setState('run'); ajax_spell.textboxObj.disabled = true; var ajax = new sack(ajax_spell.handler); ajax.AjaxFailedAlert = ''; ajax.encodeURIString = false; ajax.onCompletion = this.start; ajax.runAJAX('call=check&utf8='+ajax_spell.utf8ok+ '&data='+encodeURIComponent(ajax_spell.textboxObj.value)); } /** * Rewrites the HTML back to text again using an AJAX request * * @author Andreas Gohr */ this.resume = function(){ ajax_spell.setState('run'); var text = ajax_spell.showboxObj.innerHTML; if(text != ''){ var ajax = new sack(ajax_spell.handler); ajax.AjaxFailedAlert = ''; ajax.encodeURIString = false; ajax.onCompletion = ajax_spell.stop; ajax.runAJAX('call=resume&utf8='+ajax_spell.utf8ok+ '&data='+encodeURIComponent(text)); } } } // create the global object ajax_spell = new ajax_spell_class(); //Setup VIM: ex: et ts=2 enc=utf-8 :