From 6e8e2f78c01404fd70cb92c533f034106c557ef8 Mon Sep 17 00:00:00 2001
From: Fil <fil@rezo.net>
Date: Sat, 14 Jul 2007 23:00:19 +0000
Subject: [PATCH] passage a la version courante de jquery.form.js, qui permet
 l'upload de fichiers en ajax  ; ce script revele les limites de jscompressor,
 on utilise donc le packer de Dean Edwards, en mode 0 (nettoyage des
 commentaires, sans compression supplementaire ; a voir si on ajoute un
 parametre)

---
 .gitattributes                        |   1 +
 dist/javascript/form.js               | 428 ++++++++++-----
 ecrire/inc/class.JavaScriptPacker.php | 732 ++++++++++++++++++++++++++
 ecrire/inc/filtres.php                |   9 +-
 4 files changed, 1034 insertions(+), 136 deletions(-)
 create mode 100644 ecrire/inc/class.JavaScriptPacker.php

diff --git a/.gitattributes b/.gitattributes
index 9199ca687e..3122cc3343 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -542,6 +542,7 @@ ecrire/inc/autoriser.php -text
 ecrire/inc/boutons.php -text
 ecrire/inc/chercher_logo.php -text
 ecrire/inc/chercher_rubrique.php -text
+ecrire/inc/class.JavaScriptPacker.php -text
 ecrire/inc/commencer_page.php -text
 ecrire/inc/compacte_js.php -text
 ecrire/inc/cookie.php -text
diff --git a/dist/javascript/form.js b/dist/javascript/form.js
index 4ca1c6aa0a..67134b35be 100644
--- a/dist/javascript/form.js
+++ b/dist/javascript/form.js
@@ -1,14 +1,16 @@
 /*
  * jQuery form plugin
- * @requires jQuery v1.0.3
+ * @requires jQuery v1.1 or later
  *
+ * Examples 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
  *
- * Revision: $Id: form.js 1021 2007-01-12 01:45:09Z malsup $
+ * Revision: $Id: jquery.form.js 2261 2007-07-07 19:16:35Z malsup $
+ * Version: 1.0.1  Jul-07-2007
  */
-
+ (function($) {
 /**
  * ajaxSubmit() provides a mechanism for submitting an HTML form using AJAX.
  *
@@ -24,16 +26,13 @@
  *
  *  url:      URL to which the form data will be submitted.
  *            default value: value of form's 'action' attribute
- *  
- *  method:   @deprecated use 'type'
+ *
  *  type:     The method in which the form data should be submitted, 'GET' or 'POST'.
  *            default value: value of form's 'method' attribute (or 'GET' if none found)
  *
- *  before:   @deprecated use 'beforeSubmit'
  *  beforeSubmit:  Callback method to be invoked before the form is submitted.
  *            default value: null
  *
- *  after:    @deprecated use 'success'
  *  success:  Callback method to be invoked after the form has been successfully submitted
  *            and the response has been returned from the server
  *            default value: null
@@ -52,7 +51,7 @@
  * The 'beforeSubmit' callback can be provided as a hook for running pre-submit logic or for
  * validating the form data.  If the 'beforeSubmit' callback returns false then the form will
  * not be submitted. The 'beforeSubmit' callback is invoked with three arguments: the form data
- * in array format, the jQuery object, and the options object passed into ajaxSubmit.  
+ * in array format, the jQuery object, and the options object passed into ajaxSubmit.
  * The form data array takes the following form:
  *
  *     [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
@@ -64,7 +63,7 @@
  *
  * The dataType option provides a means for specifying how the server response should be handled.
  * This maps directly to the jQuery.httpData method.  The following values are supported:
- * 
+ *
  *      'xml':    if dataType == 'xml' the server response is treated as XML and the 'after'
  *                   callback method, if specified, will be passed the responseXML value
  *      'json':   if dataType == 'json' the server response will be evaluted and passed to
@@ -78,7 +77,7 @@
  * The semantic argument can be used to force form serialization in semantic order.
  * This is normally true anyway, unless the form contains input elements of type='image'.
  * If your form must be submitted with name/value pairs in semantic order and your form
- * contains an input of type='image" then pass true for this arg, otherwise pass false 
+ * contains an input of type='image" then pass true for this arg, otherwise pass false
  * (or nothing) to avoid the overhead for this logic.
  *
  *
@@ -174,34 +173,30 @@
  * @param options  object literal containing options which control the form submission process
  * @cat Plugins/Form
  * @return jQuery
- * @see formToArray
- * @see ajaxForm
- * @see $.ajax
- * @author jQuery Community
  */
-jQuery.fn.ajaxSubmit = function(options) {
+$.fn.ajaxSubmit = function(options) {
     if (typeof options == 'function')
         options = { success: options };
 
-    options = jQuery.extend({
-        url:    this.attr('action') || '',
-        method: this.attr('method') || 'GET'
+    options = $.extend({
+        url:  this.attr('action') || window.location,
+        type: this.attr('method') || 'GET'
     }, options || {});
 
-    // remap deprecated options (temporarily)
-    options.success = options.success || options.after;
-    options.beforeSubmit = options.beforeSubmit || options.before;
-    options.type = options.type || options.method;
-
     var a = this.formToArray(options.semantic);
 
     // give pre-submit callback an opportunity to abort the submit
-    if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) return;
+    if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) return this;
 
-    var q = jQuery.param(a);
+    // fire vetoable 'validate' event
+    var veto = {};
+    $.event.trigger('form.submit.validate', [a, this, options, veto]);
+    if (veto.veto)
+        return this;
+
+    var q = $.param(a);//.replace(/%20/g,'+');
 
     if (options.type.toUpperCase() == 'GET') {
-        // if url already has a '?' then append args after '&'
         options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
         options.data = null;  // data is null for 'get'
     }
@@ -214,22 +209,155 @@ jQuery.fn.ajaxSubmit = function(options) {
 
     // perform a load on the target only if dataType is not provided
     if (!options.dataType && options.target) {
-        var oldSuccess = options.success || function(){};
+        var oldSuccess = options.success;// || function(){};
         callbacks.push(function(data, status) {
-            jQuery(options.target).html(data).evalScripts().each(oldSuccess, [data, status]);
+            $(options.target).attr("innerHTML", data).evalScripts().each(oldSuccess, [data, status]);
         });
     }
-    else if (options.success) 
+    else if (options.success)
         callbacks.push(options.success);
 
     options.success = function(data, status) {
         for (var i=0, max=callbacks.length; i < max; i++)
             callbacks[i](data, status);
     };
-        
-    jQuery.ajax(options);
+
+    // are there files to upload?
+    var files = $('input:file', this).fieldValue();
+    var found = false;
+    for (var j=0; j < files.length; j++)
+        if (files[j]) 
+            found = true;
+
+    if (options.iframe || found) // options.iframe allows user to force iframe mode
+        fileUpload();
+    else
+        $.ajax(options);
+
+    // fire 'notify' event
+    $.event.trigger('form.submit.notify', [this, options]);
     return this;
+
+
+    // private function for handling file uploads (hat tip to YAHOO!)
+    function fileUpload() {
+        var form = $form[0];
+        var opts = $.extend({}, $.ajaxSettings, options);
+        
+        var id = 'jqFormIO' + $.fn.ajaxSubmit.counter++;
+        var $io = $('<iframe id="' + id + '" name="' + id + '" />');
+        var io = $io[0];
+        var op8 = $.browser.opera && window.opera.version() < 9;
+        if ($.browser.msie || op8) io.src = 'javascript:false;document.write("");';
+        $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
+
+        var xhr = { // mock object
+            responseText: null,
+            responseXML: null,
+            status: 0,
+            statusText: 'n/a',
+            getAllResponseHeaders: function() {},
+            getResponseHeader: function() {},
+            setRequestHeader: function() {}
+        };
+        
+        var g = opts.global;
+        // trigger ajax global events so that activity/block indicators work like normal
+        if (g && ! $.active++) $.event.trigger("ajaxStart");
+        if (g) $.event.trigger("ajaxSend", [xhr, opts]);
+        
+        var cbInvoked = 0;
+        var timedOut = 0;
+        
+        // take a breath so that pending repaints get some cpu time before the upload starts
+        setTimeout(function() {
+            $io.appendTo('body');
+            // jQuery's event binding doesn't work for iframe events in IE
+            io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);
+            
+            // make sure form attrs are set
+            var encAttr = form.encoding ? 'encoding' : 'enctype';
+            var t = $form.attr('target');
+            $form.attr({
+                target:   id,
+                method:  'POST',
+                encAttr: 'multipart/form-data',
+                action:   opts.url
+            });
+
+            // support timout
+            if (opts.timeout)
+                setTimeout(function() { timedOut = true; cb(); }, opts.timeout);
+
+            form.submit();
+            $form.attr('target', t); // reset target
+        }, 10);
+        
+        function cb() {
+            if (cbInvoked++) return;
+            
+            io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);
+
+            var ok = true;
+            try {
+                if (timedOut) throw 'timeout';
+                // extract the server response from the iframe
+                var data, doc;
+                doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
+                xhr.responseText = doc.body ? doc.body.innerHTML : null;
+                xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
+                
+                if (opts.dataType == 'json' || opts.dataType == 'script') {
+                    var ta = doc.getElementsByTagName('textarea')[0];
+                    data = ta ? ta.value : xhr.responseText;
+                    if (opts.dataType == 'json')
+                        eval("data = " + data);
+                    else
+                        $.globalEval(data);
+                }
+                else if (opts.dataType == 'xml') {
+                    data = xhr.responseXML;
+                    if (!data && xhr.responseText != null)
+                        data = toXml(xhr.responseText);
+                }
+                else {
+                    data = xhr.responseText;
+                }
+            }
+            catch(e){
+                ok = false;
+                $.handleError(opts, xhr, 'error', e);
+            }
+
+            // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
+            if (ok) {
+                opts.success(data, 'success');
+                if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);
+            }
+            if (g) $.event.trigger("ajaxComplete", [xhr, opts]);
+            if (g && ! --$.active) $.event.trigger("ajaxStop");
+            if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error');
+
+            // clean up
+            setTimeout(function() { 
+                $io.remove(); 
+                xhr.responseXML = null;
+            }, 100);
+        };
+        
+        function toXml(s, doc) {
+            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.tagName != 'parsererror') ? doc : null;
+        };
+    };
 };
