mirror of
https://github.com/Lissy93/dashy.git
synced 2025-12-06 16:43:13 +01:00
feat(search): Enter opens exact-match item; left/right navigate; add hint
This commit is contained in:
parent
bf84199b71
commit
e8f7cc11d6
1 changed files with 115 additions and 6 deletions
|
|
@ -16,10 +16,10 @@
|
||||||
v-on:input="userIsTypingSomething"
|
v-on:input="userIsTypingSomething"
|
||||||
@keydown.esc="clearFilterInput"
|
@keydown.esc="clearFilterInput"
|
||||||
/>
|
/>
|
||||||
<p
|
<p v-if="showOpenItemNote" class="web-search-note">
|
||||||
v-if="(!searchPrefs.disableWebSearch) && input.length > 0"
|
Press Enter to open the item
|
||||||
class="web-search-note"
|
</p>
|
||||||
>
|
<p v-else-if="showWebSearchNote" class="web-search-note">
|
||||||
{{ $t('search.enter-to-search-web') }}
|
{{ $t('search.enter-to-search-web') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -120,6 +120,34 @@ export default {
|
||||||
searchPrefs() {
|
searchPrefs() {
|
||||||
return this.$store.getters.webSearch || {};
|
return this.$store.getters.webSearch || {};
|
||||||
},
|
},
|
||||||
|
showOpenItemNote() {
|
||||||
|
// Show when Enter will open an exact match item (advanced on + matches)
|
||||||
|
const input = (this.input || '').trim();
|
||||||
|
if (input.length === 0) return false;
|
||||||
|
// If Go-to-Link would intercept, don't show
|
||||||
|
if (this.goToLinkEnabled && this.isUrlLike(input)) return false;
|
||||||
|
const adv = this.$store.getters.advancedSearch || {};
|
||||||
|
if (!adv.enabled) return false;
|
||||||
|
const exactItems = this.getExactMatchItemsList();
|
||||||
|
return !!(exactItems && exactItems.length > 0);
|
||||||
|
},
|
||||||
|
showWebSearchNote() {
|
||||||
|
// Only show hint when pressing Enter will actually search the web
|
||||||
|
const input = (this.input || '').trim();
|
||||||
|
if (input.length === 0) return false;
|
||||||
|
// If Go-to-Link would intercept, then Enter does not search web
|
||||||
|
if (this.goToLinkEnabled && this.isUrlLike(input)) return false;
|
||||||
|
// If web search is disabled, don't show
|
||||||
|
if (this.searchPrefs && this.searchPrefs.disableWebSearch) return false;
|
||||||
|
// If advanced search is enabled and there are exact matches,
|
||||||
|
// Enter opens tile instead of web search
|
||||||
|
const adv = this.$store.getters.advancedSearch || {};
|
||||||
|
if (adv.enabled) {
|
||||||
|
const exactItems = this.getExactMatchItemsList();
|
||||||
|
if (exactItems && exactItems.length > 0) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
goToLinkEnabled: {
|
goToLinkEnabled: {
|
||||||
get() {
|
get() {
|
||||||
return this.$store.getters.goToLinkEnabled;
|
return this.$store.getters.goToLinkEnabled;
|
||||||
|
|
@ -156,6 +184,32 @@ export default {
|
||||||
window.removeEventListener('keydown', this.handleKeyPress);
|
window.removeEventListener('keydown', this.handleKeyPress);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
// Selection utilities for Advanced Search tile navigation (Exact Match only)
|
||||||
|
getExactMatchItemsList() {
|
||||||
|
const container = document.querySelector('.exact-match-block');
|
||||||
|
if (!container) return [];
|
||||||
|
return Array.from(container.querySelectorAll('.item'));
|
||||||
|
},
|
||||||
|
setSelectionClass(el) {
|
||||||
|
const items = this.getExactMatchItemsList();
|
||||||
|
items.forEach(i => i.classList.remove('tile--selected'));
|
||||||
|
if (el) el.classList.add('tile--selected');
|
||||||
|
},
|
||||||
|
clearSelectionHighlight() {
|
||||||
|
const items = this.getExactMatchItemsList();
|
||||||
|
items.forEach(i => i.classList.remove('tile--selected'));
|
||||||
|
},
|
||||||
|
updateDefaultSelection() {
|
||||||
|
const adv = this.$store.getters.advancedSearch || {};
|
||||||
|
if (!adv.enabled) return;
|
||||||
|
if (!this.input || this.input.trim().length === 0) { this.clearSelectionHighlight(); return; }
|
||||||
|
const items = this.getExactMatchItemsList();
|
||||||
|
if (!items || items.length === 0) { this.clearSelectionHighlight(); return; }
|
||||||
|
const focused = items.find(i => i === document.activeElement);
|
||||||
|
if (focused) { this.setSelectionClass(focused); return; }
|
||||||
|
const first = items[0];
|
||||||
|
if (first) this.setSelectionClass(first);
|
||||||
|
},
|
||||||
toggleDisableWebSearch(event) {
|
toggleDisableWebSearch(event) {
|
||||||
const value = event.target.checked;
|
const value = event.target.checked;
|
||||||
const newAppConfig = {
|
const newAppConfig = {
|
||||||
|
|
@ -217,8 +271,41 @@ export default {
|
||||||
// Number key pressed, check if user has a custom binding
|
// Number key pressed, check if user has a custom binding
|
||||||
this.handleHotKey(key);
|
this.handleHotKey(key);
|
||||||
} else if (keyCode >= 37 && keyCode <= 40) {
|
} else if (keyCode >= 37 && keyCode <= 40) {
|
||||||
// Arrow key pressed - start navigation
|
// Arrow key pressed
|
||||||
|
const adv = this.$store.getters.advancedSearch || {};
|
||||||
|
if (adv.enabled) {
|
||||||
|
const itemsArr = this.getExactMatchItemsList();
|
||||||
|
if (!itemsArr || itemsArr.length === 0) {
|
||||||
|
// No exact matches -> fall back to default navigation
|
||||||
this.akn.arrowNavigation(keyCode);
|
this.akn.arrowNavigation(keyCode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const focusedEl = itemsArr.find(i => i === document.activeElement);
|
||||||
|
let idx = focusedEl ? itemsArr.indexOf(focusedEl) : -1;
|
||||||
|
const move = (delta) => {
|
||||||
|
if (idx === -1) idx = 0; // no focus yet -> first
|
||||||
|
else idx = (idx + delta + itemsArr.length) % itemsArr.length; // wrap
|
||||||
|
const el = itemsArr[idx];
|
||||||
|
if (el) {
|
||||||
|
el.focus();
|
||||||
|
el.scrollIntoView({ block: 'nearest', inline: 'nearest' });
|
||||||
|
this.setSelectionClass(el);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (keyCode === 37) { // Left
|
||||||
|
event.preventDefault();
|
||||||
|
move(-1);
|
||||||
|
} else if (keyCode === 39) { // Right
|
||||||
|
event.preventDefault();
|
||||||
|
move(1);
|
||||||
|
} else if (keyCode === 38 || keyCode === 40) {
|
||||||
|
// For now, ignore up/down in advanced mode to keep UX simple
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Default navigation behavior
|
||||||
|
this.akn.arrowNavigation(keyCode);
|
||||||
|
}
|
||||||
} else if (keyCode === 27) {
|
} else if (keyCode === 27) {
|
||||||
// Esc key pressed - reset form
|
// Esc key pressed - reset form
|
||||||
this.clearFilterInput();
|
this.clearFilterInput();
|
||||||
|
|
@ -227,6 +314,7 @@ export default {
|
||||||
/* Emmits users's search term up to parent */
|
/* Emmits users's search term up to parent */
|
||||||
userIsTypingSomething() {
|
userIsTypingSomething() {
|
||||||
this.$emit('user-is-searchin', this.input);
|
this.$emit('user-is-searchin', this.input);
|
||||||
|
this.$nextTick(() => this.updateDefaultSelection());
|
||||||
},
|
},
|
||||||
/* Resets everything to initial state, when user is finished */
|
/* Resets everything to initial state, when user is finished */
|
||||||
clearFilterInput() {
|
clearFilterInput() {
|
||||||
|
|
@ -234,6 +322,7 @@ export default {
|
||||||
this.userIsTypingSomething(); // Emmit new empty value
|
this.userIsTypingSomething(); // Emmit new empty value
|
||||||
document.activeElement.blur(); // Remove focus
|
document.activeElement.blur(); // Remove focus
|
||||||
this.akn.resetIndex(); // Reset current element index
|
this.akn.resetIndex(); // Reset current element index
|
||||||
|
this.clearSelectionHighlight();
|
||||||
},
|
},
|
||||||
/* If configured, launch specific app when hotkey pressed */
|
/* If configured, launch specific app when hotkey pressed */
|
||||||
handleHotKey(key) {
|
handleHotKey(key) {
|
||||||
|
|
@ -273,6 +362,26 @@ export default {
|
||||||
this.clearFilterInput();
|
this.clearFilterInput();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 1.5 Advanced Search override: if enabled and user has typed something,
|
||||||
|
// and there are matched tiles on the page, pressing Enter should open the
|
||||||
|
// selected tile (focused) or the first matched tile instead of web search
|
||||||
|
const adv = this.$store.getters.advancedSearch || {};
|
||||||
|
if ((adv.enabled === true) && input.length > 0) {
|
||||||
|
const items = this.getExactMatchItemsList();
|
||||||
|
if (!items || items.length === 0) {
|
||||||
|
// No exact matches -> allow normal web search flow below
|
||||||
|
} else {
|
||||||
|
const focused = items.find(i => i === document.activeElement);
|
||||||
|
const first = items[0];
|
||||||
|
const targetEl = focused || first;
|
||||||
|
if (targetEl) {
|
||||||
|
targetEl.click();
|
||||||
|
this.clearFilterInput();
|
||||||
|
this.clearSelectionHighlight();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// 2. If not URL-like, or "Go to Link" is disabled, only search if web search is enabled
|
// 2. If not URL-like, or "Go to Link" is disabled, only search if web search is enabled
|
||||||
if (!searchPrefs.disableWebSearch) {
|
if (!searchPrefs.disableWebSearch) {
|
||||||
const bangList = { ...defaultSearchBangs, ...(searchPrefs.searchBangs || {}) };
|
const bangList = { ...defaultSearchBangs, ...(searchPrefs.searchBangs || {}) };
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue