diff --git a/index.html b/index.html
index 1836a30..90086d7 100644
--- a/index.html
+++ b/index.html
@@ -40,6 +40,24 @@
search: '/search?q={}',
url: 'https://www.google.com',
},
+ {
+ key: 'duckduckgo',
+ name: 'DuckDuckGo',
+ search: '/?q={}',
+ url: 'https://duckduckgo.com',
+ },
+ {
+ key: 'ecosia',
+ name: 'Ecosia',
+ search: '/search?q={}',
+ url: 'https://www.ecosia.org',
+ },
+ {
+ key: 'bing',
+ name: 'Bing',
+ search: '/search?q={}',
+ url: 'https://www.bing.com',
+ },
{
category: 'Google',
hues: ['217', '197'],
@@ -303,6 +321,15 @@
// to search GitHub for tilde, you'd type "g'tilde".
querySearchDelimiter: "'",
+ // Scripts allow you to open or search multiple sites at once. For example,
+ // to search Google, DuckDuckGo, Ecosia and Bing for cats at the same time,
+ // you'd type "se'cats".
+ scripts: {
+ se: ['bing', 'ecosia', 'duckduckgo', '*'],
+ sn: ['f', 'n', 's'],
+ sp: ['g/notifications', 'a', 's/client/T7K3RFA1M', 'm/mail/u/1'],
+ },
+
// Default search suggestions for the specified queries.
suggestionDefaults: {
'.': ['./inout/in/invoices/create'],
@@ -875,8 +902,8 @@
}
getSuggestions(rawQuery) {
- if (this._isTooShort(rawQuery)) return Promise.resolve([]);
const { query } = this._parseQuery(rawQuery);
+ if (this._isTooShort(rawQuery) || !query) return Promise.resolve([]);
return new Promise((resolve) => {
const callback = 'autocompleteCallback';
@@ -1150,60 +1177,95 @@
this._commands = options.commands;
this._searchDelimiter = options.searchDelimiter;
this._pathDelimiter = options.pathDelimiter;
+ this._scripts = options.scripts;
this._protocolRegex = /^[a-zA-Z]+:\/\//i;
this._urlRegex = /^((https?:\/\/)?[\w-]+(\.[\w-]+)+\.?(:\d+)?(\/\S*)?)$/i;
- this._cachedQuery = '';
- this._cachedRes = {};
this.parse = this.parse.bind(this);
}
parse(query) {
- if (this._cachedQuery === query) return this._cachedRes;
- this._cachedQuery = query;
- const res = { query: query, split: null };
+ const res = [];
+ res.query = query;
+ res.split = null;
if (this._urlRegex.test(query)) {
const hasProtocol = this._protocolRegex.test(query);
res.redirect = hasProtocol ? query : 'http://' + query;
- } else {
- const trimmed = query.trim();
- const splitSearch = trimmed.split(this._searchDelimiter);
- const splitPath = trimmed.split(this._pathDelimiter);
-
- this._commands.some(({ key, search, url }) => {
- if (query === key) {
- res.key = key;
- res.isKey = true;
- res.redirect = url;
- return true;
- }
-
- if (splitSearch[0] === key && search) {
- res.key = key;
- res.isSearch = true;
- res.split = this._searchDelimiter;
- res.query = QueryParser._shiftAndTrim(splitSearch, res.split);
- res.redirect = QueryParser._prepSearch(url, search, res.query);
- return true;
- }
-
- if (splitPath[0] === key) {
- res.key = key;
- res.isPath = true;
- res.split = this._pathDelimiter;
- res.path = QueryParser._shiftAndTrim(splitPath, res.split);
- res.redirect = QueryParser._prepPath(url, res.path);
- return true;
- }
-
- if (key === '*') {
- res.redirect = QueryParser._prepSearch(url, search, query);
- }
- });
+ res.color = QueryParser._getColorFromUrl(this._commands, res.redirect);
+ return res;
}
+ const trimmed = query.trim();
+ const splitSearch = trimmed.split(this._searchDelimiter);
+ const splitPath = trimmed.split(this._pathDelimiter);
+
+ const isScript = Object.entries(this._scripts).some(([key, script]) => {
+ if (query === key) {
+ res.key = key;
+ res.isKey = true;
+ script.forEach((command) => res.push(this.parse(command)));
+ return true;
+ }
+
+ if (splitSearch[0] === key) {
+ res.key = key;
+ res.isSearch = true;
+ res.split = this._searchDelimiter;
+ res.query = QueryParser._shiftAndTrim(splitSearch, res.split);
+
+ script.forEach((command) =>
+ res.push(this.parse(`${command}${res.split}${res.query}`))
+ );
+
+ return true;
+ }
+
+ if (splitPath[0] === key) {
+ res.key = key;
+ res.split = this._pathDelimiter;
+ res.path = QueryParser._shiftAndTrim(splitPath, res.split);
+
+ script.forEach((command) =>
+ res.push(this.parse(`${command}${this._pathDelimiter}${res.path}`))
+ );
+
+ return true;
+ }
+ });
+
+ if (isScript) return res;
+
+ this._commands.some(({ key, search, url }) => {
+ if (query === key) {
+ res.key = key;
+ res.isKey = true;
+ res.redirect = url;
+ return true;
+ }
+
+ if (splitSearch[0] === key) {
+ res.key = key;
+ res.isSearch = true;
+ res.split = this._searchDelimiter;
+ res.query = QueryParser._shiftAndTrim(splitSearch, res.split);
+ res.redirect = QueryParser._prepSearch(url, search, res.query);
+ return true;
+ }
+
+ if (splitPath[0] === key) {
+ res.key = key;
+ res.split = this._pathDelimiter;
+ res.path = QueryParser._shiftAndTrim(splitPath, res.split);
+ res.redirect = QueryParser._prepPath(url, res.path);
+ return true;
+ }
+
+ if (key === '*') {
+ res.redirect = QueryParser._prepSearch(url, search, query);
+ }
+ });
+
res.color = QueryParser._getColorFromUrl(this._commands, res.redirect);
- this._cachedRes = res;
return res;
}
@@ -1324,8 +1386,8 @@
this._setColorsFromQuery(value);
}
- _redirect(redirect) {
- if (this._newTab) {
+ _redirect(redirect, forceNewTab) {
+ if (this._newTab || forceNewTab) {
window.open(redirect, '_blank', 'noopener noreferrer');
} else {
window.location.href = redirect;
@@ -1354,7 +1416,14 @@
const query = this._inputEl.value;
if (this._suggester) this._suggester.success(query);
this.hide();
- this._redirect(this._parseQuery(query).redirect);
+ const res = this._parseQuery(query);
+
+ if (res.length) {
+ res.forEach((r) => this._redirect(r.redirect, true));
+ return;
+ }
+
+ this._redirect(res.redirect);
}
_submitWithValue(value) {
@@ -1388,6 +1457,7 @@
const queryParser = new QueryParser({
commands,
pathDelimiter: CONFIG.queryPathDelimiter,
+ scripts: CONFIG.scripts,
searchDelimiter: CONFIG.querySearchDelimiter,
});