Merge commit 'c48634cf5f3c5b50fcf6ea075121fa2d88e07c0e' into glitch-soc/merge-upstream
This commit is contained in:
commit
13499168f8
19
CHANGELOG.md
19
CHANGELOG.md
@ -2,6 +2,25 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [4.5.6] - 2026-02-03
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Fix ActivityPub collection caching logic for pinned posts and featured tags not checking blocked accounts ([GHSA-ccpr-m53r-mfwr](https://github.com/mastodon/mastodon/security/advisories/GHSA-ccpr-m53r-mfwr))
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Shorten caching of quote posts pending approval (#37570 and #37592 by @ClearlyClaire)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix relationship cache not being cleared when handling account migrations (#37664 by @ClearlyClaire)
|
||||||
|
- Fix quote cancel button not appearing after edit then delete-and-redraft (#37066 by @PGrayCS)
|
||||||
|
- Fix followers with profile subscription (bell icon) being notified of post edits (#37646 by @ClearlyClaire)
|
||||||
|
- Fix error when encountering invalid tag in updated object (#37635 by @ClearlyClaire)
|
||||||
|
- Fix cross-server conversation tracking (#37559 by @ClearlyClaire)
|
||||||
|
- Fix recycled connections not being immediately closed (#37335 and #37674 by @ClearlyClaire and @shleeable)
|
||||||
|
|
||||||
## [4.5.5] - 2026-01-20
|
## [4.5.5] - 2026-01-20
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|||||||
@ -6,17 +6,31 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
|
|||||||
vary_by -> { 'Signature' if authorized_fetch_mode? }
|
vary_by -> { 'Signature' if authorized_fetch_mode? }
|
||||||
|
|
||||||
before_action :require_account_signature!, if: :authorized_fetch_mode?
|
before_action :require_account_signature!, if: :authorized_fetch_mode?
|
||||||
|
before_action :check_authorization
|
||||||
before_action :set_items
|
before_action :set_items
|
||||||
before_action :set_size
|
before_action :set_size
|
||||||
before_action :set_type
|
before_action :set_type
|
||||||
|
|
||||||
def show
|
def show
|
||||||
expires_in 3.minutes, public: public_fetch_mode?
|
expires_in 3.minutes, public: public_fetch_mode?
|
||||||
render_with_cache json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter
|
|
||||||
|
if @unauthorized
|
||||||
|
render json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter
|
||||||
|
else
|
||||||
|
render_with_cache json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def check_authorization
|
||||||
|
# Because in public fetch mode we cache the response, there would be no
|
||||||
|
# benefit from performing the check below, since a blocked account or domain
|
||||||
|
# would likely be served the cache from the reverse proxy anyway
|
||||||
|
|
||||||
|
@unauthorized = authorized_fetch_mode? && !signed_request_account.nil? && (@account.blocking?(signed_request_account) || (!signed_request_account.domain.nil? && @account.domain_blocking?(signed_request_account.domain)))
|
||||||
|
end
|
||||||
|
|
||||||
def set_items
|
def set_items
|
||||||
case params[:id]
|
case params[:id]
|
||||||
when 'featured'
|
when 'featured'
|
||||||
@ -59,11 +73,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def for_signed_account
|
def for_signed_account
|
||||||
# Because in public fetch mode we cache the response, there would be no
|
if @unauthorized
|
||||||
# benefit from performing the check below, since a blocked account or domain
|
|
||||||
# would likely be served the cache from the reverse proxy anyway
|
|
||||||
|
|
||||||
if authorized_fetch_mode? && !signed_request_account.nil? && (@account.blocking?(signed_request_account) || (!signed_request_account.domain.nil? && @account.domain_blocking?(signed_request_account.domain)))
|
|
||||||
[]
|
[]
|
||||||
else
|
else
|
||||||
yield
|
yield
|
||||||
|
|||||||
@ -153,7 +153,8 @@ export function fetchAccountFail(id, error) {
|
|||||||
*/
|
*/
|
||||||
export function followAccount(id, options = { reblogs: true }) {
|
export function followAccount(id, options = { reblogs: true }) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const alreadyFollowing = getState().getIn(['relationships', id, 'following']);
|
const relationship = getState().getIn(['relationships', id]);
|
||||||
|
const alreadyFollowing = relationship?.following || relationship?.requested;
|
||||||
const locked = getState().getIn(['accounts', id, 'locked'], false);
|
const locked = getState().getIn(['accounts', id, 'locked'], false);
|
||||||
|
|
||||||
dispatch(followAccountRequest({ id, locked }));
|
dispatch(followAccountRequest({ id, locked }));
|
||||||
|
|||||||
@ -71,10 +71,15 @@ export const DropdownMenuItemContent: React.FC<{ item: MenuItem }> = ({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { text, description, icon } = item;
|
const { text, description, icon, iconId } = item;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{icon && <Icon icon={icon} id={`${text}-icon`} />}
|
{icon && (
|
||||||
|
<Icon
|
||||||
|
icon={icon}
|
||||||
|
id={iconId ?? text.toLowerCase().replaceAll(/[^a-z]+/g, '-')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<span className='dropdown-menu__item-content'>
|
<span className='dropdown-menu__item-content'>
|
||||||
{text}
|
{text}
|
||||||
{Boolean(description) && (
|
{Boolean(description) && (
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { useIntl, defineMessages } from 'react-intl';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { useIdentity } from '@/mastodon/identity_context';
|
import { useIdentity } from '@/mastodon/identity_context';
|
||||||
|
import { isClientFeatureEnabled } from '@/mastodon/utils/environment';
|
||||||
import {
|
import {
|
||||||
fetchRelationships,
|
fetchRelationships,
|
||||||
followAccount,
|
followAccount,
|
||||||
@ -94,7 +95,17 @@ export const FollowButton: React.FC<{
|
|||||||
|
|
||||||
if (accountId === me) {
|
if (accountId === me) {
|
||||||
return;
|
return;
|
||||||
} else if (relationship.muting) {
|
} else if (relationship.blocking) {
|
||||||
|
dispatch(
|
||||||
|
openModal({
|
||||||
|
modalType: 'CONFIRM_UNBLOCK',
|
||||||
|
modalProps: { account },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
relationship.muting &&
|
||||||
|
!isClientFeatureEnabled('profile_redesign')
|
||||||
|
) {
|
||||||
dispatch(unmuteAccount(accountId));
|
dispatch(unmuteAccount(accountId));
|
||||||
} else if (account && relationship.following) {
|
} else if (account && relationship.following) {
|
||||||
dispatch(
|
dispatch(
|
||||||
@ -107,13 +118,6 @@ export const FollowButton: React.FC<{
|
|||||||
modalProps: { account },
|
modalProps: { account },
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} else if (relationship.blocking) {
|
|
||||||
dispatch(
|
|
||||||
openModal({
|
|
||||||
modalType: 'CONFIRM_UNBLOCK',
|
|
||||||
modalProps: { account },
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
dispatch(followAccount(accountId));
|
dispatch(followAccount(accountId));
|
||||||
}
|
}
|
||||||
@ -136,7 +140,10 @@ export const FollowButton: React.FC<{
|
|||||||
label = intl.formatMessage(messages.editProfile);
|
label = intl.formatMessage(messages.editProfile);
|
||||||
} else if (!relationship) {
|
} else if (!relationship) {
|
||||||
label = <LoadingIndicator />;
|
label = <LoadingIndicator />;
|
||||||
} else if (relationship.muting) {
|
} else if (
|
||||||
|
relationship.muting &&
|
||||||
|
!isClientFeatureEnabled('profile_redesign')
|
||||||
|
) {
|
||||||
label = intl.formatMessage(messages.unmute);
|
label = intl.formatMessage(messages.unmute);
|
||||||
} else if (relationship.following) {
|
} else if (relationship.following) {
|
||||||
label = intl.formatMessage(messages.unfollow);
|
label = intl.formatMessage(messages.unfollow);
|
||||||
@ -173,7 +180,7 @@ export const FollowButton: React.FC<{
|
|||||||
(!(relationship?.following || relationship?.requested) &&
|
(!(relationship?.following || relationship?.requested) &&
|
||||||
(account?.suspended || !!account?.moved))
|
(account?.suspended || !!account?.moved))
|
||||||
}
|
}
|
||||||
secondary={following}
|
secondary={following || relationship?.blocking}
|
||||||
compact={compact}
|
compact={compact}
|
||||||
className={classNames(className, { 'button--destructive': following })}
|
className={classNames(className, { 'button--destructive': following })}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -0,0 +1,7 @@
|
|||||||
|
.stack {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 25px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import { polymorphicForwardRef } from '@/types/polymorphic';
|
||||||
|
|
||||||
|
import classes from './form_stack.module.scss';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple wrapper for providing consistent spacing to a group of form fields.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const FormStack = polymorphicForwardRef<'div'>(
|
||||||
|
({ as: Element = 'div', children, className, ...otherProps }, ref) => (
|
||||||
|
<Element
|
||||||
|
ref={ref}
|
||||||
|
{...otherProps}
|
||||||
|
className={classNames(className, classes.stack)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Element>
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
FormStack.displayName = 'FormStack';
|
||||||
@ -1,5 +1,6 @@
|
|||||||
export { TextInputField } from './text_input_field';
|
export { FormStack } from './form_stack';
|
||||||
export { TextAreaField } from './text_area_field';
|
export { TextInputField, TextInput } from './text_input_field';
|
||||||
|
export { TextAreaField, TextArea } from './text_area_field';
|
||||||
export { CheckboxField, Checkbox } from './checkbox_field';
|
export { CheckboxField, Checkbox } from './checkbox_field';
|
||||||
export { ToggleField, Toggle } from './toggle_field';
|
export { ToggleField, Toggle } from './toggle_field';
|
||||||
export { SelectField } from './select_field';
|
export { SelectField, Select } from './select_field';
|
||||||
|
|||||||
@ -0,0 +1,66 @@
|
|||||||
|
.wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
/* Dropdown indicator icon */
|
||||||
|
&::after {
|
||||||
|
--icon-size: 11px;
|
||||||
|
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
inset-inline-end: 9px;
|
||||||
|
width: var(--icon-size);
|
||||||
|
background-color: var(--color-text-tertiary);
|
||||||
|
pointer-events: none;
|
||||||
|
mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14.933 18.467' height='19.698' width='15.929'><path d='M3.467 14.967l-3.393-3.5H14.86l-3.392 3.5c-1.866 1.925-3.666 3.5-4 3.5-.335 0-2.135-1.575-4-3.5zm.266-11.234L7.467 0 11.2 3.733l3.733 3.734H0l3.733-3.734z' fill='black'/></svg>");
|
||||||
|
mask-position: right center;
|
||||||
|
mask-size: var(--icon-size);
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:has(.select:focus-visible)::after {
|
||||||
|
background-color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:has(.select:disabled)::after {
|
||||||
|
background-color: var(--color-text-disabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.select {
|
||||||
|
appearance: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 41px;
|
||||||
|
padding-inline-start: 10px;
|
||||||
|
padding-inline-end: 30px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
background: var(--color-bg-secondary);
|
||||||
|
border: 1px solid var(--color-border-primary);
|
||||||
|
border-radius: 4px;
|
||||||
|
outline: 0;
|
||||||
|
|
||||||
|
@media screen and (width <= 600px) {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
outline: var(--outline-focus-default);
|
||||||
|
outline-offset: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
color: var(--color-text-disabled);
|
||||||
|
border-color: transparent;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-has-error='true'] & {
|
||||||
|
border-color: var(--color-text-error);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||||
|
|
||||||
import { SelectField } from './select_field';
|
import { SelectField, Select } from './select_field';
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: 'Components/Form Fields/SelectField',
|
title: 'Components/Form Fields/SelectField',
|
||||||
@ -8,24 +8,19 @@ const meta = {
|
|||||||
args: {
|
args: {
|
||||||
label: 'Fruit preference',
|
label: 'Fruit preference',
|
||||||
hint: 'Select your favourite fruit or not. Up to you.',
|
hint: 'Select your favourite fruit or not. Up to you.',
|
||||||
},
|
children: (
|
||||||
render(args) {
|
<>
|
||||||
// Component styles require a wrapper class at the moment
|
<option>Apple</option>
|
||||||
return (
|
<option>Banana</option>
|
||||||
<div className='simple_form'>
|
<option>Kiwi</option>
|
||||||
<SelectField {...args}>
|
<option>Lemon</option>
|
||||||
<option>Apple</option>
|
<option>Mango</option>
|
||||||
<option>Banana</option>
|
<option>Orange</option>
|
||||||
<option>Kiwi</option>
|
<option>Pomelo</option>
|
||||||
<option>Lemon</option>
|
<option>Strawberries</option>
|
||||||
<option>Mango</option>
|
<option>Something else</option>
|
||||||
<option>Orange</option>
|
</>
|
||||||
<option>Pomelo</option>
|
),
|
||||||
<option>Strawberries</option>
|
|
||||||
<option>Something else</option>
|
|
||||||
</SelectField>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
} satisfies Meta<typeof SelectField>;
|
} satisfies Meta<typeof SelectField>;
|
||||||
|
|
||||||
@ -59,3 +54,16 @@ export const WithError: Story = {
|
|||||||
hasError: true,
|
hasError: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const Plain: Story = {
|
||||||
|
render(args) {
|
||||||
|
return <Select {...args} />;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Disabled: Story = {
|
||||||
|
...Plain,
|
||||||
|
args: {
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
import type { ComponentPropsWithoutRef } from 'react';
|
import type { ComponentPropsWithoutRef } from 'react';
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { FormFieldWrapper } from './form_field_wrapper';
|
import { FormFieldWrapper } from './form_field_wrapper';
|
||||||
import type { CommonFieldWrapperProps } from './form_field_wrapper';
|
import type { CommonFieldWrapperProps } from './form_field_wrapper';
|
||||||
|
import classes from './select.module.scss';
|
||||||
|
|
||||||
interface Props
|
interface Props
|
||||||
extends ComponentPropsWithoutRef<'select'>, CommonFieldWrapperProps {}
|
extends ComponentPropsWithoutRef<'select'>, CommonFieldWrapperProps {}
|
||||||
@ -25,14 +28,27 @@ export const SelectField = forwardRef<HTMLSelectElement, Props>(
|
|||||||
inputId={id}
|
inputId={id}
|
||||||
>
|
>
|
||||||
{(inputProps) => (
|
{(inputProps) => (
|
||||||
<div className='select-wrapper'>
|
<Select {...otherProps} {...inputProps} ref={ref}>
|
||||||
<select {...otherProps} {...inputProps} ref={ref}>
|
{children}
|
||||||
{children}
|
</Select>
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</FormFieldWrapper>
|
</FormFieldWrapper>
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
SelectField.displayName = 'SelectField';
|
SelectField.displayName = 'SelectField';
|
||||||
|
|
||||||
|
export const Select = forwardRef<
|
||||||
|
HTMLSelectElement,
|
||||||
|
ComponentPropsWithoutRef<'select'>
|
||||||
|
>(({ className, size, ...otherProps }, ref) => (
|
||||||
|
<div className={classes.wrapper}>
|
||||||
|
<select
|
||||||
|
{...otherProps}
|
||||||
|
className={classNames(className, classes.select)}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
|
||||||
|
Select.displayName = 'Select';
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||||
|
|
||||||
import { TextAreaField } from './text_area_field';
|
import { TextAreaField, TextArea } from './text_area_field';
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: 'Components/Form Fields/TextAreaField',
|
title: 'Components/Form Fields/TextAreaField',
|
||||||
@ -9,14 +9,6 @@ const meta = {
|
|||||||
label: 'Label',
|
label: 'Label',
|
||||||
hint: 'This is a description of this form field',
|
hint: 'This is a description of this form field',
|
||||||
},
|
},
|
||||||
render(args) {
|
|
||||||
// Component styles require a wrapper class at the moment
|
|
||||||
return (
|
|
||||||
<div className='simple_form'>
|
|
||||||
<TextAreaField {...args} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
} satisfies Meta<typeof TextAreaField>;
|
} satisfies Meta<typeof TextAreaField>;
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
@ -49,3 +41,17 @@ export const WithError: Story = {
|
|||||||
hasError: true,
|
hasError: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const Plain: Story = {
|
||||||
|
render(args) {
|
||||||
|
return <TextArea {...args} />;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Disabled: Story = {
|
||||||
|
...Plain,
|
||||||
|
args: {
|
||||||
|
disabled: true,
|
||||||
|
defaultValue: "This value can't be changed",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
import type { ComponentPropsWithoutRef } from 'react';
|
import type { ComponentPropsWithoutRef } from 'react';
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { FormFieldWrapper } from './form_field_wrapper';
|
import { FormFieldWrapper } from './form_field_wrapper';
|
||||||
import type { CommonFieldWrapperProps } from './form_field_wrapper';
|
import type { CommonFieldWrapperProps } from './form_field_wrapper';
|
||||||
|
import classes from './text_input.module.scss';
|
||||||
|
|
||||||
interface Props
|
interface Props
|
||||||
extends ComponentPropsWithoutRef<'textarea'>, CommonFieldWrapperProps {}
|
extends ComponentPropsWithoutRef<'textarea'>, CommonFieldWrapperProps {}
|
||||||
@ -23,9 +26,22 @@ export const TextAreaField = forwardRef<HTMLTextAreaElement, Props>(
|
|||||||
hasError={hasError}
|
hasError={hasError}
|
||||||
inputId={id}
|
inputId={id}
|
||||||
>
|
>
|
||||||
{(inputProps) => <textarea {...otherProps} {...inputProps} ref={ref} />}
|
{(inputProps) => <TextArea {...otherProps} {...inputProps} ref={ref} />}
|
||||||
</FormFieldWrapper>
|
</FormFieldWrapper>
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
TextAreaField.displayName = 'TextAreaField';
|
TextAreaField.displayName = 'TextAreaField';
|
||||||
|
|
||||||
|
export const TextArea = forwardRef<
|
||||||
|
HTMLTextAreaElement,
|
||||||
|
ComponentPropsWithoutRef<'textarea'>
|
||||||
|
>(({ className, ...otherProps }, ref) => (
|
||||||
|
<textarea
|
||||||
|
{...otherProps}
|
||||||
|
className={classNames(className, classes.input)}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
|
||||||
|
TextArea.displayName = 'TextArea';
|
||||||
|
|||||||
@ -0,0 +1,42 @@
|
|||||||
|
.input {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
resize: vertical;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 16px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
background: var(--color-bg-secondary);
|
||||||
|
border: 1px solid var(--color-border-primary);
|
||||||
|
border-radius: 4px;
|
||||||
|
outline: var(--outline-focus-default);
|
||||||
|
outline-color: transparent;
|
||||||
|
outline-offset: -1px;
|
||||||
|
transition: outline-color 0.15s ease-out;
|
||||||
|
|
||||||
|
@media screen and (width <= 600px) {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline-color: var(--color-text-brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus:user-invalid,
|
||||||
|
&:required:user-invalid,
|
||||||
|
[data-has-error='true'] & {
|
||||||
|
outline-color: var(--color-text-error);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:required:user-valid {
|
||||||
|
outline-color: var(--color-text-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
color: var(--color-text-disabled);
|
||||||
|
border-color: transparent;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||||
|
|
||||||
import { TextInputField } from './text_input_field';
|
import { TextInputField, TextInput } from './text_input_field';
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: 'Components/Form Fields/TextInputField',
|
title: 'Components/Form Fields/TextInputField',
|
||||||
@ -9,14 +9,6 @@ const meta = {
|
|||||||
label: 'Label',
|
label: 'Label',
|
||||||
hint: 'This is a description of this form field',
|
hint: 'This is a description of this form field',
|
||||||
},
|
},
|
||||||
render(args) {
|
|
||||||
// Component styles require a wrapper class at the moment
|
|
||||||
return (
|
|
||||||
<div className='simple_form'>
|
|
||||||
<TextInputField {...args} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
} satisfies Meta<typeof TextInputField>;
|
} satisfies Meta<typeof TextInputField>;
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
@ -49,3 +41,17 @@ export const WithError: Story = {
|
|||||||
hasError: true,
|
hasError: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const Plain: Story = {
|
||||||
|
render(args) {
|
||||||
|
return <TextInput {...args} />;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Disabled: Story = {
|
||||||
|
...Plain,
|
||||||
|
args: {
|
||||||
|
disabled: true,
|
||||||
|
defaultValue: "This value can't be changed",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
import type { ComponentPropsWithoutRef } from 'react';
|
import type { ComponentPropsWithoutRef } from 'react';
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { FormFieldWrapper } from './form_field_wrapper';
|
import { FormFieldWrapper } from './form_field_wrapper';
|
||||||
import type { CommonFieldWrapperProps } from './form_field_wrapper';
|
import type { CommonFieldWrapperProps } from './form_field_wrapper';
|
||||||
|
import classes from './text_input.module.scss';
|
||||||
|
|
||||||
interface Props
|
interface Props
|
||||||
extends ComponentPropsWithoutRef<'input'>, CommonFieldWrapperProps {}
|
extends ComponentPropsWithoutRef<'input'>, CommonFieldWrapperProps {}
|
||||||
@ -15,10 +18,7 @@ interface Props
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export const TextInputField = forwardRef<HTMLInputElement, Props>(
|
export const TextInputField = forwardRef<HTMLInputElement, Props>(
|
||||||
(
|
({ id, label, hint, hasError, required, ...otherProps }, ref) => (
|
||||||
{ id, label, hint, hasError, required, type = 'text', ...otherProps },
|
|
||||||
ref,
|
|
||||||
) => (
|
|
||||||
<FormFieldWrapper
|
<FormFieldWrapper
|
||||||
label={label}
|
label={label}
|
||||||
hint={hint}
|
hint={hint}
|
||||||
@ -26,11 +26,23 @@ export const TextInputField = forwardRef<HTMLInputElement, Props>(
|
|||||||
hasError={hasError}
|
hasError={hasError}
|
||||||
inputId={id}
|
inputId={id}
|
||||||
>
|
>
|
||||||
{(inputProps) => (
|
{(inputProps) => <TextInput {...otherProps} {...inputProps} ref={ref} />}
|
||||||
<input type={type} {...otherProps} {...inputProps} ref={ref} />
|
|
||||||
)}
|
|
||||||
</FormFieldWrapper>
|
</FormFieldWrapper>
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
TextInputField.displayName = 'TextInputField';
|
TextInputField.displayName = 'TextInputField';
|
||||||
|
|
||||||
|
export const TextInput = forwardRef<
|
||||||
|
HTMLInputElement,
|
||||||
|
ComponentPropsWithoutRef<'input'>
|
||||||
|
>(({ type = 'text', className, ...otherProps }, ref) => (
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
{...otherProps}
|
||||||
|
className={classNames(className, classes.input)}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
|
||||||
|
TextInput.displayName = 'TextInput';
|
||||||
|
|||||||
@ -1,33 +1,57 @@
|
|||||||
import type { FC, ReactNode } from 'react';
|
import { forwardRef } from 'react';
|
||||||
|
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import type { OmitUnion } from '@/mastodon/utils/types';
|
||||||
|
|
||||||
|
import { Icon } from '../icon';
|
||||||
|
import type { IconProp } from '../icon';
|
||||||
|
|
||||||
import classes from './styles.module.css';
|
import classes from './styles.module.css';
|
||||||
|
|
||||||
export interface MiniCardProps {
|
export type MiniCardProps = OmitUnion<
|
||||||
label: ReactNode;
|
ComponentPropsWithoutRef<'div'>,
|
||||||
value: ReactNode;
|
{
|
||||||
className?: string;
|
label: ReactNode;
|
||||||
hidden?: boolean;
|
value: ReactNode;
|
||||||
}
|
icon?: IconProp;
|
||||||
|
iconId?: string;
|
||||||
export const MiniCard: FC<MiniCardProps> = ({
|
iconClassName?: string;
|
||||||
label,
|
|
||||||
value,
|
|
||||||
className,
|
|
||||||
hidden,
|
|
||||||
}) => {
|
|
||||||
if (!label) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
return (
|
export const MiniCard = forwardRef<HTMLDivElement, MiniCardProps>(
|
||||||
<div
|
(
|
||||||
className={classNames(classes.card, className)}
|
{ label, value, className, hidden, icon, iconId, iconClassName, ...props },
|
||||||
inert={hidden ? '' : undefined}
|
ref,
|
||||||
>
|
) => {
|
||||||
<dt className={classes.label}>{label}</dt>
|
if (!label) {
|
||||||
<dd className={classes.value}>{value}</dd>
|
return null;
|
||||||
</div>
|
}
|
||||||
);
|
|
||||||
};
|
return (
|
||||||
|
<div
|
||||||
|
{...props}
|
||||||
|
className={classNames(
|
||||||
|
classes.card,
|
||||||
|
icon && classes.cardWithIcon,
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
|
{icon && (
|
||||||
|
<Icon
|
||||||
|
id={iconId ?? 'minicard'}
|
||||||
|
icon={icon}
|
||||||
|
className={classNames(classes.icon, iconClassName)}
|
||||||
|
noFill
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<dt className={classes.label}>{label}</dt>
|
||||||
|
<dd className={classes.value}>{value}</dd>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
MiniCard.displayName = 'MiniCard';
|
||||||
|
|||||||
@ -1,69 +1,37 @@
|
|||||||
import type { FC, Key, MouseEventHandler } from 'react';
|
import { forwardRef } from 'react';
|
||||||
|
import type { ComponentPropsWithoutRef, Key } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { useOverflow } from '@/mastodon/hooks/useOverflow';
|
import type { OmitUnion } from '@/mastodon/utils/types';
|
||||||
|
|
||||||
import { MiniCard } from '.';
|
import { MiniCard } from '.';
|
||||||
import type { MiniCardProps } from '.';
|
import type { MiniCardProps as BaseCardProps } from '.';
|
||||||
import classes from './styles.module.css';
|
import classes from './styles.module.css';
|
||||||
|
|
||||||
|
export type MiniCardProps = BaseCardProps & {
|
||||||
|
key?: Key;
|
||||||
|
};
|
||||||
|
|
||||||
interface MiniCardListProps {
|
interface MiniCardListProps {
|
||||||
cards?: (Pick<MiniCardProps, 'label' | 'value' | 'className'> & {
|
cards?: MiniCardProps[];
|
||||||
key?: Key;
|
|
||||||
})[];
|
|
||||||
className?: string;
|
|
||||||
onOverflowClick?: MouseEventHandler;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MiniCardList: FC<MiniCardListProps> = ({
|
export const MiniCardList = forwardRef<
|
||||||
cards = [],
|
HTMLDListElement,
|
||||||
className,
|
OmitUnion<ComponentPropsWithoutRef<'dl'>, MiniCardListProps>
|
||||||
onOverflowClick,
|
>(({ cards = [], className, children, ...props }, ref) => {
|
||||||
}) => {
|
|
||||||
const {
|
|
||||||
wrapperRef,
|
|
||||||
listRef,
|
|
||||||
hiddenCount,
|
|
||||||
hasOverflow,
|
|
||||||
hiddenIndex,
|
|
||||||
maxWidth,
|
|
||||||
} = useOverflow();
|
|
||||||
|
|
||||||
if (!cards.length) {
|
if (!cards.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(classes.wrapper, className)} ref={wrapperRef}>
|
<dl {...props} className={classNames(classes.list, className)} ref={ref}>
|
||||||
<dl className={classes.list} ref={listRef} style={{ maxWidth }}>
|
{cards.map((card, index) => (
|
||||||
{cards.map((card, index) => (
|
<MiniCard key={card.key ?? index} {...card} />
|
||||||
<MiniCard
|
))}
|
||||||
key={card.key ?? index}
|
{children}
|
||||||
label={card.label}
|
</dl>
|
||||||
value={card.value}
|
|
||||||
hidden={hasOverflow && index >= hiddenIndex}
|
|
||||||
className={card.className}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</dl>
|
|
||||||
{cards.length > 1 && (
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
type='button'
|
|
||||||
className={classNames(classes.more, !hasOverflow && classes.hidden)}
|
|
||||||
onClick={onOverflowClick}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='minicard.more_items'
|
|
||||||
defaultMessage='+{count}'
|
|
||||||
values={{ count: hiddenCount }}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
MiniCardList.displayName = 'MiniCardList';
|
||||||
|
|||||||
@ -1,30 +1,12 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||||
import { action } from 'storybook/actions';
|
|
||||||
|
import LinkIcon from '@/material-icons/400-24px/link_2.svg?react';
|
||||||
|
|
||||||
import { MiniCardList } from './list';
|
import { MiniCardList } from './list';
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: 'Components/MiniCard',
|
title: 'Components/MiniCard',
|
||||||
component: MiniCardList,
|
component: MiniCardList,
|
||||||
args: {
|
|
||||||
onOverflowClick: action('Overflow clicked'),
|
|
||||||
},
|
|
||||||
render(args) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
resize: 'horizontal',
|
|
||||||
padding: '1rem',
|
|
||||||
border: '1px solid gray',
|
|
||||||
overflow: 'auto',
|
|
||||||
width: '400px',
|
|
||||||
minWidth: '100px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<MiniCardList {...args} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
} satisfies Meta<typeof MiniCardList>;
|
} satisfies Meta<typeof MiniCardList>;
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
@ -38,10 +20,12 @@ export const Default: Story = {
|
|||||||
{
|
{
|
||||||
label: 'Website',
|
label: 'Website',
|
||||||
value: <a href='https://example.com'>bowie-the-db.meow</a>,
|
value: <a href='https://example.com'>bowie-the-db.meow</a>,
|
||||||
|
icon: LinkIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Free playlists',
|
label: 'Free playlists',
|
||||||
value: <a href='https://soundcloud.com/bowie-the-dj'>soundcloud.com</a>,
|
value: <a href='https://soundcloud.com/bowie-the-dj'>soundcloud.com</a>,
|
||||||
|
icon: LinkIcon,
|
||||||
},
|
},
|
||||||
{ label: 'Location', value: 'Purris, France' },
|
{ label: 'Location', value: 'Purris, France' },
|
||||||
],
|
],
|
||||||
@ -54,11 +38,13 @@ export const LongValue: Story = {
|
|||||||
{
|
{
|
||||||
label: 'Username',
|
label: 'Username',
|
||||||
value: 'bowie-the-dj',
|
value: 'bowie-the-dj',
|
||||||
|
style: { maxWidth: '250px' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Bio',
|
label: 'Bio',
|
||||||
value:
|
value:
|
||||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
|
||||||
|
style: { maxWidth: '250px' },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,52 +1,49 @@
|
|||||||
.wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
justify-content: flex-start;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list {
|
.list {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card,
|
.card {
|
||||||
.more {
|
|
||||||
border: 1px solid var(--color-border-primary);
|
border: 1px solid var(--color-border-primary);
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
display: grid;
|
||||||
|
grid-template-rows: repeat(2, 1fr);
|
||||||
.more {
|
column-gap: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1rem;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
font-weight: 600;
|
|
||||||
appearance: none;
|
|
||||||
background: none;
|
|
||||||
aspect-ratio: 1;
|
|
||||||
height: 100%;
|
|
||||||
transition: all 300ms linear;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.more:hover {
|
.cardWithIcon {
|
||||||
background-color: var(--color-bg-brand-softer);
|
grid-template-columns: 16px 1fr;
|
||||||
color: var(--color-text-primary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hidden {
|
.icon {
|
||||||
display: none;
|
grid-row: span 2;
|
||||||
}
|
grid-column: 1;
|
||||||
|
width: 100%;
|
||||||
.label {
|
height: auto;
|
||||||
color: var(--color-text-secondary);
|
align-self: center;
|
||||||
margin-bottom: 2px;
|
fill: currentColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
.value {
|
.value {
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--color-text-brand);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.2s ease-in-out;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
color: var(--color-text-brand-soft);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.label,
|
.label,
|
||||||
@ -54,4 +51,8 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
.cardWithIcon & {
|
||||||
|
grid-column: 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { createSelector } from '@reduxjs/toolkit';
|
|||||||
import type { List as ImmutableList } from 'immutable';
|
import type { List as ImmutableList } from 'immutable';
|
||||||
|
|
||||||
import type { SelectItem } from '@/mastodon/components/dropdown_selector';
|
import type { SelectItem } from '@/mastodon/components/dropdown_selector';
|
||||||
|
import { Select } from '@/mastodon/components/form_fields';
|
||||||
import type { RootState } from '@/mastodon/store';
|
import type { RootState } from '@/mastodon/store';
|
||||||
import { useAppSelector } from '@/mastodon/store';
|
import { useAppSelector } from '@/mastodon/store';
|
||||||
|
|
||||||
@ -104,19 +105,17 @@ export const RulesSection: FC<RulesSectionProps> = ({ isLoading = false }) => {
|
|||||||
defaultMessage='Language'
|
defaultMessage='Language'
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<div className='select-wrapper'>
|
<Select onChange={handleLocaleChange} id='language-select'>
|
||||||
<select onChange={handleLocaleChange} id='language-select'>
|
{localeOptions.map((option) => (
|
||||||
{localeOptions.map((option) => (
|
<option
|
||||||
<option
|
key={option.value}
|
||||||
key={option.value}
|
value={option.value}
|
||||||
value={option.value}
|
selected={option.value === selectedLocale}
|
||||||
selected={option.value === selectedLocale}
|
>
|
||||||
>
|
{option.text}
|
||||||
{option.text}
|
</option>
|
||||||
</option>
|
))}
|
||||||
))}
|
</Select>
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Section>
|
</Section>
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { useCallback } from 'react';
|
import type { RefCallback } from 'react';
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
@ -77,6 +78,40 @@ export const AccountHeader: React.FC<{
|
|||||||
[dispatch, account],
|
[dispatch, account],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [isFooterIntersecting, setIsIntersecting] = useState(false);
|
||||||
|
const handleIntersect: IntersectionObserverCallback = useCallback(
|
||||||
|
(entries) => {
|
||||||
|
const entry = entries.at(0);
|
||||||
|
if (!entry) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsIntersecting(entry.isIntersecting);
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
const [observer] = useState(
|
||||||
|
() =>
|
||||||
|
new IntersectionObserver(handleIntersect, {
|
||||||
|
rootMargin: '0px 0px -55px 0px', // Height of bottom nav bar.
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleObserverRef: RefCallback<HTMLDivElement> = useCallback(
|
||||||
|
(node) => {
|
||||||
|
if (node) {
|
||||||
|
observer.observe(node);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[observer],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
observer.disconnect();
|
||||||
|
};
|
||||||
|
}, [observer]);
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -114,7 +149,12 @@ export const AccountHeader: React.FC<{
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='account__header__bar'>
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'account__header__bar',
|
||||||
|
isRedesignEnabled() && redesignClasses.barWrapper,
|
||||||
|
)}
|
||||||
|
>
|
||||||
<div className='account__header__tabs'>
|
<div className='account__header__tabs'>
|
||||||
<a
|
<a
|
||||||
className='avatar'
|
className='avatar'
|
||||||
@ -144,7 +184,13 @@ export const AccountHeader: React.FC<{
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<AccountName accountId={accountId} />
|
<AccountName accountId={accountId} />
|
||||||
{isRedesignEnabled() && <AccountButtons accountId={accountId} />}
|
{isRedesignEnabled() && (
|
||||||
|
<AccountButtons
|
||||||
|
accountId={accountId}
|
||||||
|
className={redesignClasses.buttonsDesktop}
|
||||||
|
noShare
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AccountBadges accountId={accountId} />
|
<AccountBadges accountId={accountId} />
|
||||||
@ -153,11 +199,13 @@ export const AccountHeader: React.FC<{
|
|||||||
<FamiliarFollowers accountId={accountId} />
|
<FamiliarFollowers accountId={accountId} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<AccountButtons
|
{!isRedesignEnabled() && (
|
||||||
className='account__header__buttons--mobile'
|
<AccountButtons
|
||||||
accountId={accountId}
|
className='account__header__buttons--mobile'
|
||||||
noShare
|
accountId={accountId}
|
||||||
/>
|
noShare
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{!suspendedOrHidden && (
|
{!suspendedOrHidden && (
|
||||||
<div className='account__header__extra'>
|
<div className='account__header__extra'>
|
||||||
@ -181,10 +229,22 @@ export const AccountHeader: React.FC<{
|
|||||||
<AccountNumberFields accountId={accountId} />
|
<AccountNumberFields accountId={accountId} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{isRedesignEnabled() && (
|
||||||
|
<AccountButtons
|
||||||
|
className={classNames(
|
||||||
|
redesignClasses.buttonsMobile,
|
||||||
|
!isFooterIntersecting && redesignClasses.buttonsMobileIsStuck,
|
||||||
|
)}
|
||||||
|
accountId={accountId}
|
||||||
|
noShare
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</AnimateEmojiProvider>
|
</AnimateEmojiProvider>
|
||||||
|
|
||||||
{!hideTabs && !hidden && <AccountTabs acct={account.acct} />}
|
{!hideTabs && !hidden && <AccountTabs acct={account.acct} />}
|
||||||
|
<div ref={handleObserverRef} />
|
||||||
|
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{titleFromAccount(account)}</title>
|
<title>{titleFromAccount(account)}</title>
|
||||||
|
|||||||
@ -108,48 +108,6 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => {
|
|||||||
className={classNames(className, classes.badgeMuted)}
|
className={classNames(className, classes.badgeMuted)}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
} else if (
|
|
||||||
relationship.followed_by &&
|
|
||||||
(relationship.following || relationship.requested)
|
|
||||||
) {
|
|
||||||
badges.push(
|
|
||||||
<Badge
|
|
||||||
key='mutuals-badge'
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id='account.badges.mutuals'
|
|
||||||
defaultMessage='You follow each other'
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
className={className}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
} else if (relationship.followed_by) {
|
|
||||||
badges.push(
|
|
||||||
<Badge
|
|
||||||
key='follows-you-badge'
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id='account.badges.follows_you'
|
|
||||||
defaultMessage='Follows you'
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
className={className}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
} else if (relationship.requested_by) {
|
|
||||||
badges.push(
|
|
||||||
<Badge
|
|
||||||
key='requested-to-follow-badge'
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id='account.badges.requested_to_follow'
|
|
||||||
defaultMessage='Requested to follow you'
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
className={className}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,21 +1,23 @@
|
|||||||
import { useCallback, useMemo } from 'react';
|
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import IconVerified from '@/images/icons/icon_verified.svg?react';
|
import IconVerified from '@/images/icons/icon_verified.svg?react';
|
||||||
import { openModal } from '@/mastodon/actions/modal';
|
|
||||||
import { AccountFields } from '@/mastodon/components/account_fields';
|
import { AccountFields } from '@/mastodon/components/account_fields';
|
||||||
import { EmojiHTML } from '@/mastodon/components/emoji/html';
|
import { EmojiHTML } from '@/mastodon/components/emoji/html';
|
||||||
import { FormattedDateWrapper } from '@/mastodon/components/formatted_date';
|
import { FormattedDateWrapper } from '@/mastodon/components/formatted_date';
|
||||||
import { Icon } from '@/mastodon/components/icon';
|
import { IconButton } from '@/mastodon/components/icon_button';
|
||||||
import { MiniCardList } from '@/mastodon/components/mini_card/list';
|
import { MiniCard } from '@/mastodon/components/mini_card';
|
||||||
import { useElementHandledLink } from '@/mastodon/components/status/handled_link';
|
import { useElementHandledLink } from '@/mastodon/components/status/handled_link';
|
||||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||||
|
import { useOverflowScroll } from '@/mastodon/hooks/useOverflow';
|
||||||
import type { Account } from '@/mastodon/models/account';
|
import type { Account } from '@/mastodon/models/account';
|
||||||
import { useAppDispatch } from '@/mastodon/store';
|
import { isValidUrl } from '@/mastodon/utils/checks';
|
||||||
|
import IconLeftArrow from '@/material-icons/400-24px/chevron_left.svg?react';
|
||||||
|
import IconRightArrow from '@/material-icons/400-24px/chevron_right.svg?react';
|
||||||
|
import IconLink from '@/material-icons/400-24px/link_2.svg?react';
|
||||||
|
|
||||||
import { isRedesignEnabled } from '../common';
|
import { isRedesignEnabled } from '../common';
|
||||||
|
|
||||||
@ -57,61 +59,94 @@ export const AccountHeaderFields: FC<{ accountId: string }> = ({
|
|||||||
|
|
||||||
const RedesignAccountHeaderFields: FC<{ account: Account }> = ({ account }) => {
|
const RedesignAccountHeaderFields: FC<{ account: Account }> = ({ account }) => {
|
||||||
const htmlHandlers = useElementHandledLink();
|
const htmlHandlers = useElementHandledLink();
|
||||||
const cards = useMemo(
|
const intl = useIntl();
|
||||||
() =>
|
|
||||||
account.fields
|
|
||||||
.toArray()
|
|
||||||
.map(({ value_emojified, name_emojified, verified_at }) => ({
|
|
||||||
label: (
|
|
||||||
<>
|
|
||||||
<EmojiHTML
|
|
||||||
htmlString={name_emojified}
|
|
||||||
extraEmojis={account.emojis}
|
|
||||||
className='translate'
|
|
||||||
as='span'
|
|
||||||
{...htmlHandlers}
|
|
||||||
/>
|
|
||||||
{!!verified_at && (
|
|
||||||
<Icon
|
|
||||||
id='verified'
|
|
||||||
icon={IconVerified}
|
|
||||||
className={classes.fieldIconVerified}
|
|
||||||
noFill
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
value: (
|
|
||||||
<EmojiHTML
|
|
||||||
as='span'
|
|
||||||
htmlString={value_emojified}
|
|
||||||
extraEmojis={account.emojis}
|
|
||||||
{...htmlHandlers}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
className: classNames(
|
|
||||||
classes.fieldCard,
|
|
||||||
!!verified_at && classes.fieldCardVerified,
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
[account.emojis, account.fields, htmlHandlers],
|
|
||||||
);
|
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const {
|
||||||
const handleOverflowClick = useCallback(() => {
|
bodyRef,
|
||||||
dispatch(
|
canScrollLeft,
|
||||||
openModal({
|
canScrollRight,
|
||||||
modalType: 'ACCOUNT_FIELDS',
|
handleLeftNav,
|
||||||
modalProps: { accountId: account.id },
|
handleRightNav,
|
||||||
}),
|
handleScroll,
|
||||||
);
|
} = useOverflowScroll();
|
||||||
}, [account.id, dispatch]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MiniCardList
|
<div
|
||||||
cards={cards}
|
className={classNames(
|
||||||
className={classes.fieldList}
|
classes.fieldWrapper,
|
||||||
onOverflowClick={handleOverflowClick}
|
canScrollLeft && classes.fieldWrapperLeft,
|
||||||
/>
|
canScrollRight && classes.fieldWrapperRight,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{canScrollLeft && (
|
||||||
|
<IconButton
|
||||||
|
icon='more'
|
||||||
|
iconComponent={IconLeftArrow}
|
||||||
|
title={intl.formatMessage({
|
||||||
|
id: 'account.fields.scroll_prev',
|
||||||
|
defaultMessage: 'Show previous',
|
||||||
|
})}
|
||||||
|
className={classes.fieldArrowButton}
|
||||||
|
onClick={handleLeftNav}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<dl ref={bodyRef} className={classes.fieldList} onScroll={handleScroll}>
|
||||||
|
{account.fields.map(
|
||||||
|
(
|
||||||
|
{ name, name_emojified, value_emojified, value_plain, verified_at },
|
||||||
|
key,
|
||||||
|
) => (
|
||||||
|
<MiniCard
|
||||||
|
key={key}
|
||||||
|
label={
|
||||||
|
<EmojiHTML
|
||||||
|
htmlString={name_emojified}
|
||||||
|
extraEmojis={account.emojis}
|
||||||
|
className='translate'
|
||||||
|
as='span'
|
||||||
|
title={name}
|
||||||
|
{...htmlHandlers}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
value={
|
||||||
|
<EmojiHTML
|
||||||
|
as='span'
|
||||||
|
htmlString={value_emojified}
|
||||||
|
extraEmojis={account.emojis}
|
||||||
|
title={value_plain ?? undefined}
|
||||||
|
{...htmlHandlers}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
icon={fieldIcon(verified_at, value_plain)}
|
||||||
|
className={classNames(
|
||||||
|
classes.fieldCard,
|
||||||
|
verified_at && classes.fieldCardVerified,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</dl>
|
||||||
|
{canScrollRight && (
|
||||||
|
<IconButton
|
||||||
|
icon='more'
|
||||||
|
iconComponent={IconRightArrow}
|
||||||
|
title={intl.formatMessage({
|
||||||
|
id: 'account.fields.scroll_next',
|
||||||
|
defaultMessage: 'Show next',
|
||||||
|
})}
|
||||||
|
className={classes.fieldArrowButton}
|
||||||
|
onClick={handleRightNav}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function fieldIcon(verified_at: string | null, value_plain: string | null) {
|
||||||
|
if (verified_at) {
|
||||||
|
return IconVerified;
|
||||||
|
} else if (value_plain && isValidUrl(value_plain)) {
|
||||||
|
return IconLink;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,95 +0,0 @@
|
|||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
import { FormattedMessage, useIntl } from 'react-intl';
|
|
||||||
|
|
||||||
import IconVerified from '@/images/icons/icon_verified.svg?react';
|
|
||||||
import { DisplayName } from '@/mastodon/components/display_name';
|
|
||||||
import { AnimateEmojiProvider } from '@/mastodon/components/emoji/context';
|
|
||||||
import { EmojiHTML } from '@/mastodon/components/emoji/html';
|
|
||||||
import { Icon } from '@/mastodon/components/icon';
|
|
||||||
import { IconButton } from '@/mastodon/components/icon_button';
|
|
||||||
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
|
|
||||||
import { useElementHandledLink } from '@/mastodon/components/status/handled_link';
|
|
||||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
|
||||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
|
||||||
|
|
||||||
import classes from './redesign.module.scss';
|
|
||||||
|
|
||||||
export const AccountFieldsModal: FC<{
|
|
||||||
accountId: string;
|
|
||||||
onClose: () => void;
|
|
||||||
}> = ({ accountId, onClose }) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
const account = useAccount(accountId);
|
|
||||||
const htmlHandlers = useElementHandledLink();
|
|
||||||
|
|
||||||
if (!account) {
|
|
||||||
return (
|
|
||||||
<div className='modal-root__modal dialog-modal'>
|
|
||||||
<LoadingIndicator />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='modal-root__modal dialog-modal'>
|
|
||||||
<div className='dialog-modal__header'>
|
|
||||||
<IconButton
|
|
||||||
icon='close'
|
|
||||||
className={classes.modalCloseButton}
|
|
||||||
onClick={onClose}
|
|
||||||
iconComponent={CloseIcon}
|
|
||||||
title={intl.formatMessage({
|
|
||||||
id: 'account_fields_modal.close',
|
|
||||||
defaultMessage: 'Close',
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
<span className={`${classes.modalTitle} dialog-modal__header__title`}>
|
|
||||||
<FormattedMessage
|
|
||||||
id='account_fields_modal.title'
|
|
||||||
defaultMessage="{name}'s info"
|
|
||||||
values={{
|
|
||||||
name: <DisplayName account={account} variant='simple' />,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className='dialog-modal__content'>
|
|
||||||
<AnimateEmojiProvider>
|
|
||||||
<dl className={classes.modalFieldsList}>
|
|
||||||
{account.fields.map((field, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className={`${classes.modalFieldItem} ${classes.fieldCard}`}
|
|
||||||
>
|
|
||||||
<EmojiHTML
|
|
||||||
as='dt'
|
|
||||||
htmlString={field.name_emojified}
|
|
||||||
extraEmojis={account.emojis}
|
|
||||||
className='translate'
|
|
||||||
{...htmlHandlers}
|
|
||||||
/>
|
|
||||||
<dd>
|
|
||||||
<EmojiHTML
|
|
||||||
as='span'
|
|
||||||
htmlString={field.value_emojified}
|
|
||||||
extraEmojis={account.emojis}
|
|
||||||
{...htmlHandlers}
|
|
||||||
/>
|
|
||||||
{!!field.verified_at && (
|
|
||||||
<Icon
|
|
||||||
id='verified'
|
|
||||||
icon={IconVerified}
|
|
||||||
className={classes.fieldIconVerified}
|
|
||||||
noFill
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</dl>
|
|
||||||
</AnimateEmojiProvider>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -12,6 +12,7 @@ import {
|
|||||||
unpinAccount,
|
unpinAccount,
|
||||||
} from '@/mastodon/actions/accounts';
|
} from '@/mastodon/actions/accounts';
|
||||||
import { removeAccountFromFollowers } from '@/mastodon/actions/accounts_typed';
|
import { removeAccountFromFollowers } from '@/mastodon/actions/accounts_typed';
|
||||||
|
import { showAlert } from '@/mastodon/actions/alerts';
|
||||||
import { directCompose, mentionCompose } from '@/mastodon/actions/compose';
|
import { directCompose, mentionCompose } from '@/mastodon/actions/compose';
|
||||||
import {
|
import {
|
||||||
initDomainBlockModal,
|
initDomainBlockModal,
|
||||||
@ -23,16 +24,78 @@ import { initReport } from '@/mastodon/actions/reports';
|
|||||||
import { Dropdown } from '@/mastodon/components/dropdown_menu';
|
import { Dropdown } from '@/mastodon/components/dropdown_menu';
|
||||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||||
import { useIdentity } from '@/mastodon/identity_context';
|
import { useIdentity } from '@/mastodon/identity_context';
|
||||||
|
import type { Account } from '@/mastodon/models/account';
|
||||||
import type { MenuItem } from '@/mastodon/models/dropdown_menu';
|
import type { MenuItem } from '@/mastodon/models/dropdown_menu';
|
||||||
|
import type { Relationship } from '@/mastodon/models/relationship';
|
||||||
import {
|
import {
|
||||||
PERMISSION_MANAGE_FEDERATION,
|
PERMISSION_MANAGE_FEDERATION,
|
||||||
PERMISSION_MANAGE_USERS,
|
PERMISSION_MANAGE_USERS,
|
||||||
} from '@/mastodon/permissions';
|
} from '@/mastodon/permissions';
|
||||||
|
import type { AppDispatch } from '@/mastodon/store';
|
||||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||||
|
import BlockIcon from '@/material-icons/400-24px/block.svg?react';
|
||||||
|
import EditIcon from '@/material-icons/400-24px/edit_square.svg?react';
|
||||||
|
import LinkIcon from '@/material-icons/400-24px/link_2.svg?react';
|
||||||
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
||||||
|
import PersonRemoveIcon from '@/material-icons/400-24px/person_remove.svg?react';
|
||||||
|
import ReportIcon from '@/material-icons/400-24px/report.svg?react';
|
||||||
|
import ShareIcon from '@/material-icons/400-24px/share.svg?react';
|
||||||
|
|
||||||
import { isRedesignEnabled } from '../common';
|
import { isRedesignEnabled } from '../common';
|
||||||
|
|
||||||
|
export const AccountMenu: FC<{ accountId: string }> = ({ accountId }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const { signedIn, permissions } = useIdentity();
|
||||||
|
|
||||||
|
const account = useAccount(accountId);
|
||||||
|
const relationship = useAppSelector((state) =>
|
||||||
|
state.relationships.get(accountId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const menuItems = useMemo(() => {
|
||||||
|
if (!account) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRedesignEnabled()) {
|
||||||
|
return redesignMenuItems({
|
||||||
|
account,
|
||||||
|
signedIn,
|
||||||
|
permissions,
|
||||||
|
intl,
|
||||||
|
relationship,
|
||||||
|
dispatch,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return currentMenuItems({
|
||||||
|
account,
|
||||||
|
signedIn,
|
||||||
|
permissions,
|
||||||
|
intl,
|
||||||
|
relationship,
|
||||||
|
dispatch,
|
||||||
|
});
|
||||||
|
}, [account, signedIn, permissions, intl, relationship, dispatch]);
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
disabled={menuItems.length === 0}
|
||||||
|
items={menuItems}
|
||||||
|
icon='ellipsis-v'
|
||||||
|
iconComponent={MoreHorizIcon}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface MenuItemsParams {
|
||||||
|
account: Account;
|
||||||
|
signedIn: boolean;
|
||||||
|
permissions: number;
|
||||||
|
intl: ReturnType<typeof useIntl>;
|
||||||
|
relationship?: Relationship;
|
||||||
|
dispatch: AppDispatch;
|
||||||
|
}
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
|
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
|
||||||
mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' },
|
mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' },
|
||||||
@ -109,80 +172,78 @@ const messages = defineMessages({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const AccountMenu: FC<{ accountId: string }> = ({ accountId }) => {
|
function currentMenuItems({
|
||||||
const intl = useIntl();
|
account,
|
||||||
const { signedIn, permissions } = useIdentity();
|
signedIn,
|
||||||
|
permissions,
|
||||||
|
intl,
|
||||||
|
relationship,
|
||||||
|
dispatch,
|
||||||
|
}: MenuItemsParams): MenuItem[] {
|
||||||
|
const items: MenuItem[] = [];
|
||||||
|
const isRemote = account.acct !== account.username;
|
||||||
|
|
||||||
const account = useAccount(accountId);
|
if (signedIn && !account.suspended) {
|
||||||
const relationship = useAppSelector((state) =>
|
items.push(
|
||||||
state.relationships.get(accountId),
|
{
|
||||||
);
|
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const menuItems = useMemo(() => {
|
|
||||||
const arr: MenuItem[] = [];
|
|
||||||
|
|
||||||
if (!account) {
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isRemote = account.acct !== account.username;
|
|
||||||
|
|
||||||
if (signedIn && !account.suspended) {
|
|
||||||
arr.push({
|
|
||||||
text: intl.formatMessage(messages.mention, {
|
text: intl.formatMessage(messages.mention, {
|
||||||
name: account.username,
|
name: account.username,
|
||||||
}),
|
}),
|
||||||
action: () => {
|
action: () => {
|
||||||
dispatch(mentionCompose(account));
|
dispatch(mentionCompose(account));
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
arr.push({
|
{
|
||||||
text: intl.formatMessage(messages.direct, {
|
text: intl.formatMessage(messages.direct, {
|
||||||
name: account.username,
|
name: account.username,
|
||||||
}),
|
}),
|
||||||
action: () => {
|
action: () => {
|
||||||
dispatch(directCompose(account));
|
dispatch(directCompose(account));
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
arr.push(null);
|
null,
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (isRemote) {
|
if (isRemote) {
|
||||||
arr.push({
|
items.push(
|
||||||
|
{
|
||||||
text: intl.formatMessage(messages.openOriginalPage),
|
text: intl.formatMessage(messages.openOriginalPage),
|
||||||
href: account.url,
|
href: account.url,
|
||||||
});
|
},
|
||||||
arr.push(null);
|
null,
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!signedIn) {
|
if (!signedIn) {
|
||||||
return arr;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relationship?.following) {
|
if (relationship?.following) {
|
||||||
if (!relationship.muting) {
|
// Timeline options
|
||||||
if (relationship.showing_reblogs) {
|
if (!relationship.muting) {
|
||||||
arr.push({
|
if (relationship.showing_reblogs) {
|
||||||
text: intl.formatMessage(messages.hideReblogs, {
|
items.push({
|
||||||
name: account.username,
|
text: intl.formatMessage(messages.hideReblogs, {
|
||||||
}),
|
name: account.username,
|
||||||
action: () => {
|
}),
|
||||||
dispatch(followAccount(account.id, { reblogs: false }));
|
action: () => {
|
||||||
},
|
dispatch(followAccount(account.id, { reblogs: false }));
|
||||||
});
|
},
|
||||||
} else {
|
});
|
||||||
arr.push({
|
} else {
|
||||||
text: intl.formatMessage(messages.showReblogs, {
|
items.push({
|
||||||
name: account.username,
|
text: intl.formatMessage(messages.showReblogs, {
|
||||||
}),
|
name: account.username,
|
||||||
action: () => {
|
}),
|
||||||
dispatch(followAccount(account.id, { reblogs: true }));
|
action: () => {
|
||||||
},
|
dispatch(followAccount(account.id, { reblogs: true }));
|
||||||
});
|
},
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
arr.push({
|
items.push(
|
||||||
|
{
|
||||||
text: intl.formatMessage(messages.languages),
|
text: intl.formatMessage(messages.languages),
|
||||||
action: () => {
|
action: () => {
|
||||||
dispatch(
|
dispatch(
|
||||||
@ -194,13 +255,295 @@ export const AccountMenu: FC<{ accountId: string }> = ({ accountId }) => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
arr.push(null);
|
null,
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isRedesignEnabled()) {
|
items.push(
|
||||||
arr.push({
|
{
|
||||||
|
text: intl.formatMessage(
|
||||||
|
relationship.endorsed ? messages.unendorse : messages.endorse,
|
||||||
|
),
|
||||||
|
action: () => {
|
||||||
|
if (relationship.endorsed) {
|
||||||
|
dispatch(unpinAccount(account.id));
|
||||||
|
} else {
|
||||||
|
dispatch(pinAccount(account.id));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: intl.formatMessage(messages.add_or_remove_from_list),
|
||||||
|
action: () => {
|
||||||
|
dispatch(
|
||||||
|
openModal({
|
||||||
|
modalType: 'LIST_ADDER',
|
||||||
|
modalProps: {
|
||||||
|
accountId: account.id,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relationship?.followed_by) {
|
||||||
|
const handleRemoveFromFollowers = () => {
|
||||||
|
dispatch(
|
||||||
|
openModal({
|
||||||
|
modalType: 'CONFIRM',
|
||||||
|
modalProps: {
|
||||||
|
title: intl.formatMessage(messages.confirmRemoveFromFollowersTitle),
|
||||||
|
message: intl.formatMessage(
|
||||||
|
messages.confirmRemoveFromFollowersMessage,
|
||||||
|
{ name: <strong>{account.acct}</strong> },
|
||||||
|
),
|
||||||
|
confirm: intl.formatMessage(
|
||||||
|
messages.confirmRemoveFromFollowersButton,
|
||||||
|
),
|
||||||
|
onConfirm: () => {
|
||||||
|
void dispatch(
|
||||||
|
removeAccountFromFollowers({ accountId: account.id }),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
text: intl.formatMessage(messages.removeFromFollowers, {
|
||||||
|
name: account.username,
|
||||||
|
}),
|
||||||
|
action: handleRemoveFromFollowers,
|
||||||
|
dangerous: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relationship?.muting) {
|
||||||
|
items.push({
|
||||||
|
text: intl.formatMessage(messages.unmute, {
|
||||||
|
name: account.username,
|
||||||
|
}),
|
||||||
|
action: () => {
|
||||||
|
dispatch(unmuteAccount(account.id));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
items.push({
|
||||||
|
text: intl.formatMessage(messages.mute, {
|
||||||
|
name: account.username,
|
||||||
|
}),
|
||||||
|
action: () => {
|
||||||
|
dispatch(initMuteModal(account));
|
||||||
|
},
|
||||||
|
dangerous: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relationship?.blocking) {
|
||||||
|
items.push({
|
||||||
|
text: intl.formatMessage(messages.unblock, {
|
||||||
|
name: account.username,
|
||||||
|
}),
|
||||||
|
action: () => {
|
||||||
|
dispatch(unblockAccount(account.id));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
items.push({
|
||||||
|
text: intl.formatMessage(messages.block, {
|
||||||
|
name: account.username,
|
||||||
|
}),
|
||||||
|
action: () => {
|
||||||
|
dispatch(blockAccount(account.id));
|
||||||
|
},
|
||||||
|
dangerous: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!account.suspended) {
|
||||||
|
items.push({
|
||||||
|
text: intl.formatMessage(messages.report, {
|
||||||
|
name: account.username,
|
||||||
|
}),
|
||||||
|
action: () => {
|
||||||
|
dispatch(initReport(account));
|
||||||
|
},
|
||||||
|
dangerous: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const remoteDomain = isRemote ? account.acct.split('@')[1] : null;
|
||||||
|
if (remoteDomain) {
|
||||||
|
items.push(null);
|
||||||
|
|
||||||
|
if (relationship?.domain_blocking) {
|
||||||
|
items.push({
|
||||||
|
text: intl.formatMessage(messages.unblockDomain, {
|
||||||
|
domain: remoteDomain,
|
||||||
|
}),
|
||||||
|
action: () => {
|
||||||
|
dispatch(unblockDomain(remoteDomain));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
items.push({
|
||||||
|
text: intl.formatMessage(messages.blockDomain, {
|
||||||
|
domain: remoteDomain,
|
||||||
|
}),
|
||||||
|
action: () => {
|
||||||
|
dispatch(initDomainBlockModal(account));
|
||||||
|
},
|
||||||
|
dangerous: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS ||
|
||||||
|
(isRemote &&
|
||||||
|
(permissions & PERMISSION_MANAGE_FEDERATION) ===
|
||||||
|
PERMISSION_MANAGE_FEDERATION)
|
||||||
|
) {
|
||||||
|
items.push(null);
|
||||||
|
if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) {
|
||||||
|
items.push({
|
||||||
|
text: intl.formatMessage(messages.admin_account, {
|
||||||
|
name: account.username,
|
||||||
|
}),
|
||||||
|
href: `/admin/accounts/${account.id}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
isRemote &&
|
||||||
|
(permissions & PERMISSION_MANAGE_FEDERATION) ===
|
||||||
|
PERMISSION_MANAGE_FEDERATION
|
||||||
|
) {
|
||||||
|
items.push({
|
||||||
|
text: intl.formatMessage(messages.admin_domain, {
|
||||||
|
domain: remoteDomain,
|
||||||
|
}),
|
||||||
|
href: `/admin/instances/${remoteDomain}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
const redesignMessages = defineMessages({
|
||||||
|
share: { id: 'account.menu.share', defaultMessage: 'Share…' },
|
||||||
|
copy: { id: 'account.menu.copy', defaultMessage: 'Copy link' },
|
||||||
|
copied: {
|
||||||
|
id: 'account.menu.copied',
|
||||||
|
defaultMessage: 'Copied account link to clipboard',
|
||||||
|
},
|
||||||
|
mention: { id: 'account.menu.mention', defaultMessage: 'Mention' },
|
||||||
|
direct: {
|
||||||
|
id: 'account.menu.direct',
|
||||||
|
defaultMessage: 'Privately mention',
|
||||||
|
},
|
||||||
|
mute: { id: 'account.menu.mute', defaultMessage: 'Mute account' },
|
||||||
|
unmute: {
|
||||||
|
id: 'account.menu.unmute',
|
||||||
|
defaultMessage: 'Unmute account',
|
||||||
|
},
|
||||||
|
block: { id: 'account.menu.block', defaultMessage: 'Block account' },
|
||||||
|
unblock: {
|
||||||
|
id: 'account.menu.unblock',
|
||||||
|
defaultMessage: 'Unblock account',
|
||||||
|
},
|
||||||
|
domainBlock: {
|
||||||
|
id: 'account.menu.block_domain',
|
||||||
|
defaultMessage: 'Block {domain}',
|
||||||
|
},
|
||||||
|
domainUnblock: {
|
||||||
|
id: 'account.menu.unblock_domain',
|
||||||
|
defaultMessage: 'Unblock {domain}',
|
||||||
|
},
|
||||||
|
report: { id: 'account.menu.report', defaultMessage: 'Report account' },
|
||||||
|
hideReblogs: {
|
||||||
|
id: 'account.menu.hide_reblogs',
|
||||||
|
defaultMessage: 'Hide boosts in timeline',
|
||||||
|
},
|
||||||
|
showReblogs: {
|
||||||
|
id: 'account.menu.show_reblogs',
|
||||||
|
defaultMessage: 'Show boosts in timeline',
|
||||||
|
},
|
||||||
|
addToList: {
|
||||||
|
id: 'account.menu.add_to_list',
|
||||||
|
defaultMessage: 'Add to list…',
|
||||||
|
},
|
||||||
|
openOriginalPage: {
|
||||||
|
id: 'account.menu.open_original_page',
|
||||||
|
defaultMessage: 'View on {domain}',
|
||||||
|
},
|
||||||
|
removeFollower: {
|
||||||
|
id: 'account.menu.remove_follower',
|
||||||
|
defaultMessage: 'Remove follower',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function redesignMenuItems({
|
||||||
|
account,
|
||||||
|
signedIn,
|
||||||
|
permissions,
|
||||||
|
intl,
|
||||||
|
relationship,
|
||||||
|
dispatch,
|
||||||
|
}: MenuItemsParams): MenuItem[] {
|
||||||
|
const items: MenuItem[] = [];
|
||||||
|
const isRemote = account.acct !== account.username;
|
||||||
|
const remoteDomain = isRemote ? account.acct.split('@')[1] : null;
|
||||||
|
|
||||||
|
// Share and copy link options
|
||||||
|
if (account.url) {
|
||||||
|
if ('share' in navigator) {
|
||||||
|
items.push({
|
||||||
|
text: intl.formatMessage(redesignMessages.share),
|
||||||
|
action: () => {
|
||||||
|
void navigator.share({
|
||||||
|
url: account.url,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
icon: ShareIcon,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
items.push(
|
||||||
|
{
|
||||||
|
text: intl.formatMessage(redesignMessages.copy),
|
||||||
|
action: () => {
|
||||||
|
void navigator.clipboard.writeText(account.url);
|
||||||
|
dispatch(showAlert({ message: redesignMessages.copied }));
|
||||||
|
},
|
||||||
|
icon: LinkIcon,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mention and direct message options
|
||||||
|
if (signedIn && !account.suspended) {
|
||||||
|
items.push(
|
||||||
|
{
|
||||||
|
text: intl.formatMessage(redesignMessages.mention),
|
||||||
|
action: () => {
|
||||||
|
dispatch(mentionCompose(account));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
text: intl.formatMessage(redesignMessages.direct),
|
||||||
|
action: () => {
|
||||||
|
dispatch(directCompose(account));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
{
|
||||||
text: intl.formatMessage(
|
text: intl.formatMessage(
|
||||||
relationship?.note ? messages.editNote : messages.addNote,
|
relationship?.note ? messages.editNote : messages.addNote,
|
||||||
),
|
),
|
||||||
@ -214,27 +557,34 @@ export const AccountMenu: FC<{ accountId: string }> = ({ accountId }) => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
icon: EditIcon,
|
||||||
if (!relationship?.following) {
|
},
|
||||||
arr.push(null);
|
null,
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relationship?.following) {
|
// Open on remote page.
|
||||||
arr.push({
|
if (isRemote) {
|
||||||
text: intl.formatMessage(
|
items.push(
|
||||||
relationship.endorsed ? messages.unendorse : messages.endorse,
|
{
|
||||||
),
|
text: intl.formatMessage(redesignMessages.openOriginalPage, {
|
||||||
action: () => {
|
domain: remoteDomain,
|
||||||
if (relationship.endorsed) {
|
}),
|
||||||
dispatch(unpinAccount(account.id));
|
href: account.url,
|
||||||
} else {
|
},
|
||||||
dispatch(pinAccount(account.id));
|
null,
|
||||||
}
|
);
|
||||||
},
|
}
|
||||||
});
|
|
||||||
arr.push({
|
if (!signedIn) {
|
||||||
text: intl.formatMessage(messages.add_or_remove_from_list),
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
// List and featuring options
|
||||||
|
if (relationship?.following) {
|
||||||
|
items.push(
|
||||||
|
{
|
||||||
|
text: intl.formatMessage(redesignMessages.addToList),
|
||||||
action: () => {
|
action: () => {
|
||||||
dispatch(
|
dispatch(
|
||||||
openModal({
|
openModal({
|
||||||
@ -245,12 +595,76 @@ export const AccountMenu: FC<{ accountId: string }> = ({ accountId }) => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
arr.push(null);
|
{
|
||||||
|
text: intl.formatMessage(
|
||||||
|
relationship.endorsed ? messages.unendorse : messages.endorse,
|
||||||
|
),
|
||||||
|
action: () => {
|
||||||
|
if (relationship.endorsed) {
|
||||||
|
dispatch(unpinAccount(account.id));
|
||||||
|
} else {
|
||||||
|
dispatch(pinAccount(account.id));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Timeline options
|
||||||
|
if (!relationship.muting) {
|
||||||
|
items.push(
|
||||||
|
{
|
||||||
|
text: intl.formatMessage(
|
||||||
|
relationship.showing_reblogs
|
||||||
|
? redesignMessages.hideReblogs
|
||||||
|
: redesignMessages.showReblogs,
|
||||||
|
),
|
||||||
|
action: () => {
|
||||||
|
dispatch(
|
||||||
|
followAccount(account.id, {
|
||||||
|
reblogs: !relationship.showing_reblogs,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: intl.formatMessage(messages.languages),
|
||||||
|
action: () => {
|
||||||
|
dispatch(
|
||||||
|
openModal({
|
||||||
|
modalType: 'SUBSCRIBED_LANGUAGES',
|
||||||
|
modalProps: {
|
||||||
|
accountId: account.id,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relationship?.followed_by) {
|
items.push(
|
||||||
const handleRemoveFromFollowers = () => {
|
{
|
||||||
|
text: intl.formatMessage(
|
||||||
|
relationship.muting ? redesignMessages.unmute : redesignMessages.mute,
|
||||||
|
),
|
||||||
|
action: () => {
|
||||||
|
if (relationship.muting) {
|
||||||
|
dispatch(unmuteAccount(account.id));
|
||||||
|
} else {
|
||||||
|
dispatch(initMuteModal(account));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relationship?.followed_by) {
|
||||||
|
items.push({
|
||||||
|
text: intl.formatMessage(redesignMessages.removeFollower),
|
||||||
|
action: () => {
|
||||||
dispatch(
|
dispatch(
|
||||||
openModal({
|
openModal({
|
||||||
modalType: 'CONFIRM',
|
modalType: 'CONFIRM',
|
||||||
@ -273,134 +687,91 @@ export const AccountMenu: FC<{ accountId: string }> = ({ accountId }) => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
};
|
},
|
||||||
|
dangerous: true,
|
||||||
|
icon: PersonRemoveIcon,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
arr.push({
|
items.push({
|
||||||
text: intl.formatMessage(messages.removeFromFollowers, {
|
text: intl.formatMessage(
|
||||||
name: account.username,
|
relationship?.blocking
|
||||||
}),
|
? redesignMessages.unblock
|
||||||
action: handleRemoveFromFollowers,
|
: redesignMessages.block,
|
||||||
dangerous: true,
|
),
|
||||||
});
|
action: () => {
|
||||||
}
|
if (relationship?.blocking) {
|
||||||
|
dispatch(unblockAccount(account.id));
|
||||||
if (relationship?.muting) {
|
|
||||||
arr.push({
|
|
||||||
text: intl.formatMessage(messages.unmute, {
|
|
||||||
name: account.username,
|
|
||||||
}),
|
|
||||||
action: () => {
|
|
||||||
dispatch(unmuteAccount(account.id));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
arr.push({
|
|
||||||
text: intl.formatMessage(messages.mute, {
|
|
||||||
name: account.username,
|
|
||||||
}),
|
|
||||||
action: () => {
|
|
||||||
dispatch(initMuteModal(account));
|
|
||||||
},
|
|
||||||
dangerous: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (relationship?.blocking) {
|
|
||||||
arr.push({
|
|
||||||
text: intl.formatMessage(messages.unblock, {
|
|
||||||
name: account.username,
|
|
||||||
}),
|
|
||||||
action: () => {
|
|
||||||
dispatch(unblockAccount(account.id));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
arr.push({
|
|
||||||
text: intl.formatMessage(messages.block, {
|
|
||||||
name: account.username,
|
|
||||||
}),
|
|
||||||
action: () => {
|
|
||||||
dispatch(blockAccount(account.id));
|
|
||||||
},
|
|
||||||
dangerous: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!account.suspended) {
|
|
||||||
arr.push({
|
|
||||||
text: intl.formatMessage(messages.report, {
|
|
||||||
name: account.username,
|
|
||||||
}),
|
|
||||||
action: () => {
|
|
||||||
dispatch(initReport(account));
|
|
||||||
},
|
|
||||||
dangerous: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const remoteDomain = isRemote ? account.acct.split('@')[1] : null;
|
|
||||||
if (remoteDomain) {
|
|
||||||
arr.push(null);
|
|
||||||
|
|
||||||
if (relationship?.domain_blocking) {
|
|
||||||
arr.push({
|
|
||||||
text: intl.formatMessage(messages.unblockDomain, {
|
|
||||||
domain: remoteDomain,
|
|
||||||
}),
|
|
||||||
action: () => {
|
|
||||||
dispatch(unblockDomain(remoteDomain));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
arr.push({
|
dispatch(blockAccount(account.id));
|
||||||
text: intl.formatMessage(messages.blockDomain, {
|
|
||||||
domain: remoteDomain,
|
|
||||||
}),
|
|
||||||
action: () => {
|
|
||||||
dispatch(initDomainBlockModal(account));
|
|
||||||
},
|
|
||||||
dangerous: true,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
dangerous: true,
|
||||||
|
icon: BlockIcon,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!account.suspended) {
|
||||||
|
items.push({
|
||||||
|
text: intl.formatMessage(redesignMessages.report),
|
||||||
|
action: () => {
|
||||||
|
dispatch(initReport(account));
|
||||||
|
},
|
||||||
|
dangerous: true,
|
||||||
|
icon: ReportIcon,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remoteDomain) {
|
||||||
|
items.push({
|
||||||
|
text: intl.formatMessage(
|
||||||
|
relationship?.domain_blocking
|
||||||
|
? redesignMessages.domainUnblock
|
||||||
|
: redesignMessages.domainBlock,
|
||||||
|
{
|
||||||
|
domain: remoteDomain,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
action: () => {
|
||||||
|
if (relationship?.domain_blocking) {
|
||||||
|
dispatch(unblockDomain(remoteDomain));
|
||||||
|
} else {
|
||||||
|
dispatch(initDomainBlockModal(account));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dangerous: true,
|
||||||
|
icon: BlockIcon,
|
||||||
|
iconId: 'domain-block',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS ||
|
||||||
|
(isRemote &&
|
||||||
|
(permissions & PERMISSION_MANAGE_FEDERATION) ===
|
||||||
|
PERMISSION_MANAGE_FEDERATION)
|
||||||
|
) {
|
||||||
|
items.push(null);
|
||||||
|
if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) {
|
||||||
|
items.push({
|
||||||
|
text: intl.formatMessage(messages.admin_account, {
|
||||||
|
name: account.username,
|
||||||
|
}),
|
||||||
|
href: `/admin/accounts/${account.id}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
(permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS ||
|
remoteDomain &&
|
||||||
(isRemote &&
|
(permissions & PERMISSION_MANAGE_FEDERATION) ===
|
||||||
(permissions & PERMISSION_MANAGE_FEDERATION) ===
|
PERMISSION_MANAGE_FEDERATION
|
||||||
PERMISSION_MANAGE_FEDERATION)
|
|
||||||
) {
|
) {
|
||||||
arr.push(null);
|
items.push({
|
||||||
if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) {
|
text: intl.formatMessage(messages.admin_domain, {
|
||||||
arr.push({
|
domain: remoteDomain,
|
||||||
text: intl.formatMessage(messages.admin_account, {
|
}),
|
||||||
name: account.username,
|
href: `/admin/instances/${remoteDomain}`,
|
||||||
}),
|
});
|
||||||
href: `/admin/accounts/${account.id}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
isRemote &&
|
|
||||||
(permissions & PERMISSION_MANAGE_FEDERATION) ===
|
|
||||||
PERMISSION_MANAGE_FEDERATION
|
|
||||||
) {
|
|
||||||
arr.push({
|
|
||||||
text: intl.formatMessage(messages.admin_domain, {
|
|
||||||
domain: remoteDomain,
|
|
||||||
}),
|
|
||||||
href: `/admin/instances/${remoteDomain}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return arr;
|
return items;
|
||||||
}, [account, signedIn, permissions, intl, relationship, dispatch]);
|
}
|
||||||
return (
|
|
||||||
<Dropdown
|
|
||||||
disabled={menuItems.length === 0}
|
|
||||||
items={menuItems}
|
|
||||||
icon='ellipsis-v'
|
|
||||||
iconComponent={MoreHorizIcon}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
|
.barWrapper {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
.nameWrapper {
|
.nameWrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
@ -45,6 +49,43 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$button-breakpoint: 420px;
|
||||||
|
$button-fallback-breakpoint: #{$button-breakpoint} + 55px;
|
||||||
|
|
||||||
|
.buttonsDesktop {
|
||||||
|
@container (width < #{$button-breakpoint}) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (not (container-type: inline-size)) {
|
||||||
|
@media (max-width: #{$button-fallback-breakpoint}) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonsMobile {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 55px; // Height of bottom nav bar.
|
||||||
|
|
||||||
|
@container (width >= #{$button-breakpoint}) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (not (container-type: inline-size)) {
|
||||||
|
@media (min-width: (#{$button-fallback-breakpoint} + 1px)) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonsMobileIsStuck {
|
||||||
|
padding: 12px 16px;
|
||||||
|
background-color: var(--color-bg-primary);
|
||||||
|
border-top: 1px solid var(--color-border-primary);
|
||||||
|
margin: 0 -20px;
|
||||||
|
}
|
||||||
|
|
||||||
.badge {
|
.badge {
|
||||||
background-color: var(--color-bg-secondary);
|
background-color: var(--color-bg-secondary);
|
||||||
border: none;
|
border: none;
|
||||||
@ -84,36 +125,112 @@ svg.badgeIcon {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.fieldList {
|
.fieldWrapper {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fieldWrapper::before,
|
||||||
|
.fieldWrapper::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 40px;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s ease-in-out;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fieldWrapper::before {
|
||||||
|
left: 0;
|
||||||
|
background: linear-gradient(
|
||||||
|
to left,
|
||||||
|
transparent 0%,
|
||||||
|
var(--color-bg-primary) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fieldWrapper::after {
|
||||||
|
right: 0;
|
||||||
|
background: linear-gradient(
|
||||||
|
to right,
|
||||||
|
transparent 0%,
|
||||||
|
var(--color-bg-primary) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fieldWrapperLeft::before {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fieldWrapperRight::after {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fieldList {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
gap: 4px;
|
||||||
|
scroll-snap-type: x mandatory;
|
||||||
|
scroll-padding-left: 40px;
|
||||||
|
scroll-padding-right: 40px;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
overflow-x: scroll;
|
||||||
|
scrollbar-width: none;
|
||||||
|
overflow-y: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fieldCard {
|
.fieldCard {
|
||||||
position: relative;
|
scroll-snap-align: start;
|
||||||
|
|
||||||
a {
|
&:focus-visible,
|
||||||
color: var(--color-text-brand);
|
&:focus-within {
|
||||||
text-decoration: none;
|
outline: var(--outline-focus-default);
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(dt, dd) {
|
||||||
|
max-width: 200px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.fieldCardVerified {
|
.fieldCardVerified {
|
||||||
background-color: var(--color-bg-brand-softer);
|
background-color: var(--color-bg-brand-softer);
|
||||||
|
|
||||||
dt {
|
|
||||||
padding-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fieldIconVerified {
|
|
||||||
position: absolute;
|
|
||||||
top: 4px;
|
|
||||||
right: 4px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fieldIconVerified {
|
.fieldArrowButton {
|
||||||
width: 1rem;
|
position: absolute;
|
||||||
height: 1rem;
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background-color: var(--color-bg-elevated);
|
||||||
|
box-shadow: 0 1px 4px 0 var(--color-shadow-primary);
|
||||||
|
border-radius: 9999px;
|
||||||
|
transition:
|
||||||
|
color 0.2s ease-in-out,
|
||||||
|
background-color 0.2s ease-in-out;
|
||||||
|
outline-offset: 2px;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:focus-visible {
|
||||||
|
background-color: color-mix(
|
||||||
|
in oklab,
|
||||||
|
var(--color-bg-brand-base) var(--overlay-strength-brand),
|
||||||
|
var(--color-bg-elevated)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.fieldNumbersWrapper {
|
.fieldNumbersWrapper {
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { useParams } from 'react-router';
|
|||||||
import { fetchFeaturedTags } from '@/mastodon/actions/featured_tags';
|
import { fetchFeaturedTags } from '@/mastodon/actions/featured_tags';
|
||||||
import { useAppHistory } from '@/mastodon/components/router';
|
import { useAppHistory } from '@/mastodon/components/router';
|
||||||
import { Tag } from '@/mastodon/components/tags/tag';
|
import { Tag } from '@/mastodon/components/tags/tag';
|
||||||
import { useOverflow } from '@/mastodon/hooks/useOverflow';
|
import { useOverflowButton } from '@/mastodon/hooks/useOverflow';
|
||||||
import { selectAccountFeaturedTags } from '@/mastodon/selectors/accounts';
|
import { selectAccountFeaturedTags } from '@/mastodon/selectors/accounts';
|
||||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ export const FeaturedTags: FC<{ accountId: string }> = ({ accountId }) => {
|
|||||||
// Get list of tags with overflow handling.
|
// Get list of tags with overflow handling.
|
||||||
const [showOverflow, setShowOverflow] = useState(false);
|
const [showOverflow, setShowOverflow] = useState(false);
|
||||||
const { hiddenCount, wrapperRef, listRef, hiddenIndex, maxWidth } =
|
const { hiddenCount, wrapperRef, listRef, hiddenIndex, maxWidth } =
|
||||||
useOverflow();
|
useOverflowButton();
|
||||||
|
|
||||||
// Handle whether to show all tags.
|
// Handle whether to show all tags.
|
||||||
const handleOverflowClick: MouseEventHandler = useCallback(() => {
|
const handleOverflowClick: MouseEventHandler = useCallback(() => {
|
||||||
|
|||||||
@ -16,7 +16,11 @@ import type {
|
|||||||
import { Button } from 'mastodon/components/button';
|
import { Button } from 'mastodon/components/button';
|
||||||
import { Column } from 'mastodon/components/column';
|
import { Column } from 'mastodon/components/column';
|
||||||
import { ColumnHeader } from 'mastodon/components/column_header';
|
import { ColumnHeader } from 'mastodon/components/column_header';
|
||||||
import { CheckboxField, TextAreaField } from 'mastodon/components/form_fields';
|
import {
|
||||||
|
CheckboxField,
|
||||||
|
FormStack,
|
||||||
|
TextAreaField,
|
||||||
|
} from 'mastodon/components/form_fields';
|
||||||
import { TextInputField } from 'mastodon/components/form_fields/text_input_field';
|
import { TextInputField } from 'mastodon/components/form_fields/text_input_field';
|
||||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||||
import {
|
import {
|
||||||
@ -129,88 +133,80 @@ const CollectionSettings: React.FC<{
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className='simple_form app-form' onSubmit={handleSubmit}>
|
<FormStack as='form' onSubmit={handleSubmit}>
|
||||||
<div className='fields-group'>
|
<TextInputField
|
||||||
<TextInputField
|
required
|
||||||
required
|
label={
|
||||||
label={
|
<FormattedMessage
|
||||||
<FormattedMessage
|
id='collections.collection_name'
|
||||||
id='collections.collection_name'
|
defaultMessage='Name'
|
||||||
defaultMessage='Name'
|
/>
|
||||||
/>
|
}
|
||||||
}
|
hint={
|
||||||
hint={
|
<FormattedMessage
|
||||||
<FormattedMessage
|
id='collections.name_length_hint'
|
||||||
id='collections.name_length_hint'
|
defaultMessage='40 characters limit'
|
||||||
defaultMessage='40 characters limit'
|
/>
|
||||||
/>
|
}
|
||||||
}
|
value={name}
|
||||||
value={name}
|
onChange={handleNameChange}
|
||||||
onChange={handleNameChange}
|
maxLength={40}
|
||||||
maxLength={40}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='fields-group'>
|
<TextAreaField
|
||||||
<TextAreaField
|
required
|
||||||
required
|
label={
|
||||||
label={
|
<FormattedMessage
|
||||||
<FormattedMessage
|
id='collections.collection_description'
|
||||||
id='collections.collection_description'
|
defaultMessage='Description'
|
||||||
defaultMessage='Description'
|
/>
|
||||||
/>
|
}
|
||||||
}
|
hint={
|
||||||
hint={
|
<FormattedMessage
|
||||||
<FormattedMessage
|
id='collections.description_length_hint'
|
||||||
id='collections.description_length_hint'
|
defaultMessage='100 characters limit'
|
||||||
defaultMessage='100 characters limit'
|
/>
|
||||||
/>
|
}
|
||||||
}
|
value={description}
|
||||||
value={description}
|
onChange={handleDescriptionChange}
|
||||||
onChange={handleDescriptionChange}
|
maxLength={100}
|
||||||
maxLength={100}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='fields-group'>
|
<TextInputField
|
||||||
<TextInputField
|
required={false}
|
||||||
required={false}
|
label={
|
||||||
label={
|
<FormattedMessage
|
||||||
<FormattedMessage
|
id='collections.collection_topic'
|
||||||
id='collections.collection_topic'
|
defaultMessage='Topic'
|
||||||
defaultMessage='Topic'
|
/>
|
||||||
/>
|
}
|
||||||
}
|
hint={
|
||||||
hint={
|
<FormattedMessage
|
||||||
<FormattedMessage
|
id='collections.topic_hint'
|
||||||
id='collections.topic_hint'
|
defaultMessage='Add a hashtag that helps others understand the main topic of this collection.'
|
||||||
defaultMessage='Add a hashtag that helps others understand the main topic of this collection.'
|
/>
|
||||||
/>
|
}
|
||||||
}
|
value={topic}
|
||||||
value={topic}
|
onChange={handleTopicChange}
|
||||||
onChange={handleTopicChange}
|
maxLength={40}
|
||||||
maxLength={40}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='fields-group'>
|
<CheckboxField
|
||||||
<CheckboxField
|
label={
|
||||||
label={
|
<FormattedMessage
|
||||||
<FormattedMessage
|
id='collections.mark_as_sensitive'
|
||||||
id='collections.mark_as_sensitive'
|
defaultMessage='Mark as sensitive'
|
||||||
defaultMessage='Mark as sensitive'
|
/>
|
||||||
/>
|
}
|
||||||
}
|
hint={
|
||||||
hint={
|
<FormattedMessage
|
||||||
<FormattedMessage
|
id='collections.mark_as_sensitive_hint'
|
||||||
id='collections.mark_as_sensitive_hint'
|
defaultMessage="Hides the collection's description and accounts behind a content warning. The collection name will still be visible."
|
||||||
defaultMessage="Hides the collection's description and accounts behind a content warning. The collection name will still be visible."
|
/>
|
||||||
/>
|
}
|
||||||
}
|
checked={sensitive}
|
||||||
checked={sensitive}
|
onChange={handleSensitiveChange}
|
||||||
onChange={handleSensitiveChange}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='actions'>
|
<div className='actions'>
|
||||||
<Button type='submit'>
|
<Button type='submit'>
|
||||||
@ -221,7 +217,7 @@ const CollectionSettings: React.FC<{
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</FormStack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import { useEffect, useCallback, useRef, useState, useId } from 'react';
|
import { useEffect, useCallback, useId } from 'react';
|
||||||
|
|
||||||
import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
|
import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
|
||||||
|
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { useOverflowScroll } from '@/mastodon/hooks/useOverflow';
|
||||||
import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react';
|
import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react';
|
||||||
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
|
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
|
||||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||||
@ -178,74 +179,24 @@ export const InlineFollowSuggestions: React.FC<{ hidden?: boolean }> = ({
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||||
state.settings.getIn(['dismissed_banners', DISMISSIBLE_ID]) as boolean,
|
state.settings.getIn(['dismissed_banners', DISMISSIBLE_ID]) as boolean,
|
||||||
);
|
);
|
||||||
const bodyRef = useRef<HTMLDivElement>(null);
|
|
||||||
const [canScrollLeft, setCanScrollLeft] = useState(false);
|
|
||||||
const [canScrollRight, setCanScrollRight] = useState(true);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void dispatch(fetchSuggestions());
|
void dispatch(fetchSuggestions());
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!bodyRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getComputedStyle(bodyRef.current).direction === 'rtl') {
|
|
||||||
setCanScrollLeft(
|
|
||||||
bodyRef.current.clientWidth - bodyRef.current.scrollLeft <
|
|
||||||
bodyRef.current.scrollWidth,
|
|
||||||
);
|
|
||||||
setCanScrollRight(bodyRef.current.scrollLeft < 0);
|
|
||||||
} else {
|
|
||||||
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
|
|
||||||
setCanScrollRight(
|
|
||||||
bodyRef.current.scrollLeft + bodyRef.current.clientWidth <
|
|
||||||
bodyRef.current.scrollWidth,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [setCanScrollRight, setCanScrollLeft, suggestions]);
|
|
||||||
|
|
||||||
const handleLeftNav = useCallback(() => {
|
|
||||||
if (!bodyRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bodyRef.current.scrollLeft -= 200;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleRightNav = useCallback(() => {
|
|
||||||
if (!bodyRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bodyRef.current.scrollLeft += 200;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleScroll = useCallback(() => {
|
|
||||||
if (!bodyRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getComputedStyle(bodyRef.current).direction === 'rtl') {
|
|
||||||
setCanScrollLeft(
|
|
||||||
bodyRef.current.clientWidth - bodyRef.current.scrollLeft <
|
|
||||||
bodyRef.current.scrollWidth,
|
|
||||||
);
|
|
||||||
setCanScrollRight(bodyRef.current.scrollLeft < 0);
|
|
||||||
} else {
|
|
||||||
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
|
|
||||||
setCanScrollRight(
|
|
||||||
bodyRef.current.scrollLeft + bodyRef.current.clientWidth <
|
|
||||||
bodyRef.current.scrollWidth,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [setCanScrollRight, setCanScrollLeft]);
|
|
||||||
|
|
||||||
const handleDismiss = useCallback(() => {
|
const handleDismiss = useCallback(() => {
|
||||||
dispatch(changeSetting(['dismissed_banners', DISMISSIBLE_ID], true));
|
dispatch(changeSetting(['dismissed_banners', DISMISSIBLE_ID], true));
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
bodyRef,
|
||||||
|
handleScroll,
|
||||||
|
canScrollLeft,
|
||||||
|
canScrollRight,
|
||||||
|
handleLeftNav,
|
||||||
|
handleRightNav,
|
||||||
|
} = useOverflowScroll({ absoluteDistance: true });
|
||||||
|
|
||||||
if (dismissed || (!isLoading && suggestions.length === 0)) {
|
if (dismissed || (!isLoading && suggestions.length === 0)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -88,7 +88,6 @@ export const MODAL_COMPONENTS = {
|
|||||||
'ANNUAL_REPORT': AnnualReportModal,
|
'ANNUAL_REPORT': AnnualReportModal,
|
||||||
'COMPOSE_PRIVACY': () => Promise.resolve({ default: VisibilityModal }),
|
'COMPOSE_PRIVACY': () => Promise.resolve({ default: VisibilityModal }),
|
||||||
'ACCOUNT_NOTE': () => import('@/mastodon/features/account_timeline/modals/note_modal').then(module => ({ default: module.AccountNoteModal })),
|
'ACCOUNT_NOTE': () => import('@/mastodon/features/account_timeline/modals/note_modal').then(module => ({ default: module.AccountNoteModal })),
|
||||||
'ACCOUNT_FIELDS': () => import('mastodon/features/account_timeline/components/fields_modal.tsx').then(module => ({ default: module.AccountFieldsModal })),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class ModalRoot extends PureComponent {
|
export default class ModalRoot extends PureComponent {
|
||||||
|
|||||||
@ -1,14 +1,15 @@
|
|||||||
|
import type { MutableRefObject, RefCallback } from 'react';
|
||||||
import { useState, useRef, useCallback, useEffect } from 'react';
|
import { useState, useRef, useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate and manage overflow of child elements within a container.
|
* Hook to manage overflow of items in a container with a "more" button.
|
||||||
*
|
*
|
||||||
* To use, wire up the `wrapperRef` to the container element, and the `listRef` to the
|
* To use, wire up the `wrapperRef` to the container element, and the `listRef` to the
|
||||||
* child element that contains the items to be measured. If autoResize is true,
|
* child element that contains the items to be measured. If autoResize is true,
|
||||||
* the list element will have its max-width set to prevent wrapping. The listRef element
|
* the list element will have its max-width set to prevent wrapping. The listRef element
|
||||||
* requires both position:relative and overflow:hidden styles to work correctly.
|
* requires both position:relative and overflow:hidden styles to work correctly.
|
||||||
*/
|
*/
|
||||||
export function useOverflow({
|
export function useOverflowButton({
|
||||||
autoResize,
|
autoResize,
|
||||||
padding = 4,
|
padding = 4,
|
||||||
}: { autoResize?: boolean; padding?: number } = {}) {
|
}: { autoResize?: boolean; padding?: number } = {}) {
|
||||||
@ -76,6 +77,111 @@ export function useOverflow({
|
|||||||
}
|
}
|
||||||
}, [autoResize, maxWidth]);
|
}, [autoResize, maxWidth]);
|
||||||
|
|
||||||
|
const { listRefCallback, wrapperRefCallback } = useOverflowObservers({
|
||||||
|
onRecalculate: handleRecalculate,
|
||||||
|
onListRef: listRef,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
hiddenCount,
|
||||||
|
hasOverflow: hiddenCount > 0,
|
||||||
|
wrapperRef: wrapperRefCallback,
|
||||||
|
hiddenIndex,
|
||||||
|
maxWidth,
|
||||||
|
listRef: listRefCallback,
|
||||||
|
recalculate: handleRecalculate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useOverflowScroll({
|
||||||
|
widthOffset = 200,
|
||||||
|
absoluteDistance = false,
|
||||||
|
} = {}) {
|
||||||
|
const [canScrollLeft, setCanScrollLeft] = useState(false);
|
||||||
|
const [canScrollRight, setCanScrollRight] = useState(false);
|
||||||
|
|
||||||
|
const bodyRef = useRef<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
// Recalculate scrollable state
|
||||||
|
const handleRecalculate = useCallback(() => {
|
||||||
|
if (!bodyRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getComputedStyle(bodyRef.current).direction === 'rtl') {
|
||||||
|
setCanScrollLeft(
|
||||||
|
bodyRef.current.clientWidth - bodyRef.current.scrollLeft <
|
||||||
|
bodyRef.current.scrollWidth,
|
||||||
|
);
|
||||||
|
setCanScrollRight(bodyRef.current.scrollLeft < 0);
|
||||||
|
} else {
|
||||||
|
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
|
||||||
|
setCanScrollRight(
|
||||||
|
bodyRef.current.scrollLeft + bodyRef.current.clientWidth <
|
||||||
|
bodyRef.current.scrollWidth,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const { wrapperRefCallback } = useOverflowObservers({
|
||||||
|
onRecalculate: handleRecalculate,
|
||||||
|
onWrapperRef: bodyRef,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleRecalculate();
|
||||||
|
}, [handleRecalculate]);
|
||||||
|
|
||||||
|
// Handle scroll event using requestAnimationFrame to avoid excessive recalculations.
|
||||||
|
const handleScroll = useCallback(() => {
|
||||||
|
requestAnimationFrame(handleRecalculate);
|
||||||
|
}, [handleRecalculate]);
|
||||||
|
|
||||||
|
// Jump a full screen minus the width offset so that we don't skip a lot.
|
||||||
|
const handleLeftNav = useCallback(() => {
|
||||||
|
if (!bodyRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyRef.current.scrollLeft -= absoluteDistance
|
||||||
|
? widthOffset
|
||||||
|
: Math.max(widthOffset, bodyRef.current.clientWidth - widthOffset);
|
||||||
|
}, [absoluteDistance, widthOffset]);
|
||||||
|
|
||||||
|
const handleRightNav = useCallback(() => {
|
||||||
|
if (!bodyRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyRef.current.scrollLeft += absoluteDistance
|
||||||
|
? widthOffset
|
||||||
|
: Math.max(widthOffset, bodyRef.current.clientWidth - widthOffset);
|
||||||
|
}, [absoluteDistance, widthOffset]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
bodyRef: wrapperRefCallback,
|
||||||
|
canScrollLeft,
|
||||||
|
canScrollRight,
|
||||||
|
handleLeftNav,
|
||||||
|
handleRightNav,
|
||||||
|
handleScroll,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useOverflowObservers({
|
||||||
|
onRecalculate,
|
||||||
|
onListRef,
|
||||||
|
onWrapperRef,
|
||||||
|
}: {
|
||||||
|
onRecalculate: () => void;
|
||||||
|
onListRef?: RefCallback<HTMLElement> | MutableRefObject<HTMLElement | null>;
|
||||||
|
onWrapperRef?:
|
||||||
|
| RefCallback<HTMLElement>
|
||||||
|
| MutableRefObject<HTMLElement | null>;
|
||||||
|
}) {
|
||||||
|
// This is the item container element.
|
||||||
|
const listRef = useRef<HTMLElement | null>(null);
|
||||||
|
|
||||||
// Set up observers to watch for size and content changes.
|
// Set up observers to watch for size and content changes.
|
||||||
const resizeObserverRef = useRef<ResizeObserver | null>(null);
|
const resizeObserverRef = useRef<ResizeObserver | null>(null);
|
||||||
const mutationObserverRef = useRef<MutationObserver | null>(null);
|
const mutationObserverRef = useRef<MutationObserver | null>(null);
|
||||||
@ -83,10 +189,10 @@ export function useOverflow({
|
|||||||
// Helper to get or create the resize observer.
|
// Helper to get or create the resize observer.
|
||||||
const resizeObserver = useCallback(() => {
|
const resizeObserver = useCallback(() => {
|
||||||
const observer = (resizeObserverRef.current ??= new ResizeObserver(
|
const observer = (resizeObserverRef.current ??= new ResizeObserver(
|
||||||
handleRecalculate,
|
onRecalculate,
|
||||||
));
|
));
|
||||||
return observer;
|
return observer;
|
||||||
}, [handleRecalculate]);
|
}, [onRecalculate]);
|
||||||
|
|
||||||
// Iterate through children and observe them for size changes.
|
// Iterate through children and observe them for size changes.
|
||||||
const handleChildrenChange = useCallback(() => {
|
const handleChildrenChange = useCallback(() => {
|
||||||
@ -100,8 +206,8 @@ export function useOverflow({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handleRecalculate();
|
onRecalculate();
|
||||||
}, [handleRecalculate, resizeObserver]);
|
}, [onRecalculate, resizeObserver]);
|
||||||
|
|
||||||
// Helper to get or create the mutation observer.
|
// Helper to get or create the mutation observer.
|
||||||
const mutationObserver = useCallback(() => {
|
const mutationObserver = useCallback(() => {
|
||||||
@ -129,9 +235,14 @@ export function useOverflow({
|
|||||||
if (node) {
|
if (node) {
|
||||||
wrapperRef.current = node;
|
wrapperRef.current = node;
|
||||||
handleObserve();
|
handleObserve();
|
||||||
|
if (typeof onWrapperRef === 'function') {
|
||||||
|
onWrapperRef(node);
|
||||||
|
} else if (onWrapperRef && 'current' in onWrapperRef) {
|
||||||
|
onWrapperRef.current = node;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[handleObserve],
|
[handleObserve, onWrapperRef],
|
||||||
);
|
);
|
||||||
|
|
||||||
// If there are changes to the children, recalculate which are visible.
|
// If there are changes to the children, recalculate which are visible.
|
||||||
@ -140,9 +251,14 @@ export function useOverflow({
|
|||||||
if (node) {
|
if (node) {
|
||||||
listRef.current = node;
|
listRef.current = node;
|
||||||
handleObserve();
|
handleObserve();
|
||||||
|
if (typeof onListRef === 'function') {
|
||||||
|
onListRef(node);
|
||||||
|
} else if (onListRef && 'current' in onListRef) {
|
||||||
|
onListRef.current = node;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[handleObserve],
|
[handleObserve, onListRef],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -161,12 +277,7 @@ export function useOverflow({
|
|||||||
}, [handleObserve]);
|
}, [handleObserve]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hiddenCount,
|
wrapperRefCallback,
|
||||||
hasOverflow: hiddenCount > 0,
|
listRefCallback,
|
||||||
wrapperRef: wrapperRefCallback,
|
|
||||||
hiddenIndex,
|
|
||||||
maxWidth,
|
|
||||||
listRef: listRefCallback,
|
|
||||||
recalculate: handleRecalculate,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,8 +17,15 @@
|
|||||||
"account.activity": "Актыўнасць",
|
"account.activity": "Актыўнасць",
|
||||||
"account.add_note": "Дадаць асабістую нататку",
|
"account.add_note": "Дадаць асабістую нататку",
|
||||||
"account.add_or_remove_from_list": "Дадаць або выдаліць са спісаў",
|
"account.add_or_remove_from_list": "Дадаць або выдаліць са спісаў",
|
||||||
|
"account.badges.admin": "Адмін",
|
||||||
|
"account.badges.blocked": "Заблакіраваны",
|
||||||
"account.badges.bot": "Бот",
|
"account.badges.bot": "Бот",
|
||||||
|
"account.badges.domain_blocked": "Заблакіраваны дамен",
|
||||||
|
"account.badges.follows_you": "Падпісаны(-ая) на Вас",
|
||||||
"account.badges.group": "Група",
|
"account.badges.group": "Група",
|
||||||
|
"account.badges.muted": "Ігнаруецца",
|
||||||
|
"account.badges.mutuals": "Вы падпісаныя адно на аднаго",
|
||||||
|
"account.badges.requested_to_follow": "Адправіў(-ла) запыт на падпіску",
|
||||||
"account.block": "Заблакіраваць @{name}",
|
"account.block": "Заблакіраваць @{name}",
|
||||||
"account.block_domain": "Заблакіраваць дамен {domain}",
|
"account.block_domain": "Заблакіраваць дамен {domain}",
|
||||||
"account.block_short": "Заблакіраваць",
|
"account.block_short": "Заблакіраваць",
|
||||||
@ -79,7 +86,7 @@
|
|||||||
"account.mute_short": "Ігнараваць",
|
"account.mute_short": "Ігнараваць",
|
||||||
"account.muted": "Ігнаруецца",
|
"account.muted": "Ігнаруецца",
|
||||||
"account.muting": "Ігнараванне",
|
"account.muting": "Ігнараванне",
|
||||||
"account.mutual": "Вы падпісаны адно на аднаго",
|
"account.mutual": "Вы падпісаныя адно на аднаго",
|
||||||
"account.no_bio": "Апісанне адсутнічае.",
|
"account.no_bio": "Апісанне адсутнічае.",
|
||||||
"account.node_modal.callout": "Асабістыя нататкі бачныя толькі Вам.",
|
"account.node_modal.callout": "Асабістыя нататкі бачныя толькі Вам.",
|
||||||
"account.node_modal.edit_title": "Рэдагаваць асабістую нататку",
|
"account.node_modal.edit_title": "Рэдагаваць асабістую нататку",
|
||||||
|
|||||||
@ -17,8 +17,15 @@
|
|||||||
"account.activity": "Aktivitet",
|
"account.activity": "Aktivitet",
|
||||||
"account.add_note": "Tilføj en personlig note",
|
"account.add_note": "Tilføj en personlig note",
|
||||||
"account.add_or_remove_from_list": "Tilføj eller fjern fra lister",
|
"account.add_or_remove_from_list": "Tilføj eller fjern fra lister",
|
||||||
|
"account.badges.admin": "Admin",
|
||||||
|
"account.badges.blocked": "Blokeret",
|
||||||
"account.badges.bot": "Automatisert",
|
"account.badges.bot": "Automatisert",
|
||||||
|
"account.badges.domain_blocked": "Blokeret domæne",
|
||||||
|
"account.badges.follows_you": "Følger dig",
|
||||||
"account.badges.group": "Gruppe",
|
"account.badges.group": "Gruppe",
|
||||||
|
"account.badges.muted": "Skjult",
|
||||||
|
"account.badges.mutuals": "I følger hinanden",
|
||||||
|
"account.badges.requested_to_follow": "Anmodede om at følge dig",
|
||||||
"account.block": "Blokér @{name}",
|
"account.block": "Blokér @{name}",
|
||||||
"account.block_domain": "Blokér domænet {domain}",
|
"account.block_domain": "Blokér domænet {domain}",
|
||||||
"account.block_short": "Bloker",
|
"account.block_short": "Bloker",
|
||||||
|
|||||||
@ -17,8 +17,15 @@
|
|||||||
"account.activity": "Aktivitäten",
|
"account.activity": "Aktivitäten",
|
||||||
"account.add_note": "Persönliche Notiz hinzufügen",
|
"account.add_note": "Persönliche Notiz hinzufügen",
|
||||||
"account.add_or_remove_from_list": "Listen verwalten",
|
"account.add_or_remove_from_list": "Listen verwalten",
|
||||||
|
"account.badges.admin": "Admin",
|
||||||
|
"account.badges.blocked": "Blockiert",
|
||||||
"account.badges.bot": "Bot",
|
"account.badges.bot": "Bot",
|
||||||
|
"account.badges.domain_blocked": "Domain blockiert",
|
||||||
|
"account.badges.follows_you": "Folgt dir",
|
||||||
"account.badges.group": "Gruppe",
|
"account.badges.group": "Gruppe",
|
||||||
|
"account.badges.muted": "Stummgeschaltet",
|
||||||
|
"account.badges.mutuals": "Ihr folgt einander",
|
||||||
|
"account.badges.requested_to_follow": "Möchte dir folgen",
|
||||||
"account.block": "@{name} blockieren",
|
"account.block": "@{name} blockieren",
|
||||||
"account.block_domain": "{domain} blockieren",
|
"account.block_domain": "{domain} blockieren",
|
||||||
"account.block_short": "Blockieren",
|
"account.block_short": "Blockieren",
|
||||||
|
|||||||
@ -17,8 +17,15 @@
|
|||||||
"account.activity": "Δραστηριότητα",
|
"account.activity": "Δραστηριότητα",
|
||||||
"account.add_note": "Προσθέστε μια προσωπική σημείωση",
|
"account.add_note": "Προσθέστε μια προσωπική σημείωση",
|
||||||
"account.add_or_remove_from_list": "Προσθήκη ή Αφαίρεση από λίστες",
|
"account.add_or_remove_from_list": "Προσθήκη ή Αφαίρεση από λίστες",
|
||||||
|
"account.badges.admin": "Διαχειριστής",
|
||||||
|
"account.badges.blocked": "Αποκλεισμένος",
|
||||||
"account.badges.bot": "Αυτοματοποιημένος",
|
"account.badges.bot": "Αυτοματοποιημένος",
|
||||||
|
"account.badges.domain_blocked": "Αποκλεισμένος τομέας",
|
||||||
|
"account.badges.follows_you": "Σε ακολουθεί",
|
||||||
"account.badges.group": "Ομάδα",
|
"account.badges.group": "Ομάδα",
|
||||||
|
"account.badges.muted": "Σε σίγαση",
|
||||||
|
"account.badges.mutuals": "Ακολουθείτε ο ένας τον άλλο",
|
||||||
|
"account.badges.requested_to_follow": "Ζήτησε να σε ακολουθήσει",
|
||||||
"account.block": "Αποκλεισμός @{name}",
|
"account.block": "Αποκλεισμός @{name}",
|
||||||
"account.block_domain": "Αποκλεισμός τομέα {domain}",
|
"account.block_domain": "Αποκλεισμός τομέα {domain}",
|
||||||
"account.block_short": "Αποκλεισμός",
|
"account.block_short": "Αποκλεισμός",
|
||||||
|
|||||||
@ -17,8 +17,15 @@
|
|||||||
"account.activity": "Activity",
|
"account.activity": "Activity",
|
||||||
"account.add_note": "Add a personal note",
|
"account.add_note": "Add a personal note",
|
||||||
"account.add_or_remove_from_list": "Add or Remove from lists",
|
"account.add_or_remove_from_list": "Add or Remove from lists",
|
||||||
|
"account.badges.admin": "Admin",
|
||||||
|
"account.badges.blocked": "Blocked",
|
||||||
"account.badges.bot": "Automated",
|
"account.badges.bot": "Automated",
|
||||||
|
"account.badges.domain_blocked": "Blocked domain",
|
||||||
|
"account.badges.follows_you": "Follows you",
|
||||||
"account.badges.group": "Group",
|
"account.badges.group": "Group",
|
||||||
|
"account.badges.muted": "Muted",
|
||||||
|
"account.badges.mutuals": "You follow each other",
|
||||||
|
"account.badges.requested_to_follow": "Requested to follow you",
|
||||||
"account.block": "Block @{name}",
|
"account.block": "Block @{name}",
|
||||||
"account.block_domain": "Block domain {domain}",
|
"account.block_domain": "Block domain {domain}",
|
||||||
"account.block_short": "Block",
|
"account.block_short": "Block",
|
||||||
|
|||||||
@ -21,11 +21,8 @@
|
|||||||
"account.badges.blocked": "Blocked",
|
"account.badges.blocked": "Blocked",
|
||||||
"account.badges.bot": "Automated",
|
"account.badges.bot": "Automated",
|
||||||
"account.badges.domain_blocked": "Blocked domain",
|
"account.badges.domain_blocked": "Blocked domain",
|
||||||
"account.badges.follows_you": "Follows you",
|
|
||||||
"account.badges.group": "Group",
|
"account.badges.group": "Group",
|
||||||
"account.badges.muted": "Muted",
|
"account.badges.muted": "Muted",
|
||||||
"account.badges.mutuals": "You follow each other",
|
|
||||||
"account.badges.requested_to_follow": "Requested to follow you",
|
|
||||||
"account.block": "Block @{name}",
|
"account.block": "Block @{name}",
|
||||||
"account.block_domain": "Block domain {domain}",
|
"account.block_domain": "Block domain {domain}",
|
||||||
"account.block_short": "Block",
|
"account.block_short": "Block",
|
||||||
@ -49,6 +46,8 @@
|
|||||||
"account.featured.hashtags": "Hashtags",
|
"account.featured.hashtags": "Hashtags",
|
||||||
"account.featured_tags.last_status_at": "Last post on {date}",
|
"account.featured_tags.last_status_at": "Last post on {date}",
|
||||||
"account.featured_tags.last_status_never": "No posts",
|
"account.featured_tags.last_status_never": "No posts",
|
||||||
|
"account.fields.scroll_next": "Show next",
|
||||||
|
"account.fields.scroll_prev": "Show previous",
|
||||||
"account.filters.all": "All activity",
|
"account.filters.all": "All activity",
|
||||||
"account.filters.boosts_toggle": "Show boosts",
|
"account.filters.boosts_toggle": "Show boosts",
|
||||||
"account.filters.posts_boosts": "Posts and boosts",
|
"account.filters.posts_boosts": "Posts and boosts",
|
||||||
@ -80,6 +79,23 @@
|
|||||||
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
||||||
"account.media": "Media",
|
"account.media": "Media",
|
||||||
"account.mention": "Mention @{name}",
|
"account.mention": "Mention @{name}",
|
||||||
|
"account.menu.add_to_list": "Add to list…",
|
||||||
|
"account.menu.block": "Block account",
|
||||||
|
"account.menu.block_domain": "Block {domain}",
|
||||||
|
"account.menu.copied": "Copied account link to clipboard",
|
||||||
|
"account.menu.copy": "Copy link",
|
||||||
|
"account.menu.direct": "Privately mention",
|
||||||
|
"account.menu.hide_reblogs": "Hide boosts in timeline",
|
||||||
|
"account.menu.mention": "Mention",
|
||||||
|
"account.menu.mute": "Mute account",
|
||||||
|
"account.menu.open_original_page": "View on {domain}",
|
||||||
|
"account.menu.remove_follower": "Remove follower",
|
||||||
|
"account.menu.report": "Report account",
|
||||||
|
"account.menu.share": "Share…",
|
||||||
|
"account.menu.show_reblogs": "Show boosts in timeline",
|
||||||
|
"account.menu.unblock": "Unblock account",
|
||||||
|
"account.menu.unblock_domain": "Unblock {domain}",
|
||||||
|
"account.menu.unmute": "Unmute account",
|
||||||
"account.moved_to": "{name} has indicated that their new account is now:",
|
"account.moved_to": "{name} has indicated that their new account is now:",
|
||||||
"account.mute": "Mute @{name}",
|
"account.mute": "Mute @{name}",
|
||||||
"account.mute_notifications_short": "Mute notifications",
|
"account.mute_notifications_short": "Mute notifications",
|
||||||
@ -115,8 +131,6 @@
|
|||||||
"account.unmute": "Unmute @{name}",
|
"account.unmute": "Unmute @{name}",
|
||||||
"account.unmute_notifications_short": "Unmute notifications",
|
"account.unmute_notifications_short": "Unmute notifications",
|
||||||
"account.unmute_short": "Unmute",
|
"account.unmute_short": "Unmute",
|
||||||
"account_fields_modal.close": "Close",
|
|
||||||
"account_fields_modal.title": "{name}'s info",
|
|
||||||
"account_note.placeholder": "Click to add note",
|
"account_note.placeholder": "Click to add note",
|
||||||
"admin.dashboard.daily_retention": "User retention rate by day after sign-up",
|
"admin.dashboard.daily_retention": "User retention rate by day after sign-up",
|
||||||
"admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
|
"admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
|
||||||
@ -639,7 +653,6 @@
|
|||||||
"load_pending": "{count, plural, one {# new item} other {# new items}}",
|
"load_pending": "{count, plural, one {# new item} other {# new items}}",
|
||||||
"loading_indicator.label": "Loading…",
|
"loading_indicator.label": "Loading…",
|
||||||
"media_gallery.hide": "Hide",
|
"media_gallery.hide": "Hide",
|
||||||
"minicard.more_items": "+{count}",
|
|
||||||
"moved_to_account_banner.text": "Your account {disabledAccount} is currently disabled because you moved to {movedToAccount}.",
|
"moved_to_account_banner.text": "Your account {disabledAccount} is currently disabled because you moved to {movedToAccount}.",
|
||||||
"mute_modal.hide_from_notifications": "Hide from notifications",
|
"mute_modal.hide_from_notifications": "Hide from notifications",
|
||||||
"mute_modal.hide_options": "Hide options",
|
"mute_modal.hide_options": "Hide options",
|
||||||
|
|||||||
@ -17,8 +17,15 @@
|
|||||||
"account.activity": "Actividad",
|
"account.activity": "Actividad",
|
||||||
"account.add_note": "Agregar una nota personal",
|
"account.add_note": "Agregar una nota personal",
|
||||||
"account.add_or_remove_from_list": "Agregar o quitar de las listas",
|
"account.add_or_remove_from_list": "Agregar o quitar de las listas",
|
||||||
|
"account.badges.admin": "Administración",
|
||||||
|
"account.badges.blocked": "Cuenta bloqueada",
|
||||||
"account.badges.bot": "Automatizada",
|
"account.badges.bot": "Automatizada",
|
||||||
|
"account.badges.domain_blocked": "Dominio bloqueado",
|
||||||
|
"account.badges.follows_you": "Te sigue",
|
||||||
"account.badges.group": "Grupo",
|
"account.badges.group": "Grupo",
|
||||||
|
"account.badges.muted": "Cuenta silenciada",
|
||||||
|
"account.badges.mutuals": "Seguimiento mutuo",
|
||||||
|
"account.badges.requested_to_follow": "Solicitó seguirte",
|
||||||
"account.block": "Bloquear a @{name}",
|
"account.block": "Bloquear a @{name}",
|
||||||
"account.block_domain": "Bloquear dominio {domain}",
|
"account.block_domain": "Bloquear dominio {domain}",
|
||||||
"account.block_short": "Bloquear",
|
"account.block_short": "Bloquear",
|
||||||
|
|||||||
@ -17,8 +17,15 @@
|
|||||||
"account.activity": "Actividad",
|
"account.activity": "Actividad",
|
||||||
"account.add_note": "Añadir una nota personal",
|
"account.add_note": "Añadir una nota personal",
|
||||||
"account.add_or_remove_from_list": "Agregar o eliminar de las listas",
|
"account.add_or_remove_from_list": "Agregar o eliminar de las listas",
|
||||||
|
"account.badges.admin": "Administrador",
|
||||||
|
"account.badges.blocked": "Bloqueado",
|
||||||
"account.badges.bot": "Automatizada",
|
"account.badges.bot": "Automatizada",
|
||||||
|
"account.badges.domain_blocked": "Dominio bloqueado",
|
||||||
|
"account.badges.follows_you": "Te sigue",
|
||||||
"account.badges.group": "Grupo",
|
"account.badges.group": "Grupo",
|
||||||
|
"account.badges.muted": "Silenciado",
|
||||||
|
"account.badges.mutuals": "Se siguen el uno al otro",
|
||||||
|
"account.badges.requested_to_follow": "Solicitó seguirte",
|
||||||
"account.block": "Bloquear a @{name}",
|
"account.block": "Bloquear a @{name}",
|
||||||
"account.block_domain": "Bloquear dominio {domain}",
|
"account.block_domain": "Bloquear dominio {domain}",
|
||||||
"account.block_short": "Bloquear",
|
"account.block_short": "Bloquear",
|
||||||
|
|||||||
@ -17,8 +17,15 @@
|
|||||||
"account.activity": "Tegevus",
|
"account.activity": "Tegevus",
|
||||||
"account.add_note": "Lisa isiklik märge",
|
"account.add_note": "Lisa isiklik märge",
|
||||||
"account.add_or_remove_from_list": "Lisa või eemalda loenditest",
|
"account.add_or_remove_from_list": "Lisa või eemalda loenditest",
|
||||||
|
"account.badges.admin": "Peakasutaja",
|
||||||
|
"account.badges.blocked": "Blokeeritud",
|
||||||
"account.badges.bot": "Robot",
|
"account.badges.bot": "Robot",
|
||||||
|
"account.badges.domain_blocked": "Blokeeritud domeen",
|
||||||
|
"account.badges.follows_you": "Jälgib sind",
|
||||||
"account.badges.group": "Grupp",
|
"account.badges.group": "Grupp",
|
||||||
|
"account.badges.muted": "Summutatud",
|
||||||
|
"account.badges.mutuals": "Te jälgite teineteist",
|
||||||
|
"account.badges.requested_to_follow": "Soovib sind jälgida",
|
||||||
"account.block": "Blokeeri @{name}",
|
"account.block": "Blokeeri @{name}",
|
||||||
"account.block_domain": "Blokeeri kõik domeenist {domain}",
|
"account.block_domain": "Blokeeri kõik domeenist {domain}",
|
||||||
"account.block_short": "Blokeerimine",
|
"account.block_short": "Blokeerimine",
|
||||||
|
|||||||
@ -17,8 +17,15 @@
|
|||||||
"account.activity": "Toiminta",
|
"account.activity": "Toiminta",
|
||||||
"account.add_note": "Lisää henkilökohtainen muistiinpano",
|
"account.add_note": "Lisää henkilökohtainen muistiinpano",
|
||||||
"account.add_or_remove_from_list": "Lisää tai poista listoista",
|
"account.add_or_remove_from_list": "Lisää tai poista listoista",
|
||||||
|
"account.badges.admin": "Ylläpitäjä",
|
||||||
|
"account.badges.blocked": "Estetty",
|
||||||
"account.badges.bot": "Botti",
|
"account.badges.bot": "Botti",
|
||||||
|
"account.badges.domain_blocked": "Estetty verkkotunnus",
|
||||||
|
"account.badges.follows_you": "Seuraa sinua",
|
||||||
"account.badges.group": "Ryhmä",
|
"account.badges.group": "Ryhmä",
|
||||||
|
"account.badges.muted": "Mykistetty",
|
||||||
|
"account.badges.mutuals": "Seuraatte toisianne",
|
||||||
|
"account.badges.requested_to_follow": "Pyytänyt lupaa seurata sinua",
|
||||||
"account.block": "Estä @{name}",
|
"account.block": "Estä @{name}",
|
||||||
"account.block_domain": "Estä verkkotunnus {domain}",
|
"account.block_domain": "Estä verkkotunnus {domain}",
|
||||||
"account.block_short": "Estä",
|
"account.block_short": "Estä",
|
||||||
|
|||||||
@ -17,8 +17,15 @@
|
|||||||
"account.activity": "Virksemi",
|
"account.activity": "Virksemi",
|
||||||
"account.add_note": "Legg persónliga notu afturat",
|
"account.add_note": "Legg persónliga notu afturat",
|
||||||
"account.add_or_remove_from_list": "Legg afturat ella tak av listum",
|
"account.add_or_remove_from_list": "Legg afturat ella tak av listum",
|
||||||
|
"account.badges.admin": "Umsitari",
|
||||||
|
"account.badges.blocked": "Blokerað/ur",
|
||||||
"account.badges.bot": "Bottur",
|
"account.badges.bot": "Bottur",
|
||||||
|
"account.badges.domain_blocked": "Blokerað økisnavn",
|
||||||
|
"account.badges.follows_you": "Fylgir tær",
|
||||||
"account.badges.group": "Bólkur",
|
"account.badges.group": "Bólkur",
|
||||||
|
"account.badges.muted": "Doyvdur",
|
||||||
|
"account.badges.mutuals": "Tit fylgja hvønn annan",
|
||||||
|
"account.badges.requested_to_follow": "Hevur biðið um at fylgja tær",
|
||||||
"account.block": "Banna @{name}",
|
"account.block": "Banna @{name}",
|
||||||
"account.block_domain": "Banna økisnavnið {domain}",
|
"account.block_domain": "Banna økisnavnið {domain}",
|
||||||
"account.block_short": "Banna",
|
"account.block_short": "Banna",
|
||||||
|
|||||||
@ -17,8 +17,15 @@
|
|||||||
"account.activity": "Activités",
|
"account.activity": "Activités",
|
||||||
"account.add_note": "Ajouter une note personnelle",
|
"account.add_note": "Ajouter une note personnelle",
|
||||||
"account.add_or_remove_from_list": "Ajouter ou enlever de listes",
|
"account.add_or_remove_from_list": "Ajouter ou enlever de listes",
|
||||||
|
"account.badges.admin": "Admin",
|
||||||
|
"account.badges.blocked": "Bloqué",
|
||||||
"account.badges.bot": "Bot",
|
"account.badges.bot": "Bot",
|
||||||
|
"account.badges.domain_blocked": "Domaine bloqué",
|
||||||
|
"account.badges.follows_you": "Vous suit",
|
||||||
"account.badges.group": "Groupe",
|
"account.badges.group": "Groupe",
|
||||||
|
"account.badges.muted": "Masqué",
|
||||||
|
"account.badges.mutuals": "Vous vous suivez mutuellement",
|
||||||
|
"account.badges.requested_to_follow": "A demandé à vous suivre",
|
||||||
"account.block": "Bloquer @{name}",
|
"account.block": "Bloquer @{name}",
|
||||||
"account.block_domain": "Bloquer le domaine {domain}",
|
"account.block_domain": "Bloquer le domaine {domain}",
|
||||||
"account.block_short": "Bloquer",
|
"account.block_short": "Bloquer",
|
||||||
|
|||||||
@ -17,8 +17,15 @@
|
|||||||
"account.activity": "Activités",
|
"account.activity": "Activités",
|
||||||
"account.add_note": "Ajouter une note personnelle",
|
"account.add_note": "Ajouter une note personnelle",
|
||||||
"account.add_or_remove_from_list": "Ajouter ou retirer des listes",
|
"account.add_or_remove_from_list": "Ajouter ou retirer des listes",
|
||||||
|
"account.badges.admin": "Admin",
|
||||||
|
"account.badges.blocked": "Bloqué",
|
||||||
"account.badges.bot": "Bot",
|
"account.badges.bot": "Bot",
|
||||||
|
"account.badges.domain_blocked": "Domaine bloqué",
|
||||||
|
"account.badges.follows_you": "Vous suit",
|
||||||
"account.badges.group": "Groupe",
|
"account.badges.group": "Groupe",
|
||||||
|
"account.badges.muted": "Masqué",
|
||||||
|
"account.badges.mutuals": "Vous vous suivez mutuellement",
|
||||||
|
"account.badges.requested_to_follow": "A demandé à vous suivre",
|
||||||
"account.block": "Bloquer @{name}",
|
"account.block": "Bloquer @{name}",
|
||||||
"account.block_domain": "Bloquer le domaine {domain}",
|
"account.block_domain": "Bloquer le domaine {domain}",
|
||||||
"account.block_short": "Bloquer",
|
"account.block_short": "Bloquer",
|
||||||
@ -73,7 +80,7 @@
|
|||||||
"account.locked_info": "Ce compte est privé. Son ou sa propriétaire approuve manuellement qui peut le suivre.",
|
"account.locked_info": "Ce compte est privé. Son ou sa propriétaire approuve manuellement qui peut le suivre.",
|
||||||
"account.media": "Médias",
|
"account.media": "Médias",
|
||||||
"account.mention": "Mentionner @{name}",
|
"account.mention": "Mentionner @{name}",
|
||||||
"account.moved_to": "{name} a indiqué que son nouveau compte est maintenant :",
|
"account.moved_to": "{name} a indiqué que son nouveau compte est maintenant :",
|
||||||
"account.mute": "Masquer @{name}",
|
"account.mute": "Masquer @{name}",
|
||||||
"account.mute_notifications_short": "Désactiver les notifications",
|
"account.mute_notifications_short": "Désactiver les notifications",
|
||||||
"account.mute_short": "Mettre en sourdine",
|
"account.mute_short": "Mettre en sourdine",
|
||||||
|
|||||||
@ -17,8 +17,15 @@
|
|||||||
"account.activity": "Actividade",
|
"account.activity": "Actividade",
|
||||||
"account.add_note": "Engadir nota persoal",
|
"account.add_note": "Engadir nota persoal",
|
||||||
"account.add_or_remove_from_list": "Engadir ou eliminar das listaxes",
|
"account.add_or_remove_from_list": "Engadir ou eliminar das listaxes",
|
||||||
|
"account.badges.admin": "Admin",
|
||||||
|
"account.badges.blocked": "Bloqueada",
|
||||||
"account.badges.bot": "Automatizada",
|
"account.badges.bot": "Automatizada",
|
||||||
|
"account.badges.domain_blocked": "Dominio bloqueado",
|
||||||
|
"account.badges.follows_you": "Séguete",
|
||||||
"account.badges.group": "Grupo",
|
"account.badges.group": "Grupo",
|
||||||
|
"account.badges.muted": "Silenciada",
|
||||||
|
"account.badges.mutuals": "Seguimento mútuo",
|
||||||
|
"account.badges.requested_to_follow": "Solicitou seguirte",
|
||||||
"account.block": "Bloquear @{name}",
|
"account.block": "Bloquear @{name}",
|
||||||
"account.block_domain": "Bloquear o dominio {domain}",
|
"account.block_domain": "Bloquear o dominio {domain}",
|
||||||
"account.block_short": "Bloquear",
|
"account.block_short": "Bloquear",
|
||||||
|
|||||||
@ -17,8 +17,15 @@
|
|||||||
"account.activity": "פעילות",
|
"account.activity": "פעילות",
|
||||||
"account.add_note": "הוספת הערה פרטית",
|
"account.add_note": "הוספת הערה פרטית",
|
||||||
"account.add_or_remove_from_list": "הוספה או הסרה מרשימות",
|
"account.add_or_remove_from_list": "הוספה או הסרה מרשימות",
|
||||||
|
"account.badges.admin": "מנהל",
|
||||||
|
"account.badges.blocked": "חסום",
|
||||||
"account.badges.bot": "בוט",
|
"account.badges.bot": "בוט",
|
||||||
|
"account.badges.domain_blocked": "דומיין חסום",
|
||||||
|
"account.badges.follows_you": "במעקב אחריך",
|
||||||
"account.badges.group": "קבוצה",
|
"account.badges.group": "קבוצה",
|
||||||
|
"account.badges.muted": "מושתק",
|
||||||
|
"account.badges.mutuals": "אתם עוקביםות הדדית",
|
||||||
|
"account.badges.requested_to_follow": "ביקשו לעקוב אחריך",
|
||||||
"account.block": "חסמי את @{name}",
|
"account.block": "חסמי את @{name}",
|
||||||
"account.block_domain": "חסמו את קהילת {domain}",
|
"account.block_domain": "חסמו את קהילת {domain}",
|
||||||
"account.block_short": "לחסום",
|
"account.block_short": "לחסום",
|
||||||
|
|||||||
@ -17,8 +17,15 @@
|
|||||||
"account.activity": "Virkni",
|
"account.activity": "Virkni",
|
||||||
"account.add_note": "Bæta við einkaminnispunkti",
|
"account.add_note": "Bæta við einkaminnispunkti",
|
||||||
"account.add_or_remove_from_list": "Bæta við eða fjarlægja af listum",
|
"account.add_or_remove_from_list": "Bæta við eða fjarlægja af listum",
|
||||||
|
"account.badges.admin": "Stjóri",
|
||||||
|
"account.badges.blocked": "Lokað á",
|
||||||
"account.badges.bot": "Yrki",
|
"account.badges.bot": "Yrki",
|
||||||
|
"account.badges.domain_blocked": "Útilokað lén",
|
||||||
|
"account.badges.follows_you": "Fylgist með þér",
|
||||||
"account.badges.group": "Hópur",
|
"account.badges.group": "Hópur",
|
||||||
|
"account.badges.muted": "Þaggað",
|
||||||
|
"account.badges.mutuals": "Þið fylgist með hvor öðrum",
|
||||||
|
"account.badges.requested_to_follow": "Bað um að fylgjast með þér",
|
||||||
"account.block": "Loka á @{name}",
|
"account.block": "Loka á @{name}",
|
||||||
"account.block_domain": "Útiloka lénið {domain}",
|
"account.block_domain": "Útiloka lénið {domain}",
|
||||||
"account.block_short": "Útiloka",
|
"account.block_short": "Útiloka",
|
||||||
|
|||||||
@ -17,8 +17,15 @@
|
|||||||
"account.activity": "Attività",
|
"account.activity": "Attività",
|
||||||
"account.add_note": "Aggiungi una nota personale",
|
"account.add_note": "Aggiungi una nota personale",
|
||||||
"account.add_or_remove_from_list": "Aggiungi o Rimuovi dalle liste",
|
"account.add_or_remove_from_list": "Aggiungi o Rimuovi dalle liste",
|
||||||
|
"account.badges.admin": "Amministratore/trice",
|
||||||
|
"account.badges.blocked": "Bloccato",
|
||||||
"account.badges.bot": "Bot",
|
"account.badges.bot": "Bot",
|
||||||
|
"account.badges.domain_blocked": "Dominio bloccato",
|
||||||
|
"account.badges.follows_you": "Ti segue",
|
||||||
"account.badges.group": "Gruppo",
|
"account.badges.group": "Gruppo",
|
||||||
|
"account.badges.muted": "Silenziato",
|
||||||
|
"account.badges.mutuals": "Vi seguite a vicenda",
|
||||||
|
"account.badges.requested_to_follow": "Ha richiesto di seguirti",
|
||||||
"account.block": "Blocca @{name}",
|
"account.block": "Blocca @{name}",
|
||||||
"account.block_domain": "Blocca dominio {domain}",
|
"account.block_domain": "Blocca dominio {domain}",
|
||||||
"account.block_short": "Blocca",
|
"account.block_short": "Blocca",
|
||||||
|
|||||||
@ -14,9 +14,18 @@
|
|||||||
"about.powered_by": "Decentralizuota socialinė medija, veikianti pagal „{mastodon}“",
|
"about.powered_by": "Decentralizuota socialinė medija, veikianti pagal „{mastodon}“",
|
||||||
"about.rules": "Serverio taisyklės",
|
"about.rules": "Serverio taisyklės",
|
||||||
"account.account_note_header": "Asmeninė pastaba",
|
"account.account_note_header": "Asmeninė pastaba",
|
||||||
|
"account.activity": "Veikla",
|
||||||
|
"account.add_note": "Pridėti asmeninę pastabą",
|
||||||
"account.add_or_remove_from_list": "Įtraukti arba šalinti iš sąrašų",
|
"account.add_or_remove_from_list": "Įtraukti arba šalinti iš sąrašų",
|
||||||
|
"account.badges.admin": "Administratorius",
|
||||||
|
"account.badges.blocked": "Užblokuota",
|
||||||
"account.badges.bot": "Automatizuotas",
|
"account.badges.bot": "Automatizuotas",
|
||||||
|
"account.badges.domain_blocked": "Užblokuoti serveriai",
|
||||||
|
"account.badges.follows_you": "Seka jus",
|
||||||
"account.badges.group": "Grupė",
|
"account.badges.group": "Grupė",
|
||||||
|
"account.badges.muted": "Nutildytas",
|
||||||
|
"account.badges.mutuals": "Sekate vienas kitą",
|
||||||
|
"account.badges.requested_to_follow": "Prašymai sekti jus",
|
||||||
"account.block": "Blokuoti @{name}",
|
"account.block": "Blokuoti @{name}",
|
||||||
"account.block_domain": "Blokuoti serverį {domain}",
|
"account.block_domain": "Blokuoti serverį {domain}",
|
||||||
"account.block_short": "Blokuoti",
|
"account.block_short": "Blokuoti",
|
||||||
@ -27,6 +36,7 @@
|
|||||||
"account.direct": "Privačiai paminėti @{name}",
|
"account.direct": "Privačiai paminėti @{name}",
|
||||||
"account.disable_notifications": "Nustoti man pranešti, kai @{name} paskelbia",
|
"account.disable_notifications": "Nustoti man pranešti, kai @{name} paskelbia",
|
||||||
"account.domain_blocking": "Blokuoti domeną",
|
"account.domain_blocking": "Blokuoti domeną",
|
||||||
|
"account.edit_note": "Redaguoti asmeninę pastabą",
|
||||||
"account.edit_profile": "Redaguoti profilį",
|
"account.edit_profile": "Redaguoti profilį",
|
||||||
"account.edit_profile_short": "Redaguoti",
|
"account.edit_profile_short": "Redaguoti",
|
||||||
"account.enable_notifications": "Pranešti man, kai @{name} paskelbia",
|
"account.enable_notifications": "Pranešti man, kai @{name} paskelbia",
|
||||||
@ -39,6 +49,11 @@
|
|||||||
"account.featured.hashtags": "Grotažymės",
|
"account.featured.hashtags": "Grotažymės",
|
||||||
"account.featured_tags.last_status_at": "Paskutinis įrašas {date}",
|
"account.featured_tags.last_status_at": "Paskutinis įrašas {date}",
|
||||||
"account.featured_tags.last_status_never": "Nėra įrašų",
|
"account.featured_tags.last_status_never": "Nėra įrašų",
|
||||||
|
"account.filters.all": "Visa veikla",
|
||||||
|
"account.filters.boosts_toggle": "Rodyti pasidalinimus",
|
||||||
|
"account.filters.posts_boosts": "Įrašai ir pasidalinimai",
|
||||||
|
"account.filters.posts_only": "Įrašai",
|
||||||
|
"account.filters.replies_toggle": "rodyti atsakymus",
|
||||||
"account.follow": "Sekti",
|
"account.follow": "Sekti",
|
||||||
"account.follow_back": "Sekti atgal",
|
"account.follow_back": "Sekti atgal",
|
||||||
"account.follow_back_short": "Sekti atgal",
|
"account.follow_back_short": "Sekti atgal",
|
||||||
@ -57,6 +72,7 @@
|
|||||||
"account.go_to_profile": "Eiti į profilį",
|
"account.go_to_profile": "Eiti į profilį",
|
||||||
"account.hide_reblogs": "Slėpti pasidalinimus iš @{name}",
|
"account.hide_reblogs": "Slėpti pasidalinimus iš @{name}",
|
||||||
"account.in_memoriam": "Atminimui.",
|
"account.in_memoriam": "Atminimui.",
|
||||||
|
"account.joined_long": "Prisijungė {date}",
|
||||||
"account.joined_short": "Prisijungė",
|
"account.joined_short": "Prisijungė",
|
||||||
"account.languages": "Keisti prenumeruojamas kalbas",
|
"account.languages": "Keisti prenumeruojamas kalbas",
|
||||||
"account.link_verified_on": "Šios nuorodos nuosavybė buvo patikrinta {date}",
|
"account.link_verified_on": "Šios nuorodos nuosavybė buvo patikrinta {date}",
|
||||||
@ -71,6 +87,14 @@
|
|||||||
"account.muting": "Užtildymas",
|
"account.muting": "Užtildymas",
|
||||||
"account.mutual": "Sekate vienas kitą",
|
"account.mutual": "Sekate vienas kitą",
|
||||||
"account.no_bio": "Nėra pateikto aprašymo.",
|
"account.no_bio": "Nėra pateikto aprašymo.",
|
||||||
|
"account.node_modal.callout": "Asmeninės pastabos matomos tik jums.",
|
||||||
|
"account.node_modal.edit_title": "Redaguoti asmeninę pastabą",
|
||||||
|
"account.node_modal.error_unknown": "Nepavyko išsaugoti pastabos",
|
||||||
|
"account.node_modal.field_label": "Asmeninė pastaba",
|
||||||
|
"account.node_modal.save": "Išsaugoti",
|
||||||
|
"account.node_modal.title": "Pridėti asmeninę pastabą",
|
||||||
|
"account.note.edit_button": "Redaguoti",
|
||||||
|
"account.note.title": "Asmeninės pastabos (matomos tik jums)",
|
||||||
"account.open_original_page": "Atidaryti originalų puslapį",
|
"account.open_original_page": "Atidaryti originalų puslapį",
|
||||||
"account.posts": "Įrašai",
|
"account.posts": "Įrašai",
|
||||||
"account.posts_with_replies": "Įrašai ir atsakymai",
|
"account.posts_with_replies": "Įrašai ir atsakymai",
|
||||||
@ -90,6 +114,8 @@
|
|||||||
"account.unmute": "Atšaukti nutildymą @{name}",
|
"account.unmute": "Atšaukti nutildymą @{name}",
|
||||||
"account.unmute_notifications_short": "Atšaukti pranešimų nutildymą",
|
"account.unmute_notifications_short": "Atšaukti pranešimų nutildymą",
|
||||||
"account.unmute_short": "Atšaukti nutildymą",
|
"account.unmute_short": "Atšaukti nutildymą",
|
||||||
|
"account_fields_modal.close": "Uždaryti",
|
||||||
|
"account_fields_modal.title": "{name} informacija",
|
||||||
"account_note.placeholder": "Spustelėk, kad pridėtum pastabą.",
|
"account_note.placeholder": "Spustelėk, kad pridėtum pastabą.",
|
||||||
"admin.dashboard.daily_retention": "Naudotojų pasilikimo rodiklis pagal dieną po registracijos",
|
"admin.dashboard.daily_retention": "Naudotojų pasilikimo rodiklis pagal dieną po registracijos",
|
||||||
"admin.dashboard.monthly_retention": "Naudotojų pasilikimo rodiklis pagal mėnesį po registracijos",
|
"admin.dashboard.monthly_retention": "Naudotojų pasilikimo rodiklis pagal mėnesį po registracijos",
|
||||||
@ -114,9 +140,20 @@
|
|||||||
"alt_text_modal.done": "Atlikta",
|
"alt_text_modal.done": "Atlikta",
|
||||||
"announcement.announcement": "Skelbimas",
|
"announcement.announcement": "Skelbimas",
|
||||||
"annual_report.announcement.action_build": "Sukurti mano Wrapstodon",
|
"annual_report.announcement.action_build": "Sukurti mano Wrapstodon",
|
||||||
|
"annual_report.announcement.action_dismiss": "Ne, ačiū",
|
||||||
"annual_report.announcement.action_view": "Peržiūrėti mano Wrapstodon",
|
"annual_report.announcement.action_view": "Peržiūrėti mano Wrapstodon",
|
||||||
"annual_report.announcement.description": "Sužinokite daugiau apie savo aktyvumą Mastodon\"e per pastaruosius metus.",
|
"annual_report.announcement.description": "Sužinokite daugiau apie savo aktyvumą Mastodon\"e per pastaruosius metus.",
|
||||||
"annual_report.announcement.title": "Wrapstodon {year} jau čia",
|
"annual_report.announcement.title": "Wrapstodon {year} jau čia",
|
||||||
|
"annual_report.nav_item.badge": "Nauja",
|
||||||
|
"annual_report.shared_page.donate": "Paremti",
|
||||||
|
"annual_report.shared_page.footer": "Sukurta su {heart} Mastodon komandos",
|
||||||
|
"annual_report.shared_page.footer_server_info": "{username} naudoja {domain}, vieną iš daugelio bendruomenių, kurios veikia „Mastodon“ dėka.",
|
||||||
|
"annual_report.summary.archetype.booster.desc_public": "{name} tęsė ieškojimus, kokiais įrašais pasidalinti, kad padėtų kitiems kūrėjams pasiekti puikių rezultatų.",
|
||||||
|
"annual_report.summary.archetype.booster.desc_self": "Tu nuolat ieškojai įrašų, kurais galėtum pasidalinti, tuo puikiai padėdamas kitiems kūrėjams.",
|
||||||
|
"annual_report.summary.archetype.booster.name": "Lankininkas",
|
||||||
|
"annual_report.summary.archetype.die_drei_fragezeichen": "???",
|
||||||
|
"annual_report.summary.close": "Užverti",
|
||||||
|
"annual_report.summary.highlighted_post.title": "Populiariausi įrašai",
|
||||||
"annual_report.summary.most_used_app.most_used_app": "labiausiai naudota programa",
|
"annual_report.summary.most_used_app.most_used_app": "labiausiai naudota programa",
|
||||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "labiausiai naudota grotažymė",
|
"annual_report.summary.most_used_hashtag.most_used_hashtag": "labiausiai naudota grotažymė",
|
||||||
"annual_report.summary.new_posts.new_posts": "nauji įrašai",
|
"annual_report.summary.new_posts.new_posts": "nauji įrašai",
|
||||||
@ -243,11 +280,11 @@
|
|||||||
"confirmations.private_quote_notify.cancel": "Grįžti prie redagavimo",
|
"confirmations.private_quote_notify.cancel": "Grįžti prie redagavimo",
|
||||||
"confirmations.private_quote_notify.confirm": "Paskelbti įrašą",
|
"confirmations.private_quote_notify.confirm": "Paskelbti įrašą",
|
||||||
"confirmations.private_quote_notify.do_not_show_again": "Neberodyti šio pranešimo dar kartą",
|
"confirmations.private_quote_notify.do_not_show_again": "Neberodyti šio pranešimo dar kartą",
|
||||||
"confirmations.private_quote_notify.message": "Asmuo, kurį paminite, ir kiti paminėti asmenys bus informuoti ir galės peržiūrėti jūsų įrašą, net jei jie neseka jūsų.",
|
"confirmations.private_quote_notify.message": "Asmuo, kurį komentuojate, ir kiti paminėti asmenys bus informuoti ir galės peržiūrėti jūsų įrašą, net jei jie neseka jūsų.",
|
||||||
"confirmations.private_quote_notify.title": "Bendrinti su sekėjais ir paminėtais (su @) naudotojais?",
|
"confirmations.private_quote_notify.title": "Bendrinti su sekėjais ir paminėtais (su @) naudotojais?",
|
||||||
"confirmations.quiet_post_quote_info.dismiss": "Daugiau man nepriminti",
|
"confirmations.quiet_post_quote_info.dismiss": "Daugiau man nepriminti",
|
||||||
"confirmations.quiet_post_quote_info.got_it": "Supratau",
|
"confirmations.quiet_post_quote_info.got_it": "Supratau",
|
||||||
"confirmations.quiet_post_quote_info.message": "Kai norite paminėti tylų viešą įrašą, jūsų įrašas bus paslėptas Tendencijų sąrašuose.",
|
"confirmations.quiet_post_quote_info.message": "Kai norite komentuoti užtildytą viešą įrašą, jūsų įrašas bus paslėptas „Populiaru“ sąrašuose.",
|
||||||
"confirmations.quiet_post_quote_info.title": "Kai paminite tylius viešus įrašus",
|
"confirmations.quiet_post_quote_info.title": "Kai paminite tylius viešus įrašus",
|
||||||
"confirmations.redraft.confirm": "Ištrinti ir iš naujo parengti",
|
"confirmations.redraft.confirm": "Ištrinti ir iš naujo parengti",
|
||||||
"confirmations.redraft.message": "Ar tikrai nori ištrinti šį įrašą ir parengti jį iš naujo? Bus prarasti mėgstami ir pasidalinimai, o atsakymai į originalų įrašą bus panaikinti.",
|
"confirmations.redraft.message": "Ar tikrai nori ištrinti šį įrašą ir parengti jį iš naujo? Bus prarasti mėgstami ir pasidalinimai, o atsakymai į originalų įrašą bus panaikinti.",
|
||||||
@ -442,7 +479,7 @@
|
|||||||
"hints.profiles.see_more_followers": "Žiūrėti daugiau sekėjų serveryje {domain}",
|
"hints.profiles.see_more_followers": "Žiūrėti daugiau sekėjų serveryje {domain}",
|
||||||
"hints.profiles.see_more_follows": "Žiūrėti daugiau sekimų serveryje {domain}",
|
"hints.profiles.see_more_follows": "Žiūrėti daugiau sekimų serveryje {domain}",
|
||||||
"hints.profiles.see_more_posts": "Žiūrėti daugiau įrašų serveryje {domain}",
|
"hints.profiles.see_more_posts": "Žiūrėti daugiau įrašų serveryje {domain}",
|
||||||
"home.column_settings.show_quotes": "Rodyti paminėjimus",
|
"home.column_settings.show_quotes": "Rodyti komentarus",
|
||||||
"home.column_settings.show_reblogs": "Rodyti pakėlimus",
|
"home.column_settings.show_reblogs": "Rodyti pakėlimus",
|
||||||
"home.column_settings.show_replies": "Rodyti atsakymus",
|
"home.column_settings.show_replies": "Rodyti atsakymus",
|
||||||
"home.hide_announcements": "Slėpti skelbimus",
|
"home.hide_announcements": "Slėpti skelbimus",
|
||||||
@ -497,7 +534,7 @@
|
|||||||
"keyboard_shortcuts.open_media": "Atidaryti mediją",
|
"keyboard_shortcuts.open_media": "Atidaryti mediją",
|
||||||
"keyboard_shortcuts.pinned": "Atverti prisegtų įrašų sąrašą",
|
"keyboard_shortcuts.pinned": "Atverti prisegtų įrašų sąrašą",
|
||||||
"keyboard_shortcuts.profile": "Atidaryti autoriaus (-ės) profilį",
|
"keyboard_shortcuts.profile": "Atidaryti autoriaus (-ės) profilį",
|
||||||
"keyboard_shortcuts.quote": "Paminėti įrašą",
|
"keyboard_shortcuts.quote": "Komentuoti įrašą",
|
||||||
"keyboard_shortcuts.reply": "Atsakyti į įrašą",
|
"keyboard_shortcuts.reply": "Atsakyti į įrašą",
|
||||||
"keyboard_shortcuts.requests": "Atidaryti sekimo prašymų sąrašą",
|
"keyboard_shortcuts.requests": "Atidaryti sekimo prašymų sąrašą",
|
||||||
"keyboard_shortcuts.search": "Fokusuoti paieškos juostą",
|
"keyboard_shortcuts.search": "Fokusuoti paieškos juostą",
|
||||||
@ -612,7 +649,7 @@
|
|||||||
"notification.label.mention": "Paminėjimas",
|
"notification.label.mention": "Paminėjimas",
|
||||||
"notification.label.private_mention": "Privatus paminėjimas",
|
"notification.label.private_mention": "Privatus paminėjimas",
|
||||||
"notification.label.private_reply": "Privatus atsakymas",
|
"notification.label.private_reply": "Privatus atsakymas",
|
||||||
"notification.label.quote": "{name} paminėjo jūsų įrašą",
|
"notification.label.quote": "{name} pakomentavo jūsų įrašą",
|
||||||
"notification.label.reply": "Atsakymas",
|
"notification.label.reply": "Atsakymas",
|
||||||
"notification.mention": "Paminėjimas",
|
"notification.mention": "Paminėjimas",
|
||||||
"notification.mentioned_you": "{name} paminėjo jus",
|
"notification.mentioned_you": "{name} paminėjo jus",
|
||||||
@ -627,7 +664,7 @@
|
|||||||
"notification.moderation_warning.action_suspend": "Tavo paskyra buvo sustabdyta.",
|
"notification.moderation_warning.action_suspend": "Tavo paskyra buvo sustabdyta.",
|
||||||
"notification.own_poll": "Tavo apklausa baigėsi",
|
"notification.own_poll": "Tavo apklausa baigėsi",
|
||||||
"notification.poll": "Baigėsi apklausa, kurioje balsavai",
|
"notification.poll": "Baigėsi apklausa, kurioje balsavai",
|
||||||
"notification.quoted_update": "{name} redagavo jūsų cituotą įrašą",
|
"notification.quoted_update": "{name} redagavo jūsų pakomentuotą įrašą",
|
||||||
"notification.reblog": "{name} dalinosi tavo įrašu",
|
"notification.reblog": "{name} dalinosi tavo įrašu",
|
||||||
"notification.reblog.name_and_others_with_link": "{name} ir <a>{count, plural,one {dar kažkas} few {# kiti} other {# kitų}}</a> pasidalino tavo įrašu",
|
"notification.reblog.name_and_others_with_link": "{name} ir <a>{count, plural,one {dar kažkas} few {# kiti} other {# kitų}}</a> pasidalino tavo įrašu",
|
||||||
"notification.relationships_severance_event": "Prarasti sąryšiai su {name}",
|
"notification.relationships_severance_event": "Prarasti sąryšiai su {name}",
|
||||||
@ -671,7 +708,7 @@
|
|||||||
"notifications.column_settings.mention": "Paminėjimai:",
|
"notifications.column_settings.mention": "Paminėjimai:",
|
||||||
"notifications.column_settings.poll": "Balsavimo rezultatai:",
|
"notifications.column_settings.poll": "Balsavimo rezultatai:",
|
||||||
"notifications.column_settings.push": "Tiesioginiai pranešimai",
|
"notifications.column_settings.push": "Tiesioginiai pranešimai",
|
||||||
"notifications.column_settings.quote": "Paminėjimai:",
|
"notifications.column_settings.quote": "Komentarai:",
|
||||||
"notifications.column_settings.reblog": "Pasidalinimai:",
|
"notifications.column_settings.reblog": "Pasidalinimai:",
|
||||||
"notifications.column_settings.show": "Rodyti stulpelyje",
|
"notifications.column_settings.show": "Rodyti stulpelyje",
|
||||||
"notifications.column_settings.sound": "Paleisti garsą",
|
"notifications.column_settings.sound": "Paleisti garsą",
|
||||||
@ -747,18 +784,19 @@
|
|||||||
"privacy.private.short": "Sekėjai",
|
"privacy.private.short": "Sekėjai",
|
||||||
"privacy.public.long": "Bet kas iš Mastodon ir ne Mastodon",
|
"privacy.public.long": "Bet kas iš Mastodon ir ne Mastodon",
|
||||||
"privacy.public.short": "Vieša",
|
"privacy.public.short": "Vieša",
|
||||||
"privacy.quote.disabled": "{visibility}, paminėjimai išjungti",
|
"privacy.quote.anyone": "{visibility}, komentarai galimi",
|
||||||
"privacy.quote.limited": "{visibility}, paminėjimai apriboti",
|
"privacy.quote.disabled": "{visibility}, komentavimas išjungtas",
|
||||||
|
"privacy.quote.limited": "{visibility}, komentavimas apribotas",
|
||||||
"privacy.unlisted.additional": "Tai veikia lygiai taip pat, kaip ir vieša, tik įrašas nebus rodomas tiesioginiuose srautuose, grotažymėse, naršyme ar Mastodon paieškoje, net jei esi įtraukęs (-usi) visą paskyrą.",
|
"privacy.unlisted.additional": "Tai veikia lygiai taip pat, kaip ir vieša, tik įrašas nebus rodomas tiesioginiuose srautuose, grotažymėse, naršyme ar Mastodon paieškoje, net jei esi įtraukęs (-usi) visą paskyrą.",
|
||||||
"privacy.unlisted.long": "Paslėptas nuo „Mastodon“ paieškos rezultatų, tendencijų ir viešų įrašų sienų",
|
"privacy.unlisted.long": "Paslėptas nuo „Mastodon“ paieškos rezultatų, tendencijų ir viešų įrašų sienų",
|
||||||
"privacy.unlisted.short": "Tyliai vieša",
|
"privacy.unlisted.short": "Tyliai vieša",
|
||||||
"privacy_policy.last_updated": "Paskutinį kartą atnaujinta {date}",
|
"privacy_policy.last_updated": "Paskutinį kartą atnaujinta {date}",
|
||||||
"privacy_policy.title": "Privatumo politika",
|
"privacy_policy.title": "Privatumo politika",
|
||||||
"quote_error.edit": "Paminėjimai negali būti pridedami, kai keičiamas įrašas.",
|
"quote_error.edit": "Komentuoti negalima, kai keičiamas įrašas.",
|
||||||
"quote_error.poll": "Cituoti apklausose negalima.",
|
"quote_error.poll": "Komentuoti apklausose negalima.",
|
||||||
"quote_error.private_mentions": "Cituoti privačius paminėjus nėra leidžiama.",
|
"quote_error.private_mentions": "Komentuoti privačius paminėjimus nėra leidžiama.",
|
||||||
"quote_error.quote": "Leidžiama pateikti tik vieną citatą vienu metu.",
|
"quote_error.quote": "Leidžiama pateikti tik vieną komentarą vienu metu.",
|
||||||
"quote_error.unauthorized": "Jums neleidžiama cituoti šio įrašo.",
|
"quote_error.unauthorized": "Jums neleidžiama komentuoti šio įrašo.",
|
||||||
"quote_error.upload": "Cituoti ir pridėti papildomas bylas negalima.",
|
"quote_error.upload": "Cituoti ir pridėti papildomas bylas negalima.",
|
||||||
"recommended": "Rekomenduojama",
|
"recommended": "Rekomenduojama",
|
||||||
"refresh": "Atnaujinti",
|
"refresh": "Atnaujinti",
|
||||||
@ -777,7 +815,7 @@
|
|||||||
"relative_time.today": "šiandien",
|
"relative_time.today": "šiandien",
|
||||||
"remove_quote_hint.button_label": "Supratau",
|
"remove_quote_hint.button_label": "Supratau",
|
||||||
"remove_quote_hint.message": "Tai galite padaryti iš {icon} parinkčių meniu.",
|
"remove_quote_hint.message": "Tai galite padaryti iš {icon} parinkčių meniu.",
|
||||||
"remove_quote_hint.title": "Norite pašalinti savo citatą?",
|
"remove_quote_hint.title": "Norite pašalinti savo komentarą?",
|
||||||
"reply_indicator.attachments": "{count, plural, one {# priedas} few {# priedai} many {# priedo} other {# priedų}}",
|
"reply_indicator.attachments": "{count, plural, one {# priedas} few {# priedai} many {# priedo} other {# priedų}}",
|
||||||
"reply_indicator.cancel": "Atšaukti",
|
"reply_indicator.cancel": "Atšaukti",
|
||||||
"reply_indicator.poll": "Apklausa",
|
"reply_indicator.poll": "Apklausa",
|
||||||
@ -869,13 +907,13 @@
|
|||||||
"status.admin_account": "Atidaryti prižiūrėjimo sąsają @{name}",
|
"status.admin_account": "Atidaryti prižiūrėjimo sąsają @{name}",
|
||||||
"status.admin_domain": "Atidaryti prižiūrėjimo sąsają {domain}",
|
"status.admin_domain": "Atidaryti prižiūrėjimo sąsają {domain}",
|
||||||
"status.admin_status": "Atidaryti šį įrašą prižiūrėjimo sąsajoje",
|
"status.admin_status": "Atidaryti šį įrašą prižiūrėjimo sąsajoje",
|
||||||
"status.all_disabled": "Įrašo dalinimaisi ir paminėjimai išjungti",
|
"status.all_disabled": "Įrašo pasidalijimai ir komentavimai išjungti",
|
||||||
"status.block": "Blokuoti @{name}",
|
"status.block": "Blokuoti @{name}",
|
||||||
"status.bookmark": "Žymė",
|
"status.bookmark": "Žymė",
|
||||||
"status.cancel_reblog_private": "Nesidalinti",
|
"status.cancel_reblog_private": "Nesidalinti",
|
||||||
"status.cannot_quote": "Jums neleidžiama paminėti šio įrašo",
|
"status.cannot_quote": "Jums neleidžiama komentuoti šio įrašo",
|
||||||
"status.cannot_reblog": "Šis įrašas negali būti pakeltas.",
|
"status.cannot_reblog": "Šis įrašas negali būti pakeltas.",
|
||||||
"status.contains_quote": "Turi citatą",
|
"status.contains_quote": "Turi komentarą",
|
||||||
"status.context.loading": "Įkeliama daugiau atsakymų",
|
"status.context.loading": "Įkeliama daugiau atsakymų",
|
||||||
"status.context.loading_error": "Nepavyko įkelti naujų atsakymų",
|
"status.context.loading_error": "Nepavyko įkelti naujų atsakymų",
|
||||||
"status.context.loading_success": "Įkelti nauji atsakymai",
|
"status.context.loading_success": "Įkelti nauji atsakymai",
|
||||||
@ -908,8 +946,8 @@
|
|||||||
"status.mute_conversation": "Nutildyti pokalbį",
|
"status.mute_conversation": "Nutildyti pokalbį",
|
||||||
"status.open": "Išskleisti šį įrašą",
|
"status.open": "Išskleisti šį įrašą",
|
||||||
"status.pin": "Prisegti prie profilio",
|
"status.pin": "Prisegti prie profilio",
|
||||||
"status.quote": "Paminėjimai",
|
"status.quote": "Komentuoti",
|
||||||
"status.quote.cancel": "Atšaukti paminėjimą",
|
"status.quote.cancel": "Atšaukti komentarą",
|
||||||
"status.quote_error.blocked_account_hint.title": "Šis įrašas yra paslėptas, nes jūs esate užblokavę @{name}.",
|
"status.quote_error.blocked_account_hint.title": "Šis įrašas yra paslėptas, nes jūs esate užblokavę @{name}.",
|
||||||
"status.quote_error.blocked_domain_hint.title": "Šis įrašas yra paslėptas, nes jūs užblokavote {domain}.",
|
"status.quote_error.blocked_domain_hint.title": "Šis įrašas yra paslėptas, nes jūs užblokavote {domain}.",
|
||||||
"status.quote_error.filtered": "Paslėpta dėl vieno iš jūsų filtrų",
|
"status.quote_error.filtered": "Paslėpta dėl vieno iš jūsų filtrų",
|
||||||
@ -925,14 +963,14 @@
|
|||||||
"status.quote_noun": "Paminėjimas",
|
"status.quote_noun": "Paminėjimas",
|
||||||
"status.quote_policy_change": "Keisti, kas gali cituoti",
|
"status.quote_policy_change": "Keisti, kas gali cituoti",
|
||||||
"status.quote_post_author": "Paminėjo įrašą iš @{name}",
|
"status.quote_post_author": "Paminėjo įrašą iš @{name}",
|
||||||
"status.quote_private": "Privačių įrašų negalima cituoti",
|
"status.quote_private": "Privačių įrašų negalima komentuoti",
|
||||||
"status.quotes.empty": "Šio įrašo dar niekas nepaminėjo. Kai kas nors tai padarys, jie bus rodomi čia.",
|
"status.quotes.empty": "Šio įrašo dar niekas nepakomentavo. Kai kas nors tai padarys, jie bus rodomi čia.",
|
||||||
"status.quotes.local_other_disclaimer": "Autoriaus atmesti įrašo paminėjimai nebus rodomi.",
|
"status.quotes.local_other_disclaimer": "Autoriaus atmesti įrašo komentavimai nebus rodomi.",
|
||||||
"status.quotes.remote_other_disclaimer": "Čia bus rodoma tik paminėjimai iš {domain}. Autoriaus atmesti įrašo paminėjimai nebus rodomi.",
|
"status.quotes.remote_other_disclaimer": "Čia bus rodoma tik komentavimai iš {domain}. Autoriaus atmesti įrašo komentavimai nebus rodomi.",
|
||||||
"status.quotes_count": "{count, plural, one {{counter} paminėjimas} few {{counter} paminėjimai} many {{counter} paminėjimai} other {{counter} paminėjimai}}",
|
"status.quotes_count": "{count, plural, one {{counter} komentaras} few {{counter} komentarai} many {{counter} komentarų} other {{counter} komentarai}}",
|
||||||
"status.read_more": "Skaityti daugiau",
|
"status.read_more": "Skaityti daugiau",
|
||||||
"status.reblog": "Dalintis",
|
"status.reblog": "Dalintis",
|
||||||
"status.reblog_or_quote": "Dalintis arba cituoti",
|
"status.reblog_or_quote": "Dalintis arba komentuoti",
|
||||||
"status.reblog_private": "Vėl pasidalinkite su savo sekėjais",
|
"status.reblog_private": "Vėl pasidalinkite su savo sekėjais",
|
||||||
"status.reblogged_by": "{name} pasidalino",
|
"status.reblogged_by": "{name} pasidalino",
|
||||||
"status.reblogs.empty": "Šiuo įrašu dar niekas nesidalino. Kai kas nors tai padarys, jie bus rodomi čia.",
|
"status.reblogs.empty": "Šiuo įrašu dar niekas nesidalino. Kai kas nors tai padarys, jie bus rodomi čia.",
|
||||||
@ -946,8 +984,8 @@
|
|||||||
"status.reply": "Atsakyti",
|
"status.reply": "Atsakyti",
|
||||||
"status.replyAll": "Atsakyti į giją",
|
"status.replyAll": "Atsakyti į giją",
|
||||||
"status.report": "Pranešti apie @{name}",
|
"status.report": "Pranešti apie @{name}",
|
||||||
"status.request_quote": "Citavimo sutikimas",
|
"status.request_quote": "Prašymas pakomentuoti",
|
||||||
"status.revoke_quote": "Pašalinti mano įrašo citavimą iš @{name}’s įrašo",
|
"status.revoke_quote": "Pašalinti mano įrašą iš @{name}’s įrašo",
|
||||||
"status.sensitive_warning": "Jautrus turinys",
|
"status.sensitive_warning": "Jautrus turinys",
|
||||||
"status.share": "Bendrinti",
|
"status.share": "Bendrinti",
|
||||||
"status.show_less_all": "Rodyti mažiau visiems",
|
"status.show_less_all": "Rodyti mažiau visiems",
|
||||||
@ -985,7 +1023,7 @@
|
|||||||
"upload_button.label": "Pridėti vaizdų, vaizdo įrašą arba garso failą",
|
"upload_button.label": "Pridėti vaizdų, vaizdo įrašą arba garso failą",
|
||||||
"upload_error.limit": "Viršyta failo įkėlimo riba.",
|
"upload_error.limit": "Viršyta failo įkėlimo riba.",
|
||||||
"upload_error.poll": "Failų įkėlimas neleidžiamas su apklausomis.",
|
"upload_error.poll": "Failų įkėlimas neleidžiamas su apklausomis.",
|
||||||
"upload_error.quote": "Paminint įrašą bylos įkėlimas negalimas.",
|
"upload_error.quote": "Komentuojant įrašą failo įkėlimas negalimas.",
|
||||||
"upload_form.drag_and_drop.instructions": "Kad paimtum medijos priedą, paspausk tarpo arba įvedimo klavišą. Tempant naudok rodyklių klavišus, kad perkeltum medijos priedą bet kuria kryptimi. Dar kartą paspausk tarpo arba įvedimo klavišą, kad nuleistum medijos priedą naujoje vietoje, arba paspausk grįžimo klavišą, kad atšauktum.",
|
"upload_form.drag_and_drop.instructions": "Kad paimtum medijos priedą, paspausk tarpo arba įvedimo klavišą. Tempant naudok rodyklių klavišus, kad perkeltum medijos priedą bet kuria kryptimi. Dar kartą paspausk tarpo arba įvedimo klavišą, kad nuleistum medijos priedą naujoje vietoje, arba paspausk grįžimo klavišą, kad atšauktum.",
|
||||||
"upload_form.drag_and_drop.on_drag_cancel": "Nutempimas buvo atšauktas. Medijos priedas {item} buvo nuleistas.",
|
"upload_form.drag_and_drop.on_drag_cancel": "Nutempimas buvo atšauktas. Medijos priedas {item} buvo nuleistas.",
|
||||||
"upload_form.drag_and_drop.on_drag_end": "Medijos priedas {item} buvo nuleistas.",
|
"upload_form.drag_and_drop.on_drag_end": "Medijos priedas {item} buvo nuleistas.",
|
||||||
@ -1010,18 +1048,18 @@
|
|||||||
"video.volume_down": "Patildyti",
|
"video.volume_down": "Patildyti",
|
||||||
"video.volume_up": "Pagarsinti",
|
"video.volume_up": "Pagarsinti",
|
||||||
"visibility_modal.button_title": "Nustatyti matomumą",
|
"visibility_modal.button_title": "Nustatyti matomumą",
|
||||||
"visibility_modal.direct_quote_warning.text": "Jei išsaugosite dabartinius nustatymus, įterpta citata bus konvertuota į nuorodą.",
|
"visibility_modal.direct_quote_warning.text": "Jei išsaugosite dabartinius nustatymus, įterptas (embeded) komentaras bus konvertuotas į nuorodą.",
|
||||||
"visibility_modal.direct_quote_warning.title": "Cituojami įrašai negali būti įterpiami į privačius paminėjimus",
|
"visibility_modal.direct_quote_warning.title": "Komentarai negali būti įterpiami į privačius paminėjimus",
|
||||||
"visibility_modal.header": "Matomumas ir sąveika",
|
"visibility_modal.header": "Matomumas ir sąveika",
|
||||||
"visibility_modal.helper.direct_quoting": "Privatūs paminėjimai su žyma @, parašyti platformoje „Mastodon“, negali būti cituojami kitų.",
|
"visibility_modal.helper.direct_quoting": "Privatūs paminėjimai su žyma @, parašyti platformoje „Mastodon“, negali būti komentuojami kitų.",
|
||||||
"visibility_modal.helper.privacy_editing": "Matomumo nustatymai negali būti keičiami po to, kai įrašas yra paskelbtas.",
|
"visibility_modal.helper.privacy_editing": "Matomumo nustatymai negali būti keičiami po to, kai įrašas yra paskelbtas.",
|
||||||
"visibility_modal.helper.privacy_private_self_quote": "Privačių įrašų paminėjimai negali būti skelbiami viešai.",
|
"visibility_modal.helper.privacy_private_self_quote": "Prie privataus įrašo tavo pridėti komentarai negali būti skelbiami viešai.",
|
||||||
"visibility_modal.helper.private_quoting": "Tik sekėjams skirti įrašai, parašyti platformoje „Mastodon“, negali būti cituojami kitų.",
|
"visibility_modal.helper.private_quoting": "Tik sekėjams skirti įrašai, parašyti platformoje „Mastodon“, negali būti komentuojami kitų.",
|
||||||
"visibility_modal.helper.unlisted_quoting": "Kai žmonės jus cituos, jų įrašai taip pat bus paslėpti iš populiariausių naujienų srauto.",
|
"visibility_modal.helper.unlisted_quoting": "Kai žmonės jus komentuoja, jų įrašai taip pat bus paslėpti iš populiariausių naujienų srauto.",
|
||||||
"visibility_modal.instructions": "Kontroliuokite, kas gali bendrauti su šiuo įrašu. Taip pat galite taikyti nustatymus visiems būsimiems įrašams, pereidami į <link>Preferences > Posting defaults</link>.",
|
"visibility_modal.instructions": "Kontroliuokite, kas gali bendrauti su šiuo įrašu. Taip pat galite taikyti nustatymus visiems būsimiems įrašams, pereidami į <link>Preferences > Posting defaults</link>.",
|
||||||
"visibility_modal.privacy_label": "Matomumas",
|
"visibility_modal.privacy_label": "Matomumas",
|
||||||
"visibility_modal.quote_followers": "Tik sekėjai",
|
"visibility_modal.quote_followers": "Tik sekėjai",
|
||||||
"visibility_modal.quote_label": "Kas gali cituoti",
|
"visibility_modal.quote_label": "Kas gali komentuoti",
|
||||||
"visibility_modal.quote_nobody": "Tik aš",
|
"visibility_modal.quote_nobody": "Tik aš",
|
||||||
"visibility_modal.quote_public": "Visi",
|
"visibility_modal.quote_public": "Visi",
|
||||||
"visibility_modal.save": "Išsaugoti"
|
"visibility_modal.save": "Išsaugoti"
|
||||||
|
|||||||
@ -17,8 +17,15 @@
|
|||||||
"account.activity": "Veprimtari",
|
"account.activity": "Veprimtari",
|
||||||
"account.add_note": "Shtoni një shënim personal",
|
"account.add_note": "Shtoni një shënim personal",
|
||||||
"account.add_or_remove_from_list": "Shtoni ose Hiqni prej listash",
|
"account.add_or_remove_from_list": "Shtoni ose Hiqni prej listash",
|
||||||
|
"account.badges.admin": "Përgjegjës",
|
||||||
|
"account.badges.blocked": "E bllokuar",
|
||||||
"account.badges.bot": "Robot",
|
"account.badges.bot": "Robot",
|
||||||
|
"account.badges.domain_blocked": "Përkatësi e bllokuar",
|
||||||
|
"account.badges.follows_you": "Ju ndjek",
|
||||||
"account.badges.group": "Grup",
|
"account.badges.group": "Grup",
|
||||||
|
"account.badges.muted": "E heshtuar",
|
||||||
|
"account.badges.mutuals": "Ndiqni njëri-tjetrin",
|
||||||
|
"account.badges.requested_to_follow": "Kërkoi t’ju ndjekë",
|
||||||
"account.block": "Blloko @{name}",
|
"account.block": "Blloko @{name}",
|
||||||
"account.block_domain": "Blloko përkatësinë {domain}",
|
"account.block_domain": "Blloko përkatësinë {domain}",
|
||||||
"account.block_short": "Bllokoje",
|
"account.block_short": "Bllokoje",
|
||||||
@ -219,7 +226,7 @@
|
|||||||
"collections.description_length_hint": "Kufi prej 100 shenjash",
|
"collections.description_length_hint": "Kufi prej 100 shenjash",
|
||||||
"collections.error_loading_collections": "Pati një gabim teksa provohej të ngarkoheshin koleksionet tuaj.",
|
"collections.error_loading_collections": "Pati një gabim teksa provohej të ngarkoheshin koleksionet tuaj.",
|
||||||
"collections.mark_as_sensitive": "Vëri shenjë si rezervat",
|
"collections.mark_as_sensitive": "Vëri shenjë si rezervat",
|
||||||
"collections.mark_as_sensitive_hint": "Bën fshehejn e përshkrimit të koleksionit dhe llogarive prapa një sinjalizimi lënde. Emri i koleksionit do të jetë ende i dukshëm.",
|
"collections.mark_as_sensitive_hint": "Bën fshehjen e përshkrimit të koleksionit dhe llogarive prapa një sinjalizimi lënde. Emri i koleksionit do të jetë ende i dukshëm.",
|
||||||
"collections.name_length_hint": "Kufi prej 100 shenjash",
|
"collections.name_length_hint": "Kufi prej 100 shenjash",
|
||||||
"collections.no_collections_yet": "Ende pa koleksione.",
|
"collections.no_collections_yet": "Ende pa koleksione.",
|
||||||
"collections.topic_hint": "Shtoni një hashtag që ndihmon të tjerët të kuptojnë temën kryesore të këtij koleksion.",
|
"collections.topic_hint": "Shtoni një hashtag që ndihmon të tjerët të kuptojnë temën kryesore të këtij koleksion.",
|
||||||
|
|||||||
@ -17,8 +17,15 @@
|
|||||||
"account.activity": "Aktivite",
|
"account.activity": "Aktivite",
|
||||||
"account.add_note": "Kişisel bir not ekle",
|
"account.add_note": "Kişisel bir not ekle",
|
||||||
"account.add_or_remove_from_list": "Listelere ekle veya kaldır",
|
"account.add_or_remove_from_list": "Listelere ekle veya kaldır",
|
||||||
|
"account.badges.admin": "Yönetici",
|
||||||
|
"account.badges.blocked": "Engellendi",
|
||||||
"account.badges.bot": "Bot",
|
"account.badges.bot": "Bot",
|
||||||
|
"account.badges.domain_blocked": "Engellenen alan adı",
|
||||||
|
"account.badges.follows_you": "Seni takip ediyor",
|
||||||
"account.badges.group": "Grup",
|
"account.badges.group": "Grup",
|
||||||
|
"account.badges.muted": "Sessize Alındı",
|
||||||
|
"account.badges.mutuals": "Birbirinizi takip ediyorsunuz",
|
||||||
|
"account.badges.requested_to_follow": "Size takip isteği gönderdi",
|
||||||
"account.block": "@{name} adlı kişiyi engelle",
|
"account.block": "@{name} adlı kişiyi engelle",
|
||||||
"account.block_domain": "{domain} alan adını engelle",
|
"account.block_domain": "{domain} alan adını engelle",
|
||||||
"account.block_short": "Engelle",
|
"account.block_short": "Engelle",
|
||||||
@ -212,21 +219,31 @@
|
|||||||
"closed_registrations_modal.find_another_server": "Başka sunucu bul",
|
"closed_registrations_modal.find_another_server": "Başka sunucu bul",
|
||||||
"closed_registrations_modal.preamble": "Mastodon merkeziyetsizdir, bu yüzden hesabınızı nerede oluşturursanız oluşturun, bu sunucudaki herhangi birini takip edebilecek veya onunla etkileşebileceksiniz. Hatta kendi sunucunuzu bile barındırabilirsiniz!",
|
"closed_registrations_modal.preamble": "Mastodon merkeziyetsizdir, bu yüzden hesabınızı nerede oluşturursanız oluşturun, bu sunucudaki herhangi birini takip edebilecek veya onunla etkileşebileceksiniz. Hatta kendi sunucunuzu bile barındırabilirsiniz!",
|
||||||
"closed_registrations_modal.title": "Mastodon'a kayıt olmak",
|
"closed_registrations_modal.title": "Mastodon'a kayıt olmak",
|
||||||
|
"collections.collection_description": "Açıklama",
|
||||||
|
"collections.collection_name": "Ad",
|
||||||
|
"collections.collection_topic": "Konu",
|
||||||
"collections.create_a_collection_hint": "En sevdiğiniz hesapları başkalarına önermek veya paylaşmak için bir koleksiyon oluşturun.",
|
"collections.create_a_collection_hint": "En sevdiğiniz hesapları başkalarına önermek veya paylaşmak için bir koleksiyon oluşturun.",
|
||||||
"collections.create_collection": "Koleksiyon oluştur",
|
"collections.create_collection": "Koleksiyon oluştur",
|
||||||
"collections.delete_collection": "Koleksiyonu sil",
|
"collections.delete_collection": "Koleksiyonu sil",
|
||||||
|
"collections.description_length_hint": "100 karakterle sınırlı",
|
||||||
"collections.error_loading_collections": "Koleksiyonlarınızı yüklemeye çalışırken bir hata oluştu.",
|
"collections.error_loading_collections": "Koleksiyonlarınızı yüklemeye çalışırken bir hata oluştu.",
|
||||||
|
"collections.mark_as_sensitive": "Hassas olarak işaretle",
|
||||||
|
"collections.mark_as_sensitive_hint": "Koleksiyonun açıklamasını ve hesaplarını içerik uyarısının arkasında gizler. Koleksiyon adı hala görünür olacaktır.",
|
||||||
|
"collections.name_length_hint": "100 karakterle sınırlı",
|
||||||
"collections.no_collections_yet": "Henüz hiçbir koleksiyon yok.",
|
"collections.no_collections_yet": "Henüz hiçbir koleksiyon yok.",
|
||||||
|
"collections.topic_hint": "Bu koleksiyonun ana konusunu başkalarının anlamasına yardımcı olacak bir etiket ekleyin.",
|
||||||
"collections.view_collection": "Koleksiyonu görüntüle",
|
"collections.view_collection": "Koleksiyonu görüntüle",
|
||||||
"column.about": "Hakkında",
|
"column.about": "Hakkında",
|
||||||
"column.blocks": "Engellenen kullanıcılar",
|
"column.blocks": "Engellenen kullanıcılar",
|
||||||
"column.bookmarks": "Yer İşaretleri",
|
"column.bookmarks": "Yer İşaretleri",
|
||||||
"column.collections": "Koleksiyonlarım",
|
"column.collections": "Koleksiyonlarım",
|
||||||
"column.community": "Yerel ağ akışı",
|
"column.community": "Yerel ağ akışı",
|
||||||
|
"column.create_collection": "Koleksiyon oluştur",
|
||||||
"column.create_list": "Liste oluştur",
|
"column.create_list": "Liste oluştur",
|
||||||
"column.direct": "Özel bahsetmeler",
|
"column.direct": "Özel bahsetmeler",
|
||||||
"column.directory": "Profillere göz at",
|
"column.directory": "Profillere göz at",
|
||||||
"column.domain_blocks": "Engellenen alan adları",
|
"column.domain_blocks": "Engellenen alan adları",
|
||||||
|
"column.edit_collection": "Koleksiyonu düzenle",
|
||||||
"column.edit_list": "Listeyi düzenle",
|
"column.edit_list": "Listeyi düzenle",
|
||||||
"column.favourites": "Gözdelerin",
|
"column.favourites": "Gözdelerin",
|
||||||
"column.firehose": "Anlık Akışlar",
|
"column.firehose": "Anlık Akışlar",
|
||||||
@ -281,6 +298,9 @@
|
|||||||
"confirmations.delete.confirm": "Sil",
|
"confirmations.delete.confirm": "Sil",
|
||||||
"confirmations.delete.message": "Bu tootu silmek istediğinden emin misin?",
|
"confirmations.delete.message": "Bu tootu silmek istediğinden emin misin?",
|
||||||
"confirmations.delete.title": "Gönderiyi sil?",
|
"confirmations.delete.title": "Gönderiyi sil?",
|
||||||
|
"confirmations.delete_collection.confirm": "Sil",
|
||||||
|
"confirmations.delete_collection.message": "Bu işlem geri alınamaz.",
|
||||||
|
"confirmations.delete_collection.title": "\"{name}\" silinsin mi?",
|
||||||
"confirmations.delete_list.confirm": "Sil",
|
"confirmations.delete_list.confirm": "Sil",
|
||||||
"confirmations.delete_list.message": "Bu listeyi kalıcı olarak silmek istediğinden emin misin?",
|
"confirmations.delete_list.message": "Bu listeyi kalıcı olarak silmek istediğinden emin misin?",
|
||||||
"confirmations.delete_list.title": "Listeyi sil?",
|
"confirmations.delete_list.title": "Listeyi sil?",
|
||||||
|
|||||||
@ -17,8 +17,15 @@
|
|||||||
"account.activity": "Hoạt động",
|
"account.activity": "Hoạt động",
|
||||||
"account.add_note": "Thêm ghi chú",
|
"account.add_note": "Thêm ghi chú",
|
||||||
"account.add_or_remove_from_list": "Sửa danh sách",
|
"account.add_or_remove_from_list": "Sửa danh sách",
|
||||||
|
"account.badges.admin": "Quản trị viên",
|
||||||
|
"account.badges.blocked": "Đã chặn",
|
||||||
"account.badges.bot": "Bot",
|
"account.badges.bot": "Bot",
|
||||||
|
"account.badges.domain_blocked": "Máy chủ đã chặn",
|
||||||
|
"account.badges.follows_you": "Theo dõi bạn",
|
||||||
"account.badges.group": "Nhóm",
|
"account.badges.group": "Nhóm",
|
||||||
|
"account.badges.muted": "Đã phớt lờ",
|
||||||
|
"account.badges.mutuals": "Theo dõi nhau",
|
||||||
|
"account.badges.requested_to_follow": "Yêu cầu theo dõi bạn",
|
||||||
"account.block": "Chặn @{name}",
|
"account.block": "Chặn @{name}",
|
||||||
"account.block_domain": "Chặn mọi thứ từ {domain}",
|
"account.block_domain": "Chặn mọi thứ từ {domain}",
|
||||||
"account.block_short": "Chặn",
|
"account.block_short": "Chặn",
|
||||||
|
|||||||
@ -17,8 +17,15 @@
|
|||||||
"account.activity": "活动",
|
"account.activity": "活动",
|
||||||
"account.add_note": "添加个人备注",
|
"account.add_note": "添加个人备注",
|
||||||
"account.add_or_remove_from_list": "从列表中添加或移除",
|
"account.add_or_remove_from_list": "从列表中添加或移除",
|
||||||
|
"account.badges.admin": "管理员",
|
||||||
|
"account.badges.blocked": "已屏蔽",
|
||||||
"account.badges.bot": "机器人",
|
"account.badges.bot": "机器人",
|
||||||
|
"account.badges.domain_blocked": "已屏蔽域名",
|
||||||
|
"account.badges.follows_you": "关注了你",
|
||||||
"account.badges.group": "群组",
|
"account.badges.group": "群组",
|
||||||
|
"account.badges.muted": "已隐藏",
|
||||||
|
"account.badges.mutuals": "你们互相关注",
|
||||||
|
"account.badges.requested_to_follow": "向您发送了关注请求",
|
||||||
"account.block": "屏蔽 @{name}",
|
"account.block": "屏蔽 @{name}",
|
||||||
"account.block_domain": "屏蔽 {domain} 实例",
|
"account.block_domain": "屏蔽 {domain} 实例",
|
||||||
"account.block_short": "屏蔽",
|
"account.block_short": "屏蔽",
|
||||||
|
|||||||
@ -17,8 +17,15 @@
|
|||||||
"account.activity": "活動",
|
"account.activity": "活動",
|
||||||
"account.add_note": "新增個人備註",
|
"account.add_note": "新增個人備註",
|
||||||
"account.add_or_remove_from_list": "自列表中新增或移除",
|
"account.add_or_remove_from_list": "自列表中新增或移除",
|
||||||
|
"account.badges.admin": "管理員",
|
||||||
|
"account.badges.blocked": "已封鎖",
|
||||||
"account.badges.bot": "機器人",
|
"account.badges.bot": "機器人",
|
||||||
|
"account.badges.domain_blocked": "已封鎖網域",
|
||||||
|
"account.badges.follows_you": "已跟隨您",
|
||||||
"account.badges.group": "群組",
|
"account.badges.group": "群組",
|
||||||
|
"account.badges.muted": "已靜音",
|
||||||
|
"account.badges.mutuals": "跟隨彼此",
|
||||||
|
"account.badges.requested_to_follow": "要求跟隨您",
|
||||||
"account.block": "封鎖 @{name}",
|
"account.block": "封鎖 @{name}",
|
||||||
"account.block_domain": "封鎖來自 {domain} 網域之所有內容",
|
"account.block_domain": "封鎖來自 {domain} 網域之所有內容",
|
||||||
"account.block_short": "封鎖",
|
"account.block_short": "封鎖",
|
||||||
|
|||||||
@ -6,6 +6,7 @@ interface BaseMenuItem {
|
|||||||
text: string;
|
text: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
icon?: IconProp;
|
icon?: IconProp;
|
||||||
|
iconId?: string;
|
||||||
highlighted?: boolean;
|
highlighted?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
dangerous?: boolean;
|
dangerous?: boolean;
|
||||||
|
|||||||
11
app/javascript/mastodon/utils/checks.ts
Normal file
11
app/javascript/mastodon/utils/checks.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export function isValidUrl(
|
||||||
|
url: string,
|
||||||
|
allowedProtocols = ['https:'],
|
||||||
|
): boolean {
|
||||||
|
try {
|
||||||
|
const parsedUrl = new URL(url);
|
||||||
|
return allowedProtocols.includes(parsedUrl.protocol);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
app/javascript/material-icons/400-24px/link_2-fill.svg
Normal file
1
app/javascript/material-icons/400-24px/link_2-fill.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M318-120q-82 0-140-58t-58-140q0-40 15-76t43-64l134-133 56 56-134 134q-17 17-25.5 38.5T200-318q0 49 34.5 83.5T318-200q23 0 45-8.5t39-25.5l133-134 57 57-134 133q-28 28-64 43t-76 15Zm79-220-57-57 223-223 57 57-223 223Zm251-28-56-57 134-133q17-17 25-38t8-44q0-50-34-85t-84-35q-23 0-44.5 8.5T558-726L425-592l-57-56 134-134q28-28 64-43t76-15q82 0 139.5 58T839-641q0 39-14.5 75T782-502L648-368Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 493 B |
1
app/javascript/material-icons/400-24px/link_2.svg
Normal file
1
app/javascript/material-icons/400-24px/link_2.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M318-120q-82 0-140-58t-58-140q0-40 15-76t43-64l134-133 56 56-134 134q-17 17-25.5 38.5T200-318q0 49 34.5 83.5T318-200q23 0 45-8.5t39-25.5l133-134 57 57-134 133q-28 28-64 43t-76 15Zm79-220-57-57 223-223 57 57-223 223Zm251-28-56-57 134-133q17-17 25-38t8-44q0-50-34-85t-84-35q-23 0-44.5 8.5T558-726L425-592l-57-56 134-134q28-28 64-43t76-15q82 0 139.5 58T839-641q0 39-14.5 75T782-502L648-368Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 493 B |
@ -8248,7 +8248,6 @@ noscript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.account__header {
|
.account__header {
|
||||||
overflow: hidden;
|
|
||||||
container: account-header / inline-size;
|
container: account-header / inline-size;
|
||||||
|
|
||||||
&.inactive {
|
&.inactive {
|
||||||
|
|||||||
@ -994,7 +994,7 @@ de:
|
|||||||
trendable: Trendfähig
|
trendable: Trendfähig
|
||||||
unreviewed: Ungeprüft
|
unreviewed: Ungeprüft
|
||||||
usable: Verwendbar
|
usable: Verwendbar
|
||||||
name: Name
|
name: Hashtag
|
||||||
newest: Neueste
|
newest: Neueste
|
||||||
oldest: Älteste
|
oldest: Älteste
|
||||||
open: Öffentlich anzeigen
|
open: Öffentlich anzeigen
|
||||||
|
|||||||
@ -59,7 +59,7 @@ services:
|
|||||||
web:
|
web:
|
||||||
# You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes
|
# You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes
|
||||||
# build: .
|
# build: .
|
||||||
image: ghcr.io/glitch-soc/mastodon:v4.5.5
|
image: ghcr.io/glitch-soc/mastodon:v4.5.6
|
||||||
restart: always
|
restart: always
|
||||||
env_file: .env.production
|
env_file: .env.production
|
||||||
command: bundle exec puma -C config/puma.rb
|
command: bundle exec puma -C config/puma.rb
|
||||||
@ -83,7 +83,7 @@ services:
|
|||||||
# build:
|
# build:
|
||||||
# dockerfile: ./streaming/Dockerfile
|
# dockerfile: ./streaming/Dockerfile
|
||||||
# context: .
|
# context: .
|
||||||
image: ghcr.io/glitch-soc/mastodon-streaming:v4.5.5
|
image: ghcr.io/glitch-soc/mastodon-streaming:v4.5.6
|
||||||
restart: always
|
restart: always
|
||||||
env_file: .env.production
|
env_file: .env.production
|
||||||
command: node ./streaming/index.js
|
command: node ./streaming/index.js
|
||||||
@ -102,7 +102,7 @@ services:
|
|||||||
sidekiq:
|
sidekiq:
|
||||||
# You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes
|
# You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes
|
||||||
# build: .
|
# build: .
|
||||||
image: ghcr.io/glitch-soc/mastodon:v4.5.5
|
image: ghcr.io/glitch-soc/mastodon:v4.5.6
|
||||||
restart: always
|
restart: always
|
||||||
env_file: .env.production
|
env_file: .env.production
|
||||||
command: bundle exec sidekiq
|
command: bundle exec sidekiq
|
||||||
|
|||||||
@ -17,7 +17,7 @@ module Mastodon
|
|||||||
end
|
end
|
||||||
|
|
||||||
def default_prerelease
|
def default_prerelease
|
||||||
'alpha.3'
|
'alpha.4'
|
||||||
end
|
end
|
||||||
|
|
||||||
def prerelease
|
def prerelease
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user