updated docs

This commit is contained in:
Christian Mehlmauer
2012-12-07 20:18:02 +01:00
parent b85ce58fb2
commit b3cf67c8f3
20 changed files with 1618 additions and 132 deletions

142
doc/js/navigation.js Normal file
View File

@@ -0,0 +1,142 @@
/*
* Navigation allows movement using the arrow keys through the search results.
*
* When using this library you will need to set scrollIntoView to the
* appropriate function for your layout. Use scrollInWindow if the container
* is not scrollable and scrollInElement if the container is a separate
* scrolling region.
*/
Navigation = new function() {
this.initNavigation = function() {
var _this = this;
$(document).keydown(function(e) {
_this.onkeydown(e);
}).keyup(function(e) {
_this.onkeyup(e);
});
this.navigationActive = true;
}
this.setNavigationActive = function(state) {
this.navigationActive = state;
this.clearMoveTimeout();
}
this.onkeyup = function(e) {
if (!this.navigationActive) return;
switch(e.keyCode) {
case 37: //Event.KEY_LEFT:
case 38: //Event.KEY_UP:
case 39: //Event.KEY_RIGHT:
case 40: //Event.KEY_DOWN:
this.clearMoveTimeout();
break;
}
}
this.onkeydown = function(e) {
if (!this.navigationActive) return;
switch(e.keyCode) {
case 37: //Event.KEY_LEFT:
if (this.moveLeft()) e.preventDefault();
break;
case 38: //Event.KEY_UP:
if (e.keyCode == 38 || e.ctrlKey) {
if (this.moveUp()) e.preventDefault();
this.startMoveTimeout(false);
}
break;
case 39: //Event.KEY_RIGHT:
if (this.moveRight()) e.preventDefault();
break;
case 40: //Event.KEY_DOWN:
if (e.keyCode == 40 || e.ctrlKey) {
if (this.moveDown()) e.preventDefault();
this.startMoveTimeout(true);
}
break;
case 13: //Event.KEY_RETURN:
if (this.$current)
e.preventDefault();
this.select(this.$current);
break;
}
if (e.ctrlKey && e.shiftKey) this.select(this.$current);
}
this.clearMoveTimeout = function() {
clearTimeout(this.moveTimeout);
this.moveTimeout = null;
}
this.startMoveTimeout = function(isDown) {
if (!$.browser.mozilla && !$.browser.opera) return;
if (this.moveTimeout) this.clearMoveTimeout();
var _this = this;
var go = function() {
if (!_this.moveTimeout) return;
_this[isDown ? 'moveDown' : 'moveUp']();
_this.moveTimout = setTimeout(go, 100);
}
this.moveTimeout = setTimeout(go, 200);
}
this.moveRight = function() {
}
this.moveLeft = function() {
}
this.move = function(isDown) {
}
this.moveUp = function() {
return this.move(false);
}
this.moveDown = function() {
return this.move(true);
}
/*
* Scrolls to the given element in the scrollable element view.
*/
this.scrollInElement = function(element, view) {
var offset, viewHeight, viewScroll, height;
offset = element.offsetTop;
height = element.offsetHeight;
viewHeight = view.offsetHeight;
viewScroll = view.scrollTop;
if (offset - viewScroll + height > viewHeight) {
view.scrollTop = offset - viewHeight + height;
}
if (offset < viewScroll) {
view.scrollTop = offset;
}
}
/*
* Scrolls to the given element in the window. The second argument is
* ignored
*/
this.scrollInWindow = function(element, ignored) {
var offset, viewHeight, viewScroll, height;
offset = element.offsetTop;
height = element.offsetHeight;
viewHeight = window.innerHeight;
viewScroll = window.scrollY;
if (offset - viewScroll + height > viewHeight) {
window.scrollTo(window.scrollX, offset - viewHeight + height);
}
if (offset < viewScroll) {
window.scrollTo(window.scrollX, offset);
}
}
}

94
doc/js/search.js Normal file
View File

