thomascube
2009-05-18 37586429a92b1f2755cc888ab742ca6bbdf51a41
commit | author | age
dd53e2 1 /*
309d2f 2  SpellCheck
A 3     jQuery'fied spell checker based on GoogieSpell 4.0
ffa6c1 4  Copyright Amir Salihefendic 2006
309d2f 5  Copyright Aleksander Machniak 2009
ffa6c1 6      LICENSE
309d2f 7          GPL
A 8      AUTHORS
ffa6c1 9          4mir Salihefendic (http://amix.dk) - amix@amix.dk
309d2f 10      Aleksander Machniak - alec [at] alec.pl
A 11 */
12
13 var SPELL_CUR_LANG = null;
14 var GOOGIE_DEFAULT_LANG = 'en';
dd53e2 15
T 16 function GoogieSpell(img_dir, server_url) {
309d2f 17     var ref = this;
dd53e2 18
309d2f 19     this.array_keys = function(arr) {
A 20     var res = [];
21     for (var key in arr) { res.push([key]); }
22     return res;
23     }
24     
25     var cookie_value = getCookie('language');
26     GOOGIE_CUR_LANG = cookie_value != null ? cookie_value : GOOGIE_DEFAULT_LANG;
dd53e2 27
ffa6c1 28     this.img_dir = img_dir;
T 29     this.server_url = server_url;
dd53e2 30
309d2f 31     this.org_lang_to_word = {
A 32     "da": "Dansk", "de": "Deutsch", "en": "English",
33         "es": "Español", "fr": "Français", "it": "Italiano", 
34         "nl": "Nederlands", "pl": "Polski", "pt": "Português",
35         "fi": "Suomi", "sv": "Svenska"
36     };
ffa6c1 37     this.lang_to_word = this.org_lang_to_word;
309d2f 38     this.langlist_codes = this.array_keys(this.lang_to_word);
ffa6c1 39     this.show_change_lang_pic = true;
309d2f 40     this.change_lang_pic_placement = 'right';
ffa6c1 41     this.report_state_change = true;
dd53e2 42
ffa6c1 43     this.ta_scroll_top = 0;
T 44     this.el_scroll_top = 0;
dd53e2 45
ffa6c1 46     this.lang_chck_spell = "Check spelling";
T 47     this.lang_revert = "Revert to";
48     this.lang_close = "Close";
49     this.lang_rsm_edt = "Resume editing";
50     this.lang_no_error_found = "No spelling errors found";
51     this.lang_no_suggestions = "No suggestions";
dd53e2 52     
309d2f 53     this.show_spell_img = false; // roundcube mod.
ffa6c1 54     this.decoration = true;
T 55     this.use_close_btn = true;
56     this.edit_layer_dbl_click = true;
57     this.report_ta_not_found = true;
dd53e2 58
ffa6c1 59     //Extensions
T 60     this.custom_ajax_error = null;
61     this.custom_no_spelling_error = null;
62     this.custom_menu_builder = []; //Should take an eval function and a build menu function
63     this.custom_item_evaulator = null; //Should take an eval function and a build menu function
64     this.extra_menu_items = [];
65     this.custom_spellcheck_starter = null;
66     this.main_controller = true;
dd53e2 67
ffa6c1 68     //Observers
T 69     this.lang_state_observer = null;
70     this.spelling_state_observer = null;
71     this.show_menu_observer = null;
72     this.all_errors_fixed_observer = null;
dd53e2 73
ffa6c1 74     //Focus links - used to give the text box focus
T 75     this.use_focus = false;
76     this.focus_link_t = null;
77     this.focus_link_b = null;
78
79     //Counters
80     this.cnt_errors = 0;
81     this.cnt_errors_fixed = 0;
309d2f 82     
ffa6c1 83     //Set document on click to hide the language and error menu
309d2f 84     $(document).bind('click', function(e) {
A 85         if($(e.target).attr('googie_action_btn') != '1' && ref.isLangWindowShown())
86         ref.hideLangWindow();
87     if($(e.target).attr('googie_action_btn') != '1' && ref.isErrorWindowShown())
88             ref.hideErrorWindow();
89     });
dd53e2 90
T 91
309d2f 92 this.decorateTextarea = function(id) {
A 93     this.text_area = typeof(id) == 'string' ? document.getElementById(id) : id;
dd53e2 94
309d2f 95     if (this.text_area) {
A 96         if (!this.spell_container && this.decoration) {
97             var table = document.createElement('TABLE');
98             var tbody = document.createElement('TBODY');
99             var tr = document.createElement('TR');
100             var spell_container = document.createElement('TD');
dd53e2 101
309d2f 102             var r_width = this.isDefined(this.force_width) ? this.force_width : this.text_area.offsetWidth;
A 103             var r_height = this.isDefined(this.force_height) ? this.force_height : 16;
dd53e2 104
ffa6c1 105             tr.appendChild(spell_container);
T 106             tbody.appendChild(tr);
309d2f 107             $(table).append(tbody).insertBefore(this.text_area).width('100%').height(r_height);
A 108             $(spell_container).height(r_height).width(r_width).css('text-align', 'right');
ffa6c1 109
309d2f 110             this.spell_container = spell_container;
ffa6c1 111         }
T 112
113         this.checkSpellingState();
dd53e2 114     }
ffa6c1 115     else 
309d2f 116         if (this.report_ta_not_found)
A 117             alert('Text area not found');
dd53e2 118 }
T 119
ffa6c1 120 //////
T 121 // API Functions (the ones that you can call)
122 /////
309d2f 123 this.setSpellContainer = function(id) {
A 124     this.spell_container = typeof(id) == 'string' ? document.getElementById(id) : id;
125
dd53e2 126 }
T 127
309d2f 128 this.setLanguages = function(lang_dict) {
ffa6c1 129     this.lang_to_word = lang_dict;
309d2f 130     this.langlist_codes = this.array_keys(lang_dict);
dd53e2 131 }
T 132
309d2f 133 this.setCurrentLanguage = function(lan_code) {
A 134     GOOGIE_CUR_LANG = lan_code;
135
136     //Set cookie
137     var now = new Date();
138     now.setTime(now.getTime() + 365 * 24 * 60 * 60 * 1000);
139     setCookie('language', lan_code, now);
140 }
141
142 this.setForceWidthHeight = function(width, height) {
143     // Set to null if you want to use one of them
ffa6c1 144     this.force_width = width;
T 145     this.force_height = height;
146 }
2c6337 147
309d2f 148 this.setDecoration = function(bool) {
ffa6c1 149     this.decoration = bool;
T 150 }
dd53e2 151
309d2f 152 this.dontUseCloseButtons = function() {
ffa6c1 153     this.use_close_btn = false;
T 154 }
dd53e2 155
309d2f 156 this.appendNewMenuItem = function(name, call_back_fn, checker) {
ffa6c1 157     this.extra_menu_items.push([name, call_back_fn, checker]);
T 158 }
159
309d2f 160 this.appendCustomMenuBuilder = function(eval, builder) {
ffa6c1 161     this.custom_menu_builder.push([eval, builder]);
T 162 }
163
309d2f 164 this.setFocus = function() {
ffa6c1 165     try {
T 166         this.focus_link_b.focus();
167         this.focus_link_t.focus();
168         return true;
169     }
170     catch(e) {
171         return false;
172     }
173 }
174
175
176 //////
177 // Set functions (internal)
178 /////
309d2f 179 this.setStateChanged = function(current_state) {
ffa6c1 180     this.state = current_state;
309d2f 181     if (this.spelling_state_observer != null && this.report_state_change)
ffa6c1 182         this.spelling_state_observer(current_state, this);
T 183 }
184
309d2f 185 this.setReportStateChange = function(bool) {
ffa6c1 186     this.report_state_change = bool;
T 187 }
188
189
190 //////
191 // Request functions
192 /////
309d2f 193 this.getUrl = function() {
ffa6c1 194     return this.server_url + GOOGIE_CUR_LANG;
T 195 }
196
309d2f 197 this.escapeSpecial = function(val) {
ffa6c1 198     return val.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
T 199 }
200
309d2f 201 this.createXMLReq = function (text) {
A 202     return '<?xml version="1.0" encoding="utf-8" ?>'
203     + '<spellrequest textalreadyclipped="0" ignoredups="0" ignoredigits="1" ignoreallcaps="1">'
204     + '<text>' + text + '</text></spellrequest>';
ffa6c1 205 }
T 206
309d2f 207 this.spellCheck = function(ignore) {
ffa6c1 208     this.cnt_errors_fixed = 0;
T 209     this.cnt_errors = 0;
309d2f 210     this.setStateChanged('checking_spell');
ffa6c1 211
309d2f 212     if (this.main_controller)
ffa6c1 213         this.appendIndicator(this.spell_span);
T 214
215     this.error_links = [];
216     this.ta_scroll_top = this.text_area.scrollTop;
217     this.ignore = ignore;
309d2f 218     this.hideLangWindow();
ffa6c1 219
309d2f 220     if ($(this.text_area).val() == '' || ignore) {
A 221         if (!this.custom_no_spelling_error)
222             this.flashNoSpellingErrorState();
ffa6c1 223         else
309d2f 224             this.custom_no_spelling_error(this);
A 225         this.removeIndicator();
226         return;
ffa6c1 227     }
T 228     
229     this.createEditLayer(this.text_area.offsetWidth, this.text_area.offsetHeight);
230     this.createErrorWindow();
309d2f 231     $('body').append(this.error_window);
ffa6c1 232
T 233     try { netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead"); } 
234     catch (e) { }
235
309d2f 236     if (this.main_controller)
A 237         $(this.spell_span).unbind('click');
ffa6c1 238
309d2f 239     this.orginal_text = $(this.text_area).val();
A 240     var req_text = this.escapeSpecial(this.orginal_text);
241     var ref = this;
ffa6c1 242
309d2f 243     $.ajax({ type: 'POST', url: this.getUrl(),
A 244     data: this.createXMLReq(req_text), dataType: 'text',
245     error: function(o) {
246             if (ref.custom_ajax_error)
247             ref.custom_ajax_error(ref);
248             else
249             alert('An error was encountered on the server. Please try again later.');
250             if (ref.main_controller) {
251             $(ref.spell_span).remove();
252             ref.removeIndicator();
253             }
254             ref.checkSpellingState();
255     },
256         success: function(data) {
257         var r_text = data;
258             ref.results = ref.parseResult(r_text);
259             if (r_text.match(/<c.*>/) != null) {
260             //Before parsing be sure that errors were found
261             ref.showErrorsInIframe();
262             ref.resumeEditingState();
263             } else {
264             if (!ref.custom_no_spelling_error)
265             ref.flashNoSpellingErrorState();
266             else
267                     ref.custom_no_spelling_error(ref);
268             }
269             ref.removeIndicator();
270     }
271     });
ffa6c1 272 }
T 273
274
275 //////
276 // Spell checking functions
277 /////
309d2f 278 this.parseResult = function(r_text) {
A 279     // Returns an array: result[item] -> ['attrs'], ['suggestions']
ffa6c1 280     var re_split_attr_c = /\w+="(\d+|true)"/g;
T 281     var re_split_text = /\t/g;
282
283     var matched_c = r_text.match(/<c[^>]*>[^<]*<\/c>/g);
284     var results = new Array();
285
309d2f 286     if (matched_c == null)
ffa6c1 287         return results;
T 288     
309d2f 289     for (var i=0; i < matched_c.length; i++) {
ffa6c1 290         var item = new Array();
T 291         this.errorFound();
292
293         //Get attributes
294         item['attrs'] = new Array();
295         var split_c = matched_c[i].match(re_split_attr_c);
309d2f 296         for (var j=0; j < split_c.length; j++) {
ffa6c1 297             var c_attr = split_c[j].split(/=/);
T 298             var val = c_attr[1].replace(/"/g, '');
309d2f 299             item['attrs'][c_attr[0]] = val != 'true' ? parseInt(val) : val;
ffa6c1 300         }
T 301
302         //Get suggestions
303         item['suggestions'] = new Array();
309d2f 304         var only_text = matched_c[i].replace(/<[^>]*>/g, '');
ffa6c1 305         var split_t = only_text.split(re_split_text);
309d2f 306         for (var k=0; k < split_t.length; k++) {
A 307             if(split_t[k] != '')
308             item['suggestions'].push(split_t[k]);
309         }
ffa6c1 310         results.push(item);
T 311     }
312     return results;
313 }
314
315
316 //////
317 // Error menu functions
318 /////
309d2f 319 this.createErrorWindow = function() {
A 320     this.error_window = document.createElement('DIV');
321     $(this.error_window).addClass('googie_window').attr('googie_action_btn', '1');
ffa6c1 322 }
T 323
309d2f 324 this.isErrorWindowShown = function() {
A 325     return $(this.error_window).is(':visible');
ffa6c1 326 }
T 327
309d2f 328 this.hideErrorWindow = function() {
A 329     $(this.error_window).css('visibility', 'hidden');
330     $(this.error_window_iframe).css('visibility', 'hidden');
ffa6c1 331 }
T 332
309d2f 333 this.updateOrginalText = function(offset, old_value, new_value, id) {
ffa6c1 334     var part_1 = this.orginal_text.substring(0, offset);
T 335     var part_2 = this.orginal_text.substring(offset+old_value.length);
336     this.orginal_text = part_1 + new_value + part_2;
309d2f 337     $(this.text_area).val(this.orginal_text);
ffa6c1 338     var add_2_offset = new_value.length - old_value.length;
309d2f 339     for (var j=0; j < this.results.length; j++) {
ffa6c1 340         //Don't edit the offset of the current item
309d2f 341         if (j != id && j > id)
ffa6c1 342             this.results[j]['attrs']['o'] += add_2_offset;
T 343     }
344 }
345
309d2f 346 this.saveOldValue = function(elm, old_value) {
ffa6c1 347     elm.is_changed = true;
T 348     elm.old_value = old_value;
349 }
350
309d2f 351 this.createListSeparator = function() {
A 352     var td = document.createElement('TD');
353     var tr = document.createElement('TR');
ffa6c1 354
309d2f 355     $(td).html(' ').attr('googie_action_btn', '1')
A 356     .css({'cursor': 'default', 'font-size': '3px', 'border-top': '1px solid #ccc', 'padding-top': '3px'});
357     tr.appendChild(td);
358
359     return tr;
ffa6c1 360 }
T 361
309d2f 362 this.correctError = function(id, elm, l_elm, rm_pre_space) {
ffa6c1 363     var old_value = elm.innerHTML;
375864 364     var new_value = l_elm.nodeType == 3 ? l_elm.nodeValue : l_elm.innerHTML;
ffa6c1 365     var offset = this.results[id]['attrs']['o'];
T 366
309d2f 367     if (rm_pre_space) {
ffa6c1 368         var pre_length = elm.previousSibling.innerHTML;
T 369         elm.previousSibling.innerHTML = pre_length.slice(0, pre_length.length-1);
370         old_value = " " + old_value;
371         offset--;
372     }
373
374     this.hideErrorWindow();
375     this.updateOrginalText(offset, old_value, new_value, id);
376
309d2f 377     $(elm).html(new_value).css('color', 'green').attr('is_corrected', true);
ffa6c1 378
T 379     this.results[id]['attrs']['l'] = new_value.length;
380
309d2f 381     if (!this.isDefined(elm.old_value))
ffa6c1 382         this.saveOldValue(elm, old_value);
T 383     
384     this.errorFixed();
385 }
386
309d2f 387 this.showErrorWindow = function(elm, id) {
A 388     if (this.show_menu_observer)
ffa6c1 389         this.show_menu_observer(this);
T 390
309d2f 391     var ref = this;
A 392     var pos = $(elm).offset();
393     pos.top -= this.edit_layer.scrollTop;
ffa6c1 394
309d2f 395     $(this.error_window).css({'visibility': 'visible',
A 396     'top': (pos.top+20)+'px', 'left': (pos.left)+'px'}).html('');
ffa6c1 397
309d2f 398     var table = document.createElement('TABLE');
A 399     var list = document.createElement('TBODY');
ffa6c1 400
309d2f 401     $(table).addClass('googie_list').attr('googie_action_btn', '1');
ffa6c1 402
T 403     //Check if we should use custom menu builder, if not we use the default
404     var changed = false;
309d2f 405     if (this.custom_menu_builder != []) {
A 406         for (var k=0; k<this.custom_menu_builder.length; k++) {
ffa6c1 407             var eb = this.custom_menu_builder[k];
T 408             if(eb[0]((this.results[id]))){
409                 changed = eb[1](this, list, elm);
410                 break;
411             }
412         }
413     }
309d2f 414     if (!changed) {
ffa6c1 415         //Build up the result list
T 416         var suggestions = this.results[id]['suggestions'];
417         var offset = this.results[id]['attrs']['o'];
418         var len = this.results[id]['attrs']['l'];
419
309d2f 420         if (suggestions.length == 0) {
A 421             var row = document.createElement('TR');
422             var item = document.createElement('TD');
423             var dummy = document.createElement('SPAN');
424
425             $(dummy).text(this.lang_no_suggestions);
426             $(item).attr('googie_action_btn', '1').css('cursor', 'default');
427
428             item.appendChild(dummy);
ffa6c1 429             row.appendChild(item);
T 430             list.appendChild(row);
431         }
432
309d2f 433         for (i=0; i < suggestions.length; i++) {
A 434             var row = document.createElement('TR');
435             var item = document.createElement('TD');
436             var dummy = document.createElement('SPAN');
437
438             $(dummy).html(suggestions[i]);
ffa6c1 439             
309d2f 440             $(item).bind('mouseover', this.item_onmouseover)
A 441             .bind('mouseout', this.item_onmouseout)
442             .bind('click', function(e) { ref.correctError(id, elm, e.target.firstChild) });
ffa6c1 443
309d2f 444             item.appendChild(dummy);
ffa6c1 445             row.appendChild(item);
T 446             list.appendChild(row);
447         }
448
449         //The element is changed, append the revert
309d2f 450         if (elm.is_changed && elm.innerHTML != elm.old_value) {
ffa6c1 451             var old_value = elm.old_value;
309d2f 452             var revert_row = document.createElement('TR');
A 453             var revert = document.createElement('TD');
454             var rev_span = document.createElement('SPAN');
455         
456         $(rev_span).addClass('googie_list_revert').html(this.lang_revert + ' ' + old_value);
ffa6c1 457
309d2f 458             $(revert).bind('mouseover', this.item_onmouseover)
A 459             .bind('mouseout', this.item_onmouseout)
460             .bind('click', function(e) {
461                     ref.updateOrginalText(offset, elm.innerHTML, old_value, id);
462                     $(elm).attr('is_corrected', true).css('color', '#b91414').html(old_value);
463                     ref.hideErrorWindow();
464             });
465
ffa6c1 466             revert.appendChild(rev_span);
T 467             revert_row.appendChild(revert);
468             list.appendChild(revert_row);
469         }
470         
471         //Append the edit box
309d2f 472         var edit_row = document.createElement('TR');
A 473         var edit = document.createElement('TD');
474         var edit_input = document.createElement('INPUT');
475         var ok_pic = document.createElement('IMG');
476     var edit_form = document.createElement('FORM');
ffa6c1 477
T 478         var onsub = function () {
309d2f 479             if (edit_input.value != '') {
A 480                 if (!ref.isDefined(elm.old_value))
481                     ref.saveOldValue(elm, elm.innerHTML);
ffa6c1 482
309d2f 483                 ref.updateOrginalText(offset, elm.innerHTML, edit_input.value, id);
A 484         $(elm).attr('is_corrected', true).css('color', 'green').html(edit_input.value);
485                 ref.hideErrorWindow();
ffa6c1 486             }
T 487             return false;
488         };
489
309d2f 490     $(edit_input).width(120).css({'margin': 0, 'padding': 0});
A 491     $(edit_input).val(elm.innerHTML).attr('googie_action_btn', '1');
492     $(edit).css('cursor', 'default').attr('googie_action_btn', '1');
ffa6c1 493
309d2f 494     $(ok_pic).attr('src', this.img_dir + 'ok.gif')
A 495         .width(32).height(16)
496         .css({'cursor': 'pointer', 'margin-left': '2px', 'margin-right': '2px'})
497         .bind('click', onsub);
498
499         $(edit_form).attr('googie_action_btn', '1')
500         .css({'margin': 0, 'padding': 0, 'cursor': 'default', 'white-space': 'nowrap'})
501         .bind('submit', onsub);
ffa6c1 502         
309d2f 503     edit_form.appendChild(edit_input);
A 504     edit_form.appendChild(ok_pic);
ffa6c1 505         edit.appendChild(edit_form);
T 506         edit_row.appendChild(edit);
507         list.appendChild(edit_row);
508
509         //Append extra menu items
309d2f 510         if (this.extra_menu_items.length > 0)
A 511         list.appendChild(this.createListSeparator());
512     
ffa6c1 513         var loop = function(i) {
309d2f 514                 if (i < ref.extra_menu_items.length) {
A 515                     var e_elm = ref.extra_menu_items[i];
ffa6c1 516
309d2f 517                     if (!e_elm[2] || e_elm[2](elm, ref)) {
A 518                         var e_row = document.createElement('TR');
519                         var e_col = document.createElement('TD');
ffa6c1 520
309d2f 521             $(e_col).html(e_elm[0])
A 522                             .bind('mouseover', ref.item_onmouseover)
523                             .bind('mouseout', ref.item_onmouseout)
524                 .bind('click', function() { return e_elm[1](elm, ref) });
525             
526             e_row.appendChild(e_col);
527                         list.appendChild(e_row);
ffa6c1 528                     }
T 529                     loop(i+1);
530                 }
531         }
309d2f 532     
ffa6c1 533         loop(0);
T 534         loop = null;
535
536         //Close button
309d2f 537         if (this.use_close_btn) {
A 538             list.appendChild(this.createCloseButton(this.hideErrorWindow));
ffa6c1 539         }
T 540     }
541
542     table.appendChild(list);
543     this.error_window.appendChild(table);
544
545     //Dummy for IE - dropdown bug fix
309d2f 546     if ($.browser.msie) {
A 547     if (!this.error_window_iframe) {
548             var iframe = $('<iframe>').css('position', 'absolute').css('z-index', 0);
549         $('body').append(iframe);
550             this.error_window_iframe = iframe;
551         }
552     
553     $(this.error_window_iframe).css({'visibility': 'visible',
554         'top': this.error_window.offsetTop, 'left': this.error_window.offsetLeft,
555             'width': this.error_window.offsetWidth, 'height': this.error_window.offsetHeight});
ffa6c1 556     }
T 557 }
558
559
560 //////
561 // Edit layer (the layer where the suggestions are stored)
562 //////
309d2f 563 this.createEditLayer = function(width, height) {
A 564     this.edit_layer = document.createElement('DIV');
565     $(this.edit_layer).addClass('googie_edit_layer').width(width-10).height(height);
ffa6c1 566
309d2f 567     if (this.text_area.nodeName != 'INPUT' || $(this.text_area).val() == '') {
A 568         $(this.edit_layer).css('overflow', 'auto').height(height-4);
569     } else {
570         $(this.edit_layer).css('overflow', 'hidden');
dd53e2 571     }
T 572
309d2f 573     var ref = this;
A 574     if (this.edit_layer_dbl_click) {
575         $(this.edit_layer).bind('click', function(e) {
576             if (e.target.className != 'googie_link' && !ref.isErrorWindowShown()) {
577                 ref.resumeEditing();
ffa6c1 578                 var fn1 = function() {
309d2f 579                     $(ref.text_area).focus();
ffa6c1 580                     fn1 = null;
T 581                 };
309d2f 582                 window.setTimeout(fn1, 10);
ffa6c1 583             }
T 584             return false;
309d2f 585         });
ffa6c1 586     }
T 587 }
588
309d2f 589 this.resumeEditing = function() {
A 590     this.setStateChanged('ready');
ffa6c1 591
309d2f 592     if (this.edit_layer)
ffa6c1 593         this.el_scroll_top = this.edit_layer.scrollTop;
T 594
595     this.hideErrorWindow();
596
309d2f 597     if (this.main_controller)
A 598         $(this.spell_span).removeClass().addClass('googie_no_style');
ffa6c1 599
309d2f 600     if (!this.ignore) {
A 601         if (this.use_focus) {
602             $(this.focus_link_t).remove();
603             $(this.focus_link_b).remove();
ffa6c1 604         }
T 605
309d2f 606         $(this.edit_layer).remove();
A 607         $(this.text_area).show();
ffa6c1 608
309d2f 609         if (this.el_scroll_top != undefined)
ffa6c1 610             this.text_area.scrollTop = this.el_scroll_top;
T 611     }
612     this.checkSpellingState(false);
613 }
614
309d2f 615 this.createErrorLink = function(text, id) {
A 616     var elm = document.createElement('SPAN');
617     var ref = this;
ffa6c1 618     var d = function (e) {
309d2f 619             ref.showErrorWindow(elm, id);
A 620             d = null;
621             return false;
ffa6c1 622     };
309d2f 623     
A 624     $(elm).html(text).addClass('googie_link').bind('click', d)
625     .attr({'googie_action_btn' : '1', 'g_id' : id, 'is_corrected' : false});
ffa6c1 626
T 627     return elm;
628 }
629
309d2f 630 this.createPart = function(txt_part) {
A 631     if (txt_part == " ")
632         return document.createTextNode(" ");
ffa6c1 633
309d2f 634     txt_part = this.escapeSpecial(txt_part);
ffa6c1 635     txt_part = txt_part.replace(/\n/g, "<br>");
T 636     txt_part = txt_part.replace(/    /g, " &nbsp;");
637     txt_part = txt_part.replace(/^ /g, "&nbsp;");
638     txt_part = txt_part.replace(/ $/g, "&nbsp;");
639     
309d2f 640     var span = document.createElement('SPAN');
A 641     $(span).html(txt_part);
642     return span;
ffa6c1 643 }
T 644
309d2f 645 this.showErrorsInIframe = function() {
A 646     var output = document.createElement('DIV')
ffa6c1 647     var pointer = 0;
T 648     var results = this.results;
649
309d2f 650     if (results.length > 0) {
A 651         for (var i=0; i < results.length; i++) {
ffa6c1 652             var offset = results[i]['attrs']['o'];
T 653             var len = results[i]['attrs']['l'];
654             var part_1_text = this.orginal_text.substring(pointer, offset);
309d2f 655             var part_1 = this.createPart(part_1_text);
A 656     
ffa6c1 657             output.appendChild(part_1);
T 658             pointer += offset - pointer;
659             
660             //If the last child was an error, then insert some space
661             var err_link = this.createErrorLink(this.orginal_text.substr(offset, len), i);
662             this.error_links.push(err_link);
663             output.appendChild(err_link);
664             pointer += len;
665         }
666         //Insert the rest of the orginal text
667         var part_2_text = this.orginal_text.substr(pointer, this.orginal_text.length);
309d2f 668         var part_2 = this.createPart(part_2_text);
ffa6c1 669
T 670         output.appendChild(part_2);
671     }
672     else
673         output.innerHTML = this.orginal_text;
674
309d2f 675     $(output).css('text-align', 'left');
A 676
ffa6c1 677     var me = this;
309d2f 678     if (this.custom_item_evaulator)
A 679         $.map(this.error_links, function(elm){me.custom_item_evaulator(me, elm)});
ffa6c1 680     
309d2f 681     $(this.edit_layer).append(output);
ffa6c1 682
309d2f 683     //Hide text area and show edit layer
A 684     $(this.text_area).hide();
685     $(this.edit_layer).insertBefore(this.text_area);
ffa6c1 686
309d2f 687     if (this.use_focus) {
ffa6c1 688         this.focus_link_t = this.createFocusLink('focus_t');
T 689         this.focus_link_b = this.createFocusLink('focus_b');
690
309d2f 691         $(this.focus_link_t).insertBefore(this.edit_layer);
A 692         $(this.focus_link_b).insertAfter(this.edit_layer);
ffa6c1 693     }
T 694
309d2f 695 //    this.edit_layer.scrollTop = this.ta_scroll_top;
ffa6c1 696 }
T 697
698
699 //////
700 // Choose language menu
701 //////
309d2f 702 this.createLangWindow = function() {
A 703     this.language_window = document.createElement('DIV');
704     $(this.language_window).addClass('googie_window')
705     .width(100).attr('googie_action_btn', '1');
ffa6c1 706
T 707     //Build up the result list
309d2f 708     var table = document.createElement('TABLE');
A 709     var list = document.createElement('TBODY');
710     var ref = this;
ffa6c1 711
309d2f 712     $(table).addClass('googie_list').width('100%');
ffa6c1 713     this.lang_elms = new Array();
T 714
309d2f 715     for (i=0; i < this.langlist_codes.length; i++) {
A 716         var row = document.createElement('TR');
717         var item = document.createElement('TD');
718         var span = document.createElement('SPAN');
719     
720     $(span).text(this.lang_to_word[this.langlist_codes[i]]);
ffa6c1 721         this.lang_elms.push(item);
T 722
309d2f 723         $(item).attr('googieId', this.langlist_codes[i])
A 724             .bind('click', function(e) {
725             ref.deHighlightCurSel();
726             ref.setCurrentLanguage($(this).attr('googieId'));
ffa6c1 727
309d2f 728             if (ref.lang_state_observer != null) {
A 729                     ref.lang_state_observer();
730             }
ffa6c1 731
309d2f 732             ref.highlightCurSel();
A 733             ref.hideLangWindow();
734             })
735             .bind('mouseover', function(e) { 
736             if (this.className != "googie_list_selected")
737                     this.className = "googie_list_onhover";
738             })
739             .bind('mouseout', function(e) { 
740             if (this.className != "googie_list_selected")
741                     this.className = "googie_list_onout"; 
742             });
ffa6c1 743
309d2f 744     item.appendChild(span);
ffa6c1 745         row.appendChild(item);
T 746         list.appendChild(row);
747     }
748
749     //Close button
309d2f 750     if (this.use_close_btn) {
A 751         list.appendChild(this.createCloseButton(function () { ref.hideLangWindow.apply(ref) }));
ffa6c1 752     }
T 753
754     this.highlightCurSel();
755
756     table.appendChild(list);
757     this.language_window.appendChild(table);
758 }
759
309d2f 760 this.isLangWindowShown = function() {
A 761     return $(this.language_window).is(':hidden');
ffa6c1 762 }
T 763
309d2f 764 this.hideLangWindow = function() {
A 765     $(this.language_window).css('visibility', 'hidden');
766     $(this.switch_lan_pic).removeClass().addClass('googie_lang_3d_on');
ffa6c1 767 }
T 768
309d2f 769 this.deHighlightCurSel = function() {
A 770     $(this.lang_cur_elm).removeClass().addClass('googie_list_onout');
ffa6c1 771 }
T 772
309d2f 773 this.highlightCurSel = function() {
A 774     if (GOOGIE_CUR_LANG == null)
ffa6c1 775         GOOGIE_CUR_LANG = GOOGIE_DEFAULT_LANG;
309d2f 776     for (var i=0; i < this.lang_elms.length; i++) {
A 777         if ($(this.lang_elms[i]).attr('googieId') == GOOGIE_CUR_LANG) {
ffa6c1 778             this.lang_elms[i].className = "googie_list_selected";
T 779             this.lang_cur_elm = this.lang_elms[i];
780         }
781         else {
782             this.lang_elms[i].className = "googie_list_onout";
783         }
784     }
785 }
786
309d2f 787 this.showLangWindow = function(elm) {
A 788     if (this.show_menu_observer)
ffa6c1 789         this.show_menu_observer(this);
T 790
791     this.createLangWindow();
309d2f 792     $('body').append(this.language_window);
ffa6c1 793
309d2f 794     var pos = $(elm).offset();
A 795     var top = pos.top + $(elm).height();
796     var left = this.change_lang_pic_placement == 'right' ? 
797     pos.left - 100 + $(elm).width() : pos.left + $(elm).width();
798
799     $(this.language_window).css({'visibility': 'visible', 'top' : top+'px','left' : left+'px'});
ffa6c1 800
T 801     this.highlightCurSel();
802 }
803
309d2f 804 this.createChangeLangPic = function() {
A 805     var img = $('<img>')
806     .attr({src: this.img_dir + 'change_lang.gif', 'alt': 'Change language', 'googie_action_btn': '1'});
ffa6c1 807
309d2f 808     var switch_lan = document.createElement('SPAN');
A 809     var ref = this;
ffa6c1 810
309d2f 811     $(switch_lan).addClass('googie_lang_3d_on')
A 812     .append(img)
813     .bind('click', function(e) {
814             var elm = this.tagName == 'IMG' ? this.parentNode : this;
815             if($(elm).hasClass('googie_lang_3d_click')) {
816             elm.className = 'googie_lang_3d_on';
817             ref.hideLangWindow();
818             }
819             else {
820             elm.className = 'googie_lang_3d_click';
821             ref.showLangWindow(elm);
822             }
823     });
824
ffa6c1 825     return switch_lan;
T 826 }
827
309d2f 828 this.createSpellDiv = function() {
A 829     var span = document.createElement('SPAN');
ffa6c1 830
309d2f 831     $(span).addClass('googie_check_spelling_link').text(this.lang_chck_spell);
A 832
833     if (this.show_spell_img) {
834     $(span).append(' ').append($('<img>').attr('src', this.img_dir + 'spellc.gif'));
835     }
836     return span;
ffa6c1 837 }
T 838
839
840 //////
841 // State functions
842 /////
309d2f 843 this.flashNoSpellingErrorState = function(on_finish) {
A 844     this.setStateChanged('no_error_found');
ffa6c1 845
309d2f 846     var ref = this;
A 847     if (this.main_controller) {
848     var no_spell_errors;
849     if (on_finish) {
850             var fn = function() {
851             on_finish();
852             ref.checkSpellingState();
853             };
854             no_spell_errors = fn;
855     }
856     else
857             no_spell_errors = function () { ref.checkSpellingState() };
ffa6c1 858
309d2f 859         var rsm = $('<span>').text(this.lang_no_error_found);
A 860         
861         $(this.switch_lan_pic).hide();
862     $(this.spell_span).empty().append(rsm)
863         .removeClass().addClass('googie_check_spelling_ok');
ffa6c1 864
309d2f 865         window.setTimeout(no_spell_errors, 1000);
ffa6c1 866     }
T 867 }
868
309d2f 869 this.resumeEditingState = function() {
A 870     this.setStateChanged('resume_editing');
ffa6c1 871
T 872     //Change link text to resume
309d2f 873     if (this.main_controller) {
A 874         var rsm = $('<span>').text(this.lang_rsm_edt);
875     var ref = this;
ffa6c1 876
309d2f 877         $(this.switch_lan_pic).hide();
A 878         $(this.spell_span).empty().unbind().append(rsm)
879             .bind('click', function() { ref.resumeEditing() })
880             .removeClass().addClass('googie_resume_editing');
ffa6c1 881     }
T 882
883     try { this.edit_layer.scrollTop = this.ta_scroll_top; }
309d2f 884     catch (e) {};
ffa6c1 885 }
T 886
309d2f 887 this.checkSpellingState = function(fire) {
A 888     if (fire)
889         this.setStateChanged('ready');
ffa6c1 890
309d2f 891     if (this.show_change_lang_pic)
ffa6c1 892         this.switch_lan_pic = this.createChangeLangPic();
T 893     else
309d2f 894         this.switch_lan_pic = document.createElement('SPAN');
ffa6c1 895
T 896     var span_chck = this.createSpellDiv();
309d2f 897     var ref = this;
ffa6c1 898
309d2f 899     if (this.custom_spellcheck_starter)
A 900         $(span_chck).bind('click', function(e) { ref.custom_spellcheck_starter() });
ffa6c1 901     else {
309d2f 902         $(span_chck).bind('click', function(e) { ref.spellCheck() });
A 903     }
904
905     if (this.main_controller) {
906         if (this.change_lang_pic_placement == 'left') {
907         $(this.spell_container).empty().append(this.switch_lan_pic).append(' ').append(span_chck);
908         } else {
909         $(this.spell_container).empty().append(span_chck).append(' ').append(this.switch_lan_pic);
910     }
ffa6c1 911     }
T 912
913     this.spell_span = span_chck;
914 }
915
916
917 //////
918 // Misc. functions
919 /////
309d2f 920 this.isDefined = function(o) {
A 921     return (o != 'undefined' && o != null)
ffa6c1 922 }
T 923
309d2f 924 this.errorFixed = function() { 
A 925     this.cnt_errors_fixed++; 
926     if (this.all_errors_fixed_observer)
927         if (this.cnt_errors_fixed == this.cnt_errors) {
928             this.hideErrorWindow();
929             this.all_errors_fixed_observer();
930         }
ffa6c1 931 }
T 932
309d2f 933 this.errorFound = function() {
A 934     this.cnt_errors++;
935 }
ffa6c1 936
309d2f 937 this.createCloseButton = function(c_fn) {
A 938     return this.createButton(this.lang_close, 'googie_list_close', c_fn);
939 }
ffa6c1 940
309d2f 941 this.createButton = function(name, css_class, c_fn) {
A 942     var btn_row = document.createElement('TR');
943     var btn = document.createElement('TD');
ffa6c1 944     var spn_btn;
309d2f 945
A 946     if (css_class) {
947         spn_btn = document.createElement('SPAN');
948     $(spn_btn).addClass(css_class).html(name);
949     } else {
950         spn_btn = document.createTextNode(name);
ffa6c1 951     }
309d2f 952
A 953     $(btn).bind('click', c_fn)
954     .bind('mouseover', this.item_onmouseover)
955     .bind('mouseout', this.item_onmouseout);
956
ffa6c1 957     btn.appendChild(spn_btn);
T 958     btn_row.appendChild(btn);
959
960     return btn_row;
961 }
962
309d2f 963 this.removeIndicator = function(elm) {
A 964     //$(this.indicator).remove();
965     // roundcube mod.
ffa6c1 966     if (window.rcmail)
309d2f 967     rcmail.set_busy(false);
ffa6c1 968 }
T 969
309d2f 970 this.appendIndicator = function(elm) {
ffa6c1 971     // modified by roundcube
T 972     if (window.rcmail)
309d2f 973     rcmail.set_busy(true, 'checking');
A 974 /*    
975     this.indicator = document.createElement('IMG');
976     $(this.indicator).attr('src', this.img_dir + 'indicator.gif')
977     .css({'margin-right': '5px', 'text-decoration': 'none'}).width(16).height(16);
978     
979     if (elm)
980     $(this.indicator).insertBefore(elm);
981     else
982     $('body').append(this.indicator);
983 */                    
ffa6c1 984 }
T 985
309d2f 986 this.createFocusLink = function(name) {
A 987     var link = document.createElement('A');
988     $(link).attr({'href': 'javascript:;', 'name': name});
989     return link;
dd53e2 990 }
309d2f 991
A 992 this.item_onmouseover = function(e) {
993     if (this.className != "googie_list_revert" && this.className != "googie_list_close")
994         this.className = "googie_list_onhover";
995     else
996         this.parentNode.className = "googie_list_onhover";
997 }
998 this.item_onmouseout = function(e) {
999     if (this.className != "googie_list_revert" && this.className != "googie_list_close")
1000         this.className = "googie_list_onout";
1001     else
1002         this.parentNode.className = "googie_list_onout";
1003 }
1004
1005
1006 };