Skip to content

Commit

Permalink
🔀 Merge pull request gchq#224 from Lissy93/FEATURE/multi-search
Browse files Browse the repository at this point in the history
[FEATURE] Multi-Search with Custom Bangs
Fixes gchq#206
  • Loading branch information
Lissy93 authored Sep 11, 2021
2 parents 3e6a1fa + ee56183 commit fd3c043
Show file tree
Hide file tree
Showing 10 changed files with 129 additions and 40 deletions.
3 changes: 3 additions & 0 deletions .github/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## ✨ 1.7.6 - Adds Multi-Search Support with Bangs [PR #224](https://github.com/Lissy93/dashy/pull/224)
- Adds option for user to add custom search bangs, in order to specify search engine/ target app. Re: #206

## 🎨 1.7.5 - Improved Language Detection & UI [PR #223](https://github.com/Lissy93/dashy/pull/223)
- Makes the auto language detection algo smarter
- Improves responsiveness for the language selector form
Expand Down
1 change: 1 addition & 0 deletions docs/configuring.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ For more info, see the **[Authentication Docs](/docs/authentication.md)**
**`searchEngine`** | `string` | _Optional_ | Set the key name for your search engine. Can also use a custom engine by setting this property to `custom`. Currently supported: `duckduckgo`, `google`, `whoogle`, `qwant`, `startpage`, `searx-bar` and `searx-info`. Defaults to `duckduckgo`
**`customSearchEngine`** | `string` | _Optional_ | You can also use a custom search engine, or your own self-hosted instance. This requires `searchEngine: custom` to be set. Then add the URL of your service, with GET query string included here
**`openingMethod`** | `string` | _Optional_ | Set your preferred opening method for search results: `newtab`, `sametab`, `workspace`. Defaults to `newtab`
**`searchBangs`** | `object` | _Optional_ | A key-value-pair set of custom search _bangs_ for redirecting query to a specific app or search engine. The key of each should be the bang you will type (typically starting with `/`, `!` or `:`), and value is the destination, either as a search engine key (e.g. `reddit`) or a URL with search parameters (e.g. `https://en.wikipedia.org/w/?search=`)


