Merge pull request #3502 from glitch-soc/glitch-soc/merge-upstream
Merge upstream changes up to 8bbde181db0d6d79018fb1754a8296e753d47415
This commit is contained in:
commit
28b98143d5
4
.github/workflows/build-container-image.yml
vendored
4
.github/workflows/build-container-image.yml
vendored
@ -87,8 +87,8 @@ jobs:
|
||||
platforms: ${{ matrix.platform }}
|
||||
provenance: false
|
||||
push: ${{ inputs.push_to_images != '' }}
|
||||
cache-from: ${{ inputs.cache && 'type=gha' || '' }}
|
||||
cache-to: ${{ inputs.cache && 'type=gha,mode=max' || '' }}
|
||||
cache-from: ${{ inputs.cache && format('type=gha,scope=build-{0}-{1}', hashFiles(inputs.file_to_build), env.PLATFORM_PAIR) || '' }}
|
||||
cache-to: ${{ inputs.cache && format('type=gha,mode=max,scope=build-{0}-{1}', hashFiles(inputs.file_to_build), env.PLATFORM_PAIR) || '' }}
|
||||
outputs: type=image,"name=${{ env.IMAGE_NAMES }}",push-by-digest=true,name-canonical=true,push=${{ inputs.push_to_images != '' }}
|
||||
|
||||
- name: Export digest
|
||||
|
||||
2
.github/workflows/test-ruby.yml
vendored
2
.github/workflows/test-ruby.yml
vendored
@ -167,7 +167,7 @@ jobs:
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
if: matrix.ruby-version == '.ruby-version'
|
||||
uses: codecov/codecov-action@75cd11691c0faa626561e295848008c8a7dddffe # v5
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6
|
||||
with:
|
||||
files: coverage/lcov/*.lcov
|
||||
env:
|
||||
|
||||
26
.simplecov
Normal file
26
.simplecov
Normal file
@ -0,0 +1,26 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
SimpleCov.start 'rails' do
|
||||
# During parallel runs, ensure unique names for post-run merge
|
||||
command_name "job-#{ENV['TEST_ENV_NUMBER']}" if ENV['TEST_ENV_NUMBER']
|
||||
|
||||
if ENV['CI']
|
||||
require 'simplecov-lcov'
|
||||
formatter SimpleCov::Formatter::LcovFormatter
|
||||
formatter.config.report_with_single_file = true
|
||||
else
|
||||
formatter SimpleCov::Formatter::HTMLFormatter
|
||||
end
|
||||
|
||||
enable_coverage :branch
|
||||
|
||||
add_filter 'lib/linter'
|
||||
|
||||
add_group 'Libraries', 'lib'
|
||||
add_group 'Policies', 'app/policies'
|
||||
add_group 'Presenters', 'app/presenters'
|
||||
add_group 'Search', 'app/chewy'
|
||||
add_group 'Serializers', 'app/serializers'
|
||||
add_group 'Services', 'app/services'
|
||||
add_group 'Validators', 'app/validators'
|
||||
end
|
||||
@ -4,13 +4,48 @@ module Admin
|
||||
class CollectionsController < BaseController
|
||||
before_action :set_account
|
||||
before_action :set_collection, only: :show
|
||||
before_action :set_collections, except: :show
|
||||
|
||||
PER_PAGE = 20
|
||||
|
||||
def index
|
||||
authorize [:admin, :collection], :index?
|
||||
@collection_batch_action = Admin::CollectionBatchAction.new
|
||||
end
|
||||
|
||||
def show
|
||||
authorize @collection, :show?
|
||||
end
|
||||
|
||||
def batch
|
||||
authorize [:admin, :collection], :index?
|
||||
|
||||
@collection_batch_action = Admin::CollectionBatchAction.new(admin_collection_batch_action_params.merge(current_account: current_account, report_id: params[:report_id], type: 'report'))
|
||||
|
||||
@collection_batch_action.save!
|
||||
rescue ActionController::ParameterMissing
|
||||
flash[:alert] = I18n.t('admin.collections.no_collection_selected')
|
||||
ensure
|
||||
redirect_to after_create_redirect_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def after_create_redirect_path
|
||||
report_id = @collections_batch_action&.report_id || params[:report_id]
|
||||
|
||||
if report_id.present?
|
||||
admin_report_path(report_id)
|
||||
else
|
||||
admin_account_collections_path(params[:account_id], params[:page])
|
||||
end
|
||||
end
|
||||
|
||||
def admin_collection_batch_action_params
|
||||
params
|
||||
.expect(admin_collection_batch_action: [collection_ids: []])
|
||||
end
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:account_id])
|
||||
end
|
||||
@ -18,5 +53,9 @@ module Admin
|
||||
def set_collection
|
||||
@collection = @account.collections.includes(accepted_collection_items: :account).find(params[:id])
|
||||
end
|
||||
|
||||
def set_collections
|
||||
@collections = @account.collections.includes(accepted_collection_items: :account).page(params[:page]).per(PER_PAGE)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -0,0 +1,145 @@
|
||||
import { useCallback } from 'react';
|
||||
import type { FC, ReactElement, ReactNode } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import {
|
||||
authorizeFollowRequest,
|
||||
rejectFollowRequest,
|
||||
} from '@/flavours/glitch/actions/accounts';
|
||||
import { useAccountVisibility } from '@/flavours/glitch/hooks/useAccountVisibility';
|
||||
import { useRelationship } from '@/flavours/glitch/hooks/useRelationship';
|
||||
import type { Account } from '@/flavours/glitch/models/account';
|
||||
import { useAppDispatch, useAppSelector } from '@/flavours/glitch/store';
|
||||
import CheckIcon from '@/material-icons/400-24px/check.svg?react';
|
||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||
|
||||
import { AvatarOverlay } from '../avatar_overlay';
|
||||
import { Button } from '../button';
|
||||
import { DisplayName } from '../display_name';
|
||||
import { Icon } from '../icon';
|
||||
import { Permalink } from '../permalink';
|
||||
|
||||
import classes from './styles.module.scss';
|
||||
|
||||
export const AccountBanners: FC<{ account: Account }> = ({ account }) => {
|
||||
const { suspended, hidden } = useAccountVisibility(account.id);
|
||||
const relationship = useRelationship(account.id);
|
||||
|
||||
if (hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let banner: ReactNode = null;
|
||||
|
||||
if (account.memorial) {
|
||||
banner = (
|
||||
<MessageText>
|
||||
<FormattedMessage
|
||||
id='account.in_memoriam'
|
||||
defaultMessage='In Memoriam.'
|
||||
/>
|
||||
</MessageText>
|
||||
);
|
||||
}
|
||||
|
||||
if (account.moved) {
|
||||
banner = <MovedNote account={account} targetAccountId={account.moved} />;
|
||||
}
|
||||
|
||||
if (!suspended && relationship?.requested_by) {
|
||||
banner = <FollowRequestNote account={account} />;
|
||||
}
|
||||
|
||||
if (!banner) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <div className={classes.bannerWrapper}>{banner}</div>;
|
||||
};
|
||||
|
||||
const FollowRequestNote: FC<{ account: Account }> = ({ account }) => {
|
||||
const accountId = account.id;
|
||||
const dispatch = useAppDispatch();
|
||||
const handleAuthorize = useCallback(() => {
|
||||
dispatch(authorizeFollowRequest(accountId));
|
||||
}, [accountId, dispatch]);
|
||||
const handleReject = useCallback(() => {
|
||||
dispatch(rejectFollowRequest(accountId));
|
||||
}, [accountId, dispatch]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MessageText>
|
||||
<FormattedMessage
|
||||
id='account.requested_follow'
|
||||
defaultMessage='{name} has requested to follow you'
|
||||
values={{ name: <DisplayName account={account} variant='simple' /> }}
|
||||
/>
|
||||
</MessageText>
|
||||
|
||||
<div className={classes.bannerActions}>
|
||||
<Button secondary onClick={handleAuthorize}>
|
||||
<Icon id='check' icon={CheckIcon} />
|
||||
<FormattedMessage
|
||||
id='follow_request.authorize'
|
||||
defaultMessage='Authorize'
|
||||
/>
|
||||
</Button>
|
||||
|
||||
<Button secondary onClick={handleReject}>
|
||||
<Icon id='times' icon={CloseIcon} />
|
||||
<FormattedMessage
|
||||
id='follow_request.reject'
|
||||
defaultMessage='Reject'
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const MovedNote: React.FC<{
|
||||
account: Account;
|
||||
targetAccountId: string;
|
||||
}> = ({ account: from, targetAccountId }) => {
|
||||
const to = useAppSelector((state) => state.accounts.get(targetAccountId));
|
||||
|
||||
return (
|
||||
<>
|
||||
<MessageText>
|
||||
<FormattedMessage
|
||||
id='account.moved_to'
|
||||
defaultMessage='{name} has indicated that their new account is now:'
|
||||
values={{
|
||||
name: <DisplayName account={from} variant='simple' />,
|
||||
}}
|
||||
/>
|
||||
</MessageText>
|
||||
|
||||
<div className={classes.bannerActions}>
|
||||
<Permalink
|
||||
to={`/@${to?.acct}`}
|
||||
href={to?.url}
|
||||
className={classes.bannerActionsDisplayName}
|
||||
>
|
||||
<div className='detailed-status__display-avatar'>
|
||||
<AvatarOverlay account={to} friend={from} />
|
||||
</div>
|
||||
<DisplayName account={to} />
|
||||
</Permalink>
|
||||
|
||||
<Permalink to={`/@${to?.acct}`} href={to?.url} className='button'>
|
||||
<FormattedMessage
|
||||
id='account.go_to_profile'
|
||||
defaultMessage='Go to profile'
|
||||
/>
|
||||
</Permalink>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const MessageText: React.FC<{ children: ReactElement }> = ({ children }) => (
|
||||
<div className={classes.bannerText}>{children}</div>
|
||||
);
|
||||
@ -5,7 +5,6 @@ import classNames from 'classnames';
|
||||
import { Helmet } from '@unhead/react/helmet';
|
||||
|
||||
import { openModal } from '@/flavours/glitch/actions/modal';
|
||||
import FollowRequestNoteContainer from '@/flavours/glitch/features/account/containers/follow_request_note_container';
|
||||
import { useLayout } from '@/flavours/glitch/hooks/useLayout';
|
||||
import { useVisibility } from '@/flavours/glitch/hooks/useVisibility';
|
||||
import {
|
||||
@ -22,10 +21,9 @@ import { Avatar } from '../avatar';
|
||||
import { AnimateEmojiProvider } from '../emoji/context';
|
||||
import { FamiliarFollowers } from '../familiar_followers';
|
||||
|
||||
import { AccountBanners } from './banners';
|
||||
import { AccountButtons } from './buttons';
|
||||
import { AccountHeaderFields } from './fields';
|
||||
import { MemorialNote } from './memorial_note';
|
||||
import { MovedNote } from './moved_note';
|
||||
import { AccountName } from './name';
|
||||
import { AccountNote } from './note';
|
||||
import { AccountNumberFields } from './number_fields';
|
||||
@ -51,9 +49,6 @@ export const AccountHeader: React.FC<{
|
||||
}> = ({ accountId, hideTabs }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const account = useAppSelector((state) => state.accounts.get(accountId));
|
||||
const relationship = useAppSelector((state) =>
|
||||
state.relationships.get(accountId),
|
||||
);
|
||||
const hidden = useAppSelector((state) => getAccountHidden(state, accountId));
|
||||
|
||||
const handleOpenAvatar = useCallback(
|
||||
@ -98,18 +93,11 @@ export const AccountHeader: React.FC<{
|
||||
|
||||
return (
|
||||
<div>
|
||||
{!hidden && account.memorial && <MemorialNote />}
|
||||
{!hidden && account.moved && (
|
||||
<MovedNote accountId={account.id} targetAccountId={account.moved} />
|
||||
)}
|
||||
<AccountBanners account={account} />
|
||||
|
||||
<AnimateEmojiProvider
|
||||
className={classNames(!!account.moved && classes.moved)}
|
||||
>
|
||||
{!suspendedOrHidden && !account.moved && relationship?.requested_by && (
|
||||
<FollowRequestNoteContainer account={account} />
|
||||
)}
|
||||
|
||||
<div className={classes.header}>
|
||||
{!suspendedOrHidden && (
|
||||
<img
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
export const MemorialNote: React.FC = () => (
|
||||
<div className='account-memorial-banner'>
|
||||
<div className='account-memorial-banner__message'>
|
||||
<FormattedMessage
|
||||
id='account.in_memoriam'
|
||||
defaultMessage='In Memoriam.'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -1,49 +0,0 @@
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { useAppSelector } from 'flavours/glitch/store';
|
||||
|
||||
import { AvatarOverlay } from '../avatar_overlay';
|
||||
import { DisplayName } from '../display_name';
|
||||
import { Permalink } from '../permalink';
|
||||
|
||||
export const MovedNote: React.FC<{
|
||||
accountId: string;
|
||||
targetAccountId: string;
|
||||
}> = ({ accountId, targetAccountId }) => {
|
||||
const from = useAppSelector((state) => state.accounts.get(accountId));
|
||||
const to = useAppSelector((state) => state.accounts.get(targetAccountId));
|
||||
|
||||
return (
|
||||
<div className='moved-account-banner'>
|
||||
<div className='moved-account-banner__message'>
|
||||
<FormattedMessage
|
||||
id='account.moved_to'
|
||||
defaultMessage='{name} has indicated that their new account is now:'
|
||||
values={{
|
||||
name: <DisplayName account={from} variant='simple' />,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='moved-account-banner__action'>
|
||||
<Permalink
|
||||
to={`/@${to?.acct}`}
|
||||
href={to?.url}
|
||||
className='detailed-status__display-name'
|
||||
>
|
||||
<div className='detailed-status__display-avatar'>
|
||||
<AvatarOverlay account={to} friend={from} />
|
||||
</div>
|
||||
<DisplayName account={to} />
|
||||
</Permalink>
|
||||
|
||||
<Permalink to={`/@${to?.acct}`} href={to?.url} className='button'>
|
||||
<FormattedMessage
|
||||
id='account.go_to_profile'
|
||||
defaultMessage='Go to profile'
|
||||
/>
|
||||
</Permalink>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -453,13 +453,24 @@ $button-fallback-breakpoint: $button-breakpoint + 55px;
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
}
|
||||
|
||||
// Banners
|
||||
|
||||
.bannerWrapper,
|
||||
.bannerBase {
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
background: var(--color-bg-secondary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.bannerWrapper {
|
||||
background: var(--color-bg-tertiary);
|
||||
padding: 16px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.bannerBase {
|
||||
border-radius: 12px;
|
||||
background: var(--color-bg-secondary);
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
@ -475,6 +486,13 @@ $button-fallback-breakpoint: $button-breakpoint + 55px;
|
||||
}
|
||||
}
|
||||
|
||||
.bannerText {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.bannerTextAndActions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -489,6 +507,19 @@ $button-fallback-breakpoint: $button-breakpoint + 55px;
|
||||
}
|
||||
}
|
||||
|
||||
.bannerActions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
width: 100%;
|
||||
margin-top: 16px;
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.bannerDisclaimer {
|
||||
a {
|
||||
color: inherit;
|
||||
@ -519,6 +550,32 @@ $button-fallback-breakpoint: $button-breakpoint + 55px;
|
||||
}
|
||||
}
|
||||
|
||||
.bannerActionsDisplayName {
|
||||
color: var(--color-text-secondary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover strong {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
strong,
|
||||
span {
|
||||
display: block;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
}
|
||||
|
||||
// Buttons
|
||||
|
||||
.followButton {
|
||||
|
||||
@ -104,8 +104,6 @@ export const AccountSubscriptionForm: React.FC<{ accountId: string }> = ({
|
||||
.then(() => {
|
||||
setSubmitting(false);
|
||||
setSubmitted(true);
|
||||
|
||||
return '';
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
setSubmitting(false);
|
||||
@ -133,12 +131,11 @@ export const AccountSubscriptionForm: React.FC<{ accountId: string }> = ({
|
||||
className={classNames(classes.bannerBase, classes.bannerBaseCentered)}
|
||||
>
|
||||
<div className={classes.bannerTextAndActions}>
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id='email_subscriptions.submitted.title'
|
||||
defaultMessage='One more step'
|
||||
/>
|
||||
</h2>
|
||||
<FormattedMessage
|
||||
id='email_subscriptions.submitted.title'
|
||||
defaultMessage='One more step'
|
||||
tagName='h2'
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='email_subscriptions.submitted.lead'
|
||||
defaultMessage='Check your inbox for an email to finish signing up for email updates.'
|
||||
@ -151,15 +148,14 @@ export const AccountSubscriptionForm: React.FC<{ accountId: string }> = ({
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className={classes.bannerBase} noValidate>
|
||||
<div className={classes.bannerTextAndActions}>
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id='email_subscriptions.form.title'
|
||||
defaultMessage='Sign up for email updates from {name}'
|
||||
values={{
|
||||
name: <DisplayName account={account} variant='simple' />,
|
||||
}}
|
||||
/>
|
||||
</h2>
|
||||
<FormattedMessage
|
||||
id='email_subscriptions.form.title'
|
||||
defaultMessage='Sign up for email updates from {name}'
|
||||
tagName='h2'
|
||||
values={{
|
||||
name: <DisplayName account={account} variant='simple' />,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={classes.bannerInputButton}>
|
||||
|
||||
@ -1,131 +0,0 @@
|
||||
import type { ChangeEventHandler, KeyboardEventHandler } from 'react';
|
||||
import { useState, useRef, useCallback, useId } from 'react';
|
||||
|
||||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
import Textarea from 'react-textarea-autosize';
|
||||
|
||||
import { submitAccountNote } from '@/flavours/glitch/actions/account_notes';
|
||||
import { LoadingIndicator } from '@/flavours/glitch/components/loading_indicator';
|
||||
import { useAppDispatch, useAppSelector } from '@/flavours/glitch/store';
|
||||
|
||||
const messages = defineMessages({
|
||||
placeholder: {
|
||||
id: 'account_note.placeholder',
|
||||
defaultMessage: 'Click to add a note',
|
||||
},
|
||||
});
|
||||
|
||||
const AccountNoteUI: React.FC<{
|
||||
initialValue: string | undefined;
|
||||
onSubmit: (newNote: string) => void;
|
||||
wasSaved: boolean;
|
||||
}> = ({ initialValue, onSubmit, wasSaved }) => {
|
||||
const intl = useIntl();
|
||||
const uniqueId = useId();
|
||||
const [value, setValue] = useState(initialValue ?? '');
|
||||
const isLoading = initialValue === undefined;
|
||||
const canSubmitOnBlurRef = useRef(true);
|
||||
|
||||
const handleChange = useCallback<ChangeEventHandler<HTMLTextAreaElement>>(
|
||||
(e) => {
|
||||
setValue(e.target.value);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback<KeyboardEventHandler<HTMLTextAreaElement>>(
|
||||
(e) => {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
|
||||
setValue(initialValue ?? '');
|
||||
|
||||
canSubmitOnBlurRef.current = false;
|
||||
e.currentTarget.blur();
|
||||
} else if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
|
||||
onSubmit(value);
|
||||
|
||||
canSubmitOnBlurRef.current = false;
|
||||
e.currentTarget.blur();
|
||||
}
|
||||
},
|
||||
[initialValue, onSubmit, value],
|
||||
);
|
||||
|
||||
const handleBlur = useCallback(() => {
|
||||
if (initialValue !== value && canSubmitOnBlurRef.current) {
|
||||
onSubmit(value);
|
||||
}
|
||||
canSubmitOnBlurRef.current = true;
|
||||
}, [initialValue, onSubmit, value]);
|
||||
|
||||
return (
|
||||
<div className='account__header__account-note'>
|
||||
<label htmlFor={`account-note-${uniqueId}`}>
|
||||
<FormattedMessage
|
||||
id='account.account_note_header'
|
||||
defaultMessage='Personal note'
|
||||
/>{' '}
|
||||
<span
|
||||
aria-live='polite'
|
||||
role='status'
|
||||
className='inline-alert'
|
||||
style={{ opacity: wasSaved ? 1 : 0 }}
|
||||
>
|
||||
{wasSaved && (
|
||||
<FormattedMessage id='generic.saved' defaultMessage='Saved' />
|
||||
)}
|
||||
</span>
|
||||
</label>
|
||||
{isLoading ? (
|
||||
<div className='account__header__account-note__loading-indicator-wrapper'>
|
||||
<LoadingIndicator />
|
||||
</div>
|
||||
) : (
|
||||
<Textarea
|
||||
id={`account-note-${uniqueId}`}
|
||||
className='account__header__account-note__content'
|
||||
placeholder={intl.formatMessage(messages.placeholder)}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
onBlur={handleBlur}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const AccountNote: React.FC<{
|
||||
accountId: string;
|
||||
}> = ({ accountId }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const initialValue = useAppSelector((state) =>
|
||||
state.relationships.get(accountId)?.get('note'),
|
||||
);
|
||||
const [wasSaved, setWasSaved] = useState(false);
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
(note: string) => {
|
||||
setWasSaved(true);
|
||||
void dispatch(submitAccountNote({ accountId, note }));
|
||||
|
||||
setTimeout(() => {
|
||||
setWasSaved(false);
|
||||
}, 2000);
|
||||
},
|
||||
[dispatch, accountId],
|
||||
);
|
||||
|
||||
return (
|
||||
<AccountNoteUI
|
||||
key={`${accountId}-${initialValue}`}
|
||||
initialValue={initialValue}
|
||||
wasSaved={wasSaved}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -1,210 +0,0 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { useState, useRef, useCallback, useId } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import Overlay from 'react-overlays/Overlay';
|
||||
|
||||
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
||||
import BadgeIcon from '@/material-icons/400-24px/badge.svg?react';
|
||||
import GlobeIcon from '@/material-icons/400-24px/globe.svg?react';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
|
||||
export const DomainPill: React.FC<{
|
||||
domain: string;
|
||||
username: string;
|
||||
isSelf: boolean;
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
}> = ({ domain, username, isSelf, children, className }) => {
|
||||
const accessibilityId = useId();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const triggerRef = useRef(null);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
setOpen(!open);
|
||||
}, [open, setOpen]);
|
||||
|
||||
const handleExpandClick = useCallback(() => {
|
||||
setExpanded(!expanded);
|
||||
}, [expanded, setExpanded]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className={classNames('account__domain-pill', className, {
|
||||
active: open,
|
||||
})}
|
||||
ref={triggerRef}
|
||||
onClick={handleClick}
|
||||
aria-expanded={open}
|
||||
aria-controls={accessibilityId}
|
||||
type='button'
|
||||
>
|
||||
{children ?? domain}
|
||||
</button>
|
||||
|
||||
<Overlay
|
||||
show={open}
|
||||
rootClose
|
||||
onHide={handleClick}
|
||||
offset={[5, 5]}
|
||||
target={triggerRef}
|
||||
>
|
||||
{({ props }) => (
|
||||
<div
|
||||
{...props}
|
||||
role='region'
|
||||
id={accessibilityId}
|
||||
className='account__domain-pill__popout dropdown-animation'
|
||||
>
|
||||
<div className='account__domain-pill__popout__header'>
|
||||
<div className='account__domain-pill__popout__header__icon'>
|
||||
<Icon id='' icon={BadgeIcon} />
|
||||
</div>
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id='domain_pill.whats_in_a_handle'
|
||||
defaultMessage="What's in a handle?"
|
||||
/>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className='account__domain-pill__popout__handle'>
|
||||
<div className='account__domain-pill__popout__handle__label'>
|
||||
{isSelf ? (
|
||||
<FormattedMessage
|
||||
id='domain_pill.your_handle'
|
||||
defaultMessage='Your handle:'
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='domain_pill.their_handle'
|
||||
defaultMessage='Their handle:'
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className='account__domain-pill__popout__handle__handle'>
|
||||
@{username}@{domain}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='account__domain-pill__popout__parts'>
|
||||
<div>
|
||||
<div className='account__domain-pill__popout__parts__icon'>
|
||||
<Icon id='' icon={AlternateEmailIcon} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h6>
|
||||
<FormattedMessage
|
||||
id='domain_pill.username'
|
||||
defaultMessage='Username'
|
||||
/>
|
||||
</h6>
|
||||
<p>
|
||||
{isSelf ? (
|
||||
<FormattedMessage
|
||||
id='domain_pill.your_username'
|
||||
defaultMessage='Your unique identifier on this server. It’s possible to find users with the same username on different servers.'
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='domain_pill.their_username'
|
||||
defaultMessage='Their unique identifier on their server. It’s possible to find users with the same username on different servers.'
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className='account__domain-pill__popout__parts__icon'>
|
||||
<Icon id='' icon={GlobeIcon} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h6>
|
||||
<FormattedMessage
|
||||
id='domain_pill.server'
|
||||
defaultMessage='Server'
|
||||
/>
|
||||
</h6>
|
||||
<p>
|
||||
{isSelf ? (
|
||||
<FormattedMessage
|
||||
id='domain_pill.your_server'
|
||||
defaultMessage='Your digital home, where all of your posts live. Don’t like this one? Transfer servers at any time and bring your followers, too.'
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='domain_pill.their_server'
|
||||
defaultMessage='Their digital home, where all of their posts live.'
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
{isSelf ? (
|
||||
<FormattedMessage
|
||||
id='domain_pill.who_you_are'
|
||||
defaultMessage='Because your handle says who you are and where you are, people can interact with you across the social web of <button>ActivityPub-powered platforms</button>.'
|
||||
values={{
|
||||
button: (x) => (
|
||||
<button
|
||||
onClick={handleExpandClick}
|
||||
className='link-button'
|
||||
type='button'
|
||||
>
|
||||
{x}
|
||||
</button>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='domain_pill.who_they_are'
|
||||
defaultMessage='Since handles say who someone is and where they are, you can interact with people across the social web of <button>ActivityPub-powered platforms</button>.'
|
||||
values={{
|
||||
button: (x) => (
|
||||
<button
|
||||
onClick={handleExpandClick}
|
||||
className='link-button'
|
||||
type='button'
|
||||
>
|
||||
{x}
|
||||
</button>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
|
||||
{expanded && (
|
||||
<>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='domain_pill.activitypub_like_language'
|
||||
defaultMessage='ActivityPub is like the language Mastodon speaks with other social networks.'
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='domain_pill.activitypub_lets_connect'
|
||||
defaultMessage='It lets you connect and interact with people not just on Mastodon, but across different social apps too.'
|
||||
/>
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Overlay>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -1,41 +0,0 @@
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
import CheckIcon from '@/material-icons/400-24px/check.svg?react';
|
||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
import { DisplayName } from '@/flavours/glitch/components/display_name';
|
||||
|
||||
export default class FollowRequestNote extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.record.isRequired,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { account, onAuthorize, onReject } = this.props;
|
||||
|
||||
return (
|
||||
<div className='follow-request-banner'>
|
||||
<div className='follow-request-banner__message'>
|
||||
<FormattedMessage id='account.requested_follow' defaultMessage='{name} has requested to follow you' values={{ name: <DisplayName account={account} variant='simple' /> }} />
|
||||
</div>
|
||||
|
||||
<div className='follow-request-banner__action'>
|
||||
<button type='button' className='button button-secondary button--confirmation' onClick={onAuthorize}>
|
||||
<Icon id='check' icon={CheckIcon} />
|
||||
<FormattedMessage id='follow_request.authorize' defaultMessage='Authorize' />
|
||||
</button>
|
||||
|
||||
<button type='button' className='button button-secondary button--destructive' onClick={onReject}>
|
||||
<Icon id='times' icon={CloseIcon} />
|
||||
<FormattedMessage id='follow_request.reject' defaultMessage='Reject' />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { authorizeFollowRequest, rejectFollowRequest } from 'flavours/glitch/actions/accounts';
|
||||
|
||||
import FollowRequestNote from '../components/follow_request_note';
|
||||
|
||||
const mapDispatchToProps = (dispatch, { account }) => ({
|
||||
onAuthorize () {
|
||||
dispatch(authorizeFollowRequest(account.get('id')));
|
||||
},
|
||||
|
||||
onReject () {
|
||||
dispatch(rejectFollowRequest(account.get('id')));
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps)(FollowRequestNote);
|
||||
@ -8556,47 +8556,6 @@ noscript {
|
||||
}
|
||||
}
|
||||
|
||||
.moved-account-banner,
|
||||
.follow-request-banner,
|
||||
.account-memorial-banner {
|
||||
padding: 20px;
|
||||
background: var(--color-bg-tertiary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
&__message {
|
||||
color: var(--color-text-secondary);
|
||||
padding: 8px 0;
|
||||
padding-top: 0;
|
||||
padding-bottom: 4px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
&__action {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.detailed-status__display-name {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.follow-request-banner .button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.account-memorial-banner__message {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.column-inline-form {
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
|
||||
140
app/javascript/mastodon/components/account_header/banners.tsx
Normal file
140
app/javascript/mastodon/components/account_header/banners.tsx
Normal file
@ -0,0 +1,140 @@
|
||||
import { useCallback } from 'react';
|
||||
import type { FC, ReactElement, ReactNode } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
authorizeFollowRequest,
|
||||
rejectFollowRequest,
|
||||
} from '@/mastodon/actions/accounts';
|
||||
import { useAccountVisibility } from '@/mastodon/hooks/useAccountVisibility';
|
||||
import { useRelationship } from '@/mastodon/hooks/useRelationship';
|
||||
import type { Account } from '@/mastodon/models/account';
|
||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||
import CheckIcon from '@/material-icons/400-24px/check.svg?react';
|
||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||
|
||||
import { AvatarOverlay } from '../avatar_overlay';
|
||||
import { Button } from '../button';
|
||||
import { DisplayName } from '../display_name';
|
||||
import { Icon } from '../icon';
|
||||
|
||||
import classes from './styles.module.scss';
|
||||
|
||||
export const AccountBanners: FC<{ account: Account }> = ({ account }) => {
|
||||
const { suspended, hidden } = useAccountVisibility(account.id);
|
||||
const relationship = useRelationship(account.id);
|
||||
|
||||
if (hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let banner: ReactNode = null;
|
||||
|
||||
if (account.memorial) {
|
||||
banner = (
|
||||
<MessageText>
|
||||
<FormattedMessage
|
||||
id='account.in_memoriam'
|
||||
defaultMessage='In Memoriam.'
|
||||
/>
|
||||
</MessageText>
|
||||
);
|
||||
}
|
||||
|
||||
if (account.moved) {
|
||||
banner = <MovedNote account={account} targetAccountId={account.moved} />;
|
||||
}
|
||||
|
||||
if (!suspended && relationship?.requested_by) {
|
||||
banner = <FollowRequestNote account={account} />;
|
||||
}
|
||||
|
||||
if (!banner) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <div className={classes.bannerWrapper}>{banner}</div>;
|
||||
};
|
||||
|
||||
const FollowRequestNote: FC<{ account: Account }> = ({ account }) => {
|
||||
const accountId = account.id;
|
||||
const dispatch = useAppDispatch();
|
||||
const handleAuthorize = useCallback(() => {
|
||||
dispatch(authorizeFollowRequest(accountId));
|
||||
}, [accountId, dispatch]);
|
||||
const handleReject = useCallback(() => {
|
||||
dispatch(rejectFollowRequest(accountId));
|
||||
}, [accountId, dispatch]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MessageText>
|
||||
<FormattedMessage
|
||||
id='account.requested_follow'
|
||||
defaultMessage='{name} has requested to follow you'
|
||||
values={{ name: <DisplayName account={account} variant='simple' /> }}
|
||||
/>
|
||||
</MessageText>
|
||||
|
||||
<div className={classes.bannerActions}>
|
||||
<Button secondary onClick={handleAuthorize}>
|
||||
<Icon id='check' icon={CheckIcon} />
|
||||
<FormattedMessage
|
||||
id='follow_request.authorize'
|
||||
defaultMessage='Authorize'
|
||||
/>
|
||||
</Button>
|
||||
|
||||
<Button secondary onClick={handleReject}>
|
||||
<Icon id='times' icon={CloseIcon} />
|
||||
<FormattedMessage
|
||||
id='follow_request.reject'
|
||||
defaultMessage='Reject'
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const MovedNote: React.FC<{
|
||||
account: Account;
|
||||
targetAccountId: string;
|
||||
}> = ({ account: from, targetAccountId }) => {
|
||||
const to = useAppSelector((state) => state.accounts.get(targetAccountId));
|
||||
|
||||
return (
|
||||
<>
|
||||
<MessageText>
|
||||
<FormattedMessage
|
||||
id='account.moved_to'
|
||||
defaultMessage='{name} has indicated that their new account is now:'
|
||||
values={{
|
||||
name: <DisplayName account={from} variant='simple' />,
|
||||
}}
|
||||
/>
|
||||
</MessageText>
|
||||
|
||||
<div className={classes.bannerActions}>
|
||||
<Link to={`/@${to?.acct}`} className={classes.bannerActionsDisplayName}>
|
||||
<AvatarOverlay account={to} friend={from} />
|
||||
<DisplayName account={to} />
|
||||
</Link>
|
||||
|
||||
<Link to={`/@${to?.acct}`} className='button'>
|
||||
<FormattedMessage
|
||||
id='account.go_to_profile'
|
||||
defaultMessage='Go to profile'
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const MessageText: React.FC<{ children: ReactElement }> = ({ children }) => (
|
||||
<div className={classes.bannerText}>{children}</div>
|
||||
);
|
||||
@ -5,7 +5,6 @@ import classNames from 'classnames';
|
||||
import { Helmet } from '@unhead/react/helmet';
|
||||
|
||||
import { openModal } from '@/mastodon/actions/modal';
|
||||
import FollowRequestNoteContainer from '@/mastodon/features/account/containers/follow_request_note_container';
|
||||
import { useLayout } from '@/mastodon/hooks/useLayout';
|
||||
import { useVisibility } from '@/mastodon/hooks/useVisibility';
|
||||
import {
|
||||
@ -22,10 +21,9 @@ import { Avatar } from '../avatar';
|
||||
import { AnimateEmojiProvider } from '../emoji/context';
|
||||
import { FamiliarFollowers } from '../familiar_followers';
|
||||
|
||||
import { AccountBanners } from './banners';
|
||||
import { AccountButtons } from './buttons';
|
||||
import { AccountHeaderFields } from './fields';
|
||||
import { MemorialNote } from './memorial_note';
|
||||
import { MovedNote } from './moved_note';
|
||||
import { AccountName } from './name';
|
||||
import { AccountNote } from './note';
|
||||
import { AccountNumberFields } from './number_fields';
|
||||
@ -51,9 +49,6 @@ export const AccountHeader: React.FC<{
|
||||
}> = ({ accountId, hideTabs }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const account = useAppSelector((state) => state.accounts.get(accountId));
|
||||
const relationship = useAppSelector((state) =>
|
||||
state.relationships.get(accountId),
|
||||
);
|
||||
const hidden = useAppSelector((state) => getAccountHidden(state, accountId));
|
||||
|
||||
const handleOpenAvatar = useCallback(
|
||||
@ -98,18 +93,11 @@ export const AccountHeader: React.FC<{
|
||||
|
||||
return (
|
||||
<div>
|
||||
{!hidden && account.memorial && <MemorialNote />}
|
||||
{!hidden && account.moved && (
|
||||
<MovedNote accountId={account.id} targetAccountId={account.moved} />
|
||||
)}
|
||||
<AccountBanners account={account} />
|
||||
|
||||
<AnimateEmojiProvider
|
||||
className={classNames(!!account.moved && classes.moved)}
|
||||
>
|
||||
{!suspendedOrHidden && !account.moved && relationship?.requested_by && (
|
||||
<FollowRequestNoteContainer account={account} />
|
||||
)}
|
||||
|
||||
<div className={classes.header}>
|
||||
{!suspendedOrHidden && (
|
||||
<img
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
export const MemorialNote: React.FC = () => (
|
||||
<div className='account-memorial-banner'>
|
||||
<div className='account-memorial-banner__message'>
|
||||
<FormattedMessage
|
||||
id='account.in_memoriam'
|
||||
defaultMessage='In Memoriam.'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -1,46 +0,0 @@
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { useAppSelector } from '@/mastodon/store';
|
||||
|
||||
import { AvatarOverlay } from '../avatar_overlay';
|
||||
import { DisplayName } from '../display_name';
|
||||
|
||||
export const MovedNote: React.FC<{
|
||||
accountId: string;
|
||||
targetAccountId: string;
|
||||
}> = ({ accountId, targetAccountId }) => {
|
||||
const from = useAppSelector((state) => state.accounts.get(accountId));
|
||||
const to = useAppSelector((state) => state.accounts.get(targetAccountId));
|
||||
|
||||
return (
|
||||
<div className='moved-account-banner'>
|
||||
<div className='moved-account-banner__message'>
|
||||
<FormattedMessage
|
||||
id='account.moved_to'
|
||||
defaultMessage='{name} has indicated that their new account is now:'
|
||||
values={{
|
||||
name: <DisplayName account={from} variant='simple' />,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='moved-account-banner__action'>
|
||||
<Link to={`/@${to?.acct}`} className='detailed-status__display-name'>
|
||||
<div className='detailed-status__display-avatar'>
|
||||
<AvatarOverlay account={to} friend={from} />
|
||||
</div>
|
||||
<DisplayName account={to} />
|
||||
</Link>
|
||||
|
||||
<Link to={`/@${to?.acct}`} className='button'>
|
||||
<FormattedMessage
|
||||
id='account.go_to_profile'
|
||||
defaultMessage='Go to profile'
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -453,13 +453,24 @@ $button-fallback-breakpoint: $button-breakpoint + 55px;
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
}
|
||||
|
||||
// Banners
|
||||
|
||||
.bannerWrapper,
|
||||
.bannerBase {
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
background: var(--color-bg-secondary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.bannerWrapper {
|
||||
background: var(--color-bg-tertiary);
|
||||
padding: 16px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.bannerBase {
|
||||
border-radius: 12px;
|
||||
background: var(--color-bg-secondary);
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
@ -475,6 +486,13 @@ $button-fallback-breakpoint: $button-breakpoint + 55px;
|
||||
}
|
||||
}
|
||||
|
||||
.bannerText {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.bannerTextAndActions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -489,6 +507,19 @@ $button-fallback-breakpoint: $button-breakpoint + 55px;
|
||||
}
|
||||
}
|
||||
|
||||
.bannerActions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
width: 100%;
|
||||
margin-top: 16px;
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.bannerDisclaimer {
|
||||
a {
|
||||
color: inherit;
|
||||
@ -519,6 +550,32 @@ $button-fallback-breakpoint: $button-breakpoint + 55px;
|
||||
}
|
||||
}
|
||||
|
||||
.bannerActionsDisplayName {
|
||||
color: var(--color-text-secondary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover strong {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
strong,
|
||||
span {
|
||||
display: block;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
}
|
||||
|
||||
// Buttons
|
||||
|
||||
.followButton {
|
||||
|
||||
@ -104,8 +104,6 @@ export const AccountSubscriptionForm: React.FC<{ accountId: string }> = ({
|
||||
.then(() => {
|
||||
setSubmitting(false);
|
||||
setSubmitted(true);
|
||||
|
||||
return '';
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
setSubmitting(false);
|
||||
@ -133,12 +131,11 @@ export const AccountSubscriptionForm: React.FC<{ accountId: string }> = ({
|
||||
className={classNames(classes.bannerBase, classes.bannerBaseCentered)}
|
||||
>
|
||||
<div className={classes.bannerTextAndActions}>
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id='email_subscriptions.submitted.title'
|
||||
defaultMessage='One more step'
|
||||
/>
|
||||
</h2>
|
||||
<FormattedMessage
|
||||
id='email_subscriptions.submitted.title'
|
||||
defaultMessage='One more step'
|
||||
tagName='h2'
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='email_subscriptions.submitted.lead'
|
||||
defaultMessage='Check your inbox for an email to finish signing up for email updates.'
|
||||
@ -151,15 +148,14 @@ export const AccountSubscriptionForm: React.FC<{ accountId: string }> = ({
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className={classes.bannerBase} noValidate>
|
||||
<div className={classes.bannerTextAndActions}>
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id='email_subscriptions.form.title'
|
||||
defaultMessage='Sign up for email updates from {name}'
|
||||
values={{
|
||||
name: <DisplayName account={account} variant='simple' />,
|
||||
}}
|
||||
/>
|
||||
</h2>
|
||||
<FormattedMessage
|
||||
id='email_subscriptions.form.title'
|
||||
defaultMessage='Sign up for email updates from {name}'
|
||||
tagName='h2'
|
||||
values={{
|
||||
name: <DisplayName account={account} variant='simple' />,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={classes.bannerInputButton}>
|
||||
|
||||
@ -1,131 +0,0 @@
|
||||
import type { ChangeEventHandler, KeyboardEventHandler } from 'react';
|
||||
import { useState, useRef, useCallback, useId } from 'react';
|
||||
|
||||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
import Textarea from 'react-textarea-autosize';
|
||||
|
||||
import { submitAccountNote } from '@/mastodon/actions/account_notes';
|
||||
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
|
||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||
|
||||
const messages = defineMessages({
|
||||
placeholder: {
|
||||
id: 'account_note.placeholder',
|
||||
defaultMessage: 'Click to add a note',
|
||||
},
|
||||
});
|
||||
|
||||
const AccountNoteUI: React.FC<{
|
||||
initialValue: string | undefined;
|
||||
onSubmit: (newNote: string) => void;
|
||||
wasSaved: boolean;
|
||||
}> = ({ initialValue, onSubmit, wasSaved }) => {
|
||||
const intl = useIntl();
|
||||
const uniqueId = useId();
|
||||
const [value, setValue] = useState(initialValue ?? '');
|
||||
const isLoading = initialValue === undefined;
|
||||
const canSubmitOnBlurRef = useRef(true);
|
||||
|
||||
const handleChange = useCallback<ChangeEventHandler<HTMLTextAreaElement>>(
|
||||
(e) => {
|
||||
setValue(e.target.value);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback<KeyboardEventHandler<HTMLTextAreaElement>>(
|
||||
(e) => {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
|
||||
setValue(initialValue ?? '');
|
||||
|
||||
canSubmitOnBlurRef.current = false;
|
||||
e.currentTarget.blur();
|
||||
} else if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
|
||||
onSubmit(value);
|
||||
|
||||
canSubmitOnBlurRef.current = false;
|
||||
e.currentTarget.blur();
|
||||
}
|
||||
},
|
||||
[initialValue, onSubmit, value],
|
||||
);
|
||||
|
||||
const handleBlur = useCallback(() => {
|
||||
if (initialValue !== value && canSubmitOnBlurRef.current) {
|
||||
onSubmit(value);
|
||||
}
|
||||
canSubmitOnBlurRef.current = true;
|
||||
}, [initialValue, onSubmit, value]);
|
||||
|
||||
return (
|
||||
<div className='account__header__account-note'>
|
||||
<label htmlFor={`account-note-${uniqueId}`}>
|
||||
<FormattedMessage
|
||||
id='account.account_note_header'
|
||||
defaultMessage='Personal note'
|
||||
/>{' '}
|
||||
<span
|
||||
aria-live='polite'
|
||||
role='status'
|
||||
className='inline-alert'
|
||||
style={{ opacity: wasSaved ? 1 : 0 }}
|
||||
>
|
||||
{wasSaved && (
|
||||
<FormattedMessage id='generic.saved' defaultMessage='Saved' />
|
||||
)}
|
||||
</span>
|
||||
</label>
|
||||
{isLoading ? (
|
||||
<div className='account__header__account-note__loading-indicator-wrapper'>
|
||||
<LoadingIndicator />
|
||||
</div>
|
||||
) : (
|
||||
<Textarea
|
||||
id={`account-note-${uniqueId}`}
|
||||
className='account__header__account-note__content'
|
||||
placeholder={intl.formatMessage(messages.placeholder)}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
onBlur={handleBlur}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const AccountNote: React.FC<{
|
||||
accountId: string;
|
||||
}> = ({ accountId }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const initialValue = useAppSelector((state) =>
|
||||
state.relationships.get(accountId)?.get('note'),
|
||||
);
|
||||
const [wasSaved, setWasSaved] = useState(false);
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
(note: string) => {
|
||||
setWasSaved(true);
|
||||
void dispatch(submitAccountNote({ accountId, note }));
|
||||
|
||||
setTimeout(() => {
|
||||
setWasSaved(false);
|
||||
}, 2000);
|
||||
},
|
||||
[dispatch, accountId],
|
||||
);
|
||||
|
||||
return (
|
||||
<AccountNoteUI
|
||||
key={`${accountId}-${initialValue}`}
|
||||
initialValue={initialValue}
|
||||
wasSaved={wasSaved}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -1,210 +0,0 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { useState, useRef, useCallback, useId } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import Overlay from 'react-overlays/Overlay';
|
||||
|
||||
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
||||
import BadgeIcon from '@/material-icons/400-24px/badge.svg?react';
|
||||
import GlobeIcon from '@/material-icons/400-24px/globe.svg?react';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
|
||||
export const DomainPill: React.FC<{
|
||||
domain: string;
|
||||
username: string;
|
||||
isSelf: boolean;
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
}> = ({ domain, username, isSelf, children, className }) => {
|
||||
const accessibilityId = useId();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const triggerRef = useRef(null);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
setOpen(!open);
|
||||
}, [open, setOpen]);
|
||||
|
||||
const handleExpandClick = useCallback(() => {
|
||||
setExpanded(!expanded);
|
||||
}, [expanded, setExpanded]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className={classNames('account__domain-pill', className, {
|
||||
active: open,
|
||||
})}
|
||||
ref={triggerRef}
|
||||
onClick={handleClick}
|
||||
aria-expanded={open}
|
||||
aria-controls={accessibilityId}
|
||||
type='button'
|
||||
>
|
||||
{children ?? domain}
|
||||
</button>
|
||||
|
||||
<Overlay
|
||||
show={open}
|
||||
rootClose
|
||||
onHide={handleClick}
|
||||
offset={[5, 5]}
|
||||
target={triggerRef}
|
||||
>
|
||||
{({ props }) => (
|
||||
<div
|
||||
{...props}
|
||||
role='region'
|
||||
id={accessibilityId}
|
||||
className='account__domain-pill__popout dropdown-animation'
|
||||
>
|
||||
<div className='account__domain-pill__popout__header'>
|
||||
<div className='account__domain-pill__popout__header__icon'>
|
||||
<Icon id='' icon={BadgeIcon} />
|
||||
</div>
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id='domain_pill.whats_in_a_handle'
|
||||
defaultMessage="What's in a handle?"
|
||||
/>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className='account__domain-pill__popout__handle'>
|
||||
<div className='account__domain-pill__popout__handle__label'>
|
||||
{isSelf ? (
|
||||
<FormattedMessage
|
||||
id='domain_pill.your_handle'
|
||||
defaultMessage='Your handle:'
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='domain_pill.their_handle'
|
||||
defaultMessage='Their handle:'
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className='account__domain-pill__popout__handle__handle'>
|
||||
@{username}@{domain}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='account__domain-pill__popout__parts'>
|
||||
<div>
|
||||
<div className='account__domain-pill__popout__parts__icon'>
|
||||
<Icon id='' icon={AlternateEmailIcon} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h6>
|
||||
<FormattedMessage
|
||||
id='domain_pill.username'
|
||||
defaultMessage='Username'
|
||||
/>
|
||||
</h6>
|
||||
<p>
|
||||
{isSelf ? (
|
||||
<FormattedMessage
|
||||
id='domain_pill.your_username'
|
||||
defaultMessage='Your unique identifier on this server. It’s possible to find users with the same username on different servers.'
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='domain_pill.their_username'
|
||||
defaultMessage='Their unique identifier on their server. It’s possible to find users with the same username on different servers.'
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className='account__domain-pill__popout__parts__icon'>
|
||||
<Icon id='' icon={GlobeIcon} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h6>
|
||||
<FormattedMessage
|
||||
id='domain_pill.server'
|
||||
defaultMessage='Server'
|
||||
/>
|
||||
</h6>
|
||||
<p>
|
||||
{isSelf ? (
|
||||
<FormattedMessage
|
||||
id='domain_pill.your_server'
|
||||
defaultMessage='Your digital home, where all of your posts live. Don’t like this one? Transfer servers at any time and bring your followers, too.'
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='domain_pill.their_server'
|
||||
defaultMessage='Their digital home, where all of their posts live.'
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
{isSelf ? (
|
||||
<FormattedMessage
|
||||
id='domain_pill.who_you_are'
|
||||
defaultMessage='Because your handle says who you are and where you are, people can interact with you across the social web of <button>ActivityPub-powered platforms</button>.'
|
||||
values={{
|
||||
button: (x) => (
|
||||
<button
|
||||
onClick={handleExpandClick}
|
||||
className='link-button'
|
||||
type='button'
|
||||
>
|
||||
{x}
|
||||
</button>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='domain_pill.who_they_are'
|
||||
defaultMessage='Since handles say who someone is and where they are, you can interact with people across the social web of <button>ActivityPub-powered platforms</button>.'
|
||||
values={{
|
||||
button: (x) => (
|
||||
<button
|
||||
onClick={handleExpandClick}
|
||||
className='link-button'
|
||||
type='button'
|
||||
>
|
||||
{x}
|
||||
</button>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
|
||||
{expanded && (
|
||||
<>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='domain_pill.activitypub_like_language'
|
||||
defaultMessage='ActivityPub is like the language Mastodon speaks with other social networks.'
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='domain_pill.activitypub_lets_connect'
|
||||
defaultMessage='It lets you connect and interact with people not just on Mastodon, but across different social apps too.'
|
||||
/>
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Overlay>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -1,41 +0,0 @@
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
import CheckIcon from '@/material-icons/400-24px/check.svg?react';
|
||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { DisplayName } from '@/mastodon/components/display_name';
|
||||
|
||||
export default class FollowRequestNote extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.record.isRequired,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { account, onAuthorize, onReject } = this.props;
|
||||
|
||||
return (
|
||||
<div className='follow-request-banner'>
|
||||
<div className='follow-request-banner__message'>
|
||||
<FormattedMessage id='account.requested_follow' defaultMessage='{name} has requested to follow you' values={{ name: <DisplayName account={account} variant='simple' /> }} />
|
||||
</div>
|
||||
|
||||
<div className='follow-request-banner__action'>
|
||||
<button type='button' className='button button-secondary button--confirmation' onClick={onAuthorize}>
|
||||
<Icon id='check' icon={CheckIcon} />
|
||||
<FormattedMessage id='follow_request.authorize' defaultMessage='Authorize' />
|
||||
</button>
|
||||
|
||||
<button type='button' className='button button-secondary button--destructive' onClick={onReject}>
|
||||
<Icon id='times' icon={CloseIcon} />
|
||||
<FormattedMessage id='follow_request.reject' defaultMessage='Reject' />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { authorizeFollowRequest, rejectFollowRequest } from 'mastodon/actions/accounts';
|
||||
|
||||
import FollowRequestNote from '../components/follow_request_note';
|
||||
|
||||
const mapDispatchToProps = (dispatch, { account }) => ({
|
||||
onAuthorize () {
|
||||
dispatch(authorizeFollowRequest(account.get('id')));
|
||||
},
|
||||
|
||||
onReject () {
|
||||
dispatch(rejectFollowRequest(account.get('id')));
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps)(FollowRequestNote);
|
||||
@ -55,6 +55,7 @@
|
||||
"account.go_to_profile": "Gwelet ar profil",
|
||||
"account.hide_reblogs": "Kuzh skignadennoù gant @{name}",
|
||||
"account.in_memoriam": "E koun.",
|
||||
"account.join_modal.day": "Deiz",
|
||||
"account.join_modal.years": "{number, plural, two {vloavezh} many {a vloavezhioù} other {bloavezh}}",
|
||||
"account.joined_short": "Amañ abaoe",
|
||||
"account.languages": "Cheñch ar yezhoù koumanantet",
|
||||
@ -100,6 +101,7 @@
|
||||
"account_edit.image_edit.add_button": "Ouzhpennañ ur skeudenn",
|
||||
"account_edit.image_edit.remove_button": "Dilemel ar skeudenn",
|
||||
"account_edit.item_list.delete": "Dilemel {name}",
|
||||
"account_edit.item_list.edit": "Cheñch {name}",
|
||||
"account_edit.save": "Enrollañ",
|
||||
"account_edit.upload_modal.back": "Distreiñ",
|
||||
"account_edit.upload_modal.step_upload.header": "Dibab ur skeudenn",
|
||||
|
||||
@ -257,7 +257,6 @@
|
||||
"account_edit_tags.tag_status_count": "{count, plural, one {# post} other {# posts}}",
|
||||
"account_list.hidden_notice": "This is only visible to you. To show this list to others, go to <link>{page} > {modal} > {field}</link>.",
|
||||
"account_list.total": "{total, plural, one {# account} other {# accounts}}",
|
||||
"account_note.placeholder": "Click to add note",
|
||||
"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.retention.average": "Average",
|
||||
@ -596,19 +595,6 @@
|
||||
"domain_block_modal.you_will_lose_num_followers": "You will lose {followersCount, plural, one {{followersCountDisplay} follower} other {{followersCountDisplay} followers}} and {followingCount, plural, one {{followingCountDisplay} person you follow} other {{followingCountDisplay} people you follow}}.",
|
||||
"domain_block_modal.you_will_lose_relationships": "You will lose all followers and people you follow from this server.",
|
||||
"domain_block_modal.you_wont_see_posts": "You won't see posts or notifications from users on this server.",
|
||||
"domain_pill.activitypub_lets_connect": "It lets you connect and interact with people not just on Mastodon, but across different social apps too.",
|
||||
"domain_pill.activitypub_like_language": "ActivityPub is like the language Mastodon speaks with other social networks.",
|
||||
"domain_pill.server": "Server",
|
||||
"domain_pill.their_handle": "Their handle:",
|
||||
"domain_pill.their_server": "Their digital home, where all of their posts live.",
|
||||
"domain_pill.their_username": "Their unique identifier on their server. It’s possible to find users with the same username on different servers.",
|
||||
"domain_pill.username": "Username",
|
||||
"domain_pill.whats_in_a_handle": "What's in a handle?",
|
||||
"domain_pill.who_they_are": "Since handles say who someone is and where they are, you can interact with people across the social web of <button>ActivityPub-powered platforms</button>.",
|
||||
"domain_pill.who_you_are": "Because your handle says who you are and where you are, people can interact with you across the social web of <button>ActivityPub-powered platforms</button>.",
|
||||
"domain_pill.your_handle": "Your handle:",
|
||||
"domain_pill.your_server": "Your digital home, where all of your posts live. Don’t like this one? Transfer servers at any time and bring your followers, too.",
|
||||
"domain_pill.your_username": "Your unique identifier on this server. It’s possible to find users with the same username on different servers.",
|
||||
"dropdown.empty": "Select an option",
|
||||
"email_subscriptions.email": "Email",
|
||||
"email_subscriptions.form.action": "Subscribe",
|
||||
@ -745,7 +731,6 @@
|
||||
"footer.terms_of_service": "Terms of service",
|
||||
"form_error.blank": "Field cannot be blank.",
|
||||
"form_field.optional": "(optional)",
|
||||
"generic.saved": "Saved",
|
||||
"getting_started.heading": "Getting started",
|
||||
"hashtag.admin_moderation": "Open moderation interface for #{name}",
|
||||
"hashtag.browse": "Browse posts in #{hashtag}",
|
||||
|
||||
@ -79,7 +79,7 @@
|
||||
"account.join_modal.share.intro": "Compartí un mensaje introductorio",
|
||||
"account.join_modal.share.welcome": "Compartí un mensaje de bienvenida",
|
||||
"account.join_modal.years": "{number, plural, one {# año} other {# años}}",
|
||||
"account.joined_short": "Se unió",
|
||||
"account.joined_short": "En este servidor desde",
|
||||
"account.languages": "Cambiar idiomas suscritos",
|
||||
"account.last_active": "Última actividad",
|
||||
"account.link_verified_on": "La propiedad de este enlace fue verificada el {date}",
|
||||
|
||||
@ -605,6 +605,9 @@
|
||||
"dropdown.empty": "انتخاب یک گزینه",
|
||||
"email_subscriptions.email": "رایانامه",
|
||||
"email_subscriptions.form.action": "اشتراک",
|
||||
"email_subscriptions.submitted.title": "یک گام دیگر",
|
||||
"email_subscriptions.validation.email.blocked": "فراهم کنندهٔ رایانامهٔ مسدود",
|
||||
"email_subscriptions.validation.email.invalid": "نشانی رایانامه نامعتبر",
|
||||
"embed.instructions": "جاسازی این فرسته روی پایگاهتان با رونوشت کردن کد زیر.",
|
||||
"embed.preview": "این گونه دیده خواهد شد:",
|
||||
"emoji_button.activity": "فعالیت",
|
||||
@ -622,7 +625,10 @@
|
||||
"emoji_button.search_results": "نتایج جستوجو",
|
||||
"emoji_button.symbols": "نمادها",
|
||||
"emoji_button.travel": "سفر و مکان",
|
||||
"empty_column.account_featured.other": "{acct} هنوز چیزی را معرّفی نکرده.",
|
||||
"empty_column.account_featured_self.no_collections_button": "ایجاد مجموعه",
|
||||
"empty_column.account_featured_self.no_collections_hide_tab": "نهفتن این زبانه",
|
||||
"empty_column.account_featured_self.pre_collections": "منتظر مجموعهها باشید",
|
||||
"empty_column.account_featured_self.showcase_accounts": "نمایشکاه حسابهای محبوبتان",
|
||||
"empty_column.account_featured_unknown.other": "این حساب هنوز چیزی را معرّفی نکرده.",
|
||||
"empty_column.account_hides_collections": "کاربر خواسته که این اطّلاعات در دسترس نباشند",
|
||||
@ -791,6 +797,7 @@
|
||||
"keyboard_shortcuts.direct": "گشودن ستون اشارههای خصوصی",
|
||||
"keyboard_shortcuts.down": "پایین بردن در سیاهه",
|
||||
"keyboard_shortcuts.enter": "گشودن فرسته",
|
||||
"keyboard_shortcuts.explore": "گشودن خط زمانی داغ",
|
||||
"keyboard_shortcuts.favourite": "پسندیدن فرسته",
|
||||
"keyboard_shortcuts.favourites": "گشودن فهرست برگزیدهها",
|
||||
"keyboard_shortcuts.federated": "گشودن خط زمانی همگانی",
|
||||
@ -1060,6 +1067,7 @@
|
||||
"privacy.private.short": "پیگیرندگان",
|
||||
"privacy.public.long": "هرکسی در و بیرون از ماستودون",
|
||||
"privacy.public.short": "عمومی",
|
||||
"privacy.quote.anyone": "{visibility}، نقل مجاز",
|
||||
"privacy.quote.disabled": "{visibility}، نقلها از کار افتاده",
|
||||
"privacy.quote.limited": "{visibility}، نقلها محدود شده",
|
||||
"privacy.unlisted.additional": "درست مثل عمومی رفتار میکند؛ جز این که فرسته در برچسبها یا خوراکهای زنده، کشف یا جستوجوی ماستودون ظاهر نخواهد شد. حتا اگر کلیّت نمایهتان اجازه داده باشد.",
|
||||
@ -1105,6 +1113,7 @@
|
||||
"report.category.title_account": "نمایه",
|
||||
"report.category.title_status": "فرسته",
|
||||
"report.close": "انجام شد",
|
||||
"report.collection_comment": "چرا میخواهید این مجموعه را گزارش دهید؟",
|
||||
"report.comment.title": "آیا چیز دیگری هست که فکر میکنید باید بدانیم؟",
|
||||
"report.forward": "فرستادن به {target}",
|
||||
"report.forward_hint": "این حساب در کارساز دیگری ثبت شده. آیا میخواهید رونوشتی ناشناس از این گزارش به آنجا هم فرستاده شود؟",
|
||||
@ -1126,6 +1135,8 @@
|
||||
"report.rules.title": "کدام قوانین نقض شدهاند؟",
|
||||
"report.statuses.subtitle": "همهٔ موارد انجام شده را برگزینید",
|
||||
"report.statuses.title": "آیا فرستهای وجود دارد که از این گزارش پشتیبانی کند؟",
|
||||
"report.submission_error": "گزارش نتوانست ثبت شود",
|
||||
"report.submission_error_details": "لزفاً اتّصال شبکهتان را بررسی کرده و دوباره تلاش کنید.",
|
||||
"report.submit": "فرستادن",
|
||||
"report.target": "گزارش کردن {target}",
|
||||
"report.thanks.take_action": "در اینجا گزینههایی برای کنترل آنچه در ماستودون میبینید، وجود دارد:",
|
||||
@ -1173,12 +1184,15 @@
|
||||
"server_banner.active_users": "کاربر فعّال",
|
||||
"server_banner.administered_by": "به مدیریت:",
|
||||
"server_banner.is_one_of_many": "{domain} یکی از بسیاری از سرورهای مستقل ماستودون است که می توانید از آن برای شرکت در fediverse استفاده کنید.",
|
||||
"server_banner.more_about_this_server": "بیشتر دربارهٔ این کارساز",
|
||||
"server_banner.server_stats": "آمار کارساز:",
|
||||
"sign_in_banner.create_account": "ایجاد حساب",
|
||||
"sign_in_banner.follow_anyone": "هر کسی را در سراسر فدیورس دنبال کنید و همه را به ترتیب زمانی ببینید. هیچ الگوریتم، تبلیغات یا طعمه کلیکی در چشم نیست.",
|
||||
"sign_in_banner.mastodon_is": "ماستودون بهترین راه برای پیگیری اتفاقات است.",
|
||||
"sign_in_banner.sign_in": "ورود",
|
||||
"sign_in_banner.sso_redirect": "ورود یا ثبت نام",
|
||||
"skip_links.skip_to_content": "پرش به محتوای اصلی",
|
||||
"skip_links.skip_to_navigation": "پرش به پیمایش اصلی",
|
||||
"status.admin_account": "گشودن واسط مدیریت برای @{name}",
|
||||
"status.admin_domain": "گشودن واسط مدیریت برای {domain}",
|
||||
"status.admin_status": "گشودن این فرسته در واسط مدیریت",
|
||||
|
||||
@ -328,11 +328,15 @@
|
||||
"annual_report.summary.share_on_mastodon": "לשתף במסטודון",
|
||||
"attachments_list.unprocessed": "(לא מעובד)",
|
||||
"audio.hide": "השתק",
|
||||
"block_modal.no_collections": "שתיכן/שניכם לא יכוליםות להוסיף זו את זה לאוספים. אתן תוסרו אוטומטית מהאוספים האחד של השניה, אם הופעתן בהם.",
|
||||
"block_modal.remote_users_caveat": "אנו נבקש מהשרת {domain} לכבד את החלטתך. עם זאת, ציות למוסכמות איננו מובטח כיוון ששרתים מסויימים עשויים לטפל בחסימות בצורה אחרת. הודעות פומביות עדיין יהיו גלויות לעיני משתמשים שאינם מחוברים.",
|
||||
"block_modal.show_less": "הצג פחות",
|
||||
"block_modal.show_more": "הצג עוד",
|
||||
"block_modal.they_cant_mention": "לא תוכלו לאזכר, לעקוב או לצטט האחת את השני.",
|
||||
"block_modal.they_cant_see_posts": "הם לא יכולים לראות את תכניכן ואתם לא תוכלו לראות את שלהם.",
|
||||
"block_modal.they_will_know": "הם יכולים לראות שהם חסומים.",
|
||||
"block_modal.title": "לחסום משתמש?",
|
||||
"block_modal.you_wont_see_mentions": "לא תראה הודעות מאחרים שמאזכרות אותם.",
|
||||
"boost_modal.combo": "ניתן להקיש {combo} כדי לדלג בפעם הבאה",
|
||||
"boost_modal.reblog": "להדהד הודעה?",
|
||||
"boost_modal.undo_reblog": "להסיר הדהוד?",
|
||||
@ -792,6 +796,7 @@
|
||||
"info_button.label": "עזרה",
|
||||
"info_button.what_is_alt_text": "<h1>מהו כיתוב חלופי?</h1> <p>כיתוב חלופי משמש תיאור מילולי של תמונות לסובלים ממגבלות ראיה, חיבורי רשת איטיים, או אלו הצריכים הקשר יותר מפורט לתוכן המולטימדיה המצורף.</p> <p>ניתן לשפר את הנגישות והבנת התוכן לכולם ע\"י כתיבת תיאור ברור, תמציתי ונטול פניות.</p> <ul> <li>כיסוי היסודות החשובים</li> <li>סיכום המלל שבתמונות</li> <li>שימוש במבנה משפטים רגיל</li> <li>יש להמנע מחזרה על מידע</li> <li>אם העזרים הויזואליים הם דיאגרמות או מפות, התמקדו במגמות וממצאים מרכזיים.</li> </ul>",
|
||||
"interaction_modal.action": "כדי להגיב להודעה של {name}, עליך להזהות בשם המשתמש שלך בשרת המסטודון בו את.ה משתמש.ת.",
|
||||
"interaction_modal.action_follow": "כדי לעקוב אחרי {name}, עליך להזהות בשם המשתמש שלך בשרת המסטודון בו את.ה משתמש.ת.",
|
||||
"interaction_modal.go": "המשך",
|
||||
"interaction_modal.no_account_yet": "אין לך עדיין חשבון?",
|
||||
"interaction_modal.on_another_server": "על שרת אחר",
|
||||
|
||||
@ -79,7 +79,7 @@
|
||||
"account.join_modal.share.intro": "Condividi un post introduttivo",
|
||||
"account.join_modal.share.welcome": "Condividi un post di benvenuto",
|
||||
"account.join_modal.years": "{number, plural, one {anno} other {anni}}",
|
||||
"account.joined_short": "Iscritto",
|
||||
"account.joined_short": "Account iscritto",
|
||||
"account.languages": "Modifica le lingue d'iscrizione",
|
||||
"account.last_active": "Ultima attività",
|
||||
"account.link_verified_on": "La proprietà di questo link è stata controllata il {date}",
|
||||
@ -328,7 +328,7 @@
|
||||
"annual_report.summary.share_on_mastodon": "Condividi su Mastodon",
|
||||
"attachments_list.unprocessed": "(non elaborato)",
|
||||
"audio.hide": "Nascondi audio",
|
||||
"block_modal.no_collections": "Nessuno dei due può aggiungere l'altro alle rispettive raccolte. Verrete automaticamente rimossi dalle collezioni esistenti dell'altro, se presenti.",
|
||||
"block_modal.no_collections": "Nessuno dei due può aggiungere l'altro alle rispettive collezioni. Verrete automaticamente rimossi dalle collezioni esistenti dell'altro, se presenti.",
|
||||
"block_modal.remote_users_caveat": "Chiederemo al server {domain} di rispettare la tua decisione. Tuttavia, la conformità non è garantita poiché alcuni server potrebbero gestire i blocchi in modo diverso. I post pubblici potrebbero essere ancora visibili agli utenti che non hanno effettuato l'accesso.",
|
||||
"block_modal.show_less": "Mostra meno",
|
||||
"block_modal.show_more": "Mostra di più",
|
||||
@ -336,7 +336,7 @@
|
||||
"block_modal.they_cant_see_posts": "Non potrà vedere i tuoi contenuti e tu non potrai vedere i suoi.",
|
||||
"block_modal.they_will_know": "Può vedere che è stato/a bloccato/a.",
|
||||
"block_modal.title": "Bloccare l'utente?",
|
||||
"block_modal.you_wont_see_mentions": "Non vedrai messaggi di altri che menzionano l'utente.",
|
||||
"block_modal.you_wont_see_mentions": "Non vedrai post da altri che menzionano l'utente.",
|
||||
"boost_modal.combo": "Puoi premere {combo} per saltare questo passaggio, la prossima volta",
|
||||
"boost_modal.reblog": "Condividere il post?",
|
||||
"boost_modal.undo_reblog": "Annullare la condivisione del post?",
|
||||
|
||||
@ -330,6 +330,7 @@
|
||||
"column_search.cancel": "Semmet",
|
||||
"combobox.close_results": "Mdel igmaḍ",
|
||||
"combobox.loading": "Yessalay-d",
|
||||
"combobox.no_results_found": "Ulac ugmaḍ yettwafen i unadi-a",
|
||||
"combobox.open_results": "Ldi igmaḍ",
|
||||
"community.column_settings.local_only": "Adigan kan",
|
||||
"community.column_settings.media_only": "Imidyaten kan",
|
||||
|
||||
@ -224,7 +224,9 @@
|
||||
"account_edit.profile_tab.show_media.title": "Tabblad 'Media' tonen",
|
||||
"account_edit.profile_tab.show_media_replies.description": "Wanneer dit is ingeschakeld, worden op het tabblad Media zowel jouw berichten en reacties op berichten van anderen weergegeven.",
|
||||
"account_edit.profile_tab.show_media_replies.title": "Jouw reacties aan het tabblad 'Media' toevoegen",
|
||||
"account_edit.profile_tab.show_relations.description": "Accounts die je volgt en jouw volgers aan andere gebruikers op je profiel tonen. Mensen kunnen nog steeds zien of je ze volgt.",
|
||||
"account_edit.profile_tab.show_relations.title": "‘Volgers’ en ‘Gevolgde accounts’ tonen",
|
||||
"account_edit.profile_tab.subtitle": "Verander hoe je profiel wordt weergeven.",
|
||||
"account_edit.profile_tab.title": "Weergave-instellingen profiel",
|
||||
"account_edit.save": "Opslaan",
|
||||
"account_edit.upload_modal.back": "Terug",
|
||||
@ -253,6 +255,7 @@
|
||||
"account_edit_tags.search_placeholder": "Voer een hashtag in…",
|
||||
"account_edit_tags.suggestions": "Suggesties:",
|
||||
"account_edit_tags.tag_status_count": "{count, plural, one {# bericht} other {# berichten}}",
|
||||
"account_list.hidden_notice": "Dit is alleen voor jou zichtbaar. Om deze lijst aan anderen te tonen, ga naar <link>{page} > {modal} > {field}</link>.",
|
||||
"account_list.total": "{total, plural, one {# account} other {# accounts}}",
|
||||
"account_note.placeholder": "Klik om een opmerking toe te voegen",
|
||||
"admin.dashboard.daily_retention": "Retentiegraad van gebruikers per dag, vanaf registratie",
|
||||
@ -325,9 +328,11 @@
|
||||
"annual_report.summary.share_on_mastodon": "Op Mastodon delen",
|
||||
"attachments_list.unprocessed": "(niet verwerkt)",
|
||||
"audio.hide": "Audio verbergen",
|
||||
"block_modal.no_collections": "Jullie kunnen elkaar onderling niet aan verzamelingen toevoegen. Indien van toepassing worden jullie automatisch uit elkaars bestaande verzamelingen verwijderd.",
|
||||
"block_modal.remote_users_caveat": "We vragen de server {domain} om je besluit te respecteren. Het naleven hiervan is echter niet gegarandeerd, omdat sommige servers blokkades anders kunnen interpreteren. Openbare berichten zijn mogelijk nog steeds zichtbaar voor niet-ingelogde gebruikers.",
|
||||
"block_modal.show_less": "Minder tonen",
|
||||
"block_modal.show_more": "Meer tonen",
|
||||
"block_modal.they_cant_mention": "Je kunt elkaar niet vermelden, volgen of citeren.",
|
||||
"block_modal.they_cant_see_posts": "De persoon kan jouw berichten niet zien en jij ook niet diens berichten.",
|
||||
"block_modal.they_will_know": "De persoon kan zien dat die wordt geblokkeerd.",
|
||||
"block_modal.title": "Gebruiker blokkeren?",
|
||||
@ -644,6 +649,7 @@
|
||||
"empty_column.account_unavailable": "Profiel is niet beschikbaar",
|
||||
"empty_column.blocks": "Je hebt nog geen gebruikers geblokkeerd.",
|
||||
"empty_column.bookmarked_statuses": "Jij hebt nog geen berichten aan je bladwijzers toegevoegd. Wanneer je er een aan jouw bladwijzers toevoegt, valt deze hier te zien.",
|
||||
"empty_column.collections.featured_in": "Je bent aan nog geen enkele verzameling toegevoegd.",
|
||||
"empty_column.community": "De lokale tijdlijn is nog leeg. Plaats een openbaar bericht om de spits af te bijten!",
|
||||
"empty_column.direct": "Je hebt nog geen privéberichten. Wanneer je er een verstuurt of ontvangt, komen deze hier te staan.",
|
||||
"empty_column.disabled_feed": "Deze tijdlijn is uitgeschakeld door je serverbeheerders.",
|
||||
|
||||
@ -113,6 +113,7 @@
|
||||
"account.name.copy": "Kopier brukaradressa",
|
||||
"account.name.help.domain": "{domain} er tenaren som lagrar brukarprofilen og innlegga.",
|
||||
"account.name.help.domain_self": "{domain} er tenaren som lagrar brukarprofilen og innlegga dine.",
|
||||
"account.name.help.footer": "Du kan senda epost til folk som bruker ulike eposttenester, og nett slik er det med Mastodon og resten av Allheimen òg: Du kan samhandla med folk her eller andre stader uansett kva tenar dei bruker.",
|
||||
"account.name.help.header": "Ei brukaradresse er som ei epostadresse",
|
||||
"account.name.help.username": "{username} er brukarnamnet til denne kontoen på tenaren deira. Folk på andre tenarar kan ha same brukarnamnet.",
|
||||
"account.name.help.username_self": "{username} er brukarnamnet ditt på denne tenaren. Folk på andre tenarar kan ha same brukarnamnet.",
|
||||
@ -127,6 +128,7 @@
|
||||
"account.note.edit_button": "Rediger",
|
||||
"account.note.title": "Personleg notat (berre synleg for deg)",
|
||||
"account.open_original_page": "Opne originalsida",
|
||||
"account.pending": "Ventar",
|
||||
"account.posts": "Tut",
|
||||
"account.remove_from_followers": "Fjern {name} frå fylgjarane dine",
|
||||
"account.report": "Rapporter @{name}",
|
||||
@ -222,6 +224,10 @@
|
||||
"account_edit.profile_tab.show_media.title": "Vis «Medium»-fana",
|
||||
"account_edit.profile_tab.show_media_replies.description": "Når medium-fana er i bruk, syner ho både innlegga dine og svar på andre folk sine innlegg.",
|
||||
"account_edit.profile_tab.show_media_replies.title": "Ta med svar i «Medium»-fana",
|
||||
"account_edit.profile_tab.show_relations.description": "Syner fylgjarane dine og dei du fylgjer til andre brukarar til andre brukarar på profilen din. Folk vil framleis kunna sjå om du fylgjer dei.",
|
||||
"account_edit.profile_tab.show_relations.title": "Vis «fylgjarar» og «dei du fylgjer»",
|
||||
"account_edit.profile_tab.subtitle": "Tilpass korleis profilen din ser ut.",
|
||||
"account_edit.profile_tab.title": "Innstillingar for profilvising",
|
||||
"account_edit.save": "Lagre",
|
||||
"account_edit.upload_modal.back": "Tilbake",
|
||||
"account_edit.upload_modal.done": "Ferdig",
|
||||
@ -249,6 +255,7 @@
|
||||
"account_edit_tags.search_placeholder": "Skriv ein emneknagg…",
|
||||
"account_edit_tags.suggestions": "Framlegg:",
|
||||
"account_edit_tags.tag_status_count": "{count, plural, one {# innlegg} other {# innlegg}}",
|
||||
"account_list.hidden_notice": "Berre du ser dette. Gå til <link>{page} > {modal} > {field}</link> for å syna lista til andre.",
|
||||
"account_list.total": "{total, plural, one {# konto} other {# kontoar}}",
|
||||
"account_note.placeholder": "Klikk for å leggja til merknad",
|
||||
"admin.dashboard.daily_retention": "Mengda brukarar aktive ved dagar etter registrering",
|
||||
|
||||
@ -150,11 +150,11 @@
|
||||
"account_edit.advanced_settings.bot_hint": "Bu hesap temelde otomatik eylemler gerçekleştirir ve izlenmeyebiliri diğerlerine bildir",
|
||||
"account_edit.advanced_settings.bot_label": "Otomatik hesap",
|
||||
"account_edit.advanced_settings.title": "Gelişmiş ayarlar",
|
||||
"account_edit.bio.add_label": "Kişisel bilgi ekle",
|
||||
"account_edit.bio.edit_label": "Kişisel bilgiyi düzenle",
|
||||
"account_edit.bio.add_label": "Biyografi ekle",
|
||||
"account_edit.bio.edit_label": "Biyografiyi düzenle",
|
||||
"account_edit.bio.placeholder": "Diğerlerinin sizi tanımasına yardımcı olmak için kısa bir tanıtım ekleyin.",
|
||||
"account_edit.bio.title": "Kişisel bilgiler",
|
||||
"account_edit.bio_modal.add_title": "Kişisel bilgi ekle",
|
||||
"account_edit.bio.title": "Biyografi",
|
||||
"account_edit.bio_modal.add_title": "Biyografi ekle",
|
||||
"account_edit.bio_modal.edit_title": "Biyografiyi düzenle",
|
||||
"account_edit.column_button": "Tamam",
|
||||
"account_edit.column_title": "Profili Düzenle",
|
||||
@ -184,7 +184,7 @@
|
||||
"account_edit.field_edit_modal.edit_title": "Özel alanı düzenle",
|
||||
"account_edit.field_edit_modal.length_warning": "Önerilen karakter sınırı aşıldı. Mobil kullanıcılar sahayı tam olarak görmeyebilirler.",
|
||||
"account_edit.field_edit_modal.link_emoji_warning": "Url'lerle birlikte özel emoji kullanmamanızı öneririz. Her ikisini de içeren özel alanlar, kullanıcıların kafasını karıştırmamak için bağlantı yerine yalnızca metin olarak görüntülenir.",
|
||||
"account_edit.field_edit_modal.name_hint": "Örn. \"Kişisel web sitesi\"",
|
||||
"account_edit.field_edit_modal.name_hint": "Ör. \"Kişisel web sitesi\"",
|
||||
"account_edit.field_edit_modal.name_label": "Etiket",
|
||||
"account_edit.field_edit_modal.url_warning": "Bağlantı eklemek için lütfen başlangıca {protocol} ekleyin.",
|
||||
"account_edit.field_edit_modal.value_hint": "Ör. “https://example.me”",
|
||||
@ -242,9 +242,9 @@
|
||||
"account_edit.upload_modal.title_replace.avatar": "Profil fotoğrafını değiştir",
|
||||
"account_edit.upload_modal.title_replace.header": "Kapak fotoğrafını değiştirin",
|
||||
"account_edit.verified_modal.details": "Kişisel web sitelerine bağlantıları doğrulayarak Mastodon profilinize güvenilirlik katın. İşte böyle çalışıyor:",
|
||||
"account_edit.verified_modal.invisible_link.details": "Bağlantıyı başlığınıza ekleyin. Önemli olan kısım, kullanıcı tarafından oluşturulan içeriğe sahip web sitelerinde kimlik sahtekarlığını önleyen rel=\"me\" özniteliğidir. {tag} yerine sayfanın başlığında bir bağlantı etiketi bile kullanabilirsiniz, ancak HTML, JavaScript çalıştırılmadan erişilebilir olmalıdır.",
|
||||
"account_edit.verified_modal.invisible_link.details": "Bağlantıyı sitenizin header kısmına ekleyin. Önemli olan kısım, kullanıcı tarafından oluşturulan içeriğe sahip web sitelerinde kimlik sahtekarlığını önleyen rel=\"me\" özniteliğidir. {tag} yerine sayfanın başlığında bir bağlantı etiketi bile kullanabilirsiniz, ancak HTML, JavaScript çalıştırılmadan erişilebilir olmalıdır.",
|
||||
"account_edit.verified_modal.invisible_link.summary": "Bağlantıyı nasıl görünmez hale getirebilirim?",
|
||||
"account_edit.verified_modal.step1.header": "Aşağıdaki HTML kodunu kopyalayın ve web sitenizin başlığına yapıştırın",
|
||||
"account_edit.verified_modal.step1.header": "Aşağıdaki HTML kodunu kopyalayın ve web sitenizin header kısmına yapıştırın",
|
||||
"account_edit.verified_modal.step2.details": "Web sitenizi özel alan olarak zaten eklediyseniz, doğrulamayı tetiklemek için onu silip yeniden eklemeniz gerekir.",
|
||||
"account_edit.verified_modal.step2.header": "Web sitenizi özel bir alan olarak ekleyin",
|
||||
"account_edit.verified_modal.title": "Doğrulanmış bir bağlantı nasıl eklenir",
|
||||
@ -346,7 +346,7 @@
|
||||
"bundle_column_error.network.body": "Sayfayı yüklemeye çalışırken bir hata oluştu. Bu durum internet bağlantınızdaki veya bu sunucudaki geçici bir sorundan kaynaklanıyor olabilir.",
|
||||
"bundle_column_error.network.title": "Ağ hatası",
|
||||
"bundle_column_error.retry": "Tekrar deneyin",
|
||||
"bundle_column_error.return": "Anasayfaya geri dön",
|
||||
"bundle_column_error.return": "Ana sayfaya geri dön",
|
||||
"bundle_column_error.routing.body": "İstenen sayfa bulunamadı. Adres çubuğundaki URL'nin doğru olduğundan emin misiniz?",
|
||||
"bundle_column_error.routing.title": "404",
|
||||
"bundle_modal_error.close": "Kapat",
|
||||
@ -454,7 +454,7 @@
|
||||
"column.firehose_local": "Bu sunucunun canlı akışı",
|
||||
"column.firehose_singular": "Canlı akış",
|
||||
"column.follow_requests": "Takip istekleri",
|
||||
"column.home": "Anasayfa",
|
||||
"column.home": "Ana Sayfa",
|
||||
"column.list_members": "Liste üyelerini yönet",
|
||||
"column.lists": "Listeler",
|
||||
"column.mutes": "Sessize alınmış kullanıcılar",
|
||||
@ -820,7 +820,7 @@
|
||||
"keyboard_shortcuts.favourites": "Gözde listeni aç",
|
||||
"keyboard_shortcuts.federated": "Federe akışı aç",
|
||||
"keyboard_shortcuts.heading": "Klavye kısayolları",
|
||||
"keyboard_shortcuts.home": "Ana akışı aç",
|
||||
"keyboard_shortcuts.home": "Ana sayfa akışını aç",
|
||||
"keyboard_shortcuts.hotkey": "Kısayol tuşu",
|
||||
"keyboard_shortcuts.legend": "Bu efsaneyi görüntülemek için",
|
||||
"keyboard_shortcuts.load_more": "\"Daha fazlası\" düğmesine odaklan",
|
||||
@ -861,13 +861,13 @@
|
||||
"lists.add_to_list": "Listeye ekle",
|
||||
"lists.add_to_lists": "{name} kişisini listelere ekle",
|
||||
"lists.create": "Oluştur",
|
||||
"lists.create_a_list_to_organize": "Anasayfa akışınızı düzenlemek için yeni bir liste oluşturun",
|
||||
"lists.create_a_list_to_organize": "Ana sayfa akışınızı düzenlemek için yeni bir liste oluşturun",
|
||||
"lists.create_list": "Liste oluştur",
|
||||
"lists.delete": "Listeyi sil",
|
||||
"lists.done": "Tamamlandı",
|
||||
"lists.edit": "Listeleri düzenle",
|
||||
"lists.exclusive": "Anasayfada üyeleri gizle",
|
||||
"lists.exclusive_hint": "Birisi bu listede yer alıyorsa, gönderilerini iki kez görmekten kaçınmak için onu anasayfa akışınızda gizleyin.",
|
||||
"lists.exclusive": "Ana sayfada üyeleri gizle",
|
||||
"lists.exclusive_hint": "Birisi bu listede yer alıyorsa, gönderilerini iki kez görmekten kaçınmak için onu ana sayfa akışınızda gizleyin.",
|
||||
"lists.find_users_to_add": "Eklenecek kullanıcıları bul",
|
||||
"lists.list_members_count": "{count, plural, one {# üye} other {# üye}}",
|
||||
"lists.list_name": "Liste adı",
|
||||
@ -941,7 +941,7 @@
|
||||
"notification.collection_update": "{name} olduğunuz bir koleksiyonu düzenledi",
|
||||
"notification.favourite": "{name} gönderinizi beğendi",
|
||||
"notification.favourite.name_and_others_with_link": "{name} ve <a>{count, plural, one {# diğer kişi} other {# diğer kişi}}</a> gönderinizi beğendi",
|
||||
"notification.favourite_pm": "{name} özel değininizi beğendi",
|
||||
"notification.favourite_pm": "{name} özel bahsetmenizi beğendi",
|
||||
"notification.favourite_pm.name_and_others_with_link": "{name} ve <a>{count, plural, one {# diğer kişi} other {# diğer kişi}}</a> özel değininizi beğendi",
|
||||
"notification.follow": "{name} seni takip etti",
|
||||
"notification.follow.name_and_others": "{name} ve <a>{count, plural, one {# diğer kişi} other {# diğer kişi}}</a> sizi takip etti",
|
||||
@ -1060,7 +1060,7 @@
|
||||
"onboarding.profile.display_name": "Görünen isim",
|
||||
"onboarding.profile.display_name_hint": "Tam adınız veya kullanıcı adınız…",
|
||||
"onboarding.profile.finish": "Tamamla",
|
||||
"onboarding.profile.note": "Kişisel bilgiler",
|
||||
"onboarding.profile.note": "Biyografi",
|
||||
"onboarding.profile.note_hint": "Diğer insanlara @değinebilir veya #etiketler kullanabilirsiniz…",
|
||||
"onboarding.profile.title": "Profilini ayarla",
|
||||
"onboarding.profile.upload_avatar": "Profil resmi yükle",
|
||||
@ -1308,7 +1308,7 @@
|
||||
"subscribed_languages.lead": "Değişiklikten sonra ana akışınızda sadece seçili dillerdeki gönderiler görüntülenecek ve zaman akışları listelenecektir. Tüm dillerde gönderiler için hiçbirini seçin.",
|
||||
"subscribed_languages.save": "Değişiklikleri kaydet",
|
||||
"subscribed_languages.target": "{target} abone olduğu dilleri değiştir",
|
||||
"tabs_bar.home": "Anasayfa",
|
||||
"tabs_bar.home": "Ana sayfa",
|
||||
"tabs_bar.menu": "Menü",
|
||||
"tabs_bar.notifications": "Bildirimler",
|
||||
"tabs_bar.publish": "Yeni Gönderi",
|
||||
|
||||
@ -8222,47 +8222,6 @@ noscript {
|
||||
}
|
||||
}
|
||||
|
||||
.moved-account-banner,
|
||||
.follow-request-banner,
|
||||
.account-memorial-banner {
|
||||
padding: 20px;
|
||||
background: var(--color-bg-tertiary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
&__message {
|
||||
color: var(--color-text-secondary);
|
||||
padding: 8px 0;
|
||||
padding-top: 0;
|
||||
padding-bottom: 4px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
&__action {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.detailed-status__display-name {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.follow-request-banner .button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.account-memorial-banner__message {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.column-inline-form {
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
|
||||
@ -265,8 +265,14 @@ class Account < ApplicationRecord
|
||||
last_webfingered_at.nil? || last_webfingered_at <= STALE_THRESHOLD.ago
|
||||
end
|
||||
|
||||
def needs_background_refresh?
|
||||
return false if local?
|
||||
|
||||
last_webfingered_at.blank? || last_webfingered_at <= BACKGROUND_REFRESH_INTERVAL.ago
|
||||
end
|
||||
|
||||
def schedule_refresh_if_stale!
|
||||
return unless last_webfingered_at.present? && last_webfingered_at <= BACKGROUND_REFRESH_INTERVAL.ago
|
||||
return unless needs_background_refresh?
|
||||
|
||||
AccountRefreshWorker.perform_in(rand(REFRESH_DEADLINE), id)
|
||||
end
|
||||
|
||||
42
app/models/admin/collection_batch_action.rb
Normal file
42
app/models/admin/collection_batch_action.rb
Normal file
@ -0,0 +1,42 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Admin::CollectionBatchAction < Admin::BaseAction
|
||||
TYPES = %w(
|
||||
report
|
||||
remove_from_report
|
||||
).freeze
|
||||
|
||||
attr_accessor :collection_ids
|
||||
|
||||
private
|
||||
|
||||
def process_action!
|
||||
return unless collection_ids
|
||||
|
||||
add_to_report!
|
||||
end
|
||||
|
||||
def add_to_report!
|
||||
@report = Report.new(report_params) unless with_report?
|
||||
|
||||
@report.collections = (@report.collections + selected_collections).uniq
|
||||
@report.save!
|
||||
@report_id = @report.id
|
||||
end
|
||||
|
||||
def selected_collections
|
||||
@report.target_account.collections.where(id: collection_ids)
|
||||
end
|
||||
|
||||
def report_params
|
||||
{ account: current_account, target_account: target_account }
|
||||
end
|
||||
|
||||
def collection
|
||||
@collection ||= Collection.where(id: collection_ids.first)
|
||||
end
|
||||
|
||||
def target_account
|
||||
@target_account ||= collection.first.account
|
||||
end
|
||||
end
|
||||
49
app/views/admin/collections/index.html.haml
Normal file
49
app/views/admin/collections/index.html.haml
Normal file
@ -0,0 +1,49 @@
|
||||
- content_for :page_title do
|
||||
= t('admin.collections.title', name: @account.pretty_acct)
|
||||
|
||||
.filters
|
||||
.filter-subset
|
||||
%ul
|
||||
%li= filter_link_to t('generic.all'), id: nil
|
||||
.back-link
|
||||
- if params[:report_id]
|
||||
= link_to admin_report_path(params[:report_id].to_i) do
|
||||
= material_symbol 'chevron_left'
|
||||
= t('admin.collections.back_to_report')
|
||||
- else
|
||||
= link_to admin_account_path(@account.id) do
|
||||
= material_symbol 'chevron_left'
|
||||
= t('admin.collections.back_to_account')
|
||||
|
||||
%hr.spacer/
|
||||
|
||||
|
||||
= form_with model: @collection_batch_action, url: batch_admin_account_collections_path(@account.id) do |f|
|
||||
= hidden_field_tag :page, params[:page] || 1
|
||||
= hidden_field_tag :report_id, params[:report_id]
|
||||
|
||||
.batch-table
|
||||
.batch-table__toolbar
|
||||
%label.batch-table__toolbar__select.batch-checkbox-all
|
||||
= check_box_tag :batch_checkbox_all, nil, false
|
||||
.batch-table__toolbar__actions
|
||||
- unless @collections.empty?
|
||||
- if params[:report_id]
|
||||
= f.button safe_join([material_symbol('add'), t('admin.collections.batch.add_to_report', id: params[:report_id])]),
|
||||
class: 'table-action-link',
|
||||
data: { confirm: t('admin.reports.are_you_sure') },
|
||||
name: :report,
|
||||
type: :submit
|
||||
- else
|
||||
= f.button safe_join([material_symbol('flag'), t('admin.collections.batch.report')]),
|
||||
class: 'table-action-link',
|
||||
data: { confirm: t('admin.reports.are_you_sure') },
|
||||
name: :report,
|
||||
type: :submit
|
||||
.batch-table__body
|
||||
- if @collections.empty?
|
||||
= nothing_here 'nothing-here--under-tabs'
|
||||
- else
|
||||
= render partial: 'admin/shared/collection_batch_row', collection: @collections, as: :collection, locals: { f: f }
|
||||
|
||||
= paginate @collections
|
||||
@ -69,7 +69,7 @@
|
||||
|
||||
- if Mastodon::Feature.collections_enabled?
|
||||
%span.report-card__summary__item__content__icon{ title: t('admin.accounts.collections') }
|
||||
= material_symbol('groups-fill')
|
||||
= material_symbol('category')
|
||||
= report.collections.size
|
||||
|
||||
- if report.forwarded?
|
||||
|
||||
@ -67,17 +67,19 @@
|
||||
%summary
|
||||
= t 'admin.reports.collections', count: @report.collections.size
|
||||
|
||||
%form
|
||||
= form_with model: @form, url: batch_admin_account_collections_path(@report.target_account_id, report_id: @report.id) do |f|
|
||||
.batch-table
|
||||
.batch-table__toolbar
|
||||
%label.batch-table__toolbar__select.batch-checkbox-all
|
||||
-# = check_box_tag :batch_checkbox_all, nil, false
|
||||
.batch-table__toolbar__actions
|
||||
= link_to safe_join([material_symbol('add'), t('admin.reports.add_to_report')]),
|
||||
admin_account_collections_path(@report.target_account_id, report_id: @report.id),
|
||||
class: 'table-action-link'
|
||||
.batch-table__body
|
||||
- if @report.collections.empty?
|
||||
= nothing_here 'nothing-here--under-tabs'
|
||||
- else
|
||||
= render partial: 'admin/shared/collection_batch_row', collection: @report.collections, as: :collection
|
||||
= render partial: 'admin/shared/collection_batch_row', collection: @report.collections, as: :collection, locals: { f: f }
|
||||
|
||||
- if @report.unresolved?
|
||||
%hr.spacer/
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
.batch-table__row
|
||||
%label.batch-table__row__select.batch-checkbox
|
||||
-# = f.check_box :collection_ids, { multiple: true, include_hidden: false }, collection.id
|
||||
= f.check_box :collection_ids, { multiple: true, include_hidden: false }, collection.id
|
||||
.batch-table__row__content
|
||||
= render partial: 'admin/shared/collection', object: collection
|
||||
|
||||
@ -7,7 +7,7 @@ class AccountRefreshWorker
|
||||
|
||||
def perform(account_id)
|
||||
account = Account.find_by(id: account_id)
|
||||
return if account.nil? || account.last_webfingered_at > Account::BACKGROUND_REFRESH_INTERVAL.ago
|
||||
return unless account&.needs_background_refresh?
|
||||
|
||||
ResolveAccountService.new.call(account)
|
||||
end
|
||||
|
||||
@ -514,7 +514,7 @@ br:
|
||||
guide_link: https://crowdin.com/project/mastodon
|
||||
application_mailer:
|
||||
salutation: "%{name},"
|
||||
view: 'Sellet :'
|
||||
view: 'Sellet:'
|
||||
view_profile: Gwelet ar profil
|
||||
view_status: Gwelet ar c'hannad
|
||||
applications:
|
||||
|
||||
@ -345,12 +345,19 @@ da:
|
||||
updated_msg: Bekendtgørelsen er opdateret!
|
||||
collections:
|
||||
accounts: Konti
|
||||
back_to_account: Tilbage til kontoside
|
||||
back_to_report: Tilbage til anmeldelsesside
|
||||
batch:
|
||||
add_to_report: 'Føj til anmeldelse #%{id}'
|
||||
report: Anmeldelse
|
||||
collection_title: Indsamling af %{name}
|
||||
contents: Indhold
|
||||
no_collection_selected: Der blev ikke ændret nogen samlinger, da ingen var valgt
|
||||
number_of_accounts:
|
||||
one: 1 konto
|
||||
other: "%{count} konti"
|
||||
open: Åben
|
||||
title: Kontosamlinger - @%{name}
|
||||
view_publicly: Vis offentligt
|
||||
critical_update_pending: Kritisk opdatering afventer
|
||||
custom_emojis:
|
||||
|
||||
@ -345,12 +345,19 @@ de:
|
||||
updated_msg: Ankündigung erfolgreich aktualisiert!
|
||||
collections:
|
||||
accounts: Konten
|
||||
back_to_account: Zurück zum Konto
|
||||
back_to_report: Zurück zu den Meldungen
|
||||
batch:
|
||||
add_to_report: Der Meldung Nr. %{id} hinzufügen
|
||||
report: Meldung
|
||||
collection_title: Sammlung von %{name}
|
||||
contents: Inhalte
|
||||
no_collection_selected: Keine Sammlungen wurden geändert, da keine ausgewählt wurden
|
||||
number_of_accounts:
|
||||
one: 1 Konto
|
||||
other: "%{count} Konten"
|
||||
open: Öffnen
|
||||
title: Sammlungen des Kontos – @%{name}
|
||||
view_publicly: Sammlung öffnen
|
||||
critical_update_pending: Kritisches Update ausstehend
|
||||
custom_emojis:
|
||||
|
||||
@ -345,12 +345,19 @@ el:
|
||||
updated_msg: Επιτυχής ενημέρωση ανακοίνωσης!
|
||||
collections:
|
||||
accounts: Λογαριασμοί
|
||||
back_to_account: Πίσω στη σελίδα λογαριασμού
|
||||
back_to_report: Πίσω στη σελίδα αναφοράς
|
||||
batch:
|
||||
add_to_report: 'Προσθήκη στην αναφορά #%{id}'
|
||||
report: Αναφορά
|
||||
collection_title: Συλλογή από %{name}
|
||||
contents: Περιεχόμενα
|
||||
no_collection_selected: Καμία συλλογή δεν άλλαξε αφού καμία δεν ήταν επιλεγμένη
|
||||
number_of_accounts:
|
||||
one: 1 λογαριασμός
|
||||
other: "%{count} λογαριασμοί"
|
||||
open: Άνοιγμα
|
||||
title: Συλλογές λογαριασμού - @%{name}
|
||||
view_publicly: Προβολή δημόσια
|
||||
critical_update_pending: Κρίσιμη ενημέρωση σε αναμονή
|
||||
custom_emojis:
|
||||
|
||||
@ -345,12 +345,19 @@ en:
|
||||
updated_msg: Announcement successfully updated!
|
||||
collections:
|
||||
accounts: Accounts
|
||||
back_to_account: Back to account page
|
||||
back_to_report: Back to report page
|
||||
batch:
|
||||
add_to_report: 'Add to report #%{id}'
|
||||
report: Report
|
||||
collection_title: Collection by %{name}
|
||||
contents: Contents
|
||||
no_collection_selected: No collections were changed as none were selected
|
||||
number_of_accounts:
|
||||
one: 1 account
|
||||
other: "%{count} accounts"
|
||||
open: Open
|
||||
title: Account collections - @%{name}
|
||||
view_publicly: View publicly
|
||||
critical_update_pending: Critical update pending
|
||||
custom_emojis:
|
||||
|
||||
@ -345,12 +345,19 @@ es-AR:
|
||||
updated_msg: "¡Anuncio actualizado exitosamente!"
|
||||
collections:
|
||||
accounts: Cuentas
|
||||
back_to_account: Volver a la página de la cuenta
|
||||
back_to_report: Volver a la página de la denuncia
|
||||
batch:
|
||||
add_to_report: Agregar a la denuncia N°%{id}
|
||||
report: Denunciar
|
||||
collection_title: Colección de %{name}
|
||||
contents: Contenidos
|
||||
no_collection_selected: No se cambió ninguna colección, ya que ninguna fue seleccionada
|
||||
number_of_accounts:
|
||||
one: 1 cuenta
|
||||
other: "%{count} cuentas"
|
||||
open: Abrir
|
||||
title: Colecciones de cuentas - @%{name}
|
||||
view_publicly: Ver públicamente
|
||||
critical_update_pending: Actualización crítica pendiente
|
||||
custom_emojis:
|
||||
|
||||
@ -345,12 +345,19 @@ es-MX:
|
||||
updated_msg: "¡Anuncio actualizado con éxito!"
|
||||
collections:
|
||||
accounts: Cuentas
|
||||
back_to_account: Volver a la página de la cuenta
|
||||
back_to_report: Volver a la página del informe
|
||||
batch:
|
||||
add_to_report: 'Añadir al informe #%{id}'
|
||||
report: Informe
|
||||
collection_title: Colección de %{name}
|
||||
contents: Contenidos
|
||||
no_collection_selected: No se modificó ninguna colección, ya que no se seleccionó ninguna
|
||||
number_of_accounts:
|
||||
one: 1 cuenta
|
||||
other: "%{count} cuentas"
|
||||
open: Abrir
|
||||
title: Colecciones de la cuenta - @%{name}
|
||||
view_publicly: Ver públicamente
|
||||
critical_update_pending: Actualización crítica pendiente
|
||||
custom_emojis:
|
||||
|
||||
@ -345,12 +345,19 @@ es:
|
||||
updated_msg: "¡Anuncio actualizado con éxito!"
|
||||
collections:
|
||||
accounts: Cuentas
|
||||
back_to_account: Volver a la página de la cuenta
|
||||
back_to_report: Volver a la página del informe
|
||||
batch:
|
||||
add_to_report: 'Añadir al informe #%{id}'
|
||||
report: Informe
|
||||
collection_title: Colección de %{name}
|
||||
contents: Contenidos
|
||||
no_collection_selected: No se ha cambiado ninguna colección ya que no se ha seleccionado ninguna
|
||||
number_of_accounts:
|
||||
one: 1 cuenta
|
||||
other: "%{count} cuentas"
|
||||
open: Abrir
|
||||
title: Colecciones de la cuenta - @%{name}
|
||||
view_publicly: Ver públicamente
|
||||
critical_update_pending: Actualización crítica pendiente
|
||||
custom_emojis:
|
||||
|
||||
@ -12,6 +12,9 @@ fa:
|
||||
followers:
|
||||
one: پیگیرنده
|
||||
other: پیگیرنده
|
||||
following:
|
||||
one: پیگرفته
|
||||
other: پیگرفته
|
||||
instance_actor_flash: این حساب عاملی مجازیست که به نمایندگی از خود کارساز استفاده میشود و نه کاربری جداگانه. این حساب به منظور اتصال به فدراسیون استفاده میشود و نباید معلق شود.
|
||||
last_active: آخرین فعّالیت
|
||||
link_verified_on: مالکیت این پیوند در %{date} بررسی شد
|
||||
@ -53,6 +56,7 @@ fa:
|
||||
label: تغییر نقش
|
||||
no_role: بدون نقش
|
||||
title: تغییر نقش برای %{username}
|
||||
collections: مجموعهها
|
||||
confirm: تأیید
|
||||
confirmed: تأیید شد
|
||||
confirming: تأیید
|
||||
@ -263,6 +267,7 @@ fa:
|
||||
demote_user_html: "%{name} کاربر %{target} را تنزل داد"
|
||||
destroy_announcement_html: "%{name} اعلامیهٔ %{target} را حذف کرد"
|
||||
destroy_canonical_email_block_html: "%{name} رایانامه با درهمریزی %{target} را نامسدود کرد"
|
||||
destroy_collection_html: "%{name} مجموعهٔ %{target} را برداشت"
|
||||
destroy_custom_emoji_html: "%{name} شکلک %{target} را حذف کرد"
|
||||
destroy_domain_allow_html: "%{name} دامنهٔ %{target} را از فهرست مجاز برداشت"
|
||||
destroy_domain_block_html: "%{name} انسداد دامنهٔ %{target} را رفع کرد"
|
||||
@ -302,6 +307,7 @@ fa:
|
||||
unsilence_account_html: "%{name} محدودیت حساب %{target} را برداشت"
|
||||
unsuspend_account_html: "%{name} حساب %{target} را از تعلیق خارج کرد"
|
||||
update_announcement_html: "%{name} اعلامیهٔ %{target} را بهروز کرد"
|
||||
update_collection_html: "%{name} مجموعهٔ %{target} را بهروز کرد"
|
||||
update_custom_emoji_html: "%{name} شکلک %{target} را بهروز کرد"
|
||||
update_domain_block_html: "%{name} مسدودسازی دامنه را برای %{target} بهروزرسانی کرد"
|
||||
update_ip_block_html: "%{name} قانون آیپی %{target} را تغییر داد"
|
||||
@ -338,6 +344,7 @@ fa:
|
||||
unpublished_msg: انتشار اعلامیه با موفقیت لغو شد!
|
||||
updated_msg: اعلامیه با موفقیت بهروز شد!
|
||||
collections:
|
||||
accounts: حسابها
|
||||
collection_title: محموعه به دست %{name}
|
||||
contents: محتوا
|
||||
number_of_accounts:
|
||||
@ -477,9 +484,53 @@ fa:
|
||||
title: مسدودسازی دامنهٔ رایانامهٔ جدید
|
||||
no_email_domain_block_selected: هیچ انسداد دامنهٔ رایانامهای تغییر نکرد زیرا هیچکدامشان انتخاب نشده بودند
|
||||
not_permitted: مجاز نیست
|
||||
reset: بازنشانی
|
||||
resolved_dns_records_hint_html: نام دامنه به دامنههای MX زیر منتقل می شود که در نهایت مسئولیت پذیرش رایانامه را بر عهده دارند. انسداد دامنهٔ MX، ثبتنام از هر نشانی رایانامهای را که از همان دامنهٔ MX استفاده میکند را مسدود میکند؛ حتا اگر نام دامنهٔ نمایان متفاوت باشد. <strong>مراقب باشید ارائهدهندگان رایانامهٔ بزرگ را مسدود نکنید.</strong>
|
||||
resolved_through_html: از طریق %{domain} حل شد
|
||||
search: جستوجو
|
||||
title: دامنههای رایانامهٔ مسدود شده
|
||||
email_subscriptions:
|
||||
accounts:
|
||||
account: حساب
|
||||
active: فعّال
|
||||
inactive: غیرفعّال
|
||||
status: وضعیت
|
||||
subscribers: مشترکان
|
||||
title: سیاهههای پستی
|
||||
additional_footer_texts:
|
||||
show:
|
||||
title: متن پانویس اضافی
|
||||
compliance_settings:
|
||||
additional_footer_text:
|
||||
action: مدیریت
|
||||
title: متن پانویس اضافی
|
||||
privacy_policy:
|
||||
action: مدیریت
|
||||
title: سیاست محرمانگی
|
||||
danger_zone:
|
||||
disable_feature:
|
||||
action: از کار انداختن
|
||||
title: از کار انداختن ویژگی
|
||||
erase_all_data:
|
||||
action: پاک کردن دادهها
|
||||
title: پاک کردن تمام دادهها
|
||||
title: منطقهٔ خطر
|
||||
index:
|
||||
disabled:
|
||||
get_started: آغاز به کار
|
||||
roles:
|
||||
accounts: حساب ها
|
||||
edit_role: ویرایش نقش
|
||||
empty:
|
||||
hint: هیچکس اجازهٔ استفاده از این ویژگی را ندارد.
|
||||
no_roles_added: هیچ نقشی اضافه نشد
|
||||
manage_roles: مدیریت نقشها
|
||||
role_name: نام نقش
|
||||
title: نقشها
|
||||
setups:
|
||||
show:
|
||||
enable_feature: به کار انداختن ویژگی
|
||||
important_information: اطّلاعات مهم
|
||||
export_domain_allows:
|
||||
new:
|
||||
title: درونریزی اجازههای دامنه
|
||||
@ -772,6 +823,7 @@ fa:
|
||||
administrator_description: کاربرانی که این مجوز را دارند از هر مجوزی عبور می کنند
|
||||
delete_user_data: حذف دادههای کاربر
|
||||
delete_user_data_description: به کاربران این امکان را می دهد که بدون تاخیر داده های سایر کاربران را حذف کنند
|
||||
invite_bypass_approval: دعوت کاربر بدون بازبینی
|
||||
invite_users: دعوت کاربران
|
||||
invite_users_description: به کاربران اجازه می دهد افراد جدیدی را به سرور دعوت کنند
|
||||
manage_announcements: مدیریت اعلامیهها
|
||||
@ -782,6 +834,7 @@ fa:
|
||||
manage_blocks_description: میگذارد کاربران فراهمکنندگان رایانامه و نشانیهای آیپی را مسدود کنند
|
||||
manage_custom_emojis: مدیریت ایموجیهای سفارشی
|
||||
manage_custom_emojis_description: به کاربران اجازه می دهد تا ایموجی های سفارشی را روی سرور مدیریت کنند
|
||||
manage_email_subscriptions: مدیریت اشتراکهای رایانامهای
|
||||
manage_federation: مدیریت خودگردانی
|
||||
manage_federation_description: به کاربران اجازه میدهد تا اتحاد با دامنههای دیگر را مسدود یا اجازه دهند و تحویلپذیری را کنترل کنند
|
||||
manage_invites: مدیریت دعوتها
|
||||
@ -810,6 +863,7 @@ fa:
|
||||
view_devops_description: به کاربران امکان دسترسی به داشبورد Sidekiq و pgHero را می دهد
|
||||
view_feeds: دیدن خوراکهای موضوع و زنده
|
||||
view_feeds_description: میگذارد کاربران فارغ از تنظیمات کارساز به خوراکهای موضوع و زنده دسترسی داشته باشند
|
||||
requires_2fa: نیازمند هویتسنجی دو مرحلهای
|
||||
title: نقشها
|
||||
rules:
|
||||
add_new: افزودن قانون
|
||||
@ -1271,6 +1325,7 @@ fa:
|
||||
progress:
|
||||
confirm: تأیید رایانامه
|
||||
details: جزئیات شما
|
||||
list: فرایند نامنویسی
|
||||
review: بررسی ما
|
||||
rules: پذیرش قوانین
|
||||
providers:
|
||||
@ -1286,6 +1341,7 @@ fa:
|
||||
invited_by: 'با سپاس از دعوتی از این فرد دریافت کردهاید میتوانید به %{domain} بپیوندید:'
|
||||
preamble: اینها توسط گردانندگان %{domain} تنظیم و اجرا می شوند.
|
||||
preamble_invited: قبل از ادامه، لطفاً قوانین اساسی تنظیم شده توسط مدیران %{domain} را در نظر بگیرید.
|
||||
read_more: خواندن بیشتر
|
||||
title: برخی از قوانین اساسی.
|
||||
title_invited: شما دعوت شده اید.
|
||||
security: امنیت
|
||||
@ -1405,7 +1461,12 @@ fa:
|
||||
your_appeal_rejected: درخواست تجدیدنظرتان رد شد
|
||||
edit_profile:
|
||||
other: سایر
|
||||
redesign_button: آزمودن
|
||||
redesign_title: تجربهٔ نماگر جدیدی آمده است
|
||||
email_subscription_mailer:
|
||||
confirmation:
|
||||
action: تأیید نشانی رایانامه
|
||||
subject: تأیید نشانی رایانامهتان
|
||||
notification:
|
||||
create_account: ایجاد حساب ماستودون
|
||||
subject:
|
||||
@ -1413,6 +1474,17 @@ fa:
|
||||
singular: 'فرستهٔ جدید: «%{excerpt}»'
|
||||
title:
|
||||
plural: فرستههای جدید از %{name}
|
||||
singular: 'فرستهٔ جدید: «%{excerpt}»'
|
||||
email_subscriptions:
|
||||
active: فعّال
|
||||
confirmations:
|
||||
show:
|
||||
changed_your_mind: نظرتان عوض شد؟
|
||||
title: نام نوشتید
|
||||
unsubscribe: لغو اشتراک
|
||||
inactive: غیرفعّال
|
||||
status: وضعیت
|
||||
subscribers: مشترکان
|
||||
emoji_styles:
|
||||
auto: خودکار
|
||||
native: بومی
|
||||
@ -1690,6 +1762,19 @@ fa:
|
||||
copy_account_note_text: 'این کاربر از %{acct} جابهجا شده است. یادداشتهای پیشینتان دربارهاش اینهاست:'
|
||||
navigation:
|
||||
toggle_menu: تغییر وضعیت فهرست
|
||||
notification_fallbacks:
|
||||
added_to_collection:
|
||||
title_html: "%{name} به مجموعهای افزودتان"
|
||||
admin_report:
|
||||
title_html: "%{name}، %{target} را گزارش داد"
|
||||
admin_sign_up:
|
||||
title_html: "%{name} نام نوشت"
|
||||
collection_update:
|
||||
title_html: "%{name} مجموعهای که در آن هستید را بهروز کرد"
|
||||
generic:
|
||||
sign_in: ورورد به کارهٔ وب ماستودون
|
||||
severed_relationships:
|
||||
title: قطع ارتباط با %{name}
|
||||
notification_mailer:
|
||||
admin:
|
||||
report:
|
||||
@ -1782,6 +1867,7 @@ fa:
|
||||
posting_defaults: تنظیمات پیشفرض انتشار
|
||||
public_timelines: خط زمانیهای عمومی
|
||||
privacy:
|
||||
email_subscriptions: ارسال فرستهها با رایانامه
|
||||
hint_html: "<strong>شخصیسازی چگونگی پیدا شدن فرستهها و نمایهتان.</strong> ویژگیهای متعدّدی در ماستودون میتوانند هنگام به کار افتادن در رسیدن به مخاطبینی گستردهتر یاریتان کنند. کمی وقت برای بازبینی این تنظیمات گذاشته تا مطمئن شوید برایتان مناسبند."
|
||||
privacy: محرمانگی
|
||||
privacy_hint_html: واپایش میزان باز شدن به نفع دیگران. افراد نمایههای جالب و کارههای باحال را با مرور پیگرفتگان دیگران و دیدن کارههایی که از آنها میفرستند پیدا میکنند. با این حال شاید بخواهید پنهان نگهشان دارید.
|
||||
@ -2015,6 +2101,8 @@ fa:
|
||||
past_preamble_html: شرایط خدماتمان را تغییر دادهایم. تشویقتان میکنیم که شرایط بهروز شده را بازبینی کنید.
|
||||
review_link: بازبینی شرایط استفاده
|
||||
title: شرایط خدمات %{domain} در حال تغییر است
|
||||
themes:
|
||||
default: ماستودون
|
||||
time:
|
||||
formats:
|
||||
default: "%d %b %Y, %H:%M"
|
||||
@ -2039,7 +2127,26 @@ fa:
|
||||
recovery_codes: پشتیبانگیری از کدهای بازیابی
|
||||
recovery_codes_regenerated: کدهای بازیابی با موفقیت ساخته شدند
|
||||
recovery_instructions_html: اگر تلفن خود را گم کردید، میتوانید با یکی از کدهای بازیابی زیر کنترل حساب خود را به دست بگیرید. <strong>این کدها را در جای امنی نگه دارید.</strong> مثلاً آنها را چاپ کنید و کنار سایر مدارک مهم خود قرار دهید.
|
||||
resume_app_authorization: از سر گیری تأیید درخواست
|
||||
webauthn: کلیدهای امنیتی
|
||||
unsubscriptions:
|
||||
create:
|
||||
action: رفتن به صفحهٔ خانگی کارساز
|
||||
email_subscription:
|
||||
confirmation_html: دیگر از %{name} رایانامه نخواهید گرفت.
|
||||
title: اشتراکتان لغو شد
|
||||
notification_emails:
|
||||
favourite: رایانامههای آگاهی برگزیدن
|
||||
follow: رایانامههای آگاهی پیگیری
|
||||
follow_request: رایانامههای درخواست پیگیری
|
||||
mention: رایانامههای آگاهی اشاره
|
||||
reblog: رایانامههای آگاهی تقویت
|
||||
show:
|
||||
action: لغو اشتراک
|
||||
email_subscription:
|
||||
title: لغو اشتراک %{name}؟
|
||||
user:
|
||||
title: لغو اشتراک %{type}؟
|
||||
user_mailer:
|
||||
announcement_published:
|
||||
description: 'مدیران %{domain} اعلامیهای دادهاند:'
|
||||
@ -2182,6 +2289,7 @@ fa:
|
||||
error: حذف کلید امنیتیتان با مشکل مواجه شد. لطفاً دوباره تلاش کنید.
|
||||
success: کلید امنیتیتان با موفّقیت حذف شد.
|
||||
invalid_credential: کلید امنیتی نامعتبر
|
||||
nickname: نام مستعار
|
||||
nickname_hint: نام مستعار کلید امنیتی جدیدتان را وارد کنید
|
||||
not_enabled: شما هنوز WebAuthn را فعال نکردهاید
|
||||
not_supported: این مرورگر از کلیدهای امنیتی پشتیبانی نمیکند
|
||||
|
||||
@ -345,12 +345,19 @@ fi:
|
||||
updated_msg: Tiedotteen päivitys onnistui!
|
||||
collections:
|
||||
accounts: Tilit
|
||||
back_to_account: Takaisin tilisivulle
|
||||
back_to_report: Takaisin raporttisivulle
|
||||
batch:
|
||||
add_to_report: Lisää raporttiin nro %{id}
|
||||
report: Raportoi
|
||||
collection_title: Kokoelma, koonnut %{name}
|
||||
contents: Sisältö
|
||||
no_collection_selected: Kokoelmia ei muutettu, koska yhtään ei ollut valittuna
|
||||
number_of_accounts:
|
||||
one: 1 tili
|
||||
other: "%{count} tiliä"
|
||||
open: Avaa
|
||||
title: Tilin kokoelmat – @%{name}
|
||||
view_publicly: Näytä julkisesti
|
||||
critical_update_pending: Kriittinen päivitys odottaa
|
||||
custom_emojis:
|
||||
|
||||
@ -345,12 +345,19 @@ fr-CA:
|
||||
updated_msg: L’annonce a été mise à jour avec succès !
|
||||
collections:
|
||||
accounts: Comptes
|
||||
back_to_account: Retour à la page du compte
|
||||
back_to_report: Retour à la page de signalement
|
||||
batch:
|
||||
add_to_report: 'Ajouter au signalement #%{id}'
|
||||
report: Signaler
|
||||
collection_title: Collection par %{name}
|
||||
contents: Contenu
|
||||
no_collection_selected: Aucune collection n'a était changé vu qu'aucune n'a était sélectionné
|
||||
number_of_accounts:
|
||||
one: 1 compte
|
||||
other: "%{count} comptes"
|
||||
open: Ouvrir
|
||||
title: Collection du compte - @%{name}
|
||||
view_publicly: Afficher publiquement
|
||||
critical_update_pending: Mise à jour critique en attente
|
||||
custom_emojis:
|
||||
|
||||
@ -345,12 +345,19 @@ fr:
|
||||
updated_msg: L’annonce a été mise à jour avec succès !
|
||||
collections:
|
||||
accounts: Comptes
|
||||
back_to_account: Retour à la page du compte
|
||||
back_to_report: Retour à la page de signalement
|
||||
batch:
|
||||
add_to_report: 'Ajouter au signalement #%{id}'
|
||||
report: Signaler
|
||||
collection_title: Collection par %{name}
|
||||
contents: Contenu
|
||||
no_collection_selected: Aucune collection n'a était changé vu qu'aucune n'a était sélectionné
|
||||
number_of_accounts:
|
||||
one: 1 compte
|
||||
other: "%{count} comptes"
|
||||
open: Ouvrir
|
||||
title: Collection du compte - @%{name}
|
||||
view_publicly: Afficher publiquement
|
||||
critical_update_pending: Mise à jour critique en attente
|
||||
custom_emojis:
|
||||
|
||||
@ -345,12 +345,19 @@ gl:
|
||||
updated_msg: Anuncio actualizado de xeito correcto!
|
||||
collections:
|
||||
accounts: Contas
|
||||
back_to_account: Volver a páxina da conta
|
||||
back_to_report: Volver á páxina da denuncia
|
||||
batch:
|
||||
add_to_report: 'Engadir á denuncia #%{id}'
|
||||
report: Denunciar
|
||||
collection_title: Colección de %{name}
|
||||
contents: Contido
|
||||
no_collection_selected: Sen cambios nas coleccións por que non había ningunha seleccionada
|
||||
number_of_accounts:
|
||||
one: 1 conta
|
||||
other: "%{count} contas"
|
||||
open: Abrir
|
||||
title: Coleccións da conta - @%{name}
|
||||
view_publicly: Ver publicamente
|
||||
critical_update_pending: Actualización crítica pendente
|
||||
custom_emojis:
|
||||
|
||||
@ -504,9 +504,76 @@ he:
|
||||
title: חסימת מתחם כתובות דואל חדש
|
||||
no_email_domain_block_selected: לא בוצעו שינויים לחסימת מתחמי דוא"ל שכן לא נבחרו מתחמים
|
||||
not_permitted: נאסר
|
||||
reset: איפוס
|
||||
resolved_dns_records_hint_html: שם הדומיין מוביל למתחמי ה-MX הבאים, שהם בסופו של דבר אחראיים לקבלת דוא"ל. חסימת שם MX תוביל לחסימת הרשמות מכל כתובת דוא"ל שעושה שימוש בכתובת MX זו, אפילו אם הדומיין הגלוי שונה. <strong>יש להמנע מלחסום ספקי דוא"ל מובילים.</strong>
|
||||
resolved_through_html: נמצא דרך %{domain}
|
||||
search: חיפוש
|
||||
title: מתחמי כתובות דוא"ל חסומים
|
||||
email_subscriptions:
|
||||
accounts:
|
||||
account: חשבון
|
||||
active: פעילים
|
||||
empty:
|
||||
hint: עדיין אין מנוי לאף חשבון.
|
||||
no_lists_yet: אין רשימות עדיין
|
||||
inactive: לא פעילים
|
||||
last_email: הודעת דואל אחרונה
|
||||
lead: חשבונות שבהם הופעלה האפשרות ויש להם מנויים יופיעו להלן.
|
||||
status: מצב
|
||||
subscribers: מנויים
|
||||
title: רשימות תפוצה
|
||||
additional_footer_texts:
|
||||
show:
|
||||
title: מלל תחתית נוסף
|
||||
compliance_settings:
|
||||
additional_footer_text:
|
||||
action: ניהול
|
||||
hint: ניתן להוסיף מלל לתחתית מנשרי דואל
|
||||
title: מלל תחתית נוסף
|
||||
lead: מנשרי דואל עשויים להחשב לדואל שיווקי, בכפוף לחוקי תחום השיפוט בו אתם פועלים.
|
||||
privacy_policy:
|
||||
action: ניהול
|
||||
hint: מדיניות זו מקושרת בתחתית כל דיוור דואל
|
||||
title: מדיניות פרטיות
|
||||
title: כיוונוני תאימות לחוק
|
||||
danger_zone:
|
||||
disable_feature:
|
||||
action: השבתה
|
||||
hint: כיבוי התכונה לכל החשבונות
|
||||
title: כיבוי תכונה
|
||||
erase_all_data:
|
||||
action: מחיקת נתונים
|
||||
hint: מחיקה לצמיתות של כל כתובות הדואל מכל רשימות התפוצה
|
||||
title: מחק את כל הנתונים
|
||||
title: אזור סכנה
|
||||
disabled_msg: הרשמות דואל כובו בהצלחה.
|
||||
index:
|
||||
disabled:
|
||||
cannot_be_enabled: הספק הטכני לא איפשר תכונה זו לשרת שלך.
|
||||
description: התכונה מאפשרת לחשבונות שצוינו להוסיף כרטיסיה לפרופיל שלהם המאפשרת למבקרים חסרי חשבון מסטודון להרשם לקבלת הודעותיהם בדוא"ל.
|
||||
get_started: בואו נתחיל
|
||||
lead: לאפשר למבקרים לקבל הודעות משרת זה דרך הדואל בעזרת חשבונות יעודיים.
|
||||
title: מנשרי דואל
|
||||
purged_msg: כל מידע הרשמות הדואל עובר מחיקה.
|
||||
roles:
|
||||
accounts: חשבונות
|
||||
edit_role: עריכת תפקיד
|
||||
empty:
|
||||
hint: לאף אחד אין רשות להשתמש בתכונה זו.
|
||||
no_roles_added: לא הוספו תפקידים
|
||||
lead: חשבונות עם התפקידים המפורטים יוכלו לאפשר תכונה זו בפרופילים שלהם.
|
||||
manage_roles: ניהול תפקידים
|
||||
role_name: שם תפקיד
|
||||
title: תפקידים
|
||||
setups:
|
||||
show:
|
||||
enable_feature: אפשר תכונה
|
||||
important_information: מידע חשוב
|
||||
list:
|
||||
1_permission_explanation: כאשר תכונה זו מאופשרת, חשבונות עם ההרשאות היעודיות יוכלו להוסיף טופס אוסף דואל לפרופילים שלהם.
|
||||
2_feature_explanation: כאשר מבקרים נרשמים בעמוד הפרופיל של חשבון ומאשרים את ההרשמה, הן יתחילו לקבל עידכוני דוא"ל כאשר החשבון ייצור הודעות פומביות חדשות.
|
||||
3_privacy_policy_warning: למנהלי שרתים יש גישה למידע מזהה אישי שנאסף (כתובות דואל). בשל כך יש לעדכן את מדיניות הפרטיות שתכלול מידע זה לפני אפשור התכונה.
|
||||
4_cost_warning: שליחת דואל עלולה לעלות כסף בכפוף לתצורת ניהול השרת. דונו עם ספק האירוח לפני שתאפשרו, שכן תכונה זו עשויה להעלות משמעותית את כמות הדואל הנשלח מהשרת.
|
||||
export_domain_allows:
|
||||
new:
|
||||
title: יבוא רשימת שרתים מאושרים
|
||||
@ -824,6 +891,7 @@ he:
|
||||
manage_custom_emojis: ניהול סמלונים בהתאמה אישית
|
||||
manage_custom_emojis_description: מאפשר למשתמשים לנהל סמלונים בהתאמה אישית של השרת
|
||||
manage_email_subscriptions: ניהול הרשמות דוא"ל
|
||||
manage_email_subscriptions_description: להתיר למתמשים עם הרשאה זו לאפשר תכונת מנשר הדואל לחשבונם
|
||||
manage_federation: ניהול פדרציה
|
||||
manage_federation_description: מאפשר למשתמשים לחסום או לאפשר התממשקות עם שמות מתחם אחרים
|
||||
manage_invites: ניהול הזמנות
|
||||
@ -2314,7 +2382,7 @@ he:
|
||||
feature_audience_title: בנו את הקהל שלכםן בבטחה
|
||||
feature_control: אתםן יודעיםות בדיוק מה תרצו לראות בפיד הבית. אין פה פרסומות ואלגוריתמים שיבזבזו את זמנכם. עקבו אחרי כל משתמשי מסטודון משלל שרתים מתוך חשבון אחד וקבלו את ההודעות שלהםן לפי סדר הפרסום, וכך תצרו לכם את פינת האינטרנט המתאימה לכן אישית.
|
||||
feature_control_title: השארו בשליטה על ציר הזמן שלכם
|
||||
feature_creativity: מסטודון תומך בהודעות עם שמע, חוזי ותמונות, עם תאורים למוגבלי ראייה, משאלים, אזהרות תוכן, אווטרים מונפשים, אמוג'י מותאמים אישית, שליטה בחיתוך תמונות מוקטנות ועוד, כדי שתוכלו לבטא את עצמכןם ברשת. בין אם תפרסמו אמנות, מוזיקה או פודקסטים אישיים, מסטודון פה בשבילכם.
|
||||
feature_creativity: מסטודון תומך בהודעות עם שמע, חוזי ותמונות, עם תאורים ללקויי ראייה, משאלים, אזהרות תוכן, אווטרים מונפשים, אמוג'י מותאמים אישית, שליטה בחיתוך תמונות מוקטנות ועוד, כדי שתוכלו לבטא את עצמכןם ברשת. בין אם תפרסמו אמנות, מוזיקה או פודקסטים אישיים, מסטודון פה בשבילכם.
|
||||
feature_creativity_title: יצירתיות ללא פשרות
|
||||
feature_moderation: מסטודון מחזיר לידיכן את ההחלטות. כל שרת יוצר לעצמו את חוקיו, שנאכפים מקומית ולא "מלמעלה" כמו באתרים מסחריים, מה שהופך אותו לגמיש ביותר בשירות הרצונות והצרכים של קבוצות שונות. הצטרפו לשרת שאתםן מסכימותים עם חוקיו, או הקימו משלכןם.
|
||||
feature_moderation_title: ניהול דיונים כמו שצריך
|
||||
|
||||
@ -345,12 +345,19 @@ hu:
|
||||
updated_msg: A közlemény sikeresen frissítve!
|
||||
collections:
|
||||
accounts: Fiókok
|
||||
back_to_account: Vissza a fiók oldalára
|
||||
back_to_report: Vissza a bejelentés oldalra
|
||||
batch:
|
||||
add_to_report: 'Hozzáadás ehhez a jelentéshez: #%{id}'
|
||||
report: Jelentés
|
||||
collection_title: "%{name} gyűjteménye"
|
||||
contents: Tartalom
|
||||
no_collection_selected: Nem változott meg egy gyűjtemény sem, mert semmi sem volt kiválasztva
|
||||
number_of_accounts:
|
||||
one: 1 fiók
|
||||
other: "%{count} fiók"
|
||||
open: Megnyitás
|
||||
title: Fiók gyűjteményei – @%{name}
|
||||
view_publicly: Megtekintés nyilvánosan
|
||||
critical_update_pending: Függőben levő kritikus frissítés
|
||||
custom_emojis:
|
||||
|
||||
@ -345,12 +345,19 @@ is:
|
||||
updated_msg: Það tókst að uppfæra auglýsinguna!
|
||||
collections:
|
||||
accounts: Notandaaðgangar
|
||||
back_to_account: Fara aftur á síðu notandaaðgangsins
|
||||
back_to_report: Til baka á kærusíðu
|
||||
batch:
|
||||
add_to_report: 'Bæta við skýrslu #%{id}'
|
||||
report: Kæra
|
||||
collection_title: Safn frá %{name}
|
||||
contents: Efni
|
||||
no_collection_selected: Engum söfnum var breytt því engin voru valin
|
||||
number_of_accounts:
|
||||
one: 1 aðgangur
|
||||
other: "%{count} aðgangar"
|
||||
open: Opið
|
||||
title: Söfn notandaaðgangs - @%{name}
|
||||
view_publicly: Skoða opinberlega
|
||||
critical_update_pending: Áríðandi uppfærsla í bið
|
||||
custom_emojis:
|
||||
|
||||
@ -85,7 +85,7 @@ it:
|
||||
invite_request_text: Motivi d'iscrizione
|
||||
invited_by: Invitato da
|
||||
ip: IP
|
||||
joined: Iscritto il
|
||||
joined: Account iscritto il
|
||||
location:
|
||||
all: Tutto
|
||||
local: Locale
|
||||
@ -345,12 +345,19 @@ it:
|
||||
updated_msg: Annuncio aggiornato!
|
||||
collections:
|
||||
accounts: Account
|
||||
back_to_account: Torna alla pagina dell'account
|
||||
back_to_report: Torna alla pagina di segnalazione
|
||||
batch:
|
||||
add_to_report: 'Aggiungi alla segnalazione #%{id}'
|
||||
report: Segnala
|
||||
collection_title: Collezione di %{name}
|
||||
contents: Contenuti
|
||||
no_collection_selected: Nessuna collezione è stata cambiata in quanto nessuna è stata selezionata
|
||||
number_of_accounts:
|
||||
one: 1 account
|
||||
other: "%{count} account"
|
||||
open: Apri
|
||||
title: Collezioni dell'account - @%{name}
|
||||
view_publicly: Visualizza pubblicamente
|
||||
critical_update_pending: Aggiornamento critico in sospeso
|
||||
custom_emojis:
|
||||
@ -747,7 +754,7 @@ it:
|
||||
actions_description_html: Decidi quale azione intraprendere per risolvere questa segnalazione. Se intraprendi un'azione punitiva nei confronti dell'account segnalato, gli verrà inviata una notifica via e-mail, tranne quando è selezionata la categoria <strong>Spam</strong>.
|
||||
actions_description_remote_html: Decide quali azioni intraprendere per risolvere la relazione. Questo influenzerà solo come <strong>il tuo</strong> server comunica con questo account remoto e ne gestisce il contenuto.
|
||||
actions_no_posts: Questa segnalazione non ha alcun post associato da eliminare
|
||||
add_to_report: Aggiungi altro al report
|
||||
add_to_report: Aggiungi altro alla segnalazione
|
||||
already_suspended_badges:
|
||||
local: Già sospeso su questo server
|
||||
remote: Già sospeso sul loro server
|
||||
@ -987,7 +994,7 @@ it:
|
||||
account: Autore
|
||||
application: Applicazione
|
||||
back_to_account: Torna alla pagina dell'account
|
||||
back_to_report: Torna alla pagina del report
|
||||
back_to_report: Torna alla pagina di segnalazione
|
||||
batch:
|
||||
add_to_report: 'Aggiungi alla segnalazione #%{id}'
|
||||
remove_from_report: Rimuovi dal report
|
||||
|
||||
@ -135,6 +135,7 @@ kab:
|
||||
create_domain_allow: Rnu-d taɣult yettusirgen
|
||||
create_domain_block: Rnu-d asewḥel n taɣult
|
||||
create_ip_block: Rnu alugen n IP
|
||||
create_relay: Snulfu-d amedwel
|
||||
create_unavailable_domain: Rnu-d taɣult ur nelli ara
|
||||
create_user_role: Snulfu-d tamlilt
|
||||
destroy_announcement: Kkes ulɣu
|
||||
@ -285,6 +286,7 @@ kab:
|
||||
domain: Taɣult
|
||||
new:
|
||||
create: Rnu taɣult
|
||||
search: Nadi
|
||||
email_subscriptions:
|
||||
accounts:
|
||||
account: Amiḍan
|
||||
@ -293,13 +295,17 @@ kab:
|
||||
last_email: Imayl aneggaru
|
||||
status: Addad
|
||||
compliance_settings:
|
||||
additional_footer_text:
|
||||
action: Sefrek
|
||||
privacy_policy:
|
||||
action: Sefrek
|
||||
title: Tasertit n tbaḍnit
|
||||
danger_zone:
|
||||
disable_feature:
|
||||
action: Kkes armad
|
||||
erase_all_data:
|
||||
action: Kkes isefka
|
||||
title: Kkes akk isefka
|
||||
index:
|
||||
disabled:
|
||||
get_started: Aha bdu tura
|
||||
|
||||
@ -345,12 +345,19 @@ nl:
|
||||
updated_msg: Bijwerken van mededeling geslaagd!
|
||||
collections:
|
||||
accounts: Accounts
|
||||
back_to_account: Terug naar de accountpagina
|
||||
back_to_report: Terug naar de rapportagepagina
|
||||
batch:
|
||||
add_to_report: 'Aan rapportage #%{id} toevoegen'
|
||||
report: Rapportage
|
||||
collection_title: Verzameling van %{name}
|
||||
contents: Inhoud
|
||||
no_collection_selected: Er werden geen verzamelingen gewijzigd, omdat er geen enkele werd geselecteerd
|
||||
number_of_accounts:
|
||||
one: 1 account
|
||||
other: "%{count} accounts"
|
||||
open: Openen
|
||||
title: Accountverzamelingen - @%{name}
|
||||
view_publicly: Openbare verzameling bekijken
|
||||
critical_update_pending: Kritieke update in behandeling
|
||||
custom_emojis:
|
||||
@ -541,6 +548,7 @@ nl:
|
||||
empty:
|
||||
hint: Niemand heeft het recht om deze functionaliteit te gebruiken.
|
||||
no_roles_added: Geen rollen toegevoegd
|
||||
lead: Accounts met de volgende rollen kunnen deze functionaliteit op hun profiel inschakelen.
|
||||
manage_roles: Rollen beheren
|
||||
role_name: Naam rol
|
||||
title: Rollen
|
||||
|
||||
@ -60,6 +60,7 @@ fa:
|
||||
setting_default_quote_policy_private: فرستههای فقط پیگیران روی ماستودون نمیتوانند به دست دیگران نقل شوند.
|
||||
setting_default_quote_policy_unlisted: هنگامی که کسی نقلتان میکند هم فرستهاش از خط زمانیهای داغ پنهان خواهد بود.
|
||||
setting_default_sensitive: تصاویر حساس به طور پیشفرض پنهان هستند و میتوانند با یک کلیک آشکار شوند
|
||||
setting_display_media_hide_all: هشدار پیش از نمایش همهٔ رسانهها
|
||||
setting_emoji_style: چگونگی نمایش شکلکها. «خودکار» تلاش خواهد کرد از شکلکهای بومی استفاده کند؛ ولی برای مرورگرهای قدیمی به توییموجی برخواهد گشت.
|
||||
setting_quick_boosting_html: هنگام به کار افتادن، زدن روی %{boost_icon} نقشک تقویت به جای گشودنِ فهرست پایین افتادنی تقویت و نقل، بلافاصله تقویت خواهد کرد. کنشِ نقل قول را به فهرست %{options_icon} (گزینهها) منتقل میکند.
|
||||
setting_system_scrollbars_ui: فقط برای مرورگرهای دسکتاپ مبتنی بر سافاری و کروم اعمال می شود
|
||||
@ -217,6 +218,7 @@ fa:
|
||||
email: نشانی رایانامه
|
||||
expires_in: تاریخ انقضا
|
||||
fields: اطلاعات تکمیلی نمایه
|
||||
filter_action: کنش پالایه
|
||||
header: تصویر زمینه
|
||||
honeypot: "%{label} (پر نکنید)"
|
||||
inbox_url: نشانی صندوق ورودی رله
|
||||
@ -283,6 +285,7 @@ fa:
|
||||
closed_registrations_message: پیام سفارشی هنگام در دسترس نبودن ثبتنامها
|
||||
content_cache_retention_period: دوره نگهداری محتوا از راه دور
|
||||
custom_css: سبک CSS سفارشی
|
||||
email_footer_text: متن پانویس اضافی
|
||||
favicon: نمادک
|
||||
landing_page: صفحهٔ فرود برای بینندگان جدید
|
||||
local_live_feed_access: دسترسی به خوراکهای زندهٔ نمایانگر فرستههای محلی
|
||||
@ -307,6 +310,7 @@ fa:
|
||||
status_page_url: نشانی صفحهٔ وضعیت
|
||||
theme: زمینهٔ پیشگزیده
|
||||
thumbnail: بندانگشتی کارساز
|
||||
thumbnail_description: متن جایگزین بندانگشتی
|
||||
trendable_by_default: اجازهٔ پرطرفدار شدن بدون بازبینی پیشین
|
||||
trends: به کار انداختن پرطرفدارها
|
||||
wrapstodon: به کار انداختن خلاصهٔ ماستودون
|
||||
@ -348,6 +352,7 @@ fa:
|
||||
hint: اطّلاعات اضافی
|
||||
text: قانون
|
||||
settings:
|
||||
email_subscriptions: به کار انداختن نامنویسیهای رایانامهای
|
||||
indexable: بودن صفحهٔ نمایه در نتیجههای جستوجو
|
||||
show_application: نمایش این که فرسته را از کدام کاره فرستادهاید
|
||||
tag:
|
||||
@ -376,11 +381,13 @@ fa:
|
||||
role: نقش
|
||||
time_zone: منطقهٔ زمانی
|
||||
user_role:
|
||||
collection_limit: بیشینهٔ تعداد مجموعهٔ هر کاربر
|
||||
color: رنگ نشان
|
||||
highlighted: نمایش نقش به شکل نشان روی نمایهً کاربری
|
||||
name: نام
|
||||
permissions_as_keys: اجازهها
|
||||
position: اولویت
|
||||
require_2fa: نیازمند هویتسنجی دو مرحلهای
|
||||
username_block:
|
||||
allow_with_approval: اجازهٔ ثبتنام با تأیید
|
||||
comparison: روش مقایسه
|
||||
|
||||
@ -92,6 +92,7 @@ he:
|
||||
closed_registrations_message: להציג כאשר הרשמות חדשות אינן מאופשרות
|
||||
content_cache_retention_period: כל ההודעות משרתים אחרים (לרבות הדהודים ותגובות) ימחקו אחרי מספר ימים, ללא קשר לאינטראקציה של משתמשים מקומיים איתם. בכלל זה הודעות שהמתשתמשים המקומיים סימנו בסימניה או חיבוב. איזכורים פרטיים ("דיאם") בין משתמשים בין שרתים שונים יאבדו גם הם ולא תהיה אפשרות לשחזרם. השימוש באפשרות הזו מיועד לשרתים עם ייעוד מיוחד ושובר את ציפיותיהם של רב המשתמשים כאשר האפשרות מופעלת בשרת לשימוש כללי.
|
||||
custom_css: ניתן לבחור ערכות סגנון אישיות בגרסת הדפדפן של מסטודון.
|
||||
email_footer_text: ניתן להוסיף מלל לתחתית מנשרי דואל.
|
||||
favicon: WEBP, PNG, GIF או JPG. גובר על "פאבאייקון" ברירת המחדל ומחליף אותו באייקון נבחר בדפדפן.
|
||||
landing_page: בחירה בעמוד שיוצג ראשון למבקרים חדשים בביקור הראשון בשרת שלך. אם תבחרו "נושאים חמים", אזי הנושאים החמים צריכים להיות מאופשרים בהעדפות "תגליות". אם תבחרו "פיד מקומי", אז "גישה לפידים חיים המציגים הודעות מקומיות" חייב להיות מכוון למצב "כולם" בהעדפות תגליות.
|
||||
mascot: בחירת ציור למנשק הווב המתקדם.
|
||||
@ -296,6 +297,7 @@ he:
|
||||
closed_registrations_message: הודעה מיוחדת כשההרשמה לא מאופשרת
|
||||
content_cache_retention_period: תקופת השמירה על תוכן חיצוני
|
||||
custom_css: CSS בהתאמה אישית
|
||||
email_footer_text: מלל תחתית נוסף
|
||||
favicon: סמל מועדפים (Favicon)
|
||||
landing_page: דף נחיתה למבקרים חדשים
|
||||
local_live_feed_access: גישה לפידים חיים המציגים הודעות מקומיות
|
||||
@ -324,6 +326,9 @@ he:
|
||||
trendable_by_default: הרשאה לפריטים להופיע בנושאים החמים ללא אישור מוקדם
|
||||
trends: אפשר פריטים חמים (טרנדים)
|
||||
wrapstodon: הפעלת סיכומודון
|
||||
form_email_subscriptions_confirmation:
|
||||
agreement_email_volume: אני מבין.ה כי אפשור תכונה זו עשוי להגדיל משמעותית את כמות הדואל הנשלחת מהשרת ושאני לבדי אחראי לעלויות הנלוות לכך.
|
||||
agreement_privacy_and_terms: עדכנתי את מדיניות הפרטיות ותנאי השירות.
|
||||
interactions:
|
||||
must_be_follower: חסימת התראות משאינם עוקבים
|
||||
must_be_following: חסימת התראות משאינם נעקבים
|
||||
|
||||
@ -324,6 +324,8 @@ nl:
|
||||
trendable_by_default: Trends goedkeuren zonder voorafgaande beoordeling
|
||||
trends: Trends inschakelen
|
||||
wrapstodon: Wrapstodon inschakelen
|
||||
form_email_subscriptions_confirmation:
|
||||
agreement_privacy_and_terms: Ik heb het Privacybeleid en de Gebruiksvoorwaarden bijgewerkt.
|
||||
interactions:
|
||||
must_be_follower: Meldingen van mensen die jou niet volgen blokkeren
|
||||
must_be_following: Meldingen van mensen die jij niet volgt blokkeren
|
||||
|
||||
@ -236,7 +236,7 @@ tr:
|
||||
locale: Arayüz dili
|
||||
max_uses: Maksimum kullanım sayısı
|
||||
new_password: Yeni parola
|
||||
note: Kişisel bilgiler
|
||||
note: Biyografi
|
||||
otp_attempt: İki adımlı doğrulama kodu
|
||||
password: Parola
|
||||
phrase: Anahtar kelime veya kelime öbeği
|
||||
|
||||
@ -345,12 +345,19 @@ sq:
|
||||
updated_msg: Lajmërimi u përditësua me sukses!
|
||||
collections:
|
||||
accounts: Llogari
|
||||
back_to_account: Mbrapsht te faqja e llogarisë
|
||||
back_to_report: Mbrapsht te faqja e raportimit
|
||||
batch:
|
||||
add_to_report: 'Shtoje te raportimi #%{id}'
|
||||
report: Raportim
|
||||
collection_title: Koleksion nga %{name}
|
||||
contents: Lëndë
|
||||
no_collection_selected: S’u ndryshua ndonjë koleksion, ngaqë s’u përzgjodh ndonjë
|
||||
number_of_accounts:
|
||||
one: 1 llogari
|
||||
other: "%{count} llogari"
|
||||
open: Hape
|
||||
title: Koleksione llogarish - @%{name}
|
||||
view_publicly: Shiheni publikisht
|
||||
critical_update_pending: Përditësim kritik pezull
|
||||
custom_emojis:
|
||||
|
||||
@ -2178,7 +2178,7 @@ tr:
|
||||
webauthn: Güvenlik anahtarları
|
||||
unsubscriptions:
|
||||
create:
|
||||
action: Sunucu anasayfasına git
|
||||
action: Sunucu ana sayfasına git
|
||||
email_subscription:
|
||||
confirmation_html: "%{name} kişisinden artık e-posta almayacaksınız."
|
||||
title: Abonelikten ayrıldınız
|
||||
@ -2294,7 +2294,7 @@ tr:
|
||||
feature_moderation_title: Olması gerektiği gibi moderasyon
|
||||
follow_action: Takip et
|
||||
follow_step: Kendi akışınızı düzenliyorsunuz. Hadi onu ilginç kullanıcılarla dolduralım.
|
||||
follow_title: Anasayfa akışınızı kişiselleştirin
|
||||
follow_title: Ana sayfa akışınızı kişiselleştirin
|
||||
follows_subtitle: Tanınmış hesapları takip edin
|
||||
follows_title: Takip edebileceklerin
|
||||
follows_view_more: Takip edecek daha fazla kişi görüntüleyin
|
||||
|
||||
@ -341,11 +341,18 @@ zh-CN:
|
||||
updated_msg: 公告已成功更新!
|
||||
collections:
|
||||
accounts: 账号
|
||||
back_to_account: 返回账号信息页
|
||||
back_to_report: 返回举报页
|
||||
batch:
|
||||
add_to_report: '添加到举报 #%{id}'
|
||||
report: 举报
|
||||
collection_title: "%{name} 的收藏列表"
|
||||
contents: 内容
|
||||
no_collection_selected: 因为没有选中任何收藏列表,所以没有更改
|
||||
number_of_accounts:
|
||||
other: "%{count} 个账号"
|
||||
open: 打开
|
||||
title: 该账号的收藏列表 - @%{name}
|
||||
view_publicly: 以公开身份查看
|
||||
critical_update_pending: 紧急更新待处理
|
||||
custom_emojis:
|
||||
|
||||
@ -341,11 +341,18 @@ zh-TW:
|
||||
updated_msg: 成功更新公告!
|
||||
collections:
|
||||
accounts: 帳號
|
||||
back_to_account: 返回帳號資訊頁面
|
||||
back_to_report: 回到檢舉報告頁面
|
||||
batch:
|
||||
add_to_report: '新增至報告 #%{id}'
|
||||
report: 檢舉
|
||||
collection_title: "%{name} 的收藏名單"
|
||||
contents: 內容
|
||||
no_collection_selected: 因未選取任何收藏名單,所以什麼事都沒發生
|
||||
number_of_accounts:
|
||||
other: "%{count} 個帳號"
|
||||
open: 開啟
|
||||
title: 收藏名單帳號 - @%{name}
|
||||
view_publicly: 公開檢視
|
||||
critical_update_pending: 重要更新待升級
|
||||
custom_emojis:
|
||||
|
||||
@ -158,7 +158,7 @@ namespace :admin do
|
||||
resource :reset, only: [:create]
|
||||
resource :action, only: [:new, :create], controller: 'account_actions'
|
||||
|
||||
resources :collections, only: [:show]
|
||||
resources :collections, only: [:index, :show], concerns: :batch
|
||||
resources :statuses, only: [:index, :show], concerns: :batch
|
||||
|
||||
resources :relationships, only: [:index]
|
||||
|
||||
@ -840,4 +840,36 @@ RSpec.describe Account do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#needs_background_refresh?' do
|
||||
subject { account.needs_background_refresh? }
|
||||
|
||||
context 'when account is local' do
|
||||
let(:account) { Fabricate(:account) }
|
||||
|
||||
it { is_expected.to be false }
|
||||
end
|
||||
|
||||
context 'when account is remote' do
|
||||
let(:account) { Fabricate(:remote_account, last_webfingered_at:) }
|
||||
|
||||
context 'when account has never been updated or last update is unknown' do
|
||||
let(:last_webfingered_at) { nil }
|
||||
|
||||
it { is_expected.to be true }
|
||||
end
|
||||
|
||||
context 'when account has not been updated for over a week' do
|
||||
let(:last_webfingered_at) { 8.days.ago }
|
||||
|
||||
it { is_expected.to be true }
|
||||
end
|
||||
|
||||
context 'when account has been updated in the last week' do
|
||||
let(:last_webfingered_at) { 4.days.ago }
|
||||
|
||||
it { is_expected.to be false }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
50
spec/models/admin/collection_batch_action_spec.rb
Normal file
50
spec/models/admin/collection_batch_action_spec.rb
Normal file
@ -0,0 +1,50 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Admin::CollectionBatchAction do
|
||||
subject do
|
||||
described_class.new(
|
||||
current_account:,
|
||||
collection_ids:,
|
||||
report_id:,
|
||||
type:
|
||||
)
|
||||
end
|
||||
|
||||
let(:account) { Fabricate(:account) }
|
||||
let(:target_account) { Fabricate(:account) }
|
||||
let(:current_account) { Fabricate(:admin_user).account }
|
||||
let(:collection) { Fabricate(:collection, account: account) }
|
||||
let(:collection_ids) { [collection.id] }
|
||||
let(:report) { Fabricate(:report, account: target_account, target_account: account) }
|
||||
let(:type) { 'report' }
|
||||
|
||||
before 'create collection report' do
|
||||
report.collections << collection
|
||||
report.save!
|
||||
end
|
||||
|
||||
describe '#save!' do
|
||||
context 'when with_report? is true' do
|
||||
let(:other_collection) { Fabricate(:collection, account: account) }
|
||||
let(:report_id) { report.id }
|
||||
|
||||
it 'creates no report and adds the collection to the existing report' do
|
||||
collection_ids << other_collection.id
|
||||
|
||||
expect { subject.save! }.to_not change(Report, :count)
|
||||
expect(report.collections).to include(other_collection)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when with_report? is false' do
|
||||
let(:report_id) { nil }
|
||||
|
||||
it 'creates a new report from the collection_ids' do
|
||||
expect { subject.save! }.to change(Report, :count).by(1)
|
||||
expect(Report.last.collections).to include(collection)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -2,40 +2,7 @@
|
||||
|
||||
ENV['RAILS_ENV'] ||= 'test'
|
||||
|
||||
if ENV.fetch('COVERAGE', false)
|
||||
require 'simplecov'
|
||||
|
||||
SimpleCov.start 'rails' do
|
||||
# During parallel runs, ensure unique names for post-run merge
|
||||
command_name "job-#{ENV['TEST_ENV_NUMBER']}" if ENV['TEST_ENV_NUMBER']
|
||||
|
||||
if ENV['CI']
|
||||
require 'simplecov-lcov'
|
||||
formatter SimpleCov::Formatter::LcovFormatter
|
||||
formatter.config.report_with_single_file = true
|
||||
else
|
||||
formatter SimpleCov::Formatter::HTMLFormatter
|
||||
end
|
||||
|
||||
enable_coverage :branch
|
||||
|
||||
add_filter 'lib/linter'
|
||||
|
||||
add_group 'Libraries', 'lib'
|
||||
add_group 'Policies', 'app/policies'
|
||||
add_group 'Presenters', 'app/presenters'
|
||||
add_group 'Search', 'app/chewy'
|
||||
add_group 'Serializers', 'app/serializers'
|
||||
add_group 'Services', 'app/services'
|
||||
add_group 'Validators', 'app/validators'
|
||||
end
|
||||
|
||||
if defined?(Flatware)
|
||||
Flatware.configure do |config|
|
||||
config.after_fork { |test| SimpleCov.at_fork.call(test) } # Combines parallel coverage results
|
||||
end
|
||||
end
|
||||
end
|
||||
require 'simplecov' if ENV.fetch('COVERAGE', false)
|
||||
|
||||
# This needs to be defined before Rails is initialized
|
||||
STREAMING_PORT = ENV.fetch('TEST_STREAMING_PORT', '4020')
|
||||
|
||||
@ -3,13 +3,19 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Admin Collections' do
|
||||
let(:account) { Fabricate(:account) }
|
||||
let(:other_account) { Fabricate(:account) }
|
||||
let(:collection) { Fabricate(:collection, account: account) }
|
||||
let!(:second_collection) { Fabricate(:collection, account: account) }
|
||||
let(:report) { Fabricate(:report, account: other_account, target_account: account) }
|
||||
|
||||
before do
|
||||
sign_in Fabricate(:admin_user)
|
||||
end
|
||||
|
||||
describe 'GET /admin/accounts/:account_id/collections/:id' do
|
||||
let(:collection) { Fabricate(:collection) }
|
||||
|
||||
before do
|
||||
sign_in Fabricate(:admin_user)
|
||||
end
|
||||
|
||||
it 'returns success' do
|
||||
get admin_account_collection_path(collection.account_id, collection)
|
||||
|
||||
@ -17,4 +23,34 @@ RSpec.describe 'Admin Collections' do
|
||||
.to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /admin/accounts/:account_id/collections/' do
|
||||
it 'returns http success' do
|
||||
get admin_account_collections_path(account_id: account.id, id: collection.id)
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /admin/accounts/:account_id/collections/batch' do
|
||||
subject { post batch_admin_account_collections_path(account_id: account.id, report_id: report_id, admin_collection_batch_action: { collection_ids: [collection.id, second_collection.id] }) }
|
||||
|
||||
context 'with a valid report' do
|
||||
let(:report_id) { report.id }
|
||||
|
||||
it 'redirects to the report page' do
|
||||
report.collections << collection
|
||||
subject
|
||||
expect(response).to redirect_to(admin_report_path(report.id))
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an invalid report' do
|
||||
let(:report_id) { nil }
|
||||
|
||||
it 'redirects to the account collections page' do
|
||||
subject
|
||||
expect(response).to redirect_to(admin_account_collections_path(account.id))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -19,7 +19,7 @@ RSpec.describe AccountRefreshWorker do
|
||||
|
||||
context 'when account exists' do
|
||||
context 'when account does not need refreshing' do
|
||||
let(:account) { Fabricate(:account, last_webfingered_at: recent_webfinger_at) }
|
||||
let(:account) { Fabricate(:remote_account, last_webfingered_at: recent_webfinger_at) }
|
||||
|
||||
it 'returns immediately without processing' do
|
||||
worker.perform(account.id)
|
||||
@ -29,9 +29,9 @@ RSpec.describe AccountRefreshWorker do
|
||||
end
|
||||
|
||||
context 'when account needs refreshing' do
|
||||
let(:account) { Fabricate(:account, last_webfingered_at: outdated_webfinger_at) }
|
||||
let(:account) { Fabricate(:remote_account, last_webfingered_at: outdated_webfinger_at) }
|
||||
|
||||
it 'schedules an account update' do
|
||||
it 'performs an account update' do
|
||||
worker.perform(account.id)
|
||||
|
||||
expect(service).to have_received(:call)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user