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