diff --git a/public_html/js/files_api.js b/public_html/js/files_api.js index 2e98580..563f3c3 100644 --- a/public_html/js/files_api.js +++ b/public_html/js/files_api.js @@ -1,766 +1,832 @@ /* +--------------------------------------------------------------------------+ | This file is part of the Kolab File API | | | | Copyright (C) 2012-2013, Kolab Systems AG | | | | This program is free software: you can redistribute it and/or modify | | it under the terms of the GNU Affero General Public License as published | | by the Free Software Foundation, either version 3 of the License, or | | (at your option) any later version. | | | | This program is distributed in the hope that it will be useful, | | but WITHOUT ANY WARRANTY; without even the implied warranty of | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | | GNU Affero General Public License for more details. | | | | You should have received a copy of the GNU Affero General Public License | | along with this program. If not, see | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ function files_api() { var ref = this; // default config this.translations = {}; this.env = { url: 'api/', directory_separator: '/', resources_dir: 'resources' }; /*********************************************************/ /********* Basic utilities *********/ /*********************************************************/ // set environment variable(s) this.set_env = function(p, value) { if (p != null && typeof p === 'object' && !value) for (var n in p) this.env[n] = p[n]; else this.env[p] = value; }; // add a localized label(s) to the client environment this.tdef = function(p, value) { if (typeof p == 'string') this.translations[p] = value; else if (typeof p == 'object') $.extend(this.translations, p); }; // return a localized string this.t = function(label) { if (this.translations[label]) return this.translations[label]; else return label; }; // print a message into browser console this.log = function(msg) { if (window.console && console.log) console.log(msg); }; /********************************************************/ /********* Remote request methods *********/ /********************************************************/ // send a http POST request to the API service this.post = function(action, data, func) { var url = this.env.url + '?method=' + action; if (!func) func = 'response'; this.set_request_time(); return $.ajax({ type: 'POST', url: url, data: JSON.stringify(data), dataType: 'json', contentType: 'application/json; charset=utf-8', success: function(response) { ref[func](response); }, error: function(o, status, err) { ref.http_error(o, status, err); }, cache: false, beforeSend: function(xmlhttp) { xmlhttp.setRequestHeader('X-Session-Token', ref.env.token); } }); }; // send a http GET request to the API service this.get = function(action, data, func) { var url = this.env.url; if (!func) func = 'response'; this.set_request_time(); data.method = action; return $.ajax({ type: 'GET', url: url, data: data, dataType: 'json', success: function(response) { ref[func](response); }, error: function(o, status, err) { ref.http_error(o, status, err); }, cache: false, beforeSend: function(xmlhttp) { xmlhttp.setRequestHeader('X-Session-Token', ref.env.token); } }); }; // send request with auto-selection of POST/GET method this.request = function(action, data, func) { // Use POST for modification actions with probable big request size var method = /(create|delete|move|copy|update|auth)/.test(action) ? 'post' : 'get'; return this[method](action, data, func); }; // handle HTTP request errors this.http_error = function(request, status, err) { var errmsg = request.statusText; this.set_busy(false); request.abort(); if (request.status && errmsg) this.display_message(this.t('servererror') + ' (' + errmsg + ')', 'error'); }; this.response = function(response) { this.update_request_time(); this.set_busy(false); return this.response_parse(response); }; this.response_parse = function(response) { if (!response || response.status != 'OK') { // Logout on invalid-session error if (response && response.code == 403) this.logout(response); else this.display_message(response && response.reason ? response.reason : this.t('servererror'), 'error'); return false; } return true; }; /*********************************************************/ /********* Utilities *********/ /*********************************************************/ // Called on "session expired" session this.logout = function(response) {}; // set state this.set_busy = function(state, message) {}; // displays error message this.display_message = function(label, type) {}; // called when a request timed out this.request_timed_out = function() {}; // called on start of the request this.set_request_time = function() {}; // called on request response this.update_request_time = function() {}; /*********************************************************/ /********* Helpers *********/ /*********************************************************/ // compose a valid url with the given parameters this.url = function(action, query) { var k, param = {}, querystring = typeof query === 'string' ? '&' + query : ''; if (typeof action !== 'string') query = action; else if (!query || typeof query !== 'object') query = {}; // overwrite task name if (action) query.method = action; // remove undefined values for (k in query) { if (query[k] !== undefined && query[k] !== null) param[k] = query[k]; } return '?' + $.param(param) + querystring; }; // Folder list parser, converts it into structure this.folder_list_parse = function(list, num, subscribed) { var i, n, j, items, items_len, f, tmp, folder, subs_support, subs_prefixes = {}, found, separator = this.env.directory_separator, len = list ? list.length : 0, folders = {}; if (!num) num = 1; if (subscribed === undefined) subscribed = true; // prepare subscriptions support detection if (len && this.env.caps) { subs_support = !!this.env.caps.SUBSCRIPTIONS; $.each(this.env.caps.MOUNTPOINTS || [], function(i, v) { subs_prefixes[i] = !!v.SUBSCRIPTIONS; }); } for (i=0; i= 0) folders[folder].tree[diff] = folders[folder].tree[diff] ? folders[folder].tree[diff] + 2 : 2; } tree.push(i); } for (i in folders) { if (tree = folders[i].tree) { var html = '', divs = []; for (n=0; n 2) divs.push({'class': 'l3', width: 15}); else if (tree[n] > 1) divs.push({'class': 'l2', width: 15}); else if (tree[n] > 0) divs.push({'class': 'l1', width: 15}); // separator else if (divs.length && !divs[divs.length-1]['class']) divs[divs.length-1].width += 15; else divs.push({'class': null, width: 15}); } for (n=divs.length-1; n>=0; n--) { if (divs[n]['class']) html += ''; else html += ''; } if (html) $('#' + folders[i].id + ' span.branch').html(html); } } }; // convert content-type string into class name this.file_type_class = function(type) { if (!type) return ''; type = type.replace(/[^a-z0-9]/g, '_'); return type; }; // convert bytes into number with size unit this.file_size = function(size) { if (size >= 1073741824) return parseFloat(size/1073741824).toFixed(2) + ' GB'; if (size >= 1048576) return parseFloat(size/1048576).toFixed(2) + ' MB'; if (size >= 1024) return parseInt(size/1024) + ' kB'; return parseInt(size || 0) + ' B'; }; // Extract file name from full path this.file_name = function(path) { var path = path.split(this.env.directory_separator); return path.pop(); }; // Extract file path from full path this.file_path = function(path) { var path = path.split(this.env.directory_separator); path.pop(); return path.join(this.env.directory_separator); }; // compare two sortable objects this.sort_compare = function(data1, data2) { var key = this.env.sort_col || 'name'; if (key == 'mtime') key = 'modified'; data1 = data1[key]; data2 = data2[key]; if (key == 'size' || key == 'modified') // numeric comparison return this.env.sort_reverse ? data2 - data1 : data1 - data2; else { // use Array.sort() for string comparison var arr = [data1, data2]; arr.sort(function (a, b) { // @TODO: use localeCompare() arguments for better results return a.localeCompare(b); }); if (this.env.sort_reverse) arr.reverse(); return arr[0] === data2 ? 1 : -1; } }; // Checks if specified mimetype is supported natively by the browser (return 1) // or can be displayed in the browser using File API viewer (return 2) // or using Manticore - WebODF collaborative editor (return 4) this.file_type_supported = function(type, capabilities) { var i, t, res = 0, regexps = [], img = 'jpg|jpeg|gif|bmp|png', caps = this.env.browser_capabilities || {}, doc = /^application\/vnd.oasis.opendocument.(text)$/i; // Manticore? if (capabilities && capabilities.MANTICORE && doc.test(type)) res |= 4; if (caps.tif) img += '|tiff'; if ((new RegExp('^image/(' + img + ')$', 'i')).test(type)) res |= 1; // prefer text viewer for any text type if (/^text\/(?!(pdf|x-pdf))/i.test(type)) res |= 2; if (caps.pdf) { regexps.push(/^application\/(pdf|x-pdf|acrobat|vnd.pdf)/i); regexps.push(/^text\/(pdf|x-pdf)/i); } if (caps.flash) regexps.push(/^application\/x-shockwave-flash/i); for (i in regexps) if (regexps[i].test(type)) res |= 1; for (i in navigator.mimeTypes) { t = navigator.mimeTypes[i].type; if (t == type && navigator.mimeTypes[i].enabledPlugin) res |= 1; } // types with viewer support if ($.inArray(type, this.env.supported_mimetypes) > -1) res |= 2; return res; }; // Return browser capabilities this.browser_capabilities = function() { var i, caps = [], ctypes = ['pdf', 'flash', 'tif']; for (i in ctypes) if (this.env.browser_capabilities[ctypes[i]]) caps.push(ctypes[i]); return caps; }; // Checks browser capabilities eg. PDF support, TIF support this.browser_capabilities_check = function() { if (!this.env.browser_capabilities) this.env.browser_capabilities = {}; if (this.env.browser_capabilities.pdf === undefined) this.env.browser_capabilities.pdf = this.pdf_support_check(); if (this.env.browser_capabilities.flash === undefined) this.env.browser_capabilities.flash = this.flash_support_check(); if (this.env.browser_capabilities.tif === undefined) this.tif_support_check(); }; this.tif_support_check = function() { var img = new Image(), ref = this; img.onload = function() { ref.env.browser_capabilities.tif = 1; }; img.onerror = function() { ref.env.browser_capabilities.tif = 0; }; img.src = this.env.resources_dir + '/blank.tif'; }; this.pdf_support_check = function() { var plugin = navigator.mimeTypes ? navigator.mimeTypes["application/pdf"] : {}, plugins = navigator.plugins, len = plugins.length, regex = /Adobe Reader|PDF|Acrobat/i, ref = this; if (plugin && plugin.enabledPlugin) return 1; if (window.ActiveXObject) { try { if (axObj = new ActiveXObject("AcroPDF.PDF")) return 1; } catch (e) {} try { if (axObj = new ActiveXObject("PDF.PdfCtrl")) return 1; } catch (e) {} } for (i=0; i= 60*60*24) return '-'; return (new Date(1970, 1, 1, 0, 0, s, 0)).toTimeString().replace(/.*(\d{2}:\d{2}:\d{2}).*/, '$1'); }; }; /** * Class implementing Manticore Client API + * + * Configuration: + * iframe - manticore iframe element + * title_input - document title element + * export_menu - export formats list + * members_list - collaborators list + * photo_url - src for a collaborator + * photo_default_url - default image of a collaborator + * set_busy, display_message, hide_message, gettext - methods */ function manticore_api(conf) { - var domain, locks = {}, callbacks = {}, + var domain, + locks = {}, + callbacks = {}, + members = {}, self = this, manticore = conf.iframe.contentWindow; if (/^(https?:\/\/[^/]+)/i.test(conf.iframe.src)) domain = RegExp.$1; // Register 'message' event to receive messages from Manticore iframe window.addEventListener('message', function(event) { if (event.source == manticore && event.origin == domain) { self.message_handler(event.data); } }); - // Handle document title changes + // Bind for document title changes if (conf.title_input) $(conf.title_input).change(function() { self.set_title($(this).val()); }); - // set state + // Sets state this.set_busy = function(state, message) { if (conf.set_busy) return conf.set_busy(state, message); }; - // displays error/notification message + // Displays error/notification message this.display_message = function(label, type) { if (conf.display_message) return conf.display_message(label, type); if (type == 'error') alert(this.gettext(label)); }; - // hides the error/notification message + // Hides the error/notification message this.hide_message = function(id) { if (conf.hide_message) return conf.hide_message(id); }; - // localization method + // Localization method this.gettext = function(label) { if (conf.gettext) return conf.gettext(label); return label; }; - // display loading message + // Display loading message this.init_lock = this.set_busy(true, 'loading'); // Handle messages from Manticore this.message_handler = function(data) { var result; console.log(data); if (callbacks[data.id]) result = callbacks[data.id](data); - if (result !== false && conf[data.name]) + if (result !== false && data.name && conf[data.name]) result = conf[data.name](data); delete callbacks[data.id]; if (locks[data.id]) { this.set_busy(false); this.hide_message(data.id); delete locks[data.id]; } if (result === false) return; switch (data.name) { case 'ready': this.ready(); break; - case 'titleChangeEvent': + case 'titleChanged': if (conf.title_input) $(conf.title_input).val(data.value); break; + + case 'memberAdded': + // @TODO: display notification? + if (conf.members_list) + $(conf.members_list).append(this.member_item(data)); + break; + + case 'memberRemoved': + // @TODO: display notification? + if (conf.members_list) { + $('#' + members[data.memberId].id, conf.members_list).remove(); + delete members[data.memberId]; + } + break; } }; this.post = function(action, data, callback, lock_label) { if (!data) data = {}; if (lock_label) { data.id = this.set_busy(true, this.gettext(lock_label)); locks[data.id] = true; } if (!data.id) data.id = (new Date).getTime(); + // make sure the id is not in use + while (callbacks[data.id]) + data.id++; + data.name = action; callbacks[data.id] = callback; console.log(data); manticore.postMessage(data, domain); }; this.ready = function() { if (this.init_lock) { this.set_busy(false); this.hide_message(this.init_lock); delete this.init_lock; } if (conf.export_menu) this.export_menu(conf.export_menu); + if (conf.members_list) + this.get_members(function(data) { + var images = [], id = (new Date).getTime(); + $.each(data.value || [], function() { + images.push(self.member_item(this, id++)); + }); + $(conf.members_list).html('').append(images); + }); + if (conf.title_input) this.get_title(function(data) { - $(conf.title_input).val(data.value || ''); + $(conf.title_input).val(data.value); }); }; // Save current document this.save = function(callback) { this.post('actionSave', {}, callback, 'saving'); }; // Export/download current document this.export = function(type, callback) { this.post('actionExport', {value: type}, callback); }; // Get supported export formats and create content of menu element this.export_menu = function(menu) { this.post('getExportFormats', {}, function(data) { var items = []; $.each(data.value || [], function(i, v) { items.push($('
  • ').attr({role: 'menuitem'}).append( $('').attr({href: '#', role: 'button', tabindex: 0, 'aria-disabled': false, 'class': 'active'}) .text(v.label).click(function() { self.export(v.format); }) )); }); $(menu).html('').append(items); }); }; // Get document title this.get_title = function(callback) { this.post('getTitle', {}, callback); }; // Set document title this.set_title = function(title, callback) { this.post('setTitle', {value: title}, callback); }; + + // Get document session members + this.get_members = function(callback) + { + this.post('getMembers', {}, callback); + }; + + // Creates session member image element + this.member_item = function(member, id) + { + member.id = 'member' + (id || (new Date).getTime()); + member.name = member.fullName + ' (' + member.email + ')'; + + members[member.memberId] = member; + + var img = $('').attr({title: member.name, id: member.id, 'class': 'photo', src: conf.photo_default_url}) + .css({'border-color': member.color}) + .text(name); + + if (conf.photo_url) { + img.attr('src', conf.photo_url.replace(/%email/, urlencode(member.email))); + if (conf.photo_default_url) + img.error(function() { this.src = conf.photo_default_url; }); + } + + return img; + }; }; // Add escape() method to RegExp object // http://dev.rubyonrails.org/changeset/7271 RegExp.escape = function(str) { return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); }; // define String's startsWith() method for old browsers if (!String.prototype.startsWith) { String.prototype.startsWith = function(search, position) { position = position || 0; return this.slice(position, search.length) === search; }; }; // make a string URL safe (and compatible with PHP's rawurlencode()) function urlencode(str) { if (window.encodeURIComponent) return encodeURIComponent(str).replace('*', '%2A'); return escape(str) .replace('+', '%2B') .replace('*', '%2A') .replace('/', '%2F') .replace('@', '%40'); }; function escapeHTML(str) { return str === undefined ? '' : String(str) .replace(/&/g, '&') .replace(/>/g, '>') .replace(/