]> granicus.if.org Git - transmission/commitdiff
In Web Client, use jQuery.ajax() to upload files
authorJordan Lee <jordan@transmissionbt.com>
Sun, 10 Feb 2013 18:33:04 +0000 (18:33 +0000)
committerJordan Lee <jordan@transmissionbt.com>
Sun, 10 Feb 2013 18:33:04 +0000 (18:33 +0000)
If we use FormData and jQuery.ajax() calls to upload a torrent,
we can stop bundling the jquery.form.js module. In addition, this
simplifies passing arguments in the headers s.t. rpc-server.c doesn't
have to look for the CSRF token as one of the multiparts.

This changes the upload POST behavior, so give it a new name (upload2).
The old function (upload) will be deprecated but kept until 2.90 so
that third-party web clients using the old POST semantics will have
time to update.

Bug #5290 <https://trac.transmissionbt.com/ticket/5290>

libtransmission/rpc-server.c
web/index.html
web/javascript/.transmission.js.swo [new file with mode: 0644]
web/javascript/.transmission.js.swp [new file with mode: 0644]
web/javascript/jquery/Makefile.am
web/javascript/jquery/jquery.form.js [deleted file]
web/javascript/jquery/jquery.form.min.js [deleted file]
web/javascript/remote.js
web/javascript/transmission.js

index 0098f7accc12cc6b8f479f65ac8c0fbcae640207..80737de413e3df6f591afbaacf619f9e06f40531 100644 (file)
@@ -299,6 +299,78 @@ handle_upload (struct evhttp_request * req,
     }
 }
 
+/***
+****
+***/
+
+static void
+handle_rpc_from_json (struct evhttp_request * req,
+                      struct tr_rpc_server  * server,
+                      const char            * json,
+                      size_t                  json_len);
+
+static void
+handle_upload2 (struct evhttp_request * req,
+                struct tr_rpc_server  * server)
+{
+  if (req->type != EVHTTP_REQ_POST)
+    {
+      send_simple_response (req, 405, NULL);
+    }
+  else
+    {
+      const char * val;
+      tr_variant top;
+      tr_variant * args;
+      char * json;
+      int json_len;
+
+      tr_variantInitDict (&top, 2);
+      tr_variantDictAddStr (&top, TR_KEY_method, "torrent-add");
+      args = tr_variantDictAddDict (&top, TR_KEY_arguments, 3);
+
+      if ((val = evhttp_find_header (req->input_headers, "X-Transmission-Add-Paused")))
+        tr_variantDictAddBool (args, TR_KEY_paused, !tr_strcmp0(val,"true"));
+
+      if ((val = evhttp_find_header (req->input_headers, "X-Transmission-Add-Download-Dir")) && *val)
+        tr_variantDictAddStr (args, TR_KEY_download_dir, val);
+
+      if ((val = evhttp_find_header (req->input_headers, "X-Transmission-Add-URL")) && *val)
+        {
+          tr_variantDictAddStr (args, TR_KEY_filename, val);
+        }
+      else
+        {
+          int i;
+          int n;
+          bool have_source = false;
+          tr_ptrArray parts = TR_PTR_ARRAY_INIT;
+
+          extract_parts_from_multipart (req->input_headers, req->input_buffer, &parts);
+          n = tr_ptrArraySize (&parts);
+          for (i=0; !have_source && i<n; ++i)
+            {
+              tr_variant test;
+              const struct tr_mimepart * p = tr_ptrArrayNth (&parts, i);
+              if (!tr_variantFromBenc (&test, p->body, p->body_len))
+                {
+                  char * b64 = tr_base64_encode (p->body, p->body_len, NULL);
+                  tr_variantDictAddStr (args, TR_KEY_metainfo, b64);
+                  have_source = true;
+                  tr_free (b64);
+                  tr_variantFree (&test);
+                }
+            }
+          tr_ptrArrayDestruct (&parts, (PtrArrayForeachFunc)tr_mimepart_free);
+        }
+
+      json = tr_variantToStr (&top, TR_VARIANT_FMT_JSON, &json_len);
+      handle_rpc_from_json (req, server, json, json_len);
+      tr_free (json);
+      tr_variantFree (&top);
+    }
+}
+
 static const char*
 mimetype_guess (const char * path)
 {
@@ -541,29 +613,43 @@ rpc_response_func (tr_session      * session UNUSED,
   tr_free (data);
 }
 
-
 static void
-handle_rpc (struct evhttp_request * req, struct tr_rpc_server  * server)
+handle_rpc_from_json (struct evhttp_request * req,
+                      struct tr_rpc_server  * server,
+                      const char            * json,
+                      size_t                  json_len)
 {
-  struct rpc_response_data * data = tr_new0 (struct rpc_response_data, 1);
+  struct rpc_response_data * data;
 
+  data = tr_new0 (struct rpc_response_data, 1);
   data->req = req;
   data->server = server;
 
-  if (req->type == EVHTTP_REQ_GET)
+  tr_rpc_request_exec_json (server->session, json, json_len, rpc_response_func, data);
+}
+
+static void
+handle_rpc (struct evhttp_request * req, struct tr_rpc_server  * server)
+{
+  const char * q;
+
+  if (req->type == EVHTTP_REQ_POST)
     {
-      const char * q;
-      if ((q = strchr (req->uri, '?')))
-        tr_rpc_request_exec_uri (server->session, q+1, -1, rpc_response_func, data);
+      handle_rpc_from_json (req, server,
+                            (const char *) evbuffer_pullup (req->input_buffer, -1),
+                            evbuffer_get_length (req->input_buffer));
     }
-  else if (req->type == EVHTTP_REQ_POST)
+  else if ((req->type == EVHTTP_REQ_GET) && ((q = strchr (req->uri, '?'))))
     {
-      tr_rpc_request_exec_json (server->session,
-                                evbuffer_pullup (req->input_buffer, -1),
-                                evbuffer_get_length (req->input_buffer),
-                                rpc_response_func, data);
+      struct rpc_response_data * data = tr_new0 (struct rpc_response_data, 1);
+      data->req = req;
+      data->server = server;
+      tr_rpc_request_exec_uri (server->session, q+1, -1, rpc_response_func, data);
+    }
+  else
+    {
+      send_simple_response (req, 405, NULL);
     }
-
 }
 
 static bool
@@ -644,7 +730,7 @@ handle_request (struct evhttp_request * req, void * arg)
         {
           handle_web_client (req, server);
         }
-      else if (!strncmp (req->uri + strlen (server->url), "upload", 6))
+      else if (!strcmp (req->uri + strlen (server->url), "upload"))
         {
           handle_upload (req, server);
         }
@@ -669,6 +755,10 @@ handle_request (struct evhttp_request * req, void * arg)
           tr_free (tmp);
         }
 #endif
