mirror of
https://github.com/xvvvyz/tilde.git
synced 2026-03-11 14:44:24 +00:00
make suggestions a bit more robust
This commit is contained in:
parent
4a8e5607cd
commit
978c95fcfc
1 changed files with 99 additions and 30 deletions
129
index.html
129
index.html
|
|
@ -41,16 +41,24 @@
|
|||
// e.g. type "r/r/unixporn" to go to "reddit.com/r/unixporn".
|
||||
pathDelimiter: '/',
|
||||
|
||||
// set to true to instantly redirect when a key is matched.
|
||||
// put a space before any search queries to prevent unwanted redirects.
|
||||
// instantly redirect when a key is matched.
|
||||
// put a space before any other queries to prevent unwanted redirects.
|
||||
instantRedirect: false,
|
||||
|
||||
// suggest your most popular queries as you type.
|
||||
suggestions: false,
|
||||
// give suggestions as you type.
|
||||
suggestions: true,
|
||||
|
||||
// max amount of suggestions to display.
|
||||
// max amount of suggestions that will ever be displayed.
|
||||
suggestionsLimit: 4,
|
||||
|
||||
// the order and limit for each suggestion influencer.
|
||||
// the following would give you 1 suggestion from your search history
|
||||
// and 4 suggestions from Duck Duck Go.
|
||||
influencers: [
|
||||
{ name: 'History', limit: 1 },
|
||||
{ name: 'DuckDuckGo', limit: 4 },
|
||||
],
|
||||
|
||||
// open queries in a new tab.
|
||||
newTab: true,
|
||||
|
||||
|
|
@ -279,6 +287,12 @@
|
|||
const $ = {
|
||||
el: s => document.querySelector(s),
|
||||
els: s => [].slice.call(document.querySelectorAll(s) || []),
|
||||
|
||||
jsonp: url => {
|
||||
let script = document.createElement('script');
|
||||
script.src = url;
|
||||
$.el('head').appendChild(script);
|
||||
},
|
||||
};
|
||||
|
||||
class Clock {
|
||||
|
|
@ -355,7 +369,8 @@
|
|||
}
|
||||
|
||||
class History {
|
||||
constructor() {
|
||||
constructor(suggestionsLimit = 0) {
|
||||
this._suggestionsLimit = suggestionsLimit;
|
||||
this._history = this._getFromStorage('history');
|
||||
}
|
||||
|
||||
|
|
@ -370,7 +385,7 @@
|
|||
return new Promise(resolve => {
|
||||
const suggestions = this._history
|
||||
.filter(item => this._itemContainsQuery(query, item[0]))
|
||||
.slice(0, CONFIG.suggestionsLimit)
|
||||
.slice(0, this._suggestionsLimit)
|
||||
.map(item => item[0]);
|
||||
|
||||
resolve(suggestions);
|
||||
|
|
@ -382,8 +397,7 @@
|
|||
}
|
||||
|
||||
_itemContainsQuery(query, item) {
|
||||
const match = item.indexOf(query) !== -1;
|
||||
return query && match && query !== item;
|
||||
return query && item.indexOf(query) !== -1;
|
||||
}
|
||||
|
||||
_saveToStorage(name, data) {
|
||||
|
|
@ -412,31 +426,56 @@
|
|||
}
|
||||
}
|
||||
|
||||
class DuckDuckGo {
|
||||
constructor(suggestionsLimit = 0) {
|
||||
this._endpoint = 'https://duckduckgo.com/ac';
|
||||
this._callback = 'autocompleteCallback';
|
||||
this._suggestionsLimit = suggestionsLimit;
|
||||
}
|
||||
|
||||
addItem() {}
|
||||
|
||||
getSuggestionsPromise(query) {
|
||||
return new Promise(resolve => {
|
||||
this._resolve = resolve;
|
||||
window[this._callback] = this._handleResponse.bind(this);
|
||||
$.jsonp(`${this._endpoint}?callback=${this._callback}&q=${query}`);
|
||||
});
|
||||
}
|
||||
|
||||
_handleResponse(res) {
|
||||
const suggestions = res.slice(0, this._suggestionsLimit)
|
||||
.map(i => i.phrase);
|
||||
|
||||
this._resolve(suggestions);
|
||||
}
|
||||
}
|
||||
|
||||
class Suggester {
|
||||
constructor(influencers) {
|
||||
this._inputEl = $.el('#js-search-input');
|
||||
this._suggestionsEl = $.el('#js-search-suggestions');
|
||||
this._totalSuggestions = 0;
|
||||
this._suggestionEls = [];
|
||||
this._influencers = influencers;
|
||||
this._handleKeydown = this._handleKeydown.bind(this);
|
||||
document.addEventListener('keydown', this._handleKeydown);
|
||||
}
|
||||
|
||||
suggest(input, clickCallback = () => {}) {
|
||||
this._clearSuggestions();
|
||||
input = input.trim();
|
||||
if (!input) return;
|
||||
this._handleClick = clickCallback;
|
||||
add(query) {
|
||||
this._influencers.forEach(i => i.addItem(query));
|
||||
}
|
||||
|
||||
this._influencers
|
||||
.map(influencer => influencer.getSuggestionsPromise(input))
|
||||
.forEach(promise => {
|
||||
promise.then(suggestions => {
|
||||
suggestions.forEach(item => this._appendSuggestion(item));
|
||||
this._suggestionEls = $.els('.js-search-suggestion');
|
||||
this._registerClickEvents();
|
||||
});
|
||||
});
|
||||
suggest(input, clickCallback = () => {}) {
|
||||
input = input.trim();
|
||||
|
||||
if (!input) {
|
||||
this._clearSuggestions();
|
||||
return;
|
||||
}
|
||||
|
||||
this._handleClick = clickCallback;
|
||||
this._suggest(input);
|
||||
}
|
||||
|
||||
_appendSuggestion(suggestion) {
|
||||
|
|
@ -452,6 +491,7 @@
|
|||
);
|
||||
|
||||
this._inputEl.classList.add('bottom-no-radius');
|
||||
return ++this._totalSuggestions === CONFIG.suggestionsLimit;
|
||||
}
|
||||
|
||||
_clearClickEvents() {
|
||||
|
|
@ -462,11 +502,17 @@
|
|||
}
|
||||
|
||||
_clearSuggestions() {
|
||||
this._totalSuggestions = 0;
|
||||
this._clearClickEvents();
|
||||
this._suggestionsEl.innerHTML = '';
|
||||
this._inputEl.classList.remove('bottom-no-radius');
|
||||
}
|
||||
|
||||
// [[1, 2], [1, 2, 3, 4]] -> [1, 2, 3, 4]
|
||||
_flattenAndUnique(array) {
|
||||
return [...new Set([].concat.apply([], array))];
|
||||
}
|
||||
|
||||
_focusNext() {
|
||||
if (this._suggestionEls.length) {
|
||||
const active = document.activeElement;
|
||||
|
|
@ -498,6 +544,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
_gatherInfluencers(input) {
|
||||
return this._influencers
|
||||
.map(influencer => influencer.getSuggestionsPromise(input));
|
||||
}
|
||||
|
||||
_handleKeydown(event) {
|
||||
const isDown = event.which === 40;
|
||||
const isUp = event.which === 38;
|
||||
|
|
@ -513,6 +564,18 @@
|
|||
el.addEventListener('click', this._handleClick.bind(null, el.value));
|
||||
});
|
||||
}
|
||||
|
||||
_suggest(input) {
|
||||
Promise.all(this._gatherInfluencers(input)).then(res => {
|
||||
this._clearSuggestions();
|
||||
|
||||
this._flattenAndUnique(res)
|
||||
.some(item => this._appendSuggestion(item));
|
||||
|
||||
this._suggestionEls = $.els('.js-search-suggestion');
|
||||
this._registerClickEvents();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class QueryParser {
|
||||
|
|
@ -597,9 +660,8 @@
|
|||
}
|
||||
|
||||
class Form {
|
||||
constructor(help, history, suggester, queryParser) {
|
||||
constructor(help, suggester, queryParser) {
|
||||
this._help = help;
|
||||
this._history = history;
|
||||
this._suggester = suggester;
|
||||
this._queryParser = queryParser;
|
||||
this._formEl = $.el('#js-search-form');
|
||||
|
|
@ -636,7 +698,10 @@
|
|||
}
|
||||
|
||||
_handleKeyup(event) {
|
||||
if (CONFIG.suggestions && this._inputElVal !== this._inputEl.value) {
|
||||
if (
|
||||
CONFIG.suggestions &&
|
||||
this._inputElVal.trim() !== this._inputEl.value.trim()
|
||||
) {
|
||||
this._suggester.suggest(this._inputEl.value, this._submitWithValue);
|
||||
this._inputElVal = this._inputEl.value;
|
||||
}
|
||||
|
|
@ -661,7 +726,7 @@
|
|||
this._inputEl.value = '';
|
||||
this._help.toggle();
|
||||
} else {
|
||||
this._history.addItem(query);
|
||||
this._suggester.add(query);
|
||||
this._suggester.suggest('');
|
||||
this._inputEl.value = '';
|
||||
this._redirect(this._queryParser.generateRedirect(query));
|
||||
|
|
@ -678,8 +743,12 @@
|
|||
<script>
|
||||
const clock = new Clock();
|
||||
const help = new Help();
|
||||
const history = new History();
|
||||
const suggester = new Suggester([history]);
|
||||
const influencers = { History: History, DuckDuckGo: DuckDuckGo };
|
||||
|
||||
const suggester = new Suggester(
|
||||
CONFIG.influencers.map(i => new influencers[i.name](i.limit))
|
||||
);
|
||||
|
||||
const parser = new QueryParser();
|
||||
const form = new Form(help, history, suggester, parser);
|
||||
const form = new Form(help, suggester, parser);
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Reference in a new issue