Index: /trunk/spip/esqueleto-redcta/plugins/crayons/action/crayons_html.php
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/action/crayons_html.php	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/action/crayons_html.php	(revision 69)
@@ -0,0 +1,363 @@
+<?php
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+function affiche_controleur($class, $c=null) {
+	$return = array('$erreur'=>'');
+
+	if (preg_match(_PREG_CRAYON, $class, $regs)) {
+		list(,$nomcrayon,$type,$champ,$id) = $regs;
+		$regs[] = $class;
+		include_spip('inc/autoriser');
+		if (!autoriser('modifier',$type, $id, NULL, array('champ'=>$champ))) {
+			$return['$erreur'] = "$type $id: " . _U('crayons:non_autorise');
+		} else {
+			$f = charger_fonction($type.'_'.$champ, 'controleurs', true)
+			OR $f = charger_fonction($champ, 'controleurs', true)
+			OR $f = charger_fonction($type, 'controleurs', true)
+			OR $f = 'controleur_dist';
+			list($html,$status) = $f($regs, $c);
+			if ($status) {
+				$return['$erreur'] = $html;
+			} else {
+				$return['$html'] = $html;
+			}
+		}
+	} else {
+		$return['$erreur'] = _U('crayons:donnees_mal_formatees');
+	}
+	return $return;
+}
+
+function controleur_dist($regs, $c=null) {
+	list( , $nomcrayon, $type, $champ, $id, $class) = $regs;
+	$options = array(
+		'class' => $class
+	);
+	// Si le controleur est un squelette html, on va chercher
+	// les champs qu'il lui faut dans la table demandee
+	// Attention, un controleur multi-tables ne fonctionnera
+	// que si les champs ont le meme nom dans toutes les tables
+	// (par exemple: hyperlien est ok, mais pas nom)
+	if (($fichier = find_in_path(
+	($controleur = 'controleurs/' . $type . '_' . $champ) . '.html'))
+	|| ($fichier = find_in_path(
+	($controleur = 'controleurs/' . $champ) .'.html'))) {
+		if (!lire_fichier($fichier, $controldata))
+			die('erreur lecture controleur');
+		if (preg_match_all('/\bname=(["\'])#ENV\{name_(\w+)\}\1/',
+		$controldata, $matches, PREG_PATTERN_ORDER)) {
+			$champ = $matches[2];
+		}
+	} else {
+		$controleur = '';
+	}
+
+	$valeur = valeur_colonne_table($type, $champ, $id);
+
+	#spip_log("$valeur = valeur_colonne_table($type, $champ, $id);");
+	#spip_log($champ);
+
+	if ($valeur === false) {
+		return array("$type $id $champ: " . _U('crayons:pas_de_valeur'), 6);
+	}
+/*	if (is_scalar($valeur)) {
+		$valeur = array($champ => $valeur);
+	}*/
+
+	// type du crayon (a revoir quand le core aura type ses donnees)
+	$inputAttrs = array();
+	if ($controleur) {
+		$options['hauteurMini'] = 80; // base de hauteur mini
+		$option['inmode'] = 'controleur';
+		$options['controleur'] = $controleur;
+	} else
+	// si la valeur fait plusieurs lignes on doit mettre un textarea
+	if (
+	preg_match(",[\n\r],", $valeur[$champ])
+		OR
+	// on regarde le type tel que defini dans serial
+	// (attention il y avait des blob dans les vieux spip)
+	($sqltype = colonne_table($type, $champ)) &&
+	   ( in_array($sqltype['type'] , array('mediumtext', 'longblob', 'longtext')) ||
+	   (($sqltype['type'] == 'text' || $sqltype['type'] == 'blob') && in_array($champ, array('descriptif', 'bio'))))) {
+		$options['hauteurMini'] = 80; // hauteur mini d'un textarea
+		$option['inmode'] = 'texte';
+	} else { // ligne, hauteur naturelle
+		$options['hauteurMaxi'] = 0;
+		$option['inmode'] = 'ligne';
+		if ($sqltype['long']) {
+			$inputAttrs['maxlength'] = is_array($sqltype['long']) ?
+					$sqltype['long'][0] : $sqltype['long'];
+		}
+	}
+
+	$crayon = new Crayon($nomcrayon, $valeur, $options, $c);
+	$inputAttrs['style'] = join($crayon->styles);
+
+	if (!$controleur) {
+		$inputAttrs['style'] .= 'width:' . $crayon->largeur . 'px;' .
+		 ($crayon->hauteur ? ' height:' . $crayon->hauteur . 'px;' : '');
+	}
+
+	$html = $controleur ? $crayon->formulaire(null, $inputAttrs) :
+					$crayon->formulaire($option['inmode'], $inputAttrs);
+	$status = NULL;
+
+	return array($html,$status);
+}
+
+// Definition des crayons
+class Crayon {
+	// le nom du crayon "type-modele-id" comme "article-introduction-237"
+	var $name;
+	// type, a priori une table, extrait du nom
+	var $type;
+	// modele, un champ comme "texte" ou un modele, extrait du nom
+	var $modele;
+	// l'identificateur dans le type, comme un numero d'article
+	var $id;
+	// la ou les valeurs des champs du crayon, tableau associatif champ => valeur
+	var $texts = array();
+	// une cle unique pour chaque crayon demande
+	var $key;
+	// un md5 associe aux valeurs pour verifier et detecter si elles changent
+	var $md5;
+	// dimensions indicatives
+	var $largeurMini = 170;
+	var $largeurMaxi = 700;
+	var $hauteurMini = 80;
+	var $hauteurMaxi = 700;
+	var $largeur;
+	// le mode d'entree: texte, ligne ou controleur
+	var $inmode = '';
+	// eventuellement le fond modele pour le controleur
+	var $controleur = '';
+	var $styles = array();
+
+	// le constructeur du crayon
+	// $name : son nom
+	// $texts : tableau associatif des valeurs ou valeur unique si crayon monochamp
+	// $options : options directes du crayon (developpement)
+	function Crayon($name, $texts = array(), $options = array(), $c=null) {
+		$this->name = $name;
+		list($this->type, $this->modele, $this->id) = explode('-', $this->name, 3);
+		if (is_scalar($texts) || is_null($texts)) {
+			$texts = array($this->modele => $texts);
+		}
+		$this->texts = $texts;
+		$this->key = strtr(uniqid('wid', true), '.', '_');
+		$this->md5 = $this->md5();
+		foreach ($options as $opt=>$val) {
+			$this->$opt = $val;
+		}
+		$this->dimension($c);
+		$this->css();
+	}
+
+	// calcul du md5 associe aux valeurs
+	function md5() {
+		return md5(serialize($this->texts));
+	}
+
+	// dimensions indicatives
+	function dimension($c) {
+		// largeur du crayon
+		$this->largeur = min(max(intval(_request('w', $c)),
+					$this->largeurMini), $this->largeurMaxi);
+		// hauteur maxi d'un textarea selon wh: window height
+		$maxheight = min(max(intval(_request('wh', $c)) - 50, 400), $this->hauteurMaxi);
+		$this->hauteur = min(max(intval(_request('h', $c)), $this->hauteurMini), $maxheight);
+	}
+
+	// recuperer les elements de style
+	function css() {
+		foreach(array('color', 'font-size', 'font-family', 'font-weight', 'line-height') as $property) {
+			if (null !== ($p = _request($property)))
+				$this->styles[] = "$property:$p;";
+		}
+		
+		$property = 'background-color';
+		if (!$p = _request($property)
+		OR $p == 'transparent') {
+			$p = 'white';
+		}
+		$this->styles[] = "$property:$p;";
+	}
+
+	// formulaire standard
+	function formulaire($contexte = array(), $inputAttrs = array()) {
+		return
+			$this->code() .
+			$this->input($contexte, $inputAttrs);
+	}
+
+	// balises input type hidden d'identification du crayon
+	function code() {
+		return
+		 '<input type="hidden" class="crayon-id" name="crayons[]"'
+		.' value="'.$this->key.'" />'."\n"
+		. '<input type="hidden" name="name_'.$this->key
+		.'" value="'.$this->name.'" />'."\n"
+		. '<input type="hidden" name="class_' . $this->key
+		. '" value="' . $this->class . '" />' . "\n"
+		. '<input type="hidden" name="md5_'.$this->key
+		.'" value="'.$this->md5.'" />'."\n"
+		. '<input type="hidden" name="fields_'.$this->key
+		.'" value="'.join(',',array_keys($this->texts)).'" />'
+		."\n"
+		;
+	}
+
+/*
+ Fabriquer les balises des champs d'apres un modele controleurs/(type_)modele.html
+	$contexte est un tableau (nom=>valeur) qui sera enrichi puis passe Ã  recuperer_fond
+*/
+	function fond($contexte = array()) {
+		include_spip('inc/filtres');
+		$contexte['id_' . $this->type] = $this->id;
+		$contexte['lang'] = $GLOBALS['spip_lang'];
+		$contexte['key'] = $this->key;
+		$contexte['largeur'] = $this->largeur;
+		$contexte['hauteur'] = $this->hauteur;
+		$contexte['self'] = _request('self');
+		foreach ($this->texts as $champ => $val) {
+			$contexte['name_' . $champ] = 'content_' . $this->key . '_' . $champ;
+		}
+		$contexte['style'] = join(' ',$this->styles);
+		include_spip('public/assembler');
+		return recuperer_fond($this->controleur, $contexte);
+	}
+
+/*
+ Fabriquer les balises du ou des champs
+	$spec est soit un scalaire 'ligne' ou 'texte' prÃ©cisant le type de balise
+	soit un array($champ=>array('type'=>'...', 'attrs'=>array(attributs specifique du champs)))
+	$attrs est un tableau (attr=>val) d'attributs communs ou pour le champs unique
+*/
+	function input($spec = 'ligne', $attrs = array()) {
+		if ($this->controleur) {
+			return $this->fond($spec);
+		}
+		include_spip('inc/filtres');
+		$return = '';
+		foreach ($this->texts as $champ => $val) {
+			$type = is_array($spec) ? $spec[$champ]['type'] : $spec;
+			switch ($type) {
+				case 'texte':
+					$id = uniqid('wid');
+					$input = '<textarea style="width:100%;" class="crayon-active"'
+					. ' name="content_'.$this->key.'_'.$champ.'" id="'.$id.'">'
+					. entites_html($val)
+					. "</textarea>\n";
+
+					// petit truc crado pour mettre la barre typo si demandee
+					// pour faire propre il faudra reprogrammer la bt en jquery
+					$meta_crayon = unserialize($GLOBALS['meta']['crayons']);
+					if ($meta_crayon['barretypo']) {
+						include_spip('inc/barre');
+						$input = "<span style='width:100%; height:20px;'>"
+							. afficher_barre("document.getElementById('$id')")
+							. '</span>'
+							. $input;
+					}
+
+					break;
+				case 'ligne':
+				default:
+					$input = '<input class="crayon-active" type="text"'
+					. ' name="content_'.$this->key.'_'.$champ.'"'
+					. ' value="'
+					. entites_html($val)
+					. '" />'."\n";
+			}
+			if (is_array($spec) && isset($spec[$champ]['attrs'])) {
+				foreach ($spec[$champ]['attrs'] as $attr=>$val) {
+					$input = inserer_attribut($input, $attr, $val);
+				}
+			}
+
+			foreach ($attrs as $attr=>$val) {
+				$input = inserer_attribut($input, $attr, $val);
+			}
+			$return .= $input;
+		}
+		return $return;
+	}
+
+}
+
+
+/*
+	Fabriquer les boutons du formulaire
+*/
+function crayons_boutons($boutons = array()) {
+	$crayonsImgPath = dirname(url_absolue(find_in_path('images/cancel.png')));
+	$boutons['submit'] = array('ok', texte_backend(_T('bouton_enregistrer')));
+	$boutons['cancel'] = array('cancel', texte_backend(_T('crayons:annuler')));
+
+	$html = '';
+	foreach ($boutons as $bnam => $bdef) if ($bdef) {
+		$html .= '<a class="crayon-' . $bnam .
+			'" title="' . $bdef[1] . '"';
+		if (!empty($bdef[2])) {
+			$html .= ' href="' . $bdef[2] . '"';
+		}
+		$html .= '><img src="' . $crayonsImgPath . '/' .
+			$bdef[0] . '.png" width="20" height="20" /></a>';
+	}
+
+	if ($html)
+		return '<div class="crayon-boutons"><div>'.$html.'</div></div>';
+}
+
+function crayons_formulaire($html) {
+	if (!$html)
+		return '';
+
+	include_spip('inc/filtres');
+	return liens_absolus(
+		'<form class="formulaire_crayon" method="post" action="'
+		. url_absolue(parametre_url(self(),'action', 'crayons_store'))
+		. '" enctype="multipart/form-data">'
+		. $html
+		. crayons_boutons()
+		. '</form>'
+	);
+}
+
+//
+// Un Crayon avec une verification de code de securite
+//
+class SecureCrayon extends Crayon {
+
+	function SecureCrayon($name, $text='') {
+		parent::Crayon($name, $text);
+	}
+
+	function code() {
+		$code = parent::code();
+		$secu = md5($GLOBALS['meta']['alea_ephemere']. '=' . $this->name);
+
+		return
+			$code
+			.'<input type="hidden" name="secu_'.$this->key.'" value="'.$secu.'" />'."\n";
+	}
+}
+
+function action_crayons_html_dist() {
+
+	header("Content-Type: text/html; charset=".$GLOBALS['meta']['charset']);
+
+	// CONTROLEUR
+	// on affiche le formulaire demande
+	include_spip('inc/crayons');
+	lang_select($GLOBALS['auteur_session']['lang']);
+	$return = affiche_controleur(_request('class'));
+	$return['$html'] = crayons_formulaire($return['$html']);
+
+	echo var2js($return);
+	exit;
+}
+
+?>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/action/crayons_store.php
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/action/crayons_store.php	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/action/crayons_store.php	(revision 69)
@@ -0,0 +1,347 @@
+<?php
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+function verif_secu($w, $secu) {
+    return (
+        $secu == md5($GLOBALS['meta']['alea_ephemere'].'='.$w)
+    OR
+        $secu == md5($GLOBALS['meta']['alea_ephemere_ancien'].'='.$w)
+    );
+}
+
+function post_crayons() {
+    $results = array();
+    if (isset($_POST['crayons']) AND is_array($_POST['crayons']))
+    foreach ($_POST['crayons'] as $crayon) {
+
+        $name = $_POST['name_'.$crayon];
+        $content = array();
+        if ($_POST['fields_'.$crayon]) {
+          foreach (explode(',', $_POST['fields_'.$crayon]) as $field) {
+            // cas particulier d'un envoi de fichier
+            if (isset($_FILES['content_'.$crayon.'_'.$field])) {
+                $content[$field] = $_FILES['content_'.$crayon.'_'.$field];
+            } else {
+            	$content[$field] = $_POST['content_'.$crayon.'_'.$field];
+            	// Compatibilite charset autre que utf8 ; en effet on recoit
+            	// obligatoirement les donnees en utf-8, par la magie d'ajax
+            	if ($GLOBALS['meta']['charset']!='utf-8') {
+            	    include_spip('inc/charsets');
+            	    $content[$field] = importer_charset($content[$field], 'utf-8');
+            	}
+            }
+          }
+        }
+
+        // Si les donnees POSTees ne correspondent pas a leur md5,
+        // il faut les traiter
+        if (md5(serialize($content)) <> $_POST['md5_'.$crayon]) {
+            if (!isset($_POST['secu_'.$crayon])
+            OR verif_secu($name, $_POST['secu_'.$crayon])) {
+                $results[] = array($name, $content, $_POST['md5_'.$crayon], $crayon);
+            }
+            else {
+                return false; // erreur secu
+            }
+        }
+        // cas inchange
+        else
+            $results[] = array($name, $content, false, $crayon);
+    }
+
+    return $results;
+}
+
+
+function crayons_store() {
+	include_spip('inc/crayons');
+	$wdgcfg = wdgcfg();
+	
+	$return = array('$erreur'=>'');
+
+	$postees = post_crayons();
+
+	$modifs = $updates = array();
+	if (!is_array($postees)) {
+		$return['$erreur'] = _U('crayons:donnees_mal_formatees');
+	} else {
+		include_spip('inc/autoriser');
+
+		foreach ($postees as $postee) {
+			$name = $postee[0];
+			$content = $postee[1];
+
+			if ($content && preg_match(_PREG_CRAYON, 'crayon '.$name, $regs)) {
+				list(,$crayon,$type,$modele,$id) = $regs;
+				$wid = $postee[3];
+
+				if (!autoriser('modifier', $type, $id, NULL, array('modele'=>$modele))) {
+					$return['$erreur'] =
+						"$type $id: " . _U('crayons:non_autorise');
+				} else {
+
+					// recuperer l'existant pour calculer son md5 et verifier
+					// qu'il n'a pas ete modifie entre-temps
+					$data = valeur_colonne_table($type, array_keys($content), $id);
+					$md5 = md5(serialize($data));
+
+					// est-ce que le champ a ete modifie dans la base entre-temps ?
+					if ($md5 != $postee[2]) {
+						// si oui, la modif demandee correspond peut-etre
+						// a la nouvelle valeur ? dans ce cas on procede
+						// comme si "pas de modification", sinon erreur
+						if ($md5 != md5(serialize($content))) {
+							$return['$erreur'] = "$type $id $champtable: " .
+								_U('crayons:modifie_par_ailleurs');
+						}
+					}
+
+					$modifs[] = array($type, $modele, $id, $content, $wid);
+				}
+			}
+		}
+	}
+
+	if (!$modifs AND !$return['$erreur']) {
+		$return['$erreur'] = $wdgcfg['msgNoChange'] ?
+		   _U('crayons:pas_de_modification') : ' ';
+		$return['$annuler'] = true;
+	}
+
+	// une quelconque erreur ... ou rien ==> on ne fait rien !
+	if ($return['$erreur'])
+		return $return;
+
+	// sinon on bosse : toutes les modifs ont ete acceptees
+	// verifier qu'on a tout ce qu'il faut pour mettre a jour la base
+	// et regrouper les mises a jour par type/id
+	foreach ($modifs as $modif) {
+		list($type, $modele, $id, $content, $wid) = $modif;
+		if (!isset($updates[$type])) {
+			// MODELE
+			$fun = '';
+			if (function_exists($f = $type.'_'. $modele . "_revision")
+			OR function_exists($f = $modele . "_revision")
+			OR function_exists($f = $type . "_revision"))
+				$fun = $f;
+			else switch($type) {
+				case 'article':
+				    $fun = 'crayons_update_article';
+				    break;
+				case 'breve':
+				    include_spip('action/editer_breve');
+				    $fun = 'revisions_breves';
+				    break;
+				case 'forum':
+				    include_spip('inc/forum');
+				    $fun = 'enregistre_et_modifie_forum';
+				    break;
+				case 'rubrique':
+				    include_spip('action/editer_rubrique');
+				    $fun = 'revisions_rubriques';
+				    break;
+				case 'syndic':
+				case 'site':
+				    include_spip('action/editer_site');
+				    $fun = 'revisions_sites';
+				    break;
+				// cas geres de la maniere la plus standard
+				case 'auteur':
+				case 'document':
+				case 'mot':
+				case 'signature':
+				case 'petition':
+				default:
+				    include_spip('inc/modifier');
+				    $fun = 'revision_'.$type;
+				    break;
+			}
+			if (!$fun or !function_exists($fun)) {
+				    $fun = 'crayons_update';
+//			    $return['$erreur'] = "$type: " . _U('crayons:non_implemente');
+//			    break;
+			}
+			$updates[$type] = array('fun'=>$fun, 'ids'=>array());
+		}
+		if (!isset($updates[$type]['ids'][$id])) {
+			$updates[$type]['ids'][$id] = array('wdg'=>array(), 'chval'=>array());
+		}
+		// pour reaffecter le retour d'erreur sql au cas ou
+		$updates[$type]['ids'][$id]['wdg'][] = $wid;
+		foreach ($content as $champtable => $val) {
+			$updates[$type]['ids'][$id]['chval'][$champtable] = $val;
+		}
+	}
+
+	// il manque une fonction de mise Ã  jour ==> on ne fait rien !
+	if ($return['$erreur'])
+	    return $return;
+
+	// hop ! mises Ã  jour table par table et id par id
+	foreach ($updates as $type => $idschamps) {
+		foreach ($idschamps['ids'] as $id => $champsvaleurs) {
+	        // Enregistrer dans la base
+	        // $updok = ... quand on aura un retour
+	        // -- revisions_articles($id_article, $c) --
+	        $idschamps['fun']($id, $champsvaleurs['chval'], $type, $champsvaleurs['wdg']);
+	    }
+	}
+
+	// et maintenant refaire l'affichage des crayons modifies
+	include_spip('inc/texte');
+	foreach ($modifs as $m) {
+		list($type, $modele, $id, $content, $wid) = $m;
+			$f = charger_fonction($type.'_'.$modele, 'vues', true)
+			  OR $f = charger_fonction($modele, 'vues', true)
+			  OR $f = charger_fonction($type, 'vues', true)
+			  OR $f = 'vues_dist';
+			$return[$wid] = $f($type, $modele, $id, $content);
+	}
+	return $return;
+}
+
+//
+// VUE
+//
+function vues_dist($type, $modele, $id, $content){
+
+	// pour ce qui a une {lang_select} par defaut dans la boucle,
+	// la regler histoire d'avoir la bonne typo dans le propre()
+	// NB: ceci n'a d'impact que sur le "par defaut" en bas
+	if (colonne_table($type, 'lang')) {
+		$b = valeur_colonne_table($type, 'lang', $id);
+		lang_select($a = array_pop($b));
+	} else {
+		lang_select($a = $GLOBALS['meta']['langue_site']);
+	}
+
+  // chercher vues/article_toto.html
+  // sinon vues/toto.html
+  if (find_in_path( ($fond = 'vues/' . $type . '_' . $modele) . '.html')
+  OR find_in_path( ($fond = 'vues/' . $modele) .'.html')
+  OR find_in_path( ($fond = 'vues/' . $type) .'.html')) {
+		$contexte = array(
+		    'id_' . $type => $id,
+		    'champ' => $modele,
+		    'class' => _request('class'),
+		    'self' => _request('self'),
+		    'lang' => $GLOBALS['spip_lang']
+		);
+		$contexte = array_merge($contexte, $content);
+		include_spip('public/assembler');
+		return recuperer_fond($fond, $contexte);
+  }
+	// vue par defaut
+	else {
+		// Par precaution on va rechercher la valeur
+		// dans la base de donnees (meme si a priori la valeur est
+		// ce qu'on vient d'envoyer, il y a nettoyage des caracteres et
+		// eventuellement d'autres filtres de saisie...)
+		$valeur = array_pop(valeur_colonne_table($type, $modele, $id));
+		
+		// seul spip core sait rendre les donnees
+		if (in_array($modele,
+		  array('chapo', 'texte', 'descriptif', 'ps', 'bio'))) {
+			return propre($valeur);
+		} else {
+			return typo($valeur);
+		}
+	}
+}
+
+//
+// Fonctions de mise a jour generique
+//
+function crayons_update($id, $colval = array(), $type = '')
+{
+	if (!$colval) {
+		return false;
+	}
+	list($nom_table, $where) = table_where($type, $id);
+	if (!$nom_table) {
+		return false;
+	}
+
+	$update = $sep = '';
+	foreach ($colval as $col => $val) {
+		$update .= $sep . '`' . $col . '`=' . _q($val);
+		$sep = ', ';
+	}
+
+	$a = spip_query($q = 
+        'UPDATE `' . $nom_table . '` SET ' . $update . ' WHERE ' . $where);
+
+	#spip_log($q);
+	include_spip('inc/invalideur');
+	suivre_invalideur($cond, $modif=true);
+
+	return $a;
+}
+
+//
+// Fonctions de mise a jour
+//
+function crayons_update_article($id_article, $c = false) {
+	include_spip('action/editer_article');
+
+	// Enregistrer les nouveaux contenus
+	revisions_articles($id_article, $c);
+
+	// En cas de statut ou de id_rubrique
+	// NB: instituer_article veut id_parent, et pas id_rubrique !
+	if (isset($c['id_rubrique'])) {
+		$c['id_parent'] = $c['id_rubrique'];
+		unset ($c['id_rubrique']);
+	}
+	instituer_article($id_article, $c);
+}
+
+// TODO:
+// Ce modele est cense enregistrer les tags sous forme de ??
+// une ligne dans un champ spip_articles.tags, et/ou des mots-clÃ©s...
+function modeles_tags($id, $c) {
+	var_dump($id); #id_article
+	var_dump($c); # perturbant : ici on a array('id_article'=>'valeur envoyee')
+}
+
+function action_crayons_store_dist() {
+	header("Content-Type: text/html; charset=".$GLOBALS['meta']['charset']);
+	lang_select($GLOBALS['auteur_session']['lang']);
+
+	$r = crayons_store();
+
+	// Si on a ete appeles par jQuery, on renvoie tout, c'est le client
+	// crayons.js qui va traiter l'affichage du resultat et status
+	# Attention le test $_SERVER["HTTP_X_REQUESTED_WITH"] === "XMLHttpRequest"
+	# n'est pas bon car le cas d'un fichier uploade via iframe n'est pas detecte
+
+	// S'il y a une adresse de redirection, on renvoie vers elle
+	// En cas d'erreur il faudrait ajouter &err=... dans l'url ?
+	if (_request('redirect')) {
+		if (!$r['$erreur']
+		OR $r['$annuler']) {
+			include_spip('inc/headers');
+			redirige_par_entete(_request('redirect'));
+		} else {
+			echo "<h4 class='status'>".$r['$erreur']."</h4>\n";
+
+			foreach ($r as $wid => $v) {
+				if ($wid !== '$erreur')
+					echo "<div id='$wid'>$v</div><hr />\n";
+			}
+			echo "<a href='".quote_amp(_request('redirect'))."'>"
+				.quote_amp(_request('redirect'))
+				."</a>\n";
+		}
+	}
+
+	// Cas normal : JSON
+	else {
+		echo json_export($r);
+	}
+
+	exit;
+}
+
+?>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/article_intro.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/article_intro.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/article_intro.html	(revision 69)
@@ -0,0 +1,10 @@
+[(#REM)
+
+	Controleur pour le crayon 'article_intro' , uniquement html
+
+]
+#CACHE{0}
+<BOUCLE_a(ARTICLES){id_article}{statut==.}>
+<textarea class="crayon-active" name="#ENV{name_descriptif}"
+ style="width:#ENV{largeur}px; height:#ENV{hauteur}px;#ENV{style}">[(#DESCRIPTIF**|sinon{#INTRODUCTION|textebrut}|entites_html)]</textarea>
+</BOUCLE_a>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/article_intro2.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/article_intro2.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/article_intro2.html	(revision 69)
@@ -0,0 +1,15 @@
+[(#REM)
+
+	Controleur pour le crayon 'article_intro2' , uniquement html
+
+]
+#CACHE{0}
+<BOUCLE_a(ARTICLES){id_article}{statut==.}>
+<fieldset style="width:#ENV{largeur}px;#ENV{style}">
+<legend>Introduction</legend>
+<label>Descriptif</label><textarea class="crayon-active" name="#ENV{name_descriptif}"
+ style="width:100%; height:#ENV{hauteur}px;">[(#DESCRIPTIF**|entites_html)]</textarea>
+<label>Chapo</label><textarea class="crayon-active" name="#ENV{name_chapo}"
+ style="width:100%; height:#ENV{hauteur}|mul{2}px;">[(#CHAPO**|entites_html)]</textarea>
+</fieldset>
+</BOUCLE_a>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/article_intro3.php
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/article_intro3.php	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/article_intro3.php	(revision 69)
@@ -0,0 +1,36 @@
+<?php
+// un controleur qui n'utilise que php et les inputs dÃ©fauts
+function controleurs_article_intro3_dist($regs) {
+    list(,$crayon,$type,$champ,$id) = $regs;
+    $valeur = valeur_colonne_table($type, array('descriptif', 'chapo', 'texte'), $id);
+    if ($valeur === false) {
+	    return array("$type $id $champ: " . _U('crayons:pas_de_valeur'), 6);
+    }
+
+	$n = new Crayon('article-intro3-' . $id, $valeur,
+			array('hauteurMini' => 234));
+    
+    $return = array(
+    	// html
+	    $n->formulaire(
+	    	// champs et attributs propres
+	    	array(
+	    	'descriptif' => array('type' => 'texte', 'attrs' => array(
+		        'style' => 'height:' . ceil($n->hauteur*2/13) . 'px;' .
+					        'width:' . $n->largeur . 'px;')),
+			'chapo' =>  array('type' => 'texte', 'attrs' => array(
+		        'style' => 'height:' . ceil($n->hauteur*4/13) . 'px;' .
+					        'width:' . $n->largeur . 'px;')),
+			'texte' =>  array('type' => 'texte', 'attrs' => array(
+		        'style' => 'height:' . ceil($n->hauteur*4/13) . 'px;' .
+					        'width:' . $n->largeur . 'px;'))) //,
+		    // attributs communs :( marche pas pour style , pas 2 fois ?
+//	    	array('style' => 'width:' . $n->largeur . 'px;')
+    	),
+    	// status
+    	null);
+
+	return $return;
+}
+
+?>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/article_introduction.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/article_introduction.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/article_introduction.html	(revision 69)
@@ -0,0 +1,19 @@
+[(#REM)
+
+	Controleur pour le crayon 'article_introduction' php+html
+
+]
+#CACHE{0}
+<BOUCLE_a(ARTICLES){id_article}{statut==.}>
+<fieldset style="width:#ENV{largeur}px;#ENV{style}"><legend>Introduction</legend>
+<label>Descriptif</label><textarea class="crayon-active maxheight150" name="#ENV{name_descriptif}"
+	style="width:100%;
+	height:#ENV{h_descriptif}px;">[(#DESCRIPTIF**|entites_html)]</textarea>
+<label>Chapo</label><textarea class="crayon-active" name="#ENV{name_chapo}"
+	style="width:100%;
+	height:#ENV{h_chapo}px;">[(#CHAPO**|entites_html)]</textarea>
+<label>Texte</label><textarea class="crayon-active" name="#ENV{name_texte}"
+	style="width:100%;
+	height:#ENV{h_texte}px;">[(#TEXTE**|entites_html)]</textarea>
+</fieldset>
+</BOUCLE_a>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/article_introduction.php
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/article_introduction.php	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/article_introduction.php	(revision 69)
@@ -0,0 +1,25 @@
+<?php
+// un controleur php + html
+// html == avec un modele, controleurs/article_introduction.html)
+function controleurs_article_introduction_dist($regs) {
+    list(,$crayon,$type,$champ,$id) = $regs;
+    $valeur = valeur_colonne_table($type, array('descriptif', 'chapo', 'texte'), $id);
+    if ($valeur === false) {
+	    return array("$type $id $champ: " . _U('crayons:pas_de_valeur'), 6);
+    }
+
+	$n = new Crayon('article-introduction-' . $id, $valeur,
+			array('hauteurMini' => 234,
+				  'controleur' => 'controleurs/article_introduction'));
+    
+    $contexte = array(
+    	'h_descriptif' => (int)ceil($n->hauteur*2/13),
+		'h_chapo' => (int)ceil($n->hauteur*4/13),
+		'h_texte' => (int)ceil($n->hauteur*4/13));
+    $html = $n->formulaire($contexte);
+    $status = NULL;
+
+	return array($html, $status);
+}
+
+?>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/auteur_email.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/auteur_email.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/auteur_email.html	(revision 69)
@@ -0,0 +1,11 @@
+[(#REM)
+
+	Controleur pour le crayon 'auteur_email', uniquement html
+
+]
+#CACHE{0}
+
+<BOUCLE_a(AUTEURS){id_auteur}{tous}>
+<input class="crayon-active" name="#ENV{name_email}"
+ style="width:#ENV{largeur}px;#ENV{style}" value="[(#EMAIL**|entites_html)]" />
+</BOUCLE_a>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/auteur_nom.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/auteur_nom.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/auteur_nom.html	(revision 69)
@@ -0,0 +1,11 @@
+[(#REM)
+
+	Controleur pour le crayon 'auteur_nom', uniquement html
+
+]
+#CACHE{0}
+
+<BOUCLE_a(AUTEURS){id_auteur}{tous}>
+<input class="crayon-active" name="#ENV{name_nom}"
+ style="width:#ENV{largeur}px;#ENV{style}" value="[(#NOM**|entites_html)]" />
+</BOUCLE_a>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/auteur_qui.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/auteur_qui.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/auteur_qui.html	(revision 69)
@@ -0,0 +1,16 @@
+[(#REM)
+
+	Controleur pour le crayon 'auteur_qui', uniquement html
+
+]
+#CACHE{0}
+
+<BOUCLE_a(AUTEURS){id_auteur}{tous}>
+<fieldset style="#ENV{style}"><legend>Qui ?</legend>
+<label><:ecrire:info_nom:> :</label> <input class="crayon-active" name="#ENV{name_nom}"
+ style="width:#ENV{largeur}px;" value="[(#NOM**|entites_html)]" />
+ <br />
+ <label><:ecrire:email_2:></label> <input class="crayon-active" name="#ENV{name_email}"
+ style="width:#ENV{largeur}px;" value="[(#EMAIL**|entites_html)]" />
+</fieldset>
+</BOUCLE_a>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/document_fichier.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/document_fichier.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/document_fichier.html	(revision 69)
@@ -0,0 +1,46 @@
+[(#REM)
+
+	Controleur pour le crayon 'document'
+
+]
+#CACHE{0}
+
+
+[(#REM) En fonction de du contexte, choisir un logo ]
+
+<BOUCLE_d(DOCUMENTS){id_document}>
+
+[(#SET{image,[<img src="(#LOGO_DOCUMENT||extraire_attribut{src})" />]})]
+
+<fieldset[ style="(#ENV{style});"]>
+<legend>
+	[(#SET{logo,[(#GET{image}|reduire_image{0,40})]})]
+
+	[(#REM) hacke l'url pour eviter le cache du navigateur ]
+	[(#SET{url,[(#GET{logo}|extraire_attribut{src}
+		|parametre_url{s,#EVAL{time()}})]})]
+
+	<small>[(#GET{logo}|inserer_attribut{src,#GET{url}}
+		|inserer_attribut{style,vertical-align:middle;})]
+	&nbsp; remplacer ce document #EXTENSION
+	[((#GET{image}
+		|extraire_attribut{width})&times;[(#GET{image}
+		|extraire_attribut{height})]px)]&nbsp;:</small>
+
+</legend>
+
+<input class="crayon-active" name="#ENV{name_document}" type="file"[
+onchange="(#MODE|=={vignette}|?{' '})if (this.value && !this.value.match(/\.(png|jpeg|jpg|gif)$/i)) { alert('Il faut choisir une image :-)'); this.value=''; };"] />
+
+[(#REM)
+
+TODO: document distant (cf. document_fichier_revision dans inc/crayons)
+
+<br />
+<input class="crayon-active" name="#ENV{name_fichier}" type="text" value="#FICHIER" />
+
+]
+
+</fieldset>
+
+</BOUCLE_d>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/forum_email.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/forum_email.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/forum_email.html	(revision 69)
@@ -0,0 +1,11 @@
+[(#REM)
+
+	Controleur pour le crayon 'forum-email', uniquement html
+
+]
+#CACHE{0}
+
+<BOUCLE_b(FORUMS){id_forum}{statut==.}>
+<input class="crayon-active" name="#ENV{name_email_auteur}"
+ style="width:#ENV{largeur}px;#ENV{style}" value="[(#EMAIL**|entites_html)]" />
+</BOUCLE_b>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/forum_id_article.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/forum_id_article.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/forum_id_article.html	(revision 69)
@@ -0,0 +1,33 @@
+[(#REM)
+
+	Controleur pour le crayon 'forum-id_article'
+
+	NB: il est possible d'ajouter ci-dessous un second menu
+	qui permettrait de passer d'un article a une breve ; la seule
+	difficulte etant de savoir remettre a zero la valeur id_article
+	si une breve a ete selectionnee (et vice-versa)
+
+	Tant qu'on en  pas besoin, hein, on va laisser comme a.
+
+]
+#CACHE{0}
+
+<BOUCLE_f(FORUMS){id_forum}{statut==.}{id_parent=0}{id_article>0}>
+
+<B_a>
+<fieldset style="#ENV{style}">
+Associer ce forum &#224; l'article :
+<select class="crayon-active" name="#ENV{name_id_article}"
+	onchange="cQuery(this).parents('form.formulaire_spip').submit();">
+<option value="0"[
+	(#ID_ARTICLE|?{'',selected})]>&gt;&gt;</option>
+<BOUCLE_a(ARTICLES)>
+<option value="#ID_ARTICLE"[
+	(#ID_ARTICLE|=={#_f:ID_ARTICLE}|?{selected})]>#ID_ARTICLE - [(#TITRE|couper{30})]</option>
+</BOUCLE_a>
+</select>
+</fieldset>
+</B_a>
+
+
+</BOUCLE_f>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/forum_nom.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/forum_nom.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/forum_nom.html	(revision 69)
@@ -0,0 +1,11 @@
+[(#REM)
+
+	Controleur pour le crayon 'forum-nom', uniquement html
+
+]
+#CACHE{0}
+
+<BOUCLE_b(FORUMS){id_forum}{statut==.}>
+<input class="crayon-active" name="#ENV{name_auteur}"
+ style="width:#ENV{largeur}px;#ENV{style}" value="[(#NOM**|entites_html)]" />
+</BOUCLE_b>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/forum_qui.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/forum_qui.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/forum_qui.html	(revision 69)
@@ -0,0 +1,15 @@
+[(#REM)
+
+	Controleur pour le crayon 'forum_qui', uniquement html
+
+]
+#CACHE{0}
+
+<BOUCLE_d(FORUMS){id_forum}{statut==.}>
+<fieldset style="#ENV{style};text-align:right;"><legend>Qui ?</legend>
+<label><:ecrire:info_nom:> : <input class="crayon-active" name="#ENV{name_auteur}"
+ style="width:#ENV{largeur}px;" value="[(#NOM**|entites_html)]" /></label><br />
+ <label><:ecrire:email_2:> <input class="crayon-active" name="#ENV{name_email_auteur}"
+ style="width:#ENV{largeur}px;" value="[(#EMAIL**|entites_html)]" /></label>
+</fieldset>
+</BOUCLE_d>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/hyperlien.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/hyperlien.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/hyperlien.html	(revision 69)
@@ -0,0 +1,64 @@
+[(#REM)
+
+	Controleur pour le crayon 'hyperlien', uniquement html
+
+	(attention pour les syndic_articles, les champs ne sont
+	pas les memes, il faut donc un autre controleur)
+
+]
+#CACHE{0}
+
+<BOUCLE_a(AUTEURS){id_auteur}{tous}>
+<fieldset style="#ENV{largeur}px;#ENV{style}"><legend>Hyperlien</legend>
+<label><:form_prop_nom_site:></label> <input class="crayon-active" name="#ENV{name_nom_site}"
+ style="width:100%;" value="[(#NOM_SITE**|entites_html)]" />
+ <br />
+ <label><:forum_url:></label> <input class="crayon-active" name="#ENV{name_url_site}"
+ style="width:100%;" value="[(#URL_SITE**|entites_html)]" />
+</fieldset>
+</BOUCLE_a>
+
+<BOUCLE_b(ARTICLES){id_article}{statut==.}>
+<fieldset style="#ENV{largeur}px;#ENV{style}"><legend>Hyperlien</legend>
+<label><:form_prop_nom_site:></label> <input class="crayon-active" name="#ENV{name_nom_site}"
+ style="width:100%;" value="[(#NOM_SITE**|entites_html)]" />
+ <br />
+ <label><:forum_url:></label> <input class="crayon-active" name="#ENV{name_url_site}"
+ style="width:100%;" value="[(#URL_SITE**|entites_html)]" />
+</fieldset>
+</BOUCLE_b>
+
+<BOUCLE_c(SIGNATURES){id_signature}{statut==.}>
+<fieldset style="#ENV{largeur}px;#ENV{style}"><legend>Hyperlien</legend>
+<label><:form_prop_nom_site:></label> <input class="crayon-active" name="#ENV{name_nom_site}"
+ style="width:100%;" value="[(#NOM_SITE**|entites_html)]" />
+ <br />
+ <label><:forum_url:></label> <input class="crayon-active" name="#ENV{name_url_site}"
+ style="width:100%;" value="[(#URL_SITE**|entites_html)]" />
+</fieldset>
+</BOUCLE_c>
+
+<BOUCLE_d(FORUMS){id_forum}{statut==.}>
+<fieldset style="#ENV{largeur}px;#ENV{style}"><legend>Hyperlien</legend>
+<label><:form_prop_nom_site:></label> <input class="crayon-active" name="#ENV{name_nom_site}"
+ style="width:100%;" value="[(#NOM_SITE**|entites_html)]" />
+ <br />
+ <label><:forum_url:></label> <input class="crayon-active" name="#ENV{name_url_site}"
+ style="width:100%;" value="[(#URL_SITE**|entites_html)]" />
+</fieldset>
+</BOUCLE_d>
+
+<BOUCLE_e(SITES){id_syndic=#ENV{id_site}}{statut==.}>
+<fieldset style="#ENV{largeur}px;#ENV{style}"><legend>Hyperlien</legend>
+<label><:form_prop_nom_site:></label> <input class="crayon-active" name="#ENV{name_nom_site}"
+ style="width:100%;" value="[(#NOM_SITE**|entites_html)]" />
+ <br />
+ <label><:forum_url:></label> <input class="crayon-active" name="#ENV{name_url_site}"
+ style="width:100%;" value="[(#URL_SITE**|entites_html)]" />
+</fieldset>
+</BOUCLE_e>
+
+<//B_d>
+<//B_c>
+<//B_b>
+<//B_a>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/id_parent.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/id_parent.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/id_parent.html	(revision 69)
@@ -0,0 +1,19 @@
+[(#REM)
+
+	Controleur pour le crayon 'id_rubrique', uniquement html
+
+]
+#CACHE{0}
+
+<BOUCLE_a(ARTICLES){id_article}{statut==.}>
+#SET{id,#ID_RUBRIQUE}
+</BOUCLE_a>
+
+
+<select class="crayon-active" name="#ENV{name_id_rubrique}"
+ style="width:#ENV{largeur}px;#ENV{style}" />
+<BOUCLE_r(RUBRIQUES){par num titre, titre}>
+<option value="#ID_RUBRIQUE"[
+	(#GET{id}|=={#ID_RUBRIQUE}|?{selected})]>[(#TITRE|couper{30})]</option>
+</BOUCLE_r>
+</select>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/logo.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/logo.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/logo.html	(revision 69)
@@ -0,0 +1,51 @@
+[(#REM)
+
+	Controleur pour le crayon 'logo'
+
+]
+#CACHE{0}
+
+
+[(#REM) En fonction de du contexte, choisir un logo ]
+
+[(#SET{image,[(#ID_ARTICLE|?{#LOGO_ARTICLE_NORMAL}
+	|sinon{[(#ID_AUTEUR|?{#LOGO_AUTEUR_NORMAL})]}
+	|sinon{[(#ID_BREVE|?{#LOGO_BREVE_NORMAL})]}
+	|sinon{[(#ID_RUBRIQUE|?{#LOGO_RUBRIQUE_NORMAL})]}
+	|sinon{[(#ID_SYNDIC|?{#LOGO_SITE_NORMAL})]}
+	|sinon{[(#ID_MOT|?{#LOGO_MOT_NORMAL})]})]})]
+
+
+<fieldset[ style="(#ENV{style});"]>
+<legend>[(#GET{image}|?{
+
+	[(#SET{logo,[(#GET{image}|reduire_image{0,40})]})]
+
+	[(#REM) hacke l'url pour eviter le cache du navigateur ]
+	[(#SET{url,[(#GET{logo}|extraire_attribut{src}
+		|parametre_url{s,#EVAL{time()}})]})]
+
+	<small>[(#GET{logo}|inserer_attribut{src,#GET{url}}
+		|inserer_attribut{style,vertical-align:middle;})]
+	&nbsp; remplacer ce logo
+	[((#GET{image}
+		|extraire_attribut{width})&times;[(#GET{image}
+		|extraire_attribut{height})]px)]&nbsp;:</small>
+
+	[(#SET{supprimer,
+		[<label><br />
+			<input type="checkbox" name="(#ENV{name_logo})_supprimer" id="#ENV{name_logo}_supprimer" />
+			supprimer ce logo
+			</label>]
+	})]
+}|sinon{
+	Choisir un logo&nbsp;:
+})]
+</legend>
+
+<input class="crayon-active" name="#ENV{name_logo}" type="file"
+onchange="if (this.value && !this.value.match(/\.(png|jpeg|jpg|gif)$/i)) { alert('Il faut choisir une image :-)'); this.value=''; }; var q=cQuery('##ENV{name_logo}_supprimer').parent(); this.value ? q.hide() : q.show();" />
+
+#GET{supprimer}
+
+</fieldset>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/mots_article_brut.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/mots_article_brut.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/mots_article_brut.html	(revision 69)
@@ -0,0 +1,20 @@
+[(#REM)
+
+	Controleur pour le crayon 'mots_article_brut'
+
+]
+#CACHE{0}
+<BOUCLE_a(ARTICLES){id_article}{statut==.}>
+<fieldset style="#ENV{style}"><legend>Mots-clÃ© pour #TITRE (#ID_ARTICLE)</legend>
+<B_moex>
+<label>Existants</label>
+<select class="crayon-active" name="#ENV{name_existants}" style="width:#ENV{largeur}px; height:#ENV{hauteur}px;">
+<BOUCLE_moex(MOTS){id_article}{doublons}>
+	<option value="#ID_GROUPE:#ID_MOT">[(#TITRE|entites_html)]</option>
+</BOUCLE_moex>
+</select>
+</B_moex>
+Pas d'existant
+<//B_moex>
+</fieldset>
+</BOUCLE_a>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/portfolio.php
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/portfolio.php	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/portfolio.php	(revision 69)
@@ -0,0 +1,27 @@
+<?php
+
+// un controleur php (TODO -- NE FONCTIONNE PAS DU TOUT)
+
+function controleurs_portfolio_dist($regs) {
+    list(,$crayon,$type,$champ,$id) = $regs;
+
+	include_spip('inc/minipres'); # pour aide()
+	include_spip('inc/presentation'); # pour debut_cadre()
+	include_spip('inc/layer'); # pour le js des fleches
+	include_spip('inc/documents'); # pour aide()
+
+	$html =
+		http_script("\nvar ajax_image_searching = \n'<div style=\"float: ".$GLOBALS['spip_lang_right'].";\"><img src=\"".url_absolue(_DIR_IMG_PACK."searching.gif")."\" alt=\"\" /></div>';")
+		. http_script('', generer_url_public('jquery.js'))
+		. http_script('', _DIR_JAVASCRIPT . 'layer.js','')
+		. afficher_documents_colonne($id, $type, 'portfolio');
+
+    $status = NULL;
+
+	return array($html, $status);
+}
+
+
+
+
+?>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/signature_email.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/signature_email.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/signature_email.html	(revision 69)
@@ -0,0 +1,11 @@
+[(#REM)
+
+	Controleur pour le crayon 'signature-email', uniquement html
+
+]
+#CACHE{0}
+
+<BOUCLE_c(SIGNATURES){id_signature}{statut==.}>
+<input class="crayon-active" name="#ENV{name_ad_email}"
+ style="width:#ENV{largeur}px;#ENV{style}" value="[(#EMAIL**|entites_html)]" />
+</BOUCLE_c>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/signature_nom.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/signature_nom.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/signature_nom.html	(revision 69)
@@ -0,0 +1,10 @@
+[(#REM)
+
+	Controleur pour le crayon 'signature-nom', uniquement html
+]
+#CACHE{0}
+
+<BOUCLE_c(SIGNATURES){id_signature}{statut==.}>
+<input class="crayon-active" name="#ENV{name_nom_email}"
+ style="width:#ENV{largeur}px;#ENV{style}" value="[(#NOM**|entites_html)]" />
+</BOUCLE_c>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/signature_qui.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/signature_qui.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/signature_qui.html	(revision 69)
@@ -0,0 +1,16 @@
+[(#REM)
+
+	Controleur pour le crayon 'signature_qui', uniquement html
+
+]
+#CACHE{0}
+
+<BOUCLE_c(SIGNATURES){id_signature}{statut==.}>
+<fieldset style="width:#ENV{largeur}px;#ENV{style}"><legend>Qui ?</legend>
+<label><:ecrire:info_nom:> :</label> <input class="crayon-active" name="#ENV{name_nom_email}"
+ style="width:100%;" value="[(#NOM**|entites_html)]" />
+ <br />
+ <label><:ecrire:email_2:></label> <input class="crayon-active" name="#ENV{name_ad_email}"
+ style="width:100%;" value="[(#EMAIL**|entites_html)]" />
+</fieldset>
+</BOUCLE_c>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/statut.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/statut.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/statut.html	(revision 69)
@@ -0,0 +1,26 @@
+
+[(#REM)
+
+	Un crayon pour changer le statut (autrement dit, une gomme)
+	
+	Plutot qu'un menu on peut avoir envie de faire une interface plus jolie
+	mais techniquement Ã§a tourne (version SVN > 1.9.2).
+
+]
+
+<BOUCLE_a(ARTICLES){id_article}{statut==.}>
+#SET{id,#STATUT}
+<select class="crayon-active" name="#ENV{name_statut}"
+ style="width:#ENV{largeur}px;" />
+	<option value="publie"[
+		(#GET{id}|=={publie}?{selected})]>publi&#233;</option>
+	<option value="prop"[
+		(#GET{id}|=={prop}?{selected})]>propos&#233;</option>
+	<option value="prepa"[
+		(#GET{id}|=={prepa}?{selected})]>en pr&#233;paration</option>
+</select>
+
+</BOUCLE_a>
+
+
+
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/syndic_article_hyperlien.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/syndic_article_hyperlien.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/controleurs/syndic_article_hyperlien.html	(revision 69)
@@ -0,0 +1,17 @@
+[(#REM)
+
+	Controleur pour le crayon 'hyperlien' des articles syndiques
+
+]
+#CACHE{0}
+
+<BOUCLE_f(SYNDIC_ARTICLES){id_syndic_article=#ENV{id_syndic_article}}
+	{statut==.}>
+<fieldset style="#ENV{largeur}px;#ENV{style}"><legend>Hyperlien</legend>
+<label><:forum_titre:></label> <input class="crayon-active" name="#ENV{name_titre}"
+ style="width:100%;" value="[(#TITRE**|entites_html)]" />
+ <br />
+ <label><:forum_url:></label> <input class="crayon-active" name="#ENV{name_url}"
+ style="width:100%;" value="[(#URL_ARTICLE**|sinon{http://}|entites_html)]" />
+</fieldset>
+</BOUCLE_f>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/crayons.css
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/crayons.css	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/crayons.css	(revision 69)
@@ -0,0 +1,87 @@
+em.crayon-img-changed {
+	display: none;
+}
+.crayon-changed em.crayon-img-changed {
+	display: block;
+}
+.crayon-changed em.crayon-crayon {
+	display: none;
+}
+em.crayon-edit, em.crayon-crayon {
+	display: none;
+}
+.crayon-boutons, .crayon-searching {
+	float: right;
+}
+.crayon-boutons {
+	width: 40px;
+}
+.crayon-boutons a {
+	cursor: pointer;
+}
+.crayon-icones {
+	float: right;
+	width: 20px;
+	text-align: right;
+	position: relative;
+	top:0; left:0;
+	z-index:4999;
+}
+.crayon-icones span,.crayon-icones em {
+	position:absolute;
+	left:0px;
+}
+em.crayon-edit, em.crayon-crayon, em.crayon-img-changed {
+	cursor: pointer;
+	border:0;
+} 
+
+/* definition des icones */
+.crayon-icones em.crayon-crayon {
+	background: url(images/crayon20.png) no-repeat;
+	height: 20px;
+	width: 20px;
+}
+.crayon-icones em.crayon-searching {
+	background: url(images/searching.gif) no-repeat;
+	height: 13px;
+	width: 13px;
+}
+.crayon-icones em.crayon-edit {
+	background: url(images/edit.png) no-repeat;
+	height: 20px;
+	width: 20px;
+}
+.crayon-icones em.crayon-img-changed {
+	background: url(images/changed.png) no-repeat;
+	height: 20px;
+	width: 20px;
+}
+
+
+.formulaire_crayon .crayon-active {
+	padding: 1px;
+	border: 1px inset;
+	font: inherit;
+}
+.crayon-html .formulaire_crayon {
+	max-width: 703px;
+}
+.crayon-html textarea {
+	overflow: auto;
+	font: inherit;
+}
+.crayon-html {
+	z-index:5000;
+}
+.crayon-html .crayon-boutons {
+	position:absolute;
+	right:0px;
+}
+
+/* resizer */
+.resizehandle {
+	background:transparent url("images/resizer.png") no-repeat scroll 45%;
+	cursor:s-resize;
+	font-size:0.1em;
+}
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/crayons.js.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/crayons.js.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/crayons.js.html	(revision 69)
@@ -0,0 +1,30 @@
+[(#REM)
+
+	Charger la librairie crayons et ses addenda
+
+]
+#HTTP_HEADER{'Content-Type: text/javascript'}
+
+[(#ENV**{debug_crayons}|?{
+	#CACHE{0}
+,
+	#CACHE{7*24*3600,cache-client}
+})]
+
+/* cQuery est jQuery, renommee pour eviter tout conflit */
+
+[(#CHEMIN{js/jquery.js}|pack_cQuery)]
+
+[(#CHEMIN{js/jquery.dimensions.js}|pack_cQuery)]
+
+[(#CHEMIN{js/jquery.form.js}|pack_cQuery)]
+
+[(#CHEMIN{js/jquery.px.js}|pack_cQuery)]
+
+[(#CHEMIN{js/crayons.js}|pack_cQuery)]
+
+[(#CHEMIN{js/resizehandle.js}|pack_cQuery)]
+
+[(#CONFIG{crayons}|match{s:11:"yellow_fade";s:2:"on";}|?{' ',''})
+	[(#CHEMIN{js/crayons-fade.js}|pack_cQuery)]
+]
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/crayons.js_fonctions.php
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/crayons.js_fonctions.php	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/crayons.js_fonctions.php	(revision 69)
@@ -0,0 +1,30 @@
+<?php
+
+//
+// Les crayons ont leur propre copie du pack de Dean Edwards
+// pour compatibilite avec SPIP < [9717]
+//
+
+function pack_cQuery($chemin) {
+	$flux = spip_file_get_contents($chemin);
+	$flux = str_replace('jQuery', 'cQuery', $flux);
+	$flux = str_replace('var $ = cQuery','// dont set dollar', $flux);
+	$flux = str_replace('window.$ = cQuery','// dont set dollar', $flux);
+
+	if (!strlen($flux)
+	OR _request('debug_crayons'))
+		return $flux;
+
+	include_spip('inc/class.JavaScriptPacker');
+	$packer = new JavaScriptPacker($flux, 0, true, false);
+
+	// en cas d'echec (?) renvoyer l'original
+	if (strlen($t = $packer->pack()))
+		return $t;
+
+	// erreur
+	spip_log('erreur de pack_js');
+	return $flux;
+}
+
+?>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/fonds/cfg_crayons.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/fonds/cfg_crayons.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/fonds/cfg_crayons.html	(revision 69)
@@ -0,0 +1,68 @@
+[(#REM) descriptif=
+<h4>
+Ici vous pouvez configurer le plugin Crayons.
+</h4>
+<a href="http://www.spip-contrib.net/Les-Crayons" class="spip_out">Documentation sur contrib</a>
+]
+
+[(#REM) titre=Les Crayons]
+[(#REM) icone=images/crayon20.png]
+
+#CACHE{0}
+
+<form method="post" action="#SELF">[<div>(#ENV{_cfg_}|form_hidden)</div>]
+
+<fieldset><legend>Barre typographique</legend>
+<p>
+<label><input type="checkbox" name="barretypo"[ checked="(#ENV{barretypo})"] class="fondl" />
+Activer la barre typo dans les crayons.</label>
+</p>
+</fieldset>
+
+
+<fieldset><legend><:crayons:messages:></legend>
+<p>
+<label><input type="checkbox" name="msgNoChange" [checked="(#ENV{msgNoChange})"] class="fondl" />
+Si pas de changement</label>
+<br />
+<label><input type="checkbox" name="msgAbandon" [checked="(#ENV{msgAbandon})"] class="fondl" />
+Sur abandon</label>
+</p>
+</fieldset>
+
+<fieldset><legend><:crayons:effets:></legend>
+<p>
+<label><input type="checkbox" name="filet" [checked="(#ENV{filet})"] class="fondl" /> Filet</label>
+<br />
+<label><input type="checkbox" name="yellow_fade" [checked="(#ENV{yellow_fade})"] class="fondl" />
+Fade</label>
+
+<br />
+<label>
+<input type="checkbox" name="clickhide" [checked="(#ENV{clickhide}|?{checked})"] class="fondl" />
+Un clic en-dehors masque les crayons
+</label>
+</p>
+</fieldset>
+
+<fieldset><legend>Pour le crayon &#171;&nbsp;logo&nbsp;&#187;</legend>
+<label>R&#233;duire le logo s'il d&#233;passe la taille maximale de&nbsp;:</label>  <label><input type="text" name="reduire_logo" value="[(#ENV{reduire_logo}|intval)]" size="3" class="fondl" />&nbsp;pixels (0 = jamais).</label>
+</fieldset>
+
+<fieldset><legend>(Experimental) Editer l'espace privÃ©</legend>
+<p>
+<label><input type="checkbox" name="espaceprive"[ checked="(#ENV{espaceprive})"] class="fondl" />
+Activer crayons pour la partie privÃ©e.</label>
+<br/>
+<label>Pages autorisÃ©es Ã  la modification (?exec=...) : </label>  <label><input type="text" name="exec_autorise" value="[(#ENV{exec_autorise})]" size="10" class="fondl" />&nbsp;(, pour sÃ©parer les exec).</label>
+</p>
+</fieldset>
+
+
+<div>
+	<input type="submit" name="_cfg_ok" value="<:oK:>" class="fondo" />
+	<input type="reset" value="<:reset:>" class="fondo" />
+	<input type="submit" name="_cfg_delete" value="<:supprimer:>" class="fondo" />
+</div>
+</form>
+
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/inc/class.JavaScriptPacker.php
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/inc/class.JavaScriptPacker.php	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/inc/class.JavaScriptPacker.php	(revision 69)
@@ -0,0 +1,775 @@
+<?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
+	);
+	
+// http://doc.spip.org/@JavaScriptPacker
+	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;
+	}
+	
+// http://doc.spip.org/@pack
+	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
+// http://doc.spip.org/@_pack
+	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();
+// http://doc.spip.org/@_addParser
+	function _addParser($parser) {
+		$this->_parsers[] = $parser;
+	}
+	
+	// zero encoding - just removal of white space and comments
+// http://doc.spip.org/@_basicCompression
+	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+/', '');
+# Modif fil@rezo.net pour conserver les \n
+		$parser->add('/(\\b|\\x24)[\\t ]+(\\b|\\x24)/', '$2 $3');
+		$parser->add('/([+\\-])[\\t ]+([+\\-])/', '$2 $3');
+		$parser->add('/[\\t ]+/', '');
+		$parser->add('/\\s+/', "\n");
+		// done
+		return $parser->exec($script);
+	}
+	
+// http://doc.spip.org/@_encodeSpecialChars
+	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);
+	}
+	
+// http://doc.spip.org/@_encodeKeywords
+	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);
+		}
+	}
+	
+// http://doc.spip.org/@_analyze
+	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();
+// http://doc.spip.org/@_sortWords
+	function _sortWords($match1, $match2) {
+		return $this->_count[$match2] - $this->_count[$match1];
+	}
+	
+	// build the boot function used for loading and decoding
+// http://doc.spip.org/@_bootStrap
+	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;
+// http://doc.spip.org/@_insertFastDecode
+	function _insertFastDecode($match) {
+		return '{' . $this->buffer . ';';
+	}
+// http://doc.spip.org/@_insertFastEncode
+	function _insertFastEncode($match) {
+		return '{$encode=' . $this->buffer . ';';
+	}
+	
+	// mmm.. ..which one do i need ??
+// http://doc.spip.org/@_getEncoder
+	function _getEncoder($ascii) {
+		return $ascii > 10 ? $ascii > 36 ? $ascii > 62 ?
+		       '_encode95' : '_encode62' : '_encode36' : '_encode10';
+	}
+	
+	// zero encoding
+	// characters: 0123456789
+// http://doc.spip.org/@_encode10
+	function _encode10($charCode) {
+		return $charCode;
+	}
+	
+	// inherent base36 support
+	// characters: 0123456789abcdefghijklmnopqrstuvwxyz
+// http://doc.spip.org/@_encode36
+	function _encode36($charCode) {
+		return base_convert($charCode, 10, 36);
+	}
+	
+	// hitch a ride on base36 and add the upper case alpha characters
+	// characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
+// http://doc.spip.org/@_encode62
+	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: Â¡Â¢Â£Â€Â¥ÂŠÂ§ÂšÂ©ÂªÂ«Â¬Â­Â®Â¯Â°Â±Â²Â³ÂŽÂµÂ¶Â·ÂžÂ¹ÂºÂ»ÂŒÂœÂŸÂ¿ÃÃ?ÃÃÃÃ
+ÃÃÃÃÃÃÃÃ?ÃÃ?Ã?ÃÃÃÃÃÃÃÃÃÃÃÃÃ?ÃÃÃ Ã¡Ã¢Ã£Ã€Ã¥ÃŠÃ§ÃšÃ©ÃªÃ«Ã¬Ã­Ã®Ã¯Ã°Ã±Ã²Ã³ÃŽÃµÃ¶Ã·ÃžÃ¹ÃºÃ»ÃŒÃœÃŸ
+// http://doc.spip.org/@_encode95
+	function _encode95($charCode) {
+		$res = '';
+		if ($charCode >= $this->_encoding)
+			$res = $this->_encode95($charCode / $this->_encoding);
+		
+		return $res . chr(($charCode % $this->_encoding) + 161);
+	}
+	
+// http://doc.spip.org/@_safeRegExp
+	function _safeRegExp($string) {
+		return '/'.preg_replace('/\$/', '\\\$', $string).'/';
+	}
+	
+// http://doc.spip.org/@_encodePrivate
+	function _encodePrivate($charCode) {
+		return "_" . $charCode;
+	}
+	
+	// protect characters used by the parser
+// http://doc.spip.org/@_escape
+	function _escape($script) {
+		return preg_replace('/([\\\\\'])/', '\\\$1', $script);
+	}
+	
+	// protect high-ascii characters already in the script
+// http://doc.spip.org/@_escape95
+	function _escape95($script) {
+		return preg_replace_callback(
+			'/[\\xa1-\\xff]/',
+			array(&$this, '_escape95Bis'),
+			$script
+		);
+	}
+// http://doc.spip.org/@_escape95Bis
+	function _escape95Bis($match) {
+		return '\x'.((string)dechex(ord($match)));
+	}
+	
+	
+// http://doc.spip.org/@_getJSFunction
+	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
+	
+// http://doc.spip.org/@add
+	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);
+	}
+	
+// http://doc.spip.org/@exec
+	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);
+	}
+		
+// http://doc.spip.org/@reset
+	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
+// http://doc.spip.org/@_add
+	function _add() {
+		$arguments = func_get_args();
+		$this->_patterns[] = $arguments;
+	}
+	
+	// this is the global replace function (it's quite complicated)
+// http://doc.spip.org/@_replacement
+	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];
+			}
+		}
+	}
+	
+// http://doc.spip.org/@_backReferences
+	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;
+	}
+	
+// http://doc.spip.org/@_replace_name
+	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];
+	}
+	
+// http://doc.spip.org/@_replace_encoded
+	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
+// http://doc.spip.org/@_escape
+	function _escape($string, $escapeChar) {
+		if ($escapeChar) {
+			$this->buffer = $escapeChar;
+			return preg_replace_callback(
+				'/\\' . $escapeChar . '(.)' .'/',
+				array(&$this, '_escapeBis'),
+				$string
+			);
+			
+		} else {
+			return $string;
+		}
+	}
+// http://doc.spip.org/@_escapeBis
+	function _escapeBis($match) {
+		$this->_escaped[] = $match[1];
+		return $this->buffer;
+	}
+	
+	// decode escaped characters
+// http://doc.spip.org/@_unescape
+	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;
+		}
+	}
+// http://doc.spip.org/@_unescapeBis
+	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;
+	}
+	
+// http://doc.spip.org/@_internalEscape
+	function _internalEscape($string) {
+		return preg_replace($this->ESCAPE, '', $string);
+	}
+}
+?>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/inc/compat_crayons.php
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/inc/compat_crayons.php	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/inc/compat_crayons.php	(revision 69)
@@ -0,0 +1,78 @@
+<?php
+
+## ceci n'est pas l'original mais la copie pour crayons
+
+// Module de compatibilite pour les plugins qui veulent passer en 1.9.3
+// tout en restant compatibles avec 1.9.2 voire 1.9.1 : il permet de faire
+// tourner sur ces versions du code prevu pour 1.9.3
+
+// C'est l'inverse de vieilles_defs (lequel vise a permettre a du code
+// fait pour 1.9.2 de tourner sur 1.9.3)
+
+// Ce module doit etre appele par le plugin ("nouveau code") de la
+// maniere suivante :
+/*
+
+// Si SPIP est vieux, charger les fonctions de compat
+if ($GLOBALS['spip_version_code'] < '1.93'
+AND $f = charger_fonction('compat', 'inc'))
+	$f();
+
+qui charge toutes les defs de compat connues
+
+ou plus precis :
+
+// Si SPIP est vieux, charger les fonctions de compat
+if ($GLOBALS['spip_version_code'] < '1.93'
+AND $f = charger_fonction('compat', 'inc'))
+	$f('sql_fetch');
+
+ou encore :
+
+// Si SPIP est vieux, charger les fonctions de compat
+if ($GLOBALS['spip_version_code'] < '1.93'
+AND $f = charger_fonction('compat', 'inc'))
+	$f(array('sql_fetch', '_q'));
+
+
+*/
+
+// En termes de distribution ce fichier PEUT etre recopie dans chaque plugin
+// qui desire en avoir une version autonome (voire forkee), A CONDITION DE
+// RENOMMER le fichier et ses deux fonctions ; c'est un peu lourd a maintenir
+// mais c'est le prix a payer pour l'independance des plugins entre eux :-(
+
+// la version commune a tous est developpee sur
+// svn://zone.spip.org/spip-zone/_dev_/compat/
+
+
+function inc_compat_crayons_dist($quoi = NULL) {
+	if (!function_exists($f = 'compat_crayons_defs')) $f .= '_dist';
+	$defs = $f();
+
+	if (is_string($quoi))
+		$quoi = array($quoi);
+	else if (is_null($quoi))
+		$quoi = array_keys($defs);
+
+	foreach ($quoi as $d) {
+		if (!function_exists($d)
+		AND isset($defs[$d])) {
+			eval ("function $d".$defs[$d]);
+		}
+	}
+}
+
+function compat_crayons_defs_dist() {
+	$defs = array();
+
+	// http://trac.rezo.net/trac/spip/changeset/9919
+	if ($GLOBALS['spip_version_code'] < '1.9259')
+	$defs['sql_fetch'] = '($res, $serveur=\'\') {
+		return spip_fetch_array($res);
+	}';
+
+	return $defs;
+}
+
+?>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/inc/crayons.php
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/inc/crayons.php	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/inc/crayons.php	(revision 69)
@@ -0,0 +1,391 @@
+<?php
+
+if (!defined("_ECRIRE_INC_VERSION")) return;
+
+define('_PREG_CRAYON', ',crayon\b[^<>\'"]+?\b((\w+)-(\w+)-(\d+(?:-\w+)?))\b,');
+
+// Compatibilite pour 1.92 : on a besoin de sql_fetch
+if ($GLOBALS['spip_version_code'] < '1.93' AND $f = charger_fonction('compat_crayons', 'inc'))
+	$f('sql_fetch');
+
+
+// Si un logo est demande, on renvoie la date dudit logo (permettra de gerer
+// un "modifie par ailleurs" si la date a change, rien de plus)
+function valeur_champ_logo($table, $id, $champ) {
+	$chercher_logo = charger_fonction('chercher_logo', 'inc');
+	$on = $chercher_logo($id, id_table_objet($table), 'on');
+	return $on ? filemtime($on[0]) : false;
+}
+
+// Idem : si un doc est demande, on renvoie la date du doc
+function valeur_champ_document($table, $id, $champ) {
+	$s = spip_query("SELECT date FROM spip_documents WHERE id_document="._q($id));
+	if ($t = sql_fetch($s))
+		return $t['date'];
+}
+
+// cette fonction de revision recoit le fichier upload a passer en logo
+// en reference : le nom du widget, pour aller chercher d'autres donnees
+// (ex: supprimer)
+function logo_revision($id, $file, $type, $ref) {
+
+	$chercher_logo = charger_fonction('chercher_logo', 'inc');
+	$_id_objet = id_table_objet($type);
+
+	// Chargement d'un nouveau logo ?
+	if ($file['logo']) {
+		define('FILE_UPLOAD', true); // message pour json_export :(
+
+		// supprimer l'ancien logo
+		$on = $chercher_logo($id, $_id_objet, 'on');
+		if ($on) @unlink($on[0]);
+
+		// ajouter le nouveau
+		include_spip('action/iconifier');
+		action_spip_image_ajouter_dist(
+			type_du_logo($_id_objet).'on'.$id, false, false
+		); // beurk
+	}
+
+	else
+
+	// Suppression du logo ?
+	if ($wid = array_pop($ref)
+	AND $_POST['content_'.$wid.'_logo_supprimer'] == 'on') {
+		if ($on = $chercher_logo($id, $_id_objet, 'on'))
+			@unlink($on[0]);
+	}
+
+
+	// Reduire le logo ?
+	if (is_array($cfg = @unserialize($GLOBALS['meta']['crayons']))
+	AND $max = intval($cfg['reduire_logo'])) {
+		$on = $chercher_logo($id, $_id_objet, 'on');
+		include_spip('inc/filtres');
+		@copy($on[0], $temp = _DIR_VAR.'tmp'.rand(0,999).'.'.$on[3]);
+		$img1 = filtrer('image_reduire', $temp, $max);
+		$img2 = preg_replace(',[?].*,', '', extraire_attribut($img1, 'src'));
+		if (@file_exists($img2)
+		AND $img2 !=  $temp) {
+			@unlink($on[0]);
+			$dest = $on[1].$on[2].'.'
+				.preg_replace(',^.*\.(gif|jpg|png)$,', '\1', $img2);
+			@rename($img2,$dest);
+		}
+		@unlink($temp);
+	}
+
+	return true;
+}
+
+
+// cette fonction de revision recoit le fichier upload a passer en document
+function document_fichier_revision($id, $data, $type, $ref) {
+
+	$s = spip_query("SELECT * FROM spip_documents WHERE id_document="._q($id));
+	if (!$t = sql_fetch($s))
+		return false;
+
+	/*
+	// Envoi d'une URL de document distant ?
+	// TODO: verifier l'extension distante, sinon tout explose
+	if ($data['fichier']
+	AND preg_match(',^(https?|ftp)://.+,', $data['fichier'])) {
+		include_spip('inc/modifier');
+		modifier_contenu('document', $id,
+			array('champs' => array('fichier', 'distant')),
+			array('fichier' => $data['fichier'], 'distant' => 'oui')
+		);
+		return true;
+	}
+	else
+	*/
+
+	// Chargement d'un nouveau doc ?
+	if ($data['document']) {
+
+		$ajouter_documents = charger_fonction('ajouter_documents', 'inc');
+		$arg = $data['document'];
+		check_upload_error($arg['error']);
+		$x = $ajouter_documents($arg['tmp_name'], $arg['name'], 
+			'article', 0, 'document', null, $actifs);
+
+		// $actifs contient l'id_document nouvellement cree
+		// on recopie les donnees interessantes dans l'ancien
+		if ($id_new = array_pop($actifs)
+		AND $s = spip_query("SELECT fichier, taille, largeur, hauteur, extension, distant FROM spip_documents
+			WHERE id_document="._q($id_new))
+		AND $new = sql_fetch($s)) {
+			define('FILE_UPLOAD', true); // message pour json_export :(
+
+			// Une vignette doit rester une image
+			if ($t['mode'] == 'vignette'
+			AND !in_array($new['extension'], array('jpg', 'gif', 'png')))
+				return false;
+
+			// Maintenant on est bon, on recopie les nouvelles donnees
+			// dans l'ancienne ligne spip_documents
+			include_spip('inc/modifier');
+			modifier_contenu('document', $id,
+				array('champs' => array_keys($new)),
+				$new);
+
+			// supprimer l'ancien document (sauf s'il etait distant)
+			if ($t['distant'] != 'oui'
+			AND file_exists(get_spip_doc($t['fichier'])))
+				supprimer_fichier(get_spip_doc($t['fichier']));
+
+			// Effacer la ligne temporaire de spip_document
+			spip_query("DELETE FROM spip_documents WHERE id_document="._q($id_new));
+
+			// oublier id_document temporaire (ca marche chez moi, sinon bof)
+			spip_query("ALTER TABLE spip_documents AUTO_INCREMENT="._q($id_new));
+
+			return true;
+		}
+	}
+
+}
+
+
+function colonne_table($table, $col) {
+	$nom_table = '';
+	if (!(($tabref = &crayons_get_table($table, $nom_table)) && ($brut = $tabref['field'][$col]))) {
+		return false;
+	}
+	$ana = explode(' ', $brut);
+	$sta = 0;
+	$sep = '';
+	$ret = array('brut' => $brut,
+		'type' => '', 'notnull' => false, 'long' => 0, 'def' => '');
+	foreach ($ana as $mot) {
+		switch ($sta) {
+			case 0:	$ret['type'] = ($mot = strtolower($mot));
+			case 1:	if ($mot[strlen($mot) - 1] == ')') {
+					$pos = strpos($mot, '(');
+					$ret['type'] = strtolower(substr($mot, 0, $pos++));
+					$vir = explode(',', substr($mot, $pos, -1));
+					if ($ret['type'] == 'enum') {
+						$ret['enum'] = $vir;
+					} elseif (count($vir) > 1) {
+						$ret['long'] = $vir;
+					} else {
+						$ret['long'] = $vir[0];
+					}
+					$sta = 1;
+					continue;
+				}
+				if (!$sta) {
+					$sta = 1;
+					continue;
+				}
+			case 2: switch (strtolower($mot)) {
+				case 'not':
+					$sta = 3;
+					continue;
+				case 'default':
+					$sta = 4;
+					continue;
+				}
+				continue;
+			case 3: 	$ret['notnull'] = strtolower($mot) == 'null';
+				$sta = 2;
+				continue;
+			case 4:	$df1 = strpos('"\'', $mot[0]) !== false? $mot[0] : '';
+				$sta = 5;
+			case 5:	$ret['def'] .= $sep . $mot;
+				if (!$df1) {
+					$sta = 2;
+					continue;
+				}
+				if ($df1 == $mot[strlen($mot) - 1]) {
+					$ret['def'] = substr($ret['def'], 1, -1);
+					$sta = 2;
+				}
+				$sep = ' ';
+				continue;
+		}
+	}
+	return $ret;
+}
+//	var_dump(colonne_table('forum', 'id_syndic')); die();
+
+function table_where($table, $id)
+{
+	$nom_table = '';
+	if (!(($tabref = &crayons_get_table($table, $nom_table))
+			&& ($tabid = explode(',', $tabref['key']['PRIMARY KEY'])))) {
+		spip_log('crayons: table ' . $table . ' inconnue');
+		return array(false, false);
+	}
+	if (is_scalar($id)) {
+		$id = explode('-', $id);
+	}
+	$where = $and = '';
+	foreach ($id as $idcol => $idval) {
+		$where .= $and . '`' . (is_int($idcol) ? trim($tabid[$idcol]) : $idcol) . '`=' . _q($idval);
+		$and = ' AND ';
+	}
+	return array($nom_table, $where);
+}
+//	var_dump(colonne_table('forum', 'id_syndic')); die();
+
+function valeur_colonne_table_dist($table, $col, $id) {
+	list($nom_table, $where) = table_where($table, $id);
+	if (!$nom_table)
+		return false;
+
+	$r = array();
+
+	// valeurs non SQL
+	foreach ($col as $champ) {
+		if (function_exists($f = 'valeur_champ_'.$table.'_'.$champ) OR function_exists($f = 'valeur_champ_'.$champ)) {
+			$r[$champ] = $f($table, $id, $champ);
+			$col = array_diff($col, array($champ));
+		}
+	}
+
+	// valeurs SQL
+	if (count($col)
+	AND $s = spip_query(
+			'SELECT `' . implode($col, '`, `') .
+			'` FROM ' . $nom_table . ' WHERE ' . $where)
+	AND $t = sql_fetch($s))
+		$r = array_merge($r, $t);
+
+	return $r;
+}
+
+function valeur_colonne_table($table, $col, $id) {
+	if (!is_array($col))
+		$col = array($col);
+
+	if (function_exists($f = $table.'_valeur_colonne_table_dist')
+	OR function_exists($f = $table.'_valeur_colonne_table')
+	OR $f = 'valeur_colonne_table_dist')
+		return $f($table, $col, $id);
+}
+
+/**
+    * Transform a variable into its javascript equivalent (recursive)
+    * @access private
+    * @param mixed the variable
+    * @return string js script | boolean false if error
+    */
+function var2js($var) {
+    $asso = false;
+    switch (true) {
+        case is_null($var) :
+            return 'null';
+        case is_string($var) :
+            return '"' . str_replace('&', '\x26', addcslashes($var, "\"\\\n\r")) . '"';
+        case is_bool($var) :
+            return $var ? 'true' : 'false';
+        case is_scalar($var) :
+            return $var;
+        case is_object( $var) :
+            $var = get_object_vars($var);
+            $asso = true;
+        case is_array($var) :
+            $keys = array_keys($var);
+            $ikey = count($keys);
+            while (!$asso && $ikey--) {
+                $asso = $ikey !== $keys[$ikey];
+            }
+            $sep = '';
+            if ($asso) {
+                $ret = '{';
+                foreach ($var as $key => $elt) {
+                    $ret .= $sep . '"' . $key . '":' . var2js($elt);
+                    $sep = ',';
+                }
+                return $ret ."}\n";
+            } else {
+                $ret = '[';
+                foreach ($var as $elt) {
+                    $ret .= $sep . var2js($elt);
+                    $sep = ',';
+                }
+                return $ret ."]\n";
+            }
+    }
+    return false;
+}
+
+function json_export($var) {
+	$var = var2js($var);
+
+	// flag indiquant qu'on est en iframe et qu'il faut proteger nos
+	// donnees dans un <textarea> ; attention $_FILES a ete vide par array_pop
+	if (defined('FILE_UPLOAD'))
+		return "<textarea>".htmlspecialchars($var)."</textarea>";
+	else
+		return $var;
+}
+
+function return_log($var) {
+	die(json_export(array('$erreur'=> var_export($var,true))));
+}
+
+function _U($texte)
+{
+    include_spip('inc/charsets');
+    return unicode2charset(html2unicode(_T($texte)));
+}
+
+function wdgcfg() {
+	$php = function_exists('crayons_config') ? crayons_config() : array();
+	include_spip('inc/meta');
+	lire_metas();
+	global $meta;
+	$metacrayons = empty($meta['crayons']) ? array() : unserialize($meta['crayons']);
+	$wdgcfg = array();
+	foreach (array(
+		'msgNoChange' => false,
+		'msgAbandon' => false,  /* etait: true */
+		'filet' => false,
+		'yellow_fade' => false,
+		'clickhide' => false /* etait: true */
+	)
+	as $cfgi => $def) {
+		$wdgcfg[$cfgi] = isset($php[$cfgi]) ? $php[$cfgi] :
+			isset($metacrayons[$cfgi]) ? $metacrayons[$cfgi] : $def;
+	}
+	return $wdgcfg;
+}
+
+function &crayons_get_table($table, &$nom_table) {
+	static $catab = array('tables_principales',	'tables_auxiliaires');
+	static $return = array();
+	static $noms = array();
+	if (!isset($return[$table])) {
+		$return[$table] = $noms[$table] = '';
+		include_spip('base/serial');
+		include_spip('base/auxiliaires');
+		include_spip('public/parametrer');
+		$try = array('spip_'.table_objet($table), 'spip_' . $table . 's', $table . 's', 'spip_' . $table, $table);
+		foreach ($catab as $i=>$categ) {
+			// 1ere possibilite : c'est connu de SPIP
+			foreach ($try as $nom) {
+				if (isset($GLOBALS[$categ][$nom])) {
+					$noms[$table] = $nom;
+					$return[$table] = & $GLOBALS[$categ][$nom];
+					break 2;
+				}
+			}
+			// seconde possibilite : regarder directement la base
+			if (function_exists('sql_showtable'))
+			foreach ($try as $nom) {
+				if ($q = sql_showtable($nom)) {
+					$noms[$table] = $nom;
+					$return[$table] = $q;
+				}
+			}
+		}
+	}
+
+	$nom_table = $noms[$table];
+	return $return[$table];
+}
+
+?>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/js/crayons-fade.js
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/js/crayons-fade.js	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/js/crayons-fade.js	(revision 69)
@@ -0,0 +1,101 @@
+
+// Gestion du Yellow Fade (fonctionnalite optionnelle)
+function easeInOut(minValue,maxValue,totalSteps,actualStep,powr) {
+	var delta = maxValue - minValue;
+	var stepp = minValue+(Math.pow(((1 / totalSteps)*actualStep),powr)*delta);
+	return Math.ceil(stepp)
+};
+
+function doBGFade(elem,startRGB,endRGB,finalColor,steps,intervals,powr) {
+	if (elem.bgFadeInt) window.clearInterval(elem.bgFadeInt);
+	var actStep = 0;
+	elem.bgFadeInt = window.setInterval(
+		function() {
+			elem.style.backgroundColor = "rgb("+
+				easeInOut(startRGB[0],endRGB[0],steps,actStep,powr)+","+
+				easeInOut(startRGB[1],endRGB[1],steps,actStep,powr)+","+
+				easeInOut(startRGB[2],endRGB[2],steps,actStep,powr)+")";
+			actStep++;
+			if (actStep > steps) {
+			elem.style.backgroundColor = finalColor;
+			window.clearInterval(elem.bgFadeInt);
+			}
+		}
+		,intervals)
+};
+
+function findPos(obj) {
+	var curleft = curtop = 0;
+	if (obj.offsetParent) {
+		curleft = obj.offsetLeft;
+		curtop = obj.offsetTop;
+		while (obj = obj.offsetParent) {
+			curleft += obj.offsetLeft;
+			curtop += obj.offsetTop;
+		}
+	}
+	return [curleft,curtop];
+};
+
+// demarrage crayons-fade
+jQuery(document).ready(function() {
+	if (configCrayons.cfg.yellow_fade) {
+		// Activer le Yellow Fade pour les elements editables
+		jQuery("div.crayon").hover(function(){doBGFade(this,[255,255,180],[255,255,255],'transparent',40,20,4);}, function(){});
+	}
+
+	if (configCrayons.cfg.filet) {
+		// on rajoute une div supplÃ©mentaire qui se cale avec la div courante 
+		// C'est elle qui va s'afficher lors du hover
+		
+		// esthetique
+		jQuery('.crayon-icones img',this).css({
+			'padding':'2px',
+			'border':'2px solid #999',
+			'border-left':'0',
+			'background-color':'#FFF'
+		});
+			
+		var test=0;
+		
+		jQuery('.crayon-autorise').each(
+			function(){
+				var contenu = jQuery(this).html();
+				jQuery(this)
+					.css('height',this.offsetHeight + 'px')
+					.html('<div></div>');
+				jQuery(this)
+					.find('div')
+						.html(contenu)
+						.css('position','absolute')
+						.css('width',this.offsetWidth + 'px');
+				jQuery(this)
+					.prepend('<div class="survol"></div>')
+					.find('.survol')
+						.css('border','1px solid red')
+						.css('display','none')
+						.css('position','absolute')
+						.css('height',(this.offsetHeight - 2) + 'px')
+						.css('width',(this.offsetWidth - 2) + 'px');
+				if (jQuery.browser.msie) {
+			    jQuery('#survol')
+						.css('width',this.offsetWidth + 'px')
+						.css('height',this.offsetHeight + 'px');
+				}
+			}
+		);
+
+		jQuery('.crayon-autorise').hover(
+			function(){
+				if (jQuery.browser.msie) jQuery(this).addClass('crayon-hover');
+				jQuery('.survol', this).css('display','block');
+			},
+			function(){
+				if (jQuery.browser.msie) jQuery(this).removeClass('crayon-hover');
+				jQuery('.survol', this).css('display','none');
+			}
+		);
+		
+	}
+
+});
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/js/crayons.js
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/js/crayons.js	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/js/crayons.js	(revision 69)
@@ -0,0 +1,455 @@
+(function($){
+/*
+ *  crayons.js (c) Fil, toggg 2006-2007 -- licence GPL
+ */
+
+// le prototype configuration de Crayons
+$.prototype.cfgCrayons = function (options) {
+  this.url_crayons_html = options['dir_racine']+'spip.php?action=crayons_html';
+  this.img = {
+    'searching':{'txt':'En attente du serveur ...'},
+    'edit':{'txt':'Editer'},
+    'img-changed':{'txt':'Deja modifie'}
+  };
+  this.txt = {
+  };
+  for (opt in options) {
+    this[opt] = options[opt];
+  }
+};
+
+$.prototype.cfgCrayons.prototype.mkimg = function(what, extra) {
+  return '<em class="crayon-'+what+'" title="'+ this.img[what].txt + (extra ? extra : '') + '"></em>';
+/*  return '<img class="crayon-' + what +
+    '" src="' + this.imgPath + '/' + this.img[what].file +
+    '" title="' + this.img[what].txt + (extra ? extra : '') + ' /">';
+*/
+};
+
+$.prototype.cfgCrayons.prototype.iconclick = function(c) {
+
+  // le + qui passe en prive pour editer tout si classe type--id
+  var link = c.match(/\b(\w+)--(\d+)\b/);
+  link = link ? 
+    '<a href="ecrire/?exec=' + link[1] + 's_edit&id_' + link[1] + '=' + link[2] +
+    '">' + this.mkimg('edit', ' (' + link[1] + ' ' + link[2] + ')') + '</a>' : '';
+
+  var cray = c.match(/\b\w+-(\w+)-\d+\b/);
+  var boite = !cray ? '' : this.mkimg('crayon', ' (' + cray[1] + ')');
+
+  return "<span class='crayon-icones'><span>" + boite +
+      this.mkimg('img-changed', cray ? ' (' + cray[1] + ')': '') +
+      link +"</span></span>";
+};
+
+function entity2unicode(txt)
+{
+  var reg = txt.split(/&#(\d+);/i);
+  for (var i = 1; i < reg.length; i+=2) {
+    reg[i] = String.fromCharCode(parseInt(reg[i]));
+  }
+  return reg.join('');
+};
+
+function uniAlert(txt)
+{
+  alert(entity2unicode(txt));
+};
+
+function uniConfirm(txt)
+{
+  return confirm(entity2unicode(txt));
+};
+
+// donne le crayon d'un element
+$.fn.crayon = function(){
+  if (this.length)
+    return $(
+      $.map(this, function(a){
+        return '#'+($(a).find('.crayon-icones').attr('rel'));
+      })
+      .join(','));
+  else
+    return $([]);
+};
+
+// ouvre un crayon
+$.fn.opencrayon = function(evt, percent) {
+  if (evt && evt.stopPropagation) {
+    evt.stopPropagation();
+  }
+  return this
+  .each(function(){
+    // verifier que je suis un crayon
+    if (!$(this).is('.crayon'))
+      return;
+
+    // voir si je dispose deja du crayon comme voisin
+    if ($(this).is('.crayon-has')) {
+      $(this)
+      .css('visibility','hidden')
+      .crayon()
+        .show();
+    }
+    // sinon charger le formulaire
+    else {
+      // sauf si je suis deja en train de le charger (lock)
+      if ($(this).find("em.crayon-searching").length) {
+        return;
+      }
+      $(this)
+      .find('>span.crayon-icones span')
+      .append(configCrayons.mkimg('searching')); // icone d'attente
+      var me=this;
+      var params = {
+        'w': $(this).width(),
+        'h': $(this).height(),
+        'wh': window.innerHeight,
+        'em': $(this).px('fontSize'), // eviter un bug MSIE sur fontSize
+        'class': me.className,
+        'color': $(this).css('color'),
+        'font-size': $(this).px('fontSize'),
+        'font-family': $(this).css('fontFamily'),
+        'font-weight': $(this).css('fontWeight'),
+        'line-height': $(this).css('lineHeight'),
+        'background-color': $(this).css('backgroundColor'),
+        'self': configCrayons.self
+      };
+      if (params['background-color'] == 'transparent'
+      || params['background-color'] == 'rgba(0, 0, 0, 0)') {
+        $(me).parents()
+        .each(function(){
+          var bg = $(this).css('backgroundColor');
+          if (bg != 'transparent'
+          && (params['background-color'] == 'transparent'
+          || params['background-color'] == 'rgba(0, 0, 0, 0)'))
+            params['background-color'] = bg;
+        });
+      }
+      $.post(configCrayons.url_crayons_html,
+        params,
+        function (c) {
+          eval('c = '+c); // JSON
+          $(me)
+          .find("em.crayon-searching")
+            .remove();
+          if (c.$erreur) {
+            uniAlert(c.$erreur);
+            return false;
+          }
+          id_crayon++;
+          
+          var position = 'absolute';
+          $(me).parents().each(function(){
+            if($(this).css("position") == "fixed")
+              position = 'fixed';
+          });
+
+          $(me)
+          .css('visibility','hidden')
+          .addClass('crayon-has')
+          .find('>.crayon-icones')
+            .attr('rel','crayon_'+id_crayon);
+          if ($.browser.msie) $(me).css({'zoom':1});
+          var pos = $(me).offset({'scroll':false});
+          $('<div class="crayon-html" id="crayon_'+id_crayon+'"></div>')
+          .css({
+            'position':position,
+            'top':pos['top']-1,
+            'left':pos['left']-1
+          })
+          .appendTo('body')
+          .html(c.$html);
+          $(me)
+          .activatecrayon(percent);
+        }
+      );
+    }
+  });
+};
+
+// annule le crayon ouvert (fonction destructive)
+$.fn.cancelcrayon = function() {
+  this
+    .filter('.crayon-has')
+    .css('visibility','visible')
+    .removeClass('crayon-has')
+    .removeClass('crayon-changed')
+  .crayon()
+    .remove();
+  return this;
+};
+
+// masque le crayon ouvert
+$.fn.hidecrayon = function() {
+  this
+  .filter('.crayon-has')
+  .css('visibility','visible')
+  .crayon()
+    .hide()
+    .removeClass('crayon-hover');
+  return this;
+};
+
+// active un crayon qui vient d'etre charge
+$.fn.activatecrayon = function(percent) {
+  this
+  .crayon()
+  .click(function(e){
+    e.stopPropagation();
+  });
+  this
+  .each(function(){
+    var me = $(this);
+    var crayon = $(this).crayon();
+    crayon
+    .find('form')
+      .append(
+        $('<input type="hidden" name="self" />')
+        .attr('value',configCrayons.self)
+      )
+      .ajaxForm({
+      "dataType":"json",
+      "success": function(d) {
+        me
+        .find("em.crayon-searching")
+          .remove();
+        if (d.$erreur > '') {
+          if (d.$annuler) {
+            if (d.$erreur > ' ') {
+              uniAlert(d.$erreur);
+            }
+            me
+            .cancelcrayon();
+          } else {
+              uniAlert(d.$erreur+'\n'+configCrayons.txt.error);
+              crayon
+              .find('form')
+                .css('opacity', 1.0)
+                .find(".crayon-boutons,.resizehandle")
+                  .show()
+                .end()
+                .find('.crayon-searching')
+                  .remove();
+          }
+          return false;
+        }
+        // Desactive celui pour qui on vient de recevoir les nouvelles donnees
+        $(me)
+        .cancelcrayon();
+        // Insere les donnees dans *tous* les elements ayant le meme code
+        $(
+          '.crayon.crayon-autorise.' +
+            me[0].className.match(/crayon ([^ ]+)/)[1]
+        )
+        .html(
+          d[$('input.crayon-id', crayon).val()]
+        )
+        .iconecrayon();
+      }})
+      .one('submit', function(){
+        crayon
+        .find('form')
+          .css('opacity', 0.5)
+          .after(configCrayons.mkimg('searching')) // icone d'attente
+          .find(".crayon-boutons,.resizehandle")
+            .hide();
+      })
+      // keyup pour les input et textarea ...
+      .keyup(function(e){
+        crayon
+        .find(".crayon-boutons")
+          .show();
+        me
+        .addClass('crayon-changed');
+        e.cancelBubble = true; // ne pas remonter l'evenement vers la page
+      })
+      // ... change pour les select : ici on submit direct, pourquoi pas
+      .change(function(e){
+        crayon
+        .find(".crayon-boutons")
+          .show();
+        me
+        .addClass('crayon-changed');
+        e.cancelBubble = true;
+      })
+      .keypress(function(e){
+        e.cancelBubble = true;
+      })
+      .find(".crayon-active[@type!=file]")
+        .each(function(n){
+          // focus pour commencer a taper son texte directement dans le champ
+          // on essaie de positionner la selection (la saisie) au niveau du clic
+          // ne pas le faire sur un input de [@type=file]
+          if (n==0) {
+            this.focus();
+            // premiere approximation, en fonction de la hauteur du clic
+            var position = parseInt(percent * this.textLength);
+            this.selectionStart=position;
+            this.selectionEnd=position;
+          }
+        })
+        .keydown(function(e){
+          if(!e.charCode && e.keyCode == 119 /* F8, windows */) {
+              crayon
+              .find("form.formulaire_crayon")
+              .submit();        
+          }        
+        })
+        .keypress(function(e){
+          if (e.keyCode == 27) {
+            me
+            .cancelcrayon();
+          }
+          // Clavier pour sauver
+          if (
+          (e.ctrlKey && (
+            /* ctrl-s ou ctrl-maj-S, firefox */
+            ((e.charCode||e.keyCode) == 115) || ((e.charCode||e.keyCode) == 83))
+            /* ctrl-s, safari */
+            || (e.charCode==19 && e.keyCode==19)
+          )
+          ) {
+            crayon
+            .find("form.formulaire_crayon")
+            .submit();
+          }
+          var maxh = this.className.match(/\bmaxheight(\d+)?\b/);
+          if (maxh) {
+            maxh = maxh[1] ? parseInt(maxh[1]) : 200;
+            maxh = this.scrollHeight < maxh ? this.scrollHeight : maxh;
+            if (maxh > this.clientHeight) {
+              $(this).css('height', maxh + 'px');
+            }
+          }
+        })
+      .end()
+      .find(".crayon-submit")
+        .click(function(e){
+          e.stopPropagation();
+          $(this)
+          .parents("form:eq(0)")
+          .submit();
+        })
+      .end()
+      .find(".crayon-cancel")
+        .click(function(e){
+          e.stopPropagation();
+          me
+          .cancelcrayon();
+        })
+      .end()
+      // decaler verticalement si la fenetre d'edition n'est pas visible
+      .each(function(){
+        var offset = $(this).offset({'scroll':false});
+        var hauteur = parseInt($(this).css('height'));
+        var scrolltop = $(window).scrollTop();
+        var h = $(window).height();
+        if (offset['top'] - 5 <= scrolltop)
+          $(window).scrollTop(offset['top'] - 5);
+        else if (offset['top'] + hauteur - h + 20 > scrolltop)
+          $(window).scrollTop(offset['top'] + hauteur - h + 30);
+        // Si c'est textarea, on essaie de caler verticalement son contenu
+        // et on lui ajoute un resizehandle
+        $("textarea", this)
+        .each(function(){
+          if (percent && this.scrollHeight > hauteur) {
+            this.scrollTop = this.scrollHeight * percent - hauteur;
+          }
+        })
+        .resizehandle()
+          // decaler les boutons qui suivent un resizer de 16px vers le haut
+          .next('.resizehandle')
+            .next('.crayon-boutons')
+            .css('margin-top', '-16px');
+      })
+    .end();
+  });
+};
+
+// insere les icones dans l'element
+$.fn.iconecrayon = function(){
+  return this.each(function() {
+    $(this).prepend(configCrayons.iconclick(this.className))
+    .find('.crayon-crayon, .crayon-img-changed') // le crayon a clicker lui-meme et sa memoire
+      .click(function(e){
+        $(this).parents('.crayon:eq(0)').opencrayon(e);
+      });
+    });
+};
+
+// initialise les crayons
+$.fn.initcrayon = function(){
+  this
+  .addClass('crayon-autorise')
+  .dblclick(function(e){
+    $(this).opencrayon(e,
+      // calcul du "percent" du click par rapport a la hauteur totale du div
+      ((e.pageY ? e.pageY : e.clientY) - document.body.scrollTop - this.offsetTop)
+      / this.clientHeight);
+  })
+  .iconecrayon();
+
+  // :hover pour MSIE
+  this.hover(
+    function(){
+      $(this)
+      .addClass('crayon-hover')
+      .find('>span.crayon-icones')
+        .find('>span>em.crayon-crayon,>span>em.crayon-edit')
+          .show();//'visibility','visible');
+    },function(){
+      $(this)
+      .removeClass('crayon-hover')
+      .find('>span.crayon-icones')
+        .find('>span>em.crayon-crayon,>span>em.crayon-edit')
+          .hide();//('visibility','hidden');
+    }
+  );
+
+  return this;
+};
+
+/* une fonction pour initialiser les crayons dynamiquement */
+$.fn.initcrayons = function(){
+  this
+  .find('.crayon')
+  .not('.crayon-autorise')
+  .filter(configCrayons.droits)
+  .initcrayon();
+};
+
+// demarrage
+$.fn.crayonsstart = function() {
+  if (!configCrayons.droits) return;
+  id_crayon = 0; // global
+
+  // sortie, demander pour sauvegarde si oubli
+  if (configCrayons.txt.sauvegarder) {
+    $(window).unload(function(e) {
+      var chg = $(".crayon-changed");
+      if (chg.length && uniConfirm(configCrayons.txt.sauvegarder)) {
+        chg.crayon().find('form').submit();
+      }
+    });
+  }
+
+  // on limite l'init auto aux 1000 premiers crayons
+  // setTimeout sert a passer en execution asynchrone pour confort d'affichage
+  if ((typeof crayons_init_dynamique == 'undefined') || (crayons_init_dynamique==false))
+    setTimeout(function(){
+      $(".crayon:lt(1000)")
+      .filter(configCrayons.droits)
+      .initcrayon();
+    }, 300);
+
+  // un clic en dehors ferme tous les crayons ouverts ?
+  if (configCrayons.cfg.clickhide)
+  $("html")
+  .click(function(){
+    $('.crayon-has')
+    .hidecrayon();
+  });
+};
+
+})(jQuery);
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/js/jquery.dimensions.js
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/js/jquery.dimensions.js	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/js/jquery.dimensions.js	(revision 69)
@@ -0,0 +1,119 @@
+/* Copyright (c) 2007 Paul Bakaus (paul.bakaus@googlemail.com) and Brandon Aaron (brandon.aaron@gmail.com || http://brandonaaron.net)
+ * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
+ * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
+ *
+ * $LastChangedDate: 2007-11-14 21:20:00 +0100 (Wed, 14 Nov 2007) $
+ * $Rev: 3824 $
+ *
+ * Version: @VERSION
+ *
+ * Requires: jQuery 1.2+
+ */
+
+(function($){
+	
+$.dimensions = {
+	version: '@VERSION'
+};
+
+// Create innerHeight, innerWidth, outerHeight and outerWidth methods
+$.each( [ 'Height', 'Width' ], function(i, name){
+	
+	// innerHeight and innerWidth
+	$.fn[ 'inner' + name ] = function() {
+		if (!this[0]) return;
+		
+		var torl = name == 'Height' ? 'Top'    : 'Left',  // top or left
+		    borr = name == 'Height' ? 'Bottom' : 'Right'; // bottom or right
+		
+		return this.is(':visible') ? this[0]['client' + name] : num( this, name.toLowerCase() ) + num(this, 'padding' + torl) + num(this, 'padding' + borr);
+	};
+	
+	// outerHeight and outerWidth
+	$.fn[ 'outer' + name ] = function(options) {
+		if (!this[0]) return;
+		
+		var torl = name == 'Height' ? 'Top'    : 'Left',  // top or left
+		    borr = name == 'Height' ? 'Bottom' : 'Right'; // bottom or right
+		
+		options = $.extend({ margin: false }, options || {});
+		
+		var val = this.is(':visible') ? 
+				this[0]['offset' + name] : 
+				num( this, name.toLowerCase() )
+					+ num(this, 'border' + torl + 'Width') + num(this, 'border' + borr + 'Width')
+					+ num(this, 'padding' + torl) + num(this, 'padding' + borr);
+		
+		return val + (options.margin ? (num(this, 'margin' + torl) + num(this, 'margin' + borr)) : 0);
+	};
+});
+
+// Create scrollLeft and scrollTop methods
+$.each( ['Left', 'Top'], function(i, name) {
+	$.fn[ 'scroll' + name ] = function(val) {
+		if (!this[0]) return;
+		
+		return val != undefined ?
+		
+			// Set the scroll offset
+			this.each(function() {
+				this == window || this == document ?
+					window.scrollTo( 
+						name == 'Left' ? val : $(window)[ 'scrollLeft' ](),
+						name == 'Top'  ? val : $(window)[ 'scrollTop'  ]()
+					) :
+					this[ 'scroll' + name ] = val;
+			}) :
+			
+			// Return the scroll offset
+			this[0] == window || this[0] == document ?
+				self[ (name == 'Left' ? 'pageXOffset' : 'pageYOffset') ] ||
+					$.boxModel && document.documentElement[ 'scroll' + name ] ||
+					document.body[ 'scroll' + name ] :
+				this[0][ 'scroll' + name ];
+	};
+});
+
+$.fn.extend({
+	position: function() {
+		var left = 0, top = 0, elem = this[0], offset, parentOffset, offsetParent, results;
+		
+		if (elem) {
+			// Get *real* offsetParent
+			offsetParent = this.offsetParent();
+			
+			// Get correct offsets
+			offset       = this.offset();
+			parentOffset = offsetParent.offset();
+			
+			// Subtract element margins
+			offset.top  -= num(elem, 'marginTop');
+			offset.left -= num(elem, 'marginLeft');
+			
+			// Add offsetParent borders
+			parentOffset.top  += num(offsetParent, 'borderTopWidth');
+			parentOffset.left += num(offsetParent, 'borderLeftWidth');
+			
+			// Subtract the two offsets
+			results = {
+				top:  offset.top  - parentOffset.top,
+				left: offset.left - parentOffset.left
+			};
+		}
+		
+		return results;
+	},
+	
+	offsetParent: function() {
+		var offsetParent = this[0].offsetParent;
+		while ( offsetParent && (!/^body|html$/i.test(offsetParent.tagName) && $.css(offsetParent, 'position') == 'static') )
+			offsetParent = offsetParent.offsetParent;
+		return $(offsetParent);
+	}
+});
+
+function num(el, prop) {
+	return parseInt($.css(el.jquery?el[0]:el,prop))||0;
+};
+
+})(jQuery);
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/js/jquery.form.js
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/js/jquery.form.js	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/js/jquery.form.js	(revision 69)
@@ -0,0 +1,873 @@
+/*
+ * jQuery Form Plugin
+ * version: 2.01 (10/31/2007)
+ * @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: jquery.form.js 3767 2007-10-31 11:23:47Z malsup $
+ */
+ (function($) {
+/**
+ * ajaxSubmit() provides a mechanism for submitting an HTML form using AJAX.
+ *
+ * ajaxSubmit accepts a single argument which can be either a success callback function
+ * or an options Object.  If a function is provided it will be invoked upon successful
+ * completion of the submit and will be passed the response from the server.
+ * If an options Object is provided, the following attributes are supported:
+ *
+ *  target:   Identifies the element(s) in the page to be updated with the server response.
+ *            This value may be specified as a jQuery selection string, a jQuery object,
+ *            or a DOM element.
+ *            default value: null
+ *
+ *  url:      URL to which the form data will be submitted.
+ *            default value: value of form's 'action' attribute
+ *
+ *  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)
+ *
+ *  data:     Additional data to add to the request, specified as key/value pairs (see $.ajax).
+ *
+ *  beforeSubmit:  Callback method to be invoked before the form is submitted.
+ *            default value: null
+ *
+ *  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
+ *
+ *  dataType: Expected dataType of the response.  One of: null, 'xml', 'script', or 'json'
+ *            default value: null
+ *
+ *  semantic: Boolean flag indicating whether data must be submitted in semantic order (slower).
+ *            default value: false
+ *
+ *  resetForm: Boolean flag indicating whether the form should be reset if the submit is successful
+ *
+ *  clearForm: Boolean flag indicating whether the form should be cleared if the submit is successful
+ *
+ *
+ * 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.
+ * The form data array takes the following form:
+ *
+ *     [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
+ *
+ * If a 'success' callback method is provided it is invoked after the response has been returned
+ * from the server.  It is passed the responseText or responseXML value (depending on dataType).
+ * See jQuery.ajax for further details.
+ *
+ *
+ * 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 'success'
+ *                   callback method, if specified, will be passed the responseXML value
+ *      'json':   if dataType == 'json' the server response will be evaluted and passed to
+ *                   the 'success' callback, if specified
+ *      'script': if dataType == 'script' the server response is evaluated in the global context
+ *
+ *
+ * Note that it does not make sense to use both the 'target' and 'dataType' options.  If both
+ * are provided the target will be ignored.
+ *
+ * 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
+ * (or nothing) to avoid the overhead for this logic.
+ *
+ *
+ * When used on its own, ajaxSubmit() is typically bound to a form's submit event like this:
+ *
+ * $("#form-id").submit(function() {
+ *     $(this).ajaxSubmit(options);
+ *     return false; // cancel conventional submit
+ * });
+ *
+ * When using ajaxForm(), however, this is done for you.
+ *
+ * @example
+ * $('#myForm').ajaxSubmit(function(data) {
+ *     alert('Form submit succeeded! Server returned: ' + data);
+ * });
+ * @desc Submit form and alert server response
+ *
+ *
+ * @example
+ * var options = {
+ *     target: '#myTargetDiv'
+ * };
+ * $('#myForm').ajaxSubmit(options);
+ * @desc Submit form and update page element with server response
+ *
+ *
+ * @example
+ * var options = {
+ *     success: function(responseText) {
+ *         alert(responseText);
+ *     }
+ * };
+ * $('#myForm').ajaxSubmit(options);
+ * @desc Submit form and alert the server response
+ *
+ *
+ * @example
+ * var options = {
+ *     beforeSubmit: function(formArray, jqForm) {
+ *         if (formArray.length == 0) {
+ *             alert('Please enter data.');
+ *             return false;
+ *         }
+ *     }
+ * };
+ * $('#myForm').ajaxSubmit(options);
+ * @desc Pre-submit validation which aborts the submit operation if form data is empty
+ *
+ *
+ * @example
+ * var options = {
+ *     url: myJsonUrl.php,
+ *     dataType: 'json',
+ *     success: function(data) {
+ *        // 'data' is an object representing the the evaluated json data
+ *     }
+ * };
+ * $('#myForm').ajaxSubmit(options);
+ * @desc json data returned and evaluated
+ *
+ *
+ * @example
+ * var options = {
+ *     url: myXmlUrl.php,
+ *     dataType: 'xml',
+ *     success: function(responseXML) {
+ *        // responseXML is XML document object
+ *        var data = $('myElement', responseXML).text();
+ *     }
+ * };
+ * $('#myForm').ajaxSubmit(options);
+ * @desc XML data returned from server
+ *
+ *
+ * @example
+ * var options = {
+ *     resetForm: true
+ * };
+ * $('#myForm').ajaxSubmit(options);
+ * @desc submit form and reset it if successful
+ *
+ * @example
+ * $('#myForm).submit(function() {
+ *    $(this).ajaxSubmit();
+ *    return false;
+ * });
+ * @desc Bind form's submit event to use ajaxSubmit
+ *
+ *
+ * @name ajaxSubmit
+ * @type jQuery
+ * @param options  object literal containing options which control the form submission process
+ * @cat Plugins/Form
+ * @return jQuery
+ */
+$.fn.ajaxSubmit = function(options) {
+    if (typeof options == 'function')
+        options = { success: options };
+
+    options = $.extend({
+        url:  this.attr('action') || window.location.toString(),
+        type: this.attr('method') || 'GET'
+    }, options || {});
+
+    // hook for manipulating the form data before it is extracted;
+    // convenient for use with rich editors like tinyMCE or FCKEditor
+    var veto = {};
+    $.event.trigger('form.pre.serialize', [this, options, veto]);
+    if (veto.veto) return this;
+
+    var a = this.formToArray(options.semantic);
+	if (options.data) {
+	    for (var n in options.data)
+	        a.push( { name: n, value: options.data[n] } );
+	}
+
+    // give pre-submit callback an opportunity to abort the submit
+    if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) return this;
+
+    // fire vetoable 'validate' event
+    $.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') {
+        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 $form = this, callbacks = [];
+    if (options.resetForm) callbacks.push(function() { $form.resetForm(); });
+    if (options.clearForm) callbacks.push(function() { $form.clearForm(); });
+
+    // 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) {
+            if (this.evalScripts)
+                $(options.target).attr("innerHTML", data).evalScripts().each(oldSuccess, arguments);
+            else // jQuery v1.1.4
+                $(options.target).html(data).each(oldSuccess, arguments);
+        });
+    }
+    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, $form);
+    };
+
+    // 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;
+
+    // options.iframe allows user to force iframe mode
+   if (options.iframe || found) { 
+       // hack to fix Safari hang (thanks to Tim Molendijk for this)
+       // see:  http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
+       if ($.browser.safari && options.closeKeepAlive)
+           $.get(options.closeKeepAlive, fileUpload);
+       else
+           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',
+                action:   opts.url
+            });
+            form[encAttr] = 'multipart/form-data';
+
+            // 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.
+ *
+ * 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.
+ *
+ * Note that for accurate x/y coordinates of image submit elements in all browsers
+ * you need to also use the "dimensions" plugin (this method will auto-detect its presence).
+ *
+ * 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.  See ajaxSubmit for a full description of the options argument.
+ *
+ *
+ * @example
+ * var options = {
+ *     target: '#myTargetDiv'
+ * };
+ * $('#myForm').ajaxSForm(options);
+ * @desc Bind form's submit event so that 'myTargetDiv' is updated with the server response
+ *       when the form is submitted.
+ *
+ *
+ * @example
+ * var options = {
+ *     success: function(responseText) {
+ *         alert(responseText);
+ *     }
+ * };
+ * $('#myForm').ajaxSubmit(options);
+ * @desc Bind form's submit event so that server response is alerted after the form is submitted.
+ *
+ *
+ * @example
+ * var options = {
+ *     beforeSubmit: function(formArray, jqForm) {
+ *         if (formArray.length == 0) {
+ *             alert('Please enter data.');
+ *             return false;
+ *         }
+ *     }
+ * };
+ * $('#myForm').ajaxSubmit(options);
+ * @desc Bind form's submit event so that pre-submit callback is invoked before the form
+ *       is submitted.
+ *
+ *
+ * @name   ajaxForm
+ * @param  options  object literal containing options which control the form submission process
+ * @return jQuery
+ * @cat    Plugins/Form
+ * @type   jQuery
+ */
+$.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
+ * 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.
+ *
+ * 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
+ * (or nothing) to avoid the overhead for this logic.
+ *
+ * @example var data = $("#myForm").formToArray();
+ * $.post( "myscript.cgi", data );
+ * @desc Collect all the data from a form and submit it to the server.
+ *
+ * @name formToArray
+ * @param semantic true if serialization must maintain strict semantic ordering of elements (slower)
+ * @type Array<Object>
+ * @cat Plugins/Form
+ */
+$.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];
+        var 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+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
+            continue;
+        }
+
+        var v = $.fieldValue(el, true);
+        if (v && v.constructor == Array) {
+            for(var j=0, jmax=v.length; j < jmax; j++)
+                a.push({name: n, value: v[j]});
+        }
+        else if (v !== null && typeof v != 'undefined')
+            a.push({name: n, value: v});
+    }
+
+    if (!semantic && form.clk) {
+        // input type=='image' are not found in elements array! handle them here
+        var inputs = form.getElementsByTagName("input");
+        for(var i=0, max=inputs.length; i < max; i++) {
+            var input = inputs[i];
+            var n = input.name;
+            if(n && !input.disabled && input.type == "image" && form.clk == input)
+                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
+ *
+ * The semantic argument can be used to force form serialization in semantic order.
+ * If your form must be submitted with name/value pairs in semantic order then pass
+ * true for this arg, otherwise pass false (or nothing) to avoid the overhead for
+ * this logic (which can be significant for very large forms).
+ *
+ * @example var data = $("#myForm").formSerialize();
+ * $.ajax('POST', "myscript.cgi", data);
+ * @desc Collect all the data from a form into a single string
+ *
+ * @name formSerialize
+ * @param semantic true if serialization must maintain strict semantic ordering of elements (slower)
+ * @type String
+ * @cat Plugins/Form
+ */
+$.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
+ *
+ * The successful argument controls whether or not serialization is limited to
+ * 'successful' controls (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
+ * The default value of the successful argument is true.
+ *
+ * @example var data = $("input").formSerialize();
+ * @desc Collect the data from all successful input elements into a query string
+ *
+ * @example var data = $(":radio").formSerialize();
+ * @desc Collect the data from all successful radio input elements into a query string
+ *
+ * @example var data = $("#myForm :checkbox").formSerialize();
+ * @desc Collect the data from all successful checkbox input elements in myForm into a query string
+ *
+ * @example var data = $("#myForm :checkbox").formSerialize(false);
+ * @desc Collect the data from all checkbox elements in myForm (even the unchecked ones) into a query string
+ *
+ * @example var data = $(":input").formSerialize();
+ * @desc Collect the data from all successful input, select, textarea and button elements into a query string
+ *
+ * @name fieldSerialize
+ * @param successful true if only successful controls should be serialized (default is true)
+ * @type String
+ * @cat Plugins/Form
+ */
+$.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.
+ *
+ * @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 = $("#myForm :checkbox").fieldValue();
+ * @desc Get the value(s) for the successful checkbox element(s) in the jQuery object.
+ *
+ * @example var data = $("#mySingleSelect").fieldValue();
+ * @desc Get the value(s) of the select control
+ *
+ * @example var data = $(':text').fieldValue();
+ * @desc Get the value(s) of the text input or textarea elements
+ *
+ * @example var data = $("#myMultiSelect").fieldValue();
+ * @desc Get the values for the select-multiple control
+ *
+ * @name fieldValue
+ * @param Boolean successful true if only the values for successful controls should be returned (default is true)
+ * @type Array<String>
+ * @cat Plugins/Form
+ */
+$.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;
+        v.constructor == Array ? $.merge(val, v) : val.push(v);
+    }
+    return val;
+};
+
+/**
+ * Returns the value of the field element.
+ *
+ * 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 the given element is not
+ * successful and the successful arg is not false then the returned value will be null.
+ *
+ * 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
+ *
+ * @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> or null or undefined
+ * @cat Plugins/Form
+ */
+$.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' || 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) {
+                // extra pain for IE...
+                var v = $.browser.msie && !(op.attributes['value'].specified) ? op.text : op.value;
+                if (one) return v;
+                a.push(v);
+            }
+        }
+        return a;
+    }
+    return el.value;
+};
+
+
+/**
+ * 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
+ *
+ * @example $('form').clearForm();
+ * @desc Clears all forms on the page.
+ *
+ * @name clearForm
+ * @type jQuery
+ * @cat Plugins/Form
+ */
+$.fn.clearForm = function() {
+    return this.each(function() {
+        $('input,select,textarea', this).clearFields();
+    });
+};
+
+/**
+ * Clears the selected form elements.  Takes the following actions on the matched elements:
+ *  - 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
+ *
+ * @example $('.myInputs').clearFields();
+ * @desc Clears all inputs with class myInputs
+ *
+ * @name clearFields
+ * @type jQuery
+ * @cat Plugins/Form
+ */
+$.fn.clearFields = $.fn.clearInputs = function() {
+    return this.each(function() {
+        var t = this.type, tag = this.tagName.toLowerCase();
+        if (t == 'text' || t == 'password' || tag == 'textarea')
+            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.
+ *
+ * @example $('form').resetForm();
+ * @desc Resets all forms on the page.
+ *
+ * @name resetForm
+ * @type jQuery
+ * @cat Plugins/Form
+ */
+$.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.
+ *
+ * @example $(':radio').enabled(false);
+ * @desc Disables all radio buttons
+ *
+ * @name select
+ * @type jQuery
+ * @cat Plugins/Form
+ */
+$.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.
+ *
+ * @example $(':checkbox').selected();
+ * @desc Checks all checkboxes
+ *
+ * @name select
+ * @type jQuery
+ * @cat Plugins/Form
+ */
+$.fn.select = 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').select(false);
+            }
+            this.selected = select;
+        }
+    });
+};
+
+})(jQuery);
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/js/jquery.js
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/js/jquery.js	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/js/jquery.js	(revision 69)
@@ -0,0 +1,3859 @@
+(function(){
+/*
+ * jQuery 1.2.2b - New Wave Javascript
+ *
+ * Copyright (c) 2007 John Resig (jquery.com)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * $Date: 2007-12-18 04:53:09 +0100 (Tue, 18 Dec 2007) $
+ * $Rev: 4217 $
+ */
+
+// Map over jQuery in case of overwrite
+if ( window.jQuery )
+	var _jQuery = window.jQuery;
+
+var jQuery = window.jQuery = function( selector, context ) {
+	// The jQuery object is actually just the init constructor 'enhanced'
+	return new jQuery.prototype.init( selector, context );
+};
+
+// Map over the $ in case of overwrite
+if ( window.$ )
+	var _$ = window.$;
+	
+// Map the jQuery namespace to the '$' one
+window.$ = jQuery;
+
+// A simple way to check for HTML strings or ID strings
+// (both of which we optimize for)
+var quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/;
+
+// Is it a simple selector
+var isSimple = /^.[^:#\[\.]*$/;
+
+jQuery.fn = jQuery.prototype = {
+	init: function( selector, context ) {
+		// Make sure that a selection was provided
+		selector = selector || document;
+
+		// Handle $(DOMElement)
+		if ( selector.nodeType ) {
+			this[0] = selector;
+			this.length = 1;
+			return this;
+
+		// Handle HTML strings
+		} else if ( typeof selector == "string" ) {
+			// Are we dealing with HTML string or an ID?
+			var match = quickExpr.exec( selector );
+
+			// Verify a match, and that no context was specified for #id
+			if ( match && (match[1] || !context) ) {
+
+				// HANDLE: $(html) -> $(array)
+				if ( match[1] )
+					selector = jQuery.clean( [ match[1] ], context );
+
+				// HANDLE: $("#id")
+				else {
+					var elem = document.getElementById( match[3] );
+
+					// Make sure an element was located
+					if ( elem )
+						// Handle the case where IE and Opera return items
+						// by name instead of ID
+						if ( elem.id != match[3] )
+							return jQuery().find( selector );
+
+						// Otherwise, we inject the element directly into the jQuery object
+						else {
+							this[0] = elem;
+							this.length = 1;
+							return this;
+						}
+
+					else
+						selector = [];
+				}
+
+			// HANDLE: $(expr, [context])
+			// (which is just equivalent to: $(content).find(expr)
+			} else
+				return new jQuery( context ).find( selector );
+
+		// HANDLE: $(function)
+		// Shortcut for document ready
+		} else if ( jQuery.isFunction( selector ) )
+			return new jQuery( document )[ jQuery.fn.ready ? "ready" : "load" ]( selector );
+
+		return this.setArray(
+			// HANDLE: $(array)
+			selector.constructor == Array && selector ||
+
+			// HANDLE: $(arraylike)
+			// Watch for when an array-like object, contains DOM nodes, is passed in as the selector
+			(selector.jquery || selector.length && selector != window && !selector.nodeType && selector[0] != undefined && selector[0].nodeType) && jQuery.makeArray( selector ) ||
+
+			// HANDLE: $(*)
+			[ selector ] );
+	},
+	
+	// The current version of jQuery being used
+	jquery: "1.2.2b",
+
+	// The number of elements contained in the matched element set
+	size: function() {
+		return this.length;
+	},
+	
+	// The number of elements contained in the matched element set
+	length: 0,
+
+	// Get the Nth element in the matched element set OR
+	// Get the whole matched element set as a clean array
+	get: function( num ) {
+		return num == undefined ?
+
+			// Return a 'clean' array
+			jQuery.makeArray( this ) :
+
+			// Return just the object
+			this[ num ];
+	},
+	
+	// Take an array of elements and push it onto the stack
+	// (returning the new matched element set)
+	pushStack: function( elems ) {
+		// Build a new jQuery matched element set
+		var ret = jQuery( elems );
+
+		// Add the old object onto the stack (as a reference)
+		ret.prevObject = this;
+
+		// Return the newly-formed element set
+		return ret;
+	},
+	
+	// Force the current matched set of elements to become
+	// the specified array of elements (destroying the stack in the process)
+	// You should use pushStack() in order to do this, but maintain the stack
+	setArray: function( elems ) {
+		// Resetting the length to 0, then using the native Array push
+		// is a super-fast way to populate an object with array-like properties
+		this.length = 0;
+		Array.prototype.push.apply( this, elems );
+		
+		return this;
+	},
+
+	// Execute a callback for every element in the matched set.
+	// (You can seed the arguments with an array of args, but this is
+	// only used internally.)
+	each: function( callback, args ) {
+		return jQuery.each( this, callback, args );
+	},
+
+	// Determine the position of an element within 
+	// the matched set of elements
+	index: function( elem ) {
+		var ret = -1;
+
+		// Locate the position of the desired element
+		this.each(function(i){
+			if ( this == elem )
+				ret = i;
+		});
+
+		return ret;
+	},
+
+	attr: function( name, value, type ) {
+		var options = name;
+		
+		// Look for the case where we're accessing a style value
+		if ( name.constructor == String )
+			if ( value == undefined )
+				return this.length && jQuery[ type || "attr" ]( this[0], name ) || undefined;
+
+			else {
+				options = {};
+				options[ name ] = value;
+			}
+		
+		// Check to see if we're setting style values
+		return this.each(function(i){
+			// Set all the styles
+			for ( name in options )
+				jQuery.attr(
+					type ?
+						this.style :
+						this,
+					name, jQuery.prop( this, options[ name ], type, i, name )
+				);
+		});
+	},
+
+	css: function( key, value ) {
+		// ignore negative width and height values
+		if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 )
+			value = undefined;
+		return this.attr( key, value, "curCSS" );
+	},
+
+	text: function( text ) {
+		if ( typeof text != "object" && text != null )
+			return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
+
+		var ret = "";
+
+		jQuery.each( text || this, function(){
+			jQuery.each( this.childNodes, function(){
+				if ( this.nodeType != 8 )
+					ret += this.nodeType != 1 ?
+						this.nodeValue :
+						jQuery.fn.text( [ this ] );
+			});
+		});
+
+		return ret;
+	},
+
+	wrapAll: function( html ) {
+		if ( this[0] )
+			// The elements to wrap the target around
+			jQuery( html, this[0].ownerDocument )
+				.clone()
+				.insertBefore( this[0] )
+				.map(function(){
+					var elem = this;
+
+					while ( elem.firstChild )
+						elem = elem.firstChild;
+
+					return elem;
+				})
+				.append(this);
+
+		return this;
+	},
+
+	wrapInner: function( html ) {
+		return this.each(function(){
+			jQuery( this ).contents().wrapAll( html );
+		});
+	},
+
+	wrap: function( html ) {
+		return this.each(function(){
+			jQuery( this ).wrapAll( html );
+		});
+	},
+
+	append: function() {
+		return this.domManip(arguments, true, false, function(elem){
+			if (this.nodeType == 1)
+				this.appendChild( elem );
+		});
+	},
+
+	prepend: function() {
+		return this.domManip(arguments, true, true, function(elem){
+			if (this.nodeType == 1)
+				this.insertBefore( elem, this.firstChild );
+		});
+	},
+	
+	before: function() {
+		return this.domManip(arguments, false, false, function(elem){
+			this.parentNode.insertBefore( elem, this );
+		});
+	},
+
+	after: function() {
+		return this.domManip(arguments, false, true, function(elem){
+			this.parentNode.insertBefore( elem, this.nextSibling );
+		});
+	},
+
+	end: function() {
+		return this.prevObject || jQuery( [] );
+	},
+
+	find: function( selector ) {
+		var elems = jQuery.map(this, function(elem){
+			return jQuery.find( selector, elem );
+		});
+
+		return this.pushStack( /[^+>] [^+>]/.test( selector ) || selector.indexOf("..") > -1 ?
+			jQuery.unique( elems ) :
+			elems );
+	},
+
+	clone: function( events ) {
+		// Do the clone
+		var ret = this.map(function(){
+			if ( jQuery.browser.msie && !jQuery.isXMLDoc(this) ) {
+				// IE copies events bound via attachEvent when
+				// using cloneNode. Calling detachEvent on the
+				// clone will also remove the events from the orignal
+				// In order to get around this, we use innerHTML.
+				// Unfortunately, this means some modifications to 
+				// attributes in IE that are actually only stored 
+				// as properties will not be copied (such as the
+				// the name attribute on an input).
+				var clone = this.cloneNode(true),
+					container = document.createElement("div"),
+					container2 = document.createElement("div");
+				container.appendChild(clone);
+				container2.innerHTML = container.innerHTML;
+				return container2.firstChild;
+			} else
+				return this.cloneNode(true);
+		});
+
+		// Need to set the expando to null on the cloned set if it exists
+		// removeData doesn't work here, IE removes it from the original as well
+		// this is primarily for IE but the data expando shouldn't be copied over in any browser
+		var clone = ret.find("*").andSelf().each(function(){
+			if ( this[ expando ] != undefined )
+				this[ expando ] = null;
+		});
+		
+		// Copy the events from the original to the clone
+		if ( events === true )
+			this.find("*").andSelf().each(function(i){
+				var events = jQuery.data( this, "events" );
+
+				for ( var type in events )
+					for ( var handler in events[ type ] )
+						jQuery.event.add( clone[ i ], type, events[ type ][ handler ], events[ type ][ handler ].data );
+			});
+
+		// Return the cloned set
+		return ret;
+	},
+
+	filter: function( selector ) {
+		return this.pushStack(
+			jQuery.isFunction( selector ) &&
+			jQuery.grep(this, function(elem, i){
+				return selector.call( elem, i );
+			}) ||
+
+			jQuery.multiFilter( selector, this ) );
+	},
+
+	not: function( selector ) {
+		if ( selector.constructor == String )
+			// test special case where just one selector is passed in
+			if ( isSimple.test( selector ) )
+				return this.pushStack( jQuery.multiFilter( selector, this, true ) );
+			else
+				selector = jQuery.multiFilter( selector, this );
+
+		var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType;
+		return this.filter(function() {
+			return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector;
+		});
+	},
+
+	add: function( selector ) {
+		return !selector ? this : this.pushStack( jQuery.merge( 
+			this.get(),
+			selector.constructor == String ? 
+				jQuery( selector ).get() :
+				selector.length != undefined && (!selector.nodeName || jQuery.nodeName(selector, "form")) ?
+					selector : [selector] ) );
+	},
+
+	is: function( selector ) {
+		return selector ?
+			jQuery.multiFilter( selector, this ).length > 0 :
+			false;
+	},
+
+	hasClass: function( selector ) {
+		return this.is( "." + selector );
+	},
+	
+	val: function( value ) {
+		if ( value == undefined ) {
+
+			if ( this.length ) {
+				var elem = this[0];
+
+				// We need to handle select boxes special
+				if ( jQuery.nodeName( elem, "select" ) ) {
+					var index = elem.selectedIndex,
+						values = [],
+						options = elem.options,
+						one = elem.type == "select-one";
+					
+					// Nothing was selected
+					if ( index < 0 )
+						return null;
+
+					// Loop through all the selected options
+					for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
+						var option = options[ i ];
+
+						if ( option.selected ) {
+							// Get the specifc value for the option
+							value = jQuery.browser.msie && !option.attributes.value.specified ? option.text : option.value;
+							
+							// We don't need an array for one selects
+							if ( one )
+								return value;
+							
+							// Multi-Selects return an array
+							values.push( value );
+						}
+					}
+					
+					return values;
+					
+				// Everything else, we just grab the value
+				} else
+					return (this[0].value || "").replace(/\r/g, "");
+
+			}
+
+		}
+
+		return this.each(function(){
+			if ( this.nodeType != 1 )
+				return;
+
+			if ( value.constructor == Array && /radio|checkbox/.test( this.type ) )
+				this.checked = (jQuery.inArray(this.value, value) >= 0 ||
+					jQuery.inArray(this.name, value) >= 0);
+
+			else if ( jQuery.nodeName( this, "select" ) ) {
+				var values = value.constructor == Array ?
+					value :
+					[ value ];
+
+				jQuery( "option", this ).each(function(){
+					this.selected = (jQuery.inArray( this.value, values ) >= 0 ||
+						jQuery.inArray( this.text, values ) >= 0);
+				});
+
+				if ( !values.length )
+					this.selectedIndex = -1;
+
+			} else
+				this.value = value;
+		});
+	},
+	
+	html: function( value ) {
+		return value == undefined ?
+			(this.length ?
+				this[0].innerHTML :
+				null) :
+			this.empty().append( value );
+	},
+
+	replaceWith: function( value ) {
+		return this.after( value ).remove();
+	},
+
+	eq: function( i ) {
+		return this.slice( i, i + 1 );
+	},
+
+	slice: function() {
+		return this.pushStack( Array.prototype.slice.apply( this, arguments ) );
+	},
+
+	map: function( callback ) {
+		return this.pushStack( jQuery.map(this, function(elem, i){
+			return callback.call( elem, i, elem );
+		}));
+	},
+
+	andSelf: function() {
+		return this.add( this.prevObject );
+	},
+	
+	domManip: function( args, table, reverse, callback ) {
+		var clone = this.length > 1, elems; 
+
+		return this.each(function(){
+			if ( !elems ) {
+				elems = jQuery.clean( args, this.ownerDocument );
+
+				if ( reverse )
+					elems.reverse();
+			}
+
+			var obj = this;
+
+			if ( table && jQuery.nodeName( this, "table" ) && jQuery.nodeName( elems[0], "tr" ) )
+				obj = this.getElementsByTagName("tbody")[0] || this.appendChild( this.ownerDocument.createElement("tbody") );
+
+			var scripts = jQuery( [] );
+
+			jQuery.each(elems, function(){
+				var elem = clone ?
+					this.cloneNode( true ) :
+					this;
+
+				// execute all scripts after the elements have been injected
+				if ( jQuery.nodeName( elem, "script" ) ) {
+					scripts = scripts.add( elem );
+				} else {
+					// Remove any inner scripts for later evaluation
+					if ( elem.nodeType == 1 )
+						scripts = scripts.add( jQuery( "script", elem ).remove() );
+
+					// Inject the elements into the document
+					callback.call( obj, elem );
+				}
+			});
+
+			scripts.each( evalScript );
+		});
+	}
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.prototype.init.prototype = jQuery.prototype;
+
+function evalScript( i, elem ) {
+	if ( elem.src )
+		jQuery.ajax({
+			url: elem.src,
+			async: false,
+			dataType: "script"
+		});
+
+	else
+		jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );
+
+	if ( elem.parentNode )
+		elem.parentNode.removeChild( elem );
+}
+
+jQuery.extend = jQuery.fn.extend = function() {
+	// copy reference to target object
+	var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options;
+
+	// Handle a deep copy situation
+	if ( target.constructor == Boolean ) {
+		deep = target;
+		target = arguments[1] || {};
+		// skip the boolean and the target
+		i = 2;
+	}
+
+	// Handle case when target is a string or something (possible in deep copy)
+	if ( typeof target != "object" && typeof target != "function" )
+		target = {};
+
+	// extend jQuery itself if only one argument is passed
+	if ( length == 1 ) {
+		target = this;
+		i = 0;
+	}
+
+	for ( ; i < length; i++ )
+		// Only deal with non-null/undefined values
+		if ( (options = arguments[ i ]) != null )
+			// Extend the base object
+			for ( var name in options ) {
+				// Prevent never-ending loop
+				if ( target === options[ name ] )
+					continue;
+
+				// Recurse if we're merging object values
+				if ( deep && options[ name ] && typeof options[ name ] == "object" && target[ name ] && !options[ name ].nodeType )
+					target[ name ] = jQuery.extend( target[ name ], options[ name ] );
+
+				// Don't bring in undefined values
+				else if ( options[ name ] != undefined )
+					target[ name ] = options[ name ];
+
+			}
+
+	// Return the modified object
+	return target;
+};
+
+var expando = "jQuery" + (new Date()).getTime(), uuid = 0, windowData = {};
+
+// exclude the following css properties to add px
+var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i;
+
+jQuery.extend({
+	noConflict: function( deep ) {
+		window.$ = _$;
+
+		if ( deep )
+			window.jQuery = _jQuery;
+
+		return jQuery;
+	},
+
+	// This may seem like some crazy code, but trust me when I say that this
+	// is the only cross-browser way to do this. --John
+	isFunction: function( fn ) {
+		return !!fn && typeof fn != "string" && !fn.nodeName && 
+			fn.constructor != Array && /function/i.test( fn + "" );
+	},
+	
+	// check if an element is in a (or is an) XML document
+	isXMLDoc: function( elem ) {
+		return elem.documentElement && !elem.body ||
+			elem.tagName && elem.ownerDocument && !elem.ownerDocument.body;
+	},
+
+	// Evalulates a script in a global context
+	globalEval: function( data ) {
+		data = jQuery.trim( data );
+
+		if ( data ) {
+			// Inspired by code by Andrea Giammarchi
+			// http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
+			var head = document.getElementsByTagName("head")[0] || document.documentElement,
+				script = document.createElement("script");
+
+			script.type = "text/javascript";
+			if ( jQuery.browser.msie )
+				script.text = data;
+			else
+				script.appendChild( document.createTextNode( data ) );
+
+			head.appendChild( script );
+			head.removeChild( script );
+		}
+	},
+
+	nodeName: function( elem, name ) {
+		return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase();
+	},
+	
+	cache: {},
+	
+	data: function( elem, name, data ) {
+		elem = elem == window ?
+			windowData :
+			elem;
+
+		var id = elem[ expando ];
+
+		// Compute a unique ID for the element
+		if ( !id ) 
+			id = elem[ expando ] = ++uuid;
+
+		// Only generate the data cache if we're
+		// trying to access or manipulate it
+		if ( name && !jQuery.cache[ id ] )
+			jQuery.cache[ id ] = {};
+		
+		// Prevent overriding the named cache with undefined values
+		if ( data != undefined )
+			jQuery.cache[ id ][ name ] = data;
+		
+		// Return the named cache data, or the ID for the element	
+		return name ?
+			jQuery.cache[ id ][ name ] :
+			id;
+	},
+	
+	removeData: function( elem, name ) {
+		elem = elem == window ?
+			windowData :
+			elem;
+
+		var id = elem[ expando ];
+
+		// If we want to remove a specific section of the element's data
+		if ( name ) {
+			if ( jQuery.cache[ id ] ) {
+				// Remove the section of cache data
+				delete jQuery.cache[ id ][ name ];
+
+				// If we've removed all the data, remove the element's cache
+				name = "";
+
+				for ( name in jQuery.cache[ id ] )
+					break;
+
+				if ( !name )
+					jQuery.removeData( elem );
+			}
+
+		// Otherwise, we want to remove all of the element's data
+		} else {
+			// Clean up the element expando
+			try {
+				delete elem[ expando ];
+			} catch(e){
+				// IE has trouble directly removing the expando
+				// but it's ok with using removeAttribute
+				if ( elem.removeAttribute )
+					elem.removeAttribute( expando );
+			}
+
+			// Completely remove the data cache
+			delete jQuery.cache[ id ];
+		}
+	},
+
+	// args is for internal usage only
+	each: function( object, callback, args ) {
+		if ( args ) {
+			if ( object.length == undefined )
+				for ( var name in object )
+					callback.apply( object[ name ], args );
+			else
+				for ( var i = 0, length = object.length; i < length; i++ )
+					if ( callback.apply( object[ i ], args ) === false )
+						break;
+
+		// A special, fast, case for the most common use of each
+		} else {
+			if ( object.length == undefined )
+				for ( var name in object )
+					callback.call( object[ name ], name, object[ name ] );
+			else
+				for ( var i = 0, length = object.length, value = object[0]; 
+					i < length && callback.call( value, i, value ) !== false; value = object[++i] ){}
+		}
+
+		return object;
+	},
+	
+	prop: function( elem, value, type, i, name ) {
+			// Handle executable functions
+			if ( jQuery.isFunction( value ) )
+				value = value.call( elem, i );
+				
+			// Handle passing in a number to a CSS property
+			return value && value.constructor == Number && type == "curCSS" && !exclude.test( name ) ?
+				value + "px" :
+				value;
+	},
+
+	className: {
+		// internal only, use addClass("class")
+		add: function( elem, classNames ) {
+			jQuery.each((classNames || "").split(/\s+/), function(i, className){
+				if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) )
+					elem.className += (elem.className ? " " : "") + className;
+			});
+		},
+
+		// internal only, use removeClass("class")
+		remove: function( elem, classNames ) {
+			if (elem.nodeType == 1)
+				elem.className = classNames != undefined ?
+					jQuery.grep(elem.className.split(/\s+/), function(className){
+						return !jQuery.className.has( classNames, className );	
+					}).join(" ") :
+					"";
+		},
+
+		// internal only, use is(".class")
+		has: function( elem, className ) {
+			return jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1;
+		}
+	},
+
+	// A method for quickly swapping in/out CSS properties to get correct calculations
+	swap: function( elem, options, callback ) {
+		var old = {};
+		// Remember the old values, and insert the new ones
+		for ( var name in options ) {
+			old[ name ] = elem.style[ name ];
+			elem.style[ name ] = options[ name ];
+		}
+
+		callback.call( elem );
+
+		// Revert the old values
+		for ( var name in options )
+			elem.style[ name ] = old[ name ];
+	},
+
+	css: function( elem, name, force ) {
+		if ( name == "width" || name == "height" ) {
+			var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ];
+		
+			function getWH() {
+				val = name == "width" ? elem.offsetWidth : elem.offsetHeight;
+				var padding = 0, border = 0;
+				jQuery.each( which, function() {
+					padding += parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0;
+					border += parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0;
+				});
+				val -= Math.round(padding + border);
+			}
+		
+			if ( jQuery(elem).is(":visible") )
+				getWH();
+			else
+				jQuery.swap( elem, props, getWH );
+			
+			return val;
+		}
+		
+		return jQuery.curCSS( elem, name, force );
+	},
+
+	curCSS: function( elem, name, force ) {
+		var ret;
+
+		// A helper method for determining if an element's values are broken
+		function color( elem ) {
+			if ( !jQuery.browser.safari )
+				return false;
+
+			var ret = document.defaultView.getComputedStyle( elem, null );
+			return !ret || ret.getPropertyValue("color") == "";
+		}
+
+		// We need to handle opacity special in IE
+		if ( name == "opacity" && jQuery.browser.msie ) {
+			ret = jQuery.attr( elem.style, "opacity" );
+
+			return ret == "" ?
+				"1" :
+				ret;
+		}
+		// Opera sometimes will give the wrong display answer, this fixes it, see #2037
+		if ( jQuery.browser.opera && name == "display" ) {
+			var save = elem.style.display;
+			elem.style.display = "block";
+			elem.style.display = save;
+		}
+		
+		// Make sure we're using the right name for getting the float value
+		if ( name.match( /float/i ) )
+			name = styleFloat;
+
+		if ( !force && elem.style[ name ] )
+			ret = elem.style[ name ];
+
+		else if ( document.defaultView && document.defaultView.getComputedStyle ) {
+
+			// Only "float" is needed here
+			if ( name.match( /float/i ) )
+				name = "float";
+
+			name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase();
+
+			var getComputedStyle = document.defaultView.getComputedStyle( elem, null );
+
+			if ( getComputedStyle && !color( elem ) )
+				ret = getComputedStyle.getPropertyValue( name );
+
+			// If the element isn't reporting its values properly in Safari
+			// then some display: none elements are involved
+			else {
+				var swap = [], stack = [];
+
+				// Locate all of the parent display: none elements
+				for ( var a = elem; a && color(a); a = a.parentNode )
+					stack.unshift(a);
+
+				// Go through and make them visible, but in reverse
+				// (It would be better if we knew the exact display type that they had)
+				for ( var i = 0; i < stack.length; i++ )
+					if ( color( stack[ i ] ) ) {
+						swap[ i ] = stack[ i ].style.display;
+						stack[ i ].style.display = "block";
+					}
+
+				// Since we flip the display style, we have to handle that
+				// one special, otherwise get the value
+				ret = name == "display" && swap[ stack.length - 1 ] != null ?
+					"none" :
+					( getComputedStyle && getComputedStyle.getPropertyValue( name ) ) || "";
+
+				// Finally, revert the display styles back
+				for ( var i = 0; i < swap.length; i++ )
+					if ( swap[ i ] != null )
+						stack[ i ].style.display = swap[ i ];
+			}
+
+			// We should always get a number back from opacity
+			if ( name == "opacity" && ret == "" )
+				ret = "1";
+
+		} else if ( elem.currentStyle ) {
+			var camelCase = name.replace(/\-(\w)/g, function(all, letter){
+				return letter.toUpperCase();
+			});
+
+			ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ];
+
+			// From the awesome hack by Dean Edwards
+			// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+			// If we're not dealing with a regular pixel number
+			// but a number that has a weird ending, we need to convert it to pixels
+			if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) {
+				// Remember the original values
+				var style = elem.style.left, runtimeStyle = elem.runtimeStyle.left;
+
+				// Put in the new values to get a computed value out
+				elem.runtimeStyle.left = elem.currentStyle.left;
+				elem.style.left = ret || 0;
+				ret = elem.style.pixelLeft + "px";
+
+				// Revert the changed values
+				elem.style.left = style;
+				elem.runtimeStyle.left = runtimeStyle;
+			}
+		}
+
+		return ret;
+	},
+	
+	clean: function( elems, context ) {
+		var ret = [];
+		context = context || document;
+		// !context.createElement fails in IE with an error but returns typeof 'object'
+		if (typeof context.createElement == 'undefined') 
+			context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+
+		jQuery.each(elems, function(i, elem){
+			if ( !elem )
+				return;
+
+			if ( elem.constructor == Number )
+				elem = elem.toString();
+			
+			// Convert html string into DOM nodes
+			if ( typeof elem == "string" ) {
+				// Fix "XHTML"-style tags in all browsers
+				elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){
+					return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area)$/i) ?
+						all :
+						front + "></" + tag + ">";
+				});
+
+				// Trim whitespace, otherwise indexOf won't work as expected
+				var tags = jQuery.trim( elem ).toLowerCase(), div = context.createElement("div");
+
+				var wrap =
+					// option or optgroup
+					!tags.indexOf("<opt") &&
+					[ 1, "<select multiple='multiple'>", "</select>" ] ||
+					
+					!tags.indexOf("<leg") &&
+					[ 1, "<fieldset>", "</fieldset>" ] ||
+					
+					tags.match(/^<(thead|tbody|tfoot|colg|cap)/) &&
+					[ 1, "<table>", "</table>" ] ||
+					
+					!tags.indexOf("<tr") &&
+					[ 2, "<table><tbody>", "</tbody></table>" ] ||
+					
+				 	// <thead> matched above
+					(!tags.indexOf("<td") || !tags.indexOf("<th")) &&
+					[ 3, "<table><tbody><tr>", "</tr></tbody></table>" ] ||
+					
+					!tags.indexOf("<col") &&
+					[ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ] ||
+
+					// IE can't serialize <link> and <script> tags normally
+					jQuery.browser.msie &&
+					[ 1, "div<div>", "</div>" ] ||
+					
+					[ 0, "", "" ];
+
+				// Go to html and back, then peel off extra wrappers
+				div.innerHTML = wrap[1] + elem + wrap[2];
+				
+				// Move to the right depth
+				while ( wrap[0]-- )
+					div = div.lastChild;
+				
+				// Remove IE's autoinserted <tbody> from table fragments
+				if ( jQuery.browser.msie ) {
+					
+					// String was a <table>, *may* have spurious <tbody>
+					var tbody = !tags.indexOf("<table") && tags.indexOf("<tbody") < 0 ?
+						div.firstChild && div.firstChild.childNodes :
+						
+						// String was a bare <thead> or <tfoot>
+						wrap[1] == "<table>" && tags.indexOf("<tbody") < 0 ?
+							div.childNodes :
+							[];
+				
+					for ( var j = tbody.length - 1; j >= 0 ; --j )
+						if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length )
+							tbody[ j ].parentNode.removeChild( tbody[ j ] );
+					
+					// IE completely kills leading whitespace when innerHTML is used	
+					if ( /^\s/.test( elem ) )	
+						div.insertBefore( context.createTextNode( elem.match(/^\s*/)[0] ), div.firstChild );
+				
+				}
+				
+				elem = jQuery.makeArray( div.childNodes );
+			}
+
+			if ( elem.length === 0 && (!jQuery.nodeName( elem, "form" ) && !jQuery.nodeName( elem, "select" )) )
+				return;
+
+			if ( elem[0] == undefined || jQuery.nodeName( elem, "form" ) || elem.options )
+				ret.push( elem );
+
+			else
+				ret = jQuery.merge( ret, elem );
+
+		});
+
+		return ret;
+	},
+	
+	attr: function( elem, name, value ) {
+		// don't set attributes on text and comment nodes
+		if (!elem || elem.nodeType == 3 || elem.nodeType == 8)
+			return undefined;
+
+		var fix = jQuery.isXMLDoc( elem ) ?
+			{} :
+			jQuery.props;
+
+		// Safari mis-reports the default selected property of a hidden option
+		// Accessing the parent's selectedIndex property fixes it
+		if ( name == "selected" && jQuery.browser.safari )
+			elem.parentNode.selectedIndex;
+		
+		// Certain attributes only work when accessed via the old DOM 0 way
+		if ( fix[ name ] ) {
+			if ( value != undefined )
+				elem[ fix[ name ] ] = value;
+
+			return elem[ fix[ name ] ];
+
+		} else if ( jQuery.browser.msie && name == "style" )
+			return jQuery.attr( elem.style, "cssText", value );
+
+		else if ( value == undefined && jQuery.browser.msie && jQuery.nodeName( elem, "form" ) && (name == "action" || name == "method") )
+			return elem.getAttributeNode( name ).nodeValue;
+
+		// IE elem.getAttribute passes even for style
+		else if ( elem.tagName ) {
+
+			if ( value != undefined ) {
+				// We can't allow the type property to be changed (since it causes problems in IE)
+				if ( name == "type" && jQuery.nodeName( elem, "input" ) && elem.parentNode )
+					throw "type property can't be changed";
+
+				// convert the value to a string (all browsers do this but IE) see #1070
+				elem.setAttribute( name, "" + value );
+			}
+
+			if ( jQuery.browser.msie && /href|src/.test( name ) && !jQuery.isXMLDoc( elem ) ) 
+				return elem.getAttribute( name, 2 );
+
+			return elem.getAttribute( name );
+
+		// elem is actually elem.style ... set the style
+		} else {
+			// IE actually uses filters for opacity
+			if ( name == "opacity" && jQuery.browser.msie ) {
+				if ( value != undefined ) {
+					// IE has trouble with opacity if it does not have layout
+					// Force it by setting the zoom level
+					elem.zoom = 1; 
+	
+					// Set the alpha filter to set the opacity
+					elem.filter = (elem.filter || "").replace( /alpha\([^)]*\)/, "" ) +
+						(parseFloat( value ).toString() == "NaN" ? "" : "alpha(opacity=" + value * 100 + ")");
+				}
+	
+				return elem.filter && elem.filter.indexOf("opacity=") >= 0 ?
+					(parseFloat( elem.filter.match(/opacity=([^)]*)/)[1] ) / 100).toString() :
+					"";
+			}
+
+			name = name.replace(/-([a-z])/ig, function(all, letter){
+				return letter.toUpperCase();
+			});
+
+			if ( value != undefined )
+				elem[ name ] = value;
+
+			return elem[ name ];
+		}
+	},
+	
+	trim: function( text ) {
+		return (text || "").replace( /^\s+|\s+$/g, "" );
+	},
+
+	makeArray: function( array ) {
+		var ret = [];
+
+		// Need to use typeof to fight Safari childNodes crashes
+		if ( typeof array != "array" )
+			for ( var i = 0, length = array.length; i < length; i++ )
+				ret.push( array[ i ] );
+		else
+			ret = array.slice( 0 );
+
+		return ret;
+	},
+
+	inArray: function( elem, array ) {
+		for ( var i = 0, length = array.length; i < length; i++ )
+			if ( array[ i ] == elem )
+				return i;
+
+		return -1;
+	},
+
+	merge: function( first, second ) {
+		// We have to loop this way because IE & Opera overwrite the length
+		// expando of getElementsByTagName
+
+		// Also, we need to make sure that the correct elements are being returned
+		// (IE returns comment nodes in a '*' query)
+		if ( jQuery.browser.msie ) {
+			for ( var i = 0; second[ i ]; i++ )
+				if ( second[ i ].nodeType != 8 )
+					first.push( second[ i ] );
+
+		} else
+			for ( var i = 0; second[ i ]; i++ )
+				first.push( second[ i ] );
+
+		return first;
+	},
+
+	unique: function( array ) {
+		var ret = [], done = {};
+
+		try {
+
+			for ( var i = 0, length = array.length; i < length; i++ ) {
+				var id = jQuery.data( array[ i ] );
+
+				if ( !done[ id ] ) {
+					done[ id ] = true;
+					ret.push( array[ i ] );
+				}
+			}
+
+		} catch( e ) {
+			ret = array;
+		}
+
+		return ret;
+	},
+
+	grep: function( elems, callback, inv ) {
+		// If a string is passed in for the function, make a function
+		// for it (a handy shortcut)
+		if ( typeof callback == "string" )
+			callback = eval("false||function(a,i){return " + callback + "}");
+
+		var ret = [];
+
+		// Go through the array, only saving the items
+		// that pass the validator function
+		for ( var i = 0, length = elems.length; i < length; i++ )
+			if ( !inv && callback( elems[ i ], i ) || inv && !callback( elems[ i ], i ) )
+				ret.push( elems[ i ] );
+
+		return ret;
+	},
+
+	map: function( elems, callback ) {
+		var ret = [];
+
+		// Go through the array, translating each of the items to their
+		// new value (or values).
+		for ( var i = 0, length = elems.length; i < length; i++ ) {
+			var value = callback( elems[ i ], i );
+
+			if ( value !== null && value != undefined ) {
+				if ( value.constructor != Array )
+					value = [ value ];
+
+				ret = ret.concat( value );
+			}
+		}
+
+		return ret;
+	}
+});
+
+var userAgent = navigator.userAgent.toLowerCase();
+
+// Figure out what browser is being used
+jQuery.browser = {
+	version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [])[1],
+	safari: /webkit/.test( userAgent ),
+	opera: /opera/.test( userAgent ),
+	msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ),
+	mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent )
+};
+
+var styleFloat = jQuery.browser.msie ?
+	"styleFloat" :
+	"cssFloat";
+	
+jQuery.extend({
+	// Check to see if the W3C box model is being used
+	boxModel: !jQuery.browser.msie || document.compatMode == "CSS1Compat",
+	
+	props: {
+		"for": "htmlFor",
+		"class": "className",
+		"float": styleFloat,
+		cssFloat: styleFloat,
+		styleFloat: styleFloat,
+		innerHTML: "innerHTML",
+		className: "className",
+		value: "value",
+		disabled: "disabled",
+		checked: "checked",
+		readonly: "readOnly",
+		selected: "selected",
+		maxlength: "maxLength",
+		selectedIndex: "selectedIndex",
+		defaultValue: "defaultValue",
+		tagName: "tagName",
+		nodeName: "nodeName"
+	}
+});
+
+jQuery.each({
+	parent: "elem.parentNode",
+	parents: "jQuery.dir(elem,'parentNode')",
+	next: "jQuery.nth(elem,2,'nextSibling')",
+	prev: "jQuery.nth(elem,2,'previousSibling')",
+	nextAll: "jQuery.dir(elem,'nextSibling')",
+	prevAll: "jQuery.dir(elem,'previousSibling')",
+	siblings: "jQuery.sibling(elem.parentNode.firstChild,elem)",
+	children: "jQuery.sibling(elem.firstChild)",
+	contents: "jQuery.nodeName(elem,'iframe')?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes)"
+}, function(name, fn){
+	fn = eval("false||function(elem){return " + fn + "}");
+
+	jQuery.fn[ name ] = function( selector ) {
+		var ret = jQuery.map( this, fn );
+
+		if ( selector && typeof selector == "string" )
+			ret = jQuery.multiFilter( selector, ret );
+
+		return this.pushStack( jQuery.unique( ret ) );
+	};
+});
+
+jQuery.each({
+	appendTo: "append",
+	prependTo: "prepend",
+	insertBefore: "before",
+	insertAfter: "after",
+	replaceAll: "replaceWith"
+}, function(name, original){
+	jQuery.fn[ name ] = function() {
+		var args = arguments;
+
+		return this.each(function(){
+			for ( var i = 0, length = args.length; i < length; i++ )
+				jQuery( args[ i ] )[ original ]( this );
+		});
+	};
+});
+
+jQuery.each({
+	removeAttr: function( name ) {
+		jQuery.attr( this, name, "" );
+		if (this.nodeType == 1) 
+			this.removeAttribute( name );
+	},
+
+	addClass: function( classNames ) {
+		jQuery.className.add( this, classNames );
+	},
+
+	removeClass: function( classNames ) {
+		jQuery.className.remove( this, classNames );
+	},
+
+	toggleClass: function( classNames ) {
+		jQuery.className[ jQuery.className.has( this, classNames ) ? "remove" : "add" ]( this, classNames );
+	},
+
+	remove: function( selector ) {
+		if ( !selector || jQuery.filter( selector, [ this ] ).r.length ) {
+			// Prevent memory leaks
+			jQuery( "*", this ).add(this).each(function(){
+				jQuery.event.remove(this);
+				jQuery.removeData(this);
+			});
+			if (this.parentNode)
+				this.parentNode.removeChild( this );
+		}
+	},
+
+	empty: function() {
+		// Remove element nodes and prevent memory leaks
+		jQuery( ">*", this ).remove();
+		
+		// Remove any remaining nodes
+		while ( this.firstChild )
+			this.removeChild( this.firstChild );
+	}
+}, function(name, fn){
+	jQuery.fn[ name ] = function(){
+		return this.each( fn, arguments );
+	};
+});
+
+jQuery.each([ "Height", "Width" ], function(i, name){
+	var type = name.toLowerCase();
+	
+	jQuery.fn[ type ] = function( size ) {
+		// Get window width or height
+		return this[0] == window ?
+			// Opera reports document.body.client[Width/Height] properly in both quirks and standards
+			jQuery.browser.opera && document.body[ "client" + name ] || 
+			
+			// Safari reports inner[Width/Height] just fine (Mozilla and Opera include scroll bar widths)
+			jQuery.browser.safari && window[ "inner" + name ] ||
+			
+			// Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
+			document.compatMode == "CSS1Compat" && document.documentElement[ "client" + name ] || document.body[ "client" + name ] :
+		
+			// Get document width or height
+			this[0] == document ?
+				// Either scroll[Width/Height] or offset[Width/Height], whichever is greater
+				Math.max( 
+					Math.max(document.body["scroll" + name], document.documentElement["scroll" + name]), 
+					Math.max(document.body["offset" + name], document.documentElement["offset" + name]) 
+				) :
+
+				// Get or set width or height on the element
+				size == undefined ?
+					// Get width or height on the element
+					(this.length ? jQuery.css( this[0], type ) : null) :
+
+					// Set the width or height on the element (default to pixels if value is unitless)
+					this.css( type, size.constructor == String ? size : size + "px" );
+	};
+});
+
+var chars = jQuery.browser.safari && parseInt(jQuery.browser.version) < 417 ?
+		"(?:[\\w*_-]|\\\\.)" :
+		"(?:[\\w\u0128-\uFFFF*_-]|\\\\.)",
+	quickChild = new RegExp("^>\\s*(" + chars + "+)"),
+	quickID = new RegExp("^(" + chars + "+)(#)(" + chars + "+)"),
+	quickClass = new RegExp("^([#.]?)(" + chars + "*)");
+
+jQuery.extend({
+	expr: {
+		"": "m[2]=='*'||jQuery.nodeName(a,m[2])",
+		"#": "a.getAttribute('id')==m[2]",
+		":": {
+			// Position Checks
+			lt: "i<m[3]-0",
+			gt: "i>m[3]-0",
+			nth: "m[3]-0==i",
+			eq: "m[3]-0==i",
+			first: "i==0",
+			last: "i==r.length-1",
+			even: "i%2==0",
+			odd: "i%2",
+
+			// Child Checks
+			"first-child": "a.parentNode.getElementsByTagName('*')[0]==a",
+			"last-child": "jQuery.nth(a.parentNode.lastChild,1,'previousSibling')==a",
+			"only-child": "!jQuery.nth(a.parentNode.lastChild,2,'previousSibling')",
+
+			// Parent Checks
+			parent: "a.firstChild",
+			empty: "!a.firstChild",
+
+			// Text Check
+			contains: "(a.textContent||a.innerText||jQuery(a).text()||'').indexOf(m[3])>=0",
+
+			// Visibility
+			visible: '"hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden"',
+			hidden: '"hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden"',
+
+			// Form attributes
+			enabled: "!a.disabled",
+			disabled: "a.disabled",
+			checked: "a.checked",
+			selected: "a.selected||jQuery.attr(a,'selected')",
+
+			// Form elements
+			text: "'text'==a.type",
+			radio: "'radio'==a.type",
+			checkbox: "'checkbox'==a.type",
+			file: "'file'==a.type",
+			password: "'password'==a.type",
+			submit: "'submit'==a.type",
+			image: "'image'==a.type",
+			reset: "'reset'==a.type",
+			button: '"button"==a.type||jQuery.nodeName(a,"button")',
+			input: "/input|select|textarea|button/i.test(a.nodeName)",
+
+			// :has()
+			has: "jQuery.find(m[3],a).length",
+
+			// :header
+			header: "/h\\d/i.test(a.nodeName)",
+
+			// :animated
+			animated: "jQuery.grep(jQuery.timers,function(fn){return a==fn.elem;}).length"
+		}
+	},
+	
+	// The regular expressions that power the parsing engine
+	parse: [
+		// Match: [@value='test'], [@foo]
+		/^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,
+
+		// Match: :contains('foo')
+		/^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/,
+
+		// Match: :even, :last-chlid, #id, .class
+		new RegExp("^([:.#]*)(" + chars + "+)")
+	],
+
+	multiFilter: function( expr, elems, not ) {
+		var old, cur = [];
+
+		while ( expr && expr != old ) {
+			old = expr;
+			var f = jQuery.filter( expr, elems, not );
+			expr = f.t.replace(/^\s*,\s*/, "" );
+			cur = not ? elems = f.r : jQuery.merge( cur, f.r );
+		}
+
+		return cur;
+	},
+
+	find: function( t, context ) {
+		// Quickly handle non-string expressions
+		if ( typeof t != "string" )
+			return [ t ];
+
+		// check to make sure context is a DOM element or a document
+		if ( context && context.nodeType != 1 && context.nodeType != 9)
+			return [ ];
+
+		// Set the correct context (if none is provided)
+		context = context || document;
+
+		// Initialize the search
+		var ret = [context], done = [], last, nodeName;
+
+		// Continue while a selector expression exists, and while
+		// we're no longer looping upon ourselves
+		while ( t && last != t ) {
+			var r = [];
+			last = t;
+
+			t = jQuery.trim(t);
+
+			var foundToken = false;
+
+			// An attempt at speeding up child selectors that
+			// point to a specific element tag
+			var re = quickChild;
+			var m = re.exec(t);
+
+			if ( m ) {
+				nodeName = m[1].toUpperCase();
+
+				// Perform our own iteration and filter
+				for ( var i = 0; ret[i]; i++ )
+					for ( var c = ret[i].firstChild; c; c = c.nextSibling )
+						if ( c.nodeType == 1 && (nodeName == "*" || c.nodeName.toUpperCase() == nodeName) )
+							r.push( c );
+
+				ret = r;
+				t = t.replace( re, "" );
+				if ( t.indexOf(" ") == 0 ) continue;
+				foundToken = true;
+			} else {
+				re = /^([>+~])\s*(\w*)/i;
+
+				if ( (m = re.exec(t)) != null ) {
+					r = [];
+
+					nodeName = m[2].toUpperCase(), merge = {};
+					m = m[1];
+
+					for ( var j = 0, rl = ret.length; j < rl; j++ ) {
+						var n = m == "~" || m == "+" ? ret[j].nextSibling : ret[j].firstChild;
+						for ( ; n; n = n.nextSibling )
+							if ( n.nodeType == 1 ) {
+								var id = jQuery.data(n);
+
+								if ( m == "~" && merge[id] ) break;
+								
+								if (!nodeName || n.nodeName.toUpperCase() == nodeName ) {
+									if ( m == "~" ) merge[id] = true;
+									r.push( n );
+								}
+								
+								if ( m == "+" ) break;
+							}
+					}
+
+					ret = r;
+
+					// And remove the token
+					t = jQuery.trim( t.replace( re, "" ) );
+					foundToken = true;
+				}
+			}
+
+			// See if there's still an expression, and that we haven't already
+			// matched a token
+			if ( t && !foundToken ) {
+				// Handle multiple expressions
+				if ( !t.indexOf(",") ) {
+					// Clean the result set
+					if ( context == ret[0] ) ret.shift();
+
+					// Merge the result sets
+					done = jQuery.merge( done, ret );
+
+					// Reset the context
+					r = ret = [context];
+
+					// Touch up the selector string
+					t = " " + t.substr(1,t.length);
+
+				} else {
+					// Optimize for the case nodeName#idName
+					var re2 = quickID;
+					var m = re2.exec(t);
+					
+					// Re-organize the results, so that they're consistent
+					if ( m ) {
+						m = [ 0, m[2], m[3], m[1] ];
+
+					} else {
+						// Otherwise, do a traditional filter check for
+						// ID, class, and element selectors
+						re2 = quickClass;
+						m = re2.exec(t);
+					}
+
+					m[2] = m[2].replace(/\\/g, "");
+
+					var elem = ret[ret.length-1];
+
+					// Try to do a global search by ID, where we can
+					if ( m[1] == "#" && elem && elem.getElementById && !jQuery.isXMLDoc(elem) ) {
+						// Optimization for HTML document case
+						var oid = elem.getElementById(m[2]);
+						
+						// Do a quick check for the existence of the actual ID attribute
+						// to avoid selecting by the name attribute in IE
+						// also check to insure id is a string to avoid selecting an element with the name of 'id' inside a form
+						if ( (jQuery.browser.msie||jQuery.browser.opera) && oid && typeof oid.id == "string" && oid.id != m[2] )
+							oid = jQuery('[@id="'+m[2]+'"]', elem)[0];
+
+						// Do a quick check for node name (where applicable) so
+						// that div#foo searches will be really fast
+						ret = r = oid && (!m[3] || jQuery.nodeName(oid, m[3])) ? [oid] : [];
+					} else {
+						// We need to find all descendant elements
+						for ( var i = 0; ret[i]; i++ ) {
+							// Grab the tag name being searched for
+							var tag = m[1] == "#" && m[3] ? m[3] : m[1] != "" || m[0] == "" ? "*" : m[2];
+
+							// Handle IE7 being really dumb about <object>s
+							if ( tag == "*" && ret[i].nodeName.toLowerCase() == "object" )
+								tag = "param";
+
+							r = jQuery.merge( r, ret[i].getElementsByTagName( tag ));
+						}
+
+						// It's faster to filter by class and be done with it
+						if ( m[1] == "." )
+							r = jQuery.classFilter( r, m[2] );
+
+						// Same with ID filtering
+						if ( m[1] == "#" ) {
+							var tmp = [];
+
+							// Try to find the element with the ID
+							for ( var i = 0; r[i]; i++ )
+								if ( r[i].getAttribute("id") == m[2] ) {
+									tmp = [ r[i] ];
+									break;
+								}
+
+							r = tmp;
+						}
+
+						ret = r;
+					}
+
+					t = t.replace( re2, "" );
+				}
+
+			}
+
+			// If a selector string still exists
+			if ( t ) {
+				// Attempt to filter it
+				var val = jQuery.filter(t,r);
+				ret = r = val.r;
+				t = jQuery.trim(val.t);
+			}
+		}
+
+		// An error occurred with the selector;
+		// just return an empty set instead
+		if ( t )
+			ret = [];
+
+		// Remove the root context
+		if ( ret && context == ret[0] )
+			ret.shift();
+
+		// And combine the results
+		done = jQuery.merge( done, ret );
+
+		return done;
+	},
+
+	classFilter: function(r,m,not){
+		m = " " + m + " ";
+		var tmp = [];
+		for ( var i = 0; r[i]; i++ ) {
+			var pass = (" " + r[i].className + " ").indexOf( m ) >= 0;
+			if ( !not && pass || not && !pass )
+				tmp.push( r[i] );
+		}
+		return tmp;
+	},
+
+	filter: function(t,r,not) {
+		var last;
+
+		// Look for common filter expressions
+		while ( t && t != last ) {
+			last = t;
+
+			var p = jQuery.parse, m;
+
+			for ( var i = 0; p[i]; i++ ) {
+				m = p[i].exec( t );
+
+				if ( m ) {
+					// Remove what we just matched
+					t = t.substring( m[0].length );
+
+					m[2] = m[2].replace(/\\/g, "");
+					break;
+				}
+			}
+
+			if ( !m )
+				break;
+
+			// :not() is a special case that can be optimized by
+			// keeping it out of the expression list
+			if ( m[1] == ":" && m[2] == "not" )
+				// optimize if only one selector found (most common case)
+				r = isSimple.test( m[3] ) ?
+					jQuery.filter(m[3], r, true).r :
+					jQuery( r ).not( m[3] );
+
+			// We can get a big speed boost by filtering by class here
+			else if ( m[1] == "." )
+				r = jQuery.classFilter(r, m[2], not);
+
+			else if ( m[1] == "[" ) {
+				var tmp = [], type = m[3];
+				
+				for ( var i = 0, rl = r.length; i < rl; i++ ) {
+					var a = r[i], z = a[ jQuery.props[m[2]] || m[2] ];
+					
+					if ( z == null || /href|src|selected/.test(m[2]) )
+						z = jQuery.attr(a,m[2]) || '';
+
+					if ( (type == "" && !!z ||
+						 type == "=" && z == m[5] ||
+						 type == "!=" && z != m[5] ||
+						 type == "^=" && z && !z.indexOf(m[5]) ||
+						 type == "$=" && z.substr(z.length - m[5].length) == m[5] ||
+						 (type == "*=" || type == "~=") && z.indexOf(m[5]) >= 0) ^ not )
+							tmp.push( a );
+				}
+				
+				r = tmp;
+
+			// We can get a speed boost by handling nth-child here
+			} else if ( m[1] == ":" && m[2] == "nth-child" ) {
+				var merge = {}, tmp = [],
+					// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+					test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
+						m[3] == "even" && "2n" || m[3] == "odd" && "2n+1" ||
+						!/\D/.test(m[3]) && "0n+" + m[3] || m[3]),
+					// calculate the numbers (first)n+(last) including if they are negative
+					first = (test[1] + (test[2] || 1)) - 0, last = test[3] - 0;
+ 
+				// loop through all the elements left in the jQuery object
+				for ( var i = 0, rl = r.length; i < rl; i++ ) {
+					var node = r[i], parentNode = node.parentNode, id = jQuery.data(parentNode);
+
+					if ( !merge[id] ) {
+						var c = 1;
+
+						for ( var n = parentNode.firstChild; n; n = n.nextSibling )
+							if ( n.nodeType == 1 )
+								n.nodeIndex = c++;
+
+						merge[id] = true;
+					}
+
+					var add = false;
+
+					if ( first == 0 ) {
+						if ( node.nodeIndex == last )
+							add = true;
+					} else if ( (node.nodeIndex - last) % first == 0 && (node.nodeIndex - last) / first >= 0 )
+						add = true;
+
+					if ( add ^ not )
+						tmp.push( node );
+				}
+
+				r = tmp;
+
+			// Otherwise, find the expression to execute
+			} else {
+				var f = jQuery.expr[m[1]];
+				if ( typeof f != "string" )
+					f = jQuery.expr[m[1]][m[2]];
+
+				// Build a custom macro to enclose it
+				f = eval("false||function(a,i){return " + f + "}");
+
+				// Execute it against the current filter
+				r = jQuery.grep( r, f, not );
+			}
+		}
+
+		// Return an array of filtered elements (r)
+		// and the modified expression string (t)
+		return { r: r, t: t };
+	},
+
+	dir: function( elem, dir ){
+		var matched = [];
+		var cur = elem[dir];
+		while ( cur && cur != document ) {
+			if ( cur.nodeType == 1 )
+				matched.push( cur );
+			cur = cur[dir];
+		}
+		return matched;
+	},
+	
+	nth: function(cur,result,dir,elem){
+		result = result || 1;
+		var num = 0;
+
+		for ( ; cur; cur = cur[dir] )
+			if ( cur.nodeType == 1 && ++num == result )
+				break;
+
+		return cur;
+	},
+	
+	sibling: function( n, elem ) {
+		var r = [];
+
+		for ( ; n; n = n.nextSibling ) {
+			if ( n.nodeType == 1 && (!elem || n != elem) )
+				r.push( n );
+		}
+
+		return r;
+	}
+});
+
+/*
+
+ * A number of helper functions used for managing events.
+
+ * Many of the ideas behind this code orignated from 
+
+ * Dean Edwards' addEvent library.
+
+ */
+
+jQuery.event = {
+
+
+
+	// Bind an event to an element
+
+	// Original by Dean Edwards
+
+	add: function(elem, types, handler, data) {
+
+		if ( elem.nodeType == 3 || elem.nodeType == 8 )
+
+			return;
+
+
+
+		// For whatever reason, IE has trouble passing the window object
+
+		// around, causing it to be cloned in the process
+
+		if ( jQuery.browser.msie && elem.setInterval != undefined )
+
+			elem = window;
+
+
+
+		// Make sure that the function being executed has a unique ID
+
+		if ( !handler.guid )
+
+			handler.guid = this.guid++;
+
+			
+
+		// if data is passed, bind to handler 
+
+		if( data != undefined ) { 
+
+			// Create temporary function pointer to original handler 
+
+			var fn = handler; 
+
+
+
+			// Create unique handler function, wrapped around original handler 
+
+			handler = function() { 
+
+				// Pass arguments and context to original handler 
+
+				return fn.apply(this, arguments); 
+
+			};
+
+
+
+			// Store data in unique handler 
+
+			handler.data = data;
+
+
+
+			// Set the guid of unique handler to the same of original handler, so it can be removed 
+
+			handler.guid = fn.guid;
+
+		}
+
+
+
+		// Init the element's event structure
+
+		var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}),
+
+			handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){
+
+				// returned undefined or false
+
+				var val;
+
+
+
+				// Handle the second event of a trigger and when
+
+				// an event is called after a page has unloaded
+
+				if ( typeof jQuery == "undefined" || jQuery.event.triggered )
+
+					return val;
+
+		
+
+				val = jQuery.event.handle.apply(elem, arguments);
+
+		
+
+				return val;
+
+			});
+
+			
+
+			// Handle multiple events seperated by a space
+
+			// jQuery(...).bind("mouseover mouseout", fn);
+
+			jQuery.each(types.split(/\s+/), function(index, type) {
+
+				// Namespaced event handlers
+
+				var parts = type.split(".");
+
+				type = parts[0];
+
+				handler.type = parts[1];
+
+
+
+				// Get the current list of functions bound to this event
+
+				var handlers = events[type];
+
+
+
+				// Init the event handler queue
+
+				if (!handlers) {
+
+					handlers = events[type] = {};
+
+		
+
+					// Check for a special event handler
+
+					// Only use addEventListener/attachEvent if the special
+
+					// events handler returns false
+
+					if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem) === false ) {
+
+						// Bind the global event handler to the element
+
+						if (elem.addEventListener)
+
+							elem.addEventListener(type, handle, false);
+
+						else if (elem.attachEvent)
+
+							elem.attachEvent("on" + type, handle);
+
+					}
+
+				}
+
+
+
+				// Add the function to the element's handler list
+
+				handlers[handler.guid] = handler;
+
+
+
+				// Keep track of which events have been used, for global triggering
+
+				jQuery.event.global[type] = true;
+
+			});
+
+	},
+
+
+
+	guid: 1,
+
+	global: {},
+
+
+
+	// Detach an event or set of events from an element
+
+	remove: function(elem, types, handler) {
+
+		// don't do events on text and comment nodes
+
+		if ( elem.nodeType == 3 || elem.nodeType == 8 )
+
+			return;
+
+
+
+		var events = jQuery.data(elem, "events"), ret, index;
+
+
+
+		if ( events ) {
+
+			// Unbind all events for the element
+
+			if ( !types )
+
+				for ( var type in events )
+
+					this.remove( elem, type );
+
+			else {
+
+				// types is actually an event object here
+
+				if ( types.type ) {
+
+					handler = types.handler;
+
+					types = types.type;
+
+				}
+
+				
+
+				// Handle multiple events seperated by a space
+
+				// jQuery(...).unbind("mouseover mouseout", fn);
+
+				jQuery.each(types.split(/\s+/), function(index, type){
+
+					// Namespaced event handlers
+
+					var parts = type.split(".");
+
+					type = parts[0];
+
+					
+
+					if ( events[type] ) {
+
+						// remove the given handler for the given type
+
+						if ( handler )
+
+							delete events[type][handler.guid];
+
+			
+
+						// remove all handlers for the given type
+
+						else
+
+							for ( handler in events[type] )
+
+								// Handle the removal of namespaced events
+
+								if ( !parts[1] || events[type][handler].type == parts[1] )
+
+									delete events[type][handler];
+
+
+
+						// remove generic event handler if no more handlers exist
+
+						for ( ret in events[type] ) break;
+
+						if ( !ret ) {
+
+							if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem) === false ) {
+
+								if (elem.removeEventListener)
+
+									elem.removeEventListener(type, jQuery.data(elem, "handle"), false);
+
+								else if (elem.detachEvent)
+
+									elem.detachEvent("on" + type, jQuery.data(elem, "handle"));
+
+							}
+
+							ret = null;
+
+							delete events[type];
+
+						}
+
+					}
+
+				});
+
+			}
+
+
+
+			// Remove the expando if it's no longer used
+
+			for ( ret in events ) break;
+
+			if ( !ret ) {
+
+				jQuery.removeData( elem, "events" );
+
+				jQuery.removeData( elem, "handle" );
+
+			}
+
+		}
+
+	},
+
+
+
+	trigger: function(type, data, elem, donative, extra) {
+
+		// Clone the incoming data, if any
+
+		data = jQuery.makeArray(data || []);
+
+
+
+		// Handle a global trigger
+
+		if ( !elem ) {
+
+			// Only trigger if we've ever bound an event for it
+
+			if ( this.global[type] )
+
+				jQuery("*").add([window, document]).trigger(type, data);
+
+
+
+		// Handle triggering a single element
+
+		} else {
+
+			// don't do events on text and comment nodes
+
+			if ( elem.nodeType == 3 || elem.nodeType == 8 )
+
+				return undefined;
+
+
+
+			var val, ret, fn = jQuery.isFunction( elem[ type ] || null ),
+
+				// Check to see if we need to provide a fake event, or not
+
+				event = !data[0] || !data[0].preventDefault;
+
+			
+
+			// Pass along a fake event
+
+			if ( event )
+
+				data.unshift( this.fix({ type: type, target: elem }) );
+
+
+
+			// Enforce the right trigger type
+
+			data[0].type = type;
+
+
+
+			// Trigger the event
+
+			if ( jQuery.isFunction( jQuery.data(elem, "handle") ) )
+
+				val = jQuery.data(elem, "handle").apply( elem, data );
+
+
+
+			// Handle triggering native .onfoo handlers
+
+			if ( !fn && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
+
+				val = false;
+
+
+
+			// Extra functions don't get the custom event object
+
+			if ( event )
+
+				data.shift();
+
+
+
+			// Handle triggering of extra function
+
+			if ( extra && jQuery.isFunction( extra ) ) {
+
+				// call the extra function and tack the current return value on the end for possible inspection
+
+				var ret = extra.apply( elem, data.concat( val ) );
+
+				// if anything is returned, give it precedence and have it overwrite the previous value
+
+				if (ret !== undefined)
+
+					val = ret;
+
+			}
+
+
+
+			// Trigger the native events (except for clicks on links)
+
+			if ( fn && donative !== false && val !== false && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
+
+				this.triggered = true;
+
+				try {
+
+					elem[ type ]();
+
+				// prevent IE from throwing an error for some hidden elements
+
+				} catch (e) {}
+
+			}
+
+
+
+			this.triggered = false;
+
+		}
+
+
+
+		return val;
+
+	},
+
+
+
+	handle: function(event) {
+
+		// returned undefined or false
+
+		var val;
+
+
+
+		// Empty object is for triggered events with no data
+
+		event = jQuery.event.fix( event || window.event || {} ); 
+
+
+
+		// Namespaced event handlers
+
+		var parts = event.type.split(".");
+
+		event.type = parts[0];
+
+
+
+		var handlers = jQuery.data(this, "events") && jQuery.data(this, "events")[event.type], args = Array.prototype.slice.call( arguments, 1 );
+
+		args.unshift( event );
+
+
+
+		for ( var j in handlers ) {
+
+			var handler = handlers[j];
+
+			// Pass in a reference to the handler function itself
+
+			// So that we can later remove it
+
+			args[0].handler = handler;
+
+			args[0].data = handler.data;
+
+
+
+			// Filter the functions by class
+
+			if ( !parts[1] || handler.type == parts[1] ) {
+
+				var ret = handler.apply( this, args );
+
+
+
+				if ( val !== false )
+
+					val = ret;
+
+
+
+				if ( ret === false ) {
+
+					event.preventDefault();
+
+					event.stopPropagation();
+
+				}
+
+			}
+
+		}
+
+
+
+		// Clean up added properties in IE to prevent memory leak
+
+		if (jQuery.browser.msie)
+
+			event.target = event.preventDefault = event.stopPropagation =
+
+				event.handler = event.data = null;
+
+
+
+		return val;
+
+	},
+
+
+
+	fix: function(event) {
+
+		// store a copy of the original event object 
+
+		// and clone to set read-only properties
+
+		var originalEvent = event;
+
+		event = jQuery.extend({}, originalEvent);
+
+		
+
+		// add preventDefault and stopPropagation since 
+
+		// they will not work on the clone
+
+		event.preventDefault = function() {
+
+			// if preventDefault exists run it on the original event
+
+			if (originalEvent.preventDefault)
+
+				originalEvent.preventDefault();
+
+			// otherwise set the returnValue property of the original event to false (IE)
+
+			originalEvent.returnValue = false;
+
+		};
+
+		event.stopPropagation = function() {
+
+			// if stopPropagation exists run it on the original event
+
+			if (originalEvent.stopPropagation)
+
+				originalEvent.stopPropagation();
+
+			// otherwise set the cancelBubble property of the original event to true (IE)
+
+			originalEvent.cancelBubble = true;
+
+		};
+
+		
+
+		// Fix target property, if necessary
+
+		if ( !event.target )
+
+			event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
+
+				
+
+		// check if target is a textnode (safari)
+
+		if ( event.target.nodeType == 3 )
+
+			event.target = originalEvent.target.parentNode;
+
+
+
+		// Add relatedTarget, if necessary
+
+		if ( !event.relatedTarget && event.fromElement )
+
+			event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
+
+
+
+		// Calculate pageX/Y if missing and clientX/Y available
+
+		if ( event.pageX == null && event.clientX != null ) {
+
+			var doc = document.documentElement, body = document.body;
+
+			event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
+
+			event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
+
+		}
+
+			
+
+		// Add which for key events
+
+		if ( !event.which && (event.charCode || event.keyCode) )
+
+			event.which = event.charCode || event.keyCode;
+
+		
+
+		// Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
+
+		if ( !event.metaKey && event.ctrlKey )
+
+			event.metaKey = event.ctrlKey;
+
+
+
+		// Add which for click: 1 == left; 2 == middle; 3 == right
+
+		// Note: button is not normalized, so don't use it
+
+		if ( !event.which && event.button )
+
+			event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
+
+			
+
+		return event;
+
+	},
+
+	
+
+	special: {
+
+		ready: {
+
+			setup: function() {
+
+				// Make sure the ready event is setup
+
+				bindReady();
+
+				return;
+
+			},
+
+			
+
+			teardown: function() { return; }
+
+		},
+
+		
+
+		mouseenter: {
+
+			setup: function() {
+
+				if ( jQuery.browser.msie ) return false;
+
+				jQuery(this).bind("mouseover", jQuery.event.special.mouseenter.handler);
+
+				return true;
+
+			},
+
+		
+
+			teardown: function() {
+
+				if ( jQuery.browser.msie ) return false;
+
+				jQuery(this).unbind("mouseover", jQuery.event.special.mouseenter.handler);
+
+				return true;
+
+			},
+
+			
+
+			handler: function(event) {
+
+				// If we actually just moused on to a sub-element, ignore it
+
+				if ( withinElement(event, this) ) return true;
+
+				// Execute the right handlers by setting the event type to mouseenter
+
+				arguments[0].type = "mouseenter";
+
+				return jQuery.event.handle.apply(this, arguments);
+
+			}
+
+		},
+
+	
+
+		mouseleave: {
+
+			setup: function() {
+
+				if ( jQuery.browser.msie ) return false;
+
+				jQuery(this).bind("mouseout", jQuery.event.special.mouseleave.handler);
+
+				return true;
+
+			},
+
+		
+
+			teardown: function() {
+
+				if ( jQuery.browser.msie ) return false;
+
+				jQuery(this).unbind("mouseout", jQuery.event.special.mouseleave.handler);
+
+				return true;
+
+			},
+
+			
+
+			handler: function(event) {
+
+				// If we actually just moused on to a sub-element, ignore it
+
+				if ( withinElement(event, this) ) return true;
+
+				// Execute the right handlers by setting the event type to mouseleave
+
+				arguments[0].type = "mouseleave";
+
+				return jQuery.event.handle.apply(this, arguments);
+
+			}
+
+		}
+
+	}
+
+};
+
+
+
+jQuery.fn.extend({
+
+	bind: function( type, data, fn ) {
+
+		return type == "unload" ? this.one(type, data, fn) : this.each(function(){
+
+			jQuery.event.add( this, type, fn || data, fn && data );
+
+		});
+
+	},
+
+	
+
+	one: function( type, data, fn ) {
+
+		return this.each(function(){
+
+			jQuery.event.add( this, type, function(event) {
+
+				jQuery(this).unbind(event);
+
+				return (fn || data).apply( this, arguments);
+
+			}, fn && data);
+
+		});
+
+	},
+
+
+
+	unbind: function( type, fn ) {
+
+		return this.each(function(){
+
+			jQuery.event.remove( this, type, fn );
+
+		});
+
+	},
+
+
+
+	trigger: function( type, data, fn ) {
+
+		return this.each(function(){
+
+			jQuery.event.trigger( type, data, this, true, fn );
+
+		});
+
+	},
+
+
+
+	triggerHandler: function( type, data, fn ) {
+
+		if ( this[0] )
+
+			return jQuery.event.trigger( type, data, this[0], false, fn );
+
+		return undefined;
+
+	},
+
+
+
+	toggle: function() {
+
+		// Save reference to arguments for access in closure
+
+		var args = arguments;
+
+
+
+		return this.click(function(event) {
+
+			// Figure out which function to execute
+
+			this.lastToggle = 0 == this.lastToggle ? 1 : 0;
+
+			
+
+			// Make sure that clicks stop
+
+			event.preventDefault();
+
+			
+
+			// and execute the function
+
+			return args[this.lastToggle].apply( this, arguments ) || false;
+
+		});
+
+	},
+
+
+
+	hover: function(fnOver, fnOut) {
+
+		return this.bind('mouseenter', fnOver).bind('mouseleave', fnOut);
+
+	},
+
+	
+
+	ready: function(fn) {
+
+		// Attach the listeners
+
+		bindReady();
+
+
+
+		// If the DOM is already ready
+
+		if ( jQuery.isReady )
+
+			// Execute the function immediately
+
+			fn.call( document, jQuery );
+
+			
+
+		// Otherwise, remember the function for later
+
+		else
+
+			// Add the function to the wait list
+
+			jQuery.readyList.push( function() { return fn.call(this, jQuery); } );
+
+	
+
+		return this;
+
+	}
+
+});
+
+
+
+jQuery.extend({
+
+	isReady: false,
+
+	readyList: [],
+
+	// Handle when the DOM is ready
+
+	ready: function() {
+
+		// Make sure that the DOM is not already loaded
+
+		if ( !jQuery.isReady ) {
+
+			// Remember that the DOM is ready
+
+			jQuery.isReady = true;
+
+			
+
+			// If there are functions bound, to execute
+
+			if ( jQuery.readyList ) {
+
+				// Execute all of them
+
+				jQuery.each( jQuery.readyList, function(){
+
+					this.apply( document );
+
+				});
+
+				
+
+				// Reset the list of functions
+
+				jQuery.readyList = null;
+
+			}
+
+		
+
+			// Trigger any bound ready events
+
+			jQuery(document).triggerHandler("ready");
+
+		}
+
+	}
+
+});
+
+
+
+var readyBound = false;
+
+
+
+function bindReady(){
+
+	if ( readyBound ) return;
+
+	readyBound = true;
+
+
+
+	// Mozilla, Opera and webkit nightlies currently support this event
+
+	if ( document.addEventListener )
+
+		// Use the handy event callback
+
+		document.addEventListener( "DOMContentLoaded", jQuery.ready, false );
+
+	
+
+	// If Safari or IE is used
+
+	// Continually check to see if the document is ready
+
+	if (jQuery.browser.msie || jQuery.browser.safari ) (function(){
+
+		try {
+
+			// If IE is used, use the trick by Diego Perini
+
+			// http://javascript.nwbox.com/IEContentLoaded/
+
+			if ( jQuery.browser.msie || document.readyState != "loaded" && document.readyState != "complete" )
+
+				document.documentElement.doScroll("left");
+
+		} catch( error ) {
+
+			return setTimeout( arguments.callee, 0 );
+
+		}
+
+
+
+		// and execute any waiting functions
+
+		jQuery.ready();
+
+	})();
+
+
+
+	// A fallback to window.onload, that will always work
+
+	jQuery.event.add( window, "load", jQuery.ready );
+
+}
+
+
+
+jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
+
+	"mousedown,mouseup,mousemove,mouseover,mouseout,change,select," + 
+
+	"submit,keydown,keypress,keyup,error").split(","), function(i, name){
+
+	
+
+	// Handle event binding
+
+	jQuery.fn[name] = function(fn){
+
+		return fn ? this.bind(name, fn) : this.trigger(name);
+
+	};
+
+});
+
+
+
+// Checks if an event happened on an element within another element
+
+// Used in jQuery.event.special.mouseenter and mouseleave handlers
+
+var withinElement = function(event, elem) {
+
+	// Check if mouse(over|out) are still within the same parent element
+
+	var parent = event.relatedTarget;
+
+	// Traverse up the tree
+
+	while ( parent && parent != elem ) try { parent = parent.parentNode } catch(error) { parent = elem; };
+
+	// Return true if we actually just moused on to a sub-element
+
+	return parent == elem;
+
+};
+
+
+
+// Prevent memory leaks in IE
+
+// And prevent errors on refresh with events like mouseover in other browsers
+
+// Window isn't included so as not to unbind existing unload events
+
+jQuery(window).bind("unload", function() {
+
+	jQuery("*").add(document).unbind();
+
+});
+
+jQuery.fn.extend({
+	load: function( url, params, callback ) {
+		if ( jQuery.isFunction( url ) )
+			return this.bind("load", url);
+
+		var off = url.indexOf(" ");
+		if ( off >= 0 ) {
+			var selector = url.slice(off, url.length);
+			url = url.slice(0, off);
+		}
+
+		callback = callback || function(){};
+
+		// Default to a GET request
+		var type = "GET";
+
+		// If the second parameter was provided
+		if ( params )
+			// If it's a function
+			if ( jQuery.isFunction( params ) ) {
+				// We assume that it's the callback
+				callback = params;
+				params = null;
+
+			// Otherwise, build a param string
+			} else {
+				params = jQuery.param( params );
+				type = "POST";
+			}
+
+		var self = this;
+
+		// Request the remote document
+		jQuery.ajax({
+			url: url,
+			type: type,
+			dataType: "html",
+			data: params,
+			complete: function(res, status){
+				// If successful, inject the HTML into all the matched elements
+				if ( status == "success" || status == "notmodified" )
+					// See if a selector was specified
+					self.html( selector ?
+						// Create a dummy div to hold the results
+						jQuery("<div/>")
+							// inject the contents of the document in, removing the scripts
+							// to avoid any 'Permission Denied' errors in IE
+							.append(res.responseText.replace(/<script(.|\s)*?\/script>/g, ""))
+
+							// Locate the specified elements
+							.find(selector) :
+
+						// If not, just inject the full result
+						res.responseText );
+
+				self.each( callback, [res.responseText, status, res] );
+			}
+		});
+		return this;
+	},
+
+	serialize: function() {
+		return jQuery.param(this.serializeArray());
+	},
+	serializeArray: function() {
+		return this.map(function(){
+			return jQuery.nodeName(this, "form") ?
+				jQuery.makeArray(this.elements) : this;
+		})
+		.filter(function(){
+			return this.name && !this.disabled && 
+				(this.checked || /select|textarea/i.test(this.nodeName) || 
+					/text|hidden|password/i.test(this.type));
+		})
+		.map(function(i, elem){
+			var val = jQuery(this).val();
+			return val == null ? null :
+				val.constructor == Array ?
+					jQuery.map( val, function(val, i){
+						return {name: elem.name, value: val};
+					}) :
+					{name: elem.name, value: val};
+		}).get();
+	}
+});
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( "ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","), function(i,o){
+	jQuery.fn[o] = function(f){
+		return this.bind(o, f);
+	};
+});
+
+var jsc = (new Date).getTime();
+
+jQuery.extend({
+	get: function( url, data, callback, type ) {
+		// shift arguments if data argument was ommited
+		if ( jQuery.isFunction( data ) ) {
+			callback = data;
+			data = null;
+		}
+		
+		return jQuery.ajax({
+			type: "GET",
+			url: url,
+			data: data,
+			success: callback,
+			dataType: type
+		});
+	},
+
+	getScript: function( url, callback ) {
+		return jQuery.get(url, null, callback, "script");
+	},
+
+	getJSON: function( url, data, callback ) {
+		return jQuery.get(url, data, callback, "json");
+	},
+
+	post: function( url, data, callback, type ) {
+		if ( jQuery.isFunction( data ) ) {
+			callback = data;
+			data = {};
+		}
+
+		return jQuery.ajax({
+			type: "POST",
+			url: url,
+			data: data,
+			success: callback,
+			dataType: type
+		});
+	},
+
+	ajaxSetup: function( settings ) {
+		jQuery.extend( jQuery.ajaxSettings, settings );
+	},
+
+	ajaxSettings: {
+		global: true,
+		type: "GET",
+		timeout: 0,
+		contentType: "application/x-www-form-urlencoded",
+		processData: true,
+		async: true,
+		data: null
+	},
+	
+	// Last-Modified header cache for next request
+	lastModified: {},
+
+	ajax: function( s ) {
+		var jsonp, jsre = /=\?(&|$)/g, status, data;
+
+		// Extend the settings, but re-extend 's' so that it can be
+		// checked again later (in the test suite, specifically)
+		s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s));
+
+		// convert data if not already a string
+		if ( s.data && s.processData && typeof s.data != "string" )
+			s.data = jQuery.param(s.data);
+
+		// Handle JSONP Parameter Callbacks
+		if ( s.dataType == "jsonp" ) {
+			if ( s.type.toLowerCase() == "get" ) {
+				if ( !s.url.match(jsre) )
+					s.url += (s.url.match(/\?/) ? "&" : "?") + (s.jsonp || "callback") + "=?";
+			} else if ( !s.data || !s.data.match(jsre) )
+				s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?";
+			s.dataType = "json";
+		}
+
+		// Build temporary JSONP function
+		if ( s.dataType == "json" && (s.data && s.data.match(jsre) || s.url.match(jsre)) ) {
+			jsonp = "jsonp" + jsc++;
+
+			// Replace the =? sequence both in the query string and the data
+			if ( s.data )
+				s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
+			s.url = s.url.replace(jsre, "=" + jsonp + "$1");
+
+			// We need to make sure
+			// that a JSONP style response is executed properly
+			s.dataType = "script";
+
+			// Handle JSONP-style loading
+			window[ jsonp ] = function(tmp){
+				data = tmp;
+				success();
+				complete();
+				// Garbage collect
+				window[ jsonp ] = undefined;
+				try{ delete window[ jsonp ]; } catch(e){}
+				if ( head )
+					head.removeChild( script );
+			};
+		}
+
+		if ( s.dataType == "script" && s.cache == null )
+			s.cache = false;
+
+		if ( s.cache === false && s.type.toLowerCase() == "get" ) {
+			var ts = (new Date()).getTime();
+			// try replacing _= if it is there
+			var ret = s.url.replace(/(\?|&)_=.*?(&|$)/, "$1_=" + ts + "$2");
+			// if nothing was replaced, add timestamp to the end
+			s.url = ret + ((ret == s.url) ? (s.url.match(/\?/) ? "&" : "?") + "_=" + ts : "");
+		}
+
+		// If data is available, append data to url for get requests
+		if ( s.data && s.type.toLowerCase() == "get" ) {
+			s.url += (s.url.match(/\?/) ? "&" : "?") + s.data;
+
+			// IE likes to send both get and post data, prevent this
+			s.data = null;
+		}
+
+		// Watch for a new set of requests
+		if ( s.global && ! jQuery.active++ )
+			jQuery.event.trigger( "ajaxStart" );
+
+		// If we're requesting a remote document
+		// and trying to load JSON or Script with a GET
+		if ( (!s.url.indexOf("http") || !s.url.indexOf("//")) && ( s.dataType == "script" || s.dataType =="json" ) && s.type.toLowerCase() == "get" ) {
+			var head = document.getElementsByTagName("head")[0];
+			var script = document.createElement("script");
+			script.src = s.url;
+			if (s.scriptCharset)
+				script.charset = s.scriptCharset;
+
+			// Handle Script loading
+			if ( !jsonp ) {
+				var done = false;
+
+				// Attach handlers for all browsers
+				script.onload = script.onreadystatechange = function(){
+					if ( !done && (!this.readyState || 
+							this.readyState == "loaded" || this.readyState == "complete") ) {
+						done = true;
+						success();
+						complete();
+						head.removeChild( script );
+					}
+				};
+			}
+
+			head.appendChild(script);
+
+			// We handle everything using the script element injection
+			return undefined;
+		}
+
+		var requestDone = false;
+
+		// Create the request object; Microsoft failed to properly
+		// implement the XMLHttpRequest in IE7, so we use the ActiveXObject when it is available
+		var xml = window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();
+
+		// Open the socket
+		xml.open(s.type, s.url, s.async);
+
+		// Need an extra try/catch for cross domain requests in Firefox 3
+		try {
+			// Set the correct header, if data is being sent
+			if ( s.data )
+				xml.setRequestHeader("Content-Type", s.contentType);
+
+			// Set the If-Modified-Since header, if ifModified mode.
+			if ( s.ifModified )
+				xml.setRequestHeader("If-Modified-Since",
+					jQuery.lastModified[s.url] || "Thu, 01 Jan 1970 00:00:00 GMT" );
+
+			// Set header so the called script knows that it's an XMLHttpRequest
+			xml.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+		} catch(e){}
+
+		// Allow custom headers/mimetypes
+		if ( s.beforeSend )
+			s.beforeSend(xml);
+			
+		if ( s.global )
+			jQuery.event.trigger("ajaxSend", [xml, s]);
+
+		// Wait for a response to come back
+		var onreadystatechange = function(isTimeout){
+			// The transfer is complete and the data is available, or the request timed out
+			if ( !requestDone && xml && (xml.readyState == 4 || isTimeout == "timeout") ) {
+				requestDone = true;
+				
+				// clear poll interval
+				if (ival) {
+					clearInterval(ival);
+					ival = null;
+				}
+				
+				status = isTimeout == "timeout" && "timeout" ||
+					!jQuery.httpSuccess( xml ) && "error" ||
+					s.ifModified && jQuery.httpNotModified( xml, s.url ) && "notmodified" ||
+					"success";
+
+				if ( status == "success" ) {
+					// Watch for, and catch, XML document parse errors
+					try {
+						// process the data (runs the xml through httpData regardless of callback)
+						data = jQuery.httpData( xml, s.dataType );
+					} catch(e) {
+						status = "parsererror";
+					}
+				}
+
+				// Make sure that the request was successful or notmodified
+				if ( status == "success" ) {
+					// Cache Last-Modified header, if ifModified mode.
+					var modRes;
+					try {
+						modRes = xml.getResponseHeader("Last-Modified");
+					} catch(e) {} // swallow exception thrown by FF if header is not available
+	
+					if ( s.ifModified && modRes )
+						jQuery.lastModified[s.url] = modRes;
+
+					// JSONP handles its own success callback
+					if ( !jsonp )
+						success();	
+				} else
+					jQuery.handleError(s, xml, status);
+
+				// Fire the complete handlers
+				complete();
+
+				// Stop memory leaks
+				if ( s.async )
+					xml = null;
+			}
+		};
+		
+		if ( s.async ) {
+			// don't attach the handler to the request, just poll it instead
+			var ival = setInterval(onreadystatechange, 13); 
+
+			// Timeout checker
+			if ( s.timeout > 0 )
+				setTimeout(function(){
+					// Check to see if the request is still happening
+					if ( xml ) {
+						// Cancel the request
+						xml.abort();
+	
+						if( !requestDone )
+							onreadystatechange( "timeout" );
+					}
+				}, s.timeout);
+		}
+			
+		// Send the data
+		try {
+			xml.send(s.data);
+		} catch(e) {
+			jQuery.handleError(s, xml, null, e);
+		}
+		
+		// firefox 1.5 doesn't fire statechange for sync requests
+		if ( !s.async )
+			onreadystatechange();
+
+		function success(){
+			// If a local callback was specified, fire it and pass it the data
+			if ( s.success )
+				s.success( data, status );
+
+			// Fire the global callback
+			if ( s.global )
+				jQuery.event.trigger( "ajaxSuccess", [xml, s] );
+		}
+
+		function complete(){
+			// Process result
+			if ( s.complete )
+				s.complete(xml, status);
+
+			// The request was completed
+			if ( s.global )
+				jQuery.event.trigger( "ajaxComplete", [xml, s] );
+
+			// Handle the global AJAX counter
+			if ( s.global && ! --jQuery.active )
+				jQuery.event.trigger( "ajaxStop" );
+		}
+		
+		// return XMLHttpRequest to allow aborting the request etc.
+		return xml;
+	},
+
+	handleError: function( s, xml, status, e ) {
+		// If a local callback was specified, fire it
+		if ( s.error ) s.error( xml, status, e );
+
+		// Fire the global callback
+		if ( s.global )
+			jQuery.event.trigger( "ajaxError", [xml, s, e] );
+	},
+
+	// Counter for holding the number of active queries
+	active: 0,
+
+	// Determines if an XMLHttpRequest was successful or not
+	httpSuccess: function( r ) {
+		try {
+			// IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450
+			return !r.status && location.protocol == "file:" ||
+				( r.status >= 200 && r.status < 300 ) || r.status == 304 || r.status == 1223 ||
+				jQuery.browser.safari && r.status == undefined;
+		} catch(e){}
+		return false;
+	},
+
+	// Determines if an XMLHttpRequest returns NotModified
+	httpNotModified: function( xml, url ) {
+		try {
+			var xmlRes = xml.getResponseHeader("Last-Modified");
+
+			// Firefox always returns 200. check Last-Modified date
+			return xml.status == 304 || xmlRes == jQuery.lastModified[url] ||
+				jQuery.browser.safari && xml.status == undefined;
+		} catch(e){}
+		return false;
+	},
+
+	httpData: function( r, type ) {
+		var ct = r.getResponseHeader("content-type");
+		var xml = type == "xml" || !type && ct && ct.indexOf("xml") >= 0;
+		var data = xml ? r.responseXML : r.responseText;
+
+		if ( xml && data.documentElement.tagName == "parsererror" )
+			throw "parsererror";
+
+		// If the type is "script", eval it in global context
+		if ( type == "script" )
+			jQuery.globalEval( data );
+
+		// Get the JavaScript object, if JSON is used.
+		if ( type == "json" )
+			data = eval("(" + data + ")");
+
+		return data;
+	},
+
+	// Serialize an array of form elements or a set of
+	// key/values into a query string
+	param: function( a ) {
+		var s = [];
+
+		// If an array was passed in, assume that it is an array
+		// of form elements
+		if ( a.constructor == Array || a.jquery )
+			// Serialize the form elements
+			jQuery.each( a, function(){
+				s.push( encodeURIComponent(this.name) + "=" + encodeURIComponent( this.value ) );
+			});
+
+		// Otherwise, assume that it's an object of key/value pairs
+		else
+			// Serialize the key/values
+			for ( var j in a )
+				// If the value is an array then the key names need to be repeated
+				if ( a[j] && a[j].constructor == Array )
+					jQuery.each( a[j], function(){
+						s.push( encodeURIComponent(j) + "=" + encodeURIComponent( this ) );
+					});
+				else
+					s.push( encodeURIComponent(j) + "=" + encodeURIComponent( a[j] ) );
+
+		// Return the resulting serialization
+		return s.join("&").replace(/%20/g, "+");
+	}
+
+});
+jQuery.fn.extend({
+	show: function(speed,callback){
+		return speed ?
+			this.animate({
+				height: "show", width: "show", opacity: "show"
+			}, speed, callback) :
+			
+			this.filter(":hidden").each(function(){
+				this.style.display = this.oldblock || "";
+				if ( jQuery.css(this,"display") == "none" ) {
+					var elem = jQuery("<" + this.tagName + " />").appendTo("body");
+					this.style.display = elem.css("display");
+					elem.remove();
+				}
+			}).end();
+	},
+	
+	hide: function(speed,callback){
+		return speed ?
+			this.animate({
+				height: "hide", width: "hide", opacity: "hide"
+			}, speed, callback) :
+			
+			this.filter(":visible").each(function(){
+				this.oldblock = this.oldblock || jQuery.css(this,"display");
+				this.style.display = "none";
+			}).end();
+	},
+
+	// Save the old toggle function
+	_toggle: jQuery.fn.toggle,
+	
+	toggle: function( fn, fn2 ){
+		return jQuery.isFunction(fn) && jQuery.isFunction(fn2) ?
+			this._toggle( fn, fn2 ) :
+			fn ?
+				this.animate({
+					height: "toggle", width: "toggle", opacity: "toggle"
+				}, fn, fn2) :
+				this.each(function(){
+					jQuery(this)[ jQuery(this).is(":hidden") ? "show" : "hide" ]();
+				});
+	},
+	
+	slideDown: function(speed,callback){
+		return this.animate({height: "show"}, speed, callback);
+	},
+	
+	slideUp: function(speed,callback){
+		return this.animate({height: "hide"}, speed, callback);
+	},
+
+	slideToggle: function(speed, callback){
+		return this.animate({height: "toggle"}, speed, callback);
+	},
+	
+	fadeIn: function(speed, callback){
+		return this.animate({opacity: "show"}, speed, callback);
+	},
+	
+	fadeOut: function(speed, callback){
+		return this.animate({opacity: "hide"}, speed, callback);
+	},
+	
+	fadeTo: function(speed,to,callback){
+		return this.animate({opacity: to}, speed, callback);
+	},
+	
+	animate: function( prop, speed, easing, callback ) {
+		var optall = jQuery.speed(speed, easing, callback);
+
+		return this[ optall.queue === false ? "each" : "queue" ](function(){
+			if ( this.nodeType != 1)
+				return false;
+
+			var opt = jQuery.extend({}, optall);
+			var hidden = jQuery(this).is(":hidden"), self = this;
+			
+			for ( var p in prop ) {
+				if ( prop[p] == "hide" && hidden || prop[p] == "show" && !hidden )
+					return jQuery.isFunction(opt.complete) && opt.complete.apply(this);
+
+				if ( p == "height" || p == "width" ) {
+					// Store display property
+					opt.display = jQuery.css(this, "display");
+
+					// Make sure that nothing sneaks out
+					opt.overflow = this.style.overflow;
+				}
+			}
+
+			if ( opt.overflow != null )
+				this.style.overflow = "hidden";
+
+			opt.curAnim = jQuery.extend({}, prop);
+			
+			jQuery.each( prop, function(name, val){
+				var e = new jQuery.fx( self, opt, name );
+
+				if ( /toggle|show|hide/.test(val) )
+					e[ val == "toggle" ? hidden ? "show" : "hide" : val ]( prop );
+				else {
+					var parts = val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),
+						start = e.cur(true) || 0;
+
+					if ( parts ) {
+						var end = parseFloat(parts[2]),
+							unit = parts[3] || "px";
+
+						// We need to compute starting value
+						if ( unit != "px" ) {
+							self.style[ name ] = (end || 1) + unit;
+							start = ((end || 1) / e.cur(true)) * start;
+							self.style[ name ] = start + unit;
+						}
+
+						// If a +=/-= token was provided, we're doing a relative animation
+						if ( parts[1] )
+							end = ((parts[1] == "-=" ? -1 : 1) * end) + start;
+
+						e.custom( start, end, unit );
+					} else
+						e.custom( start, val, "" );
+				}
+			});
+
+			// For JS strict compliance
+			return true;
+		});
+	},
+	
+	queue: function(type, fn){
+		if ( jQuery.isFunction(type) || ( type && type.constructor == Array )) {
+			fn = type;
+			type = "fx";
+		}
+
+		if ( !type || (typeof type == "string" && !fn) )
+			return queue( this[0], type );
+
+		return this.each(function(){
+			if ( this.nodeType != 1)
+				return;
+
+			if ( fn.constructor == Array )
+				queue(this, type, fn);
+			else {
+				queue(this, type).push( fn );
+			
+				if ( queue(this, type).length == 1 )
+					fn.apply(this);
+			}
+		});
+	},
+
+	stop: function(clearQueue, gotoEnd){
+		var timers = jQuery.timers;
+
+		if (clearQueue)
+			this.queue([]);
+
+		this.each(function(){
+			// go in reverse order so anything added to the queue during the loop is ignored
+			for ( var i = timers.length - 1; i >= 0; i-- )
+				if ( timers[i].elem == this ) {
+					if (gotoEnd)
+						// force the next step to be the last
+						timers[i](true);
+					timers.splice(i, 1);
+				}
+		});
+
+		// start the next in the queue if the last step wasn't forced
+		if (!gotoEnd)
+			this.dequeue();
+
+		return this;
+	}
+
+});
+
+var queue = function( elem, type, array ) {
+	if ( !elem )
+		return undefined;
+
+	type = type || "fx";
+
+	var q = jQuery.data( elem, type + "queue" );
+
+	if ( !q || array )
+		q = jQuery.data( elem, type + "queue", 
+			array ? jQuery.makeArray(array) : [] );
+
+	return q;
+};
+
+jQuery.fn.dequeue = function(type){
+	type = type || "fx";
+
+	return this.each(function(){
+		var q = queue(this, type);
+
+		q.shift();
+
+		if ( q.length )
+			q[0].apply( this );
+	});
+};
+
+jQuery.extend({
+	
+	speed: function(speed, easing, fn) {
+		var opt = speed && speed.constructor == Object ? speed : {
+			complete: fn || !fn && easing || 
+				jQuery.isFunction( speed ) && speed,
+			duration: speed,
+			easing: fn && easing || easing && easing.constructor != Function && easing
+		};
+
+		opt.duration = (opt.duration && opt.duration.constructor == Number ? 
+			opt.duration : 
+			{ slow: 600, fast: 200 }[opt.duration]) || 400;
+	
+		// Queueing
+		opt.old = opt.complete;
+		opt.complete = function(){
+			if ( opt.queue !== false )
+				jQuery(this).dequeue();
+			if ( jQuery.isFunction( opt.old ) )
+				opt.old.apply( this );
+		};
+	
+		return opt;
+	},
+	
+	easing: {
+		linear: function( p, n, firstNum, diff ) {
+			return firstNum + diff * p;
+		},
+		swing: function( p, n, firstNum, diff ) {
+			return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
+		}
+	},
+	
+	timers: [],
+	timerId: null,
+
+	fx: function( elem, options, prop ){
+		this.options = options;
+		this.elem = elem;
+		this.prop = prop;
+
+		if ( !options.orig )
+			options.orig = {};
+	}
+
+});
+
+jQuery.fx.prototype = {
+
+	// Simple function for setting a style value
+	update: function(){
+		if ( this.options.step )
+			this.options.step.apply( this.elem, [ this.now, this ] );
+
+		(jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );
+
+		// Set display property to block for height/width animations
+		if ( this.prop == "height" || this.prop == "width" )
+			this.elem.style.display = "block";
+	},
+
+	// Get the current size
+	cur: function(force){
+		if ( this.elem[this.prop] != null && this.elem.style[this.prop] == null )
+			return this.elem[ this.prop ];
+
+		var r = parseFloat(jQuery.css(this.elem, this.prop, force));
+		return r && r > -10000 ? r : parseFloat(jQuery.curCSS(this.elem, this.prop)) || 0;
+	},
+
+	// Start an animation from one number to another
+	custom: function(from, to, unit){
+		this.startTime = (new Date()).getTime();
+		this.start = from;
+		this.end = to;
+		this.unit = unit || this.unit || "px";
+		this.now = this.start;
+		this.pos = this.state = 0;
+		this.update();
+
+		var self = this;
+		function t(gotoEnd){
+			return self.step(gotoEnd);
+		}
+
+		t.elem = this.elem;
+
+		jQuery.timers.push(t);
+
+		if ( jQuery.timerId == null ) {
+			jQuery.timerId = setInterval(function(){
+				var timers = jQuery.timers;
+				
+				for ( var i = 0; i < timers.length; i++ )
+					if ( !timers[i]() )
+						timers.splice(i--, 1);
+
+				if ( !timers.length ) {
+					clearInterval( jQuery.timerId );
+					jQuery.timerId = null;
+				}
+			}, 13);
+		}
+	},
+
+	// Simple 'show' function
+	show: function(){
+		// Remember where we started, so that we can go back to it later
+		this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
+		this.options.show = true;
+
+		// Begin the animation
+		this.custom(0, this.cur());
+
+		// Make sure that we start at a small width/height to avoid any
+		// flash of content
+		if ( this.prop == "width" || this.prop == "height" )
+			this.elem.style[this.prop] = "1px";
+		
+		// Start by showing the element
+		jQuery(this.elem).show();
+	},
+
+	// Simple 'hide' function
+	hide: function(){
+		// Remember where we started, so that we can go back to it later
+		this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
+		this.options.hide = true;
+
+		// Begin the animation
+		this.custom(this.cur(), 0);
+	},
+
+	// Each step of an animation
+	step: function(gotoEnd){
+		var t = (new Date()).getTime();
+
+		if ( gotoEnd || t > this.options.duration + this.startTime ) {
+			this.now = this.end;
+			this.pos = this.state = 1;
+			this.update();
+
+			this.options.curAnim[ this.prop ] = true;
+
+			var done = true;
+			for ( var i in this.options.curAnim )
+				if ( this.options.curAnim[i] !== true )
+					done = false;
+
+			if ( done ) {
+				if ( this.options.display != null ) {
+					// Reset the overflow
+					this.elem.style.overflow = this.options.overflow;
+				
+					// Reset the display
+					this.elem.style.display = this.options.display;
+					if ( jQuery.css(this.elem, "display") == "none" )
+						this.elem.style.display = "block";
+				}
+
+				// Hide the element if the "hide" operation was done
+				if ( this.options.hide )
+					this.elem.style.display = "none";
+
+				// Reset the properties, if the item has been hidden or shown
+				if ( this.options.hide || this.options.show )
+					for ( var p in this.options.curAnim )
+						jQuery.attr(this.elem.style, p, this.options.orig[p]);
+			}
+
+			// If a callback was provided, execute it
+			if ( done && jQuery.isFunction( this.options.complete ) )
+				// Execute the complete function
+				this.options.complete.apply( this.elem );
+
+			return false;
+		} else {
+			var n = t - this.startTime;
+			this.state = n / this.options.duration;
+
+			// Perform the easing function, defaults to swing
+			this.pos = jQuery.easing[this.options.easing || (jQuery.easing.swing ? "swing" : "linear")](this.state, n, 0, 1, this.options.duration);
+			this.now = this.start + ((this.end - this.start) * this.pos);
+
+			// Perform the next step of the animation
+			this.update();
+		}
+
+		return true;
+	}
+
+};
+
+jQuery.fx.step = {
+	scrollLeft: function(fx){
+		fx.elem.scrollLeft = fx.now;
+	},
+
+	scrollTop: function(fx){
+		fx.elem.scrollTop = fx.now;
+	},
+
+	opacity: function(fx){
+		jQuery.attr(fx.elem.style, "opacity", fx.now);
+	},
+
+	_default: function(fx){
+		fx.elem.style[ fx.prop ] = fx.now + fx.unit;
+	}
+};
+// The Offset Method
+// Originally By Brandon Aaron, part of the Dimension Plugin
+// http://jquery.com/plugins/project/dimensions
+jQuery.fn.offset = function() {
+	var left = 0, top = 0, elem = this[0], results;
+	
+	if ( elem ) with ( jQuery.browser ) {
+		var parent       = elem.parentNode, 
+		    offsetChild  = elem,
+		    offsetParent = elem.offsetParent, 
+		    doc          = elem.ownerDocument,
+		    safari2      = safari && parseInt(version) < 522,
+		    fixed        = jQuery.css(elem, "position") == "fixed";
+	
+		// Use getBoundingClientRect if available
+		if ( elem.getBoundingClientRect ) {
+			var box = elem.getBoundingClientRect();
+		
+			// Add the document scroll offsets
+			add(box.left + Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft),
+				box.top  + Math.max(doc.documentElement.scrollTop,  doc.body.scrollTop));
+		
+			// IE adds the HTML element's border, by default it is medium which is 2px
+			// IE 6 and 7 quirks mode the border width is overwritable by the following css html { border: 0; }
+			// IE 7 standards mode, the border is always 2px
+			// This border/offset is typically represented by the clientLeft and clientTop properties
+			// However, in IE6 and 7 quirks mode the clientLeft and clientTop properties are not updated when overwriting it via CSS
+			// Therefore this method will be off by 2px in IE while in quirksmode
+			add( -doc.documentElement.clientLeft, -doc.documentElement.clientTop );
+	
+		// Otherwise loop through the offsetParents and parentNodes
+		} else {
+		
+			// Initial element offsets
+			add( elem.offsetLeft, elem.offsetTop );
+			
+			// Get parent offsets
+			while ( offsetParent ) {
+				// Add offsetParent offsets
+				add( offsetParent.offsetLeft, offsetParent.offsetTop );
+			
+				// Mozilla and Safari > 2 does not include the border on offset parents
+				// However Mozilla adds the border for table or table cells
+				if ( mozilla && !/^t(able|d|h)$/i.test(offsetParent.tagName) || safari && !safari2 )
+					border( offsetParent );
+					
+				// Add the document scroll offsets if position is fixed on any offsetParent
+				if ( !fixed && jQuery.css(offsetParent, "position") == "fixed" )
+					fixed = true;
+			
+				// Set offsetChild to previous offsetParent unless it is the body element
+				offsetChild  = /^body$/i.test(offsetParent.tagName) ? offsetChild : offsetParent;
+				// Get next offsetParent
+				offsetParent = offsetParent.offsetParent;
+			}
+		
+			// Get parent scroll offsets
+			while ( parent.tagName && !/^body|html$/i.test(parent.tagName) ) {
+				// Remove parent scroll UNLESS that parent is inline or a table to work around Opera inline/table scrollLeft/Top bug
+				if ( !/^inline|table.*$/i.test(jQuery.css(parent, "display")) )
+					// Subtract parent scroll offsets
+					add( -parent.scrollLeft, -parent.scrollTop );
+			
+				// Mozilla does not add the border for a parent that has overflow != visible
+				if ( mozilla && jQuery.css(parent, "overflow") != "visible" )
+					border( parent );
+			
+				// Get next parent
+				parent = parent.parentNode;
+			}
+		
+			// Safari <= 2 doubles body offsets with a fixed position element/offsetParent or absolutely positioned offsetChild
+			// Mozilla doubles body offsets with a non-absolutely positioned offsetChild
+			if ( (safari2 && (fixed || jQuery.css(offsetChild, "position") == "absolute")) || 
+				(mozilla && jQuery.css(offsetChild, "position") != "absolute") )
+					add( -doc.body.offsetLeft, -doc.body.offsetTop );
+			
+			// Add the document scroll offsets if position is fixed
+			if ( fixed )
+				add(Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft),
+					Math.max(doc.documentElement.scrollTop,  doc.body.scrollTop));
+		}
+
+		// Return an object with top and left properties
+		results = { top: top, left: left };
+	}
+
+	function border(elem) {
+		add( jQuery.css(elem, "borderLeftWidth"), jQuery.css(elem, "borderTopWidth") );
+	}
+
+	function add(l, t) {
+		left += parseInt(l) || 0;
+		top += parseInt(t) || 0;
+	}
+
+	return results;
+};
+})();
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/js/jquery.px.js
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/js/jquery.px.js	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/js/jquery.px.js	(revision 69)
@@ -0,0 +1,18 @@
+// Ce plugin permet de recuperer css(fontSize) en px meme sous MSIE
+(function($){
+	$.fn.px = function(prop) {
+		var val;
+		if($.browser.msie) {
+			$('<span><\/span>')
+			.css({display: 'block', width: '1em'})
+			.appendTo(this[0])
+			.each(function(){
+				val = parseInt($(this).width()) + 'px';
+			})
+			.remove();
+		} else {
+			val = this.css(prop);
+		}
+		return val;
+	};
+})(jQuery);
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/js/resizehandle.js
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/js/resizehandle.js	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/js/resizehandle.js	(revision 69)
@@ -0,0 +1,32 @@
+(function($){
+/*
+ * resizehandle.js (c) Fil 2007, plugin pour jQuery
+ * @ http://www.jquery.info/spip.php?article44
+ * Distribue sous licence GNU/LGPL et MIT
+ */
+$.fn.resizehandle = function() {
+  return this.each(function() {
+    var me = $(this);
+    me.after(
+      $('<div class="resizehandle"></div>')
+      .css({height:'16px',width:Math.max(me.width()-4,10)}) // bug MSIE si 100%
+      .bind('mousedown', function(e) {
+        var h = me.height();
+        var y = e.clientY;
+        var moveHandler = function(e) {
+          me
+          .height(Math.max(20, e.clientY + h - y));
+        };
+        var upHandler = function(e) {
+          $('html')
+          .unbind('mousemove',moveHandler)
+          .unbind('mouseup',upHandler);
+        };
+        $('html')
+        .bind('mousemove', moveHandler)
+        .bind('mouseup', upHandler);
+      })
+    );
+  });
+};
+})(jQuery);
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/lang/crayons_ar.php
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/lang/crayons_ar.php	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/lang/crayons_ar.php	(revision 69)
@@ -0,0 +1,21 @@
+<?php
+
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+	'annuler' => '&#1573;&#1604;&#1594;&#1575;&#1569;',
+	'editer_@type@_@id@' => '&#1578;&#1581;&#1585;&#1610;&#1585; @type@ @id@ &#1576;&#1575;&#1604;&#1603;&#1575;&#1605;&#1604;',
+    'deja_modifie' => '&#1593;&#1583;&#1617;&#1604; &#1587;&#1575;&#1576;&#1602;&#1575;&#1611;',
+    'donnees_mal_formatees' => '&#1578;&#1606;&#1587;&#1610;&#1602; &#1576;&#1610;&#1575;&#1606;&#1575;&#1578; &#1594;&#1610;&#1585; &#1587;&#1604;&#1610;&#1605;',
+    'editer' => '&#1578;&#1581;&#1585;&#1610;&#1585;',
+    'editer_tout' => '&#1578;&#1581;&#1585;&#1610;&#1585; &#1575;&#1604;&#1603;&#1604;',
+    'modifie_par_ailleurs' => '&#1593;&#1583;&#1617;&#1604; &#1601;&#1610; &#1605;&#1603;&#1575;&#1606; &#1570;&#1582;&#1585;',
+    'non_autorise' => '&#1594;&#1610;&#1585; &#1605;&#1587;&#1605;&#1608;&#1581;',
+    'non_implemente' => '&#1594;&#1610;&#1585; &#1605;&#1583;&#1593;&#1608;&#1605;',
+    'pas_de_modification' => '&#1604;&#1605; &#1578;&#1583;&#1582;&#1604; &#1575;&#1610; &#1578;&#1593;&#1583;&#1610;&#1604;&#1575;&#1578;',
+    'pas_de_valeur' => '&#1604;&#1575; &#1578;&#1608;&#1580;&#1583; &#1602;&#1610;&#1605;&#1577;',
+    'sauvegarder' => '&#1581;&#1601;&#1592; &#1575;&#1604;&#1578;&#1593;&#1583;&#1610;&#1604;&#1575;&#1578;&#1567;',
+    'svp_copier_coller' => '&#1593;&#1584;&#1585;&#1575;&#1611;&#1548; &#1602;&#1605; &#1576;&#1606;&#1587;&#1582; &#1575;&#1604;&#1578;&#1593;&#1583;&#1610;&#1604;&#1575;&#1578; &#1608;&#1604;&#1589;&#1602;&#1607;&#1575; &#1608;&#1573;&#1593;&#1575;&#1583;&#1577; &#1575;&#1604;&#1605;&#1581;&#1575;&#1608;&#1604;&#1577;',
+    'veuillez_patienter'=>'&#1575;&#1604;&#1585;&#1580;&#1575;&#1569; &#1575;&#1604;&#1575;&#1606;&#1578;&#1592;&#1575;&#1585;...'
+    );
+?>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/lang/crayons_de.php
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/lang/crayons_de.php	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/lang/crayons_de.php	(revision 69)
@@ -0,0 +1,21 @@
+<?php
+
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+	'annuler' => 'Widerrufen',
+	'editer_@type@_@id@' => '@type@ @id@ voll &auml;ndern',
+	'deja_modifie' => 'Schon ge&auml;ndert',
+	'donnees_mal_formatees' => 'Anforderung fehlerhaft formartiert',
+	'editer' => 'Inhalt &auml;ndern',
+	'editer_tout' => 'Voll &auml;ndern',
+	'modifie_par_ailleurs' => 'Wurde von jemand anderem bereits ge&auml;ndert',
+	'non_autorise' => 'Fehlende Rechte',
+	'non_implemente' => 'Nicht implementiert',
+	'pas_de_modification' => 'Keine &Auml;nderung',
+	'pas_de_valeur' => 'Inhalt existiert nicht',
+	'sauvegarder' => '&Auml;nderungen erfassen ?',
+	'svp_copier_coller' => 'Bitte, &auml;nderungen sicher stellen und wiederholen',
+	'veuillez_patienter'=>'Bitte warten ...'
+	);
+?>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/lang/crayons_en.php
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/lang/crayons_en.php	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/lang/crayons_en.php	(revision 69)
@@ -0,0 +1,21 @@
+<?php
+
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+	'annuler' => 'Cancel',
+	'editer_@type@_@id@' => 'Edit the whole @type@ @id@',
+	'deja_modifie' => 'Already modified',
+	'donnees_mal_formatees' => 'Invalid request format',
+	'editer' => 'Edit',
+	'editer_tout' => 'Edit as a whole',
+	'modifie_par_ailleurs' => 'Was changed elsewhere in the meantime',
+	'non_autorise' => 'Not allowed',
+	'non_implemente' => 'Not implemented',
+	'pas_de_modification' => 'No change',
+	'pas_de_valeur' => 'No value for this id',
+	'sauvegarder' => 'Save changes anyway ?',
+	'svp_copier_coller' => 'Sorry, copy/paste your changes and retry',
+	'veuillez_patienter'=>'Searching ...'
+	);
+?>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/lang/crayons_es.php
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/lang/crayons_es.php	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/lang/crayons_es.php	(revision 69)
@@ -0,0 +1,21 @@
+<?php
+
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+	'annuler' => 'Cancelar',
+	'editer_@type@_@id@' => 'Editar @type@ @id@ completamente',
+    'deja_modifie' => 'Ya modificado',
+    'donnees_mal_formatees' => 'Datos fuera de formato',
+    'editer' => 'Modificar',
+    'editer_tout' => 'Modificar todo',
+    'modifie_par_ailleurs' => 'Modificado por otra persona',
+    'non_autorise' => 'No autorizado',
+    'non_implemente' => 'No implementado',
+    'pas_de_modification' => 'Sin modificaciones',
+    'pas_de_valeur' => 'Sin valor',
+    'sauvegarder' => '&iquest;Guardar las modificaciones?',
+    'svp_copier_coller' => 'Lo lamentamos copie/pegue los cambios y vuelva a comenzar',
+    'veuillez_patienter'=>'Por favor, espere...'
+    );
+?>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/lang/crayons_fr.php
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/lang/crayons_fr.php	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/lang/crayons_fr.php	(revision 69)
@@ -0,0 +1,21 @@
+<?php
+
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+	'annuler' => 'Annuler',
+	'editer_@type@_@id@' => '&Eacute;diter @type@ @id@ en entier',
+    'deja_modifie' => 'D&eacute;j&agrave; modifi&eacute;',
+    'donnees_mal_formatees' => 'Donn&eacute;es mal format&eacute;es',
+    'editer' => '&Eacute;diter',
+    'editer_tout' => '&Eacute;diter tout',
+    'modifie_par_ailleurs' => 'Modifi&eacute; par ailleurs',
+    'non_autorise' => 'Non autoris&eacute;',
+    'non_implemente' => 'Non impl&eacute;ment&eacute;',
+    'pas_de_modification' => 'Pas de modification',
+    'pas_de_valeur' => 'Pas de valeur',
+    'sauvegarder' => 'Sauvegarder les modifications ?',
+    'svp_copier_coller' => 'D&eacute;sol&eacute;, copiez/collez vos changements et recommencez',
+    'veuillez_patienter'=>'Veuillez patienter...'
+    );
+?>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/lang/crayons_gl.php
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/lang/crayons_gl.php	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/lang/crayons_gl.php	(revision 69)
@@ -0,0 +1,21 @@
+<?php
+
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP -- Este e un ficheiro de idioma de SPIP
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+	'annuler' => 'Cancelar',
+	'editer_@type@_@id@' => 'Editar @type@ @id@ completamente',
+    'deja_modifie' => 'Xa modificado',
+    'donnees_mal_formatees' => 'Datos fra de formato',
+    'editer' => 'Mudar',
+    'editer_tout' => 'Mudar todo',
+    'modifie_par_ailleurs' => 'Mudado por outra persoa',
+    'non_autorise' => 'Non autorizado',
+    'non_implemente' => 'Non implementado',
+    'pas_de_modification' => 'Sen modificacins',
+    'pas_de_valeur' => 'Sen valor',
+    'sauvegarder' => '&iquest;Gardar as modificacins?',
+    'svp_copier_coller' => 'O lamentamos copie/pegue os cambios e comeze de novo',
+    'veuillez_patienter'=>'Por favor, espere...'
+    );
+?>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/lang/crayons_it.php
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/lang/crayons_it.php	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/lang/crayons_it.php	(revision 69)
@@ -0,0 +1,21 @@
+<?php
+
+// This is a SPIP language file  --  Ceci est un fichier langue de SPIP
+
+$GLOBALS[$GLOBALS['idx_lang']] = array(
+    'annuler' => 'Annulla',
+    'editer_@type@_@id@' => 'Modifica @type@ @id@ dalla redazione',
+    'deja_modifie' => 'Gi&agrave; modificato',
+    'donnees_mal_formatees' => 'Dati mal formattati',
+		'editer' => 'Modifica',
+		'editer_tout' => 'Modifica tutto',
+    'modifie_par_ailleurs' => 'Modificato da altri',
+    'non_autorise' => 'Non autorizzato',
+    'non_implemente' => 'Non implementato',
+    'pas_de_modification' => 'Nessun cambiamento',
+    'pas_de_valeur' => 'Nessun valore',
+    'sauvegarder' => 'Salvare le modifiche?',
+    'svp_copier_coller' => 'Spiacente, copia ed incolla le tue modifiche e ricomincia',
+    'veuillez_patienter'=>'Attendere prego...'
+    );
+?>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/plugin.xml
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/plugin.xml	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/plugin.xml	(revision 69)
@@ -0,0 +1,71 @@
+<plugin>
+	<nom>
+	<multi>
+	Crayons
+	[ar]&#1575;&#1604;&#1602;&#1604;&#1605;
+	[it]Pennarelli
+	</multi>
+	</nom>
+	<auteur>
+	<multi>
+  Fil, toggg
+	&#169; 2006-2007 -
+	Distribu&#233; sous licence GNU/GPL
+	[es]   Fil, toggg
+	&#169; 2006-2007 -
+  Distribuido bajo licencia GNU/GPL
+	[it]   Fil, toggg
+	&#169; 2006-2007 -
+  Distribuito sotto licenza GNU/GPL
+	[ar]&#1601;&#1610;&#1604; &#1608;&#1578;&#1608;&#1594;&#1594;&#1594;
+	&#169; &#1634;&#1632;&#1632;&#1638; - &#1634;&#1632;&#1632;&#1639;
+	</multi>
+	</auteur>
+	<version>
+	0.9
+	</version>
+	<etat>
+	stable
+	</etat>
+	<description>
+<multi>Ce plugin {{disponible uniquement &#224; partir de SPIP 1.9.2}} fournit des fonctions d'&#233;dition rapide depuis les pages publiques du site. Pour en b&#233;n&#233;ficier, il faut des squelettes
+&#171;&#160;compatibles crayons&#160;&#187;.
+
+Comme dans les squelettes de la dist/, il suffit d'avoir une balise {#EDIT&#123;machin&#125;} &#224; chaque endroit que l'on veut rendre 
+&#233;ditable. Par exemple, pour que le titre soit &#233;ditable:
+_ <code><h1 class="#EDIT{titre} autres classes">#TITRE</h1></code>.
+[it] Questo plugin {{disponibile unicamente per una versione 1.9.2 aggiornata}} fornisce delle funzioni di modifica rapida dalle pagine pubbliche del sito. Per utilizzarlo, &#232; necessario avere dei modelli
+&#147;compatibili con Pennarelli&#148;.
+
+Come per i modelli recenti della dist/ 1.9.2, &#232; sufficiente inserire un tag {#EDIT&#123;qualcosa&#125;} su ogni blocco che si vuol rendere
+modificabile. Ad esempio, affinch&#233; il titolo sia modificabile:
+_ <code><h1 class="#EDIT{titre} altre classi">#TITRE</h1></code>.
+[ar]&#1607;&#1584;&#1575; &#1575;&#1604;&#1605;&#1604;&#1581;&#1602;&#1548; {{&#1575;&#1604;&#1605;&#1578;&#1608;&#1575;&#1601;&#1585; &#1604;&#1604;&#1575;&#1589;&#1583;&#1575;&#1585; &#1633;.&#1641;.&#1634; &#1601;&#1602;&#1591;}} &#1610;&#1602;&#1583;&#1605; &#1608;&#1592;&#1575;&#1574;&#1601; &#1578;&#1581;&#1585;&#1610;&#1585; &#1587;&#1585;&#1610;&#1593;&#1577; &#1605;&#1606; &#1601;&#1610; &#1589;&#1601;&#1581;&#1575;&#1578; &#1575;&#1604;&#1605;&#1608;&#1602;&#1593; &#1575;&#1604;&#1593;&#1605;&#1608;&#1605;&#1610;. &#1604;&#1604;&#1575;&#1587;&#1578;&#1601;&#1575;&#1583;&#1577; &#1605;&#1606;&#1607; &#1610;&#1580;&#1576; &#1575;&#1606;&#1588;&#1575;&#1569; &#1589;&#1601;&#1581;&#1575;&#1578; &#1606;&#1605;&#1608;&#1584;&#1580;&#1610;&#1577; &#1605;&#1578;&#1608;&#1575;&#1601;&#1602;&#1577; &#1605;&#1593;&#1607;.
+
+&#1603;&#1605;&#1575; &#1601;&#1610; &#1589;&#1601;&#1581;&#1575;&#1578; &#1575;&#1604;&#1575;&#1589;&#1583;&#1575;&#1585; 1.9.2 &#1575;&#1604;&#1606;&#1605;&#1608;&#1584;&#1580;&#1610;&#1577; &#1610;&#1603;&#1601;&#1610; &#1575;&#1583;&#1582;&#1575;&#1604; &#1593;&#1604;&#1575;&#1605;&#1577; {#EDIT} &#1601;&#1610; &#1603;&#1604; &#1605;&#1603;&#1575;&#1606; &#1606;&#1585;&#1610;&#1583; &#1578;&#1581;&#1585;&#1610;&#1585;&#1607;. &#1605;&#1579;&#1604;&#1575;&#1611;&#1548; &#1604;&#1610;&#1589;&#1576;&#1581; &#1575;&#1604;&#1593;&#1606;&#1608;&#1575;&#1606; &#1602;&#1575;&#1576;&#1604; &#1604;&#1604;&#1578;&#1581;&#1585;&#1610;&#1585;:
+_ <code><h1 class="#EDIT{titre} &#1593;&#1604;&#1575;&#1605;&#1575;&#1578; &#1575;&#1582;&#1585;&#1609;">#TITRE</h1></code>.
+</multi>
+	</description>
+	<lien>
+	[<multi>Documentation[ar][&#1578;&#1608;&#1579;&#1610;&#1602;[it]Documentazione</multi>->http://www.spip-contrib.net/?article1475],
+<multi>et si vous avez[ar]&#1608;&#1575;&#1584;&#1575; &#1603;&#1575;&#1606; &#1604;&#1583;&#1610;&#1603;[it] e se avete installato anche</multi> cfg:
+	[<multi>Configuration[ar]&#1575;&#1604;&#1575;&#1593;&#1583;&#1575;&#1583;[it]Configurazione</multi>->.?exec=cfg&amp;cfg=crayons]
+	</lien>
+	<prefix>
+	Crayons
+	</prefix>
+	<icon>
+	images/crayon.png
+	</icon>
+	<fonctions>tetecrayons.php</fonctions>
+	<pipeline>
+		<nom>affichage_final</nom>
+		<inclure>tetecrayons.php</inclure>
+	</pipeline>
+	<pipeline>
+		<nom>header_prive</nom>
+		<action>insert_head</action>
+		<inclure>tetecrayons.php</inclure>
+	</pipeline>
+	<chemin dir='' />
+</plugin>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/svn.revision
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/svn.revision	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/svn.revision	(revision 69)
@@ -0,0 +1,15 @@
+<svn_revision><text_version>
+Origine svn://zone.spip.org/spip-zone/_plugins_/_stable_/crayons le vendredi 7 mars 2008, 17:04:11 (UTC+0100)
+Revision: 19015
+Dernier commit 2008-03-07 16:49:47 +0100 (Fri, 07 Mar 2008)
+Voir http://trac.rezo.net/trac/spip-zone/browser/_plugins_/_stable_/crayons
+Archive: http://files.spip.org/spip-zone/crayons.zip
+</text_version>
+<uri>http://trac.rezo.net/trac/spip-zone/browser/_plugins_/_stable_/crayons</uri>
+<archive>http://files.spip.org/spip-zone/crayons.zip</archive>
+<origine>svn://zone.spip.org/spip-zone/_plugins_/_stable_/crayons</origine>
+<date_production>vendredi 7 mars 2008, 17:04:12 (UTC+0100)</date_production>
+<revision>19015</revision>
+<commit>2008-03-07 16:49:47 +0100 (Fri, 07 Mar 2008)</commit>
+</svn_revision>
+<archivelist>_plugins_/_stable_/crayons;crayons;crayons</archivelist>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/tetecrayons.php
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/tetecrayons.php	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/tetecrayons.php	(revision 69)
@@ -0,0 +1,263 @@
+<?php
+
+/* inserer le css et le js externes pour Crayons dans le <head>
+ *
+ *  Crayons plugin for spip (c) Fil, toggg 2006-2007 -- licence GPL
+ */
+
+# mettre a true dans mes_options pour avoir les crayons non compresses
+@define('_DEBUG_CRAYONS', false);
+
+// Dire rapidement si ca vaut le coup de chercher des droits
+function analyse_droits_rapide_dist() {
+    return isset($GLOBALS['auteur_session']['statut']);
+}
+
+// Le pipeline header_prive (pour y tester les crayons)
+function Crayons_insert_head($head) {
+    include_spip('cfg_options');
+
+    //par dÃ©faut, pas de traitement sur les entetes
+    $res = $head;
+    
+    //vÃ©rifie la prÃ©sence de cfg, si pas de cfg on cherche meme pas Ã  faire de l'espace privÃ©
+    if (!function_exists('lire_config')) {
+        return $res;
+    }
+    
+    
+    //vÃ©rifie que l'Ã©ditiojn de l'espace privÃ© est autorisÃ©
+    if (lire_config('crayons/espaceprive') == 'on') {
+        //determine les pages (exec) crayonnables
+        if (in_array(_request('exec'), explode(',',lire_config('crayons/exec_autorise')))) {
+            //Calcul des droits
+            include_spip('inc/crayons');
+            $res = Crayons_preparer_page($head, '*', wdgcfg(), 'head');
+        } 
+    }
+    
+    //retourne l'entete modifiÃ©e
+    return $res;
+        
+    /*
+    // Est-on autorise ?
+    include_spip('inc/autoriser');
+    switch (_request('exec')) {
+        case 'articles':
+            $droits = autoriser('modifier', 'article', _request('id_article'));
+            break;
+
+        default:
+            $droits=false;
+    }
+
+    if (!$droits)
+        return $head;
+    */
+
+}
+
+// Le pipeline affichage_final, execute a chaque hit sur toute la page
+function &Crayons_affichage_final(&$page) {
+
+    // ne pas se fatiguer si le visiteur n'a aucun droit
+    if (!(function_exists('analyse_droits_rapide')?analyse_droits_rapide():analyse_droits_rapide_dist()))
+        return $page;
+
+    // sinon regarder rapidement si la page a des classes crayon
+    if (strpos($page, 'crayon')===FALSE)
+        return $page;
+
+    // voir un peu plus precisement lesquelles
+    include_spip('inc/crayons');
+    if (!preg_match_all(_PREG_CRAYON, $page, $regs, PREG_SET_ORDER))
+        return $page;
+    $wdgcfg = wdgcfg();
+
+    // calculer les droits sur ces crayons
+    include_spip('inc/autoriser');
+    $droits = array();
+    $droits_accordes = 0;
+    foreach ($regs as $reg) {
+        list(,$crayon,$type,$champ,$id) = $reg;
+        if (autoriser('modifier', $type, $id, NULL, array('champ'=>$champ))) {
+            $droits['.' . $crayon]++;
+            $droits_accordes ++;
+        }
+    }
+    // et les signaler dans la page
+    if ($droits_accordes == count($regs)) // tous les droits
+        $page = Crayons_preparer_page($page, '*', $wdgcfg);
+    else if ($droits) // seulement certains droits, preciser lesquels
+        $page = Crayons_preparer_page($page, join(',',array_keys($droits)), $wdgcfg);
+
+    return $page;
+}
+
+function &Crayons_preparer_page(&$page, $droits, $wdgcfg = array(), $mode='page') {
+	lang_select($GLOBALS['auteur_session']['lang']);
+
+	$jsFile = generer_url_public('crayons.js');
+	if (_DEBUG_CRAYONS)
+		$jsFile = parametre_url($jsFile,'debug_crayons',1);
+	include_spip('inc/filtres'); // rien que pour direction_css() :(
+	$cssFile = direction_css(find_in_path('crayons.css'));
+
+	$config = var2js(array(
+		'imgPath' => dirname(find_in_path('images/crayon.png')),
+		'droits' => $droits,
+		'dir_racine' => _DIR_RACINE,
+		'self' => self('&'),
+		'txt' => array(
+			'error' => _U('crayons:svp_copier_coller'),
+			'sauvegarder' => $wdgcfg['msgAbandon'] ? _U('crayons:sauvegarder') : ''
+		),
+		'img' => array(
+			'searching' => array(
+				'txt' => _U('crayons:veuillez_patienter')
+			),
+			'crayon' => array(
+				'txt' => _U('crayons:editer')
+			),
+			'edit' => array(
+				'txt' => _U('crayons:editer_tout')
+			),
+			'img-changed' => array(
+				'txt' => _U('crayons:deja_modifie')
+			)
+		),
+		'cfg' => $wdgcfg
+	));
+
+
+	$incHead = <<<EOH
+
+<link rel="stylesheet" href="{$cssFile}" type="text/css" media="all" />
+<script src="{$jsFile}" type="text/javascript"></script>
+<script type="text/javascript"><!--
+    var configCrayons = new cQuery.prototype.cfgCrayons({$config});
+    ( window.jQuery ? jQuery : cQuery )(document).ready(cQuery.fn.crayonsstart);
+    // cQuery.ready() plante le jQuery.ready() sous MSIE
+//-->
+</script>
+EOH;
+
+	if ($mode == 'head')
+		return $page . $incHead;
+
+	$pos_head = strpos($page, '</head>');
+	if ($pos_head === false)
+		return $page;
+
+	return substr_replace($page, $incHead, $pos_head, 0);
+
+}
+
+// #EDIT{ps} pour appeler le crayon ps ;
+// si cette fonction est absente, balise_EDIT_dist() met a vide
+function balise_EDIT($p) {
+
+	$i_boucle = $p->nom_boucle ? $p->nom_boucle : $p->id_boucle;
+	// #EDIT hors boucle ? ne rien faire
+	if (!$type = $p->boucles[$i_boucle]->type_requete) {
+		$p->code = "''";
+		$p->interdire_scripts = false;
+		return $p;
+	}
+
+	// crayon sur une base distante 'nua:article-intro-5'
+	// on ne sait pas encore les gerer, mais au moins on les detecte
+	if ($distant = $p->boucles[$i_boucle]->sql_serveur)
+		$type = "$distant:$type";
+
+	// le compilateur 1.9.2 ne calcule pas primary pour les tables secondaires
+	if (!($primary = $p->boucles[$i_boucle]->primary)) {
+		list($nom, $desc) = trouver_def_table(
+			$p->boucles[$i_boucle]->type_requete, $p->boucles[$i_boucle]);
+		$primary = $desc['key']['PRIMARY KEY'];
+	}
+	$primary = explode(',',$primary);
+	$id = array();
+	foreach($primary as $key)
+		$id[] = champ_sql(trim($key),$p);
+	$primary = implode(".'-'.",$id);
+	$p->code = "classe_boucle_crayon('"
+		. $type
+		."',"
+		.sinon(interprete_argument_balise(1,$p),"''")
+		.","
+		. $primary
+		.").' '";
+	$p->interdire_scripts = false;
+	return $p;
+}
+
+function creer_le_crayon($type, $champ, $id) {
+	include_spip('inc/crayons');
+	include_spip('action/crayons_html');
+	$a = affiche_controleur("crayon $type-$champ-$id",
+		array('w' => 485, 'h' => 300, 'wh' => 500)
+	);
+	return $a['$erreur'] ? $a['$erreur'] : $a['$html'];
+}
+
+// #CRAYON{ps} pour creer le crayon ps SI ?edit=1;
+function balise_CRAYON($p) {
+
+	$i_boucle = $p->nom_boucle ? $p->nom_boucle : $p->id_boucle;
+	// #CRAYON hors boucle ? ne rien faire
+	if (!$type = $p->boucles[$i_boucle]->type_requete) {
+		$p->code = "''";
+		$p->interdire_scripts = false;
+		return $p;
+	}
+
+	// crayon sur une base distante 'nua:article-intro-5'
+	// on ne sait pas encore les gerer, mais au moins on les detecte
+	if ($distant = $p->boucles[$i_boucle]->sql_serveur)
+		$type = "$distant:$type";
+
+	// le compilateur 1.9.2 ne calcule pas primary pour les tables secondaires
+	if (!($primary = $p->boucles[$i_boucle]->primary)) {
+		list($nom, $desc) = trouver_def_table(
+			$p->boucles[$i_boucle]->type_requete, $p->boucles[$i_boucle]);
+		$primary = $desc['key']['PRIMARY KEY'];
+	}
+	$primary = explode(',',$primary);
+	$id = array();
+	foreach($primary as $key)
+		$id[] = champ_sql(trim($key),$p);
+	$primary = implode(".'-'.",$id);
+	$p->code = "creer_le_crayon('"
+		. $type
+		."',"
+		.sinon(interprete_argument_balise(1,$p),"''")
+		.","
+		. $primary
+		.").' '";
+	$p->interdire_scripts = false;
+	return $p;
+}
+
+
+// Donne la classe crayon en fonction
+// - du type de la boucle
+// (attention aux exceptions pour #EDIT dans les boucles HIERARCHIE et SITES)
+// - du champ demande (vide, + ou se terminant par + : (+)classe type--id)
+// - de l'id courant
+function classe_boucle_crayon($type, $champ, $id) {
+	$type = $type[strlen($type) - 1] == 's' ?
+			substr($type, 0, -1) : 
+			str_replace(
+				array('hierarchie', 'syndication'),
+				array('rubrique',   'site'),
+				$type);
+
+	$plus = (substr($champ, -1) == '+' AND $champ = substr($champ, 0, -1))
+		? " $type--$id"
+		: '';
+
+	return 'crayon ' . $type . '-' . $champ . '-' . $id . $plus;
+}
+
+?>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/vues/article_intro.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/vues/article_intro.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/vues/article_intro.html	(revision 69)
@@ -0,0 +1,9 @@
+[(#REM)
+
+	Vue pour le crayon 'article_intro'
+
+]
+#CACHE{0}
+<BOUCLE_a(ARTICLES){id_article}{statut==.}>
+#INTRODUCTION
+</BOUCLE_a>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/vues/article_intro2.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/vues/article_intro2.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/vues/article_intro2.html	(revision 69)
@@ -0,0 +1,9 @@
+[(#REM)
+
+	Vue pour le crayon 'article_intro3'
+
+]
+#CACHE{0}
+<BOUCLE_a(ARTICLES){id_article}{statut==.}>
+#INTRODUCTION
+</BOUCLE_a>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/vues/article_intro3.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/vues/article_intro3.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/vues/article_intro3.html	(revision 69)
@@ -0,0 +1,9 @@
+[(#REM)
+
+	Vue pour le crayon 'article_intro3'
+
+]
+#CACHE{0}
+<BOUCLE_a(ARTICLES){id_article}{statut==.}>
+#INTRODUCTION
+</BOUCLE_a>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/vues/article_introduction.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/vues/article_introduction.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/vues/article_introduction.html	(revision 69)
@@ -0,0 +1,9 @@
+[(#REM)
+
+	Vue pour le crayon 'article_introduction'
+
+]
+#CACHE{0}
+<BOUCLE_a(ARTICLES){id_article}{statut==.}>
+#INTRODUCTION
+</BOUCLE_a>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/vues/article_texte.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/vues/article_texte.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/vues/article_texte.html	(revision 69)
@@ -0,0 +1,23 @@
+[(#REM)
+
+	Vue pour le crayon 'article_texte'
+
+	Juste pour le bonheur de tester les limites : la classe
+	imagereduire.NNN associee a #EDIT{texte} provoque l'application
+	du filtre |image_reduire{NNN,0}
+
+]
+#CACHE{0}
+
+[(#REM) met dans la variable r la valeur de la class ]
+[(#SET{r,[(#ENV{class}|match{\bimagereduire\.\d+\b}|match{\b\d+\b})]})]
+
+<BOUCLE_a(ARTICLES){id_article}{statut==.}>
+[(#GET{r}|?{
+	[(#TEXTE|image_reduire{#GET{r},0})]
+,
+	#TEXTE
+})]
+</BOUCLE_a>
+
+[<small>(#NOTES)</small>]
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/vues/document_fichier.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/vues/document_fichier.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/vues/document_fichier.html	(revision 69)
@@ -0,0 +1,11 @@
+[(#REM)
+
+	Vue pour le controleur de document
+
+]
+
+<BOUCLE_d(DOCUMENTS){id_document}>
+
+[(#VAL{<img}|concat{#ID_DOCUMENT}|concat{>}|propre)]
+
+</BOUCLE_d>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/vues/hyperlien.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/vues/hyperlien.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/vues/hyperlien.html	(revision 69)
@@ -0,0 +1,21 @@
+[(#REM)
+
+	Vue pour le crayon 'hyperlien'
+
+]
+#CACHE{0}
+
+#SET{titre,#ENV*{nom_site}}
+#SET{url,#ENV*{url_site}}
+[(#ENV{id_syndic_article}|?{
+	[(#SET{titre,#ENV*{titre}})]
+	[(#SET{url,#ENV*{url}})]
+})]
+
+[(#ENV{id_article}|?{' '})<:voir_en_ligne:>&nbsp;:]
+
+[(#GET{url}|?{
+	<a href="[(#GET{url})]" class="spip_out">[(#GET{titre}|typo|sinon{[(#GET{url}|couper{80})]})]</a>
+,
+	[(#GET{titre}|typo|sinon{<:info_sans_titre:>})]
+})]
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/vues/id_parent.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/vues/id_parent.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/vues/id_parent.html	(revision 69)
@@ -0,0 +1,11 @@
+[(#REM)
+
+	Vue pour le crayon id_parent
+	Cf. controleurs/id_parent.html
+
+	nb: teste sur les articles uniquement
+]
+
+<BOUCLE_r(RUBRIQUES){id_rubrique=#ENV{id_rubrique}}>
+#TITRE
+</BOUCLE_r>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/vues/logo.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/vues/logo.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/vues/logo.html	(revision 69)
@@ -0,0 +1,37 @@
+[(#REM)
+
+	Vue du logo qu'on vient d'uploader
+
+	On doit hacker l'url du logo avec sa date pour eviter le cache du navigateur
+
+]
+
+
+[(#REM) En fonction de du contexte, choisir un logo ]
+
+[(#SET{image,[(#ID_ARTICLE|?{#LOGO_ARTICLE_NORMAL}
+	|sinon{[(#ID_AUTEUR|?{#LOGO_AUTEUR_NORMAL})]}
+	|sinon{[(#ID_BREVE|?{#LOGO_BREVE_NORMAL})]}
+	|sinon{[(#ID_RUBRIQUE|?{#LOGO_RUBRIQUE_NORMAL})]}
+	|sinon{[(#ID_SYNDIC|?{#LOGO_SITE_NORMAL})]}
+	|sinon{[(#ID_MOT|?{#LOGO_MOT_NORMAL})]})]})]
+
+
+[(#REM) Si la class contient resize-XXX, on appliquera ce filtre au logo ]
+
+[(#SET{resize,[(#ENV{class}|match{\bresize-\d+\b}|replace{resize-})]})]
+
+
+[(#GET{image}|?{
+
+	[(#SET{logo,[(#GET{image}
+		|image_reduire{#GET{resize,0},#GET{resize,120}})]})]
+	[(#SET{url,[(#GET{logo}|extraire_attribut{src}|replace{\?.*$})]})]
+	[(#SET{url,[(#GET{url}|parametre_url{s,#EVAL{time()}})]})]
+	[(#SET{width,[(#GET{logo}|extraire_attribut{width})]})]
+
+	[(#GET{logo}|inserer_attribut{src,#GET{url}})]
+
+,
+	'<small><abbr title="logo">?</abbr></small>'
+})]
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/vues/mots_article_brut.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/vues/mots_article_brut.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/vues/mots_article_brut.html	(revision 69)
@@ -0,0 +1,17 @@
+[(#REM)
+
+	Vue pour le crayon 'mots_article_brut'
+
+]
+#CACHE{0}
+<BOUCLE_a(ARTICLES){id_article}{lang}{statut==.}>
+<B_moex>
+<ul class="mots_article"><p>Mots-clÃ© pour #TITRE (#ID_ARTICLE)</p>
+<BOUCLE_moex(MOTS){id_article}{doublons}>
+	<li class="#EDIT{id_mot} mots_article">#ID_GROUPE:#ID_MOT : [(#TITRE|entites_html)] </li>
+</BOUCLE_moex>
+</ul>
+</B_moex>
+<p>Pas de mot-clÃ© existant</p>
+<//B_moex>
+</BOUCLE_a>
Index: /trunk/spip/esqueleto-redcta/plugins/crayons/vues/qui.html
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/crayons/vues/qui.html	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/crayons/vues/qui.html	(revision 69)
@@ -0,0 +1,21 @@
+[(#REM)
+
+	Vue pour le crayon 'qui'
+
+]
+#CACHE{0}
+
+<BOUCLE_a(AUTEURS){id_auteur}{tous}>
+[<a title="(#EMAIL|texte_backend)">][(#NOM|sinon{#EMAIL})][(#EMAIL|?{</a>})]
+</BOUCLE_a>
+
+<BOUCLE_c(SIGNATURES){id_signature}{statut==.}>
+[<a title="(#EMAIL|texte_backend)">][(#NOM|sinon{#EMAIL})][(#EMAIL|?{</a>})]
+</BOUCLE_c>
+
+<BOUCLE_d(FORUMS){id_forum}{statut==.}>
+[<a title="(#EMAIL|texte_backend)">][(#NOM|sinon{#EMAIL})][(#EMAIL|?{</a>})]
+</BOUCLE_d>
+
+<//B_c>
+<//B_a>
Index: /trunk/spip/esqueleto-redcta/plugins/svn_update/svn_update_list.nuevo.txt
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/svn_update/svn_update_list.nuevo.txt	(revision 69)
+++ /trunk/spip/esqueleto-redcta/plugins/svn_update/svn_update_list.nuevo.txt	(revision 69)
@@ -0,0 +1,45 @@
+Linea que fue agregada
+## Fichier d'exemple ##
+##
+## On note ici la source SVN de chaque paquet qui nous interesse
+## et sa destination sur le disque (a partir de la racine du site)
+##
+##
+## la plupart des sources sont commentees :
+## recopiez ce fichier dans votre repertoire personnel, et
+## ajoutez/configurez ce qu'il vous faut
+##
+######################################################################
+
+
+##### SPIP
+# la branche 1.9, a la racine
+# svn://trac.rezo.net/spip/branches/spip-1.9 .
+
+
+
+##### PLUGINS
+# auto-installation de ce plugin svn_update
+# svn://zone.spip.org/spip-zone/_plugins_/_stable_/svn_update plugins/svn_update
+
+# Forms en version 1.9.1
+#svn://zone.spip.org/spip-zone/_plugins_/_stable_/forms/1_9_1 plugins/Forms
+
+# jQuery et Thickbox
+#svn://zone.spip.org/spip-zone/_plugins_/_dev_/-jQuery plugins/jquery
+#svn://zone.spip.org/spip-zone/_plugins_/_dev_/thickbox plugins/thickbox
+
+
+##### SQUELETTES
+# sedna
+#svn://zone.spip.org/spip-zone/_squelettes_/sedna sedna
+
+# et enfin mes squelettes, pris dans mon svn perso
+# svn://mon.svn.perso/chemin/vers/mes/squelettes squelettes
+
+# ou dans spip-party
+# svn://zone.spip.org/spip-zone/_squelettes_/spip-party squelettes
+
+# test svn redcta
+https://svn.redcta.org.ar/proyectos/redcta/trunk/spip test-svn
+
Index: /trunk/spip/esqueleto-redcta/plugins/svn_update/svn_update_list.txt
===================================================================
--- /trunk/spip/esqueleto-redcta/plugins/svn_update/svn_update_list.txt	(revision 68)
+++ /trunk/spip/esqueleto-redcta/plugins/svn_update/svn_update_list.txt	(revision 69)
@@ -1,2 +1,3 @@
+######################################################################
 ## Fichier d'exemple ##
 ##
Index: /trunk/spip/esqueleto-redcta/themes/redcta/article.html
===================================================================
--- /trunk/spip/esqueleto-redcta/themes/redcta/article.html	(revision 68)
+++ /trunk/spip/esqueleto-redcta/themes/redcta/article.html	(revision 69)
@@ -55,7 +55,7 @@
 				#DEBUT_SURLIGNE
 				[(#LOGO_ARTICLE||image_reduire{200,200})]
-				[<p class="surtitre">(#SURTITRE)</p>]
-				<h3>[(#TITRE|supprimer_numero)]</h3>
-				[<p class="soustitre">(#SOUSTITRE)</p>]
+				[<p class="surtitre #EDIT{surtitre}">(#SURTITRE)</p>]
+				<h3 class=" #EDIT{titre}">[(#TITRE|supprimer_numero)]</h3>
+				[<p class="soustitre #EDIT{soustitre}">(#SOUSTITRE)</p>]
 				#FIN_SURLIGNE
 				<BOUCLE_sivafecha(MOTS){id_rubrique}{titre=con fecha}>
@@ -69,5 +69,5 @@
 			
 			#DEBUT_SURLIGNE
-			[<div class="chapo">(#CHAPO)</div>]
+			[<div class="chapo #EDIT{chapo}">(#CHAPO)</div>]
 			[<div id="sommaire">
 				<h4><:icone_brouteur:></h4> [(#REM) = "Navigation rapide" on aurait prefere "Sommaire", mais pas de traduction ]
@@ -76,5 +76,5 @@
 			<br class="nettoyeur" />
 			[<p class="lien"><:voir_en_ligne:> : <a href="(#URL_SITE)" class="spip_out">[(#NOM_SITE|sinon{[(#URL_SITE|couper{80})]})]</a></p>]
-			[<div class="texte">(#TEXTE*|decouper_en_page{#ID_ARTICLE}|propre|reduire_image{510,0})</div>]
+			[<div class="texte #EDIT{texte}">(#TEXTE*|decouper_en_page{#ID_ARTICLE}|propre|reduire_image{510,0})</div>]
 			#FIN_SURLIGNE
 
Index: /trunk/spip/esqueleto-redcta/themes/redcta/inc/inc-comunicados.html
===================================================================
--- /trunk/spip/esqueleto-redcta/themes/redcta/inc/inc-comunicados.html	(revision 68)
+++ /trunk/spip/esqueleto-redcta/themes/redcta/inc/inc-comunicados.html	(revision 69)
@@ -12,5 +12,4 @@
 							</B_muestradoc1>
 							<div>	
-								<!--[(#LOGO_ARTICLE|#URL_SITE|sinon{<a href="#URL_SITE" title="#NOM_SITE">[<div class="subtituloaviso">#SURTITRE</div><div class="tituloaviso">(#TITRE|supprimer_numero)</div>]</a>}|reduire_image{120,0}) ]-->
 								[(#LOGO_ARTICLE|#URL_ARTICLE|sinon{<a href="#URL_ARTICLE" >[<div class="subtituloaviso">#SURTITRE</div><div class="tituloaviso">(#TITRE|supprimer_numero)</div>]</a>}|reduire_image{780,0}) ]
 							</div><!-- avisossuperior -->
@@ -20,9 +19,9 @@
 				<BOUCLE_comunicado(ARTICLES) {lang?}{titre_mot=comunicado de prensa}{lang} {par date}{0,1} {inverse}>
       					<div class="comunicado-in">
-							[<div id="rubriqueportada">(#SURTITRE)</div>]
-							<a href="#URL_ARTICLE"><h3>[(#TITRE|supprimer_numero)]</h3>
+							[<div class="rubriqueportada #EDIT{surtitre}">(#SURTITRE)</div>]
+							<a href="#URL_ARTICLE"><h3>[(#TITRE|supprimer_numero)]</h3></a>
 								[[(#DATE|journum)]/[(#DATE|mois)]/[(#DATE|annee)] - Prensa #NOM_SITE_SPIP]
-      							[<div class="sous-titre">(#SOUSTITRE)</div>]
-	  							[<div class="texte">(#CHAPO)<small>(ver articulo completo)</small></div>]</a>	
+      							[<div class="sous-titre #EDIT{soustitre}">(#SOUSTITRE)</div>]
+	  							[<div class="texte #EDIT{chapo}" >(#CHAPO)<a href="#URL_ARTICLE"><small>(ver articulo completo)</small></div>]</a>	
 						</div>
 					</BOUCLE_comunicado>
Index: /trunk/spip/esqueleto-redcta/themes/redcta/inc/inc-destacadalateral.html
===================================================================
--- /trunk/spip/esqueleto-redcta/themes/redcta/inc/inc-destacadalateral.html	(revision 68)
+++ /trunk/spip/esqueleto-redcta/themes/redcta/inc/inc-destacadalateral.html	(revision 69)
@@ -12,9 +12,9 @@
 				[(#LOGO_DOCUMENT|right||image_reduire{110,0})]
             </BOUCLE_foto>
-			[<div class="surtitrep">(#SURTITRE)</div>]
+			[<div class="surtitrep #EDIT{surtitre}">(#SURTITRE)</div>]
 			<div class="h5p">
 				<a href="#URL_ARTICLE">[(#TITRE|supprimer_numero)]</a>
 			</div>
-			[<div class="sous-titrep">(#SOUSTITRE)</div>]
+			[<div class="sous-titrep #EDIT{soustitre}">(#SOUSTITRE)</div>]
 		</BOUCLE_articles_por_secciones1>
 	</div>
Index: /trunk/spip/esqueleto-redcta/themes/redcta/inc/inc-head.html
===================================================================
--- /trunk/spip/esqueleto-redcta/themes/redcta/inc/inc-head.html	(revision 68)
+++ /trunk/spip/esqueleto-redcta/themes/redcta/inc/inc-head.html	(revision 69)
@@ -1,4 +1,5 @@
  
 <!-- links a las hojas de estilo -->
+
 
   	<link rel="stylesheet" 
@@ -42,5 +43,5 @@
 	<B_keywords_mot><meta name="Keywords" content='<BOUCLE_keywords_mot(MOTS){id_mot}{","}>[(#TITRE|supprimer_numero|supprimer_tags|attribut_html)]</BOUCLE_keywords_mot>' /></B_keywords_mot>
 </BOUCLE_mot_head><BOUCLE_rubrique_head(RUBRIQUES) {id_rubrique}>
-	<!-- Para una sección -->
+	<!-- Para una secciï¿œn -->
 	[<meta name="Description" content="(#DESCRIPTIF|supprimer_tags|attribut_html)" />]
 	<B_keywords_rubrique><meta name="Keywords" content='<BOUCLE_keywords_rubrique(MOTS){id_rubrique}{","}>[(#TITRE|supprimer_numero|supprimer_tags|attribut_html)]</BOUCLE_keywords_rubrique>' /></B_keywords_rubrique>
Index: /trunk/spip/esqueleto-redcta/themes/redcta/inc/inc-nota-principal.html
===================================================================
--- /trunk/spip/esqueleto-redcta/themes/redcta/inc/inc-nota-principal.html	(revision 68)
+++ /trunk/spip/esqueleto-redcta/themes/redcta/inc/inc-nota-principal.html	(revision 69)
@@ -10,10 +10,11 @@
 		<a href="#URL_ARTICLE">
 			<b>#SURTITRE</b>
-			<h3>#TITRE</h3>
-			<div class="chapodest1">
+			<h3>#TITRE</h3></a>
+			<div class="chapodest1 #EDIT{chapo}">
 				[[(#DATE|journum)]/[(#DATE|mois)]/[(#DATE|annee)] - #NOM_SITE_SPIP] #CHAPO
-				<span class="vermas"> &gt; nota completa</span>
+				<a href="#URL_ARTICLE"><span class="vermas"> &gt; nota completa</span></a>
 			</div>
-		</a>	
+			
+			
 	</div>
 </BOUCLE_principal>
Index: /trunk/spip/esqueleto-redcta/themes/redcta/inc/inc-nota-secundaria.html
===================================================================
--- /trunk/spip/esqueleto-redcta/themes/redcta/inc/inc-nota-secundaria.html	(revision 68)
+++ /trunk/spip/esqueleto-redcta/themes/redcta/inc/inc-nota-secundaria.html	(revision 69)
@@ -9,10 +9,10 @@
 		</div>
 		<a href="#URL_ARTICLE">#SURTITRE
-		<h3>#TITRE</h3>
-		<div class="chapodest1">
+		<h3>#TITRE</h3></a>
+		<div class="chapodest1  #EDIT{chapo}">
 				[[(#DATE|journum)]/[(#DATE|mois)]/[(#DATE|annee)] - #NOM_SITE_SPIP] #CHAPO
-				<span class="vermas"> &gt; nota completa</span>
+				<a href="#URL_ARTICLE"><span class="vermas"> &gt; nota completa</span></a>
 		</div>
-		</a>
+		
 	</div>
 	</td></tr></table>
Index: /trunk/spip/esqueleto-redcta/themes/redcta/inc/inc-notas-destacadas.html
===================================================================
--- /trunk/spip/esqueleto-redcta/themes/redcta/inc/inc-notas-destacadas.html	(revision 68)
+++ /trunk/spip/esqueleto-redcta/themes/redcta/inc/inc-notas-destacadas.html	(revision 69)
@@ -4,9 +4,10 @@
 			[<div id="rubriquedestacada">[(#LOGO_RUBRIQUE|left|#URL_RUBRIQUE|reduire_image{70}|inserer_attribut{hspace,5}||inserer_attribut{style,border:0px;margin-left:-3px})]<a href="#URL_RUBRIQUE">(#TITRE|supprimer_numero|unique{dest})</a></div>]</BOUCLE_nom_rub2>
 			<div class="spacedestacada"></div>
+			<table><TR><TD>
 			[(#LOGO_ARTICLE|left|#URL_ARTICLE|reduire_image{122}|inserer_attribut{hspace,5})] 
-			[<div class="surtitre">(#SURTITRE)</div>]
+			[<div class="surtitre #EDIT{surtitre}">(#SURTITRE)</div>]
 			<a href="#URL_ARTICLE"><h3>[(#TITRE|supprimer_numero)]</h3></a>
-      		[<div class="sous-titre">(#SOUSTITRE)</div>]
-	  		[<div class="texte">(#CHAPO)</div>]
+      		[<div class="sous-titre #EDIT{soustitre}">(#SOUSTITRE)</div>]
+	  		[<div class="texte #EDIT{chapo}">(#CHAPO)</div>]</TD></TR></table>
 						
 	</BOUCLE_articles_por_secciones>
Index: /trunk/spip/esqueleto-redcta/themes/redcta/rubrique.html
===================================================================
--- /trunk/spip/esqueleto-redcta/themes/redcta/rubrique.html	(revision 68)
+++ /trunk/spip/esqueleto-redcta/themes/redcta/rubrique.html	(revision 69)
@@ -78,7 +78,7 @@
 			</BOUCLE_foto> 
 							
-								<h3>[(#TITRE|supprimer_numero)]</h3>
-      							[<div class="sous-titre">(#SOUSTITRE)</div>]
-	  							[<div class="texte">(#CHAPO)</div>] 
+								<h3 class" #EDIT{titre}">[(#TITRE|supprimer_numero)]</h3>
+      							[<div class="sous-titre #EDIT{soustitre}">(#SOUSTITRE)</div>]
+	  							[<div class="texte #EDIT{chapo}">(#CHAPO)</div>] 
 								#TEXTE
 								
@@ -105,5 +105,5 @@
 										[(#LOGO_ARTICLE|left|#URL_ARTICLE|reduire_image{140,0}|inserer_attribut{hspace,5})]
 										<//B_foto1>
-			[(#LOGO_ARTICLE|right||reduire_image{80,0}|inserer_attribut{hspace,5})]<a href="#URL_ARTICLE"><strong>[(#TITRE|supprimer_numero)]</strong><br />
+			[(#LOGO_ARTICLE|right||reduire_image{80,0}|inserer_attribut{hspace,5})]<a href="#URL_ARTICLE"><strong>[(#TITRE|supprimer_numero)]</strong><br /></a>
 				<BOUCLE_sivafecha(MOTS){id_rubrique}{titre=con fecha}>
 					<small>[(#DATE|nom_jour) ][(#DATE|affdate)]</small>
@@ -112,5 +112,5 @@
 					<small>[, <:par_auteur:> (#LESAUTEURS)]</small>
 				</BOUCLE_sivaautor>
-				[<br />(#CHAPO)]</a>
+				[<br /><div class=" #EDIT{chapo}">(#CHAPO)]</div>
 			</td></tr></table>
 			</BOUCLE_articles_rubriques><br />
Index: /trunk/spip/esqueleto-redcta/themes/redcta/styles/estilo.css
===================================================================
--- /trunk/spip/esqueleto-redcta/themes/redcta/styles/estilo.css	(revision 68)
+++ /trunk/spip/esqueleto-redcta/themes/redcta/styles/estilo.css	(revision 69)
@@ -613,5 +613,5 @@
 
 #galeria{float:left}
-#rubriqueportada{font-size:.9em;
+.rubriqueportada{font-size:.9em;
 font-weight:bold;
 padding-top:5px;
