mirror of
https://github.com/mastodon/mastodon.git
synced 2025-09-05 17:31:12 +00:00
Compare commits
15 Commits
d5e164a0c3
...
8c1702a846
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8c1702a846 | ||
![]() |
14cb5ff881 | ||
![]() |
bc952ebde9 | ||
![]() |
c1542643f5 | ||
![]() |
7217edfd7f | ||
![]() |
43f9ae9ee4 | ||
![]() |
e3a69530a7 | ||
![]() |
25309b92ad | ||
![]() |
1f3e5b9324 | ||
![]() |
941e86e902 | ||
![]() |
7bdbf0c25b | ||
![]() |
f2a151805f | ||
![]() |
46e3c18071 | ||
![]() |
6c38ab5b6b | ||
![]() |
5e50840986 |
|
@ -8,6 +8,7 @@ const meta = {
|
|||
component: Button,
|
||||
args: {
|
||||
secondary: false,
|
||||
plain: false,
|
||||
compact: false,
|
||||
dangerous: false,
|
||||
disabled: false,
|
||||
|
@ -57,6 +58,14 @@ export const Secondary: Story = {
|
|||
play: buttonTest,
|
||||
};
|
||||
|
||||
export const Plain: Story = {
|
||||
args: {
|
||||
plain: true,
|
||||
children: 'Plain button',
|
||||
},
|
||||
play: buttonTest,
|
||||
};
|
||||
|
||||
export const Compact: Story = {
|
||||
args: {
|
||||
compact: true,
|
||||
|
@ -101,6 +110,14 @@ export const SecondaryDisabled: Story = {
|
|||
play: disabledButtonTest,
|
||||
};
|
||||
|
||||
export const PlainDisabled: Story = {
|
||||
args: {
|
||||
...Plain.args,
|
||||
disabled: true,
|
||||
},
|
||||
play: disabledButtonTest,
|
||||
};
|
||||
|
||||
const loadingButtonTest: Story['play'] = async ({
|
||||
args,
|
||||
canvas,
|
||||
|
|
|
@ -9,6 +9,7 @@ interface BaseProps
|
|||
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
|
||||
block?: boolean;
|
||||
secondary?: boolean;
|
||||
plain?: boolean;
|
||||
compact?: boolean;
|
||||
dangerous?: boolean;
|
||||
loading?: boolean;
|
||||
|
@ -35,6 +36,7 @@ export const Button: React.FC<Props> = ({
|
|||
disabled,
|
||||
block,
|
||||
secondary,
|
||||
plain,
|
||||
compact,
|
||||
dangerous,
|
||||
loading,
|
||||
|
@ -62,6 +64,7 @@ export const Button: React.FC<Props> = ({
|
|||
<button
|
||||
className={classNames('button', className, {
|
||||
'button-secondary': secondary,
|
||||
'button--plain': plain,
|
||||
'button--compact': compact,
|
||||
'button--block': block,
|
||||
'button--dangerous': dangerous,
|
||||
|
|
|
@ -10,6 +10,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
|
||||
import { translateStatusSuccess } from 'mastodon/actions/statuses';
|
||||
import { undoStatusTranslation } from 'mastodon/actions/statuses';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { Poll } from 'mastodon/components/poll';
|
||||
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
|
||||
|
@ -19,6 +21,8 @@ import { isModernEmojiEnabled } from '../utils/environment';
|
|||
|
||||
const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top)
|
||||
|
||||
const supportsTranslator = 'Translator' in globalThis;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {any} status
|
||||
|
@ -69,10 +73,18 @@ class TranslateButton extends PureComponent {
|
|||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
languages: state.getIn(['server', 'translationLanguages', 'items']),
|
||||
languages: supportsTranslator ? new Map() : state.getIn(['server', 'translationLanguages', 'items']),
|
||||
});
|
||||
|
||||
class StatusContent extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
showTranslateButton: false,
|
||||
};
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
identity: identityContextPropShape,
|
||||
status: ImmutablePropTypes.map.isRequired,
|
||||
|
@ -176,8 +188,36 @@ class StatusContent extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
async componentDidMount () {
|
||||
this._updateStatusLinks();
|
||||
|
||||
const { status, intl, languages } = this.props;
|
||||
const contentLocale = intl.locale.replace(/[_-].*/, '');
|
||||
const targetLanguages = languages?.get(status.get('language') || 'und');
|
||||
|
||||
// The Translator API translates all locally on the client, so private and direct toots are fine to translate.
|
||||
const allowedVisibilities = supportsTranslator ? ['public', 'unlisted', 'private', 'direct'] : ['public', 'unlisted'];
|
||||
const shouldAttemptTranslate =
|
||||
this.props.onTranslate &&
|
||||
this.props.identity.signedIn &&
|
||||
allowedVisibilities.includes(status.get('visibility')) &&
|
||||
status.get('search_index').trim().length > 0;
|
||||
|
||||
if (!shouldAttemptTranslate) return;
|
||||
|
||||
let available = false;
|
||||
if (supportsTranslator) {
|
||||
available = (await Translator.availability({
|
||||
sourceLanguage: status.get('language'),
|
||||
targetLanguage: contentLocale,
|
||||
})) !== 'unavailable';
|
||||
} else {
|
||||
available = targetLanguages?.includes(contentLocale);
|
||||
}
|
||||
|
||||
if (available) {
|
||||
this.setState({ showTranslateButton: true });
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
|
@ -227,8 +267,37 @@ class StatusContent extends PureComponent {
|
|||
this.startXY = null;
|
||||
};
|
||||
|
||||
handleTranslate = () => {
|
||||
handleTranslate = async () => {
|
||||
if (!supportsTranslator) {
|
||||
this.props.onTranslate();
|
||||
return;
|
||||
}
|
||||
|
||||
const { intl, status, statusContent } = this.props;
|
||||
|
||||
if (status.get('translation')) {
|
||||
this.props.dispatch(undoStatusTranslation(status.get('id'), status.get('poll')));
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceLanguage = status.get('language');
|
||||
const targetLanguage = intl.locale.replace(/[_-].*/, '');
|
||||
try {
|
||||
const translator = await Translator.create({
|
||||
sourceLanguage,
|
||||
targetLanguage,
|
||||
});
|
||||
const translatedText = await translator.translate(statusContent);
|
||||
const translation = {
|
||||
content: translatedText,
|
||||
provider: 'Translator API',
|
||||
detected_source_language: sourceLanguage,
|
||||
language: targetLanguage,
|
||||
};
|
||||
this.props.dispatch(translateStatusSuccess(status.get('id'), translation));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
setRef = (c) => {
|
||||
|
@ -236,12 +305,9 @@ class StatusContent extends PureComponent {
|
|||
};
|
||||
|
||||
render () {
|
||||
const { status, intl, statusContent } = this.props;
|
||||
const { status, statusContent } = this.props;
|
||||
|
||||
const renderReadMore = this.props.onClick && status.get('collapsed');
|
||||
const contentLocale = intl.locale.replace(/[_-].*/, '');
|
||||
const targetLanguages = this.props.languages?.get(status.get('language') || 'und');
|
||||
const renderTranslate = this.props.onTranslate && this.props.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('search_index').trim().length > 0 && targetLanguages?.includes(contentLocale);
|
||||
|
||||
const content = statusContent ?? getStatusContent(status);
|
||||
const language = status.getIn(['translation', 'language']) || status.get('language');
|
||||
|
@ -256,7 +322,7 @@ class StatusContent extends PureComponent {
|
|||
</button>
|
||||
);
|
||||
|
||||
const translateButton = renderTranslate && (
|
||||
const translateButton = this.state.showTranslateButton && (
|
||||
<TranslateButton onClick={this.handleTranslate} translation={status.get('translation')} />
|
||||
);
|
||||
|
||||
|
@ -297,8 +363,7 @@ class StatusContent extends PureComponent {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
export default withRouter(withIdentity(connect(mapStateToProps)(injectIntl(StatusContent))));
|
||||
|
|
|
@ -201,6 +201,41 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.button--plain {
|
||||
color: $highlight-text-color;
|
||||
background: transparent;
|
||||
padding: 6px;
|
||||
|
||||
// The button has no outline, so we use negative margin to
|
||||
// visually align its label with its surroundings while maintaining
|
||||
// a generous click target
|
||||
margin-inline: -6px;
|
||||
border: 1px solid transparent;
|
||||
|
||||
&:active,
|
||||
&:focus,
|
||||
&:hover {
|
||||
border-color: transparent;
|
||||
color: lighten($highlight-text-color, 4%);
|
||||
background-color: transparent;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&:disabled,
|
||||
&.disabled {
|
||||
opacity: 0.7;
|
||||
border-color: transparent;
|
||||
color: $ui-button-disabled-color;
|
||||
|
||||
&:active,
|
||||
&:focus,
|
||||
&:hover {
|
||||
border-color: transparent;
|
||||
color: $ui-button-disabled-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.button-tertiary {
|
||||
background: transparent;
|
||||
padding: 6px 17px;
|
||||
|
|
|
@ -232,6 +232,15 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
|
|||
canQuote: {
|
||||
automaticApproval: approved_uris,
|
||||
},
|
||||
canReply: {
|
||||
always: 'https://www.w3.org/ns/activitystreams#Public',
|
||||
},
|
||||
canLike: {
|
||||
always: 'https://www.w3.org/ns/activitystreams#Public',
|
||||
},
|
||||
canAnnounce: {
|
||||
always: 'https://www.w3.org/ns/activitystreams#Public',
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -11957,8 +11957,8 @@ __metadata:
|
|||
linkType: hard
|
||||
|
||||
"sass@npm:^1.62.1":
|
||||
version: 1.91.0
|
||||
resolution: "sass@npm:1.91.0"
|
||||
version: 1.92.0
|
||||
resolution: "sass@npm:1.92.0"
|
||||
dependencies:
|
||||
"@parcel/watcher": "npm:^2.4.1"
|
||||
chokidar: "npm:^4.0.0"
|
||||
|
@ -11969,7 +11969,7 @@ __metadata:
|
|||
optional: true
|
||||
bin:
|
||||
sass: sass.js
|
||||
checksum: 10c0/5be1c98f7a618cb5f90b62f63d2aa0f78f9bf369c93ec7cd9880752a26b0ead19aa63cc341e8a26ce6c74d080baa5705f1685dff52fe6a3f28a7828ae50182b6
|
||||
checksum: 10c0/bdff9fa6988620e2a81962efdd016e3894d19934cfadc105cf41db767f59dd47afd8ff32840e612ef700cb67e19d9e83c108f1724eb8f0bef56c4877dbe6f14d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user