Last active
May 25, 2021 21:31
-
-
Save pkrusche/4625397 to your computer and use it in GitHub Desktop.
Static site search using RSS in JQuery
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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> ' | |
+ '<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