Merge last changes from rv
authorymh <ymh.work@gmail.com>
Tue, 01 Jan 2013 09:28:03 +0100
changeset 50 7b517a54b708
parent 49 d0da34fda4b4
child 51 3247fccfbd3f
Merge last changes from rv
server/src/main/webapp/static/css/renkan.css
server/src/main/webapp/static/img/topbarbuttons.png
server/src/main/webapp/static/js/i18n.js
server/src/main/webapp/static/js/ldtjson-bin.js
server/src/main/webapp/static/js/main.js
server/src/main/webapp/static/js/paper-renderer.js
server/src/main/webapp/static/js/renkan-publish.js
server/src/main/webapp/static/js/twitter-bin.js
server/src/main/webapp/static/js/underscore.js
server/src/main/webapp/static/js/wikipedia-bin.js
--- a/server/src/main/webapp/static/css/renkan.css	Mon Dec 31 13:37:31 2012 +0100
+++ b/server/src/main/webapp/static/css/renkan.css	Tue Jan 01 09:28:03 2013 +0100
@@ -71,7 +71,7 @@
 }
 
 .Rk-PadTitle {
-    float: left; font-size: 14px; height: 16px; margin: 4px 5px; width: 130px; background: #666666; padding: 4px; border: 1px solid #333333;
+    float: left; font-size: 14px; height: 16px; margin: 4px 5px; width: 200px; background: #666666; padding: 4px; border: 1px solid #333333;
     border-radius: 3px; box-shadow: 0 1px 0 #505050; color: #ffffff; font-weight: bold;
 }
 
@@ -146,6 +146,22 @@
 .Rk-AddEdge-Button:hover {
     background-position: -68px -35px;
 }
