diff --git a/README.md b/README.md
index 0dd08bc..48ddc85 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-[Booklight](https://chrome.google.com/webstore/detail/booklight/lkdhojpobehkcldjmileiancjjpdeakk)
+Booklight
==========
I got fed up wasting my time trying to navigate my way through bunch of bookmarks folder to arrange them. So if you are:
@@ -8,28 +8,82 @@ I got fed up wasting my time trying to navigate my way through bunch of bookmark
then you came to the right place. **Booklight** is a clean Chrome Extension to ease the way of adding a bookmark.
-To Launch press (ctrl/Control + b) and thats it !
+- To launch press (ctrl/Control + b)
+- To enable bookmarks search mode hit `space` after booklight is launched
-[![booklightVideo](https://www.dropbox.com/s/dgu57k0424rnjhq/booklight_video.png?dl=1)](https://www.youtube.com/watch?v=8AB1kE6U-2g)
+[Download from Chrome Store](https://chrome.google.com/webstore/detail/booklight/lkdhojpobehkcldjmileiancjjpdeakk)
+
+## Watch Booklight Video
+[![booklightVideo](https://www.dropbox.com/s/dgu57k0424rnjhq/booklight_video.png?dl=1)](https://www.youtube.com/watch?v=fxqaToLRLNo)
### Features
+
- Filter bookmarks based on manual entry
- Show the path of the current selected folder
- Navigate easily through the folders tree using keyboard
- if the folder is highlighted in blue this means that it contains sub-folders as well. The right arrow (->) keyboard key will go inside that folder. You can go back one step to the back using the left keyboard arrow (<-)
- Bookmark directly when you find your target
+- The ability to switch to urls search **NEW**
+- Launching urls in current or new tab **NEW**
+- Fuzzy search enabled for filtering on both folders and urls **NEW**
+- Clean current URL before bookmarking (sometimes the url is polluted with query strings e.g `?source= ...` for various tracking information). To clean the url from those, hit `ctrl+alt+x` and this will solve this issue.
+
+![booklight](http://g.recordit.co/ZsvnnFqYdu.gif)
+
+## Bookmark Search & launch
+
+Booklight now has the ability to search on your bookmakrs **and it is blazing fast**. I have around 20,000 bookmarks ! and through smart lazy loading and fuzzy search, you can now easily search and launch bookmarks anywhere while browsing.
+To switch to the url search mode just hit `space` and then you will see that you can now search urls by having the `|` symbol in the input box.
+To launch a url in the current window, simply hit `enter` and to open it in a new tab hit `ctr\control + enter`
+![booklight-urls](http://g.recordit.co/aala9MAKo9.gif)
+
+### Booklight Performance
+I currently have over 1000 folders and 20,000 bookmarked urls. Booklight is blazing fast, to achieve this i implement various hacks to minimize DOM manipulations and most importantly lazy-loading of urls. The lazy loading happens in the following function:
+
+```javascript
+lazyloader: function lazyloader(elements){
+
+ var lazyloader = this;
+
+ this.elements = elements;
+ this.showLimit = 15;
+ this.urlsDOM = '';
+
+ this.load = function(empty, hide) {
+
+ var urlsDOM = '';
+ var currentAttachedUrls = this.urlsDOM == '' ? 0 : $('.booklight_list li[data-type="url"]').length;
+ var limit = this.elements.length > this.showLimit ? this.showLimit : this.elements.length;
+ var urlsToAdd = this.elements.slice(currentAttachedUrls, currentAttachedUrls + limit);
+
+ // the idea is build a kind of lazy loading for urls to minimize the building of the DOM elements
+ urlsToAdd.forEach(function(url){
+ urlsDOM += '
' +
+ '' +
+ url.title + '
';
+ });
+
+ lazyloader.urlsDOM += urlsDOM;
+
+ booklight.UI.showSection(urlsDOM, empty, hide);
+ booklight.UI.updateCounter();
+ }
+}
+```
+You can tweak the number of elements you want to show on every iteration and it works for both searching and filtering.
### Things i would like to do
+
- Add mouse interactions
- Add better logic to the star icon (at the moment it only shows when the page is successfully bookmarked) but it will not update if remove the bookmark ... etc.
-- Add fuzzy search for filtering from input box
+- ~~Add fuzzy search for filtering from input box~~
- Smart folder suggestions
- ~~Remember last location when going back to main screen or removing filters~~ **done**
-## Life before Booklight
-![before-booklight](http://g.recordit.co/uqYqp8o08e.gif)
-
-## Life AFTER Booklight
-![before-booklight](http://g.recordit.co/mprXGGOr1k.gif)
[Download from Chrome Store](https://chrome.google.com/webstore/detail/booklight/lkdhojpobehkcldjmileiancjjpdeakk)
+
+### Thoughts
+
+ - [Google Chrome’s awful new bookmark manager (and how to switch it off)](http://blog.garethjmsaunders.co.uk/2015/04/19/google-chromes-awful-new-bookmark-manager-and-how-to-switch-it-off/)
+ - [Chrome users roast Google on spit of hate over revamped bookmarks manager](http://www.computerworld.com/article/2913426/web-browsers/chrome-users-roast-google-on-spit-of-hate-over-revamped-bookmarks-manager.html)
diff --git a/background.js b/background.js
index 08b8daf..58ba738 100644
--- a/background.js
+++ b/background.js
@@ -1,46 +1,63 @@
-var foldersList = [], urls = [];
+var booklight = function booklight() {
-chrome.bookmarks.getTree(function(bookmarksTree) {
+ var booklight = this;
- foldersList = filterRecursively(bookmarksTree, "children", function(node) {
- if (node.url) urls.push(node);
- return !node.url && node.id > 0;
- }).sort(function(a, b) {
- // The sort functions make sure that we will have the last used folders on top
- return b.dateGroupModified - a.dateGroupModified;
- });
+ this.foldersList = [];
+ this.urls = [];
+
+ this.getBookmarks = function() {
- chrome.storage.local.set({"booklight": foldersList }, function(bookmarks) { console.log("Setting the folders list into the local storage !!") });
- chrome.storage.local.set({"urls": urls }, function(bookmarks) { console.log("Setting the urls list into the local storage !!") });
+ chrome.bookmarks.getTree(function(bookmarksTree) {
- });
+ booklight.foldersList = filterRecursively(bookmarksTree, "children", function(node) {
+ if (node.url) booklight.urls.push(node);
+ return !node.url && node.id > 0;
+ }).sort(function(a, b) {
+ // The sort functions make sure that we will have the last used folders on top
+ return b.dateGroupModified - a.dateGroupModified;
+ });
-// Recursively filter the passed TreeNodes
-function filterRecursively(nodeArray, childrenProperty, filterFn, results) {
+ chrome.storage.local.set({"booklightFolders": booklight.foldersList }, function(bookmarks) { console.log("Setting the folders list into the local storage !!") });
+ chrome.storage.local.set({"booklightUrls": booklight.urls }, function(bookmarks) { console.log("Setting the urls list into the local storage !!") });
+
+ });
- results = results || [];
+ // Recursively filter the passed TreeNodes
+ function filterRecursively(nodeArray, childrenProperty, filterFn, results) {
+
+ results = results || [];
+
+ nodeArray.forEach( function( node ) {
+ if (filterFn(node)) results.push({title: node.title, id: node.id, dateGroupModified: node.dateGroupModified, folder: isLeaf(node), parentId: node.parentId});
+ if (node.children) filterRecursively(node.children, childrenProperty, filterFn, results);
+ });
+ return results;
+ };
+
+ // Check if the current bookmark is a leaf (does not contain more folders)
+ function isLeaf(node) {
+ var leafyNodes = [];
+ node.children.forEach(function(child){
+ if (!child.hasOwnProperty('children')) leafyNodes.push(1);
+ });
+ var isLeaf = leafyNodes.length == node.children.length ? true : false;
+ return isLeaf;
+ }
+ }
- nodeArray.forEach( function( node ) {
- if (filterFn(node)) results.push({title: node.title, id: node.id, dateGroupModified: node.dateGroupModified, folder: isLeaf(node), parent: node.parentId});
- if (node.children) filterRecursively(node.children, childrenProperty, filterFn, results);
- });
- return results;
-};
+ this.attachListeners = function() {
-function isLeaf(node) {
- var leafyNodes = [];
- node.children.forEach(function(child){
- if (!child.hasOwnProperty('children')) leafyNodes.push(1);
- });
- var isLeaf = leafyNodes.length == node.children.length ? true : false;
- return isLeaf;
+ chrome.runtime.onMessage.addListener(function(request, sender, sendrequest) {
+ if (request.message == "booklight") {
+ console.log("adding: " + request.url + " title: " + request.title + " to folder id: " + request.folder);
+ chrome.bookmarks.create({ 'parentId': request.folder, 'title': request.title, 'url': request.url });
+ sendrequest({message: "success"});
+ }
+ });
+ }
}
-chrome.runtime.onMessage.addListener(
- function(request, sender, sendrequest) {
- if (request.message == "booklight") {
- console.log("adding: " + request.url + " title: " + request.title + " to folder id: " + request.folder);
- chrome.bookmarks.create({ 'parentId': request.folder, 'title': request.title, 'url': request.url });
- sendrequest({message: "success"});
- }
- });
+var booklight = new booklight();
+
+booklight.attachListeners();
+booklight.getBookmarks();
\ No newline at end of file
diff --git a/booklight.js b/booklight.js
index eaf9160..a55a1d2 100644
--- a/booklight.js
+++ b/booklight.js
@@ -1,19 +1,35 @@
var booklight = function booklight() {
var booklight = this;
+
// The array (stack) that will hold the navigation of the main elements and their subfolders
- this.elementStack = [];
+ this.elementStack = [];
+ this.urls = [];
+ this.context = 'folder';
+ this.foldersDOM = '';
+
+ this.urlsLazyloader;
+ this.searchLazyLoader;
+ this.fuzzyFolderSearch;
+ this.fuzzyURLsSearch;
this.attachKeyboardEvents = function attachKeyboardEvents() {
- key('control+b, ctrl+b', function(){ booklight.UI.show(); });
- key('esc, escape', function(){ booklight.UI.close(); });
- key('enter', 'input', function(){ booklight.manager.addBookmark(); });
- key('up', 'input', function(){ booklight.navigator.moveInList("UP") });
- key('down', 'input', function(){ booklight.navigator.moveInList("DOWN") });
- key('right', 'input', function(){ booklight.navigator.moveInList("RIGHT") });
- key('left', 'input', function(){ booklight.navigator.moveInList("LEFT") });
- key('control+x, ctrl+x', function(){ booklight.util.cleanURL(); });
+ var globalListener = new window.keypress.Listener($('body')[0],{is_solitary: true});
+ var booklightListener = new window.keypress.Listener($('#booklightManager')[0]);
+ var executionListener = new window.keypress.Listener($('.booklight>input')[0], {is_solitary : true});
+
+ globalListener.simple_combo("ctrl b", function() { booklight.UI.show() });
+ globalListener.simple_combo("esc", function() { booklight.UI.close() });
+ globalListener.simple_combo('ctrl alt x', function(){ booklight.util.cleanURL() });
+
+ executionListener.simple_combo('enter', function(){ booklight.manager.addBookmark() });
+ executionListener.simple_combo('ctrl enter', function(){ booklight.manager.openURL('_blank') });
+
+ booklightListener.simple_combo('up', function(){ booklight.navigator.moveInList("UP") });
+ booklightListener.simple_combo('down', function(){ booklight.navigator.moveInList("DOWN") });
+ booklightListener.simple_combo('right', function(){ booklight.navigator.moveInList("RIGHT") });
+ booklightListener.simple_combo('left', function(){ booklight.navigator.moveInList("LEFT") });
}
@@ -30,7 +46,7 @@ var booklight = function booklight() {
build : function build() {
// Append the search lightbox to the body DOM element
- $('body').append('
'+
+ $('body').append('
'+
'' +
''+
'' +
@@ -44,47 +60,134 @@ var booklight = function booklight() {
booklight.resultBar = $('.booklight_resultsbar');
booklight.statusBar = $('.booklight_statusbar');
- // Get the bookmarks from the local storage
- chrome.storage.local.get("booklight", function(bookmarks) {
- booklight.resultBar.text(bookmarks.booklight.length + " folders found");
- bookmarks.booklight.forEach(function(bookmark){
- var elem = $('
', { text: bookmark.title, id: bookmark.id, 'data-dateGroupModified': bookmark.dateGroupModified, 'data-parent': bookmark.parent});
- if (!bookmark.folder) elem.addClass('isFolder');
- booklight.bookmarksList.append(elem);
- });
- });
+ booklight.UI.addFolders();
+ booklight.UI.getURLs();
+ booklight.attachKeyboardEvents();
// Attach the filtering functions for the inputbox
booklight.searchBar.on('input', function() {
var filter = $(this).val();
- // Hide all the folders list and only show those matching the input query
- $('.booklight_list li').hide();
-
- // Check if you are inside a folder, filter only on that folders children
- if (booklight.elementStack.length) {
- var nestedFolderID = booklight.elementStack[booklight.elementStack.length - 1].id ;
- booklight.bookmarksList.find('li[data-parent="' + nestedFolderID + '"]:contains(' + filter + ')').show();
- } else booklight.bookmarksList.find('li:contains(' + filter + ')').show();
+ // Check if the user is switching into files or folders context
+ if (!filter) booklight.context = 'folder';
+ // Check if the value entered is space which is the trigger for urls search
+ else if (filter == ' ') { $(this).val("|"); booklight.context = 'url'; booklight.urlsLazyloader.load(false, true); }
+
+ if (filter) {
+
+ // hide all the current elements
+ $('.booklight_list li').hide();
+
+ if (booklight.searchBar.val().indexOf('|') !== -1 ) {
+ var filter = filter.replace('|','');
+ if (filter.length > 1) {
+ // Now we will be filtering on urls only. Create a new lazyloader instance for the urls
+ booklight.searchLazyLoader = new booklight.UI.lazyloader(booklight.fuzzyURLsSearch.search(filter));
+ booklight.searchLazyLoader.load(true);
+ } else booklight.UI.showSection(booklight.urlsLazyloader.urlsDOM,false,true);
+
+ } else {
+ if (context = "folder" && booklight.elementStack.length) {
+ var nestedFolderID = booklight.elementStack[booklight.elementStack.length - 1].id ;
+ booklight.fuzzyFolderSearch.search(filter).forEach(function(folder){ if (folder.parentId == nestedFolderID) booklight.bookmarksList.find('li#'+ folder.id).show() });
+ } else {
+ booklight.fuzzyFolderSearch.search(filter).forEach(function(folder){ booklight.bookmarksList.find('li#'+ folder.id).show() });
+ }
+ }
+ } else {
+ if (booklight.elementStack.length) {
+ booklight.bookmarksList.find('li[data-parent="'+ booklight.elementStack[booklight.elementStack.length - 1].id + '"]').show();
+ } else booklight.context == "folder" ? booklight.UI.showSection(null, true, false, "url") : booklight.UI.showSection(booklight.urlsLazyloader.urlsDOM,false,true);
+ }
booklight.UI.updateCounter();
booklight.UI.higlightFirstElement();
});
- }, show : function show() {
+ },addFolders: function addFolders() {
+
+ // Get the bookmarks folders from the local storage
+ chrome.storage.local.get("booklightFolders", function(bookmarks) {
+ booklight.fuzzyFolderSearch = new Fuse(bookmarks.booklightFolders, { keys: ['title'], threshold: 0.3});
+ bookmarks.booklightFolders.forEach(function(bookmark){
+ booklight.foldersDOM += '
' + bookmark.title + '
';
+ });
+ booklight.bookmarksList.append(booklight.foldersDOM);
+ });
+
+ },getURLs: function getURLs() {
+
+ // Get the bookmarks urls from the local storage
+ chrome.storage.local.get("booklightUrls", function(urls) {
+ booklight.fuzzyURLsSearch = new Fuse(urls.booklightUrls, { keys: ['title'], threshold: 0.4});
+ booklight.urls = urls.booklightUrls;
+ // Create a new lazyloader instance for the urls
+ booklight.urlsLazyloader = new booklight.UI.lazyloader(urls.booklightUrls);
+ });
+
+ },lazyloader: function lazyloader(elements){
+
+ var lazyloader = this;
+
+ this.elements = elements;
+ this.showLimit = 15;
+ this.urlsDOM = '';
+
+ this.load = function(empty, hide) {
+
+ var urlsDOM = '';
+ var currentAttachedUrls = this.urlsDOM == '' ? 0 : $('.booklight_list li[data-type="url"]:visible').length;
+ var limit = this.elements.length > this.showLimit ? this.showLimit : this.elements.length;
+ var urlsToAdd = this.elements.slice(currentAttachedUrls, currentAttachedUrls + limit);
+
+ if (urlsToAdd.length) {
+ // the idea is build a kind of lazy loading for urls to minimize the building of the DOM elements
+ urlsToAdd.forEach(function(url){
+ urlsDOM += '
' +
+ '' +
+ url.title + '
';
+ });
+ lazyloader.urlsDOM += urlsDOM;
+
+ booklight.UI.showSection(urlsDOM, empty, hide);
+ booklight.UI.updateCounter();
+ }
+
+ }
+
+ },show : function show() {
+
+ booklight.context = "folder";
// Show the booklight main UI window and all of its elements if they were hidden from a previous filter operation
booklight.booklightBox.show();
- $('.booklight_list li').show();
+ booklight.UI.showContext();
// Empty the searchbar input box and make it focused for direct query entry
booklight.searchBar.val('').focus();
+ booklight.searchBar.attr('placeholder', 'Filter...');
// Highlight the first element of the results
booklight.UI.higlightFirstElement();
+ booklight.UI.updateCounter();
},close : function close() {
booklight.booklightBox.hide();
+ booklight.elementStack = [];
+ booklight.UI.showSection(null, true, false, "url");
+
+ },showContext: function showHideContext() {
+
+ $('.booklight_list li[data-type="' + booklight.context + '"]').show();
+
+ },showSection: function showSection(section, empty, hide, context){
+
+ if (empty) $('.booklight_list li[data-type="' + context + '"]').remove();
+ if (hide) $('.booklight_list li').hide();
+
+ section ? booklight.bookmarksList.append(section) : $('.booklight_list li[data-type="' + booklight.context + '"]').show();
},focusItem : function(index, subFolder, isMouse) {
@@ -118,7 +221,7 @@ var booklight = function booklight() {
},higlightFirstElement: function(text) {
- booklight.UI.focusItem($('.booklight_list li:visible').first().index(), text);
+ booklight.UI.focusItem($('.booklight_list li[data-type="' + booklight.context + '"]:visible').first().index(), text);
},updateCounter: function() {
@@ -129,15 +232,15 @@ var booklight = function booklight() {
// Check if the root parent for the current node is not the bookmarks bar or other bookmarks
var parentsList = getStatus(element, []);
+ if (element.attr('data-type') == "url") parentsList.shift();
+
booklight.statusBar.text(parentsList.reverse().join(' > '));
// This function will recursively fetch the parent hierarchy for a current folder
function getStatus (element, parentsArray) {
var parentID = element.attr('data-parent');
-
if (!parentID) return parentsArray;
- if (parentID == "1" && parentID == "2") return parentsArray;
parentsArray.push(element.text());
return getStatus($('#' + parentID), parentsArray);
@@ -153,7 +256,10 @@ var booklight = function booklight() {
booklight.UI.updateCounter();
booklight.searchBar.val('');
- id && booklight.elementStack.length ? booklight.UI.focusItem(id) : booklight.UI.higlightFirstElement(isFolder);
+
+ isFolder ? booklight.UI.higlightFirstElement(isFolder) : booklight.UI.focusItem(id);
+ //id && booklight.elementStack.length ? booklight.UI.focusItem(id) : booklight.UI.higlightFirstElement(isFolder);
+
}, isRoot: function(){
if (booklight.searchBar.attr('placeholder').indexOf('>') === -1) return true;
else return false;
@@ -177,6 +283,11 @@ var booklight = function booklight() {
switch (direction) {
case ('DOWN') : {
index !== lastElementIndex ? booklight.UI.focusItem($('.booklight_list li:visible.activeFolder').nextAll('li:visible').first().index()) : booklight.UI.focusItem(firstElementIndex);
+ if (booklight.context == 'url' && index >= lastElementIndex - 3) {
+ // Now we have checked that we are in a url context and the urls have been lazyloaded, we need to fetch more
+ // We need now to check if the lazy loader is for search results or for normal urls fetch
+ booklight.searchBar.val().length > 1 ? booklight.searchLazyLoader.load(false, false) : booklight.urlsLazyloader.load(false, false) ;
+ }
} break;
case ('UP') : {
index !== firstElementIndex ? booklight.UI.focusItem($('.booklight_list li:visible.activeFolder').prevAll('li:visible').first().index()) : booklight.UI.focusItem(lastElementIndex);
@@ -228,15 +339,24 @@ var booklight = function booklight() {
addBookmark: function(url, title, folder) {
// Extract the parameters needed to add a bookmark in the Chrome API
- var url = window.location.href;
- var title = document.title;
- var folder = $('.booklight_list li.activeFolder').attr('id');
+ var element = $('.booklight_list li.activeFolder');
+ var url = window.location.href;
+ var title = document.title;
+ var folder = element.attr('id');
+ var type = element.attr('data-type');
+
+ if (type !== "folder") {
+ booklight.manager.openURL("_self")
+ } else {
+ chrome.runtime.sendMessage({message: "booklight", url: url, folder: folder, title: title}, function(response) {
+ if (response.message == "success"){
+ $('span.isBooklit').show();
+ }
+ });
+ }
- chrome.runtime.sendMessage({message: "booklight", url: url, folder: folder, title: title}, function(response) {
- if (response.message == "success"){
- $('span.isBooklit').show();
- }
- });
+ },openURL: function openURL(target) {
+ window.open($('.booklight_list li.activeFolder').attr('data-url'), target);
}
}
@@ -254,5 +374,4 @@ var booklight = function booklight() {
var booklight = new booklight();
-booklight.attachKeyboardEvents();
-booklight.UI.build();
+booklight.UI.build();
\ No newline at end of file
diff --git a/css/booklight.css b/css/booklight.css
index 403166f..6a8b0a7 100644
--- a/css/booklight.css
+++ b/css/booklight.css
@@ -12,7 +12,8 @@
.booklight input[type="text"]::-webkit-input-placeholder {color: #BEB9B9;}
.booklight input[type="text"]:focus{border:none;box-shadow: none}
.booklight_list {position: relative;overflow-y: scroll;height: 215px;list-style: none;width: 96%;padding: 0 5px 0 0;color: #777; margin: 0 auto;}
-.booklight_list>li{margin: 0; padding: 0 5px; cursor:pointer;line-height: 1.5em;font-size: 13px; text-align: left}
+.booklight_list>li{margin: 0; padding: 0 5px; cursor:pointer;line-height: 1.5em;font-size: 13px; text-align: left; list-style: none; width:100%;}
+.booklight_list > li > img {vertical-align: middle;margin: 0 10px 0 0;}
.booklight_list>li.isFolder {color:#3498db;}
.booklight_resultsbar {position: absolute;font-size: 9px;top: 55px;right: 10px;color: #ddd;}
.booklight_list>li.activeFolder {background: #2ecc71; color: #fff;}
diff --git a/css/fontello.css b/css/fontello.css
index ff3d090..10e6ed6 100644
--- a/css/fontello.css
+++ b/css/fontello.css
@@ -17,40 +17,4 @@
src: url('../font/fontello.svg?76547315#fontello') format('svg');
}
}
-*/
-
- [class^="icon-"]:before, [class*=" icon-"]:before {
- font-family: "fontello-booklight";
- font-style: normal;
- font-weight: normal;
- speak: none;
-
- display: inline-block;
- text-decoration: inherit;
- width: 1em;
- margin-right: .2em;
- text-align: center;
- /* opacity: .8; */
-
- /* For safety - reset parent styles, that can break glyph codes*/
- font-variant: normal;
- text-transform: none;
-
- /* fix buttons height, for twitter bootstrap */
- line-height: 1em;
-
- /* Animation center compensation - margins should be symmetric */
- /* remove if not needed */
- margin-left: .2em;
-
- /* you can be more comfortable with increased icons size */
- /* font-size: 120%; */
-
- /* Uncomment for 3D effect */
- /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
-}
-
-.icon-folder-empty:before { content: '\e800'; } /* '' */
-.icon-angle-double-right:before { content: '\e801'; } /* '' */
-.icon-lightbulb:before { content: '\e802'; } /* '' */
-.icon-star:before { content: '\e803'; } /* '' */
\ No newline at end of file
+*/
\ No newline at end of file
diff --git a/helper.js b/helper.js
index b847556..6254540 100644
--- a/helper.js
+++ b/helper.js
@@ -1,16 +1,10 @@
// Additional Helper classes and overriders
// replace a string at a certain range with another string
+
function replaceRange(s, start, end, substitute) {
return s.substring(0, start) + substitute + s.substring(end);
}
-// Overriding the filter function to make it work on the input boxes
-key.filter = function(event){
- var tagName = (event.target || event.srcElement).tagName;
- key.setScope(/^(INPUT)$/.test(tagName) ? 'input' : 'other');
- return true;
-}
-
// Overriding the default jQuery contains to make it case insensitive
$.expr[":"].contains = $.expr.createPseudo(function(arg) {
return function( elem ) {
diff --git a/lib/fuse.js b/lib/fuse.js
new file mode 100644
index 0000000..423a3e5
--- /dev/null
+++ b/lib/fuse.js
@@ -0,0 +1,342 @@
+/**
+ * Fuse - Lightweight fuzzy-search
+ *
+ * Copyright (c) 2012 Kirollos Risk .
+ * All Rights Reserved. Apache Software License 2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function () {
+ /**
+ * Adapted from "Diff, Match and Patch", by Google
+ *
+ * http://code.google.com/p/google-diff-match-patch/
+ *
+ * Modified by: Kirollos Risk
+ * -----------------------------------------------
+ * Details: the algorithm and structure was modified to allow the creation of
+ * instances with a method inside which does the actual
+ * bitap search. The (the string that is searched for) is only defined
+ * once per instance and thus it eliminates redundant re-creation when searching
+ * over a list of strings.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ */
+ function Searcher(pattern, options) {
+ options = options || {};
+
+ // Aproximately where in the text is the pattern expected to be found?
+ var MATCH_LOCATION = options.location || 0,
+
+ // Determines how close the match must be to the fuzzy location (specified above).
+ // An exact letter match which is 'distance' characters away from the fuzzy location
+ // would score as a complete mismatch. A distance of '0' requires the match be at
+ // the exact location specified, a threshold of '1000' would require a perfect match
+ // to be within 800 characters of the fuzzy location to be found using a 0.8 threshold.
+ MATCH_DISTANCE = options.distance || 100,
+
+ // At what point does the match algorithm give up. A threshold of '0.0' requires a perfect match
+ // (of both letters and location), a threshold of '1.0' would match anything.
+ MATCH_THRESHOLD = options.threshold || 0.6,
+
+
+ pattern = options.caseSensitive ? pattern : pattern.toLowerCase(),
+ patternLen = pattern.length;
+
+ if (patternLen > 32) {
+ throw new Error('Pattern length is too long');
+ }
+
+ var matchmask = 1 << (patternLen - 1);
+
+ /**
+ * Initialise the alphabet for the Bitap algorithm.
+ * @return {Object} Hash of character locations.
+ * @private
+ */
+ var pattern_alphabet = (function () {
+ var mask = {},
+ i = 0;
+
+ for (i = 0; i < patternLen; i++) {
+ mask[pattern.charAt(i)] = 0;
+ }
+
+ for (i = 0; i < patternLen; i++) {
+ mask[pattern.charAt(i)] |= 1 << (pattern.length - i - 1);
+ }
+
+ return mask;
+ })();
+
+ /**
+ * Compute and return the score for a match with errors and = start; j--) {
+ // The alphabet is a sparse hash, so the following line generates warnings.
+ charMatch = pattern_alphabet[text.charAt(j - 1)];
+ if (i === 0) {
+ // First pass: exact match.
+ rd[j] = ((rd[j + 1] << 1) | 1) & charMatch;
+ } else {
+ // Subsequent passes: fuzzy match.
+ rd[j] = ((rd[j + 1] << 1) | 1) & charMatch | (((lastRd[j + 1] | lastRd[j]) << 1) | 1) | lastRd[j + 1];
+ }
+ if (rd[j] & matchmask) {
+ score = match_bitapScore(i, j - 1);
+ // This match will almost certainly be better than any existing match.
+ // But check anyway.
+ if (score <= scoreThreshold) {
+ // Told you so.
+ scoreThreshold = score;
+ bestLoc = j - 1;
+ locations.push(bestLoc);
+
+ if (bestLoc > MATCH_LOCATION) {
+ // When passing loc, don't exceed our current distance from loc.
+ start = Math.max(1, 2 * MATCH_LOCATION - bestLoc);
+ } else {
+ // Already passed loc, downhill from here on in.
+ break;
+ }
+ }
+ }
+ }
+ // No hope for a (better) match at greater error levels.
+ if (match_bitapScore(i + 1, MATCH_LOCATION) > scoreThreshold) {
+ break;
+ }
+ lastRd = rd;
+ }
+
+ return {
+ isMatch: bestLoc >= 0,
+ score: score
+ };
+
+ }
+ }
+
+ /**
+ * @param {Array} list
+ * @param {Object} options
+ * @public
+ */
+ function Fuse(list, options) {
+ options = options || {};
+ var keys = options.keys;
+
+ /**
+ * Searches for all the items whose keys (fuzzy) match the pattern.
+ * @param {String} pattern The pattern string to fuzzy search on.
+ * @return {Array} A list of all serch matches.
+ * @public
+ */
+ this.search = function (pattern) {
+ //console.time('total');
+
+ var searcher = new Searcher(pattern, options),
+ i, j, item, text, dataLen = list.length,
+ bitapResult, rawResults = [], resultMap = {},
+ rawResultsLen, existingResult, results = [],
+ compute = null;
+
+ //console.time('search');
+
+ /**
+ * Calls for bitap analysis. Builds the raw result list.
+ * @param {String} text The pattern string to fuzzy search on.
+ * @param {String|Int} entity If the is an Array, then entity will be an index,
+ * otherwise it's the item object.
+ * @param {Int} index
+ * @return {Object|Int}
+ * @private
+ */
+ function analyzeText(text, entity, index) {
+ // Check if the text can be searched
+ if (text !== undefined && text !== null && typeof text === 'string') {
+
+ // Get the result
+ bitapResult = searcher.search(text);
+
+ // If a match is found, add the item to , including its score
+ if (bitapResult.isMatch) {
+
+ //console.log(bitapResult.score);
+
+ // Check if the item already exists in our results
+ existingResult = resultMap[index];
+ if (existingResult) {
+ // Use the lowest score
+ existingResult.score = Math.min(existingResult.score, bitapResult.score);
+ } else {
+ // Add it to the raw result list
+ resultMap[index] = {
+ item: entity,
+ score: bitapResult.score
+ };
+ rawResults.push(resultMap[index]);
+ }
+ }
+ }
+ }
+
+ // Check the first item in the list, if it's a string, then we assume
+ // that every item in the list is also a string, and thus it's a flattened array.
+ if (typeof list[0] === 'string') {
+ // Iterate over every item
+ for (i = 0; i < dataLen; i++) {
+ analyzeText(list[i], i, i);
+ }
+ } else {
+ // Otherwise, the first item is an Object (hopefully), and thus the searching
+ // is done on the values of the keys of each item.
+
+ // Iterate over every item
+ for (i = 0; i < dataLen; i++) {
+ item = list[i];
+ // Iterate over every key
+ for (j = 0; j < keys.length; j++) {
+ analyzeText(item[keys[j]], item, i);
+ }
+ }
+ }
+
+ //console.timeEnd('search');
+
+ // Sort the results, form lowest to highest score
+ //console.time('sort');
+ rawResults.sort(function (a, b) {
+ return a.score - b.score;
+ });
+ //console.timeEnd('sort');
+
+ // From the results, push into a new array only the item identifier (if specified)
+ // of the entire item. This is because we don't want to return the ,
+ // since it contains other metadata;
+ //console.time('build');
+ rawResultsLen = rawResults.length;
+ for (i = 0; i < rawResultsLen; i++) {
+ results.push(options.id ? rawResults[i].item[options.id] : rawResults[i].item);
+ }
+
+ //console.timeEnd('build');
+
+ //console.timeEnd('total');
+
+ return results;
+ }
+ }
+
+ //Export to Common JS Loader
+ if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
+ if (typeof module.setExports === 'function') {
+ module.setExports(Fuse);
+ } else {
+ module.exports = Fuse;
+ }
+ } else {
+ window.Fuse = Fuse;
+ }
+
+})();
\ No newline at end of file
diff --git a/lib/hideSeek.js b/lib/hideSeek.js
deleted file mode 100644
index 2b01a89..0000000
--- a/lib/hideSeek.js
+++ /dev/null
@@ -1,202 +0,0 @@
-/**
- * HideSeek jQuery plugin
- *
- * @copyright Copyright 2013, Dimitris Krestos
- * @license Apache License, Version 2.0 (http://www.opensource.org/licenses/apache2.0.php)
- * @link http://vdw.staytuned.gr
- * @version v0.5.5
- *
- * Dependencies are include in minified versions at the bottom:
- * 1. Highlight v4 by Johann Burkard
- *
- */
-
- /* Sample html structure
-
-
-