absolutely massive refactor
This commit is contained in:
+378
-292
@@ -2,61 +2,63 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
const CONFIG = {
|
const CONFIG = {
|
||||||
categories: [
|
|
||||||
{ name: "Work", commands: [
|
// the key, name, redirect url and search path for your commands
|
||||||
{ key: 'g', name: 'GitHub', url: 'https://github.com', search: '/search?q={}' },
|
commands: [
|
||||||
{ key: 'm', name: 'Inbox', url: 'https://inbox.google.com', search: '/search/{}' },
|
['7', '7digital', 'https://us.7digital.com', '/search?q={}'],
|
||||||
{ key: 'k', name: 'Keep', url: 'https://keep.google.com', search: '/#search/text={}' },
|
['g', 'GitHub', 'https://github.com', '/search?q={}'],
|
||||||
] },
|
['h', 'Hypem', 'http://hypem.com/popular', '/search/{}'],
|
||||||
{ name: "Lurk", commands: [
|
['m', 'Inbox', 'https://inbox.google.com', '/search/{}'],
|
||||||
{ key: 'i', name: 'Instagram', url: 'https://www.instagram.com', search: false },
|
['i', 'Instagram', 'https://www.instagram.com', false],
|
||||||
{ key: 'r', name: 'Reddit', url: 'https://www.reddit.com', search: '/search?q={}' },
|
['k', 'Keep', 'https://keep.google.com', '/#search/text={}'],
|
||||||
{ key: 't', name: 'Twitter', url: 'https://twitter.com', search: '/search?q={}' },
|
['l', 'Line Radio', 'https://linerad.io', '/#{}'],
|
||||||
] },
|
['n', 'Netflix', 'https://www.netflix.com/browse', '/search?q={}'],
|
||||||
{ name: "Listen", commands: [
|
['r', 'Reddit', 'https://www.reddit.com', '/search?q={}'],
|
||||||
{ key: 'h', name: 'Hypem', url: 'http://hypem.com/popular', search: '/search/{}' },
|
['s', 'SoundCloud', 'https://soundcloud.com/discover', '/search?q={}'],
|
||||||
{ key: 'l', name: 'Line Radio', url: 'https://linerad.io', search: '/#{}' },
|
['T', 'TPB', 'https://thepiratebay.org', '/search/{}'],
|
||||||
{ key: 's', name: 'SoundCloud', url: 'https://soundcloud.com/discover', search: '/search?q={}' },
|
['w', 'Twitch', 'https://www.twitch.tv/directory/following', false],
|
||||||
] },
|
['t', 'Twitter', 'https://twitter.com', '/search?q={}'],
|
||||||
{ name: "Watch", commands: [
|
['Y', 'YIFY', 'https://yts.ag/browse-movies/0/1080p/all/7/latest', '/browse-movies/{}/1080p/all/0/rating'],
|
||||||
{ key: 'n', name: 'Netflix', url: 'https://www.netflix.com/browse', search: '/search?q={}' },
|
['y', 'YouTube', 'https://youtube.com/feed/subscriptions', '/results?search_query={}'],
|
||||||
{ key: 'w', name: 'Twitch', url: 'https://www.twitch.tv/directory/following', search: false },
|
|
||||||
{ key: 'y', name: 'YouTube', url: 'https://youtube.com/feed/subscriptions', search: '/results?search_query={}' },
|
|
||||||
] },
|
|
||||||
{ name: "Download", commands: [
|
|
||||||
{ key: 'T', name: 'TPB', url: 'https://thepiratebay.org', search: '/search/{}' },
|
|
||||||
{ key: 'Y', name: 'YIFY', url: 'https://yts.ag/browse-movies/0/1080p/all/7/latest', search: '/browse-movies/{}/1080p/all/0/rating' },
|
|
||||||
{ key: '7', name: '7digital', url: 'https://us.7digital.com', search: '/search?q={}' },
|
|
||||||
] },
|
|
||||||
],
|
],
|
||||||
|
|
||||||
// if none of the keys are matched, this is used for searching.
|
// the categories & commands that show up on the help overlay
|
||||||
|
// use the exact name of each command
|
||||||
|
categories: {
|
||||||
|
'Work': ['GitHub', 'Inbox', 'Keep'],
|
||||||
|
'Lurk': ['Instagram', 'Reddit', 'Twitter'],
|
||||||
|
'Listen': ['Hypem', 'Line Radio', 'SoundCloud'],
|
||||||
|
'Watch': ['Netflix', 'Twitch', 'YouTube'],
|
||||||
|
'Download': ['7digital', 'TPB', 'YIFY'],
|
||||||
|
},
|
||||||
|
|
||||||
|
// if none of the specified keys are matched, this is used for searching
|
||||||
defaultSearch: 'https://encrypted.google.com/search?q={}',
|
defaultSearch: 'https://encrypted.google.com/search?q={}',
|
||||||
|
|
||||||
// the delimiter between the key and your search query.
|
// the delimiter between the key and your search query
|
||||||
// e.g. to search GitHub for potatoes you'd type "g:potatoes".
|
// e.g. to search GitHub for potatoes you'd type "g:potatoes"
|
||||||
searchDelimiter: ':',
|
searchDelimiter: ':',
|
||||||
|
|
||||||
// the delimiter between the key and a path.
|
// the delimiter between the key and a path
|
||||||
// e.g. type "r/r/unixporn" to go to "reddit.com/r/unixporn".
|
// e.g. type "r/r/unixporn" to go to "reddit.com/r/unixporn"
|
||||||
pathDelimiter: '/',
|
pathDelimiter: '/',
|
||||||
|
|
||||||
// instantly redirect when a key is matched.
|
// instantly redirect when a key is matched
|
||||||
// put a space before any other queries to prevent unwanted redirects.
|
// put a space before any other queries to prevent unwanted redirects
|
||||||
instantRedirect: false,
|
instantRedirect: false,
|
||||||
|
|
||||||
// give suggestions as you type.
|
// give suggestions as you type
|
||||||
suggestions: true,
|
suggestions: true,
|
||||||
|
|
||||||
// max amount of suggestions that will ever be displayed.
|
// max amount of suggestions that will ever be displayed
|
||||||
suggestionsLimit: 6,
|
suggestionsLimit: 4,
|
||||||
|
|
||||||
// the order and limit for each suggestion influencer.
|
// the order and limit for each suggestion influencer
|
||||||
// the following would give you 1 suggestion from your search history
|
// the following would give you 2 suggestion from your search history
|
||||||
// and 4 suggestions from Duck Duck Go.
|
// and 4 suggestions from Duck Duck Go.
|
||||||
influencers: [
|
influencers: [
|
||||||
{ name: 'History', limit: 1 },
|
{ name: 'History', limit: 2 },
|
||||||
{ name: 'DuckDuckGo', limit: 6 },
|
{ name: 'DuckDuckGo', limit: 4 },
|
||||||
],
|
],
|
||||||
|
|
||||||
// open queries in a new tab.
|
// open queries in a new tab.
|
||||||
@@ -64,13 +66,6 @@
|
|||||||
|
|
||||||
// the delimiter between the hours and minutes in the clock.
|
// the delimiter between the hours and minutes in the clock.
|
||||||
clockDelimiter: ' ',
|
clockDelimiter: ' ',
|
||||||
|
|
||||||
// used for determining when to redirect directly to a url.
|
|
||||||
urlRegex: /^(?:(http|https)?:\/\/)?(?:[\w-]+\.)+([a-z]|[A-Z]|[0-9]){2,6}/i,
|
|
||||||
|
|
||||||
// if "urlRegex" matches but this doesn't, "http://" will be
|
|
||||||
// prepended to the beginning of the query before redirecting.
|
|
||||||
protocolRegex: /^[a-zA-Z]+:\/\//i
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -140,19 +135,19 @@
|
|||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-input,
|
#search-input,
|
||||||
.search-input:focus {
|
#search-input:focus {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background: #222;
|
background: #222;
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-input.bottom-no-radius {
|
body.suggestions #search-input {
|
||||||
border-radius: 2px 2px 0 0;
|
border-radius: 2px 2px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-suggestions {
|
#search-suggestions {
|
||||||
display: none;
|
display: none;
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
border-radius: 0 0 2px 2px;
|
border-radius: 0 0 2px 2px;
|
||||||
@@ -160,7 +155,7 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-suggestions.active {
|
body.suggestions #search-suggestions {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +173,7 @@
|
|||||||
background: #333;
|
background: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay {
|
#help {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -197,7 +192,7 @@
|
|||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay[data-toggled='true'] {
|
body.help #help {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
@@ -261,7 +256,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1000px) {
|
@media (min-width: 1000px) {
|
||||||
.overlay {
|
#help {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -269,7 +264,7 @@
|
|||||||
font-size: 1.3vw;
|
font-size: 1.3vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lists {
|
#help>ul {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -283,23 +278,25 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<time id="js-clock"></time>
|
<time id="clock"></time>
|
||||||
<form id="js-search-form" autocomplete="off">
|
<form id="search-form" autocomplete="off">
|
||||||
<input id="js-search-input" class="search-input" type="text" autofocus>
|
<input id="search-input" type="text" autofocus>
|
||||||
<ul id="js-search-suggestions" class="search-suggestions"></ul>
|
<ul id="search-suggestions"></ul>
|
||||||
</form>
|
</form>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<aside id="js-overlay" class="overlay">
|
<aside id="help"></aside>
|
||||||
<ul id="js-lists" class="lists"></ul>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const $ = {
|
const $ = {
|
||||||
|
bodyClassRemove: c => $.el('body').classList.remove(c),
|
||||||
|
bodyClassAdd: c => $.el('body').classList.add(c),
|
||||||
el: s => document.querySelector(s),
|
el: s => document.querySelector(s),
|
||||||
els: s => [].slice.call(document.querySelectorAll(s) || []),
|
els: s => [].slice.call(document.querySelectorAll(s) || []),
|
||||||
isDown: e => ['c-n', 'down'].includes($.key(e)),
|
escapeRegex: s => s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'),
|
||||||
isUp: e => ['c-p', 'up'].includes($.key(e)),
|
isDown: e => ['c-n', 'down', 'tab'].includes($.key(e)),
|
||||||
|
isRemove: e => ['backspace', 'delete'].includes($.key(e)),
|
||||||
|
isUp: e => ['c-p', 'up', 's-tab'].includes($.key(e)),
|
||||||
|
|
||||||
jsonp: url => {
|
jsonp: url => {
|
||||||
let script = document.createElement('script');
|
let script = document.createElement('script');
|
||||||
@@ -309,12 +306,16 @@
|
|||||||
|
|
||||||
key: e => {
|
key: e => {
|
||||||
const ctrl = e.ctrlKey;
|
const ctrl = e.ctrlKey;
|
||||||
|
const shift = e.shiftKey;
|
||||||
|
|
||||||
switch (e.which) {
|
switch (e.which) {
|
||||||
|
case 8: return 'backspace';
|
||||||
|
case 9: return shift ? 's-tab' : 'tab';
|
||||||
case 13: return 'enter';
|
case 13: return 'enter';
|
||||||
case 27: return 'escape';
|
case 27: return 'escape';
|
||||||
case 38: return 'up';
|
case 38: return 'up';
|
||||||
case 40: return 'down';
|
case 40: return 'down';
|
||||||
|
case 46: return 'delete';
|
||||||
case 78: return ctrl ? 'c-n' : 'n';
|
case 78: return ctrl ? 'c-n' : 'n';
|
||||||
case 80: return ctrl ? 'c-p' : 'n';
|
case 80: return ctrl ? 'c-p' : 'n';
|
||||||
}
|
}
|
||||||
@@ -322,21 +323,22 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
class Clock {
|
class Clock {
|
||||||
constructor() {
|
constructor({ delimiter }) {
|
||||||
this._clockEl = $.el('#js-clock');
|
this._el = $.el('#clock');
|
||||||
|
this._delimiter = delimiter;
|
||||||
this._setTime = this._setTime.bind(this);
|
this._setTime = this._setTime.bind(this);
|
||||||
this._start();
|
this._start();
|
||||||
}
|
}
|
||||||
|
|
||||||
_pad(num) {
|
_pad(num) {
|
||||||
return (`0${num.toString()}`).slice(-2);
|
return ('0' + num.toString()).slice(-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
_setTime() {
|
_setTime() {
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
const hours = this._pad(date.getHours());
|
const hours = this._pad(date.getHours());
|
||||||
const minutes = this._pad(date.getMinutes());
|
const minutes = this._pad(date.getMinutes());
|
||||||
this._clockEl.innerHTML = `${hours}${CONFIG.clockDelimiter}${minutes}`;
|
this._el.innerHTML = hours + this._delimiter + minutes;
|
||||||
}
|
}
|
||||||
|
|
||||||
_start() {
|
_start() {
|
||||||
@@ -346,42 +348,59 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Help {
|
class Help {
|
||||||
constructor() {
|
constructor({ commands, categories, newTab }) {
|
||||||
this._overlayEl = $.el('#js-overlay');
|
this._el = $.el('#help');
|
||||||
this._listsEl = $.el('#js-lists');
|
this._commands = commands;
|
||||||
this._handleKeydown = this._handleKeydown.bind(this);
|
this._categories = categories;
|
||||||
|
this._newTab = newTab;
|
||||||
|
this._toggled = false;
|
||||||
this._buildAndAppendLists();
|
this._buildAndAppendLists();
|
||||||
|
this._bindMethods();
|
||||||
this._registerEvents();
|
this._registerEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle(show) {
|
toggle(show) {
|
||||||
const toggle = (typeof show !== 'undefined') ? show :
|
this._toggled = (typeof show !== 'undefined') ? show : !this._toggled;
|
||||||
this._overlayEl.getAttribute('data-toggled') !== 'true';
|
this._toggled ? $.bodyClassAdd('help') : $.bodyClassRemove('help');
|
||||||
|
}
|
||||||
|
|
||||||
this._overlayEl.setAttribute('data-toggled', toggle);
|
_bindMethods() {
|
||||||
|
this._handleKeydown = this._handleKeydown.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildAndAppendLists() {
|
_buildAndAppendLists() {
|
||||||
CONFIG.categories.forEach(category => {
|
const lists = document.createElement('ul');
|
||||||
this._listsEl.insertAdjacentHTML(
|
|
||||||
|
for (let name in this._categories) {
|
||||||
|
lists.insertAdjacentHTML(
|
||||||
'beforeend',
|
'beforeend',
|
||||||
`<li class="category">
|
`<li class="category">
|
||||||
<h2 class="category-name">${category.name}</h2>
|
<h2 class="category-name">${name}</h2>
|
||||||
<ul>${this._buildListCommands(category)}</ul>
|
<ul>${this._buildListCommands(this._categories[name])}</ul>
|
||||||
</li>`
|
</li>`
|
||||||
);
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildListCommands(category) {
|
this._el.appendChild(lists);
|
||||||
return category.commands.map(({ url, key, name }) => (
|
}
|
||||||
|
|
||||||
|
_buildListCommands(commandNames) {
|
||||||
|
return commandNames.map(commandName => {
|
||||||
|
const [key, name, url] = this._getCommandFromName(commandName);
|
||||||
|
|
||||||
|
return (
|
||||||
`<li class="command">
|
`<li class="command">
|
||||||
<a href="${url}" target="${CONFIG.newTab ? '_blank' : '_self'}">
|
<a href="${url}" target="${this._newTab ? '_blank' : '_self'}">
|
||||||
<span class="command-key">${key}</span>
|
<span class="command-key">${key}</span>
|
||||||
<span class="command-name">${name}</span>
|
<span class="command-name">${name}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>`
|
</li>`
|
||||||
)).join('');
|
);
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
_getCommandFromName(commandName) {
|
||||||
|
return this._commands.filter(([key, name]) => name === commandName)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleKeydown(e) {
|
_handleKeydown(e) {
|
||||||
@@ -393,147 +412,176 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class History {
|
class Influencer {
|
||||||
constructor(suggestionsLimit = 0) {
|
constructor({ limit }) {
|
||||||
this._suggestionsLimit = suggestionsLimit;
|
this._limit = limit;
|
||||||
this._history = this._getFromStorage('history');
|
}
|
||||||
|
|
||||||
|
addItem() {}
|
||||||
|
getSuggestions() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class History extends Influencer {
|
||||||
|
constructor() {
|
||||||
|
super(...arguments);
|
||||||
|
this._storeName = 'history';
|
||||||
}
|
}
|
||||||
|
|
||||||
addItem(query) {
|
addItem(query) {
|
||||||
if (query.length < 2) return;
|
if (query.length < 2) return;
|
||||||
this._updateHistory(query);
|
let exists;
|
||||||
this._sortHistory();
|
|
||||||
this._saveToStorage('history', this._history);
|
const history = this._getHistory().map(([item, count]) => {
|
||||||
|
const match = item === query;
|
||||||
|
if (match) exists = true;
|
||||||
|
return [item, match ? count + 1 : count];
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!exists) history.push([query, 1]);
|
||||||
|
this._setHistory(this._sort(history));
|
||||||
}
|
}
|
||||||
|
|
||||||
getSuggestionsPromise(query) {
|
getSuggestions(query) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const suggestions = this._history
|
const suggestions = this._getHistory()
|
||||||
.filter(item => this._itemContainsQuery(query, item[0]))
|
.filter(([item]) => this._itemContainsQuery(query, item))
|
||||||
.slice(0, this._suggestionsLimit)
|
.slice(0, this._limit)
|
||||||
.map(item => item[0]);
|
.map(([item]) => item);
|
||||||
|
|
||||||
resolve(suggestions);
|
resolve(suggestions);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_getFromStorage(name) {
|
_fetch() {
|
||||||
return JSON.parse(localStorage.getItem(name)) || [];
|
return JSON.parse(localStorage.getItem(this._storeName)) || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
_getHistory() {
|
||||||
|
this._history = this._history || this._fetch();
|
||||||
|
return this._history;
|
||||||
}
|
}
|
||||||
|
|
||||||
_itemContainsQuery(query, item) {
|
_itemContainsQuery(query, item) {
|
||||||
return query && item.indexOf(query) !== -1;
|
return query && item.indexOf(query) !== -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
_saveToStorage(name, data) {
|
_save(history) {
|
||||||
localStorage.setItem(name, JSON.stringify(data));
|
localStorage.setItem(this._storeName, JSON.stringify(history));
|
||||||
}
|
}
|
||||||
|
|
||||||
_sortHistory() {
|
_setHistory(history) {
|
||||||
this._history = this._history
|
this._history = history;
|
||||||
.sort((current, next) => current[1] - next[1])
|
this._save(history);
|
||||||
.reverse();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateHistory(query) {
|
_sort(history) {
|
||||||
let exists = false;
|
return history.sort((current, next) => current[1] - next[1]).reverse();
|
||||||
|
|
||||||
this._history = this._history.map(item => {
|
|
||||||
if (item[0] === query) {
|
|
||||||
item[1]++;
|
|
||||||
exists = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return item
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!exists) this._history.push([query, 1]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DuckDuckGo {
|
class DuckDuckGo extends Influencer {
|
||||||
constructor(suggestionsLimit = 0) {
|
constructor() {
|
||||||
this._endpoint = 'https://duckduckgo.com/ac/';
|
super(...arguments);
|
||||||
this._callback = 'autocompleteCallback';
|
|
||||||
this._suggestionsLimit = suggestionsLimit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addItem() {}
|
getSuggestions(query) {
|
||||||
|
|
||||||
getSuggestionsPromise(query) {
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
this._resolve = resolve;
|
const endpoint = 'https://duckduckgo.com/ac/';
|
||||||
window[this._callback] = this._handleResponse.bind(this);
|
const callback = 'autocompleteCallback';
|
||||||
$.jsonp(`${this._endpoint}?callback=${this._callback}&q=${query}`);
|
|
||||||
|
window[callback] = res => {
|
||||||
|
resolve(res.slice(0, this._limit).map(i => i.phrase));
|
||||||
|
};
|
||||||
|
|
||||||
|
$.jsonp(`${endpoint}?callback=${callback}&q=${query}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleResponse(res) {
|
|
||||||
const suggestions = res.slice(0, this._suggestionsLimit)
|
|
||||||
.map(i => i.phrase);
|
|
||||||
|
|
||||||
this._resolve(suggestions);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Suggester {
|
class Suggester {
|
||||||
constructor(influencers) {
|
constructor({ influencers, limit }) {
|
||||||
this._inputEl = $.el('#js-search-input');
|
this._el = $.el('#search-suggestions');
|
||||||
this._suggestionsEl = $.el('#js-search-suggestions');
|
|
||||||
this._totalSuggestions = 0;
|
|
||||||
this._suggestionEls = [];
|
|
||||||
this._influencers = influencers;
|
this._influencers = influencers;
|
||||||
this._handleKeydown = this._handleKeydown.bind(this);
|
this._limit = limit;
|
||||||
document.addEventListener('keydown', this._handleKeydown);
|
this._suggestionEls = [];
|
||||||
|
this._bindMethods();
|
||||||
|
this._registerEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
add(query) {
|
setOnClick(callback) {
|
||||||
|
this._onClick = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
setOnHighlight(callback) {
|
||||||
|
this._onHighlight = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
setOnUnhighlight(callback) {
|
||||||
|
this._onUnhighlight = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
success(query) {
|
||||||
|
this._clearSuggestions();
|
||||||
this._influencers.forEach(i => i.addItem(query));
|
this._influencers.forEach(i => i.addItem(query));
|
||||||
}
|
}
|
||||||
|
|
||||||
suggest(input, clickCallback = () => {}) {
|
suggest(input) {
|
||||||
if (!input) {
|
input = input.trim();
|
||||||
|
if (input === '') this._clearSuggestions();
|
||||||
|
|
||||||
|
Promise.all(this._getInfluencerPromises(input)).then(res => {
|
||||||
|
const suggestions = this._flattenAndUnique(res);
|
||||||
|
|
||||||
|
if (suggestions.length) {
|
||||||
this._clearSuggestions();
|
this._clearSuggestions();
|
||||||
return;
|
this._appendSuggestions(suggestions, input);
|
||||||
|
this._registerSuggestionEvents();
|
||||||
|
$.bodyClassAdd('suggestions');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this._handleClick = clickCallback;
|
_appendSuggestions(suggestions, input) {
|
||||||
this._suggest(input);
|
suggestions.some((suggestion, i) => {
|
||||||
}
|
const match = new RegExp($.escapeRegex(input), 'g');
|
||||||
|
const suggestionHtml = suggestion.replace(match, `<b>${input}</b>`);
|
||||||
|
|
||||||
_appendSuggestion(suggestion, input) {
|
this._el.insertAdjacentHTML(
|
||||||
const suggestionHtml = suggestion
|
|
||||||
.replace(new RegExp(input, 'g'), `<b>${input}</b>`);
|
|
||||||
|
|
||||||
this._suggestionsEl.insertAdjacentHTML(
|
|
||||||
'beforeend',
|
'beforeend',
|
||||||
`<li>
|
`<li>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="js-search-suggestion search-suggestion"
|
class="js-search-suggestion search-suggestion"
|
||||||
data-suggestion="${suggestion}"
|
data-suggestion="${suggestion}"
|
||||||
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
${suggestionHtml}
|
${suggestionHtml}
|
||||||
</button>
|
</button>
|
||||||
</li>`
|
</li>`
|
||||||
);
|
);
|
||||||
|
|
||||||
this._showSuggestions();
|
return i + 1 >= this._limit;
|
||||||
return ++this._totalSuggestions === CONFIG.suggestionsLimit;
|
});
|
||||||
|
|
||||||
|
this._suggestionEls = $.els('.js-search-suggestion');
|
||||||
|
}
|
||||||
|
|
||||||
|
_bindMethods() {
|
||||||
|
this._handleKeydown = this._handleKeydown.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
_clearClickEvents() {
|
_clearClickEvents() {
|
||||||
this._suggestionEls.forEach(el => {
|
this._suggestionEls.forEach(el => {
|
||||||
const callback = this._handleClick.bind(null, el.value);
|
const callback = this._onClick.bind(null, el.value);
|
||||||
el.removeEventListener('click', callback);
|
el.removeEventListener('click', callback);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_clearSuggestions() {
|
_clearSuggestions() {
|
||||||
this._hideSuggestions();
|
$.bodyClassRemove('suggestions');
|
||||||
this._clearClickEvents();
|
this._clearClickEvents();
|
||||||
this._suggestionsEl.innerHTML = '';
|
this._suggestionEls = [];
|
||||||
this._totalSuggestions = 0;
|
this._el.innerHTML = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// [[1, 2], [1, 2, 3, 4]] -> [1, 2, 3, 4]
|
// [[1, 2], [1, 2, 3, 4]] -> [1, 2, 3, 4]
|
||||||
@@ -541,113 +589,104 @@
|
|||||||
return [...new Set([].concat.apply([], array))];
|
return [...new Set([].concat.apply([], array))];
|
||||||
}
|
}
|
||||||
|
|
||||||
_focusNext() {
|
_focusNext(e) {
|
||||||
const active = $.el('.highlight');
|
const exists = this._suggestionEls.some((el, i) => {
|
||||||
|
if (el.classList.contains('highlight')) {
|
||||||
if (active) {
|
this._highlight(this._suggestionEls[i + 1], e);
|
||||||
this._suggestionEls.forEach((el, index) => {
|
return true;
|
||||||
const next = this._suggestionEls[index + 1];
|
|
||||||
if (el === active && next) this._highlight(next);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this._highlight(this._suggestionEls[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_focusPrevious() {
|
|
||||||
const active = $.el('.highlight');
|
|
||||||
|
|
||||||
if (active) {
|
|
||||||
this._suggestionEls.forEach((el, index) => {
|
|
||||||
if (el === active) {
|
|
||||||
const previous = this._suggestionEls[index - 1];
|
|
||||||
this._noHighlight();
|
|
||||||
if (previous) this._highlight(previous);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
if (!exists) this._highlight(this._suggestionEls[0], e);
|
||||||
}
|
}
|
||||||
|
|
||||||
_gatherInfluencers(input) {
|
_focusPrevious(e) {
|
||||||
|
const exists = this._suggestionEls.some((el, i) => {
|
||||||
|
if (el.classList.contains('highlight') && i) {
|
||||||
|
this._highlight(this._suggestionEls[i - 1], e);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!exists) this._unHighlight(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
_getInfluencerPromises(input) {
|
||||||
return this._influencers
|
return this._influencers
|
||||||
.map(influencer => influencer.getSuggestionsPromise(input));
|
.map(influencer => influencer.getSuggestions(input));
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleKeydown(e) {
|
_handleKeydown(e) {
|
||||||
if ($.isDown(e) || $.isUp(e)) e.preventDefault();
|
if ($.isDown(e)) this._focusNext(e);
|
||||||
if ($.isDown(e)) this._focusNext();
|
if ($.isUp(e)) this._focusPrevious(e);
|
||||||
if ($.isUp(e)) this._focusPrevious();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_highlight(el) {
|
_highlight(el, e) {
|
||||||
this._noHighlight();
|
this._unHighlight();
|
||||||
|
|
||||||
|
if (el) {
|
||||||
|
this._onHighlight(el.getAttribute('data-suggestion'));
|
||||||
el.classList.add('highlight');
|
el.classList.add('highlight');
|
||||||
this._inputEl.value = el.getAttribute('data-suggestion');
|
e.preventDefault();
|
||||||
this._inputEl.focus()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_noHighlight() {
|
|
||||||
this._inputEl.value = this._originalValue;
|
|
||||||
this._suggestionEls.forEach(el => el.classList.remove('highlight'));
|
|
||||||
}
|
|
||||||
|
|
||||||
_showSuggestions() {
|
|
||||||
this._inputEl.classList.add('bottom-no-radius');
|
|
||||||
this._suggestionsEl.classList.add('active');
|
|
||||||
}
|
|
||||||
|
|
||||||
_hideSuggestions() {
|
|
||||||
this._inputEl.classList.remove('bottom-no-radius');
|
|
||||||
this._suggestionsEl.classList.remove('active');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_registerEvents() {
|
_registerEvents() {
|
||||||
|
document.addEventListener('keydown', this._handleKeydown);
|
||||||
|
}
|
||||||
|
|
||||||
|
_registerSuggestionEvents() {
|
||||||
this._suggestionEls.forEach(el => {
|
this._suggestionEls.forEach(el => {
|
||||||
const value = el.getAttribute('data-suggestion');
|
const value = el.getAttribute('data-suggestion');
|
||||||
el.addEventListener('mouseover', this._highlight.bind(this, el));
|
el.addEventListener('mouseover', this._highlight.bind(this, el));
|
||||||
el.addEventListener('mouseout', this._noHighlight.bind(this));
|
el.addEventListener('mouseout', this._unHighlight.bind(this));
|
||||||
el.addEventListener('click', this._handleClick.bind(null, value));
|
el.addEventListener('click', this._onClick.bind(null, value));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_suggest(input) {
|
_unHighlight(e) {
|
||||||
this._originalValue = this._inputEl.value;
|
const el = $.el('.highlight');
|
||||||
|
|
||||||
Promise.all(this._gatherInfluencers(input)).then(res => {
|
if (el) {
|
||||||
this._clearSuggestions();
|
this._onUnhighlight();
|
||||||
|
el.classList.remove('highlight');
|
||||||
this._flattenAndUnique(res)
|
if (e) e.preventDefault();
|
||||||
.some(item => this._appendSuggestion(item, input));
|
}
|
||||||
|
|
||||||
this._suggestionEls = $.els('.js-search-suggestion');
|
|
||||||
this._registerEvents();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class QueryParser {
|
class QueryParser {
|
||||||
|
constructor({ commands, defaultSearch, pathDelimiter, searchDelimiter }) {
|
||||||
|
this._commands = commands;
|
||||||
|
this._defaultSearch = defaultSearch;
|
||||||
|
this._searchDelimiter = searchDelimiter;
|
||||||
|
this._pathDelimiter = pathDelimiter;
|
||||||
|
this._protocolRegex = /^[a-zA-Z]+:\/\//i;
|
||||||
|
this._urlRegex = /^(?:(http|https)?:\/\/)?(?:[\w-]+\.)+([a-z]|[A-Z]|[0-9]){2,6}/i;
|
||||||
|
}
|
||||||
|
|
||||||
generateRedirect(query) {
|
generateRedirect(query) {
|
||||||
const encodedQuery = encodeURIComponent(query);
|
const encodedQuery = encodeURIComponent(query);
|
||||||
let redirectUrl = CONFIG.defaultSearch.replace('{}', encodedQuery);
|
let redirectUrl = this._defaultSearch.replace('{}', encodedQuery);
|
||||||
|
|
||||||
if (query.match(CONFIG.urlRegex)) {
|
if (query.match(this._urlRegex)) {
|
||||||
const hasProtocol = query.match(CONFIG.protocolRegex);
|
const hasProtocol = query.match(this._protocolRegex);
|
||||||
redirectUrl = hasProtocol ? query : 'http://' + query;
|
redirectUrl = hasProtocol ? query : 'http://' + query;
|
||||||
} else {
|
} else {
|
||||||
const splitSearch = query.split(CONFIG.searchDelimiter);
|
const splitSearch = query.split(this._searchDelimiter);
|
||||||
const splitPath = query.split(CONFIG.pathDelimiter);
|
const splitPath = query.split(this._pathDelimiter);
|
||||||
|
|
||||||
this._loopThroughCommands(command => {
|
this._commands.some(([key, name, url, searchPath]) => {
|
||||||
const isSearch = splitSearch[0] === command.key;
|
const isSearch = splitSearch[0] === key;
|
||||||
const isPath = splitPath[0] === command.key;
|
const isPath = splitPath[0] === key;
|
||||||
|
|
||||||
if (isSearch || isPath) {
|
if (isSearch || isPath) {
|
||||||
if (splitSearch[1] && command.search) {
|
if (splitSearch[1] && searchPath) {
|
||||||
redirectUrl = this._prepSearch(command, splitSearch);
|
redirectUrl = this._prepSearch(url, searchPath, splitSearch);
|
||||||
} else if (splitPath[1]) {
|
} else if (splitPath[1]) {
|
||||||
redirectUrl = this._prepPath(command, splitPath);
|
redirectUrl = this._prepPath(url, splitPath);
|
||||||
} else {
|
} else {
|
||||||
redirectUrl = command.url;
|
redirectUrl = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -658,35 +697,31 @@
|
|||||||
return redirectUrl;
|
return redirectUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
instantRedirect(keypressEvent, query, callback) {
|
instantRedirect(query, callback) {
|
||||||
this._loopThroughCommands(command => {
|
let exists;
|
||||||
if (command.key === query) {
|
|
||||||
keypressEvent.preventDefault();
|
this._commands.forEach(([key, name, url]) => {
|
||||||
callback(command.url);
|
if (key === query) {
|
||||||
|
callback(url);
|
||||||
|
exists = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return exists;
|
||||||
}
|
}
|
||||||
|
|
||||||
_loopThroughCommands(callback) {
|
_prepPath(url, splitPath) {
|
||||||
CONFIG.categories
|
const path = this._shiftAndTrim(splitPath, this._pathDelimiter);
|
||||||
.map(category => category.commands)
|
return this._stripUrlPath(url) + '/' + path;
|
||||||
.forEach(commands => commands.forEach(command => {
|
|
||||||
if (callback(command)) return;
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_prepPath(command, query) {
|
_prepSearch(url, searchPath, splitSearch) {
|
||||||
const baseUrl = this._stripUrlPath(command.url);
|
if (!searchPath) return url;
|
||||||
const path = this._shiftAndTrim(query, CONFIG.pathDelimiter);
|
const baseUrl = this._stripUrlPath(url);
|
||||||
return `${baseUrl}/${path}`;
|
const query = this._shiftAndTrim(splitSearch, this._searchDelimiter);
|
||||||
}
|
const urlQuery = encodeURIComponent(query);
|
||||||
|
searchPath = searchPath.replace('{}', urlQuery);
|
||||||
_prepSearch(command, query) {
|
return baseUrl + searchPath;
|
||||||
if (!command.search) return command.url;
|
|
||||||
const baseUrl = this._stripUrlPath(command.url);
|
|
||||||
const search = this._shiftAndTrimAndEncode(query);
|
|
||||||
const searchPath = command.search.replace('{}', search);
|
|
||||||
return `${baseUrl}${searchPath}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_shiftAndTrim(arr, delimiter) {
|
_shiftAndTrim(arr, delimiter) {
|
||||||
@@ -694,11 +729,6 @@
|
|||||||
return arr.join(delimiter).trim();
|
return arr.join(delimiter).trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
_shiftAndTrimAndEncode(arr) {
|
|
||||||
const clean = this._shiftAndTrim(arr, CONFIG.searchDelimiter);
|
|
||||||
return encodeURIComponent(clean);
|
|
||||||
}
|
|
||||||
|
|
||||||
_stripUrlPath(url) {
|
_stripUrlPath(url) {
|
||||||
const parser = document.createElement('a');
|
const parser = document.createElement('a');
|
||||||
parser.href = url;
|
parser.href = url;
|
||||||
@@ -707,73 +737,93 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Form {
|
class Form {
|
||||||
constructor(help, suggester, queryParser) {
|
constructor({ help, instantRedirect, newTab, suggester, queryParser }) {
|
||||||
|
this._formEl = $.el('#search-form');
|
||||||
|
this._inputEl = $.el('#search-input');
|
||||||
this._help = help;
|
this._help = help;
|
||||||
this._suggester = suggester;
|
this._suggester = suggester;
|
||||||
this._queryParser = queryParser;
|
this._queryParser = queryParser;
|
||||||
this._formEl = $.el('#js-search-form');
|
this._instantRedirect = instantRedirect;
|
||||||
this._inputEl = $.el('#js-search-input');
|
this._newTab = newTab;
|
||||||
this._inputElVal = '';
|
this._inputElVal = '';
|
||||||
this._bindMethods();
|
this._bindMethods();
|
||||||
this._registerEvents();
|
this._registerEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
_bindMethods() {
|
_bindMethods() {
|
||||||
|
this._clearPreview = this._clearPreview.bind(this);
|
||||||
this._handleKeypress = this._handleKeypress.bind(this);
|
this._handleKeypress = this._handleKeypress.bind(this);
|
||||||
this._submitForm = this._submitForm.bind(this);
|
|
||||||
this._handleKeyup = this._handleKeyup.bind(this);
|
this._handleKeyup = this._handleKeyup.bind(this);
|
||||||
|
this._previewValue = this._previewValue.bind(this);
|
||||||
|
this._submitForm = this._submitForm.bind(this);
|
||||||
this._submitWithValue = this._submitWithValue.bind(this);
|
this._submitWithValue = this._submitWithValue.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_clearInput() {
|
||||||
|
this._inputEl.value = '';
|
||||||
|
this._inputElVal = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
_clearPreview() {
|
||||||
|
this._inputEl.value = this._inputElVal;
|
||||||
|
this._inputEl.focus();
|
||||||
|
}
|
||||||
|
|
||||||
_handleKeypress(e) {
|
_handleKeypress(e) {
|
||||||
const newChar = String.fromCharCode(e.which);
|
const newChar = String.fromCharCode(e.which);
|
||||||
const newQuery = this._inputEl.value + newChar;
|
const newQuery = this._inputEl.value + newChar;
|
||||||
const isNotEmpty = newChar.length;
|
const validChar = newChar.length && $.key(e) !== 'enter';
|
||||||
|
const queryDiffers = this._inputElVal !== newQuery;
|
||||||
|
|
||||||
if (isNotEmpty && $.key(e) !== 'enter') {
|
if (validChar) this._help.toggle(false);
|
||||||
this._help.toggle(false);
|
|
||||||
this._inputEl.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CONFIG.instantRedirect) {
|
if (
|
||||||
this._queryParser
|
this._instantRedirect &&
|
||||||
.instantRedirect(e, newQuery, this._submitWithValue);
|
this._queryParser.instantRedirect(newQuery, this._submitWithValue)
|
||||||
|
) {
|
||||||
|
e.preventDefault();
|
||||||
|
} else if (this._suggester && validChar && queryDiffers) {
|
||||||
|
this._suggester.suggest(newQuery);
|
||||||
|
this._inputElVal = newQuery;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleKeyup(e) {
|
_handleKeyup(e) {
|
||||||
const oldVal = this._inputElVal;
|
const newQuery = this._inputEl.value;
|
||||||
const newVal = this._inputEl.value.trim();
|
|
||||||
const ignored = $.isDown(e) || $.isUp(e);
|
|
||||||
this._inputElVal = newVal;
|
|
||||||
|
|
||||||
if (CONFIG.suggestions && oldVal !== newVal && !ignored) {
|
if (this._suggester && $.isRemove(e) && this._inputElVal !== newQuery) {
|
||||||
this._suggester.suggest(newVal, this._submitWithValue);
|
this._suggester.suggest(newQuery);
|
||||||
|
this._inputElVal = newQuery;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_previewValue(value) {
|
||||||
|
this._inputEl.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
_redirect(redirect) {
|
_redirect(redirect) {
|
||||||
if (CONFIG.newTab) window.open(redirect, '_blank');
|
if (this._newTab) window.open(redirect, '_blank');
|
||||||
else window.location.href = redirect;
|
else window.location.href = redirect;
|
||||||
}
|
}
|
||||||
|
|
||||||
_registerEvents() {
|
_registerEvents() {
|
||||||
document.addEventListener('keypress', this._handleKeypress);
|
this._inputEl.addEventListener('keypress', this._handleKeypress);
|
||||||
this._inputEl.addEventListener('keyup', this._handleKeyup);
|
this._inputEl.addEventListener('keyup', this._handleKeyup);
|
||||||
this._formEl.addEventListener('submit', this._submitForm, false);
|
this._formEl.addEventListener('submit', this._submitForm, false);
|
||||||
|
this._suggester.setOnClick(this._submitWithValue);
|
||||||
|
this._suggester.setOnHighlight(this._previewValue);
|
||||||
|
this._suggester.setOnUnhighlight(this._clearPreview);
|
||||||
}
|
}
|
||||||
|
|
||||||
_submitForm(e) {
|
_submitForm(e) {
|
||||||
if (e) e.preventDefault();
|
if (e) e.preventDefault();
|
||||||
const query = this._inputEl.value.trim();
|
const query = this._inputEl.value.trim();
|
||||||
|
this._clearInput();
|
||||||
|
|
||||||
if (!query || query === '?') {
|
if (!query || query === '?') {
|
||||||
this._inputEl.value = '';
|
|
||||||
this._help.toggle();
|
this._help.toggle();
|
||||||
} else {
|
} else {
|
||||||
this._suggester.add(query);
|
this._suggester.success(query);
|
||||||
this._suggester.suggest('');
|
|
||||||
this._inputEl.value = '';
|
|
||||||
this._redirect(this._queryParser.generateRedirect(query));
|
this._redirect(this._queryParser.generateRedirect(query));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -786,14 +836,50 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const clock = new Clock();
|
const getHelp = () => {
|
||||||
const help = new Help();
|
return new Help({
|
||||||
const influencers = { History: History, DuckDuckGo: DuckDuckGo };
|
categories: CONFIG.categories,
|
||||||
|
commands: CONFIG.commands,
|
||||||
|
newTab: CONFIG.newTab,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const suggester = new Suggester(
|
const getInfluencers = () => {
|
||||||
CONFIG.influencers.map(i => new influencers[i.name](i.limit))
|
const availableInfluencers = {
|
||||||
);
|
DuckDuckGo: DuckDuckGo,
|
||||||
|
History: History,
|
||||||
|
};
|
||||||
|
|
||||||
const parser = new QueryParser();
|
return CONFIG.influencers.map(i => {
|
||||||
const form = new Form(help, suggester, parser);
|
return new availableInfluencers[i.name]({ limit: i.limit });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSuggester = () => {
|
||||||
|
return new Suggester({
|
||||||
|
influencers: getInfluencers(),
|
||||||
|
limit: CONFIG.suggestionsLimit,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getQueryParser = () => {
|
||||||
|
return new QueryParser({
|
||||||
|
commands: CONFIG.commands,
|
||||||
|
defaultSearch: CONFIG.defaultSearch,
|
||||||
|
pathDelimiter: CONFIG.pathDelimiter,
|
||||||
|
searchDelimiter: CONFIG.searchDelimiter,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
new Clock({
|
||||||
|
delimiter: CONFIG.clockDelimiter,
|
||||||
|
});
|
||||||
|
|
||||||
|
new Form({
|
||||||
|
help: getHelp(),
|
||||||
|
instantRedirect: CONFIG.instantRedirect,
|
||||||
|
newTab: CONFIG.newTab,
|
||||||
|
queryParser: getQueryParser(),
|
||||||
|
suggester: CONFIG.suggestions ? getSuggester() : false,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user