diff --git a/src/renderer/components/ft-input/ft-input.css b/src/renderer/components/ft-input/ft-input.css
index 799dab09e0153..9587079ad6665 100644
--- a/src/renderer/components/ft-input/ft-input.css
+++ b/src/renderer/components/ft-input/ft-input.css
@@ -219,6 +219,17 @@ body[dir='rtl'] .ft-input-component.search.showClearTextButton:focus-within .inp
block-size: 16px;
}
+.removeButton {
+ text-decoration: none;
+ float: var(--float-right-ltr-rtl-value);
+ font-size: 13px;
+}
+
+.removeButton:hover,
+.removeButton.removeButtonSelected {
+ text-decoration: underline;
+}
+
.hover {
background-color: var(--scrollbar-color-hover);
color: var(--scrollbar-text-color-hover);
diff --git a/src/renderer/components/ft-input/ft-input.js b/src/renderer/components/ft-input/ft-input.js
index 1a7cd3281d5d2..0dea2b751db6d 100644
--- a/src/renderer/components/ft-input/ft-input.js
+++ b/src/renderer/components/ft-input/ft-input.js
@@ -67,6 +67,10 @@ export default defineComponent({
type: Array,
default: null
},
+ canRemoveResults: {
+ type: Boolean,
+ default: false
+ },
showDataWhenEmpty: {
type: Boolean,
default: false
@@ -76,7 +80,7 @@ export default defineComponent({
default: ''
}
},
- emits: ['clear', 'click', 'input'],
+ emits: ['clear', 'click', 'input', 'remove'],
data: function () {
let actionIcon = ['fas', 'search']
if (this.forceActionButtonIconName !== null) {
@@ -96,6 +100,8 @@ export default defineComponent({
// As the text input box should be empty
clearTextButtonExisting: false,
clearTextButtonVisible: false,
+ removeButtonSelectedIndex: -1,
+ removalMade: false,
actionButtonIconName: actionIcon
}
},
@@ -166,6 +172,7 @@ export default defineComponent({
this.searchState.showOptions = false
this.searchState.selectedOption = -1
this.searchState.keyboardSelectedOptionIndex = -1
+ this.removeButtonSelectedIndex = -1
this.$emit('input', this.inputData)
this.$emit('click', this.inputData, { event: e })
},
@@ -247,19 +254,34 @@ export default defineComponent({
},
handleOptionClick: function (index) {
+ if (this.removeButtonSelectedIndex !== -1) {
+ this.handleRemoveClick(index)
+ return
+ }
this.searchState.showOptions = false
this.inputData = this.visibleDataList[index]
this.$emit('input', this.inputData)
this.handleClick()
},
+ handleRemoveClick: function (index) {
+ if (!this.canRemoveResults) { return }
+
+ // keep focus in input even if removed "Remove" button was clicked
+ this.$refs.input.focus()
+ this.removalMade = true
+ this.$emit('remove', this.visibleDataList[index])
+ },
+
/**
* @param {KeyboardEvent} event
*/
handleKeyDown: function (event) {
if (event.key === 'Enter') {
// Update Input box value if enter key was pressed and option selected
- if (this.searchState.selectedOption !== -1) {
+ if (this.removeButtonSelectedIndex !== -1) {
+ this.handleRemoveClick(this.removeButtonSelectedIndex)
+ } else if (this.searchState.selectedOption !== -1) {
this.searchState.showOptions = false
event.preventDefault()
this.inputData = this.getTextForArrayAtIndex(this.visibleDataList, this.searchState.selectedOption)
@@ -274,7 +296,7 @@ export default defineComponent({
if (this.visibleDataList.length === 0) { return }
this.searchState.showOptions = true
- const isArrow = event.key === 'ArrowDown' || event.key === 'ArrowUp'
+ const isArrow = event.key === 'ArrowDown' || event.key === 'ArrowUp' || event.key === 'ArrowLeft' || event.key === 'ArrowRight'
if (!isArrow) {
const selectedOptionValue = this.searchStateKeyboardSelectedOptionValue
// Keyboard selected & is char
@@ -288,17 +310,33 @@ export default defineComponent({
}
event.preventDefault()
- if (event.key === 'ArrowDown') {
- this.searchState.selectedOption++
- } else if (event.key === 'ArrowUp') {
- this.searchState.selectedOption--
+
+ if (event.key === 'ArrowRight') {
+ this.removeButtonSelectedIndex = this.searchState.selectedOption
+ }
+
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
+ const newIndex = this.searchState.selectedOption + (event.key === 'ArrowDown' ? 1 : -1)
+ this.updateSelectedOptionIndex(newIndex)
+
+ // reset removal
+ this.removeButtonSelectedIndex = -1
+ }
+
+ if (this.searchState.selectedOption !== -1 && event.key === 'ArrowRight') {
+ this.removeButtonSelectedIndex = this.searchState.selectedOption
}
+ },
+
+ updateSelectedOptionIndex: function (index) {
+ this.searchState.selectedOption = index
// Allow deselecting suggestion
if (this.searchState.selectedOption < -1) {
this.searchState.selectedOption = this.visibleDataList.length - 1
} else if (this.searchState.selectedOption > this.visibleDataList.length - 1) {
this.searchState.selectedOption = -1
}
+
// Update displayed value
this.searchState.keyboardSelectedOptionIndex = this.searchState.selectedOption
},
@@ -313,8 +351,14 @@ export default defineComponent({
updateVisibleDataList: function () {
// Reset selected option before it's updated
- this.searchState.selectedOption = -1
- this.searchState.keyboardSelectedOptionIndex = -1
+ if (!this.removalMade || this.searchState.selectedOption >= this.dataList.length) {
+ this.searchState.selectedOption = -1
+ this.searchState.keyboardSelectedOptionIndex = -1
+ this.removeButtonSelectedIndex = -1
+ }
+
+ this.removalMade = false
+
if (this.inputData.trim() === '') {
this.visibleDataList = this.dataList
return
diff --git a/src/renderer/components/ft-input/ft-input.vue b/src/renderer/components/ft-input/ft-input.vue
index 8a8429bda9861..a0046ada87c23 100644
--- a/src/renderer/components/ft-input/ft-input.vue
+++ b/src/renderer/components/ft-input/ft-input.vue
@@ -82,14 +82,25 @@
:class="{ hover: searchState.selectedOption === index }"
@click="handleOptionClick(index)"
@mouseenter="searchState.selectedOption = index"
- @mouseleave="searchState.selectedOption = -1"
+ @mouseleave="searchState.selectedOption = -1; removeButtonSelectedIndex = -1"
>
- {{ entry }}
+ {{ entry }}
+
+ {{ $t('Search Bar.Remove') }}
+
diff --git a/src/renderer/components/top-nav/top-nav.js b/src/renderer/components/top-nav/top-nav.js
index 57f8dd774aca5..a46c9eddf7c6f 100644
--- a/src/renderer/components/top-nav/top-nav.js
+++ b/src/renderer/components/top-nav/top-nav.js
@@ -1,5 +1,5 @@
import { defineComponent } from 'vue'
-import { mapActions } from 'vuex'
+import { mapActions, mapMutations } from 'vuex'
import FtInput from '../ft-input/ft-input.vue'
import FtProfileSelector from '../ft-profile-selector/ft-profile-selector.vue'
import FtIconButton from '../ft-icon-button/ft-icon-button.vue'
@@ -121,17 +121,21 @@ export default defineComponent({
)
},
+ usingSearchHistoryResults: function () {
+ return this.lastSuggestionQuery === ''
+ },
+
// show latest search history when the search bar is empty
activeDataList: function () {
if (!this.enableSearchSuggestions) {
return
}
- return this.lastSuggestionQuery === '' ? this.$store.getters.getLatestUniqueSearchHistoryNames : this.searchSuggestionsDataList
+ return this.usingSearchHistoryResults ? this.$store.getters.getLatestUniqueSearchHistoryNames : this.searchSuggestionsDataList
},
searchResultIcon: function () {
- return this.lastSuggestionQuery === '' ? ['fas', 'clock-rotate-left'] : ['fas', 'magnifying-glass']
- }
+ return this.usingSearchHistoryResults ? ['fas', 'clock-rotate-left'] : ['fas', 'magnifying-glass']
+ },
},
watch: {
$route: function () {
@@ -424,10 +428,19 @@ export default defineComponent({
this.navigationHistoryDropdownActiveEntry.label = value
}
},
+ removeSearchHistoryEntryInDbAndCache(query) {
+ this.removeSearchHistoryEntry(query)
+ this.removeFromSessionSearchHistory(query)
+ },
...mapActions([
'getYoutubeUrlInfo',
+ 'removeSearchHistoryEntry',
'showSearchFilters'
+ ]),
+
+ ...mapMutations([
+ 'removeFromSessionSearchHistory'
])
}
})
diff --git a/src/renderer/components/top-nav/top-nav.vue b/src/renderer/components/top-nav/top-nav.vue
index b5c6861f06f25..e44a23b1beda5 100644
--- a/src/renderer/components/top-nav/top-nav.vue
+++ b/src/renderer/components/top-nav/top-nav.vue
@@ -97,9 +97,11 @@
:spellcheck="false"
:show-clear-text-button="true"
:show-data-when-empty="true"
+ :can-remove-results="usingSearchHistoryResults"
@input="getSearchSuggestionsDebounce"
@click="goToSearch"
@clear="() => lastSuggestionQuery = ''"
+ @remove="removeSearchHistoryEntryInDbAndCache"
/>
{
- return searchHistoryEntry._id === _id
- })
-
- state.searchHistoryEntries.splice(i, 1)
+ state.searchHistoryEntries = state.searchHistoryEntries.filter((searchHistoryEntry) => searchHistoryEntry._id !== _id)
},
removeSearchHistoryEntriesFromList(state, ids) {
diff --git a/src/renderer/store/modules/utils.js b/src/renderer/store/modules/utils.js
index b76cb04811fc1..c5b9075487899 100644
--- a/src/renderer/store/modules/utils.js
+++ b/src/renderer/store/modules/utils.js
@@ -830,6 +830,10 @@ const mutations = {
vueSet(state.deArrowCache, payload.videoId, payload)
},
+ removeFromSessionSearchHistory (state, query) {
+ state.sessionSearchHistory = state.sessionSearchHistory.filter((search) => search.query !== query)
+ },
+
addToSessionSearchHistory (state, payload) {
const sameSearch = state.sessionSearchHistory.findIndex((search) => {
return search.query === payload.query && searchFiltersMatch(payload.searchSettings, search.searchSettings)
diff --git a/static/locales/en-US.yaml b/static/locales/en-US.yaml
index 4e0a172cd9803..38b0aed05d7e3 100644
--- a/static/locales/en-US.yaml
+++ b/static/locales/en-US.yaml
@@ -63,6 +63,7 @@ Global:
Search / Go to URL: Search / Go to URL
Search Bar:
Clear Input: Clear Input
+ Remove: Remove
Search character limit: Search query is over the {searchCharacterLimit} character limit
Search Listing:
Label: