diff --git a/plugins/speechrecognition/trumbowyg.speechrecognition.js b/plugins/speechrecognition/trumbowyg.speechrecognition.js index 3fb1f13e..143635b5 100644 --- a/plugins/speechrecognition/trumbowyg.speechrecognition.js +++ b/plugins/speechrecognition/trumbowyg.speechrecognition.js @@ -3,197 +3,194 @@ * Speech recognition plugin for Trumbowyg * http://alex-d.github.com/Trumbowyg * =========================================================== - * Author : Tobias Rohde + * Authors : + * - Tobias Rohde + * - Alexandre Demode (Alex-D) * Website : tobiasrohde.de */ (function ($) { 'use strict'; const defaultOptions = { - lang: 'en-GB' + lang: 'en-US' }; - const iconWrap = $(document.createElementNS('http://www.w3.org/2000/svg', 'svg')); - let btnElement = null; - let editor = null; - let finalText = ''; - let recognizing = false; - const SpeechRecognition = window.webkitSpeechRecognition || window.SpeechRecognition; - const recognition = new SpeechRecognition(); - recognition.continuous = true; - recognition.interimResults = true; + function buildButtonDef(trumbowyg) { + let btnElement = null; + let isRecognizing = false; + let $resultTextParagraph = null; - recognition.onstart = function() { - recognizing = true; - btnElement.style.fill='red'; - }; + const recognition = new SpeechRecognition(); - recognition.onerror = function() { - recognizing = false; - btnElement.style.fill='#222'; - }; + recognition.continuous = true; + recognition.interimResults = true; + recognition.maxAlternatives = 1; // We only read the first - recognition.onend = function() { - recognizing = false; - btnElement.style.fill='#222'; - }; + recognition.onstart = function () { + isRecognizing = true; + btnElement.style.color = '#e71d36'; + }; - recognition.onresult = function(event) { - let interimText = ''; - if (typeof(event.results) === 'undefined') { - recognition.onend = null; - recognition.stop(); - return; - } - for (let i = event.resultIndex; i < event.results.length; i+=1) { - if (event.results[i].isFinal) { - finalText += event.results[i][0].transcript + '
'; - editor.html(finalText); - } else { - interimText += event.results[i][0].transcript; - editor.html(finalText + interimText); - } - } - }; + recognition.onerror = function () { + isRecognizing = false; + btnElement.style.removeProperty('color'); + }; + + recognition.onend = function () { + isRecognizing = false; + btnElement.style.removeProperty('color'); + }; + + recognition.onresult = function (event) { + const resultText = [...event.results].map((result) => { + return result[0].transcript + (result.isFinal ? '
' : ''); + }).join(''); + $resultTextParagraph.html(resultText); + trumbowyg.range.setEndAfter($resultTextParagraph[0]); + trumbowyg.range.collapse(); + trumbowyg.syncCode(); + }; - function buildButtonDef (trumbowyg) { return { + isSupported, fn: function () { - if (recognizing) { + if (isRecognizing) { recognition.stop(); return; } - // I don't know it there's a more elegant way to access the speech recognition button. - btnElement = document.body.querySelector(`#${trumbowyg.$c[0].id}`).parentElement.querySelector('.trumbowyg-speechrecognition-button').firstChild; - editor = trumbowyg; - finalText = ''; - recognition.lang = trumbowyg.o.plugins.speechrecognition.lang; - recognition.start(); - btnElement.style.fill='red'; + // Get the actual button to allow to switch his color + btnElement = trumbowyg.$btnPane.find('.' + trumbowyg.o.prefix + 'speechrecognition-button svg')[0]; + + // Create a container if needed in which we will put the recognized text + trumbowyg.$ed.focus(); + setTimeout(() => { + trumbowyg.saveRange(); + if ( + trumbowyg.range.startContainer === trumbowyg.range.endContainer && + trumbowyg.range.startContainer.nodeName === 'P' && + trumbowyg.range.startContainer.innerText.trim() === '' + ) { + $resultTextParagraph = $(trumbowyg.range.startContainer); + } else { + $resultTextParagraph = $('

'); + trumbowyg.range.deleteContents(); + trumbowyg.range.insertNode($resultTextParagraph[0]); + } + + // Set up the recognition + recognition.lang = trumbowyg.o.plugins.speechRecognition.lang; + recognition.start(); + }); } }; } - function buildButtonIcon() { - if ($('#trumbowyg-speechrecognition').length > 0) { - return; - } - - iconWrap.addClass('trumbowyg-icons'); - - // Mic icon from Remix Icon - https://remixicon.com/ - iconWrap.html(` - - - - `).appendTo(document.body); + function isSupported() { + return SpeechRecognition !== undefined; } $.extend(true, $.trumbowyg, { langs: { az: { - speechrecognition: 'Nitqin tanınması' + speechRecognition: 'Nitqin tanınması' }, bg: { - speechrecognition: 'Разпознаване на реч' + speechRecognition: 'Разпознаване на реч' }, by: { - speechrecognition: 'Распазнаванне маўлення' + speechRecognition: 'Распазнаванне маўлення' }, ca: { - speechrecognition: 'Reconeixement de veu' + speechRecognition: 'Reconeixement de veu' }, cs: { - speechrecognition: 'Rozpoznávání řeči' + speechRecognition: 'Rozpoznávání řeči' }, da: { - speechrecognition: 'Talegenkendelse' + speechRecognition: 'Talegenkendelse' }, de: { - speechrecognition: 'Spracherkennung' + speechRecognition: 'Spracherkennung' }, el: { - speechrecognition: 'Αναγνώριση ομιλίας' + speechRecognition: 'Αναγνώριση ομιλίας' }, en: { - speechrecognition: 'Speech recognition' + speechRecognition: 'Speech recognition' }, es: { - speechrecognition: 'Reconocimiento de voz' + speechRecognition: 'Reconocimiento de voz' }, et: { - speechrecognition: 'Kõnetuvastus' + speechRecognition: 'Kõnetuvastus' }, fi: { - speechrecognition: 'Puheentunnistus' + speechRecognition: 'Puheentunnistus' }, fr: { - speechrecognition: 'Reconnaissance vocale' + speechRecognition: 'Reconnaissance vocale' }, hr: { - speechrecognition: 'Prepoznavanje govora' + speechRecognition: 'Prepoznavanje govora' }, hu: { - speechrecognition: 'Beszédfelismerés' + speechRecognition: 'Beszédfelismerés' }, it: { - speechrecognition: 'Riconoscimento vocale' + speechRecognition: 'Riconoscimento vocale' }, lt: { - speechrecognition: 'Kalbos atpažinimas' + speechRecognition: 'Kalbos atpažinimas' }, nb: { - speechrecognition: 'Talegjenkjenning' + speechRecognition: 'Talegjenkjenning' }, nl: { - speechrecognition: 'Spraakherkenning' + speechRecognition: 'Spraakherkenning' }, pl: { - speechrecognition: 'Rozpoznawanie mowy' + speechRecognition: 'Rozpoznawanie mowy' }, pt: { - speechrecognition: 'Reconhecimento de voz' + speechRecognition: 'Reconhecimento de voz' }, ro: { - speechrecognition: 'Recunoașterea vorbirii' + speechRecognition: 'Recunoașterea vorbirii' }, rs: { - speechrecognition: 'Препознавање говора' + speechRecognition: 'Препознавање говора' }, ru: { - speechrecognition: 'Распознавание речи' + speechRecognition: 'Распознавание речи' }, sk: { - speechrecognition: 'Rozpoznávanie reči' + speechRecognition: 'Rozpoznávanie reči' }, sq: { - speechrecognition: 'Njohja e të folurit' + speechRecognition: 'Njohja e të folurit' }, sv: { - speechrecognition: 'Taligenkänning' + speechRecognition: 'Taligenkänning' }, ua: { - speechrecognition: 'Розпізнавання мови' + speechRecognition: 'Розпізнавання мови' } }, plugins: { - speechrecognition: { + speechRecognition: { + shouldInit: isSupported, init: function (trumbowyg) { - trumbowyg.o.plugins.speechrecognition = $.extend(true, {}, + trumbowyg.o.plugins.speechRecognition = $.extend(true, {}, defaultOptions, - trumbowyg.o.plugins.speechrecognition || {} + trumbowyg.o.plugins.speechRecognition || {} ); - // Unfortunately Firefox has not implemented the WebSpeechAPI yet. - if(!navigator.userAgent.toLowerCase().includes('firefox')) { - buildButtonIcon(); - trumbowyg.addBtnDef('speechrecognition', buildButtonDef(trumbowyg)); - } + trumbowyg.addBtnDef('speechrecognition', buildButtonDef(trumbowyg)); } } } diff --git a/plugins/speechrecognition/ui/icons/speechrecognition.svg b/plugins/speechrecognition/ui/icons/speechrecognition.svg index 039a168b..a25f8dba 100644 --- a/plugins/speechrecognition/ui/icons/speechrecognition.svg +++ b/plugins/speechrecognition/ui/icons/speechrecognition.svg @@ -1 +1,4 @@ - \ No newline at end of file + + +