**[⬆️ Back to Top](#configuring)**
Expand Down
32 changes: 28 additions & 4 deletions docs/searching.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ In the above example, pressing <kbd>2</kbd> will launch Bookstack. Or hitting <k
## Web Search
It's possible to search the web directly from Dashy, which might be useful if you're using Dashy as your start page. This can be done by typing your query as normal, and then pressing <kbd>⏎</kbd>. Web search options are configured under `appConfig.webSearch`.

#### Setting Search Engine
### Setting Search Engine
Set your default search engine using the `webSearch.searchEngine` property. This defaults to DuckDuckGo. Search engine must be referenced by their key, the following providers are supported:
- [`duckduckgo`](https://duckduckgo.com), [`google`](https://google.com), [`whoogle`](https://whoogle.sdf.org), [`qwant`](https://www.qwant.com), [`startpage`](https://www.startpage.com), [`searx-bar`](https://searx.bar), [`searx-info`](https://searx.info)
- [`searx-tiekoetter`](https://searx.tiekoetter.com), [`searx-bissisoft`](https://searx.bissisoft.com), [`ecosia`](https://www.ecosia.org), [`metager`](https://metager.org/meta), [`swisscows`](https://swisscows.com), [`mojeek`](https://www.mojeek.com)
- [`wikipedia`](https://en.wikipedia.org), [`wolframalpha`](https://www.wolframalpha.com), [`stackoverflow`](https://stackoverflow.com), [`github`](https://github.com), [`reddit`](https://www.reddit.com), [`youtube`](https://youtube.com), [`bbc`](https://www.bbc.co.uk)

#### Using Custom Search Engine
### Using Custom Search Engine
You can also use a custom search engine, that isn't included in the above list (like a self-hosted instance of [Whoogle](https://github.com/benbusby/whoogle-search) or [Searx](https://searx.github.io/searx/)). Set `searchEngine: custom`, and then specify the URL (plus query params) to you're search engine under `customSearchEngine`.

For example:
Expand All @@ -67,10 +67,34 @@ appConfig:
customSearchEngine: 'https://searx.local/search?q='
```

#### Setting Opening Method
### Setting Opening Method
In a similar way to opening apps, you can specify where you would like search results to be opened. This is done under the `openingMethod` attribute, and can be set to either `newtab`, `sametab` or `workspace`. By default results are opened in a new tab.

#### Disabling Web Search
### Using Bangs
An insanely useful feature of DDG is [Bangs](https://duckduckgo.com/bang), where you type a specific character combination at the start of your search query, and it will be redirected the that website, such as '!w Docker' will display the Docker wikipedia page. Dashy has a similar feature, enabling you to define your own custom bangs to redirect search results to a specific app, website or search engine.

This is done under the `searchBangs` property, with a list of key value pairs. The key is what you will type, and the value is the destination, either as an identifier or a URL with query parameters.

For example:

```yaml
appConfig:
webSearch:
searchEngine: 'duckduckgo'
openingMethod: 'newtab'
searchBangs:
/r: reddit
/w: wikipedia
/s: https://whoogle.local/search?q=
/a: https://www.amazon.co.uk/s?k=
':wolf': wolframalpha
':so': stackoverflow
':git': github
```

Note that bangs begging with `!` or `:` must be surrounded them in quotes

### Disabling Web Search
Web search can be disabled, by setting `disableWebSearch`, for example:

```yaml
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "Dashy",
"version": "1.7.5",
"version": "1.7.6",
"license": "MIT",
"main": "server",
"scripts": {
Expand Down
70 changes: 41 additions & 29 deletions src/components/Settings/SearchBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
:placeholder="$t('search.search-placeholder')"
v-on:input="userIsTypingSomething"
@keydown.esc="clearFilterInput" />
<p v-if="webSearchEnabled && input.length > 0" class="web-search-note">
<p v-if="(!searchPrefs.disableWebSearch) && input.length > 0" class="web-search-note">
{{ $t('search.enter-to-search-web') }}
</p>
</div>
Expand All @@ -25,7 +25,13 @@ import router from '@/router';
import ArrowKeyNavigation from '@/utils/ArrowKeyNavigation';
import ErrorHandler from '@/utils/ErrorHandler';
import { getCustomKeyShortcuts } from '@/utils/ConfigHelpers';
import { searchEngineUrls, defaultSearchEngine, defaultSearchOpeningMethod } from '@/utils/defaults';
import { getSearchEngineFromBang, findUrlForSearchEngine, stripBangs } from '@/utils/Search';
import {
searchEngineUrls,
defaultSearchEngine,
defaultSearchOpeningMethod,
searchBangs as defaultSearchBangs,
} from '@/utils/defaults';
export default {
name: 'FilterTile',
Expand All @@ -41,37 +47,39 @@ export default {
};
},
computed: {
webSearchEnabled() {
const { appConfig } = this.config;
if (appConfig && appConfig.webSearch) {
return !appConfig.webSearch.disableWebSearch;
}
return true;
searchPrefs() {
return this.config.appConfig.webSearch || {};
},
},
mounted() {
window.addEventListener('keydown', (event) => {
window.addEventListener('keydown', this.handleKeyPress);
},
beforeDestroy() {
window.removeEventListener('keydown', this.handleKeyPress);
},
methods: {
/* Call correct function dependending on which key is pressed */
handleKeyPress(event) {
const currentElem = document.activeElement.id;
const { key, keyCode } = event;
/* If a modal is open, then do nothing */
const notAlreadySearching = currentElem !== 'filter-tiles';
// If a modal is open, then do nothing
if (!this.active) return;
if (/^[a-zA-Z]$/.test(key) && currentElem !== 'filter-tiles') {
/* Letter key pressed - start searching */
if (/^[/:!a-zA-Z]$/.test(key) && notAlreadySearching) {
// Letter or bang key pressed - start searching
if (this.$refs.filter) this.$refs.filter.focus();
this.userIsTypingSomething();
} else if (/^[0-9]$/.test(key)) {
/* Number key pressed, check if user has a custom binding */
// Number key pressed, check if user has a custom binding
this.handleHotKey(key);
} else if (keyCode >= 37 && keyCode <= 40) {
/* Arrow key pressed - start navigation */
// Arrow key pressed - start navigation
this.akn.arrowNavigation(keyCode);
} else if (keyCode === 27) {
/* Esc key pressed - reset form */
// Esc key pressed - reset form
this.clearFilterInput();
}
});
},
methods: {
},
/* Emmits users's search term up to parent */
userIsTypingSomething() {
this.$emit('user-is-searchin', this.input);
Expand All @@ -83,6 +91,7 @@ export default {
document.activeElement.blur(); // Remove focus
this.akn.resetIndex(); // Reset current element index
},
/* If configured, launch specific app when hotkey pressed */
handleHotKey(key) {
const usersHotKeys = this.getCustomKeyShortcuts();
usersHotKeys.forEach((hotkey) => {
Expand All @@ -91,6 +100,7 @@ export default {
}
});
},
/* Launch search results, with users desired opening method */
launchWebSearch(url, method) {
switch (method) {
case 'newtab':
Expand All @@ -107,22 +117,24 @@ export default {
window.open(url, '_blank');
}
},
/* Launch web search, to correct search engine, passing in users query */
searchSubmitted() {
// Get search preferences from appConfig
const { appConfig } = this.config;
const searchPrefs = appConfig.webSearch || {};
if (this.webSearchEnabled) { // Only proceed if user hasn't disabled web search
const { searchPrefs } = this;
if (!searchPrefs.disableWebSearch) { // Only proceed if user hasn't disabled web search
const bangList = { ...defaultSearchBangs, ...(searchPrefs.searchBangs || {}) };
const openingMethod = searchPrefs.openingMethod || defaultSearchOpeningMethod;
// Get search engine, and make URL
const searchBang = getSearchEngineFromBang(this.input, bangList);
const searchEngine = searchPrefs.searchEngine || defaultSearchEngine;
let searchUrl = searchEngineUrls[searchEngine];
if (!searchUrl) ErrorHandler(`Search engine not found - ${searchEngine}`);
if (searchEngine === 'custom' && searchPrefs.customSearchEngine) {
searchUrl = searchPrefs.customSearchEngine;
// Use either search bang, or preffered search engine
const desiredSearchEngine = searchBang || searchEngine;
let searchUrl = findUrlForSearchEngine(desiredSearchEngine, searchEngineUrls);
if (searchUrl) { // Append search query to URL, and launch
searchUrl += encodeURIComponent(stripBangs(this.input, bangList));
this.launchWebSearch(searchUrl, openingMethod);
this.clearFilterInput();
}
// Append users encoded query onto search URL, and launch
searchUrl += encodeURIComponent(this.input);
this.launchWebSearch(searchUrl, openingMethod);
}
},
},
Expand Down
11 changes: 11 additions & 0 deletions src/utils/ConfigSchema.json
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,17 @@
],
"default": "newtab",
"description": "Set where you would like search results to open to"
},
"searchBangs": {
"type": "object",
"additionalProperties": true,
"examples": [
{
"/r": "reddit",
"!w": "https://whoogle.local/search?q="
}
],
"description": "A KV-pair of custom search bangs. The key should be the shortcut to type, and the value is the search engine, specified either by key or full URL"
}
}
},
Expand Down
31 changes: 29 additions & 2 deletions src/utils/Search.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* Dashy: Licensed under MIT, (C) Alicia Sykes 2021 <https://aliciasykes.com> */

/* Tile filtering utility */
import ErrorHandler from '@/utils/ErrorHandler';

/**
* Extracts the site name from domain
Expand Down Expand Up @@ -35,7 +36,7 @@ const filterHelper = (compareStr, searchStr) => {
* @param {string} searchTerm The users search term
* @returns A filtered array of tiles
*/
const search = (allTiles, searchTerm) => {
export const searchTiles = (allTiles, searchTerm) => {
if (!allTiles) return []; // If no data, then skip
return allTiles.filter((tile) => {
const {
Expand All @@ -49,4 +50,30 @@ const search = (allTiles, searchTerm) => {
});
};

export default search;
/* From a list of search bangs, return the URL associated with it */
export const getSearchEngineFromBang = (searchQuery, bangList) => {
const bangNames = Object.keys(bangList);
const foundBang = bangNames.find((bang) => searchQuery.includes(bang));
return bangList[foundBang];
};

/* For a given search engine key, return the corresponding URL, or throw error */
export const findUrlForSearchEngine = (searchEngine, availableSearchEngines) => {
// If missing search engine, report error return false
if (!searchEngine) { ErrorHandler('No search engine specified'); return undefined; }
// If search engine is already a URL, then return it
if ((/(http|https):\/\/[^]*/).test(searchEngine)) return searchEngine;
// If search engine was found successfully, return the URL
if (availableSearchEngines[searchEngine]) return availableSearchEngines[searchEngine];
// Otherwise, there's been an error, log it and return false
ErrorHandler(`Specified Search Engine was not Found: '${searchEngine}'`);
return undefined;
};

/* Removes all known bangs from a search query */
export const stripBangs = (searchQuery, bangList) => {
const bangNames = Object.keys(bangList || {});
let q = searchQuery;
bangNames.forEach((bang) => { q = q.replace(bang, ''); });
return q.trim();
};
11 changes: 11 additions & 0 deletions src/utils/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,17 @@ module.exports = {
},
defaultSearchEngine: 'duckduckgo',
defaultSearchOpeningMethod: 'newtab',
searchBangs: {
'/b': 'bbc',
'/d': 'duckduckgo',
'/g': 'google',
'/r': 'reddit',
'/w': 'wikipedia',
'/y': 'youtube',
'/gh': 'github',
'/so': 'stackoverflow',
'/wa': 'wolframalpha',
},
/* Available built-in colors for the theme builder */
swatches: [
['#eb5cad', '#985ceb', '#5346f3', '#5c90eb'],
Expand Down
4 changes: 2 additions & 2 deletions src/views/Home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@

import SettingsContainer from '@/components/Settings/SettingsContainer.vue';
import Section from '@/components/LinkItems/Section.vue';
import SearchUtil from '@/utils/Search';
import { searchTiles } from '@/utils/Search';
import Defaults, { localStorageKeys, iconCdns } from '@/utils/defaults';

export default {
Expand Down Expand Up @@ -115,7 +115,7 @@ export default {
},
/* Returns only the tiles that match the users search query */
filterTiles(allTiles, searchTerm) {
return SearchUtil(allTiles, searchTerm);
return searchTiles(allTiles, searchTerm);
},
/* Returns optional section display preferences if available */
getDisplayData(section) {
Expand Down
4 changes: 2 additions & 2 deletions src/views/Minimal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ import MinimalSection from '@/components/MinimalView/MinimalSection.vue';
import MinimalHeading from '@/components/MinimalView/MinimalHeading.vue';
import MinimalSearch from '@/components/MinimalView/MinimalSearch.vue';
import { GetTheme, ApplyLocalTheme, ApplyCustomVariables } from '@/utils/ThemeHelper';
import SearchUtil from '@/utils/Search';
import { searchTiles } from '@/utils/Search';
import Defaults, { localStorageKeys } from '@/utils/defaults';
import ConfigLauncher from '@/components/Settings/ConfigLauncher';
Expand Down Expand Up @@ -123,7 +123,7 @@ export default {
/* Returns only the tiles that match the users search query */
filterTiles(allTiles) {
if (!allTiles) return [];
return SearchUtil(allTiles, this.searchValue);
return searchTiles(allTiles, this.searchValue);
},
/* Update data when modal is open (so that key bindings can be disabled) */
updateModalVisibility(modalState) {
Expand Down

0 comments on commit fd3c043

Please sign in to comment.