Added in-graph search
authorveltr
Thu, 04 Jul 2013 12:43:30 +0200
changeset 196 2a2fcec209d0
parent 195 15e048e00002
child 197 4e13edb46c0d
Added in-graph search
client/css/renkan.css
client/js/defaults.js
client/js/ldtjson-bin.js
client/js/list-bin.js
client/js/main.js
client/js/paper-renderer.js
client/js/wikipedia-bin.js
--- a/client/css/renkan.css	Wed Jul 03 13:42:59 2013 +0200
+++ b/client/css/renkan.css	Thu Jul 04 12:43:30 2013 +0200
@@ -228,6 +228,34 @@
 .Rk-Open-Button:hover {
     background-position: -240px -35px;
 }
+
+.Rk-GraphSearch-Form {
+    float: right; width: 170px; position: relative;
+}
+
+.Rk-GraphSearch-Form:before, .Rk-GraphSearch-Form:after {
+    position: absolute; display: block; content: "."; text-indent: -9999px;
+}
+
+.Rk-GraphSearch-Form:before {
+    right: 10px; top: 20px; width: 7px; height: 2px; border: none; padding: 0; background: #666666;
+    transform: rotate(40deg); -webkit-transform: rotate(40deg);
+}
+
+.Rk-GraphSearch-Form:after {
+    right: 13px; top: 11px; width: 6px; height: 6px; border-radius: 8px; border: 2px solid #666666;
+}
+
+.Rk-GraphSearch-Field {
+    line-height: 23px; font-size: 14px; height: 23px; padding: 0 5px; border: none; margin: 6px 5px;
+    width: 150px; background: #f0f0f0; box-shadow: 1px 1px 1px #999999 inset; border-radius: 5px;
+    -webkit-appearance: none;
+    -webkit-box-sizing: content-box;
+    -moz-box-sizing: content-box;
+    box-sizing: content-box;
+}
+
+
 /* Canvas */
 
 .Rk-Editing-Space {
@@ -247,6 +275,10 @@
 
 /* Node Labels */
 
+.Rk-Highlighted {
+    background: rgba(255,255,0,.5);
+}
+
 .Rk-Labels {
     position: absolute; left: 0; top: 0; z-index: 1;
     font-family: "Segoe UI", "Helvetica Neue", Arial, Helvetica, sans-serif;
--- a/client/js/defaults.js	Wed Jul 03 13:42:59 2013 +0200
+++ b/client/js/defaults.js	Thu Jul 04 12:43:30 2013 +0200
@@ -36,6 +36,7 @@
     autoscale_padding: 50,
     
     /* TOP BAR BUTTONS */
+    show_search_field: true,
     show_user_list: true,
     show_save_button: true,
     show_open_button: false,
--- a/client/js/ldtjson-bin.js	Wed Jul 03 13:42:59 2013 +0200
+++ b/client/js/ldtjson-bin.js	Thu Jul 04 12:43:30 2013 +0200
@@ -1,5 +1,7 @@
 (function(Rkns) {
-"use strict"
+"use strict";
+
+var _ = Rkns._;
 
 var Ldt = Rkns.Ldt = {};
 
@@ -15,12 +17,12 @@
 
 var ProjectBin = Ldt.ProjectBin = Rkns.Utils.inherit(Rkns._BaseBin);
 
-ProjectBin.prototype.tagTemplate = Rkns._.template(
+ProjectBin.prototype.tagTemplate = _.template(
     '<li class="Rk-Bin-Item" draggable="true" data-image="<%- Rkns.Utils.getFullURL(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><%=htitle%></h4><div class="Rk-Clear"></div></li>'
 );
 
-ProjectBin.prototype.annotationTemplate = Rkns._.template(
+ProjectBin.prototype.annotationTemplate = _.template(
     '<li class="Rk-Bin-Item" draggable="true" data-image="<%- Rkns.Utils.getFullURL(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><%=htitle%></h4><p><%=hdescription%></p><p>Start: <%=start%>, End: <%=end%>, Duration: <%=duration%></p><div class="Rk-Clear"></div></li>'
 );
@@ -34,14 +36,11 @@
     this.refresh();
 };
 
-ProjectBin.prototype.render = function(searchstr) {
-    if (searchstr) {
-        var rxbase = searchstr.replace(/(\W)/g,'\\$1'),
-            _rgxp = new RegExp('('+rxbase+')','gi'),
-            rxtest = new RegExp(rxbase,'i');
-    }
+ProjectBin.prototype.render = function(searchbase) {
+    var search = searchbase || Rkns.Utils.regexpFromTextOrArray();
     function highlight(_text) {
-        return searchstr ? _text.replace(_rgxp, "<span class='searchmatch'>$1</span>") : _text;
+        var _e = _(_text).escape();
+        return search.isempty ? _e : search.replace(_e, "<span class='searchmatch'>$1</span>");
     }
     function convertTC(_ms) {
         function pad(_n) {
@@ -68,9 +67,9 @@
         _this = this,
         count = 0;
     _this.title_$.text('LDT Project: "' + _projtitle + '"');
-    Rkns._(_this.data.tags).map(function(_tag) {
+    _(_this.data.tags).map(function(_tag) {
         var _title = _tag.meta["dc:title"];
-        if (searchstr && !rxtest.test(_title)) {
+        if (!search.isempty && !search.test(_title)) {
             return;
         }
         count++;
@@ -83,10 +82,10 @@
         });
     });
     _html += '<li><h3>Annotations</h3></li>';
-    Rkns._(_this.data.annotations).map(function(_annotation) {
+    _(_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)) {
+        if (!search.isempty && !search.test(_title) && !search.test(_description)) {
             return;
         }
         count++;
@@ -113,12 +112,12 @@
     });
     
     this.main_$.html(_html);
-    if (searchstr && count) {
+    if (!search.isempty && count) {
         this.count_$.text(count).show();
     } else {
         this.count_$.hide();
     }
-    if (searchstr && !count) {
+    if (!search.isempty && !count) {
         this.$.hide();
     } else {
         this.$.show();
@@ -161,7 +160,7 @@
 
 var ResultsBin = Ldt.ResultsBin = Rkns.Utils.inherit(Rkns._BaseBin);
 
-ResultsBin.prototype.segmentTemplate = Rkns._.template(
+ResultsBin.prototype.segmentTemplate = _.template(
     '<li class="Rk-Bin-Item" draggable="true" data-image="<%- Rkns.Utils.getFullURL(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><%=htitle%></h4><p><%=hdescription%></p><p>Start: <%=start%>, End: <%=end%>, Duration: <%=duration%></p><div class="Rk-Clear"></div></li>'
 );
@@ -176,17 +175,14 @@
     this.refresh();
 };
 
-ResultsBin.prototype.render = function(searchstr) {
-    if (searchstr) {
-        var rxbase = searchstr.replace(/(\W)/g,'\\$1'),
-            _rgxp = new RegExp('('+rxbase+')','gi'),
-            rxtest = new RegExp(rxbase,'i');
-    } else {
-        var rxbase = this.search.replace(/(\W)/g,'\\$1'),
-            _rgxp = new RegExp('('+rxbase+')','gi');
+ResultsBin.prototype.render = function(searchbase) {
+    if (!this.data) {
+        return;
     }
+    var search = searchbase || Rkns.Utils.regexpFromTextOrArray();
+    var highlightrx = (search.isempty ? Rkns.Utils.regexpFromTextOrArray(this.search) : search);
     function highlight(_text) {
-        return _text.replace(_rgxp, "<span class='searchmatch'>$1</span>");
+        return highlightrx.replace(_(_text).escape(), "<span class='searchmatch'>$1</span>");
     }
     function convertTC(_ms) {
         function pad(_n) {
@@ -211,10 +207,10 @@
     var _html = '',
         _this = this,
         count = 0;
-    Rkns._(_this.data.objects).each(function(_segment) {
+    _(this.data.objects).each(function(_segment) {
         var _description = _segment.abstract,
             _title = _segment.title;
-        if (searchstr && !rxtest.test(_title) && !rxtest.test(_description)) {
+        if (!search.isempty && !search.test(_title) && !search.test(_description)) {
             return;
         }
         count++;
@@ -244,12 +240,12 @@
     });
     
     this.main_$.html(_html);
-    if (searchstr && count) {
+    if (!search.isempty && count) {
         this.count_$.text(count).show();
     } else {
         this.count_$.hide();
     }
-    if (searchstr && !count) {
+    if (!search.isempty && !count) {
         this.$.hide();
     } else {
         this.$.show();
--- a/client/js/list-bin.js	Wed Jul 03 13:42:59 2013 +0200
+++ b/client/js/list-bin.js	Thu Jul 04 12:43:30 2013 +0200
@@ -20,17 +20,11 @@
     this.refresh();
 };
 
-Rkns.ResourceList.Bin.prototype.render = function(searchstr) {
-    if (searchstr) {
-        var _rgxp = new RegExp('('+(searchstr).replace(/(\W)/g,'\\$1')+')','gi'),
-            rxtest = new RegExp(searchstr.replace(/(\W)/g,'\\$1'),'i');
-    }
+Rkns.ResourceList.Bin.prototype.render = function(searchbase) {
+    var search = searchbase || Rkns.Utils.regexpFromTextOrArray();
     function highlight(_text) {
-        if (searchstr) {
-            return _text.replace(_rgxp, "<span class='searchmatch'>$1</span>");
-        } else {
-            return _text;
-        }
+        var _e = _(_text).escape();
+        return search.isempty ? _e : search.replace(_e, "<span class='searchmatch'>$1</span>");
     }
     var _html = "",
         _this = this,
@@ -60,7 +54,7 @@
         if (url && !/^https?:\/\//.test(url)) {
             url = 'http://' + url;
         }
-        if (searchstr && !rxtest.test(title) && !rxtest.test(description)) {
+        if (!search.isempty && !search.test(title) && !search.test(description)) {
             return;
         }
         count++;
@@ -75,12 +69,12 @@
         });
     });
     _this.main_$.html(_html);
-    if (searchstr && count) {
+    if (!search.isempty && count) {
         this.count_$.text(count).show();
     } else {
         this.count_$.hide();
     }
-    if (searchstr && !count) {
+    if (!search.isempty && !count) {
         this.$.hide();
     } else {
         this.$.show();
--- a/client/js/main.js	Wed Jul 03 13:42:59 2013 +0200
+++ b/client/js/main.js	Thu Jul 04 12:43:30 2013 +0200
@@ -125,7 +125,7 @@
     
     this.options = _.defaults(_opts, Rkns.defaults);
         
-    Rkns._(this.options.property_files).each(function(f) {
+    _(this.options.property_files).each(function(f) {
         Rkns.$.getJSON(f, function(data) {
             _this.options.properties = _this.options.properties.concat(data);
         });
@@ -166,17 +166,17 @@
     if (!this.options.search.length) {
         this.$.find(".Rk-Web-Search-Form").detach();
     } else {
-        var _tmpl = Rkns._.template('<li class="<%= className %>" data-key="<%= key %>"><%= title %></li>'),
+        var _tmpl = _.template('<li class="<%= className %>" data-key="<%= key %>"><%= title %></li>'),
             _select = this.$.find(".Rk-Search-List"),
             _input = this.$.find(".Rk-Web-Search-Input"),
             _form = this.$.find(".Rk-Web-Search-Form");
-        Rkns._(this.options.search).each(function(_search, _key) {
+        _(this.options.search).each(function(_search, _key) {
             if (Rkns[_search.type] && Rkns[_search.type].Search) {
                 _this.search_engines.push(new Rkns[_search.type].Search(_this, _search));
             }
         });
         _select.html(
-            Rkns._(this.search_engines).map(function(_search, _key) {
+            _(this.search_engines).map(function(_search, _key) {
                 return _tmpl({
                     key: _key,
                     title: _search.getSearchTitle(),
@@ -204,7 +204,7 @@
         );
         this.setSearchEngine(0);
     }
-    Rkns._(this.options.bins).each(function(_bin) {
+    _(this.options.bins).each(function(_bin) {
         if (Rkns[_bin.type] && Rkns[_bin.type].Bin) {
             _this.tabs.push(new Rkns[_bin.type].Bin(_this, _bin));
         }
@@ -229,7 +229,7 @@
                 var _models = _this.project.get("nodes").where({
                     uri: $(_t).attr("data-uri")
                 });
-                Rkns._(_models).each(function(_model) {
+                _(_models).each(function(_model) {
                     _this.renderer.highlightModel(_model);
                 });
             }
@@ -281,11 +281,22 @@
         _this.resizeBins();
     });
     
+    var lastsearch = false, lastval = '';
+    
     this.$.find(".Rk-Bins-Search-Input").on("change keyup paste input", function() {
-       var val = Rkns.$(this).val();
-       Rkns._(_this.tabs).each(function(tab) {
-           tab.render(val);
-       });
+        var val = Rkns.$(this).val();
+        if (val === lastval) {
+            return;
+        }
+        var search = Rkns.Utils.regexpFromTextOrArray(val.length > 1 ? val: null);
+        if (search.source === lastsearch) {
+            return;
+        }
+        lastsearch = search.source;
+        _(_this.tabs).each(function(tab) {
+            tab.render(search);
+        });
+        
     });
     this.$.find(".Rk-Bins-Search-Form").submit(function() {
         return false;
@@ -293,7 +304,7 @@
     
 };
 
-Renkan.prototype.template = Rkns._.template(
+Renkan.prototype.template = _.template(
     '<% if (options.show_bins) { %><div class="Rk-Bins"><div class="Rk-Bins-Head"><h2 class="Rk-Bins-Title"><%- translate("Select contents:")%></h2>'
     + '<form class="Rk-Web-Search-Form Rk-Search-Form"><input class="Rk-Web-Search-Input Rk-Search-Input" type="search" placeholder="<%- translate("Search the Web") %>" />'
     + '<div class="Rk-Search-Select"><div class="Rk-Search-Current"></div><ul class="Rk-Search-List"></ul></div>'
@@ -336,10 +347,10 @@
 /* Utility functions */
 
 Rkns.Utils = {
-    _ID_AUTO_INCREMENT : 0,
-    _ID_BASE : (function(_d) {
-        
-        function pad(n){return n<10 ? '0'+n : n;}
+    getUID : (function() {
+        function pad(n){
+            return n<10 ? '0'+n : n;
+        }
         function fillrand(n) {
             var _res = '';
             for (var i=0; i<n; i++) {
@@ -347,22 +358,19 @@
             }
             return _res;
         }
-        return _d.getUTCFullYear() + '-'  
+        var _d = new Date(),
+            ID_AUTO_INCREMENT = 0,
+            ID_BASE = _d.getUTCFullYear() + '-'  
             + pad(_d.getUTCMonth()+1) + '-'  
             + pad(_d.getUTCDate()) + '-'
             + fillrand(16);
-        
-    })(new Date()),
-    getUID : function(_base) {
-        
-        var _n = (++this._ID_AUTO_INCREMENT).toString(16),
-            _base = (typeof _base === "undefined" ? "" : _base + "-" );
-        while (_n.length < 4) {
-            _n = '0' + _n;
+        return function(_base) {
+            var _n = (++ID_AUTO_INCREMENT).toString(16),
+                _base = (typeof _base === "undefined" ? "" : _base + "-" );
+            while (_n.length < 4) { _n = '0' + _n; }
+            return _base + this._ID_BASE + '-' + _n;
         }
-        return _base + this._ID_BASE + '-' + _n;
-        
-    },
+    })(),
     getFullURL : function(url) {
         
         if(typeof(url) == 'undefined' || url == null ) {
@@ -390,10 +398,86 @@
                 this._initialized = true;
             }
         };
-        Rkns._(_class.prototype).extend(_baseClass.prototype);
+        _(_class.prototype).extend(_baseClass.prototype);
         return _class;
         
-    }
+    },
+    regexpFromTextOrArray: (function() {
+        var charsub = [
+                '[aáàâä]',
+                '[cç]',
+                '[eéèêë]',
+                '[iíìîï]',
+                '[oóòôö]',
+                '[uùûü]'
+            ],
+            removeChars = [
+                String.fromCharCode(768), String.fromCharCode(769), String.fromCharCode(770), String.fromCharCode(771), String.fromCharCode(807),
+                "{", "}", "(", ")", "[", "]", "【", "】", "、", "・", "‥", "。", "「", "」", "『", "』", "〜", ":", "!", "?", " ",
+                ",", " ", ";", "(", ")", ".", "*", "+", "\\", "?", "|", "{", "}", "[", "]", "^", "#", "/"
+            ],
+            remsrc = "[\\" + removeChars.join("\\") + "]",
+            remrx = new RegExp(remsrc, "gm"),
+            charsrx = _(charsub).map(function(c) {
+                return new RegExp(c);
+            });
+        
+        function replaceText(_text) {
+            var txt = _text.toLowerCase().replace(remrx,""), src = "";
+            for (var j = 0; j < txt.length; j++) {
+                if (j) {
+                    src += remsrc + "*";
+                }
+                var l = txt[j];
+                _(charsub).each(function(v, k) {
+                    l = l.replace(charsrx[k], v);
+                });
+                src += l;
+            }
+            return src;
+        }
+        
+        function getSource(inp) {
+            switch (typeof inp) {
+                case "string":
+                    return replaceText(inp);
+                case "object":
+                    var src = '';
+                    _(inp).each(function(v) {
+                        var res = getSource(v);
+                        if (res) {
+                            if (src) {
+                                src += '|';
+                            }
+                            src += res;
+                        }
+                    });
+                    return src;
+            }
+            return '';
+        }
+        
+        return function(_textOrArray) {
+            var source = getSource(_textOrArray);
+            if (source) {
+                var testrx = new RegExp( source, "im"),
+                    replacerx = new RegExp( '(' + source + ')', "igm")
+                return {
+                    isempty: false,
+                    source: source,
+                    test: function(_t) { return testrx.test(_t) },
+                    replace: function(_text, _replace) { return _text.replace(replacerx, _replace); }
+                }
+            } else {
+                return {
+                    isempty: true,
+                    source: '',
+                    test: function() { return true },
+                    replace: function(_text) { return text }
+                }
+            }
+        }
+    })()
 };
 })(window);
 
--- a/client/js/paper-renderer.js	Wed Jul 03 13:42:59 2013 +0200
+++ b/client/js/paper-renderer.js	Thu Jul 04 12:43:30 2013 +0200
@@ -282,7 +282,13 @@
     
     var _text = this.model.get("title") || this.renkan.translate(this.options.label_untitled_nodes) || "";
     _text = shortenText(_text, this.options.node_label_max_length);
-    this.title.text(_text);
+    
+    if (typeof this.highlighted === "object") {
+        this.title.html(this.highlighted.replace(_(_text).escape(),'<span class="Rk-Highlighted">$1</span>'));
+    } else {
+        this.title.text(_text);
+    }
+    
     this.title.css({
         left: this.paper_coords.x,
         top: this.paper_coords.y + this.circle_radius * this.h_ratio + this.options.node_label_distance,
@@ -512,11 +518,12 @@
         this._super("unselect");
     }
 },
-highlight: function() {
-    if (this.highlighted) {
+highlight: function(textToReplace) {
+    var hlvalue = textToReplace || true;
+    if (this.highlighted === hlvalue) {
         return;
     }
-    this.highlighted = true;
+    this.highlighted = hlvalue;
     this.redraw();
     this.renderer.throttledPaperDraw();
 },
@@ -935,7 +942,7 @@
     '<h2><span class="Rk-CloseX">&times;</span><% if (options.show_node_tooltip_color) { %><span class="Rk-UserColor" style="background:<%-node.color%>;"></span><% } %>'
     + '<span class="Rk-Display-Title"><% if (node.uri) { %><a href="<%-node.uri%>" target="_blank"><% } %><%-node.title%><% if (node.uri) { %></a><% } %></span></h2>'
     + '<% if (node.uri && options.show_node_tooltip_uri) { %><p class="Rk-Display-URI"><a href="<%-node.uri%>" target="_blank"><%-node.short_uri%></a></p><% } %>'
-    + '<% if (options.show_node_tooltip_description) { %><p><%-node.description%></p><% } %>'
+    + '<% if (options.show_node_tooltip_description) { %><p class="Rk-Display-Description"><%-node.description%></p><% } %>'
     + '<% if (node.image && options.show_node_tooltip_image) { %><img class="Rk-Display-ImgPreview" src="<%-node.image%>" /><% } %>'
     + '<% if (node.has_creator && options.show_node_tooltip_creator) { %><p><span class="Rk-Editor-Label"><%-renkan.translate("Created by:")%></span><span class="Rk-UserColor" style="background:<%-node.created_by_color%>;"></span><%- shortenText(node.created_by_title, 25) %></p><% } %>'
 ),
@@ -1089,6 +1096,14 @@
             shiftSize(1);
             return false;
         });
+    } else {
+        if (typeof this.source_representation.highlighted === "object") {
+            var titlehtml = this.source_representation.highlighted.replace(_(_model.get("title")).escape(),'<span class="Rk-Highlighted">$1</span>');
+            this.editor_$.find(".Rk-Display-Title" + (_model.get("uri") ? " a" : "")).html(titlehtml);
+            if (this.options.show_node_tooltip_description) {
+                this.editor_$.find(".Rk-Display-Description").html(this.source_representation.highlighted.replace(_(_model.get("description")).escape(),'<span class="Rk-Highlighted">$1</span>'))
+            }
+        }
     }
     this.editor_$.find("img").load(function() {
         _this.redraw();
@@ -1863,6 +1878,33 @@
         );
     }
     
+    if (_renkan.options.show_search_field) {
+        
+        var lastval = '';
+        
+        this.$.find(".Rk-GraphSearch-Field").on("keyup change paste input", function() {
+            var $this = $(this),
+                val = $this.val();
+            if (val === lastval) {
+                return;
+            }
+            if (val.length < 2) {
+                _renkan.project.get("nodes").each(function(n) {
+                    _this.getRepresentationByModel(n).unhighlight();
+                });
+            } else {
+                var rxs = Rkns.Utils.regexpFromTextOrArray(val);
+                _renkan.project.get("nodes").each(function(n) {
+                    if (rxs.test(n.get("title")) || rxs.test(n.get("description"))) {
+                        _this.getRepresentationByModel(n).highlight(rxs);
+                    } else {
+                        _this.getRepresentationByModel(n).unhighlight(); 
+                    }
+                });
+            }
+        });
+    }
+    
     this.redraw();
     
     window.setInterval(function() {
@@ -1910,8 +1952,8 @@
     + '<% if (options.show_open_button) { %><div class="Rk-TopBar-Separator"></div><div class="Rk-TopBar-Button Rk-Open-Button"><div class="Rk-TopBar-Tooltip"><div class="Rk-TopBar-Tooltip-Contents"><%-translate("Open Project")%></div></div></div><% } %>'
     + '<% if (options.show_bookmarklet) { %><div class="Rk-TopBar-Separator"></div><a class="Rk-TopBar-Button Rk-Bookmarklet-Button" href="#"><div class="Rk-TopBar-Tooltip"><div class="Rk-TopBar-Tooltip-Contents">'
     + '<%-translate("Renkan \'Drag-to-Add\' bookmarklet")%></div></div></a><% } %>'
-    + '<div class="Rk-TopBar-Separator"></div>'
-    + '<% } %></div><% } %>'
+    + '<div class="Rk-TopBar-Separator"></div><% }; if (options.show_search_field) { %>'
+    + '<form action="#" class="Rk-GraphSearch-Form"><input type="search" class="Rk-GraphSearch-Field" placeholder="<%- translate("Search in graph") %>" /></form><div class="Rk-TopBar-Separator"></div><% } %></div><% } %>'
     + '<div class="Rk-Editing-Space<% if (!options.show_top_bar) { %> Rk-Editing-Space-Full<% } %>">'
     + '<div class="Rk-Labels"></div><canvas class="Rk-Canvas" resize></canvas><div class="Rk-Notifications"></div><div class="Rk-Editor">'
     + '<% if (options.show_bins) { %><div class="Rk-Fold-Bins">&laquo;</div><% } %>'
--- a/client/js/wikipedia-bin.js	Wed Jul 03 13:42:59 2013 +0200
+++ b/client/js/wikipedia-bin.js	Thu Jul 04 12:43:30 2013 +0200
@@ -50,13 +50,11 @@
     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');
-    }
+Rkns.Wikipedia.Bin.prototype.render = function(searchbase) {
+    var search = searchbase || Rkns.Utils.regexpFromTextOrArray();
+    var highlightrx = (search.isempty ? Rkns.Utils.regexpFromTextOrArray(this.search) : search);
     function highlight(_text) {
-        return _text.replace(_rgxp, "<span class='searchmatch'>$1</span>");
+        return highlightrx.replace(_(_text).escape(), "<span class='searchmatch'>$1</span>");
     }
     var _html = "",
         _this = this,
@@ -65,7 +63,7 @@
         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)) {
+        if (!search.isempty && !search.test(title) && !search.test(description)) {
             return;
         }
         count++;
@@ -79,12 +77,12 @@
         });
     });
     _this.main_$.html(_html);
-    if (searchstr && count) {
+    if (!search.isempty && count) {
         this.count_$.text(count).show();
     } else {
         this.count_$.hide();
     }
-    if (searchstr && !count) {
+    if (!search.isempty && !count) {
         this.$.hide();
     } else {
         this.$.show();