Merge remote-tracking branch 'parent/main' into upstream-20240401
This commit is contained in:
commit
bef755a577
111 changed files with 989 additions and 720 deletions
2
.nvmrc
2
.nvmrc
|
@ -1 +1 @@
|
|||
20.11
|
||||
20.12
|
||||
|
|
|
@ -194,7 +194,7 @@ GEM
|
|||
database_cleaner-core (~> 2.0.0)
|
||||
database_cleaner-core (2.0.1)
|
||||
date (3.3.4)
|
||||
debug (1.9.1)
|
||||
debug (1.9.2)
|
||||
irb (~> 1.10)
|
||||
reline (>= 0.3.8)
|
||||
debug_inspector (1.2.0)
|
||||
|
@ -373,7 +373,7 @@ GEM
|
|||
json-ld-preloaded (3.3.0)
|
||||
json-ld (~> 3.3)
|
||||
rdf (~> 3.3)
|
||||
json-schema (4.2.0)
|
||||
json-schema (4.3.0)
|
||||
addressable (>= 2.8)
|
||||
jsonapi-renderer (0.2.2)
|
||||
jwt (2.7.1)
|
||||
|
|
|
@ -170,8 +170,11 @@ module ApplicationHelper
|
|||
if theme == 'system'
|
||||
concat stylesheet_pack_tag('mastodon-light', media: 'not all and (prefers-color-scheme: dark)', crossorigin: 'anonymous')
|
||||
concat stylesheet_pack_tag('default', media: '(prefers-color-scheme: dark)', crossorigin: 'anonymous')
|
||||
concat tag.meta name: 'theme-color', content: Themes::MASTODON_DARK_THEME_COLOR, media: '(prefers-color-scheme: dark)'
|
||||
concat tag.meta name: 'theme-color', content: Themes::MASTODON_LIGHT_THEME_COLOR, media: '(prefers-color-scheme: light)'
|
||||
else
|
||||
stylesheet_pack_tag theme, media: 'all', crossorigin: 'anonymous'
|
||||
concat stylesheet_pack_tag theme, media: 'all', crossorigin: 'anonymous'
|
||||
concat tag.meta name: 'theme-color', content: theme == 'mastodon-light' ? Themes::MASTODON_LIGHT_THEME_COLOR : Themes::MASTODON_DARK_THEME_COLOR
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
import { openModal } from './modal';
|
||||
|
||||
export const BOOSTS_INIT_MODAL = 'BOOSTS_INIT_MODAL';
|
||||
export const BOOSTS_CHANGE_PRIVACY = 'BOOSTS_CHANGE_PRIVACY';
|
||||
|
||||
export function initBoostModal(props) {
|
||||
return (dispatch, getState) => {
|
||||
const default_privacy = getState().getIn(['compose', 'default_privacy']);
|
||||
|
||||
const privacy = props.status.get('visibility_ex') === 'private' ? 'private' : default_privacy;
|
||||
|
||||
dispatch({
|
||||
type: BOOSTS_INIT_MODAL,
|
||||
privacy,
|
||||
});
|
||||
|
||||
dispatch(openModal({
|
||||
modalType: 'BOOST',
|
||||
modalProps: props,
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function changeBoostPrivacy(privacy) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: BOOSTS_CHANGE_PRIVACY,
|
||||
privacy,
|
||||
});
|
||||
};
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
import { List as ImmutableList } from 'immutable';
|
||||
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import api from '../api';
|
||||
import { compareId } from '../compare_id';
|
||||
|
||||
export const MARKERS_FETCH_REQUEST = 'MARKERS_FETCH_REQUEST';
|
||||
export const MARKERS_FETCH_SUCCESS = 'MARKERS_FETCH_SUCCESS';
|
||||
export const MARKERS_FETCH_FAIL = 'MARKERS_FETCH_FAIL';
|
||||
export const MARKERS_SUBMIT_SUCCESS = 'MARKERS_SUBMIT_SUCCESS';
|
||||
|
||||
export const synchronouslySubmitMarkers = () => (dispatch, getState) => {
|
||||
const accessToken = getState().getIn(['meta', 'access_token'], '');
|
||||
const params = _buildParams(getState());
|
||||
|
||||
if (Object.keys(params).length === 0 || accessToken === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// The Fetch API allows us to perform requests that will be carried out
|
||||
// after the page closes. But that only works if the `keepalive` attribute
|
||||
// is supported.
|
||||
if (window.fetch && 'keepalive' in new Request('')) {
|
||||
fetch('/api/v1/markers', {
|
||||
keepalive: true,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${accessToken}`,
|
||||
},
|
||||
body: JSON.stringify(params),
|
||||
});
|
||||
|
||||
return;
|
||||
} else if (navigator && navigator.sendBeacon) {
|
||||
// Failing that, we can use sendBeacon, but we have to encode the data as
|
||||
// FormData for DoorKeeper to recognize the token.
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('bearer_token', accessToken);
|
||||
|
||||
for (const [id, value] of Object.entries(params)) {
|
||||
formData.append(`${id}[last_read_id]`, value.last_read_id);
|
||||
}
|
||||
|
||||
if (navigator.sendBeacon('/api/v1/markers', formData)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If neither Fetch nor sendBeacon worked, try to perform a synchronous
|
||||
// request.
|
||||
try {
|
||||
const client = new XMLHttpRequest();
|
||||
|
||||
client.open('POST', '/api/v1/markers', false);
|
||||
client.setRequestHeader('Content-Type', 'application/json');
|
||||
client.setRequestHeader('Authorization', `Bearer ${accessToken}`);
|
||||
client.send(JSON.stringify(params));
|
||||
} catch (e) {
|
||||
// Do not make the BeforeUnload handler error out
|
||||
}
|
||||
};
|
||||
|
||||
const _buildParams = (state) => {
|
||||
const params = {};
|
||||
|
||||
const lastHomeId = state.getIn(['timelines', 'home', 'items'], ImmutableList()).find(item => item !== null);
|
||||
const lastNotificationId = state.getIn(['notifications', 'lastReadId']);
|
||||
|
||||
if (lastHomeId && compareId(lastHomeId, state.getIn(['markers', 'home'])) > 0) {
|
||||
params.home = {
|
||||
last_read_id: lastHomeId,
|
||||
};
|
||||
}
|
||||
|
||||
if (lastNotificationId && compareId(lastNotificationId, state.getIn(['markers', 'notifications'])) > 0) {
|
||||
params.notifications = {
|
||||
last_read_id: lastNotificationId,
|
||||
};
|
||||
}
|
||||
|
||||
return params;
|
||||
};
|
||||
|
||||
const debouncedSubmitMarkers = debounce((dispatch, getState) => {
|
||||
const accessToken = getState().getIn(['meta', 'access_token'], '');
|
||||
const params = _buildParams(getState());
|
||||
|
||||
if (Object.keys(params).length === 0 || accessToken === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
api(getState).post('/api/v1/markers', params).then(() => {
|
||||
dispatch(submitMarkersSuccess(params));
|
||||
}).catch(() => {});
|
||||
}, 300000, { leading: true, trailing: true });
|
||||
|
||||
export function submitMarkersSuccess({ home, notifications }) {
|
||||
return {
|
||||
type: MARKERS_SUBMIT_SUCCESS,
|
||||
home: (home || {}).last_read_id,
|
||||
notifications: (notifications || {}).last_read_id,
|
||||
};
|
||||
}
|
||||
|
||||
export function submitMarkers(params = {}) {
|
||||
const result = (dispatch, getState) => debouncedSubmitMarkers(dispatch, getState);
|
||||
|
||||
if (params.immediate === true) {
|
||||
debouncedSubmitMarkers.flush();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export const fetchMarkers = () => (dispatch, getState) => {
|
||||
const params = { timeline: ['notifications'] };
|
||||
|
||||
dispatch(fetchMarkersRequest());
|
||||
|
||||
api(getState).get('/api/v1/markers', { params }).then(response => {
|
||||
dispatch(fetchMarkersSuccess(response.data));
|
||||
}).catch(error => {
|
||||
dispatch(fetchMarkersFail(error));
|
||||
});
|
||||
};
|
||||
|
||||
export function fetchMarkersRequest() {
|
||||
return {
|
||||
type: MARKERS_FETCH_REQUEST,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchMarkersSuccess(markers) {
|
||||
return {
|
||||
type: MARKERS_FETCH_SUCCESS,
|
||||
markers,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchMarkersFail(error) {
|
||||
return {
|
||||
type: MARKERS_FETCH_FAIL,
|
||||
error,
|
||||
skipLoading: true,
|
||||
skipAlert: true,
|
||||
};
|
||||
}
|
165
app/javascript/mastodon/actions/markers.ts
Normal file
165
app/javascript/mastodon/actions/markers.ts
Normal file
|
@ -0,0 +1,165 @@
|
|||
import { List as ImmutableList } from 'immutable';
|
||||
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import type { MarkerJSON } from 'mastodon/api_types/markers';
|
||||
import type { RootState } from 'mastodon/store';
|
||||
import { createAppAsyncThunk } from 'mastodon/store/typed_functions';
|
||||
|
||||
import api, { authorizationTokenFromState } from '../api';
|
||||
import { compareId } from '../compare_id';
|
||||
|
||||
export const synchronouslySubmitMarkers = createAppAsyncThunk(
|
||||
'markers/submit',
|
||||
async (_args, { getState }) => {
|
||||
const accessToken = authorizationTokenFromState(getState);
|
||||
const params = buildPostMarkersParams(getState());
|
||||
|
||||
if (Object.keys(params).length === 0 || !accessToken) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The Fetch API allows us to perform requests that will be carried out
|
||||
// after the page closes. But that only works if the `keepalive` attribute
|
||||
// is supported.
|
||||
if ('fetch' in window && 'keepalive' in new Request('')) {
|
||||
await fetch('/api/v1/markers', {
|
||||
keepalive: true,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
body: JSON.stringify(params),
|
||||
});
|
||||
|
||||
return;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
} else if ('navigator' && 'sendBeacon' in navigator) {
|
||||
// Failing that, we can use sendBeacon, but we have to encode the data as
|
||||
// FormData for DoorKeeper to recognize the token.
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('bearer_token', accessToken);
|
||||
|
||||
for (const [id, value] of Object.entries(params)) {
|
||||
if (value.last_read_id)
|
||||
formData.append(`${id}[last_read_id]`, value.last_read_id);
|
||||
}
|
||||
|
||||
if (navigator.sendBeacon('/api/v1/markers', formData)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If neither Fetch nor sendBeacon worked, try to perform a synchronous
|
||||
// request.
|
||||
try {
|
||||
const client = new XMLHttpRequest();
|
||||
|
||||
client.open('POST', '/api/v1/markers', false);
|
||||
client.setRequestHeader('Content-Type', 'application/json');
|
||||
client.setRequestHeader('Authorization', `Bearer ${accessToken}`);
|
||||
client.send(JSON.stringify(params));
|
||||
} catch (e) {
|
||||
// Do not make the BeforeUnload handler error out
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
interface MarkerParam {
|
||||
last_read_id?: string;
|
||||
}
|
||||
|
||||
function getLastHomeId(state: RootState): string | undefined {
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */
|
||||
return (
|
||||
state
|
||||
// @ts-expect-error state.timelines is not yet typed
|
||||
.getIn(['timelines', 'home', 'items'], ImmutableList())
|
||||
// @ts-expect-error state.timelines is not yet typed
|
||||
.find((item) => item !== null)
|
||||
);
|
||||
}
|
||||
|
||||
function getLastNotificationId(state: RootState): string | undefined {
|
||||
// @ts-expect-error state.notifications is not yet typed
|
||||
return state.getIn(['notifications', 'lastReadId']);
|
||||
}
|
||||
|
||||
const buildPostMarkersParams = (state: RootState) => {
|
||||
const params = {} as { home?: MarkerParam; notifications?: MarkerParam };
|
||||
|
||||
const lastHomeId = getLastHomeId(state);
|
||||
const lastNotificationId = getLastNotificationId(state);
|
||||
|
||||
if (lastHomeId && compareId(lastHomeId, state.markers.home) > 0) {
|
||||
params.home = {
|
||||
last_read_id: lastHomeId,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
lastNotificationId &&
|
||||
compareId(lastNotificationId, state.markers.notifications) > 0
|
||||
) {
|
||||
params.notifications = {
|
||||
last_read_id: lastNotificationId,
|
||||
};
|
||||
}
|
||||
|
||||
return params;
|
||||
};
|
||||
|
||||
export const submitMarkersAction = createAppAsyncThunk<{
|
||||
home: string | undefined;
|
||||
notifications: string | undefined;
|
||||
}>('markers/submitAction', async (_args, { getState }) => {
|
||||
const accessToken = authorizationTokenFromState(getState);
|
||||
const params = buildPostMarkersParams(getState());
|
||||
|
||||
if (Object.keys(params).length === 0 || accessToken === '') {
|
||||
return { home: undefined, notifications: undefined };
|
||||
}
|
||||
|
||||
await api(getState).post<MarkerJSON>('/api/v1/markers', params);
|
||||
|
||||
return {
|
||||
home: params.home?.last_read_id,
|
||||
notifications: params.notifications?.last_read_id,
|
||||
};
|
||||
});
|
||||
|
||||
const debouncedSubmitMarkers = debounce(
|
||||
(dispatch) => {
|
||||
dispatch(submitMarkersAction());
|
||||
},
|
||||
300000,
|
||||
{
|
||||
leading: true,
|
||||
trailing: true,
|
||||
},
|
||||
);
|
||||
|
||||
export const submitMarkers = createAppAsyncThunk(
|
||||
'markers/submit',
|
||||
(params: { immediate?: boolean }, { dispatch }) => {
|
||||
debouncedSubmitMarkers(dispatch);
|
||||
|
||||
if (params.immediate) {
|
||||
debouncedSubmitMarkers.flush();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export const fetchMarkers = createAppAsyncThunk(
|
||||
'markers/fetch',
|
||||
async (_args, { getState }) => {
|
||||
const response = await api(getState).get<Record<string, MarkerJSON>>(
|
||||
`/api/v1/markers`,
|
||||
{ params: { timeline: ['notifications'] } },
|
||||
);
|
||||
|
||||
return { markers: response.data };
|
||||
},
|
||||
);
|
|
@ -1,46 +0,0 @@
|
|||
// @ts-check
|
||||
|
||||
export const PICTURE_IN_PICTURE_DEPLOY = 'PICTURE_IN_PICTURE_DEPLOY';
|
||||
export const PICTURE_IN_PICTURE_REMOVE = 'PICTURE_IN_PICTURE_REMOVE';
|
||||
|
||||
/**
|
||||
* @typedef MediaProps
|
||||
* @property {string} src
|
||||
* @property {boolean} muted
|
||||
* @property {number} volume
|
||||
* @property {number} currentTime
|
||||
* @property {string} poster
|
||||
* @property {string} backgroundColor
|
||||
* @property {string} foregroundColor
|
||||
* @property {string} accentColor
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {string} statusId
|
||||
* @param {string} accountId
|
||||
* @param {string} playerType
|
||||
* @param {MediaProps} props
|
||||
* @returns {object}
|
||||
*/
|
||||
export const deployPictureInPicture = (statusId, accountId, playerType, props) => {
|
||||
// @ts-expect-error
|
||||
return (dispatch, getState) => {
|
||||
// Do not open a player for a toot that does not exist
|
||||
if (getState().hasIn(['statuses', statusId])) {
|
||||
dispatch({
|
||||
type: PICTURE_IN_PICTURE_DEPLOY,
|
||||
statusId,
|
||||
accountId,
|
||||
playerType,
|
||||
props,
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
* @return {object}
|
||||
*/
|
||||
export const removePictureInPicture = () => ({
|
||||
type: PICTURE_IN_PICTURE_REMOVE,
|
||||
});
|
31
app/javascript/mastodon/actions/picture_in_picture.ts
Normal file
31
app/javascript/mastodon/actions/picture_in_picture.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { createAction } from '@reduxjs/toolkit';
|
||||
|
||||
import type { PIPMediaProps } from 'mastodon/reducers/picture_in_picture';
|
||||
import { createAppAsyncThunk } from 'mastodon/store/typed_functions';
|
||||
|
||||
interface DeployParams {
|
||||
statusId: string;
|
||||
accountId: string;
|
||||
playerType: 'audio' | 'video';
|
||||
props: PIPMediaProps;
|
||||
}
|
||||
|
||||
export const removePictureInPicture = createAction('pip/remove');
|
||||
|
||||
export const deployPictureInPictureAction =
|
||||
createAction<DeployParams>('pip/deploy');
|
||||
|
||||
export const deployPictureInPicture = createAppAsyncThunk(
|
||||
'pip/deploy',
|
||||
(args: DeployParams, { dispatch, getState }) => {
|
||||
const { statusId } = args;
|
||||
|
||||
// Do not open a player for a toot that does not exist
|
||||
|
||||
// @ts-expect-error state.statuses is not yet typed
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
if (getState().hasIn(['statuses', statusId])) {
|
||||
dispatch(deployPictureInPictureAction(args));
|
||||
}
|
||||
},
|
||||
);
|
|
@ -29,9 +29,14 @@ const setCSRFHeader = () => {
|
|||
|
||||
void ready(setCSRFHeader);
|
||||
|
||||
export const authorizationTokenFromState = (getState?: GetState) => {
|
||||
return (
|
||||
getState && (getState().meta.get('access_token', '') as string | false)
|
||||
);
|
||||
};
|
||||
|
||||
const authorizationHeaderFromState = (getState?: GetState) => {
|
||||
const accessToken =
|
||||
getState && (getState().meta.get('access_token', '') as string);
|
||||
const accessToken = authorizationTokenFromState(getState);
|
||||
|
||||
if (!accessToken) {
|
||||
return {};
|
||||
|
|
7
app/javascript/mastodon/api_types/markers.ts
Normal file
7
app/javascript/mastodon/api_types/markers.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
// See app/serializers/rest/account_serializer.rb
|
||||
|
||||
export interface MarkerJSON {
|
||||
last_read_id: string;
|
||||
version: string;
|
||||
updated_at: string;
|
||||
}
|
|
@ -1,26 +1,26 @@
|
|||
import type { PropsWithChildren } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
interface BaseProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
interface BaseProps
|
||||
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
|
||||
block?: boolean;
|
||||
secondary?: boolean;
|
||||
text?: JSX.Element;
|
||||
}
|
||||
|
||||
interface PropsWithChildren extends BaseProps {
|
||||
text?: never;
|
||||
interface PropsChildren extends PropsWithChildren<BaseProps> {
|
||||
text?: undefined;
|
||||
}
|
||||
|
||||
interface PropsWithText extends BaseProps {
|
||||
text: JSX.Element;
|
||||
children: never;
|
||||
text: JSX.Element | string;
|
||||
children?: undefined;
|
||||
}
|
||||
|
||||
type Props = PropsWithText | PropsWithChildren;
|
||||
type Props = PropsWithText | PropsChildren;
|
||||
|
||||
export const Button: React.FC<Props> = ({
|
||||
text,
|
||||
type = 'button',
|
||||
onClick,
|
||||
disabled,
|
||||
|
@ -28,6 +28,7 @@ export const Button: React.FC<Props> = ({
|
|||
secondary,
|
||||
className,
|
||||
title,
|
||||
text,
|
||||
children,
|
||||
...props
|
||||
}) => {
|
||||
|
|
|
@ -191,7 +191,7 @@ const timeRemainingString = (
|
|||
interface Props {
|
||||
intl: IntlShape;
|
||||
timestamp: string;
|
||||
year: number;
|
||||
year?: number;
|
||||
futureDate?: boolean;
|
||||
short?: boolean;
|
||||
}
|
||||
|
@ -203,11 +203,6 @@ class RelativeTimestamp extends Component<Props, States> {
|
|||
now: Date.now(),
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
year: new Date().getFullYear(),
|
||||
short: true,
|
||||
};
|
||||
|
||||
_timer: number | undefined;
|
||||
|
||||
shouldComponentUpdate(nextProps: Props, nextState: States) {
|
||||
|
@ -257,7 +252,13 @@ class RelativeTimestamp extends Component<Props, States> {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { timestamp, intl, year, futureDate, short } = this.props;
|
||||
const {
|
||||
timestamp,
|
||||
intl,
|
||||
futureDate,
|
||||
year = new Date().getFullYear(),
|
||||
short = true,
|
||||
} = this.props;
|
||||
|
||||
const timeGiven = timestamp.includes('T');
|
||||
const date = new Date(timestamp);
|
||||
|
|
|
@ -91,7 +91,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
|
||||
static propTypes = {
|
||||
status: ImmutablePropTypes.map.isRequired,
|
||||
relationship: ImmutablePropTypes.map,
|
||||
relationship: ImmutablePropTypes.record,
|
||||
onReply: PropTypes.func,
|
||||
onFavourite: PropTypes.func,
|
||||
onEmojiReact: PropTypes.func,
|
||||
|
@ -345,7 +345,9 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
menu.push(null);
|
||||
}
|
||||
|
||||
menu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancelReblog : messages.reblog), action: this.handleReblogForceModalClick });
|
||||
if (status.get('visibility_ex') !== 'limited') {
|
||||
menu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancelReblog : messages.reblog), action: this.handleReblogForceModalClick });
|
||||
}
|
||||
|
||||
if (publicStatus) {
|
||||
menu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference });
|
||||
|
|
|
@ -11,22 +11,10 @@ import QuietTimeIcon from '@/material-icons/400-24px/quiet_time.svg?react';
|
|||
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
||||
import LimitedIcon from '@/material-icons/400-24px/shield.svg?react';
|
||||
import PersonalIcon from '@/material-icons/400-24px/sticky_note.svg?react';
|
||||
import type { StatusVisibility } from 'mastodon/models/status';
|
||||
|
||||
import { Icon } from './icon';
|
||||
|
||||
type Visibility =
|
||||
| 'public'
|
||||
| 'unlisted'
|
||||
| 'private'
|
||||
| 'direct'
|
||||
| 'public_unlisted'
|
||||
| 'login'
|
||||
| 'mutual'
|
||||
| 'circle'
|
||||
| 'personal'
|
||||
| 'reply'
|
||||
| 'limited';
|
||||
|
||||
const messages = defineMessages({
|
||||
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||
public_unlisted_short: {
|
||||
|
@ -71,7 +59,7 @@ const messages = defineMessages({
|
|||
},
|
||||
});
|
||||
|
||||
export const VisibilityIcon: React.FC<{ visibility: Visibility }> = ({
|
||||
export const VisibilityIcon: React.FC<{ visibility: StatusVisibility }> = ({
|
||||
visibility,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
|
|
@ -8,7 +8,6 @@ import {
|
|||
} from '../actions/accounts';
|
||||
import { showAlertForError } from '../actions/alerts';
|
||||
import { initBlockModal } from '../actions/blocks';
|
||||
import { initBoostModal } from '../actions/boosts';
|
||||
import {
|
||||
replyCompose,
|
||||
mentionCompose,
|
||||
|
@ -112,12 +111,12 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
|
|||
if ((e && e.shiftKey) || !boostModal) {
|
||||
this.onModalReblog(status);
|
||||
} else {
|
||||
dispatch(initBoostModal({ status, onReblog: this.onModalReblog }));
|
||||
dispatch(openModal({ modalType: 'BOOST', modalProps: { status, onReblog: this.onModalReblog } }));
|
||||
}
|
||||
},
|
||||
|
||||
onReblogForceModal (status) {
|
||||
dispatch(initBoostModal({ status, onReblog: this.onModalReblog }));
|
||||
dispatch(openModal({ modalType: 'BOOST', modalProps: { status, onReblog: this.onModalReblog } }));
|
||||
},
|
||||
|
||||
onFavourite (status) {
|
||||
|
@ -296,7 +295,7 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
|
|||
},
|
||||
|
||||
deployPictureInPicture (status, type, mediaProps) {
|
||||
dispatch(deployPictureInPicture(status.get('id'), status.getIn(['account', 'id']), type, mediaProps));
|
||||
dispatch(deployPictureInPicture({statusId: status.get('id'), accountId: status.getIn(['account', 'id']), playerType: type, props: mediaProps}));
|
||||
},
|
||||
|
||||
onInteractionModal (type, status) {
|
||||
|
|
|
@ -87,7 +87,7 @@ class GettingStarted extends ImmutablePureComponent {
|
|||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
myAccount: ImmutablePropTypes.map,
|
||||
myAccount: ImmutablePropTypes.record,
|
||||
multiColumn: PropTypes.bool,
|
||||
fetchFollowRequests: PropTypes.func.isRequired,
|
||||
unreadFollowRequests: PropTypes.number,
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import { initBoostModal } from '../../../actions/boosts';
|
||||
import { mentionCompose } from '../../../actions/compose';
|
||||
import {
|
||||
reblog,
|
||||
|
@ -9,6 +8,7 @@ import {
|
|||
unfavourite,
|
||||
emojiReact,
|
||||
} from '../../../actions/interactions';
|
||||
import { openModal } from '../../../actions/modal';
|
||||
import {
|
||||
hideStatus,
|
||||
revealStatus,
|
||||
|
@ -50,7 +50,7 @@ const mapDispatchToProps = dispatch => ({
|
|||
if (e.shiftKey || !boostModal) {
|
||||
this.onModalReblog(status);
|
||||
} else {
|
||||
dispatch(initBoostModal({ status, onReblog: this.onModalReblog }));
|
||||
dispatch(openModal({ modalType: 'BOOST', modalProps: { status, onReblog: this.onModalReblog } }));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -59,7 +59,7 @@ const mapDispatchToProps = dispatch => ({
|
|||
if (status.get('reblogged')) {
|
||||
dispatch(unreblog(status));
|
||||
} else {
|
||||
dispatch(initBoostModal({ status, onReblog: this.onModalReblog }));
|
||||
dispatch(openModal({ modalType: 'BOOST', modalProps: { status, onReblog: this.onModalReblog } }));
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
|||
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
||||
import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react';
|
||||
import StarIcon from '@/material-icons/400-24px/star.svg?react';
|
||||
import { initBoostModal } from 'mastodon/actions/boosts';
|
||||
import { replyCompose } from 'mastodon/actions/compose';
|
||||
import { reblog, favourite, unreblog, unfavourite } from 'mastodon/actions/interactions';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
|
@ -140,7 +139,7 @@ class Footer extends ImmutablePureComponent {
|
|||
} else if ((e && e.shiftKey) || !boostModal) {
|
||||
this._performReblog(status);
|
||||
} else {
|
||||
dispatch(initBoostModal({ status, onReblog: this._performReblog }));
|
||||
dispatch(openModal({ modalType: 'BOOST', modalProps: { status, onReblog: this._performReblog } }));
|
||||
}
|
||||
} else {
|
||||
dispatch(openModal({
|
||||
|
@ -210,4 +209,4 @@ class Footer extends ImmutablePureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default withRouter(connect(makeMapStateToProps)(injectIntl(Footer)));
|
||||
export default connect(makeMapStateToProps)(withRouter(injectIntl(Footer)));
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||
import { Avatar } from 'mastodon/components/avatar';
|
||||
import { DisplayName } from 'mastodon/components/display_name';
|
||||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
|
||||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, { accountId }) => ({
|
||||
account: state.getIn(['accounts', accountId]),
|
||||
});
|
||||
|
||||
class Header extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
accountId: PropTypes.string.isRequired,
|
||||
statusId: PropTypes.string.isRequired,
|
||||
account: ImmutablePropTypes.record.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { account, statusId, onClose, intl } = this.props;
|
||||
|
||||
return (
|
||||
<div className='picture-in-picture__header'>
|
||||
<Link to={`/@${account.get('acct')}/${statusId}`} className='picture-in-picture__header__account'>
|
||||
<Avatar account={account} size={36} />
|
||||
<DisplayName account={account} />
|
||||
</Link>
|
||||
|
||||
<IconButton icon='times' iconComponent={CloseIcon} onClick={onClose} title={intl.formatMessage(messages.close)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(injectIntl(Header));
|
|
@ -0,0 +1,46 @@
|
|||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||
import { Avatar } from 'mastodon/components/avatar';
|
||||
import { DisplayName } from 'mastodon/components/display_name';
|
||||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
import { useAppSelector } from 'mastodon/store';
|
||||
|
||||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
});
|
||||
|
||||
interface Props {
|
||||
accountId: string;
|
||||
statusId: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const Header: React.FC<Props> = ({ accountId, statusId, onClose }) => {
|
||||
const account = useAppSelector((state) => state.accounts.get(accountId));
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
if (!account) return null;
|
||||
|
||||
return (
|
||||
<div className='picture-in-picture__header'>
|
||||
<Link
|
||||
to={`/@${account.get('acct')}/${statusId}`}
|
||||
className='picture-in-picture__header__account'
|
||||
>
|
||||
<Avatar account={account} size={36} />
|
||||
<DisplayName account={account} />
|
||||
</Link>
|
||||
|
||||
<IconButton
|
||||
icon='times'
|
||||
iconComponent={CloseIcon}
|
||||
onClick={onClose}
|
||||
title={intl.formatMessage(messages.close)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,89 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { Component } from 'react';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { removePictureInPicture } from 'mastodon/actions/picture_in_picture';
|
||||
import Audio from 'mastodon/features/audio';
|
||||
import Video from 'mastodon/features/video';
|
||||
|
||||
import Footer from './components/footer';
|
||||
import Header from './components/header';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
...state.get('picture_in_picture'),
|
||||
});
|
||||
|
||||
class PictureInPicture extends Component {
|
||||
|
||||
static propTypes = {
|
||||
statusId: PropTypes.string,
|
||||
accountId: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
src: PropTypes.string,
|
||||
muted: PropTypes.bool,
|
||||
volume: PropTypes.number,
|
||||
currentTime: PropTypes.number,
|
||||
poster: PropTypes.string,
|
||||
backgroundColor: PropTypes.string,
|
||||
foregroundColor: PropTypes.string,
|
||||
accentColor: PropTypes.string,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
handleClose = () => {
|
||||
const { dispatch } = this.props;
|
||||
dispatch(removePictureInPicture());
|
||||
};
|
||||
|
||||
render () {
|
||||
const { type, src, currentTime, accountId, statusId } = this.props;
|
||||
|
||||
if (!currentTime) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let player;
|
||||
|
||||
if (type === 'video') {
|
||||
player = (
|
||||
<Video
|
||||
src={src}
|
||||
currentTime={this.props.currentTime}
|
||||
volume={this.props.volume}
|
||||
muted={this.props.muted}
|
||||
autoPlay
|
||||
inline
|
||||
alwaysVisible
|
||||
/>
|
||||
);
|
||||
} else if (type === 'audio') {
|
||||
player = (
|
||||
<Audio
|
||||
src={src}
|
||||
currentTime={this.props.currentTime}
|
||||
volume={this.props.volume}
|
||||
muted={this.props.muted}
|
||||
poster={this.props.poster}
|
||||
backgroundColor={this.props.backgroundColor}
|
||||
foregroundColor={this.props.foregroundColor}
|
||||
accentColor={this.props.accentColor}
|
||||
autoPlay
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='picture-in-picture'>
|
||||
<Header accountId={accountId} statusId={statusId} onClose={this.handleClose} />
|
||||
|
||||
{player}
|
||||
|
||||
<Footer statusId={statusId} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(PictureInPicture);
|
|
@ -0,0 +1,79 @@
|
|||
import { useCallback } from 'react';
|
||||
|
||||
import { removePictureInPicture } from 'mastodon/actions/picture_in_picture';
|
||||
import Audio from 'mastodon/features/audio';
|
||||
import Video from 'mastodon/features/video';
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store/typed_functions';
|
||||
|
||||
import Footer from './components/footer';
|
||||
import { Header } from './components/header';
|
||||
|
||||
export const PictureInPicture: React.FC = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
dispatch(removePictureInPicture());
|
||||
}, [dispatch]);
|
||||
|
||||
const pipState = useAppSelector((s) => s.picture_in_picture);
|
||||
|
||||
if (pipState.type === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
type,
|
||||
src,
|
||||
currentTime,
|
||||
accountId,
|
||||
statusId,
|
||||
volume,
|
||||
muted,
|
||||
poster,
|
||||
backgroundColor,
|
||||
foregroundColor,
|
||||
accentColor,
|
||||
} = pipState;
|
||||
|
||||
let player;
|
||||
|
||||
switch (type) {
|
||||
case 'video':
|
||||
player = (
|
||||
<Video
|
||||
src={src}
|
||||
currentTime={currentTime}
|
||||
volume={volume}
|
||||
muted={muted}
|
||||
autoPlay
|
||||
inline
|
||||
alwaysVisible
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'audio':
|
||||
player = (
|
||||
<Audio
|
||||
src={src}
|
||||
currentTime={currentTime}
|
||||
volume={volume}
|
||||
muted={muted}
|
||||
poster={poster}
|
||||
backgroundColor={backgroundColor}
|
||||
foregroundColor={foregroundColor}
|
||||
accentColor={accentColor}
|
||||
autoPlay
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='picture-in-picture'>
|
||||
<Header accountId={accountId} statusId={statusId} onClose={handleClose} />
|
||||
|
||||
{player}
|
||||
|
||||
<Footer statusId={statusId} />
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -83,7 +83,7 @@ class ActionBar extends PureComponent {
|
|||
|
||||
static propTypes = {
|
||||
status: ImmutablePropTypes.map.isRequired,
|
||||
relationship: ImmutablePropTypes.map,
|
||||
relationship: ImmutablePropTypes.record,
|
||||
onReply: PropTypes.func.isRequired,
|
||||
onReblog: PropTypes.func.isRequired,
|
||||
onReblogForceModal: PropTypes.func.isRequired,
|
||||
|
@ -271,7 +271,10 @@ class ActionBar extends PureComponent {
|
|||
|
||||
if (signedIn) {
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancel_reblog : messages.reblog), action: this.handleReblogForceModalClick });
|
||||
|
||||
if (status.get('visibility_ex') !== 'limited') {
|
||||
menu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancel_reblog : messages.reblog), action: this.handleReblogForceModalClick });
|
||||
}
|
||||
|
||||
if (publicStatus) {
|
||||
menu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference });
|
||||
|
|
|
@ -4,7 +4,6 @@ import { connect } from 'react-redux';
|
|||
|
||||
import { showAlertForError } from '../../../actions/alerts';
|
||||
import { initBlockModal } from '../../../actions/blocks';
|
||||
import { initBoostModal } from '../../../actions/boosts';
|
||||
import {
|
||||
replyCompose,
|
||||
mentionCompose,
|
||||
|
@ -87,7 +86,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
if (e.shiftKey || !boostModal) {
|
||||
this.onModalReblog(status);
|
||||
} else {
|
||||
dispatch(initBoostModal({ status, onReblog: this.onModalReblog }));
|
||||
dispatch(openModal({ modalType: 'BOOST', modalProps: { status, onReblog: this.onModalReblog } }));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -96,7 +95,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
if (status.get('reblogged')) {
|
||||
dispatch(unreblog(status));
|
||||
} else {
|
||||
dispatch(initBoostModal({ status, onReblog: this.onModalReblog }));
|
||||
dispatch(openModal({ modalType: 'BOOST', modalProps: { status, onReblog: this.onModalReblog } }));
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -27,7 +27,6 @@ import {
|
|||
unmuteAccount,
|
||||
} from '../../actions/accounts';
|
||||
import { initBlockModal } from '../../actions/blocks';
|
||||
import { initBoostModal } from '../../actions/boosts';
|
||||
import {
|
||||
replyCompose,
|
||||
mentionCompose,
|
||||
|
@ -353,7 +352,7 @@ class Status extends ImmutablePureComponent {
|
|||
if (!force && ((e && e.shiftKey) || !boostModal)) {
|
||||
this.handleModalReblog(status);
|
||||
} else {
|
||||
dispatch(initBoostModal({ status, onReblog: this.handleModalReblog }));
|
||||
dispatch(openModal({ modalType: 'BOOST', modalProps: { status, onReblog: this.handleModalReblog } }));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -1,126 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
||||
import { changeBoostPrivacy } from 'mastodon/actions/boosts';
|
||||
import AttachmentList from 'mastodon/components/attachment_list';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { VisibilityIcon } from 'mastodon/components/visibility_icon';
|
||||
import PrivacyDropdown from 'mastodon/features/compose/components/privacy_dropdown';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
import { Avatar } from '../../../components/avatar';
|
||||
import { Button } from '../../../components/button';
|
||||
import { DisplayName } from '../../../components/display_name';
|
||||
import { RelativeTimestamp } from '../../../components/relative_timestamp';
|
||||
import StatusContent from '../../../components/status_content';
|
||||
|
||||
const messages = defineMessages({
|
||||
cancel_reblog: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
|
||||
reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
|
||||
});
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
privacy: state.getIn(['boosts', 'new', 'privacy']),
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
onChangeBoostPrivacy(value) {
|
||||
dispatch(changeBoostPrivacy(value));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
class BoostModal extends ImmutablePureComponent {
|
||||
static propTypes = {
|
||||
status: ImmutablePropTypes.map.isRequired,
|
||||
onReblog: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onChangeBoostPrivacy: PropTypes.func.isRequired,
|
||||
privacy: PropTypes.string.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
...WithRouterPropTypes,
|
||||
};
|
||||
|
||||
handleReblog = () => {
|
||||
this.props.onReblog(this.props.status, this.props.privacy);
|
||||
this.props.onClose();
|
||||
};
|
||||
|
||||
handleAccountClick = (e) => {
|
||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.props.onClose();
|
||||
this.props.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
|
||||
}
|
||||
};
|
||||
|
||||
_findContainer = () => {
|
||||
return document.getElementsByClassName('modal-root__container')[0];
|
||||
};
|
||||
|
||||
render () {
|
||||
const { status, privacy, intl } = this.props;
|
||||
const buttonText = status.get('reblogged') ? messages.cancel_reblog : messages.reblog;
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal boost-modal'>
|
||||
<div className='boost-modal__container'>
|
||||
<div className={classNames('status', `status-${status.get('visibility_ex')}`, 'light')}>
|
||||
<div className='status__info'>
|
||||
<a href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
|
||||
<span className='status__visibility-icon'><VisibilityIcon visibility={status.get('limited_scope') || status.get('visibility_ex')} /></span>
|
||||
<RelativeTimestamp timestamp={status.get('created_at')} />
|
||||
</a>
|
||||
|
||||
<a onClick={this.handleAccountClick} href={`/@${status.getIn(['account', 'acct'])}`} className='status__display-name'>
|
||||
<div className='status__avatar'>
|
||||
<Avatar account={status.get('account')} size={48} />
|
||||
</div>
|
||||
|
||||
<DisplayName account={status.get('account')} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<StatusContent status={status} />
|
||||
|
||||
{status.get('media_attachments').size > 0 && (
|
||||
<AttachmentList
|
||||
compact
|
||||
media={status.get('media_attachments')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='boost-modal__action-bar'>
|
||||
<div><FormattedMessage id='boost_modal.combo' defaultMessage='You can press {combo} to skip this next time' values={{ combo: <span>Shift + <Icon id='retweet' icon={RepeatIcon} /></span> }} /></div>
|
||||
{status.get('visibility') !== 'private' && !status.get('reblogged') && (
|
||||
<PrivacyDropdown
|
||||
noDirect
|
||||
noLimited
|
||||
value={privacy}
|
||||
container={this._findContainer}
|
||||
onChange={this.props.onChangeBoostPrivacy}
|
||||
/>
|
||||
)}
|
||||
<Button text={intl.formatMessage(buttonText)} onClick={this.handleReblog} autoFocus />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(injectIntl(BoostModal)));
|
164
app/javascript/mastodon/features/ui/components/boost_modal.tsx
Normal file
164
app/javascript/mastodon/features/ui/components/boost_modal.tsx
Normal file
|
@ -0,0 +1,164 @@
|
|||
import type { MouseEventHandler } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { useHistory } from 'react-router';
|
||||
|
||||
import type Immutable from 'immutable';
|
||||
|
||||
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
||||
import AttachmentList from 'mastodon/components/attachment_list';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { VisibilityIcon } from 'mastodon/components/visibility_icon';
|
||||
import PrivacyDropdown from 'mastodon/features/compose/components/privacy_dropdown';
|
||||
import type { Account } from 'mastodon/models/account';
|
||||
import type { Status, StatusVisibility } from 'mastodon/models/status';
|
||||
import { useAppSelector } from 'mastodon/store';
|
||||
|
||||
import { Avatar } from '../../../components/avatar';
|
||||
import { Button } from '../../../components/button';
|
||||
import { DisplayName } from '../../../components/display_name';
|
||||
import { RelativeTimestamp } from '../../../components/relative_timestamp';
|
||||
import StatusContent from '../../../components/status_content';
|
||||
|
||||
const messages = defineMessages({
|
||||
cancel_reblog: {
|
||||
id: 'status.cancel_reblog_private',
|
||||
defaultMessage: 'Unboost',
|
||||
},
|
||||
reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
|
||||
});
|
||||
|
||||
export const BoostModal: React.FC<{
|
||||
status: Status;
|
||||
onClose: () => void;
|
||||
onReblog: (status: Status, privacy: StatusVisibility) => void;
|
||||
}> = ({ status, onReblog, onClose }) => {
|
||||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
|
||||
const default_privacy = useAppSelector(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
||||
(state) => state.compose.get('default_privacy') as StatusVisibility,
|
||||
);
|
||||
|
||||
const account = status.get('account') as Account;
|
||||
const statusVisibility = (status.get('limited_scope') ||
|
||||
status.get('visibility_ex')) as StatusVisibility;
|
||||
|
||||
const [privacy, setPrivacy] = useState<StatusVisibility>(
|
||||
statusVisibility === 'private' ? 'private' : default_privacy,
|
||||
);
|
||||
|
||||
const onPrivacyChange = useCallback((value: StatusVisibility) => {
|
||||
setPrivacy(value);
|
||||
}, []);
|
||||
|
||||
const handleReblog = useCallback(() => {
|
||||
onReblog(status, privacy);
|
||||
onClose();
|
||||
}, [onClose, onReblog, status, privacy]);
|
||||
|
||||
const handleAccountClick = useCallback<MouseEventHandler>(
|
||||
(e) => {
|
||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
onClose();
|
||||
history.push(`/@${account.acct}`);
|
||||
}
|
||||
},
|
||||
[history, onClose, account],
|
||||
);
|
||||
|
||||
const buttonText = status.get('reblogged')
|
||||
? messages.cancel_reblog
|
||||
: messages.reblog;
|
||||
|
||||
const findContainer = useCallback(
|
||||
() => document.getElementsByClassName('modal-root__container')[0],
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal boost-modal'>
|
||||
<div className='boost-modal__container'>
|
||||
<div
|
||||
className={classNames(
|
||||
'status',
|
||||
`status-${statusVisibility}`,
|
||||
'light',
|
||||
)}
|
||||
>
|
||||
<div className='status__info'>
|
||||
<a
|
||||
href={`/@${account.acct}/${status.get('id') as string}`}
|
||||
className='status__relative-time'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
>
|
||||
<span className='status__visibility-icon'>
|
||||
<VisibilityIcon visibility={statusVisibility} />
|
||||
</span>
|
||||
<RelativeTimestamp
|
||||
timestamp={status.get('created_at') as string}
|
||||
/>
|
||||
</a>
|
||||
|
||||
<a
|
||||
onClick={handleAccountClick}
|
||||
href={`/@${account.acct}`}
|
||||
className='status__display-name'
|
||||
>
|
||||
<div className='status__avatar'>
|
||||
<Avatar account={account} size={48} />
|
||||
</div>
|
||||
|
||||
<DisplayName account={account} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* @ts-expect-error Expected until StatusContent is typed */}
|
||||
<StatusContent status={status} />
|
||||
|
||||
{(status.get('media_attachments') as Immutable.List<unknown>).size >
|
||||
0 && (
|
||||
<AttachmentList compact media={status.get('media_attachments')} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='boost-modal__action-bar'>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='boost_modal.combo'
|
||||
defaultMessage='You can press {combo} to skip this next time'
|
||||
values={{
|
||||
combo: (
|
||||
<span>
|
||||
Shift + <Icon id='retweet' icon={RepeatIcon} />
|
||||
</span>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{statusVisibility !== 'private' && !status.get('reblogged') && (
|
||||
<PrivacyDropdown
|
||||
noDirect
|
||||
noLimited
|
||||
value={privacy}
|
||||
container={findContainer}
|
||||
onChange={onPrivacyChange}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
text={intl.formatMessage(buttonText)}
|
||||
onClick={handleReblog}
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -29,7 +29,7 @@ import BundleContainer from '../containers/bundle_container';
|
|||
|
||||
import ActionsModal from './actions_modal';
|
||||
import AudioModal from './audio_modal';
|
||||
import BoostModal from './boost_modal';
|
||||
import { BoostModal } from './boost_modal';
|
||||
import BundleModalError from './bundle_modal_error';
|
||||
import ConfirmationModal from './confirmation_modal';
|
||||
import FocalPointModal from './focal_point_modal';
|
||||
|
|
|
@ -14,7 +14,7 @@ import { HotKeys } from 'react-hotkeys';
|
|||
import { focusApp, unfocusApp, changeLayout } from 'mastodon/actions/app';
|
||||
import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodon/actions/markers';
|
||||
import { INTRODUCTION_VERSION } from 'mastodon/actions/onboarding';
|
||||
import PictureInPicture from 'mastodon/features/picture_in_picture';
|
||||
import { PictureInPicture } from 'mastodon/features/picture_in_picture';
|
||||
import { layoutFromWindow } from 'mastodon/is_mobile';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
|
|
|
@ -90,6 +90,8 @@
|
|||
"attachments_list.unprocessed": "(غير معالَج)",
|
||||
"audio.hide": "إخفاء المقطع الصوتي",
|
||||
"block_modal.remote_users_caveat": "Do t’i kërkojmë shërbyesit {domain} të respektojë vendimin tuaj. Por, pajtimi s’është i garantuar, ngaqë disa shërbyes mund t’i trajtojnë ndryshe bllokimet. Psotimet publike mundet të jenë ende të dukshme për përdorues pa bërë hyrje në llogari.",
|
||||
"block_modal.show_less": "اعرض أقلّ",
|
||||
"block_modal.show_more": "أظهر المزيد",
|
||||
"boost_modal.combo": "يُمكنك الضّغط على {combo} لتخطي هذا في المرة المُقبلة",
|
||||
"bundle_column_error.copy_stacktrace": "انسخ تقرير الخطأ",
|
||||
"bundle_column_error.error.body": "لا يمكن تقديم الصفحة المطلوبة. قد يكون بسبب خطأ في التعليمات البرمجية، أو مشكلة توافق المتصفح.",
|
||||
|
@ -201,6 +203,13 @@
|
|||
"dismissable_banner.explore_statuses": "هذه هي المنشورات الرائجة على الشبكات الاجتماعيّة اليوم. تظهر المنشورات المعاد نشرها والحائزة على مفضّلات أكثر في مرتبة عليا.",
|
||||
"dismissable_banner.explore_tags": "هذه هي الوسوم تكتسب جذب الاهتمام حاليًا على الويب الاجتماعي. الوسوم التي يستخدمها مختلف الناس تحتل مرتبة عليا.",
|
||||
"dismissable_banner.public_timeline": "هذه هي أحدث المنشورات العامة من الناس على الشبكة الاجتماعية التي يتبعها الناس على {domain}.",
|
||||
"domain_pill.server": "الخادِم",
|
||||
"domain_pill.their_handle": "مُعرِّفُه:",
|
||||
"domain_pill.their_server": "بيتهم الرقمي، حيث تُستضاف كافة منشوراتهم.",
|
||||
"domain_pill.their_username": "مُعرّفُهم الفريد على الخادم. من الممكن العثور على مستخدمين بنفس اسم المستخدم على خوادم مختلفة.",
|
||||
"domain_pill.username": "اسم المستخدم",
|
||||
"domain_pill.whats_in_a_handle": "ما المقصود بالمُعرِّف؟",
|
||||
"domain_pill.your_handle": "عنوانك الكامل:",
|
||||
"embed.instructions": "يمكنكم إدماج هذا المنشور على موقعكم الإلكتروني عن طريق نسخ الشفرة أدناه.",
|
||||
"embed.preview": "إليك ما سيبدو عليه:",
|
||||
"emoji_button.activity": "الأنشطة",
|
||||
|
@ -395,6 +404,13 @@
|
|||
"loading_indicator.label": "جاري التحميل…",
|
||||
"media_gallery.toggle_visible": "{number, plural, zero {} one {اخف الصورة} two {اخف الصورتين} few {اخف الصور} many {اخف الصور} other {اخف الصور}}",
|
||||
"moved_to_account_banner.text": "حسابك {disabledAccount} معطل حاليًا لأنك انتقلت إلى {movedToAccount}.",
|
||||
"mute_modal.hide_options": "إخفاء الخيارات",
|
||||
"mute_modal.show_options": "إظهار الخيارات",
|
||||
"mute_modal.they_can_mention_and_follow": "سيكون بإمكانه الإشارة إليك ومتابعتك، لكنك لن تره.",
|
||||
"mute_modal.they_wont_know": "لن يَعرف أنه قد تم كتمه.",
|
||||
"mute_modal.title": "أتريد كتم المُستخدم؟",
|
||||
"mute_modal.you_wont_see_mentions": "سوف لن تر المنشورات التي يُشار إليه.",
|
||||
"mute_modal.you_wont_see_posts": "سيكون بإمكانه رؤية منشوراتك، لكنك لن ترى منشوراته.",
|
||||
"navigation_bar.about": "عن",
|
||||
"navigation_bar.advanced_interface": "افتحه في واجهة الويب المتقدمة",
|
||||
"navigation_bar.blocks": "الحسابات المحجوبة",
|
||||
|
@ -430,14 +446,21 @@
|
|||
"notification.own_poll": "انتهى استطلاعك للرأي",
|
||||
"notification.poll": "لقد انتهى استطلاع رأي شاركتَ فيه",
|
||||
"notification.reblog": "قام {name} بمشاركة منشورك",
|
||||
"notification.relationships_severance_event.learn_more": "اعرف المزيد",
|
||||
"notification.status": "{name} نشر للتو",
|
||||
"notification.update": "عدّلَ {name} منشورًا",
|
||||
"notification_requests.accept": "موافقة",
|
||||
"notification_requests.dismiss": "تخطي",
|
||||
"notification_requests.notifications_from": "إشعارات من {name}",
|
||||
"notification_requests.title": "الإشعارات المصفاة",
|
||||
"notifications.clear": "مسح الإشعارات",
|
||||
"notifications.clear_confirmation": "متأكد من أنك تود مسح جميع الإشعارات الخاصة بك و المتلقاة إلى حد الآن ؟",
|
||||
"notifications.column_settings.admin.report": "التبليغات الجديدة:",
|
||||
"notifications.column_settings.admin.sign_up": "التسجيلات الجديدة:",
|
||||
"notifications.column_settings.alert": "إشعارات سطح المكتب",
|
||||
"notifications.column_settings.favourite": "المفضلة:",
|
||||
"notifications.column_settings.filter_bar.advanced": "عرض جميع الفئات",
|
||||
"notifications.column_settings.filter_bar.category": "شريط التصفية السريعة",
|
||||
"notifications.column_settings.follow": "متابعُون جُدُد:",
|
||||
"notifications.column_settings.follow_request": "الطلبات الجديد لِمتابَعتك:",
|
||||
"notifications.column_settings.mention": "الإشارات:",
|
||||
|
@ -463,6 +486,10 @@
|
|||
"notifications.permission_denied": "تنبيهات سطح المكتب غير متوفرة بسبب رفض أذونات المتصفح مسبقاً",
|
||||
"notifications.permission_denied_alert": "لا يمكن تفعيل إشعارات سطح المكتب، لأن إذن المتصفح قد تم رفضه سابقاً",
|
||||
"notifications.permission_required": "إشعارات سطح المكتب غير متوفرة لأنه لم يتم منح الإذن المطلوب.",
|
||||
"notifications.policy.filter_new_accounts_title": "حسابات جديدة",
|
||||
"notifications.policy.filter_not_followers_title": "أشخاص لا يتابعونك",
|
||||
"notifications.policy.filter_not_following_hint": "حتى توافق عليهم يدويا",
|
||||
"notifications.policy.filter_not_following_title": "أشخاص لا تتابعهم",
|
||||
"notifications_permission_banner.enable": "تفعيل إشعارات سطح المكتب",
|
||||
"notifications_permission_banner.how_to_control": "لتلقي الإشعارات عندما لا يكون ماستدون مفتوح، قم بتفعيل إشعارات سطح المكتب، يمكنك التحكم بدقة في أنواع التفاعلات التي تولد إشعارات سطح المكتب من خلال زر الـ{icon} أعلاه بمجرد تفعيلها.",
|
||||
"notifications_permission_banner.title": "لا تفوت شيئاً أبداً",
|
||||
|
|
|
@ -471,6 +471,7 @@
|
|||
"notification.own_poll": "Ваша апытанне скончылася",
|
||||
"notification.poll": "Апытанне, дзе вы прынялі ўдзел, скончылася",
|
||||
"notification.reblog": "{name} пашырыў ваш допіс",
|
||||
"notification.relationships_severance_event.learn_more": "Даведацца больш",
|
||||
"notification.status": "Новы допіс ад {name}",
|
||||
"notification.update": "Допіс {name} адрэдагаваны",
|
||||
"notification_requests.accept": "Прыняць",
|
||||
|
@ -484,6 +485,7 @@
|
|||
"notifications.column_settings.alert": "Апавяшчэнні на працоўным стале",
|
||||
"notifications.column_settings.favourite": "Упадабанае:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Паказаць усе катэгорыі",
|
||||
"notifications.column_settings.filter_bar.category": "Панэль хуткай фільтрацыі",
|
||||
"notifications.column_settings.follow": "Новыя падпісчыкі:",
|
||||
"notifications.column_settings.follow_request": "Новыя запыты на падпіску:",
|
||||
"notifications.column_settings.mention": "Згадванні:",
|
||||
|
|
|
@ -471,6 +471,11 @@
|
|||
"notification.own_poll": "Анкетата ви приключи",
|
||||
"notification.poll": "Анкета, в която гласувахте, приключи",
|
||||
"notification.reblog": "{name} подсили ваша публикация",
|
||||
"notification.relationships_severance_event": "Изгуби се връзката с {name}",
|
||||
"notification.relationships_severance_event.account_suspension": "Администратор от {from} спря {target}, което значи че повече не може да получавате новости от тях или да взаимодействате с тях.",
|
||||
"notification.relationships_severance_event.domain_block": "Администратор от {from} блокира {target}, вкючващо {followersCount} от последователите ви и {followingCount, plural, one {# акаунт, който} other {# акаунта, които}} следвате.",
|
||||
"notification.relationships_severance_event.learn_more": "Научете повече",
|
||||
"notification.relationships_severance_event.user_domain_block": "Блокирахте {target}, премахвайки {followersCount} от последователите си и {followingCount, plural, one {# акаунт, който} other {# акаунта, които}} следвате.",
|
||||
"notification.status": "{name} току-що публикува",
|
||||
"notification.update": "{name} промени публикация",
|
||||
"notification_requests.accept": "Приемам",
|
||||
|
|
|
@ -472,7 +472,7 @@
|
|||
"notification.own_poll": "La teva enquesta ha finalitzat",
|
||||
"notification.poll": "Ha finalitzat una enquesta en què has votat",
|
||||
"notification.reblog": "{name} t'ha impulsat",
|
||||
"notification.relationships_severance_event": "Connexions perdudes amb {name}",
|
||||
"notification.relationships_severance_event": "S'han perdut les connexions amb {name}",
|
||||
"notification.relationships_severance_event.account_suspension": "Un administrador de {from} ha suspès {target}; això vol dir que ja no en podreu rebre actualitzacions o interactuar-hi.",
|
||||
"notification.relationships_severance_event.domain_block": "Un administrador de {from} ha blocat {target}, incloent-hi {followersCount} dels vostres seguidors i {followingCount, plural, one {# compte} other {# comptes}} que seguiu.",
|
||||
"notification.relationships_severance_event.learn_more": "Per a saber-ne més",
|
||||
|
|
|
@ -298,6 +298,7 @@
|
|||
"filter_modal.select_filter.title": "Filtrar esta publicación",
|
||||
"filter_modal.title.status": "Filtrar una publicación",
|
||||
"filtered_notifications_banner.pending_requests": "Notificaciones de {count, plural, =0 {nadie} one {una persona} other {# personas}} que podrías conocer",
|
||||
"filtered_notifications_banner.private_mentions": "{count, plural, one {mención privada} other {menciones privadas}}",
|
||||
"filtered_notifications_banner.title": "Notificaciones filtradas",
|
||||
"firehose.all": "Todas",
|
||||
"firehose.local": "Este servidor",
|
||||
|
|
|
@ -298,6 +298,7 @@
|
|||
"filter_modal.select_filter.title": "Filtrar esta publicación",
|
||||
"filter_modal.title.status": "Filtrar una publicación",
|
||||
"filtered_notifications_banner.pending_requests": "Notificaciones de {count, plural, =0 {nadie} one {una persona} other {# personas}} que podrías conocer",
|
||||
"filtered_notifications_banner.private_mentions": "{count, plural, one {mención privada} other {menciones privadas}}",
|
||||
"filtered_notifications_banner.title": "Notificaciones filtradas",
|
||||
"firehose.all": "Todas",
|
||||
"firehose.local": "Este servidor",
|
||||
|
|
|
@ -472,7 +472,11 @@
|
|||
"notification.own_poll": "Äänestyksesi on päättynyt",
|
||||
"notification.poll": "Kysely, johon osallistuit, on päättynyt",
|
||||
"notification.reblog": "{name} tehosti julkaisuasi",
|
||||
"notification.relationships_severance_event": "Menetettiin yhteydet palvelimeen {name}",
|
||||
"notification.relationships_severance_event.account_suspension": "Palvelimen {from} ylläpitäjä on jäädyttänyt verkkotunnuksen {target}, minkä takia et voi enää vastaanottaa heidän päivityksiään tai olla vuorovaikutuksessa heidän kanssaan.",
|
||||
"notification.relationships_severance_event.domain_block": "Palvelimen {from} ylläpitäjä on estänyt verkkotunnuksen {target}, mukaan lukien {followersCount} seuraajistasi ja {followingCount, plural, one {# seuratuistasi} other {# seuratuistasi}}.",
|
||||
"notification.relationships_severance_event.learn_more": "Lue lisää",
|
||||
"notification.relationships_severance_event.user_domain_block": "Olet estänyt verkkotunnuksen {target}, mikä poisti {followersCount} seuraajistasi ja {followingCount, plural, one {# seuratuistasi} other {# seuratuistasi}}.",
|
||||
"notification.status": "{name} julkaisi juuri",
|
||||
"notification.update": "{name} muokkasi julkaisua",
|
||||
"notification_requests.accept": "Hyväksy",
|
||||
|
|
|
@ -222,14 +222,14 @@
|
|||
"domain_pill.server": "Serveur",
|
||||
"domain_pill.their_handle": "Son identifiant :",
|
||||
"domain_pill.their_server": "Son foyer numérique, là où tous ses posts résident.",
|
||||
"domain_pill.their_username": "Son identifiant unique sur leur serveur. Il est possible de rencontrer des utilisateurs avec le même nom sur différents serveurs.",
|
||||
"domain_pill.their_username": "Son identifiant unique sur leur serveur. Il est possible de rencontrer des utilisateur·rice·s avec le même nom sur différents serveurs.",
|
||||
"domain_pill.username": "Nom d’utilisateur",
|
||||
"domain_pill.whats_in_a_handle": "Qu'est-ce qu'un identifiant ?",
|
||||
"domain_pill.who_they_are": "Comme un identifiant contient le nom et le service hébergeant une personne, vous pouvez interagir sur <button>les plateformes sociales implémentant ActivityPub</button>.",
|
||||
"domain_pill.who_you_are": "Comme un identifiant indique votre nom et le service vous hébergeant, vous pouvez interagir avec <button>les autres plateformes sociales implémentant ActivityPub</button>.",
|
||||
"domain_pill.your_handle": "Votre identifiant :",
|
||||
"domain_pill.your_server": "Votre foyer numérique, là où vos messages résident. Vous souhaitez changer ? Lancez un transfert vers un autre serveur quand vous le voulez et vos abonné·e·s suivront automatiquement.",
|
||||
"domain_pill.your_username": "Votre identifiant unique sur ce serveur. Il est possible de trouver des utilisateurs ayant le même nom d'utilisateur sur différents serveurs.",
|
||||
"domain_pill.your_username": "Votre identifiant unique sur ce serveur. Il est possible de rencontrer des utilisateur·rice·s ayant le même nom d'utilisateur sur différents serveurs.",
|
||||
"embed.instructions": "Intégrez cette publication à votre site en copiant le code ci-dessous.",
|
||||
"embed.preview": "Voici comment il apparaîtra:",
|
||||
"emoji_button.activity": "Activité",
|
||||
|
@ -298,6 +298,7 @@
|
|||
"filter_modal.select_filter.title": "Filtrer cette publication",
|
||||
"filter_modal.title.status": "Filtrer une publication",
|
||||
"filtered_notifications_banner.pending_requests": "Notifications {count, plural, =0 {de personne} one {d’une personne} other {de # personnes}} que vous pouvez connaitre",
|
||||
"filtered_notifications_banner.private_mentions": "{count, plural, one {mention privée} other {mentions privées}}",
|
||||
"filtered_notifications_banner.title": "Notifications filtrées",
|
||||
"firehose.all": "Tout",
|
||||
"firehose.local": "Ce serveur",
|
||||
|
@ -471,6 +472,11 @@
|
|||
"notification.own_poll": "Votre sondage est terminé",
|
||||
"notification.poll": "Un sondage auquel vous avez participé est terminé",
|
||||
"notification.reblog": "{name} a boosté votre message",
|
||||
"notification.relationships_severance_event": "Connexions perdues avec {name}",
|
||||
"notification.relationships_severance_event.account_suspension": "Un·e administrateur·rice de {from} a suspendu {target}, ce qui signifie que vous ne pourrez plus recevoir de mises à jour ou interagir avec lui.",
|
||||
"notification.relationships_severance_event.domain_block": "Un·e administrateur·rice de {from} en a bloqué {target}, comprenant {followersCount} de vos abonné·e·s et {followingCount, plural, one {# compte} other {# comptes}} vous suivez.",
|
||||
"notification.relationships_severance_event.learn_more": "En savoir plus",
|
||||
"notification.relationships_severance_event.user_domain_block": "Vous avez bloqué {target}, en supprimant {followersCount} de vos abonnés et {followingCount, plural, one {# compte} other {# comptes}} que vous suivez.",
|
||||
"notification.status": "{name} vient de publier",
|
||||
"notification.update": "{name} a modifié une publication",
|
||||
"notification_requests.accept": "Accepter",
|
||||
|
@ -483,6 +489,8 @@
|
|||
"notifications.column_settings.admin.sign_up": "Nouvelles inscriptions:",
|
||||
"notifications.column_settings.alert": "Notifications navigateur",
|
||||
"notifications.column_settings.favourite": "Favoris:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Afficher toutes les catégories",
|
||||
"notifications.column_settings.filter_bar.category": "Barre de filtre rapide",
|
||||
"notifications.column_settings.follow": "Nouveaux⋅elles abonné⋅e⋅s:",
|
||||
"notifications.column_settings.follow_request": "Nouvelles demandes d’abonnement:",
|
||||
"notifications.column_settings.mention": "Mentions:",
|
||||
|
|
|
@ -222,14 +222,14 @@
|
|||
"domain_pill.server": "Serveur",
|
||||
"domain_pill.their_handle": "Son identifiant :",
|
||||
"domain_pill.their_server": "Son foyer numérique, là où tous ses posts résident.",
|
||||
"domain_pill.their_username": "Son identifiant unique sur leur serveur. Il est possible de rencontrer des utilisateurs avec le même nom sur différents serveurs.",
|
||||
"domain_pill.their_username": "Son identifiant unique sur leur serveur. Il est possible de rencontrer des utilisateur·rice·s avec le même nom sur différents serveurs.",
|
||||
"domain_pill.username": "Nom d’utilisateur",
|
||||
"domain_pill.whats_in_a_handle": "Qu'est-ce qu'un identifiant ?",
|
||||
"domain_pill.who_they_are": "Comme un identifiant contient le nom et le service hébergeant une personne, vous pouvez interagir sur <button>les plateformes sociales implémentant ActivityPub</button>.",
|
||||
"domain_pill.who_you_are": "Comme un identifiant indique votre nom et le service vous hébergeant, vous pouvez interagir avec <button>les autres plateformes sociales implémentant ActivityPub</button>.",
|
||||
"domain_pill.your_handle": "Votre identifiant :",
|
||||
"domain_pill.your_server": "Votre foyer numérique, là où vos messages résident. Vous souhaitez changer ? Lancez un transfert vers un autre serveur quand vous le voulez et vos abonné·e·s suivront automatiquement.",
|
||||
"domain_pill.your_username": "Votre identifiant unique sur ce serveur. Il est possible de trouver des utilisateurs ayant le même nom d'utilisateur sur différents serveurs.",
|
||||
"domain_pill.your_username": "Votre identifiant unique sur ce serveur. Il est possible de rencontrer des utilisateur·rice·s ayant le même nom d'utilisateur sur différents serveurs.",
|
||||
"embed.instructions": "Intégrez ce message à votre site en copiant le code ci-dessous.",
|
||||
"embed.preview": "Il apparaîtra comme cela :",
|
||||
"emoji_button.activity": "Activités",
|
||||
|
@ -298,6 +298,7 @@
|
|||
"filter_modal.select_filter.title": "Filtrer ce message",
|
||||
"filter_modal.title.status": "Filtrer un message",
|
||||
"filtered_notifications_banner.pending_requests": "Notifications {count, plural, =0 {de personne} one {d’une personne} other {de # personnes}} que vous pouvez connaitre",
|
||||
"filtered_notifications_banner.private_mentions": "{count, plural, one {mention privée} other {mentions privées}}",
|
||||
"filtered_notifications_banner.title": "Notifications filtrées",
|
||||
"firehose.all": "Tout",
|
||||
"firehose.local": "Ce serveur",
|
||||
|
@ -471,6 +472,11 @@
|
|||
"notification.own_poll": "Votre sondage est terminé",
|
||||
"notification.poll": "Un sondage auquel vous avez participé vient de se terminer",
|
||||
"notification.reblog": "{name} a partagé votre message",
|
||||
"notification.relationships_severance_event": "Connexions perdues avec {name}",
|
||||
"notification.relationships_severance_event.account_suspension": "Un·e administrateur·rice de {from} a suspendu {target}, ce qui signifie que vous ne pourrez plus recevoir de mises à jour ou interagir avec lui.",
|
||||
"notification.relationships_severance_event.domain_block": "Un·e administrateur·rice de {from} en a bloqué {target}, comprenant {followersCount} de vos abonné·e·s et {followingCount, plural, one {# compte} other {# comptes}} vous suivez.",
|
||||
"notification.relationships_severance_event.learn_more": "En savoir plus",
|
||||
"notification.relationships_severance_event.user_domain_block": "Vous avez bloqué {target}, en supprimant {followersCount} de vos abonnés et {followingCount, plural, one {# compte} other {# comptes}} que vous suivez.",
|
||||
"notification.status": "{name} vient de publier",
|
||||
"notification.update": "{name} a modifié un message",
|
||||
"notification_requests.accept": "Accepter",
|
||||
|
@ -483,6 +489,8 @@
|
|||
"notifications.column_settings.admin.sign_up": "Nouvelles inscriptions :",
|
||||
"notifications.column_settings.alert": "Notifications du navigateur",
|
||||
"notifications.column_settings.favourite": "Favoris :",
|
||||
"notifications.column_settings.filter_bar.advanced": "Afficher toutes les catégories",
|
||||
"notifications.column_settings.filter_bar.category": "Barre de filtre rapide",
|
||||
"notifications.column_settings.follow": "Nouveaux·elles abonné·e·s :",
|
||||
"notifications.column_settings.follow_request": "Nouvelles demandes d’abonnement :",
|
||||
"notifications.column_settings.mention": "Mentions :",
|
||||
|
|
|
@ -298,6 +298,7 @@
|
|||
"filter_modal.select_filter.title": "Filtrar esta publicación",
|
||||
"filter_modal.title.status": "Filtrar unha publicación",
|
||||
"filtered_notifications_banner.pending_requests": "Notificacións de {count, plural, =0 {ninguén} one {unha persoa} other {# persoas}} que poderías coñecer",
|
||||
"filtered_notifications_banner.private_mentions": "{count, plural, one {mención privada} other {mencións privadas}}",
|
||||
"filtered_notifications_banner.title": "Notificacións filtradas",
|
||||
"firehose.all": "Todo",
|
||||
"firehose.local": "Este servidor",
|
||||
|
@ -471,6 +472,11 @@
|
|||
"notification.own_poll": "A túa enquisa rematou",
|
||||
"notification.poll": "Rematou a enquisa na que votaches",
|
||||
"notification.reblog": "{name} compartiu a túa publicación",
|
||||
"notification.relationships_severance_event": "Perdeuse a conexión con {name}",
|
||||
"notification.relationships_severance_event.account_suspension": "A administración de {from} suspendeu a {target}, o que significa que xa non vas recibir actualizacións de esa conta ou interactuar con ela.",
|
||||
"notification.relationships_severance_event.domain_block": "A administración de {from} bloqueou a {target}, que inclúe a {followersCount} das túas seguidoras e a {followingCount, plural, one {# conta} other {# contas}} que sigues.",
|
||||
"notification.relationships_severance_event.learn_more": "Saber máis",
|
||||
"notification.relationships_severance_event.user_domain_block": "Bloqueaches a {target}, eliminando a {followersCount} das túas seguidoras e a {followingCount, plural, one {# conta} other {# contas}} que sigues.",
|
||||
"notification.status": "{name} publicou",
|
||||
"notification.update": "{name} editou unha publicación",
|
||||
"notification_requests.accept": "Aceptar",
|
||||
|
|
|
@ -124,7 +124,7 @@
|
|||
"column.domain_blocks": "Letiltott domainek",
|
||||
"column.favourites": "Kedvencek",
|
||||
"column.firehose": "Hírfolyamok",
|
||||
"column.follow_requests": "Követési kérelmek",
|
||||
"column.follow_requests": "Követési kérések",
|
||||
"column.home": "Kezdőlap",
|
||||
"column.lists": "Listák",
|
||||
"column.mutes": "Némított felhasználók",
|
||||
|
@ -133,8 +133,8 @@
|
|||
"column.public": "Föderációs idővonal",
|
||||
"column_back_button.label": "Vissza",
|
||||
"column_header.hide_settings": "Beállítások elrejtése",
|
||||
"column_header.moveLeft_settings": "Oszlop elmozdítása balra",
|
||||
"column_header.moveRight_settings": "Oszlop elmozdítása jobbra",
|
||||
"column_header.moveLeft_settings": "Oszlop áthelyezése balra",
|
||||
"column_header.moveRight_settings": "Oszlop áthelyezése jobbra",
|
||||
"column_header.pin": "Kitűzés",
|
||||
"column_header.show_settings": "Beállítások megjelenítése",
|
||||
"column_header.unpin": "Kitűzés eltávolítása",
|
||||
|
@ -143,7 +143,7 @@
|
|||
"community.column_settings.media_only": "Csak média",
|
||||
"community.column_settings.remote_only": "Csak távoli",
|
||||
"compose.language.change": "Nyelv megváltoztatása",
|
||||
"compose.language.search": "Nyelv keresése...",
|
||||
"compose.language.search": "Nyelvek keresése…",
|
||||
"compose.published.body": "A bejegyzés publikálásra került.",
|
||||
"compose.published.open": "Megnyitás",
|
||||
"compose.saved.body": "A bejegyzés mentve.",
|
||||
|
@ -473,8 +473,8 @@
|
|||
"notification.poll": "Egy szavazás, melyben részt vettél, véget ért",
|
||||
"notification.reblog": "{name} megtolta a bejegyzésedet",
|
||||
"notification.relationships_severance_event": "Elvesztek a kapcsolatok vele: {name}",
|
||||
"notification.relationships_severance_event.account_suspension": "Egy admin a {from} kiszolgálóról felfüggesztette {target} fiókot, ami azt jelenti, hogy mostantól nem tudsz vele interaktálni vagy tőle értesítéseket kapni.",
|
||||
"notification.relationships_severance_event.domain_block": "Egy admin a {from} kiszolgálón letiltotta {target} domaint, beleértve {followersCount} követődet és {followingCount, plural, one {#} other {#}} általad követett személyt.",
|
||||
"notification.relationships_severance_event.account_suspension": "Egy admin a(z) {from} kiszolgálóról felfüggesztette {target} fiókját, ami azt jelenti, hogy mostantól nem fogsz róla értesítést kapni, és nem fogsz tudni vele kapcsolatba lépni.",
|
||||
"notification.relationships_severance_event.domain_block": "Egy admin a(z) {from} kiszolgálón letiltotta {target} domaint, beleértve {followersCount} követőt és {followingCount, plural, one {#} other {#}} követett fiókot.",
|
||||
"notification.relationships_severance_event.learn_more": "További információk",
|
||||
"notification.relationships_severance_event.user_domain_block": "Letiltottad a(z) {target} domaint, ezzel eltávolítva {followersCount} követőt és {followingCount, plural, one {#} other {#}} követett fiókot.",
|
||||
"notification.status": "{name} bejegyzést tett közzé",
|
||||
|
@ -492,7 +492,7 @@
|
|||
"notifications.column_settings.filter_bar.advanced": "Minden kategória megjelenítése",
|
||||
"notifications.column_settings.filter_bar.category": "Gyorsszűrő sáv",
|
||||
"notifications.column_settings.follow": "Új követők:",
|
||||
"notifications.column_settings.follow_request": "Új követési kérelmek:",
|
||||
"notifications.column_settings.follow_request": "Új követési kérések:",
|
||||
"notifications.column_settings.mention": "Megemlítések:",
|
||||
"notifications.column_settings.poll": "Szavazási eredmények:",
|
||||
"notifications.column_settings.push": "Leküldéses értesítések",
|
||||
|
@ -552,14 +552,14 @@
|
|||
"onboarding.share.next_steps": "Lehetséges következő lépések:",
|
||||
"onboarding.share.title": "Profil megosztása",
|
||||
"onboarding.start.lead": "Az új Mastodon-fiók használatra kész. Így hozhatod ki belőle a legtöbbet:",
|
||||
"onboarding.start.skip": "Szeretnél előreugrani?",
|
||||
"onboarding.start.skip": "Nincs szükséged segítségre a kezdéshez?",
|
||||
"onboarding.start.title": "Ez sikerült!",
|
||||
"onboarding.steps.follow_people.body": "A Mastodon az érdekes emberek követéséről szól.",
|
||||
"onboarding.steps.follow_people.title": "{count, plural, one {egy ember} other {# ember}} követése",
|
||||
"onboarding.steps.publish_status.body": "Üdvözöljük a világot.",
|
||||
"onboarding.steps.follow_people.title": "Szabd személyre a kezdőlapodat",
|
||||
"onboarding.steps.publish_status.body": "Köszöntsd a világot szöveggel, fotókkal, videókkal vagy szavazásokkal {emoji}",
|
||||
"onboarding.steps.publish_status.title": "Az első bejegyzés létrehozása",
|
||||
"onboarding.steps.setup_profile.body": "Mások nagyobb valószínűséggel lépnek kapcsolatba veled egy kitöltött profil esetén.",
|
||||
"onboarding.steps.setup_profile.title": "Profilod testreszabása",
|
||||
"onboarding.steps.setup_profile.body": "Növeld az interakciók számát a profilod részletesebb kitöltésével.",
|
||||
"onboarding.steps.setup_profile.title": "Szabd személyre a profilodat",
|
||||
"onboarding.steps.share_profile.body": "Tudasd az ismerőseiddel, hogyan találhatnak meg a Mastodonon",
|
||||
"onboarding.steps.share_profile.title": "Oszd meg a Mastodon profilodat",
|
||||
"onboarding.tips.2fa": "<strong>Tudtad?</strong> A fiókod biztonságossá teheted, ha a fiók beállításaiban beállítod a kétlépcsős hitelesítést. Bármilyen választott TOTP alkalmazással működik, nincs szükség telefonszámra!",
|
||||
|
@ -787,9 +787,9 @@
|
|||
"upload_modal.hint": "Kattints vagy húzd a kört az előnézetben arra a fókuszpontra, mely minden bélyegképen látható kell, hogy legyen.",
|
||||
"upload_modal.preparing_ocr": "OCR előkészítése…",
|
||||
"upload_modal.preview_label": "Előnézet ({ratio})",
|
||||
"upload_progress.label": "Feltöltés...",
|
||||
"upload_progress.label": "Feltöltés…",
|
||||
"upload_progress.processing": "Feldolgozás…",
|
||||
"username.taken": "Ez a felhasználónév foglalt. Válassz másikat",
|
||||
"username.taken": "Ez a felhasználónév foglalt. Válassz másikat.",
|
||||
"video.close": "Videó bezárása",
|
||||
"video.download": "Fájl letöltése",
|
||||
"video.exit_fullscreen": "Kilépés teljes képernyőből",
|
||||
|
@ -799,5 +799,5 @@
|
|||
"video.mute": "Hang némítása",
|
||||
"video.pause": "Szünet",
|
||||
"video.play": "Lejátszás",
|
||||
"video.unmute": "Hang némításának vége"
|
||||
"video.unmute": "Hang némításának feloldása"
|
||||
}
|
||||
|
|
|
@ -473,7 +473,10 @@
|
|||
"notification.poll": "Könnun sem þú tókst þátt í er lokið",
|
||||
"notification.reblog": "{name} endurbirti færsluna þína",
|
||||
"notification.relationships_severance_event": "Missti tengingar við {name}",
|
||||
"notification.relationships_severance_event.account_suspension": "Stjórnandi á {from} hefur fryst {target}, sem þýðir að þú færð ekki lengur skilaboð frá viðkomandi né átt í samskiptum við viðkomandi.",
|
||||
"notification.relationships_severance_event.domain_block": "Stjórnandi á {from} hefur lokað á {target} og þar með {followersCount} fylgjendur þína auk {followingCount, plural, one {# aðgangs} other {# aðganga}} sem þú fylgist með.",
|
||||
"notification.relationships_severance_event.learn_more": "Kanna nánar",
|
||||
"notification.relationships_severance_event.user_domain_block": "Þú hefur lokað á {target} og þar með fjarlægt {followersCount} fylgjendur þína auk {followingCount, plural, one {# aðgangs} other {# aðganga}} sem þú fylgist með.",
|
||||
"notification.status": "{name} sendi inn rétt í þessu",
|
||||
"notification.update": "{name} breytti færslu",
|
||||
"notification_requests.accept": "Samþykkja",
|
||||
|
|
|
@ -298,6 +298,7 @@
|
|||
"filter_modal.select_filter.title": "Filtra questo post",
|
||||
"filter_modal.title.status": "Filtra un post",
|
||||
"filtered_notifications_banner.pending_requests": "Notifiche da {count, plural, =0 {nessuno} one {una persona} other {# persone}} che potresti conoscere",
|
||||
"filtered_notifications_banner.private_mentions": "{count, plural,one {menzione privata} other {menzioni private}}",
|
||||
"filtered_notifications_banner.title": "Notifiche filtrate",
|
||||
"firehose.all": "Tutto",
|
||||
"firehose.local": "Questo server",
|
||||
|
@ -473,7 +474,9 @@
|
|||
"notification.reblog": "{name} ha rebloggato il tuo post",
|
||||
"notification.relationships_severance_event": "Connessioni perse con {name}",
|
||||
"notification.relationships_severance_event.account_suspension": "Un amministratore da {from} ha sospeso {target}, il che significa che non puoi più ricevere aggiornamenti da loro o interagire con loro.",
|
||||
"notification.relationships_severance_event.domain_block": "Un amministratore da {from} ha bloccato {target}, inclusi {followersCount} dei tuoi seguaci e {followingCount, plural, one {# account} other {# account}} che segui.",
|
||||
"notification.relationships_severance_event.learn_more": "Scopri di più",
|
||||
"notification.relationships_severance_event.user_domain_block": "Tu hai bloccato {target}, rimuovendo {followersCount} dei tuoi seguaci e {followingCount, plural, one {# account} other {# account}} che segui.",
|
||||
"notification.status": "{name} ha appena pubblicato un post",
|
||||
"notification.update": "{name} ha modificato un post",
|
||||
"notification_requests.accept": "Accetta",
|
||||
|
|
|
@ -264,6 +264,7 @@
|
|||
"confirmations.delete_antenna.message": "本当にこのアンテナを完全に削除しますか?",
|
||||
"confirmations.discard_edit_media.confirm": "破棄",
|
||||
"confirmations.discard_edit_media.message": "メディアの説明またはプレビューに保存されていない変更があります。それでも破棄しますか?",
|
||||
"confirmations.domain_block.confirm": "サーバーをブロック",
|
||||
"confirmations.domain_block.message": "本当に{domain}全体を非表示にしますか? 多くの場合は個別にブロックやミュートするだけで充分であり、また好ましいです。公開タイムラインにそのドメインのコンテンツが表示されなくなり、通知も届かなくなります。そのドメインのフォロワーはアンフォローされます。",
|
||||
"confirmations.edit.confirm": "編集",
|
||||
"confirmations.edit.message": "今編集すると現在作成中のメッセージが上書きされます。本当に実行しますか?",
|
||||
|
|
|
@ -221,6 +221,7 @@
|
|||
"filter_modal.select_filter.prompt_new": "Taggayt tamaynutt : {name}",
|
||||
"filter_modal.select_filter.search": "Nadi neɣ snulfu-d",
|
||||
"filter_modal.select_filter.title": "Sizdeg tassufeɣt-a",
|
||||
"filter_modal.title.status": "Sizdeg tassufeɣt",
|
||||
"firehose.all": "Akk",
|
||||
"firehose.local": "Deg uqeddac-ayi",
|
||||
"firehose.remote": "Iqeddacen nniḍen",
|
||||
|
@ -335,6 +336,7 @@
|
|||
"mute_modal.show_options": "Sken-d tinefrunin",
|
||||
"mute_modal.title": "Sgugem aseqdac?",
|
||||
"navigation_bar.about": "Ɣef",
|
||||
"navigation_bar.advanced_interface": "Ldi deg ugrudem n web leqqayen",
|
||||
"navigation_bar.blocks": "Iseqdacen yettusḥebsen",
|
||||
"navigation_bar.bookmarks": "Ticraḍ",
|
||||
"navigation_bar.community_timeline": "Tasuddemt tadigant",
|
||||
|
@ -364,6 +366,7 @@
|
|||
"notification.own_poll": "Tafrant-ik·im tfuk",
|
||||
"notification.poll": "Tfukk tefrant ideg tettekkaḍ",
|
||||
"notification.reblog": "{name} yebḍa tajewwiqt-ik i tikelt-nniḍen",
|
||||
"notification.relationships_severance_event.learn_more": "Issin ugar",
|
||||
"notification.status": "{name} akken i d-yessufeɣ",
|
||||
"notification_requests.accept": "Qbel",
|
||||
"notification_requests.dismiss": "Agi",
|
||||
|
@ -372,6 +375,8 @@
|
|||
"notifications.clear_confirmation": "Tebɣiḍ s tidet ad tekkseḍ akk tilɣa-inek·em i lebda?",
|
||||
"notifications.column_settings.alert": "Tilɣa n tnarit",
|
||||
"notifications.column_settings.favourite": "Imenyafen:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Sken-d akk taggayin",
|
||||
"notifications.column_settings.filter_bar.category": "Iri n usizdeg uzrib",
|
||||
"notifications.column_settings.follow": "Imeḍfaṛen imaynuten:",
|
||||
"notifications.column_settings.follow_request": "Isuturen imaynuten n teḍfeṛt:",
|
||||
"notifications.column_settings.mention": "Abdar:",
|
||||
|
|
|
@ -298,6 +298,7 @@
|
|||
"filter_modal.select_filter.title": "이 게시물을 필터",
|
||||
"filter_modal.title.status": "게시물 필터",
|
||||
"filtered_notifications_banner.pending_requests": "알 수도 있는 {count, plural, =0 {0 명} one {한 명} other {# 명}}의 사람들로부터의 알림",
|
||||
"filtered_notifications_banner.private_mentions": "{count, plural, other {개인적인 멘션}}",
|
||||
"filtered_notifications_banner.title": "걸러진 알림",
|
||||
"firehose.all": "모두",
|
||||
"firehose.local": "이 서버",
|
||||
|
@ -471,6 +472,11 @@
|
|||
"notification.own_poll": "설문을 마침",
|
||||
"notification.poll": "참여한 설문이 종료됨",
|
||||
"notification.reblog": "{name} 님이 부스트했습니다",
|
||||
"notification.relationships_severance_event": "{name} 님과의 연결이 끊어졌습니다",
|
||||
"notification.relationships_severance_event.account_suspension": "{from}의 관리자가 {target}를 정지시켰기 때문에 그들과 더이상 상호작용 할 수 없고 정보를 받아볼 수 없습니다.",
|
||||
"notification.relationships_severance_event.domain_block": "{from}의 관리자가 {target}를 차단하였고 여기에는 나의 {followersCount} 명의 팔로워와 {followingCount, plural, other {#}} 명의 팔로우가 포함되었습니다.",
|
||||
"notification.relationships_severance_event.learn_more": "더 알아보기",
|
||||
"notification.relationships_severance_event.user_domain_block": "내가 {target}를 차단하여 {followersCount} 명의 팔로워와 {followingCount, plural, other {#}} 명의 팔로우가 제거되었습니다.",
|
||||
"notification.status": "{name} 님이 방금 게시물을 올렸습니다",
|
||||
"notification.update": "{name} 님이 게시물을 수정했습니다",
|
||||
"notification_requests.accept": "수락",
|
||||
|
@ -484,6 +490,7 @@
|
|||
"notifications.column_settings.alert": "데스크탑 알림",
|
||||
"notifications.column_settings.favourite": "좋아요:",
|
||||
"notifications.column_settings.filter_bar.advanced": "모든 범주 표시",
|
||||
"notifications.column_settings.filter_bar.category": "빠른 필터 막대",
|
||||
"notifications.column_settings.follow": "새 팔로워:",
|
||||
"notifications.column_settings.follow_request": "새 팔로우 요청:",
|
||||
"notifications.column_settings.mention": "멘션:",
|
||||
|
|
|
@ -89,8 +89,12 @@
|
|||
"announcement.announcement": "Comunicados",
|
||||
"attachments_list.unprocessed": "(não processado)",
|
||||
"audio.hide": "Ocultar áudio",
|
||||
"block_modal.remote_users_caveat": "Pediremos ao servidor {domínio} que respeite sua decisão. No entanto, a conformidade não é garantida pois alguns servidores podem lidar com os blocos de maneira diferente. As postagens públicas ainda podem estar visíveis para usuários não logados.",
|
||||
"block_modal.show_less": "Mostrar menos",
|
||||
"block_modal.show_more": "Mostrar mais",
|
||||
"block_modal.they_cant_mention": "Eles não podem mencionar ou seguir você.",
|
||||
"block_modal.they_cant_see_posts": "Eles não podem ver suas postagens e você não verá as deles.",
|
||||
"block_modal.they_will_know": "Eles podem ver que estão bloqueados.",
|
||||
"block_modal.title": "Bloquear usuário?",
|
||||
"block_modal.you_wont_see_mentions": "Você não verá publicações que os mencionem.",
|
||||
"boost_modal.combo": "Pressione {combo} para pular isso na próxima vez",
|
||||
|
@ -173,6 +177,7 @@
|
|||
"confirmations.delete_list.message": "Você tem certeza de que deseja excluir esta lista?",
|
||||
"confirmations.discard_edit_media.confirm": "Descartar",
|
||||
"confirmations.discard_edit_media.message": "Há mudanças não salvas na descrição ou pré-visualização da mídia. Descartar assim mesmo?",
|
||||
"confirmations.domain_block.confirm": "Servidor de blocos",
|
||||
"confirmations.domain_block.message": "Você tem certeza de que deseja bloquear tudo de {domain}? Você não verá mais o conteúdo desta instância em nenhuma linha do tempo pública ou nas suas notificações. Seus seguidores desta instância serão removidos.",
|
||||
"confirmations.edit.confirm": "Editar",
|
||||
"confirmations.edit.message": "Editar agora irá substituir a mensagem que está sendo criando. Tem certeza de que deseja continuar?",
|
||||
|
@ -204,8 +209,27 @@
|
|||
"dismissable_banner.explore_statuses": "Estas são postagens de toda a rede social que estão ganhando tração hoje. Postagens mais recentes com mais impulsos e favoritos têm classificações mais altas.",
|
||||
"dismissable_banner.explore_tags": "Estas hashtags estão ganhando popularidade no momento entre as pessoas deste e de outros servidores da rede descentralizada.",
|
||||
"dismissable_banner.public_timeline": "Estas são as publicações públicas mais recentes de pessoas na rede social que pessoas em {domain} seguem.",
|
||||
"domain_block_modal.block": "Servidor de blocos.",
|
||||
"domain_block_modal.block_account_instead": "Bloco @(nome)",
|
||||
"domain_block_modal.they_can_interact_with_old_posts": "Pessoas deste servidor podem interagir com suas publicações antigas.",
|
||||
"domain_block_modal.they_cant_follow": "Ninguém deste servidor pode lhe seguir.",
|
||||
"domain_block_modal.they_wont_know": "Eles não saberão que foram bloqueados.",
|
||||
"domain_block_modal.title": "Dominio do bloco",
|
||||
"domain_block_modal.you_will_lose_followers": "Todos os seus seguidores deste servidor serão removidos.",
|
||||
"domain_block_modal.you_wont_see_posts": "Você não verá postagens ou notificações de usuários neste servidor.",
|
||||
"domain_pill.activitypub_lets_connect": "Ele permite que você se conecte e interaja com pessoas não apenas no Mastodon, mas também em diferentes aplicativos sociais.",
|
||||
"domain_pill.activitypub_like_language": "ActivityPub é como a linguagem que o Mastodon fala com outras redes sociais.",
|
||||
"domain_pill.server": "Servidor",
|
||||
"domain_pill.their_handle": "Seu identificador:",
|
||||
"domain_pill.their_server": "Sua casa digital, onde ficam todas as suas postagens.",
|
||||
"domain_pill.their_username": "Seu identificador exclusivo em seu servidor. É possível encontrar usuários com o mesmo nome de usuário em servidores diferentes.",
|
||||
"domain_pill.username": "Nome de usuário",
|
||||
"domain_pill.whats_in_a_handle": "O que há em uma alça?",
|
||||
"domain_pill.who_they_are": "Como os identificadores indicam quem alguém é e onde está, você pode interagir com pessoas na web social de <button>plataformas alimentadas pelo ActivityPub</button>.",
|
||||
"domain_pill.who_you_are": "Como seu identificador indica quem você é e onde está, as pessoas podem interagir com você nas redes sociais das <button>plataformas alimentadas pelo ActivityPub</button>.",
|
||||
"domain_pill.your_handle": "Seu identificador:",
|
||||
"domain_pill.your_server": "Sua casa digital, onde ficam todas as suas postagens. Não gosta deste? Transfira servidores a qualquer momento e traga seus seguidores também.",
|
||||
"domain_pill.your_username": "Seu identificador exclusivo neste servidor. É possível encontrar usuários com o mesmo nome de usuário em servidores diferentes.",
|
||||
"embed.instructions": "Incorpore este toot no seu site ao copiar o código abaixo.",
|
||||
"embed.preview": "Aqui está como vai ficar:",
|
||||
"emoji_button.activity": "Atividade",
|
||||
|
@ -273,6 +297,8 @@
|
|||
"filter_modal.select_filter.subtitle": "Use uma categoria existente ou crie uma nova",
|
||||
"filter_modal.select_filter.title": "Filtrar esta publicação",
|
||||
"filter_modal.title.status": "Filtrar uma publicação",
|
||||
"filtered_notifications_banner.pending_requests": "Notificações de {count, plural, =0 {no one} one {one person} other {# people}} que você talvez conheça",
|
||||
"filtered_notifications_banner.private_mentions": "{count, plural, one {private mention} other {private mentions}}",
|
||||
"filtered_notifications_banner.title": "Notificações filtradas",
|
||||
"firehose.all": "Tudo",
|
||||
"firehose.local": "Este servidor",
|
||||
|
@ -402,7 +428,9 @@
|
|||
"loading_indicator.label": "Carregando…",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Ocultar mídia} other {Ocultar mídias}}",
|
||||
"moved_to_account_banner.text": "Sua conta {disabledAccount} está desativada porque você a moveu para {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Ocultar das notificações",
|
||||
"mute_modal.hide_options": "Ocultar opções",
|
||||
"mute_modal.indefinite": "Até que eu os ative",
|
||||
"mute_modal.show_options": "Mostrar opções",
|
||||
"mute_modal.they_can_mention_and_follow": "Eles podem mencionar e seguir você, mas você não os verá.",
|
||||
"mute_modal.they_wont_know": "Eles não saberão que foram silenciados.",
|
||||
|
@ -444,6 +472,11 @@
|
|||
"notification.own_poll": "Sua enquete terminou",
|
||||
"notification.poll": "Uma enquete que você votou terminou",
|
||||
"notification.reblog": "{name} deu boost no teu toot",
|
||||
"notification.relationships_severance_event": "Conexões perdidas com {name}",
|
||||
"notification.relationships_severance_event.account_suspension": "Um administrador de {from} suspendeu {target}, o que significa que você não pode mais receber atualizações deles ou interagir com eles.",
|
||||
"notification.relationships_severance_event.domain_block": "An admin from {from} has blocked {target}, including {followersCount} of your followers and {followingCount, plural, one {# account} other {# accounts}} you follow.",
|
||||
"notification.relationships_severance_event.learn_more": "Saber mais",
|
||||
"notification.relationships_severance_event.user_domain_block": "You have blocked {target}, removing {followersCount} of your followers and {followingCount, plural, one {# account} other {# accounts}} you follow.",
|
||||
"notification.status": "{name} acabou de tootar",
|
||||
"notification.update": "{name} editou uma publicação",
|
||||
"notification_requests.accept": "Aceitar",
|
||||
|
@ -456,6 +489,8 @@
|
|||
"notifications.column_settings.admin.sign_up": "Novas inscrições:",
|
||||
"notifications.column_settings.alert": "Notificações no computador",
|
||||
"notifications.column_settings.favourite": "Favoritos:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Exibir todas as categorias",
|
||||
"notifications.column_settings.filter_bar.category": "Barra de filtro rápido",
|
||||
"notifications.column_settings.follow": "Seguidores:",
|
||||
"notifications.column_settings.follow_request": "Seguidores pendentes:",
|
||||
"notifications.column_settings.mention": "Menções:",
|
||||
|
@ -481,7 +516,9 @@
|
|||
"notifications.permission_denied": "Navegador não tem permissão para ativar notificações no computador.",
|
||||
"notifications.permission_denied_alert": "Verifique a permissão do navegador para ativar notificações no computador.",
|
||||
"notifications.permission_required": "Ativar notificações no computador exige permissão do navegador.",
|
||||
"notifications.policy.filter_new_accounts.hint": "Created within the past {days, plural, one {one day} other {# days}}",
|
||||
"notifications.policy.filter_new_accounts_title": "Novas contas",
|
||||
"notifications.policy.filter_not_followers_hint": "Including people who have been following you fewer than {days, plural, one {one day} other {# days}}",
|
||||
"notifications.policy.filter_not_followers_title": "Pessoas que não estão te seguindo",
|
||||
"notifications.policy.filter_not_following_hint": "Até que você os aprove manualmente",
|
||||
"notifications.policy.filter_not_following_title": "Pessoas que você não segue",
|
||||
|
@ -569,6 +606,7 @@
|
|||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"relative_time.today": "hoje",
|
||||
"reply_indicator.attachments": "{count, plural, one {# attachment} other {# attachments}}",
|
||||
"reply_indicator.cancel": "Cancelar",
|
||||
"reply_indicator.poll": "Enquete",
|
||||
"report.block": "Bloquear",
|
||||
|
@ -667,6 +705,7 @@
|
|||
"status.edited_x_times": "Editado {count, plural, one {{count} hora} other {{count} vezes}}",
|
||||
"status.embed": "Incorporar",
|
||||
"status.favourite": "Favorita",
|
||||
"status.favourites": "{count, plural, one {favorite} other {favorites}}",
|
||||
"status.filter": "Filtrar esta publicação",
|
||||
"status.filtered": "Filtrado",
|
||||
"status.hide": "Ocultar publicação",
|
||||
|
@ -687,6 +726,7 @@
|
|||
"status.reblog": "Dar boost",
|
||||
"status.reblog_private": "Dar boost para o mesmo público",
|
||||
"status.reblogged_by": "{name} deu boost",
|
||||
"status.reblogs": "{count, plural, one {boost} other {boosts}}",
|
||||
"status.reblogs.empty": "Nada aqui. Quando alguém der boost, o usuário aparecerá aqui.",
|
||||
"status.redraft": "Excluir e rascunhar",
|
||||
"status.remove_bookmark": "Remover do Salvos",
|
||||
|
|
|
@ -298,6 +298,7 @@
|
|||
"filter_modal.select_filter.title": "Filtrar esta publicação",
|
||||
"filter_modal.title.status": "Filtrar uma publicação",
|
||||
"filtered_notifications_banner.pending_requests": "Notificações de {count, plural, =0 {ninguém} one {uma pessoa} other {# pessoas}} que talvez conheça",
|
||||
"filtered_notifications_banner.private_mentions": "{count, plural,one {menção privada} other {menções privadas}}",
|
||||
"filtered_notifications_banner.title": "Notificações filtradas",
|
||||
"firehose.all": "Todas",
|
||||
"firehose.local": "Este servidor",
|
||||
|
@ -471,6 +472,11 @@
|
|||
"notification.own_poll": "A sua votação terminou",
|
||||
"notification.poll": "Uma votação em que participaste chegou ao fim",
|
||||
"notification.reblog": "{name} reforçou a tua publicação",
|
||||
"notification.relationships_severance_event": "Perdeu as ligações com {name}",
|
||||
"notification.relationships_severance_event.account_suspension": "Um administrador de {from} suspendeu {target}, o que significa que já não pode receber atualizações dele ou interagir com ele.",
|
||||
"notification.relationships_severance_event.domain_block": "Um administrador de {from} bloqueou {target}, incluindo {followersCount} dos seus seguidores e {followingCount, plural, one {# conta} other {# contas}} que segue.",
|
||||
"notification.relationships_severance_event.learn_more": "Saber mais",
|
||||
"notification.relationships_severance_event.user_domain_block": "Bloqueou {target}, removendo {followersCount} dos seus seguidores e {followingCount, plural, one {# conta} other {# contas}} que segue.",
|
||||
"notification.status": "{name} acabou de publicar",
|
||||
"notification.update": "{name} editou uma publicação",
|
||||
"notification_requests.accept": "Aceitar",
|
||||
|
|
|
@ -298,6 +298,7 @@
|
|||
"filter_modal.select_filter.title": "Filtriraj to objavo",
|
||||
"filter_modal.title.status": "Filtrirajte objavo",
|
||||
"filtered_notifications_banner.pending_requests": "Obvestila od {count, plural, =0 {nikogar, ki bi ga} one {# človeka, ki bi ga} two {# ljudi, ki bi ju} few {# ljudi, ki bi jih} other {# ljudi, ki bi jih}} lahko poznali",
|
||||
"filtered_notifications_banner.private_mentions": "{count, plural, one {zasebna omemba} two {zasebni omembi} few {zasebne omembe} other {zasebnih omemb}}",
|
||||
"filtered_notifications_banner.title": "Filtrirana obvestila",
|
||||
"firehose.all": "Vse",
|
||||
"firehose.local": "Ta strežnik",
|
||||
|
@ -471,6 +472,11 @@
|
|||
"notification.own_poll": "Vaša anketa je zaključena",
|
||||
"notification.poll": "Anketa, v kateri ste sodelovali, je zaključena",
|
||||
"notification.reblog": "{name} je izpostavila/a vašo objavo",
|
||||
"notification.relationships_severance_event": "Povezave z {name} prekinjene",
|
||||
"notification.relationships_severance_event.account_suspension": "Skrbnik na {from} je suspendiral račun {target}, kar pomeni, da od računa ne morete več prejemati posodobitev ali imeti z njim interakcij.",
|
||||
"notification.relationships_severance_event.domain_block": "Skrbnik na {from} je blokiral domeno {target}, vključno z vašimi sledilci ({followersCount}) in {followingCount, plural, one {# računom, ki mu sledite} two {# računoma, ki jima sledite} few {# računi, ki jim sledite} other {# računi, ki jim sledite}}.",
|
||||
"notification.relationships_severance_event.learn_more": "Več o tem",
|
||||
"notification.relationships_severance_event.user_domain_block": "Blokirali ste domeno {target}, vključno z vašimi sledilci ({followersCount}) in {followingCount, plural, one {# računom, ki mu sledite} two {# računoma, ki jima sledite} few {# računi, ki jim sledite} other {# računi, ki jim sledite}}.",
|
||||
"notification.status": "{name} je pravkar objavil/a",
|
||||
"notification.update": "{name} je uredil(a) objavo",
|
||||
"notification_requests.accept": "Sprejmi",
|
||||
|
|
|
@ -462,6 +462,7 @@
|
|||
"notification.own_poll": "Ваша анкета је завршена",
|
||||
"notification.poll": "Завршена је анкета у којој сте гласали",
|
||||
"notification.reblog": "{name} је подржао вашу објаву",
|
||||
"notification.relationships_severance_event.learn_more": "Сазнајте више",
|
||||
"notification.status": "{name} је управо објавио",
|
||||
"notification.update": "{name} је уредио објаву",
|
||||
"notification_requests.accept": "Прихвати",
|
||||
|
@ -474,6 +475,7 @@
|
|||
"notifications.column_settings.admin.sign_up": "Нове рагистрације:",
|
||||
"notifications.column_settings.alert": "Обавештења на радној површини",
|
||||
"notifications.column_settings.favourite": "Омиљено:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Прикажи све категорије",
|
||||
"notifications.column_settings.follow": "Нови пратиоци:",
|
||||
"notifications.column_settings.follow_request": "Нови захтеви за праћење:",
|
||||
"notifications.column_settings.mention": "Помињања:",
|
||||
|
|
|
@ -287,6 +287,7 @@
|
|||
"filter_modal.select_filter.title": "Фільтрувати цей допис",
|
||||
"filter_modal.title.status": "Фільтрувати допис",
|
||||
"filtered_notifications_banner.pending_requests": "Сповіщення від {count, plural, =0 {жодної особи} one {однієї особи} few {# осіб} many {# осіб} other {# особи}}, котрих ви можете знати",
|
||||
"filtered_notifications_banner.private_mentions": "{count, plural, one {приватна згадка} few {приватні згадки} many {приватні згадки} other {приватна згадка}}",
|
||||
"filtered_notifications_banner.title": "Відфільтровані сповіщення",
|
||||
"firehose.all": "Всі",
|
||||
"firehose.local": "Цей сервер",
|
||||
|
@ -476,7 +477,7 @@
|
|||
"notifications.column_settings.alert": "Сповіщення стільниці",
|
||||
"notifications.column_settings.favourite": "Уподобане:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Показати всі категорії",
|
||||
"notifications.column_settings.filter_bar.category": "Панель швидкого фільтру",
|
||||
"notifications.column_settings.filter_bar.category": "Панель швидкого фільтра",
|
||||
"notifications.column_settings.follow": "Нові підписники:",
|
||||
"notifications.column_settings.follow_request": "Нові запити на підписку:",
|
||||
"notifications.column_settings.mention": "Згадки:",
|
||||
|
|
|
@ -298,6 +298,7 @@
|
|||
"filter_modal.select_filter.title": "過濾此帖文",
|
||||
"filter_modal.title.status": "過濾一則帖文",
|
||||
"filtered_notifications_banner.pending_requests": "來自 {count, plural, =0 {0 位} other {# 位}}你可能認識的人的通知",
|
||||
"filtered_notifications_banner.private_mentions": "{count, plural, one {則私人提及} other {則私人提及}}",
|
||||
"filtered_notifications_banner.title": "已過濾之通知",
|
||||
"firehose.all": "全部",
|
||||
"firehose.local": "本伺服器",
|
||||
|
@ -471,6 +472,11 @@
|
|||
"notification.own_poll": "你的投票已結束",
|
||||
"notification.poll": "你參與過的一個投票已經結束",
|
||||
"notification.reblog": "{name} 轉推你的文章",
|
||||
"notification.relationships_severance_event": "失去與 {name} 的連結",
|
||||
"notification.relationships_severance_event.account_suspension": "{from} 的管理員已將 {target} 停權,這表示你無法再收到他們的更新或與他們互動。",
|
||||
"notification.relationships_severance_event.domain_block": "{from} 的管理員已封鎖 {target},包括你的 {followersCount} 位追蹤者和 {followingCount, plural, other {# 個你追蹤的帳號}}。",
|
||||
"notification.relationships_severance_event.learn_more": "了解更多",
|
||||
"notification.relationships_severance_event.user_domain_block": "你已封鎖 {target},並移除了你的 {followersCount} 位追蹤者和你追蹤的 {followingCount, plural, other {# 個帳號}}。",
|
||||
"notification.status": "{name} 剛發表了文章",
|
||||
"notification.update": "{name} 編輯了帖文",
|
||||
"notification_requests.accept": "接受",
|
||||
|
@ -483,6 +489,8 @@
|
|||
"notifications.column_settings.admin.sign_up": "新註冊:",
|
||||
"notifications.column_settings.alert": "顯示桌面通知",
|
||||
"notifications.column_settings.favourite": "最愛:",
|
||||
"notifications.column_settings.filter_bar.advanced": "顯示所有分類",
|
||||
"notifications.column_settings.filter_bar.category": "快速篩選欄",
|
||||
"notifications.column_settings.follow": "新追蹤者:",
|
||||
"notifications.column_settings.follow_request": "新的追蹤請求:",
|
||||
"notifications.column_settings.mention": "提及你:",
|
||||
|
|
15
app/javascript/mastodon/models/status.ts
Normal file
15
app/javascript/mastodon/models/status.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
export type StatusVisibility =
|
||||
| 'public'
|
||||
| 'unlisted'
|
||||
| 'private'
|
||||
| 'direct'
|
||||
| 'public_unlisted'
|
||||
| 'login'
|
||||
| 'mutual'
|
||||
| 'circle'
|
||||
| 'personal'
|
||||
| 'reply'
|
||||
| 'limited';
|
||||
|
||||
// Temporary until we type it correctly
|
||||
export type Status = Immutable.Map<string, unknown>;
|
|
@ -1,25 +0,0 @@
|
|||
import Immutable from 'immutable';
|
||||
|
||||
import {
|
||||
BOOSTS_INIT_MODAL,
|
||||
BOOSTS_CHANGE_PRIVACY,
|
||||
} from 'mastodon/actions/boosts';
|
||||
|
||||
const initialState = Immutable.Map({
|
||||
new: Immutable.Map({
|
||||
privacy: 'public',
|
||||
}),
|
||||
});
|
||||
|
||||
export default function mutes(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case BOOSTS_INIT_MODAL:
|
||||
return state.withMutations((state) => {
|
||||
state.setIn(['new', 'privacy'], action.privacy);
|
||||
});
|
||||
case BOOSTS_CHANGE_PRIVACY:
|
||||
return state.setIn(['new', 'privacy'], action.privacy);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
|
@ -13,7 +13,6 @@ import antennas from './antennas';
|
|||
import bookmark_categories from './bookmark_categories';
|
||||
import bookmarkCategoryAdder from './bookmark_category_adder';
|
||||
import bookmarkCategoryEditor from './bookmark_category_editor';
|
||||
import boosts from './boosts';
|
||||
import circleAdder from './circle_adder';
|
||||
import circleEditor from './circle_editor';
|
||||
import circles from './circles';
|
||||
|
@ -30,14 +29,14 @@ import history from './history';
|
|||
import listAdder from './list_adder';
|
||||
import listEditor from './list_editor';
|
||||
import lists from './lists';
|
||||
import markers from './markers';
|
||||
import { markersReducer } from './markers';
|
||||
import media_attachments from './media_attachments';
|
||||
import meta from './meta';
|
||||
import { modalReducer } from './modal';
|
||||
import { notificationPolicyReducer } from './notification_policy';
|
||||
import { notificationRequestsReducer } from './notification_requests';
|
||||
import notifications from './notifications';
|
||||
import picture_in_picture from './picture_in_picture';
|
||||
import { pictureInPictureReducer } from './picture_in_picture';
|
||||
import polls from './polls';
|
||||
import push_notifications from './push_notifications';
|
||||
import reaction_deck from './reaction_deck';
|
||||
|
@ -70,7 +69,6 @@ const reducers = {
|
|||
relationships: relationshipsReducer,
|
||||
settings,
|
||||
push_notifications,
|
||||
boosts,
|
||||
server,
|
||||
contexts,
|
||||
compose,
|
||||
|
@ -96,8 +94,8 @@ const reducers = {
|
|||
suggestions,
|
||||
polls,
|
||||
trends,
|
||||
markers,
|
||||
picture_in_picture,
|
||||
markers: markersReducer,
|
||||
picture_in_picture: pictureInPictureReducer,
|
||||
history,
|
||||
tags,
|
||||
followed_tags,
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
import { Map as ImmutableMap } from 'immutable';
|
||||
|
||||
import {
|
||||
MARKERS_SUBMIT_SUCCESS,
|
||||
} from '../actions/markers';
|
||||
|
||||
|
||||
const initialState = ImmutableMap({
|
||||
home: '0',
|
||||
notifications: '0',
|
||||
});
|
||||
|
||||
export default function markers(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case MARKERS_SUBMIT_SUCCESS:
|
||||
if (action.home) {
|
||||
state = state.set('home', action.home);
|
||||
}
|
||||
if (action.notifications) {
|
||||
state = state.set('notifications', action.notifications);
|
||||
}
|
||||
return state;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
18
app/javascript/mastodon/reducers/markers.ts
Normal file
18
app/javascript/mastodon/reducers/markers.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { createReducer } from '@reduxjs/toolkit';
|
||||
|
||||
import { submitMarkersAction } from 'mastodon/actions/markers';
|
||||
|
||||
const initialState = {
|
||||
home: '0',
|
||||
notifications: '0',
|
||||
};
|
||||
|
||||
export const markersReducer = createReducer(initialState, (builder) => {
|
||||
builder.addCase(
|
||||
submitMarkersAction.fulfilled,
|
||||
(state, { payload: { home, notifications } }) => {
|
||||
if (home) state.home = home;
|
||||
if (notifications) state.notifications = notifications;
|
||||
},
|
||||
);
|
||||
});
|
|
@ -13,7 +13,7 @@ import {
|
|||
unfocusApp,
|
||||
} from '../actions/app';
|
||||
import {
|
||||
MARKERS_FETCH_SUCCESS,
|
||||
fetchMarkers,
|
||||
} from '../actions/markers';
|
||||
import {
|
||||
notificationsUpdate,
|
||||
|
@ -258,8 +258,8 @@ const recountUnread = (state, last_read_id) => {
|
|||
|
||||
export default function notifications(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case MARKERS_FETCH_SUCCESS:
|
||||
return action.markers.notifications ? recountUnread(state, action.markers.notifications.last_read_id) : state;
|
||||
case fetchMarkers.fulfilled.type:
|
||||
return action.payload.markers.notifications ? recountUnread(state, action.payload.markers.notifications.last_read_id) : state;
|
||||
case NOTIFICATIONS_MOUNT:
|
||||
return updateMounted(state);
|
||||
case NOTIFICATIONS_UNMOUNT:
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
import { PICTURE_IN_PICTURE_DEPLOY, PICTURE_IN_PICTURE_REMOVE } from 'mastodon/actions/picture_in_picture';
|
||||
|
||||
import { TIMELINE_DELETE } from '../actions/timelines';
|
||||
|
||||
const initialState = {
|
||||
statusId: null,
|
||||
accountId: null,
|
||||
type: null,
|
||||
src: null,
|
||||
muted: false,
|
||||
volume: 0,
|
||||
currentTime: 0,
|
||||
};
|
||||
|
||||
export default function pictureInPicture(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case PICTURE_IN_PICTURE_DEPLOY:
|
||||
return { statusId: action.statusId, accountId: action.accountId, type: action.playerType, ...action.props };
|
||||
case PICTURE_IN_PICTURE_REMOVE:
|
||||
return { ...initialState };
|
||||
case TIMELINE_DELETE:
|
||||
return (state.statusId === action.id) ? { ...initialState } : state;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
56
app/javascript/mastodon/reducers/picture_in_picture.ts
Normal file
56
app/javascript/mastodon/reducers/picture_in_picture.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
import type { Reducer } from '@reduxjs/toolkit';
|
||||
|
||||
import {
|
||||
deployPictureInPictureAction,
|
||||
removePictureInPicture,
|
||||
} from 'mastodon/actions/picture_in_picture';
|
||||
|
||||
import { TIMELINE_DELETE } from '../actions/timelines';
|
||||
|
||||
export interface PIPMediaProps {
|
||||
src: string;
|
||||
muted: boolean;
|
||||
volume: number;
|
||||
currentTime: number;
|
||||
poster: string;
|
||||
backgroundColor: string;
|
||||
foregroundColor: string;
|
||||
accentColor: string;
|
||||
}
|
||||
|
||||
interface PIPStateWithValue extends Partial<PIPMediaProps> {
|
||||
statusId: string;
|
||||
accountId: string;
|
||||
type: 'audio' | 'video';
|
||||
}
|
||||
|
||||
interface PIPStateEmpty extends Partial<PIPMediaProps> {
|
||||
type: null;
|
||||
}
|
||||
|
||||
type PIPState = PIPStateWithValue | PIPStateEmpty;
|
||||
|
||||
const initialState = {
|
||||
type: null,
|
||||
muted: false,
|
||||
volume: 0,
|
||||
currentTime: 0,
|
||||
};
|
||||
|
||||
export const pictureInPictureReducer: Reducer<PIPState> = (
|
||||
state = initialState,
|
||||
action,
|
||||
) => {
|
||||
if (deployPictureInPictureAction.match(action))
|
||||
return {
|
||||
statusId: action.payload.statusId,
|
||||
accountId: action.payload.accountId,
|
||||
type: action.payload.playerType,
|
||||
...action.payload.props,
|
||||
};
|
||||
else if (removePictureInPicture.match(action)) return initialState;
|
||||
else if (action.type === TIMELINE_DELETE)
|
||||
if (state.type && state.statusId === action.id) return initialState;
|
||||
|
||||
return state;
|
||||
};
|
|
@ -80,7 +80,7 @@ export const makeGetStatus = () => {
|
|||
|
||||
export const makeGetPictureInPicture = () => {
|
||||
return createSelector([
|
||||
(state, { id }) => state.get('picture_in_picture').statusId === id,
|
||||
(state, { id }) => state.picture_in_picture.statusId === id,
|
||||
(state) => state.getIn(['meta', 'layout']) !== 'mobile',
|
||||
], (inUse, available) => ImmutableMap({
|
||||
inUse: inUse && available,
|
||||
|
|
|
@ -1,16 +1,27 @@
|
|||
import { isAction } from '@reduxjs/toolkit';
|
||||
import {
|
||||
isAction,
|
||||
isAsyncThunkAction,
|
||||
isRejectedWithValue,
|
||||
} from '@reduxjs/toolkit';
|
||||
import type { Action, Middleware } from '@reduxjs/toolkit';
|
||||
|
||||
import type { RootState } from '..';
|
||||
import { showAlertForError } from '../../actions/alerts';
|
||||
import type { AsyncThunkRejectValue } from '../typed_functions';
|
||||
|
||||
const defaultFailSuffix = 'FAIL';
|
||||
const isFailedAction = new RegExp(`${defaultFailSuffix}$`, 'g');
|
||||
|
||||
interface ActionWithMaybeAlertParams extends Action {
|
||||
skipAlert?: boolean;
|
||||
skipNotFound?: boolean;
|
||||
error?: unknown;
|
||||
interface ActionWithMaybeAlertParams extends Action, AsyncThunkRejectValue {}
|
||||
|
||||
interface RejectedAction extends Action {
|
||||
payload: AsyncThunkRejectValue;
|
||||
}
|
||||
|
||||
function isRejectedActionWithPayload(
|
||||
action: unknown,
|
||||
): action is RejectedAction {
|
||||
return isAsyncThunkAction(action) && isRejectedWithValue(action);
|
||||
}
|
||||
|
||||
function isActionWithmaybeAlertParams(
|
||||
|
@ -23,7 +34,11 @@ export const errorsMiddleware: Middleware<Record<string, never>, RootState> =
|
|||
({ dispatch }) =>
|
||||
(next) =>
|
||||
(action) => {
|
||||
if (
|
||||
if (isRejectedActionWithPayload(action) && !action.payload.skipAlert) {
|
||||
dispatch(
|
||||
showAlertForError(action.payload.error, action.payload.skipNotFound),
|
||||
);
|
||||
} else if (
|
||||
isActionWithmaybeAlertParams(action) &&
|
||||
!action.skipAlert &&
|
||||
action.type.match(isFailedAction)
|
||||
|
|
|
@ -7,8 +7,14 @@ import type { AppDispatch, RootState } from './store';
|
|||
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
|
||||
export const useAppSelector = useSelector.withTypes<RootState>();
|
||||
|
||||
export interface AsyncThunkRejectValue {
|
||||
skipAlert?: boolean;
|
||||
skipNotFound?: boolean;
|
||||
error?: unknown;
|
||||
}
|
||||
|
||||
export const createAppAsyncThunk = createAsyncThunk.withTypes<{
|
||||
state: RootState;
|
||||
dispatch: AppDispatch;
|
||||
rejectValue: string;
|
||||
rejectValue: AsyncThunkRejectValue;
|
||||
}>();
|
||||
|
|
|
@ -2695,6 +2695,7 @@ a.account__display-name {
|
|||
}
|
||||
|
||||
$ui-header-height: 55px;
|
||||
$ui-header-logo-wordmark-width: 99px;
|
||||
|
||||
.ui__header {
|
||||
display: none;
|
||||
|
@ -2710,6 +2711,10 @@ $ui-header-height: 55px;
|
|||
&__logo {
|
||||
display: inline-flex;
|
||||
padding: 15px;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
overflow: hidden;
|
||||
container: header-logo / inline-size;
|
||||
|
||||
.logo {
|
||||
height: $ui-header-height - 30px;
|
||||
|
@ -2720,7 +2725,7 @@ $ui-header-height: 55px;
|
|||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (width >= 370px) {
|
||||
@container header-logo (min-width: #{$ui-header-logo-wordmark-width}) {
|
||||
.logo--wordmark {
|
||||
display: block;
|
||||
}
|
||||
|
@ -2747,6 +2752,7 @@ $ui-header-height: 55px;
|
|||
gap: 8px;
|
||||
padding: 0 9px;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
|
||||
.button {
|
||||
flex: 0 0 auto;
|
||||
|
@ -2764,7 +2770,7 @@ $ui-header-height: 55px;
|
|||
}
|
||||
|
||||
.tabs-bar__wrapper {
|
||||
background: var(--background-color-tint);
|
||||
background: var(--background-color);
|
||||
backdrop-filter: var(--background-filter);
|
||||
position: sticky;
|
||||
top: $ui-header-height;
|
||||
|
@ -2986,7 +2992,7 @@ $ui-header-height: 55px;
|
|||
.layout-single-column {
|
||||
.ui__header {
|
||||
display: flex;
|
||||
background: var(--background-color-tint);
|
||||
background: var(--background-color);
|
||||
border-bottom: 1px solid var(--background-border-color);
|
||||
}
|
||||
|
||||
|
@ -5342,6 +5348,7 @@ a.status-card {
|
|||
.language-dropdown__dropdown {
|
||||
box-shadow: var(--dropdown-shadow);
|
||||
background: var(--dropdown-background-color);
|
||||
backdrop-filter: var(--background-filter);
|
||||
border: 1px solid var(--dropdown-border-color);
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
|
|
|
@ -6,6 +6,9 @@ require 'yaml'
|
|||
class Themes
|
||||
include Singleton
|
||||
|
||||
MASTODON_DARK_THEME_COLOR = '#191b22'
|
||||
MASTODON_LIGHT_THEME_COLOR = '#f3f5f7'
|
||||
|
||||
def initialize
|
||||
@conf = YAML.load_file(Rails.root.join('config', 'themes.yml'))
|
||||
end
|
||||
|
|
|
@ -28,6 +28,8 @@ class AccountWarning < ApplicationRecord
|
|||
suspend: 4_000,
|
||||
}, suffix: :action
|
||||
|
||||
RECENT_PERIOD = 3.months.freeze
|
||||
|
||||
normalizes :text, with: ->(text) { text.to_s }, apply_to_nil: true
|
||||
|
||||
belongs_to :account, inverse_of: :account_warnings
|
||||
|
@ -38,7 +40,7 @@ class AccountWarning < ApplicationRecord
|
|||
|
||||
scope :latest, -> { order(id: :desc) }
|
||||
scope :custom, -> { where.not(text: '') }
|
||||
scope :recent, -> { where('account_warnings.created_at >= ?', 3.months.ago) }
|
||||
scope :recent, -> { where(created_at: RECENT_PERIOD.ago..) }
|
||||
|
||||
def statuses
|
||||
Status.with_discarded.where(id: status_ids || [])
|
||||
|
|
|
@ -142,7 +142,7 @@ class Status < ApplicationRecord
|
|||
scope :with_public_search_visibility, -> { merge(where(visibility: [:public, :public_unlisted, :login]).or(Status.where(searchability: [:public, :public_unlisted]))) }
|
||||
scope :tagged_with, ->(tag_ids) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag_ids }) }
|
||||
scope :not_excluded_by_account, ->(account) { where.not(account_id: account.excluded_from_timeline_account_ids) }
|
||||
scope :not_domain_blocked_by_account, ->(account) { account.excluded_from_timeline_domains.blank? ? left_outer_joins(:account) : left_outer_joins(:account).where('accounts.domain IS NULL OR accounts.domain NOT IN (?)', account.excluded_from_timeline_domains) }
|
||||
scope :not_domain_blocked_by_account, ->(account) { account.excluded_from_timeline_domains.blank? ? left_outer_joins(:account) : left_outer_joins(:account).merge(Account.not_domain_blocked_by_account(account)) }
|
||||
scope :tagged_with_all, lambda { |tag_ids|
|
||||
Array(tag_ids).map(&:to_i).reduce(self) do |result, id|
|
||||
result.where(<<~SQL.squish, tag_id: id)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class REST::MarkerSerializer < ActiveModel::Serializer
|
||||
# Please update `app/javascript/mastodon/api_types/markers.ts` when making changes to the attributes
|
||||
|
||||
attributes :last_read_id, :version, :updated_at
|
||||
|
||||
def last_read_id
|
||||
|
|
|
@ -16,16 +16,14 @@
|
|||
= image_tag frontend_asset_url('images/mailer-new/welcome/checkbox-off.png'), alt: '', width: 20, height: 20
|
||||
%td.email-checklist-icons-step-td
|
||||
- if defined?(key)
|
||||
= image_tag frontend_asset_url("images/mailer-new/welcome-icons/#{key}-#{checked ? 'on' : 'off'}.png"), alt: '', width: 40, height: 40
|
||||
= image_tag frontend_asset_url("images/mailer-new/welcome-icons/#{key}_step-#{checked ? 'on' : 'off'}.png"), alt: '', width: 40, height: 40
|
||||
%td.email-checklist-text-td
|
||||
.email-desktop-flex
|
||||
/[if mso]
|
||||
<table border="0" cellpadding="0" cellspacing="0" align="center" style="width:100%;" role="presentation"><tr><td vertical-align:top;">
|
||||
%div
|
||||
- if defined?(title)
|
||||
%h3= title
|
||||
- if defined?(text)
|
||||
%p= text
|
||||
%h3= t("user_mailer.welcome.#{key}_title")
|
||||
%p= t("user_mailer.welcome.#{key}_step")
|
||||
/[if mso]
|
||||
</td><td style="vertical-align:top;">
|
||||
%div
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
|
||||
%link{ rel: 'mask-icon', href: frontend_asset_path('images/logo-symbol-icon.svg'), color: '#6364FF' }/
|
||||
%link{ rel: 'manifest', href: manifest_path(format: :json) }/
|
||||
%meta{ name: 'theme-color', content: '#191b22' }/
|
||||
%meta{ name: 'apple-mobile-web-app-capable', content: 'yes' }/
|
||||
|
||||
%title= html_title
|
||||
|
|
|
@ -23,11 +23,11 @@
|
|||
%td.email-body-huge-padding-td
|
||||
%h2.email-h2= t('user_mailer.welcome.checklist_title')
|
||||
%p.email-h-sub= t('user_mailer.welcome.checklist_subtitle')
|
||||
= render 'application/mailer/checklist', key: 'edit_profile_step', title: t('user_mailer.welcome.edit_profile_title'), text: t('user_mailer.welcome.edit_profile_step'), checked: @has_account_fields, button_text: t('user_mailer.welcome.edit_profile_action'), button_url: web_url('start/profile')
|
||||
= render 'application/mailer/checklist', key: 'follow_step', title: t('user_mailer.welcome.follow_title'), text: t('user_mailer.welcome.follow_step'), checked: @has_active_relationships, button_text: t('user_mailer.welcome.follow_action'), button_url: web_url('start/follows')
|
||||
= render 'application/mailer/checklist', key: 'post_step', title: t('user_mailer.welcome.post_title'), text: t('user_mailer.welcome.post_step'), checked: @has_statuses, button_text: t('user_mailer.welcome.post_action'), button_url: web_url
|
||||
= render 'application/mailer/checklist', key: 'share_step', title: t('user_mailer.welcome.share_title'), text: t('user_mailer.welcome.share_step'), checked: false, button_text: t('user_mailer.welcome.share_action'), button_url: web_url('start/share')
|
||||
= render 'application/mailer/checklist', key: 'apps_step', title: t('user_mailer.welcome.apps_title'), text: t('user_mailer.welcome.apps_step'), checked: false, show_apps_buttons: true
|
||||
= render 'application/mailer/checklist', key: 'edit_profile', checked: @has_account_fields, button_text: t('user_mailer.welcome.edit_profile_action'), button_url: web_url('start/profile')
|
||||
= render 'application/mailer/checklist', key: 'follow', checked: @has_active_relationships, button_text: t('user_mailer.welcome.follow_action'), button_url: web_url('start/follows')
|
||||
= render 'application/mailer/checklist', key: 'post', checked: @has_statuses, button_text: t('user_mailer.welcome.post_action'), button_url: web_url
|
||||
= render 'application/mailer/checklist', key: 'share', checked: false, button_text: t('user_mailer.welcome.share_action'), button_url: web_url('start/share')
|
||||
= render 'application/mailer/checklist', key: 'apps', checked: false, show_apps_buttons: true
|
||||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-body-columns-td
|
||||
|
|
|
@ -1962,7 +1962,28 @@ ar:
|
|||
silence: الحساب محدود
|
||||
suspend: الحساب مُعلَّق
|
||||
welcome:
|
||||
apps_android_action: احصل عليه من متجر جوجل للتطبيقات
|
||||
apps_ios_action: التنزيل من App Store
|
||||
apps_step: تنزيل تطبيقاتنا الرسمية.
|
||||
apps_title: تطبيقات مَستُدون
|
||||
checklist_subtitle: 'هيا بنا نبدأ مغامرتنا على الويب الاجتماعي الجديد:'
|
||||
checklist_title: الخطوات الترحيبية الأولى
|
||||
edit_profile_action: تخصيص
|
||||
edit_profile_step: قم بتعزيز تفاعلاتك بامتلاك مِلَفّ تعريفي كامل.
|
||||
edit_profile_title: قم بتخصيص ملفك التعريفي
|
||||
explanation: ها هي بعض النصائح قبل بداية الاستخدام
|
||||
feature_action: اعرف المزيد
|
||||
follow_action: تابِع
|
||||
follows_title: مَن عليك متابعته
|
||||
hashtags_title: الوسوم الرائجة
|
||||
hashtags_view_more: عرض المزيد من الوسوم الرائجة
|
||||
post_action: إنشاء
|
||||
post_step: قل مرحبا للعالَم عبر نصّ أو صور أو فيديوهات أو استطلاعات رأي.
|
||||
post_title: قم بإنشاء منشورك الأول
|
||||
share_action: شارِك
|
||||
share_step: أخبر أصدقائك بكيفية العثور عليك على مَستُدون.
|
||||
share_title: شارك مِلَفّ مَستُدون التعريفي الخاص بك
|
||||
sign_in_action: تسجيل الدخول
|
||||
subject: أهلًا بك على ماستدون
|
||||
title: أهلاً بك، %{name}!
|
||||
users:
|
||||
|
|
|
@ -1768,6 +1768,7 @@ bg:
|
|||
contrast: Mastodon (висок контраст)
|
||||
default: Mastodon (тъмно)
|
||||
mastodon-light: Mastodon (светло)
|
||||
system: Самодейно (употреба на системната тема)
|
||||
time:
|
||||
formats:
|
||||
default: "%d %b, %Y, %H:%M"
|
||||
|
|
|
@ -1768,6 +1768,7 @@ ca:
|
|||
contrast: Mastodon (alt contrast)
|
||||
default: Mastodon (fosc)
|
||||
mastodon-light: Mastodon (clar)
|
||||
system: Automàtic (utilitza el tema del sistema)
|
||||
time:
|
||||
formats:
|
||||
default: "%b %d, %Y, %H:%M"
|
||||
|
|
|
@ -1767,6 +1767,7 @@ da:
|
|||
contrast: Mastodon (høj kontrast)
|
||||
default: Mastodont (mørkt)
|
||||
mastodon-light: Mastodon (lyst)
|
||||
system: Automatisk (benyt systemtema)
|
||||
time:
|
||||
formats:
|
||||
default: "%d. %b %Y, %H:%M"
|
||||
|
|
|
@ -1768,6 +1768,7 @@ de:
|
|||
contrast: Mastodon (Hoher Kontrast)
|
||||
default: Mastodon (Dunkel)
|
||||
mastodon-light: Mastodon (Hell)
|
||||
system: Automatisch (mit System synchronisieren)
|
||||
time:
|
||||
formats:
|
||||
default: "%d. %b %Y, %H:%M Uhr"
|
||||
|
|
|
@ -12,6 +12,7 @@ ar:
|
|||
last_attempt: بإمكانك إعادة المحاولة مرة واحدة قبل أن يتم قفل حسابك.
|
||||
locked: إن حسابك مقفل.
|
||||
not_found_in_database: "%{authentication_keys} أو كلمة سر خاطئة."
|
||||
omniauth_user_creation_failure: خطأ في إنشاء حساب لهذه الهُوِيَّة.
|
||||
pending: إنّ حسابك في انتظار مراجعة.
|
||||
timeout: لقد انتهت مدة صَلاحِيَة جلستك. قم بتسجيل الدخول من جديد للمواصلة.
|
||||
unauthenticated: يجب عليك تسجيل الدخول أو إنشاء حساب قبل المواصلة.
|
||||
|
|
|
@ -1768,6 +1768,7 @@ es-AR:
|
|||
contrast: Alto contraste
|
||||
default: Oscuro
|
||||
mastodon-light: Claro
|
||||
system: Automático (usar tema del sistema)
|
||||
time:
|
||||
formats:
|
||||
default: "%Y.%b.%d, %H:%M"
|
||||
|
|
|
@ -1768,6 +1768,7 @@ es-MX:
|
|||
contrast: Alto contraste
|
||||
default: Mastodon
|
||||
mastodon-light: Mastodon (claro)
|
||||
system: Automático (usar tema del sistema)
|
||||
time:
|
||||
formats:
|
||||
default: "%d de %b del %Y, %H:%M"
|
||||
|
|
|
@ -1768,6 +1768,7 @@ es:
|
|||
contrast: Alto contraste
|
||||
default: Mastodon
|
||||
mastodon-light: Mastodon (claro)
|
||||
system: Automático (usar tema del sistema)
|
||||
time:
|
||||
formats:
|
||||
default: "%d de %b del %Y, %H:%M"
|
||||
|
|
|
@ -1772,6 +1772,7 @@ eu:
|
|||
contrast: Mastodon (Kontraste altua)
|
||||
default: Mastodon (Iluna)
|
||||
mastodon-light: Mastodon (Argia)
|
||||
system: Automatikoa (erabili sistemaren gaia)
|
||||
time:
|
||||
formats:
|
||||
default: "%Y(e)ko %b %d, %H:%M"
|
||||
|
|
|
@ -1768,6 +1768,7 @@ fi:
|
|||
contrast: Mastodon (Korkea kontrasti)
|
||||
default: Mastodon (Tumma)
|
||||
mastodon-light: Mastodon (Vaalea)
|
||||
system: Automaattinen (käytä järjestelmän teemaa)
|
||||
time:
|
||||
formats:
|
||||
default: "%d.%m.%Y klo %H.%M"
|
||||
|
|
|
@ -1768,6 +1768,7 @@ fo:
|
|||
contrast: Mastodon (høgur kontrastur)
|
||||
default: Mastodon (myrkt)
|
||||
mastodon-light: Mastodon (ljóst)
|
||||
system: Sjálvvirkandi (brúka vanligt uppsetingareyðkenni)
|
||||
time:
|
||||
formats:
|
||||
default: "%b %d, %Y, %H:%M"
|
||||
|
|
|
@ -1768,6 +1768,7 @@ fr-CA:
|
|||
contrast: Mastodon (Contraste élevé)
|
||||
default: Mastodon (Sombre)
|
||||
mastodon-light: Mastodon (Clair)
|
||||
system: Automatique (utiliser le thème système)
|
||||
time:
|
||||
formats:
|
||||
default: "%d %b %Y, %H:%M"
|
||||
|
|
|
@ -1768,6 +1768,7 @@ fr:
|
|||
contrast: Mastodon (Contraste élevé)
|
||||
default: Mastodon (Sombre)
|
||||
mastodon-light: Mastodon (Clair)
|
||||
system: Automatique (utiliser le thème système)
|
||||
time:
|
||||
formats:
|
||||
default: "%d %b %Y, %H:%M"
|
||||
|
|
|
@ -1768,6 +1768,7 @@ gl:
|
|||
contrast: Mastodon (Alto contraste)
|
||||
default: Mastodon (Escuro)
|
||||
mastodon-light: Mastodon (Claro)
|
||||
system: Automático (seguir ao sistema)
|
||||
time:
|
||||
formats:
|
||||
default: "%d %b, %Y, %H:%M"
|
||||
|
|
|
@ -1832,6 +1832,7 @@ he:
|
|||
contrast: מסטודון (ניגודיות גבוהה)
|
||||
default: מסטודון (כהה)
|
||||
mastodon-light: מסטודון (בהיר)
|
||||
system: אוטומטי (לפי המערכת)
|
||||
time:
|
||||
formats:
|
||||
default: "%d %b %Y, %H:%M"
|
||||
|
|
|
@ -57,27 +57,27 @@ hu:
|
|||
deleted: Törölve
|
||||
demote: Lefokozás
|
||||
destroyed_msg: A %{username} fiók adatai bekerültek a végleges törlése váró sorba
|
||||
disable: Kikapcsolás
|
||||
disable_sign_in_token_auth: Tokenes e-mail hitelesítés letiltása
|
||||
disable: Befagyasztás
|
||||
disable_sign_in_token_auth: Tokenes e-mail-hitelesítés letiltása
|
||||
disable_two_factor_authentication: Kétlépcsős hitelesítés kikapcsolása
|
||||
disabled: Kikapcsolva
|
||||
display_name: Megjelenített név
|
||||
disabled: Befagyasztva
|
||||
display_name: Megjelenítendő név
|
||||
domain: Domain
|
||||
edit: Szerkesztés
|
||||
email: E-mail
|
||||
email: E-mail-cím
|
||||
email_status: E-mail állapot
|
||||
enable: Bekapcsolás
|
||||
enable_sign_in_token_auth: Tokenes e-mail hitelesítés engedélyezése
|
||||
enable: Kiolvasztás
|
||||
enable_sign_in_token_auth: Tokenes e-mail-hitelesítés engedélyezése
|
||||
enabled: Bekapcsolva
|
||||
enabled_msg: A %{username} fiók fagyasztását sikeresen visszavontuk
|
||||
enabled_msg: "%{username} fiókja befagyasztása sikeresen visszavonva"
|
||||
followers: Követő
|
||||
follows: Követett
|
||||
header: Fejléc
|
||||
inbox_url: Beérkezett üzenetek URL-je
|
||||
inbox_url: Beérkezett üzenetek webcíme
|
||||
invite_request_text: Csatlakozás oka
|
||||
invited_by: Meghívta
|
||||
ip: IP
|
||||
joined: Csatlakozott
|
||||
ip: IP-cím
|
||||
joined: Csatlakozva
|
||||
location:
|
||||
all: Összes
|
||||
local: Helyi
|
||||
|
@ -1768,6 +1768,7 @@ hu:
|
|||
contrast: Mastodon (nagy kontrasztú)
|
||||
default: Mastodon (sötét)
|
||||
mastodon-light: Mastodon (világos)
|
||||
system: Automatikus (rendszertéma használata)
|
||||
time:
|
||||
formats:
|
||||
default: "%Y. %b %d., %H:%M"
|
||||
|
|
|
@ -1772,6 +1772,7 @@ is:
|
|||
contrast: Mastodon (mikil birtuskil)
|
||||
default: Mastodon (dökkt)
|
||||
mastodon-light: Mastodon (ljóst)
|
||||
system: Sjálfvirkt (nota þema kerfis)
|
||||
time:
|
||||
formats:
|
||||
default: "%d. %b, %Y, %H:%M"
|
||||
|
|
|
@ -1770,6 +1770,7 @@ it:
|
|||
contrast: Mastodon (contrasto elevato)
|
||||
default: Mastodon (scuro)
|
||||
mastodon-light: Mastodon (chiaro)
|
||||
system: Automatico (usa il tema di sistema)
|
||||
time:
|
||||
formats:
|
||||
default: "%d %b %Y, %H:%M"
|
||||
|
|
|
@ -391,6 +391,7 @@ kab:
|
|||
invites: Iɛeṛṛuḍen
|
||||
moderation: Aseɣyed
|
||||
delete: Kkes
|
||||
everyone: Tisirag timezwura
|
||||
privileges:
|
||||
administrator: Anedbal
|
||||
rules:
|
||||
|
@ -441,6 +442,10 @@ kab:
|
|||
system_checks:
|
||||
rules_check:
|
||||
action: Sefrek ilugan n uqeddac
|
||||
software_version_critical_check:
|
||||
action: Wali ileqqman yellan
|
||||
software_version_patch_check:
|
||||
action: Wali ileqqman yellan
|
||||
title: Tadbelt
|
||||
trends:
|
||||
allow: Sireg
|
||||
|
@ -602,6 +607,8 @@ kab:
|
|||
notifications: Ilɣa
|
||||
thread: Idiwenniyen
|
||||
edit:
|
||||
add_keyword: Rnu awal tasarut
|
||||
keywords: Awalen n tsarut
|
||||
title: Ẓreg amzizdig
|
||||
index:
|
||||
delete: Kkes
|
||||
|
@ -640,6 +647,7 @@ kab:
|
|||
blocking: Tabdart n yimiḍanen iweḥlen
|
||||
bookmarks: Ticraḍ
|
||||
following: Tabdert n wid teṭṭafareḍ
|
||||
lists: Tibdarin
|
||||
muting: Tabdert n wid tesgugmeḍ
|
||||
upload: Sali
|
||||
invites:
|
||||
|
@ -750,6 +758,7 @@ kab:
|
|||
phantom_js: PhantomJS
|
||||
qq: Iminig QQ
|
||||
safari: Safari
|
||||
unknown_browser: Iminig arussin
|
||||
weibo: Weibo
|
||||
current_session: Tiɣimit tamirant
|
||||
date: Azemz
|
||||
|
|
|
@ -1738,6 +1738,7 @@ ko:
|
|||
contrast: 마스토돈 (고대비)
|
||||
default: 마스토돈 (어두움)
|
||||
mastodon-light: 마스토돈 (밝음)
|
||||
system: 자동 선택 (시스템 테마 이용)
|
||||
time:
|
||||
formats:
|
||||
default: "%Y-%m-%d %H:%M"
|
||||
|
|
|
@ -1768,6 +1768,7 @@ nl:
|
|||
contrast: Mastodon (hoog contrast)
|
||||
default: Mastodon (donker)
|
||||
mastodon-light: Mastodon (licht)
|
||||
system: Automatisch (systeemthema gebruiken)
|
||||
time:
|
||||
formats:
|
||||
default: "%d %B %Y om %H:%M"
|
||||
|
|
|
@ -1768,6 +1768,7 @@ nn:
|
|||
contrast: Mastodon (Høg kontrast)
|
||||
default: Mastodon (Mørkt)
|
||||
mastodon-light: Mastodon (Lyst)
|
||||
system: Automatisk (bruk systemdrakta)
|
||||
time:
|
||||
formats:
|
||||
default: "%d.%b %Y, %H:%M"
|
||||
|
|
|
@ -1832,6 +1832,7 @@ pl:
|
|||
contrast: Mastodon (Wysoki kontrast)
|
||||
default: Mastodon (Ciemny)
|
||||
mastodon-light: Mastodon (Jasny)
|
||||
system: Automatyczny (odpowiadający motywowi systemu)
|
||||
time:
|
||||
formats:
|
||||
default: "%d. %b %Y, %H:%M"
|
||||
|
|
|
@ -597,6 +597,9 @@ pt-BR:
|
|||
actions_description_html: Decida que medidas tomar para resolver esta denúncia. Se você decidir punir a conta denunciada, ela receberá uma notificação por e-mail, exceto quando for selecionada a categoria <strong>spam</strong> for selecionada.
|
||||
actions_description_remote_html: Decida quais medidas tomará para resolver esta denúncia. Isso só afetará como <strong>seu servidor</strong> se comunica com esta conta remota e manipula seu conteúdo.
|
||||
add_to_report: Adicionar mais à denúncia
|
||||
already_suspended_badges:
|
||||
local: Já suspenso neste servidor
|
||||
remote: Já suspenso em seu servidor
|
||||
are_you_sure: Você tem certeza?
|
||||
assign_to_self: Atribuir para si
|
||||
assigned: Moderador responsável
|
||||
|
@ -1652,13 +1655,20 @@ pt-BR:
|
|||
import: Importar
|
||||
import_and_export: Importar e exportar
|
||||
migrate: Migração de conta
|
||||
notifications: Notificações por e-mail
|
||||
preferences: Preferências
|
||||
profile: Perfil
|
||||
relationships: Seguindo e seguidores
|
||||
severed_relationships: Relacionamentos rompidos
|
||||
statuses_cleanup: Exclusão automatizada de publicações
|
||||
strikes: Avisos de moderação
|
||||
two_factor_authentication: Autenticação de dois fatores
|
||||
webauthn_authentication: Chaves de segurança
|
||||
severed_relationships:
|
||||
download: Download %{count}
|
||||
event_type:
|
||||
account_suspension: Suspensão da conta (%{target_name})
|
||||
domain_block: Suspensão do servidor (%{target_name})
|
||||
statuses:
|
||||
attached:
|
||||
audio:
|
||||
|
|
|
@ -1768,6 +1768,7 @@ pt-PT:
|
|||
contrast: Mastodon (Elevado contraste)
|
||||
default: Mastodon (Escuro)
|
||||
mastodon-light: Mastodon (Claro)
|
||||
system: Automático (usar tema do sistema)
|
||||
time:
|
||||
formats:
|
||||
default: "%H:%M em %d de %b de %Y"
|
||||
|
|
|
@ -1899,6 +1899,7 @@ ru:
|
|||
suspend: Учётная запись заблокирована
|
||||
welcome:
|
||||
explanation: Вот несколько советов для новичков
|
||||
feature_action: Подробнее
|
||||
subject: Добро пожаловать в Mastodon
|
||||
title: Добро пожаловать на борт, %{name}!
|
||||
users:
|
||||
|
|
|
@ -39,12 +39,14 @@ ar:
|
|||
text: يمكنك الطعن في عقوبة مرة واحدة فقط
|
||||
defaults:
|
||||
autofollow: سوف يتابعك تلقائيًا الأشخاص الذين يقومون بالتسجيل من خلال الدعوة
|
||||
avatar: ملف WEBP أو PNG أو GIF أو JPG. حجمه على أقصى تصدير %{size}. سيتم تقليصه إلى %{dimensions} بيكسل
|
||||
bot: يقوم هذا الحساب أساسا بإجراءات آلية وقد لا يتم مراقبته
|
||||
context: واحد أو أكثر من السياقات التي يجب أن ينطبق عليها عامل التصفية
|
||||
current_password: لأسباب أمنية ، يرجى إدخال الكلمة السرية الخاصة بالحساب الحالي
|
||||
current_username: يرجى إدخال اسم المستخدم الخاص بالحساب الحالي قصد التأكيد
|
||||
digest: تُرسَل إليك بعد مُضيّ مدة مِن خمول نشاطك و فقط إذا ما تلقيت رسائل شخصية مباشِرة أثناء فترة غيابك مِن الشبكة
|
||||
email: سوف تتلقى رسالة إلكترونية للتأكيد
|
||||
header: ملف WEBP أو PNG أو GIF أو JPG. حجمه على أقصى تصدير %{size}. سيتم تقليصه إلى %{dimensions} بيكسل
|
||||
inbox_url: نسخ العنوان الذي تريد استخدامه مِن صفحة الاستقبال للمُرحَّل
|
||||
irreversible: المنشورات التي تم تصفيتها ستختفي لا محالة حتى و إن تمت إزالة عامِل التصفية لاحقًا
|
||||
locale: لغة واجهة المستخدم و الرسائل الإلكترونية و الإشعارات
|
||||
|
@ -114,6 +116,7 @@ ar:
|
|||
sign_up_requires_approval: التسجيلات الجديدة سوف تتطلب موافقتك
|
||||
severity: اختر ما سيحدث مع الطلبات من هذا الـIP
|
||||
rule:
|
||||
hint: اختياري. قدم المزيد من التفاصيل حول القاعدة
|
||||
text: صِف قانون أو شرط للمستخدمين على هذا الخادم. حاول أن تُبقيه قصير وبسيط
|
||||
sessions:
|
||||
otp: 'قم بإدخال رمز المصادقة بخطوتين الذي قام بتوليده تطبيق جهازك أو استخدم أحد رموز النفاذ الاحتياطية:'
|
||||
|
@ -297,6 +300,7 @@ ar:
|
|||
patch: إشعاري عند توفّر تحديثات التصحيح
|
||||
trending_tag: المتداولة الجديدة تتطلّب مراجعة
|
||||
rule:
|
||||
hint: معلومات إضافية
|
||||
text: قانون
|
||||
settings:
|
||||
indexable: السماح لمحركات البحث بفهرسة صفحتك التعريفية
|
||||
|
|
|
@ -74,8 +74,8 @@ kab:
|
|||
setting_default_language: Tutlayt n tira
|
||||
setting_default_privacy: Tabaḍnit n tira
|
||||
setting_display_media_default: Akk-a kan
|
||||
setting_display_media_hide_all: Ffer kullec
|
||||
setting_display_media_show_all: Ssken kullec
|
||||
setting_display_media_hide_all: Ffer-iten akk
|
||||
setting_display_media_show_all: Sken-iten-id akk
|
||||
setting_hide_network: Ffer azetta-k·m
|
||||
setting_theme: Asental n wesmel
|
||||
setting_use_pending_items: Askar aleɣwayan
|
||||
|
@ -115,6 +115,8 @@ kab:
|
|||
text: Alugen
|
||||
tag:
|
||||
name: Ahacṭag
|
||||
user:
|
||||
time_zone: Tamnaḍt tasragant
|
||||
user_role:
|
||||
name: Isem
|
||||
permissions_as_keys: Tisirag
|
||||
|
|
|
@ -585,6 +585,7 @@ sk:
|
|||
administration: Spravovanie
|
||||
invites: Pozvánky
|
||||
moderation: Moderácia
|
||||
special: Špeciálne
|
||||
delete: Vymaž
|
||||
edit: Uprav postavenie %{name}
|
||||
everyone: Východzie oprávnenia
|
||||
|
@ -804,6 +805,7 @@ sk:
|
|||
confirmations:
|
||||
clicking_this_link: kliknutím na tento odkaz
|
||||
login_link: prihlás sa
|
||||
proceed_to_login_html: Teraz môžeš pokračovať na %{login_link}.
|
||||
welcome_title: Vitaj, %{name}!
|
||||
delete_account: Vymaž účet
|
||||
delete_account_html: Pokiaľ chceš svoj účet odtiaľto vymazať, môžeš tak <a href="%{path}">urobiť tu</a>. Budeš požiadaný/á o potvrdenie tohto kroku.
|
||||
|
@ -822,6 +824,7 @@ sk:
|
|||
or_log_in_with: Alebo prihlás s
|
||||
progress:
|
||||
confirm: Potvrď email
|
||||
details: Tvoje údaje
|
||||
rules: Súhlas s pravidlami
|
||||
register: Zaregistruj sa
|
||||
registration_closed: "%{instance} neprijíma nových členov"
|
||||
|
@ -883,6 +886,7 @@ sk:
|
|||
username_unavailable: Tvoja prezývka ostane neprístupná
|
||||
disputes:
|
||||
strikes:
|
||||
action_taken: Vykonaný zákrok
|
||||
appeal: Namietni
|
||||
appeals:
|
||||
submit: Pošli námietku
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue