-
Notifications
You must be signed in to change notification settings - Fork 576
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
56 changed files
with
30,964 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
The MIT License (MIT) | ||
|
||
Copyright (c) 2014 Carsten Brandt | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
js-search | ||
========= | ||
|
||
This is a client side search engine for use on static pages. | ||
|
||
It uses a pre-compiled search index to add a fulltext search to static HTML pages such as | ||
[github pages][] or offline API documentation. The index is built by a PHP script using a | ||
similar yet much more simplified and dump approach than the popular search engine [Lucene]. | ||
|
||
To see how it looks like, check out the [demo][]. | ||
|
||
[github pages]: https://pages.github.com/ | ||
[Lucene]: http://lucene.apache.org/ | ||
[demo]: http://cebe.github.io/js-search/#demo | ||
|
||
|
||
Installation | ||
------------ | ||
|
||
PHP 5.4 or higher is required to run the index generator. | ||
|
||
Installation is recommended to be done via [composer][] by adding the following to the `require` section in your `composer.json`: | ||
|
||
```json | ||
"cebe/js-search": "*" | ||
``` | ||
|
||
Run `composer update` afterwards. | ||
|
||
|
||
Usage | ||
----- | ||
|
||
TODO. | ||
|
||
See [example.html](example.html) for an implementation. | ||
|
||
### Generate the index | ||
|
||
Using the command line tool: | ||
``` | ||
vendor/bin/jsindex <path-to-your-html-files> | ||
``` | ||
|
||
This will generate a `jssearch.index.js` file that you have to include in the Html header. |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
#!/usr/bin/env php | ||
<?php | ||
|
||
// Send all errors to stderr | ||
ini_set('display_errors', 'stderr'); | ||
|
||
// setup composer autoloading | ||
$composerAutoload = [ | ||
__DIR__ . '/../vendor/autoload.php', // standalone with "composer install" run | ||
__DIR__ . '/../../../autoload.php', // script is installed as a composer binary | ||
]; | ||
foreach ($composerAutoload as $autoload) { | ||
if (file_exists($autoload)) { | ||
require($autoload); | ||
break; | ||
} | ||
} | ||
|
||
if (!class_exists('cebe\jssearch\Indexer')) { | ||
error('Autoloading does not seem to work. Looks like you should run `composer install` first.'); | ||
} | ||
|
||
// check arguments | ||
$src = []; | ||
foreach($argv as $k => $arg) { | ||
if ($k == 0) { | ||
continue; | ||
} | ||
if ($arg[0] == '-') { | ||
$arg = explode('=', $arg); | ||
switch($arg[0]) { | ||
// TODO allow baseUrl to be set via arg | ||
case '-h': | ||
case '--help': | ||
echo "jssearch index builder\n"; | ||
echo "----------------------\n\n"; | ||
echo "by Carsten Brandt <[email protected]>\n\n"; | ||
usage(); | ||
break; | ||
default: | ||
error("Unknown argument " . $arg[0], "usage"); | ||
} | ||
} else { | ||
$src[] = $arg; | ||
} | ||
} | ||
|
||
if (empty($src)) { | ||
error("You have to give an input directory.", "usage"); | ||
} | ||
|
||
$indexer = new \cebe\jssearch\Indexer(); | ||
|
||
$files = []; | ||
foreach($src as $dir) { | ||
$files = array_merge($files, findFiles($dir)); | ||
|
||
if (empty($files)) { | ||
error("No files where found in $dir."); | ||
} | ||
|
||
$indexer->indexFiles($files, $dir); | ||
} | ||
|
||
$js = $indexer->exportJs(); | ||
file_put_contents('jssearch.index.js', $js); | ||
|
||
|
||
// functions | ||
|
||
/** | ||
* Display usage information | ||
*/ | ||
function usage() { | ||
global $argv; | ||
$cmd = $argv[0]; | ||
echo <<<EOF | ||
Usage: | ||
$cmd [src-directory] | ||
--help shows this usage information. | ||
creates and jssearch.index.js file in the current directory. | ||
EOF; | ||
exit(1); | ||
} | ||
|
||
/** | ||
* Send custom error message to stderr | ||
* @param $message string | ||
* @param $callback mixed called before script exit | ||
* @return void | ||
*/ | ||
function error($message, $callback = null) { | ||
$fe = fopen("php://stderr", "w"); | ||
fwrite($fe, "Error: " . $message . "\n"); | ||
|
||
if (is_callable($callback)) { | ||
call_user_func($callback); | ||
} | ||
|
||
exit(1); | ||
} | ||
|
||
function findFiles($dir, $ext = '.html') | ||
{ | ||
if (!is_dir($dir)) { | ||
error("$dir is not a directory."); | ||
} | ||
$dir = rtrim($dir, DIRECTORY_SEPARATOR); | ||
$list = []; | ||
$handle = opendir($dir); | ||
if ($handle === false) { | ||
error('Unable to open directory: ' . $dir); | ||
} | ||
while (($file = readdir($handle)) !== false) { | ||
if ($file === '.' || $file === '..') { | ||
continue; | ||
} | ||
$path = $dir . DIRECTORY_SEPARATOR . $file; | ||
if (substr($file, -($l = strlen($ext)), $l) === $ext) { | ||
if (is_file($path)) { | ||
$list[] = $path; | ||
} else { | ||
$list = array_merge($list, findFiles($path, $ext)); | ||
} | ||
} | ||
} | ||
closedir($handle); | ||
|
||
return $list; | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"name": "cebe/js-search", | ||
"description": "A client side search engine for use on static pages.", | ||
"license": "MIT", | ||
"authors": [ | ||
{ | ||
"name": "Carsten Brandt", | ||
"email": "[email protected]" | ||
} | ||
], | ||
"require": { | ||
"php": ">=5.4.0" | ||
}, | ||
"autoload": { | ||
"psr-4": { | ||
"cebe\\jssearch\\": "lib/" | ||
} | ||
}, | ||
"bin": [ | ||
"bin/jsindex" | ||
] | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
<!doctype html> | ||
<html> | ||
<head> | ||
<meta charset="UTF-8"> | ||
|
||
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script> | ||
<script src="./jssearch.js"></script> | ||
<script src="./jssearch.index.js"></script> | ||
|
||
<script type="text/javascript"> | ||
|
||
$( document ).ready(function() { | ||
$('#searchbox').on("keyup", function() { | ||
var result = jssearch.search($(this).val()); | ||
|
||
$('#query').html(jssearch.queryWords.join(' ')); | ||
|
||
$('#results').html(''); | ||
var i = 0; | ||
result.forEach(function(item) { | ||
if (i++ > 20) { | ||
return; | ||
} | ||
var div = $('#results'); | ||
div.html(div.html() + '<li>"' + item.file.title + '" ' + item.file.url + ' w:' + item.weight + '</li>'); | ||
}); | ||
}); | ||
}); | ||
</script> | ||
|
||
<title>Example</title> | ||
</head> | ||
<body> | ||
|
||
<h1>Example</h1> | ||
|
||
<label for="searchbox" style="display: inline-block; width: 160px;">Search: </label> | ||
<input id="searchbox" type="text" value=""> | ||
|
||
<br/> | ||
|
||
<label for="query" style="display: inline-block; width: 160px;">Actual query: </label> | ||
<span id="query"></span> | ||
|
||
<ul id="results"> | ||
<li>No results</li> | ||
</ul> | ||
|
||
</body> | ||
</html> |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
// polyfills for IE<9 | ||
(function(fn) { | ||
if (!fn.map) { | ||
fn.map = function(f/*, thisArg */) { | ||
if (this === void 0 || this === null) | ||
throw new TypeError(); | ||
|
||
var t = Object(this); | ||
var len = t.length >>> 0; | ||
if (typeof f !== "function") | ||
throw new TypeError(); | ||
|
||
var res = new Array(len); | ||
var thisArg = arguments.length >= 2 ? arguments[1] : void 0; | ||
for (var i = 0; i < len; i++) { | ||
if (i in t) | ||
res[i] = f.call(thisArg, t[i], i, t); | ||
} | ||
|
||
return res; | ||
} | ||
} | ||
if (!fn.forEach) { | ||
fn.forEach = function (f/*, thisArg */) { | ||
if (this === void 0 || this === null) | ||
throw new TypeError(); | ||
|
||
var t = Object(this); | ||
var len = t.length >>> 0; | ||
if (typeof f !== "function") | ||
throw new TypeError(); | ||
|
||
var thisArg = arguments.length >= 2 ? arguments[1] : void 0; | ||
for (var i = 0; i < len; i++) { | ||
if (i in t) | ||
f.call(thisArg, t[i], i, t); | ||
} | ||
} | ||
} | ||
})(Array.prototype); | ||
|
||
var jssearch = { | ||
|
||
/** | ||
* the actual words finally used to query (set by last search call) | ||
*/ | ||
queryWords: [], | ||
|
||
search: function(query) { | ||
var words = jssearch.tokenizeString(query); | ||
var result = {}; | ||
|
||
jssearch.queryWords = words.map(function(i) { return i.t; }); | ||
|
||
// do not search when no words given | ||
if (!words.length) { | ||
return result; | ||
} | ||
|
||
// result = jssearch.searchForWords(words); | ||
// if ($.isEmptyObject(result)) { | ||
words = jssearch.completeWords(words); | ||
jssearch.queryWords = words.map(function(i) { return i.t; }); | ||
result = jssearch.searchForWords(words); | ||
// } | ||
|
||
var res = []; | ||
for (var i in result) { | ||
res.push(result[i]); | ||
} | ||
res.sort(function(a,b) { return b.weight - a.weight; }); | ||
return res; | ||
}, | ||
|
||
searchForWords: function(words) { | ||
var result = {}; | ||
words.forEach(function(word) { | ||
if (jssearch.index[word.t]) { | ||
jssearch.index[word.t].forEach(function(file) { | ||
if (result[file.f]) { | ||
result[file.f].weight *= file.w * word.w; | ||
} else { | ||
result[file.f] = { | ||
file: jssearch.files[file.f], | ||
weight: file.w * word.w | ||
}; | ||
} | ||
}); | ||
} | ||
}); | ||
return result; | ||
}, | ||
|
||
completeWords: function(words) { | ||
var result = []; | ||
|
||
words.forEach(function(word) { | ||
if (!jssearch.index[word.t] && word.t.length > 2) { | ||
// complete words that are not in the index | ||
for(var w in jssearch.index) { | ||
if (w.substr(0, word.t.length) === word.t) { | ||
result.push({t: w, w: 1}); | ||
} | ||
} | ||
} else { | ||
// keep existing words | ||
result.push(word); | ||
} | ||
}); | ||
return result; | ||
}, | ||
|
||
tokenizeString: function(string) | ||
{ | ||
if (console) { | ||
console.log('Error: tokenizeString should have been overwritten by index JS file.') | ||
} | ||
return [{t: string, w: 1}]; | ||
} | ||
}; |
Oops, something went wrong.