From 55beeaf010c7f817525390c2415afd5d5d4a787a Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 11 Sep 2021 19:39:51 +0100 Subject: [PATCH 1/8] :sparkles: Adds helper functions for search bangs --- src/utils/Search.js | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/utils/Search.js b/src/utils/Search.js index e50b88bf..f03078bc 100644 --- a/src/utils/Search.js +++ b/src/utils/Search.js @@ -1,6 +1,7 @@ /* Dashy: Licensed under MIT, (C) Alicia Sykes 2021 */ /* Tile filtering utility */ +import ErrorHandler from '@/utils/ErrorHandler'; /** * Extracts the site name from domain @@ -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 { @@ -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(); +}; From 0e8eb992fb036632f1e65e81180c15741cbff361 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 11 Sep 2021 19:46:53 +0100 Subject: [PATCH 2/8] :truck: Refactor with new search util --- src/views/Home.vue | 4 ++-- src/views/Minimal.vue | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/views/Home.vue b/src/views/Home.vue index a157ddc9..097a747f 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -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 { @@ -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) { diff --git a/src/views/Minimal.vue b/src/views/Minimal.vue index f8b47506..cf2b9aa9 100644 --- a/src/views/Minimal.vue +++ b/src/views/Minimal.vue @@ -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'; @@ -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) { From 688dece9155b423b6aa62eebfedc69d48ad2807f Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 11 Sep 2021 19:47:54 +0100 Subject: [PATCH 3/8] :sparkles: Re: #206 - Implements support for search bangs --- src/components/Settings/SearchBar.vue | 44 +++++++++++++++------------ src/utils/defaults.js | 11 +++++++ 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/src/components/Settings/SearchBar.vue b/src/components/Settings/SearchBar.vue index 7979e3b3..4d8b288e 100644 --- a/src/components/Settings/SearchBar.vue +++ b/src/components/Settings/SearchBar.vue @@ -9,7 +9,7 @@ :placeholder="$t('search.search-placeholder')" v-on:input="userIsTypingSomething" @keydown.esc="clearFilterInput" /> -

+

{{ $t('search.enter-to-search-web') }}

@@ -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', @@ -41,12 +47,8 @@ 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() { @@ -55,7 +57,7 @@ export default { const { key, keyCode } = event; /* If a modal is open, then do nothing */ if (!this.active) return; - if (/^[a-zA-Z]$/.test(key) && currentElem !== 'filter-tiles') { + if (/^[/:!a-zA-Z]$/.test(key) && currentElem !== 'filter-tiles') { /* Letter key pressed - start searching */ if (this.$refs.filter) this.$refs.filter.focus(); this.userIsTypingSomething(); @@ -107,22 +109,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 + let searchUrl = searchBang || searchEngine; + searchUrl = findUrlForSearchEngine((searchBang || searchEngine), 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); } }, }, diff --git a/src/utils/defaults.js b/src/utils/defaults.js index 4ed1c35c..40365210 100644 --- a/src/utils/defaults.js +++ b/src/utils/defaults.js @@ -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'], From 0f651b281a78704ffb948e9e8fca3c69daeb096a Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 11 Sep 2021 20:11:51 +0100 Subject: [PATCH 4/8] :green_heart: Fix alert, remove event listener when component destroyed --- src/components/Settings/SearchBar.vue | 32 +++++++++++++++++---------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/components/Settings/SearchBar.vue b/src/components/Settings/SearchBar.vue index 4d8b288e..6174f9a7 100644 --- a/src/components/Settings/SearchBar.vue +++ b/src/components/Settings/SearchBar.vue @@ -52,28 +52,34 @@ export default { }, }, 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); @@ -85,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) => { @@ -93,6 +100,7 @@ export default { } }); }, + /* Launch search results, with users desired opening method */ launchWebSearch(url, method) { switch (method) { case 'newtab': @@ -120,8 +128,8 @@ export default { const searchBang = getSearchEngineFromBang(this.input, bangList); const searchEngine = searchPrefs.searchEngine || defaultSearchEngine; // Use either search bang, or preffered search engine - let searchUrl = searchBang || searchEngine; - searchUrl = findUrlForSearchEngine((searchBang || searchEngine), searchEngineUrls); + 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); From 4525f86281b5051215fb95f114464598c3ca6e90 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 11 Sep 2021 20:36:33 +0100 Subject: [PATCH 5/8] :card_file_box: Adds searchBangs to schema and docs --- docs/configuring.md | 1 + src/utils/ConfigSchema.json | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/docs/configuring.md b/docs/configuring.md index 447777c5..283d1f36 100644 --- a/docs/configuring.md +++ b/docs/configuring.md @@ -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)** diff --git a/src/utils/ConfigSchema.json b/src/utils/ConfigSchema.json index 1e88e49c..244e8168 100644 --- a/src/utils/ConfigSchema.json +++ b/src/utils/ConfigSchema.json @@ -260,6 +260,11 @@ ], "default": "newtab", "description": "Set where you would like search results to open to" + }, + "searchBangs": { + "type": "object", + "additionalProperties": true, + "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" } } }, From 2a7bf05cc3461e9a6ccd984b659308dacfefae87 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 11 Sep 2021 20:37:13 +0100 Subject: [PATCH 6/8] :memo: Adds docs for using search bangs --- docs/searching.md | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/docs/searching.md b/docs/searching.md index 1b8b6843..870618d1 100644 --- a/docs/searching.md +++ b/docs/searching.md @@ -50,13 +50,13 @@ In the above example, pressing 2 will launch Bookstack. Or hitting ⏎. 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: @@ -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 From b901a04d4a18288fe99f9d4df350dc5f17061011 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 11 Sep 2021 21:05:32 +0100 Subject: [PATCH 7/8] :card_file_box: Adds scema doc for searchBangs --- src/utils/ConfigSchema.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/utils/ConfigSchema.json b/src/utils/ConfigSchema.json index 244e8168..d3bf8ff6 100644 --- a/src/utils/ConfigSchema.json +++ b/src/utils/ConfigSchema.json @@ -264,6 +264,12 @@ "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" } } From ee56183ed937d3d258d553b22de7940c4641490e Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 11 Sep 2021 21:09:26 +0100 Subject: [PATCH 8/8] :bookmark: Bumps to V 1.7.6 and updates changelog --- .github/CHANGELOG.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index d87e9e22..4f9db590 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -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 diff --git a/package.json b/package.json index 02f40e3e..ba073aa1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Dashy", - "version": "1.7.5", + "version": "1.7.6", "license": "MIT", "main": "server", "scripts": {