Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions config/vufind/searchbox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ alphaBrowseGroup = false
; applied to the option (see group[] setting below; set to false for no label).
defaultGroupLabel = false

; Set to true to enable the voice search.
enableVoiceSearch = false

; This section controls the "combined handlers" drop-down. It must contain groups
; of settings with the following keys:
;
Expand Down
1 change: 1 addition & 0 deletions languages/cs.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1436,6 +1436,7 @@ sort_year = "Podle data sestupně"
sort_year_asc = "Podle data vzestupně"
Source = "Zdroj"
Source Title = "Název zdroje"
speech_search_button = "Hlasové vyhledávání"
spell_expand_alt = "Rozšířit vyhledávání"
spell_suggest = "Alternativní vyhledávání"
Staff View = "UNIMARC/MARC"
Expand Down
1 change: 1 addition & 0 deletions languages/en.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1459,6 +1459,7 @@ sort_year = "Date Descending"
sort_year_asc = "Date Ascending"
Source = "Source"
Source Title = "Source Title"
speech_search_button = "Voice search"
spell_expand_alt = "Expand Search"
spell_suggest = "Search alternatives"
Staff View = "Staff View"
Expand Down
10 changes: 10 additions & 0 deletions module/VuFind/src/VuFind/View/Helper/Root/SearchBox.php
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,16 @@ public function combinedHandlersActive()
return $this->config['General']['combinedHandlers'] ?? false;
}

/**
* Is voice search enabled?
*
* @return bool
*/
public function voiceSearchEnabled(): bool
{
return (bool)($this->config['General']['enableVoiceSearch'] ?? false);
}

/**
* Helper method: get special character to represent operator in filter.
*
Expand Down
2 changes: 1 addition & 1 deletion themes/bootstrap5/css/compiled.css

Large diffs are not rendered by default.

61 changes: 61 additions & 0 deletions themes/bootstrap5/js/searchbox_speech.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/* global VuFind */

VuFind.register('searchbox_speech', function SearchboxSpeech() {
let _recognition = null;
let _isListening = false;

/**
* Initialize speech recognition on the search box.
*/
function init() {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SpeechRecognition) {
return;
}

const textInput = document.getElementById('searchForm_lookfor');
const micButton = document.getElementById('searchForm-speech');
if (!textInput || !micButton) {
return;
}

_recognition = new SpeechRecognition();
_recognition.continuous = false;
_recognition.interimResults = false;
_recognition.lang = document.documentElement.lang || 'en';

_recognition.onstart = () => {
_isListening = true;
micButton.classList.add('listening');
micButton.setAttribute('aria-pressed', 'true');
};
_recognition.onend = () => {
_isListening = false;
micButton.classList.remove('listening');
micButton.setAttribute('aria-pressed', 'false');
};
_recognition.onerror = () => {
_isListening = false;
micButton.classList.remove('listening');
micButton.setAttribute('aria-pressed', 'false');
};
_recognition.onresult = (event) => {
textInput.value = event.results[0][0].transcript;
textInput.dispatchEvent(new Event('input'));
};

micButton.classList.remove('hidden');
textInput.classList.add('with-speech');

micButton.addEventListener('click', (e) => {
e.preventDefault();
if (_isListening) {
_recognition.stop();
} else {
_recognition.start();
}
});
}

return { init };
});
28 changes: 28 additions & 0 deletions themes/bootstrap5/scss/components/search.scss
Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,11 @@ table.search-history-table {
}
}

/* Adjust padding when speech button is visible */
#searchForm_lookfor.with-speech {
padding-right: 70px;
}

#searchForm_controls {
position: absolute;
top: 0;
Expand All @@ -976,9 +981,32 @@ table.search-history-table {
font-size: 1em;
cursor: pointer;
}

#searchForm-speech {
width: 2em;
height: 2em;
border: none;
border-radius: 50%;
background-color: $gray-lighter;
margin: 0;
padding: 2px;
font-size: 1em;
cursor: pointer;

&.listening {
background-color: var(--bs-danger, #dc3545);
color: white;
animation: searchbox-speech-pulse 1s infinite;
}
}
}
}

@keyframes searchbox-speech-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}

/* Hide virtual keyboard on narrow screens */
@media (max-width: 400px) {
.keyboard-selection {
Expand Down
11 changes: 11 additions & 0 deletions themes/bootstrap5/templates/search/searchbox.phtml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,17 @@
title="<?=$this->transEscAttr('searchform_reset_button')?>"
aria-label="<?=$this->transEscAttr('searchform_reset_button')?>"
><?=$this->icon('ui-reset-search');?></button>
<?php if ($this->searchbox()->voiceSearchEnabled()): ?>
<button
id="searchForm-speech"
class="searchForm-speech hidden"
type="button"
title="<?=$this->transEscAttr('speech_search_button')?>"
aria-label="<?=$this->transEscAttr('speech_search_button')?>"
aria-pressed="false"
><?=$this->icon('microphone');?></button>
<?php $this->assetManager()->appendScriptLink('searchbox_speech.js'); ?>
<?php endif; ?>
<?php if (!empty($keyboardLayouts)): ?>
<?php
$this->assetManager()->appendScriptLink('vendor/simple-keyboard/index.js');
Expand Down
1 change: 1 addition & 0 deletions themes/bootstrap5/theme.config.php
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@
'keyboard' => 'FontAwesome:keyboard fa-regular',
'lightbox-close' => 'FontAwesome:times',
'link' => 'FontAwesome:link',
'microphone' => 'FontAwesome:microphone',
'more' => 'FontAwesome:chevron-circle-right',
'more-rtl' => 'FontAwesome:chevron-circle-left',
'my-account' => 'FontAwesome:circle-user fa-regular',
Expand Down
2 changes: 1 addition & 1 deletion themes/local_theme_example/css/compiled.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion themes/sandal5/css/compiled.css

Large diffs are not rendered by default.

Loading