diff options
author | thomascube <thomas@roundcube.net> | 2006-05-01 16:27:19 +0000 |
---|---|---|
committer | thomascube <thomas@roundcube.net> | 2006-05-01 16:27:19 +0000 |
commit | dd53e2b489e8787bb339511e33f2d6c4fd5efe3f (patch) | |
tree | 59100ce9248efa611c05157aaab4d558cac544bb | |
parent | fecb03f44a3a2c3dace7d17eebebbb787a9042e3 (diff) |
Started integrating GoogieSpell
-rw-r--r-- | CHANGELOG | 6 | ||||
-rw-r--r-- | index.php | 9 | ||||
-rw-r--r-- | program/include/main.inc | 5 | ||||
-rw-r--r-- | program/include/rcube_shared.inc | 1 | ||||
-rw-r--r-- | program/js/common.js | 31 | ||||
-rwxr-xr-x | program/js/googiespell.js | 1298 | ||||
-rw-r--r-- | program/steps/mail/compose.inc | 15 | ||||
-rw-r--r-- | program/steps/mail/spell.inc | 50 | ||||
-rwxr-xr-x | skins/default/googiespell.css | 94 | ||||
-rw-r--r-- | skins/default/images/googiespell/change_lang.gif | bin | 0 -> 111 bytes | |||
-rw-r--r-- | skins/default/images/googiespell/indicator.gif | bin | 0 -> 722 bytes | |||
-rw-r--r-- | skins/default/images/googiespell/ok.gif | bin | 0 -> 143 bytes | |||
-rw-r--r-- | skins/default/images/googiespell/spellc.gif | bin | 0 -> 354 bytes | |||
-rw-r--r-- | skins/default/mail.css | 2 | ||||
-rw-r--r-- | skins/default/templates/compose.html | 1 |
15 files changed, 1504 insertions, 8 deletions
@@ -1,6 +1,12 @@ CHANGELOG RoundCube Webmail --------------------------- +- Applied prev/next patch by Leonard Bouchet +- Applied patches by Mark Bucciarelli +- Applied patch for requesting receipts by Salvatore Ansani +- Integrated GoogieSpell as suggested by phil (styling is not perfect yet, localization is missing) + + 2006/04/13 ---------- - Added Slovenian localization @@ -2,7 +2,7 @@ /* +-----------------------------------------------------------------------+ | RoundCube Webmail IMAP Client | - | Version 0.1-20060402 | + | Version 0.1-20060501 | | | | Copyright (C) 2005, RoundCube Dev. - Switzerland | | Licensed under the GNU GPL | @@ -40,7 +40,7 @@ */ -define('RCMAIL_VERSION', '0.1-20060402'); +define('RCMAIL_VERSION', '0.1-20060501'); // define global vars $CHARSET = 'UTF-8'; @@ -276,7 +276,10 @@ if ($_task=='mail') include('program/steps/mail/list.inc'); if ($_action=='search') - include('program/steps/mail/search.inc'); + include('program/steps/mail/search.inc'); + + if ($_action=='spell') + include('program/steps/mail/spell.inc'); if ($_action=='rss') include('program/steps/mail/rss.inc'); diff --git a/program/include/main.inc b/program/include/main.inc index f1102d7e1..364bfd50e 100644 --- a/program/include/main.inc +++ b/program/include/main.inc @@ -321,8 +321,9 @@ function load_gui() $javascript .= "$JS_OBJECT_NAME.set_env('framed', true);\n"; $OUTPUT->add_script($javascript); - $OUTPUT->include_script('program/js/common.js'); - $OUTPUT->include_script('program/js/app.js'); + $OUTPUT->include_script('common.js'); + $OUTPUT->include_script('app.js'); + $OUTPUT->scripts_path = 'program/js/'; // set locale setting rcmail_set_locale($sess_user_lang); diff --git a/program/include/rcube_shared.inc b/program/include/rcube_shared.inc index 77753f5b4..fe1a560a3 100644 --- a/program/include/rcube_shared.inc +++ b/program/include/rcube_shared.inc @@ -193,6 +193,7 @@ class rcube_html_page // correct absolute pathes in images and other tags $output = preg_replace('/(src|href|background)=(["\']?)(\/[a-z0-9_\-]+)/Ui', "\\1=\\2$base_path\\3", $output); + $output = str_replace('$__skin_path', $base_path, $output); print rcube_charset_convert($output, 'UTF-8', $this->charset); } diff --git a/program/js/common.js b/program/js/common.js index 8378e2e57..6cc279534 100644 --- a/program/js/common.js +++ b/program/js/common.js @@ -363,5 +363,36 @@ function rcube_get_object_pos(obj) return {x:iX, y:iY}; } + + +// cookie functions by GoogieSpell +function setCookie(name, value, expires, path, domain, secure) + { + var curCookie = name + "=" + escape(value) + + (expires ? "; expires=" + expires.toGMTString() : "") + + (path ? "; path=" + path : "") + + (domain ? "; domain=" + domain : "") + + (secure ? "; secure" : ""); + document.cookie = curCookie; + } + +function getCookie(name) + { + var dc = document.cookie; + var prefix = name + "="; + var begin = dc.indexOf("; " + prefix); + if (begin == -1) + { + begin = dc.indexOf(prefix); + if (begin != 0) return null; + } + else + begin += 2; + var end = document.cookie.indexOf(";", begin); + if (end == -1) + end = dc.length; + return unescape(dc.substring(begin + prefix.length, end)); + } + var bw = new roundcube_browser();
\ No newline at end of file diff --git a/program/js/googiespell.js b/program/js/googiespell.js new file mode 100755 index 000000000..25f9526cd --- /dev/null +++ b/program/js/googiespell.js @@ -0,0 +1,1298 @@ +/* +Last Modified: 28/04/06 16:28:09 + + AmiJs library + A very small library with DOM and Ajax functions. + For a much larger script look on http://www.mochikit.com/ + AUTHOR + 4mir Salihefendic (http://amix.dk) - amix@amix.dk + LICENSE + Copyright (c) 2006 Amir Salihefendic. All rights reserved. + Copyright (c) 2005 Bob Ippolito. All rights reserved. + http://www.opensource.org/licenses/mit-license.php + VERSION + 2.1 + SITE + http://amix.dk/amijs +**/ + +var AJS = { +//// +// Accessor functions +//// + /** + * @returns The element with the id + */ + getElement: function(id) { + if(typeof(id) == "string") + return document.getElementById(id); + else + return id; + }, + + /** + * @returns The elements with the ids + */ + getElements: function(/*id1, id2, id3*/) { + var elements = new Array(); + for (var i = 0; i < arguments.length; i++) { + var element = this.getElement(arguments[i]); + elements.push(element); + } + return elements; + }, + + /** + * @returns The GET query argument + */ + getQueryArgument: function(var_name) { + var query = window.location.search.substring(1); + var vars = query.split("&"); + for (var i=0;i<vars.length;i++) { + var pair = vars[i].split("="); + if (pair[0] == var_name) { + return pair[1]; + } + } + return null; + }, + + /** + * @returns If the browser is Internet Explorer + */ + isIe: function() { + return (navigator.userAgent.toLowerCase().indexOf("msie") != -1 && navigator.userAgent.toLowerCase().indexOf("opera") == -1); + }, + + /** + * @returns The document body + */ + getBody: function() { + return this.getElementsByTagAndClassName('body')[0] + }, + + /** + * @returns All the elements that have a specific tag name or class name + */ + getElementsByTagAndClassName: function(tag_name, class_name, /*optional*/ parent) { + var class_elements = new Array(); + if(!this.isDefined(parent)) + parent = document; + if(!this.isDefined(tag_name)) + tag_name = '*'; + + var els = parent.getElementsByTagName(tag_name); + var els_len = els.length; + var pattern = new RegExp("(^|\\s)" + class_name + "(\\s|$)"); + + for (i = 0, j = 0; i < els_len; i++) { + if ( pattern.test(els[i].className) || class_name == null ) { + class_elements[j] = els[i]; + j++; + } + } + return class_elements; + }, + + +//// +// DOM manipulation +//// + /** + * Appends some nodes to a node + */ + appendChildNodes: function(node/*, nodes...*/) { + if(arguments.length >= 2) { + for(var i=1; i < arguments.length; i++) { + var n = arguments[i]; + if(typeof(n) == "string") + n = document.createTextNode(n); + if(this.isDefined(n)) + node.appendChild(n); + } + } + return node; + }, + + /** + * Replaces a nodes children with another node(s) + */ + replaceChildNodes: function(node/*, nodes...*/) { + var child; + while ((child = node.firstChild)) { + node.removeChild(child); + } + if (arguments.length < 2) { + return node; + } else { + return this.appendChildNodes.apply(this, arguments); + } + }, + + /** + * Insert a node after another node + */ + insertAfter: function(node, referenceNode) { + referenceNode.parentNode.insertBefore(node, referenceNode.nextSibling); + }, + + /** + * Insert a node before another node + */ + insertBefore: function(node, referenceNode) { + referenceNode.parentNode.insertBefore(node, referenceNode); + }, + + /** + * Shows the element + */ + showElement: function(elm) { + elm.style.display = ''; + }, + + /** + * Hides the element + */ + hideElement: function(elm) { + elm.style.display = 'none'; + }, + + isElementHidden: function(elm) { + return elm.style.visibility == "hidden"; + }, + + /** + * Swaps one element with another. To delete use swapDOM(elm, null) + */ + swapDOM: function(dest, src) { + dest = this.getElement(dest); + var parent = dest.parentNode; + if (src) { + src = this.getElement(src); + parent.replaceChild(src, dest); + } else { + parent.removeChild(dest); + } + return src; + }, + + /** + * Removes an element from the world + */ + removeElement: function(elm) { + this.swapDOM(elm, null); + }, + + /** + * @returns Is an object a dictionary? + */ + isDict: function(o) { + var str_repr = String(o); + return str_repr.indexOf(" Object") != -1; + }, + + /** + * Creates a DOM element + * @param {String} name The elements DOM name + * @param {Dict} attrs Attributes sent to the function + */ + createDOM: function(name, attrs) { + var i=0; + elm = document.createElement(name); + + if(this.isDict(attrs[i])) { + for(k in attrs[0]) { + if(k == "style") + elm.style.cssText = attrs[0][k]; + else if(k == "class") + elm.className = attrs[0][k]; + else + elm.setAttribute(k, attrs[0][k]); + } + i++; + } + + if(attrs[0] == null) + i = 1; + + for(i; i < attrs.length; i++) { + var n = attrs[i]; + if(this.isDefined(n)) { + if(typeof(n) == "string") + n = document.createTextNode(n); + elm.appendChild(n); + } + } + return elm; + }, + + UL: function() { return this.createDOM.apply(this, ["ul", arguments]); }, + LI: function() { return this.createDOM.apply(this, ["li", arguments]); }, + TD: function() { return this.createDOM.apply(this, ["td", arguments]); }, + TR: function() { return this.createDOM.apply(this, ["tr", arguments]); }, + TH: function() { return this.createDOM.apply(this, ["th", arguments]); }, + TBODY: function() { return this.createDOM.apply(this, ["tbody", arguments]); }, + TABLE: function() { return this.createDOM.apply(this, ["table", arguments]); }, + INPUT: function() { return this.createDOM.apply(this, ["input", arguments]); }, + SPAN: function() { return this.createDOM.apply(this, ["span", arguments]); }, + B: function() { return this.createDOM.apply(this, ["b", arguments]); }, + A: function() { return this.createDOM.apply(this, ["a", arguments]); }, + DIV: function() { return this.createDOM.apply(this, ["div", arguments]); }, + IMG: function() { return this.createDOM.apply(this, ["img", arguments]); }, + BUTTON: function() { return this.createDOM.apply(this, ["button", arguments]); }, + H1: function() { return this.createDOM.apply(this, ["h1", arguments]); }, + H2: function() { return this.createDOM.apply(this, ["h2", arguments]); }, + H3: function() { return this.createDOM.apply(this, ["h3", arguments]); }, + BR: function() { return this.createDOM.apply(this, ["br", arguments]); }, + TEXTAREA: function() { return this.createDOM.apply(this, ["textarea", arguments]); }, + FORM: function() { return this.createDOM.apply(this, ["form", arguments]); }, + P: function() { return this.createDOM.apply(this, ["p", arguments]); }, + SELECT: function() { return this.createDOM.apply(this, ["select", arguments]); }, + OPTION: function() { return this.createDOM.apply(this, ["option", arguments]); }, + TN: function(text) { return document.createTextNode(text); }, + IFRAME: function() { return this.createDOM.apply(this, ["iframe", arguments]); }, + SCRIPT: function() { return this.createDOM.apply(this, ["script", arguments]); }, + +//// +// Ajax functions +//// + /** + * @returns A new XMLHttpRequest object + */ + getXMLHttpRequest: function() { + var try_these = [ + function () { return new XMLHttpRequest(); }, + function () { return new ActiveXObject('Msxml2.XMLHTTP'); }, + function () { return new ActiveXObject('Microsoft.XMLHTTP'); }, + function () { return new ActiveXObject('Msxml2.XMLHTTP.4.0'); }, + function () { throw "Browser does not support XMLHttpRequest"; } + ]; + for (var i = 0; i < try_these.length; i++) { + var func = try_these[i]; + try { + return func(); + } catch (e) { + } + } + }, + + /** + * Use this function to do a simple HTTP Request + */ + doSimpleXMLHttpRequest: function(url) { + var req = this.getXMLHttpRequest(); + req.open("GET", url, true); + return this.sendXMLHttpRequest(req); + }, + + getRequest: function(url, data) { + var req = this.getXMLHttpRequest(); + req.open("POST", url, true); + req.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + return this.sendXMLHttpRequest(req); + }, + + /** + * Send a XMLHttpRequest + */ + sendXMLHttpRequest: function(req, data) { + var d = new AJSDeferred(req); + + var onreadystatechange = function () { + if (req.readyState == 4) { + try { + status = req.status; + } + catch(e) {}; + if(status == 200 || status == 304 || req.responseText == null) { + d.callback(req, data); + } + else { + d.errback(); + } + } + } + req.onreadystatechange = onreadystatechange; + return d; + }, + + /** + * Represent an object as a string + */ + reprString: function(o) { + return ('"' + o.replace(/(["\\])/g, '\\$1') + '"' + ).replace(/[\f]/g, "\\f" + ).replace(/[\b]/g, "\\b" + ).replace(/[\n]/g, "\\n" + ).replace(/[\t]/g, "\\t" + ).replace(/[\r]/g, "\\r"); + }, + + /** + * Serialize an object to JSON notation + */ + serializeJSON: function(o) { + var objtype = typeof(o); + if (objtype == "undefined") { + return "undefined"; + } else if (objtype == "number" || objtype == "boolean") { + return o + ""; + } else if (o === null) { + return "null"; + } + if (objtype == "string") { + return this.reprString(o); + } + var me = arguments.callee; + var newObj; + if (typeof(o.__json__) == "function") { + newObj = o.__json__(); + if (o !== newObj) { + return me(newObj); + } + } + if (typeof(o.json) == "function") { + newObj = o.json(); + if (o !== newObj) { + return me(newObj); + } + } + if (objtype != "function" && typeof(o.length) == "number") { + var res = []; + for (var i = 0; i < o.length; i++) { + var val = me(o[i]); + if (typeof(val) != "string") { + val = "undefined"; + } + res.push(val); + } + return "[" + res.join(",") + "]"; + } + res = []; + for (var k in o) { + var useKey; + if (typeof(k) == "number") { + useKey = '"' + k + '"'; + } else if (typeof(k) == "string") { + useKey = this.reprString(k); + } else { + // skip non-string or number keys + continue; + } + val = me(o[k]); + if (typeof(val) != "string") { + // skip non-serializable values + continue; + } + res.push(useKey + ":" + val); + } + return "{" + res.join(",") + "}"; + }, + + /** + * Send and recive JSON using GET + */ + loadJSONDoc: function(url) { + var d = this.getRequest(url); + var eval_req = function(req) { + var text = req.responseText; + return eval('(' + text + ')'); + }; + d.addCallback(eval_req); + return d; + }, + + +//// +// Misc. +//// + /** + * Alert the objects key attrs + */ + keys: function(obj) { + var rval = []; + for (var prop in obj) { + rval.push(prop); + } + return rval; + }, + + urlencode: function(str) { + return encodeURIComponent(str.toString()); + }, + + /** + * @returns True if the object is defined, otherwise false + */ + isDefined: function(o) { + return (o != "undefined" && o != null) + }, + + /** + * @returns True if an object is a array, false otherwise + */ + isArray: function(obj) { + try { return (typeof(obj.length) == "undefined") ? false : true; } + catch(e) + { return false; } + }, + + isObject: function(obj) { + return (obj && typeof obj == 'object'); + }, + + /** + * Export DOM elements to the global namespace + */ + exportDOMElements: function() { + UL = this.UL; + LI = this.LI; + TD = this.TD; + TR = this.TR; + TH = this.TH; + TBODY = this.TBODY; + TABLE = this.TABLE; + INPUT = this.INPUT; + SPAN = this.SPAN; + B = this.B; + A = this.A; + DIV = this.DIV; + IMG = this.IMG; + BUTTON = this.BUTTON; + H1 = this.H1; + H2 = this.H2; + H3 = this.H3; + BR = this.BR; + TEXTAREA = this.TEXTAREA; + FORM = this.FORM; + P = this.P; + SELECT = this.SELECT; + OPTION = this.OPTION; + TN = this.TN; + IFRAME = this.IFRAME; + SCRIPT = this.SCRIPT; + }, + + /** + * Export AmiJS functions to the global namespace + */ + exportToGlobalScope: function() { + getElement = this.getElement; + getQueryArgument = this.getQueryArgument; + isIe = this.isIe; + $ = this.getElement; + getElements = this.getElements; + getBody = this.getBody; + getElementsByTagAndClassName = this.getElementsByTagAndClassName; + appendChildNodes = this.appendChildNodes; + ACN = appendChildNodes; + replaceChildNodes = this.replaceChildNodes; + RCN = replaceChildNodes; + insertAfter = this.insertAfter; + insertBefore = this.insertBefore; + showElement = this.showElement; + hideElement = this.hideElement; + isElementHidden = this.isElementHidden; + swapDOM = this.swapDOM; + removeElement = this.removeElement; + isDict = this.isDict; + createDOM = this.createDOM; + this.exportDOMElements(); + getXMLHttpRequest = this.getXMLHttpRequest; + doSimpleXMLHttpRequest = this.doSimpleXMLHttpRequest; + getRequest = this.getRequest; + sendXMLHttpRequest = this.sendXMLHttpRequest; + reprString = this.reprString; + serializeJSON = this.serializeJSON; + loadJSONDoc = this.loadJSONDoc; + keys = this.keys; + isDefined = this.isDefined; + isArray = this.isArray; + } +} + + + +AJSDeferred = function(req) { + this.callbacks = []; + this.req = req; + + this.callback = function (res) { + while (this.callbacks.length > 0) { + var fn = this.callbacks.pop(); + res = fn(res); + } + }; + + this.errback = function(e){ + alert("Error encountered:\n" + e); + }; + + this.addErrback = function(fn) { + this.errback = fn; + }; + + this.addCallback = function(fn) { + this.callbacks.unshift(fn); + }; + + this.addCallbacks = function(fn1, fn2) { + this.addCallback(fn1); + this.addErrback(fn2); + }; + + this.sendReq = function(data) { + if(AJS.isObject(data)) { + var post_data = []; + for(k in data) { + post_data.push(k + "=" + AJS.urlencode(data[k])); + } + post_data = post_data.join("&"); + this.req.send(post_data); + } + else if(AJS.isDefined(data)) + this.req.send(data); + else { + this.req.send(""); + } + }; +}; +AJSDeferred.prototype = new AJSDeferred(); + + + + + + +/**** +Last Modified: 28/04/06 15:26:06 + + GoogieSpell + Google spell checker for your own web-apps :) + Copyright Amir Salihefendic 2006 + LICENSE + GPL (see gpl.txt for more information) + This basically means that you can't use this script with/in proprietary software! + There is another license that permits you to use this script with proprietary software. Check out:... for more info. + AUTHOR + 4mir Salihefendic (http://amix.dk) - amix@amix.dk + VERSION + 3.22 +****/ +var GOOGIE_CUR_LANG = "en"; + +function GoogieSpell(img_dir, server_url) { + var cookie_value; + var lang; + cookie_value = getCookie('language'); + + if(cookie_value != null) + GOOGIE_CUR_LANG = cookie_value; + + this.img_dir = img_dir; + this.server_url = server_url; + + this.lang_to_word = {"da": "Dansk", "de": "Deutsch", "en": "English", + "es": "Español", "fr": "Français", "it": "Italiano", + "nl": "Nederlands", "pl": "Polski", "pt": "Português", + "fi": "Suomi", "sv": "Svenska"}; + this.langlist_codes = AJS.keys(this.lang_to_word); + + this.show_change_lang_pic = true; + + this.lang_state_observer = null; + + this.spelling_state_observer = null; + + this.request = null; + this.error_window = null; + this.language_window = null; + this.edit_layer = null; + this.orginal_text = null; + this.results = null; + this.text_area = null; + this.gselm = null; + this.ta_scroll_top = 0; + this.el_scroll_top = 0; + + this.lang_chck_spell = "Check spelling"; + this.lang_rsm_edt = "Resume editing"; + this.lang_close = "Close"; + this.lang_no_error_found = "No spelling errors found"; + this.lang_revert = "Revert to"; + this.show_spell_img = false; // modified by roundcube +} + +GoogieSpell.prototype.setStateChanged = function(current_state) { + if(this.spelling_state_observer != null) + this.spelling_state_observer(current_state); +} + +GoogieSpell.item_onmouseover = function(e) { + var elm = GoogieSpell.getEventElm(e); + if(elm.className != "googie_list_close" && elm.className != "googie_list_revert") + elm.className = "googie_list_onhover"; + else + elm.parentNode.className = "googie_list_onhover"; +} + +GoogieSpell.item_onmouseout = function(e) { + var elm = GoogieSpell.getEventElm(e); + if(elm.className != "googie_list_close" && elm.className != "googie_list_revert") + elm.className = "googie_list_onout"; + else + elm.parentNode.className = "googie_list_onout"; +} + +GoogieSpell.prototype.getGoogleUrl = function() { + return this.server_url + GOOGIE_CUR_LANG; +} + +GoogieSpell.prototype.spellCheck = function(elm, name) { + this.ta_scroll_top = this.text_area.scrollTop; + + this.appendIndicator(elm); + + try { + this.hideLangWindow(); + } + catch(e) {} + + this.gselm = elm; + + this.createEditLayer(this.text_area.offsetWidth, this.text_area.offsetHeight); + + this.createErrorWindow(); + AJS.getBody().appendChild(this.error_window); + + try { netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead"); } + catch (e) { } + + this.gselm.onclick = null; + + this.orginal_text = this.text_area.value; + var me = this; + + //Create request + var d = AJS.getRequest(this.getGoogleUrl()); + var reqdone = function(req) { + var r_text = req.responseText; + if(r_text.match(/<c.*>/) != null) { + var results = GoogieSpell.parseResult(r_text); + //Before parsing be sure that errors were found + me.results = results; + me.showErrorsInIframe(results); + me.resumeEditingState(); + } + else { + me.flashNoSpellingErrorState(); + } + me.removeIndicator(); + }; + + var reqfailed = function(req) { + alert("An error was encountered on the server. Please try again later."); + AJS.removeElement(me.gselm); + me.checkSpellingState(); + me.removeIndicator(); + }; + + d.addCallback(reqdone); + d.addErrback(reqfailed); + + var req_text = GoogieSpell.escapeSepcial(this.orginal_text); + d.sendReq(GoogieSpell.createXMLReq(req_text)); +} + +GoogieSpell.escapeSepcial = function(val) { + return val.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); +} + +GoogieSpell.createXMLReq = function (text) { + return '<?xml version="1.0" encoding="utf-8" ?><spellrequest textalreadyclipped="0" ignoredups="0" ignoredigits="1" ignoreallcaps="1"><text>' + text + '</text></spellrequest>'; +} + +//Retunrs an array +//result[item] -> ['attrs'] +// ['suggestions'] +GoogieSpell.parseResult = function(r_text) { + var re_split_attr_c = /\w="\d+"/g; + var re_split_text = /\t/g; + + var matched_c = r_text.match(/<c[^>]*>[^<]*<\/c>/g); + var results = new Array(); + + for(var i=0; i < matched_c.length; i++) { + var item = new Array(); + + //Get attributes + item['attrs'] = new Array(); + var split_c = matched_c[i].match(re_split_attr_c); + for(var j=0; j < split_c.length; j++) { + var c_attr = split_c[j].split(/=/); + item['attrs'][c_attr[0]] = parseInt(c_attr[1].replace('"', '')); + } + + //Get suggestions + item['suggestions'] = new Array(); + var only_text = matched_c[i].replace(/<[^>]*>/g, ""); + var split_t = only_text.split(re_split_text); + for(var k=0; k < split_t.length; k++) { + if(split_t[k] != "") + item['suggestions'].push(split_t[k]); + } + results.push(item); + } + return results; +} + +/**** + Error window (the drop-down window) +****/ +GoogieSpell.prototype.createErrorWindow = function() { + this.error_window = AJS.DIV(); + this.error_window.className = "googie_window"; +} + +GoogieSpell.prototype.hideErrorWindow = function() { + this.error_window.style.visibility = "hidden"; +} + +GoogieSpell.prototype.updateOrginalText = function(offset, old_value, new_value, id) { + var part_1 = this.orginal_text.substring(0, offset); + var part_2 = this.orginal_text.substring(offset+old_value.length); + this.orginal_text = part_1 + new_value + part_2; + var add_2_offset = new_value.length - old_value.length; + for(var j=0; j < this.results.length; j++) { + //Don't edit the offset of the current item + if(j != id && j > id){ + this.results[j]['attrs']['o'] += add_2_offset; + } + } +} + +GoogieSpell.prototype.saveOldValue = function (id, old_value) { + this.results[id]['is_changed'] = true; + this.results[id]['old_value'] = old_value; +} + +GoogieSpell.prototype.showErrorWindow = function(elm, id) { + var me = this; + + var abs_pos = GoogieSpell.absolutePosition(elm); + abs_pos.y -= this.edit_layer.scrollTop; + this.error_window.style.visibility = "visible"; + this.error_window.style.top = (abs_pos.y+20) + "px"; + this.error_window.style.left = (abs_pos.x) + "px"; + this.error_window.innerHTML = ""; + + //Build up the result list + var table = AJS.TABLE({'class': 'googie_list'}); + var list = AJS.TBODY(); + + var suggestions = this.results[id]['suggestions']; + var offset = this.results[id]['attrs']['o']; + var len = this.results[id]['attrs']['l']; + + if(suggestions.length == 0) { + var row = AJS.TR(); + var item = AJS.TD(); + var dummy = AJS.SPAN(); + item.appendChild(AJS.TN("No suggestions :(")); + row.appendChild(item); + list.appendChild(row); + } + + for(i=0; i < suggestions.length; i++) { + var row = AJS.TR(); + var item = AJS.TD(); + var dummy = AJS.SPAN(); + dummy.innerHTML = suggestions[i]; + item.appendChild(AJS.TN(dummy.innerHTML)); + + item.onclick = function(e) { + var l_elm = GoogieSpell.getEventElm(e); + var old_value = elm.innerHTML; + var new_value = l_elm.innerHTML; + + elm.style.color = "green"; + elm.innerHTML = l_elm.innerHTML; + me.hideErrorWindow(); + + me.updateOrginalText(offset, old_value, new_value, id); + + //Update to the new length + me.results[id]['attrs']['l'] = new_value.length; + me.saveOldValue(id, old_value); + }; + item.onmouseover = GoogieSpell.item_onmouseover; + item.onmouseout = GoogieSpell.item_onmouseout; + row.appendChild(item); + list.appendChild(row); + } + + //The element is changed, append the revert + if(this.results[id]['is_changed']) { + var old_value = this.results[id]['old_value']; + var offset = this.results[id]['attrs']['o']; + var revert_row = AJS.TR(); + var revert = AJS.TD(); + + revert.onmouseover = GoogieSpell.item_onmouseover; + revert.onmouseout = GoogieSpell.item_onmouseout; + var rev_span = AJS.SPAN({'class': 'googie_list_revert'}); + rev_span.innerHTML = this.lang_revert + " " + old_value; + revert.appendChild(rev_span); + + revert.onclick = function(e) { + me.updateOrginalText(offset, elm.innerHTML, old_value, id); + elm.style.color = "#b91414"; + elm.innerHTML = old_value; + me.hideErrorWindow(); + }; + + revert_row.appendChild(revert); + list.appendChild(revert_row); + } + + //Append the edit box + var edit_row = AJS.TR(); + var edit = AJS.TD(); + + var edit_input = AJS.INPUT({'style': 'width: 120px; margin:0; padding:0'}); + + var onsub = function () { + if(edit_input.value != "") { + me.saveOldValue(id, elm.innerHTML); + me.updateOrginalText(offset, elm.innerHTML, edit_input.value, id); + elm.style.color = "green" + elm.innerHTML = edit_input.value; + + me.hideErrorWindow(); + return false; + } + }; + + var ok_pic = AJS.IMG({'src': this.img_dir + "ok.gif", 'style': 'width: 32px; height: 16px; margin-left: 2px; margin-right: 2px;'}); + var edit_form = AJS.FORM({'style': 'margin: 0; padding: 0'}, edit_input, ok_pic); + ok_pic.onclick = onsub; + edit_form.onsubmit = onsub; + + edit.appendChild(edit_form); + edit_row.appendChild(edit); + list.appendChild(edit_row); + + //Close button + var close_row = AJS.TR(); + var close = AJS.TD(); + + close.onmouseover = GoogieSpell.item_onmouseover; + close.onmouseout = GoogieSpell.item_onmouseout; + + var spn_close = AJS.SPAN({'class': 'googie_list_close'}); + spn_close.innerHTML = this.lang_close; + close.appendChild(spn_close); + close.onclick = function() { me.hideErrorWindow()}; + close_row.appendChild(close); + list.appendChild(close_row); + + table.appendChild(list); + this.error_window.appendChild(table); +} + + +/**** + Edit layer (the layer where the suggestions are stored) +****/ +GoogieSpell.prototype.createEditLayer = function(width, height) { + this.edit_layer = AJS.DIV({'class': 'googie_edit_layer'}); + + //Set the style so it looks like edit areas + this.edit_layer.className = this.text_area.className; + this.edit_layer.style.border = "1px solid #999"; + this.edit_layer.style.overflow = "auto"; + this.edit_layer.style.backgroundColor = "#F1EDFE"; + this.edit_layer.style.padding = "3px"; + + this.edit_layer.style.width = (width-8) + "px"; + this.edit_layer.style.height = height + "px"; +} + +GoogieSpell.prototype.resumeEditing = function(e, me) { + this.setStateChanged("check_spelling"); + me.switch_lan_pic.style.display = "inline"; + + this.el_scroll_top = me.edit_layer.scrollTop; + + var elm = GoogieSpell.getEventElm(e); + AJS.replaceChildNodes(elm, this.createSpellDiv()); + + elm.onclick = function(e) { + me.spellCheck(elm, me.text_area.id); + }; + me.hideErrorWindow(); + + //Remove the EDIT_LAYER + me.edit_layer.parentNode.removeChild(me.edit_layer); + + me.text_area.value = me.orginal_text; + AJS.showElement(me.text_area); + me.gselm.className = "googie_no_style"; + + me.text_area.scrollTop = this.el_scroll_top; + + elm.onmouseout = null; +} + +GoogieSpell.prototype.createErrorLink = function(text, id) { + var elm = AJS.SPAN({'class': 'googie_link'}); + var me = this; + elm.onclick = function () { + me.showErrorWindow(elm, id); + }; + elm.innerHTML = text; + return elm; +} + +GoogieSpell.createPart = function(txt_part) { + if(txt_part == " ") + return AJS.TN(" "); + var result = AJS.SPAN(); + + var is_first = true; + var is_safari = (navigator.userAgent.toLowerCase().indexOf("safari") != -1); + + var part = AJS.SPAN(); + txt_part = GoogieSpell.escapeSepcial(txt_part); + txt_part = txt_part.replace(/\n/g, "<br>"); + txt_part = txt_part.replace(/ /g, " "); + txt_part = txt_part.replace(/^ /g, " "); + txt_part = txt_part.replace(/ $/g, " "); + + part.innerHTML = txt_part; + + return part; +} + +GoogieSpell.prototype.showErrorsInIframe = function(results) { + var output = AJS.DIV(); + output.style.textAlign = "left"; + var pointer = 0; + for(var i=0; i < results.length; i++) { + var offset = results[i]['attrs']['o']; + var len = results[i]['attrs']['l']; + + var part_1_text = this.orginal_text.substring(pointer, offset); + var part_1 = GoogieSpell.createPart(part_1_text); + output.appendChild(part_1); + pointer += offset - pointer; + + //If the last child was an error, then insert some space + output.appendChild(this.createErrorLink(this.orginal_text.substr(offset, len), i)); + pointer += len; + } + //Insert the rest of the orginal text + var part_2_text = this.orginal_text.substr(pointer, this.orginal_text.length); + + var part_2 = GoogieSpell.createPart(part_2_text); + output.appendChild(part_2); + + this.edit_layer.appendChild(output); + + //Hide text area + AJS.hideElement(this.text_area); + this.text_area.parentNode.insertBefore(this.edit_layer, this.text_area.nextSibling); + this.edit_layer.scrollTop = this.ta_scroll_top; +} + +GoogieSpell.Position = function(x, y) { + this.x = x; + this.y = y; +} + +//Get the absolute position of menu_slide +GoogieSpell.absolutePosition = function(element) { + //Create a new object that has elements y and x pos... + var posObj = new GoogieSpell.Position(element.offsetLeft, element.offsetTop); + + //Check if the element has an offsetParent - if it has .. loop until it has not + if(element.offsetParent) { + var temp_pos = GoogieSpell.absolutePosition(element.offsetParent); + posObj.x += temp_pos.x; + posObj.y += temp_pos.y; + } + return posObj; +} + +GoogieSpell.getEventElm = function(e) { + var targ; + if (!e) var e = window.event; + if (e.target) targ = e.target; + else if (e.srcElement) targ = e.srcElement; + if (targ.nodeType == 3) // defeat Safari bug + targ = targ.parentNode; + return targ; +} + +GoogieSpell.prototype.removeIndicator = function(elm) { + AJS.removeElement(this.indicator); +} + +GoogieSpell.prototype.appendIndicator = function(elm) { + var img = AJS.IMG({'src': this.img_dir + 'indicator.gif', 'style': 'margin-right: 5px;'}); + img.style.width = "16px"; + img.style.height = "16px"; + this.indicator = img; + img.style.textDecoration = "none"; + AJS.insertBefore(img, elm); +} + +/**** + Choose language +****/ +GoogieSpell.prototype.createLangWindow = function() { + this.language_window = AJS.DIV({'class': 'googie_window'}); + this.language_window.style.width = "130px"; + + //Build up the result list + var table = AJS.TABLE({'class': 'googie_list'}); + var list = AJS.TBODY(); + + this.lang_elms = new Array(); + + for(i=0; i < this.langlist_codes.length; i++) { + var row = AJS.TR(); + var item = AJS.TD(); + item.googieId = this.langlist_codes[i]; + this.lang_elms.push(item); + var lang_span = AJS.SPAN(); + lang_span.innerHTML = this.lang_to_word[this.langlist_codes[i]]; + item.appendChild(AJS.TN(lang_span.innerHTML)); + + var me = this; + + item.onclick = function(e) { + var elm = GoogieSpell.getEventElm(e); + me.deHighlightCurSel(); + + me.setCurrentLanguage(elm.googieId); + + if(me.lang_state_observer != null) { + me.lang_state_observer(); + } + + me.highlightCurSel(); + me.hideLangWindow(); + }; + + item.onmouseover = function(e) { + var i_it = GoogieSpell.getEventElm(e); + if(i_it.className != "googie_list_selected") + i_it.className = "googie_list_onhover"; + }; + item.onmouseout = function(e) { + var i_it = GoogieSpell.getEventElm(e); + if(i_it.className != "googie_list_selected") + i_it.className = "googie_list_onout"; + }; + + row.appendChild(item); + list.appendChild(row); + } + + this.highlightCurSel(); + + //Close button + var close_row = AJS.TR(); + var close = AJS.TD(); + close.onmouseover = GoogieSpell.item_onmouseover; + close.onmouseout = GoogieSpell.item_onmouseout; + var spn_close = AJS.SPAN({'class': 'googie_list_close'}); + spn_close.innerHTML = this.lang_close; + close.appendChild(spn_close); + var me = this; + close.onclick = function(e) { + me.hideLangWindow(); GoogieSpell.item_onmouseout(e); + }; + close_row.appendChild(close); + list.appendChild(close_row); + + table.appendChild(list); + this.language_window.appendChild(table); +} + +GoogieSpell.prototype.setCurrentLanguage = function(lan_code) { + GOOGIE_CUR_LANG = lan_code; + + //Set cookie + var now = new Date(); + now.setTime(now.getTime() + 365 * 24 * 60 * 60 * 1000); + setCookie('language', lan_code, now); +} + +GoogieSpell.prototype.hideLangWindow = function() { + this.language_window.style.visibility = "hidden"; + this.switch_lan_pic.className = "googie_lang_3d_on"; +} + +GoogieSpell.prototype.deHighlightCurSel = function() { + this.lang_cur_elm.className = "googie_list_onout"; +} + +GoogieSpell.prototype.highlightCurSel = function() { + for(var i=0; i < this.lang_elms.length; i++) { + if(this.lang_elms[i].googieId == GOOGIE_CUR_LANG) { + this.lang_elms[i].className = "googie_list_selected"; + this.lang_cur_elm = this.lang_elms[i]; + } + else { + this.lang_elms[i].className = "googie_list_onout"; + } + } +} + +GoogieSpell.prototype.showLangWindow = function(elm, ofst_top, ofst_left) { + if(!AJS.isDefined(ofst_top)) + ofst_top = 20; + if(!AJS.isDefined(ofst_left)) + ofst_left = 50; + + this.createLangWindow(); + AJS.getBody().appendChild(this.language_window); + + var abs_pos = GoogieSpell.absolutePosition(elm); + AJS.showElement(this.language_window); + this.language_window.style.top = (abs_pos.y+ofst_top) + "px"; + this.language_window.style.left = (abs_pos.x+ofst_left-this.language_window.offsetWidth) + "px"; + this.highlightCurSel(); + this.language_window.style.visibility = "visible"; +} + +GoogieSpell.prototype.flashNoSpellingErrorState = function() { + this.setStateChanged("no_error_found"); + var me = this; + AJS.hideElement(this.switch_lan_pic); + this.gselm.innerHTML = this.lang_no_error_found; + this.gselm.className = "googie_check_spelling_ok"; + this.gselm.style.textDecoration = "none"; + this.gselm.style.cursor = "default"; + var fu = function() { + AJS.removeElement(me.gselm); + me.checkSpellingState(); + }; + setTimeout(fu, 1000); +} + +GoogieSpell.prototype.resumeEditingState = function() { + this.setStateChanged("resume_editing"); + var me = this; + AJS.hideElement(me.switch_lan_pic); + + //Change link text to resume + me.gselm.innerHTML = this.lang_rsm_edt; + me.gselm.onclick = function(e) { + me.resumeEditing(e, me); + } + me.gselm.className = "googie_check_spelling_ok"; + me.edit_layer.scrollTop = me.ta_scroll_top; +} + +GoogieSpell.prototype.createChangeLangPic = function() { + var switch_lan = AJS.A({'class': 'googie_lang_3d_on', 'style': 'padding-left: 6px;'}, AJS.IMG({'src': this.img_dir + 'change_lang.gif', 'alt': "Change language"})); + switch_lan.onmouseover = function() { + if(this.className != "googie_lang_3d_click") + this.className = "googie_lang_3d_on"; + } + + var me = this; + switch_lan.onclick = function() { + if(this.className == "googie_lang_3d_click") { + me.hideLangWindow(); + } + else { + me.showLangWindow(switch_lan); + this.className = "googie_lang_3d_click"; + } + } + return switch_lan; +} + +GoogieSpell.prototype.createSpellDiv = function() { + var chk_spell = AJS.SPAN({'class': 'googie_check_spelling_link'}); + chk_spell.innerHTML = this.lang_chck_spell; + var spell_img = null; + if(this.show_spell_img) + spell_img = AJS.IMG({'src': this.img_dir + "spellc.gif"}); + return AJS.SPAN(spell_img, " ", chk_spell); +} + +GoogieSpell.prototype.checkSpellingState = function() { + this.setStateChanged("check_spelling"); + var me = this; + if(this.show_change_lang_pic) + this.switch_lan_pic = this.createChangeLangPic(); + else + this.switch_lan_pic = AJS.SPAN(); + + var span_chck = this.createSpellDiv(); + span_chck.onclick = function() { + me.spellCheck(span_chck); + } + AJS.appendChildNodes(this.spell_container, span_chck, " ", this.switch_lan_pic); +} + +GoogieSpell.prototype.setLanguages = function(lang_dict) { + this.lang_to_word = lang_dict; + this.langlist_codes = AJS.keys(lang_dict); +} + +GoogieSpell.prototype.decorateTextarea = function(id, /*optional*/spell_container_id, force_width) { + var me = this; + + if(typeof(id) == "string") + this.text_area = AJS.getElement(id); + else + this.text_area = id; + + var r_width; + + if(this.text_area != null) { + if(AJS.isDefined(spell_container_id)) { + if(typeof(spell_container_id) == "string") + this.spell_container = AJS.getElement(spell_container_id); + else + this.spell_container = spell_container_id; + } + else { + var table = AJS.TABLE(); + var tbody = AJS.TBODY(); + var tr = AJS.TR(); + if(AJS.isDefined(force_width)) { + r_width = force_width; + } + else { + r_width = this.text_area.offsetWidth + "px"; + } + + var spell_container = AJS.TD(); + this.spell_container = spell_container; + + tr.appendChild(spell_container); + + tbody.appendChild(tr); + table.appendChild(tbody); + + AJS.insertBefore(table, this.text_area); + + //Set width + table.style.width = '100%'; // modified by roundcube (old: r_width) + spell_container.style.width = r_width; + spell_container.style.textAlign = "right"; + } + + this.checkSpellingState(); + } + else { + alert("Text area not found"); + } +} diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index b25c3690f..1f9618d5b 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -297,11 +297,14 @@ function rcmail_compose_header_from($attrib) function rcmail_compose_body($attrib) { - global $CONFIG, $REPLY_MESSAGE, $FORWARD_MESSAGE; + global $CONFIG, $OUTPUT, $REPLY_MESSAGE, $FORWARD_MESSAGE; list($form_start, $form_end) = get_form_tags($attrib); unset($attrib['form']); + if (empty($attrib['id'])) + $attrib['id'] = 'rcmComposeMessage'; + $attrib['name'] = '_message'; $textarea = new textarea($attrib); @@ -330,7 +333,15 @@ function rcmail_compose_body($attrib) $out = $form_start ? "$form_start\n" : ''; $out .= $textarea->show($body); $out .= $form_end ? "\n$form_end" : ''; - + + // include GoogieSpell + $OUTPUT->include_script('googiespell.js'); + + $OUTPUT->add_script(sprintf("var googie1 = new GoogieSpell('\$__skin_path/images/googiespell/','%s&_action=spell&lang=');\n". + "googie1.decorateTextarea('%s');", + $GLOBALS['COMM_PATH'], + $attrib['id']), 'foot'); + return $out; } diff --git a/program/steps/mail/spell.inc b/program/steps/mail/spell.inc new file mode 100644 index 000000000..2ba524e6a --- /dev/null +++ b/program/steps/mail/spell.inc @@ -0,0 +1,50 @@ +<?php + +/* + +-----------------------------------------------------------------------+ + | program/steps/mail/spell.inc | + | | + | This file is part of the RoundCube Webmail client | + | Copyright (C) 2005, RoundCube Dev. - Switzerland | + | Licensed under the GNU GPL | + | | + | PURPOSE: | + | Submit request to Google's spell checking engine | + | | + | CREDITS: | + | Script from GoogieSpell by amix.dk | + | | + +-----------------------------------------------------------------------+ + | Author: Thomas Bruederli <roundcube@gmail.com> | + +-----------------------------------------------------------------------+ + + $Id$ + +*/ + +$REMOTE_REQUEST = TRUE; + +$google = "www.google.com"; +$lang = $_GET['lang']; +$path = "/tbproxy/spell?lang=$lang"; +$data = file_get_contents('php://input'); +$store = ""; + +if ($fp = fsockopen($google, 80, $errno, $errstr, 30)) + { + $out = "POST $path HTTP/1.0\r\n"; + $out .= "Host: $google\r\n"; + $out .= "Content-Length: " . strlen($data) . "\r\n"; + $out .= "Content-type: application/x-www-form-urlencoded\r\n"; + $out .= "Connection: Close\r\n\r\n"; + $out .= $data; + fwrite($fp, $out); + while (!feof($fp)) + $store .= fgets($fp, 128); + fclose($fp); + } + +print $store; +exit; + +?>
\ No newline at end of file diff --git a/skins/default/googiespell.css b/skins/default/googiespell.css new file mode 100755 index 000000000..46ac0b0aa --- /dev/null +++ b/skins/default/googiespell.css @@ -0,0 +1,94 @@ +/***** modified styles for GoogieSpell *****/ + +.googie_window { + font-size: 11px; + width: 185px; + text-align: left; + border: 1px solid #666666; + background-color: #ffffff; + margin: 0; + padding: 0; + position: absolute; + visibility: hidden; +} + +.googie_list { + width: 100%; + margin: 0; + padding: 0; +} + +.googie_list td { + font-size: 11px; + padding-left: 10px; + padding-right: 10px; + padding-top: 2px; + padding-bottom: 2px; + cursor: pointer; + list-style-type: none; +} + +.googie_list_onhover { + color: #FFFFFF; + background-color: #CC3333; +} + +.googie_list_onout { + background-color: #F6F6F6; +} + +.googie_list_selected { + background-color: #cccccc; + font-weight: bold; +} + +.googie_list_close { + color: #b91414; +} + +.googie_list_revert { + color: #b91479; +} + +.googie_link { + color: #b91414; + text-decoration: underline; + cursor: pointer; +} + +.googie_check_spelling_link { + color: #CC0000; + font-size: 11px; + text-decoration: none; + cursor: pointer; +} + +.googie_check_spelling_link:hover { + text-decoration: underline; +} + +.googie_no_style { + text-decoration: none; +} + +.googie_check_spelling_ok { + color: green; + text-decoration: underline; + cursor: pointer; +} + +.googie_lang_3d_click img { + vertical-align: middle; + border-top: 1px solid #555; + border-left: 1px solid #555; + border-right: 1px solid #b1b1b1; + border-bottom: 1px solid #b1b1b1; +} + +.googie_lang_3d_on img { + vertical-align: middle; + border-top: 1px solid #b1b1b1; + border-left: 1px solid #b1b1b1; + border-right: 1px solid #555; + border-bottom: 1px solid #555; +} diff --git a/skins/default/images/googiespell/change_lang.gif b/skins/default/images/googiespell/change_lang.gif Binary files differnew file mode 100644 index 000000000..81451832c --- /dev/null +++ b/skins/default/images/googiespell/change_lang.gif diff --git a/skins/default/images/googiespell/indicator.gif b/skins/default/images/googiespell/indicator.gif Binary files differnew file mode 100644 index 000000000..b556bb00f --- /dev/null +++ b/skins/default/images/googiespell/indicator.gif diff --git a/skins/default/images/googiespell/ok.gif b/skins/default/images/googiespell/ok.gif Binary files differnew file mode 100644 index 000000000..04727e28d --- /dev/null +++ b/skins/default/images/googiespell/ok.gif diff --git a/skins/default/images/googiespell/spellc.gif b/skins/default/images/googiespell/spellc.gif Binary files differnew file mode 100644 index 000000000..6ed936090 --- /dev/null +++ b/skins/default/images/googiespell/spellc.gif diff --git a/skins/default/mail.css b/skins/default/mail.css index 333aed0ae..8cfb954b1 100644 --- a/skins/default/mail.css +++ b/skins/default/mail.css @@ -749,7 +749,7 @@ div.message-part pre #compose-body { - margin-top: 10px; + margin-top: 5px; margin-bottom: 5px; width: 99% !important; width: 95%; diff --git a/skins/default/templates/compose.html b/skins/default/templates/compose.html index 07ebf82ae..0630a5713 100644 --- a/skins/default/templates/compose.html +++ b/skins/default/templates/compose.html @@ -4,6 +4,7 @@ <title><roundcube:object name="productname" /> :: <roundcube:label name="compose" /></title> <roundcube:include file="/includes/links.html" /> <link rel="stylesheet" type="text/css" href="/mail.css" /> +<link rel="stylesheet" type="text/css" href="/googiespell.css" /> <script type="text/javascript"> <!-- |