Small UI tweaks
authorveltr
Fri, 27 Sep 2013 15:58:40 +0200
changeset 123 11f2aa317b70
parent 120 a09b26c435a1
child 124 dd5358d4d2ac
Small UI tweaks
src/jocondelab/static/jocondelab/css/front-common.css
src/jocondelab/static/jocondelab/css/front-geo.css
src/jocondelab/static/jocondelab/css/front-notice.css
src/jocondelab/static/jocondelab/js/front-common.js
src/jocondelab/static/jocondelab/js/front-geo.js
src/jocondelab/static/jocondelab/js/front-notice.js
src/jocondelab/static/jocondelab/js/front-search.js
src/jocondelab/static/jocondelab/js/front-termlist.js
src/jocondelab/static/jocondelab/js/front-timeline.js
src/jocondelab/static/jocondelab/lib/L.Control.Zoomslider.css
src/jocondelab/static/jocondelab/lib/L.Control.Zoomslider.js
src/jocondelab/templates/jocondelab/front_describe.html
src/jocondelab/templates/jocondelab/front_geo.html
src/jocondelab/templates/jocondelab/front_notice.html
src/jocondelab/templates/jocondelab/partial/contributed_item.html
src/jocondelab/urls.py
src/jocondelab/views/ajax.py
src/jocondelab/views/front_office.py
--- a/src/jocondelab/static/jocondelab/css/front-common.css	Fri Sep 27 10:26:26 2013 +0200
+++ b/src/jocondelab/static/jocondelab/css/front-common.css	Fri Sep 27 15:58:40 2013 +0200
@@ -197,8 +197,12 @@
     background-position: 4px center;
 }
 
+.header-search-form {
+    font-size: 12px;
+}
+
 .header-search-input, .header-search-form .tagit {
-    margin: 0 auto; width: 260px; max-width: 100%; font-size: 14px; z-index: 1;
+    margin: 0 auto; width: 260px; max-width: 100%; z-index: 1;
 }
 
 .header-search-input {
@@ -221,6 +225,14 @@
     float: right;
 }
 
+.header-search-form .tagit-new {
+    max-width: 50%;
+}
+
+.header-search-form .tagit input[type="text"] {
+    max-width: 100%;
+}
+
 html[dir=rtl] .header-search-form .tagit {
     float: left;
 }
@@ -232,11 +244,11 @@
 /* Big Search Form (on search and home pages) */
 
 .big-search-form {
-    text-align: center; margin: 20px 0; clear: both;
+    text-align: center; margin: 20px 0; clear: both; font-size: 15px;
 }
 
 .big-search-input, .big-search-form .tagit {
-    margin: 0 auto; width: 80%; font-size: 15px;
+    margin: 0 auto; width: 80%;
 }
 
 .big-search-input {
@@ -447,7 +459,7 @@
 }
 
 .wiki-info-image {
-    max-width: 220px; max-height: 300px; float: left; margin: 10px 10px 2px;
+    float: left; margin: 10px 10px 2px;
 }
 
 html[dir=rtl] .wiki-info, html[dir=rtl] .wiki-info-image {
@@ -474,42 +486,16 @@
     .header-widgets {
         padding: 0;
     }
-}
-
-@media screen and (max-width: 740px) and (min-width: 380px) {
-    .wiki-info {
-        width: 360px; height: 240px;
-    }
-    .wiki-info-image {
-        max-width: 160px; max-height: 220px;
-    }
-    .wiki-info-abstract {
-        font-size: 12px;
-    }
-}
-
-@media screen and (max-width: 380px) and (min-width: 260px) {
-    .wiki-info {
-        width: 240px; height: 240px;
-    }
-    .wiki-info-image {
-        max-width: 100px; max-height: 120px;
-    }
+    
     .wiki-info-abstract {
         font-size: 12px;
     }
 }
 
 @media screen and (max-width: 260px) {
-    .wiki-info {
-        width: 120px; height: 360px;
-    }
     .wiki-info-image {
         display: none;
     }
-    .wiki-info-abstract {
-        font-size: 12px;
-    }
 }
 
 @media screen and (max-width: 540px) {
--- a/src/jocondelab/static/jocondelab/css/front-geo.css	Fri Sep 27 10:26:26 2013 +0200
+++ b/src/jocondelab/static/jocondelab/css/front-geo.css	Fri Sep 27 15:58:40 2013 +0200
@@ -1,5 +1,5 @@
 .map-container {
-   position: relative; width: 100%; height: 400px; margin-top: 10px;
+   position: relative; width: 100%; height: 460px; margin-top: 10px;
 }
 
 #map {
--- a/src/jocondelab/static/jocondelab/css/front-notice.css	Fri Sep 27 10:26:26 2013 +0200
+++ b/src/jocondelab/static/jocondelab/css/front-notice.css	Fri Sep 27 15:58:40 2013 +0200
@@ -144,16 +144,8 @@
     background-position: -11px -6px;
 }
 