+      else if (!strcmp (req->uri + strlen (server->url), "upload2"))
+        {
+          handle_upload2 (req, server);
+        }
       else if (!strncmp (req->uri + strlen (server->url), "rpc", 3))
         {
           handle_rpc (req, server);
index 2944d02bb65333a96d406a738d4f36d4a786d9c6..fc66dd13c74043b007a367141647bd63a74bd9a2 100755 (executable)
@@ -21,7 +21,6 @@
                <![endif]-->
                <script type="text/javascript" src="./javascript/jquery/jquery.transmenu.min.js"></script>
                <script type="text/javascript" src="./javascript/jquery/jquery.contextmenu.min.js"></script>
-               <script type="text/javascript" src="./javascript/jquery/jquery.form.min.js"></script>
                <script type="text/javascript" src="./javascript/jquery/json2.min.js"></script>
                <script type="text/javascript" src="./javascript/common.js"></script>
                <script type="text/javascript" src="./javascript/inspector.js"></script>
                                                        <input type="file" name="torrent_files[]" id="torrent_upload_file" multiple="multiple" />
                                                <label for="torrent_upload_url">Or enter a URL:</label>
                                                        <input type="url" id="torrent_upload_url"/>
+                                               <label id='add-dialog-folder-label' for="add-dialog-folder-input">Destination folder:</label>
+                                                       <input type="text" id="add-dialog-folder-input"/>
                                                        <input type="checkbox" id="torrent_auto_start" />
                                                <label for="torrent_auto_start" id="auto_start_label">Start when added</label>
                                        </div>
diff --git a/web/javascript/.transmission.js.swo b/web/javascript/.transmission.js.swo
new file mode 100644 (file)
index 0000000..9167e8f
Binary files /dev/null and b/web/javascript/.transmission.js.swo differ
diff --git a/web/javascript/.transmission.js.swp b/web/javascript/.transmission.js.swp
new file mode 100644 (file)
index 0000000..21b9e40
Binary files /dev/null and b/web/javascript/.transmission.js.swp differ
index a2582dcc359b1e13de47f49b982cb873fca8112f..3229a200fcb05ceae97046f781e7e492e2447c07 100644 (file)
@@ -4,7 +4,5 @@ dist_data_DATA = \
   jqueryui-1.8.16.min.js \
   jquery.min.js \
   jquery.contextmenu.min.js \
-  jquery.form.js \
-  jquery.form.min.js \
   jquery.transmenu.min.js \
   json2.min.js
diff --git a/web/javascript/jquery/jquery.form.js b/web/javascript/jquery/jquery.form.js
deleted file mode 100644 (file)
index cf7d9f9..0000000
+++ /dev/null
@@ -1,1050 +0,0 @@
-/*!
- * jQuery Form Plugin
- * version: 3.02 (07-MAR-2012)
- * @requires jQuery v1.3.2 or later
- *
- * Examples and documentation at: http://malsup.com/jquery/form/
- * Dual licensed under the MIT and GPL licenses:
- *    http://www.opensource.org/licenses/mit-license.php
- *    http://www.gnu.org/licenses/gpl.html
- */
-/*global ActiveXObject alert */
-;(function($) {
-"use strict";
-
-/*
-    Usage Note:
-    -----------
-    Do not use both ajaxSubmit and ajaxForm on the same form.  These
-    functions are mutually exclusive.  Use ajaxSubmit if you want
-    to bind your own submit handler to the form.  For example,
-
-    $(document).ready(function() {
-        $('#myForm').bind('submit', function(e) {
-            e.preventDefault(); // <-- important
-            $(this).ajaxSubmit({
-                target: '#output'
-            });
-        });
-    });
-
-    Use ajaxForm when you want the plugin to manage all the event binding
-    for you.  For example,
-
-    $(document).ready(function() {
-        $('#myForm').ajaxForm({
-            target: '#output'
-        });
-    });
-    
-    You can also use ajaxForm with delegation (requires jQuery v1.7+), so the
-    form does not have to exist when you invoke ajaxForm:
-
-    $('#myForm').ajaxForm({
-        delegation: true,
-        target: '#output'
-    });
-    
-    When using ajaxForm, the ajaxSubmit function will be invoked for you
-    at the appropriate time.
-*/
-
-/**
- * Feature detection
- */
-var feature = {};
-feature.fileapi = $("<input type='file'/>").get(0).files !== undefined;
-feature.formdata = window.FormData !== undefined;
-
-/**
- * ajaxSubmit() provides a mechanism for immediately submitting
- * an HTML form using AJAX.
- */
-$.fn.ajaxSubmit = function(options) {
-    /*jshint scripturl:true */
-
-    // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
-    if (!this.length) {
-        log('ajaxSubmit: skipping submit process - no element selected');
-        return this;
-    }
-    
-    var method, action, url, $form = this;
-
-    if (typeof options == 'function') {
-        options = { success: options };
-    }
-
-    method = this.attr('method');
-    action = this.attr('action');
-    url = (typeof action === 'string') ? $.trim(action) : '';
-    url = url || window.location.href || '';
-    if (url) {
-        // clean url (don't include hash vaue)
-        url = (url.match(/^([^#]+)/)||[])[1];
-    }
-
-    options = $.extend(true, {
-        url:  url,
-        success: $.ajaxSettings.success,
-        type: method || 'GET',
-        iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
-    }, options);
-
-    // hook for manipulating the form data before it is extracted;
-    // convenient for use with rich editors like tinyMCE or FCKEditor
-    var veto = {};
-    this.trigger('form-pre-serialize', [this, options, veto]);
-    if (veto.veto) {
-        log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
-        return this;
-    }
-
-    // provide opportunity to alter form data before it is serialized
-    if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
-        log('ajaxSubmit: submit aborted via beforeSerialize callback');
-        return this;
-    }
-
-    var traditional = options.traditional;
-    if ( traditional === undefined ) {
-        traditional = $.ajaxSettings.traditional;
-    }
-    
-    var qx, a = this.formToArray(options.semantic);
-    if (options.data) {
-        options.extraData = options.data;
-        qx = $.param(options.data, traditional);
-    }
-
-    // give pre-submit callback an opportunity to abort the submit
-    if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
-        log('ajaxSubmit: submit aborted via beforeSubmit callback');
-        return this;
-    }
-
-    // fire vetoable 'validate' event
-    this.trigger('form-submit-validate', [a, this, options, veto]);
-    if (veto.veto) {
-        log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
-        return this;
-    }
-
-    var q = $.param(a, traditional);
-    if (qx) {
-        q = ( q ? (q + '&' + qx) : qx );
-    }    
-    if (options.type.toUpperCase() == 'GET') {
-        options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
-        options.data = null;  // data is null for 'get'
-    }
-    else {
-        options.data = q; // data is the query string for 'post'
-    }
-
-    var callbacks = [];
-    if (options.resetForm) {
-        callbacks.push(function() { $form.resetForm(); });
-    }
-    if (options.clearForm) {
-        callbacks.push(function() { $form.clearForm(options.includeHidden); });
-    }
-
-    // perform a load on the target only if dataType is not provided
-    if (!options.dataType && options.target) {
-        var oldSuccess = options.success || function(){};
-        callbacks.push(function(data) {
-            var fn = options.replaceTarget ? 'replaceWith' : 'html';
-            $(options.target)[fn](data).each(oldSuccess, arguments);
-        });
-    }
-    else if (options.success) {
-        callbacks.push(options.success);
-    }
-
-    options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
-        var context = options.context || options;    // jQuery 1.4+ supports scope context 
-        for (var i=0, max=callbacks.length; i < max; i++) {
-            callbacks[i].apply(context, [data, status, xhr || $form, $form]);
-        }
-    };
-
-    // are there files to upload?
-    var fileInputs = $('input:file:enabled[value]', this); // [value] (issue #113)
-    var hasFileInputs = fileInputs.length > 0;
-    var mp = 'multipart/form-data';
-    var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
-
-    var fileAPI = feature.fileapi && feature.formdata;
-    log("fileAPI :" + fileAPI);
-    var shouldUseFrame = (hasFileInputs || multipart) && !fileAPI;
-
-    // options.iframe allows user to force iframe mode
-    // 06-NOV-09: now defaulting to iframe mode if file input is detected
-    if (options.iframe !== false && (options.iframe || shouldUseFrame)) {
-        // hack to fix Safari hang (thanks to Tim Molendijk for this)
-        // see:  http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
-        if (options.closeKeepAlive) {
-            $.get(options.closeKeepAlive, function() {
-                fileUploadIframe(a);
-            });
-        }
-          else {
-            fileUploadIframe(a);
-          }
-    }
-    else if ((hasFileInputs || multipart) && fileAPI) {
-        fileUploadXhr(a);
-    }
-    else {
-        $.ajax(options);
-    }
-
-     // fire 'notify' event
-     this.trigger('form-submit-notify', [this, options]);
-     return this;
-
-     // XMLHttpRequest Level 2 file uploads (big hat tip to francois2metz)
-    function fileUploadXhr(a) {
-        var formdata = new FormData();
-
-        for (var i=0; i < a.length; i++) {
-            formdata.append(a[i].name, a[i].value);
-        }
-
-        if (options.extraData) {
-            for (var k in options.extraData)
-                if (options.extraData.hasOwnProperty(k))
-                    formdata.append(k, options.extraData[k]);
-        }
-
-        options.data = null;
-
-        var s = $.extend(true, {}, $.ajaxSettings, options, {
-            contentType: false,
-            processData: false,
-            cache: false,
-            type: 'POST'
-        });
-
-               if (options.uploadProgress) {
-                       // workaround because jqXHR does not expose upload property
-                       s.xhr = function() {
-                               var xhr = jQuery.ajaxSettings.xhr();
-                               if (xhr.upload) {
-                                       xhr.upload.onprogress = function(event) {
-                                               var percent = 0;
-                                               if (event.lengthComputable)
-                                                       percent = parseInt((event.position / event.total) * 100, 10);
-                                               options.uploadProgress(event, event.position, event.total, percent);
-                                       }
-                               }
-                               return xhr;
-                       }
-               }
-
-       s.data = null;
-       var beforeSend = s.beforeSend;
-       s.beforeSend = function(xhr, o) {
-               o.data = formdata;
-            if(beforeSend)
-                beforeSend.call(o, xhr, options);
-       };
-       $.ajax(s);
-        }
-
-    // private function for handling file uploads (hat tip to YAHOO!)
-    function fileUploadIframe(a) {
-        var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle;
-        var useProp = !!$.fn.prop;
-
-        if (a) {
-            if ( useProp ) {
-                // ensure that every serialized input is still enabled
-                for (i=0; i < a.length; i++) {
-                    el = $(form[a[i].name]);
-                    el.prop('disabled', false);
-                }
-            } else {
-                for (i=0; i < a.length; i++) {
-                    el = $(form[a[i].name]);
-                    el.removeAttr('disabled');
-                }
-            }
-        }
-
-        if ($(':input[name=submit],:input[id=submit]', form).length) {
-            // if there is an input with a name or id of 'submit' then we won't be
-            // able to invoke the submit fn on the form (at least not x-browser)
-            alert('Error: Form elements must not have name or id of "submit".');
-            return;
-        }
-        
-        s = $.extend(true, {}, $.ajaxSettings, options);
-        s.context = s.context || s;
-        id = 'jqFormIO' + (new Date().getTime());
-        if (s.iframeTarget) {
-            $io = $(s.iframeTarget);
-            n = $io.attr('name');
-            if (!n)
-                 $io.attr('name', id);
-            else
-                id = n;
-        }
-        else {
-            $io = $('<iframe name="' + id + '" src="'+ s.iframeSrc +'" />');
-            $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
-        }
-        io = $io[0];
-
-
-        xhr = { // mock object
-            aborted: 0,
-            responseText: null,
-            responseXML: null,
-            status: 0,
-            statusText: 'n/a',
-            getAllResponseHeaders: function() {},
-            getResponseHeader: function() {},
-            setRequestHeader: function() {},
-            abort: function(status) {
-                var e = (status === 'timeout' ? 'timeout' : 'aborted');
-                log('aborting upload... ' + e);
-                this.aborted = 1;
-                $io.attr('src', s.iframeSrc); // abort op in progress
-                xhr.error = e;
-                if (s.error)
-                    s.error.call(s.context, xhr, e, status);
-                if (g)
-                    $.event.trigger("ajaxError", [xhr, s, e]);
-                if (s.complete)
-                    s.complete.call(s.context, xhr, e);
-            }
-        };
-
-        g = s.global;
-        // trigger ajax global events so that activity/block indicators work like normal
-        if (g && 0 === $.active++) {
-            $.event.trigger("ajaxStart");
-        }
-        if (g) {
-            $.event.trigger("ajaxSend", [xhr, s]);
-        }
-
-        if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
-            if (s.global) {
-                $.active--;
-            }
-            return;
-        }
-        if (xhr.aborted) {
-            return;
-        }
-
-        // add submitting element to data if we know it
-        sub = form.clk;
-        if (sub) {
-            n = sub.name;
-            if (n && !sub.disabled) {
-                s.extraData = s.extraData || {};
-                s.extraData[n] = sub.value;
-                if (sub.type == "image") {
-                    s.extraData[n+'.x'] = form.clk_x;
-                    s.extraData[n+'.y'] = form.clk_y;
-                }
-            }
-        }
-        
-        var CLIENT_TIMEOUT_ABORT = 1;
-        var SERVER_ABORT = 2;
-
-        function getDoc(frame) {
-            var doc = frame.contentWindow ? frame.contentWindow.document : frame.contentDocument ? frame.contentDocument : frame.document;
-            return doc;
-        }
-        
-        // Rails CSRF hack (thanks to Yvan Barthelemy)
-        var csrf_token = $('meta[name=csrf-token]').attr('content');
-        var csrf_param = $('meta[name=csrf-param]').attr('content');
-        if (csrf_param && csrf_token) {
-            s.extraData = s.extraData || {};
-            s.extraData[csrf_param] = csrf_token;
-        }
-
-        // take a breath so that pending repaints get some cpu time before the upload starts
-        function doSubmit() {
-            // make sure form attrs are set
-            var t = $form.attr('target'), a = $form.attr('action');
-
-            // update form attrs in IE friendly way
-            form.setAttribute('target',id);
-            if (!method) {
-                form.setAttribute('method', 'POST');
-            }
-            if (a != s.url) {
-                form.setAttribute('action', s.url);
-            }
-
-            // ie borks in some cases when setting encoding
-            if (! s.skipEncodingOverride && (!method || /post/i.test(method))) {
-                $form.attr({
-                    encoding: 'multipart/form-data',
-                    enctype:  'multipart/form-data'
-                });
-            }
-
-            // support timout
-            if (s.timeout) {
-                timeoutHandle = setTimeout(function() { timedOut = true; cb(CLIENT_TIMEOUT_ABORT); }, s.timeout);
-            }
-            
-            // look for server aborts
-            function checkState() {
-                try {
-                    var state = getDoc(io).readyState;
-                    log('state = ' + state);
-                    if (state && state.toLowerCase() == 'uninitialized')
-                        setTimeout(checkState,50);
-                }
-                catch(e) {
-                    log('Server abort: ' , e, ' (', e.name, ')');
-                    cb(SERVER_ABORT);
-                    if (timeoutHandle)
-                        clearTimeout(timeoutHandle);
-                    timeoutHandle = undefined;
-                }
-            }
-
-            // add "extra" data to form if provided in options
-            var extraInputs = [];
-            try {
-                if (s.extraData) {
-                    for (var n in s.extraData) {
-                        if (s.extraData.hasOwnProperty(n)) {
-                            extraInputs.push(
-                                $('<input type="hidden" name="'+n+'">').attr('value',s.extraData[n])
-                                    .appendTo(form)[0]);
-                        }
-                    }
-                }
-
-                if (!s.iframeTarget) {
-                    // add iframe to doc and submit the form
-                    $io.appendTo('body');
-                    if (io.attachEvent)
-                        io.attachEvent('onload', cb);
-                    else
-                        io.addEventListener('load', cb, false);
-                }
-                setTimeout(checkState,15);
-                form.submit();
-            }
-            finally {
-                // reset attrs and remove "extra" input elements
-                form.setAttribute('action',a);
-                if(t) {
-                    form.setAttribute('target', t);
-                } else {
-                    $form.removeAttr('target');
-                }
-                $(extraInputs).remove();
-            }
-        }
-
-        if (s.forceSync) {
-            doSubmit();
-        }
-        else {
-            setTimeout(doSubmit, 10); // this lets dom updates render
-        }
-
-        var data, doc, domCheckCount = 50, callbackProcessed;
-
-        function cb(e) {
-            if (xhr.aborted || callbackProcessed) {
-                return;
-            }
-            try {
-                doc = getDoc(io);
-            }
-            catch(ex) {
-                log('cannot access response document: ', ex);
-                e = SERVER_ABORT;
-            }
-            if (e === CLIENT_TIMEOUT_ABORT && xhr) {
-                xhr.abort('timeout');
-                return;
-            }
-            else if (e == SERVER_ABORT && xhr) {
-                xhr.abort('server abort');
-                return;
-            }
-
-            if (!doc || doc.location.href == s.iframeSrc) {
-                // response not received yet
-                if (!timedOut)
-                    return;
-            }
-            if (io.detachEvent)
-                io.detachEvent('onload', cb);
-            else    
-                io.removeEventListener('load', cb, false);
-
-            var status = 'success', errMsg;
-            try {
-                if (timedOut) {
-                    throw 'timeout';
-                }
-
-                var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
-                log('isXml='+isXml);
-                if (!isXml && window.opera && (doc.body === null || !doc.body.innerHTML)) {
-                    if (--domCheckCount) {
-                        // in some browsers (Opera) the iframe DOM is not always traversable when
-                        // the onload callback fires, so we loop a bit to accommodate
-                        log('requeing onLoad callback, DOM not available');
-                        setTimeout(cb, 250);
-                        return;
-                    }
-                    // let this fall through because server response could be an empty document
-                    //log('Could not access iframe DOM after mutiple tries.');
-                    //throw 'DOMException: not available';
-                }
-
-                //log('response detected');
-                var docRoot = doc.body ? doc.body : doc.documentElement;
-                xhr.responseText = docRoot ? docRoot.innerHTML : null;
-                xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
-                if (isXml)
-                    s.dataType = 'xml';
-                xhr.getResponseHeader = function(header){
-                    var headers = {'content-type': s.dataType};
-                    return headers[header];
-                };
-                // support for XHR 'status' & 'statusText' emulation :
-                if (docRoot) {
-                    xhr.status = Number( docRoot.getAttribute('status') ) || xhr.status;
-                    xhr.statusText = docRoot.getAttribute('statusText') || xhr.statusText;
-                }
-
-                var dt = (s.dataType || '').toLowerCase();
-                var scr = /(json|script|text)/.test(dt);
-                if (scr || s.textarea) {
-                    // see if user embedded response in textarea
-                    var ta = doc.getElementsByTagName('textarea')[0];
-                    if (ta) {
-                        xhr.responseText = ta.value;
-                        // support for XHR 'status' & 'statusText' emulation :
-                        xhr.status = Number( ta.getAttribute('status') ) || xhr.status;
-                        xhr.statusText = ta.getAttribute('statusText') || xhr.statusText;
-                    }
-                    else if (scr) {
-                        // account for browsers injecting pre around json response
-                        var pre = doc.getElementsByTagName('pre')[0];
-                        var b = doc.getElementsByTagName('body')[0];
-                        if (pre) {
-                            xhr.responseText = pre.textContent ? pre.textContent : pre.innerText;
-                        }
-                        else if (b) {
-                            xhr.responseText = b.textContent ? b.textContent : b.innerText;
-                        }
-                    }
-                }
-                else if (dt == 'xml' && !xhr.responseXML && xhr.responseText) {
-                    xhr.responseXML = toXml(xhr.responseText);
-                }
-
-                try {
-                    data = httpData(xhr, dt, s);
-                }
-                catch (e) {
-                    status = 'parsererror';
-                    xhr.error = errMsg = (e || status);
-                }
-            }
-            catch (e) {
-                log('error caught: ',e);
-                status = 'error';
-                xhr.error = errMsg = (e || status);
-            }
-
-            if (xhr.aborted) {
-                log('upload aborted');
-                status = null;
-            }
-
-            if (xhr.status) { // we've set xhr.status
-                status = (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) ? 'success' : 'error';
-            }
-
-            // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
-            if (status === 'success') {
-                if (s.success)
-                    s.success.call(s.context, data, 'success', xhr);
-                if (g)
-                    $.event.trigger("ajaxSuccess", [xhr, s]);
-            }
-            else if (status) {
-                if (errMsg === undefined)
-                    errMsg = xhr.statusText;
-                if (s.error)
-                    s.error.call(s.context, xhr, status, errMsg);
-                if (g)
-                    $.event.trigger("ajaxError", [xhr, s, errMsg]);
-            }
-
-            if (g)
-                $.event.trigger("ajaxComplete", [xhr, s]);
-
-            if (g && ! --$.active) {
-                $.event.trigger("ajaxStop");
-            }
-
-            if (s.complete)
-                s.complete.call(s.context, xhr, status);
-
-            callbackProcessed = true;
-            if (s.timeout)
-                clearTimeout(timeoutHandle);
-
-            // clean up
-            setTimeout(function() {
-                if (!s.iframeTarget)
-                    $io.remove();
-                xhr.responseXML = null;
-            }, 100);
-        }
-
-        var toXml = $.parseXML || function(s, doc) { // use parseXML if available (jQuery 1.5+)
-            if (window.ActiveXObject) {
-                doc = new ActiveXObject('Microsoft.XMLDOM');
-                doc.async = 'false';
-                doc.loadXML(s);
-            }
-            else {
-                doc = (new DOMParser()).parseFromString(s, 'text/xml');
-            }
-            return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror') ? doc : null;
-        };
-        var parseJSON = $.parseJSON || function(s) {
-            /*jslint evil:true */
-            return window['eval']('(' + s + ')');
-        };
-
-        var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4
-
-            var ct = xhr.getResponseHeader('content-type') || '',
-                xml = type === 'xml' || !type && ct.indexOf('xml') >= 0,
-                data = xml ? xhr.responseXML : xhr.responseText;
-
-            if (xml && data.documentElement.nodeName === 'parsererror') {
-                if ($.error)
-                    $.error('parsererror');
-            }
-            if (s && s.dataFilter) {
-                data = s.dataFilter(data, type);
-            }
-            if (typeof data === 'string') {
-                if (type === 'json' || !type && ct.indexOf('json') >= 0) {
-                    data = parseJSON(data);
-                } else if (type === "script" || !type && ct.indexOf("javascript") >= 0) {
-                    $.globalEval(data);
-                }
-            }
-            return data;
-        };
-    }
-};
-
-/**
- * ajaxForm() provides a mechanism for fully automating form submission.
- *
- * The advantages of using this method instead of ajaxSubmit() are:
- *
- * 1: This method will include coordinates for <input type="image" /> elements (if the element
- *    is used to submit the form).
- * 2. This method will include the submit element's name/value data (for the element that was
- *    used to submit the form).
- * 3. This method binds the submit() method to the form for you.
- *
- * The options argument for ajaxForm works exactly as it does for ajaxSubmit.  ajaxForm merely
- * passes the options argument along after properly binding events for submit elements and
- * the form itself.
- */
-$.fn.ajaxForm = function(options) {
-    options = options || {};
-    options.delegation = options.delegation && $.isFunction($.fn.on);
-    
-    // in jQuery 1.3+ we can fix mistakes with the ready state
-    if (!options.delegation && this.length === 0) {
-        var o = { s: this.selector, c: this.context };
-        if (!$.isReady && o.s) {
-            log('DOM not ready, queuing ajaxForm');
-            $(function() {
-                $(o.s,o.c).ajaxForm(options);
-            });
-            return this;
-        }
-        // is your DOM ready?  http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
-        log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)'));
-        return this;
-    }
-
-    if ( options.delegation ) {
-        $(document)
-            .off('submit.form-plugin', this.selector, doAjaxSubmit)
-            .off('click.form-plugin', this.selector, captureSubmittingElement)
-            .on('submit.form-plugin', this.selector, options, doAjaxSubmit)
-            .on('click.form-plugin', this.selector, options, captureSubmittingElement);
-        return this;
-    }
-
-    return this.ajaxFormUnbind()
-        .bind('submit.form-plugin', options, doAjaxSubmit)
-        .bind('click.form-plugin', options, captureSubmittingElement);
-};
-
-// private event handlers    
-function doAjaxSubmit(e) {
-    /*jshint validthis:true */
-    var options = e.data;
-    if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed
-        e.preventDefault();
-        $(this).ajaxSubmit(options);
-    }
-}
-    
-function captureSubmittingElement(e) {
-    /*jshint validthis:true */
-    var target = e.target;
-    var $el = $(target);
-    if (!($el.is(":submit,input:image"))) {
-        // is this a child element of the submit el?  (ex: a span within a button)
-        var t = $el.closest(':submit');
-        if (t.length === 0) {
-            return;
-        }
-        target = t[0];
-    }
-    var form = this;
-    form.clk = target;
-    if (target.type == 'image') {
-        if (e.offsetX !== undefined) {
-            form.clk_x = e.offsetX;
-            form.clk_y = e.offsetY;
-        } else if (typeof $.fn.offset == 'function') {
-            var offset = $el.offset();
-            form.clk_x = e.pageX - offset.left;
-            form.clk_y = e.pageY - offset.top;
-        } else {
-            form.clk_x = e.pageX - target.offsetLeft;
-            form.clk_y = e.pageY - target.offsetTop;
-        }
-    }
-    // clear form vars
-    setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
-}
-
-
-// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
-$.fn.ajaxFormUnbind = function() {
-    return this.unbind('submit.form-plugin click.form-plugin');
-};
-
-/**
- * formToArray() gathers form element data into an array of objects that can
- * be passed to any of the following ajax functions: $.get, $.post, or load.
- * Each object in the array has both a 'name' and 'value' property.  An example of
- * an array for a simple login form might be:
- *
- * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
- *
- * It is this array that is passed to pre-submit callback functions provided to the
- * ajaxSubmit() and ajaxForm() methods.
- */
-$.fn.formToArray = function(semantic) {
-    var a = [];
-    if (this.length === 0) {
-        return a;
-    }
-
-    var form = this[0];
-    var els = semantic ? form.getElementsByTagName('*') : form.elements;
-    if (!els) {
-        return a;
-    }
-
-    var i,j,n,v,el,max,jmax;
-    for(i=0, max=els.length; i < max; i++) {
-        el = els[i];
-        n = el.name;
-        if (!n) {
-            continue;
-        }
-
-        if (semantic && form.clk && el.type == "image") {
-            // handle image inputs on the fly when semantic == true
-            if(!el.disabled && form.clk == el) {
-                a.push({name: n, value: $(el).val(), type: el.type });
-                a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
-            }
-            continue;
-        }
-
-        v = $.fieldValue(el, true);
-        if (v && v.constructor == Array) {
-            for(j=0, jmax=v.length; j < jmax; j++) {
-                a.push({name: n, value: v[j]});
-            }
-        }
-        else if (feature.fileapi && el.type == 'file' && !el.disabled) {
-            var files = el.files;
-            for (j=0; j < files.length; j++) {
-                a.push({name: n, value: files[j], type: el.type});
-            }
-        }
-        else if (v !== null && typeof v != 'undefined') {
-            a.push({name: n, value: v, type: el.type});
-        }
-    }
-
-    if (!semantic && form.clk) {
-        // input type=='image' are not found in elements array! handle it here
-        var $input = $(form.clk), input = $input[0];
-        n = input.name;
-        if (n && !input.disabled && input.type == 'image') {
-            a.push({name: n, value: $input.val()});
-            a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
-        }
-    }
-    return a;
-};
-
-/**
- * Serializes form data into a 'submittable' string. This method will return a string
- * in the format: name1=value1&amp;name2=value2
- */
-$.fn.formSerialize = function(semantic) {
-    //hand off to jQuery.param for proper encoding
-    return $.param(this.formToArray(semantic));
-};
-
-/**
- * Serializes all field elements in the jQuery object into a query string.
- * This method will return a string in the format: name1=value1&amp;name2=value2
- */
-$.fn.fieldSerialize = function(successful) {
-    var a = [];
-    this.each(function() {
-        var n = this.name;
-        if (!n) {
-            return;
-        }
-        var v = $.fieldValue(this, successful);
-        if (v && v.constructor == Array) {
-            for (var i=0,max=v.length; i < max; i++) {
-                a.push({name: n, value: v[i]});
-            }
-        }
-        else if (v !== null && typeof v != 'undefined') {
-            a.push({name: this.name, value: v});
-        }
-    });
-    //hand off to jQuery.param for proper encoding
-    return $.param(a);
-};
-
-/**
- * Returns the value(s) of the element in the matched set.  For example, consider the following form:
- *
- *  <form><fieldset>
- *      <input name="A" type="text" />
- *      <input name="A" type="text" />
- *      <input name="B" type="checkbox" value="B1" />
- *      <input name="B" type="checkbox" value="B2"/>
- *      <input name="C" type="radio" value="C1" />
- *      <input name="C" type="radio" value="C2" />
- *  </fieldset></form>
- *
- *  var v = $(':text').fieldValue();
- *  // if no values are entered into the text inputs
- *  v == ['','']
- *  // if values entered into the text inputs are 'foo' and 'bar'
- *  v == ['foo','bar']
- *
- *  var v = $(':checkbox').fieldValue();
- *  // if neither checkbox is checked
- *  v === undefined
- *  // if both checkboxes are checked
- *  v == ['B1', 'B2']
- *
- *  var v = $(':radio').fieldValue();
- *  // if neither radio is checked
- *  v === undefined
- *  // if first radio is checked
- *  v == ['C1']
- *
- * The successful argument controls whether or not the field element must be 'successful'
- * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
- * The default value of the successful argument is true.  If this value is false the value(s)
- * for each element is returned.
- *
- * Note: This method *always* returns an array.  If no valid value can be determined the
- *    array will be empty, otherwise it will contain one or more values.
- */
-$.fn.fieldValue = function(successful) {
-    for (var val=[], i=0, max=this.length; i < max; i++) {
-        var el = this[i];
-        var v = $.fieldValue(el, successful);
-        if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) {
-            continue;
-        }
-        if (v.constructor == Array)
-            $.merge(val, v);
-        else
-            val.push(v);
-    }
-    return val;
-};
-
-/**
- * Returns the value of the field element.
- */
-$.fieldValue = function(el, successful) {
-    var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
-    if (successful === undefined) {
-        successful = true;
-    }
-
-    if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
-        (t == 'checkbox' || t == 'radio') && !el.checked ||
-        (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
-        tag == 'select' && el.selectedIndex == -1)) {
-            return null;
-    }
-
-    if (tag == 'select') {
-        var index = el.selectedIndex;
-        if (index < 0) {
-            return null;
-        }
-        var a = [], ops = el.options;
-        var one = (t == 'select-one');
-        var max = (one ? index+1 : ops.length);
-        for(var i=(one ? index : 0); i < max; i++) {
-            var op = ops[i];
-            if (op.selected) {
-                var v = op.value;
-                if (!v) { // extra pain for IE...
-                    v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
-                }
-                if (one) {
-                    return v;
-                }
-                a.push(v);
-            }
-        }
-        return a;
-    }
-    return $(el).val();
-};
-
-/**
- * Clears the form data.  Takes the following actions on the form's input fields:
- *  - input text fields will have their 'value' property set to the empty string
- *  - select elements will have their 'selectedIndex' property set to -1
- *  - checkbox and radio inputs will have their 'checked' property set to false
- *  - inputs of type submit, button, reset, and hidden will *not* be effected
- *  - button elements will *not* be effected
- */
-$.fn.clearForm = function(includeHidden) {
-    return this.each(function() {
-        $('input,select,textarea', this).clearFields(includeHidden);
-    });
-};
-
-/**
- * Clears the selected form elements.
- */
-$.fn.clearFields = $.fn.clearInputs = function(includeHidden) {
-    var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list
-    return this.each(function() {
-        var t = this.type, tag = this.tagName.toLowerCase();
-        if (re.test(t) || tag == 'textarea' || (includeHidden && /hidden/.test(t)) ) {
-            this.value = '';
-        }
-        else if (t == 'checkbox' || t == 'radio') {
-            this.checked = false;
-        }
-        else if (tag == 'select') {
-            this.selectedIndex = -1;
-        }
-    });
-};
-
-/**
- * Resets the form data.  Causes all form elements to be reset to their original value.
- */
-$.fn.resetForm = function() {
-    return this.each(function() {
-        // guard against an input with the name of 'reset'
-        // note that IE reports the reset function as an 'object'
-        if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) {
-            this.reset();
-        }
-    });
-};
-
-/**
- * Enables or disables any matching elements.
- */
-$.fn.enable = function(b) {
-    if (b === undefined) {
-        b = true;
-    }
-    return this.each(function() {
-        this.disabled = !b;
-    });
-};
-
-/**
- * Checks/unchecks any matching checkboxes or radio buttons and
- * selects/deselects and matching option elements.
- */
-$.fn.selected = function(select) {
-    if (select === undefined) {
-        select = true;
-    }
-    return this.each(function() {
-        var t = this.type;
-        if (t == 'checkbox' || t == 'radio') {
-            this.checked = select;
-        }
-        else if (this.tagName.toLowerCase() == 'option') {
-            var $sel = $(this).parent('select');
-            if (select && $sel[0] && $sel[0].type == 'select-one') {
-                // deselect all other options
-                $sel.find('option').selected(false);
-            }
-            this.selected = select;
-        }
-    });
-};
-
-// expose debug var
-$.fn.ajaxSubmit.debug = false;
-
-// helper fn for console logging
-function log() {
-    if (!$.fn.ajaxSubmit.debug) 
-        return;
-    var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
-    if (window.console && window.console.log) {
-        window.console.log(msg);
-    }
-    else if (window.opera && window.opera.postError) {
-        window.opera.postError(msg);
-    }
-}
-
-})(jQuery);
\ No newline at end of file
diff --git a/web/javascript/jquery/jquery.form.min.js b/web/javascript/jquery/jquery.form.min.js
deleted file mode 100644 (file)
index b9b74b3..0000000
+++ /dev/null
@@ -1 +0,0 @@
-(function(a){function b(){if(!a.fn.ajaxSubmit.debug)return;var b="[jquery.form] "+Array.prototype.join.call(arguments,"");if(window.console&&window.console.log){window.console.log(b)}else if(window.opera&&window.opera.postError){window.opera.postError(b)}}a.fn.ajaxSubmit=function(c){function u(e){function C(c){if(o.aborted||B){return}try{z=w(n)}catch(d){b("cannot access response document: ",d);c=v}if(c===u&&o){o.abort("timeout");return}else if(c==v&&o){o.abort("server abort");return}if(!z||z.location.href==j.iframeSrc){if(!r)return}n.detachEvent?n.detachEvent("onload",C):n.removeEventListener("load",C,false);var e="success",f;try{if(r){throw"timeout"}var g=j.dataType=="xml"||z.XMLDocument||a.isXMLDoc(z);b("isXml="+g);if(!g&&window.opera&&(z.body==null||z.body.innerHTML=="")){if(--A){b("requeing onLoad callback, DOM not available");setTimeout(C,250);return}}var h=z.body?z.body:z.documentElement;o.responseText=h?h.innerHTML:null;o.responseXML=z.XMLDocument?z.XMLDocument:z;if(g)j.dataType="xml";o.getResponseHeader=function(a){var b={"content-type":j.dataType};return b[a]};if(h){o.status=Number(h.getAttribute("status"))||o.status;o.statusText=h.getAttribute("statusText")||o.statusText}var i=(j.dataType||"").toLowerCase();var l=/(json|script|text)/.test(i);if(l||j.textarea){var p=z.getElementsByTagName("textarea")[0];if(p){o.responseText=p.value;o.status=Number(p.getAttribute("status"))||o.status;o.statusText=p.getAttribute("statusText")||o.statusText}else if(l){var q=z.getElementsByTagName("pre")[0];var t=z.getElementsByTagName("body")[0];if(q){o.responseText=q.textContent?q.textContent:q.innerText}else if(t){o.responseText=t.textContent?t.textContent:t.innerText}}}else if(i=="xml"&&!o.responseXML&&o.responseText!=null){o.responseXML=D(o.responseText)}try{y=F(o,i,j)}catch(c){e="parsererror";o.error=f=c||e}}catch(c){b("error caught: ",c);e="error";o.error=f=c||e}if(o.aborted){b("upload aborted");e=null}if(o.status){e=o.status>=200&&o.status<300||o.status===304?"success":"error"}if(e==="success"){j.success&&j.success.call(j.context,y,"success",o);k&&a.event.trigger("ajaxSuccess",[o,j])}else if(e){if(f==undefined)f=o.statusText;j.error&&j.error.call(j.context,o,e,f);k&&a.event.trigger("ajaxError",[o,j,f])}k&&a.event.trigger("ajaxComplete",[o,j]);if(k&&!--a.active){a.event.trigger("ajaxStop")}j.complete&&j.complete.call(j.context,o,e);B=true;if(j.timeout)clearTimeout(s);setTimeout(function(){if(!j.iframeTarget)m.remove();o.responseXML=null},100)}function x(){function h(){try{var a=w(n).readyState;b("state = "+a);if(a.toLowerCase()=="uninitialized")setTimeout(h,50)}catch(c){b("Server abort: ",c," (",c.name,")");C(v);s&&clearTimeout(s);s=undefined}}var c=g.attr("target"),e=g.attr("action");f.setAttribute("target",l);if(!d){f.setAttribute("method","POST")}if(e!=j.url){f.setAttribute("action",j.url)}if(!j.skipEncodingOverride&&(!d||/post/i.test(d))){g.attr({encoding:"multipart/form-data",enctype:"multipart/form-data"})}if(j.timeout){s=setTimeout(function(){r=true;C(u)},j.timeout)}var i=[];try{if(j.extraData){for(var k in j.extraData){i.push(a('<input type="hidden" name="'+k+'" />').attr("value",j.extraData[k]).appendTo(f)[0])}}if(!j.iframeTarget){m.appendTo("body");n.attachEvent?n.attachEvent("onload",C):n.addEventListener("load",C,false)}setTimeout(h,15);f.submit()}finally{f.setAttribute("action",e);if(c){f.setAttribute("target",c)}else{g.removeAttr("target")}a(i).remove()}}function w(a){var b=a.contentWindow?a.contentWindow.document:a.contentDocument?a.contentDocument:a.document;return b}var f=g[0],h,i,j,k,l,m,n,o,p,q,r,s;var t=!!a.fn.prop;if(e){if(t){for(i=0;i<e.length;i++){h=a(f[e[i].name]);h.prop("disabled",false)}}else{for(i=0;i<e.length;i++){h=a(f[e[i].name]);h.removeAttr("disabled")}}}if(a(":input[name=submit],:input[id=submit]",f).length){alert('Error: Form elements must not have name or id of "submit".');return}j=a.extend(true,{},a.ajaxSettings,c);j.context=j.context||j;l="jqFormIO"+(new Date).getTime();if(j.iframeTarget){m=a(j.iframeTarget);q=m.attr("name");if(q==null)m.attr("name",l);else l=q}else{m=a('<iframe name="'+l+'" src="'+j.iframeSrc+'" />');m.css({position:"absolute",top:"-1000px",left:"-1000px"})}n=m[0];o={aborted:0,responseText:null,responseXML:null,status:0,statusText:"n/a",getAllResponseHeaders:function(){},getResponseHeader:function(){},setRequestHeader:function(){},abort:function(c){var d=c==="timeout"?"timeout":"aborted";b("aborting upload... "+d);this.aborted=1;m.attr("src",j.iframeSrc);o.error=d;j.error&&j.error.call(j.context,o,d,c);k&&a.event.trigger("ajaxError",[o,j,d]);j.complete&&j.complete.call(j.context,o,d)}};k=j.global;if(k&&!(a.active++)){a.event.trigger("ajaxStart")}if(k){a.event.trigger("ajaxSend",[o,j])}if(j.beforeSend&&j.beforeSend.call(j.context,o,j)===false){if(j.global){a.active--}return}if(o.aborted){return}p=f.clk;if(p){q=p.name;if(q&&!p.disabled){j.extraData=j.extraData||{};j.extraData[q]=p.value;if(p.type=="image"){j.extraData[q+".x"]=f.clk_x;j.extraData[q+".y"]=f.clk_y}}}var u=1;var v=2;if(j.forceSync){x()}else{setTimeout(x,10)}var y,z,A=50,B;var D=a.parseXML||function(a,b){if(window.ActiveXObject){b=new ActiveXObject("Microsoft.XMLDOM");b.async="false";b.loadXML(a)}else{b=(new DOMParser).parseFromString(a,"text/xml")}return b&&b.documentElement&&b.documentElement.nodeName!="parsererror"?b:null};var E=a.parseJSON||function(a){return window["eval"]("("+a+")")};var F=function(b,c,d){var e=b.getResponseHeader("content-type")||"",f=c==="xml"||!c&&e.indexOf("xml")>=0,g=f?b.responseXML:b.responseText;if(f&&g.documentElement.nodeName==="parsererror"){a.error&&a.error("parsererror")}if(d&&d.dataFilter){g=d.dataFilter(g,c)}if(typeof g==="string"){if(c==="json"||!c&&e.indexOf("json")>=0){g=E(g)}else if(c==="script"||!c&&e.indexOf("javascript")>=0){a.globalEval(g)}}return g}}if(!this.length){b("ajaxSubmit: skipping submit process - no element selected");return this}var d,e,f,g=this;if(typeof c=="function"){c={success:c}}d=this.attr("method");e=this.attr("action");f=typeof e==="string"?a.trim(e):"";f=f||window.location.href||"";if(f){f=(f.match(/^([^#]+)/)||[])[1]}c=a.extend(true,{url:f,success:a.ajaxSettings.success,type:d||"GET",iframeSrc:/^https/i.test(window.location.href||"")?"javascript:false":"about:blank"},c);var h={};this.trigger("form-pre-serialize",[this,c,h]);if(h.veto){b("ajaxSubmit: submit vetoed via form-pre-serialize trigger");return this}if(c.beforeSerialize&&c.beforeSerialize(this,c)===false){b("ajaxSubmit: submit aborted via beforeSerialize callback");return this}var i=c.traditional;if(i===undefined){i=a.ajaxSettings.traditional}var j,k,l,m=this.formToArray(c.semantic);if(c.data){c.extraData=c.data;j=a.param(c.data,i)}if(c.beforeSubmit&&c.beforeSubmit(m,this,c)===false){b("ajaxSubmit: submit aborted via beforeSubmit callback");return this}this.trigger("form-submit-validate",[m,this,c,h]);if(h.veto){b("ajaxSubmit: submit vetoed via form-submit-validate trigger");return this}var n=a.param(m,i);if(j)n=n?n+"&"+j:j;if(c.type.toUpperCase()=="GET"){c.url+=(c.url.indexOf("?")>=0?"&":"?")+n;c.data=null}else{c.data=n}var o=[];if(c.resetForm){o.push(function(){g.resetForm()})}if(c.clearForm){o.push(function(){g.clearForm(c.includeHidden)})}if(!c.dataType&&c.target){var p=c.success||function(){};o.push(function(b){var d=c.replaceTarget?"replaceWith":"html";a(c.target)[d](b).each(p,arguments)})}else if(c.success){o.push(c.success)}c.success=function(a,b,d){var e=c.context||c;for(var f=0,h=o.length;f<h;f++){o[f].apply(e,[a,b,d||g,g])}};var q=a("input:file",this).length>0;var r="multipart/form-data";var s=g.attr("enctype")==r||g.attr("encoding")==r;if(c.iframe!==false&&(q||c.iframe||s)){if(c.closeKeepAlive){a.get(c.closeKeepAlive,function(){u(m)})}else{u(m)}}else{if(a.browser.msie&&d=="get"&&typeof c.type==="undefined"){var t=g[0].getAttribute("method");if(typeof t==="string")c.type=t}a.ajax(c)}this.trigger("form-submit-notify",[this,c]);return this};a.fn.ajaxForm=function(c){if(this.length===0){var d={s:this.selector,c:this.context};if(!a.isReady&&d.s){b("DOM not ready, queuing ajaxForm");a(function(){a(d.s,d.c).ajaxForm(c)});return this}b("terminating; zero elements found by selector"+(a.isReady?"":" (DOM not ready)"));return this}return this.ajaxFormUnbind().bind("submit.form-plugin",function(b){if(!b.isDefaultPrevented()){b.preventDefault();a(this).ajaxSubmit(c)}}).bind("click.form-plugin",function(b){var c=b.target;var d=a(c);if(!d.is(":submit,input:image")){var e=d.closest(":submit");if(e.length==0){return}c=e[0]}var f=this;f.clk=c;if(c.type=="image"){if(b.offsetX!=undefined){f.clk_x=b.offsetX;f.clk_y=b.offsetY}else if(typeof a.fn.offset=="function"){var g=d.offset();f.clk_x=b.pageX-g.left;f.clk_y=b.pageY-g.top}else{f.clk_x=b.pageX-c.offsetLeft;f.clk_y=b.pageY-c.offsetTop}}setTimeout(function(){f.clk=f.clk_x=f.clk_y=null},100)})};a.fn.ajaxFormUnbind=function(){return this.unbind("submit.form-plugin click.form-plugin")};a.fn.formToArray=function(b){var c=[];if(this.length===0){return c}var d=this[0];var e=b?d.getElementsByTagName("*"):d.elements;if(!e){return c}var f,g,h,i,j,k,l;for(f=0,k=e.length;f<k;f++){j=e[f];h=j.name;if(!h){continue}if(b&&d.clk&&j.type=="image"){if(!j.disabled&&d.clk==j){c.push({name:h,value:a(j).val()});c.push({name:h+".x",value:d.clk_x},{name:h+".y",value:d.clk_y})}continue}i=a.fieldValue(j,true);if(i&&i.constructor==Array){for(g=0,l=i.length;g<l;g++){c.push({name:h,value:i[g]})}}else if(i!==null&&typeof i!="undefined"){c.push({name:h,value:i})}}if(!b&&d.clk){var m=a(d.clk),n=m[0];h=n.name;if(h&&!n.disabled&&n.type=="image"){c.push({name:h,value:m.val()});c.push({name:h+".x",value:d.clk_x},{name:h+".y",value:d.clk_y})}}return c};a.fn.formSerialize=function(b){return a.param(this.formToArray(b))};a.fn.fieldSerialize=function(b){var c=[];this.each(function(){var d=this.name;if(!d){return}var e=a.fieldValue(this,b);if(e&&e.constructor==Array){for(var f=0,g=e.length;f<g;f++){c.push({name:d,value:e[f]})}}else if(e!==null&&typeof e!="undefined"){c.push({name:this.name,value:e})}});return a.param(c)};a.fn.fieldValue=function(b){for(var c=[],d=0,e=this.length;d<e;d++){var f=this[d];var g=a.fieldValue(f,b);if(g===null||typeof g=="undefined"||g.constructor==Array&&!g.length){continue}g.constructor==Array?a.merge(c,g):c.push(g)}return c};a.fieldValue=function(b,c){var d=b.name,e=b.type,f=b.tagName.toLowerCase();if(c===undefined){c=true}if(c&&(!d||b.disabled||e=="reset"||e=="button"||(e=="checkbox"||e=="radio")&&!b.checked||(e=="submit"||e=="image")&&b.form&&b.form.clk!=b||f=="select"&&b.selectedIndex==-1)){return null}if(f=="select"){var g=b.selectedIndex;if(g<0){return null}var h=[],i=b.options;var j=e=="select-one";var k=j?g+1:i.length;for(var l=j?g:0;l<k;l++){var m=i[l];if(m.selected){var n=m.value;if(!n){n=m.attributes&&m.attributes["value"]&&!m.attributes["value"].specified?m.text:m.value}if(j){return n}h.push(n)}}return h}return a(b).val()};a.fn.clearForm=function(b){return this.each(function(){a("input,select,textarea",this).clearFields(b)})};a.fn.clearFields=a.fn.clearInputs=function(a){var b=/^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i;return this.each(function(){var c=this.type,d=this.tagName.toLowerCase();if(b.test(c)||d=="textarea"||a&&/hidden/.test(c)){this.value=""}else if(c=="checkbox"||c=="radio"){this.checked=false}else if(d=="select"){this.selectedIndex=-1}})};a.fn.resetForm=function(){return this.each(function(){if(typeof this.reset=="function"||typeof this.reset=="object"&&!this.reset.nodeType){this.reset()}})};a.fn.enable=function(a){if(a===undefined){a=true}return this.each(function(){this.disabled=!a})};a.fn.selected=function(b){if(b===undefined){b=true}return this.each(function(){var c=this.type;if(c=="checkbox"||c=="radio"){this.checked=b}else if(this.tagName.toLowerCase()=="option"){var d=a(this).parent("select");if(b&&d[0]&&d[0].type=="select-one"){d.find("option").selected(false)}this.selected=b}})};a.fn.ajaxSubmit.debug=false;})(jQuery)
\ No newline at end of file
index dcd0668033219183232fd8c5075aea5f548fe70f..af6ef7d42e57ed8bea0bc62b70d9247c27b09bcf 100644 (file)
@@ -124,6 +124,18 @@ TransmissionRemote.prototype =
                });
        },
 
+       getFreeSpace: function(dir, callback, context) {
+               var remote = this;
+               var o = {
+                       method: 'free-space',
+                       arguments: { path: dir }
+               };
+               this.sendRequest(o, function(response) {
+                       var args = response['arguments'];
+                       callback.call (context, args.path, args['size-bytes']);
+               });
+       },
+
        changeFileCommand: function(torrentId, fileIndices, command) {
                var remote = this,
                    args = { ids: [torrentId] };
index 6164deb8aa2f14600559262bcec167b39b6c5153..7fe291794f9b93f8e679ee294f17cdd19efd45d8 100644 (file)
@@ -224,6 +224,32 @@ Transmission.prototype =
                $('#unlimited_upload_rate').selectMenuItem();
        },
 
+       /****
+       *****
+       ****/
+
+       updateFreeSpaceInAddDialog: function()
+       {
+               var formdir = $('input#add-dialog-folder-input').val();
+               this.remote.getFreeSpace (formdir, this.onFreeSpaceResponse, this);
+       },
+
+       onFreeSpaceResponse: function(dir, bytes)
+       {
+               var e, str, formdir;
+
+               formdir = $('input#add-dialog-folder-input').val();
+               if (formdir == dir)
+               {
+                       e = $('label#add-dialog-folder-label');
+                       if (bytes > 0)
+                               str = '  <i>(' + Transmission.fmt.size(bytes) + ' Free)</i>';
+                       else
+                               str = '';
+                       e.html ('Destination folder' + str + ':');
+               }
+       },
+
 
        /****
        *****
@@ -872,33 +898,49 @@ Transmission.prototype =
 
        /*
         * Select a torrent file to upload
-        * FIXME
         */
        uploadTorrentFile: function(confirmed)
        {
-               // Display the upload dialog
-               if (! confirmed) {
-                       $('input#torrent_upload_file').attr('value', '');
-                       $('input#torrent_upload_url').attr('value', '');
-                       $('input#torrent_auto_start').attr('checked', this.shouldAddedTorrentsStart());
-                       $('#upload_container').show();
-                       $('#torrent_upload_url').focus();
+               var formData,
+                   fileInput   = $('input#torrent_upload_file'),
+                   folderInput = $('input#add-dialog-folder-input'),
+                   startInput  = $('input#torrent_auto_start'),
+                   urlInput    = $('input#torrent_upload_url');
 
-               // Submit the upload form
-               } else {
-                       var args = {};
-                       var remote = this.remote;
-                       var paused = !$('#torrent_auto_start').is(':checked');
-                       if ('' != $('#torrent_upload_url').val()) {
-                               remote.addTorrentByUrl($('#torrent_upload_url').val(), { paused: paused });
-                       } else {
-                               args.url = '../upload?paused=' + paused;
-                               args.type = 'POST';
-                               args.data = { 'X-Transmission-Session-Id' : remote._token };
-                               args.dataType = 'xml';
-                               args.iframe = true;
-                               $('#torrent_upload_form').ajaxSubmit(args);
-                       }
+               if (!confirmed)
+               {
+                       // update the upload dialog's fields
+                       fileInput.attr('value', '');
+                       urlInput.attr('value', '');
+                       startInput.attr('checked', this.shouldAddedTorrentsStart());
+                       folderInput.attr('value', $("#download-dir").val());
+                       folderInput.change($.proxy(this.updateFreeSpaceInAddDialog,this));
+                       this.updateFreeSpaceInAddDialog();
+
+                       // show the dialog
+                       $('#upload_container').show();
+                       urlInput.focus();
+               }
+               else
+               {
+                       formData = new FormData ();
+                       jQuery.each(fileInput[0].files, function(i, file) {
+                               formData.append ('file-'+i, file);
+                       });
+                       $.ajax ({
+                               url: '../upload2',
+                               data: formData,
+                               headers : {
+                                       'X-Transmission-Session-Id':        this.remote._token,
+                                       'X-Transmission-Add-Paused':        !startInput.is(':checked'),
+                                       'X-Transmission-Add-Download-Dir':  folderInput.val(),
+                                       'X-Transmission-Add-URL':           urlInput.val()
+                               },
+                               cache: false,
+                               contentType: false,
+                               processData: false,
+                               type: 'POST'
+                       });
                }
        },