Let's add speech

pull/360/head
Lim Chee Aun 2023-12-21 18:17:14 +08:00
rodzic 33b55c937b
commit 92d6fe7ebe
4 zmienionych plików z 93 dodań i 30 usunięć

Wyświetl plik

@ -104,6 +104,7 @@ export const ICONS = {
cloud: () => import('@iconify-icons/mingcute/cloud-line'),
month: () => import('@iconify-icons/mingcute/calendar-month-line'),
media: () => import('@iconify-icons/mingcute/photo-album-line'),
speak: () => import('@iconify-icons/mingcute/radar-line'),
};
function Icon({

Wyświetl plik

@ -4,6 +4,7 @@ import { useSnapshot } from 'valtio';
import getTranslateTargetLanguage from '../utils/get-translate-target-language';
import localeMatch from '../utils/locale-match';
import { speak, supportsTTS } from '../utils/speech';
import states from '../utils/states';
import Icon from './icon';
@ -51,6 +52,16 @@ export default function MediaAltModal({ alt, lang, onClose }) {
<Icon icon="translate" />
<span>Translate</span>
</MenuItem>
{supportsTTS && (
<MenuItem
onClick={() => {
speak(alt, lang);
}}
>
<Icon icon="speak" />
<span>Speak</span>
</MenuItem>
)}
</Menu2>
</div>
</header>

Wyświetl plik

@ -63,6 +63,7 @@ import Media from './media';
import { isMediaCaptionLong } from './media';
import MenuLink from './menu-link';
import RelativeTime from './relative-time';
import { speak, supportsTTS } from '../utils/speech';
import TranslationBlock from './translation-block';
const SHOW_COMMENT_COUNT_LIMIT = 280;
@ -90,6 +91,26 @@ const isIOS =
const REACTIONS_LIMIT = 80;
function getPollText(poll) {
if (!poll?.options?.length) return '';
return `📊:\n${poll.options
.map(
(option) =>
`- ${option.title}${
option.votesCount >= 0 ? ` (${option.votesCount})` : ''
}`,
)
.join('\n')}`;
}
function getPostText(status) {
const { spoilerText, content, poll } = status;
return (
(spoilerText ? `${spoilerText}\n\n` : '') +
getHTMLText(content) +
getPollText(poll)
);
}
function Status({
statusID,
status,
@ -782,23 +803,53 @@ function Status({
</>
)}
{enableTranslate ? (
<MenuItem
disabled={forceTranslate}
onClick={() => {
setForceTranslate(true);
}}
>
<Icon icon="translate" />
<span>Translate</span>
</MenuItem>
) : (
(!language || differentLanguage) && (
<MenuLink
to={`${instance ? `/${instance}` : ''}/s/${id}?translate=1`}
<div class={supportsTTS ? 'menu-horizontal' : ''}>
<MenuItem
disabled={forceTranslate}
onClick={() => {
setForceTranslate(true);
}}
>
<Icon icon="translate" />
<span>Translate</span>
</MenuLink>
</MenuItem>
{supportsTTS && (
<MenuItem
onClick={() => {
const postText = getPostText(status);
if (postText) {
speak(postText, language);
}
}}
>
<Icon icon="speak" />
<span>Speak</span>
</MenuItem>
)}
</div>
) : (
(!language || differentLanguage) && (
<div class={supportsTTS ? 'menu-horizontal' : ''}>
<MenuLink
to={`${instance ? `/${instance}` : ''}/s/${id}?translate=1`}
>
<Icon icon="translate" />
<span>Translate</span>
</MenuLink>
{supportsTTS && (
<MenuItem
onClick={() => {
const postText = getPostText(status);
if (postText) {
speak(postText, language);
}
}}
>
<Icon icon="speak" />
<span>Speak</span>
</MenuItem>
)}
</div>
)
)}
{((!isSizeLarge && sameInstance) || enableTranslate) && <MenuDivider />}
@ -1578,22 +1629,7 @@ function Status({
forceTranslate={forceTranslate || inlineTranslate}
mini={!isSizeLarge && !withinContext}
sourceLanguage={language}
text={
(spoilerText ? `${spoilerText}\n\n` : '') +
getHTMLText(content) +
(poll?.options?.length
? `\n\nPoll:\n${poll.options
.map(
(option) =>
`- ${option.title}${
option.votesCount >= 0
? ` (${option.votesCount})`
: ''
}`,
)
.join('\n')}`
: '')
}
text={getPostText(status)}
/>
)}
{!spoilerText && sensitive && !!mediaAttachments.length && (

Wyświetl plik

@ -0,0 +1,15 @@
export const supportsTTS = 'speechSynthesis' in window;
export function speak(text, lang) {
if (!supportsTTS) return;
try {
if (speechSynthesis.speaking) {
speechSynthesis.cancel();
}
const utterance = new SpeechSynthesisUtterance(text);
if (lang) utterance.lang = lang;
speechSynthesis.speak(utterance);
} catch (e) {
alert(e);
}
}