Update.
This commit is contained in:
parent
74074093dc
commit
6099fc651c
@ -380,12 +380,12 @@ textToLeetAdvanced(text) {
|
|||||||
|
|
||||||
this.appendChild(this.textarea);
|
this.appendChild(this.textarea);
|
||||||
|
|
||||||
this.snekSpeaker = document.createElement("snek-speaker");
|
|
||||||
this.appendChild(this.snekSpeaker);
|
|
||||||
|
|
||||||
this.sttButton = document.createElement("stt-button");
|
this.sttButton = document.createElement("stt-button");
|
||||||
this.appendChild(this.sttButton);
|
this.appendChild(this.sttButton);
|
||||||
|
|
||||||
|
this.ttsButton = document.createElement("tts-button");
|
||||||
|
this.appendChild(this.ttsButton);
|
||||||
|
|
||||||
this.uploadButton = document.createElement("upload-button");
|
this.uploadButton = document.createElement("upload-button");
|
||||||
this.uploadButton.setAttribute("channel", this.channelUid);
|
this.uploadButton.setAttribute("channel", this.channelUid);
|
||||||
this.uploadButton.addEventListener("upload", (e) => {
|
this.uploadButton.addEventListener("upload", (e) => {
|
||||||
|
|||||||
@ -268,8 +268,7 @@ Try i, Esc, v, :, yy, dd, 0, $, gg, G, and p`;
|
|||||||
}
|
}
|
||||||
|
|
||||||
goToLine(lineNum) {
|
goToLine(lineNum) {
|
||||||
const lines = this.editor.innerText.split('
|
const lines = this.editor.innerText.split('\n');
|
||||||
');
|
|
||||||
if (lineNum < 0 || lineNum >= lines.length) return;
|
if (lineNum < 0 || lineNum >= lines.length) return;
|
||||||
|
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
|
|||||||
@ -38,9 +38,7 @@ export const registerServiceWorker = async (silent = false) => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error registering service worker:", error);
|
console.error("Error registering service worker:", error);
|
||||||
if (!silent) {
|
if (!silent) {
|
||||||
alert("Registering push notifications failed. Please check your browser settings and try again.
|
alert("Registering push notifications failed. Please check your browser settings and try again.\n\n" + error);
|
||||||
|
|
||||||
" + error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,70 +1,241 @@
|
|||||||
// retoor <retoor@molodetz.nl>
|
// retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
class SnekSpeaker extends HTMLElement {
|
import { app } from "./app.js";
|
||||||
|
|
||||||
_enabled = false
|
|
||||||
|
|
||||||
constructor() {
|
class TTSButton extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.attachShadow({ mode: 'open' });
|
this.attachShadow({ mode: 'open' });
|
||||||
|
this._enabled = false;
|
||||||
|
this._synthesis = window.speechSynthesis;
|
||||||
|
this._voices = [];
|
||||||
|
this._selectedVoice = null;
|
||||||
|
this._messageHandler = null;
|
||||||
|
this._spokenMessages = new Set();
|
||||||
|
|
||||||
// Optionally show something in the DOM
|
const style = document.createElement('style');
|
||||||
this.shadowRoot.innerHTML = `<slot></slot>`;
|
style.textContent = `
|
||||||
|
:host {
|
||||||
this._utterance = new SpeechSynthesisUtterance();
|
display: inline-block;
|
||||||
this._selectVoice();
|
|
||||||
}
|
|
||||||
toggle() {
|
|
||||||
if (window.speechSynthesis.speaking) {
|
|
||||||
window.speechSynthesis.pause();
|
|
||||||
} else {
|
|
||||||
window.speechSynthesis.resume();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stop() {
|
|
||||||
window.speechSynthesis.cancel();
|
|
||||||
}
|
|
||||||
disable() {
|
|
||||||
this._enabled = false
|
|
||||||
}
|
|
||||||
enable() {
|
|
||||||
this._enabled = true
|
|
||||||
}
|
|
||||||
set enabled(val) {
|
|
||||||
if (val) {
|
|
||||||
this.enable()
|
|
||||||
} else {
|
|
||||||
this.disable()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
get enabled() {
|
|
||||||
return this._enabled
|
|
||||||
}
|
|
||||||
_selectVoice() {
|
|
||||||
const updateVoice = () => {
|
|
||||||
const voices = window.speechSynthesis.getVoices();
|
|
||||||
const maleEnglishVoices = voices.filter(voice =>
|
|
||||||
voice.lang.startsWith('en') && voice.name.toLowerCase().includes('male')
|
|
||||||
);
|
|
||||||
if (maleEnglishVoices.length > 0) {
|
|
||||||
this._utterance.voice = maleEnglishVoices[0];
|
|
||||||
}
|
}
|
||||||
|
.tts-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.tts-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 10px 20px;
|
||||||
|
margin-left: 10px;
|
||||||
|
background-color: #000000;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: background-color 0.2s ease, transform 0.1s ease;
|
||||||
|
}
|
||||||
|
.tts-button:hover {
|
||||||
|
background-color: #222222;
|
||||||
|
}
|
||||||
|
.tts-button:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
:host([enabled]) .tts-button {
|
||||||
|
background-color: #2e7d32;
|
||||||
|
animation: pulse 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(46, 125, 50, 0.6);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
box-shadow: 0 0 0 8px rgba(46, 125, 50, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tts-button:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.className = 'tts-container';
|
||||||
|
|
||||||
|
this._button = document.createElement('button');
|
||||||
|
this._button.className = 'tts-button';
|
||||||
|
this._button.setAttribute('aria-label', 'Text to speech');
|
||||||
|
this._button.innerHTML = '🔊';
|
||||||
|
this._button.addEventListener('click', () => this._toggle());
|
||||||
|
|
||||||
|
container.appendChild(this._button);
|
||||||
|
this.shadowRoot.append(style, container);
|
||||||
|
|
||||||
|
this._initSynthesis();
|
||||||
|
}
|
||||||
|
|
||||||
|
_initSynthesis() {
|
||||||
|
if (!this._synthesis) {
|
||||||
|
console.warn('TTS: Speech synthesis not supported');
|
||||||
|
this._button.disabled = true;
|
||||||
|
this._button.title = 'Text-to-speech not supported';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._loadVoices();
|
||||||
|
if (this._synthesis.onvoiceschanged !== undefined) {
|
||||||
|
this._synthesis.onvoiceschanged = () => this._loadVoices();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadVoices() {
|
||||||
|
this._voices = this._synthesis.getVoices();
|
||||||
|
if (this._voices.length === 0) {
|
||||||
|
console.log('TTS: No voices available yet');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('TTS: Loaded', this._voices.length, 'voices');
|
||||||
|
|
||||||
|
const lang = navigator.language || 'en-US';
|
||||||
|
const langPrefix = lang.split('-')[0];
|
||||||
|
|
||||||
|
this._selectedVoice = this._voices.find(v => v.lang === lang);
|
||||||
|
if (!this._selectedVoice) {
|
||||||
|
this._selectedVoice = this._voices.find(v => v.lang.startsWith(langPrefix));
|
||||||
|
}
|
||||||
|
if (!this._selectedVoice) {
|
||||||
|
this._selectedVoice = this._voices.find(v => v.lang.startsWith('en'));
|
||||||
|
}
|
||||||
|
if (!this._selectedVoice) {
|
||||||
|
this._selectedVoice = this._voices[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('TTS: Selected voice:', this._selectedVoice?.name, this._selectedVoice?.lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
_toggle() {
|
||||||
|
if (this._enabled) {
|
||||||
|
this._disable();
|
||||||
|
} else {
|
||||||
|
this._enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_enable() {
|
||||||
|
if (this._voices.length === 0) {
|
||||||
|
this._loadVoices();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._enabled = true;
|
||||||
|
this.setAttribute('enabled', '');
|
||||||
|
this._spokenMessages.clear();
|
||||||
|
|
||||||
|
this._messageHandler = (data) => {
|
||||||
|
this._handleMessage(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
updateVoice();
|
app.addEventListener('channel-message', this._messageHandler);
|
||||||
// Some browsers load voices asynchronously
|
console.log('TTS: Enabled, listening for channel-message events');
|
||||||
window.speechSynthesis.onvoiceschanged = updateVoice;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
speak(text) {
|
_disable() {
|
||||||
if(!this._enabled) return
|
this._enabled = false;
|
||||||
|
this.removeAttribute('enabled');
|
||||||
|
this._synthesis.cancel();
|
||||||
|
|
||||||
if (!text) return;
|
if (this._messageHandler) {
|
||||||
|
app.removeEventListener('channel-message', this._messageHandler);
|
||||||
|
this._messageHandler = null;
|
||||||
|
}
|
||||||
|
console.log('TTS: Disabled');
|
||||||
|
}
|
||||||
|
|
||||||
this._utterance.text = text;
|
_handleMessage(data) {
|
||||||
window.speechSynthesis.speak(this._utterance);
|
if (!this._enabled) return;
|
||||||
|
if (!data) {
|
||||||
|
console.log('TTS: No data in message');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('TTS: Received message:', data.uid, 'is_final:', data.is_final);
|
||||||
|
|
||||||
|
if (!data.is_final) {
|
||||||
|
console.log('TTS: Skipping non-final message');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._spokenMessages.has(data.uid)) {
|
||||||
|
console.log('TTS: Already spoken this message');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentUser = app.user;
|
||||||
|
if (currentUser && (data.user_uid === currentUser.uid || data.username === currentUser.username)) {
|
||||||
|
console.log('TTS: Skipping own message');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = this._extractText(data.message);
|
||||||
|
if (!text) {
|
||||||
|
console.log('TTS: No text to speak');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._spokenMessages.add(data.uid);
|
||||||
|
|
||||||
|
if (this._spokenMessages.size > 100) {
|
||||||
|
const first = this._spokenMessages.values().next().value;
|
||||||
|
this._spokenMessages.delete(first);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('TTS: Speaking:', text.substring(0, 50) + '...');
|
||||||
|
this._speak(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
_extractText(message) {
|
||||||
|
if (!message) return '';
|
||||||
|
|
||||||
|
let text = message
|
||||||
|
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
|
||||||
|
.replace(/https?:\/\/[^\s]+/g, '')
|
||||||
|
.replace(/[*_~`#]/g, '')
|
||||||
|
.replace(/\n+/g, ' ')
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
_speak(text) {
|
||||||
|
if (!text) return;
|
||||||
|
if (!this._synthesis) return;
|
||||||
|
|
||||||
|
if (this._voices.length === 0) {
|
||||||
|
this._loadVoices();
|
||||||
|
}
|
||||||
|
|
||||||
|
const utterance = new SpeechSynthesisUtterance(text);
|
||||||
|
|
||||||
|
if (this._selectedVoice) {
|
||||||
|
utterance.voice = this._selectedVoice;
|
||||||
|
}
|
||||||
|
|
||||||
|
utterance.rate = 1.0;
|
||||||
|
utterance.pitch = 1.0;
|
||||||
|
utterance.volume = 1.0;
|
||||||
|
|
||||||
|
utterance.onstart = () => console.log('TTS: Started speaking');
|
||||||
|
utterance.onend = () => console.log('TTS: Finished speaking');
|
||||||
|
utterance.onerror = (e) => console.error('TTS: Error:', e.error);
|
||||||
|
|
||||||
|
this._synthesis.speak(utterance);
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
this._disable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define the element
|
customElements.define('tts-button', TTSButton);
|
||||||
customElements.define('snek-speaker', SnekSpeaker);
|
|
||||||
|
|||||||
@ -237,12 +237,7 @@ app.addEventListener("channel-message", (data) => {
|
|||||||
app.playSound("mention");
|
app.playSound("mention");
|
||||||
} else if (!isMentionForSomeoneElse(data.message)) {
|
} else if (!isMentionForSomeoneElse(data.message)) {
|
||||||
if(data.is_final){
|
if(data.is_final){
|
||||||
const speaker = document.querySelector("snek-speaker");
|
app.playSound("message");
|
||||||
if(speaker.enabled){
|
|
||||||
speaker.speak(data.message);
|
|
||||||
}else{
|
|
||||||
app.playSound("message");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user