+$.fn.ajaxSubmit.counter = 0; // used to create unique iframe ids
 
 /**
  * ajaxForm() provides a mechanism for fully automating form submission.
@@ -288,38 +416,62 @@ jQuery.fn.ajaxSubmit = function(options) {
  * @return jQuery
  * @cat    Plugins/Form
  * @type   jQuery
- * @see    ajaxSubmit
- * @author jQuery Community
  */
-jQuery.fn.ajaxForm = function(options) {
-    return this.each(function() {
-        jQuery("input:submit,input:image,button:submit", this).click(function(ev) {
-            var $form = this.form;
-            $form.clk = this;
-            if (this.type == 'image') {
-                if (ev.offsetX != undefined) {
-                    $form.clk_x = ev.offsetX;
-                    $form.clk_y = ev.offsetY;
-                } else if (typeof jQuery.fn.offset == 'function') { // try to use dimensions plugin
-                    var offset = $(this).offset();
-                    $form.clk_x = ev.pageX - offset.left;
-                    $form.clk_y = ev.pageY - offset.top;
-                } else {
-                    $form.clk_x = ev.pageX - this.offsetLeft;
-                    $form.clk_y = ev.pageY - this.offsetTop;
-                }
-            }
-            // clear form vars
-            setTimeout(function() {
-                $form.clk = $form.clk_x = $form.clk_y = null;
-                }, 10);
-        })
-    }).submit(function(e) {
-        jQuery(this).ajaxSubmit(options);
-        return false;
+$.fn.ajaxForm = function(options) {
+    return this.ajaxFormUnbind().submit(submitHandler).each(function() {
+        // store options in hash
+        this.formPluginId = $.fn.ajaxForm.counter++;
+        $.fn.ajaxForm.optionHash[this.formPluginId] = options;
+        $(":submit,input:image", this).click(clickHandler);
     });
 };
 
+$.fn.ajaxForm.counter = 1;
+$.fn.ajaxForm.optionHash = {};
+
+function clickHandler(e) {
+    var $form = this.form;
+    $form.clk = this;
+    if (this.type == 'image') {
+        if (e.offsetX != undefined) {
+            $form.clk_x = e.offsetX;
+            $form.clk_y = e.offsetY;
+        } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
+            var offset = $(this).offset();
+            $form.clk_x = e.pageX - offset.left;
+            $form.clk_y = e.pageY - offset.top;
+        } else {
+            $form.clk_x = e.pageX - this.offsetLeft;
+            $form.clk_y = e.pageY - this.offsetTop;
+        }
+    }
+    // clear form vars
+    setTimeout(function() { $form.clk = $form.clk_x = $form.clk_y = null; }, 10);
+};
+
+function submitHandler() {
+    // retrieve options from hash
+    var id = this.formPluginId;
+    var options = $.fn.ajaxForm.optionHash[id];
+    $(this).ajaxSubmit(options);
+    return false;
+};
+
+/**
+ * ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
+ *
+ * @name   ajaxFormUnbind
+ * @return jQuery
+ * @cat    Plugins/Form
+ * @type   jQuery
+ */
+$.fn.ajaxFormUnbind = function() {
+    this.unbind('submit', submitHandler);
+    return this.each(function() {
+        $(":submit,input:image", this).unbind('click', clickHandler);
+    });
+
+};
 
 /**
  * formToArray() gathers form element data into an array of objects that can
@@ -335,7 +487,7 @@ jQuery.fn.ajaxForm = function(options) {
  * The semantic argument can be used to force form serialization in semantic order.
  * This is normally true anyway, unless the form contains input elements of type='image'.
  * If your form must be submitted with name/value pairs in semantic order and your form
- * contains an input of type='image" then pass true for this arg, otherwise pass false 
+ * contains an input of type='image" then pass true for this arg, otherwise pass false
  * (or nothing) to avoid the overhead for this logic.
  *
  * @example var data = $("#myForm").formToArray();
@@ -346,19 +498,16 @@ jQuery.fn.ajaxForm = function(options) {
  * @param semantic true if serialization must maintain strict semantic ordering of elements (slower)
  * @type Array<Object>
  * @cat Plugins/Form
- * @see ajaxForm
- * @see ajaxSubmit
- * @author jQuery Community
  */
-jQuery.fn.formToArray = function(semantic) {
+$.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;
-	for(var i=0, max=els.length; i < max; i++) {
-		var el = els[i];
+    for(var i=0, max=els.length; i < max; i++) {
+        var el = els[i];
         var n = el.name;
         if (!n) continue;
 
@@ -368,15 +517,15 @@ jQuery.fn.formToArray = function(semantic) {
                 a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
             continue;
         }
-        var v = jQuery.fieldValue(el, true);
+        var v = $.fieldValue(el, true);
         if (v === null) continue;
         if (v.constructor == Array) {
             for(var j=0, jmax=v.length; j < jmax; j++)
                 a.push({name: n, value: v[j]});
         }
-        else 
+        else
             a.push({name: n, value: v});
-	}
+    }
 
     if (!semantic && form.clk) {
         // input type=='image' are not found in elements array! handle them here
@@ -409,17 +558,15 @@ jQuery.fn.formToArray = function(semantic) {
  * @param semantic true if serialization must maintain strict semantic ordering of elements (slower)
  * @type String
  * @cat Plugins/Form
- * @see formToArray
- * @author jQuery Community
  */
-jQuery.fn.formSerialize = function(semantic) {
+$.fn.formSerialize = function(semantic) {
     //hand off to jQuery.param for proper encoding
-    return jQuery.param(this.formToArray(semantic));
+    return $.param(this.formToArray(semantic));
 };
 
 
 /**
- * Serializes all field elements in the jQuery object into a query string. 
+ * 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
  *
  * The successful argument controls whether or not serialization is limited to
@@ -446,12 +593,12 @@ jQuery.fn.formSerialize = function(semantic) {
  * @type String
  * @cat Plugins/Form
  */
-jQuery.fn.fieldSerialize = function(successful) {
+$.fn.fieldSerialize = function(successful) {
     var a = [];
     this.each(function() {
         var n = this.name;
         if (!n) return;
-        var v = jQuery.fieldValue(this, successful);
+        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]});
@@ -460,65 +607,81 @@ jQuery.fn.fieldSerialize = function(successful) {
             a.push({name: this.name, value: v});
     });
     //hand off to jQuery.param for proper encoding
-    return jQuery.param(a);
+    return $.param(a);
 };
 
 
 /**
- * Returns the value of the field element in the jQuery object.  If there is more than one field element
- * in the jQuery object the value of the first successful one is returned.
+ * 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 then
- * the value of the first field element in the jQuery object is returned.
+ * The default value of the successful argument is true.  If this value is false the value(s)
+ * for each element is returned.
  *
- * Note: If no valid value can be determined the return value will be undifined.
+ * 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.
  *
- * Note: The fieldValue returned for a select-multiple element or for a checkbox input will
- *       always be an array if it is not undefined.
+ * @example var data = $("#myPasswordElement").fieldValue();
+ * alert(data[0]);
+ * @desc Alerts the current value of the myPasswordElement element
  *
+ * @example var data = $("#myForm :input").fieldValue();
+ * @desc Get the value(s) of the form elements in myForm
  *
- * @example var data = $("#myPasswordElement").formValue();
- * @desc Gets the current value of the myPasswordElement element
+ * @example var data = $("#myForm :checkbox").fieldValue();
+ * @desc Get the value(s) for the successful checkbox element(s) in the jQuery object.
  *
- * @example var data = $("#myForm :input").formValue();
- * @desc Get the value of the first successful control in the jQuery object.
+ * @example var data = $("#mySingleSelect").fieldValue();
+ * @desc Get the value(s) of the select control
  *
- * @example var data = $("#myForm :checkbox").formValue();
- * @desc Get the array of values for the first set of successful checkbox controls in the jQuery object.
+ * @example var data = $(':text').fieldValue();
+ * @desc Get the value(s) of the text input or textarea elements
  *
- * @example var data = $("#mySingleSelect").formValue();
- * @desc Get the value of the select control
- *
- * @example var data = $("#myMultiSelect").formValue();
- * @desc Get the array of selected values for the select-multiple control
+ * @example var data = $("#myMultiSelect").fieldValue();
+ * @desc Get the values for the select-multiple control
  *
  * @name fieldValue
- * @param Boolean successful true if value returned must be for a successful controls (default is true)
- * @type String or Array<String>
+ * @param Boolean successful true if only the values for successful controls should be returned (default is true)
+ * @type Array<String>
  * @cat Plugins/Form
  */
-jQuery.fn.fieldValue = function(successful) {
-    var cbVal, cbName;
-
-    // loop until we find a value
-    for (var i=0, max=this.length; i < max; i++) {
+$.fn.fieldValue = function(successful) {
+    for (var val=[], i=0, max=this.length; i < max; i++) {
         var el = this[i];
-        var v = jQuery.fieldValue(el, successful);
+        var v = $.fieldValue(el, successful);
         if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length))
             continue;
-
-        // for checkboxes, consider multiple elements, for everything else just return first valid value
-        if (el.type != 'checkbox') return v;
-
-        cbName = cbName || el.name;
-        if (cbName != el.name) // return if we hit a checkbox with a different name
-            return cbVal;
-        cbVal = cbVal || [];
-        cbVal.push(v);
+        v.constructor == Array ? $.merge(val, v) : val.push(v);
     }
-    return cbVal;
+    return val;
 };
 
 /**
@@ -529,7 +692,9 @@ jQuery.fn.fieldValue = function(successful) {
  * The default value of the successful argument is true.  If the given element is not
  * successful and the successful arg is not false then the returned value will be null.
  *
- * Note: The fieldValue returned for a select-multiple element will always be an array.
+ * Note: If the successful flag is true (default) but the element is not successful, the return will be null
+ * Note: The value returned for a successful select-multiple element will always be an array.
+ * Note: If the element has no value the return value will be undefined.
  *
  * @example var data = jQuery.fieldValue($("#myPasswordElement")[0]);
  * @desc Gets the current value of the myPasswordElement element
@@ -537,19 +702,19 @@ jQuery.fn.fieldValue = function(successful) {
  * @name fieldValue
  * @param Element el The DOM element for which the value will be returned
  * @param Boolean successful true if value returned must be for a successful controls (default is true)
- * @type String or Array<String>
+ * @type String or Array<String> or null or undefined
  * @cat Plugins/Form
  */
-jQuery.fieldValue = function(el, successful) {
+$.fieldValue = function(el, successful) {
     var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
     if (typeof successful == 'undefined') successful = true;
 
-    if (successful && ( !n || el.disabled || t == 'reset' ||
+    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;
@@ -560,7 +725,7 @@ jQuery.fieldValue = function(el, successful) {
             var op = ops[i];
             if (op.selected) {
                 // extra pain for IE...
-                var v = jQuery.browser.msie && !(op.attributes['value'].specified) ? op.text : op.value;
+                var v = $.browser.msie && !(op.attributes['value'].specified) ? op.text : op.value;
                 if (one) return v;
                 a.push(v);
             }
@@ -585,13 +750,12 @@ jQuery.fieldValue = function(el, successful) {
  * @name clearForm
  * @type jQuery
  * @cat Plugins/Form
- * @see resetForm
  */
-jQuery.fn.clearForm = function() {
+$.fn.clearForm = function() {
     return this.each(function() {
-        jQuery('input,select,textarea', this).clearInputs();
+        $('input,select,textarea', this).clearFields();
     });
-}
+};
 
 /**
  * Clears the selected form elements.  Takes the following actions on the matched elements:
@@ -601,15 +765,14 @@ jQuery.fn.clearForm = function() {
  *  - inputs of type submit, button, reset, and hidden will *not* be effected
  *  - button elements will *not* be effected
  *
- * @example $('.myInputs').clearInputs();
+ * @example $('.myInputs').clearFields();
  * @desc Clears all inputs with class myInputs
  *
- * @name clearInputs
+ * @name clearFields
  * @type jQuery
  * @cat Plugins/Form
- * @see clearForm
  */
-jQuery.fn.clearInputs = function() {
+$.fn.clearFields = $.fn.clearInputs = function() {
     return this.each(function() {
         var t = this.type, tag = this.tagName.toLowerCase();
         if (t == 'text' || t == 'password' || tag == 'textarea')
@@ -619,7 +782,7 @@ jQuery.fn.clearInputs = function() {
         else if (tag == 'select')
             this.selectedIndex = -1;
     });
-}
+};
 
 
 /**
@@ -631,13 +794,14 @@ jQuery.fn.clearInputs = function() {
  * @name resetForm
  * @type jQuery
  * @cat Plugins/Form
- * @see clearForm
  */
-jQuery.fn.resetForm = function() {
+$.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)) 
+        if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType))
             this.reset();
     });
-}
+};
+
+})(jQuery);
diff --git a/ecrire/inc/class.JavaScriptPacker.php b/ecrire/inc/class.JavaScriptPacker.php
new file mode 100644
index 0000000000..d3d37e4378
--- /dev/null
+++ b/ecrire/inc/class.JavaScriptPacker.php
@@ -0,0 +1,732 @@
+<?php
+/* 7 December 2006. version 1.0
+ * 
+ * This is the php version of the Dean Edwards JavaScript 's Packer,
+ * Based on :
+ * 
+ * ParseMaster, version 1.0.2 (2005-08-19) Copyright 2005, Dean Edwards
+ * a multi-pattern parser.
+ * KNOWN BUG: erroneous behavior when using escapeChar with a replacement
+ * value that is a function
+ * 
+ * packer, version 2.0.2 (2005-08-19) Copyright 2004-2005, Dean Edwards
+ * 
+ * License: http://creativecommons.org/licenses/LGPL/2.1/
+ * 
+ * Ported to PHP by Nicolas Martin.
+ * modified by Mark Fabrizio Jr. to work with php 4 
+ * 
+ * ----------------------------------------------------------------------
+ * 
+ * examples of usage :
+ * $myPacker = new JavaScriptPacker($script, 62, true, false);
+ * $packed = $myPacker->pack();
+ * 
+ * or
+ * 
+ * $myPacker = new JavaScriptPacker($script, 'Normal', true, false);
+ * $packed = $myPacker->pack();
+ * 
+ * or (default values)
+ * 
+ * $myPacker = new JavaScriptPacker($script);
+ * $packed = $myPacker->pack();
+ * 
+ * 
+ * params of the constructor :
+ * $script:       the JavaScript to pack, string.
+ * $encoding:     level of encoding, int or string :
+ *                0,10,62,95 or 'None', 'Numeric', 'Normal', 'High ASCII'.
+ *                default: 62.
+ * $fastDecode:   include the fast decoder in the packed result, boolean.
+ *                default : true.
+ * $specialChars: if you are flagged your private and local variables
+ *                in the script, boolean.
+ *                default: false.
+ * 
+ * The pack() method return the compressed JavasScript, as a string.
+ * 
+ * see http://dean.edwards.name/packer/usage/ for more information.
+ * 
+ * Notes :
+ * # [del]need PHP 5 . Tested with PHP 5.1.2[/del]
+ *   this is a modified version for PHP 4
+ * 
+ * # The packed result may be different than with the Dean Edwards
+ *   version, but with the same length. The reason is that the PHP
+ *   function usort to sort array don't necessarily preserve the
+ *   original order of two equal member. The Javascript sort function
+ *   in fact preserve this order (but that's not require by the
+ *   ECMAScript standard). So the encoded keywords order can be
+ *   different in the two results.
+ * 
+ * # Be careful with the 'High ASCII' Level encoding if you use
+ *   UTF-8 in your files... 
+ */
+ 
+ /*
+ * modified by Mark Fabrizio Jr. to work with php 4
+ */
+
+
+class JavaScriptPacker {
+	var $IGNORE = '$1';
+
+	// validate parameters
+	var $_script = '';
+	var $_encoding = 62;
+	var $_fastDecode = true;
+	var $_specialChars = false;
+	
+	var $LITERAL_ENCODING = array(
+		'None' => 0,
+		'Numeric' => 10,
+		'Normal' => 62,
+		'High ASCII' => 95
+	);
+	
+	function JavaScriptPacker($_script, $_encoding = 62, $_fastDecode = true, $_specialChars = false)
+	{
+		$this->_script = $_script . "\n";
+		if (array_key_exists($_encoding, $this->LITERAL_ENCODING))
+			$_encoding = $this->LITERAL_ENCODING[$_encoding];
+		$this->_encoding = min((int)$_encoding, 95);
+		$this->_fastDecode = $_fastDecode;	
+		$this->_specialChars = $_specialChars;
+	}
+	
+	function pack() {
+		$this->_addParser('_basicCompression');
+		if ($this->_specialChars)
+			$this->_addParser('_encodeSpecialChars');
+		if ($this->_encoding)
+			$this->_addParser('_encodeKeywords');
+		
+		// go!
+		return $this->_pack($this->_script);
+	}
+	
+	// apply all parsing routines
+	function _pack($script) {
+		for ($i = 0; isset($this->_parsers[$i]); $i++) {
+			$script = call_user_func(array(&$this,$this->_parsers[$i]), $script);
+		}
+		return $script;
+	}
+	
+	// keep a list of parsing functions, they'll be executed all at once
+	var $_parsers = array();
+	function _addParser($parser) {
+		$this->_parsers[] = $parser;
+	}
+	
+	// zero encoding - just removal of white space and comments
+	function _basicCompression($script) {
+		$parser = new ParseMaster();
+		// make safe
+		$parser->escapeChar = '\\';
+		// protect strings
+		$parser->add('/\'[^\'\\n\\r]*\'/',$this->IGNORE);
+		$parser->add('/"[^"\\n\\r]*"/', $this->IGNORE);
+		// remove comments
+		$parser->add('/\\/\\/[^\\n\\r]*[\\n\\r]/', ' ');
+		$parser->add('/\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\//', ' ');
+		// protect regular expressions
+		$parser->add('/\\s+(\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?)/', '$2'); // IGNORE
+		$parser->add('/[^\\w\\x24\\/\'"*)\\?:]\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?/', $this->IGNORE);
+		// remove: ;;; doSomething();
+		if ($this->_specialChars) $parser->add('/;;;[^\\n\\r]+[\\n\\r]/');
+		// remove redundant semi-colons
+		$parser->add('/\\(;;\\)/', $this->IGNORE); // protect for (;;) loops
+		$parser->add('/;+\\s*([};])/', '$2');
+		// apply the above
+		$script = $parser->exec($script);
+
+		// remove white-space
+		$parser->add('/(\\b|\\x24)\\s+(\\b|\\x24)/', '$2 $3');
+		$parser->add('/([+\\-])\\s+([+\\-])/', '$2 $3');
+		$parser->add('/\\s+/', '');
+		// done
+		return $parser->exec($script);
+	}
+	
+	function _encodeSpecialChars($script) {
+		$parser = new ParseMaster();
+		// replace: $name -> n, $$name -> na
+		$parser->add('/((\\x24+)([a-zA-Z$_]+))(\\d*)/',
+					 array('fn' => '_replace_name')
+		);
+		// replace: _name -> _0, double-underscore (__name) is ignored
+		$regexp = '/\\b_[A-Za-z\\d]\\w*/';
+		// build the word list
+		$keywords = $this->_analyze($script, $regexp, '_encodePrivate');
+		// quick ref
+		$encoded = $keywords['encoded'];
+		
+		$parser->add($regexp,
+			array(
+				'fn' => '_replace_encoded',
+				'data' => $encoded
+			)
+		);
+		return $parser->exec($script);
+	}
+	
+	function _encodeKeywords($script) {
+		// escape high-ascii values already in the script (i.e. in strings)
+		if ($this->_encoding > 62)
+			$script = $this->_escape95($script);
+		// create the parser
+		$parser = new ParseMaster();
+		$encode = $this->_getEncoder($this->_encoding);
+		// for high-ascii, don't encode single character low-ascii
+		$regexp = ($this->_encoding > 62) ? '/\\w\\w+/' : '/\\w+/';
+		// build the word list
+		$keywords = $this->_analyze($script, $regexp, $encode);
+		$encoded = $keywords['encoded'];
+		
+		// encode
+		$parser->add($regexp,
+			array(
+				'fn' => '_replace_encoded',
+				'data' => $encoded
+			)
+		);
+		if (empty($script)) return $script;
+		else {
+			//$res = $parser->exec($script);
+			//$res = $this->_bootStrap($res, $keywords);
+			//return $res;
+			return $this->_bootStrap($parser->exec($script), $keywords);
+		}
+	}
+	
+	function _analyze($script, $regexp, $encode) {
+		// analyse
+		// retreive all words in the script
+		$all = array();
+		preg_match_all($regexp, $script, $all);
+		$_sorted = array(); // list of words sorted by frequency
+		$_encoded = array(); // dictionary of word->encoding
+		$_protected = array(); // instances of "protected" words
+		$all = $all[0]; // simulate the javascript comportement of global match
+		if (!empty($all)) {
+			$unsorted = array(); // same list, not sorted
+			$protected = array(); // "protected" words (dictionary of word->"word")
+			$value = array(); // dictionary of charCode->encoding (eg. 256->ff)
+			$this->_count = array(); // word->count
+			$i = count($all); $j = 0; //$word = null;
+			// count the occurrences - used for sorting later
+			do {
+				--$i;
+				$word = '$' . $all[$i];
+				if (!isset($this->_count[$word])) {
+					$this->_count[$word] = 0;
+					$unsorted[$j] = $word;
+					// make a dictionary of all of the protected words in this script
+					//  these are words that might be mistaken for encoding
+					//if (is_string($encode) && method_exists($this, $encode))
+					$values[$j] = call_user_func(array(&$this, $encode), $j);
+					$protected['$' . $values[$j]] = $j++;
+				}
+				// increment the word counter
+				$this->_count[$word]++;
+			} while ($i > 0);
+			// prepare to sort the word list, first we must protect
+			//  words that are also used as codes. we assign them a code
+			//  equivalent to the word itself.
+			// e.g. if "do" falls within our encoding range
+			//      then we store keywords["do"] = "do";
+			// this avoids problems when decoding
+			$i = count($unsorted);
+			do {
+				$word = $unsorted[--$i];
+				if (isset($protected[$word]) /*!= null*/) {
+					$_sorted[$protected[$word]] = substr($word, 1);
+					$_protected[$protected[$word]] = true;
+					$this->_count[$word] = 0;
+				}
+			} while ($i);
+			
+			// sort the words by frequency
+			// Note: the javascript and php version of sort can be different :
+			// in php manual, usort :
+			// " If two members compare as equal,
+			// their order in the sorted array is undefined."
+			// so the final packed script is different of the Dean's javascript version
+			// but equivalent.
+			// the ECMAscript standard does not guarantee this behaviour,
+			// and thus not all browsers (e.g. Mozilla versions dating back to at
+			// least 2003) respect this. 
+			usort($unsorted, array(&$this, '_sortWords'));
+			$j = 0;
+			// because there are "protected" words in the list
+			//  we must add the sorted words around them
+			do {
+				if (!isset($_sorted[$i]))
+					$_sorted[$i] = substr($unsorted[$j++], 1);
+				$_encoded[$_sorted[$i]] = $values[$i];
+			} while (++$i < count($unsorted));
+		}
+		return array(
+			'sorted'  => $_sorted,
+			'encoded' => $_encoded,
+			'protected' => $_protected);
+	}
+	
+	var $_count = array();
+	function _sortWords($match1, $match2) {
+		return $this->_count[$match2] - $this->_count[$match1];
+	}
+	
+	// build the boot function used for loading and decoding
+	function _bootStrap($packed, $keywords) {
+		$ENCODE = $this->_safeRegExp('$encode\\($count\\)');
+
+		// $packed: the packed script
+		$packed = "'" . $this->_escape($packed) . "'";
+
+		// $ascii: base for encoding
+		$ascii = min(count($keywords['sorted']), $this->_encoding);
+		if ($ascii == 0) $ascii = 1;
+
+		// $count: number of words contained in the script
+		$count = count($keywords['sorted']);
+
+		// $keywords: list of words contained in the script
+		foreach ($keywords['protected'] as $i=>$value) {
+			$keywords['sorted'][$i] = '';
+		}
+		// convert from a string to an array
+		ksort($keywords['sorted']);
+		$keywords = "'" . implode('|',$keywords['sorted']) . "'.split('|')";
+
+		$encode = ($this->_encoding > 62) ? '_encode95' : $this->_getEncoder($ascii);
+		$encode = $this->_getJSFunction($encode);
+		$encode = preg_replace('/_encoding/','$ascii', $encode);
+		$encode = preg_replace('/arguments\\.callee/','$encode', $encode);
+		$inline = '\\$count' . ($ascii > 10 ? '.toString(\\$ascii)' : '');
+
+		// $decode: code snippet to speed up decoding
+		if ($this->_fastDecode) {
+			// create the decoder
+			$decode = $this->_getJSFunction('_decodeBody');
+			if ($this->_encoding > 62)
+				$decode = preg_replace('/\\\\w/', '[\\xa1-\\xff]', $decode);
+			// perform the encoding inline for lower ascii values
+			elseif ($ascii < 36)
+				$decode = preg_replace($ENCODE, $inline, $decode);
+			// special case: when $count==0 there are no keywords. I want to keep
+			//  the basic shape of the unpacking funcion so i'll frig the code...
+			if ($count == 0)
+				$decode = preg_replace($this->_safeRegExp('($count)\\s*=\\s*1'), '$1=0', $decode, 1);
+		}
+
+		// boot function
+		$unpack = $this->_getJSFunction('_unpack');
+		if ($this->_fastDecode) {
+			// insert the decoder
+			$this->buffer = $decode;
+			$unpack = preg_replace_callback('/\\{/', array(&$this, '_insertFastDecode'), $unpack, 1);
+		}
+		$unpack = preg_replace('/"/', "'", $unpack);
+		if ($this->_encoding > 62) { // high-ascii
+			// get rid of the word-boundaries for regexp matches
+			$unpack = preg_replace('/\'\\\\\\\\b\'\s*\\+|\\+\s*\'\\\\\\\\b\'/', '', $unpack);
+		}
+		if ($ascii > 36 || $this->_encoding > 62 || $this->_fastDecode) {
+			// insert the encode function
+			$this->buffer = $encode;
+			$unpack = preg_replace_callback('/\\{/', array(&$this, '_insertFastEncode'), $unpack, 1);
+		} else {
+			// perform the encoding inline
+			$unpack = preg_replace($ENCODE, $inline, $unpack);
+		}
+		// pack the boot function too
+		$unpackPacker = new JavaScriptPacker($unpack, 0, false, true);
+		$unpack = $unpackPacker->pack();
+		
+		// arguments
+		$params = array($packed, $ascii, $count, $keywords);
+		if ($this->_fastDecode) {
+			$params[] = 0;
+			$params[] = '{}';
+		}
+		$params = implode(',', $params);
+		
+		// the whole thing
+		return 'eval(' . $unpack . '(' . $params . "))\n";
+	}
+	
+	var $buffer;
+	function _insertFastDecode($match) {
+		return '{' . $this->buffer . ';';
+	}
+	function _insertFastEncode($match) {
+		return '{$encode=' . $this->buffer . ';';
+	}
+	
+	// mmm.. ..which one do i need ??
+	function _getEncoder($ascii) {
+		return $ascii > 10 ? $ascii > 36 ? $ascii > 62 ?
+		       '_encode95' : '_encode62' : '_encode36' : '_encode10';
+	}
+	
+	// zero encoding
+	// characters: 0123456789
+	function _encode10($charCode) {
+		return $charCode;
+	}
+	
+	// inherent base36 support
+	// characters: 0123456789abcdefghijklmnopqrstuvwxyz
+	function _encode36($charCode) {
+		return base_convert($charCode, 10, 36);
+	}
+	
+	// hitch a ride on base36 and add the upper case alpha characters
+	// characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
+	function _encode62($charCode) {
+		$res = '';
+		if ($charCode >= $this->_encoding) {
+			$res = $this->_encode62((int)($charCode / $this->_encoding));
+		}
+		$charCode = $charCode % $this->_encoding;
+		
+		if ($charCode > 35)
+			return $res . chr($charCode + 29);
+		else
+			return $res . base_convert($charCode, 10, 36);
+	}
+	
+	// use high-ascii values
+	// characters: ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿À�ÂÃÄÅÆÇÈÉÊËÌ�Î��ÑÒÓÔÕÖרÙÚÛÜ�Þßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ
+	function _encode95($charCode) {
+		$res = '';
+		if ($charCode >= $this->_encoding)
+			$res = $this->_encode95($charCode / $this->_encoding);
+		
+		return $res . chr(($charCode % $this->_encoding) + 161);
+	}
+	
+	function _safeRegExp($string) {
+		return '/'.preg_replace('/\$/', '\\\$', $string).'/';
+	}
+	
+	function _encodePrivate($charCode) {
+		return "_" . $charCode;
+	}
+	
+	// protect characters used by the parser
+	function _escape($script) {
+		return preg_replace('/([\\\\\'])/', '\\\$1', $script);
+	}
+	
+	// protect high-ascii characters already in the script
+	function _escape95($script) {
+		return preg_replace_callback(
+			'/[\\xa1-\\xff]/',
+			array(&$this, '_escape95Bis'),
+			$script
+		);
+	}
+	function _escape95Bis($match) {
+		return '\x'.((string)dechex(ord($match)));
+	}
+	
+	
+	function _getJSFunction($aName) {
+		$func = 'JSFUNCTION'.$aName;
+		if (isset($this->$func)){
+			return $this->$func;
+		}
+		else 
+			return '';
+	}
+	
+	// JavaScript Functions used.
+	// Note : In Dean's version, these functions are converted
+	// with 'String(aFunctionName);'.
+	// This internal conversion complete the original code, ex :
+	// 'while (aBool) anAction();' is converted to
+	// 'while (aBool) { anAction(); }'.
+	// The JavaScript functions below are corrected.
+	
+	// unpacking function - this is the boot strap function
+	//  data extracted from this packing routine is passed to
+	//  this function when decoded in the target
+	// NOTE ! : without the ';' final.
+	var $JSFUNCTION_unpack = 'function($packed, $ascii, $count, $keywords, $encode, $decode) {
+    while ($count--) {
+        if ($keywords[$count]) {
+            $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]);
+        }
+    }
+    return $packed;
+}';
+/*
+'function($packed, $ascii, $count, $keywords, $encode, $decode) {
+    while ($count--)
+        if ($keywords[$count])
+            $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]);
+    return $packed;
+}';
+*/
+	
+	// code-snippet inserted into the unpacker to speed up decoding
+	var $JSFUNCTION_decodeBody = '    if (!\'\'.replace(/^/, String)) {
+        // decode all the values we need
+        while ($count--) {
+            $decode[$encode($count)] = $keywords[$count] || $encode($count);
+        }
+        // global replacement function
+        $keywords = [function ($encoded) {return $decode[$encoded]}];
+        // generic match
+        $encode = function () {return \'\\\\w+\'};
+        // reset the loop counter -  we are now doing a global replace
+        $count = 1;
+    }
+';
+//};
+/*
+'	if (!\'\'.replace(/^/, String)) {
+        // decode all the values we need
+        while ($count--) $decode[$encode($count)] = $keywords[$count] || $encode($count);
+        // global replacement function
+        $keywords = [function ($encoded) {return $decode[$encoded]}];
+        // generic match
+        $encode = function () {return\'\\\\w+\'};
+        // reset the loop counter -  we are now doing a global replace
+        $count = 1;
+    }';
+*/
+	
+	 // zero encoding
+	 // characters: 0123456789
+	 var $JSFUNCTION_encode10 = 'function($charCode) {
+    return $charCode;
+}';//;';
+	
+	 // inherent base36 support
+	 // characters: 0123456789abcdefghijklmnopqrstuvwxyz
+	 var $JSFUNCTION_encode36 = 'function($charCode) {
+    return $charCode.toString(36);
+}';//;';
+	
+	// hitch a ride on base36 and add the upper case alpha characters
+	// characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
+	var $JSFUNCTION_encode62 = 'function($charCode) {
+    return ($charCode < _encoding ? \'\' : arguments.callee(parseInt($charCode / _encoding))) +
+    (($charCode = $charCode % _encoding) > 35 ? String.fromCharCode($charCode + 29) : $charCode.toString(36));
+}';
+	
+	// use high-ascii values
+	// characters: ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿À�ÂÃÄÅÆÇÈÉÊËÌ�Î��ÑÒÓÔÕÖרÙÚÛÜ�Þßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ
+	var $JSFUNCTION_encode95 = 'function($charCode) {
+    return ($charCode < _encoding ? \'\' : arguments.callee($charCode / _encoding)) +
+        String.fromCharCode($charCode % _encoding + 161);
+}'; 
+	
+}
+
+
+class ParseMaster {
+	var $ignoreCase = false;
+	var $escapeChar = '';
+	
+	// constants
+	var $EXPRESSION = 0;
+	var $REPLACEMENT = 1;
+	var $LENGTH = 2;
+	
+	// used to determine nesting levels
+	var $GROUPS = '/\\(/';//g
+	var $SUB_REPLACE = '/\\$\\d/';
+	var $INDEXED = '/^\\$\\d+$/';
+	var $TRIM = '/([\'"])\\1\\.(.*)\\.\\1\\1$/';
+	var $ESCAPE = '/\\\./';//g
+	var $QUOTE = '/\'/';
+	var $DELETED = '/\\x01[^\\x01]*\\x01/';//g
+	
+	function add($expression, $replacement = '') {
+		// count the number of sub-expressions
+		//  - add one because each pattern is itself a sub-expression
+		$length = 1 + preg_match_all($this->GROUPS, $this->_internalEscape((string)$expression), $out);
+		
+		// treat only strings $replacement
+		if (is_string($replacement)) {
+			// does the pattern deal with sub-expressions?
+			if (preg_match($this->SUB_REPLACE, $replacement)) {
+				// a simple lookup? (e.g. "$2")
+				if (preg_match($this->INDEXED, $replacement)) {
+					// store the index (used for fast retrieval of matched strings)
+					$replacement = (int)(substr($replacement, 1)) - 1;
+				} else { // a complicated lookup (e.g. "Hello $2 $1")
+					// build a function to do the lookup
+					$quote = preg_match($this->QUOTE, $this->_internalEscape($replacement))
+					         ? '"' : "'";
+					$replacement = array(
+						'fn' => '_backReferences',
+						'data' => array(
+							'replacement' => $replacement,
+							'length' => $length,
+							'quote' => $quote
+						)
+					);
+				}
+			}
+		}
+		// pass the modified arguments
+		if (!empty($expression)) $this->_add($expression, $replacement, $length);
+		else $this->_add('/^$/', $replacement, $length);
+	}
+	
+	function exec($string) {
+		// execute the global replacement
+		$this->_escaped = array();
+		
+		// simulate the _patterns.toSTring of Dean
+		$regexp = '/';
+		foreach ($this->_patterns as $reg) {
+			$regexp .= '(' . substr($reg[$this->EXPRESSION], 1, -1) . ')|';
+		}
+		$regexp = substr($regexp, 0, -1) . '/';
+		$regexp .= ($this->ignoreCase) ? 'i' : '';
+		
+		$string = $this->_escape($string, $this->escapeChar);
+		$string = preg_replace_callback(
+			$regexp,
+			array(
+				&$this,
+				'_replacement'
+			),
+			$string
+		);
+		$string = $this->_unescape($string, $this->escapeChar);
+		
+		return preg_replace($this->DELETED, '', $string);
+	}
+		
+	function reset() {
+		// clear the patterns collection so that this object may be re-used
+		$this->_patterns = array();
+	}
+
+	// private
+	var $_escaped = array();  // escaped characters
+	var $_patterns = array(); // patterns stored by index
+	
+	// create and add a new pattern to the patterns collection
+	function _add() {
+		$arguments = func_get_args();
+		$this->_patterns[] = $arguments;
+	}
+	
+	// this is the global replace function (it's quite complicated)
+	function _replacement($arguments) {
+		if (empty($arguments)) return '';
+		
+		$i = 1; $j = 0;
+		// loop through the patterns
+		while (isset($this->_patterns[$j])) {
+			$pattern = $this->_patterns[$j++];
+			// do we have a result?
+			if (isset($arguments[$i]) && ($arguments[$i] != '')) {
+				$replacement = $pattern[$this->REPLACEMENT];
+				
+				if (is_array($replacement) && isset($replacement['fn'])) {
+					
+					if (isset($replacement['data'])) $this->buffer = $replacement['data'];
+					return call_user_func(array(&$this, $replacement['fn']), $arguments, $i);
+					
+				} elseif (is_int($replacement)) {
+					return $arguments[$replacement + $i];
+				
+				}
+				$delete = ($this->escapeChar == '' ||
+				           strpos($arguments[$i], $this->escapeChar) === false)
+				        ? '' : "\x01" . $arguments[$i] . "\x01";
+				return $delete . $replacement;
+			
+			// skip over references to sub-expressions
+			} else {
+				$i += $pattern[$this->LENGTH];
+			}
+		}
+	}
+	
+	function _backReferences($match, $offset) {
+		$replacement = $this->buffer['replacement'];
+		$quote = $this->buffer['quote'];
+		$i = $this->buffer['length'];
+		while ($i) {
+			$replacement = str_replace('$'.$i--, $match[$offset + $i], $replacement);
+		}
+		return $replacement;
+	}
+	
+	function _replace_name($match, $offset){
+		$length = strlen($match[$offset + 2]);
+		$start = $length - max($length - strlen($match[$offset + 3]), 0);
+		return substr($match[$offset + 1], $start, $length) . $match[$offset + 4];
+	}
+	
+	function _replace_encoded($match, $offset) {
+		return $this->buffer[$match[$offset]];
+	}
+	
+	
+	// php : we cannot pass additional data to preg_replace_callback,
+	// and we cannot use &$this in create_function, so let's go to lower level
+	var $buffer;
+	
+	// encode escaped characters
+	function _escape($string, $escapeChar) {
+		if ($escapeChar) {
+			$this->buffer = $escapeChar;
+			return preg_replace_callback(
+				'/\\' . $escapeChar . '(.)' .'/',
+				array(&$this, '_escapeBis'),
+				$string
+			);
+			
+		} else {
+			return $string;
+		}
+	}
+	function _escapeBis($match) {
+		$this->_escaped[] = $match[1];
+		return $this->buffer;
+	}
+	
+	// decode escaped characters
+	function _unescape($string, $escapeChar) {
+		if ($escapeChar) {
+			$regexp = '/'.'\\'.$escapeChar.'/';
+			$this->buffer = array('escapeChar'=> $escapeChar, 'i' => 0);
+			return preg_replace_callback
+			(
+				$regexp,
+				array(&$this, '_unescapeBis'),
+				$string
+			);
+			
+		} else {
+			return $string;
+		}
+	}
+	function _unescapeBis() {
+		if (!empty($this->_escaped[$this->buffer['i']])) {
+			 $temp = $this->_escaped[$this->buffer['i']];
+		} else {
+			$temp = '';
+		}
+		$this->buffer['i']++;
+		return $this->buffer['escapeChar'] . $temp;
+	}
+	
+	function _internalEscape($string) {
+		return preg_replace($this->ESCAPE, '', $string);
+	}
+}
+?>
diff --git a/ecrire/inc/filtres.php b/ecrire/inc/filtres.php
index 128ff690be..a0833c0013 100644
--- a/ecrire/inc/filtres.php
+++ b/ecrire/inc/filtres.php
@@ -1955,14 +1955,15 @@ function charge_scripts($scripts) {
   return $flux;
 }
 
-// Compacte du javascript grace a javascriptcompressor
+// Compacte du javascript grace a Dean Edward's JavaScriptPacker
 // utile pour dist/jquery.js par exemple
 // http://doc.spip.org/@compacte_js
 function compacte_js($flux) {
-	include_spip('inc/compacte_js');
-	$k = new JavaScriptCompressor();
+	include_spip('inc/class.JavaScriptPacker');
+	$packer = new JavaScriptPacker($flux, 0, true, false);
+
 	// en cas d'echec (?) renvoyer l'original
-	if (strlen($t = $k->getClean($flux)))
+	if (strlen($t = $packer->pack()))
 		return $t;
 
 	// erreur
-- 
GitLab