Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Finish documentation on extras, extras README, update some docs

  • Loading branch information...
commit 3426f90d8fdb967c33f73ae92680c030dde8cd88 1 parent f1690be
@jbt authored
View
4 History.md
@@ -2,8 +2,8 @@
## 0.2.0
- * Added option to specify extra CSS and JavaScript files to include
- * Added optional flag to enable line numbers in output
+ * Added `--js` and `--css` options to specify extra CSS and JavaScript files to include
+ * Added optional `-n` flag to enable line numbers in output
* Added "disable" value for sidebar to hide it completely
* Added support for matching files by name rather than just extension (e.g. Makefile)
* Added first two "extras" - optionally enable-able extras for the output
View
25 README.md
@@ -10,7 +10,7 @@ Take a look at this project's [public page](http://jbt.github.com/docker) for an
Simple: `npm install -g docker`
-Requires [Pygments](http://pygments.org/)
+**Requires [Pygments](http://pygments.org/)**
## Usage
@@ -26,8 +26,12 @@ Available options are:
* `-c` or `--colour_scheme` (yes, I'm British): Colour scheme to use. Colour schemes are as below.
* `-I` or `--ignore_hidden`: Ignore files and directories whose names begin with `.` or `_`.
* `-w` or `--watch`: Keep the process running, watch for changes on the directory, and process updated files.
- * `-s` or `--sidebar`: Whether or not the sidebar should be opened by default in the output (defaults to yes, can be yes, no, true, false)
+ * `-s` or `--sidebar`: Whether or not the sidebar should be opened by default in the output (defaults to yes, can be yes, no, true, false). Value `disable` will disable the sidebar entirely in the output.
* `-x` or `--exclude`: Comma-separated list of paths to exclude. Supports basic `*` wildcards too.
+ * `-n` or `--line-number`: Include line numbers in the output (default is off)
+ * `--js`: Specify a comma-separated list of extra javascript files (relative to the current dir) to include
+ * `--css`: Same as for `--js` but for CSS files
+ * `--extras`: Comma-separated list of optional extras to activate (see below)
If no file list is given, docker will run recursively on every file in the current directory
@@ -68,15 +72,27 @@ This is the command I use to generate [this project's documentation](http://jbt.
* Output to a directory on the `gh-pages` branch of this repo
* Use the "manni" colour scheme
+ * Show the sidebar by default
* Ignore files starting with `_` or `.`
* Only process updated files
* Exclude the node_modules directory
* Watch the directory for further changes as the code is updated.
+ * Include the File Search extra
```sh
-$ docker -o ../docker_gh-pages -c manni -I -u -x node_modules --watch
+$ docker -o ../docker_gh-pages -c manni -s yes -I -u -x node_modules -w --extras fileSearch
```
+## Extras
+
+The output of Docker is designed to be fairly lightweight, so doesn't include much other than the bare
+minimum to power the basic features. Optional extras like file searching and line jumping are therefore
+contained in there own files and can be turned on by a specific flag.
+
+If you're viewing this on GitHub, take a look [here](/jbt/docker/tree/master/extras); if you're looking
+at the Docker output, look [here](extras/README.md.html), for further explanation.
+
+
## Colour Schemes
@@ -105,4 +121,5 @@ These are exactly as in `pygmentize -L styles`:
## Important note
-All files must be inside the input directory (specified by `-i`) or one of its descendant subdirectories. If they're not then it'll just get horribly confused and get into an infinite loop. Which isn't nice.
+All files must be inside the input directory (specified by `-i`) or one of its descendant subdirectories. If they're not then various file paths in the output won't work properly and the file will probably get generated
+in the wrong place. Still, it's better than what it used to do, which was to get stuck in an infinite loop.
View
25 extras/README.md
@@ -0,0 +1,25 @@
+# Optional Docker Extras
+
+This directory contains optional files that can be enabled in Docker output
+
+File stucture in this directory is incredibly simple: for a given extra name
+(e.g. `extraName`) create a directory with that name, and one single JavaScript
+and one CSS file for the extra (i.e. `extraName/extraName.js`, `extraName/extraName.css`).
+This format may change in the future to allow for multiple files or for extending
+the functionality of the Docker process itself, but this format will remain supported.
+
+# Current Extras
+
+## fileSearch
+
+Fuzzy string-matching against file names. Once enabled, hit Ctrl/Cmd + P
+(à la Sublime Text 2) to bring up a search field, type some important
+characters that should be enough to identify a file, hit return and jump straight
+to the file. Alternatively, just start typing and the search field will automatically appear.
+
+## goToLine
+
+Simple option to quickly jump to a particular code line. Just hit Ctrl/Cmd + G and
+type a line number into the field to instantly jump to the given line. Currently this
+only works when the `--line-numbers` option is used, and only works for code lines
+(i.e. it won't allow you to jump to a line with a comment on it)
View
627 extras/fileSearch/fileSearch.js
@@ -1,16 +1,38 @@
-var fuzzyRank = function(){
+// # fileSearch.js
+//
+// Contains methods for performing a fuzzy string search on file names
+// for easily jumping between files. The ranking algorithm is not based
+// on anything in particular - it's just a hack project that works reasonably
+// well. If anyone has any suggestions on how it can be improved, then
+// let me know.
+
+
+// Wrap everything inside a closure so we don't get any collisions in
+// the global scope
+(function(){
+
+ /**
+ * ## escapeRegex
+ *
+ * Escapes special characters in a string to use when creating a new RegExp
+ *
+ * @param {string} term String to escape
+ * @return {string} Regex-compatible escaped string
+ */
function escapeRegex(term){
return term.replace(/\[\]\{\}\(\)\^\$\.\*\+\|/g, function(a){
return '\\' + a;
});
}
+ // A few heper constants; they don't really do much, but they
+ // indicate the corresponding rows and columns in the matrix below.
var UPPER = 0, LOWER = 1, NUMBER = 2, COMMON_DELIMS = 3, OTHER = 4;
// Amount by which one character stands out when compared
// to another character. Row = character in question,
// col = character to compare to. E.g. uppercase letter
- // stands out with a factor of 220 compared to lowercase letter.
+ // stands out with a factor of 240 compared to lowercase letter.
// These numbers are pretty much plucked out of thin air.
var relevanceMatrix = [
[ 0, 240, 120, 240, 220],
@@ -20,6 +42,15 @@ var fuzzyRank = function(){
[120, 120, 120, 160, 0]
];
+ /**
+ * ## charType
+ *
+ * Categorizes a character as either lowercase, uppercase,
+ * digit, strong delimiter, or other.
+ *
+ * @param {string} c The character to check
+ * @return {number} One of the constants defined above
+ */
function charType(c){
if(/[a-z]/.test(c)) return LOWER;
if(/[A-Z]/.test(c)) return UPPER;
@@ -28,16 +59,46 @@ var fuzzyRank = function(){
return OTHER;
}
+ /**
+ * ## compareCharacters
+ *
+ * Compares a character to the characters before and
+ * after it to see how much it stands out. For example
+ * The letter B would stand out strongly in aBc
+ *
+ * @param {string} theChar The character in question
+ * @param {string} before The immediately preceding character
+ * @param {string} after The immediately following character
+ */
function compareCharacters(theChar, before, after){
+
+ // Grab the character types of all three characters
var theType = charType(theChar),
beforeType = charType(before),
afterType = charType(after);
+ // **MAGIC NUMBER ALERT** 0.4 is a number that makes it work best in my tests
return relevanceMatrix[theType][beforeType] +
0.4 * relevanceMatrix[theType][afterType];
}
+ /**
+ * ## bestRank
+ *
+ * The real meat of this searching algorithm. Provides a score for a
+ * given string against a given search term.
+ *
+ * The `startingFrom` parameter is necessary (rather than just truncating
+ * `item` so we can use the initial characters of `item` to provide better
+ * context.
+ *
+ * @param {string} item The string to rank
+ * @param {string} term The search term against which to rank it
+ * @param {number} startingFrom Ignore the first _n_ characters
+ * @return {object} Rank of `item` against `term` with highlights
+ */
function bestRank(item, term, startingFrom){
+
// If we've reached the end of our search term, add some extra points for being short
if(term.length === 0) return startingFrom * 100 / item.length;
@@ -51,17 +112,23 @@ var fuzzyRank = function(){
return -1;
}
+ // Grab the first character that we're going to look at
var firstSearchChar = term.charAt(0);
+
+ // These variables store our best guess so far, and the character
+ // indices to which it applies
var bestRankSoFar = -1;
var highlights;
+ // Now loop over the item, and test all instances of `firstSearchChar` (case-insensitive)
for(var i = startingFrom; i < item.length; i += 1){
if(item.charAt(i).toLowerCase() !== firstSearchChar.toLowerCase()) continue;
+ // Find out what the rest of the string scores against the rest of the term
var subsequentRank = bestRank(item.substr(i), term.slice(1), 1);
if(subsequentRank == -1) continue;
- // Inverse quadratic score for the character. Earlier in string = much better
+ // Inverse linear score for the character. Earlier in string = much better
var characterScore = 400 / Math.max(1, i);
// If, starting at this character, we have the whole of the search term in order, that's really
@@ -78,9 +145,11 @@ var fuzzyRank = function(){
// Add on score from the rest of the string
characterScore += subsequentRank;
+ // If we've managed to better what we have so far, store it away
if(characterScore > bestRankSoFar){
bestRankSoFar = characterScore;
+ // Save highlighted characters as well
highlights = [i];
var subsequentHighlights = subsequentRank.highlights || [];
for(var j = 0; j < subsequentHighlights.length; j += 1){
@@ -89,6 +158,8 @@ var fuzzyRank = function(){
}
}
+ // Return an object with valueOf so it can be directly compared using < and >
+ // but also stores the highlight indices
return {
__score: bestRankSoFar,
valueOf: function(){ return this.__score; },
@@ -96,24 +167,60 @@ var fuzzyRank = function(){
};
}
+ /**
+ * ## fuzzyScoreStr
+ *
+ * Actual function to use when matching an item against a term
+ * (bestRank should only be used internally)
+ *
+ * @param {string} item Item to search
+ * @param {string} term Term against which to search
+ * @return {object} Rank of `item` against `term` with highlights
+ */
function fuzzyScoreStr(item, term){
return bestRank(item, term, 0);
}
+ /**
+ * ## fuzzyScore
+ *
+ * Matches an object against a given term with particular weights being
+ * applied to different properties. If the given item is instead a string,
+ * just match it directly against the term.
+ *
+ * The `relevances` parameter should be an object containing properties
+ * with the same names as those on `item` that should be counted. For
+ * example, a value of `{ propA: 2, propB: 1}` would count matches in
+ * `propA` twice as highly as matches in `propB`.
+ *
+ * The returned `highlights` property contains arrays of character indices
+ * to highlight in the term, indexed by the same property names
+ *
+ * @param {object} item Item containing multiple properties to search
+ * @param {string} term Term against which to search
+ * @param {object} relevances Object congaining key/val pairs as above
+ * @return {object} Rank of `item` against `term` with highlights.
+ */
function fuzzyScore(item, term, relevances){
+
+ // If we have a string, just match it directly
if(typeof item == 'string') return fuzzyScoreStr(item, term);
+ // Initialize the return object
var result = {
__score: 0,
valueOf: function(){ return this.__score; },
highlights: {}
};
+ // Loop through all the specified properties
for(var i in relevances){
if(!relevances.hasOwnProperty(i) || !item.hasOwnProperty(i)) continue;
+ // Grab the score for that particular property
var thatScore = fuzzyScoreStr(item[i], term);
+ // Add the rank on to the return object
result.__score += relevances[i] * thatScore;
result.highlights[i] = thatScore > 0 ? thatScore.highlights : [];
}
@@ -121,218 +228,386 @@ var fuzzyRank = function(){
return result;
}
- return fuzzyScore;
-}();
+ // ## Right, that's the end of the ranking stuff
+ // Now onwards to building it into our page!
-var fileList = [];
+ // Loop through the tree and parse it all into a big array
+ var fileList = [];
-function addDirToList(dir, path){
- if(dir.dirs){
- for(var i in dir.dirs){
- if(dir.dirs.hasOwnProperty(i)) addDirToList(dir.dirs[i], path + i + '/');
+ function addDirToList(dir, path){
+ if(dir.dirs){
+ for(var i in dir.dirs)
+ if(dir.dirs.hasOwnProperty(i)) addDirToList(dir.dirs[i], path + i + '/');
}
- }
- if(dir.files){
- for(var i = 0; i < dir.files.length; i += 1){
- fileList.push(path + dir.files[i]);
+ if(dir.files){
+ for(var i = 0; i < dir.files.length; i += 1) fileList.push(path + dir.files[i]);
}
}
-}
-addDirToList(tree, '');
-
-var searchBoxShown = false;
-var searchingTimeout, selectedSearchIndex, selectedItem;
+ addDirToList(tree, '');
+
+ // Some variables to store the state of the search box
+ var searchBoxShown = false;
+ var searchingTimeout, selectedSearchIndex, selectedItem;
+
+ /**
+ * ## doSearch
+ *
+ * Actually perform the file search
+ */
+ function doSearch(){
+
+ // Grab the search term from the input
+ var term = document.getElementById('searchbox').value;
+
+ var items = [];
+
+ // Loop through all the files and rank them all
+ for(var i = 0; i < fileList.length; i += 1){
+ var f = fileList[i];
+
+ // Split up the file name so we can grab the base name
+ var parts = f.split('/');
+
+ // This is the object that'll get passed to fuzzyScore
+ var file = {
+ fullPath: f,
+ fileName: parts[parts.length - 1]
+ };
+
+ // Rank the file, primarily against the full path,
+ // but if the file base name also matches, give it a
+ // boost. 0.6 is a number that produces nice results
+ var rank = fuzzyScore(file, term, {
+ fullPath: 1,
+ fileName: 0.6
+ });
+
+ // If the file scores positively against the search, add it in
+ if(rank > 0){
+ file.highlight = rank.highlights;
+ file.score = +rank;
+ items.push(file);
+ }
+ }
-function doSearch(){
- var term = document.getElementById('searchbox').value;
+ // Sort the items order of decreasing relevance
+ items.sort(function(a, b){
+ if(a.score > b.score) return -1;
+ if(a.score < b.score) return 1;
+ return 0;
+ });
- var items = [];
+ // And render them all
+ renderSearchResults(items);
+ }
- for(var i = 0; i < fileList.length; i += 1){
- var f = fileList[i];
- var parts = f.split('/');
- var file = {
- fullPath: f,
- fileName: parts[parts.length - 1]
- };
- var rank = fuzzyRank(file, term, {
- fullPath: 1,
- fileName: 0.6
- });
- if(rank > 0){
- file.highlight = rank.highlights;
- file.score = +rank;
- items.push(file);
+ /**
+ * ## highlightString
+ *
+ * Highlights the characters at given indices in a string
+ *
+ * @param {string} str String to highlight
+ * @param {array} indexes Array of indices to make bold
+ * @return {string} HTML of string with given characters made bold
+ */
+ function highlightString(str, indexes){
+
+ // If there's nothing to do, return immediately
+ if(!indexes || !indexes.length) return str;
+
+ var out = '';
+
+ // Loop through string one character at a time, highlighting as necessary
+ for(var i = 0; i < str.length; i += 1){
+ out += indexes.indexOf(i) !== -1 ? str.charAt(i).bold() : str.charAt(i);
}
+ return out;
}
- items.sort(function(a, b){
- if(a.score > b.score) return -1;
- if(a.score < b.score) return 1;
- return 0;
- });
+ /**
+ * ## renderSearchResults
+ *
+ * Renders the processed search items as HTML with highlighting
+ * in the results list
+ *
+ * @param {array} items Search results to render
+ */
+ function renderSearchResults(items){
+ var html = '';
+
+ // Loop through items, building up an HTML string
+ for(var i = 0; i < items.length; i += 1){
+ var f = items[i];
+ html += [
+ '<a class="item" data-value="', f.fullPath, '.html">',
+ '<span class="score">', ~~f.score, '</span>',
+ '<span class="filename">', highlightString(f.fileName, f.highlight.fileName), '</span>',
+ '<span class="fullpath">', highlightString(f.fullPath, f.highlight.fullPath), '</span>',
+ '</a>'
+ ].join('');
+ }
- renderSearchResults(items);
-}
+ // Dump all the html into the results list
+ document.getElementById('searchresults').innerHTML = html;
-function highlightString(str, indexes){
- if(!indexes) return str;
- var out = '';
- for(var i = 0; i < str.length; i += 1){
- out += indexes.indexOf(i) !== -1 ? str.charAt(i).bold() : str.charAt(i);
- }
- return out;
-}
-
-function renderSearchResults(items){
- var html = '';
- for(var i = 0; i < items.length; i += 1){
- var f = items[i];
- html += [
- '<a class="item" data-value="', f.fullPath, '.html">',
- '<span class="score">', ~~f.score, '</span>',
- '<span class="filename">', highlightString(f.fileName, f.highlight.fileName), '</span>',
- '<span class="fullpath">', highlightString(f.fullPath, f.highlight.fullPath), '</span>',
- '</a>'
- ].join('');
+ // Select the first item
+ selectIndex(0);
}
- document.getElementById('searchresults').innerHTML = html;
+ /**
+ * ## selectIndex
+ *
+ * Selects the search result at the given index in the list,
+ * and deselcts any currently-selected item
+ *
+ * @param {number} idx Index of the item to select
+ */
+ function selectIndex(idx){
+
+ // If another item is currently selected, deselect it
+ if(selectedItem) selectedItem.className = selectedItem.className.replace(/\s?selected/,'');
+
+ // Grab the search results straight from the DOM
+ var r = document.getElementById('searchresults');
+ var items = r.childNodes;
+
+ // If we actually have no items, there's not much we can do
+ if(items.length === 0){
+ selectedSearchIndex = -1;
+ selectedItem = false;
+ return;
+ }
- selectIndex(0);
-}
+ // Store the item and its index in helper variables
+ selectedSearchIndex = idx;
+ var s = selectedItem = items[idx];
-function selectIndex(idx){
- if(selectedItem) selectedItem.className = selectedItem.className.replace(/\s?selected/,'');
- var r = document.getElementById('searchresults');
- var items = r.childNodes;
+ // Select the item
+ s.className += ' selected';
- if(items.length === 0){
- selectedSearchIndex = -1;
- selectedItem = false;
- return;
+ // Figure out whether or not the item is fully visible inside
+ // the scrollable view, and if not, then scroll appropriately
+ var o = s.offsetTop - r.offsetTop - r.scrollTop;
+ if(o < 0){
+ r.scrollTop = s.offsetTop - r.offsetTop;
+ }else if(o > r.offsetHeight - s.offsetHeight){
+ r.scrollTop = o + r.scrollTop - r.offsetHeight + s.offsetHeight;
+ }
}
- selectedSearchIndex = idx;
- var s = selectedItem = items[idx];
- s.className += ' selected';
-
- var o = s.offsetTop - r.offsetTop - r.scrollTop;
- if(o < 0){
- r.scrollTop = s.offsetTop - r.offsetTop;
- }else if(o > r.offsetHeight - s.offsetHeight){
- r.scrollTop = o + r.scrollTop - r.offsetHeight + s.offsetHeight;
- }
-}
-
-function selectNextItem(){
- var items = document.getElementById('searchresults').childNodes;
- selectIndex((selectedSearchIndex + 1) % items.length);
-}
-
-function selectPreviousItem(){
- var items = document.getElementById('searchresults').childNodes;
- var l = items.length;
- selectIndex((selectedSearchIndex + l - 1) % l);
-}
-
-function searchFormKeyDown(e){
- e = e || window.event;
- if(e.keyCode == 27){
- document.body.removeChild(document.getElementById('search'));
- searchBoxShown = false;
- }else if(e.keyCode == 40){
- selectNextItem();
- e.preventDefault();
- e.stopPropagation();
- return false;
- }else if(e.keyCode == 38){
- selectPreviousItem();
- e.preventDefault();
- e.stopPropagation();
- return false;
- }else{
- clearTimeout(searchingTimeout);
- searchingTimeout = setTimeout(doSearch, 150);
+ /**
+ * ## selectNextItem
+ *
+ * Selects the result immediately after the currently selected item,
+ * or the first item if the currently selected one is last in the list
+ */
+ function selectNextItem(){
+ var items = document.getElementById('searchresults').childNodes;
+ selectIndex((selectedSearchIndex + 1) % items.length);
}
-}
-function addEvent(obj, evt, func, a){
- if((a = obj.addEventListener)){
- a.call(obj, evt, func, false);
- }else{
- obj.attachEvent('on' + evt, func);
+ /**
+ * ## selectPreviouis
+ *
+ * Selects the result immediately preceding the currently selected item,
+ * or the last item if the currently selected one is first in the list
+ */
+ function selectPreviousItem(){
+ var items = document.getElementById('searchresults').childNodes;
+ var l = items.length;
+ selectIndex((selectedSearchIndex + l - 1) % l);
}
-}
-
-function searchFormSubmitted(e){
- e = e || window.event;
- e.preventDefault();
-
- if(!selectedItem) return false;
- window.location.href = relativeDir + selectedItem.getAttribute('data-value');
-
- return false;
-}
+ /**
+ * ## searchFormKeyDown
+ *
+ * Fired whenever a key is pressed on the search form and triggers
+ * the necessary events
+ *
+ * @param {KeyDownEvent} e The event object
+ */
+ function searchFormKeyDown(e){
+ e = e || window.event;
+
+ // 27 = escape, so hide the form
+ if(e.keyCode == 27){
+ document.body.removeChild(document.getElementById('search'));
+ searchBoxShown = false;
+
+ // 40 = down arrow, select the next item
+ }else if(e.keyCode == 40){
+ selectNextItem();
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+
+ // 38 = up arrow, select the previous item
+ }else if(e.keyCode == 38){
+ selectPreviousItem();
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+
+ // Most likely a letter typed or deleted in the search box.
+ // So queue another search
+ }else{
+ clearTimeout(searchingTimeout);
+ searchingTimeout = setTimeout(doSearch, 150);
+ }
+ }
-function itemClicked(e){
- var levels = 5;
- var target = (e || window.event).target;
- while(levels-- && target.tagName !== 'A') target = target.parentNode;
+ /**
+ * ## addEvent
+ *
+ * Helper function for binding DOM events
+ *
+ * @param {Element} obj The DOM element to bind to
+ * @param {string} evt The name of the event to bind to
+ * @param {function} func Listener to attach to the event
+ */
+ function addEvent(obj, evt, func){
+ var a;
+
+ // Sensible browsers use `addEventListener`
+ if((a = obj.addEventListener)){
+ a.call(obj, evt, func, false);
+
+ // IE uses `attachEvent`
+ }else{
+ obj.attachEvent('on' + evt, func);
+ }
+ }
- selectedItem = target;
- searchFormSubmitted(e);
-}
+ /**
+ * ## searchFormSubmitted
+ *
+ * Called when the user hits enter on the search form
+ * so find the selected item and jump to that page
+ *
+ * @param {FormSubmitEvent} e The submit event
+ */
+ function searchFormSubmitted(e){
+ e = e || window.event;
+ e.preventDefault();
-function showSearchBox(val){
- if(searchBoxShown) return;
- searchBoxShown = true;
+ // If there's no selected item, do nothing
+ if(!selectedItem) return false;
- var f = document.createElement('div');
+ // Otherwise, jump to the page
+ window.location.href = relativeDir + selectedItem.getAttribute('data-value');
- f.id = "search";
+ return false;
+ }
- f.innerHTML = [
- '<div class="overlay"></div>',
- '<div class="box">',
- '<form id="searchform">',
- '<input id="searchbox" type="text" name="file" placeholder="Go to file..." autocomplete="off" value="', val, '"/>',
- '</form>',
- '<div id="searchresults"></div>',
- '</div>'
- ].join('');
+ /**
+ * ## itemClicked
+ *
+ * Called when a search result item is clicked. So jump
+ * to the relecant page using [searchFormSubmitted](#searchformsubmitted)
+ *
+ * @param {MouseClickEvent} e The mouse event
+ */
+ function itemClicked(e){
+
+ // Maximum number of levels to jump up the DOM tree
+ var levels = 5;
+ var target = (e || window.event).target;
+
+ // The click event may have propagated from a child node,
+ // so loop upwards until we actually find the result item
+ while(levels-- && target.tagName !== 'A') target = target.parentNode;
+
+ // Set the selected item, then fire off the submit event on the form
+ selectedItem = target;
+ searchFormSubmitted(e);
+ }
- addEvent(f, 'keydown', searchFormKeyDown);
+ /**
+ * ## showSearchBox
+ *
+ * Constructs and shows the search box, optionally pre-populated
+ * with a value for the search field
+ *
+ * @param {string,optional} val Pre-populated value for the search field
+ */
+ function showSearchBox(val){
+
+ // If the box is already visible, do nothing
+ if(searchBoxShown) return;
+ searchBoxShown = true;
+
+ // Create the containing element
+ var f = document.createElement('div');
+ f.id = "search";
+
+ // Construct some basic HTML
+ f.innerHTML = [
+ '<div class="overlay"></div>',
+ '<div class="box">',
+ '<form id="searchform">',
+ '<input id="searchbox" type="text" name="file" placeholder="Go to file..." autocomplete="off" value="', val, '"/>',
+ '</form>',
+ '<div id="searchresults"></div>',
+ '</div>'
+ ].join('');
- document.body.appendChild(f);
+ // Add the keydown event
+ addEvent(f, 'keydown', searchFormKeyDown);
- document.getElementById('searchbox').focus();
- addEvent(document.getElementById('searchform'), 'submit', searchFormSubmitted);
- addEvent(document.getElementById('searchresults'), 'click', itemClicked);
+ // Add the container straight onto the body
+ document.body.appendChild(f);
- if(val) doSearch();
-}
+ // Focus the search field, and bind the necessary events
+ document.getElementById('searchbox').focus();
+ addEvent(document.getElementById('searchform'), 'submit', searchFormSubmitted);
+ addEvent(document.getElementById('searchresults'), 'click', itemClicked);
-function fileSearch_kd(e){
- e = e || window.event;
- if(e.keyCode === 80 && (e.ctrlKey || e.metaKey)){
- showSearchBox();
- e.preventDefault();
- return false;
+ // If we have a pre-populated value, also fire off a search immediately
+ if(val) doSearch();
}
-}
-function fileSearch_kp(e){
- e = e || window.event;
+ /**
+ * ## fileSearch_kd
+ *
+ * Fired as a global keyDown event on the document. Checks
+ * if ctrl/cmd+P has been pressed and shows the search form
+ *
+ * @param {KeyDownEvent} e The key event
+ */
+ function fileSearch_kd(e){
+ e = e || window.event;
+
+ // 80 = p, so listen for ctrl/cmd+P
+ if(e.keyCode === 80 && (e.ctrlKey || e.metaKey)){
+ showSearchBox();
+ e.preventDefault();
+ return false;
+ }
+ }
- if(e.ctrlKey || e.altKey || e.metaKey || e.target.tagName === 'INPUT') return true;
- var theChar = String.fromCharCode(e.which);
+ /**
+ * ## fileSearch_kp
+ *
+ * Fired as a global keyPress event. Checks to see if the key pressed
+ * was a sensible character, and if so then shows the search box and
+ * fires off the first search
+ */
+ function fileSearch_kp(e){
+ e = e || window.event;
+
+ // If there are any modifiers or if we're already entering text into
+ // another input field elsewhere, then do nothing
+ if(e.ctrlKey || e.altKey || e.metaKey || e.target.tagName === 'INPUT') return true;
+
+ // Grab the typed character, test it, and show the box if appropriate
+ var theChar = String.fromCharCode(e.which);
+ if(/[a-zA-Z0-9\.\/\_\-]/.test(theChar)) showSearchBox(theChar);
+ }
- if(/[a-zA-Z0-9\.\/\_\-]/.test(theChar)) showSearchBox(theChar);
-}
+ // Attach the global events to the document
+ addEvent(document, 'keydown', fileSearch_kd);
+ addEvent(document, 'keypress', fileSearch_kp);
-addEvent(document, 'keydown', fileSearch_kd);
-addEvent(document, 'keypress', fileSearch_kp);
+})();
View
207 extras/goToLine/goToLine.js
@@ -1,82 +1,157 @@
-var jumpBoxShown = false;
-var jumpTimeout;
-
-function jumpFormKeyDown(e){
- e = e || window.event;
- if(e.keyCode == 27){
- document.body.removeChild(document.getElementById('jumpto'));
- jumpBoxShown = false;
- }else{
- clearTimeout(jumpTimeout);
- jumpTimeout = setTimeout(doJump, 150);
+// # goToLine.js
+//
+// Contains methods for showing a form allowing instant jumping
+// to any line in a file. Currently only works if line numbers
+// have been enabled in the output
+
+// Wrap everything inside a closure so we don't get any collisions in
+// the global scope
+(function(){
+
+ // Some useful variables
+ var jumpBoxShown = false;
+ var jumpTimeout;
+
+ /**
+ * ## jumpFormKeyDown
+ *
+ * Fired when a key is pressed on the jump form. Performs the
+ * appropriate action depending on which key was pressed
+ *
+ * @param {KeyDownEvent} e The key event
+ */
+ function jumpFormKeyDown(e){
+ e = e || window.event;
+
+ // 27 = esc, so hide the form
+ if(e.keyCode == 27){
+ document.body.removeChild(document.getElementById('jumpto'));
+ jumpBoxShown = false;
+
+ // Otherwise, most likely the user has typed or deleted on the
+ // form, so queue a jump. Don't do it immediately do avoid
+ // disorientating jumps during quick typing
+ }else{
+ clearTimeout(jumpTimeout);
+ jumpTimeout = setTimeout(doJump, 150);
+ }
}
-}
-function addEvent(obj, evt, func, a){
- if((a = obj.addEventListener)){
- a.call(obj, evt, func, false);
- }else{
- obj.attachEvent('on' + evt, func);
+ /**
+ * ## addEvent
+ *
+ * Helper function for binding DOM events
+ *
+ * @param {Element} obj The DOM element to bind to
+ * @param {string} evt The name of the event to bind to
+ * @param {function} func Listener to attach to the event
+ */
+ function addEvent(obj, evt, func, a){
+ if((a = obj.addEventListener)){
+ a.call(obj, evt, func, false);
+ }else{
+ obj.attachEvent('on' + evt, func);
+ }
}
-}
-
-function jumpFormSubmitted(e){
- e = e || window.event;
- e.preventDefault();
-
- doJump();
-
- document.body.removeChild(document.getElementById('jumpto'));
- jumpBoxShown = false;
-
- return false;
-}
-
-function doJump(){
- if(!jumpBoxShown) return;
-
- var line = document.getElementById('jumpbox').value;
-
- var theLine = document.getElementById('line-' + line);
- if(!theLine) return;
+ /**
+ * ## jumpFormSubmitted
+ *
+ * Called when the user hits enter on the jump form
+ * so find the given line and jump to it
+ *
+ * @param {FormSubmitEvent} e The submit event
+ */
+ function jumpFormSubmitted(e){
+ e = e || window.event;
+ e.preventDefault();
- window.location.hash = 'line-' + line;
-}
+ doJump();
-function showJumpBox(){
- if(jumpBoxShown) return;
- jumpBoxShown = true;
+ // Hide the jump box
+ document.body.removeChild(document.getElementById('jumpto'));
+ jumpBoxShown = false;
- var f = document.createElement('div');
+ return false;
+ }
- f.id = "jumpto";
+ /**
+ * ## doJump
+ *
+ * Performs the line jump by manipulating the location hash
+ */
+ function doJump(){
+ if(!jumpBoxShown) return;
- f.innerHTML = [
- '<div class="overlay"></div>',
- '<div class="box">',
- '<form id="jumpform">',
- '<input id="jumpbox" type="text" name="line" placeholder="Go to line..." autocomplete="off" />',
- '</form>',
- '</div>'
- ].join('');
+ // Figure out which line we need to jump to
+ var line = document.getElementById('jumpbox').value;
- addEvent(f, 'keydown', jumpFormKeyDown);
+ // Grab the line anchor if it exists
+ var theLine = document.getElementById('line-' + line);
- document.body.appendChild(f);
+ // If it doesn't exist, there's not much we can do
+ if(!theLine) return;
- document.getElementById('jumpbox').focus();
- addEvent(document.getElementById('jumpform'), 'submit', jumpFormSubmitted);
+ // If it does exist, jump to it
+ window.location.hash = 'line-' + line;
+ }
-}
+ /**
+ * ## showJumpBox
+ *
+ * Constructs and shows the jump box
+ */
+ function showJumpBox(){
+
+ // If the box is already visible, do nothing
+ if(jumpBoxShown) return;
+ jumpBoxShown = true;
+
+ // Create the containing element
+ var f = document.createElement('div');
+ f.id = "jumpto";
+
+ // Construct some basic HTML
+ f.innerHTML = [
+ '<div class="overlay"></div>',
+ '<div class="box">',
+ '<form id="jumpform">',
+ '<input id="jumpbox" type="text" name="line" placeholder="Go to line..." autocomplete="off" />',
+ '</form>',
+ '</div>'
+ ].join('');
+
+ // Add the keydown event
+ addEvent(f, 'keydown', jumpFormKeyDown);
+
+ // Add the container straight onto the body
+ document.body.appendChild(f);
+
+ // Focus the field, and bund the submit event
+ document.getElementById('jumpbox').focus();
+ addEvent(document.getElementById('jumpform'), 'submit', jumpFormSubmitted);
+ }
-function goToLine_kd(e){
- e = e || window.event;
- if(e.keyCode === 71 && (e.ctrlKey || e.metaKey)){
- showJumpBox();
- e.preventDefault();
- return false;
+ /**
+ * ## goToLine_kd
+ *
+ * Fired as a global keyDown event on the document. Checks
+ * if ctrl/cmd+G has been pressed and shows the jump form
+ *
+ * @param {KeyDownEvent} e The key event
+ */
+ function goToLine_kd(e){
+ e = e || window.event;
+
+ // 71 = g, so listen for ctrl/cmd+P
+ if(e.keyCode === 71 && (e.ctrlKey || e.metaKey)){
+ showJumpBox();
+ e.preventDefault();
+ return false;
+ }
}
-}
-addEvent(document, 'keydown', goToLine_kd);
+ // Attach the global event to the document
+ addEvent(document, 'keydown', goToLine_kd);
+
+})();
Please sign in to comment.
Something went wrong with that request. Please try again.