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