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