| | |
| | | /* |
| | | +-----------------------------------------------------------------------+ |
| | | | Roundcube Webmail Client Script | |
| | | | | |
| | | | This file is part of the Roundcube Webmail client | |
| | | | Copyright (C) 2005-2014, The Roundcube Dev Team | |
| | | | Copyright (C) 2011-2014, Kolab Systems AG | |
| | | | | |
| | | | Licensed under the GNU General Public License version 3 or | |
| | | | any later version with exceptions for skins & plugins. | |
| | | | See the README file for a full license statement. | |
| | | | | |
| | | +-----------------------------------------------------------------------+ |
| | | | Authors: Thomas Bruederli <roundcube@gmail.com> | |
| | | | Aleksander 'A.L.E.C' Machniak <alec@alec.pl> | |
| | | | Charles McNulty <charles@charlesmcnulty.com> | |
| | | +-----------------------------------------------------------------------+ |
| | | | Requires: jquery.js, common.js, list.js | |
| | | +-----------------------------------------------------------------------+ |
| | | */ |
| | | /** |
| | | * Roundcube Webmail Client Script |
| | | * |
| | | * This file is part of the Roundcube Webmail client |
| | | * |
| | | * @licstart The following is the entire license notice for the |
| | | * JavaScript code in this file. |
| | | * |
| | | * Copyright (C) 2005-2014, The Roundcube Dev Team |
| | | * Copyright (C) 2011-2014, Kolab Systems AG |
| | | * |
| | | * The JavaScript code in this page is free software: you can |
| | | * redistribute it and/or modify it under the terms of the GNU |
| | | * General Public License (GNU GPL) as published by the Free Software |
| | | * Foundation, either version 3 of the License, or (at your option) |
| | | * any later version. The code is distributed WITHOUT ANY WARRANTY; |
| | | * without even the implied warranty of MERCHANTABILITY or FITNESS |
| | | * FOR A PARTICULAR PURPOSE. See the GNU GPL for more details. |
| | | * |
| | | * As additional permission under GNU GPL version 3 section 7, you |
| | | * may distribute non-source (e.g., minimized or compacted) forms of |
| | | * that code without the copy of the GNU GPL normally required by |
| | | * section 4, provided you include this license notice and a URL |
| | | * through which recipients can access the Corresponding Source. |
| | | * |
| | | * @licend The above is the entire license notice |
| | | * for the JavaScript code in this file. |
| | | * |
| | | * @author Thomas Bruederli <roundcube@gmail.com> |
| | | * @author Aleksander 'A.L.E.C' Machniak <alec@alec.pl> |
| | | * @author Charles McNulty <charles@charlesmcnulty.com> |
| | | * |
| | | * @requires jquery.js, common.js, list.js |
| | | */ |
| | | |
| | | function rcube_webmail() |
| | | { |
| | |
| | | if (this.pending_message) |
| | | this.display_message(this.pending_message[0], this.pending_message[1], this.pending_message[2]); |
| | | |
| | | // map implicit containers |
| | | if (this.gui_objects.folderlist) { |
| | | this.gui_containers.foldertray = $(this.gui_objects.folderlist); |
| | | |
| | | // init treelist widget |
| | | if (window.rcube_treelist_widget) { |
| | | this.treelist = new rcube_treelist_widget(this.gui_objects.folderlist, { |
| | | // init treelist widget |
| | | if (this.gui_objects.folderlist && window.rcube_treelist_widget) { |
| | | this.treelist = new rcube_treelist_widget(this.gui_objects.folderlist, { |
| | | id_prefix: 'rcmli', |
| | | id_encode: this.html_identifier_encode, |
| | | id_decode: this.html_identifier_decode, |
| | | check_droptarget: function(node) { return !node.virtual && ref.check_droptarget(node.id) } |
| | | }); |
| | | this.treelist |
| | | .addEventListener('collapse', function(node) { ref.folder_collapsed(node) }) |
| | | .addEventListener('expand', function(node) { ref.folder_collapsed(node) }) |
| | | .addEventListener('select', function(node) { ref.triggerEvent('selectfolder', { folder:node.id, prefix:'rcmli' }) }); |
| | | } |
| | | }); |
| | | |
| | | this.treelist |
| | | .addEventListener('collapse', function(node) { ref.folder_collapsed(node) }) |
| | | .addEventListener('expand', function(node) { ref.folder_collapsed(node) }) |
| | | .addEventListener('select', function(node) { ref.triggerEvent('selectfolder', { folder:node.id, prefix:'rcmli' }) }); |
| | | } |
| | | |
| | | // activate html5 file drop feature (if browser supports it and if configured) |
| | |
| | | break; |
| | | |
| | | case 'list': |
| | | // re-send search query for the selected folder |
| | | if (props && props != '' && this.env.search_request && this.gui_objects.qsearchbox.value) { |
| | | var oldmbox = this.env.search_scope == 'all' ? '*' : this.env.mailbox; |
| | | this.env.search_mods[props] = this.env.search_mods[oldmbox]; // copy search mods from active search |
| | | this.env.mailbox = props; |
| | | this.env.search_scope = 'sub'; |
| | | this.qsearch(this.gui_objects.qsearchbox.value); |
| | | this.select_folder(this.env.mailbox, '', true); |
| | | break; |
| | | if (props && props != '') { |
| | | this.reset_qsearch(); |
| | | } |
| | | |
| | | if (this.env.action == 'compose' && this.env.extwin) |
| | | if (this.env.action == 'compose' && this.env.extwin) { |
| | | window.close(); |
| | | } |
| | | else if (this.task == 'mail') { |
| | | this.list_mailbox(props); |
| | | this.set_button_titles(); |
| | |
| | | list = this.message_list, |
| | | rows = list.rows, |
| | | message = this.env.messages[uid], |
| | | msg_id = this.html_identifier(uid,true), |
| | | row_class = 'message' |
| | | + (!flags.seen ? ' unread' : '') |
| | | + (flags.deleted ? ' deleted' : '') |
| | | + (flags.flagged ? ' flagged' : '') |
| | | + (message.selected ? ' selected' : ''), |
| | | row = { cols:[], style:{}, id:'rcmrow'+this.html_identifier(uid,true), uid:uid }; |
| | | row = { cols:[], style:{}, id:'rcmrow'+msg_id, uid:uid }; |
| | | |
| | | // message status icons |
| | | css_class = 'msgicon'; |
| | |
| | | if (this.env.threading) { |
| | | if (message.depth) { |
| | | // This assumes that div width is hardcoded to 15px, |
| | | tree += '<span id="rcmtab' + row.id + '" class="branch" style="width:' + (message.depth * 15) + 'px;"> </span>'; |
| | | tree += '<span id="rcmtab' + msg_id + '" class="branch" style="width:' + (message.depth * 15) + 'px;"> </span>'; |
| | | |
| | | if ((rows[message.parent_uid] && rows[message.parent_uid].expanded === false) |
| | | || ((this.env.autoexpand_threads == 0 || this.env.autoexpand_threads == 2) && |
| | |
| | | html = '<span class="prio'+flags.prio+'"> </span>'; |
| | | else |
| | | html = ' '; |
| | | } |
| | | else if (c == 'folder') { |
| | | html = '<span onmouseover="rcube_webmail.long_subject_title(this)">' + cols[c] + '<span>'; |
| | | } |
| | | else |
| | | html = cols[c]; |
| | |
| | | rows = this.message_list ? this.message_list.rows : {}; |
| | | |
| | | if (typeof uids == 'string') |
| | | uids = String(uids).split(','); |
| | | uids = uids.split(','); |
| | | |
| | | for (i=0, len=uids.length; i<len; i++) { |
| | | uid = uids[i]; |
| | |
| | | $(tinyMCE.get(props.id).getBody()).css('font-family', rcmail.env.default_font); |
| | | }, 500); |
| | | } |
| | | else { |
| | | var thisMCE = tinyMCE.get(props.id), existingHtml; |
| | | |
| | | if (existingHtml = thisMCE.getContent()) { |
| | | if (!confirm(this.get_label('editorwarning'))) { |
| | | return false; |
| | | } |
| | | this.html2plain(existingHtml, props.id); |
| | | } |
| | | else if (this.html2plain(tinyMCE.get(props.id).getContent(), props.id)) |
| | | tinyMCE.execCommand('mceRemoveControl', false, props.id); |
| | | } |
| | | |
| | | return true; |
| | | }; |
| | |
| | | return false; |
| | | }; |
| | | |
| | | this.continue_search = function(request_id) |
| | | { |
| | | var lock = ref.set_busy(true, 'stillsearching'); |
| | | |
| | | setTimeout(function(){ |
| | | var url = ref.search_params(); |
| | | url._continue = request_id; |
| | | ref.env.qsearch = { lock: lock, request: ref.http_request('search', url, lock) }; |
| | | }, 100); |
| | | }; |
| | | |
| | | // build URL params for search |
| | | this.search_params = function(search, filter, smods) |
| | | { |
| | |
| | | this.ksearch_input.value = pre + insert + end; |
| | | |
| | | // set caret to insert pos |
| | | cpos = p+insert.length; |
| | | if (this.ksearch_input.setSelectionRange) |
| | | this.ksearch_input.setSelectionRange(cpos, cpos); |
| | | this.set_caret_pos(this.ksearch_input, p + insert.length); |
| | | |
| | | if (trigger) { |
| | | this.triggerEvent('autocomplete_insert', { field:this.ksearch_input, insert:insert, data:this.env.contacts[id] }); |
| | |
| | | |
| | | this.html2plain = function(htmlText, id) |
| | | { |
| | | // warn the user (if converted content is not empty) |
| | | if (!htmlText || !(htmlText.replace(/<[^>]+>| |\s/g, '')).length) { |
| | | // without setTimeout() here, textarea is filled with initial (onload) content |
| | | setTimeout(function() { $('#'+id).val(''); }, 50); |
| | | return true; |
| | | } |
| | | |
| | | if (!confirm(this.get_label('editorwarning'))) |
| | | return false; |
| | | |
| | | var url = '?_task=utils&_action=html2text', |
| | | lock = this.set_busy(true, 'converting'); |
| | | |
| | |
| | | error: function(o, status, err) { ref.http_error(o, status, err, lock); }, |
| | | success: function(data) { ref.set_busy(false, null, lock); $('#'+id).val(data); ref.log(data); } |
| | | }); |
| | | |
| | | return true; |
| | | }; |
| | | |
| | | this.plain2html = function(plain, id) |
| | |
| | | // helper method to send an HTTP request with the given iterator value |
| | | this.multi_thread_send_request = function(prop, item) |
| | | { |
| | | var postdata, query; |
| | | var k, postdata, query; |
| | | |
| | | // replace %s in post data |
| | | if (prop.postdata) { |
| | | postdata = {}; |
| | | for (var k in prop.postdata) { |
| | | for (k in prop.postdata) { |
| | | postdata[k] = String(prop.postdata[k]).replace('%s', item); |
| | | } |
| | | postdata._reqid = prop.reqid; |
| | |
| | | } |
| | | else if (typeof prop.query == 'object' && prop.query) { |
| | | query = {}; |
| | | for (var k in prop.query) { |
| | | for (k in prop.query) { |
| | | query[k] = String(prop.query[k]).replace('%s', item); |
| | | } |
| | | query._reqid = prop.reqid; |
| | |
| | | { |
| | | var msg = this.env.messages ? this.env.messages[uid] : {}; |
| | | return msg.mbox || this.env.mailbox; |
| | | } |
| | | }; |
| | | |
| | | // gets cursor position |
| | | this.get_caret_pos = function(obj) |
| | |
| | | if (obj.selectionEnd !== undefined) |
| | | return obj.selectionEnd; |
| | | |
| | | if (document.selection && document.selection.createRange) { |
| | | var range = document.selection.createRange(); |
| | | if (range.parentElement() != obj) |
| | | return 0; |
| | | |
| | | var gm = range.duplicate(); |
| | | if (obj.tagName == 'TEXTAREA') |
| | | gm.moveToElementText(obj); |
| | | else |
| | | gm.expand('textedit'); |
| | | |
| | | gm.setEndPoint('EndToStart', range); |
| | | var p = gm.text.length; |
| | | |
| | | return p <= obj.value.length ? p : -1; |
| | | } |
| | | |
| | | return obj.value.length; |
| | | }; |
| | | |
| | | // moves cursor to specified position |
| | | this.set_caret_pos = function(obj, pos) |
| | | { |
| | | if (obj.setSelectionRange) |
| | | obj.setSelectionRange(pos, pos); |
| | | else if (obj.createTextRange) { |
| | | var range = obj.createTextRange(); |
| | | range.collapse(true); |
| | | range.moveEnd('character', pos); |
| | | range.moveStart('character', pos); |
| | | range.select(); |
| | | try { |
| | | if (obj.setSelectionRange) |
| | | obj.setSelectionRange(pos, pos); |
| | | } |
| | | catch(e) {}; // catch Firefox exception if obj is hidden |
| | | }; |
| | | |
| | | // get selected text from an input field |
| | | // http://stackoverflow.com/questions/7186586/how-to-get-the-selected-text-in-textarea-using-jquery-in-internet-explorer-7 |
| | | this.get_input_selection = function(obj) |
| | | { |
| | | var start = 0, end = 0, |
| | | normalizedValue, range, |
| | | textInputRange, len, endRange; |
| | | var start = 0, end = 0, normalizedValue = ''; |
| | | |
| | | if (typeof obj.selectionStart == "number" && typeof obj.selectionEnd == "number") { |
| | | normalizedValue = obj.value; |
| | | start = obj.selectionStart; |
| | | end = obj.selectionEnd; |
| | | } |
| | | else { |
| | | range = document.selection.createRange(); |
| | | |
| | | if (range && range.parentElement() == obj) { |
| | | len = obj.value.length; |
| | | normalizedValue = obj.value; //.replace(/\r\n/g, "\n"); |
| | | |
| | | // create a working TextRange that lives only in the input |
| | | textInputRange = obj.createTextRange(); |
| | | textInputRange.moveToBookmark(range.getBookmark()); |
| | | |
| | | // Check if the start and end of the selection are at the very end |
| | | // of the input, since moveStart/moveEnd doesn't return what we want |
| | | // in those cases |
| | | endRange = obj.createTextRange(); |
| | | endRange.collapse(false); |
| | | |
| | | if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) { |
| | | start = end = len; |
| | | } |
| | | else { |
| | | start = -textInputRange.moveStart("character", -len); |
| | | start += normalizedValue.slice(0, start).split("\n").length - 1; |
| | | |
| | | if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) { |
| | | end = len; |
| | | } |
| | | else { |
| | | end = -textInputRange.moveEnd("character", -len); |
| | | end += normalizedValue.slice(0, end).split("\n").length - 1; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | return { start:start, end:end, text:normalizedValue.substr(start, end-start) }; |
| | | return {start: start, end: end, text: normalizedValue.substr(start, end-start)}; |
| | | }; |
| | | |
| | | // disable/enable all fields of a form |
| | |
| | | // remember which elem was disabled before lock |
| | | if (lock && elm.disabled) |
| | | this.disabled_form_elements.push(elm); |
| | | // check this.disabled_form_elements before inArray() as a workaround for FF5 bug |
| | | // http://bugs.jquery.com/ticket/9873 |
| | | else if (lock || (this.disabled_form_elements && $.inArray(elm, this.disabled_form_elements)<0)) |
| | | else if (lock || $.inArray(elm, this.disabled_form_elements) < 0) |
| | | elm.disabled = lock; |
| | | } |
| | | }; |
| | |
| | | { |
| | | if (!elem.title) { |
| | | var $elem = $(elem); |
| | | if ($elem.width() + indent * 15 > $elem.parent().width()) |
| | | if ($elem.width() + (indent || 0) * 15 > $elem.parent().width()) |
| | | elem.title = $elem.text(); |
| | | } |
| | | }; |