diff --git a/public_html/js/files_api.js b/public_html/js/files_api.js index cccccd4..2e98580 100644 --- a/public_html/js/files_api.js +++ b/public_html/js/files_api.js @@ -1,584 +1,766 @@ /* +--------------------------------------------------------------------------+ | 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(a, message) {}; + this.set_busy = function(state, message) {}; // displays error message - this.display_message = function(label) {}; + 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) - this.file_type_supported = function(type) + // or using Manticore - WebODF collaborative editor (return 4) + this.file_type_supported = function(type, capabilities) { - var i, t, regexps = [], img = 'jpg|jpeg|gif|bmp|png', - caps = this.env.browser_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)) - return 1; + res |= 1; // prefer text viewer for any text type if (/^text\/(?!(pdf|x-pdf))/i.test(type)) - return 2; + 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)) - return 1; + res |= 1; for (i in navigator.mimeTypes) { t = navigator.mimeTypes[i].type; if (t == type && navigator.mimeTypes[i].enabledPlugin) - return 1; + res |= 1; } // types with viewer support if ($.inArray(type, this.env.supported_mimetypes) > -1) - return 2; + 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 + */ +function manticore_api(conf) +{ + var domain, locks = {}, callbacks = {}, + 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 + if (conf.title_input) + $(conf.title_input).change(function() { self.set_title($(this).val()); }); + + // set state + this.set_busy = function(state, message) + { + if (conf.set_busy) + return conf.set_busy(state, 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 + this.hide_message = function(id) + { + if (conf.hide_message) + return conf.hide_message(id); + }; + + // localization method + this.gettext = function(label) + { + if (conf.gettext) + return conf.gettext(label); + + return label; + }; + + // 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]) + 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': + if (conf.title_input) + $(conf.title_input).val(data.value); + 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(); + + 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.title_input) + this.get_title(function(data) { + $(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); + }; +}; + // 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(/