-.contribution-novote .contribution-upvote {
-    display: none;
-}
-
-.contribution-novote .contribution-downvote {
-    background-image: none; height: 16px; line-height: 16px; font-size: 12px; font-weight: bold; text-align: right;
-}
-
-.contribution-novote .contribution-downvote:before {
-    content: "×";
+.contribution-remove {
+    display: block; height: 16px; line-height: 16px; font-size: 12px; font-weight: bold; text-align: center;
 }
 
 .button-links {
--- a/src/jocondelab/static/jocondelab/js/front-common.js	Fri Sep 27 10:26:26 2013 +0200
+++ b/src/jocondelab/static/jocondelab/js/front-common.js	Fri Sep 27 15:58:40 2013 +0200
@@ -215,6 +215,7 @@
    
     var gridsize = 160,
         $popin = null,
+        $results = $(".results"),
         hoverPopin = false;
     
     function removePopin() {
@@ -236,12 +237,26 @@
         var $tblist = $(".notice-list");
         if ($tblist.length) {
             gridsize = $(".notice-item").width() || 160;
-            var delta = $tblist.parent().width() % gridsize,
-                l = Math.floor(delta/2),
-                r = delta - l;
+            var outerw = $results.width(),
+                delta = outerw % gridsize,
+                innerw = outerw - delta,
+                p = Math.floor(delta/2);
             $tblist.css({
-                padding: "0 " + l + "px 0 " + r + "px"
+                padding: "0 " + p + "px"
             });
+            var $wikinfo = $(".wiki-info");
+            if ($wikinfo.length) {
+                var wkw = Math.min(3*gridsize, innerw),
+                    wkh = 2*gridsize;
+                $wikinfo.css({
+                    width: wkw + "px",
+                    height: wkh + "px"
+                });
+                $wikinfo.find(".wiki-info-image").css({
+                    "max-width": (wkw / 2 - 10) + "px",
+                    "max-height": wkh - 20
+                });
+            }
         }
         throttledCheckSizes();
     }
@@ -383,14 +398,14 @@
     window.loadSearchResults = function(query) {
         $(".hide-on-search").hide();
         $win.off("scroll.ajaxload");
-        $(".results").empty();
+        $results.empty();
         $(".loading-please-wait").show();
         $.ajax({
             url: urls.ajax_search,
             data: query,
             dataType: "html",
             success: function(html) {
-                $(".results").html(html);
+                $results.html(html);
                 bindResultsMouseover();
                 $(".loading-please-wait").hide();
                 scrollLoad(query);
--- a/src/jocondelab/static/jocondelab/js/front-geo.js	Fri Sep 27 10:26:26 2013 +0200
+++ b/src/jocondelab/static/jocondelab/js/front-geo.js	Fri Sep 27 15:58:40 2013 +0200
@@ -1,6 +1,6 @@
 $(function() {
     var map = L.map('map', {
-        center: [30, 0],
+        center: [20, 0],
         zoom: 2,
         maxBounds: [[-100,-200],[100,200]]
     });
--- a/src/jocondelab/static/jocondelab/js/front-notice.js	Fri Sep 27 10:26:26 2013 +0200
+++ b/src/jocondelab/static/jocondelab/js/front-notice.js	Fri Sep 27 15:58:40 2013 +0200
@@ -234,7 +234,6 @@
                 $tmpItem = $(tmpTemplate);
             $ntl.append($tmpItem);
             $tmpItem.text(gettext("Validating Resource…"));
-            $this.autocomplete("close");
             $this.val("");
             getDbpedia(ui.item.label, function(label, termdata) {
                 $tmpItem.text(gettext("Saving contribution…"));
@@ -274,6 +273,7 @@
                 $tmpItem.remove();
                 alert(gettext("No DbPedia resource found for term") + " " + ui.item.label);
             });
+            return false;
         },
         close: function(e, ui) {
             $curitem = null;
@@ -282,7 +282,7 @@
         minLength: 2
     });
     
-    $(".notice-contribution-list").on("click",".contribution-upvote, .contribution-downvote", function() {
+    $(".notice-contribution-list").on("click",".contribution-upvote, .contribution-downvote, .contribution-remove", function() {
         var $this = $(this),
             $li = $(this).parents("li"),
             $list = $li.parent(),
--- a/src/jocondelab/static/jocondelab/js/front-search.js	Fri Sep 27 10:26:26 2013 +0200
+++ b/src/jocondelab/static/jocondelab/js/front-search.js	Fri Sep 27 15:58:40 2013 +0200
@@ -5,7 +5,6 @@
 $(function() {
     
     bindResultsMouseover();
-    dbpediaBox.bind(".term-cloud a");
     if (typeof queryobj === "object" && queryobj) {
         scrollLoad(queryobj);
     }
--- a/src/jocondelab/static/jocondelab/js/front-termlist.js	Fri Sep 27 10:26:26 2013 +0200
+++ b/src/jocondelab/static/jocondelab/js/front-termlist.js	Fri Sep 27 15:58:40 2013 +0200
@@ -14,6 +14,7 @@
                     loadUrl($(this).attr("href"));
                     return false;
                 });
+                dbpediaBox.bind($tc.find(".terms h3"));
             }
         });
     }
@@ -25,4 +26,6 @@
         loadUrl($this.find("a").attr("href"));
         return false;
     });
+    
+    dbpediaBox.bind(".term-cloud a");
 });
--- a/src/jocondelab/static/jocondelab/js/front-timeline.js	Fri Sep 27 10:26:26 2013 +0200
+++ b/src/jocondelab/static/jocondelab/js/front-timeline.js	Fri Sep 27 15:58:40 2013 +0200
@@ -21,12 +21,42 @@
         fromYear, toYear, cWidth, wLineDist, zoomFactor,
         isRtl = $("html").is("[dir=rtl]");
     
+    /*
+     *  HOW UNIT CONVERSIONS WORK ?
+     * 
+     *  First, Years from startYear (-20K) to endYear (now) are mapped to a [0,1] range that we call T
+     *    - tToYr converts units in this range to years, and yrToT does the reverse conversion
+     *  
+     *  Then, a non-linear scale is applied to have a focus on recent years.
+     *  In this case, I chose a gamma correction : y = x^gamma which also yields a [0,1] range
+     *    - The Years-Based ranged is converted to the range used for pixel distances with the function
+     *      wToT (reverse tToW -> x = y^(1/gamma))
+     *  
+     *  This new range "w" is used for all pixel distance calculations.
+     *  
+     *  For the "mini-Timeline" (full year range, at the top), these are converted directly
+     * 
+     *  For the main timeline, which only show a subset (window) of the full timeline,
+     *  two more parameters are required :
+     *    - The zoom level (variable zoomLevel)
+     *    - The position of the centre of the window relative to the range (called wCenter)
+     *  
+     *  The year range of our window is calculated from these parameters
+     *  
+     *  Each time we zoom in, the scale is increased by 41 % (that's the square root of 2
+     *  and also the ratio between An and An-1 - e.g. A4 and A3 paper sheets)
+     *  This way, the scale is proportional to 1.41 to the power of the zoom level.
+     *  This is why we calculate a zoomFactor from zoomLevel
+     *  
+     * 
+     * */
+    
     function tToW(t) {
-        //return Math.pow(2 / (2-Math.min(1,Math.max(0,t))) - 1,gamma);
+        //return 2 / (2-Math.min(1,Math.max(0,t))) - 1,;
         return Math.pow(t, gamma);
     }
     function wToT(w) {
-        //return 2 - 2/(Math.pow(Math.min(1,Math.max(0,w)),1/gamma)+1);
+        //return 2 - 2/(Math.min(1,Math.max(0,w))+1);
         return Math.pow(w, 1/gamma);
     }
     function tToYr(t) {
@@ -96,11 +126,17 @@
         zoomFactor = Math.pow(Math.SQRT2, zoomLevel);
     }
     function updateCoords() {
+        /* Let's first check if the timeline width has been changed */
         cWidth = $tlcontainer.width();
         cHeight = $tlcontainer.height();
         wLineDist = null;
+        /* We calculate the bounding years of our window */
         fromYear = wToYr(wCenter - .5 / zoomFactor);
         toYear = wToYr(wCenter + .5 / zoomFactor);
+        /*
+         We want to choose the distance between lines that we want to display, so that
+         two lines at the first quarter of our range are more than 50 pixels apart.
+        */
         var wAtQuarter = (wCenter - .25 /zoomFactor),
             yearAtQuarter = wToYr(wAtQuarter),
             posAtQuarter = wToX(wAtQuarter);
@@ -113,6 +149,7 @@
             }
             wLineDist = lineDistances[i];
         }
+        /* If the slider range is larger than the window, we constrain it to the window */
         startSlide = boundValue(userStartSlide);
         endSlide = boundValue(userEndSlide);
         var startPos = yrToSliderPos(startSlide),
@@ -130,17 +167,19 @@
     function redrawView() {
         canvas.width = cWidth;
         canvas.height = cHeight;
-        
-        var ctx = canvas.getContext("2d");
-        
-        var xstart = yrToMiniX(fromYear),
+        var ctx = canvas.getContext("2d"),
+            xstart = yrToMiniX(fromYear),
             xend = yrToMiniX(toYear),
             xleft = Math.min(xstart, xend),
             xright = Math.max(xstart, xend);
+        
+        /* We first draw the darkish rectangle showing the full timeline */
         ctx.fillStyle = "#e0e0e0";
         ctx.fillRect( 0, 0, cWidth, 30 );
+        /* We draw the rectangle for the main timeline */
         ctx.fillStyle = "#f8f8f8";
         ctx.fillRect(0, 60, cWidth, cHeight - 60);
+        /* We now draw the blue "funnel" showing the relationship between timelines */
         ctx.fillStyle = "rgba(0, 64, 255, .05)";
         ctx.strokeStyle = "#3090ff";
         ctx.beginPath();
@@ -155,8 +194,11 @@
         ctx.closePath();
         ctx.stroke();
         ctx.fill();
+        /* Whoaw, that was a long line to draw */
+        /* Now, we draw the years on the full timeline */
         ctx.font = 'bold 12px Arial,Helvetica';
         ctx.fillStyle = "#333333";
+        ctx.strokeStyle = "#333333";
         for (var i = 0; i < miniLines.length; i++ ) {
             var y = miniLines[i],
                 x = yrToMiniX(y);
@@ -169,6 +211,7 @@
             ctx.textAlign = (i ? (i == miniLines.length - 1 ? (isRtl ? "left" : "right" ) : "center") : (isRtl ? "right" : "left" ) );
             ctx.fillText(y || 1, x,20);
         }
+        /* Now, we draw the years on the main timeline */
         ctx.textAlign = 'center';
         ctx.font = 'bold 14px Arial,Helvetica';
         ctx.fillStyle = "#000000";
@@ -185,13 +228,14 @@
                 ctx.fillText(y || 1, x, 80);
             }
         }
