Skip to content
Permalink
Browse files

Custom search solution :-S

  • Loading branch information...
sunesimonsen committed Mar 4, 2015
1 parent 0ee9118 commit 4e1e704f6dca6d8e359a3d7a67b208b326b55549
Showing with 244 additions and 0 deletions.
  1. +20 −0 site/build.js
  2. +39 −0 site/static/main.less
  3. +175 −0 site/static/search.js
  4. +4 −0 site/templates/_footer.ejs
  5. +6 −0 site/templates/_header.ejs
@@ -67,6 +67,26 @@ metalSmith(__dirname)
metadata.assertionsByType = assertionsByType;
next();
})
.use(function (files, metalsmith, next) {
var metadata = metalsmith.metadata();
var indexData = [];

metadata.collections.apiPages.forEach(function (assertion) {
indexData.push({
label: assertion.title + ' (api)',
url: assertion.url
});
});

metadata.collections.assertions.forEach(function (assertion) {
indexData.push({
label: assertion.title + ' (' + assertion.type + ')',
url: assertion.url
});
});
files['searchIndex.json'] = { contents: JSON.stringify(indexData, null, 2) };
next();
})
.use(require('./metalsmith-unexpected-markdown')())
// permalinks with no options will just make pretty urls...
.use(require('metalsmith-permalinks')({ relative: false }))
@@ -215,6 +215,10 @@ code {
}

@media only screen and (max-width : 900px) {
.search {
display: none;
}

body {
font-size: 0.9em;
}
@@ -310,4 +314,39 @@ code {
.github-ribbon {
display: none;
}
}

.search {
color: black;
position: absolute;
top: 9px;
right: 9px;
input {
width: 200px;
}
.dropDown {
position: absolute;
visibility: hidden;
top: 100%;
min-width: 100%;
right: 0;
height: auto;
background-color: white;
border: thin solid gray;
border-top: none;
box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.5);
white-space: nowrap;
box-sizing: border-box;
ul {
margin: 0;
padding: 0;
li {
list-style: none;
padding: 4px;
&.active {
background-color: lighten(gray, 40%);
}
}
}
}
}
@@ -0,0 +1,175 @@
/*global document, window, XMLHttpRequest, searchIndexPath*/
function setupSearch(searchIndex) {
var search = document.getElementById('search');
var searchDropDown = document.getElementById('searchDropDown');
var searchResults = document.getElementById('searchResults');

var keys = {
escape: 27,
up: 38,
down: 40,
pageUp: 33,
pageDown: 34,
enter: 13,
tab: 9
};

var renderedMatches = [];
var query = '';
var activeIndex = 0;

searchDropDown.addEventListener('mousedown', function (e) {
if (e.button === 0 && e.target.hasAttribute('data-index')) {
var index = e.target.getAttribute('data-index');
window.location.href = renderedMatches[index].url;
}
});

searchDropDown.addEventListener('touchstart', function (e) {
if (e.target.hasAttribute('data-index')) {
var index = e.target.getAttribute('data-index');
window.location.href = renderedMatches[index].url;
}
});

function htmlEncode(text) {
return text.replace(/&/g, "&amp;").replace(/>/g, "&gt;").replace(/</g, "&lt;").replace(/"/g, "&quot;");
}

function renderMatches(matches) {
if (matches.length > 0) {

var html = matches.map(function (match, index) {
return '<li' + (activeIndex === index ? ' class="active"' : '') + ' data-index="' + index + '"'+ '>' + htmlEncode(match.label) + '</li>';
}).join('');


searchResults.innerHTML = html;
searchDropDown.style.visibility = 'visible';
} else {
searchResults.innerHTML = '';
searchDropDown.style.visibility = 'hidden';
}
renderedMatches = matches;
}

function preventDefault(e) {
if (e.preventDefault) { e.preventDefault(); }
return false;
}

function selectPrevious() {
var newIndex = (activeIndex - 1) % renderedMatches.length;

if (newIndex < 0) {
newIndex = renderedMatches.length - 1;
}

if (activeIndex !== newIndex) {
activeIndex = newIndex;
renderMatches(renderedMatches);
}

return true;
}

function selectNext() {
var newIndex = (activeIndex + 1) % renderedMatches.length;

if (activeIndex !== newIndex) {
activeIndex = newIndex;
renderMatches(renderedMatches);
}

return true;
}

function clearSearch() {
if (renderedMatches.length > 0) {
query = '';
search.value = '';
renderMatches([]);
}
}

function selectActive() {
if (renderedMatches.length === 0) {
return false;
}

window.location.href = renderedMatches[activeIndex].url;
clearSearch();

return true;
}

search.addEventListener('keydown', function (e) {
if (e.ctrlKey || e.metaKey || e.altKey || e.shiftKey) {
// This is a modified key event. Don't react to those.
return true;
}
switch (e.which) {
case keys.up:
if (selectPrevious()) {
return preventDefault(e);
}
break;
case keys.down:
if (selectNext()) {
return preventDefault(e);
}
break;
case keys.enter:
if (selectActive()) {
return preventDefault(e);
}
break;
case keys.tab:
selectActive();
break;
}
return true;
});

search.addEventListener('blur', function () {
clearSearch();
});

search.addEventListener('keyup', function (e) {
if (e.which === keys.escape) {
query = '';
search.value = '';
renderMatches([]);
search.blur();
} else if (e.which !== keys.enter && search.value !== query) {
query = search.value.toLowerCase();
var matches = !query ? [] : searchIndex.filter(function (entry) {
return entry.label.toLowerCase().indexOf(query) !== -1;
});

activeIndex = 0;
renderMatches(matches.slice(0, 8));
}
});

search.style.visibility = 'visible';
}

var getJSON = function(url, successHandler, errorHandler) {
var xhr = new XMLHttpRequest();
xhr.open('get', url, true);
xhr.responseType = 'json';
xhr.onload = function() {
var status = xhr.status;
if (status === 200) {
if (successHandler) { successHandler(status, xhr.response); }
} else {
if (errorHandler) { errorHandler(status); }
}
};
xhr.send();
};

getJSON(searchIndexPath, function(status, data) {
setupSearch(data);
});
@@ -1,4 +1,8 @@
<script type="text/javascript" src="<%= relative('/static/toggleSidebar.js') %>"></script>
<script type="text/javascript" src="<%= relative('/static/rememberScrollPosition.js') %>"></script>
<script type="text/javascript">
var searchIndexPath = "<%= relative('/searchIndex.json') %>";
</script>
<script type="text/javascript" src="<%= relative('/static/search.js') %>"></script>
</body>
</html>
@@ -20,4 +20,10 @@
<li class="<%- path.match(/^api\/?/) ? 'active' : '' %>"><a href="<%= relative(collections.apiPages[0].url)%>">API</a></li>
</ul>
</nav>
<div class="search" style="visibility: hidden">
<input id="search" placeholder="Search..." value="">
<div id="searchDropDown" class="dropDown">
<ul id="searchResults"></ul>
</div>
</div>
</header>

0 comments on commit 4e1e704

Please sign in to comment.
You can’t perform that action at this time.