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