+        /* Now displaying the different terms that we will show on the timeline */
         var html = _(tlCache).chain()
-            .filter(function(item) {
+            .filter(function(item) { // Only show those within the range
                 return (item.end_year >= fromYear && item.start_year <= toYear);
-            }).first(itemCount)
-            .sortBy(function(item) {
+            }).first(itemCount) // Take the first 12
+            .sortBy(function(item) { // Sort by mean year
                 return (item.start_year + item.end_year);
-            }).map(function(item) {
+            }).map(function(item) { // Render them as HTML
                 var l = Math.min(cWidth, Math.max(0, yrToX(item.start_year))),
                     r = Math.min(cWidth, Math.max(0, yrToX(item.end_year + 1)));
                 return itemTpl({
@@ -202,6 +246,7 @@
                 });
             }).value().join("");
         $(".timeline-list").html(html);
+        // Binding events to the freshly rendered HTML
         dbpediaBox.bind(".timeline-item-box");
         $(".timeline-item-box").click(function() {
             var $this = $(this);
@@ -254,6 +299,7 @@
         
     var mousedown = false, dragging = false, xstart = null, wcStart, dragmini;
     
+    /* Managing Timeline dragging */
     $tlcontainer.mousedown(function(e) {
         dragging = false;
         mousedown = true;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jocondelab/static/jocondelab/lib/L.Control.Zoomslider.css	Fri Sep 27 15:58:40 2013 +0200
@@ -0,0 +1,103 @@
+/** Slider **/
+.leaflet-control-zoomslider-wrap {
+	padding-top: 5px;
+	padding-bottom: 5px;
+	background-color: #fff;
+	border-bottom: 1px solid #ccc;
+}
+.leaflet-control-zoomslider-body {
+	width: 2px;
+	border: solid #fff;
+	border-width: 0px 9px 0px 9px;
+	background-color: black;
+	margin: 0 auto;
+}
+.leaflet-control-zoomslider-knob {
+	position: relative;
+	width: 12px;
+	height: 4px;
+	background-color: #efefef;
+	-webkit-border-radius: 2px;
+	border-radius: 2px;
+	border: 1px solid #000;
+	margin-left: -6px;
+}
+.leaflet-control-zoomslider-body:hover {
+	cursor: pointer;
+}
+.leaflet-control-zoomslider-knob:hover {
+	cursor: default;
+	cursor: -webkit-grab;
+	cursor:    -moz-grab;
+}
+
+.leaflet-dragging .leaflet-control-zoomslider,
+.leaflet-dragging .leaflet-control-zoomslider-wrap,
+.leaflet-dragging .leaflet-control-zoomslider-body,
+.leaflet-dragging .leaflet-control-zoomslider a,
+.leaflet-dragging .leaflet-control-zoomslider a.leaflet-control-zoomslider-disabled,
+.leaflet-dragging .leaflet-control-zoomslider-knob:hover  {
+	cursor: move;
+	cursor: -webkit-grabbing;
+	cursor:    -moz-grabbing;
+}
+
+/** Leaflet Zoom Styles **/
+.leaflet-container .leaflet-control-zoomslider {
+	margin-left: 10px;
+	margin-top: 10px;
+}
+.leaflet-control-zoomslider a {
+	width: 26px;
+	height: 26px;
+	text-align: center;
+	text-decoration: none;
+	color: black;
+	display: block;
+}
+.leaflet-control-zoomslider a:hover {
+	background-color: #f4f4f4;
+}
+.leaflet-control-zoomslider-in {
+	font: bold 18px 'Lucida Console', Monaco, monospace;
+}
+.leaflet-control-zoomslider-in:after{
+	content:"+"
+}
+.leaflet-control-zoomslider-out {
+	font: bold 22px 'Lucida Console', Monaco, monospace;
+}
+.leaflet-control-zoomslider-out:after{
+	content:"-"
+}
+.leaflet-control-zoomslider a.leaflet-control-zoomslider-disabled {
+	cursor: default;
+	color: #bbb;
+}
+
+/* Touch */
+.leaflet-touch .leaflet-control-zoomslider-body {
+	background-position: 10px 0px;
+}
+.leaflet-touch .leaflet-control-zoomslider-knob {
+	width:16px;
+	margin-left: -1px;
+}
+.leaflet-touch .leaflet-control-zoomslider a {
+	width: 30px;
+	height: 30px;
+}
+.leaflet-touch .leaflet-control-zoomslider-in {
+	font-size: 24px;
+	line-height: 29px;
+}
+.leaflet-touch .leaflet-control-zoomslider-out {
+	font-size: 28px;
+	line-height: 30px;
+}
+.leaflet-touch .leaflet-control-zoomslider {
+	box-shadow: none;
+}
+.leaflet-touch .leaflet-control-zoomslider {
+	border: 4px solid rgba(0,0,0,0.3);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jocondelab/static/jocondelab/lib/L.Control.Zoomslider.js	Fri Sep 27 15:58:40 2013 +0200
@@ -0,0 +1,206 @@
+L.Control.Zoomslider = (function () {
+
+	var Knob = L.Draggable.extend({
+		initialize: function (element, stepHeight, knobHeight) {
+			L.Draggable.prototype.initialize.call(this, element, element);
+			this._element = element;
+
+			this._stepHeight = stepHeight;
+			this._knobHeight = knobHeight;
+
+			this.on('predrag', function () {
+				this._newPos.x = 0;
+				this._newPos.y = this._adjust(this._newPos.y);
+			}, this);
+		},
+
+		_adjust: function (y) {
+			var value = Math.round(this._toValue(y));
+			value = Math.max(0, Math.min(this._maxValue, value));
+			return this._toY(value);
+		},
+
+		// y = k*v + m
+		_toY: function (value) {
+			return this._k * value + this._m;
+		},
+		// v = (y - m) / k
+		_toValue: function (y) {
+			return (y - this._m) / this._k;
+		},
+
+		setSteps: function (steps) {
+			var sliderHeight = steps * this._stepHeight;
+			this._maxValue = steps - 1;
+
+			// conversion parameters
+			// the conversion is just a common linear function.
+            this._k = -this._stepHeight;
+            this._m = sliderHeight - (this._stepHeight + this._knobHeight) / 2;
+		},
+
+		setPosition: function (y) { 
+			L.DomUtil.setPosition(this._element,
+								  L.point(0, this._adjust(y)));
+		},
+
+		setValue: function (v) {
+			this.setPosition(this._toY(v));
+		},
+
+		getValue: function () {
+			return this._toValue(L.DomUtil.getPosition(this._element).y);
+		}
+	});
+
+	var Zoomslider = L.Control.extend({
+		options: {
+			position: 'topleft',
+			// Height of zoom-slider.png in px
+			stepHeight: 8,
+			// Height of the knob div in px (including border)
+			knobHeight: 6,
+			styleNS: 'leaflet-control-zoomslider'
+		},
+
+		onAdd: function (map) {
+			this._map = map;
+			this._ui = this._createUI();
+			this._knob = new Knob(this._ui.knob,
+								  this.options.stepHeight,
+								  this.options.knobHeight);
+
+			map .whenReady(this._initKnob,           this)
+				.whenReady(this._initEvents,         this)
+				.whenReady(this._updateSize,         this)
+				.whenReady(this._updateKnobValue,    this)
+				.whenReady(this._updateDisabled,     this);
+			return this._ui.bar;
+		},
+
+		onRemove: function (map) {
+			map .off('zoomlevelschange',         this._updateSize,      this)
+				.off('zoomend zoomlevelschange', this._updateKnobValue, this)
+				.off('zoomend zoomlevelschange', this._updateDisabled,  this);
+		},
+
+		_createUI: function () {
+			var ui = {},
+				ns = this.options.styleNS;
+
+			ui.bar     = L.DomUtil.create('div', ns + ' leaflet-bar'),
+			ui.zoomIn  = this._createZoomBtn('in', 'top', ui.bar),
+			ui.wrap    = L.DomUtil.create('div', ns + '-wrap leaflet-bar-part', ui.bar),
+			ui.zoomOut = this._createZoomBtn('out', 'bottom', ui.bar),
+			ui.body    = L.DomUtil.create('div', ns + '-body', ui.wrap),
+			ui.knob    = L.DomUtil.create('div', ns + '-knob');
+
+			L.DomEvent.disableClickPropagation(ui.bar);
+			L.DomEvent.disableClickPropagation(ui.knob);
+
+			return ui;
+		},
+		_createZoomBtn: function (zoomDir, end, container) {
+			var classDef = this.options.styleNS + '-' + zoomDir
+					+ ' leaflet-bar-part'
+					+ ' leaflet-bar-part-' + end,
+				link = L.DomUtil.create('a', classDef, container);
+
+			link.href = '#';
+			link.title = 'Zoom ' + zoomDir;
+
+			L.DomEvent.on(link, 'click', L.DomEvent.preventDefault);
+
+			return link;
+		},
+
+		_initKnob: function () {
+			this._knob.enable();
+			this._ui.body.appendChild(this._ui.knob);
+		},
+		_initEvents: function (map) {
+			this._map
+				.on('zoomlevelschange',         this._updateSize,      this)
+				.on('zoomend zoomlevelschange', this._updateKnobValue, this)
+				.on('zoomend zoomlevelschange', this._updateDisabled,  this);
+
+			L.DomEvent.on(this._ui.body,    'click', this._onSliderClick, this);
+			L.DomEvent.on(this._ui.zoomIn,  'click', this._zoomIn,        this);
+			L.DomEvent.on(this._ui.zoomOut, 'click', this._zoomOut,       this);
+
+			this._knob.on('dragend', this._updateMapZoom, this);
+		},
+
+		_onSliderClick: function (e) {
+			var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
+				y = L.DomEvent.getMousePosition(first).y
+					- L.DomUtil.getViewportOffset(this._ui.body).y; // Cache this?
+
+			this._knob.setPosition(y);
+			this._updateMapZoom();
+		},
+
+		_zoomIn: function (e) {
+			this._map.zoomIn(e.shiftKey ? 3 : 1);
+		},
+		_zoomOut: function (e) {
+			this._map.zoomOut(e.shiftKey ? 3 : 1);
+		},
+
+		_zoomLevels: function () {
+			var zoomLevels = this._map.getMaxZoom() - this._map.getMinZoom() + 1;
+			return zoomLevels < Infinity ? zoomLevels : 0;
+		},
+		_toZoomLevel: function (value) {
+			return value + this._map.getMinZoom();
+		},
+		_toValue: function (zoomLevel) {
+			return zoomLevel - this._map.getMinZoom();
+		},
+
+		_updateSize: function () {
+			var steps = this._zoomLevels();
+
+			this._ui.body.style.height = this.options.stepHeight * steps + 'px';
+			this._knob.setSteps(steps);
+		},
+		_updateMapZoom: function () {
+			this._map.setZoom(this._toZoomLevel(this._knob.getValue()));
+		},
+		_updateKnobValue: function () {
+			this._knob.setValue(this._toValue(this._map.getZoom()));
+		},
+		_updateDisabled: function () {
+			var zoomLevel = this._map.getZoom(),
+				className = this.options.styleNS + '-disabled';
+
+			L.DomUtil.removeClass(this._ui.zoomIn,  className);
+			L.DomUtil.removeClass(this._ui.zoomOut, className);
+
+			if (zoomLevel === this._map.getMinZoom()) {
+				L.DomUtil.addClass(this._ui.zoomOut, className);
+			}
+			if (zoomLevel === this._map.getMaxZoom()) {
+				L.DomUtil.addClass(this._ui.zoomIn, className);
+			}
+		}
+	});
+
+	return Zoomslider;
+})();
+
+L.Map.mergeOptions({
+	zoomControl: false,
+	zoomsliderControl: true
+});
+
+L.Map.addInitHook(function () {
+	if (this.options.zoomsliderControl) {
+		this.zoomsliderControl = new L.Control.Zoomslider();
+		this.addControl(this.zoomsliderControl);
+	}
+});
+
+L.control.zoomslider = function (options) {
+	return new L.Control.Zoomslider(options);
+};
--- a/src/jocondelab/templates/jocondelab/front_describe.html	Fri Sep 27 10:26:26 2013 +0200
+++ b/src/jocondelab/templates/jocondelab/front_describe.html	Fri Sep 27 15:58:40 2013 +0200
@@ -1,6 +1,10 @@
 {% extends "jocondelab/front_notice.html" %}
 {% load i18n %}
 
+{% block title %}JocondeLab &raquo; {% trans "Contribuer à l'iconographie" %}{% endblock %}
+
+{% block breadcrumbs %}<a href="{% url 'random_describe' %}">{% trans "Contibuer à l'iconographie" %}</a>{% endblock %}
+
 {% block contribution %}
     <div class="contribution-frame description-frame">
         <h2>{% trans "Complétez l'iconographie" %}</h2>
--- a/src/jocondelab/templates/jocondelab/front_geo.html	Fri Sep 27 10:26:26 2013 +0200
+++ b/src/jocondelab/templates/jocondelab/front_geo.html	Fri Sep 27 15:58:40 2013 +0200
@@ -4,11 +4,14 @@
 {% block js_import %}
     {{block.super}}
     <script type="text/javascript" src="{{STATIC_URL}}jocondelab/lib/leaflet.js"></script>
+    <script type="text/javascript" src="{{STATIC_URL}}jocondelab/lib/L.Control.Zoomslider.js"></script>
+    <script type="text/javascript" src="{{STATIC_URL}}jocondelab/lib/oms.min.js"></script>
 {% endblock %}
 
 {% block css_import %}
     {{block.super}}
     <link rel="stylesheet" type="text/css" href="{{STATIC_URL}}jocondelab/lib/leaflet.css" />
+    <link rel="stylesheet" type="text/css" href="{{STATIC_URL}}jocondelab/lib/L.Control.Zoomslider.css" />
 {% endblock %}
 
 {% block js_declaration %}
--- a/src/jocondelab/templates/jocondelab/front_notice.html	Fri Sep 27 10:26:26 2013 +0200
+++ b/src/jocondelab/templates/jocondelab/front_notice.html	Fri Sep 27 15:58:40 2013 +0200
@@ -131,9 +131,6 @@
                             {% include "jocondelab/partial/contributed_item.html" %}
                         {% endfor %}
                         </ul>
-                        <p class="button-links">
-                            <a href="{% url 'front_describe' object.id %}">{% trans "Aidez-nous à approfondir l'iconographie" %}</a>
-                        </p>
                     </div>
 {% endblock %}
 
--- a/src/jocondelab/templates/jocondelab/partial/contributed_item.html	Fri Sep 27 10:26:26 2013 +0200
+++ b/src/jocondelab/templates/jocondelab/partial/contributed_item.html	Fri Sep 27 15:58:40 2013 +0200
@@ -2,8 +2,12 @@
 
 <li class="notice-term contribution-{{term.li_style}}" data-contribution-id="{{term.contribution_id}}" style="font-size: {{term.font_size}}px">
     <div class="contribution-vote">
+    {% if term.vote_mode %}
         <a href="#" class="contribution-upvote" title="{% trans 'Votez pour ce terme' %}"></a>
         <a href="#" class="contribution-downvote" title="{% trans 'Votez contre ce terme' %}"></a>
+    {% else %}
+        <a href="#" class="contribution-remove" title="{% trans 'Annulez votre contribution' %}">&times;</a>
+    {% endif %}
     </div>
     <a href="{% url 'front_search' %}?q={{term.label|urlencode}}" data-dbpedia-uri="{{term.dbpedia_uri}}">{{term.label}}</a>
 </li>
\ No newline at end of file
--- a/src/jocondelab/urls.py	Fri Sep 27 10:26:26 2013 +0200
+++ b/src/jocondelab/urls.py	Fri Sep 27 15:58:40 2013 +0200
@@ -39,8 +39,6 @@
     url(r'^timeline/$', TemplateView.as_view(template_name="jocondelab/front_timeline.html"), name='front_timeline'),
     url(r'^about/$', TemplateView.as_view(template_name="jocondelab/front_about.html"), name='front_about'),
     url(r'^notice/(?P<pk>\d+)/$', NoticeView.as_view(), name='front_notice'),
-    url(r'^notice/$', NoticeView.as_view(), name='random_notice'),
-    url(r'^describe/(?P<pk>\d+)/$', NoticeView.as_view(template_name="jocondelab/front_describe.html", show_contributions=False), name='front_describe'),
     url(r'^describe/$', NoticeView.as_view(template_name="jocondelab/front_describe.html", show_contributions=False), name='random_describe'),
     url(r'^ajax/terms/$', 'jocondelab.views.ajax.terms', name='ajax_terms'),
     url(r'^ajax/years/$', 'jocondelab.views.ajax.years', name='ajax_years'),
--- a/src/jocondelab/views/ajax.py	Fri Sep 27 10:26:26 2013 +0200
+++ b/src/jocondelab/views/ajax.py	Fri Sep 27 15:58:40 2013 +0200
@@ -20,7 +20,7 @@
     lang = request.GET.get('lang', request.LANGUAGE_CODE)[:2]
     q = request.GET.get('term', None)
     count = request.GET.get('count', 20)
-    qs = DbpediaFields.objects.filter(language_code=lang,label__icontains=q).values('dbpedia_uri','label').distinct().order_by('label')[:count]
+    qs = DbpediaFields.objects.filter(language_code=lang,label__iregex=r"\y%s"%q).values('dbpedia_uri','label').distinct().order_by('label')[:count]
     res = [{"dbpedia_uri": r['dbpedia_uri'], "label": r['label']} for r in qs]
     return HttpResponse(content=json.dumps(res), mimetype='application/json')
 
@@ -118,7 +118,8 @@
             "dbpedia_uri": contribution.term.dbpedia_uri,
             "contribution_id": contribution.id,
             "li_style": "positive" if contribution.contribution_count > 0 else "null",
-            "font_size": "%.1f"%(12. + .5 * max(0., min(12., contribution.contribution_count)))
+            "font_size": "%.1f"%(12. + .5 * max(0., min(12., contribution.contribution_count))),
+            "vote_mode": (contribution.thesaurus is None)
         }
         return self.render_to_response({"term": termdict})
     
--- a/src/jocondelab/views/front_office.py	Fri Sep 27 10:26:26 2013 +0200
+++ b/src/jocondelab/views/front_office.py	Fri Sep 27 15:58:40 2013 +0200
@@ -163,7 +163,8 @@
                     "dbpedia_uri": ct.term.dbpedia_uri,
                     "contribution_id": ct.id,
                     "li_style": "positive" if ct.contribution_count > 0 else "null",
-                    "font_size": "%.1f"%(12. + .5 * max(0., min(12., ct.contribution_count)))
+                    "font_size": "%.1f"%(12. + .5 * max(0., min(12., ct.contribution_count))),
+                    "vote_mode": True
                 } for ct in cqs.filter(thesaurus=None,term__dbpedia_fields__language_code=lang).order_by('-contribution_count')]
             context["contributions"] = contributions