+
+.Rk-Save-Button {
+    width: 34px; background-position: -102px 0;
+}
+
+.Rk-Save-Button.disabled {
+    opacity: .5; cursor: default;
+}
+
+.Rk-Save-Button:hover {
+    background-position: -102px -35px;
+}
+
+.Rk-Save-Button.disabled:hover {
+    opacity: 1; background-position: -102px 0;
+}
 /* Canvas */
 
 .Rk-Canvas {
@@ -260,24 +276,36 @@
 /* Bin Search Field */
 
 .Rk-Search-Form {
-    padding: 10px; height: 27px;
+    padding: 5px 10px; height: 27px;
     background: #666666;
     background: -moz-linear-gradient(top, #606060 20%, #909090 80%);
     background: -webkit-linear-gradient(top, #606060 20%, #909090 80%);
 }
 
 .Rk-Search-Input, .Rk-Search-Select {
-    float: left; border-width: 1px; border-color: #003050;
+    float: left;  margin: 0;
 }
 
 .Rk-Search-Input {
-    border-top-left-radius: 5px; border-bottom-left-radius: 5px; border-style: solid;
-    font-size: 13px; width: 190px; background: #ffffff; height: 15px; padding: 5px;
+    border-top-left-radius: 5px; border-bottom-left-radius: 5px; border: 1px solid #003050;
+    font-size: 13px; background: #ffffff; height: 25px; padding: 0 5px; line-height: 25px;
+    -webkit-appearance: none;
+    -webkit-box-sizing: content-box;
+    -moz-box-sizing: content-box;
+    box-sizing: content-box; 
+}
+
+.Rk-Web-Search-Input {
+     width: 190px;
+}
+
+.Rk-Bins-Search-Input {
+     width: 235px;
 }
 
 .Rk-Search-Select {
     display: inline-block; position: relative; width: 45px;
-    border-style: solid none; cursor: pointer;
+    border-width: 1px; border-color: #003050; border-style: solid none; cursor: pointer;
     height: 25px; background: #ffffff url(../img/more.png) 30px 10px no-repeat;
 }
 
@@ -348,6 +376,11 @@
     background-position: -18px 0;
 }
 
+.Rk-Bin-Count {
+    float: right; background: #c000a0; color: #FFFFFF; text-shadow: 1px 1px 1px #000000; display: none;
+    border-radius: 4px; padding: 1px 3px; font-size: 10px; font-weight: bold; margin-top: 4px;
+}
+
 .Rk-Bin-Title-Icon {
     float: left; width: 25px; margin: 2px;
 }
Binary file server/src/main/webapp/static/img/topbarbuttons.png has changed
--- a/server/src/main/webapp/static/js/i18n.js	Mon Dec 31 13:37:31 2012 +0100
+++ b/server/src/main/webapp/static/js/i18n.js	Tue Jan 01 09:28:03 2013 +0100
@@ -11,6 +11,7 @@
         full_screen: "Full Screen",
         add_node: "Add Node",
         add_edge: "Add Edge",
+        save_project: "Archive Project",
         created_by: "Created by:",
         zoom_in: "Zoom In",
         zoom_out: "Zoom Out",
@@ -39,6 +40,7 @@
         full_screen: "Mode plein écran",
         add_node: "Ajouter un nœud",
         add_edge: "Ajouter un lien",
+        save_project: "Archiver le projet",
         created_by: "Créé par :",
         zoom_in: "Agrandir l’échelle",
         zoom_out: "Rapetisser l’échelle",
--- a/server/src/main/webapp/static/js/ldtjson-bin.js	Mon Dec 31 13:37:31 2012 +0100
+++ b/server/src/main/webapp/static/js/ldtjson-bin.js	Tue Jan 01 09:28:03 2013 +0100
@@ -4,12 +4,12 @@
 
 Rkns.Ldt.ProjectBin.prototype.tagTemplate = Rkns._.template(
     '<li class="Rk-Bin-Item" data-image="<%=static_url%>img/ldt-tag.png" data-uri="<%=ldt_platform%>ldtplatform/ldt/front/search/?search=<%=encodedtitle%>&field=all" data-title="<%-title%>" data-description="Tag \'<%-title%>\'">'
-    + '<img class="Rk-Ldt-Tag-Icon" src="<%=static_url%>img/ldt-tag.png" /><h4><%-title%></h4><div class="Rk-Clear"></div></li>'
+    + '<img class="Rk-Ldt-Tag-Icon" src="<%=static_url%>img/ldt-tag.png" /><h4><%=htitle%></h4><div class="Rk-Clear"></div></li>'
 );
 
 Rkns.Ldt.ProjectBin.prototype.annotationTemplate = Rkns._.template(
     '<li class="Rk-Bin-Item" data-image="<%=image%>" data-uri="<%=ldt_platform%>ldtplatform/ldt/front/player/<%=mediaid%>/#id=<%=annotationid%>" data-title="<%-title%>" data-description="<%-description%>">'
-    + '<img class="Rk-Ldt-Annotation-Icon" src="<%=image%>"/><h4><%-title%></h4><p><%-description%></p><p>Start: <%=start%>, End: <%=end%>, Duration: <%=duration%></p><div class="Rk-Clear"></div></li>'
+    + '<img class="Rk-Ldt-Annotation-Icon" src="<%=image%>"/><h4><%=htitle%></h4><p><%=hdescription%></p><p>Start: <%=start%>, End: <%=end%>, Duration: <%=duration%></p><div class="Rk-Clear"></div></li>'
 );
 
 Rkns.Ldt.ProjectBin.prototype._init = function(_renkan, _opts) {
@@ -19,8 +19,15 @@
     this.refresh();
 }
 
-Rkns.Ldt.ProjectBin.prototype.refresh = function() {
-    var _this = this;
+Rkns.Ldt.ProjectBin.prototype.render = function(searchstr) {
+    if (searchstr) {
+        var rxbase = searchstr.replace(/(\W)/g,'\\$1'),
+            _rgxp = new RegExp('('+rxbase+')','gi'),
+            rxtest = new RegExp(rxbase,'i')
+    }
+    function highlight(_text) {
+        return searchstr ? _text.replace(_rgxp, "<span class='searchmatch'>$1</span>") : _text;
+    }
     function convertTC(_ms) {
         function pad(_n) {
             var _res = _n.toString();
@@ -40,48 +47,78 @@
         _res += pad(_minutes) + ':' + pad(_seconds);
         return _res;
     }
+    
+    var _html = '<li><h3>Tags</h3></li>',
+        _projtitle = this.data.meta["dc:title"],
+        _this = this,
+        count = 0;
+    _this.title_$.text('LDT Project: "' + _projtitle + '"');
+    Rkns._(_this.data.tags).map(function(_tag) {
+        var _title = _tag.meta["dc:title"];
+        if (searchstr && !rxtest.test(_title)) {
+            return;
+        }
+        count++;
+        _html += _this.tagTemplate({
+            ldt_platform: _this.ldt_platform,
+            title: _title,
+            htitle: highlight(_title),
+            encodedtitle : encodeURIComponent(_title),
+            static_url : _this.renkan.static_url
+        })
+    });
+    _html += '<li><h3>Annotations</h3></li>';
+    Rkns._(_this.data.annotations).map(function(_annotation) {
+        var _description = _annotation.content.description,
+            _title = _annotation.content.title.replace(_description,"");
+        if (searchstr && !rxtest.test(_title) && !rxtest.test(_description)) {
+            return;
+        }
+        count++;
+        var _duration = _annotation.end - _annotation.begin,
+            _img = (
+                (_annotation.content && _annotation.content.img && _annotation.content.img.src)
+                ? _annotation.content.img.src
+                : _this.renkan.static_url + ( _duration ? "img/ldt-segment.png" : "img/ldt-point.png" )
+            );
+        _html += _this.annotationTemplate({
+            ldt_platform: _this.ldt_platform,
+            static_url: _this.renkan.static_url,
+            title: _title,
+            htitle: highlight(_title),
+            description: _description,
+            hdescription: highlight(_description),
+            start: convertTC(_annotation.begin),
+            end: convertTC(_annotation.end),
+            duration: convertTC(_duration),
+            mediaid: _annotation.media,
+            annotationid: _annotation.id,
+            image: _img
+        });
+    });
+    
+    this.main_$.html(_html);
+    if (searchstr && count) {
+        this.count_$.text(count).show();
+    } else {
+        this.count_$.hide();
+    }
+    if (searchstr && !count) {
+        this.$.hide();
+    } else {
+        this.$.show();
+    }
+    _renkan.resizeBins();
+}
+
+Rkns.Ldt.ProjectBin.prototype.refresh = function() {
+    var _this = this;
     Rkns.$.ajax({
         url: this.ldt_platform + 'ldtplatform/ldt/cljson/id/' + this.proj_id,
         dataType: "jsonp",
         success: function(_data) {
-            var _html = '<li><h3>Tags</h3></li>',
-                _projtitle = _data.meta["dc:title"];
-            _this.title_$.html('LDT Project: "' + _projtitle + '"');
-            _html += Rkns._(_data.tags).map(function(_tag) {
-                var _title = _tag.meta["dc:title"]
-                return _this.tagTemplate({
-                    ldt_platform: _this.ldt_platform,
-                    title: _title,
-                    encodedtitle : encodeURIComponent(_title),
-                    static_url: _this.renkan.static_url
-                })
-            }).join("");
-            _html += '<li><h3>Annotations</h3></li>';
-            _html += Rkns._(_data.annotations).map(function(_annotation) {
-                var _description = _annotation.content.description,
-                    _title = _annotation.content.title.replace(_description,""),
-                    _duration = _annotation.end - _annotation.begin,
-                    _img = (
-                        (_annotation.content && _annotation.content.img && _annotation.content.img.src)
-                        ? _annotation.content.img.src
-                        : _this.renkan.static_url + ( _duration ? "img/ldt-segment.png" : "img/ldt-point.png" )
-                    );
-                return _this.annotationTemplate({
-                    ldt_platform: _this.ldt_platform,
-                    title: _title,
-                    description: _description,
-                    start: convertTC(_annotation.begin),
-                    end: convertTC(_annotation.end),
-                    duration: convertTC(_duration),
-                    mediaid: _annotation.media,
-                    annotationid: _annotation.id,
-                    image: _img,
-                    static_url: _this.renkan.static_url
-                });
-            }).join("");
-            
-            _this.main_$.html(_html);
-            _renkan.resizeBins();
+            _this.data = _data;
+            _this.render();
         }
     });
 }
\ No newline at end of file
--- a/server/src/main/webapp/static/js/main.js	Mon Dec 31 13:37:31 2012 +0100
+++ b/server/src/main/webapp/static/js/main.js	Tue Jan 01 09:28:03 2013 +0100
@@ -56,6 +56,9 @@
                 _this.refresh();
                 return false;
             });
+        this.count_$ = Rkns.$('<div>')
+            .addClass("Rk-Bin-Count")
+            .appendTo(this.$);
         this.title_$ = Rkns.$('<h2>')
             .addClass("Rk-Bin-Title")
             .appendTo(this.$);
--- a/server/src/main/webapp/static/js/paper-renderer.js	Mon Dec 31 13:37:31 2012 +0100
+++ b/server/src/main/webapp/static/js/paper-renderer.js	Tue Jan 01 09:28:03 2013 +0100
@@ -295,9 +295,9 @@
             this.node_image.position = this.paper_coords;
         }
     }
