diff --git a/ecrire/action/editer_liens.php b/ecrire/action/editer_liens.php
index 3b33fbd61c7c0b3e0c827aaad8fc29e09a461b06..967dd17c917c93ba8c6c36abc34d3c2aecad575f 100644
--- a/ecrire/action/editer_liens.php
+++ b/ecrire/action/editer_liens.php
@@ -374,7 +374,7 @@ function lien_insert($objet_source, $primary, $table_lien, $id, $objets, $qualif
 			roles_trouver_dans_qualif($objet_source, $objet, $qualif);
 
 		foreach ($id_objets as $id_objet) {
-			$objet = ($objet == '*') ? $objet : objet_type($objet); # securite
+			$objet = (($objet == '*') ? $objet : objet_type($objet)); # securite
 
 			$insertions = array(
 				'id_objet' => $id_objet,
@@ -387,6 +387,21 @@ function lien_insert($objet_source, $primary, $table_lien, $id, $objets, $qualif
 					$colonne_role => $role
 				);
 			}
+
+			if (lien_triables($table_lien)) {
+				$where = lien_where($primary, $id, $objet, $id_objet);
+				// si il y a deja un lien pour ce couple (avec un autre role?) on reprend le meme rang si non nul
+				if (!$rang = intval(sql_getfetsel('rang_lien', $table_lien, $where))) {
+					$where = lien_where($primary, '*', $objet, $id_objet);
+					$rang = intval(sql_getfetsel('max(rang_lien)', $table_lien, $where));
+					// si aucun lien n'a de rang, on en introduit pas, on garde zero
+					if ($rang>0) {
+						$rang = intval($rang) + 1;
+					}
+				}
+				$insertions['rang_lien'] = $rang;
+			}
+
 			$args = array(
 				'table_lien' => $table_lien,
 				'objet_source' => $objet_source,
@@ -413,6 +428,12 @@ function lien_insert($objet_source, $primary, $table_lien, $id, $objets, $qualif
 				and !sql_getfetsel($primary, $table_lien, $where)
 			) {
 
+				if (lien_triables($table_lien) and isset($insertions['rang_lien']) and intval($insertions['rang_lien'])) {
+					// on decale les liens de rang_lien>=la valeur inseree pour faire la place
+					$w = lien_where($primary, '*', $objet, $id_objet, array('rang_lien>='.intval($insertions['rang_lien']),"$primary!=".intval($id)));
+					sql_update($table_lien, array('rang_lien'=>'rang_lien+1'), $w);
+				}
+
 				$e = sql_insertq($table_lien, $insertions);
 				if ($e !== false) {
 					$ins++;
@@ -431,10 +452,75 @@ function lien_insert($objet_source, $primary, $table_lien, $id, $objets, $qualif
 			}
 		}
 	}
+	// si on a fait des insertions, on reordonne les liens concernes
+	if ($ins>0) {
+		lien_ordonner($objet_source, $primary, $table_lien, $id, $objets);
+	}
 
 	return ($echec ? false : $ins);
 }
 
+
+/**
+ * Reordonner les liens sur lesquels on est intervenus
+ * @param string $objet_source
+ * @param string $primary
+ * @param string $table_lien
+ * @param int $id
+ * @param array|string $objets
+ */
+function lien_ordonner($objet_source, $primary, $table_lien, $id, $objets) {
+	if (!lien_triables($table_lien)) {
+		return;
+	}
+
+	foreach ($objets as $objet => $id_objets) {
+		if (!is_array($id_objets)) {
+			$id_objets = array($id_objets);
+		}
+
+		foreach ($id_objets as $id_objet) {
+			$objet = (($objet == '*') ? $objet : objet_type($objet)); # securite
+
+			$where = lien_where($primary, '*', $objet, $id_objet);
+			$liens = sql_allfetsel("$primary, id_objet, objet, rang_lien", $table_lien, $where, $primary,"rang_lien");
+
+			$rangs = array_column($liens, 'rang_lien');
+			if (max($rangs)>0 or min($rangs)<0) {
+				$rang = 1;
+				foreach ($liens as $lien) {
+					$where = lien_where($primary, $lien[$primary], $objet, $id_objet, array('rang_lien!='.intval($rang)));
+					sql_updateq($table_lien, array('rang_lien' => $rang), $where);
+					$rang++;
+				}
+			}
+		}
+	}
+}
+
+
+/**
+ * Une table de lien est-elle triable ?
+ * elle doit disposer d'un champ rang_lien pour cela
+ * @param $table_lien
+ * @return mixed
+ */
+function lien_triables($table_lien) {
+	static $triables = array();
+	if (!isset($triables[$table_lien])) {
+		$trouver_table = charger_fonction('trouver_table', 'base');
+		$desc = $trouver_table($table_lien);
+		if ($desc and isset($desc['field']['rang_lien'])) {
+			$triables[$table_lien] = true;
+		}
+		else {
+			$triables[$table_lien] = false;
+		}
+	}
+	return $triables[$table_lien];
+}
+
+
 /**
  * Fabriquer la condition where en tenant compte des jokers *
  *
@@ -591,6 +677,10 @@ function lien_delete($objet_source, $primary, $table_lien, $id, $objets, $cond =
 			}
 		}
 	}
+	// si on a supprime des liens, on reordonne les liens concernes
+	if ($dels) {
+		lien_ordonner($objet_source, $primary, $table_lien, $id, $objets);
+	}
 
 	pipeline('trig_supprimer_objets_lies', $retire);
 
@@ -700,6 +790,7 @@ function lien_optimise($objet_source, $primary, $table_lien, $id, $objets) {
 function lien_set($objet_source, $primary, $table_lien, $id, $objets, $qualif) {
 	$echec = null;
 	$ok = 0;
+	$reordonner = false;
 	if (!$qualif) {
 		return false;
 	}
@@ -743,6 +834,13 @@ function lien_set($objet_source, $primary, $table_lien, $id, $objets, $qualif) {
 			);
 			$args['id_objet'] = $id_objet;
 
+			if (lien_triables($table_lien) and isset($qualif['rang_lien']) and intval($qualif['rang_lien'])) {
+				// on decale les liens de rang_lien>=la valeur inseree pour faire la place
+				$w = lien_where($primary, '*', $objet, $id_objet, array('rang_lien>='.intval($qualif['rang_lien']),"$primary!=".intval($id)));
+				sql_update($table_lien, array('rang_lien'=>'rang_lien+1'), $w);
+				$reordonner = true;
+			}
+
 			$where = lien_where($primary, $id, $objet, $id_objet, $cond);
 			$e = sql_updateq($table_lien, $qualif, $where);
 
@@ -760,6 +858,10 @@ function lien_set($objet_source, $primary, $table_lien, $id, $objets, $qualif) {
 			}
 		}
 	}
+	// si on a fait des modif de rang, on reordonne les liens concernes
+	if ($reordonner) {
+		lien_ordonner($objet_source, $primary, $table_lien, $id, $objets);
+	}
 
 	return ($echec ? false : $ok);
 }
diff --git a/ecrire/inc/filtres_ecrire.php b/ecrire/inc/filtres_ecrire.php
index d13df87f52fe739a8c636e9fafea11f72aebc9cd..72379adc24b3b1b826134052950c64a760d637ab 100644
--- a/ecrire/inc/filtres_ecrire.php
+++ b/ecrire/inc/filtres_ecrire.php
@@ -554,6 +554,9 @@ function afficher_plus_info($lien, $titre = "+", $titre_lien = "") {
 	}
 }
 
+
+
+
 /**
  * Lister les id objet_source associés à l'objet id_objet
  * via la table de lien objet_lien
@@ -567,17 +570,99 @@ function afficher_plus_info($lien, $titre = "+", $titre_lien = "") {
  * @return array
  */
 function lister_objets_lies($objet_source, $objet, $id_objet, $objet_lien) {
-	include_spip('action/editer_liens');
-	$l = array();
-	// quand $objet == $objet_lien == $objet_source on reste sur le cas par defaut de $objet_lien == $objet_source
-	if ($objet_lien == $objet and $objet_lien !== $objet_source) {
-		$res = objet_trouver_liens(array($objet => $id_objet), array($objet_source => '*'));
-	} else {
-		$res = objet_trouver_liens(array($objet_source => '*'), array($objet => $id_objet));
+	$res = lister_objets_liens($objet_source, $objet, $id_objet, $objet_lien);
+	if (count($res)) {
+		$r = reset($res);
+		if (isset($r['rang_lien'])) {
+			$l = array_column($res, 'rang_lien', $objet_source);
+			asort($l);
+			$l = array_keys($l);
+		}
+		else {
+			$l = array_column($res, $objet_source);
+		}
 	}
-	while ($row = array_shift($res)) {
-		$l[] = $row[$objet_source];
+	return $l;
+}
+
+
+/**
+ * Retrouver le rang du lien entre un objet source et un obet lie
+ * utilisable en direct dans un formulaire d'edition des liens, mais #RANG doit faire le travail automatiquement
+ * [(#ENV{objet_source}|rang_lien{#ID_AUTEUR,#ENV{objet},#ENV{id_objet},#ENV{_objet_lien}})]
+ *
+ * @param $objet_source
+ * @param $ids
+ * @param $objet_lie
+ * @param $idl
+ * @param $objet_lien
+ * @return string
+ */
+function retrouver_rang_lien($objet_source, $ids, $objet_lie, $idl, $objet_lien){
+	$res = lister_objets_liens($objet_source, $objet_lie, $idl, $objet_lien);
+	$res = array_column($res, 'rang_lien', $objet_source);
+
+	return (isset($res[$ids]) ? $res[$ids] : '');
+}
+
+
+/**
+ * Lister les liens en le memoizant dans une static
+ * pour utilisation commune par lister_objets_lies et retrouver_rang_lien dans un formuluaire d'edition de liens
+ * (evite de multiplier les requetes)
+ *
+ * @param $objet_source
+ * @param $objet
+ * @param $id_objet
+ * @param $objet_lien
+ * @return mixed
+ * @private
+ */
+function lister_objets_liens($objet_source, $objet, $id_objet, $objet_lien) {
+	static $liens = array();
+	if (!isset($liens["$objet_source-$objet-$id_objet-$objet_lien"])) {
+		include_spip('action/editer_liens');
+		// quand $objet == $objet_lien == $objet_source on reste sur le cas par defaut de $objet_lien == $objet_source
+		if ($objet_lien == $objet and $objet_lien !== $objet_source) {
+			$res = objet_trouver_liens(array($objet => $id_objet), array($objet_source => '*'));
+		} else {
+			$res = objet_trouver_liens(array($objet_source => '*'), array($objet => $id_objet));
+		}
+
+		$liens["$objet_source-$objet-$id_objet-$objet_lien"] = $res;
 	}
+	return $liens["$objet_source-$objet-$id_objet-$objet_lien"];
+}
 
-	return $l;
+/**
+ * Calculer la balise #RANG
+ * quand ce n'est pas un champ rang :
+ * peut etre le num titre, le champ rang_lien ou le rang du lien en edition des liens, a retrouver avec les infos du formulaire
+ * @param $titre
+ * @param $rang_lien
+ * @param $objet_source
+ * @param $id
+ * @param $env
+ * @return int|string
+ */
+function calculer_rang_smart($titre, $rang_lien, $objet_source, $id, $env) {
+	// Cas du #RANG utilisé dans #FORMULAIRE_EDITER_LIENS -> attraper le rang du lien
+	if (isset($env['form']) and $env['form']
+		and isset($env['_objet_lien']) and $env['_objet_lien']
+		and (function_exists('lien_triables') or include_spip('action/editer_liens'))
+		and $r = objet_associable($env['_objet_lien'])
+		and list($p, $table_lien) = $r
+	  and lien_triables($table_lien)
+	  and isset($env['objet']) and $env['objet']
+		and isset($env['id_objet']) and $env['id_objet']
+		and $objet_source
+		and $id = intval($id)
+	) {
+		$rang = retrouver_rang_lien($objet_source, $id, $env['objet'], $env['id_objet'], $env['_objet_lien']);
+		return ($rang ? $rang : '');
+	}
+	elseif(!is_null($rang_lien) and strlen($rang_lien)) {
+		return $rang_lien;
+	}
+	return recuperer_numero($titre);
 }
diff --git a/ecrire/public/balises.php b/ecrire/public/balises.php
index 3a19baa2594b84feb728efd6341dafabe5896598..e160eaf4c8b24a74432f126e757956a970df2efa 100644
--- a/ecrire/public/balises.php
+++ b/ecrire/public/balises.php
@@ -972,6 +972,8 @@ function balise_RANG_dist($p) {
 		// si pas trouve de champ sql rang :
 		if (!$_rang or $_rang == "''") {
 			$boucle = &$p->boucles[$b];
+
+			// on gere le cas ou #RANG est une extraction du numero dans le titre
 			$trouver_table = charger_fonction('trouver_table', 'base');
 			$desc = $trouver_table($boucle->id_table);
 			$_titre = ''; # où extraire le numero ?
@@ -998,13 +1000,29 @@ function balise_RANG_dist($p) {
 					}
 				}
 			}
-			
+
 			// si on n'a rien trouvé, on utilise le champ titre classique
 			if (!$_titre) {
 				$_titre = champ_sql('titre', $p);
 			}
-			
-			$_rang = "recuperer_numero($_titre)";
+
+			// ca peut etre un rang sur le lien
+			// (mais pareil, uniquement sur la boucle immediatement englobante uniquement)
+			$_rang_lien = champ_sql('rang_lien', $p, '', false);
+
+			// et on recupere aussi les infos de liaison si on est en train d'editer les liens justement
+			// cas des formulaires xxx_lies utilises par #FORMULAIRE_EDITER_LIENS
+			$type_boucle = $boucle->type_requete;
+			$objet = objet_type($type_boucle);
+			$id_table_objet = id_table_objet($type_boucle);
+			$_primary = champ_sql($id_table_objet, $p, '', false);
+			$_env = '$Pile[0]';
+
+			if (!$_titre) {$_titre = "''";}
+			if (!$_rang_lien) {$_rang_lien = "''";}
+			if (!$_primary) {$_primary = "''";}
+			$_rang = "calculer_rang_smart($_titre, $_rang_lien, '$objet', $_primary, $_env)";
+
 		}
 		
 		$p->code = $_rang;
diff --git a/prive/formulaires/editer_liens.php b/prive/formulaires/editer_liens.php
index e9ade964ea8b05ee51e691569ec5fa2688ca6145..334e8be838f2da2d6d1facc05e206e6b3182b422 100644
--- a/prive/formulaires/editer_liens.php
+++ b/prive/formulaires/editer_liens.php
@@ -142,6 +142,8 @@ function formulaires_editer_liens_charger_dist($a, $b, $c, $options = array()) {
 		'ajouter_lien' => '',
 		'supprimer_lien' => '',
 		'qualifier_lien' => '',
+		'ordonner_lien' => '',
+		'desordonner_liens' => '',
 		'_roles' => $roles, # description des roles
 		'_oups' => _request('_oups'),
 		'editable' => $editable,
@@ -160,6 +162,8 @@ function formulaires_editer_liens_charger_dist($a, $b, $c, $options = array()) {
  * - ajouter_lien et supprimer_lien
  * - remplacer_lien
  * - qualifier_lien
+ * - ordonner_lien
+ * - desordonner_liens
  *
  * Les deux premières peuvent être de trois formes différentes :
  * ajouter_lien[]="objet1-id1-objet2-id2"
@@ -177,6 +181,11 @@ function formulaires_editer_liens_charger_dist($a, $b, $c, $options = array()) {
  * qualifier_lien[objet1-id1-objet2-id2][valeur] = array("truc", "chose")
  * produira 2 liens chacun avec array("role"=>"role1","valeur"=>"truc") et array("role"=>"autre_role","valeur"=>"chose")
  *
+ * ordonner_lien doit être de la forme, et sert pour trier les liens
+ * ordonner_lien[objet1-id1-objet2-id2] = nouveau_rang
+ *
+ * desordonner_liens n'a pas de forme precise, il doit simplement estre non nul/non vide
+ *
  * @param string $a
  * @param string|int $b
  * @param int|string $c
@@ -233,6 +242,12 @@ function formulaires_editer_liens_traiter_dist($a, $b, $c, $options = array()) {
 
 		$supprimer = _request('supprimer_lien');
 		$ajouter = _request('ajouter_lien');
+		$ordonner = _request('ordonner_lien');
+
+		if (_request('desordonner_liens')) {
+			include_spip('action/editer_liens');
+			objet_qualifier_liens(array($objet_lien => '*'), array($objet => $id_objet), array('rang_lien' => 0));
+		}
 
 		// il est possible de preciser dans une seule variable un remplacement :
 		// remplacer_lien[old][new]
@@ -313,6 +328,24 @@ function formulaires_editer_liens_traiter_dist($a, $b, $c, $options = array()) {
 				set_request('_oups');
 			}
 		}
+
+		if ($ordonner) {
+			include_spip('action/editer_liens');
+			foreach ($ordonner as $k => $rang_lien) {
+				if ($lien = lien_verifier_action($k, '')) {
+					list($objet1, $ids, $objet2, $idl) = explode('-', $lien);
+					$qualif = array('rang_lien' => $rang_lien);
+
+					if ($objet_lien == $objet1) {
+						objet_qualifier_liens(array($objet1 => $ids), array($objet2 => $idl), $qualif);
+					} else {
+						objet_qualifier_liens(array($objet2 => $idl), array($objet1 => $ids), $qualif);
+					}
+					set_request('id_lien_ajoute', $ids);
+					set_request('_oups');
+				}
+			}
+		}
 	}