alecpl
2010-05-19 c435173eb411b9fb6a5f7c17cd408fb6591df111
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     
1617db 83     //Set document's onclick 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) {
91a35e 97             var table = document.createElement('table');
A 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;
dd53e2 125 }
T 126
309d2f 127 this.setLanguages = function(lang_dict) {
ffa6c1 128     this.lang_to_word = lang_dict;
309d2f 129     this.langlist_codes = this.array_keys(lang_dict);
dd53e2 130 }
T 131
309d2f 132 this.setCurrentLanguage = function(lan_code) {
A 133     GOOGIE_CUR_LANG = lan_code;
134
135     //Set cookie
136     var now = new Date();
137     now.setTime(now.getTime() + 365 * 24 * 60 * 60 * 1000);
138     setCookie('language', lan_code, now);
139 }
140
141 this.setForceWidthHeight = function(width, height) {
142     // Set to null if you want to use one of them
ffa6c1 143     this.force_width = width;
T 144     this.force_height = height;
145 }
2c6337 146
309d2f 147 this.setDecoration = function(bool) {
ffa6c1 148     this.decoration = bool;
T 149 }
dd53e2 150
309d2f 151 this.dontUseCloseButtons = function() {
ffa6c1 152     this.use_close_btn = false;
T 153 }
dd53e2 154
309d2f 155 this.appendNewMenuItem = function(name, call_back_fn, checker) {
ffa6c1 156     this.extra_menu_items.push([name, call_back_fn, checker]);
T 157 }
158
309d2f 159 this.appendCustomMenuBuilder = function(eval, builder) {
ffa6c1 160     this.custom_menu_builder.push([eval, builder]);
T 161 }
162
309d2f 163 this.setFocus = function() {
ffa6c1 164     try {
T 165         this.focus_link_b.focus();
166         this.focus_link_t.focus();
167         return true;
168     }
169     catch(e) {
170         return false;
171     }
172 }
173
174
175 //////
176 // Set functions (internal)
177 /////
309d2f 178 this.setStateChanged = function(current_state) {
ffa6c1 179     this.state = current_state;
309d2f 180     if (this.spelling_state_observer != null && this.report_state_change)
ffa6c1 181         this.spelling_state_observer(current_state, this);
T 182 }
183
309d2f 184 this.setReportStateChange = function(bool) {
ffa6c1 185     this.report_state_change = bool;
T 186 }
187
188
189 //////
190 // Request functions
191 /////
309d2f 192 this.getUrl = function() {
ffa6c1 193     return this.server_url + GOOGIE_CUR_LANG;
T 194 }
195
309d2f 196 this.escapeSpecial = function(val) {
ffa6c1 197     return val.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
T 198 }
199
309d2f 200 this.createXMLReq = function (text) {
A 201     return '<?xml version="1.0" encoding="utf-8" ?>'
202     + '<spellrequest textalreadyclipped="0" ignoredups="0" ignoredigits="1" ignoreallcaps="1">'
203     + '<text>' + text + '</text></spellrequest>';
ffa6c1 204 }
T 205
309d2f 206 this.spellCheck = function(ignore) {
ffa6c1 207     this.cnt_errors_fixed = 0;
T 208     this.cnt_errors = 0;
309d2f 209     this.setStateChanged('checking_spell');
ffa6c1 210
309d2f 211     if (this.main_controller)
ffa6c1 212         this.appendIndicator(this.spell_span);
T 213
214     this.error_links = [];
215     this.ta_scroll_top = this.text_area.scrollTop;
216     this.ignore = ignore;
309d2f 217     this.hideLangWindow();
ffa6c1 218
309d2f 219     if ($(this.text_area).val() == '' || ignore) {
A 220         if (!this.custom_no_spelling_error)
221             this.flashNoSpellingErrorState();
ffa6c1 222         else
309d2f 223             this.custom_no_spelling_error(this);
A 224         this.removeIndicator();
225         return;
ffa6c1 226     }
1617db 227
ffa6c1 228     this.createEditLayer(this.text_area.offsetWidth, this.text_area.offsetHeight);
T 229     this.createErrorWindow();
309d2f 230     $('body').append(this.error_window);
ffa6c1 231
T 232     try { netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead"); } 
233     catch (e) { }
234
309d2f 235     if (this.main_controller)
A 236         $(this.spell_span).unbind('click');
ffa6c1 237
309d2f 238     this.orginal_text = $(this.text_area).val();
A 239     var req_text = this.escapeSpecial(this.orginal_text);
240     var ref = this;
ffa6c1 241
309d2f 242     $.ajax({ type: 'POST', url: this.getUrl(),
A 243     data: this.createXMLReq(req_text), dataType: 'text',
244     error: function(o) {
245             if (ref.custom_ajax_error)
246             ref.custom_ajax_error(ref);
247             else
248             alert('An error was encountered on the server. Please try again later.');
249             if (ref.main_controller) {
250             $(ref.spell_span).remove();
251             ref.removeIndicator();
252             }
253             ref.checkSpellingState();
254     },
255         success: function(data) {
256         var r_text = data;
257             ref.results = ref.parseResult(r_text);
258             if (r_text.match(/<c.*>/) != null) {
259             //Before parsing be sure that errors were found
260             ref.showErrorsInIframe();
261             ref.resumeEditingState();
262             } else {
263             if (!ref.custom_no_spelling_error)
264             ref.flashNoSpellingErrorState();
265             else
266                     ref.custom_no_spelling_error(ref);
267             }
268             ref.removeIndicator();
269     }
270     });
ffa6c1 271 }
T 272
273
274 //////
275 // Spell checking functions
276 /////
309d2f 277 this.parseResult = function(r_text) {
A 278     // Returns an array: result[item] -> ['attrs'], ['suggestions']
ffa6c1 279     var re_split_attr_c = /\w+="(\d+|true)"/g;
T 280     var re_split_text = /\t/g;
281
282     var matched_c = r_text.match(/<c[^>]*>[^<]*<\/c>/g);
283     var results = new Array();
284
309d2f 285     if (matched_c == null)
ffa6c1 286         return results;
T 287     
309d2f 288     for (var i=0; i < matched_c.length; i++) {
ffa6c1 289         var item = new Array();
T 290         this.errorFound();
291
292         //Get attributes
293         item['attrs'] = new Array();
294         var split_c = matched_c[i].match(re_split_attr_c);
309d2f 295         for (var j=0; j < split_c.length; j++) {
ffa6c1 296             var c_attr = split_c[j].split(/=/);
T 297             var val = c_attr[1].replace(/"/g, '');
309d2f 298             item['attrs'][c_attr[0]] = val != 'true' ? parseInt(val) : val;
ffa6c1 299         }
T 300
301         //Get suggestions
302         item['suggestions'] = new Array();
309d2f 303         var only_text = matched_c[i].replace(/<[^>]*>/g, '');
ffa6c1 304         var split_t = only_text.split(re_split_text);
309d2f 305         for (var k=0; k < split_t.length; k++) {
A 306             if(split_t[k] != '')
307             item['suggestions'].push(split_t[k]);
308         }
ffa6c1 309         results.push(item);
T 310     }
1617db 311
ffa6c1 312     return results;
T 313 }
314
315
316 //////
317 // Error menu functions
318 /////
309d2f 319 this.createErrorWindow = function() {
91a35e 320     this.error_window = document.createElement('div');
309d2f 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() {
91a35e 352     var td = document.createElement('td');
A 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;
aee460 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
91a35e 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;
1617db 405     for (var k=0; k<this.custom_menu_builder.length; k++) {
A 406         var eb = this.custom_menu_builder[k];
407         if(eb[0]((this.results[id]))){
408             changed = eb[1](this, list, elm);
409             break;
ffa6c1 410         }
T 411     }
309d2f 412     if (!changed) {
ffa6c1 413         //Build up the result list
T 414         var suggestions = this.results[id]['suggestions'];
415         var offset = this.results[id]['attrs']['o'];
416         var len = this.results[id]['attrs']['l'];
417
309d2f 418         if (suggestions.length == 0) {
91a35e 419             var row = document.createElement('tr');
A 420             var item = document.createElement('td');
421             var dummy = document.createElement('span');
309d2f 422
A 423             $(dummy).text(this.lang_no_suggestions);
424             $(item).attr('googie_action_btn', '1').css('cursor', 'default');
425
426             item.appendChild(dummy);
ffa6c1 427             row.appendChild(item);
T 428             list.appendChild(row);
429         }
430
309d2f 431         for (i=0; i < suggestions.length; i++) {
91a35e 432             var row = document.createElement('tr');
A 433             var item = document.createElement('td');
434             var dummy = document.createElement('span');
309d2f 435
A 436             $(dummy).html(suggestions[i]);
ffa6c1 437             
309d2f 438             $(item).bind('mouseover', this.item_onmouseover)
A 439             .bind('mouseout', this.item_onmouseout)
440             .bind('click', function(e) { ref.correctError(id, elm, e.target.firstChild) });
ffa6c1 441
309d2f 442             item.appendChild(dummy);
ffa6c1 443             row.appendChild(item);
T 444             list.appendChild(row);
445         }
446
447         //The element is changed, append the revert
309d2f 448         if (elm.is_changed && elm.innerHTML != elm.old_value) {
ffa6c1 449             var old_value = elm.old_value;
91a35e 450             var revert_row = document.createElement('tr');
A 451             var revert = document.createElement('td');
452             var rev_span = document.createElement('span');
309d2f 453         
A 454         $(rev_span).addClass('googie_list_revert').html(this.lang_revert + ' ' + old_value);
ffa6c1 455
309d2f 456             $(revert).bind('mouseover', this.item_onmouseover)
A 457             .bind('mouseout', this.item_onmouseout)
458             .bind('click', function(e) {
459                     ref.updateOrginalText(offset, elm.innerHTML, old_value, id);
460                     $(elm).attr('is_corrected', true).css('color', '#b91414').html(old_value);
461                     ref.hideErrorWindow();
462             });
463
ffa6c1 464             revert.appendChild(rev_span);
T 465             revert_row.appendChild(revert);
466             list.appendChild(revert_row);
467         }
1617db 468
ffa6c1 469         //Append the edit box
91a35e 470         var edit_row = document.createElement('tr');
A 471         var edit = document.createElement('td');
472         var edit_input = document.createElement('input');
473         var ok_pic = document.createElement('img');
474     var edit_form = document.createElement('form');
ffa6c1 475
T 476         var onsub = function () {
309d2f 477             if (edit_input.value != '') {
A 478                 if (!ref.isDefined(elm.old_value))
479                     ref.saveOldValue(elm, elm.innerHTML);
ffa6c1 480
309d2f 481                 ref.updateOrginalText(offset, elm.innerHTML, edit_input.value, id);
A 482         $(elm).attr('is_corrected', true).css('color', 'green').html(edit_input.value);
483                 ref.hideErrorWindow();
ffa6c1 484             }
T 485             return false;
486         };
487
309d2f 488     $(edit_input).width(120).css({'margin': 0, 'padding': 0});
A 489     $(edit_input).val(elm.innerHTML).attr('googie_action_btn', '1');
490     $(edit).css('cursor', 'default').attr('googie_action_btn', '1');
ffa6c1 491
309d2f 492     $(ok_pic).attr('src', this.img_dir + 'ok.gif')
A 493         .width(32).height(16)
494         .css({'cursor': 'pointer', 'margin-left': '2px', 'margin-right': '2px'})
495         .bind('click', onsub);
496
497         $(edit_form).attr('googie_action_btn', '1')
498         .css({'margin': 0, 'padding': 0, 'cursor': 'default', 'white-space': 'nowrap'})
499         .bind('submit', onsub);
ffa6c1 500         
309d2f 501     edit_form.appendChild(edit_input);
A 502     edit_form.appendChild(ok_pic);
ffa6c1 503         edit.appendChild(edit_form);
T 504         edit_row.appendChild(edit);
505         list.appendChild(edit_row);
506
507         //Append extra menu items
309d2f 508         if (this.extra_menu_items.length > 0)
A 509         list.appendChild(this.createListSeparator());
510     
ffa6c1 511         var loop = function(i) {
309d2f 512                 if (i < ref.extra_menu_items.length) {
A 513                     var e_elm = ref.extra_menu_items[i];
ffa6c1 514
309d2f 515                     if (!e_elm[2] || e_elm[2](elm, ref)) {
91a35e 516                         var e_row = document.createElement('tr');
A 517                         var e_col = document.createElement('td');
ffa6c1 518
309d2f 519             $(e_col).html(e_elm[0])
A 520                             .bind('mouseover', ref.item_onmouseover)
521                             .bind('mouseout', ref.item_onmouseout)
522                 .bind('click', function() { return e_elm[1](elm, ref) });
523             
524             e_row.appendChild(e_col);
525                         list.appendChild(e_row);
ffa6c1 526                     }
T 527                     loop(i+1);
528                 }
529         }
309d2f 530     
ffa6c1 531         loop(0);
T 532         loop = null;
533
534         //Close button
309d2f 535         if (this.use_close_btn) {
A 536             list.appendChild(this.createCloseButton(this.hideErrorWindow));
ffa6c1 537         }
T 538     }
539
540     table.appendChild(list);
541     this.error_window.appendChild(table);
542
543     //Dummy for IE - dropdown bug fix
309d2f 544     if ($.browser.msie) {
A 545     if (!this.error_window_iframe) {
1617db 546             var iframe = $('<iframe>').css({'position': 'absolute', 'z-index': -1});
309d2f 547         $('body').append(iframe);
A 548             this.error_window_iframe = iframe;
549         }
1617db 550
309d2f 551     $(this.error_window_iframe).css({'visibility': 'visible',
A 552         'top': this.error_window.offsetTop, 'left': this.error_window.offsetLeft,
553             'width': this.error_window.offsetWidth, 'height': this.error_window.offsetHeight});
ffa6c1 554     }
T 555 }
556
557
558 //////
559 // Edit layer (the layer where the suggestions are stored)
560 //////
309d2f 561 this.createEditLayer = function(width, height) {
91a35e 562     this.edit_layer = document.createElement('div');
309d2f 563     $(this.edit_layer).addClass('googie_edit_layer').width(width-10).height(height);
ffa6c1 564
91a35e 565     if (this.text_area.nodeName.toLowerCase() != 'input' || $(this.text_area).val() == '') {
309d2f 566         $(this.edit_layer).css('overflow', 'auto').height(height-4);
A 567     } else {
568         $(this.edit_layer).css('overflow', 'hidden');
dd53e2 569     }
T 570
309d2f 571     var ref = this;
A 572     if (this.edit_layer_dbl_click) {
573         $(this.edit_layer).bind('click', function(e) {
574             if (e.target.className != 'googie_link' && !ref.isErrorWindowShown()) {
575                 ref.resumeEditing();
ffa6c1 576                 var fn1 = function() {
309d2f 577                     $(ref.text_area).focus();
ffa6c1 578                     fn1 = null;
T 579                 };
309d2f 580                 window.setTimeout(fn1, 10);
ffa6c1 581             }
T 582             return false;
309d2f 583         });
ffa6c1 584     }
T 585 }
586
309d2f 587 this.resumeEditing = function() {
A 588     this.setStateChanged('ready');
ffa6c1 589
309d2f 590     if (this.edit_layer)
ffa6c1 591         this.el_scroll_top = this.edit_layer.scrollTop;
T 592
593     this.hideErrorWindow();
594
309d2f 595     if (this.main_controller)
A 596         $(this.spell_span).removeClass().addClass('googie_no_style');
ffa6c1 597
309d2f 598     if (!this.ignore) {
A 599         if (this.use_focus) {
600             $(this.focus_link_t).remove();
601             $(this.focus_link_b).remove();
ffa6c1 602         }
T 603
309d2f 604         $(this.edit_layer).remove();
A 605         $(this.text_area).show();
ffa6c1 606
309d2f 607         if (this.el_scroll_top != undefined)
ffa6c1 608             this.text_area.scrollTop = this.el_scroll_top;
T 609     }
610     this.checkSpellingState(false);
611 }
612
309d2f 613 this.createErrorLink = function(text, id) {
91a35e 614     var elm = document.createElement('span');
309d2f 615     var ref = this;
ffa6c1 616     var d = function (e) {
309d2f 617             ref.showErrorWindow(elm, id);
A 618             d = null;
619             return false;
ffa6c1 620     };
309d2f 621     
A 622     $(elm).html(text).addClass('googie_link').bind('click', d)
623     .attr({'googie_action_btn' : '1', 'g_id' : id, 'is_corrected' : false});
ffa6c1 624
T 625     return elm;
626 }
627
309d2f 628 this.createPart = function(txt_part) {
A 629     if (txt_part == " ")
630         return document.createTextNode(" ");
ffa6c1 631
309d2f 632     txt_part = this.escapeSpecial(txt_part);
ffa6c1 633     txt_part = txt_part.replace(/\n/g, "<br>");
T 634     txt_part = txt_part.replace(/    /g, " &nbsp;");
635     txt_part = txt_part.replace(/^ /g, "&nbsp;");
636     txt_part = txt_part.replace(/ $/g, "&nbsp;");
637     
91a35e 638     var span = document.createElement('span');
309d2f 639     $(span).html(txt_part);
A 640     return span;
ffa6c1 641 }
T 642
309d2f 643 this.showErrorsInIframe = function() {
91a35e 644     var output = document.createElement('div')
ffa6c1 645     var pointer = 0;
T 646     var results = this.results;
647
309d2f 648     if (results.length > 0) {
A 649         for (var i=0; i < results.length; i++) {
ffa6c1 650             var offset = results[i]['attrs']['o'];
T 651             var len = results[i]['attrs']['l'];
652             var part_1_text = this.orginal_text.substring(pointer, offset);
309d2f 653             var part_1 = this.createPart(part_1_text);
A 654     
ffa6c1 655             output.appendChild(part_1);
T 656             pointer += offset - pointer;
657             
658             //If the last child was an error, then insert some space
659             var err_link = this.createErrorLink(this.orginal_text.substr(offset, len), i);
660             this.error_links.push(err_link);
661             output.appendChild(err_link);
662             pointer += len;
663         }
664         //Insert the rest of the orginal text
665         var part_2_text = this.orginal_text.substr(pointer, this.orginal_text.length);
309d2f 666         var part_2 = this.createPart(part_2_text);
ffa6c1 667
T 668         output.appendChild(part_2);
669     }
670     else
671         output.innerHTML = this.orginal_text;
672
309d2f 673     $(output).css('text-align', 'left');
A 674
ffa6c1 675     var me = this;
309d2f 676     if (this.custom_item_evaulator)
A 677         $.map(this.error_links, function(elm){me.custom_item_evaulator(me, elm)});
ffa6c1 678     
309d2f 679     $(this.edit_layer).append(output);
ffa6c1 680
309d2f 681     //Hide text area and show edit layer
A 682     $(this.text_area).hide();
683     $(this.edit_layer).insertBefore(this.text_area);
ffa6c1 684
309d2f 685     if (this.use_focus) {
ffa6c1 686         this.focus_link_t = this.createFocusLink('focus_t');
T 687         this.focus_link_b = this.createFocusLink('focus_b');
688
309d2f 689         $(this.focus_link_t).insertBefore(this.edit_layer);
A 690         $(this.focus_link_b).insertAfter(this.edit_layer);
ffa6c1 691     }
T 692
309d2f 693 //    this.edit_layer.scrollTop = this.ta_scroll_top;
ffa6c1 694 }
T 695
696
697 //////
698 // Choose language menu
699 //////
309d2f 700 this.createLangWindow = function() {
91a35e 701     this.language_window = document.createElement('div');
309d2f 702     $(this.language_window).addClass('googie_window')
A 703     .width(100).attr('googie_action_btn', '1');
ffa6c1 704
T 705     //Build up the result list
91a35e 706     var table = document.createElement('table');
A 707     var list = document.createElement('tbody');
309d2f 708     var ref = this;
ffa6c1 709
309d2f 710     $(table).addClass('googie_list').width('100%');
ffa6c1 711     this.lang_elms = new Array();
T 712
309d2f 713     for (i=0; i < this.langlist_codes.length; i++) {
91a35e 714         var row = document.createElement('tr');
A 715         var item = document.createElement('td');
716         var span = document.createElement('span');
309d2f 717     
A 718     $(span).text(this.lang_to_word[this.langlist_codes[i]]);
ffa6c1 719         this.lang_elms.push(item);
T 720
309d2f 721         $(item).attr('googieId', this.langlist_codes[i])
A 722             .bind('click', function(e) {
723             ref.deHighlightCurSel();
724             ref.setCurrentLanguage($(this).attr('googieId'));
ffa6c1 725
309d2f 726             if (ref.lang_state_observer != null) {
A 727                     ref.lang_state_observer();
728             }
ffa6c1 729
309d2f 730             ref.highlightCurSel();
A 731             ref.hideLangWindow();
732             })
733             .bind('mouseover', function(e) { 
734             if (this.className != "googie_list_selected")
735                     this.className = "googie_list_onhover";
736             })
737             .bind('mouseout', function(e) { 
738             if (this.className != "googie_list_selected")
739                     this.className = "googie_list_onout"; 
740             });
ffa6c1 741
309d2f 742     item.appendChild(span);
ffa6c1 743         row.appendChild(item);
T 744         list.appendChild(row);
745     }
746
747     //Close button
309d2f 748     if (this.use_close_btn) {
A 749         list.appendChild(this.createCloseButton(function () { ref.hideLangWindow.apply(ref) }));
ffa6c1 750     }
T 751
752     this.highlightCurSel();
753
754     table.appendChild(list);
755     this.language_window.appendChild(table);
756 }
757
309d2f 758 this.isLangWindowShown = function() {
A 759     return $(this.language_window).is(':hidden');
ffa6c1 760 }
T 761
309d2f 762 this.hideLangWindow = function() {
A 763     $(this.language_window).css('visibility', 'hidden');
764     $(this.switch_lan_pic).removeClass().addClass('googie_lang_3d_on');
ffa6c1 765 }
T 766
309d2f 767 this.deHighlightCurSel = function() {
A 768     $(this.lang_cur_elm).removeClass().addClass('googie_list_onout');
ffa6c1 769 }
T 770
309d2f 771 this.highlightCurSel = function() {
A 772     if (GOOGIE_CUR_LANG == null)
ffa6c1 773         GOOGIE_CUR_LANG = GOOGIE_DEFAULT_LANG;
309d2f 774     for (var i=0; i < this.lang_elms.length; i++) {
A 775         if ($(this.lang_elms[i]).attr('googieId') == GOOGIE_CUR_LANG) {
ffa6c1 776             this.lang_elms[i].className = "googie_list_selected";
T 777             this.lang_cur_elm = this.lang_elms[i];
778         }
779         else {
780             this.lang_elms[i].className = "googie_list_onout";
781         }
782     }
783 }
784
309d2f 785 this.showLangWindow = function(elm) {
A 786     if (this.show_menu_observer)
ffa6c1 787         this.show_menu_observer(this);
T 788
789     this.createLangWindow();
309d2f 790     $('body').append(this.language_window);
ffa6c1 791
309d2f 792     var pos = $(elm).offset();
A 793     var top = pos.top + $(elm).height();
794     var left = this.change_lang_pic_placement == 'right' ? 
795     pos.left - 100 + $(elm).width() : pos.left + $(elm).width();
796
797     $(this.language_window).css({'visibility': 'visible', 'top' : top+'px','left' : left+'px'});
ffa6c1 798
T 799     this.highlightCurSel();
800 }
801
309d2f 802 this.createChangeLangPic = function() {
A 803     var img = $('<img>')
804     .attr({src: this.img_dir + 'change_lang.gif', 'alt': 'Change language', 'googie_action_btn': '1'});
ffa6c1 805
91a35e 806     var switch_lan = document.createElement('span');
309d2f 807     var ref = this;
ffa6c1 808
309d2f 809     $(switch_lan).addClass('googie_lang_3d_on')
A 810     .append(img)
811     .bind('click', function(e) {
91a35e 812             var elm = this.tagName.toLowerCase() == 'img' ? this.parentNode : this;
309d2f 813             if($(elm).hasClass('googie_lang_3d_click')) {
A 814             elm.className = 'googie_lang_3d_on';
815             ref.hideLangWindow();
816             }
817             else {
818             elm.className = 'googie_lang_3d_click';
819             ref.showLangWindow(elm);
820             }
821     });
822
ffa6c1 823     return switch_lan;
T 824 }
825
309d2f 826 this.createSpellDiv = function() {
91a35e 827     var span = document.createElement('span');
ffa6c1 828
309d2f 829     $(span).addClass('googie_check_spelling_link').text(this.lang_chck_spell);
A 830
831     if (this.show_spell_img) {
832     $(span).append(' ').append($('<img>').attr('src', this.img_dir + 'spellc.gif'));
833     }
834     return span;
ffa6c1 835 }
T 836
837
838 //////
839 // State functions
840 /////
309d2f 841 this.flashNoSpellingErrorState = function(on_finish) {
A 842     this.setStateChanged('no_error_found');
ffa6c1 843
309d2f 844     var ref = this;
A 845     if (this.main_controller) {
846     var no_spell_errors;
847     if (on_finish) {
848             var fn = function() {
849             on_finish();
850             ref.checkSpellingState();
851             };
852             no_spell_errors = fn;
853     }
854     else
855             no_spell_errors = function () { ref.checkSpellingState() };
ffa6c1 856
309d2f 857         var rsm = $('<span>').text(this.lang_no_error_found);
677e1f 858
309d2f 859         $(this.switch_lan_pic).hide();
677e1f 860         $(this.spell_span).empty().append(rsm)
309d2f 861         .removeClass().addClass('googie_check_spelling_ok');
ffa6c1 862
309d2f 863         window.setTimeout(no_spell_errors, 1000);
ffa6c1 864     }
T 865 }
866
309d2f 867 this.resumeEditingState = function() {
A 868     this.setStateChanged('resume_editing');
ffa6c1 869
T 870     //Change link text to resume
309d2f 871     if (this.main_controller) {
A 872         var rsm = $('<span>').text(this.lang_rsm_edt);
873     var ref = this;
ffa6c1 874
309d2f 875         $(this.switch_lan_pic).hide();
A 876         $(this.spell_span).empty().unbind().append(rsm)
877             .bind('click', function() { ref.resumeEditing() })
878             .removeClass().addClass('googie_resume_editing');
ffa6c1 879     }
T 880
881     try { this.edit_layer.scrollTop = this.ta_scroll_top; }
309d2f 882     catch (e) {};
ffa6c1 883 }
T 884
309d2f 885 this.checkSpellingState = function(fire) {
A 886     if (fire)
887         this.setStateChanged('ready');
ffa6c1 888
309d2f 889     if (this.show_change_lang_pic)
ffa6c1 890         this.switch_lan_pic = this.createChangeLangPic();
T 891     else
91a35e 892         this.switch_lan_pic = document.createElement('span');
ffa6c1 893
T 894     var span_chck = this.createSpellDiv();
309d2f 895     var ref = this;
ffa6c1 896
309d2f 897     if (this.custom_spellcheck_starter)
A 898         $(span_chck).bind('click', function(e) { ref.custom_spellcheck_starter() });
ffa6c1 899     else {
309d2f 900         $(span_chck).bind('click', function(e) { ref.spellCheck() });
A 901     }
902
903     if (this.main_controller) {
904         if (this.change_lang_pic_placement == 'left') {
905         $(this.spell_container).empty().append(this.switch_lan_pic).append(' ').append(span_chck);
906         } else {
907         $(this.spell_container).empty().append(span_chck).append(' ').append(this.switch_lan_pic);
908     }
ffa6c1 909     }
T 910
911     this.spell_span = span_chck;
912 }
913
914
915 //////
916 // Misc. functions
917 /////
309d2f 918 this.isDefined = function(o) {
A 919     return (o != 'undefined' && o != null)
ffa6c1 920 }
T 921
309d2f 922 this.errorFixed = function() { 
A 923     this.cnt_errors_fixed++; 
924     if (this.all_errors_fixed_observer)
925         if (this.cnt_errors_fixed == this.cnt_errors) {
926             this.hideErrorWindow();
927             this.all_errors_fixed_observer();
928         }
ffa6c1 929 }
T 930
309d2f 931 this.errorFound = function() {
A 932     this.cnt_errors++;
933 }
ffa6c1 934
309d2f 935 this.createCloseButton = function(c_fn) {
A 936     return this.createButton(this.lang_close, 'googie_list_close', c_fn);
937 }
ffa6c1 938
309d2f 939 this.createButton = function(name, css_class, c_fn) {
91a35e 940     var btn_row = document.createElement('tr');
A 941     var btn = document.createElement('td');
ffa6c1 942     var spn_btn;
309d2f 943
A 944     if (css_class) {
91a35e 945         spn_btn = document.createElement('span');
309d2f 946     $(spn_btn).addClass(css_class).html(name);
A 947     } else {
948         spn_btn = document.createTextNode(name);
ffa6c1 949     }
309d2f 950
A 951     $(btn).bind('click', c_fn)
952     .bind('mouseover', this.item_onmouseover)
953     .bind('mouseout', this.item_onmouseout);
954
ffa6c1 955     btn.appendChild(spn_btn);
T 956     btn_row.appendChild(btn);
957
958     return btn_row;
959 }
960
309d2f 961 this.removeIndicator = function(elm) {
A 962     //$(this.indicator).remove();
963     // roundcube mod.
ffa6c1 964     if (window.rcmail)
309d2f 965     rcmail.set_busy(false);
ffa6c1 966 }
T 967
309d2f 968 this.appendIndicator = function(elm) {
ffa6c1 969     // modified by roundcube
T 970     if (window.rcmail)
309d2f 971     rcmail.set_busy(true, 'checking');
A 972 /*    
91a35e 973     this.indicator = document.createElement('img');
309d2f 974     $(this.indicator).attr('src', this.img_dir + 'indicator.gif')
A 975     .css({'margin-right': '5px', 'text-decoration': 'none'}).width(16).height(16);
976     
977     if (elm)
978     $(this.indicator).insertBefore(elm);
979     else
980     $('body').append(this.indicator);
981 */                    
ffa6c1 982 }
T 983
309d2f 984 this.createFocusLink = function(name) {
91a35e 985     var link = document.createElement('a');
309d2f 986     $(link).attr({'href': 'javascript:;', 'name': name});
A 987     return link;
dd53e2 988 }
309d2f 989
A 990 this.item_onmouseover = function(e) {
991     if (this.className != "googie_list_revert" && this.className != "googie_list_close")
992         this.className = "googie_list_onhover";
993     else
994         this.parentNode.className = "googie_list_onhover";
995 }
996 this.item_onmouseout = function(e) {
997     if (this.className != "googie_list_revert" && this.className != "googie_list_close")
998         this.className = "googie_list_onout";
999     else
1000         this.parentNode.className = "googie_list_onout";
1001 }
1002
1003
1004 };