-    
+        
     Rkns._.each(this.project.get("edges").filter(function (ed) { return ((ed.to === this.model) || (ed.from === this.model));}), function(edge, index, list){
-    	var repr = this.renderer.getRepresentationByModel(edge);
+        var repr = this.renderer.getRepresentationByModel(edge);
     	if(repr != null && typeof repr.from_representation.paper_coords !== "undefined" && typeof repr.to_representation.paper_coords !== "undefined") {
     		repr.redraw();
     	}
@@ -946,9 +946,7 @@
     this.renkan = _renkan;
     this.$ = Rkns.$(".Rk-Render");
     this.representations = [];
-    this.$.html(this.template({
-        l10n: _renkan.l10n
-    }))
+    this.$.html(this.template(_renkan));
     this.canvas_$ = this.$.find(".Rk-Canvas");
     this.editor_$ = this.$.find(".Rk-Editor");
     this.notif_$ = this.$.find(".Rk-Notifications");
@@ -1065,9 +1063,17 @@
     
     this.addRepresentations("Node", this.renkan.project.get("nodes"));
     this.addRepresentations("Edge", this.renkan.project.get("edges"));
+    this.renkan.project.on("change:title", function() {
+        _this.$.find(".Rk-PadTitle").val(_renkan.project.get("title"));
+    });
+    
+    this.$.find(".Rk-PadTitle").on("keyup input paste", function() {
+        _renkan.project.set({"title": $(this).val()});
+    })
+    
     this.renkan.project.get("users").each(function(_user) {
         _this.addUser(_user);
-    })
+    });
     
     this.renkan.project.on("add:users", function(_user) {
         _this.addUser(_user);
@@ -1085,11 +1091,12 @@
 }
 
 Rkns.Renderer.Scene.prototype.template = Rkns._.template(
-    '<div class="Rk-TopBar"><h3 class="Rk-PadTitle"><%=l10n.untitled_project%></h3>'
+    '<div class="Rk-TopBar"><input type="text" class="Rk-PadTitle" value="<%- project.get("title") || "" %>" placeholder="<%=l10n.untitled_project%>" />'
     + '<div class="Rk-Users"><div class="Rk-CurrentUser"><span class="Rk-CurrentUser-Color"></span><span class="Rk-CurrentUser-Name">&lt;unknown user&gt;</span></div><ul class="Rk-UserList"></ul></div>'
     + '<div class="Rk-TopBar-Separator"></div><div class="Rk-TopBar-Button Rk-FullScreen-Button"><div class="Rk-TopBar-Tooltip"><div class="Rk-TopBar-Tooltip-Tip"></div><div class="Rk-TopBar-Tooltip-Contents"><%=l10n.full_screen%></div></div></div>'
     + '<div class="Rk-TopBar-Separator"></div><div class="Rk-TopBar-Button Rk-AddNode-Button"><div class="Rk-TopBar-Tooltip"><div class="Rk-TopBar-Tooltip-Tip"></div><div class="Rk-TopBar-Tooltip-Contents"><%=l10n.add_node%></div></div></div>'
     + '<div class="Rk-TopBar-Separator"></div><div class="Rk-TopBar-Button Rk-AddEdge-Button"><div class="Rk-TopBar-Tooltip"><div class="Rk-TopBar-Tooltip-Tip"></div><div class="Rk-TopBar-Tooltip-Contents"><%=l10n.add_edge%></div></div></div>'
+    + '<div class="Rk-TopBar-Separator"></div><div class="Rk-TopBar-Button Rk-Save-Button"><div class="Rk-TopBar-Tooltip"><div class="Rk-TopBar-Tooltip-Tip"></div><div class="Rk-TopBar-Tooltip-Contents"><%=l10n.save_project%></div></div></div>'
     + '<div class="Rk-TopBar-Separator"></div></div>'
     + '<canvas class="Rk-Canvas" resize></canvas><div class="Rk-Editor"><div class="Rk-Notifications"></div>'
     + '<div class="Rk-ZoomButtons"><div class="Rk-ZoomIn" title="<%=l10n.zoom_in%>"></div><div class="Rk-ZoomOut" title="<%=l10n.zoom_out%>"></div></div>'
--- a/server/src/main/webapp/static/js/renkan-publish.js	Mon Dec 31 13:37:31 2012 +0100
+++ b/server/src/main/webapp/static/js/renkan-publish.js	Tue Jan 01 09:28:03 2013 +0100
@@ -905,9 +905,10 @@
 }
 
 Rkns.Renderer.Scene.prototype.autoScale = function() {
-    if (this.project.get("nodes").length) {
-        var _xx = this.project.get("nodes").map(function(_node) { return _node.get("position").x }),
-            _yy = this.project.get("nodes").map(function(_node) { return _node.get("position").y }),
+    var nodes = this.project.get("nodes")
+    if (nodes.length > 1) {
+        var _xx = nodes.map(function(_node) { return _node.get("position").x }),
+            _yy = nodes.map(function(_node) { return _node.get("position").y }),
             _minx = Math.min.apply(Math, _xx),
             _miny = Math.min.apply(Math, _yy),
             _maxx = Math.max.apply(Math, _xx),
@@ -916,6 +917,11 @@
         this.offset = paper.view.center.subtract(new paper.Point([(_maxx + _minx) / 2, (_maxy + _miny) / 2]).multiply(this.scale));
         this.redraw();
     }
+    if (nodes.length === 1) {
+        this.scale = 1;
+        this.offset = paper.view.center.subtract(new paper.Point([nodes.at(0).get("position").x, nodes.at(0).get("position").y]));
+        this.redraw();
+    }
 }
 
 Rkns.Renderer.Scene.prototype.toPaperCoords = function(_point) {
--- a/server/src/main/webapp/static/js/twitter-bin.js	Mon Dec 31 13:37:31 2012 +0100
+++ b/server/src/main/webapp/static/js/twitter-bin.js	Tue Jan 01 09:28:03 2013 +0100
@@ -44,81 +44,110 @@
     this.refresh();
 }
 
+Rkns.Twitter.Bin.prototype.render = function(searchstr) {
+    var _rgxp = new RegExp('('+(searchstr || this.search).replace(/(\W)/g,'\\$1')+')','gi');
+    if (searchstr) {
+        var rxtest = new RegExp(searchstr.replace(/(\W)/g,'\\$1'),'i');
+    }
+    function highlight(_text) {
+        return _text.replace(_rgxp, "<span class='searchmatch'>$1</span>");
+    }
+    var _html = ""
+        _this = this,
+        count = 0;
+    Rkns._(this.data.results).each(function(_result) {
+        if (searchstr && !rxtest.test(_result.text)) {
+            return;
+        }
+        count++;
+        var _entities = [],
+            _users = _result.text.match(/@[\w_]+/gm),
+            _lastpos = 0;
+        if (_users) {
+            for (var _i = 0; _i < _users.length; _i++) {
+                var _m = _users[_i],
+                    _start = _lastpos + _result.text.substr(_lastpos).search(_m),
+                    _lastpos = _start + _m.length;
+                _entities.push({
+                    "text" : _m,
+                    "start" : _start,
+                    "end" : _lastpos,
+                    "link" :'<a href="http://twitter.com/' + _m.substr(1) + '" onclick="filtrerTexte(\'' + _m + '\'); return false;" target="_blank">',
+                });
+            }
+        }
+        var _hashes = _result.text.match(/([^&]|^)#[^\s,.!?=#@&;()]+/gm),
+            _lastpos = 0;
+        if (_hashes) {
+            for (var _i = 0; _i < _hashes.length; _i++) {
+                var _m = _hashes[_i],
+                    _h = ( _m[0] == '#' ? _m : _m.substr(1) ),
+                    _start = _lastpos + _result.text.substr(_lastpos).search(_h),
+                    _lastpos = _start + _h.length;
+                _entities.push({
+                    "text" : _h,
+                    "start" : _start,
+                    "end" : _lastpos,
+                    "link" :'<a href="http://twitter.com/search?q=' + encodeURIComponent(_h) + '" onclick="filtrerTexte(\'' + _.escape(_h) + '\'); return false;" target="_blank">',
+                });
+            }
+        }
+        
+        var _urls = _result.text.match(/(www\.|https?:\/\/)[\w./_\-]+/gim),
+            _lastpos = 0;
+        if (_urls) {
+            for (var _i = 0; _i < _urls.length; _i++) {
+                var _m = _urls[_i],
+                    _start = _lastpos + _result.text.substr(_lastpos).search(_m),
+                    _lastpos = _start + _m.length;
+                _entities.push({
+                    "text" : _m,
+                    "start" : _start,
+                    "end" : _lastpos,
+                    "link" :'<a href="' + _m + '" target="_blank">',
+                });
+            }
+        }
+        _entities = Rkns._(_entities).sortBy(function(a) { return a.start });
+        var _lastend = 0,
+            _text = Rkns._(_entities).map(function(_e) {
+                var _txt = highlight(_result.text.substring(_lastend, _e.start)) + _e.link + highlight(_e.text) + '</a>';
+                _lastend = _e.end;
+                return _txt;
+            }).join("") + highlight(_result.text.substring(_lastend));
+        
+        _html += _this.tweetTemplate({
+            tweet: _result,
+            date: new Date(_result.created_at.replace(/(\+|-)/,'UTC$1')).toLocaleString(),
+            text: _text
+        });
+    });
+    this.main_$.html(_html);
+    if (searchstr && count) {
+        this.count_$.text(count).show();
+    } else {
+        this.count_$.hide();
+    }
+    if (searchstr && !count) {
+        this.$.hide();
+    } else {
+        this.$.show();
+    }
+    _renkan.resizeBins();
+}
+
 Rkns.Twitter.Bin.prototype.refresh = function() {
     var _this = this;
     Rkns.$.ajax({
-        url: "http://search.twitter.com/search.json?q=" + encodeURIComponent(this.search),
+        url: "http://search.twitter.com/search.json",
         dataType: "jsonp",
+        data: {
+            q: this.search,
+            rpp: 100
+        },
         success: function(_data) {
-            var _rgxp = new RegExp('('+_this.search.replace(/(\W)/g,'\\$1')+')','gi');
-            function highlight(_text) {
-                return _text.replace(_rgxp, "<span class='searchmatch'>$1</span>");
-            }
-            var _html = Rkns._(_data.results).map(function(_result) {
-                var _entities = [],
-                    _users = _result.text.match(/@[\w_]+/gm),
-                    _lastpos = 0;
-                if (_users) {
-                    for (var _i = 0; _i < _users.length; _i++) {
-                        var _m = _users[_i],
-                            _start = _lastpos + _result.text.substr(_lastpos).search(_m),
-                            _lastpos = _start + _m.length;
-                        _entities.push({
-                            "text" : _m,
-                            "start" : _start,
-                            "end" : _lastpos,
-                            "link" :'<a href="http://twitter.com/' + _m.substr(1) + '" onclick="filtrerTexte(\'' + _m + '\'); return false;" target="_blank">',
-                        });
-                    }
-                }
-                var _hashes = _result.text.match(/([^&]|^)#[^\s,.!?=#@&;()]+/gm),
-                    _lastpos = 0;
-                if (_hashes) {
-                    for (var _i = 0; _i < _hashes.length; _i++) {
-                        var _m = _hashes[_i],
-                            _h = ( _m[0] == '#' ? _m : _m.substr(1) ),
-                            _start = _lastpos + _result.text.substr(_lastpos).search(_h),
-                            _lastpos = _start + _h.length;
-                        _entities.push({
-                            "text" : _h,
-                            "start" : _start,
-                            "end" : _lastpos,
-                            "link" :'<a href="http://twitter.com/search?q=' + encodeURIComponent(_h) + '" onclick="filtrerTexte(\'' + _.escape(_h) + '\'); return false;" target="_blank">',
-                        });
-                    }
-                }
-                
-                var _urls = _result.text.match(/(www\.|https?:\/\/)[\w./_\-]+/gim),
-                    _lastpos = 0;
-                if (_urls) {
-                    for (var _i = 0; _i < _urls.length; _i++) {
-                        var _m = _urls[_i],
-                            _start = _lastpos + _result.text.substr(_lastpos).search(_m),
-                            _lastpos = _start + _m.length;
-                        _entities.push({
-                            "text" : _m,
-                            "start" : _start,
-                            "end" : _lastpos,
-                            "link" :'<a href="' + _m + '" target="_blank">',
-                        });
-                    }
-                }
-                _entities = Rkns._(_entities).sortBy(function(a) { return a.start });
-                var _lastend = 0,
-                    _text = Rkns._(_entities).map(function(_e) {
-                        var _txt = highlight(_result.text.substring(_lastend, _e.start)) + _e.link + highlight(_e.text) + '</a>';
-                        _lastend = _e.end;
-                        return _txt;
-                    }).join("") + highlight(_result.text.substring(_lastend));
-                
-                return _this.tweetTemplate({
-                    tweet: _result,
-                    date: new Date(_result.created_at.replace(/(\+|-)/,'UTC$1')).toLocaleString(),
-                    text: _text
-                });
-            }).join("");
-            _this.main_$.html(_html);
-            _renkan.resizeBins();
+            _this.data = _data;
+            _this.render();
         }
     });
 }
--- a/server/src/main/webapp/static/js/underscore.js	Mon Dec 31 13:37:31 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +0,0 @@
-// Underscore.js 1.3.3
-// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
-// Underscore is freely distributable under the MIT license.
-// Portions of Underscore are inspired or borrowed from Prototype,
-// Oliver Steele's Functional, and John Resig's Micro-Templating.
-// For all details and documentation:
-// http://documentcloud.github.com/underscore
-(function(){function r(a,c,d){if(a===c)return 0!==a||1/a==1/c;if(null==a||null==c)return a===c;a._chain&&(a=a._wrapped);c._chain&&(c=c._wrapped);if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return!1;switch(e){case "[object String]":return a==""+c;case "[object Number]":return a!=+a?c!=+c:0==a?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
-c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if("object"!=typeof a||"object"!=typeof c)return!1;for(var f=d.length;f--;)if(d[f]==a)return!0;d.push(a);var f=0,g=!0;if("[object Array]"==e){if(f=a.length,g=f==c.length)for(;f--&&(g=f in a==f in c&&r(a[f],c[f],d)););}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return!1;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&r(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,h)&&!f--)break;
-g=!f}}d.pop();return g}var s=this,I=s._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,J=k.unshift,l=p.toString,K=p.hasOwnProperty,y=k.forEach,z=k.map,A=k.reduce,B=k.reduceRight,C=k.filter,D=k.every,E=k.some,q=k.indexOf,F=k.lastIndexOf,p=Array.isArray,L=Object.keys,t=Function.prototype.bind,b=function(a){return new m(a)};"undefined"!==typeof exports?("undefined"!==typeof module&&module.exports&&(exports=module.exports=b),exports._=b):s._=b;b.VERSION="1.3.3";var j=b.each=b.forEach=function(a,
-c,d){if(a!=null)if(y&&a.forEach===y)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(d,a[e],e,a)===o)break}else for(e in a)if(b.has(a,e)&&c.call(d,a[e],e,a)===o)break};b.map=b.collect=function(a,c,b){var e=[];if(a==null)return e;if(z&&a.map===z)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});if(a.length===+a.length)e.length=a.length;return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(A&&
-a.reduce===A){e&&(c=b.bind(c,e));return f?a.reduce(c,d):a.reduce(c)}j(a,function(a,b,i){if(f)d=c.call(e,d,a,b,i);else{d=a;f=true}});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(B&&a.reduceRight===B){e&&(c=b.bind(c,e));return f?a.reduceRight(c,d):a.reduceRight(c)}var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=function(a,
-c,b){var e;G(a,function(a,g,h){if(c.call(b,a,g,h)){e=a;return true}});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(C&&a.filter===C)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(D&&a.every===D)return a.every(c,b);j(a,function(a,g,h){if(!(e=e&&c.call(b,
-a,g,h)))return o});return!!e};var G=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(E&&a.some===E)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return o});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;if(q&&a.indexOf===q)return a.indexOf(c)!=-1;return b=G(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck=
-function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&
-(e={value:a,computed:b})});return e.value};b.shuffle=function(a){var b=[],d;j(a,function(a,f){d=Math.floor(Math.random()*(f+1));b[f]=b[d];b[d]=a});return b};b.sortBy=function(a,c,d){var e=b.isFunction(c)?c:function(a){return a[c]};return b.pluck(b.map(a,function(a,b,c){return{value:a,criteria:e.call(d,a,b,c)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c===void 0?1:d===void 0?-1:c<d?-1:c>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};
-j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:b.isArray(a)||b.isArguments(a)?i.call(a):a.toArray&&b.isFunction(a.toArray)?a.toArray():b.values(a)};b.size=function(a){return b.isArray(a)?a.length:b.keys(a).length};b.first=b.head=b.take=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,
-0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-1]};b.rest=b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,
-e=[];a.length<3&&(c=true);b.reduce(d,function(d,g,h){if(c?b.last(d)!==g||!d.length:!b.include(d,g)){d.push(g);e.push(a[h])}return d},[]);return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1),true);return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=
-i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,d){if(a==null)return-1;var e;if(d){d=b.sortedIndex(a,c);return a[d]===c?d:-1}if(q&&a.indexOf===q)return a.indexOf(c);d=0;for(e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(F&&a.lastIndexOf===F)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){if(arguments.length<=
-1){b=a||0;a=0}for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;){g[f++]=a;a=a+d}return g};var H=function(){};b.bind=function(a,c){var d,e;if(a.bind===t&&t)return t.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));H.prototype=a.prototype;var b=new H,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=
-i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,c){var d={};c||(c=b.identity);return function(){var e=c.apply(this,arguments);return b.has(d,e)?d[e]:d[e]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(null,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i,j=b.debounce(function(){h=
-g=false},c);return function(){d=this;e=arguments;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);j()},c));g?h=true:i=a.apply(d,e);j();g=true;return i}};b.debounce=function(a,b,d){var e;return function(){var f=this,g=arguments;d&&!e&&a.apply(f,g);clearTimeout(e);e=setTimeout(function(){e=null;d||a.apply(f,g)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments,0));
-return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=L||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&
-c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.pick=function(a){var c={};j(b.flatten(i.call(arguments,1)),function(b){b in a&&(c[b]=a[b])});return c};b.defaults=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty=
-function(a){if(a==null)return true;if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};b.isArguments=function(a){return l.call(a)=="[object Arguments]"};b.isArguments(arguments)||(b.isArguments=function(a){return!(!a||!b.has(a,"callee"))});b.isFunction=function(a){return l.call(a)=="[object Function]"};
-b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isFinite=function(a){return b.isNumber(a)&&isFinite(a)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,
-b){return K.call(a,b)};b.noConflict=function(){s._=I;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;").replace(/\//g,"&#x2F;")};b.result=function(a,c){if(a==null)return null;var d=a[c];return b.isFunction(d)?d.call(a):d};b.mixin=function(a){j(b.functions(a),function(c){M(c,b[c]=a[c])})};var N=0;b.uniqueId=
-function(a){var b=N++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var u=/.^/,n={"\\":"\\","'":"'",r:"\r",n:"\n",t:"\t",u2028:"\u2028",u2029:"\u2029"},v;for(v in n)n[n[v]]=v;var O=/\\|'|\r|\n|\t|\u2028|\u2029/g,P=/\\(\\|'|r|n|t|u2028|u2029)/g,w=function(a){return a.replace(P,function(a,b){return n[b]})};b.template=function(a,c,d){d=b.defaults(d||{},b.templateSettings);a="__p+='"+a.replace(O,function(a){return"\\"+n[a]}).replace(d.escape||
-u,function(a,b){return"'+\n_.escape("+w(b)+")+\n'"}).replace(d.interpolate||u,function(a,b){return"'+\n("+w(b)+")+\n'"}).replace(d.evaluate||u,function(a,b){return"';\n"+w(b)+"\n;__p+='"})+"';\n";d.variable||(a="with(obj||{}){\n"+a+"}\n");var a="var __p='';var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n"+a+"return __p;\n",e=new Function(d.variable||"obj","_",a);if(c)return e(c,b);c=function(a){return e.call(this,a,b)};c.source="function("+(d.variable||"obj")+"){\n"+a+"}";return c};
-b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var x=function(a,c){return c?b(a).chain():a},M=function(a,c){m.prototype[a]=function(){var a=i.call(arguments);J.call(a,this._wrapped);return x(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return x(d,
-this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return x(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=true;return this};m.prototype.value=function(){return this._wrapped}}).call(this);
--- a/server/src/main/webapp/static/js/wikipedia-bin.js	Mon Dec 31 13:37:31 2012 +0100
+++ b/server/src/main/webapp/static/js/wikipedia-bin.js	Tue Jan 01 09:28:03 2013 +0100
@@ -26,10 +26,10 @@
 Rkns.Wikipedia.Bin = Rkns.Utils.inherit(Rkns._BaseBin);
 
 Rkns.Wikipedia.Bin.prototype.resultTemplate = Rkns._.template(
-    '<li class="Rk-Wikipedia-Result Rk-Bin-Item" data-uri="<%=wpurl%>" '
-    + 'data-title="Wikipedia: <%=result.title%>" data-description="<%=wpdesc%>" data-image="<%=static_url%>img/wikipedia.png">'
-    + '<img class="Rk-Wikipedia-Icon" src="<%=static_url%>img/wikipedia.png"></div><h4 class="Rk-Wikipedia-Title"><a href="<%=wpurl%>" target="_blank"><%=highlightedtitle%></a></h4>'
-    + '<p class="Rk-Wikipedia-Snippet"><%=result.snippet%></p></li>'
+    '<li class="Rk-Wikipedia-Result Rk-Bin-Item" data-uri="<%-url%>" '
+    + 'data-title="Wikipedia: <%-title%>" data-description="<%-description%>" data-image="<%-static_url%>img/wikipedia.png">'
+    + '<img class="Rk-Wikipedia-Icon" src="<%-static_url%>img/wikipedia.png"></div><h4 class="Rk-Wikipedia-Title"><a href="<%-url%>" target="_blank"><%=htitle%></a></h4>'
+    + '<p class="Rk-Wikipedia-Snippet"><%=hdescription%></p></li>'
 );
 
 Rkns.Wikipedia.Bin.prototype._init = function(_renkan, _opts) {
@@ -40,24 +40,56 @@
     this.refresh();
 }
 
+Rkns.Wikipedia.Bin.prototype.render = function(searchstr) {
+    var _rgxp = new RegExp('('+(searchstr || this.search).replace(/(\W)/g,'\\$1')+')','gi');
+    if (searchstr) {
+        var rxtest = new RegExp(searchstr.replace(/(\W)/g,'\\$1'),'i');
+    }
+    function highlight(_text) {
+        return _text.replace(_rgxp, "<span class='searchmatch'>$1</span>");
+    }
+    var _html = "",
+        _this = this,
+        count = 0;
+    Rkns._(this.data.query.search).each(function(_result) {
+        var title = _result.title,
+            url = "http://" + _this.lang + ".wikipedia.org/wiki/" + encodeURI(title.replace(/ /g,"_")),
+            description = Rkns.$('<div>').html(_result.snippet).text();
+        if (searchstr && !rxtest.test(title) && !rxtest.test(description)) {
+            return;
+        }
+        count++;
+        _html += _this.resultTemplate({
+            url: url,
+            title: title,
+            htitle: highlight(title),
+            description: description,
+            hdescription: highlight(description),
+            static_url: _this.renkan.static_url
+        });
+    });
+    _this.main_$.html(_html);
+    if (searchstr && count) {
+        this.count_$.text(count).show();
+    } else {
+        this.count_$.hide();
+    }
+    if (searchstr && !count) {
+        this.$.hide();
+    } else {
+        this.$.show();
+    }
+    _renkan.resizeBins();
+}
+    
 Rkns.Wikipedia.Bin.prototype.refresh = function() {
     var _this = this;
     Rkns.$.ajax({
         url: "http://" + _this.lang + ".wikipedia.org/w/api.php?action=query&list=search&srsearch=" + encodeURIComponent(this.search) + "&format=json",
         dataType: "jsonp",
         success: function(_data) {
-            var _rgxp = new RegExp('('+_this.search.replace(/(\W)/g,'\\$1')+')','gi'),
-                _html = Rkns._(_data.query.search).map(function(_result) {
-                return _this.resultTemplate({
-                    result: _result,
-                    wpurl: "http://" + _this.lang + ".wikipedia.org/wiki/" + encodeURI(_result.title.replace(/ /g,"_")),
-                    highlightedtitle: _result.title.replace(_rgxp, "<span class='searchmatch'>$1</span>"),
-                    wpdesc: Rkns.$('<div>').html(_result.snippet).text(),
-                    static_url: _this.renkan.static_url
-                });
-            }).join("");
-            _this.main_$.html(_html);
-            _renkan.resizeBins();
+            _this.data = _data;
+            _this.render();
         }
     });
 }
\ No newline at end of file