Compare commits
5 commits
a99fc68bda
...
42f1f45767
Author | SHA1 | Date | |
---|---|---|---|
Natsu Kagami | 42f1f45767 | ||
7286a4e03b | |||
1f0d2eebe6 | |||
38a13b07c5 | |||
92a4f502a0 |
|
@ -12,6 +12,13 @@
|
||||||
backdrop-filter: blur(24px);
|
backdrop-filter: blur(24px);
|
||||||
animation: appear 0.5s var(--timing-function) both;
|
animation: appear 0.5s var(--timing-function) both;
|
||||||
}
|
}
|
||||||
|
#modal-container > div .sheet {
|
||||||
|
transition: transform 0.3s var(--timing-function);
|
||||||
|
transform-origin: center bottom;
|
||||||
|
}
|
||||||
|
#modal-container > div:has(~ div) .sheet {
|
||||||
|
transform: scale(0.975);
|
||||||
|
}
|
||||||
|
|
||||||
#modal-container > .light {
|
#modal-container > .light {
|
||||||
backdrop-filter: saturate(0.75);
|
backdrop-filter: saturate(0.75);
|
||||||
|
|
|
@ -57,6 +57,7 @@ import MenuLink from './menu-link';
|
||||||
import RelativeTime from './relative-time';
|
import RelativeTime from './relative-time';
|
||||||
import TranslationBlock from './translation-block';
|
import TranslationBlock from './translation-block';
|
||||||
|
|
||||||
|
const INLINE_TRASNSLATE_LIMIT = 140;
|
||||||
const throttle = pThrottle({
|
const throttle = pThrottle({
|
||||||
limit: 1,
|
limit: 1,
|
||||||
interval: 1000,
|
interval: 1000,
|
||||||
|
@ -244,11 +245,23 @@ function Status({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isSizeLarge = size === 'l';
|
||||||
|
|
||||||
const [forceTranslate, setForceTranslate] = useState(_forceTranslate);
|
const [forceTranslate, setForceTranslate] = useState(_forceTranslate);
|
||||||
const targetLanguage = getTranslateTargetLanguage(true);
|
const targetLanguage = getTranslateTargetLanguage(true);
|
||||||
const contentTranslationHideLanguages =
|
const contentTranslationHideLanguages =
|
||||||
snapStates.settings.contentTranslationHideLanguages || [];
|
snapStates.settings.contentTranslationHideLanguages || [];
|
||||||
if (!snapStates.settings.contentTranslation) enableTranslate = false;
|
if (!snapStates.settings.contentTranslation) enableTranslate = false;
|
||||||
|
const inlineTranslate = useMemo(() => {
|
||||||
|
return (
|
||||||
|
!isSizeLarge &&
|
||||||
|
!spoilerText &&
|
||||||
|
!poll &&
|
||||||
|
!mediaAttachments?.length &&
|
||||||
|
content?.length > 0 &&
|
||||||
|
content?.length <= INLINE_TRASNSLATE_LIMIT
|
||||||
|
);
|
||||||
|
}, [isSizeLarge, content, spoilerText, poll, mediaAttachments]);
|
||||||
|
|
||||||
const [showEdited, setShowEdited] = useState(false);
|
const [showEdited, setShowEdited] = useState(false);
|
||||||
const [showReactions, setShowReactions] = useState(false);
|
const [showReactions, setShowReactions] = useState(false);
|
||||||
|
@ -306,7 +319,6 @@ function Status({
|
||||||
const createdDateText = niceDateTime(createdAtDate);
|
const createdDateText = niceDateTime(createdAtDate);
|
||||||
const editedDateText = editedAt && niceDateTime(editedAtDate);
|
const editedDateText = editedAt && niceDateTime(editedAtDate);
|
||||||
|
|
||||||
const isSizeLarge = size === 'l';
|
|
||||||
// Can boost if:
|
// Can boost if:
|
||||||
// - authenticated AND
|
// - authenticated AND
|
||||||
// - visibility != direct OR
|
// - visibility != direct OR
|
||||||
|
@ -526,7 +538,7 @@ function Status({
|
||||||
confirmLabel={
|
confirmLabel={
|
||||||
<>
|
<>
|
||||||
<Icon icon="rocket" />
|
<Icon icon="rocket" />
|
||||||
<span>Unboost?</span>
|
<span>{reblogged ? 'Unboost?' : 'Boost to everyone?'}</span>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
menuFooter={
|
menuFooter={
|
||||||
|
@ -890,7 +902,8 @@ function Status({
|
||||||
// Higher than the backdrop
|
// Higher than the backdrop
|
||||||
zIndex: 1001,
|
zIndex: 1001,
|
||||||
},
|
},
|
||||||
onClick: () => {
|
onClick: (e) => {
|
||||||
|
if (e.target === e.currentTarget)
|
||||||
menuInstanceRef.current?.closeMenu?.();
|
menuInstanceRef.current?.closeMenu?.();
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
@ -1091,10 +1104,13 @@ function Status({
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{((enableTranslate && !!content.trim() && differentLanguage) ||
|
{(((enableTranslate || inlineTranslate) &&
|
||||||
|
!!content.trim() &&
|
||||||
|
differentLanguage) ||
|
||||||
forceTranslate) && (
|
forceTranslate) && (
|
||||||
<TranslationBlock
|
<TranslationBlock
|
||||||
forceTranslate={forceTranslate}
|
forceTranslate={forceTranslate || inlineTranslate}
|
||||||
|
mini={inlineTranslate}
|
||||||
sourceLanguage={language}
|
sourceLanguage={language}
|
||||||
text={
|
text={
|
||||||
(spoilerText ? `${spoilerText}\n\n` : '') +
|
(spoilerText ? `${spoilerText}\n\n` : '') +
|
||||||
|
@ -1224,19 +1240,25 @@ function Status({
|
||||||
disabled={!canBoost}
|
disabled={!canBoost}
|
||||||
/>
|
/>
|
||||||
</div> */}
|
</div> */}
|
||||||
<Menu
|
<MenuConfirm
|
||||||
portal={{
|
disabled={!canBoost}
|
||||||
target:
|
onClick={confirmBoostStatus}
|
||||||
document.querySelector('.status-deck') || document.body,
|
confirmLabel={
|
||||||
}}
|
<>
|
||||||
align="start"
|
<Icon icon="rocket" />
|
||||||
gap={4}
|
<span>{reblogged ? 'Unboost?' : 'Boost to everyone?'}</span>
|
||||||
overflow="auto"
|
</>
|
||||||
viewScroll="close"
|
}
|
||||||
boundingBoxPadding="8 8 8 8"
|
menuFooter={
|
||||||
shift={-8}
|
mediaNoDesc &&
|
||||||
menuClassName="menu-emphasized"
|
!reblogged && (
|
||||||
menuButton={({ open }) => (
|
<div class="footer">
|
||||||
|
<Icon icon="alert" />
|
||||||
|
Some media have no descriptions.
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
<div class="action has-count">
|
<div class="action has-count">
|
||||||
<StatusButton
|
<StatusButton
|
||||||
checked={reblogged}
|
checked={reblogged}
|
||||||
|
@ -1246,22 +1268,10 @@ function Status({
|
||||||
icon="rocket"
|
icon="rocket"
|
||||||
count={reblogsCount}
|
count={reblogsCount}
|
||||||
// onClick={boostStatus}
|
// onClick={boostStatus}
|
||||||
disabled={open || !canBoost}
|
disabled={!canBoost}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</MenuConfirm>
|
||||||
>
|
|
||||||
<MenuItem onClick={confirmBoostStatus}>
|
|
||||||
<Icon icon="rocket" />
|
|
||||||
<span>Boost to everyone?</span>
|
|
||||||
</MenuItem>
|
|
||||||
{mediaNoDesc && (
|
|
||||||
<div class="footer">
|
|
||||||
<Icon icon="alert" />
|
|
||||||
Some media have no descriptions.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Menu>
|
|
||||||
<div class="action has-count">
|
<div class="action has-count">
|
||||||
<StatusButton
|
<StatusButton
|
||||||
checked={favourited}
|
checked={favourited}
|
||||||
|
|
|
@ -105,3 +105,22 @@
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
mask-image: none;
|
mask-image: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* MINI */
|
||||||
|
|
||||||
|
.status-translation-block-mini {
|
||||||
|
display: flex;
|
||||||
|
margin: 8px 0 0;
|
||||||
|
padding: 8px 0 0;
|
||||||
|
font-size: 90%;
|
||||||
|
border-top: var(--hairline-width) solid var(--outline-color);
|
||||||
|
color: var(--text-insignificant-color);
|
||||||
|
gap: 8px;
|
||||||
|
transition: color 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
.status-translation-block-mini .icon {
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
.status:is(:hover, :active) .status-translation-block-mini {
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import './translation-block.css';
|
import './translation-block.css';
|
||||||
|
|
||||||
|
import pThrottle from 'p-throttle';
|
||||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||||
|
|
||||||
import sourceLanguages from '../data/lingva-source-languages';
|
import sourceLanguages from '../data/lingva-source-languages';
|
||||||
|
@ -9,28 +10,13 @@ import localeCode2Text from '../utils/localeCode2Text';
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
import Loader from './loader';
|
import Loader from './loader';
|
||||||
|
|
||||||
function TranslationBlock({
|
const throttle = pThrottle({
|
||||||
forceTranslate,
|
limit: 1,
|
||||||
sourceLanguage,
|
interval: 2000,
|
||||||
onTranslate,
|
});
|
||||||
text = '',
|
|
||||||
}) {
|
|
||||||
const targetLang = getTranslateTargetLanguage(true);
|
|
||||||
const [uiState, setUIState] = useState('default');
|
|
||||||
const [pronunciationContent, setPronunciationContent] = useState(null);
|
|
||||||
const [translatedContent, setTranslatedContent] = useState(null);
|
|
||||||
const [detectedLang, setDetectedLang] = useState(null);
|
|
||||||
const detailsRef = useRef();
|
|
||||||
|
|
||||||
const sourceLangText = sourceLanguage
|
function lingvaTranslate(text, source, target) {
|
||||||
? localeCode2Text(sourceLanguage)
|
console.log('TRANSLATE', text, source, target);
|
||||||
: null;
|
|
||||||
const targetLangText = localeCode2Text(targetLang);
|
|
||||||
const apiSourceLang = useRef('auto');
|
|
||||||
|
|
||||||
if (!onTranslate)
|
|
||||||
onTranslate = (source, target) => {
|
|
||||||
console.log('TRANSLATE', source, target, text);
|
|
||||||
// Using another API instance instead of lingva.ml because of this bug (slashes don't work):
|
// Using another API instance instead of lingva.ml because of this bug (slashes don't work):
|
||||||
// https://github.com/thedaviddelta/lingva-translate/issues/68
|
// https://github.com/thedaviddelta/lingva-translate/issues/68
|
||||||
return fetch(
|
return fetch(
|
||||||
|
@ -50,13 +36,38 @@ function TranslationBlock({
|
||||||
// return masto.v1.statuses.translate(id, {
|
// return masto.v1.statuses.translate(id, {
|
||||||
// lang: DEFAULT_LANG,
|
// lang: DEFAULT_LANG,
|
||||||
// });
|
// });
|
||||||
};
|
}
|
||||||
|
const throttledLingvaTranslate = throttle(lingvaTranslate);
|
||||||
|
|
||||||
|
function TranslationBlock({
|
||||||
|
forceTranslate,
|
||||||
|
sourceLanguage,
|
||||||
|
onTranslate,
|
||||||
|
text = '',
|
||||||
|
mini,
|
||||||
|
}) {
|
||||||
|
const targetLang = getTranslateTargetLanguage(true);
|
||||||
|
const [uiState, setUIState] = useState('default');
|
||||||
|
const [pronunciationContent, setPronunciationContent] = useState(null);
|
||||||
|
const [translatedContent, setTranslatedContent] = useState(null);
|
||||||
|
const [detectedLang, setDetectedLang] = useState(null);
|
||||||
|
const detailsRef = useRef();
|
||||||
|
|
||||||
|
const sourceLangText = sourceLanguage
|
||||||
|
? localeCode2Text(sourceLanguage)
|
||||||
|
: null;
|
||||||
|
const targetLangText = localeCode2Text(targetLang);
|
||||||
|
const apiSourceLang = useRef('auto');
|
||||||
|
|
||||||
|
if (!onTranslate) {
|
||||||
|
onTranslate = mini ? throttledLingvaTranslate : lingvaTranslate;
|
||||||
|
}
|
||||||
|
|
||||||
const translate = async () => {
|
const translate = async () => {
|
||||||
setUIState('loading');
|
setUIState('loading');
|
||||||
try {
|
try {
|
||||||
const { content, detectedSourceLanguage, provider, ...props } =
|
const { content, detectedSourceLanguage, provider, ...props } =
|
||||||
await onTranslate(apiSourceLang.current, targetLang);
|
await onTranslate(text, apiSourceLang.current, targetLang);
|
||||||
if (content) {
|
if (content) {
|
||||||
if (detectedSourceLanguage) {
|
if (detectedSourceLanguage) {
|
||||||
const detectedLangText = localeCode2Text(detectedSourceLanguage);
|
const detectedLangText = localeCode2Text(detectedSourceLanguage);
|
||||||
|
@ -70,11 +81,13 @@ function TranslationBlock({
|
||||||
}
|
}
|
||||||
setTranslatedContent(content);
|
setTranslatedContent(content);
|
||||||
setUIState('default');
|
setUIState('default');
|
||||||
|
if (!mini) {
|
||||||
detailsRef.current.open = true;
|
detailsRef.current.open = true;
|
||||||
detailsRef.current.scrollIntoView({
|
detailsRef.current.scrollIntoView({
|
||||||
behavior: 'smooth',
|
behavior: 'smooth',
|
||||||
block: 'nearest',
|
block: 'nearest',
|
||||||
});
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error(result);
|
console.error(result);
|
||||||
setUIState('error');
|
setUIState('error');
|
||||||
|
@ -91,6 +104,23 @@ function TranslationBlock({
|
||||||
}
|
}
|
||||||
}, [forceTranslate]);
|
}, [forceTranslate]);
|
||||||
|
|
||||||
|
if (mini) {
|
||||||
|
if (!!translatedContent && detectedLang !== targetLangText) {
|
||||||
|
return (
|
||||||
|
<div class="status-translation-block-mini">
|
||||||
|
<Icon
|
||||||
|
icon="translate"
|
||||||
|
alt={`Auto-translated from ${sourceLangText}`}
|
||||||
|
/>
|
||||||
|
<output lang={targetLang} dir="auto">
|
||||||
|
{translatedContent}
|
||||||
|
</output>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class="status-translation-block"
|
class="status-translation-block"
|
||||||
|
|
Loading…
Reference in a new issue