Skip to content

Instantly share code, notes, and snippets.

@pkrusche
Last active May 25, 2021 21:31
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pkrusche/4625397 to your computer and use it in GitHub Desktop.
Save pkrusche/4625397 to your computer and use it in GitHub Desktop.
Static site search using RSS in JQuery
// Static site search using the RSS feed.
//
// Inspired by
// http://joevennix.com/2011/05/25/How-I-Implement-Static-Site-Search.html
//
// Extended to do match scoring by Peter Krusche
(function( $ ){
function htmlEscape(s) {
return $('<div/>').text(s).html();
}
function prettyDate(time){
var date = new Date(time);
var diff = (((new Date()).getTime() - date.getTime()) / 1000);
var day_diff = Math.floor(diff / 86400);
if ( !time ) {
return "";
}
if( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 )
return time;
return day_diff == 0 && (
diff < 60 && "just now" ||
diff < 120 && "1 minute ago" ||
diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||
diff < 7200 && "1 hour ago" ||
diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") ||
day_diff == 1 && "Yesterday" ||
day_diff < 7 && day_diff + " days ago" ||
day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago";
}
/**
* Return a Javascript Date for the given XML Schema date string. Return
* null if the date cannot be parsed.
*
* Does not know how to parse BC dates or AD dates < 100.
*
* Valid examples of input:
* 2010-04-28T10:46:37.0123456789Z
* 2010-04-28T10:46:37.37Z
* 2010-04-28T10:46:37Z
* 2010-04-28T10:46:37
* 2010-04-28T10:46:37.012345+05:30
* 2010-04-28T10:46:37.37-05:30
* 2013-01-23T10:21:44+0000
* 1776-04-28T10:46:37+05:30
* 0150-04-28T10:46:37-05:30
*/
var xmlDateToJavascriptDate = function(xmlDate) {
// It's times like these you wish Javascript supported multiline regex specs
var re = /^([0-9]{4,})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(\.[0-9]+)?(Z|([+-])([0-9]{2}):?([0-9]{2}))?$/;
var match = xmlDate.match(re);
if (!match)
return "?:" + xmlDate;
var all = match[0];
var year = match[1]; var month = match[2]; var day = match[3];
var hour = match[4]; var minute = match[5]; var second = match[6];
var milli = match[7];
var z_or_offset = match[8]; var offset_sign = match[9];
var offset_hour = match[10]; var offset_minute = match[11];
if (offset_sign) { // ended with +xx:xx or -xx:xx as opposed to Z or nothing
var direction = (offset_sign == "+" ? 1 : -1);
hour = parseInt(hour) + parseInt(offset_hour) * direction;
minute = parseInt(minute) + parseInt(offset_minute) * direction;
}
var utcDate = Date.UTC(parseInt(year), parseInt(month)-1, parseInt(day), parseInt(hour), parseInt(minute), parseInt(second), parseInt(milli || 0));
return new Date(utcDate);
}
// staticsearch javscript plugin
// Parameters: JQuery ids for search box, results div and an item that will trigger the search when clicked
//
$.fn.searchbox = function(o){
var curz = 10;
var o = jQuery.extend({
elem_id: '#searchbox',
results_id: '#searchresults',
clicker_id: '#clicktosearch',
feed_source: '/feed.xml',
},o);
var uninit = true;
var entries = null;
sbox = {
init: function() {
$(o.elem_id).show();
$(o.clicker_id).show().click(function() {
sbox.blur();
});
$(o.results_id).addClass('ui_widget');
$(o.results_id).hide();
$(o.elem_id).click(function() {
if (uninit) {
$(o.elem_id).val('');
uninit = false;
};
});
$(o.elem_id).change(sbox.blur);
$(o.elem_id).blur(sbox.blur);
$(o.elem_id).keyup(function(event) {
if(event.keyCode == 13) {
sbox.blur();
}
});
// we store the search query in the hash tag
$(window).bind('hashchange', function(e) {
$(o.results_id).show();
var query = decodeURIComponent($.param.fragment()).replace('search=', '');
// limit query length and char type
query = query.replace(/[^A-Za-z0-9\,\:\;\.\_\-\+\*\s]/g, "");
query = query.substr(0, 256);
$(o.elem_id).val(query);
if (query == '') {
$(o.elem_id).val('');
$(o.results_id).hide();
uninit = true;
} else {
$(o.elem_id).html('<div id="loader"></div>');
$(o.elem_id).blur().attr('disabled', true);
if (entries == null) {
$.ajax({
url: o.feed_source + '?r=' + (Math.random()*99999999999),
dataType:'xml', success: function(data) {
entries = data.getElementsByTagName('entry');
sbox.find(query);
},
error: function(r, err, t) {
$(o.results_id).html(
'<a href="#" onclick="window.location.hash=\'\'; return false;" class="ui-icon ui-icon-close" style="float:right;">Dismiss</a>'
+ "<h3>Search didn't work as expected -- '" +
err + "' !</h3>"
+ '<pre style="height: 100px; overflow: scroll;">' + t + '</pre>');
}
});
} else {
sbox.find(query);
}
$(o.elem_id).blur().attr('disabled', false);
}
});
$(window).trigger( 'hashchange' );
},
blur: function() {
var query = $(o.elem_id).val();
query = query.replace(/[^A-Za-z0-9\,\:\;\.\_\-\+\*\s]/g, "");
query = query.substr(0, 256);
window.location.hash = 'search='+encodeURIComponent(query);
},
// search for a query string q, and show the results
find: function (q) {
$(o.results_id).show();
var matches = [];
// split the query string into words
var qs = q.toLowerCase().split(/\s+/);
// this is the match score function
var rq = function(s) {
nmatches = 0.0;
s = s.toLowerCase()
for (var i = 0; i < qs.length; i++) {
if(qs[i].length == 0) {
continue;
}
var xs = s;
delta = 1.0*qs[i].length/Math.min( q.length, xs.length );
while (xs.length > 0) {
var kk = xs.indexOf(qs[i]);
if(kk < 0) {
break;
}
if( (kk == 0 || xs[kk-1].match(/[^A-Za-z0-9]/))
&& (kk+qs[i].length == xs.length || xs[kk+qs[i].length].match(/[^A-Za-z0-9]/)) ) {
nmatches += delta; // full word matches count more!
} else {
nmatches += 0.1*delta;
}
xs = xs.substr(kk+qs[i].length);
}
}
return nmatches;
}
for (var i = 0; i < entries.length; i++) {
var entry = entries[i];
var title = $(entry.getElementsByTagName('title')).text();
var link = $(entry.getElementsByTagName('link')).attr('href');
var summary = "";
if ($(entry.getElementsByTagName('summary')).length > 0) {
summary = $(entry.getElementsByTagName('summary')).text();
} else {
summary = $(entry.getElementsByTagName('content')).text();
}
var category_score = _.reduce($(entry.getElementsByTagName('category')),
function(memo, cat){
return memo + rq( $(cat).attr('label') + " " + $(cat).attr('term') );
}, 0.0);
// here's another way to improve search sensitivity:
// a) change the weights
var matchscore = rq(title) + rq(link) + rq(summary) + category_score;
if ( matchscore > 0.1 ) {
var updated = prettyDate(xmlDateToJavascriptDate($(entry.getElementsByTagName('updated')).text()));
matches.push({'title':title, 'link':link, 'date':updated, 'summary': summary, 'score' : matchscore });
}
}
// sort by match score
matches.sort(function(a,b){return b.score - a.score;});
// output results into div
var html = '<a href="#" onclick="window.location.hash=\'\'; return false;" class="ui-icon ui-icon-close" style="float:right;">Dismiss</a>';
if (matches.length > 0) {
html += '<h3 style="text-align:center; margin-bottom:40px;">Search Results for "'+htmlEscape(q)+'"</h3><div id="results">';
for (var i = 0; i < matches.length; i++) {
var match = matches[i];
var match_summary = "";
if(match.summary && typeof (match.summary) == 'string') {
match_summary = _.map(match.summary.replace(/<\/?pre>/g, "").replace(/[\n\r]\s*[\n\r]/g, "\n").split(/[\n\r]+/).slice(0, 5), htmlEscape).join("<br/>");
}
html += '<div class="search_match">';
html += '<a href="'+match.link+'" class="search_match_link"> '+htmlEscape(match.title) + ' [' + Math.round(match.score*1000)/1000 + '] ';
html += '</a>&nbsp;'
+ '<span class="search_match_date">' + match.date + '</span>'
+ ' <span class="search_match_summary">'+match_summary + '</span>'
;
html += '</div>';
}
html += '</div>';
} else {
html += '<h3 style="text-align:center; margin-bottom:40px;">Nothing found for "'+htmlEscape(q)+'"</h3>'
}
$(o.results_id).html(html);
}
};
sbox.init();
};
})( jQuery );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment