You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

googiespell.js 34KB

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