@@ -0,0 +1,94 @@
Search = function(data, input, result) {
this.data = data;
this.$input = $(input);
this.$result = $(result);
this.$current = null;
this.$view = this.$result.parent();
this.searcher = new Searcher(data.index);
this.init();
}
Search.prototype = $.extend({}, Navigation, new function() {
var suid = 1;
this.init = function() {
var _this = this;
var observer = function() {
_this.search(_this.$input[0].value);
};
this.$input.keyup(observer);
this.$input.click(observer); // mac's clear field
this.searcher.ready(function(results, isLast) {
_this.addResults(results, isLast);
})
this.initNavigation();
this.setNavigationActive(false);
}
this.search = function(value, selectFirstMatch) {
value = jQuery.trim(value).toLowerCase();
if (value) {
this.setNavigationActive(true);
} else {
this.setNavigationActive(false);
}
if (value == '') {
this.lastQuery = value;
this.$result.empty();
this.setNavigationActive(false);
} else if (value != this.lastQuery) {
this.lastQuery = value;
this.firstRun = true;
this.searcher.find(value);
}
}
this.addResults = function(results, isLast) {
var target = this.$result.get(0);
if (this.firstRun && (results.length > 0 || isLast)) {
this.$current = null;
this.$result.empty();
}
for (var i=0, l = results.length; i < l; i++) {
target.appendChild(this.renderItem.call(this, results[i]));
};
if (this.firstRun && results.length > 0) {
this.firstRun = false;
this.$current = $(target.firstChild);
this.$current.addClass('current');
}
if (jQuery.browser.msie) this.$element[0].className += '';
}
this.move = function(isDown) {
if (!this.$current) return;
var $next = this.$current[isDown ? 'next' : 'prev']();
if ($next.length) {
this.$current.removeClass('current');
$next.addClass('current');
this.scrollIntoView($next[0], this.$view[0]);
this.$current = $next;
}
return true;
}
this.hlt = function(html) {
return this.escapeHTML(html).
replace(/\u0001/g, '<em>').
replace(/\u0002/g, '</em>');
}
this.escapeHTML = function(html) {
return html.replace(/[&<>]/g, function(c) {
return '&#' + c.charCodeAt(0) + ';';
});
}
});

1
doc/js/search_index.js Normal file

File diff suppressed because one or more lines are too long

228
doc/js/searcher.js Normal file
View File

@@ -0,0 +1,228 @@
Searcher = function(data) {
this.data = data;
this.handlers = [];
}
Searcher.prototype = new function() {
// search is performed in chunks of 1000 for non-blocking user input
var CHUNK_SIZE = 1000;
// do not try to find more than 100 results
var MAX_RESULTS = 100;
var huid = 1;
var suid = 1;
var runs = 0;
this.find = function(query) {
var queries = splitQuery(query);
var regexps = buildRegexps(queries);
var highlighters = buildHilighters(queries);
var state = { from: 0, pass: 0, limit: MAX_RESULTS, n: suid++};
var _this = this;
this.currentSuid = state.n;
if (!query) return;
var run = function() {
// stop current search thread if new search started
if (state.n != _this.currentSuid) return;
var results =
performSearch(_this.data, regexps, queries, highlighters, state);
var hasMore = (state.limit > 0 && state.pass < 4);
triggerResults.call(_this, results, !hasMore);
if (hasMore) {
setTimeout(run, 2);
}
runs++;
};
runs = 0;
// start search thread
run();
}
/* ----- Events ------ */
this.ready = function(fn) {
fn.huid = huid;
this.handlers.push(fn);
}
/* ----- Utilities ------ */
function splitQuery(query) {
return jQuery.grep(query.split(/(\s+|::?|\(\)?)/), function(string) {
return string.match(/\S/)
});
}
function buildRegexps(queries) {
return jQuery.map(queries, function(query) {
return new RegExp(query.replace(/(.)/g, '([$1])([^$1]*?)'), 'i')
});
}
function buildHilighters(queries) {
return jQuery.map(queries, function(query) {
return jQuery.map(query.split(''), function(l, i) {
return '\u0001$' + (i*2+1) + '\u0002$' + (i*2+2);
}).join('');
});
}
// function longMatchRegexp(index, longIndex, regexps) {
// for (var i = regexps.length - 1; i >= 0; i--){
// if (!index.match(regexps[i]) && !longIndex.match(regexps[i])) return false;
// };
// return true;
// }
/* ----- Mathchers ------ */
/*
* This record matches if the index starts with queries[0] and the record
* matches all of the regexps
*/
function matchPassBeginning(index, longIndex, queries, regexps) {
if (index.indexOf(queries[0]) != 0) return false;
for (var i=1, l = regexps.length; i < l; i++) {
if (!index.match(regexps[i]) && !longIndex.match(regexps[i]))
return false;
};
return true;
}
/*
* This record matches if the longIndex starts with queries[0] and the
* longIndex matches all of the regexps
*/
function matchPassLongIndex(index, longIndex, queries, regexps) {
if (longIndex.indexOf(queries[0]) != 0) return false;
for (var i=1, l = regexps.length; i < l; i++) {
if (!longIndex.match(regexps[i]))
return false;
};
return true;
}
/*
* This record matches if the index contains queries[0] and the record
* matches all of the regexps
*/
function matchPassContains(index, longIndex, queries, regexps) {
if (index.indexOf(queries[0]) == -1) return false;
for (var i=1, l = regexps.length; i < l; i++) {
if (!index.match(regexps[i]) && !longIndex.match(regexps[i]))
return false;
};
return true;
}
/*
* This record matches if regexps[0] matches the index and the record
* matches all of the regexps
*/
function matchPassRegexp(index, longIndex, queries, regexps) {
if (!index.match(regexps[0])) return false;
for (var i=1, l = regexps.length; i < l; i++) {
if (!index.match(regexps[i]) && !longIndex.match(regexps[i]))
return false;
};
return true;
}
/* ----- Highlighters ------ */
function highlightRegexp(info, queries, regexps, highlighters) {
var result = createResult(info);
for (var i=0, l = regexps.length; i < l; i++) {
result.title = result.title.replace(regexps[i], highlighters[i]);
result.namespace = result.namespace.replace(regexps[i], highlighters[i]);
};
return result;
}
function hltSubstring(string, pos, length) {
return string.substring(0, pos) + '\u0001' + string.substring(pos, pos + length) + '\u0002' + string.substring(pos + length);
}
function highlightQuery(info, queries, regexps, highlighters) {
var result = createResult(info);
var pos = 0;
var lcTitle = result.title.toLowerCase();
pos = lcTitle.indexOf(queries[0]);
if (pos != -1) {
result.title = hltSubstring(result.title, pos, queries[0].length);
}
result.namespace = result.namespace.replace(regexps[0], highlighters[0]);
for (var i=1, l = regexps.length; i < l; i++) {
result.title = result.title.replace(regexps[i], highlighters[i]);
result.namespace = result.namespace.replace(regexps[i], highlighters[i]);
};
return result;
}
function createResult(info) {
var result = {};
result.title = info[0];
result.namespace = info[1];
result.path = info[2];
result.params = info[3];
result.snippet = info[4];
return result;
}
/* ----- Searching ------ */
function performSearch(data, regexps, queries, highlighters, state) {
var searchIndex = data.searchIndex;
var longSearchIndex = data.longSearchIndex;
var info = data.info;
var result = [];
var i = state.from;
var l = searchIndex.length;
var togo = CHUNK_SIZE;
var matchFunc, hltFunc;
while (state.pass < 4 && state.limit > 0 && togo > 0) {
if (state.pass == 0) {
matchFunc = matchPassBeginning;
hltFunc = highlightQuery;
} else if (state.pass == 1) {
matchFunc = matchPassLongIndex;
hltFunc = highlightQuery;
} else if (state.pass == 2) {
matchFunc = matchPassContains;
hltFunc = highlightQuery;
} else if (state.pass == 3) {
matchFunc = matchPassRegexp;
hltFunc = highlightRegexp;
}
for (; togo > 0 && i < l && state.limit > 0; i++, togo--) {
if (info[i].n == state.n) continue;
if (matchFunc(searchIndex[i], longSearchIndex[i], queries, regexps)) {
info[i].n = state.n;
result.push(hltFunc(info[i], queries, regexps, highlighters));
state.limit--;
}
};
if (searchIndex.length <= i) {
state.pass++;
i = state.from = 0;
} else {
state.from = i;
}
}
return result;
}
function triggerResults(results, isLast) {
jQuery.each(this.handlers, function(i, fn) {
fn.call(this, results, isLast)
})
}
}