Merge pull request #3461 from glitch-soc/glitch-soc/merge-upstream
Merge upstream changes up to 8e00f7cc8fab9b447f80a27a972eef78186633ad
This commit is contained in:
commit
bff48d3653
@ -13,13 +13,13 @@ class SeveredRelationshipsController < ApplicationController
|
||||
|
||||
def following
|
||||
respond_to do |format|
|
||||
format.csv { send_data following_data, filename: "following-#{@event.target_name}-#{@event.created_at.to_date.iso8601}.csv" }
|
||||
format.csv { send_data following_data, filename: }
|
||||
end
|
||||
end
|
||||
|
||||
def followers
|
||||
respond_to do |format|
|
||||
format.csv { send_data followers_data, filename: "followers-#{@event.target_name}-#{@event.created_at.to_date.iso8601}.csv" }
|
||||
format.csv { send_data followers_data, filename: }
|
||||
end
|
||||
end
|
||||
|
||||
@ -48,4 +48,8 @@ class SeveredRelationshipsController < ApplicationController
|
||||
def acct(account)
|
||||
account.local? ? account.local_username_and_domain : account.acct
|
||||
end
|
||||
|
||||
def filename
|
||||
"#{action_name}-#{@event.identifier}.csv"
|
||||
end
|
||||
end
|
||||
|
||||
@ -76,6 +76,7 @@ interface AccountProps {
|
||||
withMenu?: boolean;
|
||||
withBorder?: boolean;
|
||||
extraAccountInfo?: React.ReactNode;
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
@ -89,6 +90,7 @@ export const Account: React.FC<AccountProps> = ({
|
||||
withMenu = true,
|
||||
withBorder = true,
|
||||
extraAccountInfo,
|
||||
className,
|
||||
children,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
@ -291,7 +293,7 @@ export const Account: React.FC<AccountProps> = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('account', {
|
||||
className={classNames('account', className, {
|
||||
'account--minimal': minimal,
|
||||
'account--without-border': !withBorder,
|
||||
})}
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
background-color: var(--color-bg-brand-softest);
|
||||
color: var(--color-text-primary);
|
||||
border-radius: 12px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
|
||||
@ -7,15 +7,11 @@ import ErrorIcon from '@/material-icons/400-24px/error.svg?react';
|
||||
import InfoIcon from '@/material-icons/400-24px/info.svg?react';
|
||||
import WarningIcon from '@/material-icons/400-24px/warning.svg?react';
|
||||
|
||||
import type { FieldStatus } from '../form_fields/form_field_wrapper';
|
||||
import { Icon } from '../icon';
|
||||
|
||||
import classes from './styles.module.css';
|
||||
|
||||
export interface FieldStatus {
|
||||
variant: 'error' | 'warning' | 'info' | 'success';
|
||||
message?: string;
|
||||
}
|
||||
|
||||
const iconMap: Record<FieldStatus['variant'], React.FunctionComponent> = {
|
||||
error: ErrorIcon,
|
||||
warning: WarningIcon,
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { length } from 'stringz';
|
||||
|
||||
import { polymorphicForwardRef } from '@/types/polymorphic';
|
||||
|
||||
import classes from './styles.module.scss';
|
||||
@ -14,8 +14,6 @@ interface CharacterCounterProps {
|
||||
recommended?: boolean;
|
||||
}
|
||||
|
||||
const segmenter = new Intl.Segmenter();
|
||||
|
||||
export const CharacterCounter = polymorphicForwardRef<
|
||||
'span',
|
||||
CharacterCounterProps
|
||||
@ -31,10 +29,7 @@ export const CharacterCounter = polymorphicForwardRef<
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const currentLength = useMemo(
|
||||
() => [...segmenter.segment(currentString)].length,
|
||||
[currentString],
|
||||
);
|
||||
const currentLength = length(currentString);
|
||||
return (
|
||||
<Component
|
||||
{...props}
|
||||
|
||||
@ -13,6 +13,7 @@ export const StatusesCounter = (
|
||||
count: pluralReady,
|
||||
counter: <strong>{displayNumber}</strong>,
|
||||
}}
|
||||
tagName='span'
|
||||
/>
|
||||
);
|
||||
|
||||
@ -27,6 +28,7 @@ export const FollowingCounter = (
|
||||
count: pluralReady,
|
||||
counter: <strong>{displayNumber}</strong>,
|
||||
}}
|
||||
tagName='span'
|
||||
/>
|
||||
);
|
||||
|
||||
@ -41,6 +43,7 @@ export const FollowersCounter = (
|
||||
count: pluralReady,
|
||||
counter: <strong>{displayNumber}</strong>,
|
||||
}}
|
||||
tagName='span'
|
||||
/>
|
||||
);
|
||||
|
||||
@ -55,5 +58,6 @@ export const FollowersYouKnowCounter = (
|
||||
count: pluralReady,
|
||||
counter: <strong>{displayNumber}</strong>,
|
||||
}}
|
||||
tagName='span'
|
||||
/>
|
||||
);
|
||||
|
||||
@ -121,6 +121,7 @@ export const EditedTimestamp: React.FC<{
|
||||
/>
|
||||
),
|
||||
}}
|
||||
tagName='span'
|
||||
/>
|
||||
</button>
|
||||
</Dropdown>
|
||||
|
||||
@ -2,11 +2,9 @@ import type {
|
||||
ChangeEvent,
|
||||
ChangeEventHandler,
|
||||
ComponentPropsWithoutRef,
|
||||
Dispatch,
|
||||
FC,
|
||||
ReactNode,
|
||||
RefObject,
|
||||
SetStateAction,
|
||||
} from 'react';
|
||||
import { useCallback, useId, useRef } from 'react';
|
||||
|
||||
@ -25,7 +23,7 @@ import { TextInput } from './text_input_field';
|
||||
|
||||
export type EmojiInputProps = {
|
||||
value?: string;
|
||||
onChange?: Dispatch<SetStateAction<string>>;
|
||||
onChange?: (newValue: string) => void;
|
||||
counterMax?: number;
|
||||
recommended?: boolean;
|
||||
} & Omit<CommonFieldWrapperProps, 'wrapperClassName'>;
|
||||
@ -138,12 +136,15 @@ const EmojiFieldWrapper: FC<
|
||||
|
||||
const handlePickEmoji = useCallback(
|
||||
(emoji: string) => {
|
||||
onChange?.((prev) => {
|
||||
const position = inputRef.current?.selectionStart ?? prev.length;
|
||||
return insertEmojiAtPosition(prev, emoji, position);
|
||||
});
|
||||
if (!value) {
|
||||
onChange?.('');
|
||||
return;
|
||||
}
|
||||
const position = inputRef.current?.selectionStart ?? value.length;
|
||||
const newValue = insertEmojiAtPosition(value, emoji, position);
|
||||
onChange?.(newValue);
|
||||
},
|
||||
[onChange, inputRef],
|
||||
[inputRef, value, onChange],
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
|
||||
@ -4,10 +4,10 @@ import type { ReactNode, FC } from 'react';
|
||||
import { createContext, useId } from 'react';
|
||||
|
||||
import { A11yLiveRegion } from 'flavours/glitch/components/a11y_live_region';
|
||||
import type { FieldStatus } from 'flavours/glitch/components/callout_inline';
|
||||
import { CalloutInline } from 'flavours/glitch/components/callout_inline';
|
||||
|
||||
import classes from './fieldset.module.scss';
|
||||
import type { FieldStatus } from './form_field_wrapper';
|
||||
import { getFieldStatus } from './form_field_wrapper';
|
||||
import formFieldWrapperClasses from './form_field_wrapper.module.scss';
|
||||
|
||||
|
||||
@ -8,7 +8,6 @@ import { FormattedMessage } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { A11yLiveRegion } from 'flavours/glitch/components/a11y_live_region';
|
||||
import type { FieldStatus } from 'flavours/glitch/components/callout_inline';
|
||||
import { CalloutInline } from 'flavours/glitch/components/callout_inline';
|
||||
|
||||
import { FieldsetNameContext } from './fieldset';
|
||||
@ -20,11 +19,16 @@ export interface InputProps {
|
||||
'aria-describedby'?: string;
|
||||
}
|
||||
|
||||
export interface FieldStatus {
|
||||
variant: 'error' | 'warning' | 'info' | 'success';
|
||||
message?: string;
|
||||
}
|
||||
|
||||
interface FieldWrapperProps {
|
||||
label: ReactNode;
|
||||
hint?: ReactNode;
|
||||
required?: boolean;
|
||||
status?: FieldStatus['variant'] | FieldStatus;
|
||||
status?: FieldStatus['variant'] | FieldStatus | null;
|
||||
inputId?: string;
|
||||
describedById?: string;
|
||||
inputPlacement?: 'inline-start' | 'inline-end';
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export type { FieldStatus } from './form_field_wrapper';
|
||||
export { FormFieldWrapper } from './form_field_wrapper';
|
||||
export { FormStack } from './form_stack';
|
||||
export { Fieldset } from './fieldset';
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
||||
import type { MastodonLocationDescriptor } from 'flavours/glitch/components/router';
|
||||
|
||||
import classes from './styles.module.scss';
|
||||
|
||||
interface WrapperProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const NumberFields: React.FC<WrapperProps> = ({ children }) => {
|
||||
return <ul className={classes.list}>{children}</ul>;
|
||||
};
|
||||
|
||||
interface ItemProps {
|
||||
label: React.ReactNode;
|
||||
hint?: string;
|
||||
link?: MastodonLocationDescriptor;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const NumberFieldsItem: React.FC<ItemProps> = ({
|
||||
label,
|
||||
hint,
|
||||
link,
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<li className={classes.item} title={hint}>
|
||||
{label}
|
||||
{link ? (
|
||||
<NavLink exact to={link}>
|
||||
{children}
|
||||
</NavLink>
|
||||
) : (
|
||||
<strong>{children}</strong>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,32 @@
|
||||
.list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 8px 0;
|
||||
padding: 0;
|
||||
gap: 4px 20px;
|
||||
font-size: 13px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.item {
|
||||
@container (width < 420px) {
|
||||
flex: 1 1 0px;
|
||||
}
|
||||
|
||||
a,
|
||||
strong {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
a {
|
||||
padding: 0;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -26,6 +26,8 @@ export type LocationState = MastodonLocationState | null | undefined;
|
||||
|
||||
export type MastodonLocation = ReturnType<typeof useLocation<LocationState>>;
|
||||
|
||||
export type MastodonLocationDescriptor = LocationDescriptor<LocationState>;
|
||||
|
||||
type HistoryPath = Path | LocationDescriptor<LocationState>;
|
||||
|
||||
export const browserHistory = createBrowserHistory<LocationState>();
|
||||
|
||||
@ -241,21 +241,23 @@ export const AccountEdit: FC = () => {
|
||||
showDescription={!hasFields}
|
||||
buttons={
|
||||
<div className={classes.fieldButtons}>
|
||||
<Button
|
||||
className={classes.editButton}
|
||||
onClick={handleCustomFieldReorder}
|
||||
disabled={profile.fields.length <= 1}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='account_edit.custom_fields.reorder_button'
|
||||
defaultMessage='Reorder fields'
|
||||
{profile.fields.length > 1 && (
|
||||
<Button
|
||||
className={classes.editButton}
|
||||
onClick={handleCustomFieldReorder}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='account_edit.custom_fields.reorder_button'
|
||||
defaultMessage='Reorder fields'
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
{profile.fields.length < maxFieldCount && (
|
||||
<EditButton
|
||||
label={intl.formatMessage(messages.customFieldsAddLabel)}
|
||||
onClick={handleCustomFieldAdd}
|
||||
/>
|
||||
</Button>
|
||||
<EditButton
|
||||
label={intl.formatMessage(messages.customFieldsAddLabel)}
|
||||
onClick={handleCustomFieldAdd}
|
||||
disabled={profile.fields.length >= maxFieldCount}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
|
||||
@ -1,11 +1,5 @@
|
||||
import {
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { forwardRef, useCallback, useImperativeHandle, useState } from 'react';
|
||||
import type { FC, FocusEventHandler } from 'react';
|
||||
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
@ -13,7 +7,7 @@ import type { Map as ImmutableMap } from 'immutable';
|
||||
|
||||
import { closeModal } from '@/flavours/glitch/actions/modal';
|
||||
import { Button } from '@/flavours/glitch/components/button';
|
||||
import { Callout } from '@/flavours/glitch/components/callout';
|
||||
import type { FieldStatus } from '@/flavours/glitch/components/form_fields';
|
||||
import { EmojiTextInputField } from '@/flavours/glitch/components/form_fields';
|
||||
import {
|
||||
removeField,
|
||||
@ -71,6 +65,26 @@ const messages = defineMessages({
|
||||
id: 'account_edit.field_edit_modal.discard_confirm',
|
||||
defaultMessage: 'Discard',
|
||||
},
|
||||
errorBlank: {
|
||||
id: 'form_error.blank',
|
||||
defaultMessage: 'Field cannot be blank.',
|
||||
},
|
||||
warningLength: {
|
||||
id: 'account_edit.field_edit_modal.length_warning',
|
||||
defaultMessage:
|
||||
'Recommended character limit exceeded. Mobile users might not see your field in full.',
|
||||
},
|
||||
warningUrlEmoji: {
|
||||
id: 'account_edit.field_edit_modal.link_emoji_warning',
|
||||
defaultMessage:
|
||||
'We recommend against the use of custom emoji in combination with urls. Custom fields containing both will display as text only instead of as a link, in order to prevent user confusion.',
|
||||
},
|
||||
warningUrlProtocol: {
|
||||
id: 'account_edit.field_edit_modal.url_warning',
|
||||
defaultMessage:
|
||||
'To add a link, please include {protocol} at the beginning.',
|
||||
description: '{protocol} is https://',
|
||||
},
|
||||
});
|
||||
|
||||
// We have two different values- the hard limit set by the server,
|
||||
@ -124,34 +138,86 @@ export const EditFieldModal = forwardRef<
|
||||
const { nameLimit, valueLimit } = useAppSelector(selectFieldLimits);
|
||||
const isPending = useAppSelector((state) => state.profileEdit.isPending);
|
||||
|
||||
const disabled =
|
||||
!newLabel.trim() ||
|
||||
!newValue.trim() ||
|
||||
!isDirty ||
|
||||
!nameLimit ||
|
||||
!valueLimit ||
|
||||
newLabel.length > nameLimit ||
|
||||
newValue.length > valueLimit;
|
||||
const [fieldStatuses, setFieldStatuses] = useState<{
|
||||
label?: FieldStatus;
|
||||
value?: FieldStatus;
|
||||
}>({});
|
||||
|
||||
const customEmojiCodes = useAppSelector(selectEmojiCodes);
|
||||
const hasLinkAndEmoji = useMemo(() => {
|
||||
const text = `${newLabel} ${newValue}`; // Combine text, as we're searching it all.
|
||||
const hasLink = /https?:\/\//.test(text);
|
||||
const hasEmoji = customEmojiCodes.some((code) =>
|
||||
text.includes(`:${code}:`),
|
||||
);
|
||||
return hasLink && hasEmoji;
|
||||
}, [customEmojiCodes, newLabel, newValue]);
|
||||
const hasLinkWithoutProtocol = useMemo(
|
||||
() => isUrlWithoutProtocol(newValue),
|
||||
[newValue],
|
||||
const checkField = useCallback(
|
||||
(value: string): FieldStatus | null => {
|
||||
if (!value.trim()) {
|
||||
return {
|
||||
variant: 'error',
|
||||
message: intl.formatMessage(messages.errorBlank),
|
||||
};
|
||||
}
|
||||
|
||||
if (value.length > RECOMMENDED_LIMIT) {
|
||||
return {
|
||||
variant: 'warning',
|
||||
message: intl.formatMessage(messages.warningLength, {
|
||||
max: RECOMMENDED_LIMIT,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
const hasLink = /https?:\/\//.test(value);
|
||||
const hasEmoji = customEmojiCodes.some((code) =>
|
||||
value.includes(`:${code}:`),
|
||||
);
|
||||
if (hasLink && hasEmoji) {
|
||||
return {
|
||||
variant: 'warning',
|
||||
message: intl.formatMessage(messages.warningUrlEmoji),
|
||||
};
|
||||
}
|
||||
|
||||
if (isUrlWithoutProtocol(value)) {
|
||||
return {
|
||||
variant: 'warning',
|
||||
message: intl.formatMessage(messages.warningUrlProtocol, {
|
||||
protocol: 'https://',
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
[customEmojiCodes, intl],
|
||||
);
|
||||
|
||||
const handleBlur: FocusEventHandler<HTMLInputElement> = useCallback(
|
||||
(event) => {
|
||||
const { name, value } = event.target;
|
||||
const result = checkField(value);
|
||||
if (name !== 'label' && name !== 'value') {
|
||||
return;
|
||||
}
|
||||
setFieldStatuses((statuses) => ({
|
||||
...statuses,
|
||||
[name]: result ?? undefined,
|
||||
}));
|
||||
},
|
||||
[checkField],
|
||||
);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const handleSave = useCallback(() => {
|
||||
if (disabled || isPending) {
|
||||
if (isPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
const labelStatus = checkField(newLabel);
|
||||
const valueStatus = checkField(newValue);
|
||||
if (labelStatus || valueStatus) {
|
||||
setFieldStatuses({
|
||||
label: labelStatus ?? undefined,
|
||||
value: valueStatus ?? undefined,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
void dispatch(
|
||||
updateField({ id: fieldKey, name: newLabel, value: newValue }),
|
||||
).then(() => {
|
||||
@ -163,7 +229,7 @@ export const EditFieldModal = forwardRef<
|
||||
}),
|
||||
);
|
||||
});
|
||||
}, [disabled, dispatch, fieldKey, isPending, newLabel, newValue]);
|
||||
}, [checkField, dispatch, fieldKey, isPending, newLabel, newValue]);
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
@ -198,60 +264,33 @@ export const EditFieldModal = forwardRef<
|
||||
confirm={intl.formatMessage(messages.save)}
|
||||
onConfirm={handleSave}
|
||||
updating={isPending}
|
||||
disabled={disabled}
|
||||
className={classes.wrapper}
|
||||
>
|
||||
<EmojiTextInputField
|
||||
name='label'
|
||||
value={newLabel}
|
||||
onChange={setNewLabel}
|
||||
onBlur={handleBlur}
|
||||
label={intl.formatMessage(messages.editLabelField)}
|
||||
hint={intl.formatMessage(messages.editLabelHint)}
|
||||
status={fieldStatuses.label}
|
||||
maxLength={nameLimit}
|
||||
counterMax={RECOMMENDED_LIMIT}
|
||||
recommended
|
||||
/>
|
||||
|
||||
<EmojiTextInputField
|
||||
name='value'
|
||||
value={newValue}
|
||||
onChange={setNewValue}
|
||||
onBlur={handleBlur}
|
||||
label={intl.formatMessage(messages.editValueField)}
|
||||
hint={intl.formatMessage(messages.editValueHint)}
|
||||
status={fieldStatuses.value}
|
||||
maxLength={valueLimit}
|
||||
counterMax={RECOMMENDED_LIMIT}
|
||||
recommended
|
||||
/>
|
||||
|
||||
{hasLinkAndEmoji && (
|
||||
<Callout variant='warning'>
|
||||
<FormattedMessage
|
||||
id='account_edit.field_edit_modal.link_emoji_warning'
|
||||
defaultMessage='We recommend against the use of custom emoji in combination with urls. Custom fields containing both will display as text only instead of as a link, in order to prevent user confusion.'
|
||||
/>
|
||||
</Callout>
|
||||
)}
|
||||
|
||||
{(newLabel.length > RECOMMENDED_LIMIT ||
|
||||
newValue.length > RECOMMENDED_LIMIT) && (
|
||||
<Callout variant='warning'>
|
||||
<FormattedMessage
|
||||
id='account_edit.field_edit_modal.limit_warning'
|
||||
defaultMessage='Recommended character limit exceeded. Mobile users might not see your field in full.'
|
||||
/>
|
||||
</Callout>
|
||||
)}
|
||||
|
||||
{hasLinkWithoutProtocol && (
|
||||
<Callout variant='warning'>
|
||||
<FormattedMessage
|
||||
id='account_edit.field_edit_modal.url_warning'
|
||||
defaultMessage='To add a link, please include {protocol} at the beginning.'
|
||||
description='{protocol} is https://'
|
||||
values={{
|
||||
protocol: <code>https://</code>,
|
||||
}}
|
||||
/>
|
||||
</Callout>
|
||||
)}
|
||||
</ConfirmationModal>
|
||||
);
|
||||
});
|
||||
|
||||
@ -13,7 +13,6 @@
|
||||
|
||||
> img {
|
||||
object-fit: cover;
|
||||
object-position: top center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
@ -26,7 +25,7 @@
|
||||
|
||||
.avatar {
|
||||
margin-top: -64px;
|
||||
margin-left: 18px;
|
||||
margin-left: 22px;
|
||||
position: relative;
|
||||
width: 82px;
|
||||
|
||||
@ -251,7 +250,7 @@
|
||||
// Section component
|
||||
|
||||
.section {
|
||||
padding: 20px;
|
||||
padding: 24px;
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
@ -41,15 +41,25 @@ const selectGalleryTimeline = createAppSelector(
|
||||
(state) => state.statuses,
|
||||
],
|
||||
(accountId, timelines, accounts, statuses) => {
|
||||
let items = emptyList;
|
||||
if (!accountId) {
|
||||
return null;
|
||||
return {
|
||||
items,
|
||||
hasMore: false,
|
||||
isLoading: false,
|
||||
withReplies: false,
|
||||
};
|
||||
}
|
||||
const account = accounts.get(accountId);
|
||||
if (!account) {
|
||||
return null;
|
||||
return {
|
||||
items,
|
||||
hasMore: false,
|
||||
isLoading: false,
|
||||
withReplies: false,
|
||||
};
|
||||
}
|
||||
|
||||
let items = emptyList;
|
||||
const { show_media, show_media_replies } = account;
|
||||
// If the account disabled showing media, don't display anything.
|
||||
if (!show_media && redesignEnabled) {
|
||||
@ -57,13 +67,13 @@ const selectGalleryTimeline = createAppSelector(
|
||||
items,
|
||||
hasMore: false,
|
||||
isLoading: false,
|
||||
showingReplies: false,
|
||||
withReplies: false,
|
||||
};
|
||||
}
|
||||
|
||||
const showingReplies = show_media_replies && redesignEnabled;
|
||||
const withReplies = show_media_replies && redesignEnabled;
|
||||
const timeline = timelines.get(
|
||||
`account:${accountId}:media${showingReplies ? ':with_replies' : ''}`,
|
||||
`account:${accountId}:media${withReplies ? ':with_replies' : ''}`,
|
||||
);
|
||||
const statusIds = timeline?.get('items');
|
||||
|
||||
@ -81,8 +91,8 @@ const selectGalleryTimeline = createAppSelector(
|
||||
return {
|
||||
items,
|
||||
hasMore: !!timeline?.get('hasMore'),
|
||||
isLoading: !!timeline?.get('isLoading'),
|
||||
showingReplies,
|
||||
isLoading: timeline?.get('isLoading') ? true : false,
|
||||
withReplies,
|
||||
};
|
||||
},
|
||||
);
|
||||
@ -94,11 +104,11 @@ export const AccountGallery: React.FC<{
|
||||
const dispatch = useAppDispatch();
|
||||
const accountId = useAccountId();
|
||||
const {
|
||||
isLoading = true,
|
||||
hasMore = false,
|
||||
items: attachments = emptyList,
|
||||
showingReplies: withReplies = false,
|
||||
} = useAppSelector((state) => selectGalleryTimeline(state, accountId)) ?? {};
|
||||
isLoading,
|
||||
items: attachments,
|
||||
hasMore,
|
||||
withReplies,
|
||||
} = useAppSelector((state) => selectGalleryTimeline(state, accountId));
|
||||
|
||||
const { suspended, blockedBy, hidden } = useAccountVisibility(accountId);
|
||||
|
||||
@ -223,7 +233,7 @@ export const AccountGallery: React.FC<{
|
||||
alwaysPrepend
|
||||
append={accountId && <RemoteHint accountId={accountId} />}
|
||||
scrollKey='account_gallery'
|
||||
isLoading={isLoading}
|
||||
showLoading={isLoading}
|
||||
hasMore={!forceEmptyState && hasMore}
|
||||
onLoadMore={handleLoadMore}
|
||||
emptyMessage={emptyMessage}
|
||||
|
||||
@ -69,7 +69,9 @@ export const FamiliarFollowers: React.FC<{ accountId: string }> = ({
|
||||
<Avatar withLink key={account.id} account={account} size={28} />
|
||||
))}
|
||||
</AvatarGroup>
|
||||
<FamiliarFollowersReadout familiarFollowers={familiarFollowers} />
|
||||
<span>
|
||||
<FamiliarFollowersReadout familiarFollowers={familiarFollowers} />
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -3,7 +3,6 @@ import type { FC } from 'react';
|
||||
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
@ -12,13 +11,15 @@ import {
|
||||
StatusesCounter,
|
||||
} from '@/flavours/glitch/components/counters';
|
||||
import { FormattedDateWrapper } from '@/flavours/glitch/components/formatted_date';
|
||||
import {
|
||||
NumberFields,
|
||||
NumberFieldsItem,
|
||||
} from '@/flavours/glitch/components/number_fields';
|
||||
import { ShortNumber } from '@/flavours/glitch/components/short_number';
|
||||
import { useAccount } from '@/flavours/glitch/hooks/useAccount';
|
||||
|
||||
import { isRedesignEnabled } from '../common';
|
||||
|
||||
import classes from './redesign.module.scss';
|
||||
|
||||
const LegacyNumberFields: FC<{ accountId: string }> = ({ accountId }) => {
|
||||
const intl = useIntl();
|
||||
const account = useAccount(accountId);
|
||||
@ -77,56 +78,51 @@ const RedesignNumberFields: FC<{ accountId: string }> = ({ accountId }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<ul
|
||||
className={classNames(
|
||||
'account__header__extra__links',
|
||||
classes.fieldNumbersWrapper,
|
||||
)}
|
||||
>
|
||||
<li>
|
||||
<FormattedMessage id='account.posts' defaultMessage='Posts' />
|
||||
<strong>
|
||||
<ShortNumber value={account.statuses_count} />
|
||||
</strong>
|
||||
</li>
|
||||
<NumberFields>
|
||||
<NumberFieldsItem
|
||||
label={<FormattedMessage id='account.posts' defaultMessage='Posts' />}
|
||||
hint={intl.formatNumber(account.statuses_count)}
|
||||
>
|
||||
<ShortNumber value={account.statuses_count} />
|
||||
</NumberFieldsItem>
|
||||
|
||||
<li>
|
||||
<FormattedMessage id='account.followers' defaultMessage='Followers' />
|
||||
<NavLink
|
||||
exact
|
||||
to={`/@${account.acct}/followers`}
|
||||
title={intl.formatNumber(account.followers_count)}
|
||||
>
|
||||
<ShortNumber value={account.followers_count} />
|
||||
</NavLink>
|
||||
</li>
|
||||
<NumberFieldsItem
|
||||
label={
|
||||
<FormattedMessage id='account.followers' defaultMessage='Followers' />
|
||||
}
|
||||
hint={intl.formatNumber(account.followers_count)}
|
||||
link={`/@${account.acct}/followers`}
|
||||
>
|
||||
<ShortNumber value={account.followers_count} />
|
||||
</NumberFieldsItem>
|
||||
|
||||
<li>
|
||||
<FormattedMessage id='account.following' defaultMessage='Following' />
|
||||
<NavLink
|
||||
exact
|
||||
to={`/@${account.acct}/following`}
|
||||
title={intl.formatNumber(account.following_count)}
|
||||
>
|
||||
<ShortNumber value={account.following_count} />
|
||||
</NavLink>
|
||||
</li>
|
||||
<NumberFieldsItem
|
||||
label={
|
||||
<FormattedMessage id='account.following' defaultMessage='Following' />
|
||||
}
|
||||
hint={intl.formatNumber(account.following_count)}
|
||||
link={`/@${account.acct}/following`}
|
||||
>
|
||||
<ShortNumber value={account.following_count} />
|
||||
</NumberFieldsItem>
|
||||
|
||||
<li>
|
||||
<FormattedMessage id='account.joined_short' defaultMessage='Joined' />
|
||||
<strong>
|
||||
{createdThisYear ? (
|
||||
<FormattedDateWrapper
|
||||
value={account.created_at}
|
||||
month='short'
|
||||
day='2-digit'
|
||||
/>
|
||||
) : (
|
||||
<FormattedDateWrapper value={account.created_at} year='numeric' />
|
||||
)}
|
||||
</strong>
|
||||
</li>
|
||||
</ul>
|
||||
<NumberFieldsItem
|
||||
label={
|
||||
<FormattedMessage id='account.joined_short' defaultMessage='Joined' />
|
||||
}
|
||||
hint={intl.formatDate(account.created_at)}
|
||||
>
|
||||
{createdThisYear ? (
|
||||
<FormattedDateWrapper
|
||||
value={account.created_at}
|
||||
month='short'
|
||||
day='2-digit'
|
||||
/>
|
||||
) : (
|
||||
<FormattedDateWrapper value={account.created_at} year='numeric' />
|
||||
)}
|
||||
</NumberFieldsItem>
|
||||
</NumberFields>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -312,37 +312,6 @@ svg.badgeIcon {
|
||||
}
|
||||
}
|
||||
|
||||
.fieldNumbersWrapper {
|
||||
display: flex;
|
||||
font-size: 13px;
|
||||
padding: 0;
|
||||
margin: 8px 0;
|
||||
gap: 20px;
|
||||
|
||||
li {
|
||||
@container (width < 420px) {
|
||||
flex: 1 1 0px;
|
||||
}
|
||||
}
|
||||
|
||||
a,
|
||||
strong {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
a {
|
||||
padding: 0;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modalCloseButton {
|
||||
padding: 8px;
|
||||
border-radius: 50%;
|
||||
|
||||
@ -38,6 +38,7 @@ export function usePinnedStatusIds({
|
||||
userId: accountId,
|
||||
tagged,
|
||||
pinned: true,
|
||||
replies: true,
|
||||
});
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@ -2,6 +2,8 @@ import { useCallback, useRef, useState } from 'react';
|
||||
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { Callout } from '@/flavours/glitch/components/callout';
|
||||
import { FollowButton } from '@/flavours/glitch/components/follow_button';
|
||||
import { openModal } from 'flavours/glitch/actions/modal';
|
||||
import type {
|
||||
ApiCollectionJSON,
|
||||
@ -27,10 +29,6 @@ const messages = defineMessages({
|
||||
id: 'collections.accounts.empty_title',
|
||||
defaultMessage: 'This collection is empty',
|
||||
},
|
||||
accounts: {
|
||||
id: 'collections.detail.accounts_heading',
|
||||
defaultMessage: 'Accounts',
|
||||
},
|
||||
});
|
||||
|
||||
const SimpleAuthorName: React.FC<{ id: string }> = ({ id }) => {
|
||||
@ -41,8 +39,9 @@ const SimpleAuthorName: React.FC<{ id: string }> = ({ id }) => {
|
||||
const AccountItem: React.FC<{
|
||||
accountId: string | undefined;
|
||||
collectionOwnerId: string;
|
||||
withBio?: boolean;
|
||||
withBorder?: boolean;
|
||||
}> = ({ accountId, withBorder = true, collectionOwnerId }) => {
|
||||
}> = ({ accountId, withBio = true, withBorder = true, collectionOwnerId }) => {
|
||||
const relationship = useRelationship(accountId);
|
||||
|
||||
if (!accountId) {
|
||||
@ -59,12 +58,17 @@ const AccountItem: React.FC<{
|
||||
(relationship.following || relationship.requested));
|
||||
|
||||
return (
|
||||
<Account
|
||||
minimal={withoutButton}
|
||||
withMenu={false}
|
||||
withBorder={withBorder}
|
||||
id={accountId}
|
||||
/>
|
||||
<div className={classes.accountItemWrapper} data-with-border={withBorder}>
|
||||
<Account
|
||||
minimal
|
||||
id={accountId}
|
||||
withBio={withBio}
|
||||
withBorder={false}
|
||||
withMenu={false}
|
||||
className={classes.accountItem}
|
||||
/>
|
||||
{!withoutButton && <FollowButton accountId={accountId} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -131,19 +135,27 @@ const SensitiveScreen: React.FC<{
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.sensitiveWarning}>
|
||||
<Callout
|
||||
variant='warning'
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='collections.detail.sensitive_content'
|
||||
defaultMessage='Sensitive content'
|
||||
/>
|
||||
}
|
||||
primaryLabel={
|
||||
<FormattedMessage
|
||||
id='content_warning.show_short'
|
||||
defaultMessage='Show'
|
||||
/>
|
||||
}
|
||||
onPrimary={showAnyway}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='collections.detail.sensitive_note'
|
||||
defaultMessage='This collection contains accounts and content that may be sensitive to some users.'
|
||||
tagName='p'
|
||||
defaultMessage='The description and accounts may not be suitable for all viewers.'
|
||||
/>
|
||||
<Button onClick={showAnyway}>
|
||||
<FormattedMessage
|
||||
id='content_warning.show'
|
||||
defaultMessage='Show anyway'
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</Callout>
|
||||
);
|
||||
};
|
||||
|
||||
@ -192,13 +204,14 @@ export const CollectionAccountsList: React.FC<{
|
||||
<ItemList
|
||||
isLoading={isLoading}
|
||||
emptyMessage={intl.formatMessage(messages.empty)}
|
||||
className={classes.itemList}
|
||||
>
|
||||
{collection && currentUserInCollection ? (
|
||||
<>
|
||||
<h3 className={classes.columnSubheading}>
|
||||
<FormattedMessage
|
||||
id='collections.detail.author_added_you'
|
||||
defaultMessage='{author} added you to this collection'
|
||||
id='collections.detail.you_were_added_to_this_collection'
|
||||
defaultMessage='You were added to this collection'
|
||||
values={{
|
||||
author: <SimpleAuthorName id={collection.account_id} />,
|
||||
}}
|
||||
@ -208,9 +221,11 @@ export const CollectionAccountsList: React.FC<{
|
||||
key={currentUserInCollection.account_id}
|
||||
aria-posinset={1}
|
||||
aria-setsize={items.length}
|
||||
className={classes.youWereAddedWrapper}
|
||||
>
|
||||
<AccountItem
|
||||
withBorder={false}
|
||||
withBio={false}
|
||||
accountId={currentUserInCollection.account_id}
|
||||
collectionOwnerId={collection.account_id}
|
||||
/>
|
||||
@ -225,18 +240,30 @@ export const CollectionAccountsList: React.FC<{
|
||||
ref={listHeadingRef}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='collections.detail.other_accounts_in_collection'
|
||||
defaultMessage='Others in this collection:'
|
||||
id='collections.detail.other_accounts_count'
|
||||
defaultMessage='{count, plural, one {# other account} other {# other accounts}}'
|
||||
values={{ count: collection.item_count - 1 }}
|
||||
/>
|
||||
</h3>
|
||||
</>
|
||||
) : (
|
||||
<h3
|
||||
className='column-subheading sr-only'
|
||||
className={classes.columnSubheading}
|
||||
tabIndex={-1}
|
||||
ref={listHeadingRef}
|
||||
>
|
||||
{intl.formatMessage(messages.accounts)}
|
||||
{collection ? (
|
||||
<FormattedMessage
|
||||
id='collections.account_count'
|
||||
defaultMessage='{count, plural, one {# account} other {# accounts}}'
|
||||
values={{ count: collection.item_count }}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='collections.detail.accounts_heading'
|
||||
defaultMessage='Accounts'
|
||||
/>
|
||||
)}
|
||||
</h3>
|
||||
)}
|
||||
{collection && (
|
||||
|
||||
@ -43,7 +43,7 @@
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 8px;
|
||||
color: var(--color-text-primary);
|
||||
fill: var(--color-text-primary);
|
||||
background: var(--color-bg-warning-softest);
|
||||
}
|
||||
|
||||
|
||||
@ -4,24 +4,19 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { useHistory, useLocation, useParams } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { openModal } from '@/flavours/glitch/actions/modal';
|
||||
import { RelativeTimestamp } from '@/flavours/glitch/components/relative_timestamp';
|
||||
import { useAccountHandle } from '@/flavours/glitch/components/display_name/default';
|
||||
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
||||
import ShareIcon from '@/material-icons/400-24px/share.svg?react';
|
||||
import type { ApiCollectionJSON } from 'flavours/glitch/api_types/collections';
|
||||
import { Avatar } from 'flavours/glitch/components/avatar';
|
||||
import { Column } from 'flavours/glitch/components/column';
|
||||
import { ColumnHeader } from 'flavours/glitch/components/column_header';
|
||||
import {
|
||||
DisplayName,
|
||||
LinkedDisplayName,
|
||||
} from 'flavours/glitch/components/display_name';
|
||||
import { IconButton } from 'flavours/glitch/components/icon_button';
|
||||
import { Scrollable } from 'flavours/glitch/components/scrollable_list/components';
|
||||
import { Tag } from 'flavours/glitch/components/tags/tag';
|
||||
import { useAccount } from 'flavours/glitch/hooks/useAccount';
|
||||
import { me } from 'flavours/glitch/initial_state';
|
||||
import { domain } from 'flavours/glitch/initial_state';
|
||||
import { fetchCollection } from 'flavours/glitch/reducers/slices/collections';
|
||||
import { useAppDispatch, useAppSelector } from 'flavours/glitch/store';
|
||||
|
||||
@ -40,88 +35,29 @@ const messages = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
const CollectionMetaData: React.FC<{
|
||||
collection: ApiCollectionJSON;
|
||||
extended?: boolean;
|
||||
}> = ({ collection, extended }) => {
|
||||
return (
|
||||
<ul className={classes.metaList}>
|
||||
<FormattedMessage
|
||||
id='collections.account_count'
|
||||
defaultMessage='{count, plural, one {# account} other {# accounts}}'
|
||||
values={{ count: collection.item_count }}
|
||||
tagName='li'
|
||||
/>
|
||||
{extended && (
|
||||
<>
|
||||
{collection.discoverable ? (
|
||||
<FormattedMessage
|
||||
id='collections.visibility_public'
|
||||
defaultMessage='Public'
|
||||
tagName='li'
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='collections.visibility_unlisted'
|
||||
defaultMessage='Unlisted'
|
||||
tagName='li'
|
||||
/>
|
||||
)}
|
||||
{collection.sensitive && (
|
||||
<FormattedMessage
|
||||
id='collections.sensitive'
|
||||
defaultMessage='Sensitive'
|
||||
tagName='li'
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<FormattedMessage
|
||||
id='collections.last_updated_at'
|
||||
defaultMessage='Last updated: {date}'
|
||||
values={{
|
||||
date: <RelativeTimestamp timestamp={collection.updated_at} long />,
|
||||
}}
|
||||
tagName='li'
|
||||
/>
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
export const AuthorNote: React.FC<{ id: string; previewMode?: boolean }> = ({
|
||||
id,
|
||||
// When previewMode is enabled, your own display name
|
||||
// will not be replaced with "you"
|
||||
previewMode = false,
|
||||
}) => {
|
||||
export const AuthorNote: React.FC<{ id: string }> = ({ id }) => {
|
||||
const account = useAccount(id);
|
||||
const authorHandle = useAccountHandle(account, domain);
|
||||
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const author = (
|
||||
<span className={classes.displayNameWithAvatar}>
|
||||
<Avatar size={18} account={account} />
|
||||
{previewMode ? (
|
||||
<DisplayName account={account} variant='simple' />
|
||||
) : (
|
||||
<LinkedDisplayName displayProps={{ account, variant: 'simple' }} />
|
||||
)}
|
||||
</span>
|
||||
<Link to={`/@${account.acct}`} data-hover-card-account={account.id}>
|
||||
{authorHandle}
|
||||
</Link>
|
||||
);
|
||||
|
||||
const displayAsYou = id === me && !previewMode;
|
||||
|
||||
return (
|
||||
<p className={previewMode ? classes.previewAuthorNote : classes.authorNote}>
|
||||
{displayAsYou ? (
|
||||
<FormattedMessage
|
||||
id='collections.detail.curated_by_you'
|
||||
defaultMessage='Curated by you'
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='collections.detail.curated_by_author'
|
||||
defaultMessage='Curated by {author}'
|
||||
values={{ author }}
|
||||
/>
|
||||
)}
|
||||
<p className={classes.authorNote}>
|
||||
<FormattedMessage
|
||||
id='collections.by_account'
|
||||
defaultMessage='by {account_handle}'
|
||||
values={{
|
||||
account_handle: author,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
);
|
||||
};
|
||||
@ -156,14 +92,12 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({
|
||||
}, [history, handleShare, isNewCollection, location.pathname]);
|
||||
|
||||
return (
|
||||
<div className={classes.header}>
|
||||
<header className={classes.header}>
|
||||
<div className={classes.titleWithMenu}>
|
||||
<div className={classes.titleWrapper}>
|
||||
{tag && (
|
||||
// TODO: Make non-interactive tag component
|
||||
<Tag name={tag.name} className={classes.tag} />
|
||||
)}
|
||||
{tag && <span className={classes.tag}>#{tag.name}</span>}
|
||||
<h2 className={classes.name}>{name}</h2>
|
||||
<AuthorNote id={account_id} />
|
||||
</div>
|
||||
<div className={classes.headerButtonWrapper}>
|
||||
<IconButton
|
||||
@ -181,12 +115,7 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({
|
||||
</div>
|
||||
</div>
|
||||
{description && <p className={classes.description}>{description}</p>}
|
||||
<AuthorNote id={collection.account_id} />
|
||||
<CollectionMetaData
|
||||
extended={account_id === me}
|
||||
collection={collection}
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -97,7 +97,7 @@ export const CollectionShareModal: React.FC<{
|
||||
<div className={classes.preview}>
|
||||
<div>
|
||||
<h2 className={classes.previewHeading}>{collection.name}</h2>
|
||||
<AuthorNote previewMode id={collection.account_id} />
|
||||
<AuthorNote id={collection.account_id} />
|
||||
</div>
|
||||
<AvatarGroup>
|
||||
{collection.items.slice(0, 5).map(({ account_id }) => {
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
.header {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.titleWithMenu {
|
||||
@ -11,23 +10,46 @@
|
||||
|
||||
.titleWrapper {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tag {
|
||||
margin-bottom: 4px;
|
||||
margin-inline-start: -8px;
|
||||
display: inline-block;
|
||||
padding: 4px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-secondary);
|
||||
background: var(--color-bg-secondary);
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 28px;
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.authorNote {
|
||||
font-size: 13px;
|
||||
color: var(--color-text-secondary);
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration-color: rgb(from var(--color-text-secondary) r g b / 50%);
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 15px;
|
||||
margin-top: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.headerButtonWrapper {
|
||||
@ -39,79 +61,41 @@
|
||||
box-sizing: content-box;
|
||||
padding: 5px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--color-border-primary);
|
||||
}
|
||||
|
||||
.authorNote {
|
||||
margin-top: 8px;
|
||||
font-size: 13px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.previewAuthorNote {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.metaList {
|
||||
--gap: 0.75ch;
|
||||
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 16px;
|
||||
gap: var(--gap);
|
||||
font-size: 15px;
|
||||
|
||||
& > li:not(:last-child)::after {
|
||||
content: '·';
|
||||
margin-inline-start: var(--gap);
|
||||
}
|
||||
.itemList {
|
||||
padding-inline: 24px;
|
||||
}
|
||||
|
||||
.columnSubheading {
|
||||
background: var(--color-bg-secondary);
|
||||
padding: 15px 20px;
|
||||
padding-bottom: 12px;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
|
||||
&:focus-visible {
|
||||
outline: var(--outline-focus-default);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
.displayNameWithAvatar {
|
||||
display: inline-flex;
|
||||
gap: 4px;
|
||||
align-items: baseline;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
> :global(.account__avatar) {
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
.sensitiveWarning {
|
||||
.accountItemWrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
max-width: 460px;
|
||||
margin: auto;
|
||||
padding: 60px 30px;
|
||||
gap: 20px;
|
||||
text-align: center;
|
||||
text-wrap: balance;
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
cursor: default;
|
||||
align-items: start;
|
||||
padding-block: 16px;
|
||||
|
||||
&[data-with-border='true'] {
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.accountItem {
|
||||
--account-name-size: 15px;
|
||||
--account-handle-color: var(--color-text-secondary);
|
||||
--account-handle-size: 13px;
|
||||
--account-bio-color: var(--color-text-primary);
|
||||
--account-bio-size: 13px;
|
||||
--account-outer-spacing: 0;
|
||||
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.youWereAddedWrapper {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.revokeControlWrapper {
|
||||
@ -119,9 +103,7 @@
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: -10px;
|
||||
padding-bottom: 16px;
|
||||
padding-inline: calc(26px + var(--avatar-width)) 16px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
:global(.button) {
|
||||
min-width: 30%;
|
||||
|
||||
@ -69,6 +69,7 @@ class Bundle extends PureComponent {
|
||||
this.setState({ mod: mod.default });
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Bundle fetching error:', error);
|
||||
this.setState({ mod: null });
|
||||
});
|
||||
};
|
||||
|
||||
@ -2077,7 +2077,15 @@ body > [data-popper-placement] {
|
||||
}
|
||||
|
||||
.account {
|
||||
padding: 10px; // glitch: reduced padding
|
||||
--account-outer-spacing: 10px; // glitch: reduced padding
|
||||
--account-name-color: var(--color-text-primary);
|
||||
--account-name-size: 14px;
|
||||
--account-handle-color: var(--color-text-secondary);
|
||||
--account-handle-size: 14px;
|
||||
--account-bio-color: var(--color-text-secondary);
|
||||
--account-bio-size: 14px;
|
||||
|
||||
padding: var(--account-outer-spacing);
|
||||
|
||||
// Using :where keeps specificity low, allowing for existing
|
||||
// .account overrides to still apply
|
||||
@ -2090,10 +2098,10 @@ body > [data-popper-placement] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
color: var(--color-text-secondary);
|
||||
color: var(--account-handle-color);
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
font-size: var(--account-handle-size);
|
||||
|
||||
.display-name {
|
||||
margin-bottom: 4px;
|
||||
@ -2101,7 +2109,8 @@ body > [data-popper-placement] {
|
||||
|
||||
.display-name strong {
|
||||
display: inline;
|
||||
color: var(--color-text-primary);
|
||||
font-size: var(--account-name-size);
|
||||
color: var(--account-name-color);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2239,7 +2248,7 @@ body > [data-popper-placement] {
|
||||
}
|
||||
|
||||
&__note {
|
||||
font-size: 14px;
|
||||
font-size: var(--account-bio-size);
|
||||
font-weight: 400;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@ -2248,7 +2257,7 @@ body > [data-popper-placement] {
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
margin-top: 10px;
|
||||
color: var(--color-text-secondary);
|
||||
color: var(--account-bio-color);
|
||||
|
||||
&--missing {
|
||||
color: var(--color-text-tertiary);
|
||||
@ -10742,9 +10751,13 @@ noscript {
|
||||
padding: 0 4px;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
background: var(--color-bg-brand-softest);
|
||||
background: rgb(from var(--color-text-inverted) r g b / 10%);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--color-text-brand-on-inverted);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -18,9 +18,9 @@
|
||||
--color-text-brand-on-inverted: var(--color-indigo-600); // legacy
|
||||
--color-text-error: var(--color-red-300);
|
||||
--color-text-on-error-base: var(--color-white);
|
||||
--color-text-warning: var(--color-yellow-400); // legacy
|
||||
--color-text-warning: var(--color-yellow-400);
|
||||
--color-text-on-warning-base: var(--color-white);
|
||||
--color-text-success: var(--color-green-400); // legacy
|
||||
--color-text-success: var(--color-green-400);
|
||||
--color-text-on-success-base: var(--color-white);
|
||||
--color-text-disabled: var(--color-grey-600); // legacy
|
||||
--color-text-on-disabled: var(--color-grey-400); // legacy
|
||||
|
||||
@ -18,9 +18,9 @@
|
||||
--color-text-brand-on-inverted: var(--color-indigo-400); // legacy
|
||||
--color-text-error: var(--color-red-800);
|
||||
--color-text-on-error-base: var(--color-white);
|
||||
--color-text-warning: var(--color-yellow-600); // legacy
|
||||
--color-text-warning: var(--color-yellow-700);
|
||||
--color-text-on-warning-base: var(--color-white);
|
||||
--color-text-success: var(--color-green-600); // legacy
|
||||
--color-text-success: var(--color-green-700);
|
||||
--color-text-on-success-base: var(--color-white);
|
||||
--color-text-disabled: var(--color-grey-300); // legacy
|
||||
--color-text-on-disabled: var(--color-grey-200); // legacy
|
||||
|
||||
@ -75,6 +75,7 @@ interface AccountProps {
|
||||
withMenu?: boolean;
|
||||
withBorder?: boolean;
|
||||
extraAccountInfo?: React.ReactNode;
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
@ -88,6 +89,7 @@ export const Account: React.FC<AccountProps> = ({
|
||||
withMenu = true,
|
||||
withBorder = true,
|
||||
extraAccountInfo,
|
||||
className,
|
||||
children,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
@ -290,7 +292,7 @@ export const Account: React.FC<AccountProps> = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('account', {
|
||||
className={classNames('account', className, {
|
||||
'account--minimal': minimal,
|
||||
'account--without-border': !withBorder,
|
||||
})}
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
background-color: var(--color-bg-brand-softest);
|
||||
color: var(--color-text-primary);
|
||||
border-radius: 12px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
|
||||
@ -7,15 +7,11 @@ import ErrorIcon from '@/material-icons/400-24px/error.svg?react';
|
||||
import InfoIcon from '@/material-icons/400-24px/info.svg?react';
|
||||
import WarningIcon from '@/material-icons/400-24px/warning.svg?react';
|
||||
|
||||
import type { FieldStatus } from '../form_fields/form_field_wrapper';
|
||||
import { Icon } from '../icon';
|
||||
|
||||
import classes from './styles.module.css';
|
||||
|
||||
export interface FieldStatus {
|
||||
variant: 'error' | 'warning' | 'info' | 'success';
|
||||
message?: string;
|
||||
}
|
||||
|
||||
const iconMap: Record<FieldStatus['variant'], React.FunctionComponent> = {
|
||||
error: ErrorIcon,
|
||||
warning: WarningIcon,
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { length } from 'stringz';
|
||||
|
||||
import { polymorphicForwardRef } from '@/types/polymorphic';
|
||||
|
||||
import classes from './styles.module.scss';
|
||||
@ -14,8 +14,6 @@ interface CharacterCounterProps {
|
||||
recommended?: boolean;
|
||||
}
|
||||
|
||||
const segmenter = new Intl.Segmenter();
|
||||
|
||||
export const CharacterCounter = polymorphicForwardRef<
|
||||
'span',
|
||||
CharacterCounterProps
|
||||
@ -31,10 +29,7 @@ export const CharacterCounter = polymorphicForwardRef<
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const currentLength = useMemo(
|
||||
() => [...segmenter.segment(currentString)].length,
|
||||
[currentString],
|
||||
);
|
||||
const currentLength = length(currentString);
|
||||
return (
|
||||
<Component
|
||||
{...props}
|
||||
|
||||
@ -13,6 +13,7 @@ export const StatusesCounter = (
|
||||
count: pluralReady,
|
||||
counter: <strong>{displayNumber}</strong>,
|
||||
}}
|
||||
tagName='span'
|
||||
/>
|
||||
);
|
||||
|
||||
@ -27,6 +28,7 @@ export const FollowingCounter = (
|
||||
count: pluralReady,
|
||||
counter: <strong>{displayNumber}</strong>,
|
||||
}}
|
||||
tagName='span'
|
||||
/>
|
||||
);
|
||||
|
||||
@ -41,6 +43,7 @@ export const FollowersCounter = (
|
||||
count: pluralReady,
|
||||
counter: <strong>{displayNumber}</strong>,
|
||||
}}
|
||||
tagName='span'
|
||||
/>
|
||||
);
|
||||
|
||||
@ -55,5 +58,6 @@ export const FollowersYouKnowCounter = (
|
||||
count: pluralReady,
|
||||
counter: <strong>{displayNumber}</strong>,
|
||||
}}
|
||||
tagName='span'
|
||||
/>
|
||||
);
|
||||
|
||||
@ -121,6 +121,7 @@ export const EditedTimestamp: React.FC<{
|
||||
/>
|
||||
),
|
||||
}}
|
||||
tagName='span'
|
||||
/>
|
||||
</button>
|
||||
</Dropdown>
|
||||
|
||||
@ -2,11 +2,9 @@ import type {
|
||||
ChangeEvent,
|
||||
ChangeEventHandler,
|
||||
ComponentPropsWithoutRef,
|
||||
Dispatch,
|
||||
FC,
|
||||
ReactNode,
|
||||
RefObject,
|
||||
SetStateAction,
|
||||
} from 'react';
|
||||
import { useCallback, useId, useRef } from 'react';
|
||||
|
||||
@ -25,7 +23,7 @@ import { TextInput } from './text_input_field';
|
||||
|
||||
export type EmojiInputProps = {
|
||||
value?: string;
|
||||
onChange?: Dispatch<SetStateAction<string>>;
|
||||
onChange?: (newValue: string) => void;
|
||||
counterMax?: number;
|
||||
recommended?: boolean;
|
||||
} & Omit<CommonFieldWrapperProps, 'wrapperClassName'>;
|
||||
@ -138,12 +136,15 @@ const EmojiFieldWrapper: FC<
|
||||
|
||||
const handlePickEmoji = useCallback(
|
||||
(emoji: string) => {
|
||||
onChange?.((prev) => {
|
||||
const position = inputRef.current?.selectionStart ?? prev.length;
|
||||
return insertEmojiAtPosition(prev, emoji, position);
|
||||
});
|
||||
if (!value) {
|
||||
onChange?.('');
|
||||
return;
|
||||
}
|
||||
const position = inputRef.current?.selectionStart ?? value.length;
|
||||
const newValue = insertEmojiAtPosition(value, emoji, position);
|
||||
onChange?.(newValue);
|
||||
},
|
||||
[onChange, inputRef],
|
||||
[inputRef, value, onChange],
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
|
||||
@ -4,10 +4,10 @@ import type { ReactNode, FC } from 'react';
|
||||
import { createContext, useId } from 'react';
|
||||
|
||||
import { A11yLiveRegion } from 'mastodon/components/a11y_live_region';
|
||||
import type { FieldStatus } from 'mastodon/components/callout_inline';
|
||||
import { CalloutInline } from 'mastodon/components/callout_inline';
|
||||
|
||||
import classes from './fieldset.module.scss';
|
||||
import type { FieldStatus } from './form_field_wrapper';
|
||||
import { getFieldStatus } from './form_field_wrapper';
|
||||
import formFieldWrapperClasses from './form_field_wrapper.module.scss';
|
||||
|
||||
|
||||
@ -8,7 +8,6 @@ import { FormattedMessage } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { A11yLiveRegion } from 'mastodon/components/a11y_live_region';
|
||||
import type { FieldStatus } from 'mastodon/components/callout_inline';
|
||||
import { CalloutInline } from 'mastodon/components/callout_inline';
|
||||
|
||||
import { FieldsetNameContext } from './fieldset';
|
||||
@ -20,11 +19,16 @@ export interface InputProps {
|
||||
'aria-describedby'?: string;
|
||||
}
|
||||
|
||||
export interface FieldStatus {
|
||||
variant: 'error' | 'warning' | 'info' | 'success';
|
||||
message?: string;
|
||||
}
|
||||
|
||||
interface FieldWrapperProps {
|
||||
label: ReactNode;
|
||||
hint?: ReactNode;
|
||||
required?: boolean;
|
||||
status?: FieldStatus['variant'] | FieldStatus;
|
||||
status?: FieldStatus['variant'] | FieldStatus | null;
|
||||
inputId?: string;
|
||||
describedById?: string;
|
||||
inputPlacement?: 'inline-start' | 'inline-end';
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export type { FieldStatus } from './form_field_wrapper';
|
||||
export { FormFieldWrapper } from './form_field_wrapper';
|
||||
export { FormStack } from './form_stack';
|
||||
export { Fieldset } from './fieldset';
|
||||
|
||||
40
app/javascript/mastodon/components/number_fields/index.tsx
Normal file
40
app/javascript/mastodon/components/number_fields/index.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
||||
import type { MastodonLocationDescriptor } from 'mastodon/components/router';
|
||||
|
||||
import classes from './styles.module.scss';
|
||||
|
||||
interface WrapperProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const NumberFields: React.FC<WrapperProps> = ({ children }) => {
|
||||
return <ul className={classes.list}>{children}</ul>;
|
||||
};
|
||||
|
||||
interface ItemProps {
|
||||
label: React.ReactNode;
|
||||
hint?: string;
|
||||
link?: MastodonLocationDescriptor;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const NumberFieldsItem: React.FC<ItemProps> = ({
|
||||
label,
|
||||
hint,
|
||||
link,
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<li className={classes.item} title={hint}>
|
||||
{label}
|
||||
{link ? (
|
||||
<NavLink exact to={link}>
|
||||
{children}
|
||||
</NavLink>
|
||||
) : (
|
||||
<strong>{children}</strong>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,32 @@
|
||||
.list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 8px 0;
|
||||
padding: 0;
|
||||
gap: 4px 20px;
|
||||
font-size: 13px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.item {
|
||||
@container (width < 420px) {
|
||||
flex: 1 1 0px;
|
||||
}
|
||||
|
||||
a,
|
||||
strong {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
a {
|
||||
padding: 0;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -26,6 +26,8 @@ export type LocationState = MastodonLocationState | null | undefined;
|
||||
|
||||
export type MastodonLocation = ReturnType<typeof useLocation<LocationState>>;
|
||||
|
||||
export type MastodonLocationDescriptor = LocationDescriptor<LocationState>;
|
||||
|
||||
type HistoryPath = Path | LocationDescriptor<LocationState>;
|
||||
|
||||
export const browserHistory = createBrowserHistory<LocationState>();
|
||||
|
||||
@ -241,21 +241,23 @@ export const AccountEdit: FC = () => {
|
||||
showDescription={!hasFields}
|
||||
buttons={
|
||||
<div className={classes.fieldButtons}>
|
||||
<Button
|
||||
className={classes.editButton}
|
||||
onClick={handleCustomFieldReorder}
|
||||
disabled={profile.fields.length <= 1}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='account_edit.custom_fields.reorder_button'
|
||||
defaultMessage='Reorder fields'
|
||||
{profile.fields.length > 1 && (
|
||||
<Button
|
||||
className={classes.editButton}
|
||||
onClick={handleCustomFieldReorder}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='account_edit.custom_fields.reorder_button'
|
||||
defaultMessage='Reorder fields'
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
{profile.fields.length < maxFieldCount && (
|
||||
<EditButton
|
||||
label={intl.formatMessage(messages.customFieldsAddLabel)}
|
||||
onClick={handleCustomFieldAdd}
|
||||
/>
|
||||
</Button>
|
||||
<EditButton
|
||||
label={intl.formatMessage(messages.customFieldsAddLabel)}
|
||||
onClick={handleCustomFieldAdd}
|
||||
disabled={profile.fields.length >= maxFieldCount}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
|
||||
@ -1,11 +1,5 @@
|
||||
import {
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { forwardRef, useCallback, useImperativeHandle, useState } from 'react';
|
||||
import type { FC, FocusEventHandler } from 'react';
|
||||
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
@ -13,7 +7,7 @@ import type { Map as ImmutableMap } from 'immutable';
|
||||
|
||||
import { closeModal } from '@/mastodon/actions/modal';
|
||||
import { Button } from '@/mastodon/components/button';
|
||||
import { Callout } from '@/mastodon/components/callout';
|
||||
import type { FieldStatus } from '@/mastodon/components/form_fields';
|
||||
import { EmojiTextInputField } from '@/mastodon/components/form_fields';
|
||||
import {
|
||||
removeField,
|
||||
@ -71,6 +65,26 @@ const messages = defineMessages({
|
||||
id: 'account_edit.field_edit_modal.discard_confirm',
|
||||
defaultMessage: 'Discard',
|
||||
},
|
||||
errorBlank: {
|
||||
id: 'form_error.blank',
|
||||
defaultMessage: 'Field cannot be blank.',
|
||||
},
|
||||
warningLength: {
|
||||
id: 'account_edit.field_edit_modal.length_warning',
|
||||
defaultMessage:
|
||||
'Recommended character limit exceeded. Mobile users might not see your field in full.',
|
||||
},
|
||||
warningUrlEmoji: {
|
||||
id: 'account_edit.field_edit_modal.link_emoji_warning',
|
||||
defaultMessage:
|
||||
'We recommend against the use of custom emoji in combination with urls. Custom fields containing both will display as text only instead of as a link, in order to prevent user confusion.',
|
||||
},
|
||||
warningUrlProtocol: {
|
||||
id: 'account_edit.field_edit_modal.url_warning',
|
||||
defaultMessage:
|
||||
'To add a link, please include {protocol} at the beginning.',
|
||||
description: '{protocol} is https://',
|
||||
},
|
||||
});
|
||||
|
||||
// We have two different values- the hard limit set by the server,
|
||||
@ -124,34 +138,86 @@ export const EditFieldModal = forwardRef<
|
||||
const { nameLimit, valueLimit } = useAppSelector(selectFieldLimits);
|
||||
const isPending = useAppSelector((state) => state.profileEdit.isPending);
|
||||
|
||||
const disabled =
|
||||
!newLabel.trim() ||
|
||||
!newValue.trim() ||
|
||||
!isDirty ||
|
||||
!nameLimit ||
|
||||
!valueLimit ||
|
||||
newLabel.length > nameLimit ||
|
||||
newValue.length > valueLimit;
|
||||
const [fieldStatuses, setFieldStatuses] = useState<{
|
||||
label?: FieldStatus;
|
||||
value?: FieldStatus;
|
||||
}>({});
|
||||
|
||||
const customEmojiCodes = useAppSelector(selectEmojiCodes);
|
||||
const hasLinkAndEmoji = useMemo(() => {
|
||||
const text = `${newLabel} ${newValue}`; // Combine text, as we're searching it all.
|
||||
const hasLink = /https?:\/\//.test(text);
|
||||
const hasEmoji = customEmojiCodes.some((code) =>
|
||||
text.includes(`:${code}:`),
|
||||
);
|
||||
return hasLink && hasEmoji;
|
||||
}, [customEmojiCodes, newLabel, newValue]);
|
||||
const hasLinkWithoutProtocol = useMemo(
|
||||
() => isUrlWithoutProtocol(newValue),
|
||||
[newValue],
|
||||
const checkField = useCallback(
|
||||
(value: string): FieldStatus | null => {
|
||||
if (!value.trim()) {
|
||||
return {
|
||||
variant: 'error',
|
||||
message: intl.formatMessage(messages.errorBlank),
|
||||
};
|
||||
}
|
||||
|
||||
if (value.length > RECOMMENDED_LIMIT) {
|
||||
return {
|
||||
variant: 'warning',
|
||||
message: intl.formatMessage(messages.warningLength, {
|
||||
max: RECOMMENDED_LIMIT,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
const hasLink = /https?:\/\//.test(value);
|
||||
const hasEmoji = customEmojiCodes.some((code) =>
|
||||
value.includes(`:${code}:`),
|
||||
);
|
||||
if (hasLink && hasEmoji) {
|
||||
return {
|
||||
variant: 'warning',
|
||||
message: intl.formatMessage(messages.warningUrlEmoji),
|
||||
};
|
||||
}
|
||||
|
||||
if (isUrlWithoutProtocol(value)) {
|
||||
return {
|
||||
variant: 'warning',
|
||||
message: intl.formatMessage(messages.warningUrlProtocol, {
|
||||
protocol: 'https://',
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
[customEmojiCodes, intl],
|
||||
);
|
||||
|
||||
const handleBlur: FocusEventHandler<HTMLInputElement> = useCallback(
|
||||
(event) => {
|
||||
const { name, value } = event.target;
|
||||
const result = checkField(value);
|
||||
if (name !== 'label' && name !== 'value') {
|
||||
return;
|
||||
}
|
||||
setFieldStatuses((statuses) => ({
|
||||
...statuses,
|
||||
[name]: result ?? undefined,
|
||||
}));
|
||||
},
|
||||
[checkField],
|
||||
);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const handleSave = useCallback(() => {
|
||||
if (disabled || isPending) {
|
||||
if (isPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
const labelStatus = checkField(newLabel);
|
||||
const valueStatus = checkField(newValue);
|
||||
if (labelStatus || valueStatus) {
|
||||
setFieldStatuses({
|
||||
label: labelStatus ?? undefined,
|
||||
value: valueStatus ?? undefined,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
void dispatch(
|
||||
updateField({ id: fieldKey, name: newLabel, value: newValue }),
|
||||
).then(() => {
|
||||
@ -163,7 +229,7 @@ export const EditFieldModal = forwardRef<
|
||||
}),
|
||||
);
|
||||
});
|
||||
}, [disabled, dispatch, fieldKey, isPending, newLabel, newValue]);
|
||||
}, [checkField, dispatch, fieldKey, isPending, newLabel, newValue]);
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
@ -198,60 +264,33 @@ export const EditFieldModal = forwardRef<
|
||||
confirm={intl.formatMessage(messages.save)}
|
||||
onConfirm={handleSave}
|
||||
updating={isPending}
|
||||
disabled={disabled}
|
||||
className={classes.wrapper}
|
||||
>
|
||||
<EmojiTextInputField
|
||||
name='label'
|
||||
value={newLabel}
|
||||
onChange={setNewLabel}
|
||||
onBlur={handleBlur}
|
||||
label={intl.formatMessage(messages.editLabelField)}
|
||||
hint={intl.formatMessage(messages.editLabelHint)}
|
||||
status={fieldStatuses.label}
|
||||
maxLength={nameLimit}
|
||||
counterMax={RECOMMENDED_LIMIT}
|
||||
recommended
|
||||
/>
|
||||
|
||||
<EmojiTextInputField
|
||||
name='value'
|
||||
value={newValue}
|
||||
onChange={setNewValue}
|
||||
onBlur={handleBlur}
|
||||
label={intl.formatMessage(messages.editValueField)}
|
||||
hint={intl.formatMessage(messages.editValueHint)}
|
||||
status={fieldStatuses.value}
|
||||
maxLength={valueLimit}
|
||||
counterMax={RECOMMENDED_LIMIT}
|
||||
recommended
|
||||
/>
|
||||
|
||||
{hasLinkAndEmoji && (
|
||||
<Callout variant='warning'>
|
||||
<FormattedMessage
|
||||
id='account_edit.field_edit_modal.link_emoji_warning'
|
||||
defaultMessage='We recommend against the use of custom emoji in combination with urls. Custom fields containing both will display as text only instead of as a link, in order to prevent user confusion.'
|
||||
/>
|
||||
</Callout>
|
||||
)}
|
||||
|
||||
{(newLabel.length > RECOMMENDED_LIMIT ||
|
||||
newValue.length > RECOMMENDED_LIMIT) && (
|
||||
<Callout variant='warning'>
|
||||
<FormattedMessage
|
||||
id='account_edit.field_edit_modal.limit_warning'
|
||||
defaultMessage='Recommended character limit exceeded. Mobile users might not see your field in full.'
|
||||
/>
|
||||
</Callout>
|
||||
)}
|
||||
|
||||
{hasLinkWithoutProtocol && (
|
||||
<Callout variant='warning'>
|
||||
<FormattedMessage
|
||||
id='account_edit.field_edit_modal.url_warning'
|
||||
defaultMessage='To add a link, please include {protocol} at the beginning.'
|
||||
description='{protocol} is https://'
|
||||
values={{
|
||||
protocol: <code>https://</code>,
|
||||
}}
|
||||
/>
|
||||
</Callout>
|
||||
)}
|
||||
</ConfirmationModal>
|
||||
);
|
||||
});
|
||||
|
||||
@ -13,7 +13,6 @@
|
||||
|
||||
> img {
|
||||
object-fit: cover;
|
||||
object-position: top center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
@ -26,7 +25,7 @@
|
||||
|
||||
.avatar {
|
||||
margin-top: -64px;
|
||||
margin-left: 18px;
|
||||
margin-left: 22px;
|
||||
position: relative;
|
||||
width: 82px;
|
||||
|
||||
@ -251,7 +250,7 @@
|
||||
// Section component
|
||||
|
||||
.section {
|
||||
padding: 20px;
|
||||
padding: 24px;
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
@ -37,15 +37,25 @@ const selectGalleryTimeline = createAppSelector(
|
||||
(state) => state.statuses,
|
||||
],
|
||||
(accountId, timelines, accounts, statuses) => {
|
||||
let items = emptyList;
|
||||
if (!accountId) {
|
||||
return null;
|
||||
return {
|
||||
items,
|
||||
hasMore: false,
|
||||
isLoading: false,
|
||||
withReplies: false,
|
||||
};
|
||||
}
|
||||
const account = accounts.get(accountId);
|
||||
if (!account) {
|
||||
return null;
|
||||
return {
|
||||
items,
|
||||
hasMore: false,
|
||||
isLoading: false,
|
||||
withReplies: false,
|
||||
};
|
||||
}
|
||||
|
||||
let items = emptyList;
|
||||
const { show_media, show_media_replies } = account;
|
||||
// If the account disabled showing media, don't display anything.
|
||||
if (!show_media && redesignEnabled) {
|
||||
@ -53,13 +63,13 @@ const selectGalleryTimeline = createAppSelector(
|
||||
items,
|
||||
hasMore: false,
|
||||
isLoading: false,
|
||||
showingReplies: false,
|
||||
withReplies: false,
|
||||
};
|
||||
}
|
||||
|
||||
const showingReplies = show_media_replies && redesignEnabled;
|
||||
const withReplies = show_media_replies && redesignEnabled;
|
||||
const timeline = timelines.get(
|
||||
`account:${accountId}:media${showingReplies ? ':with_replies' : ''}`,
|
||||
`account:${accountId}:media${withReplies ? ':with_replies' : ''}`,
|
||||
);
|
||||
const statusIds = timeline?.get('items');
|
||||
|
||||
@ -77,8 +87,8 @@ const selectGalleryTimeline = createAppSelector(
|
||||
return {
|
||||
items,
|
||||
hasMore: !!timeline?.get('hasMore'),
|
||||
isLoading: !!timeline?.get('isLoading'),
|
||||
showingReplies,
|
||||
isLoading: timeline?.get('isLoading') ? true : false,
|
||||
withReplies,
|
||||
};
|
||||
},
|
||||
);
|
||||
@ -89,11 +99,11 @@ export const AccountGallery: React.FC<{
|
||||
const dispatch = useAppDispatch();
|
||||
const accountId = useAccountId();
|
||||
const {
|
||||
isLoading = true,
|
||||
hasMore = false,
|
||||
items: attachments = emptyList,
|
||||
showingReplies: withReplies = false,
|
||||
} = useAppSelector((state) => selectGalleryTimeline(state, accountId)) ?? {};
|
||||
isLoading,
|
||||
items: attachments,
|
||||
hasMore,
|
||||
withReplies,
|
||||
} = useAppSelector((state) => selectGalleryTimeline(state, accountId));
|
||||
|
||||
const { suspended, blockedBy, hidden } = useAccountVisibility(accountId);
|
||||
|
||||
@ -215,7 +225,7 @@ export const AccountGallery: React.FC<{
|
||||
alwaysPrepend
|
||||
append={accountId && <RemoteHint accountId={accountId} />}
|
||||
scrollKey='account_gallery'
|
||||
isLoading={isLoading}
|
||||
showLoading={isLoading}
|
||||
hasMore={!forceEmptyState && hasMore}
|
||||
onLoadMore={handleLoadMore}
|
||||
emptyMessage={emptyMessage}
|
||||
|
||||
@ -69,7 +69,9 @@ export const FamiliarFollowers: React.FC<{ accountId: string }> = ({
|
||||
<Avatar withLink key={account.id} account={account} size={28} />
|
||||
))}
|
||||
</AvatarGroup>
|
||||
<FamiliarFollowersReadout familiarFollowers={familiarFollowers} />
|
||||
<span>
|
||||
<FamiliarFollowersReadout familiarFollowers={familiarFollowers} />
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -3,7 +3,6 @@ import type { FC } from 'react';
|
||||
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
@ -12,13 +11,15 @@ import {
|
||||
StatusesCounter,
|
||||
} from '@/mastodon/components/counters';
|
||||
import { FormattedDateWrapper } from '@/mastodon/components/formatted_date';
|
||||
import {
|
||||
NumberFields,
|
||||
NumberFieldsItem,
|
||||
} from '@/mastodon/components/number_fields';
|
||||
import { ShortNumber } from '@/mastodon/components/short_number';
|
||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||
|
||||
import { isRedesignEnabled } from '../common';
|
||||
|
||||
import classes from './redesign.module.scss';
|
||||
|
||||
const LegacyNumberFields: FC<{ accountId: string }> = ({ accountId }) => {
|
||||
const intl = useIntl();
|
||||
const account = useAccount(accountId);
|
||||
@ -77,56 +78,51 @@ const RedesignNumberFields: FC<{ accountId: string }> = ({ accountId }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<ul
|
||||
className={classNames(
|
||||
'account__header__extra__links',
|
||||
classes.fieldNumbersWrapper,
|
||||
)}
|
||||
>
|
||||
<li>
|
||||
<FormattedMessage id='account.posts' defaultMessage='Posts' />
|
||||
<strong>
|
||||
<ShortNumber value={account.statuses_count} />
|
||||
</strong>
|
||||
</li>
|
||||
<NumberFields>
|
||||
<NumberFieldsItem
|
||||
label={<FormattedMessage id='account.posts' defaultMessage='Posts' />}
|
||||
hint={intl.formatNumber(account.statuses_count)}
|
||||
>
|
||||
<ShortNumber value={account.statuses_count} />
|
||||
</NumberFieldsItem>
|
||||
|
||||
<li>
|
||||
<FormattedMessage id='account.followers' defaultMessage='Followers' />
|
||||
<NavLink
|
||||
exact
|
||||
to={`/@${account.acct}/followers`}
|
||||
title={intl.formatNumber(account.followers_count)}
|
||||
>
|
||||
<ShortNumber value={account.followers_count} />
|
||||
</NavLink>
|
||||
</li>
|
||||
<NumberFieldsItem
|
||||
label={
|
||||
<FormattedMessage id='account.followers' defaultMessage='Followers' />
|
||||
}
|
||||
hint={intl.formatNumber(account.followers_count)}
|
||||
link={`/@${account.acct}/followers`}
|
||||
>
|
||||
<ShortNumber value={account.followers_count} />
|
||||
</NumberFieldsItem>
|
||||
|
||||
<li>
|
||||
<FormattedMessage id='account.following' defaultMessage='Following' />
|
||||
<NavLink
|
||||
exact
|
||||
to={`/@${account.acct}/following`}
|
||||
title={intl.formatNumber(account.following_count)}
|
||||
>
|
||||
<ShortNumber value={account.following_count} />
|
||||
</NavLink>
|
||||
</li>
|
||||
<NumberFieldsItem
|
||||
label={
|
||||
<FormattedMessage id='account.following' defaultMessage='Following' />
|
||||
}
|
||||
hint={intl.formatNumber(account.following_count)}
|
||||
link={`/@${account.acct}/following`}
|
||||
>
|
||||
<ShortNumber value={account.following_count} />
|
||||
</NumberFieldsItem>
|
||||
|
||||
<li>
|
||||
<FormattedMessage id='account.joined_short' defaultMessage='Joined' />
|
||||
<strong>
|
||||
{createdThisYear ? (
|
||||
<FormattedDateWrapper
|
||||
value={account.created_at}
|
||||
month='short'
|
||||
day='2-digit'
|
||||
/>
|
||||
) : (
|
||||
<FormattedDateWrapper value={account.created_at} year='numeric' />
|
||||
)}
|
||||
</strong>
|
||||
</li>
|
||||
</ul>
|
||||
<NumberFieldsItem
|
||||
label={
|
||||
<FormattedMessage id='account.joined_short' defaultMessage='Joined' />
|
||||
}
|
||||
hint={intl.formatDate(account.created_at)}
|
||||
>
|
||||
{createdThisYear ? (
|
||||
<FormattedDateWrapper
|
||||
value={account.created_at}
|
||||
month='short'
|
||||
day='2-digit'
|
||||
/>
|
||||
) : (
|
||||
<FormattedDateWrapper value={account.created_at} year='numeric' />
|
||||
)}
|
||||
</NumberFieldsItem>
|
||||
</NumberFields>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -312,37 +312,6 @@ svg.badgeIcon {
|
||||
}
|
||||
}
|
||||
|
||||
.fieldNumbersWrapper {
|
||||
display: flex;
|
||||
font-size: 13px;
|
||||
padding: 0;
|
||||
margin: 8px 0;
|
||||
gap: 20px;
|
||||
|
||||
li {
|
||||
@container (width < 420px) {
|
||||
flex: 1 1 0px;
|
||||
}
|
||||
}
|
||||
|
||||
a,
|
||||
strong {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
a {
|
||||
padding: 0;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modalCloseButton {
|
||||
padding: 8px;
|
||||
border-radius: 50%;
|
||||
|
||||
@ -38,6 +38,7 @@ export function usePinnedStatusIds({
|
||||
userId: accountId,
|
||||
tagged,
|
||||
pinned: true,
|
||||
replies: true,
|
||||
});
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@ -2,6 +2,8 @@ import { useCallback, useRef, useState } from 'react';
|
||||
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { Callout } from '@/mastodon/components/callout';
|
||||
import { FollowButton } from '@/mastodon/components/follow_button';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import type {
|
||||
ApiCollectionJSON,
|
||||
@ -27,10 +29,6 @@ const messages = defineMessages({
|
||||
id: 'collections.accounts.empty_title',
|
||||
defaultMessage: 'This collection is empty',
|
||||
},
|
||||
accounts: {
|
||||
id: 'collections.detail.accounts_heading',
|
||||
defaultMessage: 'Accounts',
|
||||
},
|
||||
});
|
||||
|
||||
const SimpleAuthorName: React.FC<{ id: string }> = ({ id }) => {
|
||||
@ -41,8 +39,9 @@ const SimpleAuthorName: React.FC<{ id: string }> = ({ id }) => {
|
||||
const AccountItem: React.FC<{
|
||||
accountId: string | undefined;
|
||||
collectionOwnerId: string;
|
||||
withBio?: boolean;
|
||||
withBorder?: boolean;
|
||||
}> = ({ accountId, withBorder = true, collectionOwnerId }) => {
|
||||
}> = ({ accountId, withBio = true, withBorder = true, collectionOwnerId }) => {
|
||||
const relationship = useRelationship(accountId);
|
||||
|
||||
if (!accountId) {
|
||||
@ -59,12 +58,17 @@ const AccountItem: React.FC<{
|
||||
(relationship.following || relationship.requested));
|
||||
|
||||
return (
|
||||
<Account
|
||||
minimal={withoutButton}
|
||||
withMenu={false}
|
||||
withBorder={withBorder}
|
||||
id={accountId}
|
||||
/>
|
||||
<div className={classes.accountItemWrapper} data-with-border={withBorder}>
|
||||
<Account
|
||||
minimal
|
||||
id={accountId}
|
||||
withBio={withBio}
|
||||
withBorder={false}
|
||||
withMenu={false}
|
||||
className={classes.accountItem}
|
||||
/>
|
||||
{!withoutButton && <FollowButton accountId={accountId} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -131,19 +135,27 @@ const SensitiveScreen: React.FC<{
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.sensitiveWarning}>
|
||||
<Callout
|
||||
variant='warning'
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='collections.detail.sensitive_content'
|
||||
defaultMessage='Sensitive content'
|
||||
/>
|
||||
}
|
||||
primaryLabel={
|
||||
<FormattedMessage
|
||||
id='content_warning.show_short'
|
||||
defaultMessage='Show'
|
||||
/>
|
||||
}
|
||||
onPrimary={showAnyway}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='collections.detail.sensitive_note'
|
||||
defaultMessage='This collection contains accounts and content that may be sensitive to some users.'
|
||||
tagName='p'
|
||||
defaultMessage='The description and accounts may not be suitable for all viewers.'
|
||||
/>
|
||||
<Button onClick={showAnyway}>
|
||||
<FormattedMessage
|
||||
id='content_warning.show'
|
||||
defaultMessage='Show anyway'
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</Callout>
|
||||
);
|
||||
};
|
||||
|
||||
@ -192,13 +204,14 @@ export const CollectionAccountsList: React.FC<{
|
||||
<ItemList
|
||||
isLoading={isLoading}
|
||||
emptyMessage={intl.formatMessage(messages.empty)}
|
||||
className={classes.itemList}
|
||||
>
|
||||
{collection && currentUserInCollection ? (
|
||||
<>
|
||||
<h3 className={classes.columnSubheading}>
|
||||
<FormattedMessage
|
||||
id='collections.detail.author_added_you'
|
||||
defaultMessage='{author} added you to this collection'
|
||||
id='collections.detail.you_were_added_to_this_collection'
|
||||
defaultMessage='You were added to this collection'
|
||||
values={{
|
||||
author: <SimpleAuthorName id={collection.account_id} />,
|
||||
}}
|
||||
@ -208,9 +221,11 @@ export const CollectionAccountsList: React.FC<{
|
||||
key={currentUserInCollection.account_id}
|
||||
aria-posinset={1}
|
||||
aria-setsize={items.length}
|
||||
className={classes.youWereAddedWrapper}
|
||||
>
|
||||
<AccountItem
|
||||
withBorder={false}
|
||||
withBio={false}
|
||||
accountId={currentUserInCollection.account_id}
|
||||
collectionOwnerId={collection.account_id}
|
||||
/>
|
||||
@ -225,18 +240,30 @@ export const CollectionAccountsList: React.FC<{
|
||||
ref={listHeadingRef}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='collections.detail.other_accounts_in_collection'
|
||||
defaultMessage='Others in this collection:'
|
||||
id='collections.detail.other_accounts_count'
|
||||
defaultMessage='{count, plural, one {# other account} other {# other accounts}}'
|
||||
values={{ count: collection.item_count - 1 }}
|
||||
/>
|
||||
</h3>
|
||||
</>
|
||||
) : (
|
||||
<h3
|
||||
className='column-subheading sr-only'
|
||||
className={classes.columnSubheading}
|
||||
tabIndex={-1}
|
||||
ref={listHeadingRef}
|
||||
>
|
||||
{intl.formatMessage(messages.accounts)}
|
||||
{collection ? (
|
||||
<FormattedMessage
|
||||
id='collections.account_count'
|
||||
defaultMessage='{count, plural, one {# account} other {# accounts}}'
|
||||
values={{ count: collection.item_count }}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='collections.detail.accounts_heading'
|
||||
defaultMessage='Accounts'
|
||||
/>
|
||||
)}
|
||||
</h3>
|
||||
)}
|
||||
{collection && (
|
||||
|
||||
@ -43,7 +43,7 @@
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 8px;
|
||||
color: var(--color-text-primary);
|
||||
fill: var(--color-text-primary);
|
||||
background: var(--color-bg-warning-softest);
|
||||
}
|
||||
|
||||
|
||||
@ -4,24 +4,19 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { useHistory, useLocation, useParams } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { openModal } from '@/mastodon/actions/modal';
|
||||
import { RelativeTimestamp } from '@/mastodon/components/relative_timestamp';
|
||||
import { useAccountHandle } from '@/mastodon/components/display_name/default';
|
||||
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
||||
import ShareIcon from '@/material-icons/400-24px/share.svg?react';
|
||||
import type { ApiCollectionJSON } from 'mastodon/api_types/collections';
|
||||
import { Avatar } from 'mastodon/components/avatar';
|
||||
import { Column } from 'mastodon/components/column';
|
||||
import { ColumnHeader } from 'mastodon/components/column_header';
|
||||
import {
|
||||
DisplayName,
|
||||
LinkedDisplayName,
|
||||
} from 'mastodon/components/display_name';
|
||||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
import { Scrollable } from 'mastodon/components/scrollable_list/components';
|
||||
import { Tag } from 'mastodon/components/tags/tag';
|
||||
import { useAccount } from 'mastodon/hooks/useAccount';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import { domain } from 'mastodon/initial_state';
|
||||
import { fetchCollection } from 'mastodon/reducers/slices/collections';
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
|
||||
@ -40,88 +35,29 @@ const messages = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
const CollectionMetaData: React.FC<{
|
||||
collection: ApiCollectionJSON;
|
||||
extended?: boolean;
|
||||
}> = ({ collection, extended }) => {
|
||||
return (
|
||||
<ul className={classes.metaList}>
|
||||
<FormattedMessage
|
||||
id='collections.account_count'
|
||||
defaultMessage='{count, plural, one {# account} other {# accounts}}'
|
||||
values={{ count: collection.item_count }}
|
||||
tagName='li'
|
||||
/>
|
||||
{extended && (
|
||||
<>
|
||||
{collection.discoverable ? (
|
||||
<FormattedMessage
|
||||
id='collections.visibility_public'
|
||||
defaultMessage='Public'
|
||||
tagName='li'
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='collections.visibility_unlisted'
|
||||
defaultMessage='Unlisted'
|
||||
tagName='li'
|
||||
/>
|
||||
)}
|
||||
{collection.sensitive && (
|
||||
<FormattedMessage
|
||||
id='collections.sensitive'
|
||||
defaultMessage='Sensitive'
|
||||
tagName='li'
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<FormattedMessage
|
||||
id='collections.last_updated_at'
|
||||
defaultMessage='Last updated: {date}'
|
||||
values={{
|
||||
date: <RelativeTimestamp timestamp={collection.updated_at} long />,
|
||||
}}
|
||||
tagName='li'
|
||||
/>
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
export const AuthorNote: React.FC<{ id: string; previewMode?: boolean }> = ({
|
||||
id,
|
||||
// When previewMode is enabled, your own display name
|
||||
// will not be replaced with "you"
|
||||
previewMode = false,
|
||||
}) => {
|
||||
export const AuthorNote: React.FC<{ id: string }> = ({ id }) => {
|
||||
const account = useAccount(id);
|
||||
const authorHandle = useAccountHandle(account, domain);
|
||||
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const author = (
|
||||
<span className={classes.displayNameWithAvatar}>
|
||||
<Avatar size={18} account={account} />
|
||||
{previewMode ? (
|
||||
<DisplayName account={account} variant='simple' />
|
||||
) : (
|
||||
<LinkedDisplayName displayProps={{ account, variant: 'simple' }} />
|
||||
)}
|
||||
</span>
|
||||
<Link to={`/@${account.acct}`} data-hover-card-account={account.id}>
|
||||
{authorHandle}
|
||||
</Link>
|
||||
);
|
||||
|
||||
const displayAsYou = id === me && !previewMode;
|
||||
|
||||
return (
|
||||
<p className={previewMode ? classes.previewAuthorNote : classes.authorNote}>
|
||||
{displayAsYou ? (
|
||||
<FormattedMessage
|
||||
id='collections.detail.curated_by_you'
|
||||
defaultMessage='Curated by you'
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='collections.detail.curated_by_author'
|
||||
defaultMessage='Curated by {author}'
|
||||
values={{ author }}
|
||||
/>
|
||||
)}
|
||||
<p className={classes.authorNote}>
|
||||
<FormattedMessage
|
||||
id='collections.by_account'
|
||||
defaultMessage='by {account_handle}'
|
||||
values={{
|
||||
account_handle: author,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
);
|
||||
};
|
||||
@ -156,14 +92,12 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({
|
||||
}, [history, handleShare, isNewCollection, location.pathname]);
|
||||
|
||||
return (
|
||||
<div className={classes.header}>
|
||||
<header className={classes.header}>
|
||||
<div className={classes.titleWithMenu}>
|
||||
<div className={classes.titleWrapper}>
|
||||
{tag && (
|
||||
// TODO: Make non-interactive tag component
|
||||
<Tag name={tag.name} className={classes.tag} />
|
||||
)}
|
||||
{tag && <span className={classes.tag}>#{tag.name}</span>}
|
||||
<h2 className={classes.name}>{name}</h2>
|
||||
<AuthorNote id={account_id} />
|
||||
</div>
|
||||
<div className={classes.headerButtonWrapper}>
|
||||
<IconButton
|
||||
@ -181,12 +115,7 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({
|
||||
</div>
|
||||
</div>
|
||||
{description && <p className={classes.description}>{description}</p>}
|
||||
<AuthorNote id={collection.account_id} />
|
||||
<CollectionMetaData
|
||||
extended={account_id === me}
|
||||
collection={collection}
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -97,7 +97,7 @@ export const CollectionShareModal: React.FC<{
|
||||
<div className={classes.preview}>
|
||||
<div>
|
||||
<h2 className={classes.previewHeading}>{collection.name}</h2>
|
||||
<AuthorNote previewMode id={collection.account_id} />
|
||||
<AuthorNote id={collection.account_id} />
|
||||
</div>
|
||||
<AvatarGroup>
|
||||
{collection.items.slice(0, 5).map(({ account_id }) => {
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
.header {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.titleWithMenu {
|
||||
@ -11,23 +10,46 @@
|
||||
|
||||
.titleWrapper {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tag {
|
||||
margin-bottom: 4px;
|
||||
margin-inline-start: -8px;
|
||||
display: inline-block;
|
||||
padding: 4px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-secondary);
|
||||
background: var(--color-bg-secondary);
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 28px;
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.authorNote {
|
||||
font-size: 13px;
|
||||
color: var(--color-text-secondary);
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration-color: rgb(from var(--color-text-secondary) r g b / 50%);
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 15px;
|
||||
margin-top: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.headerButtonWrapper {
|
||||
@ -39,79 +61,41 @@
|
||||
box-sizing: content-box;
|
||||
padding: 5px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--color-border-primary);
|
||||
}
|
||||
|
||||
.authorNote {
|
||||
margin-top: 8px;
|
||||
font-size: 13px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.previewAuthorNote {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.metaList {
|
||||
--gap: 0.75ch;
|
||||
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 16px;
|
||||
gap: var(--gap);
|
||||
font-size: 15px;
|
||||
|
||||
& > li:not(:last-child)::after {
|
||||
content: '·';
|
||||
margin-inline-start: var(--gap);
|
||||
}
|
||||
.itemList {
|
||||
padding-inline: 24px;
|
||||
}
|
||||
|
||||
.columnSubheading {
|
||||
background: var(--color-bg-secondary);
|
||||
padding: 15px 20px;
|
||||
padding-bottom: 12px;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
|
||||
&:focus-visible {
|
||||
outline: var(--outline-focus-default);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
.displayNameWithAvatar {
|
||||
display: inline-flex;
|
||||
gap: 4px;
|
||||
align-items: baseline;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
> :global(.account__avatar) {
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
.sensitiveWarning {
|
||||
.accountItemWrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
max-width: 460px;
|
||||
margin: auto;
|
||||
padding: 60px 30px;
|
||||
gap: 20px;
|
||||
text-align: center;
|
||||
text-wrap: balance;
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
cursor: default;
|
||||
align-items: start;
|
||||
padding-block: 16px;
|
||||
|
||||
&[data-with-border='true'] {
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.accountItem {
|
||||
--account-name-size: 15px;
|
||||
--account-handle-color: var(--color-text-secondary);
|
||||
--account-handle-size: 13px;
|
||||
--account-bio-color: var(--color-text-primary);
|
||||
--account-bio-size: 13px;
|
||||
--account-outer-spacing: 0;
|
||||
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.youWereAddedWrapper {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.revokeControlWrapper {
|
||||
@ -119,9 +103,7 @@
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: -10px;
|
||||
padding-bottom: 16px;
|
||||
padding-inline: calc(26px + var(--avatar-width)) 16px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
:global(.button) {
|
||||
min-width: 30%;
|
||||
|
||||
@ -69,6 +69,7 @@ class Bundle extends PureComponent {
|
||||
this.setState({ mod: mod.default });
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Bundle fetching error:', error);
|
||||
this.setState({ mod: null });
|
||||
});
|
||||
};
|
||||
|
||||
@ -359,6 +359,7 @@
|
||||
"collections.account_count": "{count, plural,one {# уліковы запіс} few {# уліковыя запісы} other {# уліковых запісаў}}",
|
||||
"collections.accounts.empty_description": "Дадайце да {count} уліковых запісаў, на якія Вы падпісаныя",
|
||||
"collections.accounts.empty_title": "Гэтая калекцыя пустая",
|
||||
"collections.by_account": "ад {account_handle}",
|
||||
"collections.collection_description": "Апісанне",
|
||||
"collections.collection_language": "Мова",
|
||||
"collections.collection_language_none": "Няма",
|
||||
|
||||
@ -359,6 +359,7 @@
|
||||
"collections.account_count": "{count, plural, one {# konto} other {# konti}}",
|
||||
"collections.accounts.empty_description": "Tilføj op til {count} konti, du følger",
|
||||
"collections.accounts.empty_title": "Denne samling er tom",
|
||||
"collections.by_account": "af {account_handle}",
|
||||
"collections.collection_description": "Beskrivelse",
|
||||
"collections.collection_language": "Sprog",
|
||||
"collections.collection_language_none": "Intet",
|
||||
|
||||
@ -359,6 +359,7 @@
|
||||
"collections.account_count": "{count, plural, one {# Konto} other {# Konten}}",
|
||||
"collections.accounts.empty_description": "Füge bis zu {count} Konten, denen du folgst, hinzu",
|
||||
"collections.accounts.empty_title": "Diese Sammlung ist leer",
|
||||
"collections.by_account": "von {account_handle}",
|
||||
"collections.collection_description": "Beschreibung",
|
||||
"collections.collection_language": "Sprache",
|
||||
"collections.collection_language_none": "Nicht festgelegt",
|
||||
@ -687,7 +688,7 @@
|
||||
"followers.hide_other_followers": "Dieses Profil möchte die weiteren Follower geheim halten",
|
||||
"followers.title": "Folgt {name}",
|
||||
"following.hide_other_following": "Dieses Profil möchte die gefolgten Profile geheim halten",
|
||||
"following.title": "Gefolgt von {name}",
|
||||
"following.title": "Von {name} gefolgt",
|
||||
"footer.about": "Über",
|
||||
"footer.about_mastodon": "Über Mastodon",
|
||||
"footer.about_server": "Über {domain}",
|
||||
|
||||
@ -359,6 +359,7 @@
|
||||
"collections.account_count": "{count, plural, one {# λογαριασμός} other {# λογαριασμοί}}",
|
||||
"collections.accounts.empty_description": "Προσθέστε μέχρι και {count} λογαριασμούς που ακολουθείτε",
|
||||
"collections.accounts.empty_title": "Αυτή η συλλογή είναι κενή",
|
||||
"collections.by_account": "από {account_handle}",
|
||||
"collections.collection_description": "Περιγραφή",
|
||||
"collections.collection_language": "Γλώσσα",
|
||||
"collections.collection_language_none": "Καμία",
|
||||
|
||||
@ -173,7 +173,7 @@
|
||||
"account_edit.field_edit_modal.discard_confirm": "Discard",
|
||||
"account_edit.field_edit_modal.discard_message": "You have unsaved changes. Are you sure you want to discard them?",
|
||||
"account_edit.field_edit_modal.edit_title": "Edit custom field",
|
||||
"account_edit.field_edit_modal.limit_warning": "Recommended character limit exceeded. Mobile users might not see your field in full.",
|
||||
"account_edit.field_edit_modal.length_warning": "Recommended character limit exceeded. Mobile users might not see your field in full.",
|
||||
"account_edit.field_edit_modal.link_emoji_warning": "We recommend against the use of custom emoji in combination with urls. Custom fields containing both will display as text only instead of as a link, in order to prevent user confusion.",
|
||||
"account_edit.field_edit_modal.name_hint": "E.g. “Personal website”",
|
||||
"account_edit.field_edit_modal.name_label": "Label",
|
||||
@ -378,14 +378,13 @@
|
||||
"collections.description_length_hint": "100 characters limit",
|
||||
"collections.detail.accept_inclusion": "Okay",
|
||||
"collections.detail.accounts_heading": "Accounts",
|
||||
"collections.detail.author_added_you": "{author} added you to this collection",
|
||||
"collections.detail.curated_by_author": "Curated by {author}",
|
||||
"collections.detail.curated_by_you": "Curated by you",
|
||||
"collections.detail.loading": "Loading collection…",
|
||||
"collections.detail.other_accounts_in_collection": "Others in this collection:",
|
||||
"collections.detail.other_accounts_count": "{count, plural, one {# other account} other {# other accounts}}",
|
||||
"collections.detail.revoke_inclusion": "Remove me",
|
||||
"collections.detail.sensitive_content": "Sensitive content",
|
||||
"collections.detail.sensitive_note": "This collection contains accounts and content that may be sensitive to some users.",
|
||||
"collections.detail.share": "Share this collection",
|
||||
"collections.detail.you_were_added_to_this_collection": "You were added to this collection",
|
||||
"collections.edit_details": "Edit details",
|
||||
"collections.error_loading_collections": "There was an error when trying to load your collections.",
|
||||
"collections.hints.accounts_counter": "{count} / {max} accounts",
|
||||
@ -541,6 +540,7 @@
|
||||
"content_warning.hide": "Hide post",
|
||||
"content_warning.show": "Show anyway",
|
||||
"content_warning.show_more": "Show more",
|
||||
"content_warning.show_short": "Show",
|
||||
"conversation.delete": "Delete conversation",
|
||||
"conversation.mark_as_read": "Mark as read",
|
||||
"conversation.open": "View conversation",
|
||||
@ -700,6 +700,7 @@
|
||||
"footer.source_code": "View source code",
|
||||
"footer.status": "Status",
|
||||
"footer.terms_of_service": "Terms of service",
|
||||
"form_error.blank": "Field cannot be blank.",
|
||||
"form_field.optional": "(optional)",
|
||||
"generic.saved": "Saved",
|
||||
"getting_started.heading": "Getting started",
|
||||
|
||||
@ -359,6 +359,7 @@
|
||||
"collections.account_count": "{count, plural, one {# hora} other {# horas}}",
|
||||
"collections.accounts.empty_description": "Agregá hasta {count} cuentas que seguís",
|
||||
"collections.accounts.empty_title": "Esta colección está vacía",
|
||||
"collections.by_account": "por {account_handle}",
|
||||
"collections.collection_description": "Descripción",
|
||||
"collections.collection_language": "Idioma",
|
||||
"collections.collection_language_none": "Ninguno",
|
||||
|
||||
@ -359,6 +359,7 @@
|
||||
"collections.account_count": "{count, plural,one {# cuenta} other {# cuentas}}",
|
||||
"collections.accounts.empty_description": "Añade hasta {count} cuentas que sigues",
|
||||
"collections.accounts.empty_title": "Esta colección está vacía",
|
||||
"collections.by_account": "de {account_handle}",
|
||||
"collections.collection_description": "Descripción",
|
||||
"collections.collection_language": "Idioma",
|
||||
"collections.collection_language_none": "Ninguno",
|
||||
|
||||
@ -171,7 +171,7 @@
|
||||
"account_edit.field_delete_modal.title": "¿Borrar campo personalizado?",
|
||||
"account_edit.field_edit_modal.add_title": "Añadir campo personalizado",
|
||||
"account_edit.field_edit_modal.discard_confirm": "Descartar",
|
||||
"account_edit.field_edit_modal.discard_message": "Tiene cambios no guardados. ¿Seguro que quieres descartarlos?",
|
||||
"account_edit.field_edit_modal.discard_message": "Tienes cambios no guardados. ¿Seguro que quieres descartarlos?",
|
||||
"account_edit.field_edit_modal.edit_title": "Editar campo personalizado",
|
||||
"account_edit.field_edit_modal.limit_warning": "Se ha superado el límite recomendado de caracteres. Es posible que los usuarios móviles no vean el campo completo.",
|
||||
"account_edit.field_edit_modal.link_emoji_warning": "Recomendamos no usar emojis personalizados combinados con enlaces. Los campos personalizados que contengan ambos solo se mostrarán como texto en vez de un enlace, para evitar confusiones.",
|
||||
@ -359,6 +359,7 @@
|
||||
"collections.account_count": "{count, plural, one {# cuenta} other {# cuentas}}",
|
||||
"collections.accounts.empty_description": "Añade hasta {count} cuentas que sigas",
|
||||
"collections.accounts.empty_title": "Esta colección está vacía",
|
||||
"collections.by_account": "de {account_handle}",
|
||||
"collections.collection_description": "Descripción",
|
||||
"collections.collection_language": "Idioma",
|
||||
"collections.collection_language_none": "Ninguno",
|
||||
|
||||
@ -359,6 +359,7 @@
|
||||
"collections.account_count": "{count, plural, one {# conta} other {# contas}}",
|
||||
"collections.accounts.empty_description": "Engade ata {count} contas que segues",
|
||||
"collections.accounts.empty_title": "A colección está baleira",
|
||||
"collections.by_account": "de {account_handle}",
|
||||
"collections.collection_description": "Descrición",
|
||||
"collections.collection_language": "Idioma",
|
||||
"collections.collection_language_none": "Ningún",
|
||||
|
||||
@ -141,6 +141,7 @@
|
||||
"account.unmute": "הפסקת השתקת @{name}",
|
||||
"account.unmute_notifications_short": "הפעלת הודעות",
|
||||
"account.unmute_short": "ביטול השתקה",
|
||||
"account_edit.bio.add_label": "הוסיפו ביוגרפיה",
|
||||
"account_edit.bio.edit_label": "עריכת ביוגרפיה",
|
||||
"account_edit.bio.placeholder": "הוסיפו הצגה קצרה כדי לעזור לאחרים לזהות אותך.",
|
||||
"account_edit.bio.title": "ביוגרפיה",
|
||||
|
||||
@ -141,6 +141,7 @@
|
||||
"account.unmute": "@{name} némításának feloldása",
|
||||
"account.unmute_notifications_short": "Értesítések némításának feloldása",
|
||||
"account.unmute_short": "Némitás feloldása",
|
||||
"account_edit.bio.add_label": "Bemutatkozás hozzáadása",
|
||||
"account_edit.bio.edit_label": "Bemutatkozás szerkesztése",
|
||||
"account_edit.bio.placeholder": "Adj meg egy rövid bemutatkozást, hogy mások könnyebben megtaláljanak.",
|
||||
"account_edit.bio.title": "Bemutatkozás",
|
||||
@ -358,6 +359,7 @@
|
||||
"collections.account_count": "{count, plural, one {# fiók} other {# fiók}}",
|
||||
"collections.accounts.empty_description": "Adj hozzá legfeljebb {count} követett fiókot",
|
||||
"collections.accounts.empty_title": "Ez a gyűjtemény üres",
|
||||
"collections.by_account": "szerző: {account_handle}",
|
||||
"collections.collection_description": "Leírás",
|
||||
"collections.collection_language": "Nyelv",
|
||||
"collections.collection_language_none": "Egyik sem",
|
||||
|
||||
@ -359,6 +359,7 @@
|
||||
"collections.account_count": "{count, plural, one {# aðgangur} other {# aðgangar}}",
|
||||
"collections.accounts.empty_description": "Bættu við allt að {count} aðgöngum sem þú fylgist með",
|
||||
"collections.accounts.empty_title": "Þetta safn er tómt",
|
||||
"collections.by_account": "frá {account_handle}",
|
||||
"collections.collection_description": "Lýsing",
|
||||
"collections.collection_language": "Tungumál",
|
||||
"collections.collection_language_none": "Ekkert",
|
||||
|
||||
@ -359,6 +359,7 @@
|
||||
"collections.account_count": "{count, plural, one {# account} other {# account}}",
|
||||
"collections.accounts.empty_description": "Aggiungi fino a {count} account che segui",
|
||||
"collections.accounts.empty_title": "Questa collezione è vuota",
|
||||
"collections.by_account": "di {account_handle}",
|
||||
"collections.collection_description": "Descrizione",
|
||||
"collections.collection_language": "Lingua",
|
||||
"collections.collection_language_none": "Nessuna",
|
||||
|
||||
@ -73,7 +73,7 @@
|
||||
"account.go_to_profile": "Ga naar profiel",
|
||||
"account.hide_reblogs": "Boosts van @{name} verbergen",
|
||||
"account.in_memoriam": "In memoriam.",
|
||||
"account.joined_short": "Geregistreerd op",
|
||||
"account.joined_short": "Geregistreerd",
|
||||
"account.languages": "Getoonde talen wijzigen",
|
||||
"account.link_verified_on": "Eigendom van deze link is gecontroleerd op {date}",
|
||||
"account.locked_info": "De privacystatus van dit account is ingesteld op vergrendeld. De eigenaar beoordeelt handmatig wie diegene kan volgen.",
|
||||
@ -141,6 +141,7 @@
|
||||
"account.unmute": "@{name} niet langer negeren",
|
||||
"account.unmute_notifications_short": "Meldingen niet langer negeren",
|
||||
"account.unmute_short": "Niet langer negeren",
|
||||
"account_edit.bio.add_label": "Biografie toevoegen",
|
||||
"account_edit.bio.edit_label": "Biografie bewerken",
|
||||
"account_edit.bio.placeholder": "Vertel iets over jezelf, zodat anderen inzicht krijgen in wat voor persoon je bent.",
|
||||
"account_edit.bio.title": "Biografie",
|
||||
@ -170,9 +171,9 @@
|
||||
"account_edit.field_delete_modal.title": "Aangepast veld verwijderen?",
|
||||
"account_edit.field_edit_modal.add_title": "Aangepast veld toevoegen",
|
||||
"account_edit.field_edit_modal.discard_confirm": "Weggooien",
|
||||
"account_edit.field_edit_modal.discard_message": "U heeft niet-opgeslagen wijzigingen. Weet u zeker dat u ze wilt weggooien?",
|
||||
"account_edit.field_edit_modal.discard_message": "Je hebt niet-opgeslagen wijzigingen. Weet je zeker dat je ze wilt weggooien?",
|
||||
"account_edit.field_edit_modal.edit_title": "Aangepast veld bewerken",
|
||||
"account_edit.field_edit_modal.limit_warning": "Aanbevolen tekenlimiet overschreden. Mobiele gebruikers zien mogelijk uw veld niet volledig.",
|
||||
"account_edit.field_edit_modal.limit_warning": "Aanbevolen tekenlimiet overschreden. Mobiele gebruikers zien mogelijk je veld niet volledig.",
|
||||
"account_edit.field_edit_modal.link_emoji_warning": "We raden aan om geen lokale emoji in combinatie met URL's te gebruiken. Aangepaste velden die beide bevatten worden alleen als tekst weergegeven, in plaats van als een link. Dit om verwarring voor de gebruiker te voorkomen.",
|
||||
"account_edit.field_edit_modal.name_hint": "Bijv. \"Persoonlijke website\"",
|
||||
"account_edit.field_edit_modal.name_label": "Label",
|
||||
@ -358,6 +359,7 @@
|
||||
"collections.account_count": "{count, plural, one {# account} other {# accounts}}",
|
||||
"collections.accounts.empty_description": "Tot {count} accounts die je volgt toevoegen",
|
||||
"collections.accounts.empty_title": "Deze verzameling is leeg",
|
||||
"collections.by_account": "door {account_handle}",
|
||||
"collections.collection_description": "Omschrijving",
|
||||
"collections.collection_language": "Taal",
|
||||
"collections.collection_language_none": "Geen",
|
||||
@ -638,7 +640,7 @@
|
||||
"featured_carousel.header": "{count, plural, one {Vastgezet bericht} other {Vastgezette berichten}}",
|
||||
"featured_carousel.slide": "Bericht {current, number} van {max, number}",
|
||||
"featured_tags.more_items": "+{count}",
|
||||
"featured_tags.suggestions": "De laatste tijd heb over {items} gepost. Deze toevoegen als uitgelichte hashtags?",
|
||||
"featured_tags.suggestions": "Je hebt de afgelopen periode over {items} bericht. Deze toevoegen als uitgelichte hashtags?",
|
||||
"featured_tags.suggestions.add": "Toevoegen",
|
||||
"featured_tags.suggestions.added": "Je kunt je uitgelichte hashtags beheren onder <link>Profiel bewerken > Uitgelichte hashtags</link>.",
|
||||
"featured_tags.suggestions.dismiss": "Nee, bedankt",
|
||||
|
||||
@ -141,6 +141,7 @@
|
||||
"account.unmute": "Dessilenciar @{name}",
|
||||
"account.unmute_notifications_short": "Ativar som de notificações",
|
||||
"account.unmute_short": "Desativar silêncio",
|
||||
"account_edit.bio.add_label": "Adicionar biografia",
|
||||
"account_edit.bio.edit_label": "Editar Biografia",
|
||||
"account_edit.bio.placeholder": "Insira uma breve introdução para ajudar os outros a lhe identificar.",
|
||||
"account_edit.bio.title": "Bio",
|
||||
@ -194,6 +195,15 @@
|
||||
"account_edit.image_alt_modal.text_hint": "Texto alternativo ajuda leitores de tela a entender seu conteúdo.",
|
||||
"account_edit.image_alt_modal.text_label": "Texto alternativo",
|
||||
"account_edit.image_delete_modal.confirm": "Tem certeza de que deseja excluir esta imagem? Esta ação não pode ser desfeita.",
|
||||
"account_edit.image_delete_modal.delete_button": "Deletar",
|
||||
"account_edit.image_delete_modal.title": "Deletar imagem?",
|
||||
"account_edit.image_edit.add_button": "Adicionar imagem",
|
||||
"account_edit.image_edit.alt_add_button": "Adicionar texto alternativo",
|
||||
"account_edit.image_edit.alt_edit_button": "Editar texto alternativo",
|
||||
"account_edit.image_edit.remove_button": "Remover imagem",
|
||||
"account_edit.image_edit.replace_button": "Substituir imagem",
|
||||
"account_edit.item_list.delete": "Deletar {name}",
|
||||
"account_edit.item_list.edit": "Editar {name}",
|
||||
"account_edit.name_modal.add_title": "Inserir nome de exibição",
|
||||
"account_edit.name_modal.edit_title": "Editar nome de exibição",
|
||||
"account_edit.profile_tab.button_label": "Personalizar",
|
||||
@ -208,17 +218,33 @@
|
||||
"account_edit.profile_tab.subtitle": "Personalizar as abas em seu perfil e o que elas exibem.",
|
||||
"account_edit.profile_tab.title": "Configurações da aba de perfil",
|
||||
"account_edit.save": "Salvar",
|
||||
"account_edit.upload_modal.back": "Voltar",
|
||||
"account_edit.upload_modal.done": "Concluído",
|
||||
"account_edit.upload_modal.next": "Próximo",
|
||||
"account_edit.upload_modal.step_crop.zoom": "Aproximar",
|
||||
"account_edit.upload_modal.step_upload.button": "Procurar arquivos",
|
||||
"account_edit.upload_modal.step_upload.dragging": "Solte para enviar",
|
||||
"account_edit.upload_modal.step_upload.header": "Escolha uma imagem",
|
||||
"account_edit.upload_modal.step_upload.hint": "WEBP, formatos GIF ou JPG, até {limit}MB.{br} imagem será redimensionada para {width}x{height}px.",
|
||||
"account_edit.upload_modal.title_add.avatar": "Adicionar foto de perfil",
|
||||
"account_edit.upload_modal.title_add.header": "Adicionar foto de capa",
|
||||
"account_edit.upload_modal.title_replace.avatar": "Substituir foto de perfil",
|
||||
"account_edit.upload_modal.title_replace.header": "Substituir foto de capa",
|
||||
"account_edit.verified_modal.details": "Dê credibilidade ao seu perfil do Mastodon, verificando links para sites pessoais. Veja como funciona:",
|
||||
"account_edit.verified_modal.invisible_link.details": "Adicione o link para seu HTML do site. A parte importante é rel=\"eu\" onde previne falsificação de identidade em sites com conteúdo gerado por usuários. Você pode até mesmo usar um link tag no HTML da página ao invés de {tag}, mas o HTML deve ser acessível sem a execução do JavaScript.",
|
||||
"account_edit.verified_modal.invisible_link.summary": "Como posso tornar o link invisível?",
|
||||
"account_edit.verified_modal.step1.header": "Copie o código HTML abaixo e cole no cabeçalho do seu site",
|
||||
"account_edit.verified_modal.step2.details": "Se já adicionou seu site como um campo personalizado, deverá excluí-lo e adicioná-lo novamente para acionar a verificação.",
|
||||
"account_edit.verified_modal.step2.header": "Adicione seu site como um campo personalizado",
|
||||
"account_edit.verified_modal.title": "Como adicionar um link verificado",
|
||||
"account_edit_tags.add_tag": "Adicionar #{tagName}",
|
||||
"account_edit_tags.column_title": "Editar Tags",
|
||||
"account_edit_tags.help_text": "Hashtags em destaque ajudam os usuários a descobrir e interagir com seu perfil. Elas aparecem como filtros na visualização de Atividade da sua página de Perfil.",
|
||||
"account_edit_tags.max_tags_reached": "Você atingiu o número máximo de hashtags em destaque.",
|
||||
"account_edit_tags.search_placeholder": "Insira uma hashtag…",
|
||||
"account_edit_tags.suggestions": "Sugestões:",
|
||||
"account_edit_tags.tag_status_count": "{count, plural, one {# publicação} other {# publicações}}",
|
||||
"account_list.total": "{total, plural,one {#conta}other {#contas}}",
|
||||
"account_note.placeholder": "Nota pessoal sobre este perfil aqui",
|
||||
"admin.dashboard.daily_retention": "Taxa de retenção de usuários por dia, após a inscrição",
|
||||
"admin.dashboard.monthly_retention": "Taxa de retenção de usuários por mês, após a inscrição",
|
||||
@ -333,7 +359,10 @@
|
||||
"collections.account_count": "{count, plural, one {# conta} other {# conta}}",
|
||||
"collections.accounts.empty_description": "Adicionar até {count} contas que você segue",
|
||||
"collections.accounts.empty_title": "Esta coleção está vazia",
|
||||
"collections.by_account": "por {account_handle}",
|
||||
"collections.collection_description": "Descrição",
|
||||
"collections.collection_language": "Língua",
|
||||
"collections.collection_language_none": "Nenhum",
|
||||
"collections.collection_name": "Nome",
|
||||
"collections.collection_topic": "Tópico",
|
||||
"collections.confirm_account_removal": "Tem certeza que deseja remover esta conta da sua coleção?",
|
||||
@ -377,6 +406,7 @@
|
||||
"collections.search_accounts_max_reached": "Você acrescentou o numero máximo de contas",
|
||||
"collections.sensitive": "Sensível",
|
||||
"collections.topic_hint": "Adicione uma hashtag que ajude os outros a entender o tópico principal desta coleção.",
|
||||
"collections.topic_special_chars_hint": "Caracteres especiais serão removidos ao salvar",
|
||||
"collections.view_collection": "Ver coleção",
|
||||
"collections.view_other_collections_by_user": "Ver outras coleções deste usuário",
|
||||
"collections.visibility_public": "Público",
|
||||
@ -610,6 +640,10 @@
|
||||
"featured_carousel.header": "{count, plural, one {Publicação fixada} other {Publicações fixadas}}",
|
||||
"featured_carousel.slide": "Publicação {current, number} de {max, number}",
|
||||
"featured_tags.more_items": "+{count}",
|
||||
"featured_tags.suggestions": "Anteriormente você postou sobre {items}. Adicionar estas como hashtags em destaque?",
|
||||
"featured_tags.suggestions.add": "Adicionar",
|
||||
"featured_tags.suggestions.added": "Gerencie suas hashtags a qualquer momento sob <link>Editar Perfil > Hashtags em destaque</link>.",
|
||||
"featured_tags.suggestions.dismiss": "Não, obrigado",
|
||||
"filter_modal.added.context_mismatch_explanation": "Esta categoria de filtro não se aplica ao contexto no qual você acessou esta publicação. Se quiser que a publicação seja filtrada nesse contexto também, você terá que editar o filtro.",
|
||||
"filter_modal.added.context_mismatch_title": "Incompatibilidade de contexto!",
|
||||
"filter_modal.added.expired_explanation": "Esta categoria de filtro expirou, você precisará alterar a data de expiração para aplicar.",
|
||||
@ -652,7 +686,9 @@
|
||||
"follow_suggestions.who_to_follow": "Quem seguir",
|
||||
"followed_tags": "Hashtags seguidas",
|
||||
"followers.hide_other_followers": "Este usuário escolheu não deixar visíveis seus seguidores",
|
||||
"followers.title": "Seguinte {name}",
|
||||
"following.hide_other_following": "Este usuário escolheu não deixar visíveis aqueles a quem segue",
|
||||
"following.title": "Seguido por {name}",
|
||||
"footer.about": "Sobre",
|
||||
"footer.about_mastodon": "Sobre o mastodon",
|
||||
"footer.about_server": "Sobre {domain}",
|
||||
@ -969,12 +1005,14 @@
|
||||
"notifications_permission_banner.title": "Nunca perca nada",
|
||||
"onboarding.follows.back": "Voltar",
|
||||
"onboarding.follows.empty": "Infelizmente, não é possível mostrar resultados agora. Você pode tentar usar a busca ou navegar na página de exploração para encontrar pessoas para seguir, ou tentar novamente mais tarde.",
|
||||
"onboarding.follows.next": "Próximo: Configure seu perfil",
|
||||
"onboarding.follows.search": "Buscar",
|
||||
"onboarding.follows.title": "Comece seguindo pessoas",
|
||||
"onboarding.profile.discoverable": "Tornar meu perfil descobrível",
|
||||
"onboarding.profile.discoverable_hint": "Quando você aceita a capacidade de descoberta no Mastodon, suas postagens podem aparecer nos resultados de busca e nas tendências, e seu perfil pode ser sugerido a pessoas com interesses similares aos seus.",
|
||||
"onboarding.profile.display_name": "Nome de exibição",
|
||||
"onboarding.profile.display_name_hint": "Seu nome completo ou apelido…",
|
||||
"onboarding.profile.finish": "Finalizar",
|
||||
"onboarding.profile.note": "Biografia",
|
||||
"onboarding.profile.note_hint": "Você pode @mencionar outras pessoas ou usar #hashtags…",
|
||||
"onboarding.profile.title": "Configuração do perfil",
|
||||
@ -1123,6 +1161,7 @@
|
||||
"sign_in_banner.mastodon_is": "O Mastodon é a melhor maneira de acompanhar o que está acontecendo.",
|
||||
"sign_in_banner.sign_in": "Entrar",
|
||||
"sign_in_banner.sso_redirect": "Entrar ou Registrar-se",
|
||||
"skip_links.hotkey": "<span>Tecla de atalho</span>{hotkey}",
|
||||
"skip_links.skip_to_content": "Conteúdo principal",
|
||||
"skip_links.skip_to_navigation": "Navegação principal",
|
||||
"status.admin_account": "Abrir interface de moderação para @{name}",
|
||||
|
||||
@ -354,6 +354,7 @@
|
||||
"collection.share_template_own": "Shihni koleksionin tim të ri: {link}",
|
||||
"collections.account_count": "{count, plural, one {# llogari} other {# llogari}}",
|
||||
"collections.accounts.empty_title": "Ky koleksion është i zbrazët",
|
||||
"collections.by_account": "nga {account_handle}",
|
||||
"collections.collection_description": "Përshkrim",
|
||||
"collections.collection_language": "Gjuhë",
|
||||
"collections.collection_language_none": "Asnjë",
|
||||
|
||||
@ -8,10 +8,14 @@
|
||||
"about.domain_blocks.silenced.title": "Ograničen",
|
||||
"about.domain_blocks.suspended.explanation": "Podaci sa ovog servera neće se obrađivati, čuvati niti razmenjivati, što će onemogućiti bilo kakvu interakciju ili komunikaciju sa korisnicima sa ovog servera.",
|
||||
"about.domain_blocks.suspended.title": "Suspendovan",
|
||||
"about.language_label": "Jezik",
|
||||
"about.not_available": "Ove informacije nisu dostupne na ovom serveru.",
|
||||
"about.powered_by": "Decentralizovana društvena mreža koju pokreće {mastodon}",
|
||||
"about.rules": "Pravila servera",
|
||||
"account.activity": "Aktivnosti",
|
||||
"account.add_or_remove_from_list": "Dodaj ili ukloni sa lista",
|
||||
"account.badges.admin": "Admin",
|
||||
"account.badges.blocked": "Blokiran",
|
||||
"account.badges.bot": "Automatizovano",
|
||||
"account.badges.group": "Grupa",
|
||||
"account.block": "Blokiraj @{name}",
|
||||
@ -23,18 +27,31 @@
|
||||
"account.direct": "Privatno pomeni @{name}",
|
||||
"account.disable_notifications": "Zaustavi obaveštavanje za objave korisnika @{name}",
|
||||
"account.edit_profile": "Uredi profil",
|
||||
"account.edit_profile_short": "Uredi",
|
||||
"account.enable_notifications": "Obavesti me kada @{name} objavi",
|
||||
"account.endorse": "Istakni na profilu",
|
||||
"account.featured.accounts": "Profili",
|
||||
"account.featured_tags.last_status_at": "Poslednja objava {date}",
|
||||
"account.featured_tags.last_status_never": "Nema objava",
|
||||
"account.filters.all": "Sve aktivnosti",
|
||||
"account.filters.posts_only": "Objave",
|
||||
"account.filters.posts_replies": "Objave i odgovori",
|
||||
"account.filters.replies_toggle": "Prikaži odgovore",
|
||||
"account.follow": "Prati",
|
||||
"account.follow_back": "Uzvrati praćenje",
|
||||
"account.follow_back_short": "Uzvrati praćenje",
|
||||
"account.follow_request": "Zahtevaj praćenje",
|
||||
"account.follow_request_cancel": "Poništi zahtev",
|
||||
"account.follow_request_cancel_short": "Poništi",
|
||||
"account.follow_request_short": "Zahtevaj",
|
||||
"account.followers": "Pratioci",
|
||||
"account.followers.empty": "Još uvek niko ne prati ovog korisnika.",
|
||||
"account.followers_counter": "{count, plural, one {{counter} pratilac} few {{counter} pratioca} other {{counter} pratilaca}}",
|
||||
"account.followers_you_know_counter": "{counter} koje ti poznaješ",
|
||||
"account.following": "Prati",
|
||||
"account.following_counter": "{count, plural, one {{counter} prati} few {{counter} prati} other {{counter} prati}}",
|
||||
"account.follows.empty": "Ovaj korisnik još uvek nikog ne prati.",
|
||||
"account.follows_you": "Prati te",
|
||||
"account.go_to_profile": "Idi na profil",
|
||||
"account.hide_reblogs": "Sakrij podržavanja @{name}",
|
||||
"account.in_memoriam": "U znak sećanja na.",
|
||||
@ -44,6 +61,12 @@
|
||||
"account.locked_info": "Status privatnosti ovog naloga je podešen na „zaključano”. Vlasnik ručno pregleda ko ga može pratiti.",
|
||||
"account.media": "Multimedija",
|
||||
"account.mention": "Pomeni korisnika @{name}",
|
||||
"account.menu.add_to_list": "Dodaj u listu",
|
||||
"account.menu.block": "Blokiraj nalog",
|
||||
"account.menu.block_domain": "Blokiraj {domain}",
|
||||
"account.menu.copy": "Kopiraj link",
|
||||
"account.menu.remove_follower": "Ukloni pratioca",
|
||||
"account.menu.report": "Prijavi nalog",
|
||||
"account.moved_to": "Korisnik {name} je naznačio da je njegov novi nalog sada:",
|
||||
"account.mute": "Ignoriši korisnika @{name}",
|
||||
"account.mute_notifications_short": "Isključi obaveštenja",
|
||||
|
||||
@ -272,6 +272,7 @@
|
||||
"closed_registrations_modal.preamble": "Mastodon är decentraliserat så oavsett var du skapar ditt konto kommer du att kunna följa och interagera med någon på denna server. Du kan också köra din egen server!",
|
||||
"closed_registrations_modal.title": "Registrera sig på Mastodon",
|
||||
"collections.accounts.empty_description": "Lägg till upp till {count} konton som du följer",
|
||||
"collections.by_account": "av {account_handle}",
|
||||
"collections.collection_language": "Språk",
|
||||
"collections.collection_language_none": "Inga",
|
||||
"collections.create_a_collection_hint": "Skapa en samling för att rekommendera eller dela dina favoritkonton med andra.",
|
||||
|
||||
@ -79,8 +79,8 @@
|
||||
"account.locked_info": "Đây là tài khoản riêng tư. Chủ tài khoản tự mình xét duyệt các yêu cầu theo dõi.",
|
||||
"account.media": "Phương tiện",
|
||||
"account.mention": "Nhắc đến @{name}",
|
||||
"account.menu.add_to_list": "Thêm vào danh sách…",
|
||||
"account.menu.block": "Chặn người này",
|
||||
"account.menu.add_to_list": "Thêm vào danh sách",
|
||||
"account.menu.block": "Chặn tài khoản",
|
||||
"account.menu.block_domain": "Chặn {domain}",
|
||||
"account.menu.copied": "Đã sao chép liên kết tài khoản vào clipboard",
|
||||
"account.menu.copy": "Sao chép liên kết",
|
||||
@ -143,7 +143,7 @@
|
||||
"account.unmute_short": "Bỏ phớt lờ",
|
||||
"account_edit.bio.add_label": "Thêm giới thiệu",
|
||||
"account_edit.bio.edit_label": "Sửa giới thiệu",
|
||||
"account_edit.bio.placeholder": "Thêm một dòng giới thiệu để giúp mọi người nhận ra bạn.",
|
||||
"account_edit.bio.placeholder": "Giúp mọi người nhận ra bạn là ai.",
|
||||
"account_edit.bio.title": "Giới thiệu",
|
||||
"account_edit.bio_modal.add_title": "Thêm giới thiệu",
|
||||
"account_edit.bio_modal.edit_title": "Sửa giới thiệu",
|
||||
@ -159,7 +159,7 @@
|
||||
"account_edit.custom_fields.verified_hint": "Làm thế nào để thêm liên kết xác minh?",
|
||||
"account_edit.display_name.add_label": "Thêm tên gọi",
|
||||
"account_edit.display_name.edit_label": "Sửa tên gọi",
|
||||
"account_edit.display_name.placeholder": "Tên gọi là tên hiển thị trên hồ sơ của bạn, cũng như bảng tin.",
|
||||
"account_edit.display_name.placeholder": "Tên gọi là tên hiển thị trên hồ sơ của bạn và bảng tin.",
|
||||
"account_edit.display_name.title": "Tên gọi",
|
||||
"account_edit.featured_hashtags.edit_label": "Thêm hashtag",
|
||||
"account_edit.featured_hashtags.placeholder": "Giúp mọi người nhận diện và truy cập nhanh những chủ đề mà bạn thích.",
|
||||
@ -359,6 +359,7 @@
|
||||
"collections.account_count": "{count, plural, other {# tài khoản}}",
|
||||
"collections.accounts.empty_description": "Thêm tối đa {count} tài khoản mà bạn theo dõi",
|
||||
"collections.accounts.empty_title": "Gói khởi đầu này trống",
|
||||
"collections.by_account": "bởi {account_handle}",
|
||||
"collections.collection_description": "Mô tả",
|
||||
"collections.collection_language": "Ngôn ngữ",
|
||||
"collections.collection_language_none": "Không",
|
||||
@ -460,7 +461,7 @@
|
||||
"compose_form.direct_message_warning_learn_more": "Tìm hiểu thêm",
|
||||
"compose_form.encryption_warning": "Các tút trên Mastodon không được mã hóa đầu cuối. Không chia sẻ bất kỳ thông tin nhạy cảm nào qua Mastodon.",
|
||||
"compose_form.hashtag_warning": "Tút này sẽ không xuất hiện công khai. Chỉ những tút công khai mới có thể được tìm kiếm thông qua hashtag.",
|
||||
"compose_form.lock_disclaimer": "Tài khoản của bạn không {locked}. Bất cứ ai cũng có thể theo dõi và xem tút chỉ dành cho người theo dõi bạn.",
|
||||
"compose_form.lock_disclaimer": "Tài khoản của bạn không {locked}. Bất kỳ ai cũng có thể theo dõi và xem tút chỉ dành cho người theo dõi bạn.",
|
||||
"compose_form.lock_disclaimer.lock": "khóa",
|
||||
"compose_form.placeholder": "Bạn đang nghĩ gì?",
|
||||
"compose_form.poll.duration": "Hết hạn sau",
|
||||
@ -604,7 +605,7 @@
|
||||
"empty_column.account_suspended": "Tài khoản vô hiệu hóa",
|
||||
"empty_column.account_timeline": "Chưa có tút nào!",
|
||||
"empty_column.account_unavailable": "Tài khoản bị đình chỉ",
|
||||
"empty_column.blocks": "Bạn chưa chặn bất cứ ai.",
|
||||
"empty_column.blocks": "Bạn chưa chặn ai.",
|
||||
"empty_column.bookmarked_statuses": "Bạn chưa lưu tút nào. Nếu có, nó sẽ hiển thị ở đây.",
|
||||
"empty_column.community": "Máy chủ của bạn chưa có tút nào công khai. Bạn hãy thử viết gì đó đi!",
|
||||
"empty_column.direct": "Bạn chưa có tin nhắn riêng nào. Khi bạn gửi hoặc nhận một tin nhắn riêng, nó sẽ xuất hiện ở đây.",
|
||||
@ -641,7 +642,7 @@
|
||||
"featured_tags.more_items": "+{count}",
|
||||
"featured_tags.suggestions": "Gần đây bạn đã đăng về {items}. Thêm cái này vào hashtag thường dùng?",
|
||||
"featured_tags.suggestions.add": "Thêm",
|
||||
"featured_tags.suggestions.added": "Quản lý hashtag bạn thường dùng vào bất cứ lúc nào tại <link>Sửa hồ sơ > Hashtag thường dùng</link>.",
|
||||
"featured_tags.suggestions.added": "Quản lý hashtag bạn thường dùng vào bất kỳ lúc nào tại <link>Sửa hồ sơ > Hashtag thường dùng</link>.",
|
||||
"featured_tags.suggestions.dismiss": "Không, cảm ơn",
|
||||
"filter_modal.added.context_mismatch_explanation": "Danh mục bộ lọc này không áp dụng cho ngữ cảnh mà bạn đã truy cập tút này. Nếu bạn muốn tút cũng được lọc trong ngữ cảnh này, bạn sẽ phải chỉnh sửa bộ lọc.",
|
||||
"filter_modal.added.context_mismatch_title": "Bối cảnh không phù hợp!",
|
||||
@ -1035,13 +1036,13 @@
|
||||
"privacy.direct.short": "Nhắn riêng",
|
||||
"privacy.private.long": "Chỉ người theo dõi",
|
||||
"privacy.private.short": "Người theo dõi",
|
||||
"privacy.public.long": "Bất cứ ai",
|
||||
"privacy.public.long": "Bất kỳ ai",
|
||||
"privacy.public.short": "Công khai",
|
||||
"privacy.quote.anyone": "{visibility}, cho phép trích dẫn",
|
||||
"privacy.quote.disabled": "{visibility}, tắt trích dẫn",
|
||||
"privacy.quote.limited": "{visibility}, hạn chế trích dẫn",
|
||||
"privacy.unlisted.additional": "Công khai, nhưng tút sẽ không hiện trong bảng tin, hashtag, khám phá hoặc tìm kiếm Mastodon, kể cả trong cài đặt tài khoản bạn chọn cho phép.",
|
||||
"privacy.unlisted.long": "Ẩn khỏi kết quả tìm kiếm, xu hướng và dòng thời gian công khai của Mastodon",
|
||||
"privacy.unlisted.long": "Ẩn khỏi kết quả tìm kiếm, xu hướng và bảng tin công khai",
|
||||
"privacy.unlisted.short": "Hạn chế",
|
||||
"privacy_policy.last_updated": "Cập nhật lần cuối {date}",
|
||||
"privacy_policy.title": "Chính sách bảo mật",
|
||||
@ -1321,6 +1322,6 @@
|
||||
"visibility_modal.quote_followers": "Chỉ người theo dõi",
|
||||
"visibility_modal.quote_label": "Ai có thể trích dẫn",
|
||||
"visibility_modal.quote_nobody": "Chỉ tôi",
|
||||
"visibility_modal.quote_public": "Bất cứ ai",
|
||||
"visibility_modal.quote_public": "Bất kỳ ai",
|
||||
"visibility_modal.save": "Lưu"
|
||||
}
|
||||
|
||||
@ -359,6 +359,7 @@
|
||||
"collections.account_count": "{count, plural, other {# 个账号}}",
|
||||
"collections.accounts.empty_description": "添加你关注的账号,最多 {count} 个",
|
||||
"collections.accounts.empty_title": "收藏列表为空",
|
||||
"collections.by_account": "由 {account_handle}",
|
||||
"collections.collection_description": "说明",
|
||||
"collections.collection_language": "语言",
|
||||
"collections.collection_language_none": "无",
|
||||
|
||||
@ -359,6 +359,7 @@
|
||||
"collections.account_count": "{count, plural, other {# 個帳號}}",
|
||||
"collections.accounts.empty_description": "加入最多 {count} 個您跟隨之帳號",
|
||||
"collections.accounts.empty_title": "此收藏名單是空的",
|
||||
"collections.by_account": "來自 {account_handle}",
|
||||
"collections.collection_description": "說明",
|
||||
"collections.collection_language": "語言",
|
||||
"collections.collection_language_none": "無",
|
||||
|
||||
@ -2011,7 +2011,15 @@ body > [data-popper-placement] {
|
||||
}
|
||||
|
||||
.account {
|
||||
padding: 16px;
|
||||
--account-outer-spacing: 16px;
|
||||
--account-name-color: var(--color-text-primary);
|
||||
--account-name-size: 14px;
|
||||
--account-handle-color: var(--color-text-secondary);
|
||||
--account-handle-size: 14px;
|
||||
--account-bio-color: var(--color-text-secondary);
|
||||
--account-bio-size: 14px;
|
||||
|
||||
padding: var(--account-outer-spacing);
|
||||
|
||||
// Using :where keeps specificity low, allowing for existing
|
||||
// .account overrides to still apply
|
||||
@ -2024,10 +2032,10 @@ body > [data-popper-placement] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
color: var(--color-text-secondary);
|
||||
color: var(--account-handle-color);
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
font-size: var(--account-handle-size);
|
||||
|
||||
.display-name {
|
||||
margin-bottom: 4px;
|
||||
@ -2035,7 +2043,8 @@ body > [data-popper-placement] {
|
||||
|
||||
.display-name strong {
|
||||
display: inline;
|
||||
color: var(--color-text-primary);
|
||||
font-size: var(--account-name-size);
|
||||
color: var(--account-name-color);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2173,7 +2182,7 @@ body > [data-popper-placement] {
|
||||
}
|
||||
|
||||
&__note {
|
||||
font-size: 14px;
|
||||
font-size: var(--account-bio-size);
|
||||
font-weight: 400;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@ -2182,7 +2191,7 @@ body > [data-popper-placement] {
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
margin-top: 10px;
|
||||
color: var(--color-text-secondary);
|
||||
color: var(--account-bio-color);
|
||||
|
||||
&--missing {
|
||||
color: var(--color-text-tertiary);
|
||||
@ -10400,9 +10409,13 @@ noscript {
|
||||
padding: 0 4px;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
background: var(--color-bg-brand-softest);
|
||||
background: rgb(from var(--color-text-inverted) r g b / 10%);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--color-text-brand-on-inverted);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -18,9 +18,9 @@
|
||||
--color-text-brand-on-inverted: var(--color-indigo-600); // legacy
|
||||
--color-text-error: var(--color-red-300);
|
||||
--color-text-on-error-base: var(--color-white);
|
||||
--color-text-warning: var(--color-yellow-400); // legacy
|
||||
--color-text-warning: var(--color-yellow-400);
|
||||
--color-text-on-warning-base: var(--color-white);
|
||||
--color-text-success: var(--color-green-400); // legacy
|
||||
--color-text-success: var(--color-green-400);
|
||||
--color-text-on-success-base: var(--color-white);
|
||||
--color-text-disabled: var(--color-grey-600); // legacy
|
||||
--color-text-on-disabled: var(--color-grey-400); // legacy
|
||||
|
||||
@ -18,9 +18,9 @@
|
||||
--color-text-brand-on-inverted: var(--color-indigo-400); // legacy
|
||||
--color-text-error: var(--color-red-800);
|
||||
--color-text-on-error-base: var(--color-white);
|
||||
--color-text-warning: var(--color-yellow-600); // legacy
|
||||
--color-text-warning: var(--color-yellow-700);
|
||||
--color-text-on-warning-base: var(--color-white);
|
||||
--color-text-success: var(--color-green-600); // legacy
|
||||
--color-text-success: var(--color-green-700);
|
||||
--color-text-on-success-base: var(--color-white);
|
||||
--color-text-disabled: var(--color-grey-300); // legacy
|
||||
--color-text-on-disabled: var(--color-grey-200); // legacy
|
||||
|
||||
@ -32,6 +32,10 @@ class AccountRelationshipSeveranceEvent < ApplicationRecord
|
||||
|
||||
before_create :set_relationships_count!
|
||||
|
||||
def identifier
|
||||
"#{target_name}-#{created_at.to_date.iso8601}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_relationships_count!
|
||||
|
||||
@ -792,6 +792,7 @@ be:
|
||||
categories:
|
||||
administration: Адміністрацыя
|
||||
devops: DevOps
|
||||
email: Эл. пошта
|
||||
invites: Запрашэнні
|
||||
moderation: Мадэрацыя
|
||||
special: Спецыяльны
|
||||
@ -822,6 +823,8 @@ be:
|
||||
manage_blocks_description: Дазваляе блакіраваць пэўных пастаўшчыкоў паслуг электроннай пошты і IP адрасы
|
||||
manage_custom_emojis: Кіраванне адвольнымі эмодзі
|
||||
manage_custom_emojis_description: Дазваляе кіраваць адвольнымі эмодзі на серверы
|
||||
manage_email_subscriptions: Наладзіць падпіскі па электроннай пошце
|
||||
manage_email_subscriptions_description: Дазвольце карыстальнікам падпісвацца на карыстальнікаў з гэтым дазволам праз электронную пошту
|
||||
manage_federation: Кіраваць федэрацыяй
|
||||
manage_federation_description: Дазваляе карыстальнікам блакіраваць або дазваляць аб'яднанне з іншымі даменамі і кантраляваць магчымасць дастаўкі
|
||||
manage_invites: Кіраванне запрашэннямі
|
||||
@ -1460,6 +1463,47 @@ be:
|
||||
basic_information: Асноўная інфармацыя
|
||||
hint_html: "<strong>Наладзьце тое, што людзі будуць бачыць у вашым профілі і побач з вашымі паведамленнямі.</strong> Іншыя людзі з большай верагоднасцю будуць сачыць і ўзаемадзейнічаць з вамі, калі ў вас ёсць запоўнены профіль і фота профілю."
|
||||
other: Іншае
|
||||
redesign_body: Рэдагаванне профілю цяпер даступнае наўпрост са старонкі профілю.
|
||||
redesign_button: Перайсці туды
|
||||
redesign_title: Адбыліся змены ў рэдагаванні профілю
|
||||
email_subscription_mailer:
|
||||
confirmation:
|
||||
action: Пацвердзіць адрас электроннай пошты
|
||||
instructions_to_confirm: Пацвердзіце, што хочаце атрымліваць электронныя лісты ад %{name} (@%{acct}), калі ён (яна) робіць новыя допісы.
|
||||
instructions_to_ignore: Калі Вы няўпэўненыя ў тым, чаму Вы атрымалі гэты ліст, можаце выдаліць яго. Вы не будзеце падпісаныя, калі не клікніце па спасылцы вышэй.
|
||||
subject: Пацвердзіце свой адрас электроннай пошты
|
||||
title: Атрымліваць абнаўленні па электроннай пошце ад %{name}?
|
||||
notification:
|
||||
create_account: Стварыць уліковы запіс Mastodon
|
||||
footer:
|
||||
privacy_html: Электронныя лісты адпраўляюцца з %{domain}, сервера Mastodon. Каб зразумець, як гэты сервер апрацоўвае Вашыя асабістыя даныя, звярніцеся да <a href="%{privacy_policy_path}">Палітыкі прыватнасці</a>.
|
||||
reason_for_email_html: Вы атрымалі гэты ліст, таму што пагадзіліся на абнаўленні праз электронную пошту ад %{name}. Не хочаце атрымліваць гэтыя лісты? <a href="%{unsubscribe_path}">Адпішыцеся</a>
|
||||
interact_with_this_post:
|
||||
few: Узаемадзейнічайце з гэтымі допісамі і знайдзіце іншыя, падобныя да іх.
|
||||
many: Узаемадзейнічайце з гэтымі допісамі і знайдзіце іншыя, падобныя да іх.
|
||||
one: Узаемадзейнічайце з гэтым допісам і знайдзіце іншыя, падобныя да яго.
|
||||
other: Узаемадзейнічайце з гэтымі допісамі і знайдзіце іншыя, падобныя да іх.
|
||||
subject:
|
||||
few: Новыя допісы ад %{name}
|
||||
many: Новыя допісы ад %{name}
|
||||
one: 'Новы допіс: "%{excerpt}"'
|
||||
other: Новыя допісы ад %{name}
|
||||
title:
|
||||
few: Новыя допісы ад %{name}
|
||||
many: Новыя допісы ад %{name}
|
||||
one: 'Новы допіс: "%{excerpt}"'
|
||||
other: Новыя допісы ад %{name}
|
||||
email_subscriptions:
|
||||
active: Актыўная
|
||||
confirmations:
|
||||
show:
|
||||
changed_your_mind: Перадумалі?
|
||||
success_html: Вы цяпер пачняце атрымліваць электронныя лісты, калі %{name} будзе рабіць новыя допісы. Дадайце %{sender} у свае кантакты, каб гэтыя допісы не траплялі ў папку са спамам.
|
||||
title: Вы падпісаліся праз эл. пошту
|
||||
unsubscribe: Адпісацца
|
||||
inactive: Неактыўная
|
||||
status: Стан
|
||||
subscribers: Падпісчыкі па эл.пошце
|
||||
emoji_styles:
|
||||
auto: Аўтаматычны
|
||||
native: Мясцовы
|
||||
@ -1871,6 +1915,8 @@ be:
|
||||
posting_defaults: Публікаваць па змаўчанні
|
||||
public_timelines: Публічныя стужкі
|
||||
privacy:
|
||||
email_subscriptions: Дасылаць допісы праз электронную пошту
|
||||
email_subscriptions_hint_html: Дадайце форму падпіскі праз электронную пошту, якую будуць бачыць карыстальнікі, што не ўвайшлі. Калі наведвальнік увядзе свой адрас электроннай пошты і дасць згоду, Mastodon будзе дасылаць яму электронныя лісты аб абнаўленнях у Вашых публічных допісах.
|
||||
hint_html: "<strong>Наладзьце тое, якім чынам ваш профіль і вашы паведамленні могуць быць знойдзеныя.</strong> Розныя функцыі ў Mastodon могуць дапамагчы вам ахапіць шырэйшую аўдыторыю. Удзяліце час гэтым наладам, каб пераканацца, што яны падыходзяць вам."
|
||||
privacy: Прыватнасць
|
||||
privacy_hint_html: Кантралюйце, колькі інфармацыі вы хочаце раскрыць для карысці іншых. Людзі адкрываюць для сябе цікавыя профілі і класныя праграмы, праглядаючы допісы іншых людзей і даведваючыся, з якіх праграм яны пішуць, але, магчыма, вы аддаеце перавагу трымаць гэта ў таямніцы.
|
||||
@ -2142,6 +2188,28 @@ be:
|
||||
resume_app_authorization: Працягнуць аўтарызацыю
|
||||
role_requirement: "%{domain} патрабуе, каб Вы ўключылі двухфактарную аўтэнтыфікацыю для таго, каб Вы маглі карыстацца Mastodon."
|
||||
webauthn: Ключы бяспекі
|
||||
unsubscriptions:
|
||||
create:
|
||||
action: Перайсці на хатнюю старонку сервера
|
||||
email_subscription:
|
||||
confirmation_html: Вы больш не атрымаеце электронныя лісты ад %{name}.
|
||||
title: Вы адпісаліся
|
||||
user:
|
||||
confirmation_html: Вы больш не атрымаеце %{type} з Mastodon на %{domain}.
|
||||
notification_emails:
|
||||
favourite: апавяшчэнні на пошту пра ўпадабанні
|
||||
follow: апавяшчэнні на пошту пра падпіскі
|
||||
follow_request: апавяшчэнні на пошту пра запыты на падпіску
|
||||
mention: апавяшчэнні на пошту пра згадванні
|
||||
reblog: апавяшчэнні на пошту пра пашырэнні
|
||||
show:
|
||||
action: Адпісацца
|
||||
email_subscription:
|
||||
confirmation_html: Вы перастанеце атрымліваць электронныя лісты, калі гэты ўліковы запіс будзе рабіць новыя допісы.
|
||||
title: Адпісацца ад %{name}?
|
||||
user:
|
||||
confirmation_html: Вы перастанеце атрымліваць %{type} з Mastodon на %{domain}.
|
||||
title: Адпісацца ад %{type}?
|
||||
user_mailer:
|
||||
announcement_published:
|
||||
description: 'Аб''ява ад адміністратараў %{domain}:'
|
||||
|
||||
@ -1421,6 +1421,9 @@ da:
|
||||
basic_information: Oplysninger
|
||||
hint_html: "<strong>Tilpas, hvad folk ser på din offentlige profil og ved siden af dine indlæg.</strong> Andre personer er mere tilbøjelige til at følge dig tilbage og interagere med dig, når du har en udfyldt profil og et profilbillede."
|
||||
other: Andre
|
||||
redesign_body: Profilredigering kan nu tilgås direkte fra profilsiden.
|
||||
redesign_button: Gå dertil
|
||||
redesign_title: Der er en ny måde at redigere sin profil på
|
||||
email_subscription_mailer:
|
||||
confirmation:
|
||||
action: Bekræft e-mailadresse
|
||||
|
||||
@ -1420,6 +1420,9 @@ de:
|
||||
basic_information: Allgemeine Informationen
|
||||
hint_html: "<strong>Bestimme, was andere auf deinem öffentlichen Profil und neben deinen Beiträgen sehen können.</strong> Wenn du ein Profilbild festlegst und dein Profil vervollständigst, werden andere eher mit dir interagieren und dir folgen."
|
||||
other: Andere
|
||||
redesign_body: Das Profil kann jetzt direkt über die Profilseite angepasst werden.
|
||||
redesign_button: Loslegen
|
||||
redesign_title: Es gibt eine brandneue Möglichkeit, das Profil zu bearbeiten
|
||||
email_subscription_mailer:
|
||||
confirmation:
|
||||
action: E-Mail-Adresse bestätigen
|
||||
|
||||
@ -1421,6 +1421,9 @@ el:
|
||||
basic_information: Βασικές πληροφορίες
|
||||
hint_html: "<strong>Προσάρμοσε τί βλέπουν άτομα στο δημόσιο προφίλ σου και δίπλα στις αναρτήσεις σου.</strong> Είναι πιο πιθανό άλλα άτομα να σε ακολουθήσουν πίσω και να αλληλεπιδράσουν μαζί σου αν έχεις ολοκληρωμένο προφίλ και εικόνα προφίλ."
|
||||
other: Άλλο
|
||||
redesign_body: Η επεξεργασία προφίλ μπορεί τώρα να προσεγγιστεί απευθείας από τη σελίδα του προφίλ.
|
||||
redesign_button: Πηγαίνετε εκεί
|
||||
redesign_title: Υπάρχει μια νέα εμπειρία επεξεργασίας προφίλ
|
||||
email_subscription_mailer:
|
||||
confirmation:
|
||||
action: Επιβεβαιώστε τη διεύθυνση email
|
||||
|
||||
@ -1421,6 +1421,9 @@ es-AR:
|
||||
basic_information: Información básica
|
||||
hint_html: "<strong>Personalizá lo que la gente ve en tu perfil público y junto a tus publicaciones.</strong> Es más probable que otras personas te sigan e interactúen con vos cuando tengas un perfil completo y una foto de perfil."
|
||||
other: Otros
|
||||
redesign_body: Ahora podés acceder a la edición del perfil desde la propia página de perfil.
|
||||
redesign_button: Ir allí
|
||||
redesign_title: Hay una nueva experiencia de edición de perfil
|
||||
email_subscription_mailer:
|
||||
confirmation:
|
||||
action: Confirmar dirección de correo electrónico
|
||||
|
||||
@ -1421,6 +1421,9 @@ es-MX:
|
||||
basic_information: Información básica
|
||||
hint_html: "<strong>Personaliza lo que la gente ve en tu perfil público junto a tus publicaciones.</strong> Es más probable que otras personas te sigan e interactúen contigo cuando completes tu perfil y agregues una foto."
|
||||
other: Otro
|
||||
redesign_body: Ahora puede acceder a la edición del perfil desde la propia página del perfil.
|
||||
redesign_button: Llévame allí
|
||||
redesign_title: Hay una nueva experiencia de edición de perfil
|
||||
email_subscription_mailer:
|
||||
confirmation:
|
||||
action: Confirmar dirección de correo electrónico
|
||||
|
||||
@ -1421,6 +1421,9 @@ es:
|
||||
basic_information: Información básica
|
||||
hint_html: "<strong>Personaliza lo que la gente ve en tu perfil público junto a tus publicaciones.</strong> Es más probable que otras personas te sigan e interactúen contigo cuando completas tu perfil y foto."
|
||||
other: Otros
|
||||
redesign_body: Ahora puedes acceder a la edición del perfil desde la propia página de perfil.
|
||||
redesign_button: Llévame allí
|
||||
redesign_title: Hay una nueva experiencia de edición de perfil
|
||||
email_subscription_mailer:
|
||||
confirmation:
|
||||
action: Confirmar dirección de correo electrónico
|
||||
|
||||
@ -807,6 +807,7 @@ ga:
|
||||
categories:
|
||||
administration: Riar
|
||||
devops: DevOps
|
||||
email: Ríomhphost
|
||||
invites: Cuirí
|
||||
moderation: Measarthacht
|
||||
special: Speisialta
|
||||
@ -838,6 +839,8 @@ ga:
|
||||
manage_blocks_description: Ligeann sé d'úsáideoirí bac a chur ar sholáthraithe ríomhphoist agus seoltaí IP
|
||||
manage_custom_emojis: Bainistigh Emojis Saincheaptha
|
||||
manage_custom_emojis_description: Ligeann sé d'úsáideoirí emojis saincheaptha a bhainistiú ar an bhfreastalaí
|
||||
manage_email_subscriptions: Bainistigh Síntiúis Ríomhphoist
|
||||
manage_email_subscriptions_description: Ceadaigh d’úsáideoirí liostáil le húsáideoirí leis an gcead seo trí ríomhphost
|
||||
manage_federation: Cónaidhm a bhainistiú
|
||||
manage_federation_description: Ligeann sé d’úsáideoirí cónaidhm a bhlocáil nó a cheadú le fearainn eile, agus inseachadacht a rialú
|
||||
manage_invites: Bainistigh Cuirí
|
||||
@ -1483,6 +1486,50 @@ ga:
|
||||
basic_information: Eolas bunúsach
|
||||
hint_html: "<strong>Saincheap a bhfeiceann daoine ar do phróifíl phoiblí agus in aice le do phostálacha.</strong> Is dóichí go leanfaidh daoine eile ar ais tú agus go n-idirghníomhóidh siad leat nuair a bhíonn próifíl líonta agus pictiúr próifíle agat."
|
||||
other: Eile
|
||||
redesign_body: Is féidir rochtain a fháil ar eagarthóireacht próifíle go díreach ón leathanach próifíle anois.
|
||||
redesign_button: Téigh ann
|
||||
redesign_title: Tá taithí nua eagarthóireachta próifíle ann
|
||||
email_subscription_mailer:
|
||||
confirmation:
|
||||
action: Deimhnigh an seoladh ríomhphoist
|
||||
instructions_to_confirm: Deimhnigh gur mhaith leat ríomhphoist a fháil ó %{name} (@%{acct}) nuair a fhoilsíonn siad poist nua.
|
||||
instructions_to_ignore: Mura bhfuil tú cinnte cén fáth ar fuair tú an ríomhphost seo, is féidir leat é a scriosadh. Ní bheidh tú síntiúsáilte mura gcliceálann tú ar an nasc thuas.
|
||||
subject: Deimhnigh do sheoladh ríomhphoist
|
||||
title: Nuashonruithe ríomhphoist a fháil ó %{name}?
|
||||
notification:
|
||||
create_account: Cruthaigh cuntas Mastodon
|
||||
footer:
|
||||
privacy_html: Seoltar ríomhphoist ó %{domain}, freastalaí atá faoi thiomáint ag Mastodon. Chun tuiscint a fháil ar an gcaoi a bpróiseálann an freastalaí seo do shonraí pearsanta, féach ar an <a href="%{privacy_policy_path}">Polasaí Príobháideachais</a>.
|
||||
reason_for_email_html: Tá an ríomhphost seo á fháil agat mar gur roghnaigh tú nuashonruithe ríomhphoist a fháil ó %{name}. Nach mian leat na ríomhphoist seo a fháil? <a href="%{unsubscribe_path}">Díliostáil</a>
|
||||
interact_with_this_post:
|
||||
few: Idirghníomhaigh leis na poist seo agus faigh amach tuilleadh.
|
||||
many: Idirghníomhaigh leis na poist seo agus faigh amach tuilleadh.
|
||||
one: Idirghníomhaigh leis an bpost seo agus faigh amach níos mó cosúil leis.
|
||||
other: Idirghníomhaigh leis na poist seo agus faigh amach tuilleadh.
|
||||
two: Idirghníomhaigh leis na poist seo agus faigh amach tuilleadh.
|
||||
subject:
|
||||
few: Poist nua ó %{name}
|
||||
many: Poist nua ó %{name}
|
||||
one: 'Post nua: "%{excerpt}"'
|
||||
other: Poist nua ó %{name}
|
||||
two: Poist nua ó %{name}
|
||||
title:
|
||||
few: Poist nua ó %{name}
|
||||
many: Poist nua ó %{name}
|
||||
one: 'Post nua: "%{excerpt}"'
|
||||
other: Poist nua ó %{name}
|
||||
two: Poist nua ó %{name}
|
||||
email_subscriptions:
|
||||
active: Gníomhach
|
||||
confirmations:
|
||||
show:
|
||||
changed_your_mind: Ar athraigh tú d’intinn?
|
||||
success_html: Tosóidh tú ag fáil ríomhphoist anois nuair a fhoilseoidh %{name} poist nua. Cuir %{sender} le do theagmhálaithe ionas nach gcríochnóidh na poist seo i do fhillteán Turscair.
|
||||
title: Tá tú cláraithe
|
||||
unsubscribe: Díliostáil
|
||||
inactive: Neamhghníomhach
|
||||
status: Stádas
|
||||
subscribers: Síntiúsóirí
|
||||
emoji_styles:
|
||||
auto: Uath
|
||||
native: Dúchasach
|
||||
@ -1914,6 +1961,8 @@ ga:
|
||||
posting_defaults: Réamhshocruithe á bpostáil
|
||||
public_timelines: Amlínte poiblí
|
||||
privacy:
|
||||
email_subscriptions: Seol poist trí ríomhphost
|
||||
email_subscriptions_hint_html: Cuir foirm chlárúcháin ríomhphoist le do phróifíl a thaispeánfar d’úsáideoirí atá logáilte amach. Nuair a chuireann cuairteoirí a seoladh ríomhphoist isteach agus a roghnaíonn siad isteach, seolfaidh Mastodon nuashonruithe ríomhphoist chugat le haghaidh do phoist phoiblí.
|
||||
hint_html: "<strong>Saincheap conas is mian leat do phróifíl agus do phostálacha a fháil.</strong> Is féidir le gnéithe éagsúla i Mastodon cabhrú leat teacht ar lucht féachana níos leithne nuair atá tú cumasaithe. Tóg nóiméad chun athbhreithniú a dhéanamh ar na socruithe seo chun a chinntiú go n-oireann siad do do chás úsáide."
|
||||
privacy: Príobháideacht
|
||||
privacy_hint_html: Rialú ar an méid is mian leat a nochtadh ar mhaithe le daoine eile. Aimsíonn daoine próifílí suimiúla agus aipeanna fionnuara trí na haipeanna seo a leanas a bhrabhsáil agus a fheiceáil cé na haipeanna a bpostálann siad, ach b’fhéidir gurbh fhearr leat é a choinneáil faoi cheilt.
|
||||
@ -2189,6 +2238,28 @@ ga:
|
||||
resume_app_authorization: Údarú iarratais atosú
|
||||
role_requirement: Éilíonn %{domain} ort Fíordheimhniú Dhá Fhachtóir a shocrú sula bhféadfaidh tú Mastodon a úsáid.
|
||||
webauthn: Eochracha slándála
|
||||
unsubscriptions:
|
||||
create:
|
||||
action: Téigh go dtí leathanach baile an fhreastalaí
|
||||
email_subscription:
|
||||
confirmation_html: Ní bhfaighidh tú ríomhphoist ó %{name} a thuilleadh.
|
||||
title: Tá tú díliostáilte
|
||||
user:
|
||||
confirmation_html: Ní bhfaighidh tú %{type} ó Mastodon ar %{domain} a thuilleadh.
|
||||
notification_emails:
|
||||
favourite: ríomhphoist fógra is fearr leat
|
||||
follow: lean ríomhphoist fógra
|
||||
follow_request: ríomhphoist iarratais leantach
|
||||
mention: ríomhphoist fógraí tráchta
|
||||
reblog: ríomhphoist fógraí a threisiú
|
||||
show:
|
||||
action: Díliostáil
|
||||
email_subscription:
|
||||
confirmation_html: Ní bheidh ríomhphoist á bhfáil agat a thuilleadh nuair a fhoilseoidh an cuntas seo poist nua.
|
||||
title: Díliostáil ó %{name}?
|
||||
user:
|
||||
confirmation_html: Scoirfidh tú de %{type} a fháil ó Mastodon ar %{domain}.
|
||||
title: Díliostáil ó %{type}?
|
||||
user_mailer:
|
||||
announcement_published:
|
||||
description: 'Tá riarthóirí %{domain} ag déanamh fógra:'
|
||||
|
||||
@ -791,6 +791,8 @@ gl:
|
||||
manage_blocks_description: Permite que as usuarias bloqueen provedoras de correo e enderezos IP
|
||||
manage_custom_emojis: Xestionar Emojis personalizados
|
||||
manage_custom_emojis_description: Permite xestionar os emojis personalizados do servidor
|
||||
manage_email_subscriptions: Xestionar subscripcións por correo
|
||||
manage_email_subscriptions_description: Permitir que as usuarias se subscriban por correo a outras usuarias con este permiso
|
||||
manage_federation: Xestionar a federación
|
||||
manage_federation_description: Permite bloquear ou permitir a federación con outros dominios, e controlar as entregas
|
||||
manage_invites: Xestionar Convites
|
||||
@ -1419,6 +1421,41 @@ gl:
|
||||
basic_information: Información básica
|
||||
hint_html: "<strong>Personaliza o que van ver no teu perfil público e ao lado das túas publicacións.</strong> As outras persoas estarán máis animadas a seguirte e interactuar contigo se engades algún dato sobre ti así como unha imaxe de perfil."
|
||||
other: Outros
|
||||
redesign_body: Agora podes acceder á edición do perfil directamente desde a páxina do perfil.
|
||||
redesign_button: Ir á edición
|
||||
redesign_title: Hai novidades no xeito en que podes editar o perfil
|
||||
email_subscription_mailer:
|
||||
confirmation:
|
||||
action: Confirmar enderezo de correo
|
||||
instructions_to_confirm: Confirma que queres recibir correos de %{name} (@%{acct}) cando publique novas publicacións.
|
||||
instructions_to_ignore: Se non sabes por que recibiches este correo, podes eliminalo. Non te vas subscribir se non premes na ligazón de arriba.
|
||||
subject: Confirma o teu enderezo de correo
|
||||
title: Queres obter actualizacións de %{name} por correo?
|
||||
notification:
|
||||
create_account: Crear unha conta Mastodon
|
||||
footer:
|
||||
privacy_html: Os correos envíanse desde %{domain}, un servidor de Mastodon. Para comprender como este servidor procesa os teus datos persoais le a <a href="%{privacy_policy_path}">Directiva de Privacidade</a>.
|
||||
reason_for_email_html: Recibes este correo porque te subscribiches para recibir actualizacións por correo de %{name}. Non queres recibir estes correos? <a href="%{unsubscribe_path}">Retira a subscrición</a>
|
||||
interact_with_this_post:
|
||||
one: Interactúa con esta publicación e descubre máis coma ela.
|
||||
other: Interactúa con estas publicacións e descubre máis coma elas.
|
||||
subject:
|
||||
one: 'Nova publicación: «%{excerpt}»'
|
||||
other: Novas publicacións desde %{name}
|
||||
title:
|
||||
one: 'Nova publicación: «%{excerpt}»'
|
||||
other: Novas publicacións desde %{name}
|
||||
email_subscriptions:
|
||||
active: Activa
|
||||
confirmations:
|
||||
show:
|
||||
changed_your_mind: Cambiaches de idea?
|
||||
success_html: Vas comezar a recibir correos cando %{name} publique novas publicacións. Engade %{sender} á túa libreta de enderezos para que os correos non vaian directamente ao cartafol de Spam.
|
||||
title: Subscribícheste
|
||||
unsubscribe: Anular subscrición
|
||||
inactive: Inactiva
|
||||
status: Estado
|
||||
subscribers: Subscritoras
|
||||
emoji_styles:
|
||||
auto: Auto
|
||||
native: Nativo
|
||||
@ -1790,6 +1827,8 @@ gl:
|
||||
posting_defaults: Valores por defecto
|
||||
public_timelines: Cronoloxías públicas
|
||||
privacy:
|
||||
email_subscriptions: Enviar publicacións por coreo
|
||||
email_subscriptions_hint_html: Engade un formulario de subscrición por correo para mostrar ás usuarias sen sesión iniciada. Ao escribir o seu enderezo de correo e así indicalo, Mastodon vaille enviar actualizacións por correo das túas publicacións públicas.
|
||||
hint_html: "<strong>Personaliza o xeito no que queres que se atope o teu perfil e publicacións.</strong> Mastodon ten variedade de ferramentas para axudarche a acadar unha audiencia maior. Dedica un minuto a revisalas e confirma que se axustan ao teu caso persoal."
|
||||
privacy: Privacidade
|
||||
privacy_hint_html: Controla canto queres mostrar ás demais persoas. As usuarias descubren perfís interesantes e apps estupendas mirando a quen seguen outras persoas e vendo as apps desde as que publican, pero ti poderías querer non mostralas.
|
||||
@ -2053,6 +2092,28 @@ gl:
|
||||
resume_app_authorization: Retomar autorización da aplicación
|
||||
role_requirement: "%{domain} require que configures un Segundo Factor de Autenticación para poder usar Mastodon"
|
||||
webauthn: Chaves de seguridade
|
||||
unsubscriptions:
|
||||
create:
|
||||
action: Ir á páxina de inicio do servidor
|
||||
email_subscription:
|
||||
confirmation_html: Xa non vas seguir recibindo correos desde %{name}.
|
||||
title: Retiraches a subscrición
|
||||
user:
|
||||
confirmation_html: Non vas recibir %{type} de Mastodon en %{domain}.
|
||||
notification_emails:
|
||||
favourite: correos de notificación de favorecementos
|
||||
follow: correos de notificación de seguimentos
|
||||
follow_request: correos de solicitudes de seguimento
|
||||
mention: correos de notificación de mencións
|
||||
reblog: correos de notificación de promocións
|
||||
show:
|
||||
action: Anular subscrición
|
||||
email_subscription:
|
||||
confirmation_html: Vas deixar de recibir correos cando esta conta publique novas publicacións.
|
||||
title: Dar de baixa a subscrición a %{name}?
|
||||
user:
|
||||
confirmation_html: Vas deixar de recibir %{type} de Mastodon en %{domain}.
|
||||
title: Dar de baixa a subscrición a %{type}?
|
||||
user_mailer:
|
||||
announcement_published:
|
||||
description: A administración de %{domain} publicou un anuncio
|
||||
|
||||
@ -762,6 +762,7 @@ hu:
|
||||
categories:
|
||||
administration: Adminisztráció
|
||||
devops: DevOps
|
||||
email: E-mail-cím
|
||||
invites: Meghívások
|
||||
moderation: Moderáció
|
||||
special: Speciális
|
||||
@ -790,6 +791,8 @@ hu:
|
||||
manage_blocks_description: Lehetővé teszi, hogy a felhasználók e-mail-szolgáltatókat és IP-címeket tiltsanak le
|
||||
manage_custom_emojis: Egyedi emodzsik kezelése
|
||||
manage_custom_emojis_description: Lehetővé teszi a felhasználó számára, hogy a kiszolgáló egyéni emodzsiait kezelje
|
||||
manage_email_subscriptions: E-mail-feliratkozások kezelése
|
||||
manage_email_subscriptions_description: Engedélyezés, hogy a felhasználók e-maillel feliratkozzanak az ilyen jogosultságú felhasználókra
|
||||
manage_federation: Föderáció kezelése
|
||||
manage_federation_description: Lehetővé teszi a felhasználó számára, hogy más domainnekkel való föderációt engedélyezzen vagy letiltson, illetve szabályozza a kézbesítést
|
||||
manage_invites: Meghívások kezelése
|
||||
@ -1418,6 +1421,30 @@ hu:
|
||||
basic_information: Általános információk
|
||||
hint_html: "<strong>Tedd egyedivé, mi látnak mások a profilodon és a bejegyzéseid mellett.</strong> Mások nagyobb eséllyel követnek vissza és lépnek veled kapcsolatba, ha van kitöltött profilod és profilképed."
|
||||
other: Egyéb
|
||||
redesign_body: A profil szerkesztése most már közvetlenül elérhető a profiloldalon.
|
||||
redesign_button: Ugrás oda
|
||||
redesign_title: Az új profilszerkesztési élmény
|
||||
email_subscription_mailer:
|
||||
confirmation:
|
||||
action: E-mail-cím megerősítése
|
||||
subject: E-mail-cím megerősítése
|
||||
notification:
|
||||
subject:
|
||||
one: 'Új bejegyzés: „%{excerpt}”'
|
||||
other: 'Új bejegyzések tőle: %{name}'
|
||||
title:
|
||||
one: 'Új bejegyzés: „%{excerpt}”'
|
||||
other: 'Új bejegyzések tőle: %{name}'
|
||||
email_subscriptions:
|
||||
active: Aktív
|
||||
confirmations:
|
||||
show:
|
||||
changed_your_mind: Meggondoltad magad?
|
||||
title: Feliratkoztál
|
||||
unsubscribe: Leiratkozás
|
||||
inactive: Inaktív
|
||||
status: Állapot
|
||||
subscribers: Feliratkozók
|
||||
emoji_styles:
|
||||
auto: Automatikus
|
||||
native: Natív
|
||||
@ -1789,6 +1816,7 @@ hu:
|
||||
posting_defaults: Bejegyzések alapértelmezései
|
||||
public_timelines: Nyilvános idővonalak
|
||||
privacy:
|
||||
email_subscriptions: Bejegyzések küldése e-mailben
|
||||
hint_html: "<strong>Testreszabható a profil és a bejegyzések megjelenése.</strong> A Mastodon számos funkciója segíthet szélesebb közönség elérésében, ha engedélyezve van. Szánj egy percet a beállítások áttekintésére, hogy megbizonyosodj arról, hogy ezek megfelelnek a te felhasználási esetednek."
|
||||
privacy: Adatvédelem
|
||||
privacy_hint_html: Szabályozd, hogy mások számára miket szeretnél nyilvánosságra hozni. Az emberek érdekes profilokat és nagyszerű alkalmazásokat fedezhetnek fel, amikor böngésznek mások követései között és látják, hogy mely alkalmazásokból tesznek közzé bejegyzéseket, de az is lehet, hogy inkább elrejtenéd ezeket az infókat.
|
||||
@ -2052,6 +2080,14 @@ hu:
|
||||
resume_app_authorization: Alkalmazás jogosultság-ellenőrzésének folytatása
|
||||
role_requirement: A(z) %{domain} megköveteli a kétlépcsős hitelesítés beállítása a Mastodon használata előtt.
|
||||
webauthn: Biztonsági kulcsok
|
||||
unsubscriptions:
|
||||
create:
|
||||
action: Ugrás a kiszolgáló kezdőlapjára
|
||||
email_subscription:
|
||||
confirmation_html: 'Már nem fogsz leveleket kapni róla: %{name}.'
|
||||
title: Leiratkoztál
|
||||
show:
|
||||
action: Leiratkozás
|
||||
user_mailer:
|
||||
announcement_published:
|
||||
description: 'A(z) %{domain} adminisztrátorai a következő bejelentést teszik:'
|
||||
|
||||
@ -1425,14 +1425,21 @@ is:
|
||||
basic_information: Grunnupplýsingar
|
||||
hint_html: "<strong>Sérsníddu hvað fólk sér á opinbera notandasniðinu þínu og næst færslunum þínum.</strong> Annað fólk er líklegra til að fylgjast með þér og eiga í samskiptum við þig ef þú fyllir út notandasniðið og setur auðkennismynd."
|
||||
other: Annað
|
||||
redesign_body: Núna er hægt að breyta notandasíðunni sinni beint á þeirri síðu.
|
||||
redesign_button: Fara þangað
|
||||
redesign_title: Núna er ný aðferð við að breyta notandasíðunni sinni
|
||||
email_subscription_mailer:
|
||||
confirmation:
|
||||
action: Staðfestu tölvupóstfangið
|
||||
instructions_to_confirm: Staðfestu að þú viljir fá tölvupósta frá %{name} (@%{acct}) þegar viðkomandi birtir nýjar færslur.
|
||||
instructions_to_ignore: Ef þú ert ekki viss af hverju þú fékkst þennan tölvupóst, þá geturðu eytt honum. Þú verður ekki áskrifandi nema ef þú smellir á tengilinn hér fyrir ofan.
|
||||
subject: Staðfestu tölvupóstfangið þitt
|
||||
title: Fá tilkynningar í tölvupósti frá %{name}?
|
||||
notification:
|
||||
create_account: Búa til nýjan Mastodon-aðgang
|
||||
footer:
|
||||
privacy_html: Tölvupóstar eru sendir frá %{domain}, netþjóni sem keyrir Mastodon. Til að sjá hvernig þessi netþjónn vinnur með persónuleg gögn þín, Þá ættirðu að skoða <a href="%{privacy_policy_path}">Meðferð persónuupplýsinga</a>.
|
||||
reason_for_email_html: Þú færð þennan tölvupóst vegna þess að þú skráðir þig til að fá pósta með færslum frá %{name}. Viltu ekki fá þessar póstsendingar? <a href="%{unsubscribe_path}">Hættu í áskrift</a>
|
||||
interact_with_this_post:
|
||||
one: Eigðu í samskiptum við þessa færslu og finndu fleiri í sama dúr.
|
||||
other: Eigðu í samskiptum við þessar færslur og finndu fleiri í sama dúr.
|
||||
@ -1447,6 +1454,7 @@ is:
|
||||
confirmations:
|
||||
show:
|
||||
changed_your_mind: Skiptirðu um skoðun?
|
||||
success_html: Þú munt núna fara að fá tölvupósta þegar %{name} birtir nýjar færslur. Bættu %{sender} í tengiliðina þína svo þessir póstar lendi ekki í ruslpóstmöppunni þinni.
|
||||
title: Þú hefur skráð þig
|
||||
unsubscribe: Hætta í áskrift
|
||||
inactive: Óvirkur
|
||||
@ -1465,7 +1473,7 @@ is:
|
||||
'422':
|
||||
content: Öryggisprófun mistókst. Ertu að loka á vefkökur/fótspor?
|
||||
title: Öryggisprófun mistókst
|
||||
'429': Í hægagangi
|
||||
'429': Of margar beiðnir
|
||||
'500':
|
||||
content: Því miður, en eitthvað fór úrskeiðis á okkar enda.
|
||||
title: Þessi síða er ekki rétt
|
||||
@ -1824,6 +1832,7 @@ is:
|
||||
public_timelines: Opinberar tímalínur
|
||||
privacy:
|
||||
email_subscriptions: Senda færslur með tölvupósti
|
||||
email_subscriptions_hint_html: Bættu áskriftarformi fyrir tölvupóst á notandasíðuna þína sem birtist fyrir notendur sem ekki eru skráðir inn. Þegar gestir setja inn tölvupóstfangið sitt og skrá sig í áskrift, mun Mastodon senda út tilkynningar um opinberar færslur þínar.
|
||||
hint_html: "<strong>Sérsníddu hvernig þú vilt að finna megi notandasnið þitt og færslur.</strong> Ýmsir eiginleikar í Mastodon geta hjálpað þér að ná til breiðari áheyrendahóps, séu þeir virkjaðir. Taktu þér tíma til að yfirfara þessar stillingar svo að þær henti þér."
|
||||
privacy: Gagnaleynd
|
||||
privacy_hint_html: Stýrðu því hve miklar upplýsingar þú birtir sem gætu gagnast öðrum. Fólk uppgötvar áhugaverða notendur og sniðug forrit með því að skoða hvað annað fólk fylgist með og hvaða forrit það notar til að birta færslur, en hinsvegar er þér frjálst að halda þessu leyndu.
|
||||
|
||||
@ -791,6 +791,8 @@ it:
|
||||
manage_blocks_description: Consente agli utenti di bloccare i provider di posta elettronica e gli indirizzi IP
|
||||
manage_custom_emojis: Gestisci emoji personalizzate
|
||||
manage_custom_emojis_description: Consente agli utenti di gestire emoji personalizzate sul server
|
||||
manage_email_subscriptions: Gestisci le iscrizioni via email
|
||||
manage_email_subscriptions_description: Consenti agli utenti di iscriversi tramite email agli utenti, con questa autorizzazione
|
||||
manage_federation: Gestisci Federazione
|
||||
manage_federation_description: Consente agli utenti di bloccare o consentire la federazione con altri domini e controllare la consegnabilità
|
||||
manage_invites: Gestisci Inviti
|
||||
@ -1419,6 +1421,21 @@ it:
|
||||
basic_information: Informazioni di base
|
||||
hint_html: "<strong>Personalizza ciò che le persone vedono sul tuo profilo pubblico e accanto ai tuoi post.</strong> È più probabile che altre persone ti seguano e interagiscano con te quando hai un profilo compilato e un'immagine del profilo."
|
||||
other: Altro
|
||||
redesign_body: Ora è possibile modificare il profilo direttamente dalla pagina del profilo stesso.
|
||||
redesign_button: Vai lì
|
||||
redesign_title: È disponibile una nuova esperienza di modifica del profilo
|
||||
email_subscription_mailer:
|
||||
confirmation:
|
||||
action: Conferma l'indirizzo email
|
||||
instructions_to_confirm: Conferma di voler ricevere email da %{name} (@%{acct}) quando pubblicano nuovi post.
|
||||
instructions_to_ignore: Se non sai perché hai ricevuto questa email, puoi cancellarla. L'iscrizione non sarà confermata se non clicchi sul link qui sopra.
|
||||
subject: Conferma il tuo indirizzo email
|
||||
title: Ricevere aggiornamenti via email da %{name}?
|
||||
notification:
|
||||
create_account: Crea un account Mastodon
|
||||
footer:
|
||||
privacy_html: Le email vengono inviate da %{domain}, un server gestito da Mastodon. Per comprendere come questo server elabora i tuoi dati personali, consulta l'<a href="%{privacy_policy_path}">Informativa sulla privacy</a>.
|
||||
reason_for_email_html: Ricevi questa email perché hai scelto di ottenere aggiornamenti via email da %{name}. Non vuoi più ricevere queste email? <a href="%{unsubscribe_path}">Disiscriviti</a>
|
||||
emoji_styles:
|
||||
auto: Automatico
|
||||
native: Nativo
|
||||
|
||||
@ -762,6 +762,7 @@ nl:
|
||||
categories:
|
||||
administration: Beheer
|
||||
devops: DevOps
|
||||
email: E-mail
|
||||
invites: Uitnodigingen
|
||||
moderation: Moderatie
|
||||
special: Speciaal
|
||||
@ -790,6 +791,8 @@ nl:
|
||||
manage_blocks_description: Staat gebruikers toe om e-mailproviders en IP-adressen te blokkeren
|
||||
manage_custom_emojis: Lokale emoji's beheren
|
||||
manage_custom_emojis_description: Staat gebruikers toe om lokale emoji's op de server te beheren
|
||||
manage_email_subscriptions: E-mailabonnementen beheren
|
||||
manage_email_subscriptions_description: Sta gebruikers toe om zich per e-mail te abonneren op gebruikers met deze rechten
|
||||
manage_federation: Federatie beheren
|
||||
manage_federation_description: Staat gebruikers toe om federatie met andere domeinen te blokkeren of toe te staan en om de bezorging te bepalen
|
||||
manage_invites: Uitnodigingen beheren
|
||||
@ -1418,6 +1421,34 @@ nl:
|
||||
basic_information: Algemene informatie
|
||||
hint_html: "<strong>Wat mensen op jouw openbare profiel en naast je berichten zien aanpassen.</strong> Andere mensen gaan je waarschijnlijk eerder volgen en hebben vaker interactie met je, wanneer je profiel is ingevuld en je een profielfoto hebt."
|
||||
other: Overige
|
||||
redesign_body: Het bewerken van je profiel is nu toegankelijk vanaf de profielpagina.
|
||||
redesign_button: Ga erheen
|
||||
redesign_title: Er is een nieuwe profielbewerkingservaring
|
||||
email_subscription_mailer:
|
||||
confirmation:
|
||||
action: E-mailadres bevestigen
|
||||
instructions_to_confirm: Bevestig dat je e-mails van %{name} (@%{acct}) wilt ontvangen wanneer deze gebruiker nieuwe berichten publiceert.
|
||||
instructions_to_ignore: Wanneer je niet zeker weet waarom je deze e-mail hebt ontvangen, kun je deze verwijderen. Je bent niet geabonneerd wanneer je niet op de bovenstaande link klikt.
|
||||
subject: Je e-mailadres bevestigen
|
||||
title: E-mailupdates van %{name} ontvangen?
|
||||
notification:
|
||||
create_account: Een Mastodon-account registreren
|
||||
subject:
|
||||
one: 'Nieuw bericht: "%{excerpt}"'
|
||||
other: Nieuwe berichten van %{name}
|
||||
title:
|
||||
one: 'Nieuw bericht: "%{excerpt}"'
|
||||
other: Nieuwe berichten van %{name}
|
||||
email_subscriptions:
|
||||
active: Actief
|
||||
confirmations:
|
||||
show:
|
||||
changed_your_mind: Van mening veranderd?
|
||||
title: Je bent ingeschreven
|
||||
unsubscribe: Uitschrijven
|
||||
inactive: Inactief
|
||||
status: Status
|
||||
subscribers: Abonnees
|
||||
emoji_styles:
|
||||
auto: Auto
|
||||
native: Systeemeigen
|
||||
@ -1789,6 +1820,7 @@ nl:
|
||||
posting_defaults: Jouw nieuwe berichten
|
||||
public_timelines: Openbare tijdlijnen
|
||||
privacy:
|
||||
email_subscriptions: Berichten per e-mail verzenden
|
||||
hint_html: "<strong>Hoe wil je dat jouw profiel en berichten kunnen worden gevonden?</strong> Een verscheidenheid aan functies in Mastodon kunnen je helpen om een groter publiek te bereiken als ze zijn ingeschakeld. Neem rustig de tijd om deze instellingen te bekijken, om er zo zeker van te zijn dat ze aan jouw wensen voldoen."
|
||||
privacy: Privacy
|
||||
privacy_hint_html: Hoeveel informatie wil je aan andere gebruikers kwijt? Mensen ontdekken interessante accounts en coole apps door te bekijken welke accounts jij volgt en door te bekijken welke app jij gebruikt voor het plaatsen van berichten. Het kan achter zo zijn dat je dit liever verborgen houdt.
|
||||
@ -2052,6 +2084,17 @@ nl:
|
||||
resume_app_authorization: Applicatie-machtiging hervatten
|
||||
role_requirement: "%{domain} vereist dat je Tweestapsverificatie instelt voordat je Mastodon kunt gebruiken."
|
||||
webauthn: Beveiligingssleutels
|
||||
unsubscriptions:
|
||||
create:
|
||||
title: Je bent uitgeschreven
|
||||
notification_emails:
|
||||
favourite: e-mailmeldingen favorieten
|
||||
follow: e-mailmeldingen volgers
|
||||
follow_request: e-mailmeldingen volgverzoeken
|
||||
mention: e-mailmeldingen vermeldingen
|
||||
reblog: e-mailmeldingen boosts
|
||||
show:
|
||||
action: Uitschrijven
|
||||
user_mailer:
|
||||
announcement_published:
|
||||
description: 'De beheerders van %{domain} doen een mededeling:'
|
||||
|
||||
@ -791,7 +791,7 @@ pt-BR:
|
||||
manage_blocks_description: Permite aos usuários bloquear provedores de e-mail e endereços IP
|
||||
manage_custom_emojis: Gerenciar Emojis Personalizados
|
||||
manage_custom_emojis_description: Permite aos usuários gerenciar emojis personalizados no servidor
|
||||
manage_email_subscriptions: Gerenciar assinaturas do correio eletrônico
|
||||
manage_email_subscriptions: Gerenciar assinaturas do e-mail
|
||||
manage_email_subscriptions_description: Permitir que usuários se inscrevam com essa permissão por e-mail
|
||||
manage_federation: Gerenciar Federação
|
||||
manage_federation_description: Permite aos usuários bloquear ou permitir federação com outros domínios e controlar a entregabilidade
|
||||
@ -1421,6 +1421,9 @@ pt-BR:
|
||||
basic_information: Informações básicas
|
||||
hint_html: "<strong>Personalize o que as pessoas veem no seu perfil público e ao lado de suas publicações.</strong> É mais provável que outras pessoas o sigam de volta e interajam com você quando você tiver um perfil preenchido e uma foto de perfil."
|
||||
other: Outro
|
||||
redesign_body: A edição de perfil pode ser acessada diretamente a partir da página de perfil.
|
||||
redesign_button: Ir para lá
|
||||
redesign_title: Há uma nova experiência de edição de perfil
|
||||
email_subscription_mailer:
|
||||
confirmation:
|
||||
action: Confirmar endereço de e-mail
|
||||
@ -2101,6 +2104,17 @@ pt-BR:
|
||||
notification_emails:
|
||||
favourite: e-mails de notificações de favoritos
|
||||
follow: receber e-mails de notificação
|
||||
follow_request: seguir e-mails solicitados
|
||||
mention: e-mails de notificação
|
||||
reblog: otimizar e-mails de notificação
|
||||
show:
|
||||
action: Cancelar inscrição
|
||||
email_subscription:
|
||||
confirmation_html: Você deixará de receber e-mails quando esta conta publicar novas postagens.
|
||||
title: Cancelar inscrição de %{name}?
|
||||
user:
|
||||
confirmation_html: Você deixará de receber %{type} do Mastodon no %{domain}.
|
||||
title: Cancelar inscrição de %{type}?
|
||||
user_mailer:
|
||||
announcement_published:
|
||||
description: 'Os administradores do %{domain} estão fazendo um anúncio:'
|
||||
|
||||
@ -134,6 +134,7 @@ be:
|
||||
otp: 'Увядзіце код двухфактарнай аўтэнтыфікацыі з вашага тэлефона або адзін з кодаў аднаўлення:'
|
||||
webauthn: Калі гэта USB прылада, устаўце яе і, калі неабходна, націсніце на яе.
|
||||
settings:
|
||||
email_subscriptions: Адключэнне захавае бягучых падпісчыкаў, але спыніць адпраўку электронных лістоў.
|
||||
indexable: Старонка вашага профілю можа з'явіцца ў выніках пошуку ў Google, Bing і іншых.
|
||||
show_application: Вы ў любым выпадку зможаце ўбачыць, якая праграма апублікавала ваш допіс.
|
||||
tag:
|
||||
@ -358,6 +359,7 @@ be:
|
||||
hint: Дадатковая інфармацыя
|
||||
text: Правіла
|
||||
settings:
|
||||
email_subscriptions: Уключыць падпіскі праз электронную пошту
|
||||
indexable: Індэксаваць профіль у пошукавых сістэмах
|
||||
show_application: Паказваць з якой праграмы было адпраўлена паведамленне
|
||||
tag:
|
||||
|
||||
@ -134,6 +134,7 @@ ga:
|
||||
otp: 'Cuir isteach an cód dhá fhachtóir ginte ag d''aip ghutháin nó úsáid ceann de do chóid athshlánaithe:'
|
||||
webauthn: Más eochair USB atá ann déan cinnte é a chur isteach agus, más gá, tapáil í.
|
||||
settings:
|
||||
email_subscriptions: Coinnítear síntiúsóirí reatha ach cuirtear stop le ríomhphoist a sheoladh má dhíchumasaítear iad.
|
||||
indexable: Seans go mbeidh do leathanach próifíle le feiceáil i dtorthaí cuardaigh ar Google, Bing agus eile.
|
||||
show_application: Beidh tú in ann a fheiceáil i gcónaí cén aip a d’fhoilsigh do phostáil beag beann ar.
|
||||
tag:
|
||||
@ -359,6 +360,7 @@ ga:
|
||||
hint: Eolas breise
|
||||
text: Riail
|
||||
settings:
|
||||
email_subscriptions: Cumasaigh clárúcháin ríomhphoist
|
||||
indexable: Cuir leathanach próifíle san innill chuardaigh
|
||||
show_application: Taispeáin cén aip ónar sheol tú postáil
|
||||
tag:
|
||||
|
||||
@ -134,6 +134,7 @@ gl:
|
||||
otp: 'Escribe o código do segundo factor creado pola aplicación do teu móbil ou usa un dos códigos de recuperación:'
|
||||
webauthn: Se é unha chave USB asegúrate de que está conectada e preme o botón.
|
||||
settings:
|
||||
email_subscriptions: A desactivación mantén as subscricións actuais pero deixa de enviar correos.
|
||||
indexable: A túa páxina de perfil podería aparecer nos resultados de busca de Google, Bing e outros.
|
||||
show_application: Independentemente, ti sempre poderás ver a app coa que publicaches a túa publicación.
|
||||
tag:
|
||||
@ -356,6 +357,7 @@ gl:
|
||||
hint: Información adicional
|
||||
text: Regra
|
||||
settings:
|
||||
email_subscriptions: Activar crear contas por correo
|
||||
indexable: Incluír páxina de perfil nos motores de busca
|
||||
show_application: Mostrar a app coa que enviaches unha publicación
|
||